Public: anyone can read this; the crew can change it.

A spawned forge's first-login next steps speak the home page's language #9846

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

mason/forge:mason/9837-a-spawned-forge-s-first-login-next-steps → main 4 commits fork 2 threads · 2 unresolved

  • Merged. mason/9837-a-spawned-forge-s-first-login-next-steps was merged into main as c98e3a1.

Review

Sign in to review this change.
5 files changed +39 −26
docs/design/areas/home.md +2
@@ -5,6 +5,8 @@
55 - `.kpi-row` — a modifier on `.stats` for the home's figure tiles, not a second layout: it only changes the phone case. `.stats` alone (`minmax(160px, 1fr)`) falls to one column at 360px (two 160px tiles and the gap need 332px of 328px), stacking nine 104px tiles to ~1,100px; under 480px `.kpi-row` is two equal `minmax(0, 1fr)` columns instead. Tile height stays fixed (`.stat`), so nothing shifts.
66 - `.home-panels-slot` — home's place for the pinned panels, right after the KPIs (`pinnedSlot` in `src/web/studio/pinned.ts`). Only while it still waits for `/live/home/panels` (`data-refresh`) it holds 240px, about one panel row, with the 16px below it that `.bgrid` keeps (the system has no spacing tokens, only the 4px scale in §4, and both sit on it), so the charts arriving after first paint shift nothing; the refresh script drops the attribute when the fragment lands, is empty, or fails, and the slot collapses to its content.
77
8 - `.step-body` — the first-login next steps (`src/web/pages/next-steps.ts`, #9837) are one plain section: `<h2>`, then a `ul.list` row per step (title, Done trail) with the pending step's body (form, app buttons, Tell the crew) as a block under its row, indented past the lead icon (44px, 16px on a phone) on the 4px scale. No box, no border; the bold title marks the primary step and the one `variant="primary"` button is its accent.
9
810 The signed-in home's door line (Notes, Talk) is `.home-doors`: accent underlined links, each a 44px tap target (#9584).
911
1012 `/explore` (#9645) speaks the same language: the kinds (All, Apps, People, Forges) are `tabs()` as on home; sort and time stay one `seg` each in a small line (they stay boxed: they are two-to-three-way switches whose current choice must read at a glance, and a plain link row would blur it from the tabs above), with "Show archived" a link (`aria-pressed`) wearing `.home-doors`' 44px hit area, no box; the rows are a `.card.bare` list, rows and space on the page's one background, no box. Row words, ranking, "Make it yours" and the URL line are unchanged, so the terminal's explore search keeps the same words. No new class.
src/web/pages/next-steps.ts +20 −25
@@ -20,7 +20,7 @@ import { getUserById, type User } from "../../core/users.ts";
2020 import type { Ctx } from "../../http/context.ts";
2121 import { html, type Html } from "../html.ts";
2222 import { icon } from "../icons.ts";
23 import { badge, btn, card, row } from "../ui.ts";
23 import { badge, btn, row } from "../ui.ts";
2424 import { tellHref } from "../studio/nouns.ts";
2525 import { everydayAsk } from "./everyday.ts";
2626 import { EVERYDAY_WHY } from "../../core/everyday.ts";
@@ -61,12 +61,13 @@ function crewAtWork(
6161 return { lines, busy };
6262 }
6363
64 /** The step's row, then its body below it as a padded block of its own:
65 * `.row-meta` is one clipped line, no place for a form. */
64 /** One step: its row (title, "Done" trail; the primary one in bold), then its
65 * body as a block of its own under the title. `.row-meta` is one clipped line,
66 * no place for a form. */
6667 function stepBlock(step: NextStep, primary: boolean, body: Html | null): Html {
6768 const head = row({
6869 lead: step.done ? icon("check") : icon("issue"),
69 title: html`${primary ? html`<b>${step.title}</b>` : step.title}`,
70 title: primary ? html`<b>${step.title}</b>` : html`${step.title}`,
7071 ...(step.done ? { trail: badge("Done", { tone: "success" }) } : {}),
7172 data: {
outdated

Principle 3 (generous rhythm) and 7 (rows over boxes): the icon, title, badge and body are inline siblings in one <li>, and global CSS strips list markers, so the steps run together and read as unordered. Use row() for the head and put the body in a block with spacing; no card needed.

7273 step: step.key,
@@ -77,7 +78,7 @@ function stepBlock(step: NextStep, primary: boolean, body: Html | null): Html {
7778 return html`<ul class="list">
7879 ${head}
7980 </ul>
80 ${!step.done && body ? html`<div class="card-body">${body}</div>` : null}`;
81 ${!step.done && body ? html`<div class="step-body">${body}</div>` : null}`;
8182 }
8283
8384 /** The card, or nothing: not a spawned forge, not its owner, or every step done. */
@@ -117,26 +118,20 @@ export async function nextStepsCard(
117118 })}
118119 </p>`,
119120 };
120 return card(
121 html`${crew.lines.length > 0
122 ? html`<div class="card-body">
123 <p>
124 ${crew.busy
125 ? "Your crew is already at work:"
126 : "Your crew has its direction:"}
127 </p>
128 </div>
129 <ul class="list">
130 ${crew.lines}
131 </ul>`
132 : null}
133 ${steps.map((s) => stepBlock(s, s.key === primary, bodies[s.key]))}`,
134 {
135 id: "next-steps",
136 head: html`<h2>Your next steps</h2>`,
137 pad: false,
138 },
139 );
121 return html`<section id="next-steps">
122 <h2>Your next steps</h2>
123 ${crew.lines.length > 0
124 ? html`<p>
125 ${crew.busy
126 ? "Your crew is already at work:"
127 : "Your crew has its direction:"}
128 </p>
129 <ul class="list">
outdated

This <ol> renders without numbers (base.ts:242 resets ol). Either style it with an existing token-based class or drop the pretence of an ordered list and use the ul.list rows the crew lines above already use.

130 ${crew.lines}
131 </ul>`
132 : null}
133 ${steps.map((s) => stepBlock(s, s.key === primary, bodies[s.key]))}
134 </section>`;
140135 }
141136
142137 /** The steps once per request, for the card and for the lean home to share. */
src/web/style/content.ts +4
@@ -84,6 +84,10 @@ a.where-place:hover, button.where-place:hover {
8484 #live-chips a:hover, #live-chips a:focus-visible, .home-doors a:hover, .home-doors a:focus-visible { text-decoration-thickness: 2px; }
8585 #live-chips a { max-width: 100%; overflow-wrap: anywhere; }
8686 .home-doors { display: flex; flex-wrap: wrap; gap: 4px 12px; }
87 .step-body { padding: 4px 16px 16px 44px; }
88 .step-body > :first-child { margin-top: 0; }
89 .step-body > :last-child { margin-bottom: 0; }
90 @media (max-width: 719px) { .step-body { padding-left: 16px; } }
8791 .tell-box .input { min-width: 0; max-width: 100%; }
8892 .row-trail form { display: inline-flex; }
8993
test/first-login.test.ts +1 −1
@@ -220,7 +220,7 @@ describe("the first login to a spawned forge", () => {
220220 // A pending step's body is a block of its own, never inside the row's
221221 // one-line, clipped `.row-meta`.
222222 expect(home).not.toContain("row-meta");
223 expect(home).toMatch(/<div class="card-body">\s*<p class="muted small">/);
223 expect(home).toMatch(/<div class="step-body">\s*<p class="muted small">/);
224224 // Each app with its way in.
225225 expect(home).toContain('href="/marias/bakery"');
226226 });
test/next-steps-card.test.ts +12
@@ -180,6 +180,18 @@ describe("the next-steps card of a spawned forge", () => {
180180 expect([...at].sort((a, b) => a - b)).toEqual(at);
181181 });
182182
183 test("the steps are one plain section, not boxed in cards", async () => {
184 const card = cardOf(await page(owner));
185 expect(card).not.toContain('class="card');
186 expect(card).not.toContain("card-body");
187 // A pending step's body is a block under its row, never an inline sibling of the title.
188 expect(card).toMatch(/<\/ul>\s*<div class="step-body">/);
189 // The first pending step, and only it, is the primary one.
190 expect(card).toMatch(/data-step="everyday"[^>]*data-primary/);
191 expect(card.match(/data-primary/g)?.length).toBe(1);
192 expect(keys(await page(owner))).toEqual(["everyday", "apps", "tell"]);
193 });
194
183195 test("each step's way in answers 200 for the admin", async () => {
184196 const home = await page(owner);
185197 const card = cardOf(home);

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs