diff --git a/apps/desktop/eslint.config.mjs b/apps/desktop/eslint.config.mjs index 3690c9dfda..51f4e97ce9 100644 --- a/apps/desktop/eslint.config.mjs +++ b/apps/desktop/eslint.config.mjs @@ -41,4 +41,39 @@ export default [ "no-restricted-syntax": "off", }, }, + // Navigation boundary (MUL-4741): the tab Coordinator must be the only + // navigation initiator — a Router location change without a Coordinator + // token is a protocol error. Application code must not navigate directly; + // it goes through the navigation adapter / Coordinator in src/platform. + // Remaining legacy sites carry an inline eslint-disable tagged MUL-4741; + // the Phase 2 migration removes them one by one, and this rule holding + // with zero disables is the machine check that the migration is complete. + { + files: ["src/renderer/src/**/*.{ts,tsx}"], + ignores: ["src/renderer/src/platform/**", "src/renderer/src/**/*.test.*"], + rules: { + "no-restricted-imports": [ + "error", + { + paths: [ + { + name: "react-router-dom", + importNames: ["useNavigate", "Navigate"], + message: + "Direct navigation from application code breaks the Coordinator protocol (MUL-4741). Use the navigation adapter from src/platform instead.", + }, + ], + }, + ], + "no-restricted-syntax": [ + "error", + { + selector: + "CallExpression[callee.object.name='router'][callee.property.name='navigate']", + message: + "Direct router.navigate from application code breaks the Coordinator protocol (MUL-4741). Route it through the navigation adapter in src/platform.", + }, + ], + }, + }, ]; diff --git a/apps/desktop/src/renderer/src/components/route-error-page.tsx b/apps/desktop/src/renderer/src/components/route-error-page.tsx index ab37076afc..61343b756f 100644 --- a/apps/desktop/src/renderer/src/components/route-error-page.tsx +++ b/apps/desktop/src/renderer/src/components/route-error-page.tsx @@ -1,4 +1,5 @@ import { useMemo } from "react"; +// eslint-disable-next-line no-restricted-imports -- MUL-4741 Phase 2: error recovery moves to the Coordinator; remove with §8.1 import { useLocation, useNavigate, useRouteError } from "react-router-dom"; import { AlertTriangle, RotateCw, Send, X } from "lucide-react"; import { Button } from "@multica/ui/components/ui/button"; diff --git a/apps/desktop/src/renderer/src/components/workspace-route-layout.tsx b/apps/desktop/src/renderer/src/components/workspace-route-layout.tsx index 959f8b6b7f..e5feee9b46 100644 --- a/apps/desktop/src/renderer/src/components/workspace-route-layout.tsx +++ b/apps/desktop/src/renderer/src/components/workspace-route-layout.tsx @@ -1,4 +1,5 @@ import { useEffect } from "react"; +// eslint-disable-next-line no-restricted-imports -- MUL-4741 Phase 2: workspace redirect + login bounce move to the Coordinator; remove with §8.1 import { Outlet, useNavigate, useParams } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; import { WorkspaceSlugProvider, paths } from "@multica/core/paths"; diff --git a/apps/desktop/src/renderer/src/hooks/use-tab-history.ts b/apps/desktop/src/renderer/src/hooks/use-tab-history.ts index 1c47fbd36e..0a3a9eb869 100644 --- a/apps/desktop/src/renderer/src/hooks/use-tab-history.ts +++ b/apps/desktop/src/renderer/src/hooks/use-tab-history.ts @@ -27,12 +27,14 @@ export function useTabHistory() { const goBack = useCallback(() => { if (!router || historyIndex <= 0) return; popDirectionHints.set(router, "back"); + // eslint-disable-next-line no-restricted-syntax -- MUL-4741 Phase 2: shell back/forward moves to the Coordinator (invariant 1); remove with §8.1 router.navigate(-1); }, [router, historyIndex]); const goForward = useCallback(() => { if (!router || historyIndex >= historyLength - 1) return; popDirectionHints.set(router, "forward"); + // eslint-disable-next-line no-restricted-syntax -- MUL-4741 Phase 2: shell back/forward moves to the Coordinator (invariant 1); remove with §8.1 router.navigate(1); }, [router, historyIndex, historyLength]); diff --git a/apps/desktop/src/renderer/src/routes.tsx b/apps/desktop/src/renderer/src/routes.tsx index 7c8faa950f..d3eb96175b 100644 --- a/apps/desktop/src/renderer/src/routes.tsx +++ b/apps/desktop/src/renderer/src/routes.tsx @@ -1,6 +1,7 @@ import { useEffect } from "react"; import { createMemoryRouter, + // eslint-disable-next-line no-restricted-imports -- MUL-4741 Phase 2: index redirect moves to the Coordinator; remove with §8.1 Navigate, Outlet, useMatches,