The board learns to zoom — eight rungs on one ruler, and Actual Size is the untouched board
View ▸ Zoom In / Zoom Out / Actual Size (⌘+ / ⌘− / ⌘0): 75%–200% in eight rungs, app-wide and persisted (the Show Comments precedent) — a viewing comfort, not a property of any one board. The level travels as BoardZoomContext in the environment, injected on BoardView alone so the banner strip, search bar, sheets and popovers stay at the system size; the environment is also what carries it through CardFaceView's equality gate, which compares nothing that moves with the level. Every BoardMetrics figure follows zoom.bodyPointSize — card and lane chrome, drag replicas and the count badge, the resize handle, the trash column — and the drop registry carries the ruler for event-time reads, with the autoscroller's three reaches turning font-derived (reachSide named as the stripGap it always equalled). Lanes still divide the window; zoom never moves the window or its floor. The toolbar gains a catalog-only Zoom In/Out pair mirroring the menu rows' predicate; zoom holds shut mid-drag (frozen geometry), each rung announces itself to VoiceOver, and the render suite pins both invariants: a rung repaints every face, a no-op Actual Size repaints nothing. Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
@@ -54,6 +54,21 @@ Observation tracks whole **properties**. Reading `.background` off `store.snapsh
|
||||
|
||||
**Selection is O(board) in card bodies.** Selecting one card re-runs all 180 faces, because `CardFaceView.body` reads `store.selection` through `isSelected`. This is the Observation half the gates explicitly do not cover, and narrowing it would mean each face taking its own selected-ness as a compared parameter — a design change, not a gate.
|
||||
|
||||
### The zoom pair (2026-08-03)
|
||||
|
||||
Board zoom (03-board-ui.md ▸ Layout — zoom) added two steps to the suite, and they are the only ones here that assert a body count is **non-zero** — because for zoom, a suppressed render is the bug.
|
||||
|
||||
| Step | strip | containers | cards |
|
||||
| --- | --- | --- | --- |
|
||||
| zoom in one rung | 1 | **12** of 12 | **540** of 540 |
|
||||
| Actual Size when already there | **0** | **0** | **0** |
|
||||
|
||||
A whole-board repaint is the *right* answer for the first row, not a budget overrun: every figure the strip draws is a multiple of the level, so every lane's chrome and every card's geometry genuinely changed. What is asserted is only that the numbers are non-zero.
|
||||
|
||||
**The gate must not swallow a level change, and it doesn't — because the level travels in the environment.** `CardFaceView.==` compares the card, its role, its store, the marquee and the drop context; a ⌘+ leaves all five identical. The faces repaint only because `@Environment` values are outside the comparison by design ("SwiftUI invalidates on those itself"), which is exactly why `BoardZoomContext` is an environment value rather than a read off `AppModel`. A zero in that row would be the board zooming its lane chrome while every card face stayed at 13pt — `zoomRepaintsTheCardFaces` is the tripwire.
|
||||
|
||||
**A board nobody zoomed pays nothing.** `@Observable` notifies on every set, equal or not, so `BoardZoomStore.setLevel` refuses an unchanged level outright rather than re-running the strip to draw what it was already drawing. `actualSizeIsTheUntouchedBoard` pins that guard, and pins that the resting ruler is the system's own body size.
|
||||
|
||||
## The signposts
|
||||
|
||||
| Signpost | Span | Emitted from |
|
||||
|
||||
Reference in New Issue
Block a user