Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ec25bc0
Pass each book the whole-text read files to listeners
imnasnainaec Oct 23, 2026
b70a768
Build a re-anchor pass's changes as a change set
imnasnainaec Oct 23, 2026
c38b1a7
Re-anchor what undo and redo restore to the current text
imnasnainaec Oct 23, 2026
f6f9a4c
Check host re-anchor parity on every WebView pass case
imnasnainaec Oct 23, 2026
b540b27
Compare the host's re-anchor with the WebView's in the bench
imnasnainaec Oct 23, 2026
0eec4dc
Apply an undo or redo and its re-anchor as one
imnasnainaec Oct 23, 2026
0831194
Pass the open book's edited text on before reporting the edit
imnasnainaec Oct 23, 2026
bb59476
Let a session start and stop the whole-text read
imnasnainaec Oct 23, 2026
4d2ca43
Re-anchor each book the session's read delivers
imnasnainaec Oct 23, 2026
664b267
Compare a restored translation with its payload
imnasnainaec Oct 26, 2026
c4d375b
Leave the unsaved mark as it was after a re-anchor pass
imnasnainaec Oct 26, 2026
bd8e259
Clear the kept draft's unsaved mark when marking it clean
imnasnainaec Oct 26, 2026
b72aed6
Fold readBooks' parameter note into its summary
imnasnainaec Oct 26, 2026
eced113
Tidy the re-anchor tests and docs from review
imnasnainaec Oct 26, 2026
1b760fa
Plan a pass over every book the read delivers
imnasnainaec Oct 26, 2026
919caf4
Keep a session's whole-text read running when its tab closes
imnasnainaec Oct 27, 2026
a9e09ce
Correct the undo, redo and re-anchor docs from review
imnasnainaec Oct 27, 2026
a92c4d4
Give the boundary undo test's session host the text-read members
imnasnainaec Oct 27, 2026
17ea50a
Re-anchor a boundary edit's book in the edit's undo step
imnasnainaec Oct 27, 2026
65aaaa5
Address review: parse-safe docs and one test per scope side
imnasnainaec Oct 27, 2026
c3bf185
Record re-anchor passes only over books the draft links into
imnasnainaec Oct 27, 2026
fedb7f3
Keep re-anchoring every book; record the pass only over linked ones
imnasnainaec Oct 27, 2026
6db3a45
Disambiguate what clears a failure in rereadTextBook's doc
imnasnainaec Oct 27, 2026
8154e05
Record undo passes over books the draft only splits, from the pass's …
imnasnainaec Oct 27, 2026
635e24f
Send onAnalysisChanged only after a pass for new text, per the plan
imnasnainaec Oct 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ Saved projects are listed through [ProjectSummaries](src/services/projectSummari

### Sessions

[AnalysisSessions](src/services/analysisSessions.ts) keeps the host's sessions, behind the `openSession`, `closeSession` and `setAnalysisLanguage` commands. A session is a handle on either one source project's draft, paired with the interlinear project it saves to, or a read-only Paratext 9 import. A source has at most one session: opening another closes it. Each session runs its commands one at a time through `run`. A command that changes stored analysis goes through `runEdit` instead, which a read-only session refuses. An edit reaches the repository as a `ChangeSet` of whole-record puts and deletes, applied through the session's [EditHistory](src/services/editHistory.ts) so that undo can revert it; boundary changes ride in the change set's scope record. The WebView doesn't use sessions yet.
[AnalysisSessions](src/services/analysisSessions.ts) keeps the host's sessions, behind the `openSession`, `closeSession` and `setAnalysisLanguage` commands. A session is a handle on either one source project's draft, paired with the interlinear project it saves to, or a read-only Paratext 9 import. A source has at most one session: opening another closes it. Opening a session also starts a fresh whole-text read of its source, which lasts until the session closes. Each session runs its commands one at a time through `run`. A command that changes stored analysis goes through `runEdit` instead, which a read-only session refuses. An edit reaches the repository as a `ChangeSet` of whole-record puts and deletes, applied through the session's [EditHistory](src/services/editHistory.ts) so that undo can revert it; boundary changes ride in the change set's scope record. Re-anchor passes are the exception. As the read delivers each book, `reanchorBook` re-anchors the draft to it through `run`; `reanchorMissingBooks` does the same for each linked book the text lacks. Both skip a read-only session. A pass is no undo step and leaves the unsaved mark as it was; the history records it, so that an undo or redo re-anchors what it restores. The WebView doesn't use sessions yet.

Host commands live in [src/services/commands/](src/services/commands/), one module per family. A command stages its changes in a [ChangeBuffer](src/services/commands/changeBuffer.ts), whose reads answer as the repository will once those changes apply, and `AnalysisSessions.edit` applies what it staged as one undo step after the approval rule passes. Each command copies the reducer it replaces, step for step, and its parity tests run one input through both with [runParity](src/__tests__/services/commands/parity.ts). A boundary command replaces no reducer; it copies the WebView's boundary dispatch, phrase breaks included, and `runBoundaryParity` runs one edit through both. A reducer or dispatch change in Phase 3 updates its command and parity tests in the same PR.
Host commands live in [src/services/commands/](src/services/commands/), one module per family. A command stages its changes in a [ChangeBuffer](src/services/commands/changeBuffer.ts), whose reads answer as the repository will once those changes apply, and `AnalysisSessions.edit` applies what it staged as one undo step after the approval rule passes. Each command copies the reducer it replaces, step for step, and its parity tests run one input through both with [runParity](src/__tests__/services/commands/parity.ts). A boundary command replaces no reducer; it copies the WebView's boundary dispatch, phrase breaks included, and `runBoundaryParity` runs one edit through both. The session then re-anchors the book's analysis to the new segments in the same undo step, as the WebView's pass does after a boundary edit. A reducer or dispatch change in Phase 3 updates its command and parity tests in the same PR.

### Components

Expand Down
12 changes: 6 additions & 6 deletions docs/data-layer-plan/plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The team decided these on 2026-10-09. "From" names the plan each choice came fro
| Addressing | _Carried:_ each open draft gets a session, a host-side handle for the draft and its interlinear project, addressed by a `sessionId` | J |
| Concurrent commands | _Carried:_ no segment-page versions; the host queues commands and reanchor passes per session and runs one at a time, since async commands would otherwise interleave, and only one WebView opens per source project | — |
| Suggestions | _Carried:_ pool buckets, the approved payloads that share a normalized form, ride in segment pages and in replies; the WebView keeps `deriveTokenSuggestion` | J |
| Whole-text read | _Carried:_ the host reads every book of the source text at session open, re-anchors each linked book as it arrives and builds a form-in-text index, so Stale covers the whole draft once the read finishes. The checkpoint after 2e decides whether that cost is affordable; if not, the read stays on demand, as on main | — |
| Whole-text read | _Carried:_ the host reads every book of the source text at session open, re-anchors each book as it arrives and builds a form-in-text index, so Stale covers the whole draft once the read finishes. The checkpoint after 2e decides whether that cost is affordable; if not, the read stays on demand, as on main | — |

## The seam

Expand Down Expand Up @@ -123,7 +123,7 @@ flowchart TB
p0["<b>0 Performance baseline · #407</b><br/>Done: host and WebView measured;<br/>baseline run on a packaged app"]
p1["<b>1 Seam and link ids · issues 1a–1e</b><br/>Link ids, repository, conformance suite;<br/>Save and picker in the host"]
p2["<b>2 Text in the host · issues 2a–2e</b><br/>Outline and segment pages;<br/>onAnalysisChanged for Scripture edits;<br/>whole-text read and form index"]
p3["<b>3 Host commands, built but not live · issues 3a–3m</b><br/>Sessions, apply and undo, derived indexes, commands with parity tests, reanchor of every linked book, analysis and phrase spans in segment pages, the record cache.<br/>Released builds still write through the WebView reducers."]
p3["<b>3 Host commands, built but not live · issues 3a–3m</b><br/>Sessions, apply and undo, derived indexes, commands with parity tests, reanchor of every book the read delivers, analysis and phrase spans in segment pages, the record cache.<br/>Released builds still write through the WebView reducers."]
subgraph p4["4 The switch · issues 4a–4c"]
direction LR
p4a["<b>4a Behind a feature flag</b><br/>Off by default;<br/>both paths tested"]
Expand Down Expand Up @@ -171,7 +171,7 @@ The host fetches and tokenizes the text, and the WebView renders from an outline
- `getBookOutline` and `getSegments` (text only) feed the segment list, height prediction, the chapter list and the continuous view. Host marks record page-fetch latency.
- A temporary `getTokenizedBook` gives the WebView whole tokenized books: for its reanchor passes and `useBookIndexes` until 4c, and for its whole-text read (the concordance and the catalog's "Word not in the text" and draft-wide Stale filters) until 5d. From 4a, segment pages and their phrase spans (3l) feed the token-level maps. The WebView stops fetching USJ.
- `onAnalysisChanged` arrives here with its first producer: a Scripture edit names the whole book, and the WebView refetches.
- A whole-text read in `BookService` reads every book, keeps a form-in-text index (the normalized forms the text holds), and reports progress and failed books through `getTextReadStatus`. Nothing calls it until 4a; then `openSession` starts it, 3j re-anchors each linked book as it arrives, and 5a and 5d answer from it.
- A whole-text read in `BookService` reads every book, keeps a form-in-text index (the normalized forms the text holds), and reports progress and failed books through `getTextReadStatus`. Nothing calls it until 4a; then `openSession` starts it, 3j re-anchors each book as it arrives, and 5a and 5d answer from it.
- These queries and the event are addressed by source project, since text needs no draft. They also accept a session from 3k, when analysis joins the segment pages; the source-project form is removed in 4c. Until 4c, boundary edits stay in the WebView draft, so the source-project form also takes the WebView's boundary delta: a boundary edit changes the request and the WebView refetches, and the import view sends none.

**Done when:** the WebView neither fetches nor tokenizes USJ, and no view walks the whole book except through `getTokenizedBook`.
Expand All @@ -187,7 +187,7 @@ The whole write path is built and tested in the host while the WebView keeps usi
- **Parity tests.** Each family has them: they run one input through today's reducer and the new command and compare the results.
- **Boundaries and wipes.** These aren't reducers, so their parity tests run against `wipeBook`, `wipeAll` and the WebView's boundary dispatch, which breaks every phrase a new boundary would cut. The host's split and move break those phrases in the same `ChangeSet`, so the WebView needs no book-wide token order for it.
- **Removal previews.** A bulk gloss clear and a stale discard or reapply first ask about any unsaved breakdown on a record they would remove. Today that check runs the reducer, which 4c deletes, so 3i also builds `previewRemovals`.
- **The reanchor pass** over every book the draft links into, through the session's queue.
- **The reanchor pass** over every book the whole-text read delivers, through the session's queue.
- **When it runs.** As 2e's read delivers each book, and again whenever `BookService` gets new text for one. After a pass for new text, that book's `onAnalysisChanged` goes out, so the refetch sees re-anchored links.
- **Undo.** A pass isn't an undo step: an undo or redo re-anchors what it restores to the current text, as `recordBookPass` does today.
- **Missing books.** Links into a book the text lacks go stale, as `reanchorDraftToMissingBook` does today.
Expand All @@ -208,7 +208,7 @@ The WebView moves to the host write path in three releasable steps, with a rollb

1. **Wire it behind a feature flag, off by default.** The WebView can run on sessions, commands, queries and the cache.
- **Read bridge.** A temporary `getFullAnalysis`, removed in 5d, feeds the catalog, the concordance and the deletion preview.
- **Reanchor.** With the flag on, the host re-anchors every linked book during the session-open read and whenever a linked book's text changes. The WebView's whole-text read still feeds the concordance and the catalog's text filters, but no longer re-anchors.
- **Reanchor.** With the flag on, the host re-anchors every book during the session-open read and whenever a book's text changes. The WebView's whole-text read still feeds the concordance and the catalog's text filters, but no longer re-anchors.
- **Stale filter.** It learns from `getTextReadStatus` when it covers the whole draft.
- **Flag.** 4a decides where it lives, such as a localStorage key or a hidden setting; developers and end-to-end tests must both be able to set it. End-to-end tests run on both paths.
2. **Flip the default,** once 4a's edit-latency check passes. The old path stays one release as the rollback; rolling back means a release that flips the default again.
Expand Down Expand Up @@ -260,7 +260,7 @@ The plan splits into 31 work issues plus #407, each one or two PRs that leave th
| **3g** | Segment free-translation and boundary commands, with parity tests; a split or move breaks every phrase the new boundary would cut, in the same `ChangeSet`. _Done when_ its commands pass their parity tests | 3d, 3f |
| **3h** | Stale-handling commands, with parity tests; discard covers orphaned free translations. _Done when_ its commands pass their parity tests | 3d |
| **3i** | Catalog-wide edits and wipes, with parity tests; a wipe's reply carries `hasFreeTranslations` beside the `ChangeSet`; `previewRemovals`. _Done when_ its commands pass their parity tests, a wipe's reply gives `hasFreeTranslations` as main computes it, and `previewRemovals` names the records `useGlossClearReclaims` and `useStaleLocationReclaims` do | 3d, 3h |
| **3j** | Reanchor pass in the host over every book the draft links into, through the session's queue, as 2e's read delivers each one and again whenever `BookService` gets new text for one, sending that book's `onAnalysisChanged` after the pass; not an undo step, since an undo or redo re-anchors what it restores to the current text, as main does; links into a book the text lacks go stale, as `reanchorDraftToMissingBook` does. _Done when_ its result matches the WebView passes on the #407 datasets, which hold no drift, on the drift cases in `reanchor-analysis.test.ts` and on the missing-book cases in `reanchor-draft.test.ts`, undo and redo restore content re-anchored to the current text, and a mid-session Scripture edit re-anchors the open book before its refetch | 2e, 3b |
| **3j** | Reanchor pass in the host over every book, through the session's queue, as 2e's read delivers each one and again whenever `BookService` gets new text for one; after a pass for new text, that book's `onAnalysisChanged` goes out; not an undo step, since an undo or redo re-anchors what it restores to the current text, as main does; links into a book the text lacks go stale, as `reanchorDraftToMissingBook` does. _Done when_ its result matches the WebView passes on the #407 datasets, which hold no drift, on the drift cases in `reanchor-analysis.test.ts` and on the missing-book cases in `reanchor-draft.test.ts`, undo and redo restore content re-anchored to the current text, and a mid-session Scripture edit re-anchors the open book before its refetch | 2e, 3b |
| **3k** | Analysis in segment pages: `getSegments` gains links, payloads, pool buckets and the orphaned free translations placed in the range, the outline gains each segment's orphaned-card count, and command replies carry the touched pool buckets beside the `ChangeSet`; `onAnalysisChanged` gains segment ranges, analysis ids and affected cross-book queries; `getBookOutline`, `getSegments`, `getTextReadStatus` and `onAnalysisChanged` also accept a session, and only the session form returns the analysis fields. _Done when_ on the #407 datasets, `getSegments` returns the same links, payloads and pool buckets the WebView store holds for each range, orphaned free translations land where the WebView places them, and each command's `onAnalysisChanged` names exactly what its `ChangeSet` touched | 2e, 3d to 3i |
| **3l** | Phrase spans in segment pages: the session form of `getSegments` also returns each phrase that touches the range with all its member tokens, and the WebView builds its token-level maps and boundary-slot guard from pages plus spans; a focus request from the catalog, the concordance or undo still lands when its target's page hasn't loaded. _Done when_ on the #407 datasets, phrase text, arcs and focus for phrases that span unloaded segments match what today's book-wide maps give, and a jump into a segment that hasn't loaded focuses its target | 3k |
| **3m** | WebView record cache and page client, built and tested but not wired up; settles how a refetch that lands after a newer reply is kept from overwriting it. _Done when_ applying a command's reply to the cache gives the same segment pages and pool buckets as refetching them, after a boundary command the outline matches a refetch too, and a refetch that lands after a newer reply leaves the reply's records in place, in tests over the #407 datasets | 2b, 3l |
Expand Down
Loading
Loading