···378378 // These roles always close regardless of session state
379379 if (['quick-view', 'palette', 'utility'].includes(role)) return 'close';
380380 if (role === 'overlay') return 'close-and-restore';
381381- // child-content, workspace, and content: only close in transient sessions.
381381+ // Workspace windows are root/persistent extension windows (groups, search, etc.).
382382+ // They must NEVER close on ESC regardless of session state — the renderer handles
383383+ // internal navigation and the window persists.
384384+ if (role === 'workspace') return 'nothing';
385385+ // child-content and content: only close in transient sessions.
382386 // In active sessions, ESC navigates internally (renderer handles it) but
383387 // never closes the window — even child-content windows are user-opened
384388 // content that should persist.
+9-4
backend/electron/izui-state.test.ts
···715715 assert.strictEqual(escPolicy('active', 'content'), 'nothing');
716716 });
717717718718- it('should close workspace in transient session', () => {
719719- assert.strictEqual(escPolicy('transient', 'workspace'), 'close');
718718+ // REGRESSION TEST: workspace must NEVER close on ESC regardless of session state.
719719+ // Workspace windows are root/persistent extension windows (groups, search, etc.)
720720+ // that should never be dismissed by the backend ESC policy.
721721+ it('should NOT close workspace in transient session (regression test)', () => {
722722+ assert.strictEqual(escPolicy('transient', 'workspace'), 'nothing',
723723+ 'workspace windows must never close on ESC — they are root/persistent extension windows');
720724 });
721725722726 it('should NOT close workspace in active session', () => {
···786790 assert.strictEqual(coordinator.getState(), 'transient');
787791 assert.strictEqual(coordinator.isTransient(), true);
788792789789- // In transient mode, ESC should close all content roles
793793+ // In transient mode, ESC should close content roles but NOT workspace
790794 assert.strictEqual(escPolicy(coordinator.getState(), 'child-content'), 'close');
791795 assert.strictEqual(escPolicy(coordinator.getState(), 'content'), 'close');
792792- assert.strictEqual(escPolicy(coordinator.getState(), 'workspace'), 'close');
796796+ assert.strictEqual(escPolicy(coordinator.getState(), 'workspace'), 'nothing',
797797+ 'workspace windows must never close on ESC, even in transient sessions');
793798 });
794799 });
795800});
+5-1
backend/electron/windows.ts
···107107 // These roles always close regardless of session state
108108 if (['quick-view', 'palette', 'utility'].includes(role)) return 'close';
109109 if (role === 'overlay') return 'close-and-restore';
110110- // child-content, workspace, and content: only close in transient sessions.
110110+ // Workspace windows are root/persistent extension windows (groups, search, etc.).
111111+ // They must NEVER close on ESC regardless of session state — the renderer handles
112112+ // internal navigation and the window persists.
113113+ if (role === 'workspace') return 'nothing';
114114+ // child-content and content: only close in transient sessions.
111115 // In active sessions, ESC navigates internally (renderer handles it) but
112116 // never closes the window — even child-content windows are user-opened
113117 // content that should persist. Root-level content windows must not close
+4-3
extensions/groups/home.js
···149149150150/**
151151 * Internal ESC handler for groups navigation
152152- * Returns { handled: true } if we navigated internally
153153- * Returns { handled: false } if at root (groups list) and window should close
152152+ * Returns { handled: true } if we navigated internally (search clear, back to groups list)
153153+ * Returns { handled: false } at root — backend escPolicy decides (workspace role = never close)
154154 */
155155const handleEscape = () => {
156156 // If search has content, clear it first
···172172 }, 0);
173173 return { handled: true };
174174 }
175175- // At root (groups list) - let window close
175175+ // At root (groups list) — defer to backend escPolicy.
176176+ // Groups window has role:'workspace', so escPolicy returns 'nothing' (never close).
176177 return { handled: false };
177178};
178179