Skip to content

feat(coc): chat canvas — AI/user co-edited side panel (docs, code, custom extensions)#325

Merged
plusplusoneplusplus merged 8 commits into
mainfrom
pr/coc-chat-canvas
Jun 13, 2026
Merged

feat(coc): chat canvas — AI/user co-edited side panel (docs, code, custom extensions)#325
plusplusoneplusplus merged 8 commits into
mainfrom
pr/coc-chat-canvas

Conversation

@plusplusoneplusplus

Copy link
Copy Markdown
Owner

Summary

Adds a chat canvas to the CoC dashboard — a live side panel next to a conversation where the AI and the user co-edit an artifact (a document, a code file, or a fully custom interactive app), in the genre of ChatGPT Canvas / Claude Artifacts / the GitHub Copilot app's canvases.

The whole feature is behind the canvas.enabled admin flag (default off), so nothing changes for existing users until they turn it on.

What's included

Built up over several phases (one commit each), then polished:

  • Side panel + foundationCanvasStore (file-based, ~/.coc/repos/<wsId>/canvases/), workspace REST routes, three LLM tools, live updates over the existing SSE (per-process) + WebSocket channels, the canvas.enabled flag, and a typed canvases domain in coc-client. Revision-checked saves with 409-on-conflict.
  • Version history, selection edits, comments — per-revision snapshots (capped at 50) with a stepper + restore-as-new-revision; select text → "Ask AI" prefills the composer; anchored comments delivered to the AI through the normal follow-up enqueue path (turn-boundary delivery).
  • Code canvases + exporttype: 'code' with a language hint rendered via the shared Monaco editor; Mermaid blocks in markdown canvases render as diagrams; Export menu (copy / download / save-to-Notes).
  • Custom extension canvases — the headline capability: interactive panels (kanban, checklist, dashboard) backed by JSON shared state. The AI authors a manifest + sandboxed-iframe UI + a capabilities script; both the AI (via a tool) and the user (via the iframe UI) mutate the same state through declared capabilities that run as pure (state, params) => nextState transforms in a node:vm sandbox.
  • Layout — collapsible chat list, the canvas as a full-height right column of a top-level split, and a fullscreen toggle.
  • Reopen + pop-out + native styling — a closed canvas collapses to a thin reopen rail instead of vanishing; header buttons reuse CoC's shared icon-button style; a pop-out button opens the canvas in a standalone window (PopOutCanvasShell, via the established #popout/... shell pattern).
  • Tool consolidation — the LLM tool surface is 3 tools (write_canvas, read_canvas, extension_canvas) rather than five, to limit the per-turn tool-schema context cost. write_canvas creates (omit canvasId) or updates a markdown/code canvas; extension_canvas both builds and runs an extension canvas, dispatched by the presence of a capability.

Architecture notes for reviewers

  • One artifact model, two surfaces. The store, revision/conflict model, panel, SSE/WS path, and REST routes are shared across all canvas types; only the LLM-tool surface and the panel's render mode differ (text str-replace + shared markdown/Monaco renderers for documents; JSON state + capabilities + sandboxed iframe for extensions). That split is intentional — the editing primitive and renderer genuinely differ.
  • Trust model for extensions. Capabilities run AI-authored JS in node:vm (no require/process, 1s timeout, 1 MB state cap) and the UI runs in <iframe sandbox="allow-scripts"> (no same-origin). This matches CoC's local single-user trust level (the same level as autopilot shell) — it prevents accidental host coupling and runaway loops, but it is not a hardened boundary against a hostile author. If this ever runs multi-tenant, the capability sandbox should move to a real isolate first. Documents run no AI-authored code.
  • Gating. When canvas.enabled is off, buildCanvasToolsAddon short-circuits to zero tools and no guidance prose, so there is no per-turn context cost; the tools are also filtered from the settings registry.
  • Steering reuses existing plumbing. Comments and selection edits go through the normal follow-up queue rather than a bespoke channel, so queued-message UI, cancellation, and persistence apply for free.

Testing

  • Server: canvas store (CRUD, revision conflict, edits, versions, comments, extension docs), the node:vm capability runner (sandbox isolation, timeout, load/throw/oversize errors), the REST routes (versions, comments, extension, capability invoke), and the three LLM tools.
  • SPA (jsdom): CanvasPanel (autosave, conflict/remote-update banners, version stepper + restore, selection → Ask AI, send-comments, code/Monaco, export, fullscreen, pop-out), the sandboxed-iframe ExtensionCanvasView postMessage protocol, PopOutCanvasShell route parsing + WS→liveEvent mapping, and RepoChatTab chat-list collapse.
  • All packages build clean (npm run build:packages); the registry/config fixtures and snapshots are updated.

Not included (deliberately deferred)

  • Work-item-backed plan canvases — needs a sync-ownership design decision (which side is source of truth) before building.
  • Gist export — needs gh credential plumbing.
  • Tightening excalidraw tool gating to match canvas's addon-level gate (noted as a follow-up; excalidraw currently relies on registry filtering).

🤖 Generated with Claude Code

plusplusoneplusplus and others added 8 commits June 12, 2026 18:07
…y AI and user)

Adds a GitHub-Copilot-app-style canvas: a live markdown document in a
resizable side panel next to the chat, edited by the AI through LLM tools
and by the user through revision-checked autosave. Gated by the new
canvas.enabled admin flag (default off).

- CanvasStore: file-based persistence under ~/.coc/repos/<ws>/canvases/
  with revision-checked updates and exact-match string edits
- REST: list/get/save canvas routes; 409 + current record on stale saves;
  canvas-updated WebSocket broadcast on user saves
- LLM tools: create_canvas / update_canvas / read_canvas (registry-listed,
  flag-gated in buildCanvasToolsAddon, emit canvas-updated SSE events)
- coc-client: CanvasesClient domain with typed contracts
- SPA: CanvasPanel (preview/edit, debounced autosave, conflict and
  remote-update banners) mounted in ChatDetail as a desktop resizable
  right column; useChatSSE surfaces canvas-updated events
- Tests: canvas store, LLM tools, routes (HTTP-level), CanvasPanel (jsdom),
  registry gating; config fixture/snapshot updates

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, anchored comments

Builds on the canvas side panel with the three phase-2 steering surfaces:

- Version snapshots: CanvasStore writes versions/<rev>.json on every
  persisted revision (capped at 50 most recent); new REST endpoints list
  version metadata and serve single snapshots. The panel's revision chip
  becomes a stepper — older revisions render read-only with a history
  banner and a "Restore as latest" action that saves the snapshot as a
  new revision (disabled while local edits are unsaved).
- Selection actions: selecting canvas text (preview or edit mode) shows
  an action bar. "Ask AI" prefills the chat composer with a prompt
  quoting the selection plus canvas id/revision; "Comment" opens an
  inline compose box.
- Anchored comments: stored in comments.json (open|sent|resolved) with
  REST CRUD; the panel lists them with a "Send N to AI" batch action
  that delivers one follow-up message via sendFollowUp(..., 'enqueue')
  — so a busy AI receives it at the next turn boundary — and then marks
  the comments sent.

coc-client gains listVersions/getVersion/listComments/addComment/
setCommentStatus/deleteComment. Tests cover snapshot pruning, comment
lifecycle, the new routes, the stepper/restore flow, selection → Ask AI
prompt, comment creation, and send-to-AI marking comments sent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Code canvas type: create_canvas accepts type 'markdown'|'code' plus a
  language hint (normalized and persisted on the descriptor). The panel
  shows a language chip, renders the preview as a fenced highlighted
  block, and uses the shared MonacoFileEditor in Edit mode with the same
  revision-checked debounced autosave. Tool guidance now points the AI
  at Mermaid blocks in markdown canvases for diagrams/charts.
- Export menu in the panel header: Copy content, Download file (extension
  derived from the language), and Save to Notes for markdown canvases
  (writes canvases/<slug>.md into the workspace Notes tree through the
  existing notes saveContent route, which creates files and parents).

Deferred from the phase 3 roadmap: work-item-backed plan canvases (needs
a sync-ownership design) and gist export.

Tests cover code-canvas creation/normalization at the store and tool
layers, fenced preview + Monaco editing + autosave, export copy, and
save-to-Notes including its absence for code canvases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds the GitHub-Copilot-app "custom canvas" model: interactive panels
(kanban boards, checklists, dashboards) backed by JSON shared state that
both the AI and the user mutate through declared capabilities.

Server:
- canvas-store: type 'extension'; extension/{manifest.json,ui.html,
  capabilities.js} documents; getExtension/saveExtension (saveExtension
  bumps the revision so open panels reload).
- canvas-capability-runner: runs a declared capability as a pure
  (state, params) => nextState transform in a node:vm sandbox — no
  require/process, 1s wall-clock timeout, 1 MB state cap. Matches the
  local trust model (same level as autopilot shell), not a hard boundary.
- routes: GET .../extension and POST .../capabilities/:name (revision-
  checked write, canvas-updated WS broadcast + SSE emit; 422 on
  capability error, 409 on concurrent edit).

Tools (gated by canvas.enabled, in CANVAS_LLM_TOOL_NAMES):
- create_or_update_extension_canvas authors the manifest + UI +
  capabilities; invoke_canvas_capability runs one; read_canvas now
  returns the manifest for extension canvases.

SPA:
- ExtensionCanvasView renders ui.html in an <iframe sandbox="allow-scripts">
  with an injected window.CanvasHost bridge (onState/invoke/setState) over
  postMessage. Human UI actions go through the same capability/save REST
  gate as AI calls. CanvasPanel routes extension canvases to it in preview
  mode and shows the raw JSON state in edit mode.

coc-client: getExtension/invokeCapability + contracts.

Tests cover vm sandbox isolation/timeout/errors, extension store + routes
+ tools, the iframe postMessage protocol, and panel routing. Docs updated
(AGENTS.md invariant, llm-tools/rest-api/dashboard-spa references).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…column, fullscreen

Optimizes the activity + canvas layout per three asks:

1. Collapsible chat list: the desktop activity split (RepoChatTab) can
   collapse the left chat-list panel to a thin "Chats" rail via a hover
   affordance on the resize handle; the choice persists in
   localStorage['activity-list-collapsed'].

2. Full-height canvas right panel: ChatDetail now wraps the conversation
   and follow-up composer in a left column and mounts the canvas as a
   full-height sibling column of a top-level split, so the panel spans the
   whole detail pane height (beside the composer) like a dedicated right
   panel instead of only the conversation height. Canvas default width
   widened (520, max 1100) and stays resizable.

3. Fullscreen canvas: CanvasPanel header gains an expand toggle that
   re-renders the panel as a fixed inset-0 overlay covering the viewport
   (Esc exits). ChatDetail collapses the in-flow canvas column to 0 width
   while fullscreen so the conversation reclaims the space.

Tests: CanvasPanel fullscreen toggle + Escape; RepoChatTab collapse/expand
+ persistence. Docs (dashboard-spa) updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three canvas UX improvements:

1. Reopen a closed canvas: closing the panel no longer detaches it —
   ChatDetail keeps a thin right-side rail (mirroring the chat-list
   collapse rail) with a reopen button, so a linked canvas stays
   reachable.

2. CoC-native header buttons: the canvas header reuses the shared
   ICON_BTN style from ChatHeader (26x26 rounded, #848484, hover bg) with
   SVG icons for pop-out/fullscreen/close, and the Preview/Edit segmented
   control + Export use standard CoC tokens instead of ad-hoc styling.

3. Pop-out window: a header pop-out button opens the canvas in a
   standalone window via the established popout-shell pattern
   (PopOutCanvasShell + entry.tsx #popout/canvas route). The window maps
   the global WebSocket canvas-updated event into the panel's liveEvent
   and refetches on focus (reloadNonce) to pick up AI tool edits that
   stream over the chat SSE channel.

Tests: pop-out button visibility/click, reloadNonce refetch,
parsePopOutCanvasRoute, and the shell's WS→liveEvent mapping. Docs
(dashboard-spa) updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The canvas feature shipped 5 tools whose schemas are sent to the model on
every turn when canvas.enabled, consuming significant context. Consolidate
to 3 and trim descriptions/guidance:

- write_canvas — create (omit canvasId) or update a markdown/code canvas
  (merges create_canvas + update_canvas).
- read_canvas — unchanged behavior, trimmed description.
- extension_canvas — BUILD (manifest/ui/capabilities) or RUN (canvasId +
  capability + params) an extension canvas, dispatched by the presence of
  `capability` (merges create_or_update_extension_canvas +
  invoke_canvas_capability).

The doc canvas tool keeps a lean schema (no extension fields); all
extension complexity is isolated in one tool. Registry, CANVAS_LLM_TOOL_NAMES,
the addon guidance prose, and user-facing tool-name references (capability
runner error, comments-to-AI prompt) updated. REST routes, store, and the
iframe capability path are unchanged. Tests + docs updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…g tests

ChatDetail now calls isCanvasEnabled() from utils/config. Two existing
tests that render real ChatDetail mock utils/config with an explicit
factory, so the missing export threw "No isCanvasEnabled export is defined
on the mock" (coc-test shards 2 & 4). Add the export to both mocks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@plusplusoneplusplus plusplusoneplusplus enabled auto-merge (squash) June 13, 2026 14:23
@plusplusoneplusplus plusplusoneplusplus merged commit 2e4b2c3 into main Jun 13, 2026
36 checks passed
@plusplusoneplusplus plusplusoneplusplus deleted the pr/coc-chat-canvas branch June 13, 2026 14:35
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