feat(ui): a design language for panels, and the layout bug it exposed - #152
Merged
pskeshu merged 1 commit intoSep 4, 2026
Merged
Conversation
## The Atrium is off by default, and stays off `enable()` wrote '1' to localStorage, so one visit to `?atrium=1` turned the Atrium on permanently, on that browser, for every later visit including plain `/`. An experimental surface that latches after a single look is the trap in gently-project#133 with the sign flipped. The flag is the URL and only the URL now; older latched values are cleared on load, and `exit` drops the parameter rather than recording a preference. ## A design language `docs/architecture/PANELS.md` gains it. A panel has to be recognisable as one: an operator scanning for a control should not have to work out where one grouping ends and the next begins, and a panel that dissolves into the background will not be found under pressure. - **Anatomy** — the `.op-block` card the instrument rail already uses; a small uppercase heading; `label — control — value` rows with fixed-width labels so values align; monospace tabular values; an em dash for unknown; red only for derived hazards. - **Composition** — standalone panels draw their own card and heading; composed ones draw neither. Two borders around one subject is the same duplication as two controls for one LED, just quieter. - **Placement** — panels go where the thing they act on is. Marking sits under the frame it marks; Light and Camera sit beside the surface they drive. - **Overflow** — the rail scrolls and must show it. Nothing may claim vertical space to say nothing. Two heading styles were quietly diverging — `.lp-title` at 0.72rem/700/0.06em against `.op-block-head` at 0.62rem/600/0.10em. One rule now. Marking gets the card it was missing, laid out across rather than down since it is a wide card under the frame. ## The layout bug Chasing "the image panel stretches awkwardly", measured rather than guessed: the camera box was 487 px wide holding a 163 px image — 324 px of dead space — inside a pane that was 464 px tall. `.op-pane` declares `grid-template-rows: auto minmax(0, 1fr)`, but `.op-lock` is `hidden` most of the time and a hidden element generates no box. So auto-placement dropped `.op-main` and `.op-inst` into row 1, the `auto` row, and the `1fr` row stayed empty. The pane sized to its content and the frame never grew. Worse, it was conditional: when the banner IS shown it fills row 1 and pushes both into row 2. The frame was one size with the sample at the objective and another without. Explicit `grid-row` on all three. The image goes from 163x163 to 356x356 — 2.2x — and dead space from 324 px to 131 px, which is what a square in a 487x356 area costs and no more. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The Atrium is off by default, and now stays off
enable()wrote'1'to localStorage, so a single visit to?atrium=1turned it on permanently on that browser — including on plain/. An experimental surface that latches after one look is #133's trap with the sign flipped.The flag is the URL and only the URL. Older latched values are cleared on load;
exitdrops the parameter instead of recording a preference.A design language
Added to
docs/architecture/PANELS.md. A panel must be recognisable as one — an operator scanning for a control shouldn't have to work out where one grouping ends and the next begins..op-blockcard the rail already uses; small uppercase heading;label — control — valuerows with fixed-width labels so values align; monospace tabular values; em dash for unknown; red only for derived hazards.Two heading styles were quietly diverging (
.lp-title0.72rem/700/0.06em vs.op-block-head0.62rem/600/0.10em). One rule now. Marking gets the card it was missing.The layout bug this exposed
Chasing "the image panel stretches awkwardly", measured rather than guessed: the camera box was 487 px wide holding a 163 px image — 324 px dead — inside a 464 px tall pane.
.op-panedeclaresgrid-template-rows: auto minmax(0, 1fr), but.op-lockishiddenmost of the time and a hidden element generates no box. Auto-placement therefore dropped.op-mainand.op-instinto row 1 — theautorow — and the1frrow stayed empty.Worse, it was conditional: when the banner is shown it fills row 1 and pushes both to row 2. So the frame was one size with the sample at the objective and another without.
Explicit
grid-rowon all three:131 px is what a square in a 487×356 area costs, and no more.
ruff, format, both mypy runs pass. JS: 7 + 13 + 32. pytest at its pre-existing 17 (#143).