Background
Eric Kansa (Slack, 2026-04-30) flagged that our faceted search shows static facet counts that don't update when other filters are selected. He pointed at OpenContext as the reference behavior:
Eric's framing: "particularly the ability to recalculate the items that occupy various facets based on the facets already selected."
Current state
After PR #153, tutorials/isamples_explorer.qmd shows facet counts that come from isamples_202601_facet_summaries.parquet — these are global, baseline counts that never change when the user toggles a filter. Selecting a Material doesn't dim or recount the Sampled Feature / Specimen Type / Source counts.
The pre-rewrite explorer attempted this via isamples_202601_facet_cross_filter.parquet (~6 KB pre-aggregated cache of (filter_facet, filter_value, target_facet, target_value, count) rows) plus on-the-fly queries against sample_facets_v2 for multi-filter combinations. That machinery was dropped in the rewrite to keep the new file small and fast on first paint.
Resolved plan (post-Codex review)
Codex reviewed the original three options and the open questions (comment). The plan is now:
Path: A — restore the cross-filter cache for the common case, with on-the-fly fallback. B is the natural fallback inside A's logic. C is deferred unless measurements show the fallback is unacceptable; it's a data-product decision, not an explorer-only patch.
Scope of dynamic counts (v1):
- In:
source + material + context + object_type. Counts are sample-level, global (not camera/viewport-aware).
- Out: search composition. Search is currently an imperative panel lookup, not persistent filter state. Folding it into count math would require promoting search to a first-class filter with URL sync and reset behavior — not in scope here.
- Out: camera-aware counts. Mixing "what filters are possible in the dataset" with "what is visible in the viewport" creates two mental models on top of the existing H3 cluster caveat. Defer.
Implementation shape:
- Keep baseline counts from
facet_summaries for the no-filters-active case.
- Restore
data-facet / data-value attributes on count spans so we can update text in place without rebuilding checkbox HTML (which would lose mid-interaction selections).
- One debounced
refreshFacetCounts() (~250 ms) with a generation/stale-result guard, mirroring the existing loadViewportSamples / refreshResultsTable patterns.
- Cache lookup (
isamples_202601_facet_cross_filter.parquet) when exactly one selected facet value is active — this matches the parquet's shape (it's a single-filter pre-aggregation).
- On-the-fly fallback for multi-filter combinations: four concurrent
SELECT facet_value, COUNT(*) FROM read_parquet(facets_url) WHERE … GROUP BY facet_value queries, each excluding the facet column being recomputed.
- Dim zero-count rows (opacity ~0.4); don't hide them. Hiding causes layout churn and obscures why selected filters eliminated options.
- Source counts are sample-level, not H3 cluster
dominant_source counts. Document that if needed (the cluster caveat in "How It Works" already covers the dominant_source semantics for the globe view).
Acceptance criteria
- Selecting any filter updates the count next to every other filter value, including the source legend.
- Counts reflect the full filter combination (source + material + context + object_type).
- No flicker on the checkboxes themselves — only the count text changes.
- Zero-count rows visibly dim but remain selectable (clicking them re-enables the value).
- Sub-second perceived response for single-filter cases on the rdhyee preview deploy; multi-filter case may take longer on first interaction.
Implementation hints
- Old explorer's count-update logic lived in cells like
crossFilteredFacets and the immediately-following // Update facet count labels in-place cell. Reference, not direct port — the new explorer is imperative-DOM, not OJS-reactive.
- Cluster-zoom interaction: at cluster zoom, the H3 tier files only carry
dominant_source per cell; material/sampled-feature/specimen filters don't apply to the globe at all. The side-panel counts should still update (showing how many samples match), but the globe view doesn't change until the user zooms to point mode. The existing #facetNote element handles part of this; revisit if it's not enough.
References
Background
Eric Kansa (Slack, 2026-04-30) flagged that our faceted search shows static facet counts that don't update when other filters are selected. He pointed at OpenContext as the reference behavior:
https://opencontext.org/query/?cat=oc-gen-cat-object&type=subjects#tab=0/ovgrd=oc/ov=sqr/aq=oc-gen-cat-object/zm=2/lat=2.8/lng=-5.6
https://opencontext.org/query/?cat=oc-gen-cat-object---oc-gen-cat-pottery&type=subjects#tab=0/ovgrd=oc/ov=sqr/aq=facet-oc-gen-category---oc-gen-cat-pottery/zm=2/lat=16.6/lng=-5.6
Eric's framing: "particularly the ability to recalculate the items that occupy various facets based on the facets already selected."
Current state
After PR #153,
tutorials/isamples_explorer.qmdshows facet counts that come fromisamples_202601_facet_summaries.parquet— these are global, baseline counts that never change when the user toggles a filter. Selecting a Material doesn't dim or recount the Sampled Feature / Specimen Type / Source counts.The pre-rewrite explorer attempted this via
isamples_202601_facet_cross_filter.parquet(~6 KB pre-aggregated cache of(filter_facet, filter_value, target_facet, target_value, count)rows) plus on-the-fly queries againstsample_facets_v2for multi-filter combinations. That machinery was dropped in the rewrite to keep the new file small and fast on first paint.Resolved plan (post-Codex review)
Codex reviewed the original three options and the open questions (comment). The plan is now:
Path: A — restore the cross-filter cache for the common case, with on-the-fly fallback. B is the natural fallback inside A's logic. C is deferred unless measurements show the fallback is unacceptable; it's a data-product decision, not an explorer-only patch.
Scope of dynamic counts (v1):
source + material + context + object_type. Counts are sample-level, global (not camera/viewport-aware).Implementation shape:
facet_summariesfor the no-filters-active case.data-facet/data-valueattributes on count spans so we can update text in place without rebuilding checkbox HTML (which would lose mid-interaction selections).refreshFacetCounts()(~250 ms) with a generation/stale-result guard, mirroring the existingloadViewportSamples/refreshResultsTablepatterns.isamples_202601_facet_cross_filter.parquet) when exactly one selected facet value is active — this matches the parquet's shape (it's a single-filter pre-aggregation).SELECT facet_value, COUNT(*) FROM read_parquet(facets_url) WHERE … GROUP BY facet_valuequeries, each excluding the facet column being recomputed.dominant_sourcecounts. Document that if needed (the cluster caveat in "How It Works" already covers the dominant_source semantics for the globe view).Acceptance criteria
Implementation hints
crossFilteredFacetsand the immediately-following// Update facet count labels in-placecell. Reference, not direct port — the new explorer is imperative-DOM, not OJS-reactive.dominant_sourceper cell; material/sampled-feature/specimen filters don't apply to the globe at all. The side-panel counts should still update (showing how many samples match), but the globe view doesn't change until the user zooms to point mode. The existing#facetNoteelement handles part of this; revisit if it's not enough.References
tutorials/isamples_explorer.qmd— current implementation