Skip to content

Keep Dashboard and Workbench on one committed workspace state and make exports consistent #341

Description

@BorisTyshkevich

Summary

Editable Dashboard and Workbench must act as two editors over the same committed StoredWorkspaceV1 aggregate.

Any successful change made from either surface must be persisted atomically and projected back into the shared application state. After reload, both surfaces must see the same saved queries, full query Specs, Dashboard tile membership/order, tile shape, filters, and Dashboard style.

Dashboard export and Workbench workspace export must be built from the same committed workspace snapshot. Workbench export may include additional non-Dashboard queries, but every query shared with the Dashboard export and the Dashboard document itself must be canonically identical.

Current bug

The standalone Dashboard keeps edits in route-local currentDoc.

For move, resize, delete, add, and layout changes, runCommand() currently:

  1. applies and normalizes the command into currentDoc;
  2. updates the viewer session;
  3. starts an asynchronous whole-workspace commit;
  4. on success, only increments the local committed revision.

It does not project the committed workspace through app.applyCommittedWorkspace(result.workspace).

As a result:

  • the Dashboard UI can show a newer document than app.state.dashboard;
  • Dashboard export can serialize the pre-edit Dashboard snapshot;
  • Workbench state in the same application context remains stale;
  • rapid Dashboard edits can start overlapping commits outside the Workbench write queue;
  • an older commit may resolve after a newer one and overwrite tile order, placement, or style;
  • the JSON seen after export or reload can differ from the Dashboard currently shown.

Dashboard export currently reads app.state.dashboard, while the editable Dashboard renders currentDoc. This split is the immediate reason export/import appears not to preserve tile ordering, shape, and style.

Authoritative model

There is one current editable workspace:

interface StoredWorkspaceV1 {
  storageVersion: 1;
  id: string;
  name: string;
  queries: SavedQueryV2[];
  dashboard: DashboardDocumentV1 | null;
}

Workbench and editable Dashboard are projections/editors of this same aggregate.

There must not be independent long-lived authoritative copies for:

  • saved queries;
  • query SQL or Spec;
  • favorite/star state;
  • Dashboard tile membership;
  • dashboard.tiles[] order;
  • layout engine, preset, fallback, spans, or heights;
  • Dashboard filters.

A route-local document may exist for rendering or an optimistic gesture preview, but a successful commit must replace the shared committed projection.

Required write pipeline

Every editable operation from either Workbench or Dashboard must follow the same write discipline:

build a complete StoredWorkspaceV1 candidate
→ validate the complete candidate
→ serialize with every other workspace write
→ commit atomically
→ project the returned committed workspace into app state
→ synchronize the active surface from the committed result

Dashboard commands must use the same serialization queue used by saved-query mutations, or a replacement workspace-level command queue with equivalent ordering guarantees.

Conceptually:

const result = await app.serializeWrite(() =>
  app.workspace.commit(candidate)
);

if (!result.ok) {
  restoreOrReloadLastCommittedWorkspace();
  showCommitError(result.diagnostics);
  return;
}

app.applyCommittedWorkspace(result.workspace);
currentDoc = result.workspace.dashboard!;
session.syncDocument(currentDocWithRuntimeFilters);

The implementation may retain optimistic rendering while a commit is pending, but it must define deterministic rollback/reload behavior on failure.

Same-tab propagation

After a successful Dashboard commit:

  • app.state.dashboard equals the committed Dashboard;
  • app.state.savedQueries equals the committed query catalog;
  • any Workbench UI reading those values receives the new state;
  • Dashboard export immediately sees the committed result;
  • a later query save/star/delete starts from the latest Dashboard, not a stale aggregate.

After a successful Workbench commit:

  • Dashboard membership changes caused by favorite/star operations are present in the committed Dashboard;
  • query SQL and full Spec updates are visible when the Dashboard is reloaded or reopened;
  • a Dashboard command starts from the latest query catalog and Dashboard document.

Cross-tab minimum contract

Editable Dashboard and Workbench often run in separate browser tabs.

The minimum requirement for this issue is reload consistency:

  • both tabs read and write the same current workspace repository;
  • reloading Workbench after Dashboard edits loads the updated Dashboard;
  • reloading Dashboard after Workbench edits loads updated saved-query SQL/Specs and favorite-driven membership;
  • exports read the committed repository state after pending writes finish.

Live cross-tab propagation through BroadcastChannel, storage notifications, focus refresh, or another mechanism is optional here.

A stale tab must not silently overwrite a newer workspace without detection. If current repository commits provide no optimistic-concurrency protection, document and implement either:

  • workspace/dashboard revision comparison with a conflict result; or
  • a clearly scoped follow-up issue, while ensuring same-tab writes are strictly serialized in this issue.

Export contract

Both exports must be generated from one committed workspace snapshot, not independently assembled UI state.

Before export:

  1. await all previously accepted workspace writes;
  2. load or obtain the latest committed StoredWorkspaceV1;
  3. build the selected bundle from that object.

A suitable API may be:

await app.flushWorkspaceWrites();
const workspace = await app.workspace.loadCurrent();

or an equivalent queue operation that returns the latest committed aggregate without a second storage read.

Dashboard export

Dashboard export contains:

{
  queries: dashboardDependencyClosure,
  dashboards: [workspace.dashboard]
}

The dependency closure includes every complete saved-query document referenced by:

  • dashboard.tiles[].queryId;
  • Dashboard filter source queries.

Each included SavedQueryV2 must preserve the complete saved resource, including SQL, full Spec, panel configuration, name, description, favorite state, and supported extension fields.

The Dashboard document must preserve exactly:

  • tile membership;
  • dashboard.tiles[] order;
  • tile IDs and query references;
  • layout type and version;
  • flow preset;
  • Grid Tiles spans and heights;
  • fallback layout;
  • filters;
  • title and persisted metadata;
  • revision, subject to the existing portable-bundle contract.

Workbench workspace export

Workbench export contains:

{
  queries: workspace.queries,
  dashboards: workspace.dashboard ? [workspace.dashboard] : []
}

It may contain extra saved queries that are not used by the Dashboard, including non-favorite/non-starred queries.

It must not reconstruct, trim, or independently normalize the Dashboard or any query shared with Dashboard export.

Canonical consistency invariant

Given one committed workspace and the same export timestamp:

const dashboardBundle = buildDashboardExportBundle(
  workspace.dashboard!,
  workspace.queries,
  nowISO,
);

const workspaceBundle = buildWorkspaceExportBundle(
  workspace,
  nowISO,
);

The Dashboard document must be canonically identical in both bundles:

canonicalEqual(
  dashboardBundle.dashboards[0],
  workspaceBundle.dashboards[0],
) === true;

For every query in the Dashboard dependency closure, the matching query in the workspace bundle must also be canonically identical:

canonicalEqual(
  dashboardQuery,
  workspaceQuery,
) === true;

Only the presence of additional unrelated Workbench queries may differ.

Import contract

Dashboard import and workspace replacement already operate on complete portable resources and must continue to preserve the imported Dashboard document except for intentional identity/reference rewrites.

After import commits:

  • project the returned workspace through the same shared-state path;
  • reload/synchronize the active Dashboard from that committed result;
  • ensure Workbench export and Dashboard export use the newly imported aggregate;
  • do not derive tile order or shape again from favorite order, query catalog order, or layout defaults.

Failure behavior

A failed Dashboard workspace commit must not leave the UI indefinitely showing an unpersisted document as though it were saved.

Required behavior:

  • show a meaningful commit error;
  • restore or reload the last committed Dashboard;
  • preserve the previous committed workspace in application state;
  • do not advance the committed revision;
  • do not export the rejected candidate as committed state;
  • ensure a failed write does not wedge the shared serialization queue.

Tests

Shared state and write ordering

  • Dashboard move-tile success projects the returned workspace into app.state.
  • Grid span/height updates project the returned workspace into app.state.
  • Layout engine and flow preset changes project the returned workspace into app.state.
  • Tile add/delete operations project the returned workspace into app.state.
  • Rapid Dashboard commands commit in invocation order.
  • A delayed older commit cannot overwrite a newer accepted command.
  • Workbench saved-query writes and Dashboard writes share one serialization order.
  • A failed Dashboard commit restores/reloads the previous committed document and leaves shared state unchanged.

Reload consistency

  • Reloading Workbench after Dashboard edits sees exact tile order, placement, shape, style, filters, and membership.
  • Reloading Dashboard after Workbench query edits sees the latest SQL and full Spec.
  • Favorite/star membership changes made in Workbench are reflected in Dashboard after reload.
  • Non-favorite saved queries remain in the Workbench workspace but need not appear in the Dashboard dependency export.

Export regression

Starting from one committed workspace:

  1. reorder tiles;
  2. resize multiple Grid Tiles;
  3. change between Grid Tiles, Report, 2 columns, and 3 columns;
  4. add and remove a tile;
  5. change a saved query's SQL and full Spec;
  6. export immediately without reloading.

Decode the downloaded bundles and verify:

  • Dashboard export contains the latest exact Dashboard document;
  • Workbench workspace export contains the canonically identical Dashboard document;
  • common saved queries are canonically identical in both bundles;
  • Workbench export may contain additional unused queries;
  • tile order is preserved;
  • spans and heights are preserved;
  • layout engine, preset, and fallback are preserved;
  • filters and query references are preserved;
  • export waits for pending workspace writes;
  • import of the Dashboard bundle reproduces the same order, shape, and style.

Real-browser verification

  • Edit a Dashboard in one tab, reload Workbench, and export the workspace.
  • Edit a saved query Spec in Workbench, reload Dashboard, and export the Dashboard.
  • Compare the common Dashboard/query JSON resources for canonical equality.
  • Repeat with rapid move + resize + style changes before export.

Acceptance criteria

  • Editable Dashboard and Workbench use one committed StoredWorkspaceV1 source of truth.
  • Successful Dashboard commits are serialized with other workspace writes.
  • Successful Dashboard commits call the shared committed-workspace projection path.
  • A failed Dashboard commit cannot leave a false saved state in the UI.
  • Reloading either surface sees changes committed from the other.
  • Dashboard export waits for pending writes and uses the latest committed workspace.
  • Workbench export waits for pending writes and uses the same committed workspace.
  • Both exports include complete saved-query SQL and Specs.
  • The Dashboard resource is canonically identical in both exports.
  • Every common saved-query resource is canonically identical in both exports.
  • Workbench export may include additional non-Dashboard queries.
  • Dashboard export/import preserves exact tile order, shape, style, filters, and membership.
  • Unit, integration, and real-browser regression tests pass.

Non-goals

  • making the read-only detached Dashboard editable;
  • redesigning portable-bundle schemas;
  • changing favorite/star product semantics;
  • adding cloud or server-side workspace synchronization;
  • requiring live cross-tab updates when reload consistency is satisfied;
  • changing Dashboard query execution or result rendering.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions