feat: workspace lineage — spawned workspaces nest under their parent in the sidebar

KI
21d ago
+2296-90

Links

  • Research/plan: plans/20260816-orca-nested-agents-research.md (in this PR)

Summary

  • Host tracks who spawned each workspace: parent_workspace_id + spawn_origin on the host workspaces table, accepted by workspaces.create/createSession, the CLI (defaulted from SUPERSET_WORKSPACE_ID, --parent/--no-parent override), and MCP workspaces_create.
  • The sidebar renders spawned workspaces as a file-tree: children nest under their parent with guide rails, hover-chevron collapse, persistent chevron + +N badge when collapsed. Works inside groups/sections.

Why / Context

An agent running superset ws create (or MCP workspaces_create) from inside a workspace produces a flat, unattributed sibling today — nothing anywhere records "who spawned this." This is Orca's most-shared surface (nested worktrees + nested agents) and the prerequisite for orchestrator/worker fleets reading as one unit. Research and competitive analysis in the included plans doc.

How It Works

  • Create-time edge: resolveParentWorkspaceId validates the requested parent (exists, live, same project); anything invalid records no lineage rather than failing the create — a wrong edge is worse than a flat row. Lineage never affects the git base branch.
  • Projection-time validation: nestSidebarWorkspaceLineage regroups each sidebar container depth-first. A child whose parent isn't rendered in the same container (pinned away, archived, in a different section) re-roots at depth 0; cycle members are re-promoted to roots — malformed data can never hide a row.
  • Explicit re-parenting (correction tool): workspace.update accepts parentWorkspaceId — a UUID re-nests, explicit null detaches to the top level, omitted leaves lineage untouched. Surfaced as CLI ws update --parent/--no-parent, MCP workspaces_update, and a "Move to top level" context-menu item on nested rows. Unlike create's ambient inference this errors loudly on any invalid target: unknown/archived parent, cross-project, self, main workspace, or a move under the workspace's own descendant (ancestor-chain walk with a visited set, so even a pre-existing DB cycle can't hang it). Moving a mid-chain node keeps its subtree. Still no DnD nesting — deliberate.
  • Collapse: persisted per workspace as sidebarState.lineageCollapsed on the existing v2WorkspaceLocalState collection row (no new localStorage key; bounded boolean; cleaned up with the row). Expanded parents swap the status icon for a chevron on row hover (same affordance as the project header); collapsed parents show a persistent chevron + direct-child count. Section collapse composes with and preserves lineage collapse.
  • Migration 0024 is a generated plain ADD COLUMN (no table rebuild, so no FK-trap exposure). SQLite records no ON DELETE action for ALTER-added FKs, so the hard-delete path detaches children app-side to match the schema's set-null intent.

Manual QA Checklist

All verified end-to-end over CDP against the dev desktop (screenshots in session):

  • tRPC create with parentWorkspaceId persists the edge + spawn_origin (host.db inspected)
  • Real CLI create with only SUPERSET_WORKSPACE_ID in env inherits the parent (spawn_origin=cli)
  • Tree renders depth-first with rails at 3 levels; chips strip aligns with the row indent
  • Nesting + rails render inside a group/section at the section's base indent
  • Chevron click collapses/expands without navigating (route unchanged); +N badge on collapsed parents; state survives reload
  • Section collapse → re-expand preserves lineage collapse inside
  • Edge cases staged live: A⇄B cycle written directly to host.db renders both rows (re-promoted, no loop); pinned parent → child re-roots at depth 0; destroyed parent → child re-roots inside its section
  • Auto-included local mains (no local-state row) render unchanged; pinned rows never nest

Testing

  • bun test workspace-reparent.test.ts — 8 pass (re-nest, explicit-null detach, omitted-field no-op, self/cycle/cross-project/archived/main rejections); filmed live E2E: UI menu detach, CLI re-nest/detach, cycle rejection
  • bun test buildDashboardSidebarProjects.test.ts — 17 pass (7 new lineage cases: nesting, grandchildren, re-rooting, in-section nesting, cycles, child counts, collapse)
  • bunx turbo typecheck --filter=@superset/desktop --filter=@superset/host-service --filter=@superset/cli --filter=@superset/mcp --filter=@superset/workspace-client — clean
  • bunx biome check on touched dirs — clean
  • Not run locally: host-service *.node-test.ts (native ABI seesaw with the dev app; relying on CI)

Design Decisions

  • Record-nothing on invalid parent instead of erroring: callers often infer the parent from ambient env; failing creates on a stale pointer would break agent workflows.
  • Column on the host table, not renderer local state: CLI/automation spawns can't write renderer-local sidebar state, so a localStorage-only edge would always land flat.
  • No instance-ID fencing (unlike Orca): our workspace ids are minted UUIDs that never get recycled; the table, not git, is authoritative.
  • Metadata-only lineage (no git-base coupling): matches Orca; stacked child-from-parent-branch + merge-into-parent is the deliberate follow-up differentiator.

Known Limitations

  • Sidebar DnD ordering interacts naively with nesting: dragging a child elsewhere in the same container snaps it back under its parent on the next build.
  • Dev-only observation: the sidebar auto-placer (usePlaceLocalWorktreesInSidebar) never fired in the dev environment because its readiness gate waits on sandbox brokering (BLAXEL_* env absent in dev). Pre-existing behavior, worth a prod sanity check that CLI-spawned children get placed.

Follow-ups

  • spawn_origin: "automation" wiring from the automation dispatch path
  • Nested agent rows under workspace rows (graduate the workspace-agents-row experiment)
  • Stacked-branch option + "merge into parent"

Summary by cubic

Spawned workspaces now nest under their parent in the sidebar, and lineage is editable. Previously all workspaces rendered flat and lineage was write‑once; now parentWorkspaceId renders a collapsible tree, and you can re‑parent or detach without affecting git branches.

  • Host/API: adds parent_workspace_id (self‑FK, indexed) and spawn_origin. workspaces.create/createSession accept parentWorkspaceId and optional spawnOrigin; invalid parents are dropped on create. workspace.update supports re‑parenting or explicit null to detach with strict validation (exists, live, same project, not self/main/not descendant). Events include parentWorkspaceId (present null clears cached lineage; absent preserves for pre‑column hosts). Exports WORKSPACE_SPAWN_ORIGINS; telemetry records parent_present and spawn_origin.

  • Desktop: sidebar nests children depth‑first with persistable collapse and chevrons; collapsed parents show a +N badge. Descendants are not draggable; dragging a root moves the whole subtree, and section moves cascade to descendants. If a parent is not in the same container, children re‑root at depth 0; cycle members promote to roots. Context menu adds “Move to top level.” Deleting a parent detaches children. Chevron reveals on hover or keyboard focus; +N badge is aria‑hidden.

  • CLI/MCP: @superset/cli create supports --parent/--no-parent (defaults from SUPERSET_WORKSPACE_ID; malformed env degrades to top level; explicit --parent stays strict). @superset/cli update adds --parent/--no-parent (mutually exclusive). @superset/mcp create/update accept lineage fields and set spawnOrigin="mcp".

  • Mobile: synthesized cloud rows include parentWorkspaceId (null when the host cannot serve lineage), matching desktop.

  • Web/Admin/Marketing: force dark theme statically and remove next-themes (set <html class="dark" style={{colorScheme:'dark'}}> and pass theme="dark" to the shared Toaster) to resolve React 19/Next 16 script warnings; no functional change.

  • Rollout: run host migration 0025_workspace_lineage. No client migrations.

Summary by CodeRabbit

  • New Features
    • Added nested workspace hierarchies in the sidebar with indentation, child counts, and collapse/expand controls.
    • Added “Move to top level” for nested workspaces.
    • Workspace creation and updates now support assigning or removing a parent through the CLI and MCP tools.
    • Workspace lineage is preserved across sessions and workspace events.
  • Bug Fixes
    • Added validation to prevent invalid, cross-project, self-referential, archived, or cyclic workspace relationships.
    • Nested workspaces can no longer be dragged independently from their parent.