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
This commit is contained in:
Bohdan Triapitsyn
2026-07-24 23:07:39 +03:00
parent 9ea1a2a728
commit d265a38365
20 changed files with 84 additions and 9 deletions
@@ -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<DraftPresetChipsProps> = ({ onSubmit, className }) => {
const DraftPresetChipsContent: React.FC<DraftPresetChipsProps> = ({ 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<DraftPresetChipsProps> = ({ onSubmit, cl
</DndContext>
);
};
export const DraftPresetChips: React.FC<DraftPresetChipsProps> = (props) => {
const visible = useUIStore((state) => state.draftStartersVisible);
return visible ? <DraftPresetChipsContent {...props} /> : null;
};
@@ -381,6 +381,8 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
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<OpenChamberVisualSettingsProps>
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<OpenChamberVisualSettingsProps>
/>
</SettingsSection>
)}
{(shouldShow('sessionAssist') || shouldShow('subagentReadOnlyBanner')) && (
<SettingsSection
title={t('settings.openchamber.visual.section.sessionAssistance')}
settingsItem="chat.session-assistance"
contentClassName={SETTINGS_OPTION_STACK_CLASS}
>
<SettingsSection
title={t('settings.openchamber.visual.section.sessionAssistance')}
settingsItem="chat.session-assistance"
contentClassName={SETTINGS_OPTION_STACK_CLASS}
>
<SettingsCheckboxRow
checked={draftStartersVisible}
onChange={handleDraftStartersVisibleChange}
label={t('settings.openchamber.visual.field.draftStartersVisible')}
ariaLabel={t('settings.openchamber.visual.field.draftStartersVisibleAria')}
settingsItem="chat.draft-starters-visible"
/>
{shouldShow('sessionAssist') && (
<>
<SettingsCheckboxRow
@@ -1799,8 +1812,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
settingsItem="chat.subagent-read-only-banner"
/>
)}
</SettingsSection>
)}
</SettingsSection>
{/* 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 && (
+1
View File
@@ -676,6 +676,7 @@ export interface SettingsPayload {
pwaAppName?: string;
mobileKeyboardMode?: 'native' | 'resize-content';
draftStarters?: DraftStarterRef[];
draftStartersVisible?: boolean;
draftStartersCraftGoalAdded?: boolean;
[key: string]: unknown;
+1
View File
@@ -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;
@@ -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',
@@ -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",
@@ -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',
@@ -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': 'コードブロックの行を折り返す',
@@ -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': '코드 블록 줄 바꿈',
@@ -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',
@@ -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",
@@ -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": "Переносити рядки блоків коду",
@@ -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': '代码块自动换行',
@@ -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': '程式碼區塊自動換行',
+3
View File
@@ -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');
});
+7
View File
@@ -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;
}
+6
View File
@@ -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',
+8
View File
@@ -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<UIStore>()(
expandedEditorToolbar: false,
showSplitAssistantMessageActions: false,
allowPromptingSubagentSessions: false,
draftStartersVisible: true,
isMobileSessionStatusBarCollapsed: false,
mobileSessionPanelOpen: false,
mobileSessionFilterProjectId: null,
@@ -1675,6 +1678,10 @@ export const useUIStore = create<UIStore>()(
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<UIStore>()(
expandedEditorToolbar: state.expandedEditorToolbar,
showSplitAssistantMessageActions: state.showSplitAssistantMessageActions,
allowPromptingSubagentSessions: state.allowPromptingSubagentSessions,
draftStartersVisible: state.draftStartersVisible,
isMobileSessionStatusBarCollapsed: state.isMobileSessionStatusBarCollapsed,
mobileSessionFilterProjectId: state.mobileSessionFilterProjectId,
shortcutOverrides: state.shortcutOverrides,