diff --git a/src/routes/v2/pages/Editor/EditorV2.tsx b/src/routes/v2/pages/Editor/EditorV2.tsx
index 4d1f9ce63..c9a3f2a59 100644
--- a/src/routes/v2/pages/Editor/EditorV2.tsx
+++ b/src/routes/v2/pages/Editor/EditorV2.tsx
@@ -50,6 +50,8 @@ import { useSelectionWindowSync } from "./hooks/useSelectionWindowSync";
import { useSpecLifecycle } from "./hooks/useSpecLifecycle";
import { useTipOfTheDayWindow } from "./hooks/useTipOfTheDayWindow";
import { useUndoRedoKeyboard } from "./hooks/useUndoRedoKeyboard";
+import { reconcileModeStore } from "./lineage/reconcileModeStore";
+import { ReconcileNavigationGuard } from "./lineage/ReconcileNavigationGuard";
import { ReconcileOverviewHost } from "./lineage/ReconcileOverviewHost";
import { useReconcileFromUrl } from "./lineage/useReconcileFromUrl";
import { editorRegistry } from "./nodes";
@@ -131,6 +133,12 @@ const PipelineEditor = withSuspenseWrapper(
PipelineEditorSkeleton,
);
+/** Hides the editor menu bar (and its navigation escape hatches) while in
+ * reconcile mode, leaving the canvas and reconcile banner. */
+const EditorTopBar = observer(function EditorTopBar() {
+ return reconcileModeStore.active ? null : ;
+});
+
function EditorV2Content({ pipelineRef }: { pipelineRef: PipelineRef | null }) {
const { navigation } = useSharedStores();
@@ -143,7 +151,8 @@ function EditorV2Content({ pipelineRef }: { pipelineRef: PipelineRef | null }) {
return (
-
+
+
{pipelineRef ? (
diff --git a/src/routes/v2/pages/Editor/lineage/ReconcileNavigationGuard.tsx b/src/routes/v2/pages/Editor/lineage/ReconcileNavigationGuard.tsx
new file mode 100644
index 000000000..bc3c4630c
--- /dev/null
+++ b/src/routes/v2/pages/Editor/lineage/ReconcileNavigationGuard.tsx
@@ -0,0 +1,65 @@
+import { useBlocker } from "@tanstack/react-router";
+
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+} from "@/components/ui/alert-dialog";
+
+import { reconcileModeStore } from "./reconcileModeStore";
+
+/**
+ * Guards against accidentally leaving reconcile mode. While reconciling, any
+ * navigation that doesn't stay within the active session (the return-to overview
+ * or another reconcile target) is intercepted with a Tangle-styled prompt;
+ * `enableBeforeUnload` additionally covers refresh / tab close (browser-native).
+ */
+export function ReconcileNavigationGuard() {
+ const blocker = useBlocker({
+ shouldBlockFn: ({ next }) => {
+ const session = reconcileModeStore.session;
+ if (!session) return false;
+ const search = (next.search ?? {}) as Record;
+ const staysInFlow =
+ search.reconcile === session.sessionId ||
+ search.reconcileOverview === session.sessionId;
+ return !staysInFlow;
+ },
+ enableBeforeUnload: () => reconcileModeStore.active,
+ withResolver: true,
+ });
+
+ if (blocker.status !== "blocked") return null;
+
+ return (
+
+
+
+ Leave reconcile?
+
+ You’re about to leave to {blocker.next.pathname}. Staged, unsaved
+ changes in this pipeline will be discarded.
+
+
+
+ blocker.reset()}>
+ Return to reconcile
+
+ {
+ reconcileModeStore.exit();
+ blocker.proceed();
+ }}
+ >
+ Continue anyway
+
+
+
+
+ );
+}
diff --git a/src/routes/v2/pages/Editor/lineage/useReconcileFromUrl.ts b/src/routes/v2/pages/Editor/lineage/useReconcileFromUrl.ts
index ce91fb834..9d05d8f1c 100644
--- a/src/routes/v2/pages/Editor/lineage/useReconcileFromUrl.ts
+++ b/src/routes/v2/pages/Editor/lineage/useReconcileFromUrl.ts
@@ -1,6 +1,8 @@
import { useSearch } from "@tanstack/react-router";
import { useEffect } from "react";
+import { focusModeStore } from "@/routes/v2/shared/hooks/useFocusMode";
+
import { reconcileModeStore } from "./reconcileModeStore";
import {
getReconcileSession,
@@ -25,16 +27,18 @@ export function useReconcileFromUrl(): void {
}, []);
useEffect(() => {
- if (!sessionId) {
- reconcileModeStore.exit();
- return;
- }
- const session = getReconcileSession(sessionId);
+ const session = sessionId ? getReconcileSession(sessionId) : undefined;
if (session) {
reconcileModeStore.enter(session);
+ // Reuse focus mode to hide dock panels while reconciling (shared store —
+ // keeps the architecture's pages → shared dependency direction).
+ focusModeStore.setActive(true);
} else {
reconcileModeStore.exit();
}
- return () => reconcileModeStore.exit();
+ return () => {
+ reconcileModeStore.exit();
+ focusModeStore.setActive(false);
+ };
}, [sessionId]);
}