diff --git a/apps/web/src/components/AppShell.tsx b/apps/web/src/components/AppShell.tsx index 8292831..28721bb 100644 --- a/apps/web/src/components/AppShell.tsx +++ b/apps/web/src/components/AppShell.tsx @@ -31,6 +31,7 @@ export function AppShell() { const railCollapsed = useWorkspace((state) => state.railCollapsed); const railWidth = useWorkspace((state) => state.railWidth); const railOverlayOpen = useWorkspace((state) => state.railOverlayOpen); + const focusMode = useWorkspace((state) => state.focusMode); const sessionListView = useWorkspace((state) => state.sessionListView); const projectExpandedById = useWorkspace((state) => state.projectExpandedById); const dismissedEmptyProjectIds = useWorkspace((state) => state.dismissedEmptyProjectIds); @@ -105,7 +106,12 @@ export function AppShell() { // Dialog-based surfaces (palette, sheets) close themselves; the // docked pane is plain layout, so Escape peels it here. const state = workspaceStore.getState(); - if (state.paletteOpen || state.railOverlayOpen) return; + if (state.paletteOpen || (state.railOverlayOpen && !state.focusMode)) return; + if (state.focusMode) { + state.setFocusMode(false); + event.preventDefault(); + return; + } const activeId = state.activeSessionId; if (activeId !== null && selectSessionView(state, activeId).paneOpen) { state.setPaneOpen(activeId, false); @@ -123,8 +129,26 @@ export function AppShell() { if (action.kind === "palette") { state.setPaletteOpen(!state.paletteOpen); } else if (action.kind === "toggle-rail") { - if (railOverlaid) state.setRailOverlayOpen(!state.railOverlayOpen); + if (state.focusMode) { + state.setFocusMode(false); + state.setRailCollapsed(false); + } else if (railOverlaid) state.setRailOverlayOpen(!state.railOverlayOpen); else state.setRailCollapsed(!state.railCollapsed); + } else if (action.kind === "toggle-terminal") { + const activeId = state.activeSessionId; + const activeSession = + activeId === null ? undefined : getShellData().sessions.find((session) => session.id === activeId); + if (activeId !== null && activeSession?.archivedAt === undefined) { + const view = selectSessionView(state, activeId); + if (state.focusMode) { + state.setFocusMode(false); + state.setTerminalDrawerOpen(activeId, true); + } else { + state.setTerminalDrawerOpen(activeId, !view.terminalDrawerOpen); + } + } + } else if (action.kind === "toggle-focus") { + state.setFocusMode(!state.focusMode); } else if (action.kind === "settings") { void navigate({ to: "/settings" }); } else { @@ -153,24 +177,29 @@ export function AppShell() { const railToggle = resolveRailTogglePresentation({ overlaid: railOverlaid, overlayOpen: railOverlayOpen, - collapsed: railCollapsed, + collapsed: railCollapsed || focusMode, }); return (
workspaceStore.getState().setFocusMode(false)} onToggleRail={() => { const state = workspaceStore.getState(); - if (railOverlaid) state.setRailOverlayOpen(!state.railOverlayOpen); + if (state.focusMode) { + state.setFocusMode(false); + state.setRailCollapsed(false); + } else if (railOverlaid) state.setRailOverlayOpen(!state.railOverlayOpen); else state.setRailCollapsed(!state.railCollapsed); }} railToggle={railToggle} />
- {!railOverlaid && ( + {!railOverlaid && !focusMode && ( <>
{railCollapsed ? ( @@ -219,7 +248,7 @@ export function AppShell() { {railOverlaid && ( workspaceStore.getState().setRailOverlayOpen(open)} - open={railOverlayOpen} + open={railOverlayOpen && !focusMode} >
- + Working folders and sessions void; } +const GROUP_LABEL = { + recent: "Recent work", + workspace: "Workspace", + navigate: "Navigate", + app: "App", +} as const; + +const DEFAULT_RECENT_LIMIT = 5; + function buildItems( groups: readonly ProjectGroup[], navigate: (sessionId: string) => void, @@ -32,6 +44,7 @@ function buildItems( for (const row of group.sessions) { items.push({ id: `session:${row.session.id}`, + group: "recent", label: row.session.title, hint: `${group.project.name} · ${row.session.model}`, status: @@ -43,27 +56,82 @@ function buildItems( } } const state = workspaceStore.getState(); + const activeSessionId = state.activeSessionId; + const activeSessionVisible = + activeSessionId !== null && + groups.some((group) => group.sessions.some((row) => row.session.id === activeSessionId)); + if (activeSessionId !== null && activeSessionVisible) { + const view = selectSessionView(state, activeSessionId); + for (const meta of PANE_FAMILY_META) { + const active = !state.focusMode && view.paneOpen && view.paneFamily === meta.id; + const Icon = meta.icon; + const label = meta.id === "terminals" ? "Agent terminals" : meta.label; + items.push({ + id: `action:pane:${meta.id}`, + group: "workspace", + label: active ? `Close ${label}` : `Open ${label}`, + hint: "Workspace · Right", + status: