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

Design: the quiet disclosure becomes a §5 component used by its four pages, and quiet-details leaves §5d #3933

Merged opened this

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

Merged

mason/forge:mason/3925-design-the-quiet-disclosure-becomes-a-5 → main 6 commits fork

  • Merged. mason/3925-design-the-quiet-disclosure-becomes-a-5 was merged into main as 7c9233d.

Review

Sign in to review this change.
18 files changed +129 −57
docs/DESIGN.md +3 −2
@@ -165,6 +165,7 @@ specification.
165165 | **tabs** `tabs()` | `.tabs > a` | — | `aria-current="page"` + 2px `--fg` underline | a header row with the active item underlined |
166166 | **segmented control** `seg()` | `.seg > a` | — | `aria-current`, or `:has(:checked)` for radios | `‹ Open | Closed | All ›` |
167167 | **menu** `menu()` | `.menu` (`<details>`) | `data-align=start\|end` | open/closed | a popup list |
168 | **disclosure** `disclosure()` | `.disclosure` (`<details>`, a quiet `›` summary) | `small` summary, `label` (aria-label), `extra` classes | open/closed; the chevron turns, the summary is at least 44px tall | a folded section |
168169 | **dialog** | `.dialog` | `data-side=bottom` (sheet) | JS `showModal()`; every dialog has a real page behind it | a modal box |
169170 | **tooltip** | `[data-tip]` | `data-tip-side=bottom` | hover/focus; hidden on coarse pointers | a status-bar hint |
170171 | **toast / flash** `Flash` | `.toast` | `data-tone=success\|danger\|attn` | one per page load, cookie-driven | a one-line banner |
@@ -364,7 +365,7 @@ The merge box gains a seventh state, _Waiting on checks_, when
364365 object that can drift.
365366
366367 **Website spec findings** — under a pull's Checks line, from the preview's
367 latest deploy run (`core/preview_findings.ts`): a `.quiet-details` whose
368 latest deploy run (`core/preview_findings.ts`): a **disclosure** whose
368369 summary is the count (edge-owned findings counted apart) and whose body is
369370 a `.list` of `.row`s, one per finding, with its specification.website
370371 section as an https link. Report-only, so the words never say failed; the
@@ -392,7 +393,7 @@ gone, so this list only shrinks.
392393 - **`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`
393394 - **`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`
394395 - **`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`
395 - **`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` `.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-sep`
396 - **`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` `.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` `.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-sep`
396397 - **`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`
397398 - **`style/content.ts`, prose (markdown)** — table cell alignment, built by `markdown.ts` as `ta-${align}`: `.ta-c` `.ta-r`
398399 - **`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/board-notes.ts +10 −7
@@ -9,7 +9,7 @@
99 */
1010
1111 import { html, type Html } from "../html.ts";
12 import { dot } from "../ui.ts";
12 import { disclosure, dot } from "../ui.ts";
1313
1414 /** One line on the board. */
1515 export interface Note {
@@ -60,10 +60,13 @@ export function notesRegion(
6060 return wrap(
6161 o.fine ? html`<span class="small muted">${dot("success")} All fine</span>` : null,
6262 );
63 return wrap(html`<details id="notes-list" class="quiet-details">
64 <summary class="small">${summaryLine(notes)}</summary>
65 <ul class="small muted">
66 ${notes.map((n) => html`<li>${n.line}</li>`)}
67 </ul>
68 </details>`);
63 return wrap(
64 disclosure(
65 summaryLine(notes),
66 html`<ul class="small muted">
67 ${notes.map((n) => html`<li>${n.line}</li>`)}
68 </ul>`,
69 { small: true, id: "notes-list" },
70 ),
71 );
6972 }
src/web/pages/looks.ts +7 −6
@@ -33,7 +33,7 @@ import { notFound } from "../../http/respond.ts";
3333 import { keepLook } from "../../looks/service.ts";
3434 import { loadRepo, repoBase } from "../chrome.ts";
3535 import { html, when, type Html } from "../html.ts";
36 import { card, icon, sha, time } from "../ui.ts";
36 import { card, disclosure, icon, sha, time } from "../ui.ts";
3737
3838 /** The newest picture at one width, when there is one. */
3939 export const pictured = (
@@ -262,13 +262,14 @@ export function lookCard(
262262 </div>
263263 </div>
264264 ${when(darkPhone ?? darkDesktop, () =>
265 html`<details class="quiet-details">
266 <summary class="muted small">In the dark</summary>
267 <div class="looks">
265 disclosure(
266 "In the dark",
267 html`<div class="looks">
268268 ${when(darkPhone, (l) => picture(l, "phone", page))}
269269 ${when(darkDesktop, (l) => picture(l, "desktop", page))}
270 </div>
271 </details>`,
270 </div>`,
271 { small: true },
272 ),
272273 )}
273274 <p class="muted small">
274275 at ${sha(shown.commitOid, `${base}/commit/${shown.commitOid}`)} ·
src/web/pages/pull-spec.ts +6 −5
@@ -7,15 +7,16 @@
77 import { html, type Html } from "../html.ts";
88 import type { Finding } from "../../release/webspec.ts";
99 import { findingsWords } from "../../core/preview_findings.ts";
10 import { disclosure } from "../ui.ts";
1011
1112 const sectionName = (url: string): string => url.slice(url.indexOf("#") + 1) || url;
1213
1314 export function specFindings(found: readonly Finding[]): Html {
1415 if (found.length === 0) return html`<p class="muted">Website spec: no findings on the preview.</p>`;
1516 const words = findingsWords(found);
16 return html`<details class="quiet-details">
17 <summary>${words[0]!.toUpperCase() + words.slice(1)} (report only)</summary>
18 <ul class="list">
17 return disclosure(
18 `${words[0]!.toUpperCase() + words.slice(1)} (report only)`,
19 html`<ul class="list">
1920 ${found.map(
2021 (f) =>
2122 html`<li class="row"><span class="mono">${f.path}</span> ${f.message} —
@@ -23,6 +24,6 @@ export function specFindings(found: readonly Finding[]): Html {
2324 ? html` <span class="muted">(the edge owns this)</span>`
2425 : ""}</li>`,
2526 )}
26 </ul>
27 </details>`;
27 </ul>`,
28 );
2829 }
src/web/studio/feed.ts +7 −6
@@ -20,7 +20,7 @@ import type { FeedItem } from "../../feed/rank.ts";
2020 import { html, type Html } from "../html.ts";
2121 import { fragment } from "../layout.ts";
2222 import { outboxJs } from "../outbox.ts";
23 import { avatar, btn, card, empty, nameMark, timeAgo } from "../ui.ts";
23 import { avatar, btn, card, disclosure, empty, nameMark, timeAgo } from "../ui.ts";
2424 import { writableAppsOf } from "./asks.ts";
2525
2626 /** Events that can change what the feed says. */
@@ -74,17 +74,18 @@ export function feedCard(
7474 <span class="badge">${KIND_LABEL[item.kind]}</span>`;
7575 const body = html`<p class="fcard-reason"><a href="${item.link}" data-feed-open>${item.what}</a></p>
7676 ${saysMore(item) ? html`<p class="fcard-why small muted">${item.reason}</p>` : null}`;
77 const foot = html`<details class="quiet-details">
78 <summary class="small muted" aria-label="${target ? "Steer, dismiss or mute" : "Tell the crew, dismiss or mute"}">${target ? "Steer" : "Tell"} · more</summary>
79 <form method="post" action="${target ?? `/${item.app}/tell`}" class="fcard-form">
77 const foot = disclosure(
78 `${target ? "Steer" : "Tell"} · more`,
79 html`<form method="post" action="${target ?? `/${item.app}/tell`}" class="fcard-form">
8080 <textarea class="input" name="text" rows="2" maxlength="2000" required aria-label="${target ? "Steer this run" : "Tell the crew"}"></textarea>
8181 ${btn(target ? "Steer" : "Tell", { size: "sm", variant: "primary" })}
8282 </form>
8383 <span class="bar">
8484 <button type="button" class="btn" data-variant="ghost" data-size="sm" data-feed="dismissed" title="Dismiss (x)">Dismiss</button>
8585 ${item.kind === "ask" || item.kind === "milestone" || item.kind === "choose" ? null : html`<button type="button" class="btn" data-variant="ghost" data-size="sm" data-feed="muted" title="Mute ${item.app}">Mute app</button>`}
86 </span>
87 </details>`;
86 </span>`,
87 { small: true, label: target ? "Steer, dismiss or mute" : "Tell the crew, dismiss or mute" },
88 );
8889 return card(body, {
8990 head,
9091 foot,
src/web/studio/feed_style.ts +1 −1
@@ -44,7 +44,7 @@ export const feedCss = /* css */ `
4444 .fcard-form textarea { width: 100%; min-height: 56px; resize: vertical; }
4545 @media (max-width: 600px) {
4646 .feed .bar .btn { padding: 8px 10px; }
47 .feed .quiet-details > summary { min-height: 44px; display: flex; align-items: center; }
47 .feed .disclosure > summary { min-height: 44px; display: flex; align-items: center; }
4848 }
4949 @media (forced-colors: active) {
5050 .feed > .card:focus-visible, #apps-select:focus-visible ~ * .home-apps-verbs label { outline: 2px solid Highlight; }
src/web/studio/tell.ts +9 −7
@@ -37,6 +37,7 @@ import {
3737 badge,
3838 btn,
3939 card,
40 disclosure,
4041 icon,
4142 labelCss,
4243 listCard,
@@ -396,13 +397,14 @@ async function studioTell(
396397 // and both are one tap away when it is. What it waits for stays in view
397398 // above them — one of the three questions the page answers — with its
398399 // form for whoever may change it.
399 const details = html`<details class="quiet-details mt">
400 <summary>Details</summary>
401 ${assigneeCard(ctx, repo, issue, `${thread}/assignee`)}
402 ${labelsCard(ctx, repo, issue, applied, allLabels, `${thread}/labels`)}
403 ${teams}
404 ${sealCard(ctx, repo, issue, `${thread}/seal`)}
405 </details>`;
400 const details = disclosure(
401 "Details",
402 html`${assigneeCard(ctx, repo, issue, `${thread}/assignee`)}
403 ${labelsCard(ctx, repo, issue, applied, allLabels, `${thread}/labels`)}
404 ${teams}
405 ${sealCard(ctx, repo, issue, `${thread}/seal`)}`,
406 { extra: "mt" },
407 );
406408
407409 const body = html`${repoHeader(ctx, repo, "issues", {
408410 compact: true,
src/web/style/components.ts +13
@@ -581,6 +581,19 @@ details[open] > summary.row > .row-lead { transform: rotate(90deg); }
581581 position-area would keep position: fixed and park the panel at the
582582 viewport's corner. Absolute-inside-the-details works everywhere. */
583583
584 /* ---- disclosure: what a person sometimes needs, closed until asked for ---- */
585 .disclosure > summary {
586 cursor: pointer; font-weight: 560; list-style: none; color: var(--muted-fg);
587 display: flex; align-items: center; gap: 6px; padding: 6px 0; min-height: 44px;
588 }
589 .disclosure > summary::-webkit-details-marker { display: none; }
590 .disclosure > summary::before {
591 content: "›"; display: inline-block; color: var(--faint);
592 transition: transform 120ms var(--ease);
593 }
594 .disclosure[open] > summary::before { transform: rotate(90deg); }
595 .disclosure > summary:hover { color: var(--accent); }
596
584597 /* ---- topbar search (always a real form, so JS-off still searches) ---- */
585598 .topbar-search {
586599 display: flex; align-items: center; gap: 6px;
src/web/style/content.ts −13
@@ -169,19 +169,6 @@ a.origin-chip:hover { border-color: var(--border-strong); color: var(--fg); }
169169 .connect-more[open] > summary::before { transform: rotate(90deg); }
170170 .connect-more > summary:hover { color: var(--accent); }
171171 .connect-more > p { margin: 6px 0 4px; }
172 /* A page's quiet Details: what a person sometimes changes, closed until
173 asked for, the same disclosure as the one above. */
174 .quiet-details > summary {
175 cursor: pointer; font-weight: 560; list-style: none; color: var(--muted-fg);
176 display: flex; align-items: center; gap: 6px; padding: 6px 0; min-height: 44px;
177 }
178 .quiet-details > summary::-webkit-details-marker { display: none; }
179 .quiet-details > summary::before {
180 content: "›"; display: inline-block; color: var(--faint);
181 transition: transform 120ms var(--ease);
182 }
183 .quiet-details[open] > summary::before { transform: rotate(90deg); }
184 .quiet-details > summary:hover { color: var(--accent); }
185172 /* Why an issue closes, beside the button that closes it. */
186173 .form-actions select[name="reason"] { width: auto; }
187174 .form-actions input[name="into"] { display: none; width: 7em; }
src/web/ui.ts +19
@@ -841,6 +841,25 @@ export function listCard(rows: Html[], o: CardOptions = {}): Html {
841841 );
842842 }
843843
844 export interface DisclosureOptions {
845 /** A small muted summary, for a disclosure tucked under a card or a strip. */
846 small?: boolean;
847 /** The summary's accessible name, when its visible words are shorter. */
848 label?: string;
849 /** Further classes for the `<details>` (a spacing utility such as `mt`). */
850 extra?: string;
851 /** The `<details>`'s id, for a script or a link that finds it. */
852 id?: string;
853 }
854
855 /** What a person sometimes needs, closed until asked for: a quiet chevron summary over its body. */
856 export function disclosure(summary: Html | string, body: Html | string, o: DisclosureOptions = {}): Html {
857 return html`<details${o.id ? html` id="${o.id}"` : null} class="disclosure${o.extra ? ` ${o.extra}` : ""}">
858 <summary${o.small ? html` class="muted small"` : null}${o.label ? html` aria-label="${o.label}"` : null}>${summary}</summary>
859 ${body}
860 </details>`;
861 }
862
844863 export interface RowOptions {
845864 href?: string;
846865 title: Html | string;
test/board-notes.test.ts +1 −1
@@ -21,7 +21,7 @@ describe("the board's notes", () => {
2121
2222 test("closed by default, one line each, escaped", () => {
2323 const out = notesRegion([{ key: "k", need: true, line: html`<a>${"<b>"}</a>` }], live).text;
24 expect(out).toContain('<details id="notes-list" class="quiet-details">');
24 expect(out).toContain('<details id="notes-list" class="disclosure">');
2525 expect(out).not.toMatch(/<details[^>]*open/);
2626 expect(out).toContain("&lt;b&gt;");
2727 });
test/board.test.ts +1 −1
@@ -578,7 +578,7 @@ describe("the board's forge strip", () => {
578578 const text = await board("root");
579579 const top = text.slice(text.indexOf('<div class="board"'), text.indexOf('class="board-grid"'));
580580 // Closed by default; one summary, counted.
581 expect(top).toMatch(/<details id="notes-list" class="quiet-details">/);
581 expect(top).toMatch(/<details id="notes-list" class="disclosure">/);
582582 expect(top).not.toMatch(/<details[^>]*\sopen/);
583583 expect(top).toMatch(/<summary[^>]*>\s*\d+ notes · <span[^>]*>\s*<span\s+class="dot"\s+data-tone="attn"\s*><\/span> \d+ need you<\/span>\s*<\/summary>/);
584584 expect(top.match(/<li>/g)?.length).toBeGreaterThanOrEqual(8);
test/disclosure.test.ts added +44
@@ -0,0 +1,44 @@
1 /**
2 * The disclosure (#3925): one §5 component for "closed until asked for",
3 * drawn by `disclosure()` in ui.ts. Its markup, its escaping, and that the
4 * page-local name it replaced is gone from every file under src/web.
5 */
6
7 import { describe, expect, test } from "bun:test";
8 import { readdirSync, readFileSync } from "node:fs";
9 import { join } from "node:path";
10 import { html } from "../src/web/html.ts";
11 import { disclosure } from "../src/web/ui.ts";
12
13 describe("disclosure", () => {
14 test("draws a <details> whose summary and body are what it was given", () => {
15 const out = disclosure("Details", html`<p>body</p>`).toString();
16 expect(out).toMatch(/^<details class="disclosure">\s*<summary>Details<\/summary>\s*<p>body<\/p>\s*<\/details>$/);
17 });
18
19 test("a small summary, an accessible name and further classes", () => {
20 const out = disclosure("Tell · more", "", { small: true, label: "Tell the crew, dismiss or mute", extra: "mt" }).toString();
21 expect(out).toContain('<details class="disclosure mt">');
22 expect(out).toContain('<summary class="muted small" aria-label="Tell the crew, dismiss or mute">Tell · more</summary>');
23 expect(disclosure("Notes", "", { id: "notes-list" }).toString()).toContain('<details id="notes-list" class="disclosure">');
24 });
25
26 test("plain strings are escaped, Html is not", () => {
27 const out = disclosure('<b>"x"</b>', "<script>", { label: '"><img>' }).toString();
28 expect(out).not.toContain("<b>");
29 expect(out).not.toContain("<script>");
30 expect(out).not.toContain('"><img>');
31 expect(out).toContain("&lt;b&gt;");
32 expect(disclosure(html`<b>x</b>`, "").toString()).toContain("<summary><b>x</b></summary>");
33 });
34
35 test("quiet-details, the page-local class it replaced, appears nowhere under src/web", () => {
36 const root = join(import.meta.dir, "..", "src", "web");
37 const files = readdirSync(root, { recursive: true, withFileTypes: true })
38 .filter((e) => e.isFile())
39 .map((e) => join(e.parentPath, e.name));
40 expect(files.length).toBeGreaterThan(10);
41 const holders = files.filter((f) => readFileSync(f, "utf8").includes("quiet-details"));
42 expect(holders).toEqual([]);
43 });
44 });
test/home-feed.test.ts +1 −1
@@ -140,7 +140,7 @@ describe("home is the feed", () => {
140140 expect(text).toContain("on “Water the tomatoes at dawn”: Mulch them first");
141141 // Every post's actions sit behind one quiet control, and "because you
142142 // own" — true of every post here — is not said again and again.
143 expect(text).toContain('class="quiet-details"');
143 expect(text).toContain('class="disclosure"');
144144 expect(text).not.toContain("because you own alice/garden");
145145 // Your apps: a narrow column, and a tab on a phone.
146146 expect(text).toContain('id="live-apps"');
test/looks.test.ts +1 −1
@@ -858,7 +858,7 @@ describe("the look", () => {
858858 );
859859 // The dark pair is named, and folded away until asked for.
860860 expect(darkPage).toMatch(
861 /<details class="quiet-details">\s*<summary class="muted small">In the dark<\/summary>/,
861 /<details class="disclosure">\s*<summary class="muted small">In the dark<\/summary>/,
862862 );
863863 // An older head's dark picture never sits under the shown commit's caption.
864864 const stale = "b".repeat(40);
test/review-waits.test.ts +1 −1
@@ -197,7 +197,7 @@ describe("an agent pull that waits for a person's review says so", () => {
197197
198198 test("the board and the autonomy settings say so", async () => {
199199 const board = await page(aliceCookie, "/board");
200 expect(board).toContain('<details id="notes-list" class="quiet-details">');
200 expect(board).toContain('<details id="notes-list" class="disclosure">');
201201 expect(board).toContain("No reviewer: agent pulls wait for you");
202202 expect(board).toContain("<b>1</b> waiting since");
203203 const settings = await page(aliceCookie, "/alice/engine/settings");
test/studio-changes.test.ts +1 −1
@@ -130,7 +130,7 @@ describe("the studio, continued", () => {
130130 const at = `/alice/publictell/issues/${told.value.number}`;
131131 const anon = await (await fetch(`${base}${at}`)).text();
132132 expect(anon).toContain("Anyone may read");
133 expect(anon).toContain('<details class="quiet-details mt">');
133 expect(anon).toContain('<details class="disclosure mt">');
134134 for (const verb of ["assignee", "labels", "dependencies", "state", "comments"])
135135 expect(anon, verb).not.toContain(`action="${at}/${verb}"`);
136136 });
test/studio.test.ts +4 −4
@@ -1092,10 +1092,10 @@ describe("the studio", () => {
10921092 expect(studio).not.toContain("?mode=dev");
10931093 // Dev's controls, folded in: closed until asked for, never a mode.
10941094 const details = studio.slice(
1095 studio.indexOf('<details class="quiet-details'),
1096 studio.indexOf("</details>", studio.indexOf('<details class="quiet-details')),
1095 studio.indexOf('<details class="disclosure'),
1096 studio.indexOf("</details>", studio.indexOf('<details class="disclosure')),
10971097 );
1098 expect(details).toMatch(/^<details class="quiet-details mt">/);
1098 expect(details).toMatch(/^<details class="disclosure mt">/);
10991099 expect(details).toContain("<h2>Labels</h2>");
11001100 expect(details).toContain("<h2>Assignee</h2>");
11011101 // What it waits for stays in view, with the form beside it.
@@ -1112,7 +1112,7 @@ describe("the studio", () => {
11121112 ]) {
11131113 const text = await dev.text();
11141114 expect(text).toContain('data-word="working"');
1115 expect(text).toContain('<details class="quiet-details mt">');
1115 expect(text).toContain('<details class="disclosure mt">');
11161116 }
11171117 });
11181118

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs