Skip to content

Add UI that enables user to gloss tokens and add render window for ContinuousView#77

Open
alex-rawlings-yyc wants to merge 13 commits into
mainfrom
gloss-token
Open

Add UI that enables user to gloss tokens and add render window for ContinuousView#77
alex-rawlings-yyc wants to merge 13 commits into
mainfrom
gloss-token

Conversation

@alex-rawlings-yyc
Copy link
Copy Markdown
Contributor

@alex-rawlings-yyc alex-rawlings-yyc commented May 19, 2026

  • Add gloss input to TokenChip (word tokens only): bordered chip now stacks surface text above an editable gloss field
  • Thread glosses (Record<string, string>) and onGlossChange(tokenId, value) props down through TokenChip → PhraseBox → SegmentView → ContinuousView
  • Introduce a phrase-window around the focused index in ContinuousView to cap rendered PhraseBoxes and reduce overhead on large books
  • Replace SegmentView's onClick with onSelect(ref, tokenId?) so the clicked token id propagates up to Interlinearizer
  • Add activePhraseIndex prop to ContinuousView for external phrase jumps when a segment-view token is clicked in continuous-scroll mode
  • Update tests to cover gloss propagation, activePhraseIndex jumps, and both phrase-window boundary branches

This change is Reviewable

Summary by CodeRabbit

  • New Features

    • Live per-token gloss editing across views; continuous strip accepts external phrase-focus commands and direct phrase-index jumps.
  • Improvements

    • Windowed token-strip rendering for large books improves responsiveness and scrolling and smoother focus/scroll behavior when switching modes.
    • Better focus preservation between continuous and segment modes; snap-to-active behavior refined.
  • Tests

    • Expanded, standardized test coverage for focus/scroll, gloss editing, mode transitions, and large-book scenarios.
  • Documentation

    • Clarified JSDoc for loader and memoized components.

Review Change Stack

@alex-rawlings-yyc alex-rawlings-yyc self-assigned this May 19, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

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 wires per-token gloss editing through TokenChip/PhraseBox/SegmentView/ContinuousView, adds external phrase focus control and windowed continuous rendering, preserves focus across mode switches, implements coordinated scrolling, and updates/extends tests to cover these behaviors.

Changes

Gloss editing and phrase focus

Layer / File(s) Summary
TokenChip gloss input support
src/components/TokenChip.tsx, src/__tests__/components/TokenChip.test.tsx
TokenChip renders a controlled gloss <input> for word tokens, forwards onFocus/onGlossChange, and updates layout; tests verify textbox presence, values, per-keystroke callbacks, and focus handling.
PhraseBox gloss wiring and interactive wrapper
src/components/PhraseBox.tsx, src/__tests__/components/PhraseBox.test.tsx
PhraseBox requires glosses/onGlossChange, forwards per-token gloss/focus handlers to TokenChip, sets tabIndex={-1} for interactive button path, and updates focused styling; tests add requiredProps/nonInteractiveProps helpers and validate gloss propagation and interactive/non-interactive branches.
SegmentView mode-specific roots and selection API
src/components/SegmentView.tsx, src/__tests__/components/SegmentView.test.tsx
SegmentView replaces onClick with onSelect(ref, tokenId?), requires glosses/onGlossChange, accepts optional focusedTokenId, and returns button for baseline-text or div with PhraseBox/TokenChip for token-chip; tests updated to assert selection and gloss flows and aria-current rendering.
ContinuousView external focus, windowing, and tests
src/components/ContinuousView.tsx, src/__tests__/components/ContinuousView.test.tsx
ContinuousView adds activePhraseIndex, requires activeVerse/onVerseChange, accepts glosses/onGlossChange and onFocusPhraseIndexChange, prefers activePhraseIndex for initial focus, window-renders phrases with PHRASE_WINDOW_HALF, coordinates external/internal jumps via refs, and standardizes/extends tests (large-book fixture, activePhraseIndex timing/regression coverage).
Interlinearizer state and coordination
src/components/Interlinearizer.tsx, src/__tests__/components/Interlinearizer.test.tsx
Interlinearizer manages shared glosses and focusedTokenId, builds wordTokens/phraseIndexByTokenId, preserves focus across mode switches, scrolls active segment into view, and wires ContinuousView/SegmentView with gloss/focus callbacks; tests assert selection, focus carryover, and gloss propagation.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant SegmentView
  participant ContinuousView
  participant TokenChip
  participant Interlinearizer
  User->>SegmentView: click token
  SegmentView->>Interlinearizer: onSelect(ref, tokenId)
  Interlinearizer->>Interlinearizer: compute activePhraseIndex
  Interlinearizer->>ContinuousView: activePhraseIndex (prop)
  ContinuousView->>ContinuousView: update focusPhraseIndex / window
  ContinuousView->>Interlinearizer: onFocusPhraseIndexChange(focusIndex)
  Interlinearizer->>SegmentView: focusedTokenId (prop)
  User->>TokenChip: type gloss
  TokenChip->>Interlinearizer: onGlossChange(tokenId, value)
  Interlinearizer->>ContinuousView: glosses (prop)
  Interlinearizer->>SegmentView: glosses (prop)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • sillsdev/interlinearizer-extension#40: Main PR extends the continuous-scroll implementation introduced in PR #40 by heavily updating ContinuousView's focus/navigation model and wiring gloss-editing callbacks.

Suggested labels

🟥High

Suggested reviewers

  • imnasnainaec
  • jasonleenaylor

Poem

🐰 I nibble letters, glosses tucked in rows,
Phrases hop to focus where the daylight glows,
From strip to segment, every token shows,
Tiny inputs bloom where little knowledge grows,
Hop — edit, focus, scroll — the interlinear flows.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% 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.
Title check ✅ Passed The title 'Add UI that enables user to gloss tokens and add render window for ContinuousView' accurately describes the two main changes: gloss input UI for tokens and a phrase-window optimization for ContinuousView.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch gloss-token

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 and usage tips.

@alex-rawlings-yyc alex-rawlings-yyc linked an issue May 19, 2026 that may be closed by this pull request
coderabbitai[bot]

This comment was marked as outdated.

alex-rawlings-yyc

This comment was marked as outdated.

@alex-rawlings-yyc

This comment was marked as outdated.

@coderabbitai

This comment was marked as outdated.

coderabbitai[bot]

This comment was marked as outdated.

@alex-rawlings-yyc

This comment was marked as outdated.

@coderabbitai

This comment was marked as outdated.

coderabbitai[bot]

This comment was marked as outdated.

@sillsdev sillsdev deleted a comment from coderabbitai Bot May 20, 2026
@sillsdev sillsdev deleted a comment from coderabbitai Bot May 20, 2026
@alex-rawlings-yyc alex-rawlings-yyc changed the title Add UI that enables user to gloss tokens Add UI that enables user to gloss tokens and add render window for ContinuousView May 20, 2026
@alex-rawlings-yyc alex-rawlings-yyc force-pushed the gloss-token branch 2 times, most recently from 46af76b to 68b2942 Compare May 20, 2026 21:48
coderabbitai[bot]

This comment was marked as outdated.

coderabbitai[bot]

This comment was marked as outdated.

@alex-rawlings-yyc alex-rawlings-yyc marked this pull request as draft May 21, 2026 16:44
- Add gloss input to TokenChip (word tokens only): bordered chip now stacks surface text above an editable gloss field
- Thread `glosses` (Record<string, string>) and `onGlossChange(tokenId, value)` props down through TokenChip → PhraseBox → SegmentView → ContinuousView
- Introduce a phrase-window around the focused index in ContinuousView to cap rendered PhraseBoxes and reduce overhead on large books
- Replace SegmentView's `onClick` with `onSelect(ref, tokenId?)` so the clicked token id propagates up to Interlinearizer
- Add `activePhraseIndex` prop to ContinuousView for external phrase jumps when a segment-view token is clicked in continuous-scroll mode
- Update tests to cover gloss propagation, `activePhraseIndex` jumps, and both phrase-window boundary branches
…nChip` type guard to ensure that `input` always has a handler; update docs
…, add coalescing nullish fallback for token chip gloss, extract names prop types for components
…tton

- Replace the `glosses`/`onGlossChange` prop-drilling pattern with a `GlossStoreProvider` context backed by `useSyncExternalStore`, so `TokenChip` reads and writes glosses directly without intermediary props passing through `PhraseBox`, `SegmentView`, `ContinuousView`, and `Interlinearizer`.
- Add a sticky "Scroll to active verse" button (using the `LocateFixed` icon) that imperatively scrolls the `aria-current` segment into view. Rename `chapterSegments` to `bookSegments` throughout.
- Extract `InertTokenChip` / `MemoizedInertTokenChip` for non-word tokens, removing the punctuation branch from `TokenChip` so it only handles words
- Convert `PhraseBox` from a `<button>` to a `<label>` so clicking anywhere on the phrase focuses the first gloss input natively
- Rename prop `onClick` → `onFocusPhrase` on `PhraseBox` to reflect the narrowed trigger (gloss-input focus only, not arbitrary clicks)
- Update `ContinuousView` and `SegmentView` to import `MemoizedInertTokenChip` and pass `onFocusPhrase` instead of `onClick`
- Update all test mocks and assertions to match the new component shapes
- `GlossStore` stored a flat string map keyed by `token.id`. `AnalysisStore`
replaces it with a `TextAnalysis`-backed store: each gloss write creates a
new approved `TokenAnalysis` + `TokenAnalysisLink`, the full analysis snapshot
is accessible via `useAnalysis`, and `onSave` propagates changes to the caller
for persistence. The store also accepts an `analysisLanguage` prop to support
non-English glosses.

- Token identity keys are migrated from token.id to token.ref throughout
(`TokenChip`, `PhraseBox`, `SegmentView`, `ContinuousView`, `Interlinearizer`,
and all corresponding tests).

- Some unneeded tests removed and some adjustments to documents made
coderabbitai[bot]

This comment was marked as outdated.

@alex-rawlings-yyc alex-rawlings-yyc marked this pull request as ready for review May 22, 2026 17:48
Copy link
Copy Markdown
Contributor

@imnasnainaec imnasnainaec left a comment

Choose a reason for hiding this comment

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

@imnasnainaec reviewed 12 files and all commit messages, and made 2 comments.
Reviewable status: 12 of 28 files reviewed, 1 unresolved discussion (waiting on alex-rawlings-yyc).


src/components/AnalysisStore.tsx line 185 at r8 (raw file):

   * @param value - The new gloss string.
   */
  const onGlossChange = useCallback(

From Devin:

src/components/AnalysisStore.tsx:R185-214

AnalysisStore creates a new TokenAnalysis on every keystroke — unbounded data growth

Each call to onGlossChange in src/components/AnalysisStore.tsx:180-207 appends a new TokenAnalysis and TokenAnalysisLink without removing or replacing the previous one. Since TokenChip.onChange fires on every keystroke (src/components/TokenChip.tsx:31), typing a 5-character gloss creates 5 approved analyses for that token. The buildApprovedGlossIndex always picks the last approved link, so behavior is correct, but the tokenAnalyses and tokenAnalysisLinks arrays grow without bound during a session. The JSDoc at line 171-173 acknowledges this as intentional ("multiple competing analyses" design), but if onSave persists to storage on every mutation, this could cause significant storage bloat over time.


src/components/component-types.ts at r8 (raw file):
⛏️ We probably want consistent organization/naming between this file and the things currently in src/types/ and src/utils/. (And I'm not implying that the latter is better.)

@alex-rawlings-yyc
Copy link
Copy Markdown
Contributor Author

src/components/component-types.ts at r8 (raw file):

Previously, imnasnainaec (D. Ror.) wrote…

⛏️ We probably want consistent organization/naming between this file and the things currently in src/types/ and src/utils/. (And I'm not implying that the latter is better.)

Given that component-types.ts has just one function, that interlinear-project-summary.ts has just one type, and that they're both only used by components, I think I'll move component-types.ts to src/types and merge the two files. Let me know if you disagree or think we could improve upon how we organize this sort of thing

@alex-rawlings-yyc
Copy link
Copy Markdown
Contributor Author

src/components/component-types.ts at r8 (raw file):

Previously, alex-rawlings-yyc (Alex Rawlings) wrote…

Given that component-types.ts has just one function, that interlinear-project-summary.ts has just one type, and that they're both only used by components, I think I'll move component-types.ts to src/types and merge the two files. Let me know if you disagree or think we could improve upon how we organize this sort of thing

nvm, I just realized what you meant better. I'm going to leave this as-is in the next commit for now and we can discuss what to really do about this on Discord

…ssChange to keep stored analyses minimal for now
Copy link
Copy Markdown
Contributor Author

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

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: 12 of 28 files reviewed, 1 unresolved discussion (waiting on alex-rawlings-yyc and imnasnainaec).


src/components/AnalysisStore.tsx line 185 at r8 (raw file):

Previously, imnasnainaec (D. Ror.) wrote…

From Devin:

src/components/AnalysisStore.tsx:R185-214

AnalysisStore creates a new TokenAnalysis on every keystroke — unbounded data growth

Each call to onGlossChange in src/components/AnalysisStore.tsx:180-207 appends a new TokenAnalysis and TokenAnalysisLink without removing or replacing the previous one. Since TokenChip.onChange fires on every keystroke (src/components/TokenChip.tsx:31), typing a 5-character gloss creates 5 approved analyses for that token. The buildApprovedGlossIndex always picks the last approved link, so behavior is correct, but the tokenAnalyses and tokenAnalysisLinks arrays grow without bound during a session. The JSDoc at line 171-173 acknowledges this as intentional ("multiple competing analyses" design), but if onSave persists to storage on every mutation, this could cause significant storage bloat over time.

This is what sent me down the rabbit hole of wiring up the suggestion engine, so I forgot to fix it again. I've fixed it in my latest commit, though note that I intentionally limited glossing to be 1:1 between token instances and analyses

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.

Gloss a word

2 participants