Skip to content

Analysis catalog: search, sort, filter, and row windowing #231

Description

@alex-rawlings-yyc

The controls deferred from #193, which shipped the panel, rows, and jump-to-usage. The query core (#192, PR #211) already backs all of this — applyCatalogQuery, deriveFacets, CatalogSort, and CatalogFilters are built and tested. This issue is the UI that varies them.

Current state

AnalysisCatalogPanel already routes its rows through applyCatalogQuery, but with a fixed query literal in a useMemo:

{ search: '', sort: 'usageCount', filters: {}, surfaceCollator, glossCollator }

The work is replacing that literal with state the controls drive. Nothing in the query core needs to change.

State placement

Search, sort, and filter state is ephemeral useState inside the panel component. The panel is conditionally rendered, so closing it unmounts the state — behavior 2 below then holds by construction rather than by a reset effect. Deliberately not useWebViewState: persisting a filter across a reload is the classic "where did my data go" bug. (Open/closed and width are already tab-scoped in useWebViewState; that stays.)

Sort control

All five CatalogSort keys: usageCount (the current default), usageCountInBook, surfaceText, gloss, firstUsage. Ties already break by surface form then gloss inside the core, so the listing holds still as unrelated analyses are recorded elsewhere in the draft.

Filter controls

All four groups ship:

  • Facet-derived — books, POS, confidence, and per-name features, built straight from deriveFacets. A facet with fewer than two distinct choices returns undefined and renders no control, which is behavior 4 below. Against today's data only books ever raises a control: nothing writes POS, confidence, or features yet, and deriveFacets correctly yields nothing for them. That is the tested-for outcome, not a gap.
  • Missing gloss — keys off the active analysis language. The most useful filter against real drafts: "what have I recorded but not yet glossed here?"
  • Has / lacks morpheme breakdown — tri-state.
  • Zero usages — decided to ship, with eyes open: Analysis catalog: pure query core (analysis-query.ts, search fold, memoized selector) #192 established that no current write path can produce a zero-usage row, because detachTokenAnalysisLink drops a payload with its last link. The control will find nothing until PT9 import (Import PT9 interlinearizations end-to-end (parser → InterlinearProject) #150) or the catalog's own delete/merge paths land. Building it now keeps the filter set complete and means those features arrive with their filter already working. Note it renders like any other filter — it just matches nothing yet.

Search semantics — accepted as-is

applyCatalogQuery matches the whole trimmed, folded query as a single substring against a per-row blob of surface form, every gloss in every language, and morpheme forms and glosses. Three consequences the search box must not promise otherwise:

  • A multi-word query never matches across fields. λόγος word finds nothing even where the row's form is λόγος and its gloss is word.
  • No match weighting. A hit on a morpheme gloss ranks identically to one on the surface form.
  • No relevance order. Ordering is the chosen sort key alone; rows carry no matched field or offset.

Match highlighting is also not derivable from searchText: the fold runs NFKD and strips nonspacing marks, so folded offsets do not map back to the original characters (šālômsalom, six characters to five). Highlighting would have to re-match against the displayed field.

Accepting these here. Multi-term search, relevance ranking, and highlighting are separate issues if wanted — each is query-core work, not UI work.

Windowing

A new useRowWindow, following the useSegmentWindow / IntersectionObserver precedent but not reusing it: that hook is scroll-anchored around a scripture reference, which a row list has no analogue for. A grow-only window over a sentinel at the list's end is the right size. Resets when the query changes (the filtered array's identity changes). No new dependency.

Behaviors to test

  1. Typing in search filters the visible rows.
  2. Search state does not survive a panel close/reopen — ephemeral by design.
  3. Changing the sort reorders the rows.
  4. A filter with fewer than two distinct values does not render its control.
  5. Each filter group narrows the list, and clearing every choice restores it.
  6. The zero-usages filter keeps only count-0 rows (tested against a hand-built TextAnalysis, since no write path produces one).
  7. Only the windowed slice of rows is in the DOM for a large row set; scrolling extends it.
  8. A row selected by a jump-to-usage survives a search that still matches it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions