diff --git a/packages/ui/src/components/sections/openchamber/DefaultsSettings.tsx b/packages/ui/src/components/sections/openchamber/DefaultsSettings.tsx
index d7edb1bd..f4ff08f8 100644
--- a/packages/ui/src/components/sections/openchamber/DefaultsSettings.tsx
+++ b/packages/ui/src/components/sections/openchamber/DefaultsSettings.tsx
@@ -30,6 +30,8 @@ export const DefaultsSettings: React.FC = () => {
const setSettingsDefaultModel = useConfigStore((state) => state.setSettingsDefaultModel);
const setSettingsDefaultVariant = useConfigStore((state) => state.setSettingsDefaultVariant);
const setSettingsDefaultAgent = useConfigStore((state) => state.setSettingsDefaultAgent);
+ const setSettingsDefaultFileViewerPreview = useConfigStore((state) => state.setSettingsDefaultFileViewerPreview);
+ const settingsDefaultFileViewerPreview = useConfigStore((state) => state.settingsDefaultFileViewerPreview);
const showDeletionDialog = useUIStore((state) => state.showDeletionDialog);
const setShowDeletionDialog = useUIStore((state) => state.setShowDeletionDialog);
const providers = useConfigStore((state) => state.providers);
@@ -187,6 +189,12 @@ export const DefaultsSettings: React.FC = () => {
[setAgent, setSettingsDefaultAgent]
);
+ const handleToggleFileViewerPreview = React.useCallback(() => {
+ const next = !settingsDefaultFileViewerPreview;
+ setSettingsDefaultFileViewerPreview(next);
+ updateDesktopSettings({ defaultFileViewerPreview: next }).catch(console.warn);
+ }, [settingsDefaultFileViewerPreview, setSettingsDefaultFileViewerPreview]);
+
const availableVariants = React.useMemo(() => {
if (!parsedModel.providerId || !parsedModel.modelId) return [];
const provider = providers.find((p) => p.id === parsedModel.providerId);
@@ -300,6 +308,23 @@ export const DefaultsSettings: React.FC = () => {
Show Deletion Dialog
+
{
+ if (event.key === ' ' || event.key === 'Enter') {
+ event.preventDefault();
+ handleToggleFileViewerPreview();
+ }
+ }}
+ >
+
+ Open files in preview mode
+
+
);
diff --git a/packages/ui/src/components/views/FilesView.tsx b/packages/ui/src/components/views/FilesView.tsx
index dab7ca74..c3f5db25 100644
--- a/packages/ui/src/components/views/FilesView.tsx
+++ b/packages/ui/src/components/views/FilesView.tsx
@@ -71,6 +71,7 @@ import { useThemeSystem } from '@/contexts/useThemeSystem';
import { useUIStore } from '@/stores/useUIStore';
import { useFilesViewTabsStore } from '@/stores/useFilesViewTabsStore';
import { useGitStatus } from '@/stores/useGitStore';
+import { useConfigStore } from '@/stores/useConfigStore';
import { buildCodeMirrorCommentWidgets, normalizeLineRange, useInlineCommentController } from '@/components/comments';
import { opencodeClient } from '@/lib/opencode/client';
import { useDirectoryShowHidden } from '@/lib/directoryShowHidden';
@@ -729,6 +730,7 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
const pendingFileFocusPath = useUIStore((state) => state.pendingFileFocusPath);
const setPendingFileFocusPath = useUIStore((state) => state.setPendingFileFocusPath);
const shortcutOverrides = useUIStore((state) => state.shortcutOverrides);
+ const settingsDefaultFileViewerPreview = useConfigStore((state) => state.settingsDefaultFileViewerPreview);
// Global mouseup to end drag selection
React.useEffect(() => {
@@ -1864,25 +1866,37 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
}
}, [canEdit, textViewMode]);
- React.useEffect(() => {
- setTextViewMode('edit');
- setHtmlViewMode('edit');
- }, [selectedFile?.path]);
-
const MD_VIEWER_MODE_KEY = 'openchamber:files:md-viewer-mode';
+ const HTML_VIEWER_MODE_KEY = 'openchamber:files:html-viewer-mode';
React.useEffect(() => {
+ const defaultMode = settingsDefaultFileViewerPreview ? 'view' : 'edit';
+ setTextViewMode(defaultMode);
+
+ // Respect per-type localStorage preference when available,
+ // falling back to the setting-derived default when nothing is stored.
+ let mdDefault: 'preview' | 'edit' = settingsDefaultFileViewerPreview ? 'preview' : 'edit';
try {
const stored = localStorage.getItem(MD_VIEWER_MODE_KEY);
- if (stored === 'preview') {
- setMdViewMode('preview');
- } else if (stored === 'edit') {
- setMdViewMode('edit');
+ if (stored === 'preview' || stored === 'edit') {
+ mdDefault = stored;
}
} catch {
// Ignore localStorage errors
}
- }, []);
+ setMdViewMode(mdDefault);
+
+ let htmlDefault: 'preview' | 'edit' = settingsDefaultFileViewerPreview ? 'preview' : 'edit';
+ try {
+ const stored = localStorage.getItem(HTML_VIEWER_MODE_KEY);
+ if (stored === 'preview' || stored === 'edit') {
+ htmlDefault = stored;
+ }
+ } catch {
+ // Ignore localStorage errors
+ }
+ setHtmlViewMode(htmlDefault);
+ }, [selectedFile?.path, settingsDefaultFileViewerPreview]);
const saveMdViewMode = React.useCallback((mode: 'preview' | 'edit') => {
setMdViewMode(mode);
@@ -1912,21 +1926,6 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
}
}, []);
- const HTML_VIEWER_MODE_KEY = 'openchamber:files:html-viewer-mode';
-
- React.useEffect(() => {
- try {
- const stored = localStorage.getItem(HTML_VIEWER_MODE_KEY);
- if (stored === 'preview') {
- setHtmlViewMode('preview');
- } else if (stored === 'edit') {
- setHtmlViewMode('edit');
- }
- } catch {
- // Ignore localStorage errors
- }
- }, []);
-
const saveJsonViewMode = React.useCallback((mode: 'tree' | 'text') => {
setJsonViewMode(mode);
try {
diff --git a/packages/ui/src/lib/desktop.ts b/packages/ui/src/lib/desktop.ts
index d3a74de1..fa231ee4 100644
--- a/packages/ui/src/lib/desktop.ts
+++ b/packages/ui/src/lib/desktop.ts
@@ -115,6 +115,7 @@ export type DesktopSettings = {
autoCreateWorktree?: boolean;
queueModeEnabled?: boolean;
gitmojiEnabled?: boolean;
+ defaultFileViewerPreview?: boolean;
zenModel?: string;
gitProviderId?: string;
gitModelId?: string;
diff --git a/packages/ui/src/stores/useConfigStore.ts b/packages/ui/src/stores/useConfigStore.ts
index a94ba23e..e139b01b 100644
--- a/packages/ui/src/stores/useConfigStore.ts
+++ b/packages/ui/src/stores/useConfigStore.ts
@@ -28,6 +28,7 @@ interface OpenChamberDefaults {
defaultAgent?: string;
autoCreateWorktree?: boolean;
gitmojiEnabled?: boolean;
+ defaultFileViewerPreview?: boolean;
zenModel?: string;
messageStreamTransport?: 'auto' | 'ws' | 'sse';
}
@@ -45,6 +46,7 @@ const fetchOpenChamberDefaults = async (): Promise => {
const defaultVariant = typeof data?.defaultVariant === 'string' ? data.defaultVariant.trim() : '';
const defaultAgent = typeof data?.defaultAgent === 'string' ? data.defaultAgent.trim() : '';
const gitmojiEnabled = typeof data?.gitmojiEnabled === 'boolean' ? data.gitmojiEnabled : undefined;
+ const defaultFileViewerPreview = typeof data?.defaultFileViewerPreview === 'boolean' ? data.defaultFileViewerPreview : undefined;
const zenModel = typeof data?.zenModel === 'string' ? data.zenModel.trim() : '';
const messageStreamTransport =
data?.messageStreamTransport === 'ws' || data?.messageStreamTransport === 'sse' || data?.messageStreamTransport === 'auto'
@@ -57,6 +59,7 @@ const fetchOpenChamberDefaults = async (): Promise => {
defaultAgent: defaultAgent.length > 0 ? defaultAgent : undefined,
autoCreateWorktree: typeof data?.autoCreateWorktree === 'boolean' ? data.autoCreateWorktree : undefined,
gitmojiEnabled,
+ defaultFileViewerPreview,
zenModel: zenModel.length > 0 ? zenModel : undefined,
messageStreamTransport,
};
@@ -79,6 +82,7 @@ const fetchOpenChamberDefaults = async (): Promise => {
const defaultVariant = typeof data?.defaultVariant === 'string' ? data.defaultVariant.trim() : '';
const defaultAgent = typeof data?.defaultAgent === 'string' ? data.defaultAgent.trim() : '';
const gitmojiEnabled = typeof data?.gitmojiEnabled === 'boolean' ? data.gitmojiEnabled : undefined;
+ const defaultFileViewerPreview = typeof data?.defaultFileViewerPreview === 'boolean' ? data.defaultFileViewerPreview : undefined;
const zenModel = typeof data?.zenModel === 'string' ? data.zenModel.trim() : '';
const messageStreamTransport =
data?.messageStreamTransport === 'ws' || data?.messageStreamTransport === 'sse' || data?.messageStreamTransport === 'auto'
@@ -91,6 +95,7 @@ const fetchOpenChamberDefaults = async (): Promise => {
defaultAgent: defaultAgent.length > 0 ? defaultAgent : undefined,
autoCreateWorktree: typeof data?.autoCreateWorktree === 'boolean' ? data.autoCreateWorktree : undefined,
gitmojiEnabled,
+ defaultFileViewerPreview,
zenModel: zenModel.length > 0 ? zenModel : undefined,
messageStreamTransport,
};
@@ -479,6 +484,7 @@ interface ConfigStore {
settingsDefaultAgent: string | undefined;
settingsAutoCreateWorktree: boolean;
settingsGitmojiEnabled: boolean;
+ settingsDefaultFileViewerPreview: boolean;
settingsZenModel: string | undefined;
settingsMessageStreamTransport: 'auto' | 'ws' | 'sse';
// Voice provider preference ('browser', 'openai', 'openai-compatible', or 'say' for macOS)
@@ -548,6 +554,7 @@ interface ConfigStore {
setSettingsDefaultAgent: (agent: string | undefined) => void;
setSettingsAutoCreateWorktree: (enabled: boolean) => void;
setSettingsGitmojiEnabled: (enabled: boolean) => void;
+ setSettingsDefaultFileViewerPreview: (enabled: boolean) => void;
setSettingsZenModel: (model: string | undefined) => void;
setSettingsMessageStreamTransport: (transport: 'auto' | 'ws' | 'sse') => void;
getResolvedGitGenerationModel: () => { providerId: string; modelId: string } | null;
@@ -601,6 +608,7 @@ export const useConfigStore = create()(
settingsDefaultAgent: undefined,
settingsAutoCreateWorktree: false,
settingsGitmojiEnabled: false,
+ settingsDefaultFileViewerPreview: false,
settingsZenModel: undefined,
settingsMessageStreamTransport: 'auto',
// Voice provider preference - load from localStorage or default to 'browser'
@@ -1275,6 +1283,7 @@ export const useConfigStore = create()(
settingsDefaultAgent: openChamberDefaults.defaultAgent,
settingsAutoCreateWorktree: openChamberDefaults.autoCreateWorktree ?? false,
settingsGitmojiEnabled: openChamberDefaults.gitmojiEnabled ?? false,
+ settingsDefaultFileViewerPreview: openChamberDefaults.defaultFileViewerPreview ?? false,
settingsZenModel: resolvedZenModel,
settingsMessageStreamTransport: openChamberDefaults.messageStreamTransport ?? state.settingsMessageStreamTransport ?? 'auto',
directoryScoped: {
@@ -1704,6 +1713,10 @@ export const useConfigStore = create()(
set({ settingsGitmojiEnabled: enabled });
},
+ setSettingsDefaultFileViewerPreview: (enabled: boolean) => {
+ set({ settingsDefaultFileViewerPreview: enabled });
+ },
+
setSettingsZenModel: (model: string | undefined) => {
set({ settingsZenModel: model });
},
@@ -2027,6 +2040,7 @@ export const useConfigStore = create()(
settingsDefaultAgent: state.settingsDefaultAgent,
settingsAutoCreateWorktree: state.settingsAutoCreateWorktree,
settingsGitmojiEnabled: state.settingsGitmojiEnabled,
+ settingsDefaultFileViewerPreview: state.settingsDefaultFileViewerPreview,
settingsZenModel: state.settingsZenModel,
settingsMessageStreamTransport: state.settingsMessageStreamTransport,
speechRate: state.speechRate,
diff --git a/packages/web/server/lib/opencode/settings-helpers.js b/packages/web/server/lib/opencode/settings-helpers.js
index 5714dcf9..913cb219 100644
--- a/packages/web/server/lib/opencode/settings-helpers.js
+++ b/packages/web/server/lib/opencode/settings-helpers.js
@@ -289,6 +289,9 @@ export const createSettingsHelpers = (dependencies) => {
if (typeof candidate.gitmojiEnabled === 'boolean') {
result.gitmojiEnabled = candidate.gitmojiEnabled;
}
+ if (typeof candidate.defaultFileViewerPreview === 'boolean') {
+ result.defaultFileViewerPreview = candidate.defaultFileViewerPreview;
+ }
if (typeof candidate.zenModel === 'string') {
const trimmed = candidate.zenModel.trim();
result.zenModel = trimmed.length > 0 ? trimmed : undefined;