Files
archy/.planning/phases/01-federation-mesh-hardening/01-03-SUMMARY.md
T

10 KiB

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, requirements-completed, coverage, duration, completed, status
phase plan subsystem tags requires provides affects tech-stack key-files key-decisions requirements-completed coverage duration completed status
01-federation-mesh-hardening 03 ui
vue
css
wallet
branding
reduced-motion
phase provides
01-federation-mesh-hardening ScreensaverRing.vue's existing default/compact size variants and segment-pulse animation, plus the two paid-tick surfaces (SendBitcoinModal's ripple burst, WalletScanModal's plain circle)
A third `badge` ring variant (160px / 192px, --viz-radius 80px / 96px) sized to sit inside a modal card
The site-wide prefers-reduced-motion guard ScreensaverRing was missing entirely, now covering every variant including the two pre-existing ones
Both paid-tick surfaces rendering the identical branded ring
wallet
web5
screensaver
added patterns
New size variants on a shared visual component are added as their own class + mapping, never as a transform scale of an existing one — a scaled ring would also scale its segment stroke widths and blur, which is why 01-UI-SPEC.md ruled out the transform hack.
created modified
neode-ui/src/components/__tests__/ScreensaverRing.test.ts
neode-ui/src/components/__tests__/PaidTick.test.ts
neode-ui/src/components/ScreensaverRing.vue
neode-ui/src/components/SendBitcoinModal.vue
neode-ui/src/components/WalletScanModal.vue
Composition is identical on both surfaces: a `position: relative` badge-sized container holding <ScreensaverRing size="badge" /> with the checkmark core absolutely centred over it — so the two paid ticks cannot drift apart visually.
SendBitcoinModal keeps its burst-pop/burst-draw check animation; only the three ripple `burst-ring` spans and the burst-ripple keyframes were removed, since the ring now carries the motion.
WalletScanModal's core was kept at 6rem (it had no ripple to replace, just a w-24 circle) against SendBitcoinModal's 7rem, preserving each surface's existing checkmark proportion rather than homogenising them.
The reduced-motion guard was added to ScreensaverRing itself rather than per call site, so the screensaver and SystemDangerZone variants gain it too — 01-UI-SPEC.md flagged its total absence as a real gap this phase should close.
FED-06
id description requirement verification human_judgment
D1 The payment-success tick in SendBitcoinModal renders the EQ-segment ring, not a CSS ripple burst FED-06
kind ref status
unit neode-ui/src/components/__tests__/PaidTick.test.ts#SendBitcoinModal: payment success shows exactly one badge ring, no ripple burst pass
false
id description requirement verification human_judgment
D2 The scan-modal paid tick renders the same ring, so the paid tick is identical on every surface FED-06
kind ref status
unit …#WalletScanModal: success pane shows the same badge ring and keeps its checkmark pass
false
id description requirement verification human_judgment
D3 A badge variant exists at 160px/192px with --viz-radius 80px/96px, alongside untouched default and compact variants FED-06
kind ref status
unit neode-ui/src/components/__tests__/ScreensaverRing.test.ts#maps each size variant to its own ring class; #keeps the existing variants off the badge class pass
false
id description requirement verification human_judgment
D4 Success amount numerals and SENT / Done copy are unchanged — only the ring geometry behind the checkmark changed FED-06
kind ref status
unit …#SendBitcoinModal… asserts the 12,345 amount and SENT copy still render pass
false
id description requirement verification human_judgment
D5 SystemDangerZone and Screensaver continue to render compact and default unchanged FED-06
kind ref status
other git status shows Screensaver.vue and SystemDangerZone.vue untouched; viz-ring-compact occurrence count unchanged at 3 pass
false
id description requirement verification human_judgment
D6 Segment animation is disabled under prefers-reduced-motion for every size variant FED-06
kind ref status
other @media (prefers-reduced-motion: reduce) { .viz-segment { animation: none; opacity: 0.55 } } — exactly one such block in ScreensaverRing.vue pass
false
id description requirement verification human_judgment
D7 The badge ring fits inside the modal card without clipping at either breakpoint FED-06
kind ref status
manual_procedural NOT yet observed live — see Deviations. Geometric check only: 160px badge inside a max-w-2xl card is ~344px of usable width at a 390px viewport and 672px at desktop, so the ring cannot exceed the card box. deferred
true
55min 2026-08-01 complete

Phase 1 Plan 3: On-Brand Paid Tick (FED-06) Summary

Both paid-tick surfaces now render the screensaver's EQ-segment ring at a new modal-sized badge variant, and the shared ring component finally honours prefers-reduced-motion — a guard it had been missing for every variant, not just the new one.

Performance

  • Duration: ~55 min
  • Completed: 2026-08-01
  • Tasks: 2/2
  • Files modified: 5 (3 components, 2 new test files)

Accomplishments

  • ScreensaverRing gained a badge size (160px mobile / 192px from 768px up, with matching --viz-radius 80px / 96px) mapped through sizeClass, following the exact shape of the existing two variants. default and compact are untouched.
  • The reduced-motion guard was added to the component itself, so the screensaver and SystemDangerZone call sites gain it as a side benefit — 01-UI-SPEC.md had flagged its complete absence.
  • SendBitcoinModal's success pane swapped its three CSS ripple spans for the ring, keeping the emerald pop-in check and its draw animation; the dead burst-ring* rules and burst-ripple keyframes were removed and the reduced-motion rule updated to drop the clause referencing them.
  • WalletScanModal's plain w-24 circle became the same composition, so both paid ticks are now literally the same markup shape.
  • Two new suites (5 tests) pin the variant mapping, the segment count, and both call sites — including that the ripple elements are gone and the amount/SENT copy is unchanged.

Task Commits

  1. Task 1: badge variant + SendBitcoinModal and Task 2: WalletScanModal — committed together with this SUMMARY (both surfaces share the composition; splitting them would have committed a half-converted pair of paid ticks).

Files Created/Modified

  • neode-ui/src/components/ScreensaverRing.vue'badge' added to the size union, sizeClass widened to an if-chain, .viz-ring-badge rule + 768px breakpoint, and the prefers-reduced-motion guard on .viz-segment.
  • neode-ui/src/components/SendBitcoinModal.vueScreensaverRing import; success pane restructured to .send-success-badge > ring + centred .send-success-burst/.burst-core; ripple markup, .burst-ring* rules and burst-ripple keyframes deleted.
  • neode-ui/src/components/WalletScanModal.vueScreensaverRing import; .scan-success-badge + .scan-success-core replacing the fixed circle and its .success-ring rule.
  • neode-ui/src/components/__tests__/ScreensaverRing.test.ts — 3 tests.
  • neode-ui/src/components/__tests__/PaidTick.test.ts — 2 tests, one per surface.

Deviations from Plan

Test assertions had to target the document, not the wrapper

Found during: Task 1, first green run Issue: wrapper.find('.burst-core') returned nothing even though the markup rendered. BaseModal teleports its content to document.body, so the rendered nodes live outside the mounted wrapper's own root element. Component-tree queries (findAllComponents) still work, which is why the ring assertions passed while the DOM ones failed. Resolution: DOM assertions switched to document.querySelector(...), with an afterEach clearing document.body so one modal's teleported nodes cannot answer the next test's queries. Files modified: neode-ui/src/components/__tests__/PaidTick.test.ts

Auto-fixed: vue-tsc strict-null on indexed access

Found during: npm run build Issue: rings[0].props('size') failed vue-tsc under noUncheckedIndexedAccess (TS2532) — the same class of failure commit 4a8925f0 fixed in usePaidItemViewer.test.ts. Resolution: optional chaining (rings[0]?.props('size')), matching that commit's fix exactly. Files modified: neode-ui/src/components/__tests__/PaidTick.test.ts

Outstanding: the live dev-preview observation Task 2 asks for was NOT made

Found during: Task 2 verification Issue: Task 2 requires confirming on a dev preview that neither ring is clipped by the modal card's scrolling container at a narrow and a desktop viewport. Reaching either success pane in a real browser requires an actually-settled payment; the built preview on :4321 has no backend to settle one, and the running demo/dev servers in this tree belong to other sessions and are not to be disturbed. Resolution: NOT resolved. Recorded honestly as deferred rather than claimed. The automated evidence (5 tests, npm run build green, viz-ring-badge present in the built bundle) covers structure but not appearance. The plan already consolidates the blocking human sign-off for this into plan 01-07, which is where the visual check belongs; the geometric argument (a 160px badge inside a max-w-2xl card, ~344px usable at a 390px viewport) says clipping is implausible, but it is not an observation. Files modified: none

Known Stubs

None.

Threat Flags

None — presentational only. The plan's safety prohibition (the ring must never render success for an unsettled payment) is structurally satisfied: no success-gating condition was touched, only the markup inside an already-gated pane.

Self-Check: PASSED

  • FOUND: viz-ring-badge in ScreensaverRing.vue (3 occurrences: mapping + rule + breakpoint)
  • FOUND: prefers-reduced-motion in ScreensaverRing.vue (exactly 1)
  • CONFIRMED: burst-ring count in SendBitcoinModal.vue == 0; success-ring in WalletScanModal.vue == 0
  • CONFIRMED: npx vitest run — 102 files, 822 tests, all pass
  • CONFIRMED: npm run build exits 0 and grep -rq 'viz-ring-badge' web/dist/neode-ui/assets/ succeeds