#208 PR4c: centralize the point/cluster mode decision (filtersForcePoint + computeTargetMode)#301
Merged
Conversation
…() (behavior-neutral)
The point/cluster mode decision was duplicated across four sites: the
camera.changed targetMode expression, applySearchFilterChange's forcePoint,
the deep-link restore's wantsPoint, plus the re-check inside camera.changed.
The forced-point predicate `searchIsActive() || hasFacetFilters()` appeared
verbatim at each.
Centralize into two helpers next to the existing getMode()/searchIsActive()
consts in the zoomWatcher cell:
- filtersForcePoint() — the single "a filter forces point mode" predicate
- computeTargetMode({alt, currentMode}) — the altitude hysteresis authority,
wrapping camera.changed's exact expression
This is the seam isamplesorg#300 needs: relaxing the FACET case above EXIT_POINT_ALT to
filtered clusters becomes a one-place change here instead of a 4-site edit.
Behavior-neutral: every substitution is expression-identical. camera.changed
routes through computeTargetMode (same expr); the other three sites call
filtersForcePoint() for the filter half and keep their exact altitude
comparisons (>= vs >, ENTER vs EXIT). handleFacetFilterChange is intentionally
untouched — it uses hasFacetFilters()/searchIsActive() separately, not the
combined predicate.
Gate: unit 13/13; ojs render OK; characterization [data] 12/13 first pass with
the 1 failure (facet hydration) a cold-cache data-load flake that re-passed in
8s; all mode/URL specs green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jun 19, 2026
Architecture: should we refactor explorer.qmd before the next big feature? (analysis + options)
#249
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Continues the #249/#208 strangler refactor (after PR4a
writeGlobeHash#288 and PR4breconcileSettledCamera#289). Behavior-neutral — no functional change; this is the seam that makes the upcoming #300 feature a one-place edit.Problem
The point/cluster mode decision was duplicated across four sites, with the predicate
searchIsActive() || hasFacetFilters()copy-pasted at each:camera.changedtargetModeexpression (+ its inner re-check)applySearchFilterChange'sforcePointwantsPointChanging the rule (as #300 needs) would mean a coordinated 4-site edit on the delicate mode state machine.
Change
Two helpers in the
zoomWatchercell, next to the existinggetMode()/searchIsActive()consts:filtersForcePoint()— the single "a filter forces point mode" predicatecomputeTargetMode(alt)— the altitude-hysteresis authority, wrappingcamera.changed's exact expressioncamera.changedroutes throughcomputeTargetMode; the other three sites callfiltersForcePoint()for the filter half and keep their exact altitude comparisons (>=vs>,ENTERvsEXIT).handleFacetFilterChangeis intentionally left untouched — it useshasFacetFilters()/searchIsActive()separately, not the combined predicate, so substituting there would not be neutral.getMode()is called incomputeTargetMode's tail branch (not threaded as a param) so it evaluates only in the hysteresis band — exactly as the original inline expression did.Why (#300)
This centralizes where #300 will relax the facet case above
EXIT_POINT_ALTto render filtered H3 clusters instead of forcing point mode.Verification
node --test tests/unit/*.test.mjs— 13/13quarto render explorer.qmd)[data]Playwrightexplorer-characterization+url-roundtripagainst a live preview — 12/13; the single failure (facet hydration source counts) was a cold-cache data-load flake that re-passed in 8s. All mode/URL specs green.🤖 Generated with Claude Code