From 4ec39de8fbbceb774fefe30468111e13f4cef644 Mon Sep 17 00:00:00 2001 From: Tanish Manku Date: Mon, 15 Jun 2026 17:23:43 -0700 Subject: [PATCH] docs: add PageDraw visual design & architecture review High-end design pass over the pagedraw editor styling and architecture (lib + webapp integration). Scores each axis against a Linear/Figma-tier app-chrome rubric and lays out a P0-P2 roadmap: token sweep to kill hardcoded greys and unify the accent, an owned type ramp, custom-eased micro-motion, double-bezel panel surfaces, a concentric radius scale, z-index hygiene, and module-size/architecture notes. --- _docs/pagedraw_design_review.md | 203 ++++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 _docs/pagedraw_design_review.md diff --git a/_docs/pagedraw_design_review.md b/_docs/pagedraw_design_review.md new file mode 100644 index 0000000000..cdcd0cb93b --- /dev/null +++ b/_docs/pagedraw_design_review.md @@ -0,0 +1,203 @@ +# PageDraw — Visual Design & Architecture Review + +**Surface:** `@storyteller/ui-pagedraw` image editor + its `artcraft-webapp` integration (`/edit-image`). +**Lens:** Awwwards / Linear-tier "premium app chrome" rubric, adapted for an in-product creative tool. +**Date:** 2026-06-15 · **Branch:** `worktree-pagedraw-webapp` + +--- + +## 0. How to read this + +PageDraw is a **professional canvas tool**, not a marketing page. So the elite-design rubric is +applied in its *app-chrome* register — the north star is **Figma / Linear / Photoshop-web**, not an +Awwwards landing page. That means: + +- **Applies:** token discipline, concentric radii, hairline + inset-highlight surfaces ("double-bezel"), + island/pill controls, custom-eased micro-motion, z-index hygiene, premium type ramp. +- **Does _not_ apply:** `py-40` macro-whitespace, hero typography, editorial-split layouts, scroll-reveal + entrances. A tool surface is dense and persistent by design; forcing landing-page whitespace would hurt it. + +Citations are `file:line` against the current tree. + +--- + +## 1. Verdict + +| Axis | Score | One-liner | +|---|---|---| +| Color system | ⚠️ 5/10 | Good token foundation, but hardcoded greys (`#1b1b1b`, `#303030`, `#404040`) leak through a *themeable lib*. | +| Typography | ❌ 3/10 | No type ramp, no font-family ownership; rides whatever the host sets. Banned-by-rubric default stack risk. | +| Radii | ⚠️ 4/10 | Five radius scales in play (`sm`→`2xl`→`full`) with no concentric nesting. | +| Surfaces (border/shadow) | ⚠️ 5/10 | Flat `shadow-lg` everywhere; no inset highlights; no double-bezel. Reads "functional", not "machined". | +| Motion | ❌ 3/10 | Only `transition-colors`/`ease-in-out`, durations 75–200ms; **zero custom easing**, no spring, no entrance choreography. | +| Layout / z-index | ⚠️ 6/10 | Clean fixed-panel composition, but a `z-[9999999]` escape hatch and a 6-tier ad-hoc z-stack. | +| Icons | ✅ 7/10 | FontAwesome Pro (solid + regular) — acceptable, but solid weight is heavier than the rubric's "ultra-light line" ideal. | +| Architecture | ⚠️ 5/10 | Clean adapter boundary; but `PaintSurface.tsx` (2005 LOC), `SceneState.ts` (1539), `PageDraw.tsx` (1292) are oversized, and the lib hardcodes host-theme colors. | + +**Bottom line:** the editor is *structurally competent and functional* but visually reads as +"capable internal tool," not "$150k product surface." The single highest-leverage fix is a **shared +design-token layer** (color + radius + motion + type), because today the lib both (a) reaches for +host tokens (`bg-ui-panel`, `text-base-fg`) *and* (b) hardcodes its own greys — so it can never be +fully reskinned by the webapp vs. the desktop app. + +--- + +## 2. What's already good (keep) + +- **Token-aware base.** Most surfaces use semantic tokens (`bg-ui-panel`, `bg-ui-controls`, + `text-base-fg`, `border-ui-panel-border`) — the right instinct (SideToolbar, PromptEditor, HistoryStack). +- **Clean platform boundary.** The lib has zero REST/Tauri imports; everything I/O flows through + `PageDrawAdapter` (`adapter.ts`). The webapp adapter (`web-adapter.tsx`) and desktop adapter stay + swappable. This is genuinely good architecture and should be protected. +- **Performance-minded canvas.** Composite pre-baking on idle (`PageDraw.tsx` ~661–710) and worker + encoding (`generatePipeline.worker.ts`) keep the generate click off the main thread. Don't regress this. +- **CORS already handled** for browser canvas export (`crossOrigin="anonymous"` in `Node.ts:134`, + `SceneState.ts:1473`, `imageHelpers.ts:16`) — the web port works because of this. + +--- + +## 3. Findings & recommendations + +### 3.1 Color — stop hardcoding greys in a themeable lib ⚠️ P0 +**Current:** semantic tokens *and* raw hex coexist: +- `#1b1b1b` pegboard (`pagedraw.css:6`), `#1A1A1A`/`#333333` context menu (`ContextMenu.tsx:44`), + `#303030`/`#404040` dividers + popovers (`SideToolbar.tsx:381,550`), `bg-zinc-700/600` swatch + (`SideToolbar.tsx:173`), plus ad-hoc `bg-blue-600/500` accents (`PageDraw.tsx:157`, base selector). +- Accent color is inconsistent: the lib's primary action uses raw **blue-600**, while the webapp's + design system uses `--primary` / `brand-primary`. So the editor's blue doesn't match the product. + +**Why it fails the bar:** a premium component library exposes *one* surface ramp and *one* accent, both +driven by tokens, so it reskins per host. Hardcoded greys mean the desktop and web themes can drift, and +the raw `blue-600` clashes with `brand-primary` used elsewhere in the webapp. + +**Do:** +- Replace every raw grey with the existing ramp: `#1b1b1b`→`bg-ui-background`, `#1A1A1A`→`bg-ui-panel`, + `#303030`/`#404040`→`border-ui-panel-border` / `bg-ui-controls`. For the pegboard, add a token + `--st-canvas` so hosts can theme the canvas backdrop. +- Replace `blue-600/500` accents with `bg-primary` / `text-primary` (the token the rest of the webapp uses), + so the Generate/Edit-3D buttons match `brand-primary`. +- Move the react-colorful overrides off pure `rgb(255,255,255)` borders (`pagedraw.css:15,19`) onto + `white/15` hairlines for consistency with the rest of the chrome. + +### 3.2 Typography — own a type ramp ❌ P0 +**Current:** no `font-family` anywhere in the lib (grep: 0 hits); only `text-xs/sm/base/lg/xl` + +`font-medium/semibold/bold`. The editor inherits whatever the host's body font is. + +**Why it fails the bar:** the rubric explicitly bans the default web stack (Inter/Roboto/Arial/Helvetica). +Inheriting an unspecified stack means the tool can render in a banned font with no recourse, and there's +no deliberate hierarchy (numerics in sliders, labels, modal titles all share one undifferentiated ramp). + +**Do:** +- Adopt the product's display/grotesk token (e.g. a `font-sans` mapped to Geist / Plus Jakarta Sans at + the app level) and reference it in the lib's root container, not per-component. +- Define a small semantic ramp: `text-[11px] tracking-[0.16em] uppercase` for toolbar/section labels, + `text-sm` for controls, `text-base font-semibold` for modal titles. Apply `tabular-nums` to slider + read-outs (`SliderWithIndicator.tsx`) and dimension fields (`BlankCanvasModal`) so numbers don't jitter. + +### 3.3 Radii — collapse to a concentric scale ⚠️ P1 +**Current:** `rounded-sm / md / lg / xl / 2xl / full` all appear, with no parent→child relationship +(e.g. `rounded-2xl` card at `PageDraw.tsx:1123`, `rounded-xl` panels in SideToolbar, `rounded-lg` +buttons in InpaintToolBar). + +**Do:** pick a 3-step system — **`rounded-2xl` shells, `rounded-xl` inner cards, `rounded-lg` controls, +`rounded-full` pills** — and apply concentrically (the "double-bezel" curve math: inner radius = +outer − padding). Drop `rounded-sm`/`rounded-md` from the editor entirely. + +### 3.4 Surfaces — add the "machined" double-bezel ⚠️ P1 +**Current:** panels are a single `bg-ui-panel` + `border` + `shadow-lg`. No nested enclosure, no inset +top-highlight. Reads flat. + +**Do (for the four floating panels — SideToolbar, HistoryStack, PromptEditor, InpaintToolBar):** +- Outer shell: `bg-ui-panel/80 ring-1 ring-white/10 backdrop-blur-xl p-1.5 rounded-2xl`. +- Inner core: own bg + `shadow-[inset_0_1px_0_rgba(255,255,255,0.08)] rounded-xl`. +- This is the single change that most moves the surface from "tool" to "premium hardware." The panels are + already `fixed` (`PageDraw.tsx:1144,1175,1195`), so `backdrop-blur` here is GPU-safe per the perf rules. + +### 3.5 Motion — replace linear/ease with spring-eased micro-motion ❌ P0 (cheap, high impact) +**Current:** `transition-colors`/`transition-all` with `ease-in-out` and `duration-75/150/200` +(`SideToolbar.tsx:381,550`, `SliderWithIndicator.tsx:65`, `ContextMenu.tsx:53`). No custom easing, +no entrance/exit on popovers, color pickers, or the context menu (they pop instantly). + +**Why it fails the bar:** instant state changes and `ease-in-out` are explicitly banned. A pro tool still +deserves *interpolated* motion — it's what separates Figma from a jQuery plugin. + +**Do:** +- Standardize on one curve: `transition-[transform,opacity,background-color] duration-300 + ease-[cubic-bezier(0.32,0.72,0,1)]`. +- Tool buttons: `active:scale-[0.97]` press physics; selected tool gets a spring-eased indicator slide + rather than a hard background swap. +- Popovers / color picker / context menu: animate in with `opacity-0 translate-y-1 scale-[0.98]` → + resolved, ~180ms. Transform+opacity only (perf rule). +- History tiles: stagger their `opacity` reveal when a generation lands (they already use + `transition-opacity`, just add per-index delay). + +### 3.6 Layout & z-index — tame the stack ⚠️ P1 +**Current:** clean fixed-panel composition, but z-index is ad-hoc across 6 tiers and includes a +`z-[9999999]` on the context menu (`ContextMenu.tsx:44`) and a one-off `z-[60]` (`PageDraw.tsx:237`). + +**Do:** define a named scale in one place — `canvas:0, chrome:10, floating-controls:20, overlay-button:40, +menu:50` — and replace the magic numbers. The portal menu only needs to clear the chrome, not `9999999`. + +### 3.7 Responsive — declare intent ⚠️ P2 +**Current:** essentially desktop-only; fixed sidebars assume a wide viewport; only LightboxDetails has +`sm:` variants. The webapp wrapper scopes the lib correctly (`pagedraw.tsx` `translateZ(0)` containing +block) but there's no mobile story. + +**Do:** mirror the Edit-3D page's explicit **mobile gate** (`pagescene.tsx` `MobileGate`) on `/edit-image` +rather than letting fixed toolbars overlap on touch. A precision canvas tool is legitimately desktop-first; +just say so deliberately instead of degrading silently. + +--- + +## 4. Architecture notes + +- **Oversized modules.** `PaintSurface.tsx` (2005 LOC), `SceneState.ts` (1539), `PageDraw.tsx` (1292). + These are review/maintenance hotspots. Suggest extracting from `SceneState.ts`: history/undo slice, + base-image-load slice, and generation/pending slice into composable Zustand slices. `PageDraw.tsx`'s + generate-flow (cases 1–3, ~780–868) could move to a `useGenerate()` hook. +- **Theme ownership.** Per §3.1, the lib should *only* consume tokens. Today it mixes tokens + hex, which + defeats the adapter pattern's promise (one component, many themed hosts). +- **Adapter surface is healthy.** `PageDrawAdapter` is small and intentional; the web vs. Tauri split is + clean. The webapp's `enqueueInpaint` stub and 402→credits-modal handling are explicit and documented. +- **Icon weight.** FontAwesome **solid** is the heaviest option; if a line/light set is available + (Phosphor/Remix or FA light), the toolbar would read more refined. Low priority. + +--- + +## 5. Prioritized roadmap + +| P | Item | Effort | Payoff | +|---|---|---|---| +| **P0** | Token sweep: kill hardcoded greys; unify accent on `--primary` (§3.1) | M | Highest — enables true theming + product-consistent accent | +| **P0** | Own a font-family + semantic type ramp at the lib root (§3.2) | S | High — removes banned-font risk, adds hierarchy | +| **P0** | One custom easing curve + press/popover micro-motion (§3.5) | S | High — cheapest "feels expensive" win | +| **P1** | Double-bezel + inset highlights on the 4 floating panels (§3.4) | M | High — "machined" surface upgrade | +| **P1** | Concentric 3-step radius system (§3.3) | S | Medium — visual coherence | +| **P1** | Named z-index scale; remove `z-[9999999]` (§3.6) | S | Medium — maintainability | +| **P2** | Explicit mobile gate on `/edit-image` (§3.7) | S | Medium — correctness on touch | +| **P2** | Split `SceneState.ts` / `PaintSurface.tsx` into slices (§4) | L | Medium — maintainability | + +--- + +## 6. Appendix — proposed token additions + +```css +/* canvas + chrome surface ramp (lib should consume only these) */ +--st-canvas: ; /* replaces #1b1b1b pegboard */ +--st-accent: var(--primary); /* replaces blue-600/500 */ +--st-hairline: rgb(255 255 255 / 0.10); +--st-inset-hi: inset 0 1px 0 rgb(255 255 255 / 0.08); + +/* motion */ +--st-ease: cubic-bezier(0.32, 0.72, 0, 1); +--st-dur-fast: 180ms; +--st-dur: 300ms; + +/* radius scale (concentric) */ +--st-r-shell: 1.25rem; /* rounded-2xl */ +--st-r-card: 0.875rem; /* rounded-xl */ +--st-r-control: 0.5rem; /* rounded-lg */ +``` + +> Scope note: every recommendation here is **styling/architecture only** — none change the generate, +> polling, upload, bg-removal, or adapter contracts validated during the functional debug pass.