Skip to content

explorer: refactor URL/mode state management (centralize writers, collapse dual mode) #208

@rdhyee

Description

@rdhyee

Structural follow-up from Codex's retrospective on #203 + #205. Two related smells; addressing them together because they touch the same code paths.

Smell 1: Many URL writers, no single boundary

Across explorer.qmd there are now (at least):

Each write has its own gate (_suppressHashWrite, freshness checks, etc.). When something goes wrong with URL state, the answer to "which writer wrote that?" is non-obvious.

Codex's sketched interface:

writeGlobeHash({ replace: true })
reconcileCameraState({ reason, writeEarlyHash: true })
setExplorerMode(nextMode, { pushHistory: false | true })

Then boot, hashchange, user camera movement, and share/copy call clear, intention-revealing paths.

Also flagged: moveEnd currently only writes the URL — sub-percentageChanged moves still bypass the rest of the camera-settled pipeline (sample reloads, cluster count refresh). If a small pan is important enough to update the URL, it should probably update the in-view stats too. A unified "settled-camera reconciliation" path would solve both.

Smell 2: Dual mode state

explorer.qmd carries the current mode in two places:

  • Closure-private mode variable (used by the camera-changed handler at lines 1971+)
  • Public viewer._globeState.mode (used by buildHash, tests, harnesses)

They're updated together today but the duplication is fragile. The Playwright investigation harness's iter 1 captured viewer._globeState.mode === 'cluster' shortly after waitReadyAndPointMode saw mode === 'point' — empirical evidence of transient sync mismatch.

Fix shape: make _globeState.mode the single source of truth, and wrap mode transitions in one setter (setExplorerMode(next, opts)). Add an invariant assertion / dev-time console log when the two diverge until the closure variable can be deleted.

Sequencing

Probably:

  1. Collapse mode to single source (smell 2) — smaller, sets the foundation.
  2. Introduce writeGlobeHash + setExplorerMode and migrate the writers (smell 1).
  3. Route both camera.changed and camera.moveEnd through a single reconcileCameraState(reason) that does URL write + viewport stats + sample reload as appropriate.

Each step is a separate PR. Codex review at each step is recommended given the structural nature.

Acceptance

  • Single source of truth for mode.
  • Single explicit URL-write API; old direct history.{push,replace}State call sites migrated.
  • camera.moveEnd and camera.changed share the same settled-camera reconciliation entry point.
  • No regression in the existing URL round-trip Playwright assertions (post-explorer: add camera.moveEnd URL-write backstop (closes #204) #205 they all pass).

Refs

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions