From c5dd5772d0ca124187dfdd38d72c8ff1a49efd5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Mazoni?= Date: Thu, 9 Jul 2026 00:20:02 +0930 Subject: [PATCH] Keep run graph zoom/pan when switching tabs (#561) Switching from a run's Overview tab to another tab and back reset the graph zoom and position to the default. Now it holds. ## Why The viewport (pan and zoom) lived in `RunOverview` component state. Overview and Stages are sibling routes under `runs/:id`, so switching tabs unmounts Overview and drops that state. ## Fix `apps/fabro-web/app/routes/run-overview.tsx`: cache the viewport per run outside the component so it survives the remount, and reset it when the run id changes, since the route instance is reused when only the id changes. Added two tests: viewport restores on remount for the same run, and does not carry across runs. Does not persist across a full page reload (in-memory only). --------- Co-authored-by: Claude Opus 4.8 (1M context) Co-authored-by: Bryan Helmkamp --- .../app/components/graph-toolbar.tsx | 25 +++--- apps/fabro-web/app/hooks/effects.ts | 82 +++++++++++-------- .../app/hooks/use-remembered-graph-view.ts | 39 +++++++++ apps/fabro-web/app/lib/graph-viewport.test.ts | 8 ++ apps/fabro-web/app/lib/graph-viewport.ts | 19 ++++- .../app/routes/run-overview.test.tsx | 64 +++++++++++++-- apps/fabro-web/app/routes/run-overview.tsx | 31 ++----- 7 files changed, 190 insertions(+), 78 deletions(-) create mode 100644 apps/fabro-web/app/hooks/use-remembered-graph-view.ts diff --git a/apps/fabro-web/app/components/graph-toolbar.tsx b/apps/fabro-web/app/components/graph-toolbar.tsx index 282317214..03067059b 100644 --- a/apps/fabro-web/app/components/graph-toolbar.tsx +++ b/apps/fabro-web/app/components/graph-toolbar.tsx @@ -1,23 +1,24 @@ import { ArrowDownIcon, ArrowRightIcon, MinusIcon, PlusIcon } from "@heroicons/react/20/solid"; +import { GRAPH_MAX_ZOOM, GRAPH_MIN_ZOOM } from "../lib/graph-viewport"; + type Direction = "LR" | "TB"; +// +/- button step. Zoom-out uses the reciprocal, keeping it symmetric with zoom-in. +const ZOOM_STEP_FACTOR = 1.25; + export function GraphToolbar({ direction, setDirection, fitToWindow, - onZoomIn, - onZoomOut, - canZoomIn, - canZoomOut, + zoom, + onZoomBy, }: { direction: Direction; setDirection: (d: Direction) => void; fitToWindow: () => void; - onZoomIn: () => void; - onZoomOut: () => void; - canZoomIn: boolean; - canZoomOut: boolean; + zoom: number; + onZoomBy: (factor: number) => void; }) { const group = "flex items-center gap-0.5 px-0.5 [&:not(:first-child)]:border-l [&:not(:first-child)]:border-line-strong [&:not(:first-child)]:pl-1 [&:not(:first-child)]:ml-1"; @@ -70,8 +71,8 @@ export function GraphToolbar({