Date: 2026-05-30 Status: Design approved; implementation pending
Replace NodeTool’s destination-based navigation (AppHeader mode label + PanelLeft’s 7 destinations + per-type full-page routes) with a single tabbed document workspace. Every open thing is a tab of a known type; each tab hosts the editor that already exists for that type. A tab carries a mode (View / Edit) that selects which surface renders. Where the left nav used to be, the shell shows a blank panel; a home/launcher screen fills that region in a later iteration.
The redesign reuses every existing editor. It builds a shell and a tab model around them; it does not rewrite the editors.
document + modeWorkspaceTab {
id: string
type: 'workflow' | 'image' | 'timeline' | 'model3d' | 'audio' | 'text'
ref: string // document id: workflowId, sequenceId, assetId, sketchDocumentId, …
mode: 'view' | 'edit'
title: string
}
(type, mode) selects the surface — generalizing the existing MiniApp(view) ⇄
NodeEditor(edit) split to all six types:
| type | view | edit |
|---|---|---|
| workflow | MiniAppPage (the app) |
NodeEditor (the graph) |
| image | ImageViewer |
SketchEditor / ImageEditor |
| model3d | Model3DViewer |
Model3DEditor |
| text | TextViewer (rendered) |
Lexical editor (textEditor/) |
| timeline | playback preview | TimelineEditor |
| audio | AudioViewer (waveform) |
— (view-only, no editor yet) |
The model allows both modes everywhere; we wire up what already exists.
WorkspaceShell (new) — top-level layout: global tab bar + content area + blank
left placeholder. Replaces the per-route layouts in web/src/index.tsx for the
unified experience.WorkspaceTabsStore (new, Zustand) — open tabs, active tab id, ordering; persists
to localStorage; syncs the active tab to the URL for deep links. Generalizes today’s
WorkflowManagerContext (open workflows) + useFileTabsStore.TabContentRouter (new) — maps the active tab’s (type, mode) to an editor
surface; lazy-loads each.ref/mode prop instead of reading route params. Most editors already separate a
“page reads param” wrapper from the inner editor (SketchEditorPage→SketchEditor;
MiniAppPage takes workflowId), so this is composition, not rewrite.WorkflowEditorSurface — bundles NodeEditor with its working panels (node menu /
inspector / logs-queue-versions-trace) that are app-global today (PanelLeft /
PanelRight / PanelBottom). These stop being global and become local to the workflow
Edit surface.WorkflowEditorSurface; no longer top-level siblings. Stores (PanelStore,
RightPanelStore, BottomPanelStore) keep working; only the mount location changes.SubgraphNode) → stay nested inside the workflow
Edit surface; they are part of editing one workflow, not top-level workspace tabs.[+] on the tab bar opens a minimal picker: New workflow / Open workflow / Open asset.
Stopgap only — the home/launcher replaces it later. Existing entry points (templates,
asset double-click) also open tabs.
In: WorkspaceShell, WorkspaceTabsStore, TabContentRouter, editor surfaces for
all six types reusing existing editors, the View/Edit mode toggle, tab persistence +
deep links, the minimal [+] picker.
Out: audio editor, the home/launcher screen, any net-new editor, redesigning AppHeader/PanelLeft destinations beyond leaving the region blank.
Build incrementally behind a new /workspace route, leaving every current route working.
MiniAppPage +
NodeEditor). This includes relocating the three global panels into
WorkflowEditorSurface — the most invasive single step.This keeps the live app intact throughout and lets each editor be validated in a tab before commitment.
ref from props, not the router; keep old param-reading page wrappers
for legacy routes during transition./workspace/:type/:ref?mode=); full tab set in localStorage.WorkflowEditorSurface is the most invasive step — do it first, behind the
route.useParams directly need a prop path; audit each for hidden router
coupling.openWorkflows localStorage on first load.