FN-6702: move Reliability into Command Center

Move Reliability from standalone dashboard navigation into Command Center while preserving legacy entry points.

- Remove the top-level Reliability view from desktop and mobile navigation.
- Add Reliability as a Command Center subtab backed by the existing ReliabilityView.
- Migrate persisted and linked legacy reliability view values to Command Center.
- Update docs and regression coverage for the new navigation surface.

Files changed:
 AGENTS.md                                          |  3 +-
 docs/dashboard-guide.md                            |  9 ++--
 packages/dashboard/app/App.tsx                     | 12 -----
 .../app/__tests__/lazy-loaded-views-docs.test.ts   | 11 +++--
 .../mobile-feature-access-regression.test.tsx      | 17 ++-----
 packages/dashboard/app/components/Header.tsx       | 14 +-----
 packages/dashboard/app/components/MobileNavBar.tsx | 11 -----
 .../components/command-center/CommandCenter.tsx    |  8 ++++
 .../__tests__/CommandCenter.test.tsx               | 54 +++++++++++++++++++++-
 .../app/hooks/__tests__/useViewState.test.ts       | 44 ++++++++++++++++++
 packages/dashboard/app/hooks/useViewState.ts       | 23 +++++++--
 packages/i18n/locales/en/app.json                  |  1 +
 12 files changed, 141 insertions(+), 66 deletions(-)

Fusion-Task-Id: FN-6702
Fusion-Task-Lineage: 2d1ca07c-9619-438d-b09e-73fe124a5ffb
This commit is contained in:
gsxdsm
2026-06-19 05:50:13 -07:00
parent 6ff930e2f2
commit c9eff71de9
12 changed files with 141 additions and 66 deletions

View File

@@ -217,7 +217,7 @@ Scoped exception (FN-5819): shared-branch-group members (`branchContext.assignme
### Lazy-Loaded Heavy Views
These 23 views are lazy-loaded via `React.lazy()` with `<Suspense fallback={null}>`.
These 22 views are lazy-loaded via `React.lazy()` with `<Suspense fallback={null}>`.
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`
@@ -230,7 +230,6 @@ Keep this AGENTS inventory in sync with App lazy imports, AppModals lazy modal i
- `DocumentsView`
- `SkillsView`
- `ResearchView`
- `ReliabilityView`
- `CommandCenter`
- `EvalsView`
- `TodoView`

View File

@@ -684,7 +684,7 @@ Rendering invariants:
- The dashboard browser-layout smoke includes a `[data-smoke="command-center-charts"]` fixture that loads emitted lazy Command Center CSS and verifies representative recharts pie, line, and empty states at mobile (390×844) and desktop breakpoints. The fixture asserts non-zero chart and SVG heights, visible empty-state text, no internal/page horizontal overflow, and no chart-level vertical scroll owner before chart layout changes are considered verified.
Data states:
- Overview shows a loading state while core analytics settle, then shows `No usage data yet. Run some agents to populate the Command Center.` only after the selected range has settled with no core usage data. Overview, Tokens, Tools, Activity, Productivity, Team, Ecosystem, GitHub, Signals, and System omit their additive recharts cards in loading/error/empty states, so non-populated data never leaves an empty chart shell.
- Overview shows a loading state while core analytics settle, then shows `No usage data yet. Run some agents to populate the Command Center.` only after the selected range has settled with no core usage data. Overview, Tokens, Tools, Activity, Productivity, Team, Ecosystem, GitHub, Signals, System, and Reliability omit their additive recharts cards in loading/error/empty states, so non-populated data never leaves an empty chart shell.
- GitHub issue analytics is local and additive: empty filed/fixed totals keep the stat cards and historical backfill button available while omitting empty chart shells; malformed historical `githubTracking` JSON is skipped instead of breaking the Command Center.
- Team analytics renders its shared loading/error/empty states for null or zero-agent responses, omits empty chart shells for zero-value datasets, and keeps the Command Center tab panel as the mobile scroll owner.
- System telemetry keeps the previous snapshot visible during refresh failures, renders a first-sample CPU `Sampling…` state without NaN values, shows zero-value task/agent bars for empty collections while omitting the zero-value task-distribution pie, and keeps the Command Center tab panel as the mobile scroll owner.
@@ -695,8 +695,8 @@ Data states:
Reliability view summarizes in-review pipeline health so operators can spot bounce/merge instability trends without leaving the dashboard.
Navigation:
- Desktop: **Header → More views → Reliability**
- Mobile: **More** sheet → **Reliability**
- Desktop and mobile: **Command Center → Reliability** tab
- Legacy persisted `reliability` view state redirects to Command Center so existing browser sessions land on the new tab container instead of an invalid top-level view.
Features:
- Headline 7-day in-review success rate (derived as `1 - inReviewFailureRate7d`) with color thresholds: success for `≥95%`, warning for `≥90%`, error below `90%`; shows **Insufficient data** when the metric is null
@@ -1301,7 +1301,7 @@ Manage project and global secrets directly inside **Settings → Project → Sec
### Lazy-Loaded Heavy Views
These 23 views are lazy-loaded via `React.lazy()` with `<Suspense fallback={null}>`. `prefetchLazyViews()` warms App-level chunks once on mount via `requestIdleCallback`; AppModals lazy modal imports (`SettingsModal`, `WorkflowNodeEditor`, `SetupWizardModal`) are part of the same inventory. **Do not make these eager.**
These 22 views are lazy-loaded via `React.lazy()` with `<Suspense fallback={null}>`. `prefetchLazyViews()` warms App-level chunks once on mount via `requestIdleCallback`; AppModals lazy modal imports (`SettingsModal`, `WorkflowNodeEditor`, `SetupWizardModal`) are part of the same inventory. **Do not make these eager.**
- `AgentsView`
- `NodesView`
@@ -1313,7 +1313,6 @@ These 23 views are lazy-loaded via `React.lazy()` with `<Suspense fallback={null
- `DocumentsView`
- `SkillsView`
- `ResearchView`
- `ReliabilityView`
- `CommandCenter`
- `EvalsView`
- `TodoView`

View File

@@ -115,7 +115,6 @@ const ChatView = lazy(() => import("./components/ChatView").then((m) => ({ defau
const SkillsView = lazy(() => import("./components/SkillsView").then((m) => ({ default: m.SkillsView })));
const MemoryView = lazy(() => import("./components/MemoryView").then((m) => ({ default: m.MemoryView })));
const SecretsView = lazy(() => import("./components/SecretsView").then((m) => ({ default: m.SecretsView })));
const ReliabilityView = lazy(() => import("./components/ReliabilityView").then((m) => ({ default: m.ReliabilityView })));
const CommandCenter = lazy(() => import("./components/command-center/CommandCenter").then((m) => ({ default: m.CommandCenter })));
const DevServerView = lazy(() => import("./components/DevServerView").then((m) => ({ default: m.DevServerView })));
const _TodoView = lazy(() => import("./components/TodoView").then((m) => ({ default: m.TodoView })));
@@ -146,7 +145,6 @@ function prefetchLazyViews() {
void import("./components/SkillsView");
void import("./components/MemoryView");
void import("./components/SecretsView");
void import("./components/ReliabilityView");
void import("./components/command-center/CommandCenter");
void import("./components/DevServerView");
void import("./components/TodoView");
@@ -1817,16 +1815,6 @@ function AppInner() {
);
}
if (taskView === "reliability") {
return (
<PageErrorBoundary>
<Suspense fallback={null}>
<ReliabilityView />
</Suspense>
</PageErrorBoundary>
);
}
if (taskView === "command-center") {
return (
<PageErrorBoundary>

View File

@@ -5,6 +5,9 @@ test enforces that the inventory in AGENTS.md stays in sync with App.tsx (and Ap
FNXC:CommandCenter 2026-06-17-09:00:
Merging main reconciled the curated count to 23 (main's 22 lazy views/modals + Command Center).
FNXC:CommandCenter 2026-06-19-00:00:
FN-6702 removes ReliabilityView from the App-level lazy inventory because Reliability now mounts inside the lazy CommandCenter chunk.
*/
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
@@ -21,7 +24,6 @@ const EXPECTED_DOCUMENTED_VIEWS = new Set([
"DocumentsView",
"SkillsView",
"ResearchView",
"ReliabilityView",
"CommandCenter",
"EvalsView",
"TodoView",
@@ -47,7 +49,6 @@ const EXPECTED_APP_LEVEL_VIEWS = new Set([
"SkillsView",
"MemoryView",
"SecretsView",
"ReliabilityView",
"CommandCenter",
"DevServerView",
"TodoView",
@@ -105,7 +106,7 @@ function extractAppModalsLazyViews(appModalsSource: string): Set<string> {
}
describe("AGENTS lazy-loaded views inventory", () => {
it("documents the App-level and AppModals lazy views accurately and keeps the curated 23-view list in sync", () => {
it("documents the App-level and AppModals lazy views accurately and keeps the curated 22-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");
@@ -113,11 +114,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(23);
expect(Number(countMatch?.[1])).toBe(22);
const documentedViews = extractBacktickedNamesFromBullets(section);
expect(new Set(documentedViews)).toEqual(EXPECTED_DOCUMENTED_VIEWS);
expect(documentedViews).toHaveLength(23);
expect(documentedViews).toHaveLength(22);
expect(section).toContain("`ResearchView`");
expect(section).toContain("`TodoView`");

View File

@@ -159,28 +159,21 @@ describe("Mobile Feature Access Regression Guard", () => {
expect(screen.getByTestId("mobile-more-item-schedules")).toBeDefined();
expect(screen.getByTestId("mobile-more-item-github")).toBeDefined();
expect(screen.getByTestId("mobile-more-item-usage")).toBeDefined();
expect(screen.getByTestId("mobile-more-item-reliability")).toBeDefined();
expect(screen.queryByTestId("mobile-more-item-reliability")).toBeNull();
expect(screen.queryByTestId("mobile-more-item-chat")).toBeNull();
expect(screen.queryByTestId("mobile-more-item-nodes")).toBeNull();
expect(screen.getByTestId("mobile-more-item-settings")).toBeDefined();
});
it("reliability view is reachable from mobile More sheet", () => {
it("reliability is no longer a mobile More item and is reached via Command Center", () => {
const props = createDefaultMobileNavProps();
render(<MobileNavBar {...props} />);
fireEvent.click(screen.getByTestId("mobile-nav-tab-more"));
expect(screen.queryByTestId("mobile-more-item-reliability")).toBeNull();
const reliabilityItem = screen.getByTestId("mobile-more-item-reliability");
expect(reliabilityItem).toBeDefined();
fireEvent.click(reliabilityItem);
expect(props.onChangeView).toHaveBeenCalledWith("reliability");
});
it("more tab is active when reliability view is open", () => {
render(<MobileNavBar {...createDefaultMobileNavProps()} view="reliability" />);
expect(screen.getByTestId("mobile-nav-tab-more").className).toContain("mobile-nav-tab--active");
fireEvent.click(screen.getByTestId("mobile-nav-tab-command-center"));
expect(props.onChangeView).toHaveBeenCalledWith("command-center");
});
it("nodes view is reachable from mobile More sheet when enabled", () => {

View File

@@ -1110,7 +1110,7 @@ export function Header({
<>
<button
ref={viewOverflowTriggerRef}
className={`view-toggle-btn${["research", "skills", "insights", "memory", "secrets", "reliability", "dev-server", "devserver", "graph", "stash-recovery"].includes(view) || (!isTablet && view === "command-center") || (isTablet && view === "documents") || (experimentalFeatures?.evalsView && view === "evals") || (experimentalFeatures?.goalsView && view === "goalsView") || (todosEnabled && todosOpen) || isPluginViewId(view) ? " active" : ""}`}
className={`view-toggle-btn${["research", "skills", "insights", "memory", "secrets", "dev-server", "devserver", "graph", "stash-recovery"].includes(view) || (!isTablet && view === "command-center") || (isTablet && view === "documents") || (experimentalFeatures?.evalsView && view === "evals") || (experimentalFeatures?.goalsView && view === "goalsView") || (todosEnabled && todosOpen) || isPluginViewId(view) ? " active" : ""}`}
onClick={() => setIsViewOverflowOpen((prev) => !prev)}
title={t("header.moreViews", "More views")}
aria-label={t("header.moreViews", "More views")}
@@ -1238,18 +1238,6 @@ export function Header({
<Lock size={14} />
<span>{t("header.secretsView", "Secrets")}</span>
</button>
<button
className={`view-toggle-overflow-item${view === "reliability" ? " active" : ""}`}
onClick={() => {
onChangeView("reliability");
setIsViewOverflowOpen(false);
}}
role="menuitem"
data-testid="view-overflow-reliability"
>
<Activity size={14} />
<span>{t("header.reliabilityView", "Reliability")}</span>
</button>
{isTablet && (
<button
className={`view-toggle-overflow-item${view === "documents" ? " active" : ""}`}

View File

@@ -293,7 +293,6 @@ export function MobileNavBar({
const isMoreActive =
view === "documents"
|| view === "reliability"
|| (Boolean(experimentalFeatures?.evalsView) && view === "evals")
|| (Boolean(experimentalFeatures?.goalsView) && view === "goalsView")
|| view === "research"
@@ -677,16 +676,6 @@ export function MobileNavBar({
<span>{t("nav.documents", "Documents")}</span>
</button>
<button
type="button"
className="mobile-more-item"
data-testid="mobile-more-item-reliability"
onClick={() => handleMoreAction(() => onChangeView("reliability"))}
>
<Activity />
<span>{t("nav.reliability", "Reliability")}</span>
</button>
{experimentalFeatures?.evalsView && (
<button
type="button"

View File

@@ -14,6 +14,7 @@ import { GithubArea } from "./areas/GithubArea";
import { SignalsArea } from "./areas/SignalsArea";
import { SystemStatsArea } from "./areas/SystemStatsArea";
import { MissionControlPanel } from "./MissionControlPanel";
import { ReliabilityView } from "../ReliabilityView";
import { SdlcFunnel } from "./SdlcFunnel";
import { Bar, type BarDatum } from "./charts/Bar";
import { Sparkline } from "./charts/Sparkline";
@@ -34,6 +35,7 @@ type SubViewId =
| "github"
| "signals"
| "system"
| "reliability"
| "mission-control";
interface SubView {
@@ -44,6 +46,9 @@ interface SubView {
/*
FNXC:CommandCenter 2026-06-18-16:57:
Team tab shows each agent's tokens/cost/files-changed/tasks-completed with live status and bar charts, reusing existing analytics primitives; GitHub-issue per-agent stats are FN-6653, not here.
FNXC:CommandCenter 2026-06-19-00:00:
FN-6702 moves Reliability from a top-level dashboard view into a Command Center tab next to System telemetry. Reuse ReliabilityView unchanged so its /api/health/reliability loading, error, insufficient-data, and populated states keep the same data flow.
*/
function useSubViews(): SubView[] {
const { t } = useTranslation("app");
@@ -58,6 +63,7 @@ function useSubViews(): SubView[] {
{ id: "github", label: t("commandCenter.tabs.github", "GitHub") },
{ id: "signals", label: t("commandCenter.tabs.signals", "Signals") },
{ id: "system", label: t("commandCenter.tabs.system", "System") },
{ id: "reliability", label: t("commandCenter.tabs.reliability", "Reliability") },
{ id: "mission-control", label: t("commandCenter.tabs.missionControl", "Mission Control") },
];
}
@@ -483,6 +489,8 @@ export function CommandCenter() {
return <SignalsArea range={range} />;
case "system":
return <SystemStatsArea />;
case "reliability":
return <ReliabilityView />;
case "mission-control":
return <MissionControlPanel />;
default:

View File

@@ -189,6 +189,28 @@ function liveFixture(columns: Array<{ column: string; count: number }> = [{ colu
};
}
function reliabilityFixture() {
return {
windowDays: 7,
generatedAt: "2026-06-19T00:00:00.000Z",
resetAt: null,
headline: { inReviewFailureRate7d: 0.25 },
perDay: [
{
date: "2026-06-19",
tasksEnteredInReview: 4,
tasksBouncedToInProgress: 1,
postMergeAuditFailures: { block: 0, warn: 0, off: 0 },
fileScopeInvariantFailures: 0,
recoverAlreadyMergedReviewTasksRecoveries: 0,
hasSamples: true,
},
],
duration: { p50Ms: 60_000, p95Ms: 120_000, sampleCount: 2 },
mergeAttempts: { mean: 1.5, max: 2, histogram: { "1": 1, "2": 1 } },
};
}
function systemStatsFixture() {
const gb = 1024 * 1024 * 1024;
const mb = 1024 * 1024;
@@ -575,8 +597,8 @@ describe("CommandCenter shell", () => {
render(<CommandCenter />);
const tablist = screen.getByRole("tablist");
const tabs = within(tablist).getAllByRole("tab");
// Overview, Tokens, Tools, Activity, Productivity, Team, Ecosystem, GitHub, Signals, System, Mission Control.
expect(tabs.length).toBe(11);
// Overview, Tokens, Tools, Activity, Productivity, Team, Ecosystem, GitHub, Signals, System, Reliability, Mission Control.
expect(tabs.length).toBe(12);
// roving tabindex: exactly one tab is focusable.
const focusable = tabs.filter((tab) => tab.getAttribute("tabindex") === "0");
expect(focusable.length).toBe(1);
@@ -616,6 +638,26 @@ describe("CommandCenter shell", () => {
expect(screen.getByTestId("cc-github-fixed").textContent).toContain("2");
});
it("renders and routes the Reliability tab exactly once", async () => {
const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue({
ok: true,
json: async () => reliabilityFixture(),
} as Response);
try {
render(<CommandCenter />);
expect(screen.getAllByTestId("command-center-tab-reliability")).toHaveLength(1);
fireEvent.click(screen.getByTestId("command-center-tab-reliability"));
expect(screen.getByTestId("command-center-tab-reliability").getAttribute("aria-selected")).toBe("true");
expect(screen.getByTestId("command-center-panel-reliability")).toBeTruthy();
expect(await screen.findByRole("heading", { name: "Reliability" })).toBeTruthy();
expect(fetchSpy).toHaveBeenCalledWith("/api/health/reliability");
} finally {
fetchSpy.mockRestore();
}
});
it("renders the Team tab with sortable per-agent stats and charts", async () => {
mockOverviewApi({ team: teamFixture() });
render(<CommandCenter />);
@@ -710,6 +752,7 @@ describe("CommandCenter shell", () => {
"github",
"signals",
"system",
"reliability",
"mission-control",
"team",
]) {
@@ -725,6 +768,13 @@ describe("CommandCenter shell", () => {
const tokensTab = screen.getByTestId("command-center-tab-tokens");
expect(tokensTab.getAttribute("aria-selected")).toBe("true");
expect(document.activeElement).toBe(tokensTab);
const systemTab = screen.getByTestId("command-center-tab-system");
systemTab.focus();
fireEvent.keyDown(systemTab, { key: "ArrowRight" });
const reliabilityTab = screen.getByTestId("command-center-tab-reliability");
expect(reliabilityTab.getAttribute("aria-selected")).toBe("true");
expect(document.activeElement).toBe(reliabilityTab);
});
it("wraps with ArrowLeft from the first tab to the last", () => {

View File

@@ -66,6 +66,33 @@ describe("useViewState", () => {
});
});
it("migrates legacy reliability taskView from localStorage to Command Center", async () => {
localStorage.setItem("kb-dashboard-task-view", "reliability");
const { result } = renderHook(() => useViewState(createOptions()));
await waitFor(() => {
expect(result.current.taskView).toBe("command-center");
});
expect(localStorage.getItem("kb-dashboard-task-view")).toBe("command-center");
});
it("migrates legacy reliability URL param to Command Center", async () => {
const originalUrl = `${window.location.pathname}${window.location.search}`;
window.history.replaceState({}, "", "?view=reliability");
try {
const { result } = renderHook(() => useViewState(createOptions()));
await waitFor(() => {
expect(result.current.taskView).toBe("command-center");
});
expect(localStorage.getItem("kb-dashboard-task-view")).toBe("command-center");
} finally {
window.history.replaceState({}, "", originalUrl || "/");
}
});
it("migrates legacy roadmaps state to plugin view when registered", async () => {
vi.spyOn(pluginViewRegistry, "isPluginViewRegistered").mockReturnValue(true);
localStorage.setItem("kb-dashboard-task-view", "roadmaps");
@@ -272,6 +299,23 @@ describe("useViewState", () => {
});
});
it("migrates legacy reliability taskView from scoped storage to Command Center", async () => {
localStorage.setItem("kb:proj_123:kb-dashboard-task-view", "reliability");
const { result } = renderHook(() =>
useViewState(
createOptions({
currentProject: PROJECT,
}),
),
);
await waitFor(() => {
expect(result.current.taskView).toBe("command-center");
});
expect(localStorage.getItem("kb:proj_123:kb-dashboard-task-view")).toBe("command-center");
});
it("persists insights taskView changes to scoped localStorage", async () => {
const { result } = renderHook(() =>
useViewState(

View File

@@ -5,7 +5,7 @@ import { getScopedItem, setScopedItem } from "../utils/projectStorage";
import { getPluginViewId, isPluginViewId, isPluginViewRegistered } from "../plugins/pluginViewRegistry";
export type ViewMode = "overview" | "project";
export type BuiltInTaskView = "board" | "list" | "graph" | "agents" | "missions" | "chat" | "documents" | "research" | "evals" | "goalsView" | "skills" | "mailbox" | "insights" | "memory" | "reliability" | "command-center" | "secrets" | "devserver" | "dev-server" | "stash-recovery" | "pull-requests";
export type BuiltInTaskView = "board" | "list" | "graph" | "agents" | "missions" | "chat" | "documents" | "research" | "evals" | "goalsView" | "skills" | "mailbox" | "insights" | "memory" | "command-center" | "secrets" | "devserver" | "dev-server" | "stash-recovery" | "pull-requests";
export type PluginTaskView = `plugin:${string}:${string}`;
export type TaskView = BuiltInTaskView | PluginTaskView;
@@ -25,7 +25,6 @@ const BUILT_IN_TASK_VIEWS: readonly BuiltInTaskView[] = [
"mailbox",
"insights",
"memory",
"reliability",
"command-center",
"secrets",
"devserver",
@@ -55,6 +54,14 @@ function migrateLegacyRoadmapsView(value: string): TaskView {
return isPluginViewRegistered("fusion-plugin-roadmap", "roadmaps") ? LEGACY_ROADMAPS_PLUGIN_VIEW : "board";
}
/*
FNXC:ViewState 2026-06-19-00:00:
FN-6702 removed the top-level Reliability task view after moving the page into Command Center. Persisted or linked legacy `reliability` values must land users on `command-center` instead of falling back to the board or becoming invalid.
*/
function migrateLegacyReliabilityView(value: string | null): TaskView | null {
return value === "reliability" ? "command-center" : null;
}
interface UseViewStateOptions {
projectsLoading: boolean;
projectsError: string | null;
@@ -99,6 +106,8 @@ export function useViewState(options: UseViewStateOptions): UseViewStateResult {
const [taskView, setTaskView] = useState<TaskView>(() => {
const saved = getScopedItem("kb-dashboard-task-view");
const legacyReliabilityView = migrateLegacyReliabilityView(saved);
if (legacyReliabilityView) return legacyReliabilityView;
if (saved === "roadmaps") return migrateLegacyRoadmapsView(saved);
if (isTaskView(saved)) return saved;
return "board";
@@ -111,7 +120,10 @@ export function useViewState(options: UseViewStateOptions): UseViewStateResult {
useEffect(() => {
const saved = getScopedItem("kb-dashboard-task-view", currentProject?.id);
if (saved === "roadmaps") {
const legacyReliabilityView = migrateLegacyReliabilityView(saved);
if (legacyReliabilityView) {
setTaskView(legacyReliabilityView);
} else if (saved === "roadmaps") {
setTaskView(migrateLegacyRoadmapsView(saved));
} else if (isTaskView(saved)) {
const preserveLegacyOnFirstScopedHydration =
@@ -137,7 +149,10 @@ export function useViewState(options: UseViewStateOptions): UseViewStateResult {
}
const viewParam = new URLSearchParams(window.location.search).get("view");
if (viewParam && isTaskView(viewParam)) {
const legacyReliabilityView = migrateLegacyReliabilityView(viewParam);
if (legacyReliabilityView) {
setTaskView(legacyReliabilityView);
} else if (viewParam && isTaskView(viewParam)) {
setTaskView(normalizeTaskView(viewParam));
}
}, []);

View File

@@ -7097,6 +7097,7 @@
"tools": "Tools",
"activity": "Activity",
"productivity": "Productivity",
"reliability": "Reliability",
"ecosystem": "Ecosystem",
"missionControl": "Mission Control"
},