Skip to content

Word the Alt-split hint the way the host OS does - #221

Merged
imnasnainaec merged 8 commits into
mainfrom
os-aware-split-hint
Aug 14, 2026
Merged

Word the Alt-split hint the way the host OS does#221
imnasnainaec merged 8 commits into
mainfrom
os-aware-split-hint

Conversation

@alex-rawlings-yyc

@alex-rawlings-yyc alex-rawlings-yyc commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

The merge button's tooltip carried both spellings of the modifier at once
("Hold Alt (Option on Mac)"), leaving every reader to discard the half that did not apply to them.

Split the hint into Alt and Option variants and pick between them with the platform's isMacOs, so each host sees only its own name for the key. The gesture is untouched: it reads event.altKey, which is what Option sets on a Mac, so only the wording varies.

The mock for isMacOs decides from the user agent as the real helper does, which keeps the choice stubbable without a jest.fn that resetMocks would clear.


This change is Reviewable

Summary by CodeRabbit

  • New Features

    • Added platform-aware keyboard hints, displaying the macOS Option symbol or “Alt” on other systems.
    • Improved tooltip formatting with keyboard markup and replacement placeholders.
    • Enhanced accessibility labels for token editing, removal, suggestions, glosses, and morphology actions.
  • Bug Fixes

    • Empty or unresolved tooltip content no longer creates a title attribute.
    • Improved localized placeholder replacement across tooltips, accessible labels, and empty-state messages.
  • Documentation

    • Added guidance for keyboard hint localization and tooltip testing.

The merge button's tooltip carried both spellings of the modifier at
once
("Hold Alt (Option on Mac)"), leaving every reader to discard the half
that did not apply to them.

Split the hint into Alt and Option variants and pick between them with
the platform's isMacOs, so each host sees only its own name for the key.
The gesture is untouched: it reads event.altKey, which is what Option
sets on a Mac, so only the wording varies.

The mock for isMacOs decides from the user agent as the real helper
does, which keeps the choice stubbable without a jest.fn that resetMocks
would clear.
@alex-rawlings-yyc alex-rawlings-yyc self-assigned this Aug 12, 2026
@alex-rawlings-yyc alex-rawlings-yyc linked an issue Aug 12, 2026 that may be closed by this pull request
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@alex-rawlings-yyc, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 96 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 00391ffe-f78c-4f33-a435-3287fc4f59c6

📥 Commits

Reviewing files that changed from the base of the PR and between cb530c8 and 1af8601.

📒 Files selected for processing (1)
  • e2e-tests/fixtures/helpers.ts
📝 Walkthrough

Walkthrough

The change adds shared placeholder formatting for accessibility labels and localized messages. Merge tooltips now render platform-aware Alt-key hints with keyboard markup and omit unresolved or empty content. Mocks and tests cover macOS detection, nested tooltip content, and empty titles.

Changes

Tooltip and accessibility formatting

Layer / File(s) Summary
Shared replacement formatting and accessibility labels
__mocks__/platform-bible-utils.ts, src/components/MorphemeBox.tsx, src/components/PhraseBox.tsx, src/components/SuggestionDropdown.tsx, src/components/TokenChip.tsx, src/components/SegmentListView.tsx
Replacement utilities now support named placeholders. Accessibility labels and the no-verse-data message use the shared formatter.
Platform-aware Alt-key hint rendering
contributions/localizedStrings.json, src/components/alt-key-hint.tsx, __mocks__/platform-bible-react.tsx
Merge hints use a {key} placeholder. altKeyHint renders on macOS and Alt elsewhere with <kbd> markup.
Merge tooltip integration
src/components/PhraseStripParts.tsx, src/components/SegmentListView.tsx, src/components/PhraseStripContext.tsx, src/hooks/usePhraseStripSetup.ts, src/utils/localized-strings.ts
Merge tooltip content supports React nodes, resolved localization values, and conditional rendering. Unresolved or empty tooltip content does not create a title.
Tooltip test support and coverage
src/__tests__/test-helpers.ts, src/__tests__/components/alt-key-hint.test.tsx, src/__tests__/components/Interlinearizer.test.tsx, src/__tests__/components/PhraseStripParts.test.tsx, src/__tests__/utils/localized-strings.test.ts, REVIEW.md
Tests cover platform-specific hints, tooltip content extraction, unresolved localization keys, empty titles, and accessibility labels. Review guidance documents the mock behavior.

Electron fixture readiness

Layer / File(s) Summary
Combined Electron readiness checks
e2e-tests/fixtures/helpers.ts
launchElectronWithExtension waits for the WebSocket server and first Electron window within the readiness timeout. Failure logging covers both checks.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Mergeability Score: 🟡 Moderate · up to cb530

The PR improves the platform-specific Alt/Option hint, but the current head still has test readiness waits that can exceed their deadline and a mock that may interpret inherited values incorrectly, making validation unreliable. Merge should wait for these bounded issues to be fixed or explicitly accepted.

Sequence Diagram(s)

sequenceDiagram
  participant MergeTooltip
  participant altKeyHint
  participant isMacOs
  participant formatReplacementStringToArray
  MergeTooltip->>altKeyHint: provide localized {key} hint
  altKeyHint->>isMacOs: detect host platform
  isMacOs-->>altKeyHint: return platform status
  altKeyHint->>formatReplacementStringToArray: replace {key}
  formatReplacementStringToArray-->>altKeyHint: return formatted parts
  altKeyHint-->>MergeTooltip: return text and keyboard markup
Loading

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 clearly summarizes the main change: using the host operating system’s wording for the Alt-split hint.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch os-aware-split-hint

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.

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

Authored by Claude Opus 5 (1M context) (claude-opus-5[1m]).

These came out of a research question rather than a review pass: is this PR's handling of OS-specific text comparable to how paranext-core handles it?

Short answer — yes at the decision level, no at the mechanism level. The branch itself matches core exactly: same exported isMacOs() helper, decided at render time on the same user-agent basis, and it removes precisely the dual-spelling wording core's keyboard-shortcuts guideline calls out. But core localizes the key name alone and lets one sentence place it via a {key} placeholder, rather than forking the whole sentence per OS; it also prefers the macOS symbol over the word, and routes key labels through getLocalizeKeyForPhysicalKey instead of hardcoding them per caller.

The inline :pick: comments below are suggestions for closing that gap. None of them are blocking, and one of them (the Alt label) can't be fully closed without a paranext-core change.

Comment thread contributions/localizedStrings.json Outdated
Comment thread src/utils/localized-strings.ts Outdated
Comment thread contributions/localizedStrings.json Outdated
Comment thread src/components/SegmentListView.tsx Outdated
Comment thread REVIEW.md 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 8 files and all commit messages.
Reviewable status: 8 of 12 files reviewed, 5 unresolved discussions (waiting on alex-rawlings-yyc).

Unfork the OS-specific split hint into one sentence with a {key}
placeholder, set in a Kbd showing ⌥ on macOS and Alt elsewhere per the
platform's keyboard-shortcuts table, and replace the hand-rolled
.replace('{…}', …) call sites throughout with the platform helper —
which substitutes every occurrence, not just the first, and cannot
destroy a placeholder a later stage still needs.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. 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 `@__mocks__/platform-bible-utils.ts`:
- Around line 93-97: Update the replacer lookup in the matchAll loop to accept
substitutions only for own properties of replacers, so inherited names such as
toString remain as bare placeholders.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f5e1fe28-93aa-4adc-8e5c-c808fc88f65a

📥 Commits

Reviewing files that changed from the base of the PR and between 19a5991 and 941024c.

📒 Files selected for processing (14)
  • REVIEW.md
  • __mocks__/platform-bible-react.tsx
  • __mocks__/platform-bible-utils.ts
  • contributions/localizedStrings.json
  • src/__tests__/components/alt-key-hint.test.tsx
  • src/components/MorphemeBox.tsx
  • src/components/PhraseBox.tsx
  • src/components/PhraseStripContext.tsx
  • src/components/PhraseStripParts.tsx
  • src/components/SegmentListView.tsx
  • src/components/SuggestionDropdown.tsx
  • src/components/TokenChip.tsx
  • src/components/alt-key-hint.tsx
  • src/hooks/usePhraseStripSetup.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/hooks/usePhraseStripSetup.ts
  • contributions/localizedStrings.json
  • src/components/PhraseStripContext.tsx
  • src/components/SegmentListView.tsx

Comment thread __mocks__/platform-bible-utils.ts
Generalize the read-the-real-source entry to cover both mocked
packages, and record that formatReplacementStringToArray resolves
replacer keys with `in` — so the mock's inherited-name behavior
matches the real function and should not be "fixed".
alex-rawlings-yyc

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.

Review by Claude Opus 5 (1M context) (inspired by Devin).

The hint itself checks out: on macOS and Alt elsewhere, placed by a {key} placeholder so each translation decides where the key goes, set in a Kbd. Three small findings below; none of them blocks the change.

  1. The Alt-split tooltip can render its raw %…% localize key during the async localization window, and the repo already has resolvedOrEmpty for exactly that flash — src/components/PhraseStripParts.tsx, src/components/SegmentListView.tsx.
  2. The new Kbd stub declares a className prop and drops it, unlike the real component and the sibling stubs — __mocks__/platform-bible-react.tsx.
  3. The new REVIEW.md section cites src/utils/localized-strings.ts for a literal that lives in src/components/alt-key-hint.tsx, in a paragraph that also restates the one above it.

Comment thread src/components/PhraseStripParts.tsx Outdated
Comment thread src/components/SegmentListView.tsx Outdated
Comment thread __mocks__/platform-bible-react.tsx Outdated
Comment thread REVIEW.md 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 15 files and all commit messages, and resolved 5 discussions.
Reviewable status: 16 of 20 files reviewed, 4 unresolved discussions (waiting on alex-rawlings-yyc).

An unresolved %…% key showed as visible hover text during PAPI's async
lookup; the aria-label deliberately keeps the raw value, since emptying
it would leave the icon button with no accessible name.

@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 4 comments.
Reviewable status: 16 of 20 files reviewed, 4 unresolved discussions (waiting on alex-rawlings-yyc and imnasnainaec).

Comment thread REVIEW.md Outdated
Comment thread __mocks__/platform-bible-react.tsx Outdated
Comment thread src/components/PhraseStripParts.tsx Outdated
Comment thread src/components/SegmentListView.tsx 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 8 files and all commit messages, made 1 comment, and resolved 4 discussions.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on alex-rawlings-yyc).


src/components/SegmentListView.tsx line 108 at r4 (raw file):

                resolvedOrEmpty(localizedStrings['%interlinearizer_boundaryControl_mergeAltHint%']),
              )}
        </TooltipContent>

⛏️ Non-blocking Devin observation:

src/components/SegmentListView.tsx:R102-107

Unresolved localize keys now yield an empty tooltip rather than a visible %key%

With resolvedOrEmpty applied, an unresolved key makes the merge tooltip content the empty string (Alt held) or the empty array altKeyHint('') returns (Alt up). The jsdom Tooltip stub now omits title entirely in that case (__mocks__/platform-bible-react.tsx:973-974), but in production the real Radix tooltip still mounts with empty content, so a hover during the brief async-localization window can pop a blank bubble instead of showing nothing. That is a cosmetic edge case in a window that resolves in one commit; noting it in case the intent was to suppress the tooltip entirely while the string is unresolved.

@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: :shipit: complete! all files reviewed, all discussions resolved (waiting on alex-rawlings-yyc).


src/components/SegmentListView.tsx line 108 at r4 (raw file):

Previously, imnasnainaec (D. Ror.) wrote…

⛏️ Non-blocking Devin observation:

src/components/SegmentListView.tsx:R102-107

Unresolved localize keys now yield an empty tooltip rather than a visible %key%

With resolvedOrEmpty applied, an unresolved key makes the merge tooltip content the empty string (Alt held) or the empty array altKeyHint('') returns (Alt up). The jsdom Tooltip stub now omits title entirely in that case (__mocks__/platform-bible-react.tsx:973-974), but in production the real Radix tooltip still mounts with empty content, so a hover during the brief async-localization window can pop a blank bubble instead of showing nothing. That is a cosmetic edge case in a window that resolves in one commit; noting it in case the intent was to suppress the tooltip entirely while the string is unresolved.

Confirmed and fixed — the intent was to show nothing, not an empty bubble. TooltipContent paints its bubble and arrow from its own classes regardless of children, and TooltipProvider defaults to delayDuration: 0 here, so there was no hover delay masking it. Both branches now go through a tooltipContentOrUndefined helper and the element is left unmounted when there's nothing to show. Applied to the strip's merge control too, which had the same shape.

TooltipContent paints its bubble and arrow from its own classes, so the
emptied content popped a blank bubble on hover during the async lookup.
The WebSocket port opens from the main process, sometimes ahead of the
first BrowserWindow, so the smoke test reading electronApp.windows()
raced startup and saw an empty list on Linux CI.
coderabbitai[bot]

This comment was marked as outdated.

Each wait started a fresh PROCESS_READY_TIMEOUT, so a stalled cold start
could outlast the Playwright test timeout bounding the worker fixture
and
surface as an opaque "Test timeout" instead of the wait that lost.

@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: 17 of 22 files reviewed, all discussions resolved (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 reviewed 5 files and all commit messages.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on alex-rawlings-yyc).

@imnasnainaec
imnasnainaec merged commit 765f6d6 into main Aug 14, 2026
10 checks passed
@imnasnainaec
imnasnainaec deleted the os-aware-split-hint branch August 14, 2026 13:43
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.

Use OS-awareness for join-segment tooltop

2 participants