From 1fb448d827845f9e63763708c865df6d2eb6d80c Mon Sep 17 00:00:00 2001 From: Serhii Dziupin Date: Mon, 13 Jul 2026 09:02:14 +0300 Subject: [PATCH] feat(chat): desktop prompt navigator rail (#2054) * feat(chat): add desktop prompt navigator rail Add a ChatGPT-style right-center prompt marker rail for web/desktop chat with hover/keyboard preview panel, load-more for partial history (panel only), Chat setting, and mod+alt+p shortcut. Disabled in VS Code across rail, shortcut, settings, help, and search surfaces. Co-authored-by: Serhii Dziupin * fix(ui): read promptNavigatorEnabled from getState in shortcut handler Match the file convention used by other shortcut handlers so the toggle does not rely on a hook-level selector closure. Co-authored-by: Serhii Dziupin * fix(ui): drop use-no-memo and default prompt navigator off Remove the project-unprecedented React Compiler opt-out, and ship the prompt navigator as opt-in to match other recent chat UI toggles. Co-authored-by: Serhii Dziupin --------- Co-authored-by: Cursor Agent Co-authored-by: Serhii Dziupin --- .../ui/src/components/chat/ChatContainer.tsx | 83 +++- .../ui/src/components/chat/TimelineDialog.tsx | 15 +- .../chat/components/PromptNavigatorRail.tsx | 389 ++++++++++++++++++ .../chat/lib/messagePreview.test.ts | 21 + .../src/components/chat/lib/messagePreview.ts | 14 + .../openchamber/KeyboardShortcutsSettings.tsx | 9 +- .../sections/openchamber/OpenChamberPage.tsx | 32 +- .../openchamber/OpenChamberVisualSettings.tsx | 39 +- packages/ui/src/components/ui/HelpDialog.tsx | 12 +- packages/ui/src/hooks/useKeyboardShortcuts.ts | 48 +++ packages/ui/src/lib/desktop.ts | 1 + .../ui/src/lib/i18n/messages/en.settings.ts | 3 + packages/ui/src/lib/i18n/messages/en.ts | 4 + .../ui/src/lib/i18n/messages/es.settings.ts | 3 + packages/ui/src/lib/i18n/messages/es.ts | 4 + .../ui/src/lib/i18n/messages/fr.settings.ts | 3 + packages/ui/src/lib/i18n/messages/fr.ts | 4 + .../ui/src/lib/i18n/messages/ja.settings.ts | 3 + packages/ui/src/lib/i18n/messages/ja.ts | 4 + .../ui/src/lib/i18n/messages/ko.settings.ts | 3 + packages/ui/src/lib/i18n/messages/ko.ts | 4 + .../ui/src/lib/i18n/messages/pl.settings.ts | 3 + packages/ui/src/lib/i18n/messages/pl.ts | 4 + .../src/lib/i18n/messages/pt-BR.settings.ts | 3 + packages/ui/src/lib/i18n/messages/pt-BR.ts | 4 + .../ui/src/lib/i18n/messages/uk.settings.ts | 3 + packages/ui/src/lib/i18n/messages/uk.ts | 4 + .../src/lib/i18n/messages/zh-CN.settings.ts | 3 + packages/ui/src/lib/i18n/messages/zh-CN.ts | 4 + .../src/lib/i18n/messages/zh-TW.settings.ts | 3 + packages/ui/src/lib/i18n/messages/zh-TW.ts | 4 + packages/ui/src/lib/persistence.ts | 6 + packages/ui/src/lib/settings/search.ts | 7 + packages/ui/src/lib/shortcuts.ts | 7 + packages/ui/src/stores/useUIStore.ts | 19 + .../server/lib/opencode/settings-helpers.js | 3 + 36 files changed, 754 insertions(+), 21 deletions(-) create mode 100644 packages/ui/src/components/chat/components/PromptNavigatorRail.tsx create mode 100644 packages/ui/src/components/chat/lib/messagePreview.test.ts create mode 100644 packages/ui/src/components/chat/lib/messagePreview.ts diff --git a/packages/ui/src/components/chat/ChatContainer.tsx b/packages/ui/src/components/chat/ChatContainer.tsx index b8babe28..0d7153a8 100644 --- a/packages/ui/src/components/chat/ChatContainer.tsx +++ b/packages/ui/src/components/chat/ChatContainer.tsx @@ -16,6 +16,7 @@ import { QuestionCard } from './QuestionCard'; import { StatusRowContainer } from './StatusRowContainer'; import { SessionRecapNote } from '@/components/chat/SessionRecapSpacer'; import ScrollToBottomButton from './components/ScrollToBottomButton'; +import { PromptNavigatorRail } from './components/PromptNavigatorRail'; import { ScrollShadow } from '@/components/ui/ScrollShadow'; import { useChatAutoFollow, type AnimationHandlers, type ContentChangeReason } from '@/hooks/useChatAutoFollow'; import { useChatTimelineController } from './hooks/useChatTimelineController'; @@ -162,6 +163,13 @@ type ChatViewportProps = { isProgrammaticFollowActive: boolean; showLoadOlderButton: boolean; onLoadOlder: () => void; + turnIds: string[]; + activeTurnId: string | null; + onSelectTurn: (turnId: string) => void; + showPromptNavigator: boolean; + canLoadEarlierPrompts: boolean; + isLoadingOlderPrompts: boolean; + onLoadEarlierPrompts: () => void; }; const ChatViewport = React.memo(({ @@ -188,8 +196,40 @@ const ChatViewport = React.memo(({ isProgrammaticFollowActive, showLoadOlderButton, onLoadOlder, + turnIds, + activeTurnId, + onSelectTurn, + showPromptNavigator, + canLoadEarlierPrompts, + isLoadingOlderPrompts, + onLoadEarlierPrompts, }: ChatViewportProps) => { const { t } = useI18n(); + const promptPreviewsByTurnIdRef = React.useRef>(new Map()); + const promptPreviewsByTurnId = React.useMemo(() => { + const next = new Map(); + for (const message of renderedMessages) { + if (message.info.role !== 'user') { + continue; + } + next.set(message.info.id, message.parts); + } + const prev = promptPreviewsByTurnIdRef.current; + if (prev.size === next.size) { + let unchanged = true; + for (const [id, parts] of next) { + if (prev.get(id) !== parts) { + unchanged = false; + break; + } + } + if (unchanged) { + return prev; + } + } + promptPreviewsByTurnIdRef.current = next; + return next; + }, [renderedMessages]); const focusScrollContainer = React.useCallback((event: React.MouseEvent) => { if (event.defaultPrevented || shouldIgnoreChatNavigationTarget(event.target)) { return; @@ -278,6 +318,17 @@ const ChatViewport = React.memo(({ + {showPromptNavigator ? ( + + ) : null} ); @@ -304,7 +355,14 @@ const ChatViewport = React.memo(({ && prev.sessionPermissions === next.sessionPermissions && prev.isProgrammaticFollowActive === next.isProgrammaticFollowActive && prev.showLoadOlderButton === next.showLoadOlderButton - && prev.onLoadOlder === next.onLoadOlder; + && prev.onLoadOlder === next.onLoadOlder + && prev.turnIds === next.turnIds + && prev.activeTurnId === next.activeTurnId + && prev.onSelectTurn === next.onSelectTurn + && prev.showPromptNavigator === next.showPromptNavigator + && prev.canLoadEarlierPrompts === next.canLoadEarlierPrompts + && prev.isLoadingOlderPrompts === next.isLoadingOlderPrompts + && prev.onLoadEarlierPrompts === next.onLoadEarlierPrompts; }); ChatViewport.displayName = 'ChatViewport'; @@ -405,6 +463,7 @@ export const ChatContainer: React.FC = ({ autoOpenDraft = tr // UI store const isExpandedInput = useUIStore((state) => state.isExpandedInput); const stickyUserHeader = useUIStore((state) => state.stickyUserHeader); + const promptNavigatorEnabled = useUIStore((state) => state.promptNavigatorEnabled); const allowPromptingSubagentSessions = useUIStore((state) => state.allowPromptingSubagentSessions); const isTimelineDialogOpen = useUIStore((s) => s.isTimelineDialogOpen); const setTimelineDialogOpen = useUIStore((s) => s.setTimelineDialogOpen); @@ -706,6 +765,21 @@ export const ChatContainer: React.FC = ({ autoOpenDraft = tr scrollToMessage: timelineController.scrollToMessage, resumeToBottom: timelineController.resumeToBottomInstant, }); + const handlePromptNavigatorSelect = React.useCallback((turnId: string) => { + void navigation.scrollToTurnId(turnId, { behavior: 'smooth' }); + }, [navigation]); + const canLoadEarlierPrompts = timelineController.historySignals.canLoadEarlier; + const showPromptNavigator = !isMobile + && !isVSCode + && !isDesktopExpandedInput + && promptNavigatorEnabled + && timelineController.turnIds.length >= 2; + + React.useEffect(() => { + if (!showPromptNavigator) { + useUIStore.getState().setPromptNavigatorPanelOpen(false); + } + }, [showPromptNavigator]); React.useEffect(() => { if (typeof window === 'undefined' || !currentSessionId) return; @@ -1012,6 +1086,13 @@ export const ChatContainer: React.FC = ({ autoOpenDraft = tr isProgrammaticFollowActive={isFollowingProgrammatically} showLoadOlderButton={showLoadOlderButton} onLoadOlder={handleLoadOlderClick} + turnIds={timelineController.turnIds} + activeTurnId={timelineController.activeTurnId} + onSelectTurn={handlePromptNavigatorSelect} + showPromptNavigator={showPromptNavigator} + canLoadEarlierPrompts={canLoadEarlierPrompts} + isLoadingOlderPrompts={timelineController.isLoadingOlder} + onLoadEarlierPrompts={handleLoadOlderClick} />
= ({ ); }; -function getFullText(parts: Part[]): string { - return parts - .filter((p): p is Part & { type: 'text'; text: string } => p.type === 'text' && typeof p.text === 'string') - .map((p) => p.text) - .join('\n'); -} - -function getMessagePreview(parts: Part[]): string { - const full = getFullText(parts); - const singleLine = full.replace(/\n/g, ' '); - return singleLine.length > 80 ? singleLine.slice(0, 80) : singleLine; -} - function getSearchSnippet(text: string, query: string, contextChars: number = 30): string | null { const lowerText = text.toLowerCase(); const lowerQuery = query.toLowerCase(); diff --git a/packages/ui/src/components/chat/components/PromptNavigatorRail.tsx b/packages/ui/src/components/chat/components/PromptNavigatorRail.tsx new file mode 100644 index 00000000..179ee522 --- /dev/null +++ b/packages/ui/src/components/chat/components/PromptNavigatorRail.tsx @@ -0,0 +1,389 @@ +import React from 'react'; +import type { Part } from '@opencode-ai/sdk/v2'; + +import { Icon } from '@/components/icon/Icon'; +import { useDeviceInfo } from '@/lib/device'; +import { useI18n } from '@/lib/i18n'; +import { useUIStore } from '@/stores/useUIStore'; +import { cn } from '@/lib/utils'; +import { getMessagePreview } from '../lib/messagePreview'; + +type PromptEntry = { + turnId: string; + preview: string; +}; + +type PromptNavigatorRailProps = { + turnIds: string[]; + previewsByTurnId: Map; + activeTurnId: string | null; + onSelectTurn: (turnId: string) => void; + canLoadEarlier: boolean; + isLoadingOlder: boolean; + onLoadEarlier: () => void; +}; + +const LINE_HIT_HEIGHT_PX = 8; +const HOVER_CLOSE_DELAY_MS = 120; +const COMPACT_BACKDROP_MAX_WIDTH_PX = 1280; + +const buildPromptEntries = ( + turnIds: string[], + previewsByTurnId: Map, +): PromptEntry[] => { + return turnIds.map((turnId) => { + const parts = previewsByTurnId.get(turnId) ?? []; + return { + turnId, + preview: getMessagePreview(parts, 120), + }; + }); +}; + +const resolveLineGapClass = (count: number): string => { + if (count > 24) { + return 'gap-px'; + } + if (count > 12) { + return 'gap-0.5'; + } + return 'gap-1'; +}; + +type LineRailProps = { + prompts: PromptEntry[]; + activeTurnId: string | null; + lineGapClass: string; + needsBackdrop: boolean; + emptyPreviewLabel: string; + onSelectTurn: (turnId: string) => void; +}; + +/** Compact marker stack only — never renders load-more. Markers stay out of tab order. */ +function LineRail({ + prompts, + activeTurnId, + lineGapClass, + needsBackdrop, + emptyPreviewLabel, + onSelectTurn, +}: LineRailProps) { + const activeButtonRef = React.useRef(null); + + React.useLayoutEffect(() => { + activeButtonRef.current?.scrollIntoView({ block: 'nearest' }); + }, [activeTurnId, prompts.length]); + + return ( +
+
+ {prompts.map((prompt) => { + const isActive = prompt.turnId === activeTurnId; + const preview = prompt.preview.trim() || emptyPreviewLabel; + + return ( + + ); + })} +
+
+ ); +} + +type PromptMenuPanelProps = { + prompts: PromptEntry[]; + activeTurnId: string | null; + canLoadEarlier: boolean; + isLoadingOlder: boolean; + emptyPreviewLabel: string; + currentPromptLabel: string; + loadMoreLabel: string; + onSelectTurn: (turnId: string) => void; + onLoadEarlier: (event: React.MouseEvent) => void; + onMouseEnter: () => void; + onMouseLeave: () => void; + focusOnMount: boolean; +}; + +/** Hover/keyboard menu — the only place load-more is allowed. */ +function PromptMenuPanel({ + prompts, + activeTurnId, + canLoadEarlier, + isLoadingOlder, + emptyPreviewLabel, + currentPromptLabel, + loadMoreLabel, + onSelectTurn, + onLoadEarlier, + onMouseEnter, + onMouseLeave, + focusOnMount, +}: PromptMenuPanelProps) { + const activeItemRef = React.useRef(null); + + React.useEffect(() => { + if (!focusOnMount) { + return; + } + activeItemRef.current?.focus(); + }, [focusOnMount]); + + return ( +
+
    + {canLoadEarlier ? ( +
  • + +
  • + ) : null} + {prompts.map((prompt) => { + const isActive = prompt.turnId === activeTurnId; + const preview = prompt.preview.trim() || emptyPreviewLabel; + + return ( +
  • + +
  • + ); + })} +
+
+ ); +} + +export function PromptNavigatorRail({ + turnIds, + previewsByTurnId, + activeTurnId, + onSelectTurn, + canLoadEarlier, + isLoadingOlder, + onLoadEarlier, +}: PromptNavigatorRailProps) { + const { t } = useI18n(); + const { screenWidth } = useDeviceInfo(); + const isPanelOpen = useUIStore((state) => state.isPromptNavigatorPanelOpen); + const setPromptNavigatorPanelOpen = useUIStore((state) => state.setPromptNavigatorPanelOpen); + const closeTimeoutRef = React.useRef(null); + const rootRef = React.useRef(null); + const openedByPointerRef = React.useRef(false); + const [focusActiveOnOpen, setFocusActiveOnOpen] = React.useState(false); + + const prompts = React.useMemo( + () => buildPromptEntries(turnIds, previewsByTurnId), + [previewsByTurnId, turnIds], + ); + + const needsBackdrop = screenWidth < COMPACT_BACKDROP_MAX_WIDTH_PX; + const lineGapClass = resolveLineGapClass(prompts.length); + const emptyPreviewLabel = t('chat.timeline.noTextContent'); + const currentPromptLabel = t('chat.promptNavigator.currentPrompt'); + const loadMoreLabel = t('chat.promptNavigator.loadMore'); + + const clearCloseTimeout = React.useCallback(() => { + if (closeTimeoutRef.current !== null) { + window.clearTimeout(closeTimeoutRef.current); + closeTimeoutRef.current = null; + } + }, []); + + const openPanel = React.useCallback(() => { + clearCloseTimeout(); + openedByPointerRef.current = true; + setFocusActiveOnOpen(false); + setPromptNavigatorPanelOpen(true); + }, [clearCloseTimeout, setPromptNavigatorPanelOpen]); + + const scheduleClosePanel = React.useCallback(() => { + clearCloseTimeout(); + closeTimeoutRef.current = window.setTimeout(() => { + // Keep keyboard-opened panel alive while focus is still inside the rail. + if (rootRef.current?.contains(document.activeElement)) { + return; + } + openedByPointerRef.current = false; + setFocusActiveOnOpen(false); + setPromptNavigatorPanelOpen(false); + }, HOVER_CLOSE_DELAY_MS); + }, [clearCloseTimeout, setPromptNavigatorPanelOpen]); + + React.useEffect(() => () => { + clearCloseTimeout(); + setPromptNavigatorPanelOpen(false); + }, [clearCloseTimeout, setPromptNavigatorPanelOpen]); + + // Keyboard shortcut flips the store open with focus outside the rail. + // Pointer open sets openedByPointerRef so we don't steal focus on hover. + React.useEffect(() => { + if (!isPanelOpen) { + setFocusActiveOnOpen(false); + return; + } + if (openedByPointerRef.current) { + openedByPointerRef.current = false; + setFocusActiveOnOpen(false); + return; + } + setFocusActiveOnOpen(true); + }, [isPanelOpen]); + + const handleSelectPrompt = React.useCallback((turnId: string) => { + onSelectTurn(turnId); + openedByPointerRef.current = false; + setFocusActiveOnOpen(false); + setPromptNavigatorPanelOpen(false); + }, [onSelectTurn, setPromptNavigatorPanelOpen]); + + const handleLoadEarlier = React.useCallback((event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + if (isLoadingOlder) { + return; + } + onLoadEarlier(); + }, [isLoadingOlder, onLoadEarlier]); + + const handleWrapperBlur = React.useCallback((event: React.FocusEvent) => { + const next = event.relatedTarget; + if (next instanceof Node && event.currentTarget.contains(next)) { + return; + } + scheduleClosePanel(); + }, [scheduleClosePanel]); + + if (prompts.length === 0) { + return null; + } + + return ( + + ); +} diff --git a/packages/ui/src/components/chat/lib/messagePreview.test.ts b/packages/ui/src/components/chat/lib/messagePreview.test.ts new file mode 100644 index 00000000..e53fd42b --- /dev/null +++ b/packages/ui/src/components/chat/lib/messagePreview.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, test } from 'bun:test' +import type { Part } from '@opencode-ai/sdk/v2' +import { getFullText, getMessagePreview } from './messagePreview' + +const textPart = (text: string): Part => ({ type: 'text', text } as Part) + +describe('messagePreview', () => { + test('joins text parts for full text', () => { + expect(getFullText([textPart('hello'), textPart('world')])).toBe('hello\nworld') + }) + + test('collapses newlines and truncates previews', () => { + expect(getMessagePreview([textPart('line one\nline two')], 80)).toBe('line one line two') + expect(getMessagePreview([textPart('abcdefghijklmnopqrstuvwxyz')], 10)).toBe('abcdefghij…') + }) + + test('returns empty string when there is no text', () => { + expect(getMessagePreview([])).toBe('') + expect(getFullText([{ type: 'file' } as Part])).toBe('') + }) +}) diff --git a/packages/ui/src/components/chat/lib/messagePreview.ts b/packages/ui/src/components/chat/lib/messagePreview.ts new file mode 100644 index 00000000..811275ec --- /dev/null +++ b/packages/ui/src/components/chat/lib/messagePreview.ts @@ -0,0 +1,14 @@ +import type { Part } from '@opencode-ai/sdk/v2'; + +export function getFullText(parts: Part[]): string { + return parts + .filter((p): p is Part & { type: 'text'; text: string } => p.type === 'text' && typeof p.text === 'string') + .map((p) => p.text) + .join('\n'); +} + +export function getMessagePreview(parts: Part[], maxLength = 80): string { + const full = getFullText(parts); + const singleLine = full.replace(/\n/g, ' '); + return singleLine.length > maxLength ? `${singleLine.slice(0, maxLength)}…` : singleLine; +} diff --git a/packages/ui/src/components/sections/openchamber/KeyboardShortcutsSettings.tsx b/packages/ui/src/components/sections/openchamber/KeyboardShortcutsSettings.tsx index 6055ddbe..aa62640a 100644 --- a/packages/ui/src/components/sections/openchamber/KeyboardShortcutsSettings.tsx +++ b/packages/ui/src/components/sections/openchamber/KeyboardShortcutsSettings.tsx @@ -6,6 +6,7 @@ import { Icon } from "@/components/icon/Icon"; import { useUIStore } from '@/stores/useUIStore'; import { cn } from '@/lib/utils'; import { updateDesktopSettings } from '@/lib/persistence'; +import { isVSCodeRuntime } from '@/lib/desktop'; import { formatShortcutForDisplay, getCustomizableShortcutActions, @@ -54,7 +55,13 @@ export const KeyboardShortcutsSettings: React.FC = () => { const clearShortcutOverride = useUIStore((state) => state.clearShortcutOverride); const resetAllShortcutOverrides = useUIStore((state) => state.resetAllShortcutOverrides); - const actions = React.useMemo(() => getCustomizableShortcutActions(), []); + const actions = React.useMemo(() => { + const all = getCustomizableShortcutActions(); + if (!isVSCodeRuntime()) { + return all; + } + return all.filter((action) => action.id !== 'toggle_prompt_navigator'); + }, []); const actionLabel = React.useCallback((id: string, fallbackLabel: string): string => { const key = `settings.openchamber.keyboardShortcuts.action.${id}.label`; const translated = tUnsafe(key); diff --git a/packages/ui/src/components/sections/openchamber/OpenChamberPage.tsx b/packages/ui/src/components/sections/openchamber/OpenChamberPage.tsx index f0bfcf28..0027e549 100644 --- a/packages/ui/src/components/sections/openchamber/OpenChamberPage.tsx +++ b/packages/ui/src/components/sections/openchamber/OpenChamberPage.tsx @@ -145,7 +145,37 @@ const VisualSectionContent: React.FC = () => { // Chat section: User message rendering, Diff layout, Mobile status bar, Show reasoning traces, Follow-up behavior, Persist draft const ChatSectionContent: React.FC = () => { - return ; + const isVSCode = isVSCodeRuntime(); + return ( + + ); }; // Sessions section: Default model & agent, Session retention diff --git a/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx b/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx index 0fd8b822..61390212 100644 --- a/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx +++ b/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx @@ -245,7 +245,7 @@ const normalizeUserMessageRenderingMode = (mode: unknown): 'markdown' | 'plain' return mode === 'markdown' ? 'markdown' : 'plain'; }; -type VisibleSetting = 'sessionAssist' | 'sessionGoal' | 'theme' | 'pwaInstallName' | 'pwaOrientation' | 'mobileKeyboardMode' | 'timeFormat' | 'weekStart' | 'fontSize' | 'terminalFontSize' | 'editorFontSize' | 'spacing' | 'inputBarOffset' | 'mermaidRendering' | 'userMessageRendering' | 'chatRenderMode' | 'messageTransport' | 'activityRenderMode' | 'collapsibleUserMessages' | 'stickyUserHeader' | 'wideChatLayout' | 'codeBlockLineWrap' | 'splitAssistantMessageActions' | 'subagentReadOnlyBanner' | 'diffLayout' | 'mobileStatusBar' | 'dotfiles' | 'fileViewerPreview' | 'reasoning' | 'showToolFileIcons' | 'showTurnChangedFiles' | 'expandedTools' | 'followUpBehavior' | 'terminalQuickKeys' | 'fileEditorKeymap' | 'persistDraft' | 'inputSpellcheck' | 'reportUsage' | 'expandedEditorToolbar'; +type VisibleSetting = 'sessionAssist' | 'sessionGoal' | 'theme' | 'pwaInstallName' | 'pwaOrientation' | 'mobileKeyboardMode' | 'timeFormat' | 'weekStart' | 'fontSize' | 'terminalFontSize' | 'editorFontSize' | 'spacing' | 'inputBarOffset' | 'mermaidRendering' | 'userMessageRendering' | 'chatRenderMode' | 'messageTransport' | 'activityRenderMode' | 'collapsibleUserMessages' | 'stickyUserHeader' | 'promptNavigatorEnabled' | 'wideChatLayout' | 'codeBlockLineWrap' | 'splitAssistantMessageActions' | 'subagentReadOnlyBanner' | 'diffLayout' | 'mobileStatusBar' | 'dotfiles' | 'fileViewerPreview' | 'reasoning' | 'showToolFileIcons' | 'showTurnChangedFiles' | 'expandedTools' | 'followUpBehavior' | 'terminalQuickKeys' | 'fileEditorKeymap' | 'persistDraft' | 'inputSpellcheck' | 'reportUsage' | 'expandedEditorToolbar'; interface OpenChamberVisualSettingsProps { /** Which settings to show. If undefined, shows all. */ @@ -280,7 +280,9 @@ export const OpenChamberVisualSettings: React.FC const collapsibleUserMessages = useUIStore(state => state.collapsibleUserMessages); const setCollapsibleUserMessages = useUIStore(state => state.setCollapsibleUserMessages); const stickyUserHeader = useUIStore(state => state.stickyUserHeader); + const promptNavigatorEnabled = useUIStore(state => state.promptNavigatorEnabled); const setStickyUserHeader = useUIStore(state => state.setStickyUserHeader); + const setPromptNavigatorEnabled = useUIStore(state => state.setPromptNavigatorEnabled); const expandedEditorToolbar = useUIStore(state => state.expandedEditorToolbar); const setExpandedEditorToolbar = useUIStore(state => state.setExpandedEditorToolbar); const wideChatLayoutEnabled = useUIStore(state => state.wideChatLayoutEnabled); @@ -441,6 +443,11 @@ export const OpenChamberVisualSettings: React.FC void updateDesktopSettings({ stickyUserHeader: enabled }); }, [setStickyUserHeader]); + const handlePromptNavigatorEnabledChange = React.useCallback((enabled: boolean) => { + setPromptNavigatorEnabled(enabled); + void updateDesktopSettings({ promptNavigatorEnabled: enabled }); + }, [setPromptNavigatorEnabled]); + const handleExpandedEditorToolbarChange = React.useCallback((enabled: boolean) => { setExpandedEditorToolbar(enabled); void updateDesktopSettings({ expandedEditorToolbar: enabled }); @@ -572,9 +579,11 @@ export const OpenChamberVisualSettings: React.FC || (shouldShow('activityRenderMode') && chatRenderMode === 'sorted') || shouldShow('collapsibleUserMessages') || shouldShow('stickyUserHeader') + || (shouldShow('promptNavigatorEnabled') && !isVSCode) || shouldShow('wideChatLayout') || shouldShow('codeBlockLineWrap') || shouldShow('splitAssistantMessageActions') + || shouldShow('subagentReadOnlyBanner') || shouldShow('diffLayout') || shouldShow('dotfiles') || shouldShow('fileViewerPreview') @@ -1901,7 +1910,7 @@ export const OpenChamberVisualSettings: React.FC )} - {(shouldShow('sessionAssist') || shouldShow('collapsibleUserMessages') || shouldShow('stickyUserHeader') || shouldShow('wideChatLayout') || shouldShow('codeBlockLineWrap') || shouldShow('splitAssistantMessageActions') || shouldShow('subagentReadOnlyBanner') || shouldShow('dotfiles') || shouldShow('fileViewerPreview') || shouldShow('persistDraft') || shouldShow('showToolFileIcons') || shouldShow('showTurnChangedFiles') || (!isMobile && shouldShow('inputSpellcheck')) || shouldShow('reasoning')) && ( + {(shouldShow('sessionAssist') || shouldShow('collapsibleUserMessages') || shouldShow('stickyUserHeader') || (shouldShow('promptNavigatorEnabled') && !isVSCode) || shouldShow('wideChatLayout') || shouldShow('codeBlockLineWrap') || shouldShow('splitAssistantMessageActions') || shouldShow('subagentReadOnlyBanner') || shouldShow('dotfiles') || shouldShow('fileViewerPreview') || shouldShow('persistDraft') || shouldShow('showToolFileIcons') || shouldShow('showTurnChangedFiles') || (!isMobile && shouldShow('inputSpellcheck')) || shouldShow('reasoning')) && (
{(shouldShow('sessionAssist') || shouldShow('subagentReadOnlyBanner')) && (
@@ -2029,7 +2038,7 @@ export const OpenChamberVisualSettings: React.FC
)} - {(shouldShow('collapsibleUserMessages') || shouldShow('stickyUserHeader') || shouldShow('wideChatLayout') || shouldShow('splitAssistantMessageActions') || shouldShow('codeBlockLineWrap')) && ( + {(shouldShow('collapsibleUserMessages') || shouldShow('stickyUserHeader') || (shouldShow('promptNavigatorEnabled') && !isVSCode) || shouldShow('wideChatLayout') || shouldShow('splitAssistantMessageActions') || shouldShow('codeBlockLineWrap')) && (

{t('settings.openchamber.visual.section.messageAppearance')}

{shouldShow('collapsibleUserMessages') && ( @@ -2080,6 +2089,30 @@ export const OpenChamberVisualSettings: React.FC
)} + {shouldShow('promptNavigatorEnabled') && !isVSCode && ( +
handlePromptNavigatorEnabledChange(!promptNavigatorEnabled)} + onKeyDown={(event) => { + if (event.key === ' ' || event.key === 'Enter') { + event.preventDefault(); + handlePromptNavigatorEnabledChange(!promptNavigatorEnabled); + } + }} + > + + {t('settings.openchamber.visual.field.promptNavigatorEnabled')} +
+ )} + {shouldShow('wideChatLayout') && (
{ const setHelpDialogOpen = useUIStore((state) => state.setHelpDialogOpen); const shortcutOverrides = useUIStore((state) => state.shortcutOverrides); const mod = getModifierLabel(); + const isVSCode = isVSCodeRuntime(); const shortcuts: ShortcutSection[] = [ { @@ -114,6 +116,12 @@ export const HelpDialog: React.FC = () => { keys: '', }, { id: 'focus_input', descriptionKey: "helpDialog.item.focusChatInput", icon: "text", keys: '' }, + { + id: 'toggle_prompt_navigator', + descriptionKey: "helpDialog.item.togglePromptNavigator", + icon: "list-unordered", + keys: '', + }, { id: 'abort_run', descriptionKey: "helpDialog.item.abortActiveRun", @@ -226,7 +234,9 @@ export const HelpDialog: React.FC = () => { {t(section.categoryKey)}
- {section.items.map((shortcut) => { + {section.items + .filter((shortcut) => !(isVSCode && shortcut.id === 'toggle_prompt_navigator')) + .map((shortcut) => { const displayKeys = shortcut.id ? renderShortcut(shortcut.id, Array.isArray(shortcut.keys) ? shortcut.keys[0] : shortcut.keys, shortcutOverrides) : (Array.isArray(shortcut.keys) ? shortcut.keys : shortcut.keys.split(" / ")); diff --git a/packages/ui/src/hooks/useKeyboardShortcuts.ts b/packages/ui/src/hooks/useKeyboardShortcuts.ts index 5e28d6a4..c335269b 100644 --- a/packages/ui/src/hooks/useKeyboardShortcuts.ts +++ b/packages/ui/src/hooks/useKeyboardShortcuts.ts @@ -35,6 +35,8 @@ export const useKeyboardShortcuts = () => { const setSettingsDialogOpen = useUIStore((s) => s.setSettingsDialogOpen); const setModelSelectorOpen = useUIStore((s) => s.setModelSelectorOpen); const setTimelineDialogOpen = useUIStore((s) => s.setTimelineDialogOpen); + const togglePromptNavigatorPanel = useUIStore((s) => s.togglePromptNavigatorPanel); + const setPromptNavigatorPanelOpen = useUIStore((s) => s.setPromptNavigatorPanelOpen); const toggleExpandedInput = useUIStore((s) => s.toggleExpandedInput); const shortcutOverrides = useUIStore((s) => s.shortcutOverrides); const currentDirectory = useDirectoryStore((s) => s.currentDirectory); @@ -142,6 +144,42 @@ export const useKeyboardShortcuts = () => { return; } + if (eventMatchesShortcut(e, combo('toggle_prompt_navigator'))) { + const { + activeMainTab, + promptNavigatorEnabled, + isSettingsDialogOpen, + isCommandPaletteOpen, + isHelpDialogOpen, + isSessionSwitcherOpen, + isAboutDialogOpen, + isTimelineDialogOpen, + isMultiRunLauncherOpen, + isImagePreviewOpen, + } = useUIStore.getState(); + + if (!promptNavigatorEnabled || isMobile || isVSCodeRuntime() || activeMainTab !== 'chat') { + return; + } + + const hasOverlay = isSettingsDialogOpen + || isCommandPaletteOpen + || isHelpDialogOpen + || isSessionSwitcherOpen + || isAboutDialogOpen + || isTimelineDialogOpen + || isMultiRunLauncherOpen + || isImagePreviewOpen; + + if (hasOverlay) { + return; + } + + e.preventDefault(); + togglePromptNavigatorPanel(); + return; + } + if (eventMatchesShortcut(e, combo('open_status'))) { e.preventDefault(); void showOpenCodeStatus(); @@ -505,6 +543,7 @@ export const useKeyboardShortcuts = () => { isMultiRunLauncherOpen, isImagePreviewOpen, activeMainTab, + isPromptNavigatorPanelOpen, } = useUIStore.getState(); if (isInsideDialog || isInsideTerminal || hasDropdownInteraction) { @@ -512,6 +551,13 @@ export const useKeyboardShortcuts = () => { return; } + if (isPromptNavigatorPanelOpen) { + e.preventDefault(); + setPromptNavigatorPanelOpen(false); + resetAbortPriming(); + return; + } + // If settings is open, close it if (isSettingsDialogOpen) { e.preventDefault(); @@ -594,6 +640,8 @@ export const useKeyboardShortcuts = () => { setSettingsDialogOpen, setModelSelectorOpen, setTimelineDialogOpen, + togglePromptNavigatorPanel, + setPromptNavigatorPanelOpen, toggleExpandedInput, setThemeMode, working, diff --git a/packages/ui/src/lib/desktop.ts b/packages/ui/src/lib/desktop.ts index 4374b86b..cd558b4a 100644 --- a/packages/ui/src/lib/desktop.ts +++ b/packages/ui/src/lib/desktop.ts @@ -157,6 +157,7 @@ export type DesktopSettings = { userMessageRenderingMode?: 'markdown' | 'plain'; collapsibleUserMessages?: boolean; stickyUserHeader?: boolean; + promptNavigatorEnabled?: boolean; expandedEditorToolbar?: boolean; wideChatLayoutEnabled?: boolean; showSplitAssistantMessageActions?: boolean; diff --git a/packages/ui/src/lib/i18n/messages/en.settings.ts b/packages/ui/src/lib/i18n/messages/en.settings.ts index b9ab1e01..f0a437b3 100644 --- a/packages/ui/src/lib/i18n/messages/en.settings.ts +++ b/packages/ui/src/lib/i18n/messages/en.settings.ts @@ -1059,6 +1059,7 @@ export const settingsDict = { 'settings.openchamber.keyboardShortcuts.action.open_model_selector.label': 'Open model selector', 'settings.openchamber.keyboardShortcuts.action.expand_input.label': 'Expand input', 'settings.openchamber.keyboardShortcuts.action.open_timeline_dialog.label': 'Open conversation timeline', + 'settings.openchamber.keyboardShortcuts.action.toggle_prompt_navigator.label': 'Toggle prompt navigator', 'settings.projects.sidebar.total': 'Total {count}', 'settings.projects.sidebar.actions.addProject': 'Add project', 'settings.projects.page.empty.noProjects': 'No projects available.', @@ -1803,6 +1804,8 @@ export const settingsDict = { 'settings.openchamber.visual.field.collapsibleUserMessages': 'Collapse Long User Messages', 'settings.openchamber.visual.field.stickyUserHeaderAria': 'Sticky user header', 'settings.openchamber.visual.field.stickyUserHeader': 'Sticky User Header', + 'settings.openchamber.visual.field.promptNavigatorEnabledAria': 'Prompt navigator', + 'settings.openchamber.visual.field.promptNavigatorEnabled': 'Prompt Navigator', 'settings.openchamber.visual.field.expandedEditorToolbarAria': 'Always show editor toolbar', 'settings.openchamber.visual.field.expandedEditorToolbar': 'Always show editor toolbar (docked under the file tabs)', 'settings.openchamber.visual.field.wideChatLayoutAria': 'Wide chat layout', diff --git a/packages/ui/src/lib/i18n/messages/en.ts b/packages/ui/src/lib/i18n/messages/en.ts index 82bb614a..a083acaa 100644 --- a/packages/ui/src/lib/i18n/messages/en.ts +++ b/packages/ui/src/lib/i18n/messages/en.ts @@ -1563,6 +1563,7 @@ export const dict = { 'helpDialog.item.createNewSession': 'Create New Session', 'helpDialog.item.createNewWorktreeDraft': 'Create New Worktree Draft', 'helpDialog.item.focusChatInput': 'Focus Chat Input', + 'helpDialog.item.togglePromptNavigator': 'Toggle Prompt Navigator', 'helpDialog.item.abortActiveRun': 'Abort active run (double press)', 'helpDialog.item.toggleRightSidebar': 'Toggle Right Sidebar', 'helpDialog.item.openRightSidebarGitTab': 'Open Right Sidebar Git Tab', @@ -1757,6 +1758,9 @@ export const dict = { 'chat.draftStarters.sectionSkills': 'Skills', 'chat.draftStarters.remove': 'Remove', 'chat.scrollToBottom.aria': 'Scroll to bottom', + 'chat.promptNavigator.aria': 'Prompt navigation', + 'chat.promptNavigator.currentPrompt': 'Current prompt', + 'chat.promptNavigator.loadMore': 'Load more prompts', 'chat.timeline.relative.justNow': 'just now', 'chat.timeline.relative.minutesAgo': '{count}m ago', 'chat.timeline.relative.hoursAgo': '{count}h ago', diff --git a/packages/ui/src/lib/i18n/messages/es.settings.ts b/packages/ui/src/lib/i18n/messages/es.settings.ts index 65d80c78..8628740e 100644 --- a/packages/ui/src/lib/i18n/messages/es.settings.ts +++ b/packages/ui/src/lib/i18n/messages/es.settings.ts @@ -1026,6 +1026,7 @@ export const settingsDict = { "settings.openchamber.keyboardShortcuts.action.open_model_selector.label": "Abrir selector de modelo", "settings.openchamber.keyboardShortcuts.action.expand_input.label": "Expandir entrada", "settings.openchamber.keyboardShortcuts.action.open_timeline_dialog.label": "Abrir línea de tiempo de conversación", + "settings.openchamber.keyboardShortcuts.action.toggle_prompt_navigator.label": "Mostrar u ocultar navegador de prompts", "settings.projects.sidebar.total": "Total {count}", "settings.projects.sidebar.actions.addProject": "Añadir proyecto", "settings.projects.page.empty.noProjects": "No hay proyectos disponibles.", @@ -1770,6 +1771,8 @@ export const settingsDict = { "settings.openchamber.visual.field.collapsibleUserMessages": "Contraer mensajes largos del usuario", "settings.openchamber.visual.field.stickyUserHeaderAria": "Encabezado del usuario fijo", "settings.openchamber.visual.field.stickyUserHeader": "Encabezado de usuario fijo", + "settings.openchamber.visual.field.promptNavigatorEnabledAria": "Navegador de prompts", + "settings.openchamber.visual.field.promptNavigatorEnabled": "Navegador de prompts", "settings.openchamber.visual.field.expandedEditorToolbarAria": "Mostrar siempre la barra de herramientas del editor", "settings.openchamber.visual.field.expandedEditorToolbar": "Mostrar siempre la barra de herramientas del editor (anclada bajo las pestañas)", "settings.openchamber.visual.field.wideChatLayoutAria": "Diseño de chat ancho", diff --git a/packages/ui/src/lib/i18n/messages/es.ts b/packages/ui/src/lib/i18n/messages/es.ts index 7dd88fed..661350b9 100644 --- a/packages/ui/src/lib/i18n/messages/es.ts +++ b/packages/ui/src/lib/i18n/messages/es.ts @@ -1541,6 +1541,7 @@ export const dict: Record = { "helpDialog.item.createNewSession": "Crear nueva sesión", "helpDialog.item.createNewWorktreeDraft": "Crear nuevo borrador de worktree", "helpDialog.item.focusChatInput": "Enfocar entrada de chat", + "helpDialog.item.togglePromptNavigator": "Mostrar u ocultar navegador de prompts", "helpDialog.item.abortActiveRun": "Detener ejecución activa (doble presionar)", "helpDialog.item.toggleRightSidebar": "Mostrar u ocultar barra lateral derecha", "helpDialog.item.openRightSidebarGitTab": "Abrir pestaña de Git en barra lateral derecha", @@ -1735,6 +1736,9 @@ export const dict: Record = { "chat.draftStarters.sectionSkills": "Skills", "chat.draftStarters.remove": "Remove", "chat.scrollToBottom.aria": "Ir al final", + "chat.promptNavigator.aria": "Navegación de prompts", + "chat.promptNavigator.currentPrompt": "Prompt actual", + "chat.promptNavigator.loadMore": "Cargar más prompts", "chat.timeline.relative.justNow": "ahora mismo", "chat.timeline.relative.minutesAgo": "hace {count}m", "chat.timeline.relative.hoursAgo": "hace {count}h", diff --git a/packages/ui/src/lib/i18n/messages/fr.settings.ts b/packages/ui/src/lib/i18n/messages/fr.settings.ts index 5739d9e7..85fce130 100644 --- a/packages/ui/src/lib/i18n/messages/fr.settings.ts +++ b/packages/ui/src/lib/i18n/messages/fr.settings.ts @@ -951,6 +951,7 @@ export const settingsDict = { 'settings.openchamber.keyboardShortcuts.action.open_model_selector.label': 'Ouvrir le sélecteur de modèle', 'settings.openchamber.keyboardShortcuts.action.expand_input.label': 'Développer l\'entrée', 'settings.openchamber.keyboardShortcuts.action.open_timeline_dialog.label': 'Chronologie de la conversation ouverte', + 'settings.openchamber.keyboardShortcuts.action.toggle_prompt_navigator.label': 'Afficher ou masquer le navigateur de prompts', 'settings.projects.sidebar.total': 'Total {count}', 'settings.projects.sidebar.actions.addProject': 'Ajouter un projet', 'settings.projects.page.empty.noProjects': 'Aucun projet disponible.', @@ -1679,6 +1680,8 @@ export const settingsDict = { 'settings.openchamber.visual.field.collapsibleUserMessages': 'Réduire les longs messages utilisateur', 'settings.openchamber.visual.field.stickyUserHeaderAria': 'En-tête utilisateur collant', 'settings.openchamber.visual.field.stickyUserHeader': 'En-tête utilisateur collant', + 'settings.openchamber.visual.field.promptNavigatorEnabledAria': 'Navigateur de prompts', + 'settings.openchamber.visual.field.promptNavigatorEnabled': 'Navigateur de prompts', 'settings.openchamber.visual.field.expandedEditorToolbarAria': 'Toujours afficher la barre d’outils de l’éditeur', 'settings.openchamber.visual.field.expandedEditorToolbar': 'Toujours afficher la barre d’outils de l’éditeur (ancrée sous les onglets de fichiers)', 'settings.openchamber.visual.field.wideChatLayoutAria': 'Large disposition de discussion', diff --git a/packages/ui/src/lib/i18n/messages/fr.ts b/packages/ui/src/lib/i18n/messages/fr.ts index 2b3d5ef9..99c95112 100644 --- a/packages/ui/src/lib/i18n/messages/fr.ts +++ b/packages/ui/src/lib/i18n/messages/fr.ts @@ -1383,6 +1383,7 @@ export const dict = { 'helpDialog.item.createNewSession': 'Créer une nouvelle session', 'helpDialog.item.createNewWorktreeDraft': 'Créer un nouveau brouillon d\'worktree', 'helpDialog.item.focusChatInput': 'Concentration sur la saisie du chat', + 'helpDialog.item.togglePromptNavigator': 'Afficher ou masquer le navigateur de prompts', 'helpDialog.item.abortActiveRun': 'Abandonner l’exécution active (double pression)', 'helpDialog.item.toggleRightSidebar': 'Basculer la barre latérale droite', 'helpDialog.item.openRightSidebarGitTab': 'Ouvrir l\'onglet Git de la barre latérale droite', @@ -1562,6 +1563,9 @@ export const dict = { 'chat.emptyState.draftTitle': 'Sur quoi travaillons-nous ?', 'chat.emptyState.draftTitleWithProject': 'Sur quoi travaillons-nous dans {project} ?', 'chat.scrollToBottom.aria': 'Faire défiler vers le bas', + 'chat.promptNavigator.aria': 'Navigation des prompts', + 'chat.promptNavigator.currentPrompt': 'Prompt actuel', + 'chat.promptNavigator.loadMore': 'Charger plus de prompts', 'chat.timeline.relative.justNow': 'à l’instant', 'chat.timeline.relative.minutesAgo': 'il y a {count} min', 'chat.timeline.relative.hoursAgo': 'il y a {count} h', diff --git a/packages/ui/src/lib/i18n/messages/ja.settings.ts b/packages/ui/src/lib/i18n/messages/ja.settings.ts index 654f6e72..99c3da33 100644 --- a/packages/ui/src/lib/i18n/messages/ja.settings.ts +++ b/packages/ui/src/lib/i18n/messages/ja.settings.ts @@ -1059,6 +1059,7 @@ export const settingsDict = { 'settings.openchamber.keyboardShortcuts.action.expand_input.label': '入力を拡大', 'settings.openchamber.keyboardShortcuts.action.open_model_selector.label': 'モデルセレクターを開く', 'settings.openchamber.keyboardShortcuts.action.open_timeline_dialog.label': '会話タイムラインを開く', + 'settings.openchamber.keyboardShortcuts.action.toggle_prompt_navigator.label': 'プロンプトナビゲーターの表示切替', 'settings.projects.sidebar.total': '合計 {count}', 'settings.projects.sidebar.actions.addProject': 'プロジェクトを追加', 'settings.projects.page.empty.noProjects': '利用可能なプロジェクトがありません。', @@ -1803,6 +1804,8 @@ export const settingsDict = { 'settings.openchamber.visual.field.collapsibleUserMessages': '長いユーザーメッセージを折りたたむ', 'settings.openchamber.visual.field.stickyUserHeaderAria': 'ユーザーヘッダー固定', 'settings.openchamber.visual.field.stickyUserHeader': 'ユーザーヘッダー固定', + 'settings.openchamber.visual.field.promptNavigatorEnabledAria': 'プロンプトナビゲーター', + 'settings.openchamber.visual.field.promptNavigatorEnabled': 'プロンプトナビゲーター', 'settings.openchamber.visual.field.expandedEditorToolbarAria': 'エディターツールバーを常に表示', 'settings.openchamber.visual.field.expandedEditorToolbar': 'エディターツールバーを常に表示(ファイルタブの下にドッキング)', 'settings.openchamber.visual.field.wideChatLayoutAria': 'ワイドチャットレイアウト', diff --git a/packages/ui/src/lib/i18n/messages/ja.ts b/packages/ui/src/lib/i18n/messages/ja.ts index 8b8911e1..00c504e5 100644 --- a/packages/ui/src/lib/i18n/messages/ja.ts +++ b/packages/ui/src/lib/i18n/messages/ja.ts @@ -1559,6 +1559,7 @@ export const dict: Record = { 'helpDialog.item.createNewSession': '新しいセッションを作成', 'helpDialog.item.createNewWorktreeDraft': '新しいワークツリー下書きを作成', 'helpDialog.item.focusChatInput': 'チャット入力にフォーカス', + 'helpDialog.item.togglePromptNavigator': 'プロンプトナビゲーターの表示切替', 'helpDialog.item.abortActiveRun': 'アクティブな実行を中止(ダブルプレス)', 'helpDialog.item.toggleRightSidebar': '右サイドバーの切り替え', 'helpDialog.item.openRightSidebarGitTab': '右サイドバーのGitタブを開く', @@ -1753,6 +1754,9 @@ export const dict: Record = { 'chat.draftStarters.sectionSkills': 'スキル', 'chat.draftStarters.remove': '削除', 'chat.scrollToBottom.aria': '一番下にスクロール', + 'chat.promptNavigator.aria': 'プロンプトナビゲーション', + 'chat.promptNavigator.currentPrompt': '現在のプロンプト', + 'chat.promptNavigator.loadMore': 'さらにプロンプトを読み込む', 'chat.timeline.relative.justNow': 'たった今', 'chat.timeline.relative.minutesAgo': '{count}分前', 'chat.timeline.relative.hoursAgo': '{count}時間前', diff --git a/packages/ui/src/lib/i18n/messages/ko.settings.ts b/packages/ui/src/lib/i18n/messages/ko.settings.ts index 38a91de0..a54eaefa 100644 --- a/packages/ui/src/lib/i18n/messages/ko.settings.ts +++ b/packages/ui/src/lib/i18n/messages/ko.settings.ts @@ -1026,6 +1026,7 @@ export const settingsDict = { 'settings.openchamber.keyboardShortcuts.action.open_model_selector.label': '모델 선택기 열기', 'settings.openchamber.keyboardShortcuts.action.expand_input.label': '입력 확장', 'settings.openchamber.keyboardShortcuts.action.open_timeline_dialog.label': '대화 타임라인 열기', + 'settings.openchamber.keyboardShortcuts.action.toggle_prompt_navigator.label': '프롬프트 탐색기 표시/숨기기', 'settings.projects.sidebar.total': '총 {count}개', 'settings.projects.sidebar.actions.addProject': '프로젝트 추가', 'settings.projects.page.empty.noProjects': '사용 가능한 프로젝트가 없습니다.', @@ -1770,6 +1771,8 @@ export const settingsDict = { 'settings.openchamber.visual.field.collapsibleUserMessages': '긴 사용자 메시지 접기', 'settings.openchamber.visual.field.stickyUserHeaderAria': '고정 사용자 헤더', 'settings.openchamber.visual.field.stickyUserHeader': '고정 사용자 헤더', + 'settings.openchamber.visual.field.promptNavigatorEnabledAria': '프롬프트 탐색기', + 'settings.openchamber.visual.field.promptNavigatorEnabled': '프롬프트 탐색기', 'settings.openchamber.visual.field.expandedEditorToolbarAria': 'Always show editor toolbar', 'settings.openchamber.visual.field.expandedEditorToolbar': 'Always show editor toolbar (docked under the file tabs)', 'settings.openchamber.visual.field.wideChatLayoutAria': '넓은 채팅 레이아웃', diff --git a/packages/ui/src/lib/i18n/messages/ko.ts b/packages/ui/src/lib/i18n/messages/ko.ts index 84c669a1..9839fd92 100644 --- a/packages/ui/src/lib/i18n/messages/ko.ts +++ b/packages/ui/src/lib/i18n/messages/ko.ts @@ -1565,6 +1565,7 @@ export const dict: Record = { 'helpDialog.item.createNewSession': '새 세션 만들기', 'helpDialog.item.createNewWorktreeDraft': '새 워크트리 드래프트 만들기', 'helpDialog.item.focusChatInput': '채팅 입력창으로 포커스 이동', + 'helpDialog.item.togglePromptNavigator': '프롬프트 탐색기 표시/숨기기', 'helpDialog.item.abortActiveRun': '활성 실행 중단(두 번 누르기)', 'helpDialog.item.toggleRightSidebar': '오른쪽 사이드바 전환', 'helpDialog.item.openRightSidebarGitTab': '오른쪽 사이드바 Git 탭 열기', @@ -1759,6 +1760,9 @@ export const dict: Record = { 'chat.draftStarters.sectionSkills': 'Skills', 'chat.draftStarters.remove': 'Remove', 'chat.scrollToBottom.aria': '맨 아래로 스크롤', + 'chat.promptNavigator.aria': '프롬프트 탐색', + 'chat.promptNavigator.currentPrompt': '현재 프롬프트', + 'chat.promptNavigator.loadMore': '더 많은 프롬프트 불러오기', 'chat.timeline.relative.justNow': '방금 전', 'chat.timeline.relative.minutesAgo': '{count}분 전', 'chat.timeline.relative.hoursAgo': '{count}시간 전', diff --git a/packages/ui/src/lib/i18n/messages/pl.settings.ts b/packages/ui/src/lib/i18n/messages/pl.settings.ts index 7020feb6..b6514e7d 100644 --- a/packages/ui/src/lib/i18n/messages/pl.settings.ts +++ b/packages/ui/src/lib/i18n/messages/pl.settings.ts @@ -761,6 +761,7 @@ export const settingsDict = { 'settings.openchamber.keyboardShortcuts.action.cycle_theme.label': 'Przełącz motyw', 'settings.openchamber.keyboardShortcuts.action.cycle_agent.label': 'Przełącz agenta', 'settings.openchamber.keyboardShortcuts.action.expand_input.label': 'Rozwiń pole wprowadzania', + 'settings.openchamber.keyboardShortcuts.action.toggle_prompt_navigator.label': 'Przełącz nawigator promptów', 'settings.openchamber.keyboardShortcuts.action.focus_input.label': 'Skup pole wprowadzania', 'settings.openchamber.keyboardShortcuts.action.new_chat.label': 'Nowa sesja', 'settings.openchamber.keyboardShortcuts.action.new_chat_worktree.label': 'Nowy szkic obszaru roboczego', @@ -1034,6 +1035,8 @@ export const settingsDict = { 'settings.openchamber.visual.field.spacingDensity': 'Gęstość odstępów', 'settings.openchamber.visual.field.stickyUserHeader': 'Przyklejony nagłówek użytkownika', 'settings.openchamber.visual.field.stickyUserHeaderAria': 'Przyklejony nagłówek użytkownika', + 'settings.openchamber.visual.field.promptNavigatorEnabledAria': 'Nawigator promptów', + 'settings.openchamber.visual.field.promptNavigatorEnabled': 'Nawigator promptów', 'settings.openchamber.visual.field.expandedEditorToolbarAria': 'Always show editor toolbar', 'settings.openchamber.visual.field.expandedEditorToolbar': 'Always show editor toolbar (docked under the file tabs)', 'settings.openchamber.visual.field.terminalFontSize': 'Rozmiar czcionki terminala', diff --git a/packages/ui/src/lib/i18n/messages/pl.ts b/packages/ui/src/lib/i18n/messages/pl.ts index ab29f5b7..adbadd61 100644 --- a/packages/ui/src/lib/i18n/messages/pl.ts +++ b/packages/ui/src/lib/i18n/messages/pl.ts @@ -651,6 +651,9 @@ export const dict: Record = { 'chat.draftStarters.sectionSkills': 'Skills', 'chat.draftStarters.remove': 'Remove', 'chat.scrollToBottom.aria': 'Przewiń na dół', + 'chat.promptNavigator.aria': 'Nawigacja promptów', + 'chat.promptNavigator.currentPrompt': 'Bieżący prompt', + 'chat.promptNavigator.loadMore': 'Wczytaj więcej promptów', 'chat.timeline.relative.justNow': 'przed chwilą', 'chat.timeline.relative.minutesAgo': '{count}m temu', 'chat.timeline.relative.hoursAgo': '{count}h temu', @@ -2183,6 +2186,7 @@ export const dict: Record = { 'helpDialog.item.cycleTheme': 'Przełącz motyw (Jasny → Ciemny → Systemowy)', 'helpDialog.item.cycleThinkingVariant': 'Przełącz wariant myślenia (skrót globalny)', 'helpDialog.item.focusChatInput': 'Ustaw fokus na polu czatu', + 'helpDialog.item.togglePromptNavigator': 'Przełącz nawigator promptów', 'helpDialog.item.navigateModels': 'Nawiguj po modelach (w selektorze)', 'helpDialog.item.newWindow': 'Nowe okno (tylko desktop)', 'helpDialog.item.openCommandPalette': 'Otwórz paletę poleceń', diff --git a/packages/ui/src/lib/i18n/messages/pt-BR.settings.ts b/packages/ui/src/lib/i18n/messages/pt-BR.settings.ts index 6561dcdc..3b9be3e7 100644 --- a/packages/ui/src/lib/i18n/messages/pt-BR.settings.ts +++ b/packages/ui/src/lib/i18n/messages/pt-BR.settings.ts @@ -1026,6 +1026,7 @@ export const settingsDict = { "settings.openchamber.keyboardShortcuts.action.open_model_selector.label": "Abrir seletor de modelo", "settings.openchamber.keyboardShortcuts.action.expand_input.label": "Expandir entrada", "settings.openchamber.keyboardShortcuts.action.open_timeline_dialog.label": "Abrir linha do tempo da conversa", + "settings.openchamber.keyboardShortcuts.action.toggle_prompt_navigator.label": "Mostrar ou ocultar navegador de prompts", "settings.projects.sidebar.total": "Total {count}", "settings.projects.sidebar.actions.addProject": "Adicionar projeto", "settings.projects.page.empty.noProjects": "Não há projetos disponíveis.", @@ -1770,6 +1771,8 @@ export const settingsDict = { "settings.openchamber.visual.field.collapsibleUserMessages": "Recolher mensagens longas do usuário", "settings.openchamber.visual.field.stickyUserHeaderAria": "Cabeçalho do usuário fixo", "settings.openchamber.visual.field.stickyUserHeader": "Cabeçalho do usuário fixo", + "settings.openchamber.visual.field.promptNavigatorEnabledAria": "Navegador de prompts", + "settings.openchamber.visual.field.promptNavigatorEnabled": "Navegador de prompts", "settings.openchamber.visual.field.expandedEditorToolbarAria": "Mostrar sempre a barra de ferramentas do editor", "settings.openchamber.visual.field.expandedEditorToolbar": "Mostrar sempre a barra de ferramentas do editor (ancorada sob as abas)", "settings.openchamber.visual.field.wideChatLayoutAria": "Layout de chat amplo", diff --git a/packages/ui/src/lib/i18n/messages/pt-BR.ts b/packages/ui/src/lib/i18n/messages/pt-BR.ts index 54a54ac9..92ede170 100644 --- a/packages/ui/src/lib/i18n/messages/pt-BR.ts +++ b/packages/ui/src/lib/i18n/messages/pt-BR.ts @@ -1541,6 +1541,7 @@ export const dict: Record = { "helpDialog.item.createNewSession": "Criar nova sessão", "helpDialog.item.createNewWorktreeDraft": "Criar novo rascunho de worktree", "helpDialog.item.focusChatInput": "Focar entrada do chat", + "helpDialog.item.togglePromptNavigator": "Mostrar ou ocultar navegador de prompts", "helpDialog.item.abortActiveRun": "Interromper execução ativa (duplo clique)", "helpDialog.item.toggleRightSidebar": "Mostrar ou ocultar barra lateral direita", "helpDialog.item.openRightSidebarGitTab": "Abrir aba de Git em barra lateral direita", @@ -1735,6 +1736,9 @@ export const dict: Record = { "chat.draftStarters.sectionSkills": "Skills", "chat.draftStarters.remove": "Remove", "chat.scrollToBottom.aria": "Ir ao final", + "chat.promptNavigator.aria": "Navegação de prompts", + "chat.promptNavigator.currentPrompt": "Prompt atual", + "chat.promptNavigator.loadMore": "Carregar mais prompts", "chat.timeline.relative.justNow": "agora mesmo", "chat.timeline.relative.minutesAgo": "há {count}m", "chat.timeline.relative.hoursAgo": "há {count}h", diff --git a/packages/ui/src/lib/i18n/messages/uk.settings.ts b/packages/ui/src/lib/i18n/messages/uk.settings.ts index 76042f93..5b701fc5 100644 --- a/packages/ui/src/lib/i18n/messages/uk.settings.ts +++ b/packages/ui/src/lib/i18n/messages/uk.settings.ts @@ -1026,6 +1026,7 @@ export const settingsDict = { "settings.openchamber.keyboardShortcuts.action.open_model_selector.label": "Відкрити вибір моделі", "settings.openchamber.keyboardShortcuts.action.expand_input.label": "Розгорнути введення", "settings.openchamber.keyboardShortcuts.action.open_timeline_dialog.label": "Відкрити хронологію розмови", + "settings.openchamber.keyboardShortcuts.action.toggle_prompt_navigator.label": "Показати або приховати навігатор промптів", "settings.projects.sidebar.total": "Усього {count}", "settings.projects.sidebar.actions.addProject": "Додати проєкт", "settings.projects.page.empty.noProjects": "Немає доступних проєктів.", @@ -1770,6 +1771,8 @@ export const settingsDict = { "settings.openchamber.visual.field.collapsibleUserMessages": "Згортати довгі повідомлення користувача", "settings.openchamber.visual.field.stickyUserHeaderAria": "Закріплений заголовок користувача", "settings.openchamber.visual.field.stickyUserHeader": "Закріплений заголовок користувача", + "settings.openchamber.visual.field.promptNavigatorEnabledAria": "Навігатор промптів", + "settings.openchamber.visual.field.promptNavigatorEnabled": "Навігатор промптів", "settings.openchamber.visual.field.expandedEditorToolbarAria": "Завжди показувати панель інструментів редактора", "settings.openchamber.visual.field.expandedEditorToolbar": "Завжди показувати панель інструментів редактора (закріплена під вкладками)", "settings.openchamber.visual.field.wideChatLayoutAria": "Широкий макет чату", diff --git a/packages/ui/src/lib/i18n/messages/uk.ts b/packages/ui/src/lib/i18n/messages/uk.ts index 463432e2..9e32a5a4 100644 --- a/packages/ui/src/lib/i18n/messages/uk.ts +++ b/packages/ui/src/lib/i18n/messages/uk.ts @@ -1541,6 +1541,7 @@ export const dict: Record = { "helpDialog.item.createNewSession": "Створити нову сесію", "helpDialog.item.createNewWorktreeDraft": "Створити нову чернетку worktree", "helpDialog.item.focusChatInput": "Фокус на полі вводу чату", + "helpDialog.item.togglePromptNavigator": "Показати або приховати навігатор промптів", "helpDialog.item.abortActiveRun": "Перервати активний запуск (подвійне натискання)", "helpDialog.item.toggleRightSidebar": "Перемкнути праву бічну панель", "helpDialog.item.openRightSidebarGitTab": "Відкрити вкладку Git у правій бічній панелі", @@ -1735,6 +1736,9 @@ export const dict: Record = { "chat.draftStarters.sectionSkills": "Скіли", "chat.draftStarters.remove": "Прибрати", "chat.scrollToBottom.aria": "Прокрутити вниз", + "chat.promptNavigator.aria": "Навігація за промптами", + "chat.promptNavigator.currentPrompt": "Поточний промпт", + "chat.promptNavigator.loadMore": "Завантажити більше промптів", "chat.timeline.relative.justNow": "щойно", "chat.timeline.relative.minutesAgo": "{count} хв тому", "chat.timeline.relative.hoursAgo": "{count} год тому", diff --git a/packages/ui/src/lib/i18n/messages/zh-CN.settings.ts b/packages/ui/src/lib/i18n/messages/zh-CN.settings.ts index d6f25171..4893c842 100644 --- a/packages/ui/src/lib/i18n/messages/zh-CN.settings.ts +++ b/packages/ui/src/lib/i18n/messages/zh-CN.settings.ts @@ -1026,6 +1026,7 @@ export const settingsDict = { 'settings.openchamber.keyboardShortcuts.action.open_model_selector.label': '打开模型选择器', 'settings.openchamber.keyboardShortcuts.action.expand_input.label': '展开输入框', 'settings.openchamber.keyboardShortcuts.action.open_timeline_dialog.label': '打开对话时间线', + 'settings.openchamber.keyboardShortcuts.action.toggle_prompt_navigator.label': '显示或隐藏提示词导航', 'settings.projects.sidebar.total': '总计 {count}', 'settings.projects.sidebar.actions.addProject': '添加项目', 'settings.projects.page.empty.noProjects': '暂无项目。', @@ -1770,6 +1771,8 @@ export const settingsDict = { 'settings.openchamber.visual.field.collapsibleUserMessages': '折叠较长的用户消息', 'settings.openchamber.visual.field.stickyUserHeaderAria': '固定用户消息头', 'settings.openchamber.visual.field.stickyUserHeader': '固定用户消息头', + 'settings.openchamber.visual.field.promptNavigatorEnabledAria': '提示词导航', + 'settings.openchamber.visual.field.promptNavigatorEnabled': '提示词导航', 'settings.openchamber.visual.field.expandedEditorToolbarAria': 'Always show editor toolbar', 'settings.openchamber.visual.field.expandedEditorToolbar': 'Always show editor toolbar (docked under the file tabs)', 'settings.openchamber.visual.field.wideChatLayoutAria': '宽聊天布局', diff --git a/packages/ui/src/lib/i18n/messages/zh-CN.ts b/packages/ui/src/lib/i18n/messages/zh-CN.ts index 24fce483..d8f16f54 100644 --- a/packages/ui/src/lib/i18n/messages/zh-CN.ts +++ b/packages/ui/src/lib/i18n/messages/zh-CN.ts @@ -1529,6 +1529,7 @@ export const dict: Record = { 'helpDialog.item.createNewSession': '创建新会话', 'helpDialog.item.createNewWorktreeDraft': '创建新工作树草稿', 'helpDialog.item.focusChatInput': '聚焦聊天输入框', + 'helpDialog.item.togglePromptNavigator': '显示或隐藏提示词导航', 'helpDialog.item.abortActiveRun': '中止当前运行(双击)', 'helpDialog.item.toggleRightSidebar': '切换右侧边栏', 'helpDialog.item.openRightSidebarGitTab': '打开右侧边栏 Git 标签', @@ -1723,6 +1724,9 @@ export const dict: Record = { 'chat.draftStarters.sectionSkills': 'Skills', 'chat.draftStarters.remove': 'Remove', 'chat.scrollToBottom.aria': '滚动到底部', + 'chat.promptNavigator.aria': '提示词导航', + 'chat.promptNavigator.currentPrompt': '当前提示', + 'chat.promptNavigator.loadMore': '加载更多提示', 'chat.timeline.relative.justNow': '刚刚', 'chat.timeline.relative.minutesAgo': '{count} 分钟前', 'chat.timeline.relative.hoursAgo': '{count} 小时前', diff --git a/packages/ui/src/lib/i18n/messages/zh-TW.settings.ts b/packages/ui/src/lib/i18n/messages/zh-TW.settings.ts index 3ead90b9..ffa4e81b 100644 --- a/packages/ui/src/lib/i18n/messages/zh-TW.settings.ts +++ b/packages/ui/src/lib/i18n/messages/zh-TW.settings.ts @@ -938,6 +938,7 @@ 'settings.openchamber.keyboardShortcuts.action.open_model_selector.label': '開啟模型選擇器', 'settings.openchamber.keyboardShortcuts.action.expand_input.label': '展開輸入方塊', 'settings.openchamber.keyboardShortcuts.action.open_timeline_dialog.label': '開啟對話時間軸', + 'settings.openchamber.keyboardShortcuts.action.toggle_prompt_navigator.label': '顯示或隱藏提示詞導覽', 'settings.projects.sidebar.total': '總計 {count}', 'settings.projects.sidebar.actions.addProject': '新增專案', 'settings.projects.page.empty.noProjects': '暫無專案。', @@ -1681,6 +1682,8 @@ 'settings.openchamber.visual.field.collapsibleUserMessages': '收合較長的使用者訊息', 'settings.openchamber.visual.field.stickyUserHeaderAria': '固定使用者訊息標頭', 'settings.openchamber.visual.field.stickyUserHeader': '固定使用者訊息標頭', + 'settings.openchamber.visual.field.promptNavigatorEnabledAria': '提示詞導覽', + 'settings.openchamber.visual.field.promptNavigatorEnabled': '提示詞導覽', 'settings.openchamber.visual.field.expandedEditorToolbarAria': 'Always show editor toolbar', 'settings.openchamber.visual.field.expandedEditorToolbar': 'Always show editor toolbar (docked under the file tabs)', 'settings.openchamber.visual.field.wideChatLayoutAria': '寬聊天佈局', diff --git a/packages/ui/src/lib/i18n/messages/zh-TW.ts b/packages/ui/src/lib/i18n/messages/zh-TW.ts index 73df1af0..be795a2d 100644 --- a/packages/ui/src/lib/i18n/messages/zh-TW.ts +++ b/packages/ui/src/lib/i18n/messages/zh-TW.ts @@ -1533,6 +1533,7 @@ export const dict: Record = { 'helpDialog.item.createNewSession': '建立新會話', 'helpDialog.item.createNewWorktreeDraft': '建立新 worktree 草稿', 'helpDialog.item.focusChatInput': '聚焦聊天輸入框', + 'helpDialog.item.togglePromptNavigator': '顯示或隱藏提示詞導覽', 'helpDialog.item.abortActiveRun': '中止目前執行(連按兩下)', 'helpDialog.item.toggleRightSidebar': '切換右側邊欄', 'helpDialog.item.openRightSidebarGitTab': '開啟右側邊欄 Git 標籤', @@ -1727,6 +1728,9 @@ export const dict: Record = { 'chat.draftStarters.sectionSkills': 'Skills', 'chat.draftStarters.remove': 'Remove', 'chat.scrollToBottom.aria': '捲動到底部', + 'chat.promptNavigator.aria': '提示詞導覽', + 'chat.promptNavigator.currentPrompt': '目前提示', + 'chat.promptNavigator.loadMore': '載入更多提示', 'chat.timeline.relative.justNow': '剛剛', 'chat.timeline.relative.minutesAgo': '{count} 分鐘前', 'chat.timeline.relative.hoursAgo': '{count} 小時前', diff --git a/packages/ui/src/lib/persistence.ts b/packages/ui/src/lib/persistence.ts index c23db33d..95418a24 100644 --- a/packages/ui/src/lib/persistence.ts +++ b/packages/ui/src/lib/persistence.ts @@ -582,6 +582,9 @@ const applyDesktopUiPreferences = (settings: DesktopSettings) => { if (typeof settings.stickyUserHeader === 'boolean' && settings.stickyUserHeader !== store.stickyUserHeader) { store.setStickyUserHeader(settings.stickyUserHeader); } + if (typeof settings.promptNavigatorEnabled === 'boolean' && settings.promptNavigatorEnabled !== store.promptNavigatorEnabled) { + store.setPromptNavigatorEnabled(settings.promptNavigatorEnabled); + } if (typeof settings.expandedEditorToolbar === 'boolean' && settings.expandedEditorToolbar !== store.expandedEditorToolbar) { store.setExpandedEditorToolbar(settings.expandedEditorToolbar); } @@ -1150,6 +1153,9 @@ const sanitizeWebSettings = (payload: unknown): DesktopSettings | null => { if (typeof candidate.stickyUserHeader === 'boolean') { result.stickyUserHeader = candidate.stickyUserHeader; } + if (typeof candidate.promptNavigatorEnabled === 'boolean') { + result.promptNavigatorEnabled = candidate.promptNavigatorEnabled; + } if (typeof candidate.wideChatLayoutEnabled === 'boolean') { result.wideChatLayoutEnabled = candidate.wideChatLayoutEnabled; } diff --git a/packages/ui/src/lib/settings/search.ts b/packages/ui/src/lib/settings/search.ts index 2b10834d..68b76bac 100644 --- a/packages/ui/src/lib/settings/search.ts +++ b/packages/ui/src/lib/settings/search.ts @@ -224,6 +224,13 @@ const SETTINGS_SEARCH_ITEMS: readonly SettingsSearchItem[] = [ titleKey: 'settings.openchamber.visual.field.stickyUserHeader', keywords: ['messages', 'header'], }, + { + id: 'chat.prompt-navigator', + page: 'chat', + titleKey: 'settings.openchamber.visual.field.promptNavigatorEnabled', + keywords: ['prompt', 'navigator', 'navigation', 'timeline', 'scroll'], + isAvailable: (ctx) => !ctx.isVSCode, + }, { id: 'chat.collapsible-user-messages', page: 'chat', diff --git a/packages/ui/src/lib/shortcuts.ts b/packages/ui/src/lib/shortcuts.ts index 93a48867..59c191c1 100644 --- a/packages/ui/src/lib/shortcuts.ts +++ b/packages/ui/src/lib/shortcuts.ts @@ -172,6 +172,13 @@ const SHORTCUT_ACTIONS: ReadonlyArray = [ description: 'Search and navigate within current conversation', customizable: true, }, + { + id: 'toggle_prompt_navigator', + defaultCombo: 'mod+alt+p', + label: 'Toggle prompt navigator', + description: 'Show or hide the prompt navigator panel in chat', + customizable: true, + }, { id: 'toggle_right_sidebar', defaultCombo: 'mod+b', diff --git a/packages/ui/src/stores/useUIStore.ts b/packages/ui/src/stores/useUIStore.ts index 460400a9..a525cdfd 100644 --- a/packages/ui/src/stores/useUIStore.ts +++ b/packages/ui/src/stores/useUIStore.ts @@ -612,6 +612,7 @@ interface UIStore { diffWrapLines: boolean; gitChangesViewMode: 'flat' | 'tree'; isTimelineDialogOpen: boolean; + isPromptNavigatorPanelOpen: boolean; isImagePreviewOpen: boolean; nativeNotificationsEnabled: boolean; notificationMode: 'always' | 'hidden-only'; @@ -655,6 +656,7 @@ interface UIStore { userMessageRenderingMode: UserMessageRenderingMode; collapsibleUserMessages: boolean; stickyUserHeader: boolean; + promptNavigatorEnabled: boolean; expandedEditorToolbar: boolean; showSplitAssistantMessageActions: boolean; allowPromptingSubagentSessions: boolean; @@ -780,6 +782,8 @@ interface UIStore { setGitChangesViewMode: (mode: 'flat' | 'tree') => void; setMultiRunLauncherOpen: (open: boolean) => void; setTimelineDialogOpen: (open: boolean) => void; + setPromptNavigatorPanelOpen: (open: boolean) => void; + togglePromptNavigatorPanel: () => void; setImagePreviewOpen: (open: boolean) => void; setNativeNotificationsEnabled: (value: boolean) => void; setNotificationMode: (mode: 'always' | 'hidden-only') => void; @@ -810,6 +814,7 @@ interface UIStore { setUserMessageRenderingMode: (value: UserMessageRenderingMode) => void; setCollapsibleUserMessages: (value: boolean) => void; setStickyUserHeader: (value: boolean) => void; + setPromptNavigatorEnabled: (value: boolean) => void; setExpandedEditorToolbar: (value: boolean) => void; setShowSplitAssistantMessageActions: (value: boolean) => void; setAllowPromptingSubagentSessions: (value: boolean) => void; @@ -918,6 +923,7 @@ export const useUIStore = create()( diffWrapLines: false, gitChangesViewMode: 'flat', isTimelineDialogOpen: false, + isPromptNavigatorPanelOpen: false, isImagePreviewOpen: false, nativeNotificationsEnabled: false, notificationMode: 'hidden-only', @@ -958,6 +964,7 @@ export const useUIStore = create()( userMessageRenderingMode: 'markdown', collapsibleUserMessages: true, stickyUserHeader: false, + promptNavigatorEnabled: false, expandedEditorToolbar: false, showSplitAssistantMessageActions: false, allowPromptingSubagentSessions: false, @@ -2037,6 +2044,14 @@ export const useUIStore = create()( set({ isTimelineDialogOpen: open }); }, + setPromptNavigatorPanelOpen: (open) => { + set({ isPromptNavigatorPanelOpen: open }); + }, + + togglePromptNavigatorPanel: () => { + set((state) => ({ isPromptNavigatorPanelOpen: !state.isPromptNavigatorPanelOpen })); + }, + setImagePreviewOpen: (open) => { set({ isImagePreviewOpen: open }); }, @@ -2119,6 +2134,9 @@ export const useUIStore = create()( setStickyUserHeader: (value) => { set({ stickyUserHeader: value }); }, + setPromptNavigatorEnabled: (value) => { + set({ promptNavigatorEnabled: value }); + }, setExpandedEditorToolbar: (value: boolean) => { set({ expandedEditorToolbar: value }); }, @@ -2369,6 +2387,7 @@ export const useUIStore = create()( userMessageRenderingMode: state.userMessageRenderingMode, collapsibleUserMessages: state.collapsibleUserMessages, stickyUserHeader: state.stickyUserHeader, + promptNavigatorEnabled: state.promptNavigatorEnabled, expandedEditorToolbar: state.expandedEditorToolbar, showSplitAssistantMessageActions: state.showSplitAssistantMessageActions, allowPromptingSubagentSessions: state.allowPromptingSubagentSessions, diff --git a/packages/web/server/lib/opencode/settings-helpers.js b/packages/web/server/lib/opencode/settings-helpers.js index 997d5b53..18c81b07 100644 --- a/packages/web/server/lib/opencode/settings-helpers.js +++ b/packages/web/server/lib/opencode/settings-helpers.js @@ -532,6 +532,9 @@ export const createSettingsHelpers = (dependencies) => { if (typeof candidate.stickyUserHeader === 'boolean') { result.stickyUserHeader = candidate.stickyUserHeader; } + if (typeof candidate.promptNavigatorEnabled === 'boolean') { + result.promptNavigatorEnabled = candidate.promptNavigatorEnabled; + } if (typeof candidate.expandedEditorToolbar === 'boolean') { result.expandedEditorToolbar = candidate.expandedEditorToolbar; }