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

The live-update pill draws §5's badge, and .live-pill leaves the markup and its script #4715

Merged opened this

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

Merged

scout/forge:scout/4702-the-live-update-pill-draws-5-s-badge-and → main 2 commits fork

  • Merged. scout/4702-the-live-update-pill-draws-5-s-badge-and was merged into main as 5dda6cb.

Review

Sign in to review this change.
6 files changed +36 −11
docs/DESIGN.md +1 −1
@@ -391,7 +391,7 @@ onto a §5 component, or into the table when a page-local thing turns out to
391391 be a component — and the check fails a name kept here after its last rule is
392392 gone, so this list only shrinks.
393393
394 - **`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`
394 - **`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` `.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`
395395 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
396396 - **`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`
397397 - **`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`
docs/design/areas/live.md added +3
@@ -0,0 +1,3 @@
1 # Live updates
2
3 - `.live-news` — the fixed, bottom-centred, click-through wrapper of the live-update pill. The pill is a §5 badge (attn tone) inside it; the wrapper keeps it out of flow so showing it moves nothing, and the rule gives that badge a 36px target. A bare `.badge` cannot carry this: it is a small inline element that would insert itself into the content where the mount sits.
src/web/live.ts +5 −3
@@ -70,9 +70,11 @@ export function liveMount(config: LiveConfig): Html {
7070 role="status"
7171 aria-live="polite"
7272 ></p>
73 <a id="live-pill" class="live-pill" href="" hidden
74 >↻ New activity — refresh</a
75 >`;
73 <div class="live-news">
74 <a id="live-pill" class="badge" data-variant="attn" href="" hidden
75 >↻ New activity — refresh</a
76 >
77 </div>`;
7678 }
7779
7880 export const liveClientJs = /* js */ `
src/web/style/base.ts +1 −1
@@ -222,7 +222,7 @@ export const utilities = /* css */ `
222222 }
223223
224224 @media print {
225 .topbar, .foot, .live-pill, .sticky-action, .toast { display: none !important; }
225 .topbar, .foot, .live-news, .sticky-action, .toast { display: none !important; }
226226 .card { box-shadow: none; }
227227 }
228228 }
src/web/style/components.ts +7 −6
@@ -893,14 +893,15 @@ details[open] > summary.row > .row-lead { transform: rotate(90deg); }
893893 @media (min-width: 720px) { .sticky-action { position: static; background: none; padding: 0; } .sticky-action .btn { flex: none; } }
894894
895895 /* ---- live updates ---- */
896 .live-pill {
897 position: fixed; left: 50%; bottom: 20px; transform: translateX(-50%); z-index: 25;
898 padding: 0 14px; min-height: 36px; display: inline-flex; align-items: center; gap: 6px;
899 background: var(--primary); color: var(--primary-fg); border-radius: 999px;
900 font-size: 13px; font-weight: 500; box-shadow: var(--shadow-2);
896 .live-news {
897 position: fixed; left: 0; right: 0; bottom: 20px; z-index: 25;
898 display: flex; justify-content: center; pointer-events: none;
899 }
900 .live-news .badge {
901 pointer-events: auto; min-height: 36px; padding: 0 14px; font-size: 13px;
902 background: var(--card); box-shadow: var(--shadow-2);
901903 animation: pop 200ms var(--ease) both;
902904 }
903 .live-pill:hover { color: var(--primary-fg); opacity: .92; }
904905
905906 }
906907
test/live-pages.test.ts +19
@@ -291,4 +291,23 @@ describe("the live mount's markup", () => {
291291 expect(out).toContain('id="live-pill"');
292292 expect(out).toContain("New activity — refresh");
293293 });
294
295 test("the pill is §5's attn badge, hidden until news, and the script finds it by id alone", async () => {
296 const { liveMount, liveClientJs } = await import("../src/web/live.ts");
297 const out = String(liveMount({ repo: "/a/b" }));
298 expect(out).toMatch(/<a\s+id="live-pill"\s+class="badge"\s+data-variant="attn"\s+href=""\s+hidden/);
299 expect(out).toMatch(/<div class="live-news">\s*<a id="live-pill"/);
300 expect(out).not.toMatch(/class="[^"]*\blive-pill\b/);
301 expect(liveClientJs).toContain("document.getElementById('live-pill')");
302 expect(liveClientJs).not.toMatch(/\.live-pill|querySelector\([^)]*live-pill/);
303 });
304 });
305
306 describe("the live pill's wrapper", () => {
307 test("is out of flow and the badge inside is a 36px target", async () => {
308 const { components } = await import("../src/web/style/components.ts");
309 const rule = (sel: string) => components.slice(components.indexOf(sel + " {")).split("}")[0];
310 expect(rule(".live-news")).toMatch(/position: fixed/);
311 expect(rule(".live-news .badge")).toMatch(/min-height: 36px/);
312 });
294313 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs