Public: anyone can read this; the crew can change it.
The board's panel grid is part of §5's board component: .bgrid and .bp-* move from content.ts into it #4838
Check · Review · Land · Live done — Landed and live in production.
mason/forge:mason/4833-the-board-s-panel-grid-is-part-of-5-s-bo → main 4 commits fork
5 files
changed +61
−14
docs/design/sections/5-components.md +1 −1
| @@ -77,7 +77,7 @@ specification. | ||
| 77 | 77 | | **assignee card** | `.card` with an `.edit-row` form | — | who is on it, then one select and one button for a writer. People first, the crew last and marked `(agent)`, because assigning an agent is what starts it working and the choice is not the same kind of choice | a picker with people, then agents | |
| 78 | 78 | | **inbox** (`asks_page.ts`) | one list above the feed | — | _On you_: what asks you and what is assigned to you, one row each, each with what it waits for (`needsYouSection`), and one count in the top bar. Absent when empty: a card saying nothing is a zero drawn | the same two panes on the dashboard | |
| 79 | 79 | | **look** (`pages/looks.ts`) | `.look` in a `.looks` strip | `.look[data-slot=phone\|desktop\|thumb\|now]` sizes the frame; `.looks[data-compare]` sets two sides (before · main, then after · the pull) beside each other | a picture always carries its own `width`/`height`, so nothing shifts when it loads; a phone keeps 200px, a desktop takes the rest up to its own size, and under 640px a strip stacks; an attached picture is a `figure` in the strip with its line under it | the picture in the terminal (§6·0c) | |
| 80 | | **board** (`pages/board.ts`) | `.board` (`.board-grid`, `.board-region`) | `data-wall` (across the room: no keys, no pointer, larger), `data-focus=working\|landed\|decided\|today` | the one whole-screen layout: four live regions fill a second monitor ≥1100px and each list scrolls on its own, one column below; above the panels only a collapsed one-line `#notes` (`<details>`: closed by default, an open list stays open across live swaps), the strips below them; inside, only §5 parts (list card rows, badges, meters, `kbd`); a swapped region's heading wears `data-fresh` for a moment | four panes, one focused by its number | | |
| 80 | | **board** (`pages/board.ts`) | `.board` (`.board-grid`, `.board-region`); a board's panels: `.bgrid` (`components.ts`), each panel a `.card` sized `.bp-w2` `.bp-w3` `.bp-h2` `.bp-h3` | `data-wall` (across the room: no keys, no pointer, larger), `data-focus=working\|landed\|decided\|today` | the one whole-screen layout: four live regions fill a second monitor ≥1100px and each list scrolls on its own, one column below; above the panels only a collapsed one-line `#notes` (`<details>`: closed by default, an open list stays open across live swaps), the strips below them; inside, only §5 parts (list card rows, badges, meters, `kbd`); a swapped region's heading wears `data-fresh` for a moment. A saved board's panels (`/boards/<slug>`, the studio's pinned panels) sit in `.bgrid`: three columns, one under 720px, a panel spanning its `w` columns. Its `h` has no rule in components.ts: `.bp-h2` and `.bp-h3` only size the panel's `.chart-box`, from content.ts, the layer `.chart-box` is drawn in, and move here when `.chart-box` does | four panes, one focused by its number | | |
| 81 | 81 | | **stacked bar chart** `windowSvg()` (`fleet-graph.ts`) | `.plots`, `.plot` (`.plot-name`), `.plot-legend` | bars wear `fg-sN`, the `--series-N` token | server-drawn SVG with `width`/`height` set, so it holds its box before the stylesheet; gridlines `--border`, figures `--muted-fg` in mono, panel names `--faint`; `role="img"` with an `aria-label` totalling it, a `<title>` per bar, and a table of the same numbers beside it | a table of the same numbers | |
| 82 | 82 | | **chart** `chartBox()` (`ui.ts`) | `.chart-box` wrapping `drawChart`'s svg `.chart` (`.big` for the headline figure) | `.is-table` for a table panel | one query or series drawn on the server: the svg carries its own `width`/`height` and the box holds its space, so nothing shifts; `role="img"` with an `aria-label` of its title. Reach for it wherever a page draws a `boards/chart.ts` chart — `/system`'s memory pair and every board panel — and size it from the page (`.bp-*`, `.plots`, `.bgrid`), never inside the frame | the same numbers as a table | |
| 83 | 83 |
docs/design/sections/5d-page-local-classes.md −5
| @@ -43,11 +43,6 @@ the same line (#4888). | ||
| 43 | 43 | |
| 44 | 44 | `.split` is the one two-column page: main, then an `<aside>` rail of 260px that sticks from 960px. `data-rail="wide"` on it (the studio's home and an app's page) widens the rail to 320px from 900px, drops the top margin, and lets the rail scroll with the page, since a column of cards runs taller than a screen |
| 45 | 45 | - **`style/content.ts`, page furniture** — what frames a page's content: page and repo heads, the front page and its tiles, explore, backups, boards, the fleet chart's series colours, lineage: |
| 46 | - `.bgrid` | |
| 47 | - `.bp-h2` | |
| 48 | - `.bp-h3` | |
| 49 | - `.bp-w2` | |
| 50 | - `.bp-w3` | |
| 51 | 46 | - `.commit-body` |
| 52 | 47 | - `.commit-head` |
| 53 | 48 | - `.filters` |
src/web/style/components.ts +7
| @@ -832,6 +832,13 @@ details[open] > summary.row > .row-lead { transform: rotate(90deg); } | ||
| 832 | 832 | .empty .btn { margin-top: 8px; } |
| 833 | 833 | .card.empty { border-style: dashed; box-shadow: none; } |
| 834 | 834 | |
| 835 | /* ---- board panel grid: a board's panels, on /boards and pinned in the studio ---- */ | |
| 836 | .bgrid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; margin-bottom: 16px; } | |
| 837 | .bp-w2 { grid-column: span 2; } | |
| 838 | .bp-w3 { grid-column: span 3; } | |
| 839 | .bgrid h2 { font-size: 14px; } | |
| 840 | @media (max-width: 720px) { .bgrid { grid-template-columns: minmax(0, 1fr); } .bp-w2, .bp-w3 { grid-column: auto; } } | |
| 841 | ||
| 835 | 842 | /* ---- settings shell ---- */ |
| 836 | 843 | .settings { display: grid; grid-template-columns: minmax(0, 1fr); gap: 20px; margin-top: 16px; } |
| 837 | 844 | .settings-nav { |
src/web/style/content.ts +5 −8
| @@ -181,18 +181,15 @@ a.origin-chip:hover { border-color: var(--border-strong); color: var(--fg); } | ||
| 181 | 181 | .chart-box circle { fill: var(--accent); } |
| 182 | 182 | .chart-box .big { fill: var(--fg); font-family: inherit; } |
| 183 | 183 | .chart-box.is-table { aspect-ratio: auto; height: 260px; overflow: auto; } |
| 184 | /* A board panel sizes its chart from here, after .chart-box: the grid itself | |
| 185 | is §5's board (components.ts), but this layer is where .chart-box is drawn. */ | |
| 186 | .bgrid .chart-box { max-width: none; } | |
| 187 | .bp-h2 .chart-box { aspect-ratio: 720 / 400; } | |
| 188 | .bp-h3 .chart-box { aspect-ratio: 720 / 540; } | |
| 184 | 189 | .bp-h2 .chart-box.is-table { height: 400px; } |
| 185 | 190 | .bp-h3 .chart-box.is-table { height: 540px; } |
| 186 | 191 | #summary { display: flex; flex-wrap: wrap; gap: 6px; } |
| 187 | 192 | #summary .pill { max-width: 100%; white-space: normal; } |
| 188 | .bgrid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; margin-bottom: 16px; } | |
| 189 | .bp-w2 { grid-column: span 2; } | |
| 190 | .bp-w3 { grid-column: span 3; } | |
| 191 | .bgrid .chart-box { max-width: none; } | |
| 192 | .bp-h2 .chart-box { aspect-ratio: 720 / 400; } | |
| 193 | .bp-h3 .chart-box { aspect-ratio: 720 / 540; } | |
| 194 | .bgrid h2 { font-size: 14px; } | |
| 195 | @media (max-width: 720px) { .bgrid { grid-template-columns: minmax(0, 1fr); } .bp-w2, .bp-w3 { grid-column: auto; } } | |
| 196 | 193 | /* The stacked bar chart's series colours, for its bars (fleet-graph.ts). |
| 197 | 194 | Okabe–Ito, told apart with common colour-vision deficiencies; the eighth, |
| 198 | 195 | the folded "others", is grey. */ |
test/board-grid-style.test.ts added +48
| @@ -0,0 +1,48 @@ | ||
| 1 | /** | |
| 2 | * #4833: the grid a board's panels sit in is §5's board, so its rules live in | |
| 3 | * components.ts, once each. What sizes a panel's chart stays in content.ts | |
| 4 | * after `.chart-box`: layers rank above specificity, so in components it | |
| 5 | * would lose to `.chart-box`'s own width and ratio. | |
| 6 | */ | |
| 7 | import { describe, expect, test } from "bun:test"; | |
| 8 | import { css } from "../src/web/style.ts"; | |
| 9 | import { components } from "../src/web/style/components.ts"; | |
| 10 | import { content } from "../src/web/style/content.ts"; | |
| 11 | ||
| 12 | /** How many times `rule` is written in `sheet`. */ | |
| 13 | const count = (sheet: string, rule: string) => sheet.split(rule).length - 1; | |
| 14 | ||
| 15 | const GRID = [ | |
| 16 | ".bgrid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; margin-bottom: 16px; }", | |
| 17 | ".bp-w2 { grid-column: span 2; }", | |
| 18 | ".bp-w3 { grid-column: span 3; }", | |
| 19 | ".bgrid h2 { font-size: 14px; }", | |
| 20 | "@media (max-width: 720px) { .bgrid { grid-template-columns: minmax(0, 1fr); } .bp-w2, .bp-w3 { grid-column: auto; } }", | |
| 21 | ]; | |
| 22 | ||
| 23 | const SIZING = [ | |
| 24 | ".bgrid .chart-box { max-width: none; }", | |
| 25 | ".bp-h2 .chart-box { aspect-ratio: 720 / 400; }", | |
| 26 | ".bp-h3 .chart-box { aspect-ratio: 720 / 540; }", | |
| 27 | ".bp-h2 .chart-box.is-table { height: 400px; }", | |
| 28 | ".bp-h3 .chart-box.is-table { height: 540px; }", | |
| 29 | ]; | |
| 30 | ||
| 31 | describe("the board's panel grid", () => { | |
| 32 | test("each grid rule is in the served stylesheet once, from components.ts", () => { | |
| 33 | for (const rule of GRID) { | |
| 34 | expect(count(css, rule)).toBe(1); | |
| 35 | expect(count(components, rule)).toBe(1); | |
| 36 | expect(count(content, rule)).toBe(0); | |
| 37 | } | |
| 38 | }); | |
| 39 | ||
| 40 | test("each panel's chart sizing is served once, from content.ts after .chart-box.is-table", () => { | |
| 41 | const table = content.indexOf(".chart-box.is-table { aspect-ratio: auto;"); | |
| 42 | expect(table).toBeGreaterThan(-1); | |
| 43 | for (const rule of SIZING) { | |
| 44 | expect(count(css, rule)).toBe(1); | |
| 45 | expect(content.indexOf(rule)).toBeGreaterThan(table); | |
| 46 | } | |
| 47 | }); | |
| 48 | }); |
new above
offline