From c68cfd283c5b7e58e67c14844dce4bbbf99312a7 Mon Sep 17 00:00:00 2001 From: Gaurav Vaidya Date: Thu, 23 Apr 2026 23:39:08 -0400 Subject: [PATCH 01/13] Add Autocomplete playground tool to Astro/Vue frontend Live-as-you-type NameRes with debounce+abort, preset dropdown (Disease/Gene/SmallMolecule/Custom), latency badges, Solr highlighting, expected-CURIE rank check, and single- or multi-instance views with rank/label/types diff highlighting. State round-trips through the URL for shareable review links. Co-Authored-By: Claude Opus 4.7 --- CLAUDE.md | 39 +- web/README.md | 52 ++- web/src/components/Navbar.astro | 5 + .../autocomplete/AutocompleteApp.vue | 367 ++++++++++++++++++ .../AutocompleteComparisonView.vue | 148 +++++++ .../autocomplete/AutocompleteForm.vue | 291 ++++++++++++++ .../autocomplete/AutocompleteResults.vue | 137 +++++++ .../autocomplete/ExpectedCuriePanel.vue | 106 +++++ .../autocomplete/HighlightedFragment.vue | 29 ++ .../components/autocomplete/LatencyBadge.vue | 38 ++ .../__tests__/AutocompleteApp.test.ts | 142 +++++++ .../__tests__/AutocompleteForm.test.ts | 93 +++++ .../lib/__tests__/autocomplete-diff.test.ts | 113 ++++++ .../__tests__/autocomplete-url-state.test.ts | 157 ++++++++ web/src/lib/__tests__/debounce.test.ts | 58 +++ .../lib/__tests__/highlight-sanitize.test.ts | 69 ++++ web/src/lib/autocomplete-diff.ts | 138 +++++++ web/src/lib/autocomplete-presets.ts | 65 ++++ web/src/lib/autocomplete-url-state.ts | 141 +++++++ web/src/lib/debounce.ts | 39 ++ web/src/lib/highlight-sanitize.ts | 30 ++ web/src/pages/autocomplete.astro | 15 + web/src/pages/index.astro | 12 + web/tests/README.md | 31 +- 24 files changed, 2282 insertions(+), 33 deletions(-) create mode 100644 web/src/components/autocomplete/AutocompleteApp.vue create mode 100644 web/src/components/autocomplete/AutocompleteComparisonView.vue create mode 100644 web/src/components/autocomplete/AutocompleteForm.vue create mode 100644 web/src/components/autocomplete/AutocompleteResults.vue create mode 100644 web/src/components/autocomplete/ExpectedCuriePanel.vue create mode 100644 web/src/components/autocomplete/HighlightedFragment.vue create mode 100644 web/src/components/autocomplete/LatencyBadge.vue create mode 100644 web/src/components/autocomplete/__tests__/AutocompleteApp.test.ts create mode 100644 web/src/components/autocomplete/__tests__/AutocompleteForm.test.ts create mode 100644 web/src/lib/__tests__/autocomplete-diff.test.ts create mode 100644 web/src/lib/__tests__/autocomplete-url-state.test.ts create mode 100644 web/src/lib/__tests__/debounce.test.ts create mode 100644 web/src/lib/__tests__/highlight-sanitize.test.ts create mode 100644 web/src/lib/autocomplete-diff.ts create mode 100644 web/src/lib/autocomplete-presets.ts create mode 100644 web/src/lib/autocomplete-url-state.ts create mode 100644 web/src/lib/debounce.ts create mode 100644 web/src/lib/highlight-sanitize.ts create mode 100644 web/src/pages/autocomplete.astro diff --git a/CLAUDE.md b/CLAUDE.md index 23d6a57..fa3099a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -117,16 +117,19 @@ cd web && npm run test:watch # Watch mode - Astro + Vue 3 static site for browser-only tools (no backend required) - npm package name: `babel-explorer` (directory stays `web/`) - Deployed to GitHub Pages - - Currently hosts: NodeNorm lookup (unified instance selection via checkboxes, expandable result table, shareable URLs) - - Calls NodeNorm API directly from the browser via `fetch()` (CORS-enabled) + - Currently hosts: + - **NodeNorm lookup** — unified instance selection, expandable result table, shareable URLs + - **NameRes lookup** — batch name → CURIE resolution with expected-CURIE validation via `[[CURIE]]` annotations + - **Autocomplete playground** — live-as-you-type NameRes with preset dropdown (Disease/Gene/SmallMolecule/Custom), debounce+abort, latency badges, expected-CURIE rank check, and side-by-side environment comparison + - Calls NodeNorm and NameRes APIs directly from the browser via `fetch()` (CORS-enabled) - Uses Bootstrap 5 (CDN) with the same dark-navbar styling as the Python frontend - CURIE link-outs via [biolink-model prefix map](https://github.com/biolink/biolink-model) (v4.3.7, fetched at runtime) - - Tested with Vitest + @vue/test-utils + happy-dom (99 tests); see `web/tests/README.md` + - Tested with Vitest + @vue/test-utils + happy-dom (275 tests); see `web/tests/README.md` - See `web/README.md` for development instructions and `web/FUTURE.md` for deferred features 7. **Shared Configuration** (`config/`): - `config/translator-endpoints.json` — single source of truth for NodeNorm and NameRes deployment URLs across all environments (dev, exp, ci, test, prod) - - Consumed by Python CLI/frontend (`nodenorm.py`) and Astro frontend (`NodeNormApp.vue`) + - Consumed by Python CLI/frontend (`nodenorm.py`) and Astro frontend (`NodeNormApp.vue`, `NameResApp.vue`, `AutocompleteApp.vue`) ### Data Flow @@ -148,25 +151,31 @@ web/src/ layouts/BaseLayout.astro # Bootstrap 5 CDN + dark navbar components/ Navbar.astro # Dark navbar matching Python frontend - nodenorm/ - NodeNormApp.vue # Root Vue island (client:only="vue") - NodeNormForm.vue # Form: textarea, checkboxes + custom URL, API toggles - ComparisonView.vue # Results table (rows=CURIEs, cols=instances) with expandable rows - CurieDetailPanel.vue # Expandable detail body: description, types, IC, equiv IDs - CurieResultCard.vue # Accordion card wrapping CurieDetailPanel (single-instance use) - ResultsSummary.vue # Unified stat tiles: normalized count, disagreements, types - EquivalentIdTable.vue # Striped table with togglable columns - ColumnVisibility.vue # Page-wide column show/hide controls + nodenorm/ # NodeNorm tool components (App, Form, ComparisonView, CurieDetailPanel, CurieResultCard, ResultsSummary, EquivalentIdTable, ColumnVisibility) + nameres/ # NameRes tool components (App, Form, ComparisonView, DetailPanel, ResultsSummary) + autocomplete/ # Autocomplete tool components (App, Form, Results, ComparisonView, HighlightedFragment, ExpectedCuriePanel, LatencyBadge) shared/ + InstanceSelector.vue # Reusable env checkboxes + custom URL + localStorage prefs CurieLink.vue # CURIE → external URL link using biolink prefix map lib/ nodenorm-api.ts # fetch() wrapper for NodeNorm get_normalized_nodes (AbortSignal support) + nameres-api.ts # fetch() wrapper for NameRes /lookup; parseSearchTerms; validateExpectedCuries + nameres-types.ts # NameResResult, NameResApiOptions, DEFAULT_NAMERES_OPTIONS + nameres-url-state.ts # Encode/decode NameRes query state + autocomplete-url-state.ts # Encode/decode Autocomplete query state (q, preset, target, expected, debounce, highlight) + autocomplete-presets.ts # Disease/Gene/SmallMolecule/Custom preset definitions + detectPreset + autocomplete-diff.ts # computeInstanceDiffs (presence/rank/label/types mismatch) + classifyExpectedCurie + debounce.ts # debounce(fn, ms) with .cancel(); synchronous path when ms===0 + highlight-sanitize.ts # Whitelist sanitizer for Solr highlighting ( only) + instance-prefs.ts # Shared sessionPrefs + localStorage helpers (used by InstanceSelector) curie-links.ts # Biolink prefix map loader + URL builder - url-state.ts # Encode/decode query state in URL params (readQueryState, buildQueryUrl) - types.ts # TypeScript interfaces + DEFAULT_API_OPTIONS + url-state.ts # Encode/decode NodeNorm query state + types.ts # NodeNorm TypeScript interfaces + DEFAULT_API_OPTIONS pages/ index.astro # Landing page with tool cards nodenorm.astro # NodeNorm tool page + nameres.astro # NameRes tool page + autocomplete.astro # Autocomplete playground page ``` Key patterns: diff --git a/web/README.md b/web/README.md index be63d53..e9a34ce 100644 --- a/web/README.md +++ b/web/README.md @@ -8,7 +8,7 @@ babel-explorer has **two** web frontends: | Frontend | Stack | Deployment | Tools | Directory | |----------|-------|------------|-------|-----------| -| **This one** | Astro + Vue 3 | GitHub Pages | NodeNorm (more planned) | `web/` | +| **This one** | Astro + Vue 3 | GitHub Pages | NodeNorm, NameRes, Autocomplete | `web/` | | **Python frontend** | FastAPI + htmx | Kubernetes | XRefs, IDs, Test Concordance, NodeNorm | `src/babel_explorer/web/` | The split follows data dependencies: tools that only call external APIs (NodeNorm, NameRes) can run entirely in the browser. Tools that query multi-GB Parquet files via DuckDB need a server. @@ -28,6 +28,26 @@ Both frontends share the same Bootstrap 5 dark-navbar styling for visual consist - **Shareable URLs**: Query state encoded in URL params (`?curie=`, `?target=`, non-default options); Share button copies link to clipboard; auto-submits on page load when URL contains CURIEs - **CURIE link-outs**: Identifiers link to external resources via [biolink-model prefix map](https://github.com/biolink/biolink-model) (v4.3.7) +### NameRes Lookup (`/nameres`) + +- **Batch name → CURIE resolution**: Enter multiple search terms (one per line), see ranked results per instance +- **Expected-CURIE validation**: Annotate any line with `[[CURIE]]` to mark expected results; validation reports success/partial/failure with the rank of the best match, configurable via a "fail if in top N" threshold +- **Multi-instance comparison**: Same unified instance selector as NodeNorm +- **API tuning**: `biolink_type`, `only_prefixes`, `exclude_prefixes`, `only_taxa` exposed in the form; limit and autocomplete mode toggles; shareable URL state and JSON export + +### Autocomplete Playground (`/autocomplete`) + +Purpose-built for evaluating NameRes as a real autocomplete (the primary way the Translator UI uses it). + +- **Live-as-you-type**: every keystroke re-queries after a configurable debounce (0/150/300/500 ms); in-flight requests are aborted via `AbortController` so stale responses never render +- **Preset dropdown**: one-click switches between the three Translator UI query shapes — Disease (`DiseaseOrPhenotypicFeature` + `only_prefixes=MONDO|HP`), Gene, Small Molecule — plus Custom. Presets only set `biolink_type` and `only_prefixes`; every other field stays user-editable +- **Advanced options**: debounce, autocomplete flag, highlighting toggle, exclude_prefixes, only_taxa (collapsed by default, auto-opens when any non-default is set) +- **Latency badges**: per-instance response time, with a tooltip noting parallel-contention when comparing multiple environments +- **Match-reason highlighting**: renders NameRes's Solr `highlighting` fragments (``-wrapped matches) behind a whitelist sanitiser — the only place `v-html` is used in the codebase +- **Single- vs multi-instance views**: one instance → rich ranked table with copy-API-URL per row; multiple → side-by-side comparison table with row/cell styling for missing CURIEs, rank drift, label/types mismatches +- **Expected-CURIE panel**: paste CURIEs you expect to see; "Check" button fires a `limit=100` parallel lookup per instance and shows whether each expected CURIE appears in the top-N (green), top-100 (amber) or is missing (red). Round-trips through URL state for shareable review links +- **Shareable state**: `q`, `preset`, repeated `target`, repeated `expected`, per-field option overrides, non-default `debounce` and `highlight` all encoded in the URL + ## Development ```bash @@ -41,7 +61,7 @@ This starts a local dev server at `http://localhost:4321/babel-explorer/`. ## Testing ```bash -npm test # Run all 99 Vitest unit + component tests +npm test # Run all Vitest unit + component tests (~275 tests across lib and components) npm run test:watch # Watch mode ``` @@ -78,24 +98,30 @@ src/ pages/ index.astro # Landing page with tool cards nodenorm.astro # Hosts NodeNormApp Vue island + nameres.astro # Hosts NameResApp Vue island + autocomplete.astro # Hosts AutocompleteApp Vue island components/ Navbar.astro # Shared navbar (Astro component) - nodenorm/ # NodeNorm Vue components - NodeNormApp.vue # Root island: orchestrates form + results - NodeNormForm.vue # CURIE input, checkbox instance selection, custom URL, API options - ComparisonView.vue # Results table with expandable per-CURIE rows - CurieDetailPanel.vue # Detail body: description, types, IC, equiv IDs table - CurieResultCard.vue # Accordion card wrapping CurieDetailPanel - ResultsSummary.vue # Stat tiles: normalized count, disagreements, type badges - EquivalentIdTable.vue # Equiv ID table with togglable columns - ColumnVisibility.vue # Column show/hide controls + nodenorm/ # NodeNorm Vue components (App, Form, ComparisonView, CurieDetailPanel, CurieResultCard, ResultsSummary, EquivalentIdTable, ColumnVisibility) + nameres/ # NameRes Vue components (App, Form, ComparisonView, DetailPanel, ResultsSummary) + autocomplete/ # Autocomplete Vue components (App, Form, Results, ComparisonView, HighlightedFragment, ExpectedCuriePanel, LatencyBadge) shared/ + InstanceSelector.vue # Env checkboxes + custom URL + localStorage prefs (used by all tools) CurieLink.vue # CURIE → external URL link lib/ nodenorm-api.ts # NodeNorm API fetch wrapper (supports AbortSignal) + nameres-api.ts # NameRes /lookup wrapper; parseSearchTerms; validateExpectedCuries + nameres-types.ts # NameRes TypeScript interfaces + DEFAULT_NAMERES_OPTIONS + nameres-url-state.ts # NameRes URL-state encode/decode + autocomplete-url-state.ts # Autocomplete URL-state encode/decode + DEFAULT_AUTOCOMPLETE_OPTIONS + parseExpectedCuries + autocomplete-presets.ts # Disease/Gene/SmallMolecule/Custom presets + detectPreset + autocomplete-diff.ts # Cross-instance diff signals + expected-CURIE classification + debounce.ts # debounce(fn, ms) with .cancel() and synchronous 0-ms path + highlight-sanitize.ts # Whitelist sanitizer — allow only / + instance-prefs.ts # sessionPrefs + localStorage helpers curie-links.ts # Biolink prefix map loader - url-state.ts # Encode/decode query state in URL params - types.ts # TypeScript interfaces + url-state.ts # NodeNorm URL-state encode/decode + types.ts # NodeNorm TypeScript interfaces ``` ## Adding a New Tool diff --git a/web/src/components/Navbar.astro b/web/src/components/Navbar.astro index d503039..e898fff 100644 --- a/web/src/components/Navbar.astro +++ b/web/src/components/Navbar.astro @@ -25,6 +25,11 @@ const base = rawBase.endsWith('/') ? rawBase : rawBase + '/'; NameRes +