Skip to content

Unified /sql routing for workspace and dashboard surfaces with live view/edit modes #407

Description

@BorisTyshkevich

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:

  1. Read ws, surface, and mode from the current URL.
  2. Missing surface means workspace.
  3. surface=dashboard selects Dashboard.
  4. Unknown surface values normalize to workspace.
  5. mode=view is meaningful only on Dashboard.
  6. Missing, edit, or unknown Dashboard mode means edit.
  7. Remove irrelevant/invalid parameters when canonicalizing.
  8. 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

/sql
  • 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:

  1. resolve one workspace;
  2. parse the requested surface;
  3. construct/render only the required surface controller;
  4. 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:

  1. Parse and canonicalize all three canonical URL forms.
  2. /sql resolves last-used workspace and rewrites URL.
  3. /sql creates a default workspace when none exist.
  4. Explicit missing ws shows not-found without fallback.
  5. Workspace surface renders for absent surface.
  6. Dashboard edit is default for surface=dashboard.
  7. mode=view renders read-only viewer with no mutation handlers.
  8. mode=edit canonicalizes away.
  9. Missing dashboard differs correctly in edit and view modes.
  10. Workspace/Dashboard same-tab navigation uses expected history behavior.
  11. View/Edit uses replaceState.
  12. OAuth callback cleanup retains ws, surface, and mode.
  13. Old detached stores/tokens are no longer required by the production route.
  14. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    inboxFiled mid-task; not yet triaged into the roadmap

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions