Skip to content

explorer: consolidate selection mutations into a controller (deferred Step 2 of #187) #189

@rdhyee

Description

@rdhyee

Background

Step 2 of #187 — deferred at PR-merge time per the Codex synthesis on that issue. Filed as its own tracking issue so #187 can close.

What this is

Today, every code path that touches selection (cluster click, sample click, hashchange, source-filter handler, boot deep-link) does its own:

  1. Mutates viewer._globeState.selectedPid / selectedH3
  2. Updates the URL via history.pushState / replaceState / nothing (depending on path)
  3. Mutates side-panel DOM (#clusterSection, #samplesSection)

Five paths × three mutation surfaces = a matrix of inconsistency that took six Codex rounds on PR #186 + two more on PR #188 to converge. The freshness primitive (freshSelectionToken) closed the async race class of bug. What's still distributed is the mutation triple itself.

Proposed Step 2

Three controller functions in explorer.qmd:

async function selectSample(viewer, pid, opts = {}) { ... }
async function selectCluster(viewer, h3Cell, opts = {}) { ... }
function clearSelection(viewer, opts = {}) { ... }

Each handles all three surfaces (state + URL + DOM) in one place. opts carries the freshness token (isStale), the URL-write mode ('push' | 'replace' | 'none'), and any caller-specific overrides.

YAGNI gate (stays as-filed until met)

Per the Codex synthesis on #187:

Consider a larger selectSample / selectCluster / clearSelection controller only if the next selection feature again has to touch click, boot, hashchange, filter, and URL paths.

So don't open a PR for this yet. Open it when:

  • A new selection feature lands that would otherwise touch all five existing paths, OR
  • A bug like "X path forgot to update the URL" / "Y path forgot to clear the side panel" recurs after Step 1's freshness primitive (which would suggest the mutation class isn't covered by freshness alone).

Until either trigger fires, the cost of consolidation isn't justified — three call sites isn't enough to force it, and the OJS-imperative idiom is the project's existing convention.

What this issue is NOT

Refs

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestexplorerInteractive Explorer features

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions