Goal
Serve the Workbench and its single workspace dashboard from the same /sql route, selected entirely by query parameters.
Canonical URLs:
/sql?ws=clickhouse_operations
/sql?ws=clickhouse_operations&surface=dashboard
/sql?ws=clickhouse_operations&surface=dashboard&mode=view
This issue depends on #406 for keyed multi-workspace persistence.
Product decisions
ws is the immutable human-readable workspace key.
- Each workspace owns zero or one dashboard.
surface selects the application surface:
- absent or
workspace -> Workbench/editor;
dashboard -> Dashboard.
- Dashboard edit mode is the default.
mode=view selects live read-only presentation mode.
mode=edit is accepted but canonicalized away.
- Dashboard view mode reads the same live workspace dashboard as edit mode.
- Durable detached dashboard snapshots are removed from the product model.
- Workbench <-> Dashboard navigation happens in the same browser tab by default.
- View <-> Edit changes presentation mode only and should not create noisy browser-history entries.
- There is no migration requirement for old development URLs or detached-view data.
Route contract
Introduce one pure parser/normalizer for /sql state. Equivalent shape:
type SqlRoute =
| {
surface: 'workspace';
workspaceKey: string | null;
}
| {
surface: 'dashboard';
workspaceKey: string | null;
mode: 'edit' | 'view';
};
Parsing rules:
- Read
ws, surface, and mode from the current URL.
- Missing
surface means workspace.
surface=dashboard selects Dashboard.
- Unknown
surface values normalize to workspace.
mode=view is meaningful only on Dashboard.
- Missing,
edit, or unknown Dashboard mode means edit.
- Remove irrelevant/invalid parameters when canonicalizing.
- Preserve unrelated application parameters that remain valid, especially OAuth callback parameters until the existing callback flow consumes them.
Canonicalization examples:
/sql?ws=x&surface=workspace -> /sql?ws=x
/sql?ws=x&surface=dashboard&mode=edit -> /sql?ws=x&surface=dashboard
/sql?ws=x&mode=view -> /sql?ws=x
/sql?ws=x&surface=unknown -> /sql?ws=x
Startup workspace resolution
After authentication/config setup:
Explicit workspace
/sql?ws=clickhouse_operations
- Resolve exactly that key through the multi-workspace repository.
- On success, set it as last-used and render the requested surface.
- On failure, render a dedicated Workspace not found state.
- Never silently substitute the last-used or another workspace.
No explicit workspace
- Resolve the last-used workspace key.
- If valid, canonicalize the URL to include it.
- If no workspace exists, create a default workspace and canonicalize to
/sql?ws=default or the actual collision-free generated key.
- If the stored last-used key is stale, choose a deterministic existing workspace (alphabetically by display name/key is acceptable), update last-used, and canonicalize.
Surface rendering
Do not bootstrap two independent applications based on pathname.
The application should:
- resolve one workspace;
- parse the requested surface;
- construct/render only the required surface controller;
- switch surfaces without a full document reload where practical.
Workspace surface
- Render normal editor/workbench UI.
- Dashboard mode parameters are absent.
Dashboard edit surface
- Load
workspace.dashboard from the same workspace aggregate.
- Register authoring capabilities: reorder, resize/layout changes, tile mutations, and persistence.
- If
dashboard === null, show an editable empty state with Create dashboard.
- Merely visiting the route must not create a dashboard.
Dashboard view surface
- Render the same current dashboard document and execute the same dashboard queries.
- Do not expose or register mutation capabilities.
- No drag/reorder/resize/delete handlers.
- No workspace commit path reachable from view-mode controls or shortcuts.
- If
dashboard === null, show This workspace has no dashboard and execute nothing.
- View mode is presentation-only, not authorization. The same local user may switch back to edit.
Navigation behavior
Workspace -> Dashboard
Use same-tab navigation and history.pushState():
/sql?ws=x
-> /sql?ws=x&surface=dashboard
This keeps browser Back useful and avoids default concurrent editing between Workbench and Dashboard tabs.
Dashboard -> Workspace
Use history.pushState() or browser Back when appropriate:
/sql?ws=x&surface=dashboard
-> /sql?ws=x
View <-> Edit
Use history.replaceState():
/sql?ws=x&surface=dashboard
<-> /sql?ws=x&surface=dashboard&mode=view
The Back button should not alternate repeatedly between view and edit presentation states.
Switching workspace
Preserve the current surface and mode when the user selects another workspace:
/sql?ws=a&surface=dashboard&mode=view
-> /sql?ws=b&surface=dashboard&mode=view
Dirty-draft confirmation is owned by the workspace-management UI issue.
Remove old Dashboard route assumptions
Retire production dependence on pathname suffix routing:
/sql/dashboard standalone route;
isDashboardRoute(pathname) as the application surface discriminator;
configBase() logic that strips /dashboard;
- dashboard-specific OAuth redirect URI behavior;
- code paths that bootstrap Dashboard separately solely because of pathname.
The OAuth callback must still preserve ws, surface, and mode while stripping only OAuth callback parameters.
Remove detached snapshot/view infrastructure
The product no longer needs durable detached read-only snapshots.
Remove or retire:
- detached dashboard view store;
- one-time dashboard state handoff token/store;
- session-bundle dashboard open source;
st dashboard transport parameter;
- detached workspace materialization and retention policy;
- Open for viewing… snapshot action;
- mode discrimination by which store contains a workspace ID.
Do not remove the existing credential handoff mechanism merely because state handoff is removed if credential handoff is still used elsewhere. Re-evaluate its necessity after same-tab navigation becomes the default.
Dashboard header controls
On Dashboard surface show:
[Workspace | Dashboard] [View | Edit]
Exact visual form may follow existing header primitives.
- Surface switch updates
surface.
- Mode switch is visible only on Dashboard.
- View mode is clearly indicated.
- Edit controls are not rendered in view mode.
Missing/deleted workspace behavior
If another tab deletes the workspace currently addressed by the URL:
- the next load/refresh/mutation failure resolves to Workspace not found;
- do not silently open another workspace;
- execute no dashboard queries after the workspace is known missing.
Real-time cross-tab invalidation is a separate follow-up; this issue only requires correct behavior when the missing state is observed.
Tests
Add pure route tests and integration tests covering at least:
- Parse and canonicalize all three canonical URL forms.
/sql resolves last-used workspace and rewrites URL.
/sql creates a default workspace when none exist.
- Explicit missing
ws shows not-found without fallback.
- Workspace surface renders for absent
surface.
- Dashboard edit is default for
surface=dashboard.
mode=view renders read-only viewer with no mutation handlers.
mode=edit canonicalizes away.
- Missing dashboard differs correctly in edit and view modes.
- Workspace/Dashboard same-tab navigation uses expected history behavior.
- View/Edit uses
replaceState.
- OAuth callback cleanup retains
ws, surface, and mode.
- Old detached stores/tokens are no longer required by the production route.
- Dashboard queries are not executed for missing workspace or missing dashboard in view mode.
Acceptance criteria
- Workbench and Dashboard are both served from
/sql.
- The canonical Dashboard edit URL is
/sql?ws=<key>&surface=dashboard.
- The canonical live read-only URL is
/sql?ws=<key>&surface=dashboard&mode=view.
- Dashboard view and edit use the same canonical workspace/dashboard data.
- Detached dashboard snapshots and one-time state handoff are removed.
- Default navigation between Workbench and Dashboard stays in the same tab.
- Explicit unknown workspace keys never fall back silently.
Non-goals
- Security/authorization enforcement for view mode.
- Multiple dashboards per workspace.
- Frozen historical dashboard snapshots.
- Cross-device bookmark portability when data is only local.
- Cross-tab stale-write prevention; tracked separately.
- Migration of old development URLs or IndexedDB records.
Goal
Serve the Workbench and its single workspace dashboard from the same
/sqlroute, selected entirely by query parameters.Canonical URLs:
This issue depends on #406 for keyed multi-workspace persistence.
Product decisions
wsis the immutable human-readable workspace key.surfaceselects the application surface:workspace-> Workbench/editor;dashboard-> Dashboard.mode=viewselects live read-only presentation mode.mode=editis accepted but canonicalized away.Route contract
Introduce one pure parser/normalizer for
/sqlstate. Equivalent shape:Parsing rules:
ws,surface, andmodefrom the current URL.surfacemeansworkspace.surface=dashboardselects Dashboard.surfacevalues normalize toworkspace.mode=viewis meaningful only on Dashboard.edit, or unknown Dashboard mode means edit.Canonicalization examples:
Startup workspace resolution
After authentication/config setup:
Explicit workspace
No explicit workspace
/sql?ws=defaultor the actual collision-free generated key.Surface rendering
Do not bootstrap two independent applications based on pathname.
The application should:
Workspace surface
Dashboard edit surface
workspace.dashboardfrom the same workspace aggregate.dashboard === null, show an editable empty state with Create dashboard.Dashboard view surface
dashboard === null, show This workspace has no dashboard and execute nothing.Navigation behavior
Workspace -> Dashboard
Use same-tab navigation and
history.pushState():This keeps browser Back useful and avoids default concurrent editing between Workbench and Dashboard tabs.
Dashboard -> Workspace
Use
history.pushState()or browser Back when appropriate:View <-> Edit
Use
history.replaceState():The Back button should not alternate repeatedly between view and edit presentation states.
Switching workspace
Preserve the current surface and mode when the user selects another workspace:
Dirty-draft confirmation is owned by the workspace-management UI issue.
Remove old Dashboard route assumptions
Retire production dependence on pathname suffix routing:
/sql/dashboardstandalone route;isDashboardRoute(pathname)as the application surface discriminator;configBase()logic that strips/dashboard;The OAuth callback must still preserve
ws,surface, andmodewhile stripping only OAuth callback parameters.Remove detached snapshot/view infrastructure
The product no longer needs durable detached read-only snapshots.
Remove or retire:
stdashboard transport parameter;Do not remove the existing credential handoff mechanism merely because state handoff is removed if credential handoff is still used elsewhere. Re-evaluate its necessity after same-tab navigation becomes the default.
Dashboard header controls
On Dashboard surface show:
Exact visual form may follow existing header primitives.
surface.Missing/deleted workspace behavior
If another tab deletes the workspace currently addressed by the URL:
Real-time cross-tab invalidation is a separate follow-up; this issue only requires correct behavior when the missing state is observed.
Tests
Add pure route tests and integration tests covering at least:
/sqlresolves last-used workspace and rewrites URL./sqlcreates a default workspace when none exist.wsshows not-found without fallback.surface.surface=dashboard.mode=viewrenders read-only viewer with no mutation handlers.mode=editcanonicalizes away.replaceState.ws,surface, andmode.Acceptance criteria
/sql./sql?ws=<key>&surface=dashboard./sql?ws=<key>&surface=dashboard&mode=view.Non-goals