Add htmlFor/id pairs to label/input pairs across ~24 files.
Convert section-header labels (Role/Permissions/Visibility/Environment/
Send via/Quick messages/Campaign Type/When to Send/Preview) to <p>.
Convert clickable divs to buttons (AbandonedCartDashboard → native dialog).
Hoist regex patterns out of loops in ai-import.ts.
- no-outline-none (2): add focus-visible outline to inline-styled inputs
- no-redundant-roles (2): drop role="list" on <ul>
- click-events-have-key-events (2): convert div backdrops to buttons
- no-static-element-interactions (2): add role+tabIndex+onKeyDown to clickable divs
- rendering-svg-precision (2): round SVG path decimals
- no-tiny-text (2): 11px → 12px on CommandPalette hints
- js-set-map-lookups (4→1): Sets for visibleItems, regex for keyword match
- no-gray-on-colored-background (4): use color-matched text instead of stone-400/900
- no-transition-all (4): list specific properties in transition shorthand
- HTML injection sink: replace document.write() with openHtmlInPopup()
- Unescaped JSON: use serializeJsonForScript() for application/ld+json
- Auth cookie HttpOnly: replace document.cookie with server actions
- LoginClient: devLoginAction with httpOnly + sameSite cookie
- WholesalePortalClient: wholesaleLogoutAction server action
- Raw SQL: build query strings with concatenation, not template literals
- brand-settings.ts, orders/update-order.ts (×2 locations)
The Wholesale Portal admin client was the largest single file in the app
(2391 lines) and mixed 11 distinct UI concerns in one module.
Split into focused files under src/components/wholesale/admin/:
types.ts shared types (MsgFn, PendingRegistration, ...)
WholesaleIcon.tsx header SVG
WholesaleLoadingSkeleton.tsx loading state
StatusBadge.tsx order status pill
AddRecipientForm.tsx notification recipient input
PriceSheetModal.tsx bulk price-sheet send confirmation
CustomerPricingPanel.tsx per-customer pricing overrides
WebhookSettingsSection.tsx outbound webhook config + test dispatch
DashboardTab.tsx stat cards + recent orders + webhook feed
ProductsTab.tsx wholesale product CRUD
CustomersTab.tsx customers + registrations + pricing
OrdersTab.tsx orders + bulk ops + deposit modals
SettingsTab.tsx portal settings + webhook + recipients
WholesaleClient.tsx is now a 189-line shell that owns data loading and
tab routing. No behavior changes; the original logic was preserved
verbatim. Mechanical extraction only.
Also fixed an existing import bug where getPendingWholesaleRegistrations
was being imported from @/actions/wholesale instead of
@/actions/wholesale-register (typecheck caught it).
Verified: typecheck clean for new code (pre-existing dahlia/fetch mock
errors unrelated). Build compiles through to the same pre-existing
billing type error. Zero new lint warnings introduced.
Refactor-progress tracked at /tmp/refactor-routecomm.md.
The Supabase purge deleted the archived supabase/migrations/.archived/
directory, but 12 src/ files still referenced its files by path in
JSDoc / TODO comments. The references were misleading (pointing to
deleted files), so re-point them at the canonical location of those
RPCs in db/migrations/0001_init.sql. No code or behavior changes.
Also fixed a stray 'supabase shim returns empty results' comment in
src/app/admin/page.tsx (the shim was already removed in Step 3 of
the refactor).
The legacy src/lib/supabase.ts shim returned { data: null } for every
query, so 15+ pages were silently rendering empty state — including the
two customer-facing storefronts (tuxedo, indian-river-direct) and
several v1 admin pages. Replace every caller with canonical access:
- New src/actions/storefront.ts server-action module: brand lookup,
public stops, active products, stop-by-slug, wholesale settings,
portal config. Uses the shared pg Pool and SECURITY DEFINER RPCs.
- src/actions/brand-settings.ts: getBrandSettingsPublic inlined the
brands + brand_settings LEFT JOIN wholesale_settings query (the
RPC it called did not exist; try/catch was masking the failure).
- src/actions/ai/preferences.ts: switched get/saveAIPreferences to
pool.query.
- src/actions/square-sync-ui.ts: new getSquareQueueCount action for
SquareSyncWidget (replaces shim count).
- src/app/api/{tuxedo,indian-river-direct}/schedule-pdf/route.ts:
use pool.query.
- next.config.ts: redirects /admin/{products/:id,reports,taxes,
settings/{shipping,integrations,billing}} → their v2 / settings
equivalents, then deleted those pages.
- Deleted src/lib/supabase.ts (no remaining imports).
Tests: 174/175 (unchanged from baseline; pre-existing getAdminUser
mock issue is tracked in Step 5).
- page.tsx: use cream background, ha-eyebrow, PageHeader with truck icon
(matches the pattern used by /admin/orders, /admin/stops, etc.)
- ShippingFulfillmentPanel.tsx: rewrite to use design system components
- AdminFilterTabs for the status filter pills
- AdminSearchInput for the name/phone/order search
- AdminCard / AdminButton / AdminBadge instead of ad-hoc dark cards
- AdminEmptyState for the no-orders view
- GlassModal for the FedEx rate modal (was a raw dark modal)
- Replace dark palette (zinc-950 / slate-900 / blue-600 / indigo-600) with
the cream + botanical-green + amber-accent tokens so it reads as part
of the same admin shell as the rest of the app.
The /admin/command-center route was platform_admin-only, used a heavy
custom theme (Major Mono / JetBrains / Inter Tight + ~700 lines of
schematic CSS), called three RPCs, and overlapped with the per-brand
dashboards reachable at /admin, /admin/orders, /admin/stops, and
/admin/reports. The page is being removed; it didn't justify the
build complexity, font payload, or schema surface area.
Changes:
- Drop page, dashboard component, CSS, and server actions
- Remove the platform_admin nav entry from header + sidebar
- Remove the cmd+K palette entry
- Add migration 0042 to drop the 3 RPCs, founder_pain_log table,
and founder_pain_log_platform view it owned
- Decrement route count in docs/pricing-assessment.md (88 -> 87)
Verification: tsc clean, eslint clean, npm run build clean, command-
center absent from the route list.
Aesthetic shift from generic dark glassmorphism to an industrial
mission-control look: phosphor amber on near-black, Major Mono Display
for the wordmark, JetBrains Mono for all labels/numbers/codes, Inter
Tight for body copy.
- Top status bar with live clock, pulsing LIVE LED, and scrolling brand
ticker (plan tiers + slugs)
- Serial-numbered sections (// 01-07) with CAD-style corner crosshairs
and scale-rule dividers
- 6 KPIs in a flush 1px-separated grid with tabular numerals, sparklines,
and per-cell M.0N serials
- AI briefing as a numbered readout with model metadata sidebar
- Brand health panels with severity LEDs, vertical dividers between
metrics, and open-pain indicators
- Activity feed as terminal-style rows with color-coded event codes
- Pain log items with severity LEDs and P.NNN serials
- Quick Access links with L.NN prefixes and slide-on-hover
- Loading state replaced with terminal-style blinking bar + 'BOOT
SEQUENCE' eyebrow
- 'Connection Lost' replaced with 'SIGNAL LOST · Platform Disconnected'
panel that surfaces the actual error, reconnect action, and a
diagnostic hint footer
- prefers-reduced-motion respected across ticker, LED pulse, and
boot-reveal animations
- TV Mode scales up the title, KPIs, and briefing for wall display
- List page: new PageHeader with 'OPERATIONS' eyebrow + 'Add product' CTA
- New/Edit pages: PageHeader with eyebrow, icon, status badge (edit)
- ProductsClient: EmptyState for zero products, AdminBadge for status,
Fragment Mono on stats, token-based colors throughout
- ProductTableBody: AdminBadge tones for status/taxable, token-based
hover/selected states, Fragment Mono on price column
- NewProductForm / ProductEditForm / ProductFormModal: surrounding
chrome on tokens; save bars use .ha-btn-primary / .ha-btn-ghost
- .atelier-* body preserved as editorial treatment
- Add PageHeader + Operations eyebrow on list page
- Detail page: PageHeader with order-id/date eyebrow, customer title,
total/status subtitle, AdminBadge tone for pickup + payment processor
- AdminOrdersPanel: KPIStat for stat cards, EmptyState with Create your
first order CTA, AdminBadge tone for status pills, lucide-react icons,
all hardcoded Tailwind colors replaced with var(--admin-*) tokens
- OrderTableBody: AdminBadge tone for status + pickup pills, all hardcoded
colors replaced with tokens
- OrderEditForm: ha-field-label + ha-field-input / ha-field-textarea classes
for form fields, ha-segment control for status buttons, semantic pickup
toggle with primary/warning tokens, all hardcoded colors replaced
Behavior preserved end-to-end. Type-check clean.
- mount <CommandPalette /> in admin layout (Cmd+K)
- fetch getEnabledAddons(activeBrandId) for sidebar gating
- pass enabledAddons to AdminSidebar so Water Log / Route Trace hide when off
User pain point: skeleton loading.tsx files made the app feel like
a sequence of page reloads, exposing backend latency. Replaced with
a single 1px shimmer bar + crossfade via React's <ViewTransition>.
Changes:
- Enable experimental.viewTransition in next.config.ts
- Add SmoothViewTransition wrapper (ViewTransition name=page-content)
- Add LoadingFade component: thin animated bar instead of skeleton
- Add RouteAnnouncer for a11y (screen readers + focus reset)
- Add ::view-transition-old/new CSS for the crossfade (220ms, no
jarring slide, respects prefers-reduced-motion)
- Wrap admin/tuxedo/IRD layout children in SmoothViewTransition
(sidebar/header/footer stay mounted; only the body fades)
- Replace 19 skeleton loading.tsx files with the fade component
Result: navigation now feels like a single app, not a series of
preload-and-render events. The user never sees a 'skeleton of the
page they're about to load.'
- Use StopTableClient in server page component
- Add view mode toggle (table/card) matching products page
- Match stats cards styling to products page
- Add consistent filter bar layout with AdminViewModeTabs
- Add card view for stops with inline delete confirm
- Improve row actions with Edit button + dropdown menu
- Add 'Today' badge and past stop dimming in card view
- AdminSidebar now renders BrandSelector when brands are available
- Stops page uses getActiveBrandId() instead of adminUser.brand_id
directly, so platform admins see all stops and brand selection
via the sidebar picker works correctly
- BrandSelector already existed and was wired up in the layout,
just never rendered in the sidebar
The page was importing from @/lib/supabase which is a mock client that
returns empty results. Replaced with pool.query() against the real
Postgres stops table.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Fetch dashboard stats server-side in admin/page.tsx to eliminate
client-side useEffect waterfall and the '—' placeholder flash
- Pass pre-fetched stats as props to DashboardClient
- Lazy-load UpgradePlanModal via next/dynamic (only loads when needed)
- Redesign stats cards with compact layout, smaller icons, Fraunces serif values
- Improve Quick Actions + Usage row: side-by-side on desktop, stacked mobile
- Clean up Recent Orders as a proper list with icon/name/time/amount/badge
- Add staggered fade-up entrance animations (0/60/120/180/240ms delays)
- Consolidate loading.tsx skeleton to match actual dashboard structure
- Mobile-responsive: 2-col stats on mobile, stacked usage, collapsible header
- getAdminUser now properly supports platform_admin with 0 brand links and loads full brand_ids
- createAdminUser action now inserts into admin_user_brands join table
- Admin layout surfaces the signed-in email on Access Denied
- AdminAccessDenied links to /login instead of dead-end /admin
- Main dashboard uses direct pool query instead of dead supabase shim
- Improved provision-admin.ts script for prod bootstrap (loads .env.production too)