diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 00000000..0327761f --- /dev/null +++ b/PLAN.md @@ -0,0 +1,472 @@ +# PLAN — OpenPCB UI refactor to the neutral EDA design (Claude Design handoff) + +status: done +owner: orchestrator session +branch: ui/neutral-eda-redesign +design bundle: /home/claude/repo/project (read-only source of truth) +design specs (extracted, exact px/hex): + - /tmp/claude-0/-home-claude-repo/caff7693-acd5-53e8-ac09-a6bfa2ebf24d/scratchpad/design-D2-pcb-schematic.md + - /tmp/claude-0/-home-claude-repo/caff7693-acd5-53e8-ac09-a6bfa2ebf24d/scratchpad/design-D3-lib-bom-home.md +current-state recon (file:line, must-preserve lists): + - .../scratchpad/recon-A1-shell.md, recon-A2-pcb.md, recon-A3-schematic.md, recon-A4-lib-bom-home.md +token source: /home/claude/repo/project/openpcb-theme.css + +--- + +## 0. Goal and non-goals + +Goal: re-skin and restructure the OpenPCB desktop frontend so the six screens in +`Compare Screens.dc.html` (2a PCB editor, 3a Schematic editor, 3b Library, 3c BOM, +3d Home, 2b Token sheet) match the handoff designs, **without changing behaviour**. +Every callback, store, hotkey, persisted key, DnD MIME contract, cross-probe path and +e2e accessible name listed in the recon files keeps working. + +Non-goals (explicitly out of scope, recorded as follow-ups in §9): +- Canvas palette (schematic wire colours, PCB layer colours, selection colour inside + WebGL canvases). Owned by external package `@openpcb/r3f-eda-canvas` + (`OpenPCB-app/shared`); `EdaCanvas` wraps its own `CanvasThemeProvider(mode)`. +- Website (4a) — different repo. +- New product features the designs sketch but the backend does not support + (multi-sheet "Sheets", design tags, recent-activity feed, ERC dock, DRC + waive/severity, BOM "Group by", file "Open…" dialog). Rule in §2.D6. +- 3D view, Settings, Assistant space, Knowledge/Docs, Tasks, import wizard, dialogs: + **token re-skin only** (they pick up new fonts/colours/radii via §3 T1), no + structural changes. + +## 1. Context (from Phase 1 recon) + +- Stack: React 19, Vite 7, Tailwind v4 (CSS-only config in + `src/core/frontend/src/index.css`: `@theme`, `@theme inline` semantic layer, + `:root` light + `html.dark` dark, class-based dark via + `@custom-variant dark (&:where(.dark, .dark *))`), lucide-react, Radix + (tabs/tooltip/dialog/dropdown/context-menu/scroll-area), zustand, clsx + + tailwind-merge via `cn()` in `src/core/frontend/src/lib/utils.ts`. +- Styling reality: 4,032 raw `slate-*` and 699 `violet-*` class usages across ~150 + TSX files; only ~80 uses of semantic tokens. Radii tokens today: card 14px, + control 8px. Fonts: "Inter" declared but never loaded (system fallback). +- Shared primitives: `src/shared/frontend/ui/*` (button, card, chip, icon-button, + pill, tabs, tooltip, textarea, stacked-card, dropdown-menu, context-menu, + relevance-bar) — hand-rolled variant maps + `cn()`, no cva. +- Shell: `AppShell.tsx` = TitleBar (36px, Electron only) + `grid-cols-[80px_1fr]` + (LeftSidebar 80px rail | main). Modules registered in rail: Designer, Library, + Docs (id `knowledge`), Assistant; Home is a fixed first item; footer = bug link + + Settings. +- Designer `Space.tsx` (1460 lines): DesignerHeader (44px, 3-col grid: DesignTabs | + view Tabs schem/pcb/3d/bom/drc | trailing cloud+chat) → error strip → main flex + row: [left sidebar (300 default, 240–520, not persisted) | resizer | canvas + wrapper | resizer + Selection Inspector dock (schem, 260–440, persisted) | + resizer + DRC dock (pcb, 280–560, persisted) | resizer + Chat dock (any, 320–560, + persisted)]. PCB/DRC views append `DesignerStatusBar` (24px). +- PCB chrome is mostly rendered by `PcbCanvas.tsx` (~6000 lines): floating + `PcbTopToolbar` (top-centre), floating `RouteHud`/`TuneHud`/`BundleHud` + (bottom-centre), floating `PcbSelectionInspector` (top-right, free + holes/pads/text only), and `PcbBoardPanel`/`PcbLayersPanel` **portalled** into + `CollapsibleSection`s in `DesignerSidebar` via `pcbSlotRef`/`pcbLayersSlotRef` + (sections stay mounted while collapsed; ids `pcb.sidebar.board`, + `pcb.sidebar.layers` are localStorage keys). PCB selection lives inside + PcbCanvas (`PcbSelection` sets); only `onSelectionCountChange` and + `onViewportChange(zoom,x,y)` reach Space. `cursorMm` state exists inside + PcbCanvas (line ~696) but is not surfaced. +- Schematic chrome: floating `DesignerFloatingToolbar` mounted by Space; docked + `OutlinePanel` (Parts/Nets/Labels tabs, search, sortable columns) in the left + sidebar; docked `SelectionInspector` (Part/Multi/Label/Wire panels) on the + right; no status bar; no ERC UI (backend only). +- Library: header + `FacetSidebar` (w-60, checkbox facets Source/Family/Mount/ + Package/Other) + card grid (`LibraryCard` h-56); detail = full page swap to + `ComponentDetailPage` (edit/clone/STEP upload/fullscreen previews live there). + `LibraryCard` sets drag MIME `application/x-openpcb-library-component`. +- BOM: `DesignerBomView.tsx` two-pane `grid-cols-[minmax(0,1fr)_360px]`; CSS-grid + table (checkbox | severity pill | Ref | Qty | Value | MPN·Source | Cost); + severity stripe + tint per row; footer stats; `BomInspector` rail with 650ms + debounced autosave; export menu. +- Home: `HomeScreen.tsx` centred column; chips All/Recent/Starred/Archived; search + ⌘K; sort; grid/list toggle; `DesignCard` with `SchematicThumbnail`; `CloudSyncPill` + ("Sign in to sync") next to "New design". DTO: id, name, revision, createdAt, + updatedAt, schematicPreview, drcStatus — **no** board size / layer count / nets. +- Verification baseline (green at start): `cd src/core/frontend && npx tsc --noEmit + --pretty false -p tsconfig.json` exit 0; `npm run test:react` 47 files / 326 tests. + E2E (Playwright) exist but need a backend + browser; not run in this session — + selectors preserved by rule (§2.D9). + +## 2. Decisions (with rationale) + +D1. **Token foundation = `openpcb-theme.css` dropped into `index.css`, plus a +compatibility layer.** Replace the `@theme` / `@theme inline` / `:root` / +`html.dark` blocks with the design's tokens (both themes). Keep the existing +semantic names alive as aliases so unmigrated files keep compiling and look +coherent: `--color-surface-card → surface-panel`, `--color-surface-card-hover → +surface-hover`, `--color-text-primary → text-strong`, `--color-accent → +selection`, `--color-accent-soft → selection-soft`, `--color-accent-text → text-strong`, +`--color-status-*-soft` (new soft values), `--radius-card → 2px`, +`--radius-control → 2px`, `--radius-float → 3px`, `--radius-pill → 999px`. +Additionally override Tailwind's own scales inside `@theme` so the ~150 untouched +files immediately lose the blue cast, violet accent and pill geometry: + - `--color-slate-50…950` → neutral ramp (50 #f7f7f8, 100 #ececee, 200 #dcdce0, + 300 #c4c4c9, 400 #a8a8ad, 500 #7f7f84, 600 #55555a, 700 #2c2c31, 800 #1c1c1f, + 900 #111113, 950 #0c0c0d). + - `--color-violet-50…950` → neutral "active" ramp (50 #f0f0f2, 100 #e4e4e7, + 200 #d0d0d5, 300 #a8a8ad, 400 #8a8a90, 500 #55555a, 600 #3a3a40, 700 #2c2c31, + 800 #1c1c1f, 900 #1c1c1f, 950 #151517). + - `--radius-sm: 2px; --radius-md: 2px; --radius-lg: 2px; --radius-xl: 2px; + --radius-2xl: 3px; --radius-3xl: 3px` (so `rounded-md/lg/xl/2xl` flatten; + `rounded-full` stays round for dots/spinners). + Rationale: one CSS file re-skins the whole app on day one; the five target + screens are then migrated to semantic utilities properly; the remap is the + documented stopgap for the rest (follow-up §9). + Added semantic tokens the designs need (both themes): `surface-hover` + (#1c1c1f / #e6e6e9), `surface-selected` (#26262b / #dcdce0), `surface-section` + (= panel-head), `surface-canvas-well` (#08090a / #08090a), `text-caps` (#6a6a70 / + #6f6f76), `border-control` (#2a2a2e / #cfcfd4), `primary` (#e8e8e8 / #111114), + `primary-foreground` (#111114 / #f5f5f5), `status-*-soft` at 12% alpha. + +D2. **Fonts bundled, not fetched.** Add `@fontsource/ibm-plex-sans` (400/500/600) +and `@fontsource/ibm-plex-mono` (400/500) to `src/core/frontend/package.json`; import +the weight CSS files in `main.tsx`. Electron runs offline; no Google Fonts. + +D3. **Light theme stays.** `ThemeToggle`/`applyThemeClass` untouched; light values +come from `openpcb-theme.css`. Designs were only drawn dark; light is the token +sheet's light column. + +D4. **Shared primitives are rewritten first** (T2) and new shared building blocks +added so screens share one vocabulary: `PanelSectionHeader`, `PropertyGrid`/ +`PropertyRow`, `DataTable` helpers (`TableHeaderRow`, `TableRow`), `SegmentedControl`, +`SearchField`, `Checkbox`, `StatusDot`, `SeverityDiamond`, `DockTabs`, `StatusBar`/ +`StatusSegment`, `ToolbarButton`/`ToolbarSeparator`. All in `src/shared/frontend/ui/`. +Exact px/hex per design-D2 §7 and design-D3 §5. + +D5. **Docked chrome via portal slots, not prop-lifting.** `PcbCanvas` owns the +state the toolbar/HUDs/inspector need. Space.tsx renders empty slot `