Skip to content

Fix review findings on the Paratext 9 import WebView - #295

Closed
alex-rawlings-yyc wants to merge 1 commit into
pt9-parsed-webviewfrom
review-fixes/pt9-parsed-webview
Closed

Fix review findings on the Paratext 9 import WebView#295
alex-rawlings-yyc wants to merge 1 commit into
pt9-parsed-webviewfrom
review-fixes/pt9-parsed-webview

Conversation

@alex-rawlings-yyc

@alex-rawlings-yyc alex-rawlings-yyc commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Review fixes for #274, branched off pt9-parsed-webview at ef8e70b. Five defects, each with a regression test that fails against that commit.

Read-only enforcement had three holes

#274 guards BoundaryControl and PhraseStripParts, but three parallel mutation routes stayed live in an import view:

  • SegmentListView.tsx:428 — the between-rows merge button dispatches segmentationDispatch.merge, which writes boundaries into the user's editable draft and flips the dirty indicator. Nothing visible happens in the import, since book is computed with resegmentBook(verseBook, undefined) there, so it silently corrupts an unrelated draft.
  • SegmentView.tsx:191 — Alt+click on a baseline-text split gap: the same silent draft write, in continuous-scroll mode. The Alt-gated SplitMarker in PhraseStripParts is covered; this is its baseline-mode twin.
  • ArcOverlay.tsx:291 — the arc split button. analysisMerger.ts:401 emits phraseAnalysisLinks, so imports do draw arcs; hovering one reveals Link2Off, and a click deletes a 2-token phrase outright. With PhraseBox's pill and TokenLinkIcon both gated, this was the only surviving route that visibly mutates the import.

ArcOverlay takes a readOnly prop rather than reading the hook — it's a presentational leaf whose tests render it bare, so the hook would have forced a provider onto every call site.

The offer mode never reached the modal

InterlinearizerLoader.tsx:1261 collapsed pt9Mode === 'offer' into 'import', leaving Pt9ImportModal's entire mode === 'offer' branch dead outside its own unit test. Both consequences contradict the spec added in user-questions.md ("a single Open (dismissing the report also opens…)"): the offer report rendered Close + Open, and dismissing it routed through handlePt9ClosesetModal('none'), so a successful conversion was never opened — the user had to go find it in Select. The test "returns to the plain view when an offer-run report is closed" locked that in; it's replaced by two tests asserting the documented behavior.

A failed import-analysis load rendered a blank pane

Once the book has loaded and neither book error applies, all three of loadingOrErrorPanel's conditionals are falsy, so the user got an empty content area plus a toast. Reachable through openImportedProject's :718 fallback. Adds an importLoadFailed state, an on-screen message, and a localized string.

handlePt9Open wrote state after the user backed out

The report stays dismissable during fetchSummary, so Escape → back to Select → resolve closed the Select modal and switched the active project anyway. Adds a modalRef in-flight guard.

One finding investigated and rejected

A claim that a manual sync remounts the store seeded with the pre-sync analysis (initialAnalysis is a non-reactive lazy-ref seed while the key carries updatedAt). Instrumenting the actual mount sequence with and without the proposed fix gave identical seeds both ways — React's batching means the remount already happens after the fresh analysis lands. No code change; the test asserting the correct behavior stays, since nothing covered it before.

Verified clean

No change needed, recorded so it isn't re-checked: savePt9Import's buildNew() spread is field-for-field equivalent to the literal it replaced; the openRequest effect's exhaustive-deps disable has no stale closure; hasDraft/hasNoInterlinearizerState handle ENOENT correctly; __setMockAnalysisReadOnly's module state is reset in every file that touches it.

Left for review

  • isPt9TooLargeError calls error.message.includes(...) after isPlatformError, whose real guard only checks for platformErrorVersion. A platform error without message would throw out of the catch in runPt9Import. Whether any producer omits it is unverified, so this is untouched rather than guessed at.
  • The on-open auto-sync path had no test; one now covers the sync wording. Worth confirming that's what users should see.

Also

A comment-rules pass: two mock docs naming the wrong lifecycle hook, one over-long doc, one mechanism-explaining inline.

This change is Reviewable

@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: effa8394-2524-4214-9a4b-703cafe2d364

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@alex-rawlings-yyc
alex-rawlings-yyc marked this pull request as ready for review September 2, 2026 16:08
@alex-rawlings-yyc alex-rawlings-yyc self-assigned this Sep 2, 2026
@alex-rawlings-yyc alex-rawlings-yyc changed the title Guard the read-only import against edits and open its offer report Fix review findings on the Paratext 9 import WebView Sep 2, 2026
@imnasnainaec

Copy link
Copy Markdown
Contributor

This may be partially redundant with #292. I'm investigating now.

@alex-rawlings-yyc

Copy link
Copy Markdown
Contributor Author

@imnasnainaec Right you are. #292 already covers everything on this branch except one thing.

handlePt9Open here drops the state write if the user dismissed the report while fetchSummary was in flight (modalRef guard). #292 wraps that call in try/catch, which handles a rejection but not a resolve the user backed out of — Escape → back to Select still closes the Select modal and switches the active project. The two guards are complementary; #292 should take this one on top of its catch.

Everything else is duplicated by #292 or worse than it. Closing in favor of #292 plus that guard.

imnasnainaec added a commit that referenced this pull request Sep 2, 2026
Adopted from the review fixes on #295. The report stays dismissable while
the Open's summary fetch is in flight, so Escape back to the picker and then
a summary landing switched the active project and closed the picker the user
had returned to. The handler now checks the report is still the modal on
screen before acting, which also keeps its failure notice out of whatever
they moved on to.

Two smaller things from the same review: the failed-load line no longer
prints under "Loading..." or a book error, which would have contradicted
them; and `Pt9ImportModal`'s `onOpen` doc said Open renders in `import` mode
alone, which stopped being true when the offer report was given its Open.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@imnasnainaec

Copy link
Copy Markdown
Contributor

I did take 3 things from this pr:

  • Its handlePt9Open in-flight guard is a live defect in this branch: dismiss the report while the summary is fetching, and the resolve switched projects and closed the picker you'd gone back to. Added the guard and a deferred-promise test.
  • Gated the failed-load line on !hasError && !showLoading, so it can't print beneath "Loading…".
  • Pt9ImportModal's onOpen doc.

alex-rawlings-yyc pushed a commit that referenced this pull request Sep 2, 2026
* Seal the read-only import and make its sync land

Every editing affordance the read-only import view still offered is gone,
and a sync now reaches the view it refreshed.

- Merge, baseline-split, and arc-split controls read the store's read-only
  flag; the import view's segmentation dispatch is inert as a backstop, and a
  phrase mode entered on the draft no longer carries into the import.
- The import's analysis is cleared before each fetch, so the store's
  mount-time seed cannot pin pre-sync content in the view; a fetch that brings
  back nothing says so in the view area.
- The accepted first-open offer gets its intended single-door report.
- Opening an import runs through the select modal's submit guard, and the
  first-open probe gives up rather than hanging the tab.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Bound the manifest read and catch the report's Open

Reading a Paratext 9 manifest now goes through one helper that gives up when
the provider never answers, so no caller can wait on it forever.

- The select modal is held inert for the whole of an import open, so a hung
  manifest read had left it with no Escape, no outside-click, and a disabled
  Cancel. A read that never answers is now an ordinary failure: one warning,
  the stored import opens, the modal comes back.
- The first-open probe reads through the same helper and keeps its plain
  try/catch.
- A rejecting fetch behind the report's Open is logged and notified rather than
  escaping the click handler unhandled.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Tag the import analysis with the version it was fetched for

The view derives what to show from the tag rather than having an effect clear
the previous analysis: a fetched analysis and the version it belongs to now
reach the view in the same commit, so the commit that carries a sync's new
modification time has no pre-sync analysis to paint - previously it mounted the
whole interlinear tree on the old content for a frame before the placeholder
replaced it.

Also records that the manifest timeout bounds the wait rather than the read,
PAPI offering no cancellation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Trim the import-analysis comments to what outlives the code

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Report a failed import load once, in the panel

The imported-analysis fetch sent a toast on top of the panel's own failure
line, so one failure produced two messages with different advice - and the
toast in the catch ran even for a fetch a sync or a switch back to the draft
had already superseded. The panel line is now the whole report: it stays on
screen next to the empty view instead of disappearing.

Also seals two ways a stale mode or hover could outlive the control it came
from: the import view pins its phrase mode to view, since the reset effect
only covers crossing into the import and a mode set from inside it has no
crossing to reset it; and a split hover clears when the analysis turns
read-only, since the button that vanishes never fires its own mouse-leave.

The read-only mock boilerplate that had been copied into three test files
moves to a module of its own, and resets in beforeEach rather than afterEach.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Drop an Open fetch the user has already walked away from

Adopted from the review fixes on #295. The report stays dismissable while
the Open's summary fetch is in flight, so Escape back to the picker and then
a summary landing switched the active project and closed the picker the user
had returned to. The handler now checks the report is still the modal on
screen before acting, which also keeps its failure notice out of whatever
they moved on to.

Two smaller things from the same review: the failed-load line no longer
prints under "Loading..." or a book error, which would have contradicted
them; and `Pt9ImportModal`'s `onOpen` doc said Open renders in `import` mode
alone, which stopped being true when the offer report was given its Open.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@alex-rawlings-yyc
alex-rawlings-yyc deleted the review-fixes/pt9-parsed-webview branch September 2, 2026 17:21
alex-rawlings-yyc pushed a commit that referenced this pull request Sep 2, 2026
* Seal the read-only import and make its sync land

Every editing affordance the read-only import view still offered is gone,
and a sync now reaches the view it refreshed.

- Merge, baseline-split, and arc-split controls read the store's read-only
  flag; the import view's segmentation dispatch is inert as a backstop, and a
  phrase mode entered on the draft no longer carries into the import.
- The import's analysis is cleared before each fetch, so the store's
  mount-time seed cannot pin pre-sync content in the view; a fetch that brings
  back nothing says so in the view area.
- The accepted first-open offer gets its intended single-door report.
- Opening an import runs through the select modal's submit guard, and the
  first-open probe gives up rather than hanging the tab.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Bound the manifest read and catch the report's Open

Reading a Paratext 9 manifest now goes through one helper that gives up when
the provider never answers, so no caller can wait on it forever.

- The select modal is held inert for the whole of an import open, so a hung
  manifest read had left it with no Escape, no outside-click, and a disabled
  Cancel. A read that never answers is now an ordinary failure: one warning,
  the stored import opens, the modal comes back.
- The first-open probe reads through the same helper and keeps its plain
  try/catch.
- A rejecting fetch behind the report's Open is logged and notified rather than
  escaping the click handler unhandled.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Tag the import analysis with the version it was fetched for

The view derives what to show from the tag rather than having an effect clear
the previous analysis: a fetched analysis and the version it belongs to now
reach the view in the same commit, so the commit that carries a sync's new
modification time has no pre-sync analysis to paint - previously it mounted the
whole interlinear tree on the old content for a frame before the placeholder
replaced it.

Also records that the manifest timeout bounds the wait rather than the read,
PAPI offering no cancellation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Trim the import-analysis comments to what outlives the code

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Report a failed import load once, in the panel

The imported-analysis fetch sent a toast on top of the panel's own failure
line, so one failure produced two messages with different advice - and the
toast in the catch ran even for a fetch a sync or a switch back to the draft
had already superseded. The panel line is now the whole report: it stays on
screen next to the empty view instead of disappearing.

Also seals two ways a stale mode or hover could outlive the control it came
from: the import view pins its phrase mode to view, since the reset effect
only covers crossing into the import and a mode set from inside it has no
crossing to reset it; and a split hover clears when the analysis turns
read-only, since the button that vanishes never fires its own mouse-leave.

The read-only mock boilerplate that had been copied into three test files
moves to a module of its own, and resets in beforeEach rather than afterEach.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Drop an Open fetch the user has already walked away from

Adopted from the review fixes on #295. The report stays dismissable while
the Open's summary fetch is in flight, so Escape back to the picker and then
a summary landing switched the active project and closed the picker the user
had returned to. The handler now checks the report is still the modal on
screen before acting, which also keeps its failure notice out of whatever
they moved on to.

Two smaller things from the same review: the failed-load line no longer
prints under "Loading..." or a book error, which would have contradicted
them; and `Pt9ImportModal`'s `onOpen` doc said Open renders in `import` mode
alone, which stopped being true when the offer report was given its Open.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
alex-rawlings-yyc pushed a commit that referenced this pull request Sep 2, 2026
…274)

* Rebuild the import replacement from buildNew and drop the summary tests

* Add the Paratext 9 import WebView experience with a first-open offer

The UI half of the PT9 import. The select modal gains the import
button (shown only when the source serves convertible data, via
usePt9ImportAvailability); Pt9ImportModal carries the run, its report,
and its failures, including the too-large refusal recognized by the
RESOURCE_EXHAUSTED platform error code with the documented message
marker as fallback. An import opens read-only: every editing affordance
stays away, a banner carries sync and copy-to-editable, and
CopyToEditableModal clones an import into an editable project. On the
first open of a source with convertible PT9 data and no stored state,
Pt9ConvertPromptModal offers the conversion up front: Yes runs the
import as the only project created, No (or dismissing) persists the
empty draft so the offer never repeats, per the user-questions entry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* State the read-only rendering on the provenance doc

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Follow the guard move and shared fixtures through the WebView tree

The Loader imports isPt9ImportReport from the converter surface that now
owns it, the frontend-mock helper rides this tree with its users, and
the offer and hasDraft tests use the shared ENOENT fixture.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Open fast, indicate the probe, and make the offer report one door

* Mark the unfolded sense counters as resolver-work TODO

* Seal the read-only import and make its sync land (#292)

* Seal the read-only import and make its sync land

Every editing affordance the read-only import view still offered is gone,
and a sync now reaches the view it refreshed.

- Merge, baseline-split, and arc-split controls read the store's read-only
  flag; the import view's segmentation dispatch is inert as a backstop, and a
  phrase mode entered on the draft no longer carries into the import.
- The import's analysis is cleared before each fetch, so the store's
  mount-time seed cannot pin pre-sync content in the view; a fetch that brings
  back nothing says so in the view area.
- The accepted first-open offer gets its intended single-door report.
- Opening an import runs through the select modal's submit guard, and the
  first-open probe gives up rather than hanging the tab.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Bound the manifest read and catch the report's Open

Reading a Paratext 9 manifest now goes through one helper that gives up when
the provider never answers, so no caller can wait on it forever.

- The select modal is held inert for the whole of an import open, so a hung
  manifest read had left it with no Escape, no outside-click, and a disabled
  Cancel. A read that never answers is now an ordinary failure: one warning,
  the stored import opens, the modal comes back.
- The first-open probe reads through the same helper and keeps its plain
  try/catch.
- A rejecting fetch behind the report's Open is logged and notified rather than
  escaping the click handler unhandled.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Tag the import analysis with the version it was fetched for

The view derives what to show from the tag rather than having an effect clear
the previous analysis: a fetched analysis and the version it belongs to now
reach the view in the same commit, so the commit that carries a sync's new
modification time has no pre-sync analysis to paint - previously it mounted the
whole interlinear tree on the old content for a frame before the placeholder
replaced it.

Also records that the manifest timeout bounds the wait rather than the read,
PAPI offering no cancellation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Trim the import-analysis comments to what outlives the code

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Report a failed import load once, in the panel

The imported-analysis fetch sent a toast on top of the panel's own failure
line, so one failure produced two messages with different advice - and the
toast in the catch ran even for a fetch a sync or a switch back to the draft
had already superseded. The panel line is now the whole report: it stays on
screen next to the empty view instead of disappearing.

Also seals two ways a stale mode or hover could outlive the control it came
from: the import view pins its phrase mode to view, since the reset effect
only covers crossing into the import and a mode set from inside it has no
crossing to reset it; and a split hover clears when the analysis turns
read-only, since the button that vanishes never fires its own mouse-leave.

The read-only mock boilerplate that had been copied into three test files
moves to a module of its own, and resets in beforeEach rather than afterEach.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Drop an Open fetch the user has already walked away from

Adopted from the review fixes on #295. The report stays dismissable while
the Open's summary fetch is in flight, so Escape back to the picker and then
a summary landing switched the active project and closed the picker the user
had returned to. The handler now checks the report is still the modal on
screen before acting, which also keeps its failure notice out of whatever
they moved on to.

Two smaller things from the same review: the failed-load line no longer
prints under "Loading..." or a book error, which would have contradicted
them; and `Pt9ImportModal`'s `onOpen` doc said Open renders in `import` mode
alone, which stopped being true when the offer report was given its Open.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: D. Ror. <imnasnainaec@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants