From b5e7d4998f87df761eda680204da4bbfe4d75d49 Mon Sep 17 00:00:00 2001 From: Max Barvian Date: Wed, 26 Aug 2026 13:36:59 -0700 Subject: [PATCH 01/13] popover for account switcher --- app/globals.css | 3 +- app/vibenet/demos/_shared/AccountSwitcher.tsx | 128 ++++++++---------- 2 files changed, 62 insertions(+), 69 deletions(-) diff --git a/app/globals.css b/app/globals.css index 46dc118..0d5ee45 100644 --- a/app/globals.css +++ b/app/globals.css @@ -9,7 +9,8 @@ * 100 mobile drawer (.drawer, below) * 110 mobile header (.mobile-header, below) * 120 modals (components/ui/Modal.tsx) - * 130 poppers over modals (components/ui/Select.tsx) + * 130 poppers over modals (components/ui/Select.tsx, + * vibenet/demos/_shared/AccountSwitcher.tsx) * * A popper below the modal layer still opens, but paints behind the panel — * which reads as "the dropdown does nothing". */ diff --git a/app/vibenet/demos/_shared/AccountSwitcher.tsx b/app/vibenet/demos/_shared/AccountSwitcher.tsx index ad24235..1d87e92 100644 --- a/app/vibenet/demos/_shared/AccountSwitcher.tsx +++ b/app/vibenet/demos/_shared/AccountSwitcher.tsx @@ -6,14 +6,14 @@ // as a full manager (toolbar) or a plain picker (e.g. a transaction "From"). import { useEffect, useRef, useState } from 'react'; +import { Popover } from '@base-ui/react/popover'; -import { Card } from '../../../components/ui/Card'; import { cn } from '../../../components/ui/cn'; import { Text } from '../../../components/ui/Text'; import type { StoredAccount } from '../account/library/model'; import { AccountAvatar, AccountIdentity, Badge, TrashIcon } from './primitives'; -function ChevronIcon({ open }: { open: boolean }) { +function ChevronIcon() { return (
- - - {open ? ( - // Match the trigger width, but never narrower than 280px; anchored right - // so the extra width overflows leftward (e.g. the narrow navbar trigger) - // instead of running off the page. - - {topLevel.map((parent) => ( -
- {row(parent, false)} - {accounts.filter((s) => s.parentId === parent.id).map((sub) => row(sub, true))} -
- ))} - {onCreate ? ( - - ) : null} -
- ) : null} -
+ + + + + + Accounts + {topLevel.map((parent) => ( +
+ {row(parent, false)} + {accounts.filter((s) => s.parentId === parent.id).map((sub) => row(sub, true))} +
+ ))} + {onCreate ? ( + + ) : null} +
+
+
+ ); } From 58f7517275df3343ee081e9fb7444e02b150c2cb Mon Sep 17 00:00:00 2001 From: Max Barvian Date: Wed, 26 Aug 2026 13:37:05 -0700 Subject: [PATCH 02/13] consistent copy icons --- app/components/ui/icons.tsx | 10 ++++++++++ app/snapshots/SnapshotsClient.tsx | 3 ++- app/vibenet/components/CopyableValue.tsx | 18 +++++++----------- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/app/components/ui/icons.tsx b/app/components/ui/icons.tsx index 2751fe2..d990b19 100644 --- a/app/components/ui/icons.tsx +++ b/app/components/ui/icons.tsx @@ -1,4 +1,5 @@ import type { SVGProps } from 'react'; +import { fitIcon } from 'morphicons'; const VIBENET_PATH = 'M30.2895 14.8575L20.0038 20.0002M20.0038 20.0002L10.2895 14.2861M20.0038 20.0002L20.0038 30.8571M30.8608 22.8275V17.1724C30.8608 15.3861 29.9078 13.7354 28.3608 12.8423L22.4737 9.44331C20.9267 8.55015 19.0207 8.55015 17.4737 9.44331L11.5865 12.8423C10.0395 13.7354 9.08649 15.3861 9.08649 17.1724V22.8275C9.08649 24.6138 10.0395 26.2644 11.5865 27.1576L17.4737 30.5566C19.0207 31.4497 20.9267 31.4497 22.4737 30.5566L28.3608 27.1576C29.9078 26.2644 30.8608 24.6138 30.8608 22.8275Z'; @@ -100,6 +101,15 @@ export const CLIPBOARD_MORPH_ICON = 'M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z'; export const CHECK_MORPH_ICON = 'M5 13l4 4L19 7'; +// Overlapping-squares copy glyph drawn on a 40x40 grid (InlineCommand on +// the snapshots page). fitIcon regrids it onto morphicons' 24x24 baseline +// so it can morph against CHECK_MORPH_ICON. Stroke 2.5 on 40x40 is 1.5 on +// 24x24 at the same display size (2.5 * 24 / 40). +export const COPY_SQUARES_PATH_40 = + 'M16.6667 23.3333V26.6667C16.6667 28.5076 18.1591 30 20 30H26.6667C28.5076 30 30 28.5076 30 26.6667V20C30 18.1591 28.5076 16.6667 26.6667 16.6667H23.3333M23.3333 16.6667V13.3333C23.3333 11.4924 21.8409 10 20 10H13.3333C11.4924 10 10 11.4924 10 13.3333V20C10 21.8409 11.4924 23.3333 13.3333 23.3333H20C21.8409 23.3333 23.3333 21.8409 23.3333 20V16.6667Z'; +export const COPY_SQUARES_MORPH_ICON = fitIcon(COPY_SQUARES_PATH_40, 40); +export const COPY_SQUARES_MORPH_STROKE_WIDTH = 2.5 * (24 / 40); + export function CheckIcon({ size = 16, className }: { size?: number; className?: string }) { return ( vo exit={{ opacity: 0, scale: 0.5 }} transition={{ duration: 0.15 }} > - + )} diff --git a/app/vibenet/components/CopyableValue.tsx b/app/vibenet/components/CopyableValue.tsx index 046e823..a1b7d21 100644 --- a/app/vibenet/components/CopyableValue.tsx +++ b/app/vibenet/components/CopyableValue.tsx @@ -2,18 +2,14 @@ import { useCallback, useState } from 'react'; import { MorphIcon } from 'morphicons/react'; -import { fitIcon } from 'morphicons'; -import { CHECK_MORPH_ICON } from '../../components/ui/icons'; +import { + CHECK_MORPH_ICON, + COPY_SQUARES_MORPH_ICON, + COPY_SQUARES_MORPH_STROKE_WIDTH, +} from '../../components/ui/icons'; import { cn } from '../../components/ui/cn'; -// Original artwork is drawn on a 40x40 grid; regrid it onto morphicons' 24x24 -// baseline once, at module scope, so it can morph against CHECK_MORPH_ICON. -const COPY_MORPH_ICON = fitIcon( - 'M16.6667 23.3333V26.6667C16.6667 28.5076 18.1591 30 20 30H26.6667C28.5076 30 30 28.5076 30 26.6667V20C30 18.1591 28.5076 16.6667 26.6667 16.6667H23.3333M23.3333 16.6667V13.3333C23.3333 11.4924 21.8409 10 20 10H13.3333C11.4924 10 10 11.4924 10 13.3333V20C10 21.8409 11.4924 23.3333 13.3333 23.3333H20C21.8409 23.3333 23.3333 21.8409 23.3333 20V16.6667Z', - 40, -); - type CopyableValueProps = { value: string; display?: string; @@ -52,9 +48,9 @@ export function CopyableValue({ value, display, className }: CopyableValueProps) > {shown} Date: Wed, 26 Aug 2026 13:45:40 -0700 Subject: [PATCH 03/13] changelog search wraps earlier --- app/upgrades/changelog/ChangelogClient.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/upgrades/changelog/ChangelogClient.tsx b/app/upgrades/changelog/ChangelogClient.tsx index 3a9c009..fb3d75c 100644 --- a/app/upgrades/changelog/ChangelogClient.tsx +++ b/app/upgrades/changelog/ChangelogClient.tsx @@ -190,7 +190,7 @@ export function ChangelogClient() { onChange={handleQueryChange} placeholder="Search by title, EIP, or summary keyword" aria-label="Search changes" - className="h-[34px] min-w-0 flex-1 rounded-full border border-bds-gray-10 bg-background px-4 text-[14px] text-foreground outline-none placeholder:text-bds-gray-50" + className="h-[34px] min-w-[min(100%,14rem)] flex-1 rounded-full border border-bds-gray-10 bg-background px-4 text-[14px] text-foreground outline-none placeholder:text-bds-gray-50" /> From dfa21e4efab744153aa5d5550b36de53cef88771 Mon Sep 17 00:00:00 2001 From: Max Barvian Date: Wed, 26 Aug 2026 13:53:53 -0700 Subject: [PATCH 04/13] fix entrance anim on features blocks --- app/vibenet/demos/account/AccountDemo.tsx | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/app/vibenet/demos/account/AccountDemo.tsx b/app/vibenet/demos/account/AccountDemo.tsx index 61e90ae..6c30c6d 100644 --- a/app/vibenet/demos/account/AccountDemo.tsx +++ b/app/vibenet/demos/account/AccountDemo.tsx @@ -165,18 +165,15 @@ export function AccountDemo() { const [appBusy, setAppBusy] = useState(null); const [transactModalOpen, setTransactModalOpen] = useState(false); - // Crossfade key for the dashboard (transact + apps). It normally tracks the - // active account so switching accounts crossfades the view. While the transact - // modal is open we hold it steady: the modal's "From" switcher changes the - // active account, and remounting the crossfaded subtree there would tear down - // and rebuild the open modal — a full-screen flash. Held steady, the dialog - // updates in place, then the dashboard crossfades once to the new account on - // close. + // Crossfade when switching accounts. Adjust the key during render (not an + // effect) so hydration does not remount the grid, and freeze it while the + // transact modal is open so its From switcher cannot tear the dialog down. + // https://react.dev/reference/react/useState#storing-information-from-previous-renders const activeAccountKey = activeAccountId ?? 'empty'; const [contentKey, setContentKey] = useState(activeAccountKey); - useEffect(() => { - if (!transactModalOpen) setContentKey(activeAccountKey); - }, [transactModalOpen, activeAccountKey]); + if (!transactModalOpen && contentKey !== activeAccountKey) { + setContentKey(activeAccountKey); + } const signableSigners = useMemo( () => [...postChangeOwnerSigners, ...sessionSigners], From a4b677a453e34b6acc570a3c34e799417ec0721b Mon Sep 17 00:00:00 2001 From: Max Barvian Date: Wed, 26 Aug 2026 13:59:45 -0700 Subject: [PATCH 05/13] features icons in dark mode --- app/vibenet/demos/account/AccountDemo.tsx | 23 ++++++++++--------- .../demos/account/components/AppsView.tsx | 4 ++-- .../account/components/FeatureGridCard.tsx | 2 +- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/app/vibenet/demos/account/AccountDemo.tsx b/app/vibenet/demos/account/AccountDemo.tsx index 6c30c6d..c62dfcf 100644 --- a/app/vibenet/demos/account/AccountDemo.tsx +++ b/app/vibenet/demos/account/AccountDemo.tsx @@ -26,7 +26,7 @@ import { toHex, } from '@aa'; import Link from 'next/link'; -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { AnimatePresence, motion } from 'motion/react'; import { Button } from '../../../components/ui/Button'; @@ -87,6 +87,7 @@ export function AccountDemo() { networkShort, setNetworkShort, deleteAccount, + hydrated, busy, error, @@ -870,7 +871,7 @@ export function AccountDemo() { - + } title="Sponsorship" @@ -1187,10 +1188,10 @@ export function AccountDemo() { - + - - - + + + } title="Batched Calls" @@ -1248,10 +1249,10 @@ export function AccountDemo() { - + - + ); } if (app.id === 'spending-account') { return ( - + ); diff --git a/app/vibenet/demos/account/components/FeatureGridCard.tsx b/app/vibenet/demos/account/components/FeatureGridCard.tsx index 2bf7aa6..2806c69 100644 --- a/app/vibenet/demos/account/components/FeatureGridCard.tsx +++ b/app/vibenet/demos/account/components/FeatureGridCard.tsx @@ -25,7 +25,7 @@ export function FeatureGridCard({
{overflowPx > 0 && ( Date: Wed, 26 Aug 2026 14:37:01 -0700 Subject: [PATCH 08/13] fix features grid initial animation --- app/vibenet/demos/account/AccountDemo.tsx | 34 +++++++++++------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/app/vibenet/demos/account/AccountDemo.tsx b/app/vibenet/demos/account/AccountDemo.tsx index cebc523..9966c99 100644 --- a/app/vibenet/demos/account/AccountDemo.tsx +++ b/app/vibenet/demos/account/AccountDemo.tsx @@ -166,18 +166,17 @@ export function AccountDemo() { const [appBusy, setAppBusy] = useState(null); const [transactModalOpen, setTransactModalOpen] = useState(false); - // Crossfade key for the dashboard (transact + apps). It normally tracks the - // active account so switching accounts crossfades the view. While the transact - // modal is open we hold it steady: the modal's "From" switcher changes the - // active account, and remounting the crossfaded subtree there would tear down - // and rebuild the open modal — a full-screen flash. Held steady, the dialog - // updates in place, then the dashboard crossfades once to the new account on - // close. + // Crossfade key for the dashboard (transact + apps). While the modal is + // closed it is the live account, so first paint after hydration uses the + // real id (`initial={false}` skips that enter). Snapshotting the key in + // state would freeze the pre-hydration `empty` value and fade the grid + // once the store loads. While the modal is open we hold the last closed + // key in a ref: the "From" switcher changes the active account, and + // remounting this subtree would tear down the open dialog. const activeAccountKey = activeAccountId ?? 'empty'; - const [contentKey, setContentKey] = useState(activeAccountKey); - useEffect(() => { - if (!transactModalOpen) setContentKey(activeAccountKey); - }, [transactModalOpen, activeAccountKey]); + const heldKeyRef = useRef(activeAccountKey); + if (!transactModalOpen) heldKeyRef.current = activeAccountKey; + const contentKey = transactModalOpen ? heldKeyRef.current : activeAccountKey; const signableSigners = useMemo( () => [...postChangeOwnerSigners, ...sessionSigners], @@ -536,8 +535,10 @@ export function AccountDemo() { }; // Reset the Transact modal's builder/review state to its defaults. Called on - // close so a preset loaded by one entry point (e.g. the "Batched Calls" demo) - // never bleeds into the next open (e.g. a plain "Create Transaction"). + // open, not close: resetting in `onClose` swaps Review back to the builder + // while the dialog is still animating out. Open always rebuilds from a clean + // slate (or a preset), so a prior "Batched Calls" demo cannot bleed into a + // later "Create Transaction". const resetTransactBuilder = () => { setCalls([newCallRow()]); setCallsAdvanced(false); @@ -556,12 +557,11 @@ export function AccountDemo() { // fully-specified transaction — loads it straight into the builder state and // jumps directly to the Review step, skipping the builder entirely. const openTransactModal = (preset?: { calls: CallRow[]; gasMode?: 'eth' | 'free' | 'usdv'; metadata?: string }) => { + resetTransactBuilder(); if (preset) { setCalls(preset.calls); if (preset.gasMode) setGasMode(preset.gasMode); setMetaField(preset.metadata ?? ''); - setResult(null); - setError(''); setTxStep('review'); } setTransactModalOpen(true); @@ -697,7 +697,7 @@ export function AccountDemo() { engine={engine} onTransactFromDetails={() => { setDetailsOpen(false); - setTransactModalOpen(true); + openTransactModal(); }} activity={} activityCount={activity.length} @@ -894,7 +894,6 @@ export function AccountDemo() { onClose={() => { if (signing) return; // don't let Escape/backdrop abandon an in-flight send setTransactModalOpen(false); - resetTransactBuilder(); }} title={ txStep === 'review' ? 'Review Transaction' : txStep === 'submitted' ? 'Submitted' : 'Create Transaction' @@ -952,7 +951,6 @@ export function AccountDemo() { size="sm" onClick={() => { setTransactModalOpen(false); - resetTransactBuilder(); }} > Done From ae8e43da8a238ab512ff44e9e2a848f1e8a570ec Mon Sep 17 00:00:00 2001 From: Max Barvian Date: Wed, 26 Aug 2026 14:41:41 -0700 Subject: [PATCH 09/13] media -> container query for changelog --- app/upgrades/changelog/ChangelogClient.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/upgrades/changelog/ChangelogClient.tsx b/app/upgrades/changelog/ChangelogClient.tsx index fb3d75c..db732ab 100644 --- a/app/upgrades/changelog/ChangelogClient.tsx +++ b/app/upgrades/changelog/ChangelogClient.tsx @@ -194,7 +194,7 @@ export function ChangelogClient() { />
-
+
@@ -275,7 +275,7 @@ export function ChangelogClient() {
-
+
{filtered.map((change) => ( Date: Wed, 26 Aug 2026 14:44:06 -0700 Subject: [PATCH 10/13] Update SnapshotsClient.tsx --- app/snapshots/SnapshotsClient.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/snapshots/SnapshotsClient.tsx b/app/snapshots/SnapshotsClient.tsx index d89066d..040bd72 100644 --- a/app/snapshots/SnapshotsClient.tsx +++ b/app/snapshots/SnapshotsClient.tsx @@ -107,7 +107,7 @@ function InlineCommand({ command, onCopy }: { command: string; onCopy?: () => vo )}
From 109f88d8be44408a327f1a108322056daac25347 Mon Sep 17 00:00:00 2001 From: Max Barvian Date: Wed, 26 Aug 2026 15:14:20 -0700 Subject: [PATCH 11/13] fix unwanted tab animation --- app/components/ui/Tabs.tsx | 86 ++++++++------------------------------ 1 file changed, 18 insertions(+), 68 deletions(-) diff --git a/app/components/ui/Tabs.tsx b/app/components/ui/Tabs.tsx index 38538b2..b292893 100644 --- a/app/components/ui/Tabs.tsx +++ b/app/components/ui/Tabs.tsx @@ -1,7 +1,6 @@ 'use client'; -import { useCallback, useEffect, useRef, useState } from 'react'; -import type { MouseEvent } from 'react'; +import { useId } from 'react'; import { motion, useReducedMotion } from 'motion/react'; import { cn } from './cn'; @@ -24,21 +23,6 @@ type TabsProps = { const PILL_TRANSITION = { type: 'spring', bounce: 0, duration: 0.3 } as const; -// Breathing room left between a scrolled-into-view tab and the scroller edge. -const SCROLL_MARGIN = 8; - -function findScrollParent(el: HTMLElement): HTMLElement | null { - let node = el.parentElement; - while (node) { - if (node.scrollWidth > node.clientWidth) { - const overflowX = getComputedStyle(node).overflowX; - if (overflowX === 'auto' || overflowX === 'scroll') return node; - } - node = node.parentElement; - } - return null; -} - export function Tabs({ items, value, @@ -47,76 +31,30 @@ export function Tabs({ className, size = 'md', }: TabsProps) { - const containerRef = useRef(null); - const buttonRefs = useRef>(new Map()); - const [pill, setPill] = useState<{ x: number; width: number } | null>(null); + const layoutId = useId(); const reducedMotion = useReducedMotion(); const pillTransition = reducedMotion ? { type: 'spring' as const, bounce: 0, duration: 0 } : PILL_TRANSITION; - const measure = useCallback(() => { - const container = containerRef.current; - const btn = buttonRefs.current.get(value); - if (!container || !btn) return; - const cr = container.getBoundingClientRect(); - const br = btn.getBoundingClientRect(); - setPill({ x: br.left - cr.left, width: br.width }); - - // On narrow screens the tab row is wider than its scroll container, so the - // selected tab can sit off-screen. Nudge it into view horizontally only — - // scrollIntoView would also move the page vertically. - const scroller = findScrollParent(container); - if (!scroller) return; - const sr = scroller.getBoundingClientRect(); - const overflowLeft = sr.left - br.left; - const overflowRight = br.right - sr.right; - if (overflowLeft > 0) scroller.scrollLeft -= overflowLeft + SCROLL_MARGIN; - else if (overflowRight > 0) scroller.scrollLeft += overflowRight + SCROLL_MARGIN; - }, [value]); - - useEffect(() => { - measure(); - }, [measure]); - - const handleClick = useCallback( - (event: MouseEvent) => { - const next = event.currentTarget.dataset.value; - if (next) onChange(next); - }, - [onChange], - ); - return (
- {pill && ( - - )} {items.map((item) => { const active = item.value === value; return ( ); })} From c194d20d6e1223922a8201c779ad6c211669ea1a Mon Sep 17 00:00:00 2001 From: Max Barvian Date: Wed, 26 Aug 2026 15:34:10 -0700 Subject: [PATCH 12/13] fix unwanted sidebar indicator animation Co-authored-by: Cursor --- app/components/AppShell.tsx | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/app/components/AppShell.tsx b/app/components/AppShell.tsx index 096665b..1113e76 100644 --- a/app/components/AppShell.tsx +++ b/app/components/AppShell.tsx @@ -36,7 +36,6 @@ type NavRowProps = { enabled: boolean; hasChildren?: boolean; onNavigate?: () => void; - layoutScope?: string; }; type NavGlyphProps = { @@ -82,9 +81,8 @@ const styles: Record = { navSlideClip: { overflow: 'hidden', }, - // `isolation` makes the nav a stacking context so the active-row pill (which - // renders at z-index -1 and travels across rows while animating) paints behind - // every row's label instead of on top of the rows it passes over. + // `isolation` keeps the selected pill (z-index -1) in this stacking context + // so it paints behind the row's label instead of behind the sidebar. nav: { display: 'flex', flexDirection: 'column', gap: 2, position: 'relative', isolation: 'isolate' }, navLink: { textDecoration: 'none', color: 'inherit' }, navRow: { @@ -294,7 +292,7 @@ function NavGlyph({ name }: NavGlyphProps) { } } -function NavRow({ icon, label, href, active, enabled, hasChildren, onNavigate, layoutScope = 'desktop' }: NavRowProps) { +function NavRow({ icon, label, href, active, enabled, hasChildren, onNavigate }: NavRowProps) { let color = DISABLED; if (enabled) { color = active ? 'var(--bds-gray-80)' : 'var(--bds-gray-50)'; @@ -311,12 +309,7 @@ function NavRow({ icon, label, href, active, enabled, hasChildren, onNavigate, l }} > {active && ( - )} {icon && ( @@ -415,10 +407,9 @@ type SidebarContentProps = { onToggleTheme: () => void; onNavigate?: () => void; hideBrand?: boolean; - layoutScope?: string; }; -function SidebarContent({ dark, onToggleTheme, onNavigate, hideBrand, layoutScope = 'desktop' }: SidebarContentProps) { +function SidebarContent({ dark, onToggleTheme, onNavigate, hideBrand }: SidebarContentProps) { const pathname = usePathname() || '/'; // The nav follows the tapped href immediately instead of waiting for the router: // usePathname() only updates once the route commits, which left the pill and the @@ -539,7 +530,6 @@ function SidebarContent({ dark, onToggleTheme, onNavigate, hideBrand, layoutScop active={active} enabled={true} onNavigate={() => selectPath(child.href)} - layoutScope={`${layoutScope}-sub`} /> ); })} @@ -568,7 +558,6 @@ function SidebarContent({ dark, onToggleTheme, onNavigate, hideBrand, layoutScop enabled={item.enabled} hasChildren={!!item.children} onNavigate={() => selectPath(item.href)} - layoutScope={layoutScope} /> ))} @@ -767,7 +756,6 @@ export function AppShell({ children }: PropsWithChildren) { onToggleTheme={toggleTheme} onNavigate={() => setMenuOpen(false)} hideBrand - layoutScope="mobile" /> From e7d2e827757c6e056eca7b672fd0dc9d20f59c90 Mon Sep 17 00:00:00 2001 From: Max Barvian Date: Wed, 26 Aug 2026 15:57:54 -0700 Subject: [PATCH 13/13] Use a lighter blue for the command shimmer in light mode. Co-authored-by: Cursor --- app/snapshots/SnapshotsClient.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/snapshots/SnapshotsClient.tsx b/app/snapshots/SnapshotsClient.tsx index 040bd72..99de84a 100644 --- a/app/snapshots/SnapshotsClient.tsx +++ b/app/snapshots/SnapshotsClient.tsx @@ -34,7 +34,7 @@ const NETWORK_LABELS: Record = { const REQUIRED_COMPONENTS = new Set(['state', 'headers']); const SHIMMER_GRADIENT = - 'linear-gradient(90deg, currentColor 0%, currentColor 40%, var(--bds-brand) 50%, currentColor 60%, currentColor 100%)'; + 'linear-gradient(90deg, currentColor 0%, currentColor 40%, var(--shimmer-highlight) 50%, currentColor 60%, currentColor 100%)'; function InlineCommand({ command, onCopy }: { command: string; onCopy?: () => void }) { const [copied, setCopied] = useState(false); @@ -95,7 +95,7 @@ function InlineCommand({ command, onCopy }: { command: string; onCopy?: () => vo >