Merge remote-tracking branch 'origin/main' into port-2884

# Conflicts:
#	CHANGELOG.md
This commit is contained in:
Bohdan Triapitsyn
2026-08-29 00:49:02 +03:00
140 changed files with 4633 additions and 428 deletions
@@ -450,7 +450,7 @@ export const AgentsPage: React.FC = () => {
inputMode="decimal"
placeholder="—"
emptyLabel="—"
className="w-16"
className="w-20"
/>
{temperature !== undefined && (
<Button
@@ -488,7 +488,7 @@ export const AgentsPage: React.FC = () => {
inputMode="decimal"
placeholder="—"
emptyLabel="—"
className="w-16"
className="w-20"
/>
{topP !== undefined && (
<Button
@@ -3,6 +3,7 @@ import { Button } from '@/components/ui/button';
import { Textarea } from '@/components/ui/textarea';
import { toast } from '@/components/ui';
import { useI18n, type I18nKey } from '@/lib/i18n';
import { cn } from '@/lib/utils';
import { reportSettingsSaveState } from '@/lib/persistence';
import { useIsVSCodeRuntime } from '@/hooks/useRuntimeAPIs';
import {
@@ -375,7 +376,7 @@ export const BehaviorPage: React.FC = () => {
onValueChange={(value) => setResponseStylePreset(value)}
disabled={isLoading || !responseStyleEnabled}
>
<SelectTrigger size={SETTINGS_SELECT_SIZE} className={SETTINGS_SELECT_ROW_TRIGGER_CLASS}>
<SelectTrigger size={SETTINGS_SELECT_SIZE} className={cn(SETTINGS_SELECT_ROW_TRIGGER_CLASS, 'max-w-72')}>
<SelectValue>
{(value) => {
if (value === 'custom') return t('settings.behavior.page.responseStyle.option.custom');
@@ -212,6 +212,7 @@ const ChatSectionContent: React.FC = () => {
'followUpBehavior',
'persistDraft',
'inputSpellcheck',
'largeTextPaste',
]}
/>
);
@@ -3,7 +3,7 @@ import { runtimeFetch } from '@/lib/runtime-fetch';
import { useThemeSystem } from '@/contexts/useThemeSystem';
import type { ThemeMode } from '@/types/theme';
import { useUIStore } from '@/stores/useUIStore';
import { useUIStore, type LargeTextPasteBehavior } from '@/stores/useUIStore';
import { useMessageQueueStore, type FollowUpBehavior } from '@/stores/messageQueueStore';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
@@ -263,11 +263,26 @@ const FOLLOW_UP_BEHAVIOR_OPTIONS: Option<FollowUpBehavior>[] = [
},
];
const LARGE_TEXT_PASTE_BEHAVIOR_OPTIONS: Option<LargeTextPasteBehavior>[] = [
{
id: 'ask',
labelKey: 'settings.openchamber.visual.option.largeTextPaste.ask.label',
},
{
id: 'attach',
labelKey: 'settings.openchamber.visual.option.largeTextPaste.attach.label',
},
{
id: 'inline',
labelKey: 'settings.openchamber.visual.option.largeTextPaste.inline.label',
},
];
const normalizeUserMessageRenderingMode = (mode: unknown): 'markdown' | 'plain' => {
return mode === 'markdown' ? 'markdown' : 'plain';
};
type VisibleSetting = 'sessionAssist' | 'sessionGoal' | 'theme' | 'windowControlsPosition' | 'pwaInstallName' | 'pwaOrientation' | 'mobileKeyboardMode' | 'timeFormat' | 'weekStart' | 'fontSize' | 'terminalFontSize' | 'terminalShell' | 'terminalLoginShell' | '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' | 'autoSaveEnabled' | 'sessionTabs';
type VisibleSetting = 'sessionAssist' | 'sessionGoal' | 'theme' | 'windowControlsPosition' | 'pwaInstallName' | 'pwaOrientation' | 'mobileKeyboardMode' | 'timeFormat' | 'weekStart' | 'fontSize' | 'terminalFontSize' | 'terminalShell' | 'terminalLoginShell' | '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' | 'largeTextPaste' | 'reportUsage' | 'autoSaveEnabled' | 'sessionTabs';
const WINDOW_CONTROLS_POSITION_OPTIONS: Array<{ id: DesktopWindowControlsPosition; labelKey: string }> = [
{ id: 'left', labelKey: 'settings.openchamber.desktopNetwork.option.windowControlsLeft' },
@@ -362,6 +377,8 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
const setPersistChatDraft = useUIStore(state => state.setPersistChatDraft);
const inputSpellcheckEnabled = useUIStore(state => state.inputSpellcheckEnabled);
const setInputSpellcheckEnabled = useUIStore(state => state.setInputSpellcheckEnabled);
const largeTextPasteBehavior = useUIStore(state => state.largeTextPasteBehavior);
const setLargeTextPasteBehavior = useUIStore(state => state.setLargeTextPasteBehavior);
const showToolFileIcons = useUIStore(state => state.showToolFileIcons);
const setShowToolFileIcons = useUIStore(state => state.setShowToolFileIcons);
const showTurnChangedFiles = useUIStore(state => state.showTurnChangedFiles);
@@ -639,6 +656,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|| shouldShow('reasoning')
|| shouldShow('followUpBehavior')
|| shouldShow('persistDraft')
|| shouldShow('largeTextPaste')
|| shouldShow('showToolFileIcons')
|| shouldShow('expandedTools')
|| (!isMobile && shouldShow('inputSpellcheck'));
@@ -661,6 +679,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|| shouldShow('dotfiles')
|| shouldShow('fileViewerPreview')
|| shouldShow('persistDraft')
|| shouldShow('largeTextPaste')
|| shouldShow('showToolFileIcons')
|| shouldShow('showTurnChangedFiles')
|| (!isMobile && shouldShow('inputSpellcheck'))
@@ -1198,7 +1217,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
controlClassName="w-full"
>
<Select value={uiFont} onValueChange={(value) => setUiFont(value as UiFontOption)}>
<SelectTrigger aria-label={t('settings.openchamber.visual.field.selectInterfaceFontAria')} size={SETTINGS_SELECT_SIZE} className={SETTINGS_SELECT_TRIGGER_CLASS}>
<SelectTrigger aria-label={t('settings.openchamber.visual.field.selectInterfaceFontAria')} size={SETTINGS_SELECT_SIZE} className={cn(SETTINGS_SELECT_TRIGGER_CLASS, 'max-w-full')}>
<SelectValue>{UI_FONT_OPTIONS.find((option) => option.id === uiFont)?.label}</SelectValue>
</SelectTrigger>
<SelectContent>
@@ -1228,7 +1247,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
controlClassName="w-full"
>
<Select value={monoFont} onValueChange={(value) => setMonoFont(value as MonoFontOption)}>
<SelectTrigger aria-label={t('settings.openchamber.visual.field.selectCodeFontAria')} size={SETTINGS_SELECT_SIZE} className={SETTINGS_SELECT_TRIGGER_CLASS}>
<SelectTrigger aria-label={t('settings.openchamber.visual.field.selectCodeFontAria')} size={SETTINGS_SELECT_SIZE} className={cn(SETTINGS_SELECT_TRIGGER_CLASS, 'max-w-full')}>
<SelectValue>{CODE_FONT_OPTIONS.find((option) => option.id === monoFont)?.label}</SelectValue>
</SelectTrigger>
<SelectContent>
@@ -1269,6 +1288,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
min={50}
max={200}
step={5}
className="w-20"
aria-label={t('settings.openchamber.visual.field.fontSizePercentageAria')}
/>
<span className={SETTINGS_NUMBER_UNIT_CLASS}>%</span>
@@ -1299,6 +1319,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
min={9}
max={52}
step={1}
className="w-20"
/>
<span className={SETTINGS_NUMBER_UNIT_CLASS}>px</span>
<Button size="sm"
@@ -1328,6 +1349,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
min={9}
max={32}
step={1}
className="w-20"
/>
<span className={SETTINGS_NUMBER_UNIT_CLASS}>px</span>
<Button size="sm"
@@ -1362,6 +1384,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
min={50}
max={200}
step={5}
className="w-20"
/>
<span className={SETTINGS_NUMBER_UNIT_CLASS}>%</span>
<Button size="sm"
@@ -1392,6 +1415,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
min={0}
max={100}
step={5}
className="w-20"
/>
<span className={SETTINGS_NUMBER_UNIT_CLASS}>px</span>
<Button size="sm"
@@ -1976,7 +2000,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
</SettingsSection>
)}
{(shouldShow('persistDraft') || (!isMobile && shouldShow('inputSpellcheck'))) && (
{(shouldShow('persistDraft') || shouldShow('largeTextPaste') || (!isMobile && shouldShow('inputSpellcheck'))) && (
<SettingsSection
title={t('settings.openchamber.visual.section.composer')}
settingsItem="chat.composer"
@@ -2001,6 +2025,26 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
settingsItem="chat.spellcheck"
/>
)}
{shouldShow('largeTextPaste') && (
<SettingsControlGroup
title={t('settings.openchamber.visual.field.largeTextPaste')}
info={t('settings.openchamber.visual.field.largeTextPasteHint')}
settingsItem="chat.large-text-paste"
>
<SettingsRadioGroup aria-label={t('settings.openchamber.visual.field.largeTextPasteAria')}>
{LARGE_TEXT_PASTE_BEHAVIOR_OPTIONS.map((option) => (
<SettingsRadioOption
key={option.id}
selected={largeTextPasteBehavior === option.id}
onSelect={() => setLargeTextPasteBehavior(option.id)}
label={tUnsafe(option.labelKey)}
ariaLabel={t('settings.openchamber.visual.field.largeTextPasteOptionAria', { option: tUnsafe(option.labelKey) })}
/>
))}
</SettingsRadioGroup>
</SettingsControlGroup>
)}
</SettingsSection>
)}
</>
@@ -219,7 +219,7 @@ export const PasskeySettings: React.FC = () => {
{passkeys.map((passkey) => (
<SettingsFieldRow
key={passkey.id}
label={<span className="truncate">{passkey.label}</span>}
label={<span title={passkey.label}>{passkey.label}</span>}
alignEnd={false}
controlClassName="justify-between sm:flex-1"
>
@@ -87,7 +87,7 @@ export const SessionRetentionSettings: React.FC = () => {
max={MAX_DAYS}
step={1}
aria-label={t('settings.openchamber.sessionRetention.field.retentionPeriodAria')}
className="w-20 tabular-nums"
className="w-24 tabular-nums"
/>
<span className="typography-ui-label text-muted-foreground">{t('settings.openchamber.sessionRetention.field.days')}</span>
<Button
@@ -1051,13 +1051,13 @@ export const VoiceSettings: React.FC = () => {
{/* Speech Rate */}
<SettingsFieldRow label={t('settings.voice.page.field.speechRate')}>
{!isMobile && <input type="range" min={0.5} max={2} step={0.1} value={speechRate} onChange={(e) => setSpeechRate(Number(e.target.value))} className={sliderClass} />}
<NumberInput value={speechRate} onValueChange={setSpeechRate} min={0.5} max={2} step={0.1} className="w-16 tabular-nums" />
<NumberInput value={speechRate} onValueChange={setSpeechRate} min={0.5} max={2} step={0.1} className="w-20 tabular-nums" />
</SettingsFieldRow>
{/* Speech Pitch */}
<SettingsFieldRow label={t('settings.voice.page.field.speechPitch')}>
{!isMobile && <input type="range" min={0.5} max={2} step={0.1} value={speechPitch} onChange={(e) => setSpeechPitch(Number(e.target.value))} className={sliderClass} />}
<NumberInput value={speechPitch} onValueChange={setSpeechPitch} min={0.5} max={2} step={0.1} className="w-16 tabular-nums" />
<NumberInput value={speechPitch} onValueChange={setSpeechPitch} min={0.5} max={2} step={0.1} className="w-20 tabular-nums" />
</SettingsFieldRow>
{/* Speech Volume */}
@@ -2322,7 +2322,7 @@ export const RemoteInstancesPage: React.FC = () => {
min={5}
max={240}
step={1}
className="w-16 tabular-nums"
className="w-20 tabular-nums"
value={draft.connectionTimeoutSec}
onValueChange={(next) => {
updateDraft((current) => ({
@@ -2350,7 +2350,7 @@ export const RemoteInstancesPage: React.FC = () => {
min={1}
max={65535}
step={1}
className="w-20 tabular-nums"
className="w-32 tabular-nums"
value={draft.remoteOpenchamber.preferredPort}
onValueChange={(next) => {
updateDraft((current) => ({
@@ -2517,7 +2517,7 @@ export const RemoteInstancesPage: React.FC = () => {
min={1}
max={65535}
step={1}
className="w-20 tabular-nums"
className="w-32 tabular-nums"
value={draft.localForward.preferredLocalPort}
onValueChange={(next) => {
updateDraft((current) => ({
@@ -2775,7 +2775,7 @@ export const RemoteInstancesPage: React.FC = () => {
min={1}
max={65535}
step={1}
className="w-16 tabular-nums"
className="w-32 tabular-nums"
value={forward.localPort}
onValueChange={(next) => {
updateForward((item) => ({
@@ -2817,7 +2817,7 @@ export const RemoteInstancesPage: React.FC = () => {
min={1}
max={65535}
step={1}
className="w-16 tabular-nums"
className="w-32 tabular-nums"
value={forward.remotePort}
onValueChange={(next) => {
updateForward((item) => ({
@@ -310,8 +310,8 @@ export const SettingsFieldRow: React.FC<SettingsFieldRowProps> = ({
)}
>
<div className="min-w-0 @xl:w-56 @xl:shrink-0">
<div className="flex items-center gap-1.5">
<div className={SETTINGS_FIELD_LABEL_CLASS}>{label}</div>
<div className="flex min-w-0 items-center gap-1.5">
<div className={cn('min-w-0 truncate', SETTINGS_FIELD_LABEL_CLASS)}>{label}</div>
{info != null ? <SettingsInfoHint>{info}</SettingsInfoHint> : null}
</div>
{description != null ? (