Files
archy/.planning/phases/01-federation-mesh-hardening/01-13-PLAN.md

18 KiB
Raw Permalink Blame History

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, gap_closure, must_haves
phase plan type wave depends_on files_modified autonomous requirements gap_closure must_haves
01-federation-mesh-hardening 13 execute 7
neode-ui/src/views/OnboardingSeedGenerate.vue
neode-ui/src/views/__tests__/OnboardingScrollCue.test.ts
true
UIFIX-03
true
truths prohibitions artifacts key_links
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
statement category
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 safety
statement category
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 safety
path provides min_lines
neode-ui/src/views/__tests__/OnboardingScrollCue.test.ts Overflow-driven show/hide behaviour of the cue, including the no-overflow no-render case 40
from to via pattern
neode-ui/src/views/OnboardingSeedGenerate.vue neode-ui/src/views/OnboardingSeedGenerate.vue 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 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.

<execution_context> @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md </execution_context>

@.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 checkbox bound to `confirmed`; and the `flex-shrink-0` fixed footer holding the Continue button gated on `confirmed`. Note the existing `watch(confirmed, …)` that focuses the Continue button, and the `onb-lock-spin` scoped keyframes block at the bottom (the house pattern for a small scoped animation in this file). - `neode-ui/src/style.css` — find the `.onb-scroll-container` rules (around line 1146 and a breakpoint block around line 1162) to see what the shared onboarding container already does. Read only; this plan must not modify the shared stylesheet, because these classes are used by every other onboarding step. - `neode-ui/src/components/RefreshIndicator.vue` — the house convention for a small, purely presentational overlay component with a scoped keyframes animation, for style reference. - `neode-ui/src/components/SendBitcoinModal.vue` — grep it for `prefers-reduced-motion` and copy that media-query syntax verbatim for the cue's guard, so all reduced-motion guards in this repo read identically. - 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 `<button type="button">` whose click smooth-scrolls the confirmation label
into view (`scrollIntoView({ behavior: 'smooth', block: 'center' })`) and nothing else — it must
never touch `confirmed`, never focus or enable the Continue button, and never call `proceed()`.
Give it an `aria-label` naming what it reveals so it is reachable and understandable without
sight, and make sure it is keyboard-focusable in the natural order.

Do not alter the header, the words/QR tab strip, the word grid, the QR block, the warning box, the
tickbox markup, the footer, the Continue button, or any existing class on the card or the scroll
region. Do not edit `style.css`. Add nothing that renders when `showScrollCue` is false.
cd neode-ui && test -f src/views/__tests__/OnboardingScrollCue.test.ts && npx vitest run src/views/__tests__/OnboardingScrollCue.test.ts - The test file exists and `cd neode-ui && npx vitest run src/views/__tests__/OnboardingScrollCue.test.ts` exits 0 (the `test -f` guard is required — `vitest.config.ts` sets `passWithNoTests: true`). - The suite contains a case asserting the cue element is absent when the scroll element reports no overflow, and a case asserting it is present when it reports overflow with the tickbox below the fold. - The suite contains a case asserting activating the cue leaves `confirmed` false. - `grep -c 'prefers-reduced-motion' neode-ui/src/views/OnboardingSeedGenerate.vue` equals 1. - `grep -c 'scrollIntoView' neode-ui/src/views/OnboardingSeedGenerate.vue` equals 1. - `git diff --stat -- neode-ui/src/style.css` reports no change. - `git diff -- neode-ui/src/views/OnboardingSeedGenerate.vue | grep -c '^-.*type="checkbox"'` equals 0 — the tickbox markup is untouched. - `git diff -- neode-ui/src/views/OnboardingSeedGenerate.vue | grep -c '^-.*path-action-button'` equals 0 — the footer button markup is untouched. - `cd neode-ui && npx vitest run` exits 0 — every existing suite stays green. - `cd neode-ui && npm run build` exits 0 and `grep -rq 'One more step below' ../web/dist/neode-ui/assets/` succeeds (per CLAUDE.md the frontend build can silently no-op). The cue appears only when the tickbox is out of reach, reveals it on activation, and leaves everything else about the step untouched. Task 2: Confirm it on a real short viewport and settle whether any other step needs it neode-ui/src/views/__tests__/OnboardingScrollCue.test.ts The local dev preview can be started (`cd neode-ui && npm run dev:mock` serves the UI on :8100 against the mock backend) and the onboarding route is reachable there — jsdom proves the logic but only a browser proves it looks right - `neode-ui/ONBOARDING_FLOW.md` — the step order and which routes make up the flow, so you know which steps to check in the next paragraph. - `neode-ui/DEV-SCRIPTS.md` lines 1-40 — starting and stopping the preview. Start the dev preview and open the seed-generate step. Check it at three heights and record each observation in the SUMMARY with the exact viewport used:
1. A short viewport (for example 1280×620, a small laptop or mobile landscape). Expected: the cue
  is visible, reads as part of the card rather than an overlay bolted on top of it, and clicking
  it brings the tickbox into view; the cue then disappears.
2. A tall viewport (for example 1440×1000). Expected: no cue at all, and the step is
  pixel-identical to before this change — compare against the current build if you are unsure.
3. A narrow phone viewport (for example 390×740). Expected: the cue reads correctly at that width
  and does not overlap the word grid or the warning box.

If the cue does not look like it belongs at any of the three, adjust the gradient depth, the pill
size, or the copy until it does, then re-run the test suite. This is the "beautiful way" the user
asked for — treat a cue that looks bolted on as a failure of this task, not a matter of taste.

Then settle the scope question the requirement leaves open. Run
`grep -l 'type="checkbox"' neode-ui/src/views/Onboarding*.vue` and, for every step that has a
confirmation tickbox inside a scrolling region, check it at the short viewport. If another step has
the same defect, apply the same cue there in this plan, add the file to the plan's
`files_modified` in the SUMMARY, and extend the test. If no other step does, record the grep
output and the verdict. Do not assume the seed step is the only one.
cd neode-ui && npx vitest run src/views/__tests__/OnboardingScrollCue.test.ts && npm run build - `cd neode-ui && npx vitest run src/views/__tests__/OnboardingScrollCue.test.ts` exits 0. - `cd neode-ui && npm run build` exits 0. - The SUMMARY records all three viewport observations with exact dimensions, and states explicitly that the tall-viewport rendering was unchanged. - The SUMMARY includes the `grep -l 'type="checkbox"' neode-ui/src/views/Onboarding*.vue` output and a per-file verdict. - `cd neode-ui && npx vitest run` exits 0. The cue is confirmed to look right at three real viewports, and every onboarding step with a confirmation tickbox has a recorded verdict.

Planner Assumptions (flagged, unresolved)

  • The seed-generate step is assumed to be the one the user hit. The todo says "verify it's this step". The planner confirmed this step has a confirmation tickbox at the bottom of a scrolling region with a pinned, disabled Continue button below it — the exact reported symptom — but did not enumerate every onboarding view. Task 2 closes this with a grep and a per-file verdict rather than leaving it as an assumption.
  • The chosen affordance is the scroll cue, not the sticky-footer alternative. The todo listed three candidate approaches. The cue was chosen because the other two change the tall-screen appearance (a sticky footer alters the card at every height; an auto-scroll moves content the user did not ask to move), and the standing rule forbids changing existing visuals. If the cue proves unsatisfying at Task 2, raise it rather than silently switching approach.

<threat_model>

Trust Boundaries

Boundary Description
displayed recovery seed → screen This step renders 24 words that grant full control of the node, identities and wallet
user consent → onboarding progression The tickbox is the recorded acknowledgement that the seed was written down

STRIDE Threat Register

Threat ID Category Component Severity Disposition Mitigation Plan
T-01-58 Repudiation an affordance that advances onboarding without a genuine acknowledgement high mitigate The prohibition and an explicit test case forbid the cue from touching confirmed; the cue only scrolls
T-01-59 Information Disclosure a new overlay covering seed words so a user transcribes them wrongly and loses recovery high mitigate The cue renders only at the bottom edge of the scroll region and only while content remains below; Task 2 requires checking at a narrow width that it does not overlap the word grid
T-01-60 Denial of Service scroll and resize handlers firing continuously on a low-power onboarding device low mitigate The handler is a few property reads and one boolean assignment with no allocation or RPC; listeners and the observer are removed in onUnmounted
T-01-61 Tampering the shared onboarding stylesheet being edited and silently restyling every other step medium mitigate All new CSS is scoped to this component, and an acceptance criterion fails the task if style.css shows any diff
T-01-SC Tampering npm/pip/cargo installs high mitigate This plan installs nothing — one component and one vitest file. If an implementation choice would add a dependency, stop: RESEARCH.md's Package Legitimacy Audit must cover it first, with a blocking human checkpoint for any [ASSUMED]/[SUS] entry
</threat_model>
- `cd neode-ui && npx vitest run` — green. - `cd neode-ui && npm run build` — green, and the built bundle carries the cue copy. - Short, tall and narrow viewport observations recorded, with the tall case confirmed unchanged.

<success_criteria>

  • A short viewport shows an on-brand cue that leads to the tickbox in one action.
  • A tall viewport renders no cue and is unchanged.
  • The cue never affects consent state or the Continue button.
  • Motion is reduced-motion guarded and the shared stylesheet is untouched.
  • Every onboarding step with a confirmation tickbox has a recorded verdict. </success_criteria>
Create `.planning/phases/01-federation-mesh-hardening/01-13-SUMMARY.md` when done, recording the three viewport observations, any design adjustments made to reach "belongs here", and the per-step grep verdict. Stage by explicit path, commit, and `git push gitea-ai main`.