|
1 | 1 | import React, { useCallback, useEffect, useRef, useState } from "react"; |
2 | 2 | import { Bell, BellOff, Ellipsis, Info, Menu, Pencil } from "lucide-react"; |
3 | 3 | import { CUSTOM_EVENTS, createCustomEvent } from "@/common/constants/events"; |
| 4 | +import { EXPERIMENT_IDS } from "@/common/constants/experiments"; |
4 | 5 | import { cn } from "@/common/lib/utils"; |
5 | 6 | import { getErrorMessage } from "@/common/utils/errors"; |
6 | 7 | import { isWorkspacePinnable, isWorkspacePinned } from "@/common/utils/pin"; |
@@ -36,6 +37,7 @@ import { DEFAULT_RUNTIME_CONFIG } from "@/common/constants/workspace"; |
36 | 37 | import { useTutorial } from "@/browser/contexts/TutorialContext"; |
37 | 38 |
|
38 | 39 | import type { TerminalSessionCreateOptions } from "@/browser/utils/terminal"; |
| 40 | +import { useExperimentValue } from "@/browser/hooks/useExperiments"; |
39 | 41 | import { useOpenTerminal } from "@/browser/hooks/useOpenTerminal"; |
40 | 42 | import { useOpenInEditor } from "@/browser/hooks/useOpenInEditor"; |
41 | 43 | import { usePersistedState } from "@/browser/hooks/usePersistedState"; |
@@ -65,6 +67,8 @@ import { |
65 | 67 | WORKSPACE_MENU_BAR_LEFT_SIDEBAR_COLLAPSED_PADDING_PX, |
66 | 68 | } from "@/constants/layout"; |
67 | 69 | import { TimelineDialog } from "@/browser/features/RightSidebar/Timeline/TimelineDialog"; |
| 70 | +import { ArtifactsDialog } from "@/browser/features/RightSidebar/ArtifactsTab/ArtifactsDialog"; |
| 71 | +import { isRightSidebarResponsivelyHidden } from "@/browser/features/RightSidebar/rightSidebarVisibility"; |
68 | 72 | import type { AgentSkillDescriptor, AgentSkillIssue } from "@/common/types/agentSkill"; |
69 | 73 |
|
70 | 74 | interface WorkspaceMenuBarProps { |
@@ -105,6 +109,7 @@ export const WorkspaceMenuBar: React.FC<WorkspaceMenuBarProps> = ({ |
105 | 109 | useWorkspaceActions(); |
106 | 110 | const isArchiving = archivingWorkspaceIds.has(workspaceId); |
107 | 111 | const { workspaceMetadata } = useWorkspaceContext(); |
| 112 | + const artifactsExperimentEnabled = useExperimentValue(EXPERIMENT_IDS.ARTIFACTS); |
108 | 113 | const openTerminalPopout = useOpenTerminal(); |
109 | 114 | const openInEditor = useOpenInEditor(); |
110 | 115 | const runtimeStatus = useRuntimeStatus(workspaceId); |
@@ -152,6 +157,13 @@ export const WorkspaceMenuBar: React.FC<WorkspaceMenuBarProps> = ({ |
152 | 157 | setTimelineDialogWorkspaceId(null); |
153 | 158 | } |
154 | 159 | const timelineDialogOpen = timelineDialogWorkspaceId === workspaceId; |
| 160 | + // Same per-workspace keying as the timeline dialog: the Artifacts tab also lives in the |
| 161 | + // right sidebar, so small viewports need this dialog to reach it. |
| 162 | + const [artifactsDialogWorkspaceId, setArtifactsDialogWorkspaceId] = useState<string | null>(null); |
| 163 | + if (artifactsDialogWorkspaceId !== null && artifactsDialogWorkspaceId !== workspaceId) { |
| 164 | + setArtifactsDialogWorkspaceId(null); |
| 165 | + } |
| 166 | + const artifactsDialogOpen = artifactsDialogWorkspaceId === workspaceId; |
155 | 167 | const [availableSkills, setAvailableSkills] = useState<AgentSkillDescriptor[]>([]); |
156 | 168 | const [invalidSkills, setInvalidSkills] = useState<AgentSkillIssue[]>([]); |
157 | 169 | const isSkillsMountedRef = useRef(true); |
@@ -219,12 +231,7 @@ export const WorkspaceMenuBar: React.FC<WorkspaceMenuBarProps> = ({ |
219 | 231 | ?.closest("[data-workspace-shell]") |
220 | 232 | ?.querySelector(".mobile-hide-right-sidebar"); |
221 | 233 | if (sidebar instanceof HTMLElement) { |
222 | | - // Immersive review hides the sidebar on any viewport (marked aria-hidden); |
223 | | - // only a responsive hide should surface the dialog entry points. |
224 | | - if (sidebar.getAttribute("aria-hidden") === "true") { |
225 | | - return false; |
226 | | - } |
227 | | - return window.getComputedStyle(sidebar).display === "none"; |
| 234 | + return isRightSidebarResponsivelyHidden(sidebar); |
228 | 235 | } |
229 | 236 | return window.matchMedia(`(max-width: ${NARROW_VIEWPORT_MAX_WIDTH_PX}px)`).matches; |
230 | 237 | }, []); |
@@ -271,6 +278,27 @@ export const WorkspaceMenuBar: React.FC<WorkspaceMenuBarProps> = ({ |
271 | 278 | return () => window.removeEventListener("keydown", handler); |
272 | 279 | }, [isTimelineSidebarHidden, workspaceId]); |
273 | 280 |
|
| 281 | + // Ctrl+Shift+K normally opens the Artifacts tab (RightSidebar owns that handler); while the |
| 282 | + // sidebar is hidden it opens the dialog instead, the tab's only entry point there. |
| 283 | + useEffect(() => { |
| 284 | + if (!artifactsExperimentEnabled) { |
| 285 | + return; |
| 286 | + } |
| 287 | + const handler = (e: KeyboardEvent) => { |
| 288 | + if ( |
| 289 | + !matchesKeybind(e, KEYBINDS.OPEN_ARTIFACTS_TAB) || |
| 290 | + isDialogOpen() || |
| 291 | + !isTimelineSidebarHidden() |
| 292 | + ) { |
| 293 | + return; |
| 294 | + } |
| 295 | + e.preventDefault(); |
| 296 | + setArtifactsDialogWorkspaceId(workspaceId); |
| 297 | + }; |
| 298 | + window.addEventListener("keydown", handler); |
| 299 | + return () => window.removeEventListener("keydown", handler); |
| 300 | + }, [artifactsExperimentEnabled, isTimelineSidebarHidden, workspaceId]); |
| 301 | + |
274 | 302 | const isDevcontainerWorkspace = isDevcontainerRuntime(runtimeConfig); |
275 | 303 | const isRuntimeRunning = isDevcontainerWorkspace && runtimeStatus === "running"; |
276 | 304 | // Mirrors TaskService.isLocalUnrelatedMessagingEndpoint: unrelated delivery requires local or |
@@ -826,6 +854,11 @@ export const WorkspaceMenuBar: React.FC<WorkspaceMenuBarProps> = ({ |
826 | 854 | onOpenTimeline={ |
827 | 855 | timelineSidebarHidden ? () => setTimelineDialogWorkspaceId(workspaceId) : null |
828 | 856 | } |
| 857 | + onOpenArtifacts={ |
| 858 | + artifactsExperimentEnabled && timelineSidebarHidden |
| 859 | + ? () => setArtifactsDialogWorkspaceId(workspaceId) |
| 860 | + : null |
| 861 | + } |
829 | 862 | onStopRuntime={isRuntimeRunning ? () => void handleStopRuntime() : null} |
830 | 863 | // Scratch chats have no repo: review events are ignored by |
831 | 864 | // RightSidebar and fork is unsupported on the backend, so hide |
@@ -903,6 +936,11 @@ export const WorkspaceMenuBar: React.FC<WorkspaceMenuBarProps> = ({ |
903 | 936 | open={timelineDialogOpen} |
904 | 937 | onOpenChange={(open) => setTimelineDialogWorkspaceId(open ? workspaceId : null)} |
905 | 938 | /> |
| 939 | + <ArtifactsDialog |
| 940 | + workspaceId={workspaceId} |
| 941 | + open={artifactsDialogOpen} |
| 942 | + onOpenChange={(open) => setArtifactsDialogWorkspaceId(open ? workspaceId : null)} |
| 943 | + /> |
906 | 944 | <DebugLlmRequestModal |
907 | 945 | workspaceId={workspaceId} |
908 | 946 | open={debugLlmRequestOpen} |
|
0 commit comments