Name the bounded frame: fill:true on the shell, and the contract minor that ships it
CI / full-gate (push) Successful in 2m52s

This commit is contained in:
2026-09-08 17:27:21 +02:00
parent 706abf1204
commit 11ba843ab1
13 changed files with 117 additions and 39 deletions
+16 -10
View File
@@ -278,16 +278,22 @@ Revisit only if the stated reason stops holding.
it means disclosure rather than popup: the nav tree. `shell.ejs` hand-rolls the same block for the
profile menu (its trigger composes escaped user values and its one item is a CSRF POST form) — keep
the two in step.
- **The document scrolls; a bounded scroll region is opt-in.** `.app` is `min-height: 100dvh`, not
a `100dvh` box with `overflow: hidden`: the sidebar is `position: sticky` at full height and the
topbar sticks too, so both stay put while the content column flows with the page. A page that wants
a region scrolling inside it — a board of full-height columns, a table whose header stays put —
gives that region a height and scrolls within it; `.table-wrap` is `overflow-x: auto` and nothing
more until a page does. The inverse default clipped the first long page that did not know the rule
(2026-09-06), silently, in every engine: nothing in a test or a console says a page is unreachable
below the fold. Document scrolling is also what keeps find-in-page, anchor links, keyboard paging,
print and reader mode working. The `visual.spec.ts` wheel test holds this; scrollIntoView would not,
since a script can scroll an overflow-hidden box and a reader cannot.
- **The document scrolls; a bounded frame is `fill: true` on the shell.** `.app` is
`min-height: 100dvh`, not a `100dvh` box with `overflow: hidden`: the sidebar is `position: sticky`
at full height and the topbar sticks with it, so the nav stays reachable — on a narrow screen the
hamburger is the only way into it, and with no script a long page would otherwise strand the reader.
Three things only the document scroller gets: **keyboard paging** unconditionally (Space, PgDn and
End reach a bounded region only once focus is inside it, which without script needs a focusable
descendant), **scroll restoration** on back/forward, and find-in-page. The inverse default clipped a
page silently in every engine — nothing in a test or a console says content is unreachable below the
fold.
A page that is a bounded frame — a board of full-height columns, a table whose header must stay put
— sets **`fill: true`** on the shell and scrolls a region inside `.app-fill` instead. The height is
the shell's to give, not the page's to compute: a page deriving it would have to know the topbar's
own height, which is a reach-through, so `.table-wrap` is `overflow-x: auto` and takes its vertical
scroll from `.app-fill`. The `visual.spec.ts` scroll test presses **End** rather than sending a
wheel event (Firefox's synthetic wheel does not reach the document) and rather than
`scrollIntoView`, which a script can apply to an overflow-hidden box that no reader can scroll.
- **`ICON_NAMES` (`src/ui/icons.ts`) is a host-owned registry, not a frozen plugin contract**, so it
is deliberately not re-exported from `@plainpages/plugin-api`. The palette may narrow when the last reference
to an id goes, and a plugin needing one gets it re-registered in the same change. Accepted cost: an