Documentation source
Whiteboard capture to structured records
Convert canvas notes/sketches into entities, views, actions
## Problem
Users sketch ideas, plan ops, and draft SOPs on the canvas surface (Excalidraw block + the in-view `canvas-engine` shipped via PR #914). Today a single right-click "Convert to record" pulls one shape's text through the AI capture parser and creates an entity — but the canvas-side artifact retains only a thin `_materializedFrom` diagnostic stamp on the swapped node. There is no first-class, query-able relation tying the new entity back to the canvas/shape it came from, no way to capture a multi-shape group or freehand region as a single coherent record with extracted children, and no whiteboard surface separate from the per-view inline canvas. Sketches stay visual; structured platform data and the originating drawing drift apart the moment the dialog closes.
Design reference PKG-034/035 ("Sprinter Views Canvas Redesign Pack" — `whiteboard.jsx`, `design-canvas.jsx`) shows the intended end state: a full-bleed whiteboard with a block palette, an agent rail, sticky notes, regions/groups, and inline records — where every captured artifact carries a visible chip back to the records it produced, and the agent rail can reason over "scope: 4 blocks · 2 stickies · 1 embed" because the link graph is real, not a stamp.
## Solution
Treat the whiteboard as the **structured-input front-door** to the entity graph. Add a "Capture to record" affordance at three granularities — single shape, multi-shape selection, and frame/group/region — that:
1. Runs the existing `parseCapture` pipeline over the shape(s)' text + optional OCR/ink-transcript payload.
2. Opens a schema-driven preview dialog (the existing `MaterializePopover`, extended) with entity-type picker, prefilled fields, suggested relations to siblings on the canvas, and (for group/region capture) a list of child records to create as a hierarchy.
3. On submit: creates the entity (or entities), creates a `captured-from-canvas` relation per record, and updates the canvas shape(s) to display a small chip showing the linked record(s) with click-to-open.
The whiteboard itself ships as a tenant-scoped view-surface (`surface_type: whiteboard`) so a board can live at a stable URL (`/v/wb_central_ops`), be embedded in other views, and be the target of `useAction("openRecord")` from an agent-authored module.
## Design
### Architecture
```
canvas-shape (single | selection | region)
↓
captureFromCanvas() — server action in features/canvas/server/capture.ts
↓
parseCapture() ──► AI capture pipeline (existing)
↓
CaptureToRecordDialog — preview + per-record review
↓
createEntity() ×N (existing) + createRelation() ×N (new wiring)
↓
canvas-shape ← chip(s) showing linked record(s)
```
No new tables. One new platform relation type — `captured-from-canvas` — registered in the same place existing relations live (`features/entities/relations/types.ts`). The relation carries `{ canvas_id, shape_id, captured_at }` in `metadata` so bidirectional navigation works without an extra join table.
Canvas-side chip rendering hangs off `EntitySlot` (existing) using a new tenant-agnostic slot `entity-card:canvas-chip` resolved through the standard 4-tier resolver (`code-locked` policy — DB overrides not appropriate for this affordance).
### API
```ts
// features/canvas/server/capture.ts
captureFromCanvas(input: {
canvasId: string; // view or whiteboard surface id
shapeIds: string[]; // 1..N — single, selection, or region members
scope: "shape" | "selection" | "region";
forceEntityTypeSlug?: string; // skip AI type detection
rasterPng?: string; // optional — ink/sketch payload for OCR
}): Promise<CaptureResult>;
interface CaptureResult {
records: Array<{ entityId: string; entityTypeSlug: string; title: string }>;
relations: Array<{ relationId: string; entityId: string; shapeId: string }>;
parentChildPairs?: Array<{ parentId: string; childId: string }>; // region/group only
}
```
The dialog's UI surface is `features/canvas/components/capture-to-record-dialog.tsx` — a thin extension of the existing `MaterializePopover` that adds: per-record review for batch/region captures, suggested-relation pickers populated from neighboring shapes already linked to records, and a "create as children of" affordance for region/group capture against hierarchical entity types.
### Whiteboard surface
A new `surface_type: whiteboard` joins the existing surface catalog (`grid`, `sequence`, `slides`, `page`, `form`, `canvas`) — `whiteboard` is `canvas` with a default-large viewport, the block palette and agent rail visible by default, and the ability to host inline `entity-list` / `entity-card` blocks as first-class shapes. The shipped `canvas-engine` is the renderer; `whiteboard` is its tenant-facing wrapper.
### Trade-offs
- **Fidelity** — capture stores extracted text + a single rasterized snapshot per shape; we do **not** persist the vector shape data inside the entity. The canvas remains the source of truth for the drawing; the entity stores the meaning. Users who edit the shape later see "captured 14m ago — refresh capture?" prompt; a re-capture creates a new `entity_responses` row, not a new entity.
- **Performance** — region capture on >25 shapes is gated to "send as background action" (the existing `action-tick` worker) rather than blocking the dialog. Single-shape and small-selection captures stay synchronous to preserve the existing fast-path UX.
- **Undo behavior** — a captured shape's chip survives an `undo` of the capture dialog itself, because the entity has been written. Undo deletes the relation only; the entity persists and is reachable by URL. A second-level "undo capture (delete record too)" lives in the chip context menu, gated 24h, mirroring the existing `ClearCanvasDialog` semantics.
- **Duplicate-capture** — the same shape captured twice creates two entities by default; the dialog warns ("This shape is already linked to opportunity_q3_HVAC. Capture again, or open existing?") and offers "open" as the primary action. Re-capture into the **same** entity is a `submitResponse`-style update path against the existing record's fields, not a new create.
- **Whiteboard as a view-surface vs a separate primitive** — the design ref shows whiteboard as its own thing (Block palette, persistence, members). We model it as `surface_type: whiteboard` inside the existing view system rather than a new top-level primitive, to keep the surface catalog flat and reuse permissions, sharing, embedding, and standalone-link infrastructure already built for views.
## Acceptance criteria
- User can right-click a single canvas shape and select **Convert to record**. The dialog opens with extracted text prefilled, an entity-type picker (defaulting to `AI detect`), and a Create button.
- User can multi-select shapes (lasso or shift-click) and use the same right-click → **Convert N shapes to records**. The dialog shows per-shape preview rows; submit creates N records, each with its own `captured-from-canvas` relation.
- User can capture a frame/group/region. Dialog offers a **single parent** mode (capture region as one record with N child records) or **flat batch** mode. Hierarchical entity types receive the parent/child wiring via the existing `parent_id` column; non-hierarchical types receive sibling-of-sibling relations.
- Every captured shape gains a small chip showing the linked record's type icon + title. Click opens the record. Hover shows a peek-card.
- Every captured record's detail page shows a "Captured from canvas" section with a thumbnail + link back to the canvas at the originating shape's viewport.
- A new view with `surface_type: whiteboard` renders the full-bleed canvas-engine with block palette and agent rail visible by default; it is reachable at `/v/{slug}` and works as a standalone shareable surface.
- Permissions: capture requires the same write permission as creating the target entity type. Capture into a private/team-only entity type from a tenant-public whiteboard is allowed; the record's visibility follows the type's default, not the whiteboard's.
- Realtime: a second user viewing the same whiteboard sees the chip appear within 2s of the first user's capture. The `captured-from-canvas` relation publishes to the same `tenant:{tenantId}:relations:{entityId}` channel the existing relation system uses.
## Files
- `content/docs/roadmap/specs/whiteboard-capture-to-records.mdx` — this spec.
- `features/canvas/server/capture.ts` — `captureFromCanvas()` server action; orchestrates `parseCapture` → `createEntity` → `createRelation`.
- `features/canvas/components/capture-to-record-dialog.tsx` — extension of `materialize-popover.tsx` with per-record review and region/group hierarchical mode.
- `features/entities/server/actions.ts` — extend to register the `captured-from-canvas` relation type and accept the `canvas_id` / `shape_id` metadata on creation.
- `features/entities/relations/types.ts` — register `captured-from-canvas` in the platform relation catalog.
- `features/blocks/components/canvas/canvas-engine.tsx` (touch) — render the chip overlay for materialized shapes from the new relation, not just the `_materializedFrom` diagnostic stamp.
- `features/views/surface-types.ts` (touch) — register `whiteboard` surface type.
- `supabase/migrations/` — optional index on `entity_relations(metadata->>canvas_id, metadata->>shape_id)` for the reverse lookup; no schema migration required.
## References
- Design refs: `~/SprinterVault/20-Ventures/Amble/03-Product/design-references/claude-design-docs-20260519/extracted/Sprinter Views Canvas Redesign Pack/whiteboard.jsx` and `design-canvas.jsx` (PKG-034/035).
- Shipped foundation: PR #914 — generic right-click node materialization primitive (`features/blocks/components/canvas/node-materialization.ts`, `materialize-popover.tsx`).
- Related work: `documents/work/2026-04-23-sop-canvas-to-execution/` (SOP-canvas program), `documents/work/2026-04-30-view-context-canvas-tools/`, `documents/work/2026-05-18-portfolio-canvas-block/`.
- Canvas-view-design skill: `.claude/skills/canvas-view-design/SKILL.md` — node-type catalog and layout heuristics.
- Capture pipeline: `content/docs/features/capture.mdx` — `parseCapture` / `createFromCapture` flow this spec composes with.
- View surfaces: `content/docs/features/view-system.mdx` — where `whiteboard` registers.
- Related ADRs: ADR-0006 (FormSpec / declarative inputs), ADR-0018 (4-tier slot resolver — the chip slot rides on this), ADR-0020 (module-runtime — agent-authored extensions to the whiteboard).