<!-- source: openspec/current/studio-workspace/entities.md -->

---
title: Studio Workspace — Entities
status: stable
owner: pavel
audience: [pavel, ai, engineer]
tldr: Local data entities for the workspace — CanvasSession, UserSettings, and more. Cross-references db for canonical schema.
---
# Studio Workspace — entities

> Local data entities owned by this capability.
> Canonical schema (columns, indexes, RLS, migrations) lives in `db/entities.md`.

---

> **RAP-1170 Wave 1 (2026-05-22) — rename.** `canvas_sessions` →
> `ideas` (with the spec / project hierarchy layered above).
> `canvas_messages` → `messages`. `canvas_files` → `files`.
> `canvas_invitees` → `spec_invitees` (scope moved one level up
> from idea to spec). The `Idea` entity in the new model carries
> Wave 3 lifecycle fields (`status` `idea_status` enum, `parent_id`
> self-FK for the epic + child pattern). See `db/entities.md §
> Project → spec → idea hierarchy` for the canonical catalog. The
> `CanvasSession` section below is preserved for archival reference
> — engineers writing new code should consume `ideas` (the
> post-rename table) directly via `packages/db/src/types.ts`.

---

## CanvasSession (`canvas_sessions`)

The primary entity of the workspace. Represents a single OpenSpec authoring document (conversation + file tree + stage tracking).

**Key fields:**
- `id` — UUID PK
- `mode` — `draft | project | legacy_promoted` — tracks file-lifecycle stage
- `status` — `empty | drafting | promoted | archived` — canvas authoring state (orthogonal to `mode`)
- `stage` — six-value enum `intake | discovery | concept | design | tasks | build_ready` — lifecycle stage computed by `recomputeStageAction`
- `stage_percent` — 0–100, computed from lifecycle YAML checks
- `project_slug` — optional FK to `projects.slug`; links canvas to a project engagement
- `github_branch` / `github_repo_full_name` / `converted_to_project_at` / `base_sha_at_conversion` — project-mode GitHub fields
- `cost_cents` — accumulated cost vs. $5 cap
- `wizard_state` — JSONB `WizardState` (problem_statement, proposed_solution, target_audience, industry, timeline, budget)

Cross-reference: `db/entities.md § Canvas access tables`, `db/entities.md § Canvas lifecycle stage columns`, `db/entities.md § canvas_sessions.wizard_state`

---

## UserSettings (`user_settings`)

Per-user preference store backing the `/settings` Workspace preferences card and the canvas chat-width toggle.

**Shape (JSONB columns):**
- `workflow.defaultPersona` — 5-value Muse persona enum
- `workflow.defaultGithubRepo` — default repo for project conversion
- `layout.canvasChatWidth` — `compact | normal | wide`
- `appearance.locale` — `auto | en | ru | ro` (dormant until `apps/studio` adopts next-intl)

Cross-reference: `db/entities.md § User settings`

---

## CanvasIdea (`canvas_ideas`)

Sticky-note items on the idea-capture board view. Spatial canvas of free-form thoughts distinct from the temporal chat surface.

**Key fields:**
- `canvas_id` FK → `canvas_sessions(id)` CASCADE
- `content` (1–2000 chars)
- `x` / `y` integer in `[0, 4096]`
- `color` — `neutral | amber | rose | sage | blueprint | terracotta`
- `cluster_id` — free-form label (1–80 chars)
- `created_by` FK → `auth.users(id)` RESTRICT

Cross-reference: `db/entities.md § canvas_ideas`

---

## CanvasFile (`canvas_files`)

Per-canvas file tree — every emitted file (proposal/design/tasks, Mirror perception, research, methodology paths).

**Key fields:**
- `canvas_id` FK → `canvas_sessions(id)`
- `path` — string key (e.g. `openspec/changes/<slug>/proposal.md`)
- `content` — file body
- `is_pending` — boolean; pending until first GitHub save or draft save action
- `base_content` / `base_sha` — project-mode GitHub anchors for diff/conflict detection
- `source_canvas_id` — non-null when file was copied from another canvas

Cross-reference: `db/entities.md § Canvas files table`

---

## CanvasStageEvent (`canvas_stage_events`)

Audit trail of every stage / percent transition. Drives the Realtime broadcast consumed by `useCanvasStage` in the progress view.

**Key fields:**
- `canvas_id`, `prev_stage`, `next_stage`, `prev_percent`, `next_percent`
- `triggered_by_action` — string identifying the upstream entry point (e.g. `send_message`, `save_artifact`, `mirror_write`)

Cross-reference: `db/entities.md § Canvas lifecycle stage columns`

---

## ProjectConnection (`project_connections`)

Binding between a `projects` row and an external service (GitHub repo, Linear team). Registry-driven — each kind (github, linear) is a TS file under `packages/connections/src/kinds/<slug>.tsx`.

**Key fields:**
- `project_id` FK → `projects(id)`
- `kind` — `github | linear | …`
- `status` — `active | error | disconnected`
- `config` — JSONB per-kind metadata (e.g. `{ repo: "org/repo" }` for GitHub)

Cross-reference: `db/entities.md § Project Connections`

---

## Canvas vocabulary retirement (applied 2026-05-28 from change `retire-canvas-vocabulary`, RAP-1252)

Per D-CV-1 (ratified 2026-05-28), the **CanvasSession**, **UserSettings.layout.canvasChatWidth**, **CanvasIdea**, and **CanvasStageEvent** sections above are historical record only. The studio's working-session entity is **`idea`** (table `ideas`); the deliverable document set is **`spec`** (table `specs`); the user-facing word for the working surface is **"Spec"**. The `CanvasSession` cross-references to `db/entities.md § Canvas access tables` should be read against `db/entities.md § Atlas-ratified table renames` (the canvas_* tables don't exist on prod since 2026-05-22; their post-rename equivalents are documented there).

The persisted user-settings JSONB key `layout.canvasChatWidth` stays (data concern, not vocabulary); the TS-facing identifier flipped to `IdeaChatWidth` per Wave 2.

Cross-reference: `openspec/archive/2026-05-28-retire-canvas-vocabulary/design.md § D-CV-1` (archived 2026-05-28 as `openspec/archive/2026-05-28-retire-canvas-vocabulary/`).
