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
Add interlinearizer project storage and createProject command (#33)
* Add interlinearizer project storage and createProject command
- Add `src/projectStorage.ts` with `createProject`, `getProject`, `listProjects`, and `deleteProject` backed by `papi.storage`
- Register `interlinearizer.createProject` command in `main.ts` that prompts for source/target projects, writes the record, and surfaces storage errors as notifications
- Add `InterlinearProject` type and `interlinearizer.createProject` signature to shared type declarations
- Extend PAPI backend mock with `papi.storage` and `papi.notifications`
- Broaden Jest coverage to all `src/**` files
- Add full test suites for the storage module and the new command
* Documentation improvement
* Documentation improvements, improve error handling when deleting project and add associated test, fix schema inconsistency
* Set `restoreMocks` to `true` in jest config
* Make sure notification failure doesn't cause unnecessary throw, reorder index/data write to `papi` storage to ensure data doesn't become inaccessible
* Prevent same-project selection when creating an interlinear project, update mocks to match real API signatures
- If the user picks the same project for both source and target roles, a warning notification is shown and the target picker re-opens until distinct projects are chosen or cancelled.
- Includes new tests for this flow, updated platform-bible-react/utils mocks to match current API shapes, a new localized error string, and minor test/parser cleanups.
* Add missing `localizedStrings`, add clarifying comment, improve test coverage
* Improve docstring coverage
* Add project creation, metadata editing, and project selection UI
Introduces three modal components (CreateProjectModal, ProjectMetadataModal, SelectInterlinearProjectModal) with supporting menu contributions, localized strings, command registrations in main.ts, storage helpers in projectStorage.ts, and updated type definitions. Adds full test coverage for all new components and expands existing test suites to cover the new flows.
* Fix modal close/callback ordering and trim whitespace from language input
- `CreateProjectModal`: guard `onClose()` behind a `!newId` early-return so the modal doesn't close when project creation returns no ID
- `ProjectMetadataModal`: trim whitespace from the language field before saving and passing to callbacks
- Replace `makeHandleProjectCreated` factory with a plain `handleProjectCreated` callback; removes the closure-over-srcId pattern and reads `projectId` from the enclosing scope directly
* Return full project JSON from createProject and add error handling for delete/update
- `interlinearizer.createProject` now returns the full persisted project as a JSON string instead of just the UUID, so the WebView can populate `activeProject` with authoritative server data rather than reconstructing it locally
- `CreateProjectModal.onProjectCreated` callback now receives the parsed `InterlinearProjectSummary` object instead of `(id, writingSystem)` pair
- Add `isInterlinearProjectSummary` type guard to `SelectInterlinearProjectModal` and reuse it in the project list filter and the new create flow
- Wrap `deleteProject` and `updateProjectMetadata` backend handlers in try/catch with logging and error notifications (previously unhandled rejections)
- Register a no-op `interlinearizer.viewProjectInfo` backend command so the platform menu system can surface it; all logic runs in the WebView
- Update tests and type declarations to match
* Use `<dialog>` for modals, guard on falsy update return, and relax gloss/senseRef constraint
- Replace `<div>` containers with `<dialog open aria-labelledby="…">` in `CreateProjectModal`, `ProjectMetadataModal`, and `SelectInterlinearProjectModal` for proper accessibility semantics.
- In `ProjectMetadataModal.handleSave`, return early when `updateProjectMetadata` resolves with a falsy value (mirrors the existing guard in `CreateProjectModal.handleCreate`).
- In `interlinearizer.d.ts`, replace the discriminated union on `Token` and `Phrase` that forbade setting both `gloss`/`glossSenseRef` (or `gloss`/`senseRef`) with optional fields, reflecting the updated rule that `gloss` serves as a local override when both are present.
- Add a test for the falsy-return early-exit path in `ProjectMetadataModal`, and add the missing `waitFor` before negative assertions in the `CreateProjectModal` falsy-return test.
* Fix analysis language default, button state, and storage error propagation
- Default analysis language to "und" (undetermined) instead of "en"
- Normalize whitespace-only language input to "und" on submit
- Disable the create button while submission is in progress
- Rethrow storage errors in getProjectsForSource so callers can distinguish an outage from an empty list
* Throw on malformed createProject response instead of silently skipping onProjectCreated
Previously, if the backend returned a JSON object that failed isInterlinearProjectSummary, the modal would call onClose() without invoking onProjectCreated, silently dropping the contract. Now it throws, routing through the existing error handler. Also fixes an incomplete fixture in the success test that was missing required summary fields.
* Cleanup after incredibly messy rebase
* More post-rebase cleanup
* Improve command registration usage and sanity
* Disable cancel during submission to avoid "I cancelled but the project was still created", update docs
* Move `projectStorage` to `services` directory
* Improve project creation/update error handling, improve project load validation
* Improve `projectStorage` error-handling and documentation
* Add missing `\@throws` docstring
* Rename modal control commands for clarity, fix handlers in InterlinearizerLoader
* Improve docs/schema description
* Split modals into separate component (#62)
Co-authored-by: alex-rawlings-yyc <alex.rawlings@wycliffe.ca>
* Remove TOCTOU race from PAPI storage writes, remove duplicate notifications on error, disable metadata modal buttons when submitting
* Add eslint ignore, add submitting ref
* Add missing docs
* Send notification when created project has unexpected shape, add submitting ref
* Validate name and description in type guard
* Prevent project write race, add aria prop
* Disable buttons while submitting/loading, update docs, make implicit zero-check in ContinuousView explicit
* Skip and log singleton corrupted project records
* Update remaining `tw-` tags
* Extract modal logic into ProjectModals, tighten type guards and coverage
- Add ProjectModals component to own modal state (select/create/metadata) and their transitions, removing this responsibility from InterlinearizerLoader
- Add ProjectModals.test.tsx with full coverage of modal visibility and transition flows
- Strengthen isPlatformError mock to require platformErrorVersion to be a valid number, not just present
- Handle SyntaxError in CreateProjectModal.handleSubmit with a user-visible notification instead of silently failing
- Add test for invalid JSON response in CreateProjectModal
- Add test for non-string description field in SelectInterlinearProjectModal
- Mark isSubmittingRef guard branches with v8 ignore in modals
* Update docs, fix misleading test
* Align `atStart` and `atEnd` length checks, get rid of `createSourceIsSelect`, add function to reset storage queues for testing
* Fix and consolidate modal styles (#74)
* Simplify interlinear model (#63)
* Simplify interlinear model: remove InterlinearAlignment/InterlinearText, add ActiveProject
* Fix model gaps for lossless LCM / PT9 / BT Extension import
* Make `analysisLanguages` required
* Add comments about mapping of BT Extension's `sideNum`
* Update docs/schema
* Further refinement; please see updated description
* Suggested model tweak
* Model idea: Split linking out from analyses
---------
Co-authored-by: D. Ror. <imnasnainaec@gmail.com>
* Align code with simplified interlinear model
Update components, storage, main, and types to match the model introduced in #63: rename commands, fix return types, update JSDoc, and adjust tests throughout.
* Memoize Modal handler callbacks, add `targetProjectId` to modals where relevant to prevent silent deletion, docstring audit
* Add missing JSDocs
* Align stub with actual implementation, send notification when save/submission fails, docs adjustments
* Trimmed down `AGENTS.md` to where it was before post-model-change update
* Prevent stale data race condition, update old TW classes, TW class consolidation
* Prevent double notifications
* Improve docs, minor fixes
* Add aria modal tag where missing
* Post-rebase cleanup
* Make `webViewNonce` optional, revert to earlier TW classes, introduce missing TW classes
* Fix lint error
* Fix Tailwind classes, clean up test files, ignore branches that don't need testing, reintroduce createSourceIsSelect to reopen SelectProjectModal if user cancels project creation
* Fix lint issue
* Fix test failure
---------
Co-authored-by: Danny Rorabaugh <imnasnainaec@gmail.com>
Copy file name to clipboardExpand all lines: AGENTS.md
+28-14Lines changed: 28 additions & 14 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -29,16 +29,20 @@ This is a **Platform.Bible extension** for interlinear Bible text alignment. Pla
29
29
30
30
### Extension entry point
31
31
32
-
`src/main.ts` — called by Platform.Bible on activation. Exports two lifecycle functions:
32
+
[src/main.ts](src/main.ts) — called by Platform.Bible on activation. Exports two lifecycle functions:
33
33
34
-
-`activate(context)` — registers the `interlinearizer.mainWebView` WebView provider, the `interlinearizer.openForWebView`command, the `interlinearizer.continuousScroll` project settings validator, and `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, the `interlinearizer.continuousScroll` project settings validator, and the`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
39
### WebView UI
40
40
41
-
`src/interlinearizer.web-view.tsx` — React component rendered inside Platform.Bible's WebView iframe. `useWebViewScrollGroupScrRef` is a **prop injected by the PAPI host** (not a hook import). Uses PAPI frontend hooks (`useProjectData`, `useProjectSetting`, `useLocalizedStrings`, `useRecentScriptureRefs`) to fetch live data. Renders verse segments as token chips with Tailwind utility classes (all prefixed `tw:`).
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).
42
+
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.
44
+
45
+
[Interlinearizer](src/components/Interlinearizer.tsx) — renders the interlinear view from the loaded book data.
42
46
43
47
The WebView is injected into the main bundle via Webpack's `?inline` query:
44
48
@@ -47,12 +51,20 @@ import interlinearizerReact from './interlinearizer.web-view?inline';
`src/webpack-env.d.ts` declares the `*?inline`, `*?raw`, and `*.scss` module types that make these imports type-safe.
54
+
[src/webpack-env.d.ts](src/webpack-env.d.ts) declares the `*?inline`, `*?raw`, and `*.scss` module types that make these imports type-safe.
51
55
52
56
Two separate Webpack configs handle this: `webpack.config.web-view.ts` builds the React component into `temp-build/`, then `webpack.config.main.ts` copies it into `dist/` alongside contributions, public assets, and type declarations.
53
57
54
58
The WebView root component is assigned to `globalThis.webViewComponent` (not exported) — this is the PAPI WebView contract. Tests must `require()` the module and read `globalThis.webViewComponent` to get the component.
55
59
60
+
### Project modals
61
+
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.
63
+
64
+
### Project storage
65
+
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`.
67
+
56
68
### Styling
57
69
58
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`.
@@ -61,23 +73,24 @@ All UI uses Tailwind CSS (via `src/tailwind.css`). Every Tailwind class is prefi
61
73
62
74
Data flows from Platform.Bible's USJ (Unified Scripture JSON) format through two stages:
63
75
64
-
1.`src/parsers/papi/usjBookExtractor.ts` — converts USJ to the internal `Book` type
65
-
2.`src/parsers/papi/bookTokenizer.ts` — segments and tokenizes the book into `Segment`/`Token` structures with character offsets
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
66
78
67
-
`src/parsers/pt9/interlinearXmlParser.ts` — separately parses Paratext 9 interlinear XML into the alignment model. The XML schema is documented in `src/parsers/pt9/pt9-xml.md`.
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).
68
80
69
-
### Data model (`src/types/interlinearizer.d.ts`)
81
+
### Data model ([src/types/interlinearizer.d.ts](src/types/interlinearizer.d.ts))
-`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.
74
87
-`Book → Segment → Token` — the text hierarchy
75
88
-`TextAnalysis` — flat analysis layer keyed by id (does **not** mirror text hierarchy)
76
-
-`TokenAnalysis / Morpheme` — parse and 1:1 glosses; multiple analyses per token are allowed, distinguished by `status`
77
-
-`AlignmentLink` — links between source and target tokens/morphemes
78
-
-`AlignmentEndpoint` — has either token-level or morpheme-level specificity, never both
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
79
92
80
-
Key invariants: `Segment.baselineText.slice(charStart, charEnd) === Token.surfaceText`; at most one `TokenAnalysis`per token may have `status: 'approved'`. Multi-string content is tagged by BCP47 writing-system codes. `tokenSnapshot` fields detect drift when baseline text changes.
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.
81
94
82
95
### TypeScript path aliases
83
96
@@ -103,9 +116,10 @@ Key semantic properties of the mock setup:
-**`__mocks__/lucide-react.tsx`** — Stubs icon components used in modals.
106
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.
107
121
-**`__mocks__/papi-core.ts`** — Empty module; exists only for module resolution since `@papi/core` is types-only at runtime.
108
-
-**`__mocks__/papi-frontend.ts`** — Stubs `logger` (debug/error/info/warn as jest fns).
122
+
-**`__mocks__/papi-frontend.ts`** — Stubs `logger` (debug/error/info/warn as jest fns) and `papi.commands.sendCommand` / `papi.notifications.send`.
0 commit comments