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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions