This issue is for us to consider an overall architecture tidy-up of how the URL state is managed. We've slowly pulled the url functionality out of certain modules like CesiumWidgetView, but the functionality remains spread out across a wide surface that can be hard to reason about. Right now, SearchParams reads and writes the browser URL, ViewfinderModel resolves and opens actions, ViewfinderView owns the debounce and sync, and MapView/VisualizationPanelView relay the state. Same for the active visualization, which is right now split across activeVisualizationAction, activeVisualizationActionId, and activeVisualizationUrl, which always need to be updated at the same time. We should find a way we can consolidate the URL coordination into a single place and make clearer ownership boundaries.
Here's one way we could do that (conceived with the help of GPT-5.3-codex)
Plan: Single URL Coordinator + Unified Visualization State
The cleanest approach is to make one coordinator the only owner of URL state, and make active visualization a single atomic object. The following could be done in one big refactor PR:
Steps
- Phase 1: Establish one URL owner (blocks all other phases).
- Add a coordinator module that is the only code allowed to read, normalize, and write browser query state.
- Move URL schema logic out of SearchParams.js into the coordinator internals (camera, layers, open panel, active action id, namespaced action params, schema versioning).
- Keep SearchParams.js as a temporary facade that delegates to coordinator methods to limit import churn during this PR.
- Parse URL once on app/map init, cache normalized state in coordinator memory, and expose it to consumers so views/models do not repeatedly parse window location.
- Phase 2: Unify active visualization state (depends on Phase 1).
- Replace Map attributes activeVisualizationAction, activeVisualizationActionId, and activeVisualizationUrl with one activeVisualization object in Map.js.
- Refactor write sites to set activeVisualization atomically in ViewfinderModel.js and MapView.js.
- Refactor read sites to consume activeVisualization atomically in MapView.js, ViewfinderView.js, and VisualizationPanelView.js.
- Add temporary read-only compatibility aliases in Map for one PR so untouched callers do not break mid-refactor.
- Phase 3: Move URL sync orchestration into coordinator (depends on Phases 1-2).
- Replace distributed URL writes from Map/Viewfinder/MapView with coordinator intent methods (camera changed, layers changed, panel changed, visualization opened/closed, visualization state message received).
- Centralize debounce policy in coordinator so there is one write schedule instead of separate timers in model/view layers.
- Keep stale-message protection in coordinator by verifying current activeVisualization.actionId before writing namespaced action params.
- Phase 4: Clarify restore boundaries (depends on Phase 3).
- Coordinator provides one restored state object to Map and Viewfinder restore paths.
- Keep ToolbarView URL-agnostic; MapView maps UI events to coordinator intents and initializes UI from restored coordinator state.
- Replace retry-fragile restore sequencing with explicit idempotent restore attempts when category render completes.
- Phase 5: Cleanup and boundary enforcement (depends on Phase 4).
- Remove direct URL mutation calls from MapView.js and ViewfinderView.js.
- Remove legacy active visualization fields from Map.js after all references migrate.
- Finalize ownership docs/comments: coordinator owns URL lifecycle, models own domain state, views own rendering and user interaction only.
Relevant files
- SearchParams.js: convert to delegation facade, then shrink or replace.
- Map.js: adopt single activeVisualization object and restored state consumption.
- ViewfinderModel.js: atomic visualization open/close state writes.
- MapView.js: consume unified visualization state, emit coordinator intents.
- ViewfinderView.js: remove URL-writing responsibility and debounce ownership.
- VisualizationPanelView.js: consume unified visualization context, keep trusted message/origin checks.
- UriTemplateUtilities.js: reused by coordinator for action-template expansion/extraction.
- js: add/update coordinator, visualization state, and race-condition coverage.
Verification
- Unit tests for coordinator parse/normalize/write behavior across camera, layers, panel, action id, and namespaced params.
- Unit tests for activeVisualization constructors and invariants (valid action, invalid action id, URL-only legacy path, clear).
- Integration test: visualization open + state message writes expected action-scoped params.
- Integration test: open action A then B quickly; assert only B wins in URL.
- Integration test: close visualization then stale message arrives; assert cleared params are not reintroduced.
- Integration test: full restore from URL (camera + layers + panel + action) is deterministic and idempotent.
- Run full test suite and manual localhost verification for map pan/zoom, layer toggles, panel open/close, visualization open/close, reload/share URL.
This issue is for us to consider an overall architecture tidy-up of how the URL state is managed. We've slowly pulled the url functionality out of certain modules like
CesiumWidgetView, but the functionality remains spread out across a wide surface that can be hard to reason about. Right now,SearchParamsreads and writes the browser URL,ViewfinderModelresolves and opens actions,ViewfinderViewowns the debounce and sync, andMapView/VisualizationPanelViewrelay the state. Same for the active visualization, which is right now split acrossactiveVisualizationAction,activeVisualizationActionId, andactiveVisualizationUrl, which always need to be updated at the same time. We should find a way we can consolidate the URL coordination into a single place and make clearer ownership boundaries.Here's one way we could do that (conceived with the help of GPT-5.3-codex)
Plan: Single URL Coordinator + Unified Visualization State
The cleanest approach is to make one coordinator the only owner of URL state, and make active visualization a single atomic object. The following could be done in one big refactor PR:
Steps
Relevant files
Verification