Skip to content

Canva Editor Improvement Backlog

Prioritised ideas for the local Canva editor (http://localhost:8790/), from a feature inventory + dedicated UI/UX review on 2026-10-09. Ordered by impact ÷ effort. Companion page: [[OpenCode Canva Design Skill]].

Verdict of the review: competent developer tool with a Canva-ish control list, not a Canva-like product. Bones are right (sidebar / stage / optional chat, live CSS-var editing, drag handles) — thumbnails, selection state, zoom, and a real export result are missing.

Status note (2026-10-11): the editor has since moved to a descriptor model — freeform chat (/api/chat) was removed and replaced by /api/ask (plain-language → change descriptors, applied by lib/patch.js), plus site-colour extraction (lib/palette.js). Item 10's "chat drawer" now refers only to the Ask panel, which is a normal sidebar panel, not an overlay. Items 1–9 and 11–16 are unchanged. See [[OpenCode Canva Design Skill]].

Bugs first (fix before building)

  1. Drag/resize runs at 2× cursor speed — pointer deltas are divided by SCALE twice; inner iframe coords are already design px (verified with a Playwright probe: outer 100px → inner 200px at scale(.5)). One-line fix at editor.js:103-104,125.
  2. Non-square templates are clipped — stage hardcoded 540×540, iframe 1080×1080 (editor.html:26-27), but 5 of 9 templates aren't square (story 1080×1920, linkedin-banner 1584×396, banner 1200×628, youtube-thumbnail 1280×720, flyer 1080×1350). Export dims are correct, so preview ≠ output.
  3. Sidebar color rows are no-ops on half the templates — hardcoded to --bg/--primary/... but logo/flyer/announcement/quote-card/story use --mark/--ink/--bg3. Build rows dynamically from the template's actual :root vars (values are already read at editor.js:189).
  4. Export success signal is popup-blocked — window.open after two awaits usually gets blocked; only feedback is an 11px break-all status line. Button never disables, so double-clicks fire duplicate renders.

High impact

  1. Fit-to-template stage + zoom/pan — size stage/frame from curW/curH (already fetched), replace the SCALE const (editor.js:5) with a live scale, add Fit/50%/100% + percentage readout, pan when zoomed past fit. Unblocks everything else.
  2. Persistent selection + contextual toolbar — .sel class exists but has no CSS; selection is a 600ms flash (editor.js:144-149). Make it stick, anchor a floating toolbar with font size, text color, duplicate, delete, forward/back, lock. Delete and z-order currently have no UI at all.
  3. Undo/redo — snapshot stack over the existing getCleanHtml() serializer (editor.js:269), pushed on drag/resize pointerup, color/font commit, image insert, template load, and chat-apply. Today a bad Hal reply blanks the canvas with zero recovery.
  4. Export panel — format select (PNG/WebP/JPG — render.js:35-38 already supports all three; server hardcodes WebP at editor-server.js:202), scale 1×/2× (server hardcodes "2" at :208), filename with real output name shown, and a result card: thumbnail + Download (<a download>, not window.open) + Copy path.
  5. Template gallery with rendered thumbnails — replace the <select> (editor.html:58) with a card grid; one-time server render of 9 previews via existing lib/render.js, cached in designs/previews/. Biggest single "Canva vs dev tool" visual gap.
  6. Chat as overlay drawer — toggling chat mutates grid columns so the canvas jumps (editor.js:264-266); below ~1400px render as right-edge overlay with scrim. Add greeting + 4 tappable suggestion chips as empty state, persistent typing indicator instead of "…", style the disabled send button, rehydrate transcript from server-side chatHistory.

Cheap polish (low effort, disproportionate effect)

  1. Quiet the canvas — dashed .ed outlines are permanent (strongest "dev tool" signal); show on hover/selection only. Handles are 16px in design space = 8 screen px at scale .5; scale inversely to zoom → 24–32 screen px.
  2. Loading/empty states — template load has zero indication (first paint is a black box); guard the unguarded fetch().json() at editor.js:16; drag-over needs text ("Drop an image"), not just an outline.
  3. Keyboard — only shortcut today is Ctrl+Enter for chat. Add Del-to-delete, arrow nudge 1px / Shift 10px (no fine positioning exists), Esc clear, Cmd/Ctrl+Z undo, +/-/0 zoom, :focus-visible rings (none anywhere).
  4. Sidebar reorder + identity — chat toggle is currently the first control; Template should be. Replace system-ui and <h1>Canva-style Editor with a real product name; delete leaked hint "Talks to the same AI as this session" (editor.html:104).
  5. Font picker — replace free-text font fields (editor.html:79-80) with a dropdown of installed Noto families (~1300) rendered in their own faces.
  6. Guards — template switch silently discards edits (editor.js:34); add dirty-dot + confirm, localStorage debounced autosave as a save bridge.

Housekeeping

  • lib/brand.json is documented as the defaults source (editor/README.md:24) but nothing reads it; its keys (background, secondary) don't match sidebar var names either. Wire it in or drop the claim.
  • Stale editor-server.js.bak.20260830 (and .bak.20261009) are publicly fetchable via the /editor/* route; the Aug 30 copy still contains the old credential-reading approach. Exclude *.bak* from that route or move backups out of editor/.
  • POST /api/export output is always .webp @ 2× despite render.js supporting png/jpg and SKILL.md documenting PNG as the skill output.
  • readTemplates dimension regex (editor-server.js:138) requires an exact one-line html, body { width: Npx; height: Npx shape; silently falls back to 1080×1080.
  • safeJoin path guard uses bare fp.startsWith(base) without a separator check (theoretical prefix confusion) — same pattern in lib/serve.js:36.

Suggested sequence

1 (one-liner) → 2+5 (stage/zoom) → 6 (selection) → 11+12 (polish) → 8 (export) → 9 (thumbnails) → 7+13 (undo+keys) → 10 (chat) → 14-16

  • [[OpenCode Canva Design Skill]] — the skill itself, architecture, CLI usage
  • [[OpenCode Voice Input]] — separate project, unrelated to the editor