Skip to content

Revamp design tokens, improve loading UX, and optimize wallet connect#845

Merged
jamespepper81 merged 33 commits into
mainfrom
dev
Jul 7, 2026
Merged

Revamp design tokens, improve loading UX, and optimize wallet connect#845
jamespepper81 merged 33 commits into
mainfrom
dev

Conversation

@jamespepper81

Copy link
Copy Markdown
Contributor

No description provided.

claude and others added 30 commits July 4, 2026 21:44
…ns, unified charts

- Load Inter via next/font/google (was declared in CSS but never loaded,
  silently falling back to system-ui); define font-headline so the
  existing font-headline classes stop being a no-op
- Add semantic success/warning/info tokens (light + dark) to globals.css
  and register them in the Tailwind v4 @theme block
- Give --accent its own subtle warm surface instead of duplicating the
  primary Bitcoin orange, so menu/dropdown hovers stop flashing brand color
- Unify the dark-mode chart palette with light mode (same Bitcoin-anchored
  hues, lightness-tuned) — charts no longer change identity across themes
- Replace the fragile hardcoded-hex [role=alert] CSS overrides with a
  token-based Alert warning variant
- Delete tailwind.config.ts: dead v3-style duplicate of the CSS @theme
  under Tailwind v4 (no @config directive references it)
- disableTransitionOnChange on ThemeProvider (kills theme-toggle flash);
  styled branded Suspense fallback instead of unstyled "Loading..."

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016A3N8t6WuMeHgiKQLWFN7T
- Load @/lib/blockchain (bitcoinjs-lib + bip32 + ecpair + secp256k1) via
  dynamic import inside getWalletData instead of statically from the
  app-wide wallet context. The crypto stack leaves every page's initial
  bundle (largest eager chunk: 820KB -> 448KB) and loads only when wallet
  data is actually fetched. Derivation stays client-side — xpubs never
  leave the browser.
- Memoize the WalletContext provider value (was a fresh ~40-field object
  every render), so the 5-minute price poll and streaming discovery
  progress no longer re-render every useWallet() consumer app-wide
- Load jspdf/jspdf-autotable and jszip on demand inside the report export
  click handlers instead of bundling them with the report page
- Add recharts to experimental.optimizePackageImports
- Remove the dead update poller in the app layout: it fetched full page
  HTML with no-store every 15 minutes and regexed for __NEXT_DATA__,
  which the App Router never emits — it could never detect an update

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016A3N8t6WuMeHgiKQLWFN7T
…okens

Sweep ~330 hardcoded Tailwind palette classes (emerald/rose/blue/amber/
purple/orange, raw hex, rgba) across 30 files to the semantic token
system so both themes stay coherent from a single source of truth:

- Financial deltas -> chart-positive/chart-negative; statuses ->
  success/warning/destructive; informational blues -> info;
  decorative purples -> chart-purple; brand oranges -> primary
- IconContainer variants now token-based (previously six hardcoded
  palettes with manual dark variants)
- AIChart: hex slice colors -> chart tokens (slices 6-9 previously
  ignored the theme entirely); white pie labels and rgba gradient
  washes -> tokens
- Orbital loader: rgba(247,147,26) glows -> hsl(var(--brand)); shimmer
  via-white/10 -> via-foreground/10 (was invisible in light mode)
- Toast destructive close button and ErrorBoundary fallback moved to
  tokens (fixes red-on-red close affordance)
- Button: outline/ghost hovers use the new subtle accent surface
  instead of a heavy primary orange wash; solid variants get shadow-sm
- Card: default transition-shadow so existing hover elevations animate
- Dashboard empty-state tip: emoji -> Lightbulb icon, readable
  foreground text on info tint

Canvas fillStyle colors in the Discover force-graph are intentionally
left as literals (CSS vars don't resolve in canvas).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016A3N8t6WuMeHgiKQLWFN7T
- Add (app)/loading.tsx: skeleton page shell (header, metric grid, list)
  shown during navigation across all 13 app routes
- Add (app)/error.tsx and root error.tsx: styled recovery cards with a
  working reset button instead of falling through to the raw boundary
- Restyle FullPageLoader in place — branded Bitcoin pulse/spinner with
  an accessible status label — upgrading the ~14 pages that use it
  without touching consumers; ErrorDisplay becomes a proper card with
  icon
- LoadingProgress: pick the long-wait message once per mount (it
  re-rolled on every render, flickering between strings); replace the
  raw checkmark glyph with a lucide Check icon
- Remove dead SkeletonCard/SkeletonTransactionRow exports from
  orbital-loader (no consumers)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016A3N8t6WuMeHgiKQLWFN7T
…unification

- Add src/lib/format.ts (formatCurrency/formatBTC/formatNumber/
  formatPercent) with cached Intl.NumberFormat instances; replace the
  ten duplicated per-page formatCurrency definitions (two files defined
  it twice) and the report pages' compact/full variants
- Add useWalletDataGuard hook replacing the identical four-line
  loading/error guard block on security, analysis, transactions and
  coin-control
- Add useCopyToClipboard hook (with failure toast) replacing three
  hand-rolled handleCopy implementations
- Add shared EmptyState component for the duplicated address/block/
  transaction "Not Found" views
- A11y: the sidebar account-switcher trigger and wallet rows
  (role="button" divs) are now keyboard-operable with visible focus
  rings (tabIndex + Enter/Space handlers)
- Branding: replace per-page sub-brand titles (BitTracker, BitInsight,
  BitWatch, …) with the section names already used in the sidebar, and
  update the e2e assertion that keyed on "BitTracker"

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016A3N8t6WuMeHgiKQLWFN7T
Both wallet-connect progress modals rendered DialogContent without a
DialogTitle, tripping Radix's accessibility error on every connect.
Add visually-hidden titles.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016A3N8t6WuMeHgiKQLWFN7T
Remove typescript.ignoreBuildErrors — the codebase typechecks clean, so
the flag only allowed regressions to ship silently. next build now runs
full type validation (verified passing).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016A3N8t6WuMeHgiKQLWFN7T
…y pool

- New src/lib/wallet-cache.ts: wallet snapshots are persisted via
  requestIdleCallback (setTimeout fallback), deduplicated per xpub by a
  cheap fingerprint (tx count + balance + latest tx id), and coalesced so
  only the newest pending snapshot is written. Previously getWalletData
  JSON.stringified the entire WalletData synchronously on every wallet
  switch and background refresh. Stored shape is unchanged, so the cache
  read path is untouched; tracking is reset wherever caches are removed.
- Nostr operations now share one lazily-created SimplePool instead of
  opening and closing four relay websockets per call; the pool closes on
  Nostr disconnect and provider unmount.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016A3N8t6WuMeHgiKQLWFN7T
Window both layouts with @tanstack/react-virtual so DOM size no longer
scales with wallet history:

- Desktop keeps real <table> semantics via spacer rows (top/bottom
  padding <tr>s around the visible window), sticky header, 65vh scroll
  container
- Mobile card list uses the standard absolute-positioned item pattern
  with measureElement for variable card heights
- Remove the unbounded Load More accumulation — the full list renders
  through the virtualizer (a high-activity wallet previously mounted
  every loaded row); the header now shows the total count

Verified in-browser with a connected wallet: windowed rendering on both
viewports, smooth scroll to end, CSV export unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016A3N8t6WuMeHgiKQLWFN7T
…ayout/*

Pure extraction, no behavior change: AnalyticsWarning, AddAccountDialog,
AccountSwitcher, CurrencySwitcher, and the Nostr footer block (summary,
connect + edit-profile dialogs, save-xpubs prompt, and their forms) move
to src/components/layout/. The layout file drops from 845 to 265 lines
and keeps only the shell: nav, header, route guard, support toast.

One drive-by fix: CurrencySwitcher rendered a nested <SelectContent>
inside another <SelectContent>.

Verified in-browser: account switcher popover, add-account dialog, Nostr
connect dialog, and currency switch (balance re-renders in EUR) all work.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016A3N8t6WuMeHgiKQLWFN7T
… slices

WalletProvider now exposes four independently memoized contexts layered
inside the single provider:

- useWalletData: balances, transactions, discovery, currency derivations
- useWalletActions: referentially stable callbacks — action-only
  consumers never re-render on provider state churn
- useWalletAi: chat messages, suggestions, recommendations — streaming
  chat no longer re-renders data-only pages
- useNostr: Nostr account state and actions

useWallet() still returns the merged shape, so all existing consumers
work unchanged; the app shell (layout, account/currency switchers,
add-account dialog, Nostr block) and the chat page are migrated to the
scoped hooks where the split pays.

Verified in-browser: full connect flow with streaming discovery,
currency switch propagating across pages, chat greeting + message send,
no console errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016A3N8t6WuMeHgiKQLWFN7T
…nect

Root cause of slow connects: every blockchain lookup was an individual
Next.js Server Action, and Server Actions execute serially per client.
The gap-limit scan's Promise.allSettled over 20 per-address calls was
fake parallelism — each call paid a full client->server round trip, one
at a time, ~70-200 round trips per fresh connect.

- Add getAddressStatsBatch (<=40 addrs) and getAddressBundleBatch
  (<=20 addrs, stats+txs+utxos with the balance>0 UTXO retry) to the
  'use server' proxy; the server fans out to the provider in parallel
  with a politeness cap of 10, per-item address sanitization unchanged
- Discovery: type detection is one batch call; each gap window resolves
  in a single round trip; the per-address data phase fetches chunks of
  15 per call instead of 3 calls per address through a client worker pool
- getWalletDataProgressive: fetch ticker and chain tip in parallel; drop
  the standalone provider readiness check (first batch surfaces errors)
- Privacy unchanged: only derived addresses cross to the server, never
  the xpub

Measured with 100ms emulated client RTT (production-like), fresh cache:
click -> full dashboard data 17.0s -> 2.8s; discovery 11.9s -> 1.3s.
On localhost the full e2e connect now lands in under a second.

Tests: update architecture-snapshot assertions, rewrite the address-data
concurrency suite for chunked batching (incl. server-side UTXO-retry and
bounds coverage; 60 -> 65 tests), fix the e2e modal assertion that
referenced long-gone "Connecting Your Wallet" copy, and make the e2e
disconnect step resilient to a slow dev redirect. Full e2e suite passes
3/3 consecutive runs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016A3N8t6WuMeHgiKQLWFN7T
Inside `if (error && !data)`, error is guaranteed truthy, so the
`?? 'Unable to load wallet data.'` fallback was unreachable. Use
`message={error}` directly. Both guard branches and their distinct
user-facing messages are unchanged (the separate no-error "No wallet
data found. Please connect a wallet." branch stays). Addresses the
github-code-quality review on PR #837.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016A3N8t6WuMeHgiKQLWFN7T
Idle-deferred snapshot writes could be dropped if the tab was closed or
backgrounded before the requestIdleCallback fired, weakening the instant
cached-reconnect path (the pre-batching code wrote synchronously). Flush
any pending writes synchronously on pagehide (close/bfcache) and when the
document becomes hidden. Listeners are registered once, lazily.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016A3N8t6WuMeHgiKQLWFN7T
…vamp-jk96vm

Revamp: design tokens, loading/error UX, and ~6× faster wallet connect
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Fix for Unused variable, import, function or class
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Fix for Unused variable, import, function or class
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Fix for Unused variable, import, function or class
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
…tion or class'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
…tion or class'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Fix for Unused variable, import, function or class
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
…tion or class'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
…tion or class'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Fix for Unused variable, import, function or class
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
jamespepper81 and others added 3 commits July 7, 2026 09:54
Fix for Unused variable, import, function or class
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Fix for Unused variable, import, function or class
@jamespepper81 jamespepper81 merged commit e0c6460 into main Jul 7, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants