You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Make Dashboard a first-class, independently constructible product module instead of deriving it directly from favorited saved queries and global AppState.
For the v1 product UX, keep the current simple model:
The browser has one current workspace. That workspace contains one ordered query collection and zero or one editable Dashboard. Starring a panel query adds one default tile to that Dashboard; un-starring removes that default tile.
Do not add dashboard collection management, dashboard folders, dashboard CRUD screens, or multi-dashboard authoring UX in this issue.
At the architecture and format level, introduce:
an atomic StoredWorkspaceV1 persistence aggregate;
an explicit DashboardDocumentV1;
separate authoring and read-only viewer sessions;
one canonical PortableBundleV1 JSON format;
versioned, lazy-loadable Dashboard layouts;
a normative flow@1 layout contract;
deterministic serialization, diagnostics, resource limits, and import remapping;
atomic, fallible Dashboard commands and whole-workspace validation.
The portable format may contain multiple dashboards for forward compatibility and external tooling, but the v1 Workbench manages at most one Dashboard.
This work should build on the narrow service/session boundaries from #276. Dashboard model and application code must not depend on the full App controller, Workbench tabs, editors, or global AppState.
Current problem
The current Dashboard looks standalone, but its domain model, persistence, and lifecycle are implicit:
membership is derived from spec.favorite;
sequence is inherited from Library query order;
layout is stored as browser-global preferences;
filters, tile cancellation, layout controls, rendering, and execution use the full App object;
saved queries, Library name, layout, filters, and other state are persisted under separate keys;
there is no persisted aggregate connecting the query collection and Dashboard;
there is no explicit Dashboard identity, tile sequence, authored placement, revision, layout engine, or filter-definition contract;
a Dashboard cannot be loaded, validated, previewed, or tested independently of Workbench state.
This prevents reliable reorder/resize persistence, atomic migration, direct read-only viewing, safe external bundle execution, future layout engines, AI-authored changes, and eventual server persistence.
Product decisions for v1
One current workspace, one Dashboard
The browser has one current workspace containing:
stable workspace ID;
workspace name;
ordered saved-query collection;
zero or one DashboardDocumentV1.
There is no Dashboard list or selector.
The user workflow remains query-centric:
Create or edit a saved query.
Configure its reusable panel presentation.
Star it to add it to the current Dashboard.
Open Dashboard authoring preview.
Reorder or resize tiles and select presentation variants.
Open a full-screen read-only preview.
Export one portable bundle.
Dashboard is an explicit aggregate
Do not continue recomputing Dashboard state from stars. Persist one explicit document containing tile instances, semantic order, layout, filters, and revision.
Star means Dashboard membership
For v1, the star action is:
Add to Dashboard / Remove from Dashboard
dashboard.tiles is the canonical membership source. Temporary compatibility dual-write to spec.favorite is allowed only during migration and must have a documented removal path.
The model may support several tile instances referencing one query, but the v1 star UI manages one default instance per query.
One canonical portable JSON format
All newly written importable/exportable JSON uses PortableBundleV1.
Continue reading legacy Library v1/v2 files through compatibility decoders, but do not write new Library-only JSON.
The v1 Workbench still imports or manages at most one Dashboard. When a bundle contains several dashboards, Open or Import asks the user to select one. This is import resolution, not multi-dashboard management UX.
Internal persistence: StoredWorkspaceV1
Portable interchange and browser persistence are separate contracts.
persist the complete workspace under one aggregate key or a storage mechanism with equivalent atomic replacement semantics;
validate the complete candidate before writing;
publish committed application state only after persistence succeeds;
failed persistence leaves the previous stored workspace intact;
failed persistence leaves the authoring draft dirty;
failed persistence does not increment Dashboard revision;
never partially replace queries, Dashboard, layout, or workspace metadata.
Workspace operation semantics
Rename workspace changes workspace.name; it does not automatically rename an existing Dashboard.
Creating a new workspace generates a new workspace ID and replaces the one current workspace after confirmation where data loss is possible.
Replace from bundle replaces queries and Dashboard atomically.
Import queries modifies the query collection only; imported favorite flags do not add Dashboard tiles.
Import Dashboard replaces the current Dashboard after explicit conflict resolution.
Browser reload loads StoredWorkspaceV1; it does not recreate Dashboard state from favorites.
Two files with the same name still produce distinct generated workspace IDs.
Export does not mutate workspace identity or Dashboard revision.
Multi-tab authoring policy
Concurrent Workbench authoring in several tabs is unsupported in v1.
WorkspaceRepository.commit is atomic replacement but not compare-and-swap.
Last successful commit wins.
The UI may warn when another tab appears active, but generation-based conflict rejection is out of scope.
This limitation must be documented and covered by a test proving that one commit never produces a partially mixed workspace.
Legacy migration marker
When no stored workspace aggregate exists:
Read legacy keys.
Build one candidate StoredWorkspaceV1.
Create the initial Dashboard from legacy favorites.
Convert legacy layout preferences to flow@1.
Validate the complete candidate.
Persist it atomically.
Treat successful aggregate persistence as migration completion.
Do not delete or modify legacy keys until the aggregate write succeeds.
Domain ownership rules
Use this decision test:
If a value normally remains the same when a query is placed in another Dashboard, it belongs to SavedQuery Spec. If it describes how one query instance is used in this Dashboard, it belongs to the Dashboard tile or layout. If it changes while viewing, it belongs to DashboardViewerSession.
SavedQuery Spec owns reusable presentation
It owns:
query name and description;
SQL and parameter contract;
base panel renderer and configuration;
field labels, units, decimals, colors, null text;
chart axes, legend, grid, stacking, curve, and renderer-specific configuration;
Dashboard role (panel, filter, or legacy setup);
reusable named Dashboard presentation variants;
default variant;
layout-neutral size hints.
It must not own actual Dashboard sequence, coordinates, span, actual height, or Dashboard-specific title overrides.
A tile owns stable instance identity, query reference, selected variant, optional small local presentation override, and optional local title/description.
Actual placement is layout-specific. Do not force flow spans, future grid coordinates, report sections, and custom canvas dimensions into one generic width/height schema.
Dashboard owns semantic sequence
dashboard.tiles[] is canonical for:
semantic order;
default execution planning;
DOM and accessibility traversal;
fallback rendering;
print/export order;
deterministic serialization;
drag and keyboard reorder.
Workspace query order remains catalog/authoring order and is independent of Dashboard tile order.
Presentation variants and tile overrides
QueryPresentationPatchV1 uses RFC 7396 JSON Merge Patch semantics against the saved query's base panel object.
For every panel tile, resolve presentation in this exact order:
SavedQuery base panel
-> apply selected named variant patch
-> apply tile-local override patch
-> validate final resolved panel
Normative patch rules
Variants and tile overrides are deltas, not complete snapshots.
Object members merge recursively according to JSON Merge Patch.
Arrays replace the previous array completely; arrays are never concatenated or merged by index.
A null member deletes an optional property.
Deleting a required property causes final resolved-panel validation to fail.
Unknown fields follow the openness rules of the underlying panel schema and are preserved only where that schema allows them.
In v1, neither a named variant nor a tile-local override may change the base renderer type. A patch containing a different panel.cfg.type fails validation.
The final resolved panel must pass the same structural and semantic validation as a normal saved-query panel, including result-column role validation when result metadata is available.
A selected variant name must exist. There is no silent fallback to the default variant when a persisted name is missing.
A missing selected variant uses defaultVariant when valid; otherwise it uses only the base panel.
A canonical resolver should be shared by Workbench preview, Dashboard viewer, import validation, tests, and future AI/MCP commands.
DashboardDocumentV1
Create canonical JSON Schema, generated types, generated validators, codec, semantic validation, diagnostics, and migration support through the repository's existing schema pipeline.
When a query is first added, authoring may derive and persist an initial placement from SavedQuery size hints. The viewer does not continuously derive placement from later hint changes.
A current-workspace route is bookmarkable. It authenticates, loads the stored workspace, verifies workspace and Dashboard IDs, and opens the Dashboard. If the current workspace was replaced, show unavailable/not-found rather than silently opening another Dashboard.
Session bundle source and cross-tab transport
Use IndexedDB one-time token records as the v1 shared origin-local transport for full-screen preview and temporary external-bundle viewing.
Requirements:
generate an unguessable token;
write the validated bundle/document and expiry to IndexedDB before opening the new tab;
the viewer atomically consumes the token record;
tokens are one-time and short-lived;
delete the record after successful consumption or expiry;
remove the token from the visible URL using history.replaceState after resolution;
ordinary sessionStorage alone is not sufficient for the cross-tab repository.
A later implementation may add postMessage or BroadcastChannel for readiness signaling, but IndexedDB is the durable source of truth for the handoff.
External file flow
For arbitrary external files:
authenticate;
choose/drop the file;
parse and validate;
show execution preflight;
require explicit confirmation;
create the viewer session and run.
Do not promise OAuth-surviving local file state unless it has been materialized into the defined one-time source repository.
External bundles are untrusted executable input
Opening without importing can still execute embedded SQL against the authenticated ClickHouse host.
Before first execution of an external bundle, show:
Dashboard title;
target ClickHouse host;
panel-query count;
filter-query count;
tile count;
parameters and defaults;
unsupported or rejected statements;
execution limits.
Require an explicit action such as:
Run Dashboard on <host>
Never auto-execute an external bundle after file selection or OAuth return.
Viewer policy must enforce:
existing read/row-returning gate for panel and filter queries;
no Setup execution;
complete semantic validation before any query starts;
row and byte caps;
bounded concurrency;
per-tile cancellation;
stale-result protection;
session destruction cancels pending and running work;
no partial execution of an invalid bundle.
Open, import, and replace semantics
Replace ambiguous Append language with resource-oriented operations.
Open bundle
Validate and view without mutating the current workspace.
one Dashboard: open its viewer after trust preflight when external;
queries only: show query-import preview;
several Dashboards: ask which one to open;
invalid dependency closure: show diagnostics and execute nothing.
Import queries
Import selected queries only. Do not add Dashboard tiles based on imported favorite flags.
Conflict actions:
use existing;
import as copy;
replace existing;
skip.
Import Dashboard and required queries
Import one selected Dashboard plus its dependency closure. Because v1 has one Dashboard, importing when one already exists requires explicit replacement or cancellation. Dashboard merge is out of scope.
Replace workspace
Replace current queries and Dashboard atomically after complete validation.
use-existing may be automatic only when canonical normalized query contents match; differing content requires explicit confirmation;
copy generates a new ID and rewrites tile query IDs, filter source IDs, and every schema-defined future query reference through one central rewrite operation;
replace keeps the source ID and replaces the query only in the candidate workspace;
skip is allowed for query-only import, but skipping a required Dashboard dependency invalidates Dashboard import;
do not silently remove dependent tiles or filters;
no application state changes before the fully rewritten candidate validates.
Legacy Library v1/v2 decoding normalizes to an in-memory portable bundle with dashboards: [].
Revision semantics
revision is a persistence-commit revision.
Increment once for each successfully committed Dashboard document mutation. Validation, preview, and export never increment revision.
Rules:
authoring commands mutate an in-memory draft and do not increment persisted revision;
one successful manual save increments once;
one successful autosave increments once;
several draft changes in one commit increment once;
failed persistence leaves revision unchanged;
initial legacy migration starts at revision 1;
import as copy generates a new Dashboard ID and starts at revision 1;
replace preserves imported Dashboard identity and revision;
the first successful local edit after replace increments from the imported revision.
Fallible, atomic authoring commands
Draft commands and persistence commits are separate atomic boundaries.
clone current draft
-> apply command to isolated candidate
-> normalize through active layout plugin
-> validate Dashboard structure
-> validate query references and roles
-> resolve and validate presentations
-> validate layout placement
-> validate resource limits
-> sort diagnostics
-> replace draft only when valid
On failure, the previous draft remains byte-for-byte unchanged.
Stale command protection
Async commands may include expectedDraftVersion. A mismatch returns a structured dashboard-command-stale diagnostic and does not mutate the draft.
Expected failures
add-query can fail for missing query, incompatible role, duplicate default instance, tile limit, invalid default variant, or invalid initial placement.
move-tile can fail for missing tile, out-of-range index, or stale draft version. Do not silently clamp indexes.
update-placement can fail for missing tile, unknown fields, unsupported span/height, plugin rejection, or size limits.
change-layout can fail when the plugin cannot load, the version is unsupported, fallback is invalid, or normalization produces an invalid document. The previous layout remains intact.
Persistence failure behavior
A valid draft may remain dirty after a failed repository commit. The committed workspace and persisted revision remain unchanged, and the user can retry.
Saved-query mutations must preserve workspace validity
Dashboard commands are not the only operations that can invalidate Dashboard references. Every saved-query mutation must construct and validate a complete candidate workspace.
This includes:
deleting a referenced query;
changing a tile query from panel to filter or setup;
changing a filter source role;
deleting a selected presentation variant;
changing parameter declarations used by Dashboard filters;
changing base panel type or structure so resolved tile presentation becomes invalid.
Policy:
Reject an invalidating saved-query mutation unless the user explicitly selects an atomic repair that produces a valid candidate workspace.
Examples of repair choices:
remove affected tiles;
remove affected filter definitions;
switch affected tiles to another valid variant;
remap to another query;
cancel the query mutation.
The repair and query mutation must be applied to one candidate workspace, validated, and committed atomically. Ordinary query editing must not bypass Dashboard invariants.
Dashboard sessions
DashboardViewerSession
Create a read-only runtime session usable by direct workspace routes, temporary session-bundle routes, full-screen preview, embedded preview, and tests.
It owns runtime-only state:
immutable Dashboard snapshot;
resolved queries and presentations;
filter values;
execution queue and bounded concurrency;
per-tile cancellation;
stale-wave protection;
tile results, errors, and progress;
loaded layout controller;
timers, listeners, effects, and destroy() cleanup.
It depends on narrow interfaces such as query execution, connection session, query resolver, presentation resolver, and layout registry. It must not depend on Workbench tabs, CodeMirror, schema browser DOM, mobile Workbench navigation, full App, or global AppState.
DashboardAuthoringSession
Owns the one editable Dashboard draft, dirty state, selected tile, atomic commands, diagnostics, preview state, and commit/export operations. Workbench and future AI/MCP integrations invoke typed commands instead of mutating signals or document objects directly.
rewritten candidate validates before atomic commit.
Dependency boundaries
Add architecture tests or lint rules proving Dashboard model/application modules do not import Workbench UI, full App, global AppState, or editor modules.
Acceptance criteria
Browser persistence uses one atomic StoredWorkspaceV1 aggregate.
The v1 browser exposes one current workspace and zero or one Dashboard.
Dashboard membership, tile order, and layout no longer derive from favorites and global layout preferences after migration.
New JSON exports use PortableBundleV1; legacy Library formats remain readable.
SavedQuery Spec owns base panel, variants, and size hints; Dashboard owns tile instances and actual placement.
Presentation variants and tile overrides use the documented JSON Merge Patch resolution rules.
Resolved presentations validate before authoring success, import success, or execution.
Dashboard filters use the concrete v1 contract; Setup execution is rejected.
flow@1 and DashboardLayoutFallbackV1 have complete portable contracts.
KPI bands are deterministic derived flow@1 structures, not persisted groups.
Canonical bundle, map-key, dependency, and diagnostic ordering is deterministic.
All stated resource limits are enforced with precise diagnostics.
Authoring commands are fallible and atomic, with draftVersion separate from persisted revision.
Every saved-query mutation validates a complete candidate workspace and requires atomic repair when needed.
Dashboard revision increments only after a successful persistence commit; export never increments it.
Open/import conflict handling builds and validates a complete ID mapping before commit.
Summary
Make Dashboard a first-class, independently constructible product module instead of deriving it directly from favorited saved queries and global
AppState.For the v1 product UX, keep the current simple model:
Do not add dashboard collection management, dashboard folders, dashboard CRUD screens, or multi-dashboard authoring UX in this issue.
At the architecture and format level, introduce:
StoredWorkspaceV1persistence aggregate;DashboardDocumentV1;PortableBundleV1JSON format;flow@1layout contract;The portable format may contain multiple dashboards for forward compatibility and external tooling, but the v1 Workbench manages at most one Dashboard.
This work should build on the narrow service/session boundaries from #276. Dashboard model and application code must not depend on the full
Appcontroller, Workbench tabs, editors, or globalAppState.Current problem
The current Dashboard looks standalone, but its domain model, persistence, and lifecycle are implicit:
spec.favorite;Appobject;This prevents reliable reorder/resize persistence, atomic migration, direct read-only viewing, safe external bundle execution, future layout engines, AI-authored changes, and eventual server persistence.
Product decisions for v1
One current workspace, one Dashboard
The browser has one current workspace containing:
DashboardDocumentV1.There is no Dashboard list or selector.
The user workflow remains query-centric:
Dashboard is an explicit aggregate
Do not continue recomputing Dashboard state from stars. Persist one explicit document containing tile instances, semantic order, layout, filters, and revision.
Star means Dashboard membership
For v1, the star action is:
dashboard.tilesis the canonical membership source. Temporary compatibility dual-write tospec.favoriteis allowed only during migration and must have a documented removal path.The model may support several tile instances referencing one query, but the v1 star UI manages one default instance per query.
One canonical portable JSON format
All newly written importable/exportable JSON uses
PortableBundleV1.Continue reading legacy Library v1/v2 files through compatibility decoders, but do not write new Library-only JSON.
Portable format may contain several dashboards
PortableBundleV1containsdashboards: DashboardDocumentV1[].The v1 Workbench still imports or manages at most one Dashboard. When a bundle contains several dashboards, Open or Import asks the user to select one. This is import resolution, not multi-dashboard management UX.
Internal persistence:
StoredWorkspaceV1Portable interchange and browser persistence are separate contracts.
WorkspaceRepositoryRequirements:
Workspace operation semantics
workspace.name; it does not automatically rename an existing Dashboard.StoredWorkspaceV1; it does not recreate Dashboard state from favorites.Multi-tab authoring policy
Concurrent Workbench authoring in several tabs is unsupported in v1.
WorkspaceRepository.commitis atomic replacement but not compare-and-swap.Legacy migration marker
When no stored workspace aggregate exists:
StoredWorkspaceV1.flow@1.Do not delete or modify legacy keys until the aggregate write succeeds.
Domain ownership rules
Use this decision test:
SavedQuery Spec owns reusable presentation
It owns:
panel,filter, or legacysetup);It must not own actual Dashboard sequence, coordinates, span, actual height, or Dashboard-specific title overrides.
Suggested shape:
Dashboard tile owns query usage in this Dashboard
A tile owns stable instance identity, query reference, selected variant, optional small local presentation override, and optional local title/description.
Dashboard layout owns actual placement and size
Actual placement is layout-specific. Do not force flow spans, future grid coordinates, report sections, and custom canvas dimensions into one generic width/height schema.
Dashboard owns semantic sequence
dashboard.tiles[]is canonical for:Workspace query order remains catalog/authoring order and is independent of Dashboard tile order.
Presentation variants and tile overrides
QueryPresentationPatchV1uses RFC 7396 JSON Merge Patch semantics against the saved query's basepanelobject.Resolution order
For every panel tile, resolve presentation in this exact order:
Normative patch rules
nullmember deletes an optional property.panel.cfg.typefails validation.defaultVariantwhen valid; otherwise it uses only the base panel.A canonical resolver should be shared by Workbench preview, Dashboard viewer, import validation, tests, and future AI/MCP commands.
DashboardDocumentV1Create canonical JSON Schema, generated types, generated validators, codec, semantic validation, diagnostics, and migration support through the repository's existing schema pipeline.
Do not add ownership, ACL, public visibility, share policy, or server identifiers in this issue.
Filter contract
Semantic validation must enforce:
filter;Setup is unsupported in Dashboard v1
Dashboard v1 does not execute Setup queries.
setupQueriesfield.setup, but Dashboard v1 never executes it.PortableBundleV1Both arrays are required even when empty.
Schema hierarchy:
Cross-resource semantic validation
Validate before any state mutation or SQL execution:
Diagnostics use stable application codes and exact paths.
Portable dependency closure
Exporting one Dashboard includes exactly:
A query referenced more than once appears once in
queries[].Opening a bundle for viewing must not import its queries into the current workspace.
Canonical output ordering
All newly encoded portable bundles and persisted workspace documents must be deterministic.
Dashboard dependency query order
For a Dashboard-only dependency closure:
dashboard.tiles[]in semantic order.dashboard.filters[]in filter order.sourceQueryIdnot already emitted.Full current-workspace export
Multi-Dashboard bundles produced by tooling
Object key ordering
layout.items, variant names, and column maps sort lexicographically.Use one canonical encoder for export, persistence snapshots, hashing, equality checks, and snapshot tests.
Diagnostic ordering
Before returning diagnostics, sort by:
Concurrent validation must not make diagnostic order nondeterministic.
Resource limits
Define concrete schema and runtime bounds. Initial v1 limits:
Requirements:
Dashboard layout registry and fallback
Use compile-time registered, lazy-loadable layout modules. Do not load arbitrary remote JavaScript plugins in v1.
Concrete fallback contract
Rules:
flow@1layout, not a second layout-neutral placement language.flow@1fallback fails before execution.dashboard.tiles[].Normative
flow@1contractFlowTilePlacementV1is closed: unknown placement fields fail validation. Future extension requiresflow@2or an explicit extension namespace.Presets
full-widthreportcolumns-2columns-3Exact pixels, gaps, and maximum report width remain renderer/theme concerns.
Missing placement defaults
When a query is first added, authoring may derive and persist an initial placement from SavedQuery size hints. The viewer does not continuously derive placement from later hint changes.
Preset changes
Changing presets does not rewrite stored spans.
One-column presets render every effective span as one while preserving stored values for switching back.
Packing and collision rules
flow@1has no persistedx/ycoordinates. It uses deterministic row-major packing:dashboard.tiles[]order.Semantic, DOM, visual, and keyboard order
For
flow@1:Do not use CSS masonry or visual ordering that diverges from DOM order.
Dragging or keyboard movement updates
dashboard.tiles[]; placement remains attached by tile ID.Responsive/mobile normalization
At the mobile breakpoint:
dashboard.tiles[];Do not persist separate mobile coordinates in v1.
Height semantics
compact < medium < largeis normative ordering. Exact pixel heights are renderer-defined.Height does not implicitly select a presentation variant.
Accessible authoring
Pointer drag/resize cannot be the only editing mechanism.
Provide equivalent commands and controls for:
Requirements:
KPI bands
KPI bands are deterministic derived structures created by
flow@1; they are not persisted as Dashboard entities.Grouping algorithm:
Rules:
User-controlled groups or sections require a future layout/document version.
Dashboard open-source contract
Do not imply that arbitrary local bundles have durable direct URLs.
Current workspace source
A current-workspace route is bookmarkable. It authenticates, loads the stored workspace, verifies workspace and Dashboard IDs, and opens the Dashboard. If the current workspace was replaced, show unavailable/not-found rather than silently opening another Dashboard.
Session bundle source and cross-tab transport
Use IndexedDB one-time token records as the v1 shared origin-local transport for full-screen preview and temporary external-bundle viewing.
Requirements:
history.replaceStateafter resolution;sessionStoragealone is not sufficient for the cross-tab repository.A later implementation may add
postMessageorBroadcastChannelfor readiness signaling, but IndexedDB is the durable source of truth for the handoff.External file flow
For arbitrary external files:
Do not promise OAuth-surviving local file state unless it has been materialized into the defined one-time source repository.
External bundles are untrusted executable input
Opening without importing can still execute embedded SQL against the authenticated ClickHouse host.
Before first execution of an external bundle, show:
Require an explicit action such as:
Never auto-execute an external bundle after file selection or OAuth return.
Viewer policy must enforce:
Open, import, and replace semantics
Replace ambiguous
Appendlanguage with resource-oriented operations.Open bundle
Validate and view without mutating the current workspace.
Import queries
Import selected queries only. Do not add Dashboard tiles based on imported favorite flags.
Conflict actions:
Import Dashboard and required queries
Import one selected Dashboard plus its dependency closure. Because v1 has one Dashboard, importing when one already exists requires explicit replacement or cancellation. Dashboard merge is out of scope.
Replace workspace
Replace current queries and Dashboard atomically after complete validation.
Transactional import planner
Required order:
Rules:
use-existingmay be automatic only when canonical normalized query contents match; differing content requires explicit confirmation;copygenerates a new ID and rewrites tile query IDs, filter source IDs, and every schema-defined future query reference through one central rewrite operation;replacekeeps the source ID and replaces the query only in the candidate workspace;skipis allowed for query-only import, but skipping a required Dashboard dependency invalidates Dashboard import;Legacy Library v1/v2 decoding normalizes to an in-memory portable bundle with
dashboards: [].Revision semantics
revisionis a persistence-commit revision.Rules:
1;1;Fallible, atomic authoring commands
Draft commands and persistence commits are separate atomic boundaries.
Atomic command algorithm
Every command must:
On failure, the previous draft remains byte-for-byte unchanged.
Stale command protection
Async commands may include
expectedDraftVersion. A mismatch returns a structureddashboard-command-stalediagnostic and does not mutate the draft.Expected failures
add-querycan fail for missing query, incompatible role, duplicate default instance, tile limit, invalid default variant, or invalid initial placement.move-tilecan fail for missing tile, out-of-range index, or stale draft version. Do not silently clamp indexes.update-placementcan fail for missing tile, unknown fields, unsupported span/height, plugin rejection, or size limits.change-layoutcan fail when the plugin cannot load, the version is unsupported, fallback is invalid, or normalization produces an invalid document. The previous layout remains intact.Persistence failure behavior
A valid draft may remain dirty after a failed repository commit. The committed workspace and persisted revision remain unchanged, and the user can retry.
Saved-query mutations must preserve workspace validity
Dashboard commands are not the only operations that can invalidate Dashboard references. Every saved-query mutation must construct and validate a complete candidate workspace.
This includes:
paneltofilterorsetup;Policy:
Examples of repair choices:
The repair and query mutation must be applied to one candidate workspace, validated, and committed atomically. Ordinary query editing must not bypass Dashboard invariants.
Dashboard sessions
DashboardViewerSessionCreate a read-only runtime session usable by direct workspace routes, temporary session-bundle routes, full-screen preview, embedded preview, and tests.
It owns runtime-only state:
destroy()cleanup.It depends on narrow interfaces such as query execution, connection session, query resolver, presentation resolver, and layout registry. It must not depend on Workbench tabs, CodeMirror, schema browser DOM, mobile Workbench navigation, full
App, or globalAppState.DashboardAuthoringSessionOwns the one editable Dashboard draft, dirty state, selected tile, atomic commands, diagnostics, preview state, and commit/export operations. Workbench and future AI/MCP integrations invoke typed commands instead of mutating signals or document objects directly.
Suggested module boundaries
Exact paths may differ, but dependency direction must remain:
Implementation phases
Phase 1: contracts and codecs
StoredWorkspaceV1,DashboardDocumentV1,PortableBundleV1, filter, presentation patch,flow@1, and fallback schemas.Phase 2: atomic persistence and migration
WorkspaceRepositoryatomic aggregate persistence.Phase 3: authoring domain
draftVersionchecks.Phase 4: viewer and flow layout
DashboardViewerSession.flow@1viewer/editor behavior.Phase 5: portable open/import/export
Phase 6: direct and full-screen viewing
Required tests
Schemas, codecs, and limits
Presentation resolution
nulldeletes optional fields;Workspace persistence and migration
Whole-workspace validity
Atomic commands
draftVersiononce;Flow layout
Open, trust, import, and remapping
Dependency boundaries
Add architecture tests or lint rules proving Dashboard model/application modules do not import Workbench UI, full
App, globalAppState, or editor modules.Acceptance criteria
StoredWorkspaceV1aggregate.PortableBundleV1; legacy Library formats remain readable.flow@1andDashboardLayoutFallbackV1have complete portable contracts.flow@1structures, not persisted groups.draftVersionseparate from persisted revision.sessionStoragesharing.Non-goals