Skip to content

fix(web): scope recent files to the browse revision - #1686

Open
dipeshbabu wants to merge 3 commits into
sourcebot-dev:mainfrom
dipeshbabu:dipeshbabu/fix-browse-recents-1387
Open

dipeshbabu wants to merge 3 commits into
sourcebot-dev:mainfrom
dipeshbabu:dipeshbabu/fix-browse-recents-1387

Conversation

@dipeshbabu

@dipeshbabu dipeshbabu commented Sep 24, 2026 •

Copy link
Copy Markdown

Fixes #1387

Opening file search after changing branches could show recently opened files from another revision and navigate to paths that do not exist on the current branch. Store recent files by repository and revision, with omitted revisions sharing the explicit HEAD history.

The storage key encodes the pair without separator collisions. Existing repository-only history is left untouched and is not imported because its revision is unknown. Regression tests cover switching revisions, restoring history after remounting, default HEAD, legacy storage, and distinct repository/revision pairs.

Validation completed before opening this PR:


Note

Low Risk
Client-only browse UI localStorage key change with no auth or server impact; users may see empty recents until they reopen files on each revision.

Overview
Fixes browse file search showing recently opened entries from another branch/revision after you switch refs, which could send navigation to paths that do not exist on the current revision.

Recent history is now stored in localStorage under a key that includes both repository and revision (revisionName, or HEAD when omitted so default and explicit HEAD share one list). Legacy keys that only keyed by repo are not migrated because their revision is unknown. New Vitest coverage exercises revision switching, remount persistence, HEAD aliasing, legacy storage, and repo/revision names that could collide with naive string joining.

Reviewed by Cursor Bugbot for commit ab293d7. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Fixes #1387 by scoping recently opened files to the current repository and revision, so switching branches no longer surfaces or navigates to files from another revision.

  • Recent file history is now stored under a key that encodes both repoName and revisionName, with omitted revisions mapped to HEAD so they share history.
  • Legacy repository-only history is left untouched because its revision is unknown.
  • Regression tests cover revision switching, remounts, default HEAD, legacy storage, and separator collisions.

Written for commit ab293d7. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes
    • Recently opened files are now kept separate for each repository revision, so files opened in one revision no longer appear in another.

@coderabbitai

coderabbitai Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 61136cd3-fd5b-4ad6-99f6-1ccf827e0694

📥 Commits

Reviewing files that changed from the base of the PR and between b493151 and ab293d7.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • packages/web/src/app/(app)/browse/components/fileSearchCommandDialog.test.tsx
  • packages/web/src/app/(app)/browse/components/fileSearchCommandDialog.tsx

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.


Walkthrough

File search recents are now stored by repository and revision. A missing revision uses HEAD. Tests cover revision-specific history, restoration after remounting, legacy entries, and distinct repository/revision pairs.

Changes

File search recents

Layer / File(s) Summary
Scope recent-file history by revision
packages/web/src/app/(app)/browse/components/fileSearchCommandDialog.tsx, packages/web/src/app/(app)/browse/components/fileSearchCommandDialog.test.tsx, CHANGELOG.md
The local storage key includes the repository and revision, with HEAD used when no revision is set. Tests cover history separation and restoration, default and explicit HEAD, legacy entries, and separator characters. The changelog records the fix.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Low

Suggested reviewers: brendan-kellam

Merge Risk: ⚪ Minimal · up to ab293

Recent files are scoped to the browse revision, and the supplied evidence identifies no issue requiring a fix before merge.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: recently opened files are scoped to the browse revision.
Linked Issues check ✅ Passed The implementation satisfies issue #1387. FileSearchCommandDialog stores recents under a key containing repoName and revisionName, with null revisions normalized to HEAD. This separates histor…
Out of Scope Changes check ✅ Passed The changes stay within issue #1387. The changelog entry documents the fix. The new tests verify the revision-scoped recent-file behavior. No unrelated product behavior or unrelated code change is sho…
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 2…
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

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

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/web/src/app/(app)/browse/components/fileSearchCommandDialog.tsx">

<violation number="1" location="packages/web/src/app/(app)/browse/components/fileSearchCommandDialog.tsx:39">
P3: Each distinct (repo, revision) pair — including every commit SHA the user browses at — permanently creates a new localStorage entry that is never evicted, and legacy `recentlyOpenedFiles-<repo>` keys are intentionally left in place too. Storage clutter grows without bound over time for active browsing. Consider capping the recents array length and pruning keys for revisions no longer reachable, e.g. store one entry per repo holding a `Map`/object of revision → files instead of one key per revision.</violation>

<violation number="2" location="packages/web/src/app/(app)/browse/components/fileSearchCommandDialog.tsx:39">
P2: Scoping the key by revision closes the cross-revision case, but the "Recently opened" list is still rendered as a stored snapshot without validating any entry against the files that currently exist at that revision (`recentlyOpened.map(...)` runs with no cross-check against `files`). If the revision's content moved since the file was opened — a branch advanced and deleted/renamed the path — selecting the entry calls `navigateToPath` to a blob that no longer exists, which is the same end-user failure mode #1387 describes, just narrowed to the matching key. Filter `recentlyOpened` against the current `files` (or remove missing paths on read), e.g. `recentlyOpened.filter(r => files.some(f => f.path === r.path))`.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic


const [recentlyOpened, setRecentlyOpened] = useLocalStorage<FileTreeItem[]>(`recentlyOpenedFiles-${repoName}`, []);
const [recentlyOpened, setRecentlyOpened] = useLocalStorage<FileTreeItem[]>(
`recentlyOpenedFiles-${JSON.stringify([repoName, revisionName ?? 'HEAD'])}`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Scoping the key by revision closes the cross-revision case, but the "Recently opened" list is still rendered as a stored snapshot without validating any entry against the files that currently exist at that revision (recentlyOpened.map(...) runs with no cross-check against files). If the revision's content moved since the file was opened — a branch advanced and deleted/renamed the path — selecting the entry calls navigateToPath to a blob that no longer exists, which is the same end-user failure mode #1387 describes, just narrowed to the matching key. Filter recentlyOpened against the current files (or remove missing paths on read), e.g. recentlyOpened.filter(r => files.some(f => f.path === r.path)).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/web/src/app/(app)/browse/components/fileSearchCommandDialog.tsx, line 39:

<comment>Scoping the key by revision closes the cross-revision case, but the "Recently opened" list is still rendered as a stored snapshot without validating any entry against the files that currently exist at that revision (`recentlyOpened.map(...)` runs with no cross-check against `files`). If the revision's content moved since the file was opened — a branch advanced and deleted/renamed the path — selecting the entry calls `navigateToPath` to a blob that no longer exists, which is the same end-user failure mode #1387 describes, just narrowed to the matching key. Filter `recentlyOpened` against the current `files` (or remove missing paths on read), e.g. `recentlyOpened.filter(r => files.some(f => f.path === r.path))`.</comment>

<file context>
@@ -35,7 +35,10 @@ export const FileSearchCommandDialog = () => {
 
-    const [recentlyOpened, setRecentlyOpened] = useLocalStorage<FileTreeItem[]>(`recentlyOpenedFiles-${repoName}`, []);
+    const [recentlyOpened, setRecentlyOpened] = useLocalStorage<FileTreeItem[]>(
+        `recentlyOpenedFiles-${JSON.stringify([repoName, revisionName ?? 'HEAD'])}`,
+        [],
+    );
</file context>


const [recentlyOpened, setRecentlyOpened] = useLocalStorage<FileTreeItem[]>(`recentlyOpenedFiles-${repoName}`, []);
const [recentlyOpened, setRecentlyOpened] = useLocalStorage<FileTreeItem[]>(
`recentlyOpenedFiles-${JSON.stringify([repoName, revisionName ?? 'HEAD'])}`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Each distinct (repo, revision) pair — including every commit SHA the user browses at — permanently creates a new localStorage entry that is never evicted, and legacy recentlyOpenedFiles-<repo> keys are intentionally left in place too. Storage clutter grows without bound over time for active browsing. Consider capping the recents array length and pruning keys for revisions no longer reachable, e.g. store one entry per repo holding a Map/object of revision → files instead of one key per revision.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/web/src/app/(app)/browse/components/fileSearchCommandDialog.tsx, line 39:

<comment>Each distinct (repo, revision) pair — including every commit SHA the user browses at — permanently creates a new localStorage entry that is never evicted, and legacy `recentlyOpenedFiles-<repo>` keys are intentionally left in place too. Storage clutter grows without bound over time for active browsing. Consider capping the recents array length and pruning keys for revisions no longer reachable, e.g. store one entry per repo holding a `Map`/object of revision → files instead of one key per revision.</comment>

<file context>
@@ -35,7 +35,10 @@ export const FileSearchCommandDialog = () => {
 
-    const [recentlyOpened, setRecentlyOpened] = useLocalStorage<FileTreeItem[]>(`recentlyOpenedFiles-${repoName}`, []);
+    const [recentlyOpened, setRecentlyOpened] = useLocalStorage<FileTreeItem[]>(
+        `recentlyOpenedFiles-${JSON.stringify([repoName, revisionName ?? 'HEAD'])}`,
+        [],
+    );
</file context>

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

File search recents are shared across browse revisions

1 participant