Skip to content

[codex] Phase 2: add globe cross-filter facet counts - #158

Merged
rdhyee merged 1 commit into
isamplesorg:mainfrom
rdhyee:codex/issue-156-phase-2-crossfilter
May 1, 2026
Merged

[codex] Phase 2: add globe cross-filter facet counts#158
rdhyee merged 1 commit into
isamplesorg:mainfrom
rdhyee:codex/issue-156-phase-2-crossfilter

Conversation

@rdhyee

@rdhyee rdhyee commented May 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements issue #156 Phase 2 on top of the merged Phase 1 work.

  • adds the cross-filter cache URL and source/facet count spans with data attributes
  • ports live cross-filter count refresh to plain JS using DuckDBClient db.query()
  • updates counts in place, dims zero-count rows, and refreshes after filter/search changes

Verification

  • quarto render tutorials/progressive_globe.qmd
  • git diff --check

Draft because phases 2, 3, and 4 are parallel PRs and may need final integration ordering.

@rdhyee
rdhyee marked this pull request as ready for review May 1, 2026 17:40
@rdhyee
rdhyee merged commit 4e6cd52 into isamplesorg:main May 1, 2026
1 check passed
rdhyee added a commit to rdhyee/isamplesorg.github.io that referenced this pull request May 8, 2026
State-contract doc for explorer.qmd: enumerates every URL query param,
hash param, DOM-as-state element, viewer.* / window.* widget field, and
OJS cell with deps + side effects. Records the search-semantics decision
(option B, side-panel lookup) and restates the facet-count contract from
isamplesorg#158. Doc-only; no code changes.

Refs isamplesorg#163, isamplesorg#158, PR isamplesorg#162.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rdhyee added a commit that referenced this pull request May 8, 2026
* docs: add EXPLORER_STATE.md (closes #164)

State-contract doc for explorer.qmd: enumerates every URL query param,
hash param, DOM-as-state element, viewer.* / window.* widget field, and
OJS cell with deps + side effects. Records the search-semantics decision
(option B, side-panel lookup) and restates the facet-count contract from
#158. Doc-only; no code changes.

Refs #163, #158, PR #162.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* explorer-state: add option C (side-panel + result-pin overlay) per #165 review

Codex review of #165 surfaced a third option that supersedes the prior
A/B framing: side-panel lookup with a temporary point-overlay of matching
samples on the globe. Backend contract is unchanged from option B —
cluster layer and facet counts remain unaffected — but the UI surface
gains a visual answer to "where did my search land geographically."

Resolves #163 item 4 (zero results + populated map UX wart) cleanly:
zero results render zero pins; non-zero results render distinguishable
result pins layered above the cluster layer.

Adds a small state-inventory addendum: viewer.searchResultPoints (new
Cesium primitive collection), and an optional viewer._searchResults
cache. URL/hash params and DOM-as-state are unchanged.

Refs #163, #165, PR #166.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* explorer-state: tighten option C UX rules + reconcile §9 (round 2 review)

Codex round-2 review on #165 flagged that option C's UX was
under-specified and §9 still pointed at the superseded "side-panel-copy
fix" framing for #163 item 4.

§6 additions:
- Implementation rules table: 50-pin cap, z-order above cluster +
  sample-mode, hollow ring styling on source palette, hover/click
  behaviors mirror existing patterns, fit-to-bounds only when result
  extent < 30° × 30° (otherwise fly-to-top-1 to avoid disorienting
  zoom-out on globally distributed result sets).
- Result-set shape acceptance table: zero, one, local-many, global-many
  must each be visually verified.

§9 reconciliation:
- #163 item 4 framing updated from "side-panel-copy fix" to "resolved
  by option C with the four shape cases verified."

Refs #163, #165, PR #166.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* explorer-state: clarify option C pin-count wording (round 3 nit)

Codex round-3 wording nit: "Never render more, never render fewer than
the result set size" was ambiguous since the full match set may exceed
50. Reword as "Pin count equals min(50, total_matches)" — the displayed
result set is what gets pinned, not the full match set.

Refs #165, PR #166.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rdhyee added a commit that referenced this pull request Aug 5, 2026
Eric Kansa reported (#340) that searching "pottery Cyprus" returns 1,305
results while facet option counts still show the whole-corpus totals.
Reproduced on prod: the counts are the verbatim unfiltered global baseline
(SESAR 4,389,231 / OpenContext 1,104,985 / GEOME 291,210 / Smithsonian
240,816), even though all 1,305 hits are OpenContext.

Root cause is NOT a missing search predicate. The search-aware count path
exists, but under an active search the recompute never reaches a terminal
repaint: nothing calls applyFacetCounts / markFacetCountsUnavailable /
markFacetCountsPending, and markFacetCountsRecomputing() only adds a CSS
class, so the previously-painted global numbers stay on screen forever.
Instrumented live: 60/60 .facet-count elements still carried .recomputing
15s+ after the search completed cleanly, and a forced camera move (a fresh
request) did not clear it. Reproduced on deep-link AND interactively-typed
search, with all three facet trees confirmed rendered.

Search-aware cross-filtered counts are PLAN_305 Phase 3, deferred past the
grant. Rather than debug the stall during closeout, apply the Honesty rule
the plan already commits to: an active search means counts are unavailable.

- refreshFacetCounts() bails to markFacetCountsUnavailable() when
  searchIsActive(), before arming the debounce. Bailing early also bumps
  facetCountsReqId so a late-settling older query cannot overwrite the dash.
- The three search producers (text, concept, substrate) call
  refreshFacetCounts() immediately after publishing active:true, closing the
  race where a refresh scheduled while inactive fires after activation and
  re-enters the bad path. Re-check at debounce fire time as defense in depth.
- markFacetCountsUnavailable/-Pending now clear stale .facet-row.zero via a
  shared clearStaleZeroRows(): an unknown count must not imply zero.

Verified against a local render with real R2 data: search -> 60/60 "(—)",
0 recomputing, 0 stale zero rows, search itself unaffected (1,305 hits, pins
and side panel intact); clearing search restores real counts; the no-search
facet path is unchanged (object_type=artifact still gives SESAR 0 /
OpenContext 261,086 / GEOME 0 / Smithsonian 0). tests/test_frontend_derived.py
40 passed.

Docs: corrected the stale #158 facet-count contract in EXPLORER_STATE.md §7
(viewport and search rows were both already false in code), flagged the
superseded option-(C) description in §6 and the obsolete count-implementation
note, and recorded Phase 3 status + a post-grant probe list in
PLAN_305_facet_counts.md.

Reviewed by Codex over three rounds; it caught the activation race and the
overstated claims in my first draft of the docs.

Refs #340, #304, #305

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QCCDurpcLzMe7L72y2HDAa
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant