Archipelago — open-source initial import

This commit is contained in:
Archipelago
2026-08-12 10:55:50 +00:00
commit 520b93d58e
1937 changed files with 441306 additions and 0 deletions
@@ -0,0 +1,170 @@
---
phase: 01-federation-mesh-hardening
plan: 03
subsystem: ui
tags: [vue, css, wallet, branding, reduced-motion]
requires:
- phase: 01-federation-mesh-hardening
provides: "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)"
provides:
- "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"
affects: [wallet, web5, screensaver]
tech-stack:
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."
key-files:
created:
- neode-ui/src/components/__tests__/ScreensaverRing.test.ts
- neode-ui/src/components/__tests__/PaidTick.test.ts
modified:
- neode-ui/src/components/ScreensaverRing.vue
- neode-ui/src/components/SendBitcoinModal.vue
- neode-ui/src/components/WalletScanModal.vue
key-decisions:
- "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."
requirements-completed: [FED-06]
coverage:
- id: D1
description: "The payment-success tick in SendBitcoinModal renders the EQ-segment ring, not a CSS ripple burst"
requirement: "FED-06"
verification:
- kind: unit
ref: "neode-ui/src/components/__tests__/PaidTick.test.ts#SendBitcoinModal: payment success shows exactly one badge ring, no ripple burst"
status: pass
human_judgment: false
- id: D2
description: "The scan-modal paid tick renders the same ring, so the paid tick is identical on every surface"
requirement: "FED-06"
verification:
- kind: unit
ref: "…#WalletScanModal: success pane shows the same badge ring and keeps its checkmark"
status: pass
human_judgment: false
- id: D3
description: "A badge variant exists at 160px/192px with --viz-radius 80px/96px, alongside untouched default and compact variants"
requirement: "FED-06"
verification:
- kind: unit
ref: "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"
status: pass
human_judgment: false
- id: D4
description: "Success amount numerals and SENT / Done copy are unchanged — only the ring geometry behind the checkmark changed"
requirement: "FED-06"
verification:
- kind: unit
ref: "…#SendBitcoinModal… asserts the 12,345 amount and SENT copy still render"
status: pass
human_judgment: false
- id: D5
description: "SystemDangerZone and Screensaver continue to render compact and default unchanged"
requirement: "FED-06"
verification:
- kind: other
ref: "git status shows Screensaver.vue and SystemDangerZone.vue untouched; viz-ring-compact occurrence count unchanged at 3"
status: pass
human_judgment: false
- id: D6
description: "Segment animation is disabled under prefers-reduced-motion for every size variant"
requirement: "FED-06"
verification:
- kind: other
ref: "@media (prefers-reduced-motion: reduce) { .viz-segment { animation: none; opacity: 0.55 } } — exactly one such block in ScreensaverRing.vue"
status: pass
human_judgment: false
- id: D7
description: "The badge ring fits inside the modal card without clipping at either breakpoint"
requirement: "FED-06"
verification:
- kind: manual_procedural
ref: "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."
status: deferred
human_judgment: true
duration: 55min
completed: 2026-08-01
status: 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.vue``ScreensaverRing` 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.vue``ScreensaverRing` 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
</content>