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

Design: an issue's close form uses the design system, and close-into/close-reason leave §5d #3816

Merged opened this

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

Merged

scout/forge:scout/3805-design-an-issue-s-close-form-uses-the-de → main 6 commits fork 3 threads · 3 unresolved

  • Merged. scout/3805-design-an-issue-s-close-form-uses-the-de was merged into main as 055a5a6.

Review

Sign in to review this change.
4 files changed +32 −8
docs/DESIGN.md +7 −1
@@ -372,6 +372,12 @@ at the end of the checks row.
372372
373373 ### 5d. Page-local classes
374374
375 The close form on an issue's thread is no class at all: its reason select and
376 "close into" input are plain §5 inputs, the height of the Comment button beside them, and `style/content.ts` shows the

Rewrap this line to the file's width; it runs well past the others.

377 input only while "split into…" is chosen, by attribute selector inside
378 `.form-actions` (`select[name="reason"]`, `input[name="into"]`). It is a rule
379 about the form, not a component.
380
375381 Every class a stylesheet defines is named in this section (`bun
376382 scripts/design-lint.ts` fails one that is not, #2700). The table above is
377383 the vocabulary; the classes below are what pages built beside it before that
@@ -385,7 +391,7 @@ gone, so this list only shrinks.
385391 - **`style/base.ts`** — the page shell (topbar, main, footer) and the layout and text utilities every page leans on: `.bar` `.bare` `.foot` `.grow` `.hide-lg` `.hide-sm` `.js-only` `.live-pill` `.mark` `.mb` `.mt` `.muted` `.narrow` `.narrow-wide` `.num` `.reduced` `.shell` `.shell-wide` `.skip` `.small` `.spacer` `.sr-only` `.stack` `.stack-tight` `.topbar` `.topbar-tools` `.truncate` `.wordmark`
386392 - **`style/components.ts`** — parts of the §5 components that the table does not spell out — the palette's list, the menu's items, a dialog's head and foot, comments, the theme picker — and a few small helpers beside them: `.cat` `.clone` `.clone-text` `.comment` `.comment-body` `.comment-form` `.dash` `.dialog-body` `.dialog-foot` `.dialog-head` `.empty-title` `.error-note` `.field-row` `.form-actions` `.hex` `.ico` `.ico-loader` `.list` `.live-status` `.menu-item` `.menu-panel` `.menu-sep` `.menu-title` `.palette-empty` `.palette-foot` `.palette-group` `.palette-heading` `.palette-input` `.palette-item` `.palette-list` `.passkey` `.passkey-lead` `.passkey-note` `.passkey-off` `.passkey-on` `.preview` `.sep` `.split` `.sprite` `.swatch` `.table-scroll` `.tell-here` `.theme-apply` `.theme-option` `.tick` `.timeline` `.tl-dot` `.toast-body` `.topbar-search`
387393 - **`style/components.ts`, settings rows** — a list whose rows are settings (limits, capacity): the `<ul class="list setting-list">` lets title and meta wrap instead of ellipsizing, and a row's controls take their own line under 640px. `.row-fields` is the form in that row's trail (input, Save, Default): always visible, never hover-gated, wrapping under the text before it would clip. Use both for any page whose rows show a name, a current value, and a control to change it; use `.row-actions` only for secondary actions (delete, wake, pin, revoke). Flags, telemetry, cloud, people, crew roles and subscription headroom/budget use `.row-fields`. Classes: `.row-fields` `.setting-list`
388 - **`style/content.ts`, page furniture** — what frames a page's content: page and repo heads, the merge box, the front page and its tiles, explore, backups, boards, the fleet chart's series colours, following, lineage: `.about` `.act` `.app-shelf` `.app-tile` `.app-tile-body` `.app-tile-desc` `.app-tile-look` `.app-tile-name` `.backups-drill` `.backups-error` `.backups-now` `.backups-restore` `.bform` `.bgrid` `.big` `.bmanage` `.boards-result` `.bp-h2` `.bp-h3` `.bp-w2` `.bp-w3` `.break` `.bt-bar` `.bt-grid` `.bt-group` `.bt-hist` `.bt-name` `.bt-procs` `.bt-row` `.bt-val` `.chart` `.chart-box` `.close-into` `.close-reason` `.commit-body` `.commit-head` `.connect-more` `.connect-path` `.domain-records` `.explore-filters` `.explore-line` `.explore-main` `.explore-name` `.explore-row` `.fg-s0` `.fg-s1` `.fg-s2` `.fg-s3` `.fg-s4` `.fg-s5` `.fg-s6` `.fg-s7` `.filters` `.flag-form` `.following` `.following-actions` `.following-eyebrow` `.following-foot` `.following-head` `.following-sentence` `.fork-bar` `.front-admin` `.front-controls` `.front-crew` `.front-featured` `.front-featured-body` `.front-featured-look` `.front-issues` `.front-live` `.front-more` `.front-pick` `.front-pin-any` `.front-weights` `.frozen` `.frozen-form` `.frozen-sentence` `.grants` `.grants-foot` `.handoff` `.handoff-links` `.headroom` `.hero` `.hero-terminal` `.issue-head` `.issue-meta` `.kv` `.lineage` `.lineage-children` `.lineage-here` `.merge-box` `.merge-form` `.merge-headline` `.merge-sub` `.mirror-form` `.number` `.origin-link` `.origin-mark` `.origin-name` `.origin-row` `.owner` `.page-head` `.path-head` `.policy-line` `.qf` `.quiet-details` `.repo-actions` `.repo-audience` `.repo-desc` `.repo-layout` `.repo-lineage` `.repo-title` `.search` `.slash` `.stage-detail` `.stage-dot` `.steps` `.sub` `.sys-pair` `.table-wrap` `.terminal-line` `.tok` `.tree-row` `.tree-total` `.wall-cells` `.where-dot` `.where-note` `.where-sep`
394 - **`style/content.ts`, page furniture** — what frames a page's content: page and repo heads, the merge box, the front page and its tiles, explore, backups, boards, the fleet chart's series colours, following, lineage: `.about` `.act` `.app-shelf` `.app-tile` `.app-tile-body` `.app-tile-desc` `.app-tile-look` `.app-tile-name` `.backups-drill` `.backups-error` `.backups-now` `.backups-restore` `.bform` `.bgrid` `.big` `.bmanage` `.boards-result` `.bp-h2` `.bp-h3` `.bp-w2` `.bp-w3` `.break` `.bt-bar` `.bt-grid` `.bt-group` `.bt-hist` `.bt-name` `.bt-procs` `.bt-row` `.bt-val` `.chart` `.chart-box` `.commit-body` `.commit-head` `.connect-more` `.connect-path` `.domain-records` `.explore-filters` `.explore-line` `.explore-main` `.explore-name` `.explore-row` `.fg-s0` `.fg-s1` `.fg-s2` `.fg-s3` `.fg-s4` `.fg-s5` `.fg-s6` `.fg-s7` `.filters` `.flag-form` `.following` `.following-actions` `.following-eyebrow` `.following-foot` `.following-head` `.following-sentence` `.fork-bar` `.front-admin` `.front-controls` `.front-crew` `.front-featured` `.front-featured-body` `.front-featured-look` `.front-issues` `.front-live` `.front-more` `.front-pick` `.front-pin-any` `.front-weights` `.frozen` `.frozen-form` `.frozen-sentence` `.grants` `.grants-foot` `.handoff` `.handoff-links` `.headroom` `.hero` `.hero-terminal` `.issue-head` `.issue-meta` `.kv` `.lineage` `.lineage-children` `.lineage-here` `.merge-box` `.merge-form` `.merge-headline` `.merge-sub` `.mirror-form` `.number` `.origin-link` `.origin-mark` `.origin-name` `.origin-row` `.owner` `.page-head` `.path-head` `.policy-line` `.qf` `.quiet-details` `.repo-actions` `.repo-audience` `.repo-desc` `.repo-layout` `.repo-lineage` `.repo-title` `.search` `.slash` `.stage-detail` `.stage-dot` `.steps` `.sub` `.sys-pair` `.table-wrap` `.terminal-line` `.tok` `.tree-row` `.tree-total` `.wall-cells` `.where-dot` `.where-note` `.where-sep`
389395 - **`style/content.ts`, code view** — the source view and the syntax-highlight token classes `highlight-html.ts` builds as `tok-${kind}`: `.blob-view` `.ln` `.tok-attribute` `.tok-comment` `.tok-constant` `.tok-decorator` `.tok-deleted` `.tok-emphasis` `.tok-function` `.tok-heading` `.tok-inserted` `.tok-keyword` `.tok-link` `.tok-meta` `.tok-number` `.tok-operator` `.tok-plain` `.tok-property` `.tok-punctuation` `.tok-regex` `.tok-string` `.tok-tag` `.tok-type` `.tok-variable`
390396 - **`style/content.ts`, prose (markdown)** — table cell alignment, built by `markdown.ts` as `ta-${align}`: `.ta-c` `.ta-r`
391397 - **`style/content.ts`, diff viewer** — the diff table: its gutters, line kinds, file head and inline review comments: `.add` `.del` `.dh` `.diff` `.diff-body` `.diff-head` `.diff-note` `.diff-path` `.diff-stat` `.diff-stub` `.diff-summary` `.diff-table` `.dl` `.dl-review` `.dn` `.dn-comment` `.nonl` `.rc-hidden` `.rc-resolve` `.rc-resolved` `.wd`
src/web/pages/thread.ts +3 −3
@@ -462,11 +462,11 @@ export function composer(ctx: Ctx, o: ComposerOptions): Html {
462462 : null;
463463 // Why it closes sits beside the button that closes it, joined to the
464464 // state form by id like the button; the numbers it split into only show
465 // once "split into…" is picked (`.close-into`, no script needed).
465 // once "split into…" is picked (an attribute rule, no script needed).
466466 const reasons =
467467 stateForm !== null && o.reasons && o.state === "open"
468468 ? html`<select
469 class="input close-reason"
469 class="input"
470470 name="reason"
471471 form="thread-state"
472472 aria-label="Why it closes"
@@ -476,7 +476,7 @@ export function composer(ctx: Ctx, o: ComposerOptions): Html {
476476 <option value="split">split into…</option>
477477 </select>
478478 <input
479 class="input close-into"
479 class="input"
480480 name="into"
481481 form="thread-state"
482482 inputmode="numeric"
src/web/style/content.ts +4 −4
@@ -182,9 +182,9 @@ a.origin-chip:hover { border-color: var(--border-strong); color: var(--fg); }
182182 .quiet-details[open] > summary::before { transform: rotate(90deg); }
183183 .quiet-details > summary:hover { color: var(--accent); }
184184 /* Why an issue closes, beside the button that closes it. */
185 .form-actions .close-reason { width: auto; min-height: 32px; padding-block: 0; }
186 .form-actions .close-into { display: none; width: 7em; min-height: 32px; }
187 .form-actions:has(.close-reason option[value="split"]:checked) .close-into { display: inline-block; }
185 .form-actions select[name="reason"] { width: auto; }
186 .form-actions input[name="into"] { display: none; width: 7em; }
187 .form-actions:has(select[name="reason"] option[value="split"]:checked) input[name="into"] { display: inline-block; }
188188 /* On a phone the Markdown hint takes its own line, so the reason, Close and
189189 Comment stay together on the next one instead of Comment wrapping alone. */
190190 @media (max-width: 480px) {
@@ -194,7 +194,7 @@ a.origin-chip:hover { border-color: var(--border-strong); color: var(--fg); }
194194 /* The reason select, Close and Comment must fit 332 px with room to
195195 spare: a tighter gap and a narrower select. */
196196 .comment-form .form-actions { gap: 6px; }
197 .comment-form .form-actions .close-reason { padding-inline: 8px 24px; max-width: 84px; }
197 .comment-form .form-actions select[name="reason"] { padding-inline: 8px 24px; max-width: 84px; }

This 84px cap and the hidden icons are the 360px fit. The control heights changed in this pull, so check it on a 360px viewport.

198198 /* Words carry the buttons here; their icons give back the width a 360 px
199199 row needs for "Close issue", and the buttons keep their full height. */
200200 .comment-form .form-actions .btn .ico { display: none; }
test/web-ui.test.ts +18
@@ -776,6 +776,11 @@ describe("web ui", () => {
776776 /<form[^>]*class="card comment-form"[^>]*data-box="Write a comment"[^>]*data-submit-key/,
777777 );
778778 expect(await as("/mel/proj/issues/1")).not.toContain("data-box=");
779 // The close form is drawn from the design system's input, not page classes.
780 expect(issue).toMatch(/<select[^>]*class="input"[^>]*name="reason"/);
781 expect(issue).toMatch(/<input[^>]*class="input"[^>]*name="into"/);
782 expect(issue).not.toContain("close-reason");
783 expect(issue).not.toContain("close-into");
779784
780785 // The verdicts are `v` chords on the radios, and only the verdicts this
781786 // person may pass: the author never sees `v a`, a reader only `v c`.
@@ -870,3 +875,16 @@ describe("web ui", () => {
870875 expect(getIssue(app.db, repo.id, 1)!.state).toBe("open");
871876 });
872877 });
878
879 describe("the issue close form's styles", () => {
880 const css = String(require("../src/web/style/content.ts").content);
881
882 test("the 'close into' input is hidden until 'split into…' is chosen", () => {
883 expect(css).toMatch(/\.form-actions input\[name="into"\]\s*\{[^}]*display:\s*none/);
884 expect(css).toMatch(
885 /\.form-actions:has\(select\[name="reason"\] option\[value="split"\]:checked\)\s+input\[name="into"\]\s*\{[^}]*display:\s*inline-block/,
886 );
887 expect(css).not.toContain("close-reason");
888 expect(css).not.toContain("close-into");
889 });
890 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs