Public: anyone can read this; the crew can change it.
The home page's front controls and app shelf draw §5's bar and stack, and the shelf test finds it by data attribute #4630
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/4608-the-home-page-s-front-controls-and-app-s → main 3 commits fork
4 files
changed +11
−11
docs/DESIGN.md +1 −1
| @@ -395,7 +395,7 @@ gone, so this list only shrinks. | ||
| 395 | 395 | A `.field` directly inside a `.bar` drops its bottom margin and the bar aligns to the end, so a labelled input and its button share a baseline |
| 396 | 396 | - **`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` `.menu-item` `.menu-panel` `.menu-sep` `.menu-title` `.palette-empty` `.palette-foot` `.palette-group` `.palette-heading` `.palette-input` `.palette-item` `.palette-list` `.passkey` `.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` |
| 397 | 397 | - **`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` |
| 398 | - **`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, lineage: `.app-shelf` `.backups-drill` `.backups-error` `.backups-now` `.backups-restore` `.bform` `.bgrid` `.bmanage` `.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` `.commit-body` `.commit-head` `.domain-records` `.fg-s0` `.fg-s1` `.fg-s2` `.fg-s3` `.fg-s4` `.fg-s5` `.fg-s6` `.fg-s7` `.filters` `.flag-form` `.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` `.headroom` `.issue-head` `.issue-meta` `.merge-box` `.merge-form` `.merge-headline` `.merge-sub` `.mirror-form` `.number` `.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` `.tok` `.wall-cells` `.where-sep` | |
| 398 | - **`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, lineage: `.backups-drill` `.backups-error` `.backups-now` `.backups-restore` `.bform` `.bgrid` `.bmanage` `.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` `.commit-body` `.commit-head` `.domain-records` `.fg-s0` `.fg-s1` `.fg-s2` `.fg-s3` `.fg-s4` `.fg-s5` `.fg-s6` `.fg-s7` `.filters` `.flag-form` `.front-admin` `.front-controls` `.front-crew` `.front-featured` `.front-featured-body` `.front-featured-look` `.front-issues` `.front-live` `.front-more` `.front-pick` `.frozen` `.frozen-form` `.frozen-sentence` `.headroom` `.issue-head` `.issue-meta` `.merge-box` `.merge-form` `.merge-headline` `.merge-sub` `.mirror-form` `.number` `.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` `.tok` `.wall-cells` `.where-sep` | |
| 399 | 399 | - **`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` |
| 400 | 400 | - **`style/content.ts`, prose (markdown)** — table cell alignment, built by `markdown.ts` as `ta-${align}`: `.ta-c` `.ta-r` |
| 401 | 401 | - **`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/studio/home.ts +3 −3
| @@ -433,7 +433,7 @@ function frontControls(ctx: Ctx, front: FrontPage): Html { | ||
| 433 | 433 | row(`${repo} (hidden)`, act("show", repo, "Show")), |
| 434 | 434 | )} |
| 435 | 435 | </ul> |
| 436 | <form method="post" action="/front" class="bar front-pin-any"> | |
| 436 | <form method="post" action="/front" class="bar"> | |
| 437 | 437 | <input type="hidden" name="action" value="pin" /> |
| 438 | 438 | <input |
| 439 | 439 | class="input" |
| @@ -444,7 +444,7 @@ function frontControls(ctx: Ctx, front: FrontPage): Html { | ||
| 444 | 444 | /> |
| 445 | 445 | ${btn("Pin", { size: "sm" })} |
| 446 | 446 | </form> |
| 447 | <form method="post" action="/front" class="front-weights"> | |
| 447 | <form method="post" action="/front" class="bar"> | |
| 448 | 448 | <input type="hidden" name="action" value="weights" /> |
| 449 | 449 | <span class="small muted">What counts, 0 to ${WEIGHT_MAX}:</span> |
| 450 | 450 | ${WEIGHT_NAMES.map( |
| @@ -535,7 +535,7 @@ export function studioDoor(ctx: Ctx): Response { | ||
| 535 | 535 | </section> |
| 536 | 536 | ${when(front.featured, (f) => html`<div class="mt">${featuredCard(f)}</div>`)} |
| 537 | 537 | ${tiles.length > 0 |
| 538 | ? html`<div class="mt app-shelf">${tiles}</div> | |
| 538 | ? html`<div class="mt stack" data-shelf>${tiles}</div> | |
| 539 | 539 | ${when( |
| 540 | 540 | front.total > tiles.length + (front.featured === undefined ? 0 : 1), |
| 541 | 541 | () => |
src/web/style/content.ts +6 −6
| @@ -347,7 +347,7 @@ a.origin-chip:hover { border-color: var(--border-strong); color: var(--fg); } | ||
| 347 | 347 | form.inline { display: inline; margin-left: auto; } |
| 348 | 348 | |
| 349 | 349 | /* The studio's front door: the public apps, live first, as pictures. */ |
| 350 | .app-shelf { | |
| 350 | .stack[data-shelf] { | |
| 351 | 351 | display: grid; gap: 16px; max-width: 1040px; margin-inline: auto; |
| 352 | 352 | grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); |
| 353 | 353 | } |
| @@ -379,14 +379,14 @@ form.inline { display: inline; margin-left: auto; } | ||
| 379 | 379 | .front-admin { max-width: 1040px; margin-inline: auto; } |
| 380 | 380 | .front-pick { gap: 8px; } |
| 381 | 381 | .front-pick form.inline { margin-left: 0; } |
| 382 | .front-pin-any { padding: 12px 16px; flex-wrap: nowrap; } | |
| 383 | .front-weights { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; padding: 0 16px 12px; } | |
| 384 | .front-weights .input { width: 4.5em; margin-left: 4px; } | |
| 385 | .front-pin-any .input { flex: 1; min-width: 0; } | |
| 382 | .front-controls .bar { padding: 12px 16px; gap: 8px 12px; } | |
| 383 | .front-controls .bar:has(> [name=repo]) { flex-wrap: nowrap; } | |
| 384 | .front-controls .bar > [name=repo] { flex: 1; min-width: 0; } | |
| 385 | .front-controls label .input { width: 4.5em; margin-left: 4px; } | |
| 386 | 386 | .front-controls .small { padding: 0 16px 14px; } |
| 387 | 387 | @media (max-width: 520px) { |
| 388 | 388 | /* Two to a row on a phone: a few live apps in one screen, not a scroll. */ |
| 389 | .app-shelf { gap: 10px; grid-template-columns: repeat(2, minmax(0, 1fr)); } | |
| 389 | .stack[data-shelf] { gap: 10px; grid-template-columns: repeat(2, minmax(0, 1fr)); } | |
| 390 | 390 | } |
| 391 | 391 | @media (max-width: 720px) { |
| 392 | 392 | .front-featured { grid-template-columns: 1fr; } |
test/front.test.ts +1 −1
| @@ -444,7 +444,7 @@ describe("the front page", () => { | ||
| 444 | 444 | // still links, the picture still links, and the words are there. |
| 445 | 445 | for (const old of ["app-tile", "app-tile-body", "app-tile-desc", "app-tile-look", "app-tile-name"]) |
| 446 | 446 | expect(body).not.toContain(old); |
| 447 | const shelf = body.slice(body.indexOf('class="mt app-shelf"')); | |
| 447 | const shelf = body.slice(body.indexOf(' data-shelf')); | |
| 448 | 448 | expect(shelf).toMatch(/<div\s+class="card"/); |
| 449 | 449 | expect(shelf).toContain('<a class="look" href="/owner/one"'); |
| 450 | 450 | expect(shelf).toMatch(/<a href="\/owner\/one"><strong>one<\/strong><\/a>/); |
new above
offline