Overlay top-layer overhaul Phase 1: primitive + tokens + guard + StatusEditor - #58
Merged
Conversation
…Modal/Popover wrappers Add the --z-* residual token scale to app.css :root (design §D, additive) and the native top-layer primitive: - src/lib/top-layer.svelte.js: the topLayer Svelte action. modal -> <dialog> showModal(); non-modal -> Popover API showPopover(). Anchored positioning via getBoundingClientRect re-run on capture-phase scroll + resize. Light-dismiss/Esc/cancel -> onClose; restoreFocus on close. CRITICAL capability-guard (design §F.3): jsdom 29.0.2 has no showPopover/ showModal/popover/elementFromPoint, so the action detects their absence and degrades gracefully (skip native show, still wire events + position + focus) so component unit tests still mount in jsdom. - src/components/Popover.svelte / Modal.svelte: thin wrappers per design §C.
tests/overlay-top-layer-guard.spec.js (design §E Tier-1). Two ACTIVE rules: (1) overlay-shaped components must reference the primitive; (2) ban raw z-index outside the var(--z-*) token scale. ALLOWED is seeded to today's tree (all currently-unmigrated overlays + toasts/banners + docked panels + bits-ui allowlist + app.css) so the pre-migration tree stays green. StatusEditor is deliberately NOT in ALLOWED: it passes on merit by using <Popover>/use:topLayer and carrying no bare z-index. Phase 2 shrinks ALLOWED toward empty.
…arquee fix) StatusEditor previously used position:fixed; z-index:90/91 while rendered inside Sidebar's .sidebar-left (backdrop-filter => own stacking context), so it painted BEHIND the center column and right-side panels. Migrate it to <Popover> (Popover API top layer), anchored to the identity row, which escapes every ancestor stacking context. Removes the backdrop trap, the position:fixed, and the z-index. Sidebar binds the profile-status row as the anchor and passes it through. Existing StatusEditor unit + e2e specs keep passing via the action's jsdom capability-guard.
e2e/scenarios/14-overlay-top-layer.spec.ts (design §E Tier-2): open StatusEditor while a backdrop-filter artifact panel is open and assert it is visible AND on-top (center hit-test via expectLocatorOnTop) AND :popover-open, plus Esc/outside-click light-dismiss closes it and focus returns to the trigger. Strengthen 06-status-editor.spec.ts with a :popover-open assertion confirming the primitive promoted the editor.
…Escape) The app's keyboard.svelte.js registers a window keydown listener that calls event.preventDefault() on Escape (universal-close), which SUPPRESSES the browser's native popover/dialog Esc light-dismiss. Intercept Escape on the overlay node (bubbles there before window), stopPropagation so only the topmost overlay closes, then drive our own close + onClose. Add a notify latch so onClose fires exactly once across the racing native signals (keydown -> close -> hidePopover -> toggle). Verified by e2e 06 + 14 Esc tests in real Chromium.
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.
Overlay / top-layer overhaul — Phase 1
Replaces the ad-hoc
position:fixed+ manual z-index overlay pattern with a single robust primitive built on the browser native top layer, and lands the marquee fix (StatusEditor). Implements design doc sections C/D/E/F, build-sequence steps 1-3. Phase 2 (the other 16 overlays, ALLOWED shrink, bits-ui audit) is intentionally out of scope.What's in this PR
src/app.css :root) — additive--z-base/sticky/banner/sidebar/panel/toast(design §D). No app-wide collapse yet (Phase 2).src/lib/top-layer.svelte.js) — thetopLayeraction: modal via<dialog>.showModal(), non-modal via Popover APIshowPopover(); anchored positioning viagetBoundingClientRectre-run on capture-phase scroll + resize; light-dismiss/Esc →onClose;restoreFocus. Capability-guard (§F.3): detects jsdom's missingshowPopover/showModaland degrades gracefully (skips native show, still wires events + positioning + focus) so existing component unit tests keep mounting/passing.keyboard.svelte.jspreventDefault()s window Escape, which would otherwise suppress native popover dismissal.<Modal>(real<dialog>+ native::backdrop) and<Popover>(anchored, light-dismiss div), per §C.web/tests/overlay-top-layer-guard.spec.js) — two ACTIVE rules (ban raw z-index outsidevar(--z-*); overlay-shape-without-primitive), with an explicitALLOWEDseed keeping today's unmigrated overlays green. StatusEditor is NOT allowed — it passes on merit. Phase 2 shrinksALLOWEDtoward empty.src/components/StatusEditor.svelte+ Sidebar anchor) — fromposition:fixed; z-index:90/91(trapped behind the center column / panels by Sidebar'sbackdrop-filterstacking context) to<Popover>in the native top layer, anchored to the identity row. Existing unit + e2e coverage preserved.web/e2e/scenarios/14-overlay-top-layer.spec.ts) — opens StatusEditor over abackdrop-filterartifact panel and asserts visible + on-top (center hit-test) +:popover-open, plus Esc/outside-click close with focus return. Scenario 06 strengthened with a:popover-openassertion.Verification
pnpm --dir web test— 100 files / 1332 tests, 0 failures, 0 skips (run 3x, stable). 0 stderr.pnpm --dir web build— green..svelte/.svelte.js.14-overlay-top-layer+06-status-editor= 25 passed (StatusEditor on-top +:popover-open+ Esc/outside-click + focus-return verified);--grep "source-level invariants"= 55 passed.🤖 Generated with Claude Code