diff --git a/.agents/global-rules b/.agents/global-rules
index 0db85e248..dd72cc456 160000
--- a/.agents/global-rules
+++ b/.agents/global-rules
@@ -1 +1 @@
-Subproject commit 0db85e24816fc4cab5316a8827c8dbfbb4628de2
+Subproject commit dd72cc456d27b8b4638d9c41ae7aa9f0cf70ee0f
diff --git a/canvas/dashboards.mdx b/canvas/dashboards.mdx
deleted file mode 100644
index 04169353c..000000000
--- a/canvas/dashboards.mdx
+++ /dev/null
@@ -1,104 +0,0 @@
----
-title: "Live dashboards"
-description: "Tiles and layouts your assistant builds on request"
----
-
-A **dashboard** is a grid of **tiles** — individual live views — that your
-assistant composes for you. Ask for a chart and you get a tile; ask for "a
-board with our key numbers" and you get a dashboard arranged from several.
-
-## Asking for one
-
-Describe the view, not the configuration:
-
-> "Build me a dashboard for the repairs team: open work orders by priority,
-> a chart of completions per week, and the average time-to-fix as a big
-> number up top."
-
-Your assistant creates the tiles, sizes and arranges them on a 12-column
-grid, and shares the result. Refinements work the same way — "make the
-trend chart wider", "add a tile for overdue items", "retitle it Repairs
-Standup".
-
-## Tiles can be anything
-
-There's no fixed menu of widget types. A tile is whatever presents the data
-best:
-
-- **Charts** — bar, line, scatter, histogram, or anything more exotic the
- data calls for.
-- **Tables** — sortable, styled, as compact or detailed as you like.
-- **KPI cards** — single big numbers with context.
-- **Custom views** — maps, timelines, progress boards; if it can render in
- a browser, it can be a tile.
-
-## Live by design
-
-The best tiles are **live**: instead of baking numbers in, a tile is wired
-to [data tables](/canvas/data) and fetches fresh values every time it's
-viewed. Tiles with live wiring show a **Live** badge and a per-tile
-**Refresh data** button.
-
-That's what makes the canvas a monitoring surface rather than a report
-archive: a scheduled task appends today's rows to a table, and every tile
-built on that table is already up to date the next time you look.
-
-## The Dashboards pane
-
-The **Dashboards** tab on your assistant's page is the viewer for
-everything visual it has built — in the Console's words, *"live tiles and
-reports your teammate builds for you, on request."* Where the
-[Data pane](/canvas/data) shows the raw tables, this pane shows the views
-on top of them.
-
-**What it's for:**
-
-- **Your standing views, in one place.** Every dashboard and standalone
- tile your assistant has built for you, ready to revisit — the morning
- KPI board, the API monitor, last quarter's analysis.
-- **A live monitoring surface.** Data-bound tiles fetch fresh values when
- viewed, so the pane doubles as your operations screen — glance rather
- than ask.
-- **The handoff point.** Pop any view into its own tab for a shareable
- link, or download tiles when you need to take a chart elsewhere.
-
-**How to use it:**
-
-1. **Pick a view.** The picker at the top switches between everything saved
- for this assistant, in two groups: **Dashboards** (composed boards) and
- **Tiles** (standalone views that aren't part of any board). Search by
- title when the collection grows.
-2. **Read the summary card.** Each dashboard opens with its description,
- tile count, and last-updated time — plus **Open in new tab** for a
- full-screen, shareable version and **Download** to grab all tiles as a
- bundle.
-3. **Work the tiles.** Tiles are laid out on the dashboard's grid; each
- collapses and expands individually (or all at once from the toolbar).
- Tiles wired to live data carry a **Live** badge and their own
- **Refresh data** button; every tile can be downloaded or popped out
- into its own tab.
-4. **Let it keep itself fresh.** While your assistant is actively working,
- the pane polls automatically — a **Live** indicator and "Updated…"
- timestamp in the footer show you're seeing current state. A refresh
- button re-pulls everything on demand.
-
-If the pane says *"No dashboards yet — ask your teammate to build a view
-when there is data to track"*, that's exactly the right instruction — this
-pane only ever displays; [creating and changing views happens in
-chat](/canvas/dashboards#asking-for-one).
-
-## Sharing
-
-Every tile and dashboard has its own **link** — pop it out and the URL in
-your browser is shareable. Send a dashboard link to a colleague and they see
-the same live views, no Console tour required. Dashboards can also live in a
-[team's shared pool](/teams/shared-context), so a whole team works from the
-same board — a standup dashboard the entire repairs patch checks each
-morning, for example.
-
-## Changing and removing
-
-Like everything else, dashboard management is conversational: "update the
-KPI tile to use this quarter's target", "remove the old pipeline chart",
-"delete the test dashboard". Tiles and dashboards are independent — removing
-a dashboard doesn't destroy its tiles, so views can be recomposed freely.
diff --git a/canvas/data.mdx b/canvas/data.mdx
index 7ff3ae430..3fcde5583 100644
--- a/canvas/data.mdx
+++ b/canvas/data.mdx
@@ -51,7 +51,7 @@ Once data is in tables, you can ask for anything you'd ask an analyst:
billing" works even when no row contains the word "billing".
The answers come back in chat — or become permanent
-[dashboard tiles](/canvas/dashboards) if they're worth watching over time.
+[canvas views](/canvas/recipes) if they're worth watching over time.
## The Data pane
@@ -60,7 +60,7 @@ your window into the whole data layer. The Console describes it as
*"everything your teammate has ingested — browse nested tables like a
directory and open any to view rows"*, and that's exactly the job: it's
where you verify, audit, and explore the raw material behind your
-assistant's answers and dashboards.
+assistant's answers and canvas views.
**What it's for:**
@@ -70,7 +70,7 @@ assistant's answers and dashboards.
- **Auditing an answer.** When your assistant reports a number, the table
it computed from is right here — open it and see the underlying rows for
yourself.
-- **Inspecting what feeds a dashboard.** Every live tile reads from tables
+- **Inspecting what feeds a view.** Every live canvas reads from tables
in this pane; if a chart looks off, the pane shows you exactly what the
data says.
- **Seeing the whole estate.** Nothing is hidden: if your assistant
diff --git a/canvas/developers.mdx b/canvas/developers.mdx
index 48c239621..85fe6f040 100644
--- a/canvas/developers.mdx
+++ b/canvas/developers.mdx
@@ -1,6 +1,6 @@
---
title: "Developers"
-description: "How DataManager and DashboardManager actually work — a code-level tour of the open-source runtime"
+description: "How DataManager and CanvasManager actually work — a code-level tour of the open-source runtime"
---
Everything else in this section describes the canvas as a user experiences
@@ -13,9 +13,8 @@ Two packages own this territory:
- [`unify/data_manager/`](https://github.com/unifyai/unify/tree/main/unify/data_manager) —
`DataManager`, the tabular data engine.
-- [`unify/dashboard_manager/`](https://github.com/unifyai/unify/tree/main/unify/dashboard_manager) —
- `DashboardManager` (soon to be renamed **CanvasManager**), the
- visualization layer built on top of it.
+- [`unify/canvas_manager/`](https://github.com/unifyai/unify/tree/main/unify/canvas_manager) —
+ `CanvasManager`, the generative-UI layer built on top of it.
## The big picture
@@ -27,9 +26,9 @@ manager in the repo:
```text
BaseStateManager
- └── BaseDataManager / BaseDashboardManager (base.py — the contract)
- ├── DataManager / DashboardManager (real impl → Orchestra via unisdk)
- └── SimulatedDataManager / SimulatedDashboardManager (in-memory, for tests)
+ └── BaseDataManager / BaseCanvasManager (base.py — the contract)
+ ├── DataManager / CanvasManager (real impl → Orchestra via unisdk)
+ └── SimulatedDataManager / SimulatedCanvasManager (in-memory, for tests)
```
Three properties define the design:
@@ -48,7 +47,7 @@ Three properties define the design:
([`unify/function_manager/primitives/registry.py`](https://github.com/unifyai/unify/blob/main/unify/function_manager/primitives/registry.py))
introspects `@abstractmethod` definitions on the base class to decide
what the Actor sees as `primitives.data.*` and
- `primitives.dashboards.*`. Add an abstract method with a docstring and
+ `primitives.canvas.*`. Add an abstract method with a docstring and
it becomes an Actor-callable primitive with no registry edits.
Both managers are synchronous internally, listed in `_SYNC_MANAGERS` in
@@ -58,8 +57,8 @@ the runtime wraps them in `_AsyncPrimitiveWrapper` (dispatching via
primitives.data.filter(...)`.
Implementation selection is env-driven: `UNITY_DATA_IMPL` and
-`UNITY_DASHBOARD_IMPL` (`"real"` | `"simulated"`), read by `DataSettings`
-and `DashboardSettings` and resolved through `ManagerRegistry` —
+`UNITY_CANVAS_IMPL` (`"real"` | `"simulated"`), read by `DataSettings`
+and `CanvasSettings` and resolved through `ManagerRegistry` —
[`unify/manager_registry.py`](https://github.com/unifyai/unify/blob/main/unify/manager_registry.py).
---
@@ -118,7 +117,7 @@ Context paths resolve through three private helpers in `DataManager`:
- `_resolve_context` (reads): strips a leading `/`, passes through any
path starting with a known absolute prefix (`Data/`, `Files/`,
- `Knowledge/`, `Teams/`, `Dashboards/`, …), and prepends the assistant's
+ `Knowledge/`, `Teams/`, …), and prepends the assistant's
base context (`{org}/{assistant_id}/Data`) for relative names.
- `_resolve_context_for_write(context, destination=)`: for Data-owned
paths, routes through `ContextRegistry.write_root(self, "Data",
@@ -130,8 +129,8 @@ Context paths resolve through three private helpers in `DataManager`:
`federated_ranked_search`, and `federated_reduce` from
[`unify/common/federated_search.py`](https://github.com/unifyai/unify/blob/main/unify/common/federated_search.py).
-
-
+
+
`ContextRegistry`
([`unify/common/context_registry.py`](https://github.com/unifyai/unify/blob/main/unify/common/context_registry.py))
@@ -247,165 +246,85 @@ routing, streaming ingest, type prescan, pipeline mechanics.
Two orphaned modules — `ops/plot_ops.py` and `ops/table_view_ops.py` —
remain from the era when DataManager rendered visuals. `plot()` and
`table_view()` were **removed from the public contract**; all visual
- output now goes through DashboardManager.
+ output now goes through CanvasManager.
---
-# DashboardManager (soon CanvasManager)
+# CanvasManager
## Design in one paragraph
-The Actor generates **arbitrary HTML** — Plotly, D3, Chart.js from a CDN,
-or hand-rolled markup — and hands it to `create_tile`. The manager stores
-the tile as a row in a `Dashboards/Tiles` context, mints a 12-character
-shareable token, and (for live tiles) stores **declarative data bindings**
-plus an `on_data` JavaScript callback. The Console renders the HTML in a
-sandboxed iframe and executes the bindings at render time, so tiles read
-fresh data on every view. From
-[`base.py`](https://github.com/unifyai/unify/blob/main/unify/dashboard_manager/base.py):
-*"The actor should **always** use DashboardManager for visualizations"* —
-it replaced the old plotting paths entirely.
+The Actor writes a **real React module** — TSX importing only `react` and
+`@unity/canvas-kit` — and hands it to `create_view` together with declared
+**query bindings** and **actions**. The manager lints, typechecks and
+bundles the module, dry-runs every binding through the live `DataManager`,
+verifies every action target exists, renders the view headlessly and
+critiques the result before anything is published. The stored row carries
+the source, the compiled bundle and its sha256; a 12-character routing
+token is registered with the backend, and the Console serves the view in a
+genuinely isolated frame. There is no raw-HTML path and no unauthenticated
+token-is-access URL — both were properties of the dashboard tiles this
+layer replaced, and both were retired with them.
## Package anatomy
| Path | Role |
|---|---|
-| [`base.py`](https://github.com/unifyai/unify/blob/main/unify/dashboard_manager/base.py) | `BaseDashboardManager` — 10-method contract (tile + dashboard CRUD), all docstrings |
-| [`dashboard_manager.py`](https://github.com/unifyai/unify/blob/main/unify/dashboard_manager/dashboard_manager.py) | `DashboardManager` — destination routing, binding pipeline, token lifecycle |
-| [`simulated.py`](https://github.com/unifyai/unify/blob/main/unify/dashboard_manager/simulated.py) | `SimulatedDashboardManager` — in-memory, sequential `sim_tile_0001` tokens |
-| [`types/tile.py`](https://github.com/unifyai/unify/blob/main/unify/dashboard_manager/types/tile.py) | Binding classes, `TileRecordRow`/`TileRecord`/`TileResult`, `DASHBOARD_BRIDGE_MAX_ROW_LIMIT` |
-| [`types/dashboard.py`](https://github.com/unifyai/unify/blob/main/unify/dashboard_manager/types/dashboard.py) | `TilePosition` (12-column grid), `DashboardRecordRow`/`DashboardRecord`/`DashboardResult` |
-| [`ops/tile_ops.py`](https://github.com/unifyai/unify/blob/main/unify/dashboard_manager/ops/tile_ops.py) | Binding validation, alias assignment, context resolution, serialization |
-| [`ops/dashboard_ops.py`](https://github.com/unifyai/unify/blob/main/unify/dashboard_manager/ops/dashboard_ops.py) | Layout serialize/deserialize |
-| [`ops/token_ops.py`](https://github.com/unifyai/unify/blob/main/unify/dashboard_manager/ops/token_ops.py) | `generate_token`, `register_token`, `delete_token` |
-
-Persistence is notable: `DashboardManager` has **no storage code of its
-own** — tile and layout rows are written through
-`ManagerRegistry.get_data_manager()` (`insert_rows`, `filter`,
-`update_rows`, `delete_rows`) into two registered contexts,
-`Dashboards/Tiles` and `Dashboards/Layouts`. The dashboard layer is a
-client of the data layer.
+| [`base.py`](https://github.com/unifyai/unify/blob/main/unify/canvas_manager/base.py) | `BaseCanvasManager` — the contract (`create_view`, `update_view`, `refresh_props`, `preview`, invocations), all docstrings |
+| [`canvas_manager.py`](https://github.com/unifyai/unify/blob/main/unify/canvas_manager/canvas_manager.py) | `CanvasManager` — destination routing, build/review pipeline, token lifecycle |
+| [`simulated.py`](https://github.com/unifyai/unify/blob/main/unify/canvas_manager/simulated.py) | `SimulatedCanvasManager` — in-memory mirror of the contract |
+| [`types/view.py`](https://github.com/unifyai/unify/blob/main/unify/canvas_manager/types/view.py) | `CanvasViewRow` / `CanvasViewRecord` — source, bundle, bindings, visibility, status |
+| [`types/binding.py`](https://github.com/unifyai/unify/blob/main/unify/canvas_manager/types/binding.py) | `PrimitiveBinding` + the four query shapes (`filter`, `reduce`, `join`, `join_reduce`) |
+| [`types/action.py`](https://github.com/unifyai/unify/blob/main/unify/canvas_manager/types/action.py) | `CanvasAction` — declared viewer-triggered operations with bounded input schemas |
+| [`ops/build_ops.py`](https://github.com/unifyai/unify/blob/main/unify/canvas_manager/ops/build_ops.py) | Lint → typecheck → bundle against the vendored kit |
+| [`ops/review_ops.py`](https://github.com/unifyai/unify/blob/main/unify/canvas_manager/ops/review_ops.py) | Headless render + critique gate |
+| [`ops/binding_ops.py`](https://github.com/unifyai/unify/blob/main/unify/canvas_manager/ops/binding_ops.py) | Binding resolution and dry-runs through DataManager |
+| [`ops/token_ops.py`](https://github.com/unifyai/unify/blob/main/unify/canvas_manager/ops/token_ops.py) | Token registry: register, publish/quarantine state, delete |
+
+Rows live in three registered contexts — `Canvas/Views`, `Canvas/Actions`,
+`Canvas/Invocations` — written through the data layer like every other
+manager. There is no separate layout table: React composes, so a
+multi-tile board is simply one view, and the dangling-reference bug class
+of a layout pointing at a deleted tile cannot exist.
## The binding type system
-Live tiles declare their data needs as a discriminated union of four
-Pydantic models (discriminator: `operation`), mirroring the DataManager
-query families:
-
-| Binding | Mirrors | Returns to `on_data` |
-|---|---|---|
-| `FilterBinding` | `filter` | `Array