The user-facing read-only catalog for #186: command, menu column, panel shell, rows, search / sort / filter controls, windowing, and jump-to-usage navigation. No mutations — those are a separate issue.
Depends on the store hoist and focus seam, and on the pure query core.
Placement
A panel inside the existing interlinearizer.mainWebView, beside the interlinear view. Not a modal (jump-to-usage must keep it open), and not a separate WebView tab (a separate iframe means a separate Redux store and no live sync — that path needs the query API in #128).
Wiring
interlinearizer.openAnalysisCatalog registered in main.ts alongside the other WebView-handled commands (the /* v8 ignore next */ async () => {} pattern), added to context.registrations.
contributions/menus.json: a new top-menu column. The existing interlinearizer.project column holds project / file / draft actions and this is none of those. New interlinearizer.view column, order 2, one group, one item.
contributions/localizedStrings.json: %interlinearizer_menu_column_view%, %interlinearizer_openAnalysisCatalog%, plus the panel's own strings.
__mocks__/platform-bible-react.tsx: stub Table, Badge, Checkbox, Select, Command, Separator, ToggleGroup as each is first used — not all up front. Add MOCK_OPEN_ANALYSIS_CATALOG_MENU_ITEM and a tab-toolbar-analysis-catalog button, matching the existing per-command pattern.
State
Open/closed and panel width go in useWebViewState — tab-scoped, so they restore with the tab. Deliberately not project settings, which are shared by every tab on that project.
Search, sort, and filter state stays ephemeral useState. Persisting a filter across a reload is the classic "where did my data go" bug.
Row display
Surface form, gloss in the active analysis language, morpheme breakdown as row detail, total usage count, usage count in the current book, and the usage locations parsed from tokenAnalysisLinks[].token.tokenRef. The inline usage list is capped with an expander.
Windowing
Follow the useSegmentWindow / IntersectionObserver precedent (src/hooks/useSegmentWindow.ts) but do not reuse that hook — it is scroll-anchored around a scripture reference, which a row list has no analogue for. A much smaller useRowWindow is the right size. No new dependency.
Behaviors to test
- The menu command opens the panel; the interlinear view stays mounted beside it.
- Open state round-trips through
useWebViewState (restores on remount).
- Panel width round-trips through
useWebViewState.
- A row renders surface form, gloss, and both usage counts.
- Typing in search filters the visible rows.
- Search state does not survive a panel close/reopen — ephemeral by design.
- Changing the sort reorders the rows.
- A filter with fewer than two distinct values does not render its control.
- Clicking a usage navigates the interlinear view to that verse and focuses that exact token (the ref carries
charStart).
- Clicking a usage in another book triggers the book load and focuses the token once it arrives.
- The panel stays open and the row stays selected after a usage click.
- Only the windowed slice of rows is in the DOM for a large row set; scrolling extends it.
- The inline usage list is capped, and the expander reveals the rest.
Cross-book clicks retarget the shared scroll group, which propagates to other tabs in that group — that is the intended behavior, not a bug.
The user-facing read-only catalog for #186: command, menu column, panel shell, rows, search / sort / filter controls, windowing, and jump-to-usage navigation. No mutations — those are a separate issue.
Depends on the store hoist and focus seam, and on the pure query core.
Placement
A panel inside the existing
interlinearizer.mainWebView, beside the interlinear view. Not a modal (jump-to-usage must keep it open), and not a separate WebView tab (a separate iframe means a separate Redux store and no live sync — that path needs the query API in #128).Wiring
interlinearizer.openAnalysisCatalogregistered inmain.tsalongside the other WebView-handled commands (the/* v8 ignore next */ async () => {}pattern), added tocontext.registrations.contributions/menus.json: a new top-menu column. The existinginterlinearizer.projectcolumn holds project / file / draft actions and this is none of those. Newinterlinearizer.viewcolumn, order 2, one group, one item.contributions/localizedStrings.json:%interlinearizer_menu_column_view%,%interlinearizer_openAnalysisCatalog%, plus the panel's own strings.__mocks__/platform-bible-react.tsx: stubTable,Badge,Checkbox,Select,Command,Separator,ToggleGroupas each is first used — not all up front. AddMOCK_OPEN_ANALYSIS_CATALOG_MENU_ITEMand atab-toolbar-analysis-catalogbutton, matching the existing per-command pattern.State
Open/closed and panel width go in
useWebViewState— tab-scoped, so they restore with the tab. Deliberately not project settings, which are shared by every tab on that project.Search, sort, and filter state stays ephemeral
useState. Persisting a filter across a reload is the classic "where did my data go" bug.Row display
Surface form, gloss in the active analysis language, morpheme breakdown as row detail, total usage count, usage count in the current book, and the usage locations parsed from
tokenAnalysisLinks[].token.tokenRef. The inline usage list is capped with an expander.Windowing
Follow the
useSegmentWindow/ IntersectionObserver precedent (src/hooks/useSegmentWindow.ts) but do not reuse that hook — it is scroll-anchored around a scripture reference, which a row list has no analogue for. A much smalleruseRowWindowis the right size. No new dependency.Behaviors to test
useWebViewState(restores on remount).useWebViewState.charStart).Cross-book clicks retarget the shared scroll group, which propagates to other tabs in that group — that is the intended behavior, not a bug.