Public: anyone can read this; the crew can change it.
Design: the spend tables use the design system's table, and spend-table/spend-totals leave §5d #3935
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/3615-design-the-spend-tables-use-the-design-s → main 6 commits fork
4 files
changed +20
−15
docs/DESIGN.md +1 −1
| @@ -392,7 +392,7 @@ gone, so this list only shrinks. | ||
| 392 | 392 | - **`style/content.ts`, apps, runs, and the crew** — run pages, their logs and checks (a pull's look is §5's **look**): `.app-url` `.check-progress` `.check-steps` `.ln-forge` `.ln-pulse` `.ln-say` `.ln-tool` `.ln-you` `.log-tail-row` `.pinned-failures` `.pinned-slot` `.run-actions` `.run-jump` `.run-live` `.run-steer` `.run-summary` `.step-state` `.tests-bar` `.tests-words` `.txt` `.who` |
| 393 | 393 | - **`style/content.ts`, the code browser: explorer beside content** — the explorer column's own parts: `.code-main` `.code-split` `.ex-empty` `.ex-find` `.ex-head` `.ex-list` `.ex-name` `.ex-ref` `.ex-twist` `.explorer-toggle` |
| 394 | 394 | - **`style/content.ts`, search results** — search results, the inbox's ask cards, the client list and the claim link: `.ask-acts` `.ask-card` `.ask-group` `.ask-kind` `.ask-meta` `.ask-more-acts` `.ask-pick` `.ask-pick-hit` `.ask-short` `.ask-waiting` `.claim-link-box` `.forges-sent` `.live-toggle` `.needs-you` `.no-hits` |
| 395 | - **`pages/board.ts`** — the spend tables `spend.ts` draws, first on the board (the board itself is §5's **board**): `.spend-table` `.spend-totals` | |
| 395 | - **`pages/board.ts`** — the board itself is §5's **board**; the spend tables `spend.ts` draws wear §5's `.table` and `.stack`, no class of their own | |
| 396 | 396 | - **`pages/map.ts`** — the map of what waits for what: `.map-ask` `.map-breath` `.map-canvas` `.map-card` `.map-counts` `.map-dot` `.map-edge` `.map-edge-crit` `.map-edge-done` `.map-empty` `.map-list` `.map-main` `.map-meta` `.map-node` `.map-pillars` `.map-rule` `.map-svg` `.map-title` `.map-tool` `.map-tools` `.map-who` `.map-who-i` `.map-who-n` `.map-word` `.map-wrap` |
| 397 | 397 | - **`studio/feed_style.ts`** — the home feed's cards, its composer and the apps rail: `.composer` `.composer-bar` `.fcard-form` `.fcard-reason` `.fcard-reply` `.fcard-text` `.fcard-unblock` `.fcard-who` `.fcard-why` `.feed` `.feed-head` `.kpi` `.kpi-note` `.kpi-spark` `.kpi-value` `.kpis` `.home-app` `.home-apps` `.home-apps-verbs` `.home-pick` `.home-rail` `.home-tabs` |
| 398 | 398 |
src/web/pages/board.ts +1 −4
| @@ -999,7 +999,7 @@ function today(ctx: Ctx, scope: BoardScope): Html { | ||
| 999 | 999 | * one key to show one. Everything inside them is §5's: rows in a list card, |
| 1000 | 1000 | * badges, meters, kbd. Assembled into the served stylesheet by |
| 1001 | 1001 | * `web/style.ts`, last in the `components` layer. The spend tables |
| 1002 | * (`spend.ts`) are drawn on the board first and keep their two rules here. | |
| 1002 | * (`spend.ts`) wear §5's table and stack, with no rules here. | |
| 1003 | 1003 | */ |
| 1004 | 1004 | export const boardCss = /* css */ ` |
| 1005 | 1005 | @layer components { |
| @@ -1019,9 +1019,6 @@ export const boardCss = /* css */ ` | ||
| 1019 | 1019 | .board-region .row-lead img { display: block; border: 1px solid var(--border); border-radius: 4px; object-fit: cover; object-position: top; } |
| 1020 | 1020 | .board-region .row-main > meter { margin-top: 4px; } |
| 1021 | 1021 | .board [data-window] > meter { width: 56px; } |
| 1022 | .spend-table { margin-top: 8px; } | |
| 1023 | .spend-table caption { text-align: left; color: var(--muted-fg); } | |
| 1024 | .spend-totals { display: flex; flex-wrap: wrap; gap: 4px 16px; margin: 8px 0 0; } | |
| 1025 | 1022 | |
| 1026 | 1023 | /* Wide: four regions fill the window, each list scrolling on its own. */ |
| 1027 | 1024 | @media (min-width: 1100px) { |
src/web/pages/spend.ts +12 −10
| @@ -48,7 +48,8 @@ function table(spend: Spend, dim: Dimension, title: string): Html { | ||
| 48 | 48 | // The week's order: most spent first. |
| 49 | 49 | const week = (n: string): number => at.get(n)!.get("week")?.costUsd ?? 0; |
| 50 | 50 | names.sort((a, b) => week(b) - week(a)); |
| 51 | return html`<table class="table spend-table" data-by="${dim}"> | |
| 51 | return html`<div class="table-scroll"> | |
| 52 | <table class="table" data-dense data-by="${dim}" aria-label="${title}"> | |
| 52 | 53 | <thead> |
| 53 | 54 | <tr> |
| 54 | 55 | <th scope="col">${title}</th> |
| @@ -82,16 +83,17 @@ function table(spend: Spend, dim: Dimension, title: string): Html { | ||
| 82 | 83 | </tr>`; |
| 83 | 84 | })} |
| 84 | 85 | </tbody> |
| 85 | </table>`; | |
| 86 | </table> | |
| 87 | </div>`; | |
| 86 | 88 | } |
| 87 | 89 | |
| 88 | 90 | /** The whole breakdown: totals, five tables, the top runs. */ |
| 89 | 91 | export function spendView(spend: Spend): Html { |
| 90 | return html`<div class="spend"> | |
| 91 | <p class="spend-totals num"> | |
| 92 | return html`<div class="spend stack"> | |
| 93 | <p class="num"> | |
| 92 | 94 | ${spend.spans.map( |
| 93 | (s) => | |
| 94 | html`<span | |
| 95 | (s, i) => | |
| 96 | html`${i > 0 ? " — " : null}<span | |
| 95 | 97 | ><b>${usd(s.total.costUsd)}</b> ${SPAN_WORDS[s.span]} · |
| 96 | 98 | ${String(s.total.units)} runs · ${tokens(s.total.tokensIn)} in / |
| 97 | 99 | ${tokens(s.total.tokensOut)} out</span |
| @@ -99,10 +101,9 @@ export function spendView(spend: Spend): Html { | ||
| 99 | 101 | )} |
| 100 | 102 | </p> |
| 101 | 103 | ${DIMENSIONS.map(([dim, title]) => table(spend, dim, title))} |
| 102 | <table class="table spend-table" data-by="top"> | |
| 103 | <caption> | |
| 104 | The 10 costliest runs, 7 days | |
| 105 | </caption> | |
| 104 | <p class="muted" id="spend-top">The 10 costliest runs, 7 days</p> | |
| 105 | <div class="table-scroll"> | |
| 106 | <table class="table" data-dense data-by="top" aria-labelledby="spend-top"> | |
| 106 | 107 | <thead> |
| 107 | 108 | <tr> |
| 108 | 109 | <th scope="col">Run</th> |
| @@ -134,6 +135,7 @@ export function spendView(spend: Spend): Html { | ||
| 134 | 135 | )} |
| 135 | 136 | </tbody> |
| 136 | 137 | </table> |
| 138 | </div> | |
| 137 | 139 | <p class="muted small"> |
| 138 | 140 | US dollars at list price, as Claude Code reports them; a running session's |
| 139 | 141 | tokens count as they arrive, its cost when it reports one. "Last 5 h" is |
test/credentials.test.ts +6
| @@ -337,5 +337,11 @@ describe("the board", () => { | ||
| 337 | 337 | |
| 338 | 338 | const spend = await (await fetch(`${servers.url}/settings/spend`, { headers: { cookie } })).text(); |
| 339 | 339 | expect(spend).toContain('data-by="byCredential"'); |
| 340 | expect(spend).not.toContain("spend-table"); | |
| 341 | expect(spend).not.toContain("spend-totals"); | |
| 342 | expect(spend).toContain('class="table-scroll"'); | |
| 343 | expect(spend).toContain("data-dense"); | |
| 344 | expect(spend).toContain('data-by="top" aria-labelledby="spend-top"'); | |
| 345 | expect(spend).toContain('id="spend-top"'); | |
| 340 | 346 | }); |
| 341 | 347 | }); |
new above
offline