Skip to content

feat(ui): a design language for panels, and the layout bug it exposed - #152

Merged
pskeshu merged 1 commit into
gently-project:developmentfrom
pskeshu:feat/panel-design-language
Sep 4, 2026
Merged

feat(ui): a design language for panels, and the layout bug it exposed#152
pskeshu merged 1 commit into
gently-project:developmentfrom
pskeshu:feat/panel-design-language

Conversation

@pskeshu

@pskeshu pskeshu commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

The Atrium is off by default, and now stays off

enable() wrote '1' to localStorage, so a single visit to ?atrium=1 turned 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; exit drops 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.

  • Anatomy — the .op-block card the rail already uses; small uppercase heading; label — control — value rows with fixed-width labels so values align; monospace tabular values; em dash for unknown; red only for derived hazards.
  • Composition — standalone panels draw their own card and heading; composed ones draw neither.
  • Placement — panels go where the thing they act on is.
  • Overflow — the rail scrolls and must show it. Nothing may claim vertical space to say nothing.

Two heading styles were quietly diverging (.lp-title 0.72rem/700/0.06em vs .op-block-head 0.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-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. Auto-placement therefore dropped .op-main and .op-inst into row 1 — the auto row — and the 1fr row 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-row on all three:

before after
image 163×163 356×356
dead space 324 px 131 px

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).

## 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>
@pskeshu
pskeshu merged commit 3a1d036 into gently-project:development Sep 4, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant