feat(reflow)!: row-count-driven layout, real cell band, overflow as a device setting (ADR-0011) - #159
Merged
Merged
Conversation
… device setting (ADR-0011) ADR-0010 sized the grid by asking how many columns fit the width, then let rows fall out ragged. Column count only sees one axis, so height had to be smuggled back in as a proxy -- the shipped code carried a tuned constant, Math.min(w / 3, Math.max(cellSize * 1.5, 200)), whose comments named the specific test cases it existed to satisfy. Pick the row count instead. It is the only genuinely free integer: the column count, the cell size and the row shape all follow from it, using both axes honestly. No tuned constants remain. - Rows fill to the column count, remainder in the bottom row alone: 10 widgets over 4 rows is 3+3+3+1, not 3+3+2+2. That is ordinary text wrapping, which CSS grid auto-placement already performs -- so spans need no special handling. - The grid block centres on both axes while rows wash left against its left edge, so columns stay aligned. justify-content over a fixed track list gives this for free. - minCell and maxCell gain distinct jobs: the floor decides how many buttons are visible, the cap stops a sparse deck from ballooning. Cell size is derived from the viewport; neither value sets it. - The dominance prune (R-1)*c >= units is load-bearing, not an optimisation: it keeps the candidate set closed under (rows, cols) -> (cols, rows), so an orientation and its counterpart resolve consistently. BREAKING CHANGE: Layout.overflow now defaults to clip instead of shrink-to-fit, and overflow becomes a device setting that the layout merely supplies a default for. Under shrink-to-fit the visible count is always every widget, so capacity is never consulted and minCell has no effect at all -- defaulting to it would ship a preference that does nothing. The two modes are identical whenever the deck already fits; they diverge only on oversized decks, which will now hide trailing widgets rather than shrinking everything. clip is also materially better than before: ADR-0010 realised it as CSS overflow:hidden over a height-blind column count, so rows past the fold were sliced mid-cell. It now trims the widget list to whole cells before render. Verified: 345 vitest pass, tsc/eslint/build clean, protocol drift check passes. Browser-driven against the built client via ?demo=: portrait 2+2+2+2 <-> landscape 4+4, every row sharing one left edge, minCell=240 trimming to 3 widgets at exactly 240px, shrink-to-fit showing all 9 at 146px while ignoring minCell. The Python suite was NOT run (no python3/uv/venv available, flox token expired). The daemon change is a one-line default plus a comment, and the matching test was renamed and updated, but that edit is unverified by execution. Interactive model of the behaviour: docs/mockups/reflow-adr0011.html Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
jonocodes
force-pushed
the
design/balanced-row-distribution
branch
from
September 23, 2026 05:33
ffc01f3 to
a0c07ac
Compare
The nix client derivation lists each Vite HTML entry by name, so the standalone help page was missing from the sandbox and Rollup failed with "Could not resolve entry module help.html". The CI test job builds from the full checkout, which is why only the nix job caught it.
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.
Supersedes ADR-0010. Keeps its model — widgets are an ordered list with no coordinates, packed in strict order — and replaces the geometry that sized them, plus the settings that drove it.
Why
ADR-0010 sized the grid by asking "how many columns fit the width?" and let rows fall out ragged. Column count only sees one axis, so height had to be smuggled back in as a proxy. The shipped code ended up with a tuned constant whose own comments named the test cases it existed to satisfy:
That is the signal the free variable was wrong.
What changed
Pick the row count instead. It's the only genuinely free integer — column count, cell size and row shape all follow from it, using both axes honestly. No tuned constants remain.
3+3+3+1, not3+3+2+2. That's ordinary text wrapping, which CSS grid auto-placement already performs — so spans need no special handling.justify-contentover a fixed track list gives this for free.minCell/maxCellgain distinct jobs. The floor decides how many buttons are visible; the cap stops a sparse deck from ballooning. Cell size is derived from the viewport — neither value sets it.(R-1)*c >= unitsis load-bearing, not an optimisation: it keeps the candidate set closed under(rows, cols) -> (cols, rows), so an orientation and its counterpart resolve consistently. Deleting it breaks that silently.Breaking change
Layout.overflownow defaults toclipinstead ofshrink-to-fit, and overflow becomes a device setting that the layout merely supplies a default for.The reason isn't aesthetic: under
shrink-to-fitthe visible count is always every widget, so capacity is never consulted andminCellhas no effect at all. Defaulting to it would ship a preference that does nothing.The two modes are identical whenever the deck already fits — they diverge only on oversized decks, which now hide trailing widgets rather than shrinking everything. No layout YAML in this repo sets
overflow, so decks that fit are unaffected.clipis also materially better than before. ADR-0010 realised it as CSSoverflow: hiddenover a height-blind column count, so rows past the fold were sliced mid-cell. It now trims the widget list to whole cells before render.Verification
tsc --noEmitclean · eslint clean ·npm run buildclean?demo=: portrait2+2+2+2⇄ landscape4+4(same widgets, reshaped), every row sharing one left edge,minCell=240trimming to 3 widgets at exactly 240px,overflow=shrink-to-fitshowing all 9 at 146px while ignoringminCellA bug the suite caught, worth a reviewer's eye: trimming initially ran before the first
ResizeObservermeasurement, whereclientWidthis 0 — so the grid rendered empty for a frame, and permanently in any host without a ResizeObserver.computeReflownow reports everything visible at zero size when unmeasured.Not verified
The Python suite was not run — no
python3/uv/venv on this machine and flox's token is expired. The daemon change is a one-line default plus a comment, andtest_layout_overflow_defaults_to_shrink_to_fitwas renamed to..._defaults_to_clipwith its assertion updated, but that edit is unverified by execution. Please runjust test-allbefore merging.Also in here
docs/adr/0011-reflow.md; ADR-0010 marked superseded; both added to the ADR index (0010 had never been listed)CONTEXT.md: the stale Grid placement entry still describing[x, y, w, h]coordinates — deleted back in ADR-0010 — replaced with Reflow, Cell, Capacitydocs/mockups/reflow-adr0011.html— an interactive model of the behaviour across ten viewports, both orientations at true ratios, with chrome geometry measured fromstyle.cssin Chromium🤖 Generated with Claude Code