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

Design: a spawned forge's next-steps card uses the design system, and the next-step* classes leave §5d #3815

Merged opened this

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

Merged

scout/forge:scout/3802-design-a-spawned-forge-s-next-steps-card → main 3 commits fork

  • Merged. scout/3802-design-a-spawned-forge-s-next-steps-card was merged into main as 8d0fcee.

Review

Sign in to review this change.
5 files changed +45 −54
docs/DESIGN.md +1 −1

Diff not shown — a line 2,062 characters wide (limit 2,000). +1 −1

View file
docs/design/baseline.json −9
@@ -17,15 +17,6 @@
1717 "focus": 2,
1818 "input": 1,
1919 "target": 1
20 },
21 "src/web/style/content.ts": {
22 "colour": 0,
23 "size": 0,
24 "motion": 0,
25 "forced": 0,
26 "focus": 0,
27 "input": 0,
28 "target": 0
2920 }
3021 }
3122 }
src/web/pages/next-steps.ts +37 −33
@@ -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 { btn, card } from "../ui.ts";
23 import { badge, btn, card, 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";
@@ -52,32 +52,32 @@ function crewAtWork(
5252 ? "is about to start on"
5353 : "has been given";
5454 lines.push(
55 html`<li>
56 <b>${agent.username}</b> ${doing}
57 <a href="${tellHref(`/${repo.ownerName}/${repo.name}`, issue.number)}"
58 >${issue.title}</a
59 >
60 </li>`,
55 row({
56 href: tellHref(`/${repo.ownerName}/${repo.name}`, issue.number),
57 title: html`<b>${agent.username}</b> ${doing} ${issue.title}`,
58 }),
6159 );
6260 }
6361 return { lines, busy };
6462 }
6563
66 function stepRow(step: NextStep, primary: boolean, body: Html | null): Html {
67 return html`<li
68 class="next-step"
69 data-step="${step.key}"
70 data-done="${step.done ? "yes" : "no"}"
71 ${primary ? html`data-primary=""` : null}
72 >
73 <span class="next-step-mark" aria-hidden="true"
74 >${step.done ? icon("check") : icon("issue")}</span
75 >
76 <span class="next-step-main">
77 <span class="next-step-title">${step.title}</span>
78 ${step.done ? null : body}
79 </span>
80 </li>`;
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. */
66 function stepBlock(step: NextStep, primary: boolean, body: Html | null): Html {
67 const head = row({
68 lead: step.done ? icon("check") : icon("issue"),
69 title: html`${primary ? html`<b>${step.title}</b>` : step.title}`,
70 ...(step.done ? { trail: badge("Done", { tone: "success" }) } : {}),
71 data: {
72 step: step.key,
73 done: step.done ? "yes" : "no",
74 ...(primary ? { primary: "" } : {}),
75 },
76 });
77 return html`<ul class="list">
78 ${head}
79 </ul>
80 ${!step.done && body ? html`<div class="card-body">${body}</div>` : null}`;
8181 }
8282
8383 /** The card, or nothing: not a spawned forge, not its owner, or every step done. */
@@ -98,7 +98,7 @@ export async function nextStepsCard(
9898 ? html`<p class="muted small">
9999 Your apps arrive here as the crew sets them up.
100100 </p>`
101 : html`<p class="next-step-apps">
101 : html`<p>
102102 ${repos.map(
103103 (r) =>
104104 html`${btn(r.name, {
@@ -119,19 +119,23 @@ export async function nextStepsCard(
119119 };
120120 return card(
121121 html`${crew.lines.length > 0
122 ? html`<p class="mb">
123 ${crew.busy
124 ? "Your crew is already at work:"
125 : "Your crew has its direction:"}
126 </p>
127 <ul class="next-crew mb">
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">
128130 ${crew.lines}
129131 </ul>`
130132 : null}
131 <ol class="next-steps">
132 ${steps.map((s) => stepRow(s, s.key === primary, bodies[s.key]))}
133 </ol>`,
134 { id: "next-steps", head: html`<h2>Your next steps</h2>` },
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 },
135139 );
136140 }
137141
src/web/style/content.ts −10
@@ -336,16 +336,6 @@ a.origin-chip:hover { border-color: var(--border-strong); color: var(--fg); }
336336 /* A subscription's headroom for people: a small number beside its windows. */
337337 .headroom { display: flex; align-items: center; gap: 6px; }
338338 .headroom .input { width: 5em; }
339 /* A spawned forge's first steps (#600): three rows, the next one leading. */
340 .next-crew { margin: 0 0 12px 1.1em; }
341 .next-steps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 14px; }
342 .next-step { display: grid; grid-template-columns: 1.4em minmax(0, 1fr); gap: 8px; align-items: start; }
343 .next-step-mark { color: var(--faint); padding-top: 2px; }
344 .next-step[data-done="yes"] .next-step-mark { color: var(--success, var(--accent)); }
345 .next-step[data-done="yes"] .next-step-title { color: var(--muted-fg); text-decoration: line-through; }
346 .next-step[data-primary] .next-step-title { font-weight: 600; }
347 .next-step-main { display: flex; flex-direction: column; gap: 6px; min-width: 0; max-width: 36rem; }
348 .next-step-apps { display: flex; flex-wrap: wrap; gap: 6px; }
349339 /* The everyday-account ask on the home: a form's width, not the page's. */
350340 .everyday-ask { max-width: 34rem; margin-bottom: 16px; }
351341 /* On a phone an account's controls go under its name, not over it. */
test/first-login.test.ts +7 −1
@@ -212,7 +212,13 @@ describe("the first login to a spawned forge", () => {
212212 expect(home).toContain("Your crew has its direction:");
213213 expect(home).not.toContain("already at work");
214214 expect(home).toContain("atlas");
215 expect(home).toMatch(/href="\/marias\/bakery\/issues\/\d+"[^>]*>[^<]*Run Maria/);
215 expect(home).toMatch(/href="\/marias\/bakery\/issues\/\d+"[^>]*>[\s\S]*?Run Maria/);
216 for (const old of ["next-step", "next-crew"]) expect(home).not.toContain(`class="${old}`);
217 expect(home).not.toMatch(/class="next-step/);
218 // A pending step's body is a block of its own, never inside the row's
219 // one-line, clipped `.row-meta`.
220 expect(home).not.toContain("row-meta");
221 expect(home).toMatch(/<div class="card-body">\s*<p class="muted small">/);
216222 // Each app with its way in.
217223 expect(home).toContain('href="/marias/bakery"');
218224 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs