MeshiTrack/docs/frontend-redesign-plan.md

20 KiB

Frontend Redesign Plan — Aligning with docs/design Mockups

This plan describes how to evolve packages/web from the current minimal Tailwind UI to match the editorial, warm-neutral design defined in docs/design/MeshiTrack.html and the screenshots under docs/design/screenshots/.

The mockups are a React (UMD + Babel) prototype using a CSS-token system. We will port the visual language and information architecture into the existing Next.js App Router + Tailwind v4 project, without dragging the prototype's runtime (React UMD, global window.* modules, inline data) into production.


1. Goals & Non-Goals

Goals

  • Match the visual language of the mockups: typography (Fraunces / Inter Tight / JetBrains Mono), warm-neutral palette, sage brand, generous whitespace, soft shadows, rounded cards.
  • Reproduce the app shell: persistent left sidebar with grouped navigation + sticky topbar with title/subtitle, search, theme toggle, notifications.
  • Reproduce the page archetypes shown in the mockups: editorial "hero" header, multi-card grid with consistent mt-card style, status pills, supply bars, mini SVG charts.
  • Support light/dark themes and an accent switcher (sage / cobalt / terracotta / graphite) via CSS custom properties.
  • Keep accessibility, responsive layout, and SSR-friendliness intact.

Non-Goals

  • Do not import the prototype's data.jsx mock data into production.
  • Do not adopt the prototype's window.* global module pattern or Babel-in-browser runtime.
  • Do not redesign data flows or API contracts; only the presentation layer changes.
  • Do not block on building every page in the mockup; food-domain pages (Phases 5-9) will reuse the system once the medicine pages are migrated.

2. Gap Analysis

Area Current state (packages/web) Target (mockups)
Theme tokens Tailwind v4 @theme with a green ramp only Full token set: warm neutrals, brand sage, status, viz palette, radii, shadows, fonts; light + dark
Typography System sans only Fraunces (display), Inter Tight (UI), JetBrains Mono (numerics/keys), tabular-nums utility
Sidebar Flat list, 3 links, no groups, no badges Grouped (Medicines section), icons, active state, badges, footer with avatar/role
TopBar Household chip + avatar Breadcrumbs, title + subtitle, search with ⌘K, notifications, theme toggle
Cards Ad-hoc Tailwind cards Reusable Card, CardHeader, Pill, Button (primary/ghost), supply bar, ring chart, sparkbar
Dashboard 2 link cards Hero greeting + ring, Today's schedule, Running low, Spending bars, Days-of-supply, Pending orders, Recent activity
Cabinet / Schedule / Regimens / Organizer / Library / Refills / Purchases / Prices / Stores / Activity Mostly placeholder grids/tables Editorial layouts with status chips, swatches, supply bars (see screenshots)
Theming None `data-theme="light dark"on<html>`, accent CSS vars
Icons None defined centrally Inline SVG icon set (Icon.jsx in mockup)

3. Target Architecture

3.1 Design tokens (Tailwind v4 @theme + CSS vars)

  • Replace the current @theme block in packages/web/src/styles/globals.css with the full token set from docs/design/styles/tokens.css:
    • Color tokens exposed both as CSS vars (--bg, --ink, --brand, ...) and as Tailwind theme colors (--color-bg, --color-ink, ...) so utilities like bg-bg, text-ink, border-border work.
    • Radii (--r-xs..--r-xl) and shadows (--shadow-sm/md/lg).
    • Font family vars + load Fraunces / Inter Tight / JetBrains Mono via next/font/google in packages/web/src/app/layout.tsx and bind them to the --font-* vars.
  • Add [data-theme='dark'] overrides in globals.css (copy from tokens.css).
  • Add small global utility classes used by the mockup: .mono, .serif, .num (tabular-nums) — keep names so mockup snippets can be lifted with minimal edits.

3.2 Theme + accent provider

  • New ThemeProvider (client component) that:
    • Persists theme (light|dark) and accent (sage|cobalt|terracotta|graphite) in localStorage.
    • Sets data-theme on document.documentElement and writes the accent's --brand, --brand-deep, --brand-soft, --brand-soft-ink, --viz-1 CSS vars (logic ported from docs/design/MeshiTrack.html useEffect).
    • Avoids FOUC by emitting a tiny inline script in app/layout.tsx that reads localStorage and applies data-theme before hydration.
  • Expose a useTheme() hook for the topbar toggle.

3.3 Component library (packages/web/src/components/ui/)

Port the mockup's reusable primitives as typed React components:

  • Icon — single component over an inline SVG map (dashboard, cabinet, clock, list, calendar, pill, refresh, truck, tag, store, trend, settings, search, bell, sun, moon, chev, arrow, check, injection, capsule).
  • Card, CardHeader (title + sub + right-side action slot).
  • Button with variant: 'primary' | 'ghost' | 'danger' and optional size.
  • Pill (status chip) with tone: 'ok' | 'warn' | 'danger' | 'info' | 'neutral'.
  • SupplyBar, Ring (SVG progress ring), SparkBars (monthly bars), Sparkline.
  • Kbd, IconButton, Avatar, SearchInput, Breadcrumbs.

These live in packages/web/src/components/ui/ and are pure presentational components (no data fetching, no 'use client' unless they need state — most do not).

3.4 App shell

Rewrite the layout primitives:

  • packages/web/src/components/layout/Sidebar.tsx:
    • Brand block (logo + wordmark + household sub-line — household.name instead of mock "Red Panda Den").
    • Grouped nav from a NAV array mirroring the mockup's structure but driven by next/navigation's usePathname for active state.
    • Footer with Avatar + displayName + role from useApi().
    • Badges (e.g. low-supply count, pending refills) wired to real SWR fetchers (Phase 2 — initially render the slot but optional).
  • packages/web/src/components/layout/TopBar.tsx:
    • Accept title, subtitle, crumbs, actions props. Provided either via a <PageHeader> component each route renders, or via a Zustand/Context "page header" store updated from each page (recommended: a server-component PageHeader slot rendered above the page body — simpler, no client state).
    • Search input (visual only at first; ⌘K palette is a follow-up).
    • Theme toggle wired to useTheme().
    • Notifications bell (visual only initially).
  • packages/web/src/app/(dashboard)/layout.tsx:
    • Switch root container to the mockup's grid: grid-template-columns: 248px 1fr; min-height: 100vh; background: var(--bg).
    • Inner <main> becomes mt-page with the mockup's padding (32px desktop, responsive down).

3.5 Page layout pattern

Each route renders:

<PageHeader title="..." subtitle="..." crumbs={[...]} actions={...} />
<PageContent>...cards / grid...</PageContent>

PageHeader is a server component that renders the topbar's middle area (title block + breadcrumbs). The sticky outer TopBar reads children via React's slots pattern: simplest is to make TopBar itself accept title/subtitle/crumbs and have pages set them via a thin PageHeaderContext (client provider) or via Next's template.tsx + parallel routes. Choose the Context approach for minimal churn.


4. Page-by-Page Mapping

For each route, port the layout from the mockup's JSX while replacing mock data with the existing service hooks.

Route Current file Mockup source Notes
/dashboard dashboard/page.tsx Dashboard.jsx Hero + Ring, Today's schedule, Running low, Spending, Supply, Pending orders, Activity. Many cards depend on data we don't yet expose; render skeletons / "Coming soon" states for missing endpoints.
/medicines/cabinet medicines/cabinet/ Cabinet.jsx Grid of medicine cards with swatch, supply bar, status pill.
/medicines/regimens medicines/regimens/ RegimensPage in OtherPages.jsx Per-regimen rows with schedule chips.
/medicines/organizer medicines/organizer/ Schedule.jsx OrganizerPage 7-day x slots grid.
/medicines/library medicines/library/ LibraryPage Filterable medicine table.
/medicines/activity medicines/activity/ ActivityPage Activity feed + spend summary.
/refills (new) RefillsPage Add route under (dashboard)/refills/.
/purchases purchases/ PurchasesPage List with status pills.
/medicine-prices medicine-prices/ PricesPage Per-store price comparison.
/stores stores/ StoresPage Store cards.
/settings settings/ SettingsPage Household + account sections.

Also add a Schedule & Log page (/schedule or /medicines/schedule) per mockup — currently missing.

Update medicines/page.tsx (and dashboard/page.tsx if kept) to use Card/CardHeader instead of ad-hoc Tailwind classes.


5. Phased Rollout

Treat this as 5 PR-sized increments, each independently shippable.

Phase A — Foundations (no visible page changes yet)

  1. Port tokens.css into globals.css (CSS vars + Tailwind v4 @theme mappings + dark overrides).
  2. Wire fonts via next/font/google in app/layout.tsx; bind to --font-* vars.
  3. Add ThemeProvider + FOUC-safe inline script.
  4. Add Icon component and the icon set.
  5. Add UI primitives: Card, CardHeader, Button, Pill, IconButton, Avatar, Kbd, SearchInput, Breadcrumbs, Ring, SupplyBar, SparkBars.
  6. Vitest unit tests + RTL smoke tests for each primitive.

Phase B — App shell

  1. Rewrite Sidebar with grouped nav, icons, active-route detection via usePathname, footer.
  2. Rewrite TopBar with title/subtitle/crumbs slots, search, theme toggle, notifications.
  3. Add PageHeaderContext (or a header slot mechanism) so pages can set the topbar content.
  4. Update (dashboard)/layout.tsx grid + <main> styling.
  5. Snapshot/RTL tests for shell.

Phase C — Dashboard

  1. Re-implement /dashboard with hero + ring + cards using real data where available, skeletons elsewhere.
  2. Wire "Running low" to existing cabinet endpoint; "Pending orders" to purchases; "Activity" to activity endpoint.
  3. Tests: render with seeded SWR cache.

Phase D — Medicine pages

  1. Port Cabinet, Library, Regimens, Organizer, Activity, Schedule & Log one at a time.
  2. Each PR: lift JSX from the mockup, replace mock data with services/* SWR hooks, add tests.

Phase E — Commerce + settings

  1. Port Refills (new route), Purchases, Prices, Stores, Settings.
  2. Add accent picker UI in Settings.

6. Tailwind v4 Token Mapping (concrete snippet)

In packages/web/src/styles/globals.css:

@import 'tailwindcss';

@theme {
  --font-sans: 'Inter Tight', ui-sans-serif, system-ui, sans-serif;
  --font-display: 'Fraunces', ui-serif, Georgia, serif;
  --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;

  --color-bg: var(--bg);
  --color-bg-elev: var(--bg-elev);
  --color-bg-inset: var(--bg-inset);
  --color-border: var(--border);
  --color-border-strong: var(--border-strong);
  --color-ink: var(--ink);
  --color-ink-strong: var(--ink-strong);
  --color-ink-muted: var(--ink-muted);
  --color-ink-faint: var(--ink-faint);
  --color-brand: var(--brand);
  --color-brand-deep: var(--brand-deep);
  --color-brand-soft: var(--brand-soft);
  --color-danger: var(--danger);
  --color-warn: var(--warn);
  --color-ok: var(--ok);
  --color-info: var(--info);

  --radius-xs: var(--r-xs);
  --radius-sm: var(--r-sm);
  --radius-md: var(--r-md);
  --radius-lg: var(--r-lg);
  --radius-xl: var(--r-xl);
}

:root {
  /* paste the :root block from docs/design/styles/tokens.css */
}
[data-theme='dark'] {
  /* paste the dark block */
}

This lets us write bg-bg-elev text-ink-strong border-border rounded-lg shadow-md while still being able to use the raw var(--brand) inside inline SVGs (matching the mockup's pattern).


7. Risks & Mitigations

Risk Mitigation
FOUC on theme switch / SSR mismatch Inline script in <head> reads localStorage and sets data-theme before React hydrates. Mark theme-dependent UI as client-only when needed.
Mockup uses inline style={{...var(--viz-X)...}} heavily Keep that pattern in components; tokens are CSS vars so this works fine.
Real data shape differs from mock data For each card, define a small typed view-model and a transformer from API DTO; render skeletons when data is unavailable.
Tailwind v4 @theme + custom CSS vars interplay Verified above: declare CSS vars in :root/[data-theme=dark], then map them to --color-* inside @theme so utilities pick them up.
Test snapshots churn Co-locate component tests, prefer RTL queries by role/text over snapshots; use snapshots only for the icon set.
Scope creep Strictly follow the phased rollout; each phase is independently mergeable.

8. Out-of-Scope Follow-ups

  • ⌘K command palette (search input is visual-only initially).
  • Real notification feed.
  • Per-user accent persistence in DB (initially localStorage only).
  • Mobile drawer for the sidebar (mockup is desktop-first; add a < 900px breakpoint that collapses to a top hamburger).
  • Animations beyond the existing CSS transitions.

9. Definition of Done (per phase)

  • All new components have unit tests; coverage for packages/web does not regress.
  • npm run lint and npm run typecheck pass at the workspace root.
  • Visual parity verified against the relevant screenshot in docs/design/screenshots/.
  • No usage of any; all view-models typed; all client-only files marked 'use client'.
  • No regressions in existing E2E tests.