Files
kandev/docs/specs/canvases/system-design/collaborative-canvases.md
T
2026-09-05 18:44:43 +00:00

22 KiB

id, title, status, system, owners, created, last_updated, requirements
id title status system owners created last_updated requirements
canvases-collaborative-canvases-design Collaborative canvases system design superseded canvases
canvases
2026-08-25 2026-08-30
REQ-CANVASES-COLLABORATIVE-CANVASES-001
REQ-CANVASES-COLLABORATIVE-CANVASES-002
REQ-CANVASES-COLLABORATIVE-CANVASES-003
REQ-CANVASES-COLLABORATIVE-CANVASES-004
REQ-CANVASES-COLLABORATIVE-CANVASES-005
REQ-CANVASES-COLLABORATIVE-CANVASES-006
REQ-CANVASES-COLLABORATIVE-CANVASES-007
REQ-CANVASES-COLLABORATIVE-CANVASES-008
REQ-CANVASES-COLLABORATIVE-CANVASES-009
REQ-CANVASES-COLLABORATIVE-CANVASES-010
REQ-CANVASES-COLLABORATIVE-CANVASES-011

Collaborative canvases system design

This design is not the current canvas design. It is replaced by Agent-authored web-app canvases.

Its blank native-canvas dialog, import flow, and prohibition on sidebar creation actions are historical. They do not constrain the replacement design.

Design summary

Kandev stores each canvas in one workspace as an owner-only snapshot. A canvas can link to several tasks in that workspace and appear in each task's Dockview.

Human controls and agent tools call one typed command service. The web client shows a closed schema with native components. It never executes canvas code.

Version 1 transfers canvases through a single inert JSON file. An import always creates an independent canvas. Version 1 has no user sharing, invitations, collaborator roles, presence, or live instance federation.

This design follows the server-owned declarative canvas decision.

System boundary

Add apps/backend/internal/canvas/ as the owner of canvas storage, workspace scope, task links, actions, events, leases, export, and import. Other systems can store a canvas identifier, but they do not store canvas blocks.

The initial adapter boundary is:

HTTP handlers ---------+
WebSocket gateway -----+--> canvas access + command service --> repository
Kandev MCP tools ------+                |
                                        +--> export/import codec
                                        +--> ordered owner events

The owner client receives agent changes through the WebSocket gateway. No other user can subscribe to the canvas.

This maps REQ-CANVASES-COLLABORATIVE-CANVASES-001, REQ-CANVASES-COLLABORATIVE-CANVASES-003, REQ-CANVASES-COLLABORATIVE-CANVASES-005, and REQ-CANVASES-COLLABORATIVE-CANVASES-008.

Persistence

Use five canvas-owned tables.

canvases

  • id
  • owner_user_id
  • workspace_id
  • title
  • schema_version
  • revision
  • compacted_through_revision
  • nullable source_export_id
  • nullable imported_at
  • nullable archived_at
  • created_at and updated_at
  • canvas_id
  • task_id
  • linked_by_user_id
  • created_at

The primary key is (canvas_id, task_id). A service check requires both records to use the same workspace. Task removal cascades only to this table. The canvas remains.

canvas_blocks

  • canvas_id
  • block_id
  • block_type
  • position
  • state_json
  • block_revision
  • timestamps

The primary key is (canvas_id, block_id). A stable sortable position avoids a full rewrite after one block move.

canvas_events

  • canvas_id
  • revision
  • unique command_id
  • actor_kind as human or agent
  • nullable actor_user_id
  • nullable actor_session_id
  • action
  • nullable target_block_id
  • bounded payload_json
  • created_at

The primary key is (canvas_id, revision).

canvas_command_receipts

  • command_id
  • canvas_id
  • normalized result
  • resulting revision
  • created_at

Receipts preserve idempotency after event compaction. Canvas removal deletes all canvas-owned rows. An import creates one canvas and its blocks in one transaction. It does not create events from the source file.

This maps REQ-CANVASES-COLLABORATIVE-CANVASES-001, REQ-CANVASES-COLLABORATIVE-CANVASES-003, REQ-CANVASES-COLLABORATIVE-CANVASES-006, and REQ-CANVASES-COLLABORATIVE-CANVASES-011.

Block and action schema

The backend owns one versioned registry.

Block State Main actions
Markdown sanitized Markdown source replace content
Checklist ordered items with stable IDs add, edit, toggle, move, remove
Kanban columns and cards with stable IDs add, edit, move, remove
Metrics named values with units and status set, remove, reorder
Timeline ordered events with time and status add, edit, move, remove

The registry validates all state before a write or import transaction. Version 1 uses these limits:

  • 100 active canvases per owner
  • 50 blocks per canvas
  • 500 structured items per canvas
  • 256 KiB of current block state per canvas
  • 32 KiB per command input
  • 512 KiB per .kandev-canvas file
  • 200 characters per title or item label
  • 1,000 retained events after compaction

One typed backend contract owns these limits. HTTP, WebSocket, MCP, export, and import tests use the same values.

This maps REQ-CANVASES-COLLABORATIVE-CANVASES-002, REQ-CANVASES-COLLABORATIVE-CANVASES-006, REQ-CANVASES-COLLABORATIVE-CANVASES-010, and REQ-CANVASES-COLLABORATIVE-CANVASES-011.

Command transaction

The canonical command is:

{
  "command_id": "uuid",
  "canvas_id": "uuid",
  "base_revision": 41,
  "action": "checklist.toggle",
  "target_block_id": "uuid",
  "input": { "item_id": "uuid", "completed": true }
}

The service does these steps in one transaction:

  1. Load the canvas and trusted actor context.
  2. Authorize the owner, task agent, or Office agent.
  3. Return the stored receipt for a duplicate command.
  4. Validate the action, input, limits, and preconditions.
  5. Apply the typed change to the block snapshot.
  6. Increment the canvas and block revisions.
  7. Append the attributed event and command receipt.
  8. Commit, then publish to the owner's authorized subscribers.

A failed transaction publishes nothing.

Structured actions contain item preconditions. A stale canvas revision is valid when the target item still matches. Otherwise, the service returns canvas_conflict with the current block and revisions.

A Markdown replacement requires the current block revision and a lease. The bounded lease manager keys each lease by canvas, block, and actor. A lease lasts 30 seconds and can renew. A restart removes all leases safely.

This maps REQ-CANVASES-COLLABORATIVE-CANVASES-003 and REQ-CANVASES-COLLABORATIVE-CANVASES-004.

Portable file contract

Version 1 uses one UTF-8 JSON file with the .kandev-canvas extension. It is not a ZIP file because all version 1 blocks contain bounded structured data.

The top-level contract is:

{
  "format": "kandev.canvas",
  "format_version": 1,
  "export_id": "uuid",
  "exported_at": "2026-08-26T12:00:00Z",
  "canvas": {
    "title": "Release readiness",
    "schema_version": 1,
    "blocks": []
  }
}

The codec emits fields in a deterministic order. The file contains no internal canvas ID, block ID, user ID, task link, event, actor, session, server address, repository data, file content, or credential. Each exported block uses a portable position and typed state without its internal identifier.

The import codec must parse with unknown-field rejection. It validates the format version, schema version, block registry, text encoding, and all limits. It completes validation before it starts the transaction.

The importer creates new canvas and block identifiers. It records export_id as source_export_id for local provenance. It assigns the current user and selected workspace. The user can select a same-workspace task link before the transaction.

Each import creates a new fork, even when source_export_id already exists. There is no update, merge, refresh, or synchronization action in version 1.

This maps REQ-CANVASES-COLLABORATIVE-CANVASES-006 and REQ-CANVASES-COLLABORATIVE-CANVASES-010.

HTTP API

Add /api/v1/canvases endpoints for:

  • workspace-scoped list, create, fetch, rename, archive, restore, and remove
  • task link list, create, and remove
  • block list and initial snapshot
  • Markdown lease acquire, renew, and release
  • GET /api/v1/canvases/{id}/export
  • POST /api/v1/canvases/import with a bounded file and optional task ID

The export response uses a safe file name and application/vnd.kandev.canvas+json. The import response returns the new canvas ID and direct route. Both endpoints require the current owner identity.

This maps REQ-CANVASES-COLLABORATIVE-CANVASES-001, REQ-CANVASES-COLLABORATIVE-CANVASES-006, REQ-CANVASES-COLLABORATIVE-CANVASES-007, and REQ-CANVASES-COLLABORATIVE-CANVASES-010.

Access policy

Version 1 has one canvas role: owner.

  • The authenticated workspace owner can read, change, export, link, archive, and remove the canvas.
  • The synthetic user has the same capability when authentication is disabled.
  • A task agent can read or change only canvases linked to its trusted task.
  • An Office agent can use canvases in its trusted workspace context.
  • Another Kandev user has no canvas access.
  • Import creates a new canvas in the selected owned workspace.

The canvas service uses the task system's access decision for every task-link operation. Canvas ownership never grants access to a linked task or repository.

This maps REQ-CANVASES-COLLABORATIVE-CANVASES-007 and REQ-CANVASES-COLLABORATIVE-CANVASES-008.

WebSocket protocol

Extend the gateway with a canvas subscription map and an authorization callback. Do not route canvas traffic through workspace broadcasts.

Client actions:

  • canvas.subscribe with canvas_id and after_revision
  • canvas.unsubscribe
  • canvas.command

Server events:

  • canvas.snapshot
  • canvas.event
  • canvas.lease
  • canvas.error with a stable code and safe recovery data

Subscription authorization requires the current owner. Every publish path repeats the access decision as a backstop. Disconnect removes leases owned by that socket. Version 1 has no presence or focus messages.

The server replays events after after_revision when the event range exists. Otherwise, it sends a snapshot. Clients apply only the next revision and request recovery after a gap.

This maps REQ-CANVASES-COLLABORATIVE-CANVASES-005 and REQ-CANVASES-COLLABORATIVE-CANVASES-011.

Agent tools

Add a canvas MCP profile group for Task and Office task surfaces:

  • list_canvases_kandev
  • create_canvas_kandev
  • get_canvas_kandev
  • apply_canvas_action_kandev

The MCP server derives the actor from its backend connection. Tool schemas do not expose another actor, export, import, task unlink, canvas removal, arbitrary URL, or raw code input.

Task sessions list only canvases linked to their task. A task-created canvas links to that task. Office sessions list canvases for their trusted workspace.

This maps REQ-CANVASES-COLLABORATIVE-CANVASES-007 and REQ-CANVASES-COLLABORATIVE-CANVASES-008.

Desktop information architecture

Add Canvases as a first-party workspace sidebar section. Model it on AutomationsSection and IntegrationsSection. The section starts folded, shows the workspace canvas count, and fetches rows only for the active workspace.

The section header toggles the rows. A persistent header shortcut opens the workspace Canvases settings page. Each row opens the canvas direct route. The sidebar contains no creation or import actions.

An empty section shows Set up a canvas. This row opens the workspace Canvases settings page.

Routes:

  • /settings/workspaces/:workspaceId/canvases manages workspace canvases
  • /canvases/:canvasId shows the focused canvas

Add canvases to WORKSPACE_SETTINGS_TABS. The settings tree and workspace tab strip derive the row from that catalog. The workspace page follows the Automations list page and Integrations index page patterns.

The task surface registers a first-party canvas Dockview component. Opening a linked canvas reuses the same canvas surface and store as the direct route.

Sidebar preview

+----------------------------+
| KANDEV                     |
|                            |
| [ ] Home                   |
| [ ] Tasks                  |
|                            |
| [#] Canvases v      3  [=] |
|     Release readiness      |
|     Incident 482           |
|     API migration          |
|                            |
| [ ] Stats                  |
| [ ] Settings               |
+----------------------------+

The number is the workspace canvas count. The [=] header shortcut opens workspace settings. The expanded list uses the Automations row density. Each row opens the direct route.

Workspace settings preview

+--------------------------------------------------------------+
| Workspace settings > Main workspace > Canvases              |
+--------------------------------------------------------------+
| Canvases                                                     |
| Create, import, and manage canvases for this workspace.      |
|                                      [Import] [New canvas]    |
+--------------------------------------------------------------+
| Search [_______________________________________________]      |
|                                                              |
| Name                 Linked tasks     Updated        Actions |
| Release readiness    Prepare release  4 minutes ago     [...] |
| Incident 482         2 tasks          Yesterday         [...] |
| API migration        No tasks         12 Aug            [...] |
+--------------------------------------------------------------+

The page owns creation and import. Row actions contain Open, Export, Archive, and Remove. The table becomes a vertical card list on phones.

New canvas preview

+------------------------------------------------+
| New canvas                                  [x] |
|                                                |
| Title                                          |
| [ Release readiness                         ]  |
|                                                |
| Link to task (optional)                        |
| [ No task                                   v] |
|                                                |
| The canvas also appears in the Canvases list.  |
|                                                |
|                    [Cancel] [Create canvas]     |
+------------------------------------------------+

The workspace settings action leaves the task option empty. A task Dockview action selects the current task by default. The dialog creates a blank canvas. Users add the first block after creation. Version 1 does not include templates.

Dockview panel preview

+---------------- Task workbench -------------------------------+
| [Agent] [Changes] [Files] [Canvas: Release readiness]      [x] |
+---------------------------------------------------------------+
| Release readiness             Saved at 12:44   [Open] [Export] |
| Linked task: Prepare release 0.52                    [...]     |
+----------------+----------------------------------------------+
| Blocks         | Checklist: Release gates             [+ Add] |
|                |                                              |
| > Release gates| [x] Backend tests                            |
|   Metrics      | [x] Web typecheck                            |
|   Timeline     | [ ] Desktop package                          |
|                | [ ] Release notes                            |
| + Add block    |                                              |
|                | Agent updated this block at 12:43            |
+----------------+----------------------------------------------+

The panel header contains Open full canvas, Export, and an overflow menu. The overflow menu contains Rename, task links, Archive, and Remove. The canvas body owns internal scrolling.

Import interaction

The Import action on the workspace Canvases settings page opens a file picker for .kandev-canvas. Kandev parses the file before it shows the preview.

The preview shows title, block count, block types, format version, file size, and an optional task link. It does not render block content before full validation. The primary action is Import as new canvas.

A successful import opens the new direct route. The interface states that the new canvas is an independent copy with no synchronization.

This maps REQ-CANVASES-COLLABORATIVE-CANVASES-006 and REQ-CANVASES-COLLABORATIVE-CANVASES-009.

Mobile design contract

  • Outcome and entry: users open the workspace Canvases settings page from mobile settings navigation. A task can open the focused canvas route.
  • Exemplars: use task-layout.tsx for full-height ownership. Use mobile-picker-sheet.tsx for the block and action drawer. Use kanban-with-preview.tsx for direct detail navigation.
  • Hierarchy: show the canvas title, save state, focused block, and primary block action. Put secondary canvas actions in an overflow control.
  • Surface: use a direct h-dvh route. Do not mount Dockview on a phone.
  • Navigation: use vertical cards on the workspace page. Keep New canvas and Import visible as 44-pixel actions. Use an inset drawer for editor blocks, task links, export, archive, and remove.
  • Geometry: use 44 by 44 CSS pixel action targets and safe-area padding. Use one internal vertical scroll owner. Prevent page-level horizontal scroll.
  • Dense content: show one Kanban column at a time. Wrap metrics. Keep checklist and timeline content vertical.

Desktop and mobile use the same API, store, command hooks, file codec responses, and access decisions. All user copy uses translation keys in every required locale.

This maps REQ-CANVASES-COLLABORATIVE-CANVASES-009.

Compaction and recovery

After a command leaves more than 1,000 events, a bounded operation removes the oldest events and advances compacted_through_revision. The block rows already contain the current snapshot.

A client older than the compaction revision receives the full snapshot. A client inside the retained range receives ordered events. Command receipts preserve idempotency after event removal.

This maps REQ-CANVASES-COLLABORATIVE-CANVASES-003, REQ-CANVASES-COLLABORATIVE-CANVASES-005, and REQ-CANVASES-COLLABORATIVE-CANVASES-011.

Observability

Add content-free metrics for:

  • command result
  • event or snapshot recovery
  • active owner subscriptions
  • lease result
  • export, import, and rejected-import results

Logs can include canvas ID, command ID, action, revision, actor kind, format version, file size, and a safe error code. They must omit titles, block state, Markdown, task data, file content, and user-supplied file names.

This maps REQ-CANVASES-COLLABORATIVE-CANVASES-010 and REQ-CANVASES-COLLABORATIVE-CANVASES-011.

Failure handling

  • Duplicate commands return the stored receipt.
  • Conflicts return the current target state and revisions.
  • A busy Markdown block returns safe lease state.
  • A lost connection leaves local state visible and marks it offline.
  • Editing remains disabled until recovery is complete.
  • A non-owner request returns one generic forbidden error.
  • An invalid import returns one safe reason and creates no rows.
  • An unknown schema version shows an unsupported-state screen.
  • Export failure does not change canvas state.

Test strategy

  • Repository tests cover migrations, task links, removal, compaction, receipts, and database replay.
  • Service tests cover block actions, limits, owner access, task-agent access, conflicts, leases, export, and atomic import.
  • Codec tests cover deterministic output, excluded fields, new identifiers, repeated import, unknown fields, versions, and limits.
  • Gateway tests cover owner authorization, event order, replay, snapshots, and lease cleanup.
  • MCP tests cover trusted actor context and the absence of portability tools.
  • Frontend tests cover the folded sidebar, settings catalog, workspace page, creation, import preview, recovery, conflicts, Dockview, and mobile layout.
  • Desktop Playwright covers workspace settings, creation, task links, Dockview, agent updates, export, import, reload, restart, and archive behavior.
  • Mobile Playwright covers navigation, creation, import, block selection, export, 44-pixel targets, safe areas, and zero horizontal overflow.

Requirement traceability

Requirement Main design sections
REQ-CANVASES-COLLABORATIVE-CANVASES-001 Persistence, HTTP API, Desktop information architecture
REQ-CANVASES-COLLABORATIVE-CANVASES-002 Block and action schema
REQ-CANVASES-COLLABORATIVE-CANVASES-003 Persistence, Command transaction, Compaction and recovery
REQ-CANVASES-COLLABORATIVE-CANVASES-004 Command transaction, Failure handling
REQ-CANVASES-COLLABORATIVE-CANVASES-005 WebSocket protocol, Compaction and recovery
REQ-CANVASES-COLLABORATIVE-CANVASES-006 Portable file contract, HTTP API, Import interaction
REQ-CANVASES-COLLABORATIVE-CANVASES-007 Access policy, Agent tools
REQ-CANVASES-COLLABORATIVE-CANVASES-008 Access policy, Agent tools
REQ-CANVASES-COLLABORATIVE-CANVASES-009 Desktop information architecture, Import interaction, Mobile design contract
REQ-CANVASES-COLLABORATIVE-CANVASES-010 Block and action schema, Portable file contract, Observability
REQ-CANVASES-COLLABORATIVE-CANVASES-011 Persistence, WebSocket protocol, Compaction and recovery, Observability