Skip to content

fix(web): the Files tree remembers expanded folders - #18211

Open
fixfon wants to merge 1 commit into
pingdotgg:mainfrom
fixfon:fix/files-tree-remember-expanded
Open

fixfon wants to merge 1 commit into
pingdotgg:mainfrom
fixfon:fix/files-tree-remember-expanded

Conversation

@fixfon

@fixfon fixfon commented Oct 11, 2026

Copy link
Copy Markdown
Contributor

Problem

The Files tree forgets which folders were open whenever its panel remounts: switching to another right-panel tab and back, switching threads, hiding the explorer, or reloading. Expansion lives only in the Pierre model and a ref, and the panel is unmounted whenever it is not the active surface. Established in #12041 and confirmed in the maintainer triage (accepted bug). This follows the triage's suggested fix: persist the expanded path set per environmentId + cwd in localStorage (not in rightPanelStore) and do not keep inactive surfaces mounted.

Fix

  • New fileTreeExpansionStorage.ts: reads and writes the expanded folders under t3code.fileTreeExpanded:<environmentId>:<cwd>, bounded to 500 paths (shallowest kept, so restore still reaches them). Unreadable or malformed storage restores nothing.
  • syncPersistedExpansion runs from the existing model.subscribe in FileBrowserPanel. Remembered folders are expanded as their rows appear, which happens only after the parent is listed, so restore is parent-before-child. The existing expand handler then load()s each one. Expanding or collapsing a folder updates storage on toggle. A remembered folder is dropped only when its parent was listed successfully and no longer contains it, so a failed listing never erases saved state.
  • useDirectoryEntries also returns the set of successfully listed directories, which the prune rule needs.
  • Selected-file reveal is unchanged and still works with empty storage. expandAll is not stored separately; the folders it opens are stored like any others.

Scope

Web and desktop only. The triage also lists mobile (FileTreeBrowser), which needs its own async per-workspace persistence in mobile preferences and native verification I could not do here, so I left it for a separate PR rather than ship it unchecked. For that reason this is Refs, not Closes.

Verification

  • vp test run src/components/files/fileTreeExpansionStorage.test.ts (apps/web): 6 passed. Covers round-trip and malformed storage, the 500-path bound, parent-before-child restore, not forgetting unlisted folders, pruning folders the parent no longer lists, and remembering user expansion. The neighboring fileTreeExpansion and fileTreePathReconciliation tests still pass (12 total).

  • tsc --noEmit in apps/web: clean. vp fmt --check clean. vp lint on src/components/files reports only warnings on code this PR does not touch.

  • Manual, web dev server on an isolated worktree .t3 seeded with migrate-dev-db, Files surface on apps/server:

    1. Expand src, src/orchestration-v2, src/orchestration-v2/Adapters.
    2. Switch to the Terminal tab and back to Files.
    3. Switch to another thread and back.
    4. Reload the window.

    Before (unpatched main code): everything is collapsed after step 2. After: the same three folders are open after steps 2, 3 and 4, with a brief row spinner while each is re-listed after reload. localStorage held ["src","src/orchestration-v2","src/orchestration-v2/Adapters"] for the workspace key.

Before: expanded Before: after Files → Terminal → Files
Before expanded Before, collapsed after tab switch
After: Files → Terminal → Files After: reload
After tab switch After reload

Recordings: before, after: tab switch, thread switch, reload. Cropped to the right panel.

Not checked

  • Mobile (out of scope, above) and the Electron desktop shell beyond the shared web code.
  • The thread switch in the recording goes to an empty thread in the same project; threads in another workspace use their own key, covered by the key format and not exercised in the browser.
  • A very large "expand all" beyond the 500-path bound was covered by a unit test only.

Prior attempts #12083 and #15256 were closed for the orchestrator V2 rebase and a missing model/harness note, respectively. This is rebuilt on current main, with the author's tooling stated below, and is smaller (no mobile, no per-restore queue state).

Refs #12041

Written by Claude Sonnet 5.5 via Claude Code in T3 Code.

🤖 Generated with Claude Code

The Files panel unmounts with its right-panel tab and on thread switch or
reload, and its expansion lived only in memory, so every remount collapsed
the tree. Persist the expanded folders per environment and workspace in
localStorage and restore them parent-before-child as each folder is listed.

Refs pingdotgg#12041

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Oct 11, 2026
@macroscopeapp

macroscopeapp Bot commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds automatic workspace-scoped persistence and restoration of Files-tree expansion state, including potentially triggering directory loads when the panel reopens. The behavior is localized and tested but changes the existing user path and is enabled automatically, warranting human review.

Notes:

  • Diff unchanged. Approvability was decided on eligibility alone.

You can add or adjust custom eligibility rules. Learn more.

@juliusmarminge juliusmarminge added the macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews label Oct 11, 2026 — with ChatGPT Codex Connector
@coderabbitai

coderabbitai Bot commented Oct 11, 2026

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Path: .coderabbit.config.ts
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 4336ea39-1a14-43ee-9e1e-cf015748950f

📥 Commits

Reviewing files that changed from the base of the PR and between ed0f7b9 and a8cfb59.


📒 Files selected for processing (5)
  • apps/web/src/components/files/FileBrowserPanel.tsx
  • apps/web/src/components/files/fileTreeExpansion.ts
  • apps/web/src/components/files/fileTreeExpansionStorage.test.ts
  • apps/web/src/components/files/fileTreeExpansionStorage.ts
  • apps/web/src/components/files/useDirectoryEntries.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 4 remain after this review.



📝 Walkthrough

Walkthrough

The file browser now persists expanded directory paths by workspace and working directory. It restores saved paths as directory rows appear, reconciles them with loaded directories, and saves changes to expansion state.

Changes

File-tree expansion persistence

Layer / File(s) Summary
Expansion storage and reconciliation
apps/web/src/components/files/fileTreeExpansion.ts, apps/web/src/components/files/fileTreeExpansionStorage.ts, apps/web/src/components/files/fileTreeExpansionStorage.test.ts
Adds workspace-scoped storage helpers and reconciliation for saved, pending, and currently expanded paths. Tests cover storage limits, restoration, and removal of paths after their parent loads.
File-browser synchronization
apps/web/src/components/files/useDirectoryEntries.ts, apps/web/src/components/files/FileBrowserPanel.tsx
The directory-entry hook returns loaded directory paths. The panel reads saved expansion paths, synchronizes them with the tree, and writes changes to storage.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant FileBrowserPanel
  participant useDirectoryEntries
  participant syncPersistedExpansion
  participant readExpandedPaths
  participant writeExpandedPaths
  FileBrowserPanel->>readExpandedPaths: Load saved paths for the workspace key
  FileBrowserPanel->>useDirectoryEntries: Read directory entries and loaded paths
  useDirectoryEntries-->>FileBrowserPanel: Return loadedDirectories
  FileBrowserPanel->>syncPersistedExpansion: Reconcile paths with directory rows and loaded directories
  syncPersistedExpansion-->>FileBrowserPanel: Return whether persisted paths changed
  FileBrowserPanel->>writeExpandedPaths: Save paths when synchronization changes them
Loading

Suggested reviewers: juliusmarminge


Merge Risk | ⚪ Minimal · up to a8cfb

Merge Risk: ⚪ Minimal · up to a8cfb

The Files tree persistence change is mergeable after normal checks; no actionable issue is established by the supplied evidence.

Security Architecture Review

Security architecture risk: 🔵 Low · up to a8cfb

Saved folder preferences continue to use the current workspace’s existing directory access. Remaining uncertainty concerns workspace-key uniqueness for nonstandard environment identifiers, rather than a demonstrated access-control bypass.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The demonstrated change affects browser-local folder metadata and automatic directory-listing requests within the selected workspace. Saved values do not independently select another environment, introduce filesystem-write authority, or directly invoke arbitrary saved paths.

Trust Boundaries and Controls

  • observed — Browser-controlled storage is decoded as a string array, with unreadable values treated as empty. Matching directory-model items gate restoration, and listing requests retain the current workspace identity. This establishes client-side reachability constraints, not independent verification of downstream server authorization.

Resilience and Maintainability Implications

  • observed — For workspace pairs with distinct component keys, switching workspaces remounts the panel and rereads preferences. The existing directory hook suppresses publication after unmount, deduplicates requests by directory path, and limits concurrent execution to four requests.

Hardening Proposals

  • proposed — Consider unambiguous tuple encoding for workspace storage and component identity. Colon concatenation can collide over the declared identifier contract. New server IDs default to UUIDs, but persisted IDs are accepted as other nonempty strings. The React-key ambiguity predates this PR; this is an isolation-hardening proposal, not a verified authorization finding.

Pre-merge checks | Passed 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check Passed The title is concise, uses the repository's conventional format, and clearly describes the main change: persistent expanded folders in the Files tree.
Description check Passed The description covers the problem, fix, scope, approval reference, verification results, screenshots, recordings, limitations, and agent details. The headings use “Fix” and “Scope” instead of the tem…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants