Triage
Confirmed on current main (b900fc94e) and on the reporter’s 0.0.41-nightly.20260910.1486. This is a real web/desktop (and mobile) UX bug, not a stale-cache problem and not a wash of #8759 / #8826 / #8968. The tree’s expansion is session-only and the Files surface is torn down whenever it is not the active right-panel tab.
useFileTree is created with initialExpansion: "closed". The Pierre model is the expansion store. expandedPathsRef is only a shadow set so newly expanded folders get load(). Both die on unmount. Nothing writes either one to storage.
| 1 | // apps/web/src/components/files/FileBrowserPanel.tsx |
| 2 | initialExpansion: "closed", |
| 3 | const expandedPathsRef = useRef(new Set<string>()); |
ChatView renders only the active right-panel surface. Switching to Diff, Terminal, or Preview unmounts FilePreviewPanel, which unmounts FileBrowserPanel. Hiding the explorer (t3code.fileExplorerOpen) does the same: showExplorer ? <FileBrowserPanel /> : null. Thread switch and reload remount it as well.
The report slightly overstates “fully collapsed” on the file surface (kind === "file"). Remount re-expands ancestors of the open file via the reveal effect. That does not restore:
- folders the user opened that are not on that file’s path
- the Files singleton (
kind === "files",selectedPath === null) expandAll(useState(false))
#8968 only keeps Pierre rows alive across in-place treePaths updates. It does not survive unmount. #11527 made remount worse: children are fetched per expanded folder, so a collapsed remount also drops the directory cache in useDirectoryEntries.
Mobile matches: FileTreeBrowser holds expandedPaths in useState and is keyed by [environmentId, cwd]. Leaving the Files route or remounting the navigator pane starts from new Set(). Selecting a file re-expands ancestors only.
Related, not duplicates
| Work | What it did | Why this still happens |
|---|---|---|
| Open #8759 / open #8826 | Diff scroll + collapsed files across right-panel tabs | Same family of state loss, different state. #8826 was already called too broad; do not fold the Files tree into it. |
| Merged #8968 | Incremental treePaths so refresh does not rebuild the tree | Survives refresh, not tab/thread/reload remount. |
| Merged #8889 (closed #4227 / #6794) | Expand/collapse all | expandAll is also useState. One more thing the remount forgets. |
| Merged #11527 | On-demand folder listing | Restore must load() each persisted path from the root down. |
| Closed #3733 | Persist reasoning/tool-trace expansion across thread switches | Precedent that expansion is durable elsewhere. Not this tree. |
rightPanelStore already says each feature owns its durable resource state. Open file tabs (t3code:right-panel-state:v2) and the explorer open flag (t3code.fileExplorerOpen) persist. The tree is the odd one out. No open PR persists Files expansion.
Suggested fix
Keep this a small persistence bugfix on the Files tree. Do not keep every right-panel surface mounted, and do not wait for #8826.
- Persist the expanded path set (and
expandAllif you want that button to survive too), keyed byenvironmentId+cwd. Workspace scope matches the tree.localStorage, same idea ast3code.fileExplorerOpen. Do not put this inrightPanelStore. - On mount, restore after paths exist. For each persisted directory:
load(path), thenitem.expand()on the Pierre model (web) /setExpandedPaths(mobile). Parent-before-child. Skip paths that are gone. - Write on toggle, not only on unmount. The current
model.subscribealready sees expand/collapse; persist from there. Prune paths that leftdirectoryPaths. - Mobile: same store in
FileTreeBrowserso the Files route and the thread navigator pane come back expanded. - Tests: remount
FileBrowserPanelwith a stored deep path and assertload+expandfor each ancestor. Empty/missing storage stays closed. A selected file still reveals even when storage is empty. - Visual close: expand a deep folder on the Files singleton, switch to Diff and back, reload — same folders open. Repeat on mobile after leaving Files.
Do not treat keep-alive of inactive right-panel surfaces as the fix. That is the #8826 scope problem. Persisting a path set is enough.
Workaround
Stay on the Files tab. If a file is already open, that path re-reveals; sibling folders do not. No in-app toggle.
Classification: bug · accepted · minor (web/desktop/mobile Files tree; extra clicks, no data loss)
Labels: add bug, accepted, via-triage
Discord tags: ui, mobile-general