--- phase: 01-federation-mesh-hardening plan: 13 type: execute wave: 7 depends_on: [] files_modified: - neode-ui/src/views/OnboardingSeedGenerate.vue - neode-ui/src/views/__tests__/OnboardingScrollCue.test.ts autonomous: true requirements: [UIFIX-03] gap_closure: true must_haves: truths: - "On a viewport too short to show the whole seed step, an on-brand cue at the bottom of the scrolling area tells the user there is more below — the confirmation tickbox is no longer silently out of sight (UIFIX-03)" - "Activating the cue brings the confirmation tickbox into view, so discovering it takes one action rather than a guess" - "The cue disappears once the tickbox is visible, and never reappears while it stays visible (UIFIX-03 adjacency edge)" - "On a viewport tall enough to show everything the cue never renders at all — no element, no reserved space, no layout shift, so tall screens look exactly as they did (UIFIX-03 empty edge)" - "The cue is absent while the seed is still generating and while an error is showing, because there is no tickbox to point at yet" - "The cue's motion is disabled under prefers-reduced-motion, matching the site-wide convention" prohibitions: - statement: "Nothing about the existing onboarding step may change other than the addition of this cue — the header, the seed word grid, the words/QR tabs, the warning box, the tickbox itself, the fixed footer and its Continue button, and every existing animation stay exactly as they are, and the shared onboarding container styles in style.css are not touched" category: safety - statement: "The cue MUST NOT let a user proceed without ticking the box — it is a wayfinding affordance only; it never sets the confirmation state, never enables the Continue button, and never auto-ticks on scroll" category: safety artifacts: - path: neode-ui/src/views/__tests__/OnboardingScrollCue.test.ts provides: "Overflow-driven show/hide behaviour of the cue, including the no-overflow no-render case" min_lines: 40 key_links: - from: neode-ui/src/views/OnboardingSeedGenerate.vue to: neode-ui/src/views/OnboardingSeedGenerate.vue via: "the cue's visibility is derived from the scroll container's own overflow measurement and the tickbox's position within it, so it is impossible for the cue to show when there is nothing below" pattern: "scrollHeight" --- Make the seed-confirmation tickbox obviously findable on short screens, in a way that looks like it was always part of the design. Purpose: UIFIX-03 is a BLOCKER — on a short viewport the tickbox sits below the fold inside the step's scrolling area while the Continue button stays pinned and disabled in the fixed footer, so onboarding reads as broken rather than incomplete. The user asked for this to be solved "in a beautiful way": the fix has to feel intentional and native to the house glass/dark style, not a bolted-on arrow, and it must be invisible on screens tall enough not to need it. Output: a bottom scroll cue on the seed step that appears only when it is needed, scrolls the tickbox into view when activated, and vanishes once the tickbox is on screen. @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md @.planning/PROJECT.md @.planning/STATE.md @.planning/phases/01-federation-mesh-hardening/01-UI-SPEC.md ## Artifacts this phase produces Created or changed by **this plan**: | Symbol | Kind | File | |---|---|---| | bottom scroll-cue overlay | new template block (conditional) | `neode-ui/src/views/OnboardingSeedGenerate.vue` | | `showScrollCue` + `updateScrollCue()` + `revealConfirm()` | new script state and handlers | same | | `.onb-cue-*` scoped styles incl. reduced-motion guard | new scoped CSS | same | | `neode-ui/src/views/__tests__/OnboardingScrollCue.test.ts` | new vitest suite | new file | Task 1: End-to-end — a short viewport shows a cue that reveals the tickbox neode-ui/src/views/OnboardingSeedGenerate.vue, neode-ui/src/views/__tests__/OnboardingScrollCue.test.ts - `neode-ui/src/views/OnboardingSeedGenerate.vue` — the whole file (262 lines). The structure that matters: a `h-[100dvh]` outer centring wrapper; a `path-glass-container onb-scroll-container flex flex-col` card; a `flex-shrink-0` header; the scrolling middle region (`flex-1 overflow-y-auto overflow-x-hidden px-6 sm:px-8 min-h-0`) that contains the loading state, the error state, the words/QR tabs, the word grid, the orange warning box and — last — the confirmation ` - With the scroll region reporting more content than fits and the tickbox below the visible area, the cue element is in the DOM. - With the scroll region reporting no overflow, the cue element is absent from the DOM entirely — not merely hidden, so it can occupy no space and cause no shift. - Scrolling to the bottom (tickbox now inside the visible area) removes the cue. - Activating the cue calls the scroll-into-view path for the tickbox and does not change `confirmed`. - While `loading` is true, or while `errorMessage` is set and no words have arrived, the cue is absent regardless of overflow. - Ticking the box removes the cue. Write the test file first and confirm it fails. In jsdom there is no layout engine, so drive the measurements by defining `scrollHeight`, `clientHeight` and `scrollTop` on the scroll element with `Object.defineProperty` and dispatching a `scroll` event — assert on what the component renders in response, not on computed geometry. In `OnboardingSeedGenerate.vue`: Add a template ref to the existing scrolling middle region and one to the confirmation label. Add a `showScrollCue` ref and an `updateScrollCue()` function that sets it true only when all of these hold: words are present, not loading, the scroll element reports more scrollable content below the current position, the confirmation label's bottom lies below the scroll element's visible bottom, and `confirmed` is still false. Call it from a `scroll` listener on the scroll element, from a `resize` listener on the window, from a `ResizeObserver` on the inner content wrapper (the word grid changes height when the user switches between the words and QR tabs), from a watcher on `words`, and from a watcher on `confirmed`. Remove every listener and disconnect the observer in `onUnmounted` alongside the existing `stopTimers()` call. Render the cue as a `v-if="showScrollCue"` overlay positioned against the scrolling region's bottom edge, inside a `Transition` so it fades rather than pops. Compose it from two layers, both `pointer-events-none` except the button itself: a soft gradient fade from transparent to the card's own dark backdrop across roughly 64px, so the content appears to slide under the edge rather than being cut off; and, centred on it, a small glass pill — the house `bg-black/60` + `backdrop-blur` treatment, `rounded-full`, `text-white/75` at `text-xs`, with the orange accent (`#fb923c` / `text-orange-400`) used only for a downward chevron drawn as inline 24×24 `stroke-width="2"` SVG per the icon convention in `01-UI-SPEC.md`. Copy for the pill: **"One more step below"**. Give the chevron a gentle 2s ease-in-out vertical bob of no more than 3px, defined in the file's existing scoped style block next to `onb-lock-spin`, and guard it with the `prefers-reduced-motion` media query copied from `SendBitcoinModal.vue`. Make the pill a real `