diff --git a/src/components/SettingsDialog.tsx b/src/components/SettingsDialog.tsx
index 7b2f246f..f0150042 100644
--- a/src/components/SettingsDialog.tsx
+++ b/src/components/SettingsDialog.tsx
@@ -15,6 +15,7 @@ import { themeToCss, detectThemeTone } from '../lib/custom-theme';
import {
store,
setTerminalFont,
+ setTerminalScreenReaderMode,
setAutoTrustFolders,
setShowPlans,
setShowPromptInput,
@@ -463,6 +464,13 @@ export function SettingsDialog(props: SettingsDialogProps) {
description="Enable antialiasing and geometric text rendering"
align="flex-start"
/>
+
diff --git a/src/components/TerminalView.tsx b/src/components/TerminalView.tsx
index 01eda0a7..aef2bd1c 100644
--- a/src/components/TerminalView.tsx
+++ b/src/components/TerminalView.tsx
@@ -416,6 +416,7 @@ export function TerminalView(props: TerminalViewProps) {
cursorBlink: true,
fontSize: initialFontSize,
fontFamily: getTerminalFontFamily(store.terminalFont),
+ screenReaderMode: store.terminalScreenReaderMode,
theme: activeTerminalTheme(),
allowProposedApi: true,
scrollback: TERMINAL_SCROLLBACK_LINES,
@@ -1107,6 +1108,12 @@ export function TerminalView(props: TerminalViewProps) {
markDirty(props.agentId);
});
+ createEffect(() => {
+ const screenReaderMode = store.terminalScreenReaderMode;
+ if (!term) return;
+ term.options.screenReaderMode = screenReaderMode;
+ });
+
createEffect(() => {
if (!term) return;
term.options.theme = activeTerminalTheme();
diff --git a/src/store/autosave.test.ts b/src/store/autosave.test.ts
index c9543e09..4cbab112 100644
--- a/src/store/autosave.test.ts
+++ b/src/store/autosave.test.ts
@@ -30,6 +30,15 @@ describe('autosave snapshot includes new-task-default fields', () => {
setStore('defaultPropagateSkipPermissions', false);
});
+ it('terminalScreenReaderMode changes the snapshot', () => {
+ setStore('terminalScreenReaderMode', false);
+ const before = persistedSnapshot();
+ setStore('terminalScreenReaderMode', true);
+ const after = persistedSnapshot();
+ expect(before).not.toBe(after);
+ setStore('terminalScreenReaderMode', false);
+ });
+
it('showSteps is not tracked separately (migrated to defaultStepsEnabled)', () => {
expect('showSteps' in store).toBe(false);
});
diff --git a/src/store/autosave.ts b/src/store/autosave.ts
index 9b269c89..7eef7b33 100644
--- a/src/store/autosave.ts
+++ b/src/store/autosave.ts
@@ -21,6 +21,7 @@ export function persistedSnapshot(): string {
mergedLinesAdded: store.mergedLinesAdded,
mergedLinesRemoved: store.mergedLinesRemoved,
terminalFont: store.terminalFont,
+ terminalScreenReaderMode: store.terminalScreenReaderMode,
themePreset: store.themePreset,
windowState: store.windowState,
autoTrustFolders: store.autoTrustFolders,
diff --git a/src/store/core.ts b/src/store/core.ts
index 016747cd..8d9addf1 100644
--- a/src/store/core.ts
+++ b/src/store/core.ts
@@ -37,6 +37,7 @@ export const [store, setStore] = createStore({
mergedLinesAdded: 0,
mergedLinesRemoved: 0,
terminalFont: DEFAULT_TERMINAL_FONT,
+ terminalScreenReaderMode: false,
themePreset: 'islands-dark',
appearanceMode: 'dark',
lightThemePreset: 'islands-light',
diff --git a/src/store/persistence.test.ts b/src/store/persistence.test.ts
index 6ae20978..71043ee7 100644
--- a/src/store/persistence.test.ts
+++ b/src/store/persistence.test.ts
@@ -80,6 +80,7 @@ beforeEach(() => {
setStore('customAgents', []);
setStore('coordinatorControlHintDismissed', false);
setStore('autoStartRemoteAccess', false);
+ setStore('terminalScreenReaderMode', false);
});
describe('resolveIncomingPanelUserSize', () => {
@@ -490,6 +491,53 @@ describe('loadState theme persistence', () => {
});
});
+describe('terminal screen reader mode persistence', () => {
+ it('defaults to false when absent from saved state', async () => {
+ mockInvoke.mockResolvedValueOnce(basePayload());
+
+ await loadState();
+
+ expect(store.terminalScreenReaderMode).toBe(false);
+ });
+
+ it('restores an enabled screen reader mode', async () => {
+ mockInvoke.mockResolvedValueOnce(basePayload({ terminalScreenReaderMode: true }));
+
+ await loadState();
+
+ expect(store.terminalScreenReaderMode).toBe(true);
+ });
+
+ it.each(['true', 1, null])('rejects a non-boolean persisted value (%s)', async (value) => {
+ setStore('terminalScreenReaderMode', true);
+ mockInvoke.mockResolvedValueOnce(basePayload({ terminalScreenReaderMode: value }));
+
+ await loadState();
+
+ expect(store.terminalScreenReaderMode).toBe(false);
+ });
+
+ it('omits the disabled default when saving', async () => {
+ setStore('terminalScreenReaderMode', false);
+ mockInvoke.mockResolvedValueOnce(undefined);
+
+ await saveState();
+
+ const saved = JSON.parse(mockInvoke.mock.calls[0][1].json);
+ expect(saved.terminalScreenReaderMode).toBeUndefined();
+ });
+
+ it('persists the enabled setting', async () => {
+ setStore('terminalScreenReaderMode', true);
+ mockInvoke.mockResolvedValueOnce(undefined);
+
+ await saveState();
+
+ const saved = JSON.parse(mockInvoke.mock.calls[0][1].json);
+ expect(saved.terminalScreenReaderMode).toBe(true);
+ });
+});
+
describe('coordinator control hint persistence', () => {
it('does not persist dismissed=false', async () => {
setStore('coordinatorControlHintDismissed', false);
diff --git a/src/store/persistence.ts b/src/store/persistence.ts
index 33086734..f0619bff 100644
--- a/src/store/persistence.ts
+++ b/src/store/persistence.ts
@@ -183,6 +183,7 @@ export async function saveState(): Promise {
mergedLinesAdded: store.mergedLinesAdded,
mergedLinesRemoved: store.mergedLinesRemoved,
terminalFont: store.terminalFont,
+ terminalScreenReaderMode: store.terminalScreenReaderMode || undefined,
themePreset: store.themePreset,
showPromptInput: store.showPromptInput,
fontSmoothing: store.fontSmoothing,
@@ -351,6 +352,7 @@ interface LegacyPersistedState {
mergedLinesAdded?: unknown;
mergedLinesRemoved?: unknown;
terminalFont?: unknown;
+ terminalScreenReaderMode?: unknown;
themePreset?: unknown;
showPromptInput?: unknown;
fontSmoothing?: unknown;
@@ -497,6 +499,8 @@ export async function loadState(): Promise {
typeof raw.terminalFont === 'string' && raw.terminalFont.trim()
? raw.terminalFont
: DEFAULT_TERMINAL_FONT;
+ s.terminalScreenReaderMode =
+ typeof raw.terminalScreenReaderMode === 'boolean' ? raw.terminalScreenReaderMode : false;
s.themePreset = isLookPreset(raw.themePreset) ? raw.themePreset : 'minimal';
s.showPromptInput = typeof raw.showPromptInput === 'boolean' ? raw.showPromptInput : true;
s.fontSmoothing = typeof raw.fontSmoothing === 'boolean' ? raw.fontSmoothing : true;
diff --git a/src/store/store.ts b/src/store/store.ts
index 8e8c35a9..e242b2c6 100644
--- a/src/store/store.ts
+++ b/src/store/store.ts
@@ -117,6 +117,7 @@ export {
toggleTaskFocusMode,
setTaskSplitMode,
setTerminalFont,
+ setTerminalScreenReaderMode,
applyAppearanceMode,
markCustomThemesReady,
setAppearanceMode,
diff --git a/src/store/types.ts b/src/store/types.ts
index e1040baf..1feb8573 100644
--- a/src/store/types.ts
+++ b/src/store/types.ts
@@ -249,6 +249,7 @@ export interface PersistedState {
mergedLinesAdded?: number;
mergedLinesRemoved?: number;
terminalFont?: string;
+ terminalScreenReaderMode?: boolean;
themePreset?: LookPreset;
showPromptInput?: boolean;
fontSmoothing?: boolean;
@@ -346,6 +347,7 @@ export interface AppStore {
mergedLinesAdded: number;
mergedLinesRemoved: number;
terminalFont: string;
+ terminalScreenReaderMode: boolean;
themePreset: LookPreset;
showPromptInput: boolean;
fontSmoothing: boolean;
diff --git a/src/store/ui.test.ts b/src/store/ui.test.ts
index 75591752..c9939fb4 100644
--- a/src/store/ui.test.ts
+++ b/src/store/ui.test.ts
@@ -9,6 +9,7 @@ type MockStore = {
panelUserSize: Record;
projectsCollapsed: boolean;
sidebarFocusedProjectId: string | null;
+ terminalScreenReaderMode: boolean;
};
let mockStore: MockStore;
@@ -63,6 +64,7 @@ import {
setPanelUserSize,
deletePanelUserSize,
setProjectsCollapsed,
+ setTerminalScreenReaderMode,
} from './ui';
beforeEach(() => {
@@ -78,6 +80,7 @@ beforeEach(() => {
panelUserSize: {},
projectsCollapsed: false,
sidebarFocusedProjectId: null,
+ terminalScreenReaderMode: false,
});
vi.stubGlobal('requestAnimationFrame', (cb: FrameRequestCallback) => {
@@ -195,3 +198,11 @@ describe('setProjectsCollapsed', () => {
expect(mockStore.sidebarFocusedProjectId).toBeNull();
});
});
+
+describe('setTerminalScreenReaderMode', () => {
+ it('updates the global terminal accessibility option', () => {
+ setTerminalScreenReaderMode(true);
+
+ expect(mockStore.terminalScreenReaderMode).toBe(true);
+ });
+});
diff --git a/src/store/ui.ts b/src/store/ui.ts
index 9368b214..e528818b 100644
--- a/src/store/ui.ts
+++ b/src/store/ui.ts
@@ -86,6 +86,10 @@ export function setTerminalFont(terminalFont: string): void {
setStore('terminalFont', terminalFont);
}
+export function setTerminalScreenReaderMode(enabled: boolean): void {
+ setStore('terminalScreenReaderMode', enabled);
+}
+
export function applyAppearanceMode(): void {
const isDark = osIsDark();
const mode = store.appearanceMode;