diff --git a/src/client/Sidebar.tsx b/src/client/Sidebar.tsx index b5d6c70..eac38a2 100644 --- a/src/client/Sidebar.tsx +++ b/src/client/Sidebar.tsx @@ -135,7 +135,7 @@ interface TabContentProps extends TabContentMemoKey { /** Render the content of one tab (dispatched by type). */ const TabContent = memo(function TabContent(props: TabContentProps) { - const { tab, sessionId, cwd, expanded, onToggleDir, onReferenceFile, ctx, store, visible, onSubagentJump, onOpenDiff } = props + const { tab, sessionId, cwd, expanded, revealed, onToggleDir, onReferenceFile, ctx, store, visible, onSubagentJump, onOpenDiff } = props const scope = { sessionId, cwd } const descriptor = ctx.get('betterSidebar')?.getTab(tab.type) if (descriptor === undefined) { diff --git a/src/client/locales-fr.ts b/src/client/locales-fr.ts index d7e2f8f..26ecff3 100644 --- a/src/client/locales-fr.ts +++ b/src/client/locales-fr.ts @@ -254,7 +254,7 @@ export const fr: Record = { settingsBrowserHttpsTitle: 'Ouvrir les pages HTTPS de côté', settingsBrowserHttpsDesc: 'Une fois activé, cliquer sur un lien externe HTTPS du chat ou de l’interface l’ouvre dans la barre latérale. Désactivé par défaut : la plupart des sites HTTPS refusent l’encapsulation, le navigateur système est plus fluide', settingsBrowserLoopbackTitle: 'Adresses locales autorisées', - settingsBrowserLoopbackDesc: 'Liste blanche séparée par des virgules d'adresses de bouclage (ex. localhost:5174 ou 127.0.0.1:8080) que le navigateur de la barre latérale peut visiter ; vide bloque toutes les adresses locales par défaut. Le bac à sable s'applique toujours — les pages ne peuvent pas lire les données de l'interface', + settingsBrowserLoopbackDesc: 'Liste blanche séparée par des virgules des adresses de bouclage (ex. localhost:5174 ou 127.0.0.1:8080) que le navigateur de la barre latérale peut visiter ; vide bloque toutes les adresses locales par défaut. Le bac à sable reste actif — les pages ne peuvent pas lire les données de l interface', settingsBrowserLoopbackPlaceholder: 'ex. localhost:5174, 127.0.0.1:8080', browserOpenExternal: 'Ouvrir dans le navigateur', browserEmbedBlocked: '{host} a refusé la demande d’encapsulation', diff --git a/src/client/locales-nl.ts b/src/client/locales-nl.ts index 123f2ae..5e07f66 100644 --- a/src/client/locales-nl.ts +++ b/src/client/locales-nl.ts @@ -259,7 +259,7 @@ export const nl: Record = { settingsBrowserHttpsTitle: 'HTTPS-pagina\'s in de zijbalk openen', settingsBrowserHttpsDesc: 'Indien aan, opent een klik op een externe HTTPS-link in de chat of GUI de zijbalk. Standaard uit: de meeste HTTPS-sites weigeren ingebed te worden, dus de systeembrowser is soepeler', settingsBrowserLoopbackTitle: 'Toegestane lokale adressen', - settingsBrowserLoopbackDesc: 'Door komma's gescheiden allowlist van loopback-adressen (bijv. localhost:5174 of 127.0.0.1:8080) die de zijbalk-browser kan bezoeken; leeg blokkeert standaard alle lokale adressen. De sandbox blijft van toepassing — pagina's kunnen geen GUI-gegevens lezen', + settingsBrowserLoopbackDesc: 'Door komma gescheiden allowlist van loopback-adressen (bijv. localhost:5174 of 127.0.0.1:8080) die de zijbalk-browser kan bezoeken; leeg blokkeert standaard alle lokale adressen. De sandbox blijft van toepassing — pagina s kunnen geen GUI-gegevens lezen', settingsBrowserLoopbackPlaceholder: 'bijv. localhost:5174, 127.0.0.1:8080', browserOpenExternal: 'Openen in browser', browserEmbedBlocked: '{host} weigerde ingebed te worden', diff --git a/src/client/tab-content-memo.ts b/src/client/tab-content-memo.ts index 50ef171..c10affa 100644 --- a/src/client/tab-content-memo.ts +++ b/src/client/tab-content-memo.ts @@ -23,6 +23,8 @@ export interface TabContentMemoKey { cwd: string | undefined visible: boolean expanded: string[] + /** Files highlighted in the file tree (the "Show in folder" reveal). */ + revealed: string[] localeRevision: string tabsVersion: number } @@ -40,6 +42,7 @@ export function tabContentCompare(prev: TabContentMemoKey, next: TabContentMemoK prev.cwd === next.cwd && prev.visible === next.visible && prev.expanded === next.expanded && + prev.revealed === next.revealed && prev.localeRevision === next.localeRevision && prev.tabsVersion === next.tabsVersion ) diff --git a/src/index.ts b/src/index.ts index 6c1ce19..1d94ef6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -946,7 +946,7 @@ export function apply(ctx: Context, config?: SidebarConfig): void { // terminals follow the session, not the reconnect grace. Agent terminals // are owned by the agent lifecycle and are not touched here. ctx.on('session/disposed', (session) => { - try { ptyManager.closeSession(session?.id) } catch {} + try { ptyManager?.closeSession(session?.id) } catch {} }, { global: true }) } diff --git a/tests/openpath-intercept.spec.ts b/tests/openpath-intercept.spec.ts index 8ad7bdc..619928b 100644 --- a/tests/openpath-intercept.spec.ts +++ b/tests/openpath-intercept.spec.ts @@ -27,6 +27,7 @@ describe('open-path interception', () => { takeoverEnabled: () => true, currentSessionId: () => 's1', openInSidebar: (path, sessionId) => { sidebar.push(`${sessionId}:${path}`) }, + revealInExplorer: () => {}, ...overrides, } } diff --git a/tests/prefs.spec.ts b/tests/prefs.spec.ts index de19201..0ce1ba3 100644 --- a/tests/prefs.spec.ts +++ b/tests/prefs.spec.ts @@ -260,9 +260,9 @@ describe('side card preferences', () => { const store = createSidebarStore() // Node environment: no window → the width falls back to PANEL_DEFAULT, // while the open flag still follows the preference. - store.setPrefs({ openByDefault: false, defaultWidthPercent: 45, autoOpenSubagent: true, autoOpenJobs: true, agentTerminalTools: false, bottomPanelAutoTerminal: true, terminalFontFamily: '', terminalFontSize: 13, interceptOpenPath: true, editorExplorer: true, terminalShell: '', terminalShellArgs: '', titleBarScheme: 'auto', titleBarPresetId: '', customCss: '', titleBarCompat: false, titleBarStripPx: 40, htmlViewerNoSandbox: false, htmlViewerDefaultUnsafe: false, browserNoSandbox: false, browserInterceptLinks: true, browserInterceptHttp: true, browserInterceptHttps: false, tabsEnabled: {}, viewersEnabled: {}, pluginSettings: {} }) + store.setPrefs({ openByDefault: false, defaultWidthPercent: 45, autoOpenSubagent: true, autoOpenJobs: true, agentTerminalTools: false, bottomPanelAutoTerminal: true, terminalFontFamily: '', terminalFontSize: 13, interceptOpenPath: true, editorExplorer: true, terminalShell: '', terminalShellArgs: '', titleBarScheme: 'auto', titleBarPresetId: '', customCss: '', titleBarCompat: false, titleBarStripPx: 40, htmlViewerNoSandbox: false, htmlViewerDefaultUnsafe: false, browserNoSandbox: false, browserInterceptLinks: true, browserInterceptHttp: true, browserInterceptHttps: false, browserAllowedLoopback: "", tabsEnabled: {}, viewersEnabled: {}, pluginSettings: {} }) store.setSession('fresh-session') - expect(store.getPrefs()).toEqual({ openByDefault: false, defaultWidthPercent: 45, autoOpenSubagent: true, autoOpenJobs: true, agentTerminalTools: false, bottomPanelAutoTerminal: true, terminalFontFamily: '', terminalFontSize: 13, interceptOpenPath: true, editorExplorer: true, terminalShell: '', terminalShellArgs: '', titleBarScheme: 'auto', titleBarPresetId: '', customCss: '', titleBarCompat: false, titleBarStripPx: 40, htmlViewerNoSandbox: false, htmlViewerDefaultUnsafe: false, browserNoSandbox: false, browserInterceptLinks: true, browserInterceptHttp: true, browserInterceptHttps: false, tabsEnabled: {}, viewersEnabled: {}, pluginSettings: {} }) + expect(store.getPrefs()).toEqual({ openByDefault: false, defaultWidthPercent: 45, autoOpenSubagent: true, autoOpenJobs: true, agentTerminalTools: false, bottomPanelAutoTerminal: true, terminalFontFamily: '', terminalFontSize: 13, interceptOpenPath: true, editorExplorer: true, terminalShell: '', terminalShellArgs: '', titleBarScheme: 'auto', titleBarPresetId: '', customCss: '', titleBarCompat: false, titleBarStripPx: 40, htmlViewerNoSandbox: false, htmlViewerDefaultUnsafe: false, browserNoSandbox: false, browserInterceptLinks: true, browserInterceptHttp: true, browserInterceptHttps: false, browserAllowedLoopback: "", tabsEnabled: {}, viewersEnabled: {}, pluginSettings: {} }) const snapshot = store.getSnapshot() expect(snapshot.sessionId).toBe('fresh-session') expect(snapshot.state?.panelOpen).toBe(false) @@ -298,7 +298,7 @@ describe('side card preferences', () => { it('skips the default seed tab when the editor (files window) type is disabled', () => { const store = createSidebarStore() - store.setPrefs({ openByDefault: true, defaultWidthPercent: 30, autoOpenSubagent: true, autoOpenJobs: true, agentTerminalTools: false, bottomPanelAutoTerminal: true, terminalFontFamily: '', terminalFontSize: 13, interceptOpenPath: true, editorExplorer: true, terminalShell: '', terminalShellArgs: '', titleBarScheme: 'auto', titleBarPresetId: '', customCss: '', titleBarCompat: false, titleBarStripPx: 40, htmlViewerNoSandbox: false, htmlViewerDefaultUnsafe: false, browserNoSandbox: false, browserInterceptLinks: true, browserInterceptHttp: true, browserInterceptHttps: false, tabsEnabled: { editor: false }, viewersEnabled: {}, pluginSettings: {} }) + store.setPrefs({ openByDefault: true, defaultWidthPercent: 30, autoOpenSubagent: true, autoOpenJobs: true, agentTerminalTools: false, bottomPanelAutoTerminal: true, terminalFontFamily: '', terminalFontSize: 13, interceptOpenPath: true, editorExplorer: true, terminalShell: '', terminalShellArgs: '', titleBarScheme: 'auto', titleBarPresetId: '', customCss: '', titleBarCompat: false, titleBarStripPx: 40, htmlViewerNoSandbox: false, htmlViewerDefaultUnsafe: false, browserNoSandbox: false, browserInterceptLinks: true, browserInterceptHttp: true, browserInterceptHttps: false, browserAllowedLoopback: "", tabsEnabled: { editor: false }, viewersEnabled: {}, pluginSettings: {} }) store.setSession('no-editor') const state = store.getSnapshot().state! const tabs = allLeaves(state.splits).flatMap(leaf => leaf.tabs) @@ -308,7 +308,7 @@ describe('side card preferences', () => { // editorExplorer modes. for (const editorExplorer of [true, false]) { const openStore = createSidebarStore() - openStore.setPrefs({ openByDefault: true, defaultWidthPercent: 30, autoOpenSubagent: true, autoOpenJobs: true, agentTerminalTools: false, bottomPanelAutoTerminal: true, terminalFontFamily: '', terminalFontSize: 13, interceptOpenPath: true, editorExplorer, terminalShell: '', terminalShellArgs: '', titleBarScheme: 'auto', titleBarPresetId: '', customCss: '', titleBarCompat: false, titleBarStripPx: 40, htmlViewerNoSandbox: false, htmlViewerDefaultUnsafe: false, browserNoSandbox: false, browserInterceptLinks: true, browserInterceptHttp: true, browserInterceptHttps: false, tabsEnabled: {}, viewersEnabled: {}, pluginSettings: {} }) + openStore.setPrefs({ openByDefault: true, defaultWidthPercent: 30, autoOpenSubagent: true, autoOpenJobs: true, agentTerminalTools: false, bottomPanelAutoTerminal: true, terminalFontFamily: '', terminalFontSize: 13, interceptOpenPath: true, editorExplorer, terminalShell: '', terminalShellArgs: '', titleBarScheme: 'auto', titleBarPresetId: '', customCss: '', titleBarCompat: false, titleBarStripPx: 40, htmlViewerNoSandbox: false, htmlViewerDefaultUnsafe: false, browserNoSandbox: false, browserInterceptLinks: true, browserInterceptHttp: true, browserInterceptHttps: false, browserAllowedLoopback: "", tabsEnabled: {}, viewersEnabled: {}, pluginSettings: {} }) openStore.setSession(`with-editor-${editorExplorer}`) const openTabs = allLeaves(openStore.getSnapshot().state!.splits).flatMap(leaf => leaf.tabs) expect(openTabs.map(tab => tab.type)).toEqual(['editor']) @@ -318,7 +318,7 @@ describe('side card preferences', () => { it('seeds the empty editor home tab (files window) in both editorExplorer modes', () => { for (const editorExplorer of [true, false]) { const store = createSidebarStore() - store.setPrefs({ openByDefault: true, defaultWidthPercent: 30, autoOpenSubagent: true, autoOpenJobs: true, agentTerminalTools: false, bottomPanelAutoTerminal: true, terminalFontFamily: '', terminalFontSize: 13, interceptOpenPath: true, editorExplorer, terminalShell: '', terminalShellArgs: '', titleBarScheme: 'auto', titleBarPresetId: '', customCss: '', titleBarCompat: false, titleBarStripPx: 40, htmlViewerNoSandbox: false, htmlViewerDefaultUnsafe: false, browserNoSandbox: false, browserInterceptLinks: true, browserInterceptHttp: true, browserInterceptHttps: false, tabsEnabled: {}, viewersEnabled: {}, pluginSettings: {} }) + store.setPrefs({ openByDefault: true, defaultWidthPercent: 30, autoOpenSubagent: true, autoOpenJobs: true, agentTerminalTools: false, bottomPanelAutoTerminal: true, terminalFontFamily: '', terminalFontSize: 13, interceptOpenPath: true, editorExplorer, terminalShell: '', terminalShellArgs: '', titleBarScheme: 'auto', titleBarPresetId: '', customCss: '', titleBarCompat: false, titleBarStripPx: 40, htmlViewerNoSandbox: false, htmlViewerDefaultUnsafe: false, browserNoSandbox: false, browserInterceptLinks: true, browserInterceptHttp: true, browserInterceptHttps: false, browserAllowedLoopback: "", tabsEnabled: {}, viewersEnabled: {}, pluginSettings: {} }) store.setSession(`fresh-${editorExplorer}`) const tabs = allLeaves(store.getSnapshot().state!.splits).flatMap(leaf => leaf.tabs) expect(tabs).toHaveLength(1) diff --git a/tests/smoke.spec.ts b/tests/smoke.spec.ts index c0806f8..55bfff6 100644 --- a/tests/smoke.spec.ts +++ b/tests/smoke.spec.ts @@ -13,7 +13,6 @@ import { apply, mediaTypeForPath } from '../src/index.ts' import { encodeHtmlUrl } from '../src/html-route.ts' import * as git from '../src/git.ts' import { listDirectory } from '../src/fs-tree.ts' -import { encodeHtmlUrl } from '../src/html-route.ts' import { defaultShell, PtyManager, type SidebarPty } from '../src/pty-manager.ts' import type { SidebarWebRoute, SidebarWebUpgradeRoute } from '../src/context-types.ts' diff --git a/tests/tab-content-memo.spec.ts b/tests/tab-content-memo.spec.ts index ee62b6f..0eb2370 100644 --- a/tests/tab-content-memo.spec.ts +++ b/tests/tab-content-memo.spec.ts @@ -18,6 +18,7 @@ function makeKey(overrides: Partial = {}): TabContentMemoKey cwd: '/workspace', visible: true, expanded: [], + revealed: [], localeRevision: 'en-US', tabsVersion: 0, ...overrides,