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

A page head's subtitle draws base.ts's muted, small text, and `.sub` leaves the stylesheet #4788

Merged opened this

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

Merged

scout/forge:scout/4749-a-page-head-s-subtitle-draws-base-ts-s-m → main 6 commits fork 3 threads · 3 unresolved

  • Merged. scout/4749-a-page-head-s-subtitle-draws-base-ts-s-m was merged into main as 9c2c675.

Review

Sign in to review this change.
12 files changed +32 −8
docs/DESIGN.md −1
@@ -450,7 +450,6 @@ the same line (#4888).
450450 - `.repo-lineage`
451451 - `.repo-title`
452452 - `.slash`
453 - `.sub`
454453 - `.tok`
455454 - `.where-sep`
456455 - **`style/content.ts`, code view** — the source view and the syntax-highlight token classes `highlight-html.ts` builds as `tok-${kind}`:
docs/design/areas/layout.md +1
@@ -10,3 +10,4 @@ The spacing and layout utilities every page leans on: one component, styled in `
1010 - `.grow` — a flex child that takes the free space and may shrink below its content (`min-width: 0`), so its text can truncate; `pages/edit.ts`, `pages/settings.ts` and `pages/board.ts` write it. Styled in `style/base.ts`; `style/components.ts` ellipsizes it inside `.menu-item` and `.palette-item`.
1111 - `.narrow` — a 400px column centred a little below the top, for a single short form or notice; `pages/oauth.ts` writes it. Styled in `style/base.ts`.
1212 - `.narrow-wide` — a 760px reading column for a page of one form or one text; `pages/edit.ts`, `pages/issues.ts` and `pages/forges.ts` write it. Styled in `style/base.ts`.
13 - `.page-head-sub` — the one line under a page head's title: `flex-basis: 100%` and a −4px top margin, so it breaks onto its own full-width line. A layout hook, not a look: it rides with `muted small` (or `bar muted small` for a row of facts); `pageHead({ sub })` in `chrome.ts` and the run, tell, change and browse heads write it. Styled in `style/content.ts`; a `.muted.small` child of a page head without it (the system page's Processes sort row) stays beside its heading.
src/web/chrome.ts +1 −1
@@ -455,7 +455,7 @@ export function pageHead(
455455 ${o.lead ?? null}
456456 <h1>${title}</h1>
457457 ${o.actions ? html`<span class="spacer"></span>${o.actions}` : null}
458 ${o.sub ? html`<span class="sub">${o.sub}</span>` : null}
458 ${o.sub ? html`<span class="page-head-sub muted small">${o.sub}</span>` : null}
outdated

Emitting the bare shared utility means the layout rule has to key on utility classes, which is what caused the leak in content.ts. Emit <span class="bar muted small"> (or a div) here so the full-width rule can be .page-head > .bar alone.

459459 </div>`;
460460 }
461461
src/web/pages/browse.ts +1 −1
@@ -715,7 +715,7 @@ async function browseThread(
715715 ${detail.issue.title}
716716 <span class="number">#${detail.issue.number}</span>
717717 </h1>
718 <div class="sub bar">
718 <div class="page-head-sub bar muted small">
719719 ${stateBadge(state)}
720720 <span
721721 >${detail.issue.originAuthor ?? detail.issue.authorName} opened this
src/web/studio/change.ts +1 −1
@@ -194,7 +194,7 @@ export function changeHead(ctx: Ctx, repo: Repo, pull: Pull): Html {
194194 const marks = marksByName(ctx.app.db, [pull.authorName, who ?? ""]);
195195 return html`<div class="page-head">
196196 <h1>${pull.title} <span class="number">#${pull.number}</span></h1>
197 <div class="sub bar">
197 <div class="page-head-sub bar muted small">
198198 ${stateBadge(
199199 pullState(pull),
200200 getIssueById(ctx.app.db, pull.id)?.closeReason === "parked",
src/web/studio/run.ts +1 −1
@@ -152,7 +152,7 @@ export async function runView(
152152 : null}
153153 >
154154 <h1>${title}</h1>
155 <div class="sub bar">
155 <div class="page-head-sub bar muted small">
156156 ${wordBadge(word, "run-word")}
157157 ${when(
158158 issue,
src/web/studio/tell.ts +1 −1
@@ -364,7 +364,7 @@ async function studioTell(
364364 const marks = marksByName(db, [issue.authorName, issue.assigneeName ?? ""]);
365365 const head = html`<div class="page-head">
366366 <h1>${issue.title} <span class="number">#${issue.number}</span></h1>
367 <div class="sub bar">
367 <div class="page-head-sub bar muted small">

The bar is now muted small, so the inner span.muted.small a few lines down is redundant; drop its classes.

368368 ${direction
369369 ? html`<span class="muted small"
370370 >${icon("map")} the repository's standing direction — always
src/web/style/content.ts +1 −1
@@ -16,7 +16,7 @@ export const content = /* css */ `
1616 margin: 20px 0 14px;
1717 }
1818 .page-head h1 { min-width: 0; }
19 .page-head .sub { flex-basis: 100%; color: var(--muted-fg); font-size: 13px; margin-top: -4px; }
19 .page-head > .page-head-sub { flex-basis: 100%; margin-top: -4px; }
outdated

This rule carries only flex-basis and margin, so the bar rows lose the muted colour and 13px size .sub gave them. Put muted small on the wrappers.

outdated

.page-head > .muted.small also matches <p class="small muted"> directly inside .page-head at src/web/pages/system.ts:190, the Processes heading with the Sort-by buttons. That paragraph used to sit inline next to the h2. It now takes flex-basis:100% and margin-top:-4px, so it drops onto its own line and shifts the Processes header. Other pages have the same shape: org.ts:323 and contribute.ts:22 are wrapped or use plain muted, but any future direct child would hit it too. Use a selector tied to the subtitle only, for example give pageHead's span an additional dedicated hook already in the design register, or wrap it as <div class="bar muted small"> like the four rows so one .page-head > .bar rule covers all of them and .muted.small is never a layout selector.

2020
2121 .repo-head { margin: 18px 0 0; view-transition-name: repo-head; }
2222 .repo-title {
test/page-head-sub.test.ts added +21
@@ -0,0 +1,21 @@
1 import { expect, test } from "bun:test";
2 import { pageHead } from "../src/web/chrome.ts";
3 import { html } from "../src/web/html.ts";
4
5 test("a page head's subtitle is the shared muted, small text, not a page-local class", () => {
6 const out = String(pageHead("Title", { sub: "under the title" }));
7 expect(out).toContain('<span class="page-head-sub muted small">under the title</span>');
8 expect(out).not.toContain('class="sub');
9 expect(String(pageHead("Bare"))).not.toContain("muted small");
10 expect(String(html`${pageHead("T", { sub: "<b>x</b>" })}`)).toContain("&lt;b&gt;");
11 });
12
13 test("only the subtitle is forced onto its own line: the stylesheet keys on .page-head-sub, not on shared utilities", async () => {
14 const { content } = await import("../src/web/style/content.ts");
15 expect(content).toContain(".page-head > .page-head-sub {");
16 expect(content).not.toContain(".page-head > .muted.small");
17 expect(content).not.toContain(".page-head > .bar");
18 // The system page's sort row is a plain muted small child, beside its heading.
19 const src = await Bun.file(new URL("../src/web/pages/system.ts", import.meta.url)).text();
20 expect(src).not.toContain("page-head-sub");
21 });
test/studio-changes.test.ts +1
@@ -700,6 +700,7 @@ describe("the studio, continued", () => {
700700 expect(changeHtml).toContain(`href="/alice/notes/pulls/${p}/files"`);
701701 expect(changeHtml).toContain('id="comment-body"');
702702 expect(changeHtml).toContain('class="page-head"');
703 expect(changeHtml).toContain('class="page-head-sub bar muted small"');
703704 expect(changeHtml).not.toContain("issue-head");
704705 expect(changeHtml).not.toContain("issue-meta");
705706 // The tabs are §5's bar, not the retired .filters wrapper.
test/studio-runs.test.ts +2 −1
@@ -508,7 +508,8 @@ describe("working: a run's own page in the studio", () => {
508508 expect(page).not.toContain("issue-head");
509509 expect(page).not.toContain("issue-meta");
510510 expect(page).toContain('class="page-head"');
511 expect(page).toContain('class="sub bar"');
511 expect(page).toContain('class="page-head-sub bar muted small"');
512 expect(page).not.toContain('class="sub');
512513 }
513514 });
514515
test/web-browse.test.ts +1
@@ -472,6 +472,7 @@ describe("browsing an origin repository on the web", () => {
472472 expect(pulls).toContain("Tighten the bolt");
473473 const pull = await bodyOf(alice, `${browse}/pulls/9`);
474474 expect(pull).toContain("tighten");
475 expect(pull).toContain('class="page-head-sub bar muted small"');
475476 // A live pull is merged where it lives, and the box says where.
476477 expect(pull).toContain("Merge on");
477478 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs