Skip to content

Phase 7 slice 5 (mock): viewer UI refresh design spec#27

Merged
vicmaster merged 5 commits into
masterfrom
feat/phase7-viewer-refresh-mock
May 16, 2026
Merged

Phase 7 slice 5 (mock): viewer UI refresh design spec#27
vicmaster merged 5 commits into
masterfrom
feat/phase7-viewer-refresh-mock

Conversation

@vicmaster
Copy link
Copy Markdown
Owner

Summary

Dogfood-first per the agreed plan: design the slice 5 chrome inside canvas-mcp itself before any CSS lands. No viewer code changes in this PR. Iterating a scene graph is cheaper than iterating real viewer CSS, and the resulting PNG doubles as a spec the implementation PR (slice 5b) can be reviewed against.

What lands

  • scripts/build-viewer-mock.ts — composes the redesigned project page as a canvas-mcp scene graph and renders to docs/viewer-refresh-mock.png via the same renderer + puppeteer pipeline that scripts/build-hero.ts uses.
  • docs/viewer-refresh-mock.png — 2880×1800 retina render of the design.

Visual direction

Linear-inspired, per the AskUserQuestion vote.

Surfaces bg #0e0e12, sidebar #131319, card #16161c — slight blue/purple cast vs the cold greys we have today
Accent indigo→violet gradient (#6366f1 → #8b5cf6) on logo dot + active project state. Active row uses #1e1b4b as a soft fill so the highlight reads as a tint, not a button
Typography workspace labels 11px tracked uppercase (#5a5a64); project name 13px (active 600 / inactive 500); title 26px slightly tight-tracked; meta + breadcrumb in lower-contrast greys
Rhythm 12 / 16 / 24 / 32 spacing scale; 3-across cards at 1440 viewport
Cards 12px radius, subtle elevation via surface tone (no heavy shadows). Empty thumbnails: radial gradient + dashed-square placeholder. Content thumbnails: linear gradient + small bar-chart preview
Archive sits below all workspaces, separated by a hairline

Bug caught while building the mock

The first pass used position: 'absolute' for chart bars without a positioned ancestor — they escaped to body coordinates and rendered inside the sidebar. Fix: position: 'relative' on the thumb frame, and switch from absolute placement to a flex-row with alignItems: 'end' so bars share a baseline naturally.

Worth surfacing: position: absolute without a relative parent is a real foot-gun in the current renderer. Not blocking this slice, but might deserve a canvas_evaluate consistency check in a future Phase 6 follow-up.

Re-running

npx tsx scripts/build-viewer-mock.ts

Same pattern as scripts/build-hero.ts. The PNG is a one-shot output — edit the scene graph in the script, rerun, the file updates in place.

What's next

Slice 5b translates this mock to actual viewer.ts CSS, applied to the gallery / project / detail / compare / archive pages. Smoke tests stay focused on markup (typography classes, surface colors via computed style) — the visual fidelity to this mock is what code review checks.

Test plan

  • npm run build clean (no production code touched; renderer + scene-graph types check the scripts/ file)
  • npx tsx scripts/build-viewer-mock.ts produces docs/viewer-refresh-mock.png cleanly
  • No regression check needed — no production code modified.

vicmaster added 5 commits May 16, 2026 20:54
Dogfood-first per the agreed plan: design the slice 5 chrome inside
canvas-mcp itself before any CSS lands. Iterating a scene graph is
cheaper than iterating real viewer CSS, and the mock doubles as a
spec the implementation PR can be reviewed against.

What this PR adds:

- scripts/build-viewer-mock.ts — composes the redesigned project page
  as a canvas-mcp scene graph and renders to docs/viewer-refresh-mock.png
  via the same renderer + puppeteer pipeline scripts/build-hero.ts uses.
- docs/viewer-refresh-mock.png — 2880×1800 retina render of the design.

Visual direction (Linear-inspired, per AskUserQuestion):
  - Warm-dark surfaces: bg #0e0e12, sidebar #131319, surface #16161c.
    Slight blue/purple cast vs the cold grays we have today.
  - Indigo→violet accent gradient (#6366f1 → #8b5cf6) for the logo dot
    and active-project state. Active row uses #1e1b4b as a soft fill
    so the highlight reads as a tint, not a button.
  - Typographic hierarchy: workspace labels in 11px uppercase tracked
    caps (#5a5a64); project name 13px (active 600 / inactive 500);
    title 26px slightly tight-tracked; meta + breadcrumb in lower-
    contrast greys.
  - Spacing rhythm: 12/16/24/32. Cards at width 288 produce a
    3-across grid at 1440 viewport (1200 main-pane width − 64 padding
    − 2*24 gap = 1088 / 3 ≈ 362; cards at 288 leave whitespace, which
    reads as breathing room).
  - Cards: 12px corner radius, subtle elevation via surface tone (no
    heavy shadows). Empty thumbnails get a radial gradient + the
    existing dashed-square placeholder. Content thumbnails get a
    linear gradient + a small bar-chart preview.
  - Archive entry sits below all workspaces, separated by a hairline.

Bug caught while building the mock:
  - First pass used `position: absolute` for the chart bars without a
    positioned ancestor. They escaped to body coordinates and rendered
    in the sidebar area. Fix: `position: 'relative'` on the thumb
    frame, and switch the chart from absolute placement to a flex-row
    with alignItems 'end' so the bars share a baseline naturally.
    Worth surfacing — `position: absolute` without a `relative` parent
    is a real foot-gun in the current renderer; not blocking but might
    deserve an evaluator check in a future Phase 6 follow-up.

Next: implementation PR (slice 5b) translates this mock to viewer.ts
CSS. The mock stays in docs/ as a living spec — re-runnable via
`npx tsx scripts/build-viewer-mock.ts`.
…the viewer

Original mock-only PR was incomplete dogfooding — you could see the design
as a static PNG in the PR but not interact with it in your localhost:3001
viewer. The whole point of using canvas-mcp to design canvas-mcp's UI
was to live-review the spec at breakpoints, in Compare mode, with the
JSON inspector — not just review a flat image attached to a code review.

Script now also writes the canvas JSON to `$CANVAS_MCP_HOME/canvases/`
(default `~/.canvas-mcp/canvases/`) using a stable canvas ID
`viewer-refresh-mock`. Re-running the script overwrites in place — no
duplicate mock entries cluttering the gallery.

After pulling this branch and running `npx tsx scripts/build-viewer-mock.ts`:
  http://localhost:3001/canvas/viewer-refresh-mock

The canvas lands in Personal / Untitled by default. Approve it as a spec
by hitting that URL and toggling through Mobile / Tablet / Desktop /
Compare to see how the design behaves; reject by saying what to change
and I'll iterate on the scene graph.

PNG byte diff is incidental puppeteer rendering jitter (anti-aliasing
variance between runs) — the design itself is unchanged.
Responding to direct feedback that v1 read as a "competent generic dark
theme," not a "designer says wow" mock. Two things rebuilt:

1. Dogfood the data — workspaces are now `canvas-mcp` (Viewer / Renderer /
   Roadmap), `Coide` (Agents tab / Memory), and `Magmalabs` (Sandbox).
   Matches how you'd actually use canvas-mcp across multiple products.
   The active project is "Viewer" — i.e. literally what slice 5 is
   redesigning. Self-referential, on purpose.

2. Push aesthetic harder. Specific changes:

   Sidebar
   - Logo: small rounded-square gradient mark (Linear-L style) instead of
     a circle. Inner highlight gives it dimension.
   - Workspaces in tracked uppercase at 10px (was 11) — feels more
     deliberate, less default.
   - Project rows: each project has a 6px colored dot for identity.
     Active project gets a soft indigo→violet horizontal gradient fill
     (not a flat blue) PLUS a 2px solid accent-gradient left bar that
     reads as a focus rail. Active name jumps to 600 weight; count
     badge shifts to soft violet (#c7b8ff).
   - Archive icon: hairline-stroked outline (was filled box).

   Main pane
   - Title: 36px / 700 / -0.6px tracking (was 26 / 600). The page now
     has a single dominant element.
   - Breadcrumb / title / meta stacked tight, with workspace name in
     the breadcrumb visibly dimmer than the project name (eye-guide).
   - "+ New canvas" CTA pill anchors the right side of the header row,
     subtle elevated surface w/ glass-rim inner shadow.
   - Ambient linear gradient on the main pane (top-left bloom) so the
     bg isn't a flat dark plane.

   Cards
   - 1px inner-top white highlight (rgba 4%) — glass rim. Plus a soft
     drop shadow (4y / 12blur / -2spread / 35%) for depth without weight.
   - Empty thumbnail: redesigned. Soft accent-color halo + two layered
     hairline rectangles. Reads as "ready canvas" not "missing image."
   - Content thumbnail: refined 5-bar chart; last bar uses the next
     palette's accent (e.g. green-chart card ends in a blue bar) so
     no two thumbnails feel identical at a glance.

Open questions parked, NOT addressed in this PR (per discussion):
- "Design systems per workspace/project" → planned Phase 8 item, added
  to VISION when we close Phase 7. Infrastructure already partly there
  via Canvas.variables (tokens) + the preset system.

Re-running:
  npx tsx scripts/build-viewer-mock.ts
The canvas auto-publishes to ~/.canvas-mcp/canvases/ with stable ID
`viewer-refresh-mock`, so iteration is fast and the live spec stays
reviewable at http://localhost:3001/canvas/viewer-refresh-mock.
…rple

Direct response to the "purple is what every AI agent picks" critique:
canvas-mcp's identity should sit in a palette the AI-default-generator
playbook doesn't reach for. Amber/gold lands somewhere intentional —
warm-paint metaphor for a canvas product, rare in dev tools, hardest
visual distance from both Linear-purple and Coide's blue.

Palette overhaul (every surface, accent, and chart palette touched):

  Surfaces — warm-tinted darks with khaki/brown undertone:
    bg0      #09070a   (was #08080c, cool dark)
    sidebar  #14110c   (was #131319, neutral)
    surface  #1a160f   (was #16161e, neutral)

  Text — slightly warm whites/greys instead of pure neutral:
    primary    #fafaf5
    secondary  #b8b3a6
    tertiary   #807965

  Accent (amber → orange → gold) — single signature:
    from  #f59e0b  amber-500
    mid   #f97316  orange-500 for warmth bend
    to    #d97706  amber-600
    soft  #fde68a  for active count badge contrast

  Project dots — red replaces violet so no purple anywhere in the
  composition. Remaining mix (green/blue/amber/pink/cyan/red) gives
  per-project identity without leaning on the default.

  Charts — chartViolet replaced with chartAmber (#fcd34d → #f59e0b);
  card variant 'violet' renamed to 'amber'. All content thumbnails now
  read in the warm half of the spectrum.

  Backgrounds:
  - Main pane ambient gradient: top-left warm bloom (#16110a) → bg1 →
    bg0, replacing the cool indigo-tinted version.
  - Empty-thumbnail halo: amber at 10% alpha → 0%, replacing the
    indigo halo. Reads as "warm canvas, ready" not "void."
  - Content-thumbnail gradient: #2a1f10 → #0e0b08, replacing the
    indigo plate.

Net effect: the design now has *a* palette identity, not the AI-default
one. Whether it crosses the "designer says wow" bar is judgement we
should make against the live render at
http://localhost:3001/canvas/viewer-refresh-mock — that's what the
dogfood-publish step is for.

Known constraints still hitting the design (parked for the planned
Phase 8 "Renderer expressiveness" — separate PR after Phase 7 closes):
  - No backdrop-filter blur → can't do proper glassmorphism
  - No custom font loading → typography stuck on system stack
  - No SVG path support → custom iconography (the archive box icon,
    the logo mark, etc.) reduced to stroked-rectangle approximations

When those land we can push further.
User feedback: gradients fit AI-generated designs more than they fit a
considered product UI. Removing them entirely is what was missing — the
amber identity reads way more deliberately as a flat color than as a
gradient stop pair.

Every gradient touched in v3 is now a flat fill:

  Logo mark:               linear (#f59e0b → #d97706)    → flat #f59e0b
  Active project bar:      vertical linear (amber→amber) → flat #f59e0b
  Active project fill:     horizontal linear (low alpha) → flat rgba 8%
  Empty-thumb halo:        radial amber → transparent    → removed
  Empty-thumb bg:          radial warm → dark             → flat #15110b
  Content-thumb bg:        135° linear warm               → flat #1a140c
  Content-thumb bar fills: vertical linear per palette    → flat per bar
  Main pane ambient:       165° three-stop warm→dark      → flat C.bg1

Palette cleanup:
  accentBarBg / accentBarTo (the gradient pair) collapsed into one
    accentActiveBg: rgba(245,158,11,0.08) — single low-alpha tint.
  accentMid / accentTo dropped from the public palette since the
    derivatives aren't needed without gradients to interpolate.
  Added accentDeep #b45309 for future hover-down states; the rest of
  the palette is now flat single-color stops only.

Chart variety preserved by using different palette per card (green-led,
blue-led, amber-led) with the last bar in the secondary color — same
compositional move that gave each thumbnail a focal point, just
without gradient interpolation doing the work.

Net effect: design feels more confident, less "trying to look premium."
The amber identity reads as a deliberate brand color, not as ambient
lighting. Discipline > flourish.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant