You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: AGENTS.md
+23-60Lines changed: 23 additions & 60 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -31,27 +31,18 @@ This is a **Platform.Bible extension** for interlinear Bible text alignment. Pla
31
31
32
32
[src/main.ts](src/main.ts) — called by Platform.Bible on activation. Exports two lifecycle functions:
33
33
34
-
-`activate(context)` — stores the `ExecutionToken`, registers the `interlinearizer.mainWebView` WebView provider, the command handlers below, and the`onDidOpenWebView` / `onDidCloseWebView` subscriptions. All registrations are added to `context.registrations` so the platform disposes them on deactivation.
34
+
-`activate(context)` — stores the `ExecutionToken`, registers the `interlinearizer.mainWebView` WebView provider, command handlers, and `onDidOpenWebView` / `onDidCloseWebView` subscriptions. All registrations are added to `context.registrations` so the platform disposes them on deactivation.
35
35
-`deactivate()` — clears `openWebViewsByProject` and returns `true`.
36
36
37
37
`openWebViewsByProject` (`Map<string, string>`) tracks one open WebView ID per project to prevent duplicates; reopening an already-open project brings that tab to front via the `existingId` option.
38
38
39
-
Registered commands:
40
-
41
-
-`interlinearizer.openForWebView` — opens the Interlinearizer for the WebView's project (or a picker if no ID is given).
42
-
-`interlinearizer.createProject` — backend handler that delegates to [projectStorage.createProject](src/services/projectStorage.ts); returns a JSON-serialized `InterlinearProject` or `undefined` on failure.
43
-
-`interlinearizer.getProjectsForSource` — returns a JSON-stringified `InterlinearProject[]` filtered by source project.
44
-
-`interlinearizer.updateProjectMetadata` — updates name/description/analysisLanguages/targetProjectId for a project.
45
-
-`interlinearizer.deleteProject` — deletes a project; no-ops silently when the ID is unknown.
46
-
-`interlinearizer.openSelectProjectModal`, `interlinearizer.openNewProjectModal`, `interlinearizer.openProjectInfoModal` — registered server-side as no-op handlers so the platform menu system knows about them; the actual behavior lives in the WebView, which listens for the matching menu-item activation.
47
-
48
39
### WebView UI
49
40
50
-
[src/interlinearizer.web-view.tsx](src/interlinearizer.web-view.tsx)is the entry point that the PAPI host renders inside its WebView iframe. It just delegates to [InterlinearizerLoader](src/components/InterlinearizerLoader.tsx) when a `projectId` is present. `useWebViewScrollGroupScrRef` and `useWebViewState` are **props injected by the PAPI host** (not hook imports).
41
+
[src/interlinearizer.web-view.tsx](src/interlinearizer.web-view.tsx)— entry point rendered inside Platform.Bible's WebView iframe; delegates to [InterlinearizerLoader](src/components/InterlinearizerLoader.tsx) when a `projectId` is present. `useWebViewScrollGroupScrRef` and `useWebViewState` are **props injected by the PAPI host** (not hook imports).
51
42
52
-
[InterlinearizerLoader](src/components/InterlinearizerLoader.tsx)is the real top of the React tree: it owns modal state, persists the active interlinear project via `useWebViewState`, fetches and tokenizes book data, renders the `TabToolbar` + `ScriptureNavControls` + `ContinuousScrollToggle`, and routes top-menu commands (`openSelectProjectModal` / `openNewProjectModal` / `openProjectInfoModal`) to the appropriate modal. The "View Project Info" item is filtered out of the menu when no project is active.
43
+
[InterlinearizerLoader](src/components/InterlinearizerLoader.tsx)— real top of the React tree: owns modal state, persists the active interlinear project, fetches and tokenizes book data, and routes top-menu commands to the appropriate modal.
53
44
54
-
[Interlinearizer](src/components/Interlinearizer.tsx) renders the actual interlinear view: an optional `ContinuousView` strip above a list of `SegmentView`s for the current chapter.
45
+
[Interlinearizer](src/components/Interlinearizer.tsx)— renders the interlinear view from the loaded book data.
55
46
56
47
The WebView is injected into the main bundle via Webpack's `?inline` query:
57
48
@@ -68,66 +59,38 @@ The WebView root component is assigned to `globalThis.webViewComponent` (not exp
68
59
69
60
### Project modals
70
61
71
-
[src/components/ProjectModals.tsx](src/components/ProjectModals.tsx) is the single mount point for all project-related dialogs; it switches between `'select' | 'create' | 'metadata' | 'none'` based on a `modal` prop owned by `InterlinearizerLoader`:
72
-
73
-
-[SelectInterlinearProjectModal](src/components/SelectInterlinearProjectModal.tsx) — lists existing projects for the source via `interlinearizer.getProjectsForSource`, with an info icon that opens the metadata modal.
74
-
-[CreateProjectModal](src/components/CreateProjectModal.tsx) — collects name, description, and analysis-language tags, then calls `interlinearizer.createProject`.
75
-
-[ProjectMetadataModal](src/components/ProjectMetadataModal.tsx) — edits / deletes an existing project via `interlinearizer.updateProjectMetadata` and `interlinearizer.deleteProject`.
76
-
77
-
The active project is persisted in WebView state under the `activeProject` key so it survives tab restores. The `isInterlinearProjectSummary` type guard in `SelectInterlinearProjectModal.tsx` validates JSON parsed from backend commands without `as` casts.
62
+
[src/components/ProjectModals.tsx](src/components/ProjectModals.tsx) — single mount point for all project-related dialogs, switching between `'select' | 'create' | 'metadata' | 'none'` states. The three modal components ([SelectInterlinearProjectModal](src/components/SelectInterlinearProjectModal.tsx), [CreateProjectModal](src/components/CreateProjectModal.tsx), [ProjectMetadataModal](src/components/ProjectMetadataModal.tsx)) call backend commands to list, create, update, and delete projects.
78
63
79
64
### Project storage
80
65
81
-
[src/services/projectStorage.ts](src/services/projectStorage.ts) owns all `papi.storage` reads and writes for interlinearizer projects:
82
-
83
-
- Projects are persisted under the `project:{uuid}` key; the ordered list of all UUIDs lives at `projectIds`.
84
-
- Two serialization queues prevent interleaved read-modify-write races: `indexQueue` guards the `projectIds` index and a per-project `projectQueues` map guards each project's record. `createProject` rolls the project write back when the index update fails.
85
-
- ENOENT (`isNotFound`) is treated as "key has never been written" rather than an error — used for both project reads and the initial-empty-index case.
86
-
- Tests must call `resetQueuesForTesting()` between tests because module state is not cleared by `resetMocks`.
66
+
[src/services/projectStorage.ts](src/services/projectStorage.ts) — owns all `papi.storage` reads and writes for interlinearizer projects. Two serialization queues prevent interleaved read-modify-write races. Tests must call `resetQueuesForTesting()` between tests because module state is not cleared by `resetMocks`.
87
67
88
68
### Styling
89
69
90
70
All UI uses Tailwind CSS (via `src/tailwind.css`). Every Tailwind class is prefixed `tw:` to avoid collisions with Platform.Bible's own styles (configured in `tailwind.config.ts`). For modifier variants the prefix comes first: `tw:hover:px-3`, not `hover:tw-px-3`.
91
71
92
-
### Hooks
93
-
94
-
[src/hooks/useInterlinearizerBookData.ts](src/hooks/useInterlinearizerBookData.ts) — orchestrates the per-project book pipeline. Reads USJ via `useProjectData('platformScripture.USJ_Book', projectId)` and the writing system via `useProjectSetting('platform.languageTag', ...)`, runs them through `extractBookFromUsj` and `tokenizeBook`, and returns `{ book, chapterSegments, isLoading, bookError, tokenizeError }`. The hook only depends on `scrRef.book` (not chapter/verse) for loading; chapter scoping happens during filtering.
95
-
96
-
[src/hooks/useOptimisticBooleanSetting.ts](src/hooks/useOptimisticBooleanSetting.ts) — wraps `useProjectSetting` with optimistic UI: a toggle update is shown immediately, the platform's confirmation is ignored for `TIMEOUT_MS` (15s) so the toggle does not visibly bounce, and if the platform never confirms the optimistic value persists rather than reverting.
97
-
98
72
### Parser pipeline
99
73
100
74
Data flows from Platform.Bible's USJ (Unified Scripture JSON) format through two stages:
101
75
102
-
1.[src/parsers/papi/usjBookExtractor.ts](src/parsers/papi/usjBookExtractor.ts) — walks USJ nodes (book / chapter / verse / para / note) into a `RawBook`(`bookCode`, `writingSystem`, `contentHash`, `verses`). Heading-class `para` markers are dropped so their text never bleeds into the verse baseline; `note` content is also skipped. The `contentHash` is an FNV-1a 32-bit hash of a stably-stringified `usj.content`, used as `Book.textVersion` to detect baseline drift.
103
-
2.[src/parsers/papi/bookTokenizer.ts](src/parsers/papi/bookTokenizer.ts) — segments and tokenizes the book into `Segment`/`Token` structures. The tokenizer regex uses Unicode property classes (`\p{L}\p{N}\p{M}\p{Join_Control}`), absorbs apostrophes/right-single-quotes at word edges (for languages where they mark phonemic glottal stops), and treats `'`, `-`, Unicode dashes, and `’` as word-internal joiners only when surrounded by word characters. Whitespace is not tokenized; the invariant `Segment.baselineText.slice(charStart, charEnd) === Token.surfaceText` is preserved.
76
+
1.[src/parsers/papi/usjBookExtractor.ts](src/parsers/papi/usjBookExtractor.ts) — converts USJ to the internal `RawBook`type
77
+
2.[src/parsers/papi/bookTokenizer.ts](src/parsers/papi/bookTokenizer.ts) — segments and tokenizes the book into `Segment`/`Token` structures with character offsets
104
78
105
-
[src/parsers/pt9/interlinearXmlParser.ts](src/parsers/pt9/interlinearXmlParser.ts) — separately parses Paratext 9 interlinear XML via `fast-xml-parser`. Strict mode for clusters (required `Range` with non-negative integer `Index`/`Length`; lexemes require `Id`), lenient for punctuation (entries with missing/invalid `Range` are silently filtered). The XML schema is documented in [pt9-xml.md](src/parsers/pt9/pt9-xml.md). The exported `InterlinearXmlParser` class holds one configured `XMLParser` — reuse a single instance across `parse()` calls.
79
+
[src/parsers/pt9/interlinearXmlParser.ts](src/parsers/pt9/interlinearXmlParser.ts) — separately parses Paratext 9 interlinear XML into the alignment model. The XML schema is documented in [pt9-xml.md](src/parsers/pt9/pt9-xml.md).
106
80
107
81
### Data model ([src/types/interlinearizer.d.ts](src/types/interlinearizer.d.ts))
108
82
109
-
The file declares two ambient modules:
110
-
111
-
-`papi-shared-types` — augments `ProjectSettingTypes` (`interlinearizer.continuousScroll: boolean`) and `CommandHandlers` (the seven interlinearizer commands described above) so Platform.Bible's typed APIs know about them.
112
-
-`interlinearizer` — the project's domain types.
113
-
114
-
The core domain types are:
115
-
116
-
-`InterlinearProject` — persisted envelope: id, createdAt, optional name/description, `sourceProjectId`, optional `targetProjectId`, `analysisLanguages`, `analysis: TextAnalysis`, and `links?: AlignmentLink[]`. Only this is serialized to storage; the `Book` hierarchy is rebuilt from USJ on each load.
117
-
-`ActiveProject` — runtime pairing of `project: InterlinearProject` with reconstructed `source: Book[]` and optional `target: Book[]`.
118
-
-`Book → Segment → Token` — the text hierarchy.
119
-
-`TextAnalysis` — flat analysis layer keyed by id (does **not** mirror the text hierarchy). Holds `segmentAnalyses`, `tokenAnalyses`, `phraseAnalyses`, plus a `*Links` array for each that attaches an analysis record to one or more text-layer targets.
120
-
-`TokenAnalysis / MorphemeAnalysis` — parse and 1:1 glosses; multiple analyses per token are allowed, distinguished by `status`.
121
-
-`AlignmentLink` — directional links between source and target endpoints.
122
-
-`AlignmentEndpoint` — has either token-level or morpheme-level specificity via the optional `morphemeLink` field (when present, both `tokenAnalysisId` and `morphemeId` are required).
123
-
-`EntryRef` / `SenseRef` / `AllomorphRef` / `GrammarRef` — references to the Lexicon extension. The file documents current gaps in `IEntryService` that the Lexicon extension is expected to close.
83
+
The core types are:
124
84
125
-
Key invariants:
85
+
-`InterlinearProject` — persisted envelope: id, createdAt, optional name/description, `sourceProjectId`, optional `targetProjectId`, `analysisLanguages`, `analysis: TextAnalysis`, and optional `links`. Only this is serialized to storage; the `Book` hierarchy is rebuilt from USJ on each load.
86
+
-`ActiveProject` — runtime pairing of `project: InterlinearProject` with reconstructed `source` and optional `target` books.
87
+
-`Book → Segment → Token` — the text hierarchy
88
+
-`TextAnalysis` — flat analysis layer keyed by id (does **not** mirror text hierarchy)
89
+
-`TokenAnalysis / MorphemeAnalysis` — parse and 1:1 glosses; multiple analyses per token are allowed, distinguished by `status`
90
+
-`AlignmentLink` — directional links between source and target endpoints
91
+
-`AlignmentEndpoint` — has either token-level or morpheme-level specificity
- At most one linked `TokenAnalysisLink` per token may have `status: 'approved'`; same for `SegmentAnalysisLink` per segment and for `PhraseAnalysisLink` per token covered.
129
-
-`MultiString` values are keyed by BCP 47 tags.
130
-
-`TokenSnapshot.surfaceText` is the drift-detection mechanism: when it no longer matches the current `Token.surfaceText`, consumers flip the link's `status` to `'stale'`.
93
+
Key invariants: `Segment.baselineText.slice(charStart, charEnd) === Token.surfaceText`; at most one linked analysis per token/segment may have `status: 'approved'`. `MultiString` values are keyed by BCP 47 tags. `TokenSnapshot.surfaceText` detects drift when baseline text changes.
131
94
132
95
### TypeScript path aliases
133
96
@@ -153,13 +116,13 @@ Key semantic properties of the mock setup:
-**`__mocks__/lucide-react.tsx`** — Stubs the icon components used in modals (`Info`, `Trash2`).
157
-
-**`__mocks__/papi-backend.ts`** — Mocks with jest fns including `commands`, `dialogs`, `notifications`, `storage`, `webViewProviders`, and `webViews`. Re-exports internal jest fns on the default export as `__mock*` properties (e.g., `papi.__mockRegisterCommand`, `papi.__mockReadUserData`) so tests can assert on them without re-importing. See file for the full list.
119
+
-**`__mocks__/lucide-react.tsx`** — Stubs icon components used in modals.
120
+
-**`__mocks__/papi-backend.ts`** — Mocks with jest fns. Re-exports internal jest fns on the default export as `__mock*` properties (e.g., `papi.__mockRegisterCommand`) so tests can assert on them without re-importing. See file for full list.
158
121
-**`__mocks__/papi-core.ts`** — Empty module; exists only for module resolution since `@papi/core` is types-only at runtime.
159
122
-**`__mocks__/papi-frontend.ts`** — Stubs `logger` (debug/error/info/warn as jest fns) and `papi.commands.sendCommand` / `papi.notifications.send`.
-**`__mocks__/platform-bible-react.tsx`** — Stubs components (`TabToolbar`, `BookChapterControl`, `ScrollGroupSelector`, `Button`, `Switch`, `Label`) with appropriate `data-testid` attributes. See file for test IDs.
162
-
-**`__mocks__/platform-bible-utils.ts`** — Stubs util functions including `isPlatformError`.
0 commit comments