Revamp: design tokens, loading/error UX, and ~6× faster wallet connect#837
Open
jamespepper81 wants to merge 12 commits into
Open
Revamp: design tokens, loading/error UX, and ~6× faster wallet connect#837jamespepper81 wants to merge 12 commits into
jamespepper81 wants to merge 12 commits into
Conversation
…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
|
|
||
| if (!activeXpub) return <FullPageLoader />; | ||
| if (isLoading && !data) return <FullPageLoader />; | ||
| if (error && !data) return <ErrorDisplay message={error ?? 'Unable to load wallet data.'} />; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A full visual + performance revamp of BitSleuth across three workstreams. 12 commits, 64 files changed.
1. Visual foundation
next/font/googlewith the previously-deadfont-headlineutility defined.success/warning/infotokens and swept ~330 hardcoded Tailwind palette classes across 30 files onto the token system, so gain/loss, statuses, and charts express one meaning with one color in both themes.--accentwas identical to the primary orange, making every menu hover flash full brand color; it's now a subtle warm surface.loading.tsx/error.tsx, a branded full-page loader (replaces bare spinners on ~14 pages), no more theme-toggle flash.2. Performance
ignoreBuildErrors: true.3. Connect speed (the headline fix)
Every blockchain lookup was an individual Next.js Server Action, and Server Actions run serially per client — so the discovery code's "parallel" batches were actually one round trip at a time (~70–200 sequential trips per connect). Discovery now uses batched Server Actions that fan out to the provider on the server. Measured at 100 ms emulated client RTT with cold caches: click → full dashboard data 17.0 s → 2.8 s (discovery phase 11.9 s → 1.3 s); sub-second on localhost.
Two intentional decisions worth a reviewer's eye: per-page sub-brand titles (BitTracker/BitInsight/…) were unified to plain names (Dashboard/Analysis/…), and
tailwind.config.tswas deleted as a dead duplicate of the CSS@themeunder Tailwind v4.Type of Change
(Bug fixes bundled in: dead update-poller, toast destructive-close contrast, connect-dialog a11y title, nested
SelectContentin the currency switcher, and stale/flaky E2E assertions.)Test Plan
npm run typecheck,npm run lint,npm run test(65 unit tests), and the type-enforcednpm run buildall pass.login-flow.spec.tspasses 3/3 across three consecutive runs.Checklist
npm run typecheckpassesnpm run lintpassesnpm run testpasses🤖 Generated with Claude Code
Generated by Claude Code