From 1b2c13ecd66b8056f2acc4a3a1c9fdfdb1d87310 Mon Sep 17 00:00:00 2001 From: Ilia Denisov Date: Wed, 27 May 2026 19:45:44 +0200 Subject: [PATCH] =?UTF-8?q?fix(ui):=20F8-09=20owner-feedback=20=E2=80=94?= =?UTF-8?q?=20fixed=20TOC=20trigger=20+=20heading=20offset=20(#52)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Owner-reported regressions in Firefox + Safari on desktop after the initial F8-09 patch landed: 1. The TOC trigger rode up with the page during scroll instead of staying pinned to the viewport (mobile worked, desktop did not). 2. Clicking a popover item scrolled the matching section so its heading went up under the chrome — only the table body was visible. Root cause for (1): the in-game shell declares `overflow-y: auto` on `.active-view-host` so mobile (where `.game-shell` is fixed at `inset: 0`) has an internal scroll region. On desktop the host grows with content, no overflow ever engages, and the document body becomes the actual scroll container. Per CSS spec the host remains the "scrollport" for any `position: sticky` descendant, so the trigger inside the report column never sees the scroll event and rides up with the body content. Fix: - Swap the trigger from `position: sticky` to `position: fixed`. The component is mounted only while the report active view is on screen, so the fixed element is naturally tied to the view's lifetime. Anchor at `top: 4rem` (below the in-game header), and on `min-width: 1024px` shift `right` by 18 rem to clear the always-on sidebar; below 1024 px the sidebar is an overlay so the default `right: 1.25rem` matches the report's right padding. - Add `padding-top: 4.5rem` to `.report-view` (4rem mobile) so the first section heading does not land under the trigger at scroll position 0. - Add `scroll-margin-top: 7.5rem` to every `
` so `scrollIntoView({ block: "start" })` lands the heading below the trigger after a popover-driven jump. - Sync `ui/docs/report-view.md` §"Table of contents and active highlight" with the new positioning rationale. Tests: `pnpm check`, `pnpm test` (821), `pnpm test:e2e report-sections` (4 projects) all green. Refs: #52 (#43 umbrella). Co-Authored-By: Claude Opus 4.7 --- ui/docs/report-view.md | 29 ++++++++++++-- ui/frontend/src/lib/active-view/report.svelte | 21 ++++++++-- .../lib/active-view/report/report-toc.svelte | 40 +++++++++++++++++-- 3 files changed, 78 insertions(+), 12 deletions(-) diff --git a/ui/docs/report-view.md b/ui/docs/report-view.md index 6822ee7..a756157 100644 --- a/ui/docs/report-view.md +++ b/ui/docs/report-view.md @@ -85,8 +85,8 @@ the body — both surfaces stay in sync by construction. ## Table of contents and active highlight -`report/report-toc.svelte` renders a single sticky icon-popup -trigger in the top-right corner of the report column. The trigger +`report/report-toc.svelte` renders a single icon-popup trigger +pinned to the top-right corner of the report column. The trigger shows `≡` followed by the title of the currently-active section (CSS-clamped with `text-overflow: ellipsis` so a long RU title cannot bloat the button). Clicking opens an anchored popover @@ -97,8 +97,29 @@ the matching `
` into view via `scrollIntoView` (with `prefers-reduced-motion` falling back to `behavior: "auto"`). -On viewports below `768.98 px` the same surface re-styles into a -fixed bottom-sheet anchored above the layout-owned bottom-tabs +The trigger uses `position: fixed` instead of `position: sticky`. +Per the CSS sticky spec a sticky element sticks within its nearest +ancestor with non-`visible` overflow — and `.active-view-host` +declares `overflow-y: auto` for the mobile scroll story. On +desktop the host grows with content and the document body becomes +the actual scroll container, so a sticky trigger inside the report +column never receives a scroll event and rides up with the page +content. A fixed trigger sidesteps the chain entirely; the +component is mounted only while the report active view is on +screen, so the fixed element is naturally tied to the view's +lifetime. The desktop offset is `right: calc(18 rem + 1.25 rem)` +to clear the always-on `lib/sidebar/sidebar.svelte`; below +1024 px the sidebar collapses to an overlay drawer, so the +default `right: 1.25 rem` matches the report's right padding. The +report-view itself adds a top padding equal to the trigger's +viewport offset plus its height so the first section's heading +does not render under the trigger at scroll position 0, and every +`
` gets `scroll-margin-top: 7.5rem` so +`scrollIntoView({ block: "start" })` lands the heading below the +trigger after a popover-driven jump. + +On viewports below `768.98 px` the popover surface re-styles into +a fixed bottom-sheet anchored above the layout-owned bottom-tabs bar (mirrors `lib/active-view/map-toggles.svelte`), so the same trigger and the same menuitem list serve desktop and mobile. diff --git a/ui/frontend/src/lib/active-view/report.svelte b/ui/frontend/src/lib/active-view/report.svelte index 11330a7..e0f0392 100644 --- a/ui/frontend/src/lib/active-view/report.svelte +++ b/ui/frontend/src/lib/active-view/report.svelte @@ -134,10 +134,14 @@ TOC and the body iterate the same data. diff --git a/ui/frontend/src/lib/active-view/report/report-toc.svelte b/ui/frontend/src/lib/active-view/report/report-toc.svelte index e267cef..d147b64 100644 --- a/ui/frontend/src/lib/active-view/report/report-toc.svelte +++ b/ui/frontend/src/lib/active-view/report/report-toc.svelte @@ -143,14 +143,43 @@ in F8-09: that switch is available in the app-shell view menu.