Skip to content

Expand e2e testing#151

Open
alex-rawlings-yyc wants to merge 23 commits into
mainfrom
expand-e2e-testing
Open

Expand e2e testing#151
alex-rawlings-yyc wants to merge 23 commits into
mainfrom
expand-e2e-testing

Conversation

@alex-rawlings-yyc

@alex-rawlings-yyc alex-rawlings-yyc commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

This change is Reviewable

Summary by CodeRabbit

  • New Features

    • Added user-defined segment boundary editing (merge/split) in the interlinearizer.
    • Enabled adjacent-edge cross-segment linking for relevant link slots.
    • Segment headers now display clearer, formatted verse-range labels.
  • Bug Fixes

    • Segment boundary changes now persist correctly across save, reopen, save-as, and overwrite flows.
    • Improved segmentation-aware refresh and focus/scroll behavior after merge/split edits.
    • Refined cross-segment link enablement/tooltip behavior for edge cases (including phrase-safe handling).
  • Tests / Chores

    • Added/expanded unit and Playwright E2E coverage for boundary editing, draft persistence, gloss round-trips, and project modals.

alex-rawlings-yyc and others added 13 commits July 6, 2026 12:21
Verse-0 superscriptions are ordinary segments now: addBoundaryBefore,
removeBoundaryAt, and moveBoundary no longer refuse edits that touch a
superscription's interior or its bordering boundaries. The UI-side wall
comes out separately.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The resegmented book is derived from the draft's ref-held segmentation,
and the auto-save's setDirty(true) bails out of the re-render once the
draft is already dirty — so clicking merge/split updated the stored
delta without updating the view. useDraftProject now bumps a dedicated
segmentationVersion on every boundary edit, and the loader keys the
resegment memo on it so the new boundaries take effect in place without
remounting the editor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
After a merge/split a segment no longer corresponds 1:1 to a verse, so
the old verse-number header could repeat or skip. Each segment now
shows a per-chapter sequential segment number (starting at 0 for a
verse-0 superscription) with the verse range it contains beside it,
e.g. "2 (2–3)". The labels are built once over the whole book by a new
pure segment-labels util so the numbering doesn't depend on which
window slice is mounted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
phrasesStraddlingBoundary finds every phrase a proposed segment
boundary would cut (including the gap between fragments of a
discontiguous phrase) and pairs each with the split point that severs
it cleanly at the boundary. usePhraseLinkByIdGetter exposes a stable,
non-subscribing read of the phrase-link map for event-time callers.
Both are consumed by the boundary-control rework that follows: one
predicate shared by the force-break dispatch and the split-control
suppression so the two can't drift apart.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Segment-boundary editing is no longer a separate mode toggled from the
view options. Instead:

- The between-group slots always carry a boundary control alongside the
  link icon: a split control inside a segment (hover-revealed with the
  slot) and a horizontal-fold merge control at a segment boundary in
  the continuous strip.
- The segment list gets an always-visible full-width merge button in
  the gap between adjacent rows.
- Hovering any of these previews the operation: the affected token
  groups render a new strong phrase-candidate outline (distinct from
  and outranking hover/focus), and hovered row merges outline and tint
  the two rows they would join.
- The not-mid-phrase rule becomes a UI-only guard sharing one predicate
  (phrasesStraddlingBoundary) with the dispatch: the split control
  hides and the cross-segment pull disables at boundaries that would
  cut a phrase, while the wrapped segmentation dispatch force-breaks
  straddled phrases for callers that cannot see them.
- The verse-0 hard wall comes out of the UI to match the model:
  superscriptions merge and split like any other segment. Merged-away
  default verse boundaries render a faint former-boundary tick
  (formerBoundaryRefs replaces verseZeroSegmentIds in the segmentation
  context) so a split can restore the original segmentation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A merge/split hands the views a fresh book object while every token
ref survives, and the old code treated that like a book swap:

- Interlinearizer reseeded focusedTokenRef to the active verse's first
  word (or the book's first phrase when a merge removed the active
  verse's segment start). It now keeps a still-resolving focus and
  reseeds only when the token no longer exists in the new book.
- useSegmentWindow faded and recentered on the new segments identity,
  snapping away from the point just clicked. A segments change at an
  unchanged anchor verse now redraws in place with no fade.
- ContinuousView's committed active segment kept naming a segment that
  no longer existed, deactivating every link button until the next
  navigation; a reconcile effect commits the new id when a segmentation
  edit changes the focused token's segment without moving focus. The
  instant-jump reveal also re-centers each frame through the link-slot
  transition so async arc/morpheme layout can't drift the strip
  off-center.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rewrites the segment-boundaries section for the no-edit-mode UX:
always-available controls (item 5), superscriptions as ordinary
segments (item 6, replacing the hard-wall interim), and silent
force-breaking of straddled phrases (new item 7).

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

Persistence: Open now loads a project's stored segmentation (validated)
into
the draft, and Save As / Overwrite send segmentationJson alongside the
analysis, so custom boundaries survive the full save/open loop instead
of
being silently wiped. markSynced compares both the analysis and
segmentation
snapshots, so a boundary edit made during a save round-trip keeps the
draft
dirty.

Boundary edits: the cross-segment pull anchors the moved boundary on the
next
word token (merging wholly when only trailing punctuation remains), so a
pull
can no longer strand a punctuation-only segment or record a punctuation
ref
in addedStarts. Former boundaries are exposed as a word-anchored map so
punct-initial verses show the restore tick and split back to the exact
default boundary. Boundary controls disable while a phrase mode is
active,
closing a path that could restore a phrase spanning two segments.

Segment window: an explicit segmentationVersion signal (threaded from
the
loader) replaces the verse-key inference, so a merge absorbing the
active
verse no longer flashes a recenter fade and a re-tokenized book
recenters
again.

Performance: the loader's book/formerBoundaries memos key on version
counters
(plus isDraftLoading for the initial load) instead of the draft
identity, so
gloss autosaves no longer re-run full-book resegmentation; the
not-mid-phrase
guard is a straddled-boundary set precomputed once per phrase-link
change,
replacing per-slot link scans and store subscriptions.

Cleanups: useBookIndexes owns segmentOrder/fullTokenOrder/wordRefByOrder
in
its single pass; autosaveDraft unifies the analysis/segmentation
autosave
pipelines; ContinuousView shares one holdCentered loop and reuses
commitPendingActiveSegment; saveAnalysis's command schema documents
segmentationJson; user-questions.md describes the shipped always-visible
boundary controls.
@alex-rawlings-yyc alex-rawlings-yyc self-assigned this Jul 6, 2026
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

This PR introduces user-defined segment boundaries: a SegmentationDelta data model, pure utilities to compute/edit boundaries, a SegmentationStore context, persistence through draft/project storage and IPC commands, boundary merge/split UI controls, cross-segment token linking, segment-label rendering, and ContinuousView recenter refactoring, plus extensive tests and new Playwright e2e specs/helpers.

Changes

User-defined segment boundaries

Layer / File(s) Summary
Segmentation contracts and delta algorithms
src/types/interlinearizer.d.ts, src/types/type-guards.ts, src/types/token-layout.ts, src/utils/segmentation.ts, src/utils/verse-ref.ts, src/hooks/useBookIndexes.ts, src/parsers/papi/resegmentBook.ts, src/utils/segment-labels.ts, src/utils/phrase-arc.ts, src/__tests__/...
Defines SegmentationDelta, type guards, and pure functions (effectiveStarts, addBoundaryBefore, removeBoundaryAt, moveBoundary, resegmentBook, buildSegmentLabels, phrasesStraddlingBoundary, segmentContainsVerse) with unit tests.
Segmentation context and phrase-link getter
src/components/SegmentationStore.tsx, src/components/AnalysisStore.tsx, src/__tests__/...
Adds SegmentationProvider/useSegmentation context and usePhraseLinkByIdGetter hook.
Draft/project persistence
src/hooks/useDraftProject.ts, src/services/projectStorage.ts, src/main.ts, src/components/modals/ProjectModals.tsx, src/__tests__/...
Persists segmentation deltas through autosave, project storage, IPC command, and open/save-as/overwrite modal flows.
Interlinearizer/Loader wiring
src/components/Interlinearizer.tsx, src/components/InterlinearizerLoader.tsx, src/hooks/useSegmentWindow.ts, src/__tests__/...
Wires segmentation dispatch, formerBoundaries, straddledBoundaryRefs, force-break logic, and boundary-edit vs re-tokenization classification.
Boundary controls and labels
src/components/PhraseStripParts.tsx, src/components/SegmentListView.tsx, src/components/SegmentView.tsx, contributions/localizedStrings.json, __mocks__/lucide-react.tsx, src/__tests__/...
Adds merge/split boundary UI controls and verse-based segment labels.
Candidate preview styling
src/components/PhraseBox.tsx, src/components/PhraseStripContext.tsx, src/components/PhraseStripParts.tsx, src/tailwind.css, src/__tests__/...
Adds isCandidate styling for hover previews on merge/split/link controls.
Cross-segment token linking
src/components/TokenLinkIcon.tsx, src/__tests__/...
Enables pulling adjacent-segment edge tokens into a phrase link via boundary move/merge.
ContinuousView recenter refactor
src/components/ContinuousView.tsx, src/__tests__/...
Introduces shared holdCentered recenter loop and immediate active-segment commit on boundary edits.
Docs and spelling
user-questions.md, cspell.json
Adds design-decision questions and dictionary entries.

E2E test infrastructure and feature specs

Layer / File(s) Summary
E2E documentation
e2e-tests/README.md
Documents smoke vs feature tests and a shared CDP-instance testing protocol.
Fixture helpers
e2e-tests/fixtures/helpers.ts
Adds helpers for opening the interlinearizer, navigating refs, ensuring the e2e project is active, rescuing drafts, wiping drafts, and closing tabs.
New feature specs
e2e-tests/tests/features/*.spec.ts
Adds draft-persistence, gloss-roundtrip, and project-modals cancel-tour specs.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant TokenLinkIcon
  participant SegmentationStore
  participant InterlinearizerLoader
  participant useDraftProject
  participant ProjectStorage

  User->>TokenLinkIcon: click link icon at adjacent edge
  TokenLinkIcon->>SegmentationStore: performBoundaryPull (move/merge)
  SegmentationStore->>InterlinearizerLoader: segmentationDispatch(merge/split/move)
  InterlinearizerLoader->>useDraftProject: autosaveSegmentation(delta)
  useDraftProject-->>InterlinearizerLoader: segmentationVersion incremented
  InterlinearizerLoader->>InterlinearizerLoader: resegmentBook(verseBook, delta)
  User->>InterlinearizerLoader: click Save
  InterlinearizerLoader->>ProjectStorage: interlinearizer.saveAnalysis(id, analysisJson, segmentationJson)
  ProjectStorage-->>InterlinearizerLoader: updated project persisted
Loading

Possibly related issues

Possibly related PRs

Suggested labels: 🟨Medium

Suggested reviewers: imnasnainaec, jasonleenaylor

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is clearly related to the main change because the PR adds and expands end-to-end test coverage.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch expand-e2e-testing

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.

Replace the per-chapter segment ordinal with verse-based labels (1a, 1b,
1c–2, 3), and match verses to segments by range containment instead of
exact start-verse match, so external jumps to a verse inside a merged or
split segment land on the segment that contains it.
@alex-rawlings-yyc alex-rawlings-yyc linked an issue Jul 7, 2026 that may be closed by this pull request
coderabbitai[bot]

This comment was marked as outdated.

autosaveSegmentation now clears the persisted segmentation field when
the
delta has no removed or added starts, not just when it is undefined — an
empty delta represents the default segmentation and shouldn't persist a
redundant custom object.

Extract getSegmentationDispatch helper in the loader test and tighten
the
move-boundary assertion to check the exact normalized delta.

@alex-rawlings-yyc alex-rawlings-yyc left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alex-rawlings-yyc resolved 4 discussions.
Reviewable status: 0 of 56 files reviewed, all discussions resolved (waiting on alex-rawlings-yyc).

@alex-rawlings-yyc alex-rawlings-yyc marked this pull request as ready for review July 7, 2026 21:31
coderabbitai[bot]

This comment was marked as outdated.

Exclude the "Open Interlinearizer" project-picker tab from the four
broad
.dock-tab matches (ensureInterlinearizerOpenOnWeb, isDraftDirty,
closeInterlinearizerTab, rescueDraftToNewProject) so they target the
real
WebView tab. Also anchor the active-project check in
ensureE2eProjectActive
to match E2E_PROJECT_NAME exactly instead of via unanchored includes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@alex-rawlings-yyc

This comment was marked as outdated.

@coderabbitai

This comment was marked as outdated.

coderabbitai[bot]

This comment was marked as outdated.

The e2e feature helpers couldn't reliably pass on the shared CDP
instance:
- ensureE2eProjectActive matched the project entry by the button's
  accessible
  name, but the modal renders name/badge/language spans with no
  separating
  whitespace, so the anchored regex never matched — every mutating test
  fell
  into the create branch and re-created an existing project. Match the
  name
  element by exact text instead (also fixes the "E2E Test Project 2"
  overmatch).
- The create branch hung on a dirty draft (Create defers behind the
  discard
  confirm); dismiss it when dirty.
- closeInterlinearizerTab used hover()+click(), which times out when the
  tab
  overflows off-viewport on CI; use dispatchEvent('click') like
  paranext-core.
- ensureInterlinearizerOpenOnWeb settles the dock before its isVisible()
  branch.
- Extract interlinearizerTabLocator/escapeRegExp helpers.

The tab's unsaved marker used dirty||pendingEdits but ProjectModals
gated its
discard confirmation on committed dirty only, so opening/creating a
project
mid-typing silently discarded the uncommitted gloss. Rename the prop to
hasUnsavedWork and feed it the combined state so the guard and marker
agree.

Document the broadened discard-on-swap behavior in user-questions.md.
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.

Add a few basic e2e tests

1 participant