Fix arc alignment, punctuation routing, and link-button polish; extract test helpers and reorganize types#100
Fix arc alignment, punctuation routing, and link-button polish; extract test helpers and reorganize types#100imnasnainaec wants to merge 20 commits into
Conversation
Inactive link slots now use visibility:hidden instead of max-width collapse, so toggling hideInactiveLinkButtons no longer shifts phrase box positions. Removes the slotAnimationTick rAF loop and the hideInactiveLinkButtons re-center effect that compensated for the old layout shift. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
link-slot was flex-col, stacking the icon wrapper above the punctuation. Changing to flex-row keeps them side-by-side so punctuation position no longer depends on the icon wrapper's height. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
buildRenderUnits was pushing into the shared TokenGroup.punctuationBetween arrays, which are owned by memoized objects that outlive a single render. Replace push() with assignment so each call resets the gap array instead of accumulating. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After the gapIndex fix, pendingIntraGroup pointed to a non-existent gap when the current token was the last in its group, causing a push() onto undefined when post-phrase punctuation arrived. Guard the assignment so the last token clears pendingIntraGroup instead; punctuation then falls into the inter-group LinkSlot as intended. Adds a regression test. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Link buttons: use foreground/60 (active) and foreground/20 (inactive) instead of muted-foreground at 100%/50%, widening the visual gap between actionable and suppressed slots. Arcs: reduce focused-phrase stroke opacity from 1.0 to 0.75 so the current phrase's arcs are less dominant relative to other phrases. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Punctuation layout: revert link-slot to flex-col so punctuation sits below the icon. Remove visibility:hidden from the icon wrapper and use opacity:0 instead; add display:inline-flex + min-height to ensure the wrapper always reserves the same vertical space even when TokenLinkIcon returns null. Arc contrast: reduce focused-phrase stroke opacity 0.75→0.60 and raise dimmed-phrase stroke opacity 0.8→1.0 (border is already low-contrast), narrowing the visual gap between the two states. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR transitions link-icon suppression from a horizontal sliding-door effect to a vertical opacity fade, simplifies re-centering and arc-recalculation logic to remove layout-dependent animation state, updates visual styling for arcs and link icons, fixes punctuation routing in grouped tokens, and updates localization copy. ChangesLink Icon Fade Transition and Arc Styling
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as outdated.
This comment was marked as outdated.
There was a problem hiding this comment.
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/components/PhraseStripParts.tsx`:
- Around line 98-105: The link icon slot in PhraseStripParts is only visually
hidden via opacity but still receives pointer events; update the inline style
logic (the style object where display, minHeight, transitionDuration, opacity,
overflowAnchor are set) to also disable hit-testing when suppressed by adding
pointerEvents: suppressLinkIcon ? 'none' : undefined and mark it non-interactive
for assistive tech (e.g., set aria-hidden or ensure interactive child controls
are removed/disabled when suppressLinkIcon is true); keep the existing
transition behavior using skipLinkTransition and LINK_SLOT_TRANSITION_MS so
visual timing is unchanged.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 02f12a2b-f234-4310-8ab3-41a84a2ccefc
📒 Files selected for processing (14)
contributions/localizedStrings.jsonsrc/__tests__/components/ContinuousView.test.tsxsrc/__tests__/components/PhraseStripParts.test.tsxsrc/__tests__/components/SegmentView.test.tsxsrc/__tests__/utils/phrase-arc.test.tssrc/__tests__/utils/token-layout.test.tssrc/components/ContinuousView.tsxsrc/components/PhraseStripContext.tsxsrc/components/PhraseStripParts.tsxsrc/components/SegmentView.tsxsrc/components/TokenLinkIcon.tsxsrc/tailwind.csssrc/utils/phrase-arc.tssrc/utils/token-layout.ts
| className="tw:transition-opacity tw:ease-in-out" | ||
| style={{ | ||
| display: 'inline-flex', | ||
| minHeight: '1rem', | ||
| transitionDuration: skipLinkTransition ? '0ms' : `${LINK_SLOT_TRANSITION_MS}ms`, | ||
| maxWidth: suppressLinkIcon ? '0' : '2rem', | ||
| opacity: suppressLinkIcon ? 0 : 1, | ||
| pointerEvents: suppressLinkIcon ? 'none' : undefined, | ||
| overflowAnchor: 'none', | ||
| }} |
There was a problem hiding this comment.
Hidden link icons remain interactive while visually suppressed.
opacity: 0 alone hides the icon visually but does not remove hit-testing, so suppressed slot controls can still intercept pointer interaction.
Proposed fix
<span
className="tw:transition-opacity tw:ease-in-out"
style={{
display: 'inline-flex',
minHeight: '1rem',
transitionDuration: skipLinkTransition ? '0ms' : `${LINK_SLOT_TRANSITION_MS}ms`,
opacity: suppressLinkIcon ? 0 : 1,
+ pointerEvents: suppressLinkIcon ? 'none' : 'auto',
overflowAnchor: 'none',
}}
>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| className="tw:transition-opacity tw:ease-in-out" | |
| style={{ | |
| display: 'inline-flex', | |
| minHeight: '1rem', | |
| transitionDuration: skipLinkTransition ? '0ms' : `${LINK_SLOT_TRANSITION_MS}ms`, | |
| maxWidth: suppressLinkIcon ? '0' : '2rem', | |
| opacity: suppressLinkIcon ? 0 : 1, | |
| pointerEvents: suppressLinkIcon ? 'none' : undefined, | |
| overflowAnchor: 'none', | |
| }} | |
| className="tw:transition-opacity tw:ease-in-out" | |
| style={{ | |
| display: 'inline-flex', | |
| minHeight: '1rem', | |
| transitionDuration: skipLinkTransition ? '0ms' : `${LINK_SLOT_TRANSITION_MS}ms`, | |
| opacity: suppressLinkIcon ? 0 : 1, | |
| pointerEvents: suppressLinkIcon ? 'none' : 'auto', | |
| overflowAnchor: 'none', | |
| }} |
🤖 Prompt for 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.
In `@src/components/PhraseStripParts.tsx` around lines 98 - 105, The link icon
slot in PhraseStripParts is only visually hidden via opacity but still receives
pointer events; update the inline style logic (the style object where display,
minHeight, transitionDuration, opacity, overflowAnchor are set) to also disable
hit-testing when suppressed by adding pointerEvents: suppressLinkIcon ? 'none' :
undefined and mark it non-interactive for assistive tech (e.g., set aria-hidden
or ensure interactive child controls are removed/disabled when suppressLinkIcon
is true); keep the existing transition behavior using skipLinkTransition and
LINK_SLOT_TRANSITION_MS so visual timing is unchanged.
Without the guard the update succeeds but the subsequent delete removes the phrase that was just updated, leaving orphaned token references. Invariant was previously enforced only by callers. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Aligns __tests__/components/controls/ and __tests__/components/modals/ with src/components/controls/ and src/components/modals/. Updates relative import paths accordingly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ressed opacity:0 alone left the invisible TokenLinkIcon hittable and focusable. Add pointerEvents:'none' and aria-hidden when suppressLinkIcon is true. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two test files redeclared the same constant that test-helpers.ts already exports. Remove the duplicates and import the canonical export. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The same minimal word-token shape was defined six times across three test files under three different local names (mkWord, mkToken, mk). A single exported factory with a stable API replaces all six. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Consolidates six independently constructed all-empty TextAnalysis objects across two source files and four test files into a single shared factory. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-layout types - emptyFactories.ts → empty-factories.ts, typeGuards.ts → type-guards.ts - Extract FocusContext/SlotFocusInfo/TokenGroup/LinkSlot/RenderUnit from utils/token-layout.ts into types/token-layout.ts - Add @file JSDoc to all three type files - Update all import sites (20 files) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Devin: https://app.devin.ai/review/sillsdev/interlinearizer-extension/pull/100
Summary
Bug fixes and visual polish
Arc alignment when hiding inactive link buttons: Inactive link-slot wrappers previously collapsed to
max-width: 0, shifting phrase boxes horizontally and causing arcs to re-align whenever the setting was toggled. The wrapper now usesopacity: 0with a reserved minimum height instead, keeping the slot's footprint constant so arcs stay put. TheslotAnimationTickrAF loop inSegmentView(which compensated for the old layout shift) and thehideInactiveLinkButtonsre-center effect inContinuousViewwere removed as they are no longer needed.Punctuation sits below link icon: The
link-slotflex container isflex-colso punctuation renders below the icon. The icon wrapper usesopacity: 0(notvisibility: hidden) and carries an explicitdisplay: inline-flex+min-heightso it always reserves the same vertical space, preventing punctuation from jumping when the icon renders nothing.Link icon a11y when suppressed: Pointer events are disabled and the element is hidden from the accessibility tree when the icon is not rendered.
In-phrase punctuation duplication:
buildRenderUnitsmutated thepunctuationBetweenarrays on memoizedTokenGroupobjects in-place. BecausebuildRenderUnitsre-runs on focus change whilegroupTokensis memoized at a coarser granularity, punctuation accumulated on every focus change. Fixed by replacing.push()with assignment so each run resets the gap rather than appending. A follow-up commit ensures the fix is fully idempotent.Punctuation routed to wrong gap in multi-token phrases: After processing a non-first word token, the
pendingIntraGrouptracker was pointed at the gap before the current token rather than the gap after it. Subsequent punctuation was pushed into the wrong slot. Fixed by advancing the tracker to the next gap index. A guard clears the tracker when the current token is the last in its group, preventing an out-of-bounds access on post-phrase punctuation.Crash on punctuation after the last token of a phrase group: Fixed an index-out-of-bounds that occurred when punctuation followed the final token in a group.
Guard
mergePhrasesagainst self-merge: Added a guard whentargetPhraseId === absorbedPhraseIdto prevent state corruption.Link button contrast: Active and inactive link buttons now use
foreground/60andforeground/20respectively (previouslymuted-foregroundat 100%/50%), producing a wider visual gap between actionable and suppressed slots.Arc contrast: Focused-phrase arc opacity reduced from 1.0 to 0.60; other-phrase arc opacity raised from 0.80 to 1.0 (the
bordervariable is already low-contrast, so full opacity is still visually quiet). The gap between the two states is substantially narrower.Tooltip copy: Removed "yet" from the cross-segment link button tooltip ("not yet supported" → "not supported").
Refactoring and cleanup
Type reorganization: Renamed type files to kebab-case, added
@fileJSDoc headers, and moved token-layout types to a dedicated module.ArcStrokePropsis no longer exported — no consumers exist outsidephrase-arc.ts.Test directory layout: Moved controls and modals test files into subdirectories that match the source layout.
Test helpers extracted (CLEANUP.md H1, H3, H5, H7, H8):
emptyAnalysisfactory →src/types/emptyFactories.tsmakeWordTokenfixture factory →src/__tests__/test-helpers.tsmakeStubProjectfactory →src/__tests__/test-helpers.tswithAnalysisStorerender wrapper →src/__tests__/components/test-helpers.tsxdefaultScrRef— removed two local redeclarations; both files now import the canonical export fromtest-helpers.tsHook test helpers extracted (CLEANUP.md M15):
mockProjectSettingReturnhelper centralized, removing 15 inline tuple spellings across two hook test files.Test plan
🤖 Generated with Claude Code
This change is