{"repo":"NousResearch/hermes-agent","public_id":"65173","display_label":"File browser sidebar auto-opens unexpectedly on desktop","summary":"Multiple reports and related fixes address the desktop file browser/sidebar panel automatically reopening on new session creation or first reply, ignoring user-collapsed state.","subsystem":"apps/desktop","counts":{"members":9,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":5,"merged_prs":2,"attempt_prs":2},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":65375},"resolution":{"state":"partially_resolved","by":"pr-65375,pr-122369"},"freshness":{"state":"verified","last_verified_at":"2026-09-26T21:43:47+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.24666666666666665},"fix_edges":[{"issue_number":65173,"pr_number":122369,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true},{"issue_number":65376,"pr_number":65375,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true},{"issue_number":66059,"pr_number":65375,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":67286,"pr_number":65375,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false}],"members":[{"kind":"issue","number":65173,"title":"File browser panel reopens on session start despite persisted closed state; no settings toggle for default","url":null,"state":"closed"},{"kind":"issue","number":65376,"title":"Right sidebar (file browser) re-opens after Cmd+J collapse when a new session is created","url":null,"state":"closed"},{"kind":"issue","number":66059,"title":"[Bug]: The file tree keeps opening automatically.","url":null,"state":"closed"},{"kind":"issue","number":66917,"title":"[desktop] File browser sidebar auto-opens on every new session when no project is active","url":null,"state":"closed"},{"kind":"issue","number":67286,"title":"[Bug]: desktop files panel auto-opens after first reply in a new session","url":null,"state":"closed"},{"kind":"pr","number":65375,"title":"fix(desktop): don't auto-expand user-collapsed side on reactive unhide","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["apps/desktop/src/components/pane-shell/tree/reactive-unhide.test.ts","apps/desktop/src/components/pane-shell/tree/store.ts","apps/desktop/src/store/review.ts"],"fix_hunks":[{"file":"apps/desktop/src/components/pane-shell/tree/reactive-unhide.test.ts","hunks":["@@ -0,0 +1,147 @@\n+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'\n+\n+// Ground-truth repro for \"reactive unhides silently re-open a user-collapsed\n+// side\": `setTreePaneHidden` is the primitive that backs `bindPaneVisibility`\n+// (e.g. `bindPaneVisibility('files', $hasWorkspace)`). The original\n+// implementation auto-called `revealTreePane` on every unhide, which expanded\n+// the parent column even when the user had explicitly collapsed it — so a\n+// workspace transition (`$currentCwd` flipping on session create / resume)\n+// forced the right sidebar open and persisted that open state to localStorage.\n+//\n+// We mirror controller.tsx exactly: register the `files` pane with\n+// `placement: 'right'`, declare the default tree, bind the right side to\n+// the file-browser open store, then collapse the right sidebar and trigger\n+// the reactive unhide via the same primitive `bindPaneVisibility` invokes.\n+\n+describe('reactive pane unhide', () => {\n+  beforeEach(() => {\n+    window.localStorage.clear()\n+    vi.resetModules()\n+  })\n+\n+  afterEach(() => {\n+    vi.resetModules()\n+  })\n+\n+  async function setupWithFiles() {\n+    const tree = await import('@/components/pane-shell/tree/store')\n+    const layout = await import('@/store/layout')\n+    const model = await import('@/components/pane-shell/tree/model')\n+    const { registry } = await import('@/contrib/registry')\n+\n+    // Register `files` like controller.tsx does — placement 'right' is what\n+    // makes `treeSideOfPane('files')` return 'right' (and therefore what\n+    // makes the buggy `revealTreePane` auto-expand the right column).\n+    registry.register({\n+      id: 'files',\n+      area: 'panes',\n+      title: 'files',\n+      data: { placement: 'right' },\n+      render: () => null\n+    })\n+\n+    // Declare a minimal default tree mirroring the production DEFAULT_TREE's\n+    // row shape (sessions | workspace | right-column-with-files).\n+    tree.declareDefaultTree(\n+      model.split('row', [\n+        model.group(['sessions'], { id: 'grp-sessions' }),\n+        model.group(['workspace'], { id: 'grp-main' }),\n+        model.split('column', [\n+          model.split('row', [\n+            model.group(['files'], { id: 'grp-files' })\n+          ], [1], 'spl-rail'),\n+          model.group(['terminal'], { id: 'grp-terminal' })\n+        ], [1.6, 1], 'spl-right')\n+      ], [0.85, 3, 1.6])\n+    )\n+\n+    // Mirror controller.tsx:512.\n+    tree.bindTreeSideVisibility('right', layout.$fileBrowserOpen, layout.setFileBrowserOpen)\n+\n+    return { tree, layout }\n+  }\n+\n+  it('reactive unhide does NOT expand a user-collapsed side', async () => {\n+    const { tree, layout } = await setupWithFiles()\n+\n+    // Simulate the production scenario: a detached chat has no cwd, so\n+    // `bindPaneVisibility('files', $hasWorkspace)` calls `setTreePaneHidden(\n+    // 'files', true)` (hidden). Then the user collapses the right sidebar.\n+    // Then a new session acquires a cwd and the workspace wiring unhides\n+    // `files`. That last step is what the bug corrupts.\n+    tree.setTreePaneHidden('files', true)\n+    expect(tree.$hiddenTreePanes.get().has('files')).toBe(true)\n+\n+    layout.setFileBrowserOpen(false)\n+    expect(layout.$fileBrowserOpen.get()).toBe(false)\n+    expect(tree.$collapsedTreeSides.get().has('right')).toBe(true)\n+\n+    // Workspace flips: `bindPaneVisibility('files', $hasWorkspace)` calls\n+    // `setTreePaneHidden('files', false)` — model it directly.\n+    tree.setTreePaneHidden('files', false)\n+\n+    // The pane unhides…\n+    expect(tree.$hiddenTreePanes.get().has('files')).toBe(false)\n+\n+    // …but the user-collapsed side MUST stay collapsed. Before the fix, the\n+    // unhide called `revealTreePane`, which saw the side was collapsed and\n+    // called `setFileBrowserOpen(true)` — silently re-opening the right\n+    // sidebar on every session create / resume.\n+    expect(layout.$fileBrowserOpen.get()).toBe(false)\n+    expect(tree.$collapsedTreeSides.get().has('right')).toBe(true)\n+  })\n+\n+  it('reactive HIDE (hidden=true) leaves the side flag alone', async () => {\n+    const { tree, layout } = await setupWithFiles()\n+\n+    layout.setFileBrowserOpen(false)\n+    expect(tree.$collapsedTreeSides.get().has('right')).toBe(true)\n+\n+    // A reactive HIDE (workspace goes away) must not flip the side either —\n+    // collapsing the side is the user's domain; hiding individual panes\n+    // inside it is the workspace domain.\n+    tree.setTreePaneHidden('files', true)\n+\n+    expect(tree.$hiddenTreePanes.get().has('files')).toBe(true)\n+    expect(layout.$fileBrowserOpen.get()).toBe(false)\n+    expect(tree.$collapsedTreeSides.get().has('right')).toBe(true)\n+  })\n+\n+  it('explicit setFileBrowserOpen still expands the side (user toggle)', async () => {\n+    const { tree, layout } = await setupWithFiles()\n+\n+    layout.setFileBrowserOpen(false)\n+    expect(tree.$collapsedTreeSides.get().has('right')).toBe(true)\n+\n+    // The user toggles the sidebar back open — this MUST still work; the\n+    // binding mirrors `$fileBrowserOpen` to `$collapsedTreeSides`.\n+    layout.setFileBrowserOpen(true)\n+    expect(layout.$fileBrowserOpen.get()).toBe(true)\n+    expect(tree.$collapsedTreeSides.get().has('right')).toBe(false)\n+  })\n+\n+  it('reactive unhide does not invoke the right side opener directly', async () => {\n+    const { tree, layout } = await setupWithFiles()\n+\n+    // Spy on the opener that `revealTreePane` would call when expanding a\n+    // collapsed side — the bug is exactly this call firing on reactive unhide.\n+    const openerSpy = vi.fn()\n+    tree.bindTreeSideVisibility('right', layout.$fileBrowserOpen, openerSpy)\n+\n+    layout.setFileBrowserOpen(false)\n+    expect(tree.$collapsedTreeSides.get().has('right')).toBe(true)\n+\n+    tree.setTreePaneHidden('files', true)\n+    expect(tree.$hiddenTreePanes.get().has('files')).toBe(true)\n+\n+    openerSpy.mockClear()\n+\n+    // Reactive unhide fires via the same primitive the workspace wiring uses.\n+    tree.setTreePaneHidden('files', false)\n+\n+    // The opener MUST NOT be called — before the fix, the auto-reveal would\n+    // call `setFileBrowserOpen(true)` via this opener.\n+    expect(tree.$hiddenTreePanes.get().has('files')).toBe(false)\n+    expect(openerSpy).not.toHaveBeenCalled()\n+  })\n+})"]},{"file":"apps/desktop/src/components/pane-shell/tree/store.ts","hunks":["@@ -126,9 +126,33 @@ export function setTreePaneHidden(paneId: string, hidden: boolean) {\n \n   $hiddenTreePanes.set(next)\n \n-  // Unhiding is an intent to SEE the pane — front it in its group.\n+  // Reactive unhides (e.g. `bindPaneVisibility('files', $hasWorkspace)`) are\n+  // state-driven, not user intent — opening the side or fronting the tab in\n+  // response to an environmental flag change would clobber an explicit user\n+  // collapse (Cmd+J) and silently re-open the rail after every session create.\n+  // Callers that want user-intent semantics (open the side, front the tab)\n+  // must call `revealTreePane` explicitly. We still front the pane in its\n+  // group so it's visible the next time the column is shown.\n   if (!hidden) {\n-    revealTreePane(paneId)\n+    frontPaneInGroup(paneId)\n+  }\n+}\n+\n+/** Make `paneId` the active tab in its group without touching side collapse\n+ *  or zone-minimized state — the safe \"make it visible next time the column\n+ *  is shown\" primitive that reactive unhides need. */\n+function frontPaneInGroup(paneId: string) {\n+  const tree = $layoutTree.get()\n+  const group = tree ? findGroupOfPane(tree, paneId) : null\n+\n+  if (!tree || !group || group.active === paneId) {\n+    return\n+  }\n+\n+  const next = setActivePaneOp(tree, group.id, paneId)\n+\n+  if (next !== tree) {\n+    commit(next)\n   }\n }\n "]},{"file":"apps/desktop/src/store/review.ts","hunks":["@@ -2,6 +2,7 @@ import { atom, computed } from 'nanostores'\n \n import { SIDEBAR_COLLAPSE_MEDIA_QUERY } from '@/app/layout-constants'\n import { PANE_TOGGLE_REVEAL_EVENT } from '@/components/pane-shell'\n+import { revealTreePane } from '@/components/pane-shell/tree/store'\n import type { HermesReviewFile, HermesReviewShipInfo } from '@/global'\n import { matchesQuery } from '@/hooks/use-media-query'\n import { desktopGit } from '@/lib/desktop-git'","@@ -274,6 +275,7 @@ export function toggleReview(): void {\n     closeReview()\n   } else {\n     openReview()\n+    revealTreePane(REVIEW_PANE_ID)\n   }\n }\n \n"]}]},{"kind":"pr","number":66079,"title":"fix(desktop): stop auto-opening file tree on new session creation (#66059)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#65375","number":65375},{"kind":"pr","value":"#65375","number":65375}],"category":"superseded_by_pr","implication":"superseded"},"merged":false},{"kind":"pr","number":67288,"title":"fix(desktop): stop files panel auto-opening on first reply","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#65375","number":65375},{"kind":"pr","value":"#65375","number":65375},{"kind":"pr","value":"#65375","number":65375},{"kind":"pr","value":"#67286","number":67286},{"kind":"pr","value":"#65375","number":65375}],"category":"superseded_by_pr","implication":"superseded"},"merged":false},{"kind":"pr","number":122369,"title":"fix(desktop): add a File Browser toggle to Appearance settings","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["apps/desktop/src/app/settings/appearance-settings.tsx","apps/desktop/src/app/settings/file-browser-setting.test.tsx","apps/desktop/src/app/settings/settings-manifest.ts","apps/desktop/src/i18n/ar.ts","apps/desktop/src/i18n/de.ts","apps/desktop/src/i18n/en.ts","apps/desktop/src/i18n/es.ts","apps/desktop/src/i18n/fr.ts","apps/desktop/src/i18n/ja.ts","apps/desktop/src/i18n/ru.ts","apps/desktop/src/i18n/types.ts","apps/desktop/src/i18n/zh-hant.ts","apps/desktop/src/i18n/zh.ts"]}],"trust":{"default_tier":"verified","includes_suggested":false}}