From d265a3836548a46d3708f1fc626959e088f381fb Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Fri, 24 Jul 2026 23:07:39 +0300 Subject: [PATCH] feat: add toggle for showing draft starters on new sessions Persists draft starter visibility across desktop and web settings Adds a new OpenChamber visual setting with localization and search support Hides the draft starter chips when the setting is off --- .../src/components/chat/DraftPresetChips.tsx | 8 +++++- .../openchamber/OpenChamberVisualSettings.tsx | 28 +++++++++++++------ packages/ui/src/lib/api/types.ts | 1 + packages/ui/src/lib/desktop.ts | 1 + .../ui/src/lib/i18n/messages/en.settings.ts | 2 ++ .../ui/src/lib/i18n/messages/es.settings.ts | 2 ++ .../ui/src/lib/i18n/messages/fr.settings.ts | 2 ++ .../ui/src/lib/i18n/messages/ja.settings.ts | 2 ++ .../ui/src/lib/i18n/messages/ko.settings.ts | 2 ++ .../ui/src/lib/i18n/messages/pl.settings.ts | 2 ++ .../src/lib/i18n/messages/pt-BR.settings.ts | 2 ++ .../ui/src/lib/i18n/messages/uk.settings.ts | 2 ++ .../src/lib/i18n/messages/zh-CN.settings.ts | 2 ++ .../src/lib/i18n/messages/zh-TW.settings.ts | 2 ++ packages/ui/src/lib/persistence.test.ts | 3 ++ packages/ui/src/lib/persistence.ts | 7 +++++ packages/ui/src/lib/settings/search.ts | 6 ++++ packages/ui/src/stores/useUIStore.ts | 8 ++++++ .../server/lib/opencode/settings-helpers.js | 3 ++ .../lib/opencode/settings-helpers.test.js | 8 ++++++ 20 files changed, 84 insertions(+), 9 deletions(-) diff --git a/packages/ui/src/components/chat/DraftPresetChips.tsx b/packages/ui/src/components/chat/DraftPresetChips.tsx index 79d4b207..d02b3665 100644 --- a/packages/ui/src/components/chat/DraftPresetChips.tsx +++ b/packages/ui/src/components/chat/DraftPresetChips.tsx @@ -32,6 +32,7 @@ import { useI18n } from '@/lib/i18n'; import { useThemeSystem } from '@/contexts/useThemeSystem'; import { useDeviceInfo } from '@/lib/device'; import { cn } from '@/lib/utils'; +import { useUIStore } from '@/stores/useUIStore'; import { useDraftStarters, type ResolvedStarter, @@ -254,7 +255,7 @@ const AddStarterPicker: React.FC<{ * Reorder is constrained to within a chip's own group; cross-group hovers are * ignored. */ -export const DraftPresetChips: React.FC = ({ onSubmit, className }) => { +const DraftPresetChipsContent: React.FC = ({ onSubmit, className }) => { const { global, project, pinnable, ensureLoaded, addStarter, removeStarter, reorder } = useDraftStarters(); const { isMobile } = useDeviceInfo(); const [isDragging, setIsDragging] = React.useState(false); @@ -331,3 +332,8 @@ export const DraftPresetChips: React.FC = ({ onSubmit, cl ); }; + +export const DraftPresetChips: React.FC = (props) => { + const visible = useUIStore((state) => state.draftStartersVisible); + return visible ? : null; +}; diff --git a/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx b/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx index 604dcc04..0485de00 100644 --- a/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx +++ b/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx @@ -381,6 +381,8 @@ export const OpenChamberVisualSettings: React.FC const setShowSplitAssistantMessageActions = useUIStore(state => state.setShowSplitAssistantMessageActions); const allowPromptingSubagentSessions = useUIStore(state => state.allowPromptingSubagentSessions); const setAllowPromptingSubagentSessions = useUIStore(state => state.setAllowPromptingSubagentSessions); + const draftStartersVisible = useUIStore(state => state.draftStartersVisible); + const setDraftStartersVisible = useUIStore(state => state.setDraftStartersVisible); const messageStreamTransport = useConfigStore((state) => state.settingsMessageStreamTransport); const setMessageStreamTransport = useConfigStore((state) => state.setSettingsMessageStreamTransport); const effectiveMessageStreamTransport = messageStreamTransport; @@ -500,6 +502,11 @@ export const OpenChamberVisualSettings: React.FC void updateDesktopSettings({ promptNavigatorEnabled: enabled }); }, [setPromptNavigatorEnabled]); + const handleDraftStartersVisibleChange = React.useCallback((enabled: boolean) => { + setDraftStartersVisible(enabled); + void updateDesktopSettings({ draftStartersVisible: enabled }); + }, [setDraftStartersVisible]); + const handleExpandedEditorToolbarChange = React.useCallback((enabled: boolean) => { setExpandedEditorToolbar(enabled); void updateDesktopSettings({ expandedEditorToolbar: enabled }); @@ -1766,12 +1773,18 @@ export const OpenChamberVisualSettings: React.FC /> )} - {(shouldShow('sessionAssist') || shouldShow('subagentReadOnlyBanner')) && ( - + + {shouldShow('sessionAssist') && ( <> settingsItem="chat.subagent-read-only-banner" /> )} - - )} + {/* The goal loop runs in the web server — VS Code only renders goal state, so the settings section is hidden there too. */} {shouldShow('sessionGoal') && !isVSCode && ( diff --git a/packages/ui/src/lib/api/types.ts b/packages/ui/src/lib/api/types.ts index c35bd84e..261aeb30 100644 --- a/packages/ui/src/lib/api/types.ts +++ b/packages/ui/src/lib/api/types.ts @@ -676,6 +676,7 @@ export interface SettingsPayload { pwaAppName?: string; mobileKeyboardMode?: 'native' | 'resize-content'; draftStarters?: DraftStarterRef[]; + draftStartersVisible?: boolean; draftStartersCraftGoalAdded?: boolean; [key: string]: unknown; diff --git a/packages/ui/src/lib/desktop.ts b/packages/ui/src/lib/desktop.ts index 26c60ddd..c1f58a93 100644 --- a/packages/ui/src/lib/desktop.ts +++ b/packages/ui/src/lib/desktop.ts @@ -207,6 +207,7 @@ export type DesktopSettings = { sttLanguage?: string; // Global draft welcome starters (pinned commands/skills), persisted to settings.json draftStarters?: DraftStarterRef[]; + draftStartersVisible?: boolean; // One-time migration marker: Craft a Goal was offered in the starter row. draftStartersCraftGoalAdded?: boolean; draftStartersScheduleTaskAdded?: boolean; diff --git a/packages/ui/src/lib/i18n/messages/en.settings.ts b/packages/ui/src/lib/i18n/messages/en.settings.ts index e3c28869..da2aed99 100644 --- a/packages/ui/src/lib/i18n/messages/en.settings.ts +++ b/packages/ui/src/lib/i18n/messages/en.settings.ts @@ -1884,6 +1884,8 @@ export const settingsDict = { 'settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip': 'Show Copy Answer, Save as image, and Read aloud on assistant text blocks that appear before later tool calls in the same response.', 'settings.openchamber.visual.field.allowPromptingSubagentSessionsAria': 'Allow prompting subagent sessions', 'settings.openchamber.visual.field.allowPromptingSubagentSessions': 'Allow Prompting Subagent Sessions', + 'settings.openchamber.visual.field.draftStartersVisible': 'Show Starters on New Session Screen', + 'settings.openchamber.visual.field.draftStartersVisibleAria': 'Show starters on the new session screen', 'settings.openchamber.visual.field.showToolFileIconsAria': 'Show tool file icons', 'settings.openchamber.visual.field.showToolFileIcons': 'Show Tool File Icons', 'settings.openchamber.visual.field.showTurnChangedFilesAria': 'Show changed files for completed turns', diff --git a/packages/ui/src/lib/i18n/messages/es.settings.ts b/packages/ui/src/lib/i18n/messages/es.settings.ts index e978b1f5..13421f11 100644 --- a/packages/ui/src/lib/i18n/messages/es.settings.ts +++ b/packages/ui/src/lib/i18n/messages/es.settings.ts @@ -1849,6 +1849,8 @@ export const settingsDict = { "settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip": "Muestra Copiar respuesta, Guardar como imagen y Leer en voz alta en bloques de texto del asistente que aparecen antes de llamadas a herramientas posteriores dentro de la misma respuesta.", "settings.openchamber.visual.field.allowPromptingSubagentSessionsAria": "Permitir enviar solicitudes a sesiones de subagentes", "settings.openchamber.visual.field.allowPromptingSubagentSessions": "Permitir solicitudes en sesiones de subagentes", + "settings.openchamber.visual.field.draftStartersVisible": "Mostrar iniciadores en la pantalla de nueva sesión", + "settings.openchamber.visual.field.draftStartersVisibleAria": "Mostrar iniciadores en la pantalla de nueva sesión", "settings.openchamber.visual.field.showToolFileIconsAria": "Mostrar iconos de archivos de herramientas", "settings.openchamber.visual.field.codeBlockLineWrapAria": "Ajustar líneas de bloques de código", "settings.openchamber.visual.field.codeBlockLineWrap": "Ajustar líneas de bloques de código", diff --git a/packages/ui/src/lib/i18n/messages/fr.settings.ts b/packages/ui/src/lib/i18n/messages/fr.settings.ts index e8c3be78..e3919233 100644 --- a/packages/ui/src/lib/i18n/messages/fr.settings.ts +++ b/packages/ui/src/lib/i18n/messages/fr.settings.ts @@ -1754,6 +1754,8 @@ export const settingsDict = { 'settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip': 'Afficher Copier la réponse, Enregistrer en image et Lire à haute voix sur les blocs de texte de l\'assistant qui apparaissent avant les appels d\'outil suivants dans la même réponse.', 'settings.openchamber.visual.field.allowPromptingSubagentSessionsAria': 'Autoriser les requêtes dans les sessions de sous-agents', 'settings.openchamber.visual.field.allowPromptingSubagentSessions': 'Autoriser les requêtes dans les sessions de sous-agents', + 'settings.openchamber.visual.field.draftStartersVisible': 'Afficher les raccourcis sur l’écran de nouvelle session', + 'settings.openchamber.visual.field.draftStartersVisibleAria': 'Afficher les raccourcis sur l’écran de nouvelle session', 'settings.openchamber.visual.field.showToolFileIconsAria': 'Afficher les icônes des fichiers d\'outils', 'settings.openchamber.visual.field.codeBlockLineWrapAria': 'Renvoyer à la ligne les blocs de code', 'settings.openchamber.visual.field.codeBlockLineWrap': 'Renvoyer à la ligne les blocs de code', diff --git a/packages/ui/src/lib/i18n/messages/ja.settings.ts b/packages/ui/src/lib/i18n/messages/ja.settings.ts index e336900e..d8cb2267 100644 --- a/packages/ui/src/lib/i18n/messages/ja.settings.ts +++ b/packages/ui/src/lib/i18n/messages/ja.settings.ts @@ -1882,6 +1882,8 @@ export const settingsDict = { 'settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip': '同じ応答内の後のツール呼び出しの前に表示されるアシスタントテキストブロックに、回答コピー、画像保存、読み上げを表示します。', 'settings.openchamber.visual.field.allowPromptingSubagentSessionsAria': 'サブエージェントセッションへのプロンプトを許可', 'settings.openchamber.visual.field.allowPromptingSubagentSessions': 'サブエージェントセッションへのプロンプトを許可', + 'settings.openchamber.visual.field.draftStartersVisible': '新規セッション画面にスターターを表示', + 'settings.openchamber.visual.field.draftStartersVisibleAria': '新規セッション画面にスターターを表示', 'settings.openchamber.visual.field.showToolFileIconsAria': 'ツールファイルアイコンを表示', 'settings.openchamber.visual.field.codeBlockLineWrapAria': 'コードブロックの行を折り返す', 'settings.openchamber.visual.field.codeBlockLineWrap': 'コードブロックの行を折り返す', diff --git a/packages/ui/src/lib/i18n/messages/ko.settings.ts b/packages/ui/src/lib/i18n/messages/ko.settings.ts index 65650d9a..5eb403a3 100644 --- a/packages/ui/src/lib/i18n/messages/ko.settings.ts +++ b/packages/ui/src/lib/i18n/messages/ko.settings.ts @@ -1849,6 +1849,8 @@ export const settingsDict = { 'settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip': '같은 응답에서 이후 도구 호출 앞에 나타나는 어시스턴트 텍스트 블록에 답변 복사, 이미지로 저장, 소리 내어 읽기 작업을 표시합니다.', 'settings.openchamber.visual.field.allowPromptingSubagentSessionsAria': '하위 에이전트 세션에 프롬프트 허용', 'settings.openchamber.visual.field.allowPromptingSubagentSessions': '하위 에이전트 세션에 프롬프트 허용', + 'settings.openchamber.visual.field.draftStartersVisible': '새 세션 화면에 시작 항목 표시', + 'settings.openchamber.visual.field.draftStartersVisibleAria': '새 세션 화면에 시작 항목 표시', 'settings.openchamber.visual.field.showToolFileIconsAria': '도구 파일 아이콘 표시', 'settings.openchamber.visual.field.codeBlockLineWrapAria': '코드 블록 줄 바꿈', 'settings.openchamber.visual.field.codeBlockLineWrap': '코드 블록 줄 바꿈', diff --git a/packages/ui/src/lib/i18n/messages/pl.settings.ts b/packages/ui/src/lib/i18n/messages/pl.settings.ts index c1fbb162..66734daa 100644 --- a/packages/ui/src/lib/i18n/messages/pl.settings.ts +++ b/packages/ui/src/lib/i18n/messages/pl.settings.ts @@ -1067,6 +1067,8 @@ export const settingsDict = { 'settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip': 'Pokaż Kopiuj odpowiedź, Zapisz jako obraz i Odczytaj na głos w blokach tekstu asystenta, które pojawiają się przed późniejszymi wywołaniami narzędzi w tej samej odpowiedzi.', 'settings.openchamber.visual.field.allowPromptingSubagentSessionsAria': 'Zezwalaj na wysyłanie promptów do sesji podagentów', 'settings.openchamber.visual.field.allowPromptingSubagentSessions': 'Zezwalaj na prompty w sesjach podagentów', + 'settings.openchamber.visual.field.draftStartersVisible': 'Pokazuj startery na ekranie nowej sesji', + 'settings.openchamber.visual.field.draftStartersVisibleAria': 'Pokazuj startery na ekranie nowej sesji', 'settings.openchamber.visual.field.showToolFileIcons': 'Pokaż ikony plików narzędzi', 'settings.openchamber.visual.field.showToolFileIconsAria': 'Pokaż ikony plików narzędzi', 'settings.openchamber.visual.field.codeBlockLineWrapAria': 'Zawijaj wiersze bloków kodu', 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 0b140b11..acf36555 100644 --- a/packages/ui/src/lib/i18n/messages/pt-BR.settings.ts +++ b/packages/ui/src/lib/i18n/messages/pt-BR.settings.ts @@ -1849,6 +1849,8 @@ export const settingsDict = { "settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip": "Mostra Copiar resposta, Salvar como imagem e Ler em voz alta em blocos de texto do assistente que aparecem antes de chamadas de ferramenta posteriores na mesma resposta.", "settings.openchamber.visual.field.allowPromptingSubagentSessionsAria": "Permitir prompts em sessões de subagentes", "settings.openchamber.visual.field.allowPromptingSubagentSessions": "Permitir prompts em sessões de subagentes", + "settings.openchamber.visual.field.draftStartersVisible": "Mostrar atalhos na tela de nova sessão", + "settings.openchamber.visual.field.draftStartersVisibleAria": "Mostrar atalhos na tela de nova sessão", "settings.openchamber.visual.field.showToolFileIconsAria": "Mostrar ícones de arquivos de ferramentas", "settings.openchamber.visual.field.codeBlockLineWrapAria": "Quebrar linhas de blocos de código", "settings.openchamber.visual.field.codeBlockLineWrap": "Quebrar linhas de blocos de código", diff --git a/packages/ui/src/lib/i18n/messages/uk.settings.ts b/packages/ui/src/lib/i18n/messages/uk.settings.ts index eeabf3a9..0671ae01 100644 --- a/packages/ui/src/lib/i18n/messages/uk.settings.ts +++ b/packages/ui/src/lib/i18n/messages/uk.settings.ts @@ -1849,6 +1849,8 @@ export const settingsDict = { "settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip": "Показує «Скопіювати відповідь», «Зберегти як зображення» та озвучення для текстових блоків асистента, які з’являються перед пізнішими викликами інструментів у тій самій відповіді.", "settings.openchamber.visual.field.allowPromptingSubagentSessionsAria": "Дозволити надсилати запити в сесії субагентів", "settings.openchamber.visual.field.allowPromptingSubagentSessions": "Дозволити запити в сесіях субагентів", + "settings.openchamber.visual.field.draftStartersVisible": "Показувати стартери на екрані нової сесії", + "settings.openchamber.visual.field.draftStartersVisibleAria": "Показувати стартери на екрані нової сесії", "settings.openchamber.visual.field.showToolFileIconsAria": "Показати значки файлів інструментів", "settings.openchamber.visual.field.codeBlockLineWrapAria": "Переносити рядки блоків коду", "settings.openchamber.visual.field.codeBlockLineWrap": "Переносити рядки блоків коду", 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 665e6d37..d31fc788 100644 --- a/packages/ui/src/lib/i18n/messages/zh-CN.settings.ts +++ b/packages/ui/src/lib/i18n/messages/zh-CN.settings.ts @@ -1849,6 +1849,8 @@ export const settingsDict = { 'settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip': '在同一条回复中,如果助手文本块后面还有工具调用,则显示“复制回答”“保存为图片”和“朗读”操作。', 'settings.openchamber.visual.field.allowPromptingSubagentSessionsAria': '允许向子智能体会话发送提示', 'settings.openchamber.visual.field.allowPromptingSubagentSessions': '允许向子智能体会话发送提示', + 'settings.openchamber.visual.field.draftStartersVisible': '在新会话页面显示快捷入口', + 'settings.openchamber.visual.field.draftStartersVisibleAria': '在新会话页面显示快捷入口', 'settings.openchamber.visual.field.showToolFileIconsAria': '显示工具文件图标', 'settings.openchamber.visual.field.codeBlockLineWrapAria': '代码块自动换行', 'settings.openchamber.visual.field.codeBlockLineWrap': '代码块自动换行', 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 137dd820..9d85784f 100644 --- a/packages/ui/src/lib/i18n/messages/zh-TW.settings.ts +++ b/packages/ui/src/lib/i18n/messages/zh-TW.settings.ts @@ -1755,6 +1755,8 @@ 'settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip': '在同一則回覆中,如果助理文字區塊後方還有工具呼叫,則顯示「複製回答」「儲存為圖片」和「朗讀」操作。', 'settings.openchamber.visual.field.allowPromptingSubagentSessionsAria': '允許向子 Agent 工作階段傳送提示', 'settings.openchamber.visual.field.allowPromptingSubagentSessions': '允許向子 Agent 工作階段傳送提示', + 'settings.openchamber.visual.field.draftStartersVisible': '在新工作階段畫面顯示快速啟動項目', + 'settings.openchamber.visual.field.draftStartersVisibleAria': '在新工作階段畫面顯示快速啟動項目', 'settings.openchamber.visual.field.showToolFileIconsAria': '顯示工具檔案圖示', 'settings.openchamber.visual.field.codeBlockLineWrapAria': '程式碼區塊自動換行', 'settings.openchamber.visual.field.codeBlockLineWrap': '程式碼區塊自動換行', diff --git a/packages/ui/src/lib/persistence.test.ts b/packages/ui/src/lib/persistence.test.ts index 36ea64b8..daa09e3f 100644 --- a/packages/ui/src/lib/persistence.test.ts +++ b/packages/ui/src/lib/persistence.test.ts @@ -360,6 +360,7 @@ describe('updateDesktopSettings', () => { favoriteModels: [{ providerID: 'anthropic', modelID: 'claude-sonnet-4' }], followUpBehavior: 'steer', draftStarters: [{ type: 'command', name: 'runtime-a' }], + draftStartersVisible: false, draftStartersCraftGoalAdded: true, draftStartersScheduleTaskAdded: true, }, source: 'web', @@ -370,6 +371,7 @@ describe('updateDesktopSettings', () => { expect(useUIStore.getState().terminalShell).toBe('fish'); expect(useUIStore.getState().favoriteModels).toHaveLength(1); expect(useUIStore.getState().globalDraftStarters).toEqual([{ type: 'command', name: 'runtime-a' }]); + expect(useUIStore.getState().draftStartersVisible).toBe(false); expect(useMessageQueueStore.getState().followUpBehavior).toBe('steer'); switchRuntimeEndpoint({ apiBaseUrl: 'https://preferences-b.example', runtimeKey: 'preferences-b' }); @@ -383,6 +385,7 @@ describe('updateDesktopSettings', () => { expect(useUIStore.getState().terminalShell).toBe('auto'); expect(useUIStore.getState().favoriteModels).toEqual([]); expect(useUIStore.getState().globalDraftStarters).toBeNull(); + expect(useUIStore.getState().draftStartersVisible).toBe(true); expect(useMessageQueueStore.getState().followUpBehavior).toBe('queue'); }); diff --git a/packages/ui/src/lib/persistence.ts b/packages/ui/src/lib/persistence.ts index e86aca7d..38bf9af9 100644 --- a/packages/ui/src/lib/persistence.ts +++ b/packages/ui/src/lib/persistence.ts @@ -568,6 +568,7 @@ const materializeAuthoritativeUiSettings = (settings: DesktopSettings): DesktopS expandedEditorToolbar: defaults.expandedEditorToolbar, wideChatLayoutEnabled: defaults.wideChatLayoutEnabled, showSplitAssistantMessageActions: defaults.showSplitAssistantMessageActions, + draftStartersVisible: defaults.draftStartersVisible, reportUsage: defaults.reportUsage, fontSize: defaults.fontSize, terminalFontSize: defaults.terminalFontSize, @@ -839,6 +840,9 @@ const applyDesktopUiPreferences = (settings: DesktopSettings) => { settings.draftStartersScheduleTaskAdded = true; } } + if (typeof settings.draftStartersVisible === 'boolean' && settings.draftStartersVisible !== store.draftStartersVisible) { + store.setDraftStartersVisible(settings.draftStartersVisible); + } if (typeof settings.terminalFontSize === 'number' && Number.isFinite(settings.terminalFontSize) && settings.terminalFontSize !== store.terminalFontSize) { store.setTerminalFontSize(settings.terminalFontSize); } @@ -1043,6 +1047,9 @@ const sanitizeWebSettings = (payload: unknown): DesktopSettings | null => { if (Array.isArray(candidate.draftStarters)) { result.draftStarters = sanitizeStarterRefs(candidate.draftStarters); } + if (typeof candidate.draftStartersVisible === 'boolean') { + result.draftStartersVisible = candidate.draftStartersVisible; + } if (typeof candidate.draftStartersCraftGoalAdded === 'boolean') { result.draftStartersCraftGoalAdded = candidate.draftStartersCraftGoalAdded; } diff --git a/packages/ui/src/lib/settings/search.ts b/packages/ui/src/lib/settings/search.ts index ea406f4a..d8b74188 100644 --- a/packages/ui/src/lib/settings/search.ts +++ b/packages/ui/src/lib/settings/search.ts @@ -275,6 +275,12 @@ const SETTINGS_SEARCH_ITEMS: readonly SettingsSearchItem[] = [ descriptionKey: 'settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip', keywords: ['copy', 'save image', 'read aloud'], }, + { + id: 'chat.draft-starters-visible', + page: 'chat', + titleKey: 'settings.openchamber.visual.field.draftStartersVisible', + keywords: ['starter', 'starters', 'new session', 'welcome', 'suggestions'], + }, { id: 'chat.subagent-read-only-banner', page: 'chat', diff --git a/packages/ui/src/stores/useUIStore.ts b/packages/ui/src/stores/useUIStore.ts index 6d1d25d7..3c650a64 100644 --- a/packages/ui/src/stores/useUIStore.ts +++ b/packages/ui/src/stores/useUIStore.ts @@ -591,6 +591,7 @@ interface UIStore { fontSize: number; // Global draft welcome starters; null = unset (use the default built-in set). globalDraftStarters: DraftStarterRef[] | null; + draftStartersVisible: boolean; terminalFontSize: number; terminalShell: TerminalShell; terminalLoginShells: TerminalShell[]; @@ -751,6 +752,7 @@ interface UIStore { setMessageLimit: (value: number) => void; setFontSize: (size: number) => void; setGlobalDraftStarters: (refs: DraftStarterRef[]) => void; + setDraftStartersVisible: (value: boolean) => void; setTerminalFontSize: (size: number) => void; setTerminalShell: (shell: TerminalShell) => void; setTerminalLoginShells: (shells: TerminalShell[]) => void; @@ -978,6 +980,7 @@ export const useUIStore = create()( expandedEditorToolbar: false, showSplitAssistantMessageActions: false, allowPromptingSubagentSessions: false, + draftStartersVisible: true, isMobileSessionStatusBarCollapsed: false, mobileSessionPanelOpen: false, mobileSessionFilterProjectId: null, @@ -1675,6 +1678,10 @@ export const useUIStore = create()( set({ globalDraftStarters: refs }); }, + setDraftStartersVisible: (value) => { + set({ draftStartersVisible: value }); + }, + setTerminalFontSize: (size) => { const rounded = Math.round(size); const clamped = Math.max(9, Math.min(52, rounded)); @@ -2420,6 +2427,7 @@ export const useUIStore = create()( expandedEditorToolbar: state.expandedEditorToolbar, showSplitAssistantMessageActions: state.showSplitAssistantMessageActions, allowPromptingSubagentSessions: state.allowPromptingSubagentSessions, + draftStartersVisible: state.draftStartersVisible, isMobileSessionStatusBarCollapsed: state.isMobileSessionStatusBarCollapsed, mobileSessionFilterProjectId: state.mobileSessionFilterProjectId, shortcutOverrides: state.shortcutOverrides, diff --git a/packages/web/server/lib/opencode/settings-helpers.js b/packages/web/server/lib/opencode/settings-helpers.js index 625d38c6..a693c6e8 100644 --- a/packages/web/server/lib/opencode/settings-helpers.js +++ b/packages/web/server/lib/opencode/settings-helpers.js @@ -248,6 +248,9 @@ export const createSettingsHelpers = (dependencies) => { } result.draftStarters = starters; } + if (typeof candidate.draftStartersVisible === 'boolean') { + result.draftStartersVisible = candidate.draftStartersVisible; + } if (typeof candidate.draftStartersCraftGoalAdded === 'boolean') { result.draftStartersCraftGoalAdded = candidate.draftStartersCraftGoalAdded; } diff --git a/packages/web/server/lib/opencode/settings-helpers.test.js b/packages/web/server/lib/opencode/settings-helpers.test.js index 3336c902..edb5afca 100644 --- a/packages/web/server/lib/opencode/settings-helpers.test.js +++ b/packages/web/server/lib/opencode/settings-helpers.test.js @@ -58,6 +58,14 @@ const createTestHelpersWithRealSanitizers = () => { }; describe('settings helpers', () => { + it('accepts only booleans for draft starter visibility', () => { + const helpers = createTestHelpers(); + + expect(helpers.sanitizeSettingsUpdate({ draftStartersVisible: true })).toEqual({ draftStartersVisible: true }); + expect(helpers.sanitizeSettingsUpdate({ draftStartersVisible: false })).toEqual({ draftStartersVisible: false }); + expect(helpers.sanitizeSettingsUpdate({ draftStartersVisible: 'false' })).toEqual({}); + }); + it('accepts messageStreamTransport as a persisted shared setting', () => { const helpers = createTestHelpers();