Skip to content

User-defined segment boundaries#126

Merged
alex-rawlings-yyc merged 46 commits into
mainfrom
segment-boundaries
Jul 15, 2026
Merged

User-defined segment boundaries#126
alex-rawlings-yyc merged 46 commits into
mainfrom
segment-boundaries

Conversation

@alex-rawlings-yyc

@alex-rawlings-yyc alex-rawlings-yyc commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

This change is Reviewable

Summary by CodeRabbit

  • New Features
    • Added per-project user-defined segment boundaries with Alt-gated split controls and merge controls, including cross-segment linking across boundary edges.
    • Added inline verse superscripts and an optional left verse gutter (including verse-0 and merged ranges).
    • Added stronger phrase candidate preview styling.
  • Bug Fixes
    • Improved focus/active-segment resolution, link enablement/tooltips, and scroll/re-centering behavior after merge/split boundary edits.
  • Changes
    • Removed “chapter label in verse”; added “show verse gutter” toggle.
    • Added segmentation persistence on save, including boundary clearing via "null".
  • Documentation
    • Added “User-defined segment boundaries” UX and save/restore documentation.

@alex-rawlings-yyc alex-rawlings-yyc self-assigned this Jun 24, 2026
@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

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 adds user-defined segment boundaries stored as deltas from default verse segmentation. It introduces boundary controls, resegmentation, phrase-aware cross-segment linking, verse labels, draft autosave, project persistence, and updated view settings.

Changes

User-defined segment boundary editing

Layer / File(s) Summary
Segmentation contracts and transforms
src/types/*, src/utils/segmentation.ts, src/utils/verse-ref.ts, src/parsers/papi/resegmentBook.ts
Defines segmentation and verse metadata, validates deltas, applies normalized boundary edits, resolves verse containment, and rebuilds merged or split books.
Boundary editing UI and interaction state
src/components/SegmentationStore.tsx, src/components/AltHeldContext.tsx, src/components/PhraseStripParts.tsx, src/components/SegmentView.tsx, src/components/SegmentListView.tsx, src/components/TokenLinkIcon.tsx
Adds merge/split controls, Alt-held gating, baseline split gestures, candidate styling, gutter labels, and adjacent-edge phrase linking.
Rendering, navigation, and reconciliation
src/components/Interlinearizer.tsx, src/components/InterlinearizerLoader.tsx, src/components/ContinuousView.tsx, src/hooks/useSegmentWindow.ts, src/hooks/useBookIndexes.ts
Propagates segmentation context and versions, preserves focus across edits, resolves active segments by verse containment, and reconciles scrolling after boundary changes.
Draft and project persistence
src/hooks/useDraftProject.ts, src/components/modals/ProjectModals.tsx, src/services/projectStorage.ts, src/main.ts
Autosaves segmentation deltas, tracks segmentation sync state, loads and validates stored boundaries, and persists updates with omit, clear, and replace semantics.
Verse metadata and display settings
src/parsers/papi/usjBookExtractor.ts, src/parsers/papi/bookTokenizer.ts, src/utils/verse-superscripts.ts, src/utils/segment-labels.ts, contributions/*, src/types/view-options.ts
Carries rendered verse numbers through extraction and tokenization, renders inline superscripts and gutter labels, and replaces the chapter-label toggle with the verse-gutter setting.
Validation and regression coverage
src/__tests__/*, __mocks__/lucide-react.tsx, src/hooks/useArcPaths.ts, src/components/AnalysisStore.tsx, src/components/PhraseBox.tsx, src/components/TokenChip.tsx
Adds coverage for segmentation, persistence, labels, navigation, controls, Alt-key behavior, phrase candidates, and settle-aware layout measurement.

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

Possibly related PRs

Suggested reviewers: imnasnainaec

🚥 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 matches the main change: adding user-defined segment boundary editing and persistence.
Docstring Coverage ✅ Passed Docstring coverage is 98.55% which is sufficient. The required threshold is 80.00%.
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 segment-boundaries

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 linked an issue Jun 24, 2026 that may be closed by this pull request
Base automatically changed from verse-zero to main June 24, 2026 18:18
@alex-rawlings-yyc alex-rawlings-yyc force-pushed the segment-boundaries branch 5 times, most recently from 11eb61a to 1c939f6 Compare July 6, 2026 15:30
coderabbitai[bot]

This comment was marked as outdated.

@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 1 discussion.
Reviewable status: 0 of 50 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 6, 2026 17:50
imnasnainaec

This comment was marked as resolved.

@imnasnainaec

This comment was marked as resolved.

@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 2 discussions.
Reviewable status: 4 of 66 files reviewed, 1 unresolved discussion.

@sillsdev sillsdev deleted a comment from coderabbitai Bot Jul 10, 2026
@sillsdev sillsdev deleted a comment from coderabbitai Bot Jul 10, 2026
@sillsdev sillsdev deleted a comment from coderabbitai Bot Jul 10, 2026
@sillsdev sillsdev deleted a comment from coderabbitai Bot Jul 10, 2026
@imnasnainaec

Copy link
Copy Markdown
Contributor

The segment join-split interaction when holding ALT is really slick!! Simple hover-text on the split and join buttons would help clarify what's about to happen when they're clicked.

A bigger concern is discoverability. We may want the segment join buttons always visible, with hover text that also mentions the ALT action for splitting segments.

@imnasnainaec

Copy link
Copy Markdown
Contributor

⛏️ I think that (at least in continuous view, but perhaps also in the full-detail segment view) the verse superscript should only show where the verse actual starts, not at mid-verse segment starts (like the second 1 below).

Screenshot 2026-07-13 085511 Screenshot 2026-07-13 085559

@imnasnainaec

Copy link
Copy Markdown
Contributor

We had also discussed an alternate (from the verse superscripts) for segment view, in which the verse(-range) shows up in an initial gutter/column instead.

@imnasnainaec

Copy link
Copy Markdown
Contributor

Claude Sonnet analysis of two Devin flags:

1. SegmentListView.tsx:306 — scroll handler perf

The mounted window is hard-capped at HARD_WINDOW_CAP = 120 (useSegmentWindow.ts:31), and readTopChapter runs on every raw scroll event with no coalescing. Native scroll events fire more often than paints during inertial/trackpad fling, so this can mean multiple 120-element getBoundingClientRect() scans per animation frame.

Suggest a quick fix now, defer the bigger rewrite:

  • Now: coalesce with requestAnimationFrame so readTopChapter runs at most once per frame:

    let scheduled = false;
    const onScroll = () => {
      if (scheduled) return;
      scheduled = true;
      requestAnimationFrame(() => {
        scheduled = false;
        readTopChapter();
      });
    };
    container.addEventListener('scroll', onScroll, { passive: true });

    Low risk, no architecture change, removes the per-event redundant reads.

  • Possible follow-up: the suggested IntersectionObserver approach is the right long-term fix and would match the pattern already used for the extend-window sentinels (useSegmentWindow.ts:635-668) — observe only chapter-boundary segments with a scrollspy-style rootMargin instead of scanning every segment. Bigger change; fine as a follow-up rather than blocking this PR.

2. InterlinearizerLoader.tsx:329 — preceding-segment fallback misses cross-chapter segments in their end chapter

The fallback filters by startRef.chapter === scrRef.chapterNum (line 335), but Segment.verseStarts carries a chapter per entry (interlinearizer.d.ts:542). A cross-chapter segment (e.g. one starting at 4:20 and covering through 5:3) has startRef.chapter === 4, so it's excluded when resolving an over-shoot within chapter 5 — even if it's the only segment touching chapter 5's content. preceding then stays undefined and the invalid ref falls through unchanged, when a correct preceding segment actually exists.

Suggest iterating segment.verseStarts (filtered by vs.chapter === scrRef.chapterNum) instead of segment.startRef, and building the returned ref from the matching verseStarts entry's own chapter/verse rather than startRef. This can reuse the verse-number parsing currently private to verseLabelCovers in verse-ref.ts:28-34 — worth exporting a small firstVerseNumber(label) helper so both call sites share it instead of duplicating the regex. Recommend adding a regression test for this alongside the existing cross-chapter case in segment-labels.test.ts:88-98.

@alex-rawlings-yyc

alex-rawlings-yyc commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

@imnasnainaec Clarify for me: You said:

We may want the segment join buttons always visible, with hover text that also mentions the ALT action for splitting segments.

Do we want join buttons always visible, with splitting buttons only visible with ALT? If that's the case, would the split tooltip be on the join buttons? And should the join buttons be enabled even when ALT isn't held?

@imnasnainaec

Copy link
Copy Markdown
Contributor

@imnasnainaec Clarify for me: You said:

We may want the segment join buttons always visible, with hover text that also mentions the ALT action for splitting segments.

Do we want join buttons always visible, with splitting buttons only visible with ALT? If that's the case, would the split tooltip be on the join buttons? And should the join buttons be enabled even when ALT isn't held?

Join button always enabled; splitting only visible with ALT. Join and split buttons both with very simple hover text when ALT is held. Otherwise, join button with additional hover text about holding ALT to enable splitting segments.

alex-rawlings-yyc and others added 14 commits July 15, 2026 11:39
Show each segment's verse/range label (5, 5a, 5b–7, 29–2:1) in a
fixed-width left gutter in both display modes, alongside the inline
verse superscripts. The label revives buildSegmentLabels, now derived
from the verseStarts covered-verse set rather than the startRef/endRef
interval so cross-chapter merges don't over-claim.
labels

Suppress the no-op merge above an empty verse and drop a wiped book's
boundaries; render buried verse superscripts in group-based strips;
accept Unicode-dash ranges; confine Alt re-renders to split-gap leaves.
Add a `showVerseGutter` view option (persisted project setting, default off)
that swaps the segment-view verse display between two mutually-exclusive styles:
the left verse-range gutter column or the inline verse superscripts, never both.
Previously the gutter always rendered alongside the superscripts.

Rewrite `buildSegmentLabels` to drop the split-verse portion lettering: the
gutter now shows the plain verse range a segment overlaps (`2–3`, `29–2:1`, or a
bare number), so two adjacent portions of one verse both read that verse's bare
number rather than `1a`/`1b`.

Also drop a stale `no-null/no-null` eslint-disable in main.test.ts left over from
when the asserted args were on one line; the sentinel `null` a few lines down
keeps its own disable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Re-centering after an instant jump in continuous view ran for a fixed
LINK_SLOT_TRANSITION_MS (200ms) hold. But the render window mounts
dozens
of phrase groups whose glosses, morpheme rows, and arcs finish laying
out
asynchronously over many frames; content that widens to the left of the
focus keeps shifting the focused box sideways well past 200ms. The hold
expired before that late reflow landed, stranding the focus (and its
arcs)
off-center — the box drifted ~940px right after the hold stopped, with
the
scroll offset unchanged, and clicking a visible token then triggered the
browser's native scroll-into-view "jump".

Make holdCentered settle-aware: observe the strip content row and treat
each resize as a fresh LINK_SLOT_TRANSITION_MS quiet-period extension,
re-centering on every reflow and only stopping once the content goes
quiet.
A HOLD_CENTERED_MAX_MS cap bounds the total hold so a strip that never
stabilizes can't spin the rAF loop forever.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every TokenChip fetched the gloss-input placeholder via its own async
useLocalizedStrings; with field-sizing: content each chip mounted ~14px
too narrow and widened when the string resolved, shifting the strip
under already-measured arcs (arc drawn off-screen after load, right
endpoint inward, per-nav jitter). The arc hook's observer watches the
fixed-width fade wrapper, which never resizes for these shifts, so no
corrective measure ran — and the 2-deep echo history could swallow a
genuine correction as a phantom oscillation.

Fetch the placeholder once per strip and deliver it through
PhraseStripContext (as a prop into the memoized TokenChip), so chips
render at final width on their first frame. Make useArcPaths
settle-aware as a backstop: re-verify at 200/400/800ms after the last
measurement until geometry reaches a fixed point, and allow up to two
consecutive flip-backs to a recent signature before freezing — genuine
collapse-then-recover corrections now apply while the cross-row gutter
period-2 oscillation still terminates.
Match the intra-phrase gap column's punctuation/icon stacking to the
PhraseSlot's, swap Link2Off for the height-matched Unlink2 glyph, and
make the icon wrapper inline-flex so its line-box leading no longer
pushes the unlink button below its inter-phrase counterpart.
The re-center hold disconnected its ResizeObserver when the tick loop
went quiet, so the ~300-500ms gloss/arc reflow stranded the focus
off-center on first load. Decouple observer lifetime from the loop:
stay connected until HOLD_CENTERED_MAX_MS and re-arm on late reflows.
Remove bug-fix backstory and comments that restate the code or a test
assertion; keep tightened invariants. Comments only — no behavior
change.
Remove the adjacent-edge pull that moved a segment boundary to link
across
it; a slot straddling two segments is now inert and shows a tooltip,
since
a phrase may not span segments.
signatureOf gates whether a measurement is re-applied; a deconfliction
shift that moved only midX matched the applied signature and left the
split button in its pre-shift position. Also add JSDoc to key/pump/apply
and fix a "grey"->"gray" comment.
A merge/split shifts segment indices, but the window's absolute range
was left unchanged on the no-fade boundary-edit path, so a merge above
the window dropped the top-visible segment (and a split pulled one in).
Shift the range by the anchor-index delta to hold the same content in
frame. Only bit when range.start > 0, which no existing test covered.
coderabbitai[bot]

This comment was marked as outdated.

The mid-glide reconcile test removed tok-1, a no-op that never merged
GEN 1:2 into 1:1; use tok-2 so it exercises the real path. Align
user-questions.md with shipped behavior (merge always visible, split
Alt-gated).
Fetch the merge/split localized labels once per strip instead of once
per BoundaryControl instance, matching the glossPlaceholder pattern.

@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 5 discussions.
Reviewable status: 19 of 80 files reviewed, 1 unresolved discussion (waiting on alex-rawlings-yyc and imnasnainaec).

@imnasnainaec imnasnainaec left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@imnasnainaec partially reviewed 18 files, made 1 comment, and resolved 1 discussion.
Reviewable status: 35 of 80 files reviewed, all discussions resolved (waiting on alex-rawlings-yyc).


src/__tests__/components/PhraseStripParts.test.tsx line 429 at r12 (raw file):

  }

  // -- Merge branch --------------------------------------------------------

⛏️ We can use nested describes instead of comment subsectioning.

@alex-rawlings-yyc

Copy link
Copy Markdown
Contributor Author

src/__tests__/components/PhraseStripParts.test.tsx line 429 at r12 (raw file):

Previously, imnasnainaec (D. Ror.) wrote…

⛏️ We can use nested describes instead of comment subsectioning.

Done

@imnasnainaec imnasnainaec left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@imnasnainaec made 1 comment.
Reviewable status: 35 of 80 files reviewed, 1 unresolved discussion (waiting on alex-rawlings-yyc).


a discussion (no related file):
🐛 I'm not seeing any hover text on the segment split/join buttons when I hold ALT.

Native title tooltips are suppressed while Alt is held, so the
split/join hover text never appeared. Route boundary and link-icon
tooltips through the Tooltip component with a 700ms open delay.

@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 made 1 comment.
Reviewable status: 35 of 80 files reviewed, 1 unresolved discussion (waiting on alex-rawlings-yyc and imnasnainaec).


a discussion (no related file):

Previously, imnasnainaec (D. Ror.) wrote…

🐛 I'm not seeing any hover text on the segment split/join buttons when I hold ALT.

Switched to Platform.Bible's dedicated Tooltip system; should be working now

coderabbitai[bot]

This comment was marked as outdated.

@imnasnainaec imnasnainaec left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@imnasnainaec reviewed 47 files and all commit messages, and resolved 1 discussion.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on alex-rawlings-yyc).

@alex-rawlings-yyc alex-rawlings-yyc merged commit 7ba086d into main Jul 15, 2026
10 checks passed
@alex-rawlings-yyc alex-rawlings-yyc deleted the segment-boundaries branch July 15, 2026 22:06
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.

Adjust segment boundaries

2 participants