Pinned toolbar that reserves its own row - #610
Conversation
A page built around a chart plotter or an embedded webpage gives that widget every pointer event, so none of the reveal gestures reach the shell. Measured on such a page: 5 px of shell survive above the frame, and on touch it takes two taps inside that band to get the toolbar back. Pinning holds the toolbar open. It also stops being an overlay: the component host becomes a row in the shell's flex column, so the routed content is shorter by the toolbar's height instead of covered by it. The dashboard grid needs no part in this — it already recomputes cell height from its own host, which the reserved row shrinks. Pinning outranks the automatic-reveal setting, which reads as disabled while it is on. Closes #606 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EC1waxZ33TqjjXJGaSUNB5
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Team Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
The other pinned tests set the setting before the fixture is created, so they pass whether the host binding reads the signal or a construction-time value. This one flips it while the toolbar is on screen, which is what Display settings does. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EC1waxZ33TqjjXJGaSUNB5
AppComponent pushes both toolbar settings from one effect on every run, so setPinned(false) landed on an already-unpinned service routinely — including right after setAutoReveal(false) had retracted the boot dwell. Its unconditional reveal() undid that, putting the toolbar back on screen for four seconds on every boot and every settings save for anyone who had turned automatic reveal off (#495). setPinned now returns on a no-change call, and a genuine unpin hands the toolbar to whichever rule governs it rather than always revealing. Pinned state also moves into ChromeVisibilityService as a signal, so the host class that reserves the layout row and the `revealed` state the template gates `inert` on come from one place and cannot disagree. The host binding moves to `host:` metadata, matching the other components. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EC1waxZ33TqjjXJGaSUNB5
The visibility service holds pinned state in a signal that its own guards read, so calling the setters from inside the effect put the effect's write back among its dependencies — convergent only because setPinned early-returns on a no-change call. Reading the settings first and pushing them untracked removes the cycle rather than relying on that guard to break it. The new AppComponent block runs against the real ChromeVisibilityService rather than a mock, which is what makes the effect's two calls observable: it fails on the unpin-reveals-a-hidden-toolbar bug. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EC1waxZ33TqjjXJGaSUNB5
Code review — 6 personasScope: Intent: a global setting that both holds the toolbar visible and turns it from an overlay into a real layout row, so a page whose widget swallows every reveal gesture stays navigable. Default behaviour unchanged. Team: correctness, testing, maintainability, project-standards (always-on) · api-contract (new field on the persisted P1 — High
Five of six reviewers reached this independently. It was invisible to CI because both halves were tested in isolation and every P2 — Moderate
P3 — Low
Applied fixesThree commits,
Two of these were checked against deliberately broken builds rather than assumed: removing the pinned guard fails the rewritten hover test, and restoring the old VerificationHeadless Chromium against the production bundle, on the page from the reproduction in #606:
Row three is finding 1's regression case, now correct. Row two is the precedence claim. The frame at 734 vs 790 is the reflow.
Pre-existing (filed)
CoverageSuppressed below the 0.60 confidence gate: none reported. No reviewer failed or timed out. Untracked files excluded from scope: four Known gaps, unfixed on purpose. The layout itself — the rules that turn the host class into a reserved row — is CSS, and jsdom does not lay out, so deleting the Verdict: ready to merge. The P1 and every P2 in the diff are fixed and verified; the one pre-existing P2 is filed. |
On a page whose only widget captures pointer input — a chart plotter, an embedded webpage — none of the toolbar's reveal gestures reach Skip's shell, because pointer events inside an iframe never reach the embedding document. Measured on the production bundle at 900×800 with a full-page
widget-iframe: mouse move, tap and swipe-down over the content all do nothing, the frame occupies y=5..795, and the only surviving route is the 5 px band above it — where a touch user needs two taps, the first raising the peek cue and the second revealing. Full gesture table in #606.A new "Keep the toolbar on screen" setting holds the toolbar open, and the point of the change is that it does not overlay.
.toolbar-hostisposition: fixedtoday and the component host has no box, so the shell's flex column gives the toolbar no space; pinned flips both, and the host becomes a non-growing row. The routed content — itsflex: 1 1 autosibling — is then shorter by exactly the toolbar's height. The dashboard grid needed no changes at all:resizeGridColumns()already derives cell height from its own host'sclientHeight, and aResizeObserveron that host already re-runs it.Pinning outranks the automatic-reveal setting from #495: with it on,
ChromeVisibilityServiceholdsrevealedtrue, never arms the idle timer, and makeshide()a no-op, and the auto-reveal toggle reads as disabled.Verified in a headless browser against the production bundle, on the page from the reproduction:
The frame shrinking from 790 to 734 is the whole point: reflowed, not covered. The clickability check is Playwright's actionability test, which includes "receives pointer events", so it fails for a control something is covering. The unpinned row is unchanged from before this branch.
npm run cigreen — lint,snc, 180 spec files, 7 MCP-schema files. New coverage: the service's pinned mode (revealed survives an idle expiry and an explicit hide, outranks a disabled auto-reveal, hands back to the idle timer when unpinned), the settings hydration and round-trip, the effect that pushes the setting, and the host class that moves the toolbar out of overlay mode.One deliberate scope call: the setting is global, matching how
autoRevealToolbarworks, not per-page. The issue asked for "global or per-page". Per-page costs a field onDashboardplus page-manager UI, and is worth adding only if the global lever proves too blunt.Closes #606