From 4fec1399271677e0ee171d560becaf61dc95b0d0 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sun, 21 Jun 2026 20:28:33 -0700 Subject: [PATCH] FN-6881: move stash recovery into Git Manager Move stash recovery into the Git Manager while retiring the standalone dashboard route. - Add a Recovery section to Git Manager that hosts the existing StashRecoveryView. - Remove stash recovery from top-level, overflow, mobile, and lazy-loaded view registries. - Preserve orphaned-stash counts on Git Manager entry points and migrate saved stash-recovery routes back to Board. - Update dashboard docs, lazy-load inventory, tests, and add a published package changeset. Files changed: .changeset/fn-6881-stash-recovery-git-manager.md | 5 +++ AGENTS.md | 3 +- docs/dashboard-guide.md | 9 ++-- packages/dashboard/app/App.tsx | 13 ------ .../app/__tests__/lazy-loaded-views-docs.test.ts | 11 ++--- .../dashboard/app/components/GitManagerModal.tsx | 19 +++++++- packages/dashboard/app/components/Header.css | 9 ++++ packages/dashboard/app/components/Header.tsx | 22 +++------- .../dashboard/app/components/LeftSidebarNav.tsx | 4 -- packages/dashboard/app/components/MobileNavBar.tsx | 16 +------ .../components/__tests__/GitManagerModal.test.tsx | 51 ++++++++++++++++++++++ .../app/components/__tests__/Header.test.tsx | 21 +++++++++ .../components/__tests__/LeftSidebarNav.test.tsx | 10 ++--- .../app/components/__tests__/MobileNavBar.test.tsx | 10 +++++ .../app/components/__tests__/RightDock.test.tsx | 4 +- .../__tests__/overflowViewRegistry.test.tsx | 3 +- .../app/components/overflowViewRegistry.tsx | 10 ----- packages/dashboard/app/hooks/__tests__/useViewState.test.ts | 28 ++++++++++++ packages/dashboard/app/hooks/useViewState.ts | 19 +++++++- packages/dashboard/src/view-chunk-manifest.ts | 1 - 20 files changed, 184 insertions(+), 84 deletions(-) Fusion-Task-Id: FN-6881 Fusion-Task-Lineage: 467d34ec-8a6a-46db-8eb8-cb0216c031da --- .../fn-6881-stash-recovery-git-manager.md | 5 ++ AGENTS.md | 3 +- docs/dashboard-guide.md | 9 ++-- packages/dashboard/app/App.tsx | 13 ----- .../__tests__/lazy-loaded-views-docs.test.ts | 11 ++-- .../app/components/GitManagerModal.tsx | 19 ++++++- packages/dashboard/app/components/Header.css | 9 ++++ packages/dashboard/app/components/Header.tsx | 22 ++------ .../app/components/LeftSidebarNav.tsx | 4 -- .../dashboard/app/components/MobileNavBar.tsx | 16 +----- .../__tests__/GitManagerModal.test.tsx | 51 +++++++++++++++++++ .../app/components/__tests__/Header.test.tsx | 21 ++++++++ .../__tests__/LeftSidebarNav.test.tsx | 10 ++-- .../__tests__/MobileNavBar.test.tsx | 10 ++++ .../components/__tests__/RightDock.test.tsx | 4 +- .../__tests__/overflowViewRegistry.test.tsx | 3 +- .../app/components/overflowViewRegistry.tsx | 10 ---- .../app/hooks/__tests__/useViewState.test.ts | 28 ++++++++++ packages/dashboard/app/hooks/useViewState.ts | 19 ++++++- packages/dashboard/src/view-chunk-manifest.ts | 1 - 20 files changed, 184 insertions(+), 84 deletions(-) create mode 100644 .changeset/fn-6881-stash-recovery-git-manager.md diff --git a/.changeset/fn-6881-stash-recovery-git-manager.md b/.changeset/fn-6881-stash-recovery-git-manager.md new file mode 100644 index 0000000000..d1b8473165 --- /dev/null +++ b/.changeset/fn-6881-stash-recovery-git-manager.md @@ -0,0 +1,5 @@ +--- +"@runfusion/fusion": minor +--- + +Move Stash Recovery into the Git Manager Recovery tab and remove the standalone top-level Stash Recovery view from dashboard navigation. diff --git a/AGENTS.md b/AGENTS.md index f68a9512d4..dca5e5bab8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -221,7 +221,7 @@ Scoped exception (FN-5819): shared-branch-group members (`branchContext.assignme ### Lazy-Loaded Heavy Views -These 21 views are lazy-loaded via `React.lazy()` with ``. +These 20 views are lazy-loaded via `React.lazy()` with ``. Keep this AGENTS inventory in sync with App lazy imports, AppModals lazy modal imports (`SettingsModal`, `WorkflowNodeEditor`, `SetupWizardModal`), and `packages/dashboard/app/__tests__/lazy-loaded-views-docs.test.ts`. - `AgentsView` @@ -237,7 +237,6 @@ Keep this AGENTS inventory in sync with App lazy imports, AppModals lazy modal i - `EvalsView` - `TodoView` - `GoalsView` -- `StashRecoveryView` - `PullRequestView` - `SetupWizardModal` - `SettingsModal` diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index 120e8cb64e..92bca0e32e 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -394,6 +394,7 @@ Features: - One-click **Sync** action in Remotes (`git pull --rebase` followed by push; it stops and surfaces an error instead of pushing when the pull conflicts or fails) - Remote editing controls - Stash inspection (view stat + patch) before apply/pop/drop actions +- Stash Recovery tab for orphaned merger-autostashes; orphan counts appear on Git Manager entry points instead of a standalone Stash Recovery view - Remotes tab keeps "Recent commits on {remote}" in sync immediately after successful push/pull actions ![Git manager](./screenshots/git-manager.png) @@ -765,13 +766,13 @@ Features: For module-level behavior and API surfaces, see [Dev Server modules](./dev-server-modules.md). -## Stash Recovery View +## Stash Recovery in Git Manager -Stash Recovery view helps recover orphaned merger autostashes (`fusion-merger-autostash:*`) left behind when merge restore could not fully complete. +Stash Recovery helps recover orphaned merger autostashes (`fusion-merger-autostash:*`) left behind when merge restore could not fully complete. It now lives as the **Recovery** tab in **Git Manager**; the former standalone top-level Stash Recovery view is removed from desktop and mobile navigation. Navigation: -- Desktop: **Header → More views → Stash Recovery** -- Mobile: **More** sheet → **Stash Recovery** +- Desktop: **Git Manager → Recovery** +- Mobile: **More** sheet → **Git Manager → Recovery** Features: - Lists orphaned stash entries grouped by source task ID (or **Unknown source** when unavailable) diff --git a/packages/dashboard/app/App.tsx b/packages/dashboard/app/App.tsx index 5a663bcfd7..c65eadbb3e 100644 --- a/packages/dashboard/app/App.tsx +++ b/packages/dashboard/app/App.tsx @@ -122,7 +122,6 @@ const CommandCenter = lazy(() => import("./components/command-center/CommandCent const DevServerView = lazy(() => import("./components/DevServerView").then((m) => ({ default: m.DevServerView }))); const TodoView = lazy(() => import("./components/TodoView").then((m) => ({ default: m.TodoView }))); const GoalsView = lazy(() => import("./components/GoalsView").then((m) => ({ default: m.GoalsView }))); -const StashRecoveryView = lazy(() => import("./components/StashRecoveryView").then((m) => ({ default: m.StashRecoveryView }))); const PullRequestView = lazy(() => import("./components/PullRequestView").then((m) => ({ default: m.PullRequestView }))); // Warm lazy chunks during browser idle so first navigation to each view is @@ -151,7 +150,6 @@ function prefetchLazyViews() { void import("./components/DevServerView"); void import("./components/TodoView"); void import("./components/GoalsView"); - void import("./components/StashRecoveryView"); void import("./components/PullRequestView"); }); } @@ -1667,16 +1665,6 @@ function AppInner() { ); } - if (taskView === "stash-recovery") { - return ( - - - - - - ); - } - if (taskView === "pull-requests") { return ( @@ -2162,7 +2150,6 @@ function AppInner() { mailboxUnreadCount={mailboxUnreadCount} mailboxPendingApprovalCount={mailboxPendingApprovalCount} chatHasUnreadResponse={chatHasUnreadResponse} - stashOrphanCount={stashOrphanCount} experimentalFeatures={{ insights: insightsEnabled, memoryView: memoryEnabled, diff --git a/packages/dashboard/app/__tests__/lazy-loaded-views-docs.test.ts b/packages/dashboard/app/__tests__/lazy-loaded-views-docs.test.ts index 2addfd5a91..6ae2982d8a 100644 --- a/packages/dashboard/app/__tests__/lazy-loaded-views-docs.test.ts +++ b/packages/dashboard/app/__tests__/lazy-loaded-views-docs.test.ts @@ -11,6 +11,9 @@ FN-6702 removes ReliabilityView from the App-level lazy inventory because Reliab FNXC:CommandCenter 2026-06-19-00:00: FN-6717 removes NodesView from the App-level lazy inventory because Nodes now mounts inside the lazy CommandCenter chunk. + +FNXC:GitManager 2026-06-21-00:00: +FN-6881 removes StashRecoveryView from the App-level lazy inventory because Stash Recovery now mounts through the lazy GitManagerModal chunk. */ import { describe, expect, it } from "vitest"; import { readFileSync } from "node:fs"; @@ -30,7 +33,6 @@ const EXPECTED_DOCUMENTED_VIEWS = new Set([ "EvalsView", "TodoView", "GoalsView", - "StashRecoveryView", "PullRequestView", "SetupWizardModal", "SettingsModal", @@ -54,7 +56,6 @@ const EXPECTED_APP_LEVEL_VIEWS = new Set([ "DevServerView", "TodoView", "GoalsView", - "StashRecoveryView", "PullRequestView", ]); @@ -99,7 +100,7 @@ function extractAppModalsLazyViews(appModalsSource: string): Set { } describe("AGENTS lazy-loaded views inventory", () => { - it("documents the App-level and AppModals lazy views accurately and keeps the curated 21-view list in sync", () => { + it("documents the App-level and AppModals lazy views accurately and keeps the curated 20-view list in sync", () => { const agentsDoc = readFileSync(resolve(__dirname, "../../../../AGENTS.md"), "utf-8"); const appSource = readFileSync(resolve(__dirname, "../App.tsx"), "utf-8"); const appModalsSource = readFileSync(resolve(__dirname, "../components/AppModals.tsx"), "utf-8"); @@ -107,11 +108,11 @@ describe("AGENTS lazy-loaded views inventory", () => { const section = extractLazyLoadedSection(agentsDoc); const countMatch = section.match(/These\s+(\d+)\s+views\s+are lazy-loaded/); expect(countMatch).toBeTruthy(); - expect(Number(countMatch?.[1])).toBe(21); + expect(Number(countMatch?.[1])).toBe(20); const documentedViews = extractBacktickedNamesFromBullets(section); expect(new Set(documentedViews)).toEqual(EXPECTED_DOCUMENTED_VIEWS); - expect(documentedViews).toHaveLength(21); + expect(documentedViews).toHaveLength(20); expect(section).toContain("`ResearchView`"); expect(section).toContain("`TodoView`"); diff --git a/packages/dashboard/app/components/GitManagerModal.tsx b/packages/dashboard/app/components/GitManagerModal.tsx index a99df3b6c3..e5b750e383 100644 --- a/packages/dashboard/app/components/GitManagerModal.tsx +++ b/packages/dashboard/app/components/GitManagerModal.tsx @@ -58,6 +58,7 @@ import { fetchRemoteCommits, fetchBranchCommits, } from "../api"; +import { StashRecoveryView } from "./StashRecoveryView"; import { GitBranch as GitBranchIcon, GitCommit as GitCommitIcon, @@ -91,11 +92,12 @@ import { Send, Pencil, Info, + History, } from "lucide-react"; // ── Types & Constants ───────────────────────────────────────────── -type SectionId = "status" | "changes" | "commits" | "branches" | "worktrees" | "stashes" | "remotes"; +type SectionId = "status" | "changes" | "commits" | "branches" | "worktrees" | "stashes" | "recovery" | "remotes"; const SECTIONS: { id: SectionId; label: string; icon: React.ComponentType<{ size?: number }> }[] = [ @@ -105,6 +107,11 @@ const SECTIONS: { id: SectionId; label: string; icon: React.ComponentType<{ size { id: "branches", label: "Branches", icon: GitBranchIcon }, { id: "worktrees", label: "Worktrees", icon: HardDrive }, { id: "stashes", label: "Stashes", icon: Archive }, + /* + FNXC:GitManager 2026-06-21-00:00: + FN-6881 re-homes orphaned-autostash Stash Recovery from a standalone top-level view into a Git Manager section so users have one canonical recovery destination while the /stash-recovery API remains unchanged. + */ + { id: "recovery", label: "Recovery", icon: History }, { id: "remotes", label: "Remotes", icon: GitMerge }, ]; @@ -333,6 +340,10 @@ export function GitManagerModal({ isOpen, onClose, tasks: _tasks, addToast, proj stashDiffRequestIdRef.current += 1; break; } + case "recovery": { + // StashRecoveryView self-fetches /stash-recovery/orphans; this branch exists so selecting Recovery clears the modal loading state without issuing an unrelated git status request. + break; + } case "remotes": { const remoteStatus = await fetchGitStatus(projectId, { extended: true }); setStatus(remoteStatus); @@ -938,6 +949,7 @@ export function GitManagerModal({ isOpen, onClose, tasks: _tasks, addToast, proj branches: t("git.sectionBranches", "Branches"), worktrees: t("git.sectionWorktrees", "Worktrees"), stashes: t("git.sectionStashes", "Stashes"), + recovery: t("git.sectionRecovery", "Recovery"), remotes: t("git.sectionRemotes", "Remotes"), }[section.id] ?? section.label; return ( @@ -1076,6 +1088,11 @@ export function GitManagerModal({ isOpen, onClose, tasks: _tasks, addToast, proj /> )} + {/* ── Recovery Panel ── */} + {activeSection === "recovery" && !loading && ( + + )} + {/* ── Remotes Panel ── */} {activeSection === "remotes" && !loading && ( )} - - {experimentalFeatures?.researchView && ( )} @@ -1600,13 +1587,14 @@ export function Header({ {/* Git Manager - in overflow on mobile */} {onOpenGitManager && ( )} {!isDesktopShell && ( diff --git a/packages/dashboard/app/components/LeftSidebarNav.tsx b/packages/dashboard/app/components/LeftSidebarNav.tsx index 7c3a1d44a6..8ca7077a9f 100644 --- a/packages/dashboard/app/components/LeftSidebarNav.tsx +++ b/packages/dashboard/app/components/LeftSidebarNav.tsx @@ -13,7 +13,6 @@ import { ChevronRight, FileText, Gauge, - History, Lightbulb, LayoutGrid, List, @@ -108,7 +107,6 @@ export interface LeftSidebarNavProps { mailboxUnreadCount?: number; mailboxPendingApprovalCount?: number; chatHasUnreadResponse?: boolean; - stashOrphanCount?: number; experimentalFeatures?: LeftSidebarExperimentalFeatures; pluginDashboardViews?: PluginDashboardViewEntry[]; showAgentsTab?: boolean; @@ -156,7 +154,6 @@ export function LeftSidebarNav({ mailboxUnreadCount = 0, mailboxPendingApprovalCount = 0, chatHasUnreadResponse = false, - stashOrphanCount = 0, experimentalFeatures, pluginDashboardViews = [], showAgentsTab = false, @@ -355,7 +352,6 @@ export function LeftSidebarNav({ ...(experimentalFeatures?.goalsView ? [{ id: "goals", label: t("header.goalsView", "Goals"), view: "goalsView" as TaskView, isActive: view === "goalsView", icon: Target, testId: "sidebar-nav-goals", onSelect: () => onChangeView("goalsView") }] : []), - { id: "stash-recovery", label: t("header.stashRecoveryView", "Stash Recovery"), view: "stash-recovery", isActive: view === "stash-recovery", icon: History, testId: "sidebar-nav-stash-recovery", badge: stashOrphanCount > 0 ? stashOrphanCount : undefined, onSelect: () => onChangeView("stash-recovery") }, ...(experimentalFeatures?.researchView ? [{ id: "research", label: t("header.researchView", "Research"), view: "research" as TaskView, isActive: view === "research", icon: Search, testId: "sidebar-nav-research", onSelect: () => onChangeView("research") }] : []), diff --git a/packages/dashboard/app/components/MobileNavBar.tsx b/packages/dashboard/app/components/MobileNavBar.tsx index c76b5fc28e..7a6ae75c95 100644 --- a/packages/dashboard/app/components/MobileNavBar.tsx +++ b/packages/dashboard/app/components/MobileNavBar.tsx @@ -13,7 +13,6 @@ import { Gauge, GitBranch, Grid3X3, - History, LayoutGrid, Lightbulb, Loader2, @@ -298,7 +297,6 @@ export function MobileNavBar({ || (view === "todos" && todoViewEnabled) || (view === "skills" && !showSkillsTopLevel) || view === "graph" - || view === "stash-recovery" || (isPluginViewId(view) && !topLevelPrimaryPluginViews.some((entry) => buildPluginTaskViewId(entry.pluginId, entry.view.viewId) === view)); return ( @@ -502,6 +500,7 @@ export function MobileNavBar({ > {t("nav.gitManager", "Git Manager")} + {stashOrphanCount > 0 ? {formatCount(stashOrphanCount)} : null}
@@ -709,19 +708,6 @@ export function MobileNavBar({ )} - - - - {experimentalFeatures?.researchView && (