diff --git a/.changeset/fn-7252-file-browser-worktree-reload.md b/.changeset/fn-7252-file-browser-worktree-reload.md
new file mode 100644
index 0000000000..9d3649ec96
--- /dev/null
+++ b/.changeset/fn-7252-file-browser-worktree-reload.md
@@ -0,0 +1,7 @@
+---
+"@runfusion/fusion": patch
+---
+
+summary: Reload the selected file when switching Files modal worktrees.
+category: fix
+dev: Keeps Files modal selected-path state while changing workspace/worktree.
diff --git a/packages/dashboard/app/components/FileBrowserModal.tsx b/packages/dashboard/app/components/FileBrowserModal.tsx
index b99be00992..547e29ba22 100644
--- a/packages/dashboard/app/components/FileBrowserModal.tsx
+++ b/packages/dashboard/app/components/FileBrowserModal.tsx
@@ -228,12 +228,27 @@ export function FileBrowserModal({
setContent(originalContent);
}, [originalContent, setContent]);
+ /*
+ FNXC:FileBrowser 2026-06-29-00:00:
+ Worktree switches must keep the last selected file path so users can recover from a load error caused by viewing the right path in the wrong workspace. The raw browser hook still resets each new workspace to root, so the modal re-points the sidebar to the selected file's parent directory after that reset while mobile keeps the editor pane active.
+ */
+ const previousWorkspaceRef = useRef(currentWorkspace);
+ useEffect(() => {
+ const workspaceChanged = previousWorkspaceRef.current !== currentWorkspace;
+ if (workspaceChanged && selectedFile) {
+ setPath(getParentDirectory(selectedFile));
+ if (isMobile) {
+ setMobileView("editor");
+ }
+ }
+ previousWorkspaceRef.current = currentWorkspace;
+ }, [currentWorkspace, isMobile, selectedFile, setPath]);
+
const handleWorkspaceSelect = useCallback((workspace: string) => {
setCurrentWorkspace(workspace);
- setSelectedFile(null);
- setMobileView("list");
+ setMobileView(selectedFile ? "editor" : "list");
onWorkspaceChange?.(workspace);
- }, [onWorkspaceChange]);
+ }, [onWorkspaceChange, selectedFile]);
const persistSidebarWidth = useCallback((width: number) => {
try {
diff --git a/packages/dashboard/app/components/__tests__/FileBrowserModal.test.tsx b/packages/dashboard/app/components/__tests__/FileBrowserModal.test.tsx
index c833990b06..6f8f632316 100644
--- a/packages/dashboard/app/components/__tests__/FileBrowserModal.test.tsx
+++ b/packages/dashboard/app/components/__tests__/FileBrowserModal.test.tsx
@@ -230,6 +230,159 @@ describe("FileBrowserModal", () => {
expect(mockOnWorkspaceChange).toHaveBeenCalledWith("FN-002");
});
+ it("keeps an errored nested selected file open on desktop after switching workspaces", async () => {
+ const user = userEvent.setup();
+ const nestedFile = "packages/dashboard/app/App.tsx";
+ mockUseWorkspaceFileEditor.mockReturnValue({
+ ...defaultEditorState,
+ error: "Could not load App.tsx",
+ });
+
+ render(
+ ,
+ );
+
+ await waitFor(() => expect(screen.getByLabelText(`Editor for ${nestedFile}`)).toBeInTheDocument());
+ expect(screen.getByText("Could not load App.tsx")).toBeInTheDocument();
+ mockSetPath.mockClear();
+
+ await user.click(screen.getByRole("button", { name: /kb/i }));
+ await user.click(screen.getByRole("button", { name: /FN-002 Task Two/i }));
+
+ await waitFor(() => {
+ expect(mockUseWorkspaceFileEditor).toHaveBeenLastCalledWith("FN-002", nestedFile, true, undefined);
+ });
+ expect(screen.getByLabelText(`Editor for ${nestedFile}`)).toBeInTheDocument();
+ expect(screen.getByText("Could not load App.tsx")).toBeInTheDocument();
+ expect(document.querySelector(".file-browser-content.mobile")).toBeNull();
+ expect(mockSetPath).toHaveBeenCalledWith("packages/dashboard/app");
+ expect(mockOnWorkspaceChange).toHaveBeenCalledWith("FN-002");
+ });
+
+ it("replaces an errored selected file with newly loaded content after switching workspaces", async () => {
+ const user = userEvent.setup();
+ const nestedFile = "packages/dashboard/app/App.tsx";
+ mockUseWorkspaceFileEditor.mockImplementation((workspace, filePath) => {
+ if (workspace === "FN-002" && filePath === nestedFile) {
+ return {
+ ...defaultEditorState,
+ content: "console.log('loaded from task worktree');",
+ originalContent: "console.log('loaded from task worktree');",
+ error: null,
+ };
+ }
+
+ if (filePath === nestedFile) {
+ return {
+ ...defaultEditorState,
+ content: "",
+ originalContent: "",
+ error: "Could not load App.tsx",
+ };
+ }
+
+ return defaultEditorState;
+ });
+
+ render(
+ ,
+ );
+
+ await waitFor(() => expect(screen.getByText("Could not load App.tsx")).toBeInTheDocument());
+ mockSetPath.mockClear();
+
+ await user.click(screen.getByRole("button", { name: /kb/i }));
+ await user.click(screen.getByRole("button", { name: /FN-002 Task Two/i }));
+
+ await waitFor(() => {
+ expect(mockUseWorkspaceFileEditor).toHaveBeenLastCalledWith("FN-002", nestedFile, true, undefined);
+ });
+ expect(screen.queryByText("Could not load App.tsx")).not.toBeInTheDocument();
+ expect(document.querySelector(".cm-content")?.textContent).toContain("console.log('loaded from task worktree');");
+ expect(mockSetPath).toHaveBeenCalledWith("packages/dashboard/app");
+ });
+
+ it("keeps mobile on the errored nested selected file after switching workspaces", async () => {
+ Object.defineProperty(window, "innerWidth", {
+ writable: true,
+ configurable: true,
+ value: 375,
+ });
+ const user = userEvent.setup();
+ const nestedFile = "packages/dashboard/app/App.tsx";
+ mockUseWorkspaceFileEditor.mockReturnValue({
+ ...defaultEditorState,
+ error: "Could not load App.tsx",
+ });
+
+ render(
+ ,
+ );
+
+ fireEvent(window, new Event("resize"));
+ await waitFor(() => expect(screen.getByLabelText("Back to file list")).toBeInTheDocument());
+ expect(screen.getByLabelText(`Editor for ${nestedFile}`)).toBeInTheDocument();
+ mockSetPath.mockClear();
+
+ await user.click(screen.getByRole("button", { name: /kb/i }));
+ await user.click(screen.getByRole("button", { name: /FN-002 Task Two/i }));
+
+ await waitFor(() => {
+ expect(mockUseWorkspaceFileEditor).toHaveBeenLastCalledWith("FN-002", nestedFile, true, undefined);
+ });
+ expect(screen.getByLabelText("Back to file list")).toBeInTheDocument();
+ expect(screen.getByLabelText(`Editor for ${nestedFile}`)).toBeInTheDocument();
+ expect(document.querySelector(".file-browser-content.mobile.active")).not.toBeNull();
+ expect(document.querySelector(".file-browser-sidebar.mobile.active")).toBeNull();
+ expect(mockSetPath).toHaveBeenCalledWith("packages/dashboard/app");
+ expect(mockOnWorkspaceChange).toHaveBeenCalledWith("FN-002");
+ });
+
+ it("keeps the placeholder after switching workspaces with no selected file", async () => {
+ const user = userEvent.setup();
+
+ render(
+ ,
+ );
+
+ expect(screen.getByText("Select a file to edit")).toBeInTheDocument();
+ mockUseWorkspaceFileEditor.mockClear();
+
+ await user.click(screen.getByRole("button", { name: /kb/i }));
+ await user.click(screen.getByRole("button", { name: /FN-002 Task Two/i }));
+
+ await waitFor(() => {
+ expect(mockUseWorkspaceFileEditor).toHaveBeenLastCalledWith("FN-002", null, true, undefined);
+ });
+ expect(screen.getByText("Select a file to edit")).toBeInTheDocument();
+ expect(screen.queryByLabelText(/Editor for /)).not.toBeInTheDocument();
+ expect(mockSetPath).not.toHaveBeenCalledWith(expect.stringContaining("packages/dashboard/app"));
+ expect(mockOnWorkspaceChange).toHaveBeenCalledWith("FN-002");
+ });
+
it("shows back button in mobile editor view", async () => {
Object.defineProperty(window, "innerWidth", {
writable: true,
diff --git a/packages/dashboard/app/hooks/useWorkspaceFileBrowser.ts b/packages/dashboard/app/hooks/useWorkspaceFileBrowser.ts
index 58419c165f..062beadfa6 100644
--- a/packages/dashboard/app/hooks/useWorkspaceFileBrowser.ts
+++ b/packages/dashboard/app/hooks/useWorkspaceFileBrowser.ts
@@ -39,6 +39,10 @@ export function useWorkspaceFileBrowser(
setError(null);
}, []);
+ /*
+ FNXC:FileBrowser 2026-06-29-19:35:
+ Workspace file pickers must start each workspace at root so SettingsModal directory/file pickers do not inherit editor selection state. FileBrowserModal restores its selected file path at the modal layer when it needs editor persistence across worktree switches.
+ */
useEffect(() => {
setCurrentPath(".");
setError(null);