From 843e0f528ca586758bd707431742c172a1ea4c50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemek=20Wi=C4=99ch?= Date: Thu, 11 Jun 2026 17:09:30 +0200 Subject: [PATCH] Shift `config` to URL as source of truth --- docs/APP_REFACTORING_DESIGN.md | 2 +- src/app.tsx | 21 +++++++-------------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/docs/APP_REFACTORING_DESIGN.md b/docs/APP_REFACTORING_DESIGN.md index 0b27668..5b4dc92 100644 --- a/docs/APP_REFACTORING_DESIGN.md +++ b/docs/APP_REFACTORING_DESIGN.md @@ -97,7 +97,7 @@ We will gradually eliminate React state variables in `src/app.tsx` and derive th * [x] **Step 3.1d:** Shift `freezeAnimation` to URL as SSOT. Derive it directly from search parameters on render and remove its React state variable. * [x] **Step 3.2:** Extract `selection` and `detailIndi` state. Parse them directly from URL params (`indi`, `gen`, and `detail`); update display selectors. Remove React states. Ensure that chart selection changes (`onSelection` callback) explicitly clear or update the `detail` query parameter to match the new selection to avoid getting stuck on the old details viewport. Also update the detail-only selection handler (`onDetailSelection`) to update the `detail` query parameter in the URL. * [x] **Step 3.3:** Extract `showSidePanel` state. Derive state directly from `?sidePanel=` parameter. Remove React state. Enhance URL args helpers to allow generating path/query target objects with replaced values, and ensure layout settings (`sidePanel`) and configuration changes use `replace: true` to prevent polluting the browser history stack. -* [ ] **Step 3.4:** Extract `config` state. Parse display settings on render using `argsToConfig` helper. Remove state. In `ViewPage`, run `updateChartWithConfig(config, data)` synchronously during the render pass (e.g. in the `useMemo` that derives query parameters/config) to update the in-memory chart data before it is rendered by the `` child component. Run E2E and visual tests to verify no rendering regressions occurred. +* [x] **Step 3.4:** Extract `config` state. Parse display settings on render using `argsToConfig` helper. Remove state. In `ViewPage`, run `updateChartWithConfig(config, data)` synchronously during the render pass (e.g. in the `useMemo` that derives query parameters/config) to update the in-memory chart data before it is rendered by the `` child component. Run E2E and visual tests to verify no rendering regressions occurred. #### Phase 4: Structural Page Nesting * [ ] **Step 4.1:** Create `src/hooks/use_google_auth.ts` to manage Google Drive authentication token updates. Remove `hasGoogleToken` state from `src/app.tsx`. diff --git a/src/app.tsx b/src/app.tsx index 2b11796..b3f167e 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -47,13 +47,7 @@ import {useWebMcpBridge} from './hooks/use_webmcp_bridge'; import {Intro} from './intro'; import {GoogleAuthModal} from './menu/google_auth_modal'; import {TopBar} from './menu/top_bar'; -import { - Config, - configToArgs, - DEFALUT_CONFIG, - Ids, - Sex, -} from './sidepanel/config/config'; +import {Config, configToArgs, Ids, Sex} from './sidepanel/config/config'; import {SidePanel} from './sidepanel/side-panel'; import {analyticsEvent} from './util/analytics'; import {TopolaError} from './util/error'; @@ -92,9 +86,6 @@ export function App() { /** Specification of the source of the data. */ const [sourceSpec, setSourceSpec] = useState(); - /** Configuration settings for chart display options (e.g. colors, hiding IDs). */ - const [config, setConfig] = useState(DEFALUT_CONFIG); - /** Controls the visibility of the Google Drive OAuth permission modal. */ const [showAuthModal, setShowAuthModal] = useState(false); /** Stores the file ID that failed to load from Google Drive due to authorization errors. */ @@ -122,6 +113,12 @@ export function App() { const freezeAnimation = args.freezeAnimation; /** Whether the side panel is shown. */ const showSidePanel = args.showSidePanel; + /** Configuration settings for chart display options (e.g. colors, hiding IDs). */ + const config = args.config; + + useMemo(() => { + updateChartWithConfig(config, data); + }, [config, data]); /** The currently selected individual. Fallback to default individual from loaded data if not specified. */ const updatedSelection = useMemo(() => { return data ? getSelection(data.chartData, args.selection) : undefined; @@ -345,7 +342,6 @@ export function App() { setState(AppState.LOADING); // Set state from URL parameters. setSourceSpec(args.sourceSpec); - setConfig(args.config); const currentFetchId = ++fetchIdRef.current; setLoadingStatus('Loading…'); try { @@ -369,7 +365,6 @@ export function App() { data.chartData, args.selection, ); - updateChartWithConfig(args.config, data); setState(AppState.SHOWING_CHART); } catch (error: unknown) { if (!isMountedRef.current || fetchIdRef.current !== currentFetchId) { @@ -587,8 +582,6 @@ export function App() { expanded={showSidePanel} onToggle={onToggleSidePanel} onConfigChange={(config) => { - setConfig(config); - updateChartWithConfig(config, data); updateUrl(configToArgs(config), {replace: true}); }} />