diff --git a/canvas/src/components/WorkspaceNode.tsx b/canvas/src/components/WorkspaceNode.tsx index e915e17d..a5e3192f 100644 --- a/canvas/src/components/WorkspaceNode.tsx +++ b/canvas/src/components/WorkspaceNode.tsx @@ -81,9 +81,23 @@ export function WorkspaceNode({ id, data }: NodeProps>) }} onDoubleClick={(e) => { e.stopPropagation(); - if (hasChildren) { - window.dispatchEvent(new CustomEvent("molecule:zoom-to-team", { detail: { nodeId: id } })); + if (!hasChildren) return; + // A collapsed parent double-click EXPANDS first (flipping the + // collapsed flag + persisting it via the API). Once expanded, + // subsequent double-clicks zoom-to-team so the user can see + // the hierarchy fit in the viewport. Matches the user's ask: + // default-collapsed for clean first paint, one gesture reveals + // the subtree. + if (data.collapsed) { + const state = useCanvasStore.getState(); + state.setCollapsed(id, false); + // Fire-and-forget persist so reload retains the expansion. + import("@/lib/api").then(({ api }) => { + api.patch(`/workspaces/${id}`, { collapsed: false }).catch(() => {}); + }); + return; } + window.dispatchEvent(new CustomEvent("molecule:zoom-to-team", { detail: { nodeId: id } })); }} onContextMenu={(e) => { e.preventDefault(); diff --git a/workspace-server/internal/handlers/org_import.go b/workspace-server/internal/handlers/org_import.go index 8435f8a0..bfc3e251 100644 --- a/workspace-server/internal/handlers/org_import.go +++ b/workspace-server/internal/handlers/org_import.go @@ -88,11 +88,19 @@ func (h *OrgHandler) createWorkspaceTree(ws OrgWorkspace, parentID *string, defa ctx := context.Background() - // Insert workspace + // Org-template imports can drop dozens of nested workspaces onto the + // canvas at once. Letting them render expanded by default sprays + // child cards across the viewport (sibling workspaces spill below + // the parent before the user can orient themselves). Default every + // parent in the imported tree to collapsed — the parent card shows + // only its header + "N sub" badge until the user double-clicks to + // expand it. Leaf workspaces stay expanded (nothing to hide). + initialCollapsed := len(ws.Children) > 0 + _, err := db.DB.ExecContext(ctx, ` - INSERT INTO workspaces (id, name, role, tier, runtime, awareness_namespace, status, parent_id, workspace_dir, workspace_access) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) - `, id, ws.Name, role, tier, runtime, awarenessNS, "provisioning", parentID, workspaceDir, workspaceAccess) + INSERT INTO workspaces (id, name, role, tier, runtime, awareness_namespace, status, parent_id, workspace_dir, workspace_access, collapsed) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) + `, id, ws.Name, role, tier, runtime, awarenessNS, "provisioning", parentID, workspaceDir, workspaceAccess, initialCollapsed) if err != nil { log.Printf("Org import: failed to create %s: %v", ws.Name, err) return fmt.Errorf("failed to create %s: %w", ws.Name, err)