From 90309661090bc44fe56a8151d9bb210fcdda2316 Mon Sep 17 00:00:00 2001 From: Haris Mahmood Date: Wed, 12 Aug 2026 14:47:50 +0500 Subject: [PATCH 1/2] docs(canvas)!: dashboards pages retire in favour of canvas canvas/dashboards.mdx and the live-tile-lifecycle images are deleted and mint.json drops the page; the canvas, teams and tasks pages that referenced tiles or dashboards now describe canvas views, and the regenerated teams-scope-model and destination-scopes diagrams carry no dashboards text. --- canvas/dashboards.mdx | 104 -------- canvas/data.mdx | 6 +- canvas/developers.mdx | 233 ++++++------------ canvas/overview.mdx | 38 ++- canvas/recipes.mdx | 8 +- communication/console-chat.mdx | 4 +- images/developers/destination-scopes-dark.svg | 14 +- .../developers/destination-scopes-light.svg | 14 +- .../developers/live-tile-lifecycle-dark.svg | 7 - .../developers/live-tile-lifecycle-light.svg | 7 - images/developers/teams-scope-model-dark.svg | 14 +- images/developers/teams-scope-model-light.svg | 14 +- introduction.mdx | 2 +- local-deployment/custom-overrides.mdx | 7 +- mint.json | 1 - tasks/overview.mdx | 4 +- teams/developers.mdx | 18 +- teams/overview.mdx | 2 +- teams/shared-context.mdx | 8 +- 19 files changed, 148 insertions(+), 357 deletions(-) delete mode 100644 canvas/dashboards.mdx delete mode 100644 images/developers/live-tile-lifecycle-dark.svg delete mode 100644 images/developers/live-tile-lifecycle-light.svg 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). -Destination scopes: write_root routes each write to exactly one destination — the personal root or a team root, each holding Data and Dashboards contexts — while read_roots federates reads across personal plus all teams; data_scope additionally lets a personal tile read team data. -Destination scopes: write_root routes each write to exactly one destination — the personal root or a team root, each holding Data and Dashboards contexts — while read_roots federates reads across personal plus all teams; data_scope additionally lets a personal tile read team data. +Destination scopes: write_root routes each write to exactly one destination — the personal root or a team root, each holding Data and Canvas contexts — while read_roots federates reads across personal plus all teams. +Destination scopes: write_root routes each write to exactly one destination — the personal root or a team root, each holding Data and Canvas contexts — while read_roots federates reads across personal plus all teams. `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` (rows) | -| `ReduceBinding` | `reduce` | scalar or `{group: value}` | -| `JoinBinding` | `filter_join` | `Array` (joined rows) | -| `JoinReduceBinding` | `reduce_join` | scalar or `{group: value}` | - -Row-returning bindings are capped by `DASHBOARD_BRIDGE_MAX_ROW_LIMIT = -1000` — beyond that, aggregate with a reduce binding instead. Each binding -carries an `alias` (a valid JS identifier, auto-derived from the context -path's last segment by `_alias_from_context` when omitted), and -`serialize_bindings` stores the whole list as compact JSON in the tile -row's `data_bindings_json` field. - -The **`on_data` contract**: the Actor supplies a plain JS body (no -function wrapper, no return). The Console wraps and invokes it as -`(function(data){ })(results)`, where `results` is keyed by -alias — `data.orders`, `data.revenue_by_region`. A guard in -`validate_on_data` warns if the script contains `UnifyData.` calls: the -bridge invocation is generated by the Console from the serialized -bindings; *the Actor never writes bridge API calls*. - -## Life of a live tile - -Live tile lifecycle: the Actor calls create_tile with HTML, data bindings, and on_data; DashboardManager validates and dry-runs the bindings, stores the tile row and registers its token; the Console TileViewer renders the iframe and injects the UnifyData bridge, which runs live filter/reduce/join queries against the Orchestra bridge at render time; results are handed to on_data so the chart updates with fresh data. -Live tile lifecycle: the Actor calls create_tile with HTML, data bindings, and on_data; DashboardManager validates and dry-runs the bindings, stores the tile row and registers its token; the Console TileViewer renders the iframe and injects the UnifyData bridge, which runs live filter/reduce/join queries against the Orchestra bridge at render time; results are handed to on_data so the chart updates with fresh data. - -The `create_tile` pipeline in `DashboardManager`, step by step: - -1. **Resolve the write root** — `ContextRegistry.write_root(self, - "Dashboards/Tiles", destination=...)`. -2. **Mint the token** — `generate_token()` is - `secrets.token_urlsafe(9)[:12]`. -3. **Validate bindings** — `validate_data_bindings` → - `validate_on_data` → `ensure_binding_aliases`. -4. **Resolve the binding root** — `_data_binding_root()` honors - `data_scope`: `"dashboard"` (default) inherits the tile's own - destination root; `"team:"` pins bindings to a team root, validated - against `SESSION_DETAILS.team_ids`. -5. **Resolve binding contexts** — `resolve_binding_contexts` turns - Actor-relative paths into fully-qualified ones (and rewrites join - expressions accordingly) so the stored binding is unambiguous. -6. **Dry-run every binding** — `verify_data_bindings` executes each - binding through the live `DataManager` (`filter` with `limit=5`, - `reduce`, `filter_join` with `result_limit=5`, `reduce_join`). A tile - with a broken query never gets stored; you get a `TileResult` carrying - the error. -7. **Store + register** — `build_tile_record_row` → - `dm.insert_rows(...)`; then `register_token(token, "tile", context, - project)` posts to Orchestra's token registry, and the returned - `TileResult` carries the shareable `{CONSOLE_URL}/tile/view/{token}` - URL. - -**Render time** (outside this repo, but essential context): the Console -resolves the token, fetches the tile row, and renders `html_content` in a -sandboxed iframe. For live tiles it injects a `UnifyData` bridge and an -auto-exec script generated from `data_bindings_json`; the bridge -`postMessage`s each query to the parent page, which proxies to Orchestra's -admin bridge endpoints (filter / reduce / join / join-reduce — the same -semantics as the DataManager methods, executed server-side under the tile -creator's identity). Results come back keyed by alias and your `on_data` -body runs. The net effect: **the queries you validated at create time are -exactly the queries that run at render time.** - -**Static (baked-in) tiles** skip all of this: self-contained HTML with -data embedded (e.g. Plotly's `fig.to_html(include_plotlyjs='cdn')`), no -bindings, no bridge. Appropriate only for small, genuinely static -snapshots. - -## Dashboards and the grid - -A dashboard is a stored layout over tile tokens: `create_dashboard` takes -`TilePosition` entries — `tile_token`, `x` (0–11), `y`, `w` (1–12), `h`, -with `x + w ≤ 12` enforced — serialized to JSON in the -`Dashboards/Layouts` row, plus its own token and share URL. - -Lifecycles are deliberately **independent**: deleting a dashboard leaves -its tiles intact; deleting a tile doesn't rewrite layouts that reference -it (they render a broken reference until updated). `update_tile` and -`update_dashboard` preserve tokens — URLs never churn. Partial-update -semantics: `None` preserves a field; `on_data=""` clears the script; a -fresh empty `data_bindings=[]` drops live mode and resets `data_scope`; -changing `data_scope` requires supplying fresh bindings in the same call. - -## `destination` vs `data_scope` - -These are orthogonal, and the distinction is the most common point of -confusion: - -- **`destination`** — where the tile/dashboard **row lives** (`"personal"` - or `"team:"`), which controls who sees it in listings. -- **`data_scope`** — which root the tile's **bindings read from** - (`"dashboard"` = inherit the destination root, or an explicit - `"team:"`). - -They can intentionally differ: a *personal* watch tile whose bindings read -*team* operations data (`destination="personal"`, -`data_scope="team:8"`) — covered by -[`tests/dashboard_manager/test_tile_data_scope.py`](https://github.com/unifyai/unify/blob/main/tests/dashboard_manager/test_tile_data_scope.py). -Reads across the board use `ContextRegistry.read_roots` fan-out, so -`list_tiles` returns personal plus all accessible team tiles; updates and -deletes must name the right `destination` or the row is simply not found. +A view declares its data as `PrimitiveBinding` entries — an `alias` the +canvas reads via `canvas.data[alias]`, the owning manager and table, and +one of four read-only query shapes mirroring the DataManager families +(`filter`, `reduce`, `join`, `join_reduce`). Every binding is dry-run at +author time, and at view time the server executes **only** the stored +binding a client names by alias: no context path, filter expression or row +limit is ever accepted from the viewer, which is the security inversion +that retired the old tile bridge. + +## Actions and invocations + +A view may declare `CanvasAction`s: named operations with JSON-Schema +input (every string and array bounded), an execution lane (stored +function, task trigger, or assistant request), optional confirmation text +rendered by the Console *outside* the frame, and per-hour rate limits. +A viewer's invocation is validated server-side, recorded as a +`Canvas/Invocations` row, then executed by the assistant — the frame never +learns which function or task sits behind an action. + +## `destination` and `visibility` + +- **`destination`** — where the view's **row lives** (`"personal"` or + `"team:"`), which controls who sees it in listings. +- **`visibility`** — who may **read** it (`private`, `team`, + `public_link`), enforced server-side on every read path; publishing and + quarantining flip `status` without reissuing the URL. ## Extending -**A new binding type** touches the full stack — in this repo: a new model -with a unique `operation` literal added to the `DataBinding` union in -`types/tile.py`; handling in `_contexts_for_binding`, -`resolve_binding_contexts`, and `verify_data_bindings` (with a real -DataManager dry-run) in `ops/tile_ops.py`; and documentation in the -`create_tile` docstring (remember: docstrings are the Actor's API). The -Console and Orchestra then need the matching bridge operation, proxy -route, and admin endpoint. +**A new binding shape** touches the full stack — in this repo: a new args +model with a unique `operation` literal in `canvas_manager/types/binding.py`; +resolution and a real DataManager dry-run in `ops/binding_ops.py`; and +documentation in the `create_view` docstring (remember: docstrings are the +Actor's API). Orchestra then needs the matching server-side execution in +its canvas query route. **A new DataManager operation** is simpler: abstract method + docstring on `BaseDataManager`, an `*_impl` in the right `ops/` module, implementations @@ -424,8 +343,8 @@ settings enum. | Scope & destinations | [`common/context_registry.py`](https://github.com/unifyai/unify/blob/main/unify/common/context_registry.py) + [`data_manager/data_manager.py`](https://github.com/unifyai/unify/blob/main/unify/data_manager/data_manager.py) resolution helpers | | Multi-root reads | [`common/federated_search.py`](https://github.com/unifyai/unify/blob/main/unify/common/federated_search.py) | | Bulk loading | [`data_manager/ops/ingest_ops.py`](https://github.com/unifyai/unify/blob/main/unify/data_manager/ops/ingest_ops.py) + [`utils/pipeline.py`](https://github.com/unifyai/unify/blob/main/unify/data_manager/utils/pipeline.py) | -| The tile/binding model | [`dashboard_manager/types/tile.py`](https://github.com/unifyai/unify/blob/main/unify/dashboard_manager/types/tile.py) | -| Binding validation | [`dashboard_manager/ops/tile_ops.py`](https://github.com/unifyai/unify/blob/main/unify/dashboard_manager/ops/tile_ops.py) | +| The view/binding model | [`canvas_manager/types/view.py`](https://github.com/unifyai/unify/blob/main/unify/canvas_manager/types/view.py) + [`types/binding.py`](https://github.com/unifyai/unify/blob/main/unify/canvas_manager/types/binding.py) | +| Binding validation | [`canvas_manager/ops/binding_ops.py`](https://github.com/unifyai/unify/blob/main/unify/canvas_manager/ops/binding_ops.py) | | Actor-facing exposure | [`function_manager/primitives/registry.py`](https://github.com/unifyai/unify/blob/main/unify/function_manager/primitives/registry.py) + [`runtime.py`](https://github.com/unifyai/unify/blob/main/unify/function_manager/primitives/runtime.py) | | Worked Actor examples | [`actor/prompt_examples.py`](https://github.com/unifyai/unify/blob/main/unify/actor/prompt_examples.py) | -| Behavioral guarantees | [`tests/data_manager/`](https://github.com/unifyai/unify/tree/main/tests/data_manager) + [`tests/dashboard_manager/`](https://github.com/unifyai/unify/tree/main/tests/dashboard_manager) | +| Behavioral guarantees | [`tests/data_manager/`](https://github.com/unifyai/unify/tree/main/tests/data_manager) + [`tests/canvas_manager/`](https://github.com/unifyai/unify/tree/main/tests/canvas_manager) | diff --git a/canvas/overview.mdx b/canvas/overview.mdx index b932351a5..22d4aabdf 100644 --- a/canvas/overview.mdx +++ b/canvas/overview.mdx @@ -10,9 +10,8 @@ assistant builds them. In the Console this lives in two dedicated panes on your assistant's - page: **Dashboards** (the views) and **Data** (the tables underneath), - each covered in depth in this section. The area is being renamed to - **Canvas** — these docs use the new name. + page: **Canvas** (the views) and **Data** (the tables underneath), each + covered in depth in this section. ## Two building blocks @@ -25,36 +24,36 @@ Everything on the canvas is built from two simple, very flexible pieces: numbers pulled from an API, results of a computation, records extracted from files. Organized like folders, browsable any time. - - Live visual tiles built on top of that data — charts, tables, KPI - cards, or any custom view — arranged into dashboards that refresh with - the data underneath. + + Live views built on top of that data — charts, tables, KPI cards, or + any custom interactive layout — that refresh with the data underneath + and can carry controls that do real work. The separation is the point: data collection and presentation are -independent. The same table can feed three different views; a dashboard -stays live as its tables grow; and either can change without rebuilding the +independent. The same table can feed three different views; a view stays +live as its tables grow; and either can change without rebuilding the other. ## You ask — it builds -There is no dashboard editor to learn and no query language to write. You +There is no editor to learn and no query language to write. You describe what you want to see, and your assistant builds it: - *"Plot repairs by category."* -- *"Create a dashboard with our sales KPIs and a revenue trend chart."* +- *"Create a view with our sales KPIs and a revenue trend chart."* - *"Track response times from our status API and chart the last 7 days."* - *"Show me open work orders per operative, updated live."* -Your assistant creates or reuses the underlying tables, builds the tiles, -arranges the dashboard, and drops a link in chat. From then on it's on your -canvas, staying current. +Your assistant creates or reuses the underlying tables, composes the +view, and drops a link in chat. From then on it's on your canvas, staying +current. ## Why this is more than charts Because your assistant controls both the data *and* the view, the canvas can -do things a normal dashboard tool can't do without an engineering project: +do things a normal reporting tool can't do without an engineering project: - **It can collect what doesn't exist yet.** No data feed? Your assistant can call an API [on a schedule](/tasks/overview), extract numbers from @@ -65,9 +64,9 @@ do things a normal dashboard tool can't do without an engineering project: pipeline against calendar. - **It can derive new columns.** "Add a total column that's unit price times quantity" is a sentence, not a formula bar. -- **Views are unlimited in form.** Tiles aren't limited to a preset chart +- **Views are unlimited in form.** They aren't limited to a preset chart menu — bar, line, scatter, maps, KPI cards, styled tables, or fully - custom layouts, whatever presents the data best. + custom interactive layouts, whatever presents the data best. ## What's in this section @@ -76,10 +75,7 @@ do things a normal dashboard tool can't do without an engineering project: What your assistant stores, how tables are organized, and browsing them in the Console. - - Tiles, layouts, live data, sharing, and the viewer in the Console. - - + Worked examples — API monitoring, KPI boards, combined sources. diff --git a/canvas/recipes.mdx b/canvas/recipes.mdx index 91b29cf39..ee931201d 100644 --- a/canvas/recipes.mdx +++ b/canvas/recipes.mdx @@ -1,9 +1,9 @@ --- title: "Recipes" -description: "Worked examples of the data + dashboard combination" +description: "Worked examples of the data + canvas combination" --- -The building blocks — [tables](/canvas/data), [tiles](/canvas/dashboards), +The building blocks — [tables](/canvas/data), [views](/canvas/overview), and [scheduled tasks](/tasks/overview) — are deliberately simple, and they compose. Here are patterns that come up again and again, each just a conversation away. @@ -19,7 +19,7 @@ conversation away. **What your assistant sets up:** a recurring task that calls the API and appends a row per check to a table like `Data/StatusAPI/checks`; a live -dashboard with an uptime KPI card and a response-time chart reading from +view with an uptime KPI card and a response-time chart reading from that table; and an alert rule as part of the task. The table quietly accumulates history — a month later, "how did latency trend after the release?" is answerable from data you already have. @@ -33,7 +33,7 @@ release?" is answerable from data you already have. > from the store API every morning at 6." **What it sets up:** a scheduled pull into `Data/Store/daily_sales`, plus a -dashboard of KPI cards and a trend chart. Because tiles are live, the 6am +view of KPI cards and a trend chart. Because views are live, the 6am task run is all it takes for the 8am glance to be current. ## Combine sources into one view diff --git a/communication/console-chat.mdx b/communication/console-chat.mdx index 2ce783dec..4f0dddf5d 100644 --- a/communication/console-chat.mdx +++ b/communication/console-chat.mdx @@ -40,8 +40,8 @@ assistant reads what you send and works with it directly. - **Call records inline** — voice calls appear in the thread as pills showing their duration; click one to read the **call transcript** without leaving chat. -- **Interactive embeds** — links to [tiles, charts, tables, and - dashboards](/canvas/dashboards) render as inline previews you can expand +- **Interactive embeds** — links to [charts, tables, and canvas + views](/canvas/overview) render as inline previews you can expand in place or pop into a new tab. ## Finding things diff --git a/images/developers/destination-scopes-dark.svg b/images/developers/destination-scopes-dark.svg index 2afa8d32e..8d8dfb056 100644 --- a/images/developers/destination-scopes-dark.svg +++ b/images/developers/destination-scopes-dark.svg @@ -1,7 +1,7 @@ -DESTINATION SCOPESwrite_root — exactly one destinationpersonalData/SalesDashboards/TilesDashboards/Layoutsteam:42Data/OpsDashboards/TilesDashboards/Layoutsread_roots — personal + all teamsdata_scope: a personal tile may read team data +DESTINATION SCOPESwrite_root — exactly one destinationpersonalData/SalesCanvas/ViewsCanvas/Actionsteam:42Data/OpsCanvas/ViewsCanvas/Actionsread_roots — personal + all teamsdata_scope: a personal tile may read team data \ No newline at end of file diff --git a/images/developers/destination-scopes-light.svg b/images/developers/destination-scopes-light.svg index 2a978b91f..767919398 100644 --- a/images/developers/destination-scopes-light.svg +++ b/images/developers/destination-scopes-light.svg @@ -1,7 +1,7 @@ -DESTINATION SCOPESwrite_root — exactly one destinationpersonalData/SalesDashboards/TilesDashboards/Layoutsteam:42Data/OpsDashboards/TilesDashboards/Layoutsread_roots — personal + all teamsdata_scope: a personal tile may read team data +DESTINATION SCOPESwrite_root — exactly one destinationpersonalData/SalesCanvas/ViewsCanvas/Actionsteam:42Data/OpsCanvas/ViewsCanvas/Actionsread_roots — personal + all teamsdata_scope: a personal tile may read team data \ No newline at end of file diff --git a/images/developers/live-tile-lifecycle-dark.svg b/images/developers/live-tile-lifecycle-dark.svg deleted file mode 100644 index bb6c88308..000000000 --- a/images/developers/live-tile-lifecycle-dark.svg +++ /dev/null @@ -1,7 +0,0 @@ -LIVE TILE LIFECYCLE1Actorcreate_tile(html, data_bindings, on_data)2DashboardManagervalidate + dry-runbindings3Console TileVieweriframe +UnifyData bridge4Orchestra bridgefilter / reduce / joinlive queriesat render timestore tile row +register token5on_data(results)chart updates with fresh data diff --git a/images/developers/live-tile-lifecycle-light.svg b/images/developers/live-tile-lifecycle-light.svg deleted file mode 100644 index 88e3deff4..000000000 --- a/images/developers/live-tile-lifecycle-light.svg +++ /dev/null @@ -1,7 +0,0 @@ -LIVE TILE LIFECYCLE1Actorcreate_tile(html, data_bindings, on_data)2DashboardManagervalidate + dry-runbindings3Console TileVieweriframe +UnifyData bridge4Orchestra bridgefilter / reduce / joinlive queriesat render timestore tile row +register token5on_data(results)chart updates with fresh data diff --git a/images/developers/teams-scope-model-dark.svg b/images/developers/teams-scope-model-dark.svg index 6cd1714a6..b5b38bd54 100644 --- a/images/developers/teams-scope-model-dark.svg +++ b/images/developers/teams-scope-model-dark.svg @@ -1,7 +1,7 @@ -TEAM SCOPESstate managersGuidance · Knowledge · Functions · Tasks · Data · Secrets · Dashboardsdestination='personal' | 'team:<id>'ContextRegistrydestination → root · membership gatedefaultrequires team_id inSESSION_DETAILS.team_idspersonal root{user_id}/{assistant_id}/…privacy floor — visible to one assistantteam rootsTeams/{team_id}/…one root per membership — visible to current memberssame table names under every rootGuidance · Knowledge · Functions/* · Tasks · Data · Secrets · Dashboards/* · Transcriptswrites target exactly one root — reads fan out across all of them +TEAM SCOPESstate managersGuidance · Knowledge · Functions · Tasks · Data · Secrets · Canvasdestination='personal' | 'team:<id>'ContextRegistrydestination → root · membership gatedefaultrequires team_id inSESSION_DETAILS.team_idspersonal root{user_id}/{assistant_id}/…privacy floor — visible to one assistantteam rootsTeams/{team_id}/…one root per membership — visible to current memberssame table names under every rootGuidance · Knowledge · Functions/* · Tasks · Data · Secrets · Canvas/* · Transcriptswrites target exactly one root — reads fan out across all of them \ No newline at end of file diff --git a/images/developers/teams-scope-model-light.svg b/images/developers/teams-scope-model-light.svg index 0b7c237ec..7bff5ce18 100644 --- a/images/developers/teams-scope-model-light.svg +++ b/images/developers/teams-scope-model-light.svg @@ -1,7 +1,7 @@ -TEAM SCOPESstate managersGuidance · Knowledge · Functions · Tasks · Data · Secrets · Dashboardsdestination='personal' | 'team:<id>'ContextRegistrydestination → root · membership gatedefaultrequires team_id inSESSION_DETAILS.team_idspersonal root{user_id}/{assistant_id}/…privacy floor — visible to one assistantteam rootsTeams/{team_id}/…one root per membership — visible to current memberssame table names under every rootGuidance · Knowledge · Functions/* · Tasks · Data · Secrets · Dashboards/* · Transcriptswrites target exactly one root — reads fan out across all of them +TEAM SCOPESstate managersGuidance · Knowledge · Functions · Tasks · Data · Secrets · Canvasdestination='personal' | 'team:<id>'ContextRegistrydestination → root · membership gatedefaultrequires team_id inSESSION_DETAILS.team_idspersonal root{user_id}/{assistant_id}/…privacy floor — visible to one assistantteam rootsTeams/{team_id}/…one root per membership — visible to current memberssame table names under every rootGuidance · Knowledge · Functions/* · Tasks · Data · Secrets · Canvas/* · Transcriptswrites target exactly one root — reads fan out across all of them \ No newline at end of file diff --git a/introduction.mdx b/introduction.mdx index 1957b09f6..2607761d4 100644 --- a/introduction.mdx +++ b/introduction.mdx @@ -38,7 +38,7 @@ of working together. watching it work. - Live data tables and custom dashboards your assistant builds on + Live data tables and custom views your assistant builds on request. diff --git a/local-deployment/custom-overrides.mdx b/local-deployment/custom-overrides.mdx index 4b7908a59..17ac4f4bf 100644 --- a/local-deployment/custom-overrides.mdx +++ b/local-deployment/custom-overrides.mdx @@ -159,11 +159,6 @@ Same `meta.json` + `rows.jsonl` shape as knowledge, for DataManager-owned reference tables. `meta.json` additionally supports `context` (target context path), `unique_keys`, and `auto_counting`. -### Dashboards — directory tree - -Tiles and layouts live under `tiles/` and `layouts/` namespaces, each with -the `meta.json` + `rows.jsonl` shape. - ### Functions and venvs — Python and TOML Custom functions are ordinary Python decorated with `@custom_function`; @@ -231,7 +226,7 @@ ManagerRegistry.get_task_scheduler().sync_custom( ) ``` -Contacts, secrets, blacklist, knowledge, data, and dashboards follow the +Contacts, secrets, blacklist, knowledge, and data follow the same collect-then-sync shape with their own collectors (`collect_contacts_from_directories`, `collect_secrets_from_directories`, and so on). Because every sync is hash-guarded, running the whole pass on diff --git a/mint.json b/mint.json index 87400cb38..c3a80ec12 100644 --- a/mint.json +++ b/mint.json @@ -142,7 +142,6 @@ "pages": [ "canvas/overview", "canvas/data", - "canvas/dashboards", "canvas/recipes", "canvas/developers" ] diff --git a/tasks/overview.mdx b/tasks/overview.mdx index 2bc5cc06f..e2d9017f6 100644 --- a/tasks/overview.mdx +++ b/tasks/overview.mdx @@ -43,8 +43,8 @@ assistant brings its full toolkit: your [workspace](/workspace/overview) (inbox, calendar, files), your [integrations](/integrations/overview), the web, every [communication channel](/communication/overview) you've connected, and the -[data tables and dashboards](/canvas/overview) it maintains for you — -recurring tasks that log fresh data each run are how live dashboards stay +[data tables and canvas views](/canvas/overview) it maintains for you — +recurring tasks that log fresh data each run are how live views stay live. Some favorites: diff --git a/teams/developers.mdx b/teams/developers.mdx index d43c52971..e91131e72 100644 --- a/teams/developers.mdx +++ b/teams/developers.mdx @@ -19,7 +19,7 @@ every reachable scope, and how the assistant is taught to route content. ## The core model: destinations, roots, and one registry Every state manager (guidance, knowledge, functions, tasks, data, secrets, -dashboards…) stores rows in **contexts** — hierarchical, table-like +canvases…) stores rows in **contexts** — hierarchical, table-like namespaces addressed by path. Team sharing is implemented as a pure *namespace* concern: the same tables exist under multiple **roots**, and one class decides which root any given operation touches. @@ -101,8 +101,8 @@ The authoritative list is `SHARED_SCOPED_TABLES` in `Tasks`, `Contacts`, `Secrets`, `Knowledge`, `Guidance`, the four `Functions/*` tables (`Compositional`, `Meta`, `Primitives`, `VirtualEnvs`), `FileRecords`, `Files`, `Data`, `BlackList`, -`Dashboards/Tiles`, `Dashboards/Layouts`, `Transcripts`, `Exchanges`, and -`Images`. +`Canvas/Views`, `Canvas/Actions`, `Canvas/Invocations`, `Transcripts`, +`Exchanges`, and `Images`. Matching is prefix-aware: `ContextRegistry._is_shared_scoped` walks parent paths, so a dynamic table like `Data/Samsara/daily_snapshots` inherits @@ -140,7 +140,7 @@ Every manager exposes the same public parameter — `destination: str | None` — on its write methods (`GuidanceManager.add_guidance`, `KnowledgeManager`'s table/row operations, `FunctionManager.add_functions`, `DataManager.create_table`, `SecretManager.add_secret`, -`DashboardManager.create_tile`, task creation in `TaskScheduler`, …). The +`CanvasManager.create_view`, task creation in `TaskScheduler`, …). The manager passes it straight through to `write_root`, which: 1. normalizes via `canonical_destination`, @@ -257,11 +257,11 @@ different semantics worth knowing: [`unify/task_scheduler/offline_runner.py`](https://github.com/unifyai/unify/blob/main/unify/task_scheduler/offline_runner.py). If the owning assistant has left the team by fire time, activation is refused (`destination_membership_revoked`). -- **Dashboards** — - [`unify/dashboard_manager/base.py`](https://github.com/unifyai/unify/blob/main/unify/dashboard_manager/base.py) - adds a second axis: a tile's *row* has a `destination`, but its live - data bindings take an independent `data_scope`, so a personal watch - tile can legitimately read a team's data. +- **Canvas** — + [`unify/canvas_manager/base.py`](https://github.com/unifyai/unify/blob/main/unify/canvas_manager/base.py) + adds a second axis: a view's *row* has a `destination`, while its + `visibility` (`private`, `team`, `public_link`) independently controls + who may read it — enforced server-side on every read path. ## How membership reaches the runtime diff --git a/teams/overview.mdx b/teams/overview.mdx index 43eb69b89..78559d006 100644 --- a/teams/overview.mdx +++ b/teams/overview.mdx @@ -84,7 +84,7 @@ top. - **Members** — the roster with online status, and a **Hire for this team** action to onboard a new [team assistant](/teams/team-assistants) directly into it. -- **Every brain section, team-scoped** — Tasks, Dashboards, Contacts, +- **Every brain section, team-scoped** — Tasks, Canvas, Contacts, Transcripts, Knowledge, Functions, Guidance, and Data all show the *team's* shared pool rather than any single assistant's memory. diff --git a/teams/shared-context.mdx b/teams/shared-context.mdx index 5a11e4b10..ed4746441 100644 --- a/teams/shared-context.mdx +++ b/teams/shared-context.mdx @@ -6,7 +6,7 @@ description: "Personal by default, shared where it belongs — the heart of team Everything an assistant accumulates — [knowledge and playbooks](/learning/overview), [skills](/learning/guidance-and-functions), [tasks](/tasks/overview), [data tables and -dashboards](/canvas/overview), credentials — lives in a **memory pool**. +canvas views](/canvas/overview), credentials — lives in a **memory pool**. Teams exist so that pool doesn't have to be all-or-nothing: every piece of context is either **personal** (belonging to one assistant) or **shared in a team** (accessible to every current member). @@ -41,7 +41,7 @@ Nearly everything can take either scope: | **Guidance** | How *you* like your reports | The SOP the whole unit follows | | **Functions** | A one-off helper script | The team's shared report-generation workflow | | **Tasks** | Your morning briefing | The team's weekly metrics run | -| **Data & dashboards** | Your scratch analysis | The ops dashboard everyone checks at standup | +| **Data & canvas** | Your scratch analysis | The ops board everyone checks at standup | | **Credentials** | Your personal API key | The team's shared service account | ## How assistants choose a scope @@ -74,7 +74,7 @@ skills, knowledge, and credentials across all of them at once: playbook lives in the team pool, not in a veteran's head. - The expense workflow that one assistant [learned last month](/learning/overview) runs identically for everyone on the team. -- The team dashboard is fed by team data tables that any member's +- The team's ops view is fed by team data tables that any member's assistant can update. Writing is the disciplined side: each saved item goes to **exactly one** @@ -102,7 +102,7 @@ org": ## Seeing scopes in the Console Every brain pane on an assistant — Contacts, Transcripts, Knowledge, -Functions, Guidance, Tasks, Data, Dashboards — carries **scope chips**: +Functions, Guidance, Tasks, Data, Canvas — carries **scope chips**: - **All** (the default) — the exact merged picture the assistant itself works from: its personal pool plus every team it belongs to, combined From 5266759438bdbb197d103ed2a2abf5795960011c Mon Sep 17 00:00:00 2001 From: Haris Mahmood Date: Wed, 12 Aug 2026 14:50:06 +0500 Subject: [PATCH 2/2] chore(rules): bump global-agent-rules to the current staging tip Picks up the auth rule whose data-endpoint example now names canvas tokens instead of dashboards, and regenerates the aggregate. --- .agents/global-rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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