Skip to content

feat(checks): highlight the repeated word in its verse on each check card#345

Merged
JEdward7777 merged 5 commits into
mainfrom
jel-highlight-in-verse-impl
Jul 9, 2026
Merged

feat(checks): highlight the repeated word in its verse on each check card#345
JEdward7777 merged 5 commits into
mainfrom
jel-highlight-in-verse-impl

Conversation

@JEdward7777

@JEdward7777 JEdward7777 commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Show the repeated word highlighted in its verse, inside the check card

Follow-up to the recently merged Repeated Word Check feature (#277 / #278). Each check card previously showed only the bare matched fragment (finding.surf, e.g. "the the") with no surrounding context. Joel asked for the repeated word to be highlighted so it's easy to spot. This PR renders, on every check card, a window of the verse text around the match with the repeated word emphasized in place — red + bold on active cards, bold-only on ignored/dimmed cards.

highlighting_working

Background — why this supersedes #328

Joel's highlight request was initially understood as highlighting in the editor pane, which would have meant turning the plain <textarea> drafting surface into a rich-text/overlay editor — a significant change, analyzed in the docs-only proposal PR #328. Talking it through in person, the actual ask was much simpler: highlight the word in the check card. Since the in-card version is a small, contained change, this PR skips the separate proposal round and proposes the solution via the implementation itself. #328 is closed as superseded (its editor-surface analysis remains useful historical context if in-editor highlighting is ever wanted).

What's in here

1. A pure verse-window utilsrc/features/checks/highlight/verse-window.ts

buildVerseWindow(verseText, matchStart, matchLength) returns { before, match, after, truncatedStart, truncatedEnd }. It slices the highlighted match from the verse text itself (surf-agnostic: what's shown is exactly what's in the verse, even if casing/whitespace differs from the finding's surf), extends the context window to word boundaries where possible, strips boundary whitespace, and reports truncation so the renderer can add only at real cuts. All tunables (context width, word-boundary search distance) live in a single constants module, verse-window.constants.ts — no magic numbers in the render path.

2. An as-checked verse-text snapshot ("hydration")useRepeatedWordsCheck

Findings carry offsets (start_position, surf.length) relative to the exact verse text sent in the check request, but the response doesn't echo that text back. The hook is the one place that both builds the request and receives the response, so it captures a snt_id → verseText map from the request it just sent and layers it onto the settled result (HydratedRepeatedWordsResponse). Because snapshot and findings travel together, offsets always line up and the cards hold still while the translator keeps typing — the "live text drifted out from under stale offsets" failure mode is designed out rather than guarded. The snake_case wire contract (fluent-ai pass-through) is untouched; the snapshot is a web-only, post-hydration value.

3. Card renderingDraftingUIChecksPanelFindingRow

The snapshot is threaded as a verseTextBySntId prop onto the inline <ChecksPanel> and down to each FindingRow. A single shared sub-render (VerseContext) is used by both the active and the ignored branches:

  • Active cards: repeated word in text-red-600 font-semibold.
  • Ignored/dimmed cards: font-semibold only — ignored means "designated not a problem", so the alarm-red is dropped; bold still shows which word was flagged. (Bold rather than a darker color because the dimmed card's opacity-50 wrapper creates a stacking context its children can't escape — color would be washed out, font-weight isn't.)
  • Leading/trailing only where the window actually truncates the verse.
  • Fallback: if the snapshot has no text for a finding's snt_id, the card renders the bare finding.surf exactly as before — never a crash, never an empty card.

Verse text is looked up by the finding's own snt_id field; no occurrence-key parsing was added.

Overlapping triples ("the the the") behave as before: two findings → two cards, each highlighting its own span; their windows may overlap, which is accepted for v1. Ignore Here on one card still leaves the other active (unchanged suppression semantics).

4. Checks panel scrolling fix — with taller cards the panel could overflow past the fold with no way to reach the rest; the panel now owns its own vertical scrolling (h-full overflow-y-auto), mirroring how ResourcePanel scrolls in the same tab slot.

Testing

  • Unit tests for buildVerseWindow (boundaries, word-boundary snapping, truncation flags, degenerate inputs).
  • FindingRow / ChecksPanel tests: windowed render + highlight classes per branch, surf-agnostic slice (verse text ≠ surf), ellipsis behavior, missing-snapshot fallback, overlapping-triple cards, snapshot threading, scroll-container regression; all pre-existing grouping/toggle/wiring tests still pass.
  • useRepeatedWordsCheck test locks in the hydration contract (snapshot keyed by the same buildSntId as the findings, mirroring the request's non-empty filter).
  • Lint, typecheck, tests, and build all green; verified visually against the running stack (active red+bold, ignored bold+dimmed, Ignore Here / Undo round-trip, panel scrolling).

Relates to #277 / #278. Supersedes #328.

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Repeated-word checks now display a verse-context window around each finding, with clearer visual emphasis for active vs ignored items.
  • Bug Fixes

    • Verse context rendering is more resilient: when no verse snapshot is available, the UI falls back to the existing snippet instead of showing blank output.
    • Improved checks panel scrolling so results stay consistently navigable when expanding repeated-word details.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@JEdward7777, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 32 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3fcbfd38-48bc-4e9d-99e7-8c9c2ca0db0d

📥 Commits

Reviewing files that changed from the base of the PR and between 1e58309 and 7e65029.

📒 Files selected for processing (2)
  • src/features/checks/highlight/verse-window.test.ts
  • src/features/checks/highlight/verse-window.ts
📝 Walkthrough

Walkthrough

This PR adds verse-context highlighting for repeated-word checks: a new buildVerseWindow utility windows verse text around a match, useRepeatedWordsCheck hydrates a verseTextBySntId snapshot, and DraftingUI, ChecksPanel, and FindingRow thread that map through to render highlighted verse context. One feature-flag comment is also updated.

Changes

Verse-context highlighting feature

Layer / File(s) Summary
buildVerseWindow utility and constants
src/features/checks/highlight/verse-window.constants.ts, src/features/checks/highlight/verse-window.ts, src/features/checks/highlight/verse-window.test.ts
Adds VERSE_WINDOW and buildVerseWindow, which clamp match offsets, slice the match, and snap window edges to whitespace or verse boundaries, with tests covering clamping and edge cases.
Hydrate verseTextBySntId in useRepeatedWordsCheck
src/features/checks/hooks/useRepeatedWordsCheck.ts, src/features/checks/hooks/useRepeatedWordsCheck.test.ts
Adds HydratedRepeatedWordsResponse and returns a verseTextBySntId snapshot built from request verses alongside the repeated-words response.
VerseContext rendering in FindingRow
src/features/checks/components/FindingRow.tsx, src/features/checks/components/FindingRow.test.tsx
Adds a verseTextBySntId prop and renders windowed verse context for active and inactive findings, falling back to finding.surf when the snapshot is missing.
ChecksPanel and DraftingUI wiring plus scroll fix
src/features/checks/components/ChecksPanel.tsx, src/features/checks/components/ChecksPanel.test.tsx, src/features/bible/components/DraftingUI.tsx
Threads verseTextBySntId from DraftingUI through ChecksPanel and renderGroups into FindingRow, and updates the panel scroll container styling.
Feature-flag comment update
src/features/flags/useFeatureFlags.ts
Rewords the feature-flags fetch comment to describe login-gated access and fail-closed behavior on 401.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

  • Build Checks View Panel #278: Matches the repeated-word Checks UI and ignore-action flow updated here, including verse-context rendering and ChecksPanel/FindingRow plumbing.

Possibly related PRs

Suggested reviewers: kaseywright

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: repeated words are highlighted in verse context on each check card.
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 jel-highlight-in-verse-impl

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.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/features/checks/highlight/verse-window.ts`:
- Around line 56-63: The start-boundary selection in pickNearest can incorrectly
prefer a whitespace candidate over a true verse-boundary candidate when
distances and indices tie, causing truncatedStart to be set at the actual verse
start. Update the tie-break logic in pickNearest (and the start-selection path
that uses it) to compare isBoundary before applying the index-based outward
preference, so exact START ties always favor the boundary candidate over
whitespace.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: eab3f926-fda8-4554-b775-b673bc714104

📥 Commits

Reviewing files that changed from the base of the PR and between 9da3455 and 9dac5b9.

📒 Files selected for processing (11)
  • src/features/bible/components/DraftingUI.tsx
  • src/features/checks/components/ChecksPanel.test.tsx
  • src/features/checks/components/ChecksPanel.tsx
  • src/features/checks/components/FindingRow.test.tsx
  • src/features/checks/components/FindingRow.tsx
  • src/features/checks/highlight/verse-window.constants.ts
  • src/features/checks/highlight/verse-window.test.ts
  • src/features/checks/highlight/verse-window.ts
  • src/features/checks/hooks/useRepeatedWordsCheck.test.ts
  • src/features/checks/hooks/useRepeatedWordsCheck.ts
  • src/features/flags/useFeatureFlags.ts

Comment thread src/features/checks/highlight/verse-window.ts
kaseywright
kaseywright previously approved these changes Jul 8, 2026
Joshua Lansford added 4 commits July 9, 2026 08:13
…ghlight

Task 1 of the highlight-repeated-word-in-verse follow-on. Adds a pure,
framework-free windowing util plus its single constants module and unit
tests. No call sites changed.

- verse-window.constants.ts: single frozen VERSE_WINDOW config (context
  chars before/after + bidirectional maxSpaceSearchDistance).
- verse-window.ts: buildVerseWindow(verseText, matchStart, matchLength)
  returns { before, match, after, truncatedStart, truncatedEnd }. Snaps
  each cut to the nearest whitespace/verse-boundary within the +/- radius,
  strips whole whitespace runs, and hard-cuts when no candidate is in
  range (space-less-script safe). Surf-agnostic: match is always derived
  from verseText.slice.
- verse-window.test.ts: 24 cases covering short/long verses, nearest-snap
  and tie-breaks, boundary candidates, whitespace-run stripping, match at
  verse edges, surf-agnostic slicing, and defensive clamps.
…on each check card

Thread an as-checked snt_id → verseText snapshot (hydrated onto the check
result in useRepeatedWordsCheck, keyed by the shared buildSntId) through
DraftingUI → ChecksPanel → FindingRow, and render the verse text windowed
around the match via buildVerseWindow with the matched slice emphasized in
place: red + bold on active cards, bold only on dimmed/ignored cards.
Falls back to the bare finding.surf when the snapshot has no entry. Wire
types untouched (web-only post-hydration layer).
The LeftPanel tabpanel slot clips overflow (its ancestor sidebar wrapper is
overflow-hidden), and ChecksPanel had no scroll container of its own — so
findings past the fold were unreachable. Give the panel root h-full +
overflow-y-auto, mirroring how ResourcePanel owns its own scrolling in the
same slot. Adds a regression test asserting the scroll-container classes.
@JEdward7777
JEdward7777 force-pushed the jel-highlight-in-verse-impl branch from 501fb9d to 1e58309 Compare July 9, 2026 12:13
CodeRabbit flagged that when the verse text begins with whitespace, the
START snap could tie between the whitespace candidate at index 0 and the
verse-start boundary candidate, pick the whitespace one, and render a
spurious leading ellipsis. Break distance ties in favor of the boundary
candidate, and strip the leading-whitespace run on boundary reaches so
'before' stays whitespace-free. The match slice is untouched (and the
existing step-7 clamp keeps the strip from ever crossing into it), so
the highlight cannot shift.
@JEdward7777
JEdward7777 merged commit 898ec8f into main Jul 9, 2026
2 checks passed
@github-actions
github-actions Bot deleted the jel-highlight-in-verse-impl branch July 9, 2026 14:07
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