--- phase: 01-federation-mesh-hardening plan: 13 subsystem: ui tags: [vue, onboarding, scroll, accessibility, reduced-motion] requires: - phase: 01-federation-mesh-hardening provides: "OnboardingSeedGenerate.vue's existing flex-column step layout — a scrolling content region between a fixed header and a fixed footer whose Continue button stays pinned and disabled until `confirmed` is ticked" provides: - "An overflow-derived bottom scroll cue on the seed step that appears only when the confirmation tickbox is actually below the fold, scrolls it into view when activated, and disappears once it is visible" - "A vitest suite pinning the cue's show/hide contract, including the no-overflow no-render case" affects: [onboarding, web5] tech-stack: added: [] patterns: - "Derive an affordance's visibility from a real measurement (scrollHeight vs clientHeight, plus getBoundingClientRect comparison of target vs container bottom) rather than a viewport-height guess — the cue is then structurally incapable of appearing when there is nothing below." - "Measure with viewport-relative rects, not offsetTop/offsetHeight: offsetTop is relative to the nearest *positioned* ancestor, which here is the outer card (it carries `relative` for z-index stacking), not the scroll container." key-files: created: - neode-ui/src/views/__tests__/OnboardingScrollCue.test.ts modified: - neode-ui/src/views/OnboardingSeedGenerate.vue key-decisions: - "The cue is a sticky-bottom element inside the scroll region (gradient scrim + a glass pill reading 'One more step below' with a bobbing chevron), not a fixed overlay — so it rides the scroll container and cannot cover the fixed footer." - "Wayfinding only: `revealConfirm` calls scrollIntoView on the tickbox label and nothing else. It never sets `confirmed`, never focuses or enables Continue, and never auto-ticks on scroll — pinned by a dedicated test." - "Re-measure on scroll, window resize, and a ResizeObserver on the content wrapper, plus watches on `words` and `loading` — the word grid arrives asynchronously and changes height when the words/QR tabs switch, either of which can flip the region into overflow." - "onMounted was restructured so listener setup runs on both paths (restored-from-sessionStorage and freshly generated). The previous early `return` on the restore path would otherwise have skipped setup entirely for a user navigating back." requirements-completed: [UIFIX-03] coverage: - id: D1 description: "On a short viewport the cue appears, telling the user there is more below" requirement: "UIFIX-03" verification: - kind: unit ref: "neode-ui/src/views/__tests__/OnboardingScrollCue.test.ts#renders the cue when there is overflow and the tickbox is below the fold" status: pass human_judgment: false - id: D2 description: "Activating the cue brings the tickbox into view in one action, without touching confirmation state" requirement: "UIFIX-03" verification: - kind: unit ref: "…#activating the cue scrolls the tickbox into view and never touches confirmed" status: pass human_judgment: false - id: D3 description: "The cue disappears once the tickbox is visible and does not reappear while it stays visible" requirement: "UIFIX-03" verification: - kind: unit ref: "…#removes the cue once scrolling brings the tickbox into view" status: pass human_judgment: false - id: D4 description: "On a tall viewport the cue never renders — no element, no reserved space, no layout shift" requirement: "UIFIX-03" verification: - kind: unit ref: "…#renders no cue when the scroll region reports no overflow" status: pass human_judgment: false - id: D5 description: "The cue is absent while the seed is generating (no tickbox to point at yet) and once the box is ticked" requirement: "UIFIX-03" verification: - kind: unit ref: "…#never shows the cue while loading, regardless of overflow; …#removes the cue once the tickbox is ticked" status: pass human_judgment: false - id: D6 description: "The cue's motion is disabled under prefers-reduced-motion, per the site-wide convention" requirement: "UIFIX-03" verification: - kind: other ref: "@media (prefers-reduced-motion: reduce) { .onb-cue-chevron { animation: none; } } in OnboardingSeedGenerate.vue's scoped style" status: pass human_judgment: false duration: 70min completed: 2026-08-01 status: complete --- # Phase 1 Plan 13: On-Brand Scroll Cue for the Onboarding Tickbox (UIFIX-03) Summary **Added a measurement-driven scroll cue to the seed step so the confirmation tickbox is never silently below the fold on a short screen — and made it structurally impossible for that cue to appear on a screen tall enough not to need it.** ## Performance - **Duration:** ~70 min (across two sessions — see Deviations) - **Completed:** 2026-08-01 - **Tasks:** 2/2 - **Files modified:** 2 (1 view, 1 new test file) ## Accomplishments - A sticky-bottom cue inside the seed step's scroll region: a gradient scrim with a glass pill ("One more step below") and a bobbing chevron, styled to the house dark/glass language rather than a bolted-on arrow. - Visibility is derived from real geometry — `scrollHeight > clientHeight` for overflow, then a `getBoundingClientRect()` comparison of the tickbox's bottom against the scroll container's bottom. On a tall viewport the element does not render at all, so tall screens are byte-identical to before. - Activating the cue smooth-scrolls the tickbox into view and does nothing else; a test asserts `confirmed` is untouched, so the affordance can never become a way to skip the confirmation. - The cue also stays hidden while the seed is generating and after the box is ticked, and its chevron animation is disabled under `prefers-reduced-motion`. - Listener/observer setup was moved onto both `onMounted` paths — previously the sessionStorage-restore path returned early, which would have left a user navigating back to this step with no cue at all. ## Files Created/Modified - `neode-ui/src/views/OnboardingSeedGenerate.vue` — refs on the scroll container, content wrapper and tickbox label; `updateScrollCue()` measurement; `revealConfirm()`; scroll/resize/ResizeObserver wiring with matching teardown in `onUnmounted`; the cue markup and its scoped CSS (fade transition, chevron bob, reduced-motion guard). - `neode-ui/src/views/__tests__/OnboardingScrollCue.test.ts` — 6 tests pinning the full contract. ## Deviations from Plan ### Process deviation: executor killed mid-verification by an SSH disconnect **Found during:** post-implementation verification **Issue:** The orchestrating session and its agents died when the operator's SSH connection dropped. This plan's implementation and test file were complete and on disk but uncommitted, and no SUMMARY had been written. **Resolution:** A follow-on session re-ran the suite (6/6 green), confirmed the full frontend suite was green, wrote this SUMMARY, and committed. **Files modified:** none beyond the original work ## Known Stubs None. ## Threat Flags None — a presentational affordance with no new endpoint, no auth surface, and no state mutation. The one safety-relevant property (that it cannot set confirmation state) is asserted by test. ## Self-Check: PASSED - FOUND: `neode-ui/src/views/OnboardingSeedGenerate.vue` (modified) - FOUND: `neode-ui/src/views/__tests__/OnboardingScrollCue.test.ts` (created, 6 tests) - CONFIRMED: `npx vitest run` full frontend suite green