docs(phase-1): UI design contract for FED-05/FED-06
This commit is contained in:
parent
9432f42acc
commit
4e9741b013
163
.planning/phases/01-federation-mesh-hardening/01-UI-SPEC.md
Normal file
163
.planning/phases/01-federation-mesh-hardening/01-UI-SPEC.md
Normal file
@ -0,0 +1,163 @@
|
||||
---
|
||||
phase: 1
|
||||
slug: federation-mesh-hardening
|
||||
status: draft
|
||||
shadcn_initialized: false
|
||||
preset: none
|
||||
created: 2026-07-29
|
||||
---
|
||||
|
||||
# Phase 1 — UI Design Contract
|
||||
|
||||
> Visual and interaction contract for the two UI-facing requirements in this phase:
|
||||
> **FED-05** (inter-node Lightning channel-opening UX) and **FED-06** (on-brand paid-tick
|
||||
> animation). The rest of Phase 1 (FED-01–04) is backend/parity work with no new UI surface.
|
||||
> Generated by gsd-ui-researcher, verified by gsd-ui-checker.
|
||||
|
||||
---
|
||||
|
||||
## Design System
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Tool | none — no `components.json` found; project is **Vue 3**, and shadcn/ui does not support Vue (React-only), so the shadcn init gate does not apply here. Registry safety gate: not applicable. |
|
||||
| Preset | not applicable |
|
||||
| Component library | none — hand-authored Tailwind utilities + a custom "glass" CSS system (`glass-card`, `glass-button`, `glass-button-warning/danger/success`, `input-glass`, `alert-error/warning/info`, `BaseModal.vue`) defined in `neode-ui/src/style.css` and reused project-wide |
|
||||
| Icon library | none — inline hand-authored SVG, 24×24 viewBox, `stroke-width="2"` outline style (heroicons-esque but not the package). The bolt path `M13 10V3L4 14h7v7l9-11h-7z` is already the house Lightning icon (used in `Server.vue`, `HomeWalletCard.vue`, `Web5Wallet.vue`) — reuse it verbatim for any new Lightning iconography in FED-05, do not source a new icon. |
|
||||
| Font | Avenir Next (`font-sans`, body/UI text), Montserrat 700/800 (`font-archipelago`, headers only — not used in modals) |
|
||||
|
||||
**Modal contract (hard rule, repeated user complaint):** Every new modal in this phase MUST use `BaseModal.vue` (already wraps `Teleport to="body"` + full-screen `bg-black/60 backdrop-blur-md` backdrop + column layout with pinned header/footer and scrolling body) or, if a bespoke modal is unavoidable, MUST replicate that exact `<Teleport to="body">` + `fixed inset-0` + `@click.self="close"` pattern. Never nest a modal inside a `transform`-affected ancestor (glass-panel `translateZ` layers trap `position:fixed`).
|
||||
|
||||
---
|
||||
|
||||
## Spacing Scale
|
||||
|
||||
Declared values (must be multiples of 4) — matches `tailwind.config.js`'s existing 4px-grid `spacing` tokens (`1`=4px … `8`=32px) plus standard Tailwind rem multiples used throughout the codebase for larger gaps:
|
||||
|
||||
| Token | Value | Usage |
|
||||
|-------|-------|-------|
|
||||
| xs | 4px | Icon-to-label gaps, badge padding |
|
||||
| sm | 8px | Compact row spacing, `gap-2` |
|
||||
| md | 16px | Default element spacing, `p-4` card padding |
|
||||
| lg | 24px | Section padding, `mb-6` between panel sections |
|
||||
| xl | 32px | Layout gaps between major picker columns |
|
||||
| 2xl | 48px | `py-12` empty-state vertical padding |
|
||||
| 3xl | 64px | Not used by this phase's new elements |
|
||||
|
||||
Exceptions: 44px minimum touch target on all new interactive buttons (global rule already enforced in `style.css` for mobile — the "Copy URI" / "Open Channel" / "Request Channel" buttons inherit `min-height: 44px` from `.glass-button` automatically, no override needed).
|
||||
|
||||
---
|
||||
|
||||
## Typography
|
||||
|
||||
Scoped to this phase's new elements only (existing typography elsewhere is unchanged):
|
||||
|
||||
| Role | Size | Weight | Line Height |
|
||||
|------|------|--------|-------------|
|
||||
| Label | 12px (`text-xs`) | 500 (medium) | 1.4 |
|
||||
| Body | 14px (`text-sm`) | 400 (regular) | 1.5 |
|
||||
| Heading | 18–20px (`text-lg`/`text-xl`) | 600 (semibold) | 1.3 |
|
||||
| Display (inherited, FED-06 only) | 48px (`text-5xl`) | 800 (`font-black`) | 1.1 |
|
||||
|
||||
The Display row is **inherited unchanged** from the existing `SendBitcoinModal.vue`/`WalletScanModal.vue` success-amount numerals (e.g. `12,345 sats`) — FED-06 only replaces the ring graphic behind/around that text, never the text itself. Modal titles ("Open Lightning Channel", "Request Channel") use Heading; node names, URI strings, and helper/meta text use Body or Label per the existing `LightningChannelsPanel.vue`/`NodeList.vue` convention (node name = Body, badges/hints = Label).
|
||||
|
||||
---
|
||||
|
||||
## Color
|
||||
|
||||
| Role | Value | Usage |
|
||||
|------|-------|-------|
|
||||
| Dominant (60%) | `#000000` + `rgba(0,0,0,.35–.65)` | Page background, `.glass`/`.glass-card` surfaces |
|
||||
| Secondary (30%) | `rgba(0,0,0,.65)` blur(18px) card, `rgba(255,255,255,.05–.08)` nested rows | Modal cards, picker list rows (`bg-black/20` per-node rows, `bg-white/5` nested detail blocks) |
|
||||
| Accent (10%) | Archipelago orange `#fb923c` / `rgba(251,146,60,*)` | **Reserved for:** the "Open Channel" / "Request Channel" / "Copy Lightning URI" primary CTA buttons (`.glass-button-warning`), the Lightning bolt icon fill, focus-visible glow rings, the active picker-tab underline (mirrors existing `.mode-switcher-btn-active` treatment) |
|
||||
| Destructive | `#ef4444` family (`.glass-button-danger`) | Not used by FED-05 v1 (no destructive action ships this phase — channel *close* is existing, out-of-scope UI in `LightningChannelsPanel.vue`); declared for consistency if a future "revoke URI sharing" action is added |
|
||||
|
||||
**Inherited semantic colors (pre-existing house convention, unchanged by this phase, NOT part of the 10% accent budget):**
|
||||
- Success/paid emerald `#4ade80` text / `rgba(16,185,129,*)` fills — the paid-tick's center badge and "SENT"/amount numerals (FED-06 keeps this palette; only the surrounding ring geometry changes).
|
||||
- Info blue `#60a5fa` — FIPS/Tor transport badges already shown next to trusted-node rows (`NodeList.vue`'s `transportBadge`); reused as-is in the FED-05 trusted-node picker rows, not introduced by this phase.
|
||||
|
||||
Accent reserved for: **primary Lightning-channel action buttons, the Lightning bolt icon, focus rings, and the active picker-tab indicator only** — never for body text, card backgrounds, or informational badges.
|
||||
|
||||
---
|
||||
|
||||
## Copywriting Contract
|
||||
|
||||
| Element | Copy |
|
||||
|---------|------|
|
||||
| Primary CTA — own URI | **"Copy Lightning URI"** (copy-to-clipboard button; on success the label flips to **"Copied!"** for ~2s, mirroring `SendBitcoinModal.vue`'s existing `copyDetail`/`Copied!` pattern — do not invent a new copy-feedback idiom) |
|
||||
| Primary CTA — trusted federated node | **"Open Channel"** (one-click; matches the verb already used in `LightningChannelsPanel.vue`'s existing Open Channel button/modal) |
|
||||
| Primary CTA — meshed Lightning peer | **"Request Channel"** (opens the request flow reusing `PeerRequestModal.vue`'s pattern — optional message field, "Send Request" submit button, `sending` → **"Sending…"** busy label — do not build a new request-modal component from scratch) |
|
||||
| Manual fallback entry point | **"Paste URI Manually"** (reveals a `Peer URI` input, placeholder `pubkey@host:port`, helper text `Format: pubkey@host:port` — verbatim reuse of `LightningChannelsPanel.vue`'s existing field copy) |
|
||||
| Empty state heading | **"No Lightning peers yet"** |
|
||||
| Empty state body | **"Add a federated node or connect with a meshed peer running Lightning to open a channel directly — or paste a peer's URI manually below."** |
|
||||
| Error state | **"Couldn't reach that peer — check they're online and try again."** (tone/placement mirrors the existing `openError`/`alert-error` treatment in `LightningChannelsPanel.vue`; LND "still starting up" transient errors reuse that same component's amber `isStartupNotice` treatment rather than the red error style) |
|
||||
| Destructive confirmation | Not applicable — FED-05 v1 ships open/request flows only, no destructive action |
|
||||
| FED-06 copy | Not applicable — pure visual swap. Existing "SENT" / success-amount / "Done" button copy in `SendBitcoinModal.vue` and `WalletScanModal.vue` is unchanged; only the ring graphic behind the checkmark changes. |
|
||||
|
||||
---
|
||||
|
||||
## UI Considerations
|
||||
|
||||
Applicable state considerations resolved: 13 covered, 3 backstop, 0 unresolved.
|
||||
|
||||
| Category | Element(s) | Status | Resolution / Reason |
|
||||
|----------|------------|--------|---------------------|
|
||||
| long-text | own-node URI display | ✅ covered | The displayed `pubkey@host:port` string truncates (CSS `truncate` + `title` tooltip, the existing house pattern) to fit its container; the full untruncated value is what gets copied to clipboard regardless of visual truncation |
|
||||
| empty | trusted-nodes picker list | ✅ covered | Empty state copy row above renders once when both the trusted and meshed-peer lists are empty (shared empty state, not duplicated per column) |
|
||||
| empty | meshed-LN-peers picker list | 🧪 backstop | Same shared empty-state copy as above; no wired test yet asserting the "shared, not duplicated" rendering rule — flag for planner/executor to add a component test |
|
||||
| loading | trusted-nodes picker list | ✅ covered | Mirrors `NodeList.vue`'s existing "Loading nodes..." spinner row treatment |
|
||||
| loading | meshed-LN-peers picker list | ✅ covered | Same spinner treatment as trusted-nodes list |
|
||||
| error | trusted-nodes / meshed-peer picker lists | ✅ covered | Ties to the Copywriting Contract error row; styled with `.alert-error`/`openError` convention already in `LightningChannelsPanel.vue` |
|
||||
| populated | trusted-nodes picker list | ✅ covered | Row layout mirrors `NodeList.vue`'s trusted-node row: name, trust badge, transport badge (FIPS/Tor), one-click "Open Channel" button |
|
||||
| populated | meshed-LN-peers picker list | ✅ covered | Same row layout, "Request Channel" button in place of "Open Channel" (peers are not bilaterally trusted, so the action is a request, never a direct open) |
|
||||
| zero-one-many | trusted-nodes / meshed-peer lists | ✅ covered (dismissed) | No item-count copy is planned for either list (unlike e.g. the channel-status tabs' count badges) — singular/plural phrasing is not applicable |
|
||||
| overflow | picker list rows (long node names) | ✅ covered | `truncate` class + `:title` tooltip on the node-name span, identical to the existing `NodeList.vue` convention |
|
||||
| partial | manual-URI-paste form | ✅ covered | A pasted pubkey without a host falls back to `lnd.openchannel`'s existing address-less-pubkey handling (`address = parts[1] \|\| undefined`), already proven in `LightningChannelsPanel.vue` |
|
||||
| error | manual-URI-paste form | 🧪 backstop | Invalid-format message ("Peer URI must be `pubkey@host:port`") is specified but no explicit format-validation test is scoped yet — planner should add one, do not silently skip client-side validation before calling `lnd.openchannel` |
|
||||
| long-text | manual-URI-paste form | ✅ covered | Same truncation/tooltip treatment as the own-node URI display |
|
||||
| unclassified | request-to-open-channel flow | ✅ covered (dismissed) | Reuses `PeerRequestModal.vue` verbatim (message field, Send Request/Sending states) — its own state coverage predates this phase and is not re-specified here |
|
||||
| overflow | paid-tick ring (`SendBitcoinModal.vue` + `WalletScanModal.vue`) | ✅ covered | New `badge` `ScreensaverRing` size variant (see below) is explicitly sized to fit inside the modal's `max-h-[90vh] overflow-y-auto` card without clipping — do not drop in the existing `compact` (240–320px) variant unscaled |
|
||||
| static-content (motion) | paid-tick ring, all `ScreensaverRing` size variants | 🧪 backstop | `ScreensaverRing.vue`'s `segment-pulse` animation currently has **no** `prefers-reduced-motion` guard anywhere (a real gap — confirmed by reading the component; contrast with `SendBitcoinModal.vue`'s existing `.burst-ring`, which already has one). This phase must add `@media (prefers-reduced-motion: reduce) { .viz-segment { animation: none; opacity: 0.6; } }` inside `ScreensaverRing.vue` itself so the guard applies to every size variant (including the new `badge` one), matching the site-wide reduced-motion convention. No existing automated test covers this — flag for planner as a Wave 0 test gap. |
|
||||
|
||||
<!-- Status vocabulary (locked by probe-core projectTruths):
|
||||
✅ covered → a plain truth string lifted into must_haves.truths
|
||||
🧪 backstop → a flat scalar { statement, verification: backstop }; at verify time, no explicit
|
||||
evidence → insufficient_spec → human_needed (never a silent pass, #1154)
|
||||
⚠ unresolved → an explicit planner assumption (surfaced, never silently dropped)
|
||||
Rows are REPLACED (not appended) on a probe re-run — idempotent. -->
|
||||
|
||||
---
|
||||
|
||||
## FED-06 Sizing Decision (resolves RESEARCH.md Pitfall 4 / Assumption A4)
|
||||
|
||||
RESEARCH.md flagged the `ScreensaverRing` size mismatch (`compact` = 240–320px vs. the current 96–112px paid-tick badges) as needing a UI-spec decision before implementation. **Decision: add a new `badge` size variant to `ScreensaverRing.vue`**, not a CSS `transform: scale()` wrapper — cleaner, reusable across both call sites, and avoids reduced-motion/layout-box mismatches that a transform hack would introduce.
|
||||
|
||||
| Variant | Diameter (mobile) | Diameter (≥768px) | `--viz-radius` | Used by |
|
||||
|---------|-------------------|--------------------|-----------------|---------|
|
||||
| `badge` (NEW) | 160px | 192px | 80px / 96px | `SendBitcoinModal.vue` `.send-success-burst` (replaces the 112px burst), `WalletScanModal.vue` `.success-ring` (replaces the 96px/`w-24` ring) |
|
||||
| `compact` (existing, unchanged) | 240px | 320px | 120px / 160px | `SystemDangerZone.vue` and other existing overlay contexts — do not touch |
|
||||
| `default` (existing, unchanged) | 280–400px (responsive) | — | 140–200px | Full-screen `Screensaver.vue` |
|
||||
|
||||
Composition at both call sites: `<ScreensaverRing size="badge" />` renders the radiating EQ segments; the existing `.burst-core` (green circle + checkmark, `SendBitcoinModal.vue`) or `.success-ring` inner content (`WalletScanModal.vue`) is layered centered on top via `position: absolute; inset: 0` within a shared `position: relative` wrapper sized to the `badge` diameter — same layering pattern `Screensaver.vue` already uses for `ScreensaverLogo` inside `ScreensaverRing`. Do not resize or restyle the checkmark/core itself; only its container changes from a bespoke 96–112px circle to the `badge`-sized wrapper.
|
||||
|
||||
---
|
||||
|
||||
## Registry Safety
|
||||
|
||||
| Registry | Blocks Used | Safety Gate |
|
||||
|----------|-------------|--------------|
|
||||
| shadcn official | none | not applicable — shadcn/ui is React-only; this is a Vue 3 project with an established hand-rolled design system (see Design System table) |
|
||||
| third-party | none | not applicable |
|
||||
|
||||
---
|
||||
|
||||
## Checker Sign-Off
|
||||
|
||||
- [ ] Dimension 1 Copywriting: PASS
|
||||
- [ ] Dimension 2 Visuals: PASS
|
||||
- [ ] Dimension 3 Color: PASS
|
||||
- [ ] Dimension 4 Typography: PASS
|
||||
- [ ] Dimension 5 Spacing: PASS
|
||||
- [ ] Dimension 6 Registry Safety: PASS
|
||||
|
||||
**Approval:** pending
|
||||
Loading…
x
Reference in New Issue
Block a user