FN-7649: resolve settings landing view to board on project switch
Fixes project-switch hydration so a persisted "settings" view resolves to the Board instead of re-opening Settings. - Extend resolveLandingTaskView() in useViewState.ts to treat "settings" the same as "command-center", resolving both to "board" for the auto-restored/hydrated landing view only - Add regression tests covering the settings->board landing resolution in useViewState.test.ts - Add changeset documenting the patch-level fix Files changed: .changeset/fn-7649-project-switch-board-landing.md | 7 ++ .../app/hooks/__tests__/useViewState.test.ts | 74 ++++++++++++++++++++++ packages/dashboard/app/hooks/useViewState.ts | 5 +- 3 files changed, 85 insertions(+), 1 deletion(-) Fusion-Task-Id: FN-7649 Fusion-Task-Lineage: 7179efd6-bb1b-4ea4-a062-479b9b1fffa3 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-7649-project-switch-board-landing.md
Normal file
7
.changeset/fn-7649-project-switch-board-landing.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Switching projects now lands on the Board instead of Settings when the last-visited view was Settings.
|
||||||
|
category: fix
|
||||||
|
dev: Extended resolveLandingTaskView in useViewState.ts to resolve "settings" (in addition to "command-center") to "board" for the auto-restored/hydrated landing view only; deep links (?view=settings) and explicit navigation still open Settings.
|
||||||
@@ -77,6 +77,55 @@ describe("useViewState", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// FNXC:ViewState FN-7649: Persisted Settings must not be the auto-restored landing view either; it lands on the Board instead.
|
||||||
|
it("lands on board when the persisted taskView is settings", async () => {
|
||||||
|
localStorage.setItem("kb-dashboard-task-view", "settings");
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useViewState(createOptions()));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.taskView).toBe("board");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("?view=settings deep link still opens Settings (not routed through the landing guard)", async () => {
|
||||||
|
const originalSearch = window.location.search;
|
||||||
|
window.history.replaceState({}, "", "?view=settings");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { result } = renderHook(() => useViewState(createOptions()));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.taskView).toBe("settings");
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
window.history.replaceState({}, "", originalSearch ? `?${originalSearch.replace(/^\?/, "")}` : "/");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("explicit setTaskView/handleChangeTaskView to settings still opens Settings", async () => {
|
||||||
|
const { result } = renderHook(() => useViewState(createOptions()));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.taskView).toBe("board");
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
result.current.setTaskView("settings");
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.current.taskView).toBe("settings");
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
result.current.handleChangeTaskView("board");
|
||||||
|
});
|
||||||
|
await act(async () => {
|
||||||
|
result.current.handleChangeTaskView("settings");
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.current.taskView).toBe("settings");
|
||||||
|
});
|
||||||
|
|
||||||
it("migrates legacy reliability taskView from localStorage to Command Center", async () => {
|
it("migrates legacy reliability taskView from localStorage to Command Center", async () => {
|
||||||
localStorage.setItem("kb-dashboard-task-view", "reliability");
|
localStorage.setItem("kb-dashboard-task-view", "reliability");
|
||||||
|
|
||||||
@@ -578,6 +627,31 @@ describe("useViewState", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// FNXC:ViewState FN-7649: Switching projects (rerender with a new currentProject) must not land on Settings when the newly selected project's scoped persisted view is settings; it resolves to board. Mirrors the existing command-center project-switch coverage above.
|
||||||
|
it("lands on board when switching to a project whose persisted scoped taskView is settings", async () => {
|
||||||
|
const projectA: ProjectInfo = { ...PROJECT, id: "proj_a", name: "Project A" };
|
||||||
|
const projectB: ProjectInfo = { ...PROJECT, id: "proj_b", name: "Project B" };
|
||||||
|
|
||||||
|
localStorage.setItem("kb:proj_a:kb-dashboard-task-view", "list");
|
||||||
|
localStorage.setItem("kb:proj_b:kb-dashboard-task-view", "settings");
|
||||||
|
|
||||||
|
const { result, rerender } = renderHook(
|
||||||
|
({ project }) => useViewState(createOptions({ currentProject: project })),
|
||||||
|
{ initialProps: { project: projectA } },
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.taskView).toBe("list");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Switch to project B, whose persisted view is settings - must land on board, not settings.
|
||||||
|
rerender({ project: projectB });
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.current.taskView).toBe("board");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("no cross-project bleed when switching projects", async () => {
|
it("no cross-project bleed when switching projects", async () => {
|
||||||
const projectA: ProjectInfo = { ...PROJECT, id: "proj_a", name: "Project A" };
|
const projectA: ProjectInfo = { ...PROJECT, id: "proj_a", name: "Project A" };
|
||||||
const projectB: ProjectInfo = { ...PROJECT, id: "proj_b", name: "Project B" };
|
const projectB: ProjectInfo = { ...PROJECT, id: "proj_b", name: "Project B" };
|
||||||
|
|||||||
@@ -76,9 +76,12 @@ function normalizeTaskView(value: TaskView): TaskView {
|
|||||||
/*
|
/*
|
||||||
FNXC:ViewState 2026-06-22-15:30:
|
FNXC:ViewState 2026-06-22-15:30:
|
||||||
Fusion must land on the Board on load, never the Command Center "Dashboard" view. A persisted/normalized `command-center` value resolves to `board` for the auto-restored landing view only (initializer + project-hydration effect). Deep links (`?view=command-center`) and explicit user navigation still reach the Command Center — this only governs the restored landing surface.
|
Fusion must land on the Board on load, never the Command Center "Dashboard" view. A persisted/normalized `command-center` value resolves to `board` for the auto-restored landing view only (initializer + project-hydration effect). Deep links (`?view=command-center`) and explicit user navigation still reach the Command Center — this only governs the restored landing surface.
|
||||||
|
|
||||||
|
FNXC:ViewState 2026-07-07-00:00:
|
||||||
|
FN-7649: an auto-restored/hydrated landing surface must never be Settings either. Once a project's per-project persisted `kb-dashboard-task-view` becomes `settings` (a common state after configuring a project through Settings), switching projects re-hydrates that scoped value and — without this guard — restores straight to Settings instead of the Board. `settings`, like `command-center`, now resolves to `board` for the auto-restored landing view only (initializer + project-hydration effect). Deep links (`?view=settings`) and explicit header/sidebar navigation to Settings still work because the `?view=` URL effect and `setTaskView`/`handleChangeTaskView` paths use `normalizeTaskView`, not this guard.
|
||||||
*/
|
*/
|
||||||
function resolveLandingTaskView(value: TaskView): TaskView {
|
function resolveLandingTaskView(value: TaskView): TaskView {
|
||||||
return value === "command-center" ? "board" : value;
|
return value === "command-center" || value === "settings" ? "board" : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function migrateLegacyRoadmapsView(value: string): TaskView {
|
function migrateLegacyRoadmapsView(value: string): TaskView {
|
||||||
|
|||||||
Reference in New Issue
Block a user