({
workspaceId,
projectName,
projectPath,
workspaceName,
workspaceTitle,
namedWorkspacePath,
runtimeConfig,
leftSidebarCollapsed,
onToggleLeftSidebarCollapsed,
onOpenTerminal,
})
| 78 | } as const; |
| 79 | |
| 80 | export const WorkspaceMenuBar: React.FC<WorkspaceMenuBarProps> = ({ |
| 81 | workspaceId, |
| 82 | projectName, |
| 83 | projectPath, |
| 84 | workspaceName, |
| 85 | workspaceTitle, |
| 86 | namedWorkspacePath, |
| 87 | runtimeConfig, |
| 88 | leftSidebarCollapsed, |
| 89 | onToggleLeftSidebarCollapsed, |
| 90 | onOpenTerminal, |
| 91 | }) => { |
| 92 | const { api } = useAPI(); |
| 93 | const { disableWorkspaceAgents } = useAgent(); |
| 94 | const { preflightArchiveWorkspace, archiveWorkspace } = useWorkspaceActions(); |
| 95 | const { workspaceMetadata } = useWorkspaceContext(); |
| 96 | const workspaceHeartbeatsEnabled = useExperimentValue(EXPERIMENT_IDS.WORKSPACE_HEARTBEATS); |
| 97 | const openTerminalPopout = useOpenTerminal(); |
| 98 | const openInEditor = useOpenInEditor(); |
| 99 | const gitStatus = useGitStatus(workspaceId); |
| 100 | const runtimeStatus = useRuntimeStatus(workspaceId); |
| 101 | const workspaceEntry = workspaceMetadata.get(workspaceId); |
| 102 | const showMultiProjectStatus = workspaceEntry != null && isMultiProject(workspaceEntry); |
| 103 | // The workspace's metadata.projectName is the parent project (since worktrees |
| 104 | // are owned by the top-most parent). When the workspace is scoped to a |
| 105 | // sub-project we surface the hierarchy as "parent / child" so the menu bar |
| 106 | // alone reveals the sub-project context. |
| 107 | const { userProjects } = useProjectContext(); |
| 108 | const subProjectPath = workspaceEntry?.subProjectPath; |
| 109 | const projectLabel = |
| 110 | subProjectPath && userProjects.has(subProjectPath) |
| 111 | ? formatProjectHierarchyLabel(subProjectPath, userProjects) |
| 112 | : projectName; |
| 113 | const runtimeStatusStore = useRuntimeStatusStoreRaw(); |
| 114 | const { canInterrupt, isStarting, awaitingUserQuestion, loadedSkills, skillLoadErrors } = |
| 115 | useWorkspaceSidebarState(workspaceId); |
| 116 | const isWorking = (canInterrupt || isStarting) && !awaitingUserQuestion; |
| 117 | const { startSequence: startTutorial } = useTutorial(); |
| 118 | const [editorError, setEditorError] = useState<string | null>(null); |
| 119 | const [debugLlmRequestOpen, setDebugLlmRequestOpen] = useState(false); |
| 120 | const [mcpModalOpen, setMcpModalOpen] = useState(false); |
| 121 | const [heartbeatModalOpen, setHeartbeatModalOpen] = useState(false); |
| 122 | const [availableSkills, setAvailableSkills] = useState<AgentSkillDescriptor[]>([]); |
| 123 | const [invalidSkills, setInvalidSkills] = useState<AgentSkillIssue[]>([]); |
| 124 | const isSkillsMountedRef = useRef(true); |
| 125 | const moreActionsButtonRef = useRef<HTMLButtonElement | null>(null); |
| 126 | |
| 127 | const skillsRequestIdRef = useRef(0); |
| 128 | const [moreMenuOpen, setMoreMenuOpen] = useState(false); |
| 129 | const [archiveConfirmOpen, setArchiveConfirmOpen] = useState(false); |
| 130 | // Untracked paths from archive preflight that the user needs to acknowledge. |
| 131 | // When set, the confirmation dialog warns about permanent file deletion. |
| 132 | const [archiveUntrackedPaths, setArchiveUntrackedPaths] = useState<string[] | null>(null); |
| 133 | // Whether the confirmation includes an active-stream interruption warning. |
| 134 | const [archiveConfirmIsStreaming, setArchiveConfirmIsStreaming] = useState(false); |
| 135 | const [isArchiving, setIsArchiving] = useState(false); |
| 136 | const archiveError = usePopoverError(); |
| 137 | const forkError = usePopoverError(); |
nothing calls this directly
no test coverage detected