explorer: support described-by=<concept-uri> search (#248 Flavor A)#252
Merged
Conversation
…Uri + buildConceptFilter First, additive pieces for `described-by=<concept-uri>` (isamplesorg#248), riding the A1 search_pids machinery (Codex plan-reviewed: "mostly sound + guardrails"): - window.conceptLabelForUri(uri): expose the facetFilters cell's URI→prefLabel resolver so the concept producer can label a URI without re-querying vocab_labels (guardrail #1). - buildConceptFilter(uri): a SECOND search_pids producer — exact-URI match across the object_type/material/context columns of sample_facets_v2, with object_type>material>context relevance ranking. Same token-scoped staging, finally-drop, shared _searchFilterToken, and empty-clear invariant as buildSearchFilter (guardrails #2/#6). Tags __searchFilter.kind ('concept' vs 'text') for mutual exclusivity (#5). Not yet wired: doDescribedBy flow (shared runPidSetResults render), the described-by= URL param + writeQueryState kind-preservation, and mutual exclusivity at producer entry. buildConceptFilter isn't called yet, so this is behavior-neutral. Verified: conceptLabelForUri('…organismpart')→"Organism part"; free-text a1-verify still ✅ COHERENT. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… filter Wire the concept-URI filter end to end on top of the A1 search_pids machinery. A `described-by=<uri>` deep link selects material samples whose URI-valued facet concept (object_type / material / context) exactly matches the URI, and filters EVERY surface (table, globe points, facet counts, side panel) via the same `pid IN (SELECT pid FROM search_pids)` semi-join. - doDescribedBy(uri): second entry point into search_pids. Reuses the already -reviewed buildConceptFilter (producer) + applySearchFilterChange (refresher, kind-agnostic) and renders its OWN side panel. Deliberately does NOT touch doSearch — keeps A1's just-shipped (isamplesorg#251) text-search stale-guard hot path untouched (RY decision: protect the path Kerstin demos Wed). - writeQueryState: search= and described-by= are mutually exclusive. Keyed off the text input (always current) rather than window.__searchFilter (stale at doSearch's early writeQueryState call), so a committed text search drops described-by= and vice-versa. - Boot: described-by= deep link auto-commits and wins over search= if both present. - tests/playwright/described-by-verify.mjs: HEADLESS deep-link coherence test (globe + panel + URL all reflect the concept; mutual-exclusivity with text). Verified: described-by-verify green; a1-verify still green (no A1 regression). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e guard, XSS) Codex round-1 found 3 real issues; all fixed + regression-tested: 1. URL could flip concept→phantom-text on draft text + facet/source toggle. writeQueryState is now intent-aware: doSearch passes commitText to authoritatively persist/clear a text search (it runs before __searchFilter updates); every other caller mirrors the COMMITTED filter in __searchFilter, so draft (un-submitted) text no longer clobbers described-by=. New Playwright check: draft text + source toggle preserves described-by=. 2. A superseded doDescribedBy could still rewrite the URL. Moved the _searchSeq stale-check to BEFORE writeQueryState (and re-check after the async applySearchFilterChange) so only the current producer persists state. 3. Reflected-XSS path: the concept side panel interpolated a URL-derived label (and result label/pid/url/name) into innerHTML. Now escapeHtml'd (the same helper the table renderer uses), covering text + attribute contexts. Verified: described-by-verify green (concept + draft-text + mutual-exclusivity); a1-verify still green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jun 1, 2026
rdhyee
added a commit
that referenced
this pull request
Jun 2, 2026
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.
Closes #248 (Flavor A).
What
Adds a
described-by=<concept-uri>query param that filters the entire explorer — table, globe points, facet counts, side panel — down to material samples whose URI-valued iSamples-vocabulary concept (object_type/material/contextinsample_facets_v2) exactly matches the URI.Example deep link (cross-domain — biology, not archaeology):
/explorer.html?described-by=https://w3id.org/isample/vocabulary/materialsampleobjecttype/1.0/wholeorganism→ "Samples described by: Whole organism material sample (50 of 291,210)", every surface filtered.
This is Eric Kansa's requested concept-URI search. Flavor B (arbitrary external/Getty URIs needing URI→label resolution + free-text fallback) is a follow-up.
How
Builds directly on the A1
search_pidsmachinery (#251). Because A1 made every surface read from the single sharedsearch_pidstable, #248 only needed a second producer — no surface changes:buildConceptFilter(uri)— mirrorsbuildSearchFilter's token-scoped staging + finally-drop guards; WHERE is exact-URI match across the three URI-valued facet columns; ranksobject_type > material > context. (escSql-escaped.)doDescribedBy(uri)— the second entry point. ReusesbuildConceptFilter+applySearchFilterChange(kind-agnostic) and renders its own side panel. Deliberately does not touchdoSearch— keeps A1's just-shipped text-search stale-guard hot path untouched.writeQueryState—search=anddescribed-by=are mutually exclusive; intent-aware so a committed text search and a committed concept filter each own the URL, and draft (un-submitted) text can't clobber an active concept filter.described-by=deep link auto-commits and wins oversearch=.Review / verification
tests/playwright/described-by-verify.mjs(new): concept deep-link coherence (globe + panel + URL) + the draft-text-safety regression + mutual-exclusivity with text search. Green headless.a1-verify.mjsstill green — no A1 regression.tests/test_smoke.py) green locally — text search path unaffected.🤖 Generated with Claude Code