Phase 7 slice 5 (mock): viewer UI refresh design spec#27
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 todocs/viewer-refresh-mock.pngvia the same renderer + puppeteer pipeline thatscripts/build-hero.tsuses.docs/viewer-refresh-mock.png— 2880×1800 retina render of the design.Visual direction
Linear-inspired, per the AskUserQuestion vote.
#0e0e12, sidebar#131319, card#16161c— slight blue/purple cast vs the cold greys we have today#6366f1 → #8b5cf6) on logo dot + active project state. Active row uses#1e1b4bas a soft fill so the highlight reads as a tint, not a button#5a5a64); project name 13px (active 600 / inactive 500); title 26px slightly tight-tracked; meta + breadcrumb in lower-contrast greysBug 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 withalignItems: 'end'so bars share a baseline naturally.Worth surfacing:
position: absolutewithout arelativeparent is a real foot-gun in the current renderer. Not blocking this slice, but might deserve acanvas_evaluateconsistency check in a future Phase 6 follow-up.Re-running
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.tsCSS, 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 buildclean (no production code touched; renderer + scene-graph types check the scripts/ file)npx tsx scripts/build-viewer-mock.tsproducesdocs/viewer-refresh-mock.pngcleanly