Skip to content

Fix arc alignment, punctuation routing, and link-button polish; extract test helpers and reorganize types#100

Merged
alex-rawlings-yyc merged 20 commits into
link-tokensfrom
lt3
Jun 10, 2026
Merged

Fix arc alignment, punctuation routing, and link-button polish; extract test helpers and reorganize types#100
alex-rawlings-yyc merged 20 commits into
link-tokensfrom
lt3

Conversation

@imnasnainaec

@imnasnainaec imnasnainaec commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

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 uses opacity: 0 with a reserved minimum height instead, keeping the slot's footprint constant so arcs stay put. The slotAnimationTick rAF loop in SegmentView (which compensated for the old layout shift) and the hideInactiveLinkButtons re-center effect in ContinuousView were removed as they are no longer needed.

  • Punctuation sits below link icon: The link-slot flex container is flex-col so punctuation renders below the icon. The icon wrapper uses opacity: 0 (not visibility: hidden) and carries an explicit display: inline-flex + min-height so 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: buildRenderUnits mutated the punctuationBetween arrays on memoized TokenGroup objects in-place. Because buildRenderUnits re-runs on focus change while groupTokens is 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 pendingIntraGroup tracker 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 mergePhrases against self-merge: Added a guard when targetPhraseId === absorbedPhraseId to prevent state corruption.

  • Link button contrast: Active and inactive link buttons now use foreground/60 and foreground/20 respectively (previously muted-foreground at 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 border variable 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 @file JSDoc headers, and moved token-layout types to a dedicated module. ArcStrokeProps is no longer exported — no consumers exist outside phrase-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):

    • emptyAnalysis factory → src/types/emptyFactories.ts
    • makeWordToken fixture factory → src/__tests__/test-helpers.ts
    • makeStubProject factory → src/__tests__/test-helpers.ts
    • withAnalysisStore render wrapper → src/__tests__/components/test-helpers.tsx
    • defaultScrRef — removed two local redeclarations; both files now import the canonical export from test-helpers.ts
  • Hook test helpers extracted (CLEANUP.md M15): mockProjectSettingReturn helper centralized, removing 15 inline tuple spellings across two hook test files.

Test plan

  • Toggle "Hide link buttons outside active segment" — arcs should not move
  • Verify punctuation between tokens of a multi-token phrase appears in the correct gaps
  • Verify punctuation after a phrase appears in the inter-phrase slot, not inside the phrase
  • Verify in-phrase punctuation does not duplicate when navigating between phrases
  • Verify no crash when punctuation follows the last token of a phrase group
  • Confirm merging a phrase with itself does not corrupt state
  • Confirm link button active/inactive states are visually distinct
  • Confirm focused-phrase arcs are less dominant without losing clarity
  • Confirm suppressed link icons are not reachable via keyboard or screen reader
  • Test in both light and dark mode

🤖 Generated with Claude Code


This change is Reviewable

imnasnainaec and others added 7 commits June 8, 2026 13:51
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>
@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

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

Changes

Link Icon Fade Transition and Arc Styling

Layer / File(s) Summary
Visual styling for arcs and link icons
src/components/TokenLinkIcon.tsx, src/tailwind.css, src/utils/phrase-arc.ts, src/__tests__/utils/phrase-arc.test.ts
Focused arcs reduce opacity from 100% to 60%, dimmed arcs increase from 80% to 100%, and link-icon buttons shift from muted-foreground to foreground colors. Test constants and assertions updated to reflect the new opacity values.
Link icon fade transition implementation
src/components/PhraseStripParts.tsx, src/components/PhraseStripContext.tsx, src/__tests__/components/PhraseStripParts.test.tsx
Link-icon wrapper transitions from maxWidth-based slide to opacity-only fade. Layout space is preserved via visibility: hidden. Documentation updated to clarify fade duration used for focus anchoring during segment flips. Tests updated to verify visibility state instead of maxWidth collapse.
Simplify re-centering and arc recalculation
src/components/ContinuousView.tsx, src/components/SegmentView.tsx, src/__tests__/components/ContinuousView.test.tsx, src/__tests__/components/SegmentView.test.tsx
Remove hideInactiveLinkButtons from re-centering and arc-path dependencies. Re-center only when simplifyPhrases changes (single scrollIntoView call instead of rAF loop). Remove slot-animation-tick state from SegmentView since fade preserves layout space. Update arc-path dependency lists accordingly. Tests verify new behavior: hideInactiveLinkButtons does not trigger re-center; simplifyPhrases triggers exactly one scroll call; fade-transition duration is asserted after mount.
Punctuation routing fix for grouped tokens
src/utils/token-layout.ts, src/__tests__/utils/token-layout.test.ts
Fix punctuation accumulation in buildRenderUnits when intra-group tokens are encountered: replace push-only logic with targeted reset/repopulate that either preserves or replaces the target gap, preventing cross-invocation accumulation on memoized token groups. New test asserts punctuation after the final group token routes to the inter-group slot, not the prior group's punctuationBetween.
Localization string update
contributions/localizedStrings.json
Update cross-segment link button tooltip from "not yet supported" to "not supported".

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

🟥High

Suggested reviewers

  • alex-rawlings-yyc
  • jasonleenaylor

Poem

🐰 The link icons now fade, not slide—
Opacity whispers where maxWidth once tied.
Arcs glow less bright when dimmed from the view,
Re-centering flows simpler, with focus renewed.
Punctuation finds its slot, no more astray.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 85.71% 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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: it lists the three primary fixes (arc alignment, punctuation routing, link-button polish) and mentions the secondary refactoring (test helpers and type reorganization) that appear throughout the changeset.

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

✨ 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 lt3

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.

@imnasnainaec imnasnainaec marked this pull request as ready for review June 8, 2026 21:41
@imnasnainaec

This comment was marked as outdated.

@coderabbitai

This comment was marked as outdated.

@imnasnainaec

This comment was marked as resolved.

@coderabbitai

This comment was marked as outdated.

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

📥 Commits

Reviewing files that changed from the base of the PR and between e9f4e48 and 9895cb7.

📒 Files selected for processing (14)
  • contributions/localizedStrings.json
  • src/__tests__/components/ContinuousView.test.tsx
  • src/__tests__/components/PhraseStripParts.test.tsx
  • src/__tests__/components/SegmentView.test.tsx
  • src/__tests__/utils/phrase-arc.test.ts
  • src/__tests__/utils/token-layout.test.ts
  • src/components/ContinuousView.tsx
  • src/components/PhraseStripContext.tsx
  • src/components/PhraseStripParts.tsx
  • src/components/SegmentView.tsx
  • src/components/TokenLinkIcon.tsx
  • src/tailwind.css
  • src/utils/phrase-arc.ts
  • src/utils/token-layout.ts

Comment on lines +98 to 105
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',
}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

Suggested change
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.

imnasnainaec and others added 13 commits June 9, 2026 14:03
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>
@imnasnainaec imnasnainaec changed the title Fix arc alignment, punctuation routing, and visual polish for hidden link buttons Fix arc alignment, punctuation routing, and link-button polish; extract test helpers and reorganize types Jun 9, 2026

@alex-rawlings-yyc alex-rawlings-yyc 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.

Reviewable is unable to connect to GitHub right now (there appears to be a general GitHub API outage at the moment). Regardless, LGTM

@alex-rawlings-yyc alex-rawlings-yyc merged commit eae720d into link-tokens Jun 10, 2026
1 of 2 checks passed
@alex-rawlings-yyc alex-rawlings-yyc deleted the lt3 branch June 10, 2026 15:32
@coderabbitai coderabbitai Bot mentioned this pull request Jun 10, 2026
alex-rawlings-yyc added a commit that referenced this pull request Jun 11, 2026
* Add UI for linking phrases together

* Adjustments to line styles

* Add link buttons and improve styling (TESTS NOT UPDATED YET)

* Rework focusRef, update tests

* Symmetrize token ref focus

* Hoist out phrase strip parts, improve split button handling

* Add useArcPaths hook

* Improve split border colour clarity

* Add useArcSplitHandler hook

* Update/add tests

* Removed `--omit=optional` from Install extension dependencies step from lint git workflow (it was causing a failure)

* Refactor phrase lookup to use id-keyed map for O(1) access

Replace the token-ref-keyed linear search (`[...map.values()].find(l => l.analysisId === id)`) with a new `selectPhraseLinkByAnalysisId` selector and `usePhraseLinkByIdMap` hook, giving O(1) phrase lookup by id in `ArcOverlay`, `useArcSplitHandler`, and `SegmentView`.

Also:
- Move the phrase-revert effect from `PhraseBox` up to `InterlinearizerInner` so it fires even when all tokens are removed from the phrase
- Fix `TokenChip` state sync to use `useEffect` instead of inline mutation during render
- Fix `useArcPaths` dep array to use a version counter instead of a spread to satisfy the rules of hooks
- Reset `phraseMode` on project switch in `InterlinearizerLoader`
- Use a stable empty map constant in `PhraseBox` to avoid breaking memo
- Improve arc key to include `phraseId` and `splitAfterTokenRef`

* Add clarifying comment about `--omit-optional` omission

* Minor adjustments, test coverage improvements

* Minor improvements

* Improve handling of inter-row arcs, other arc-related bugs and nits

* Consolidate duplicated code

* Add usePhraseHoverState test suite

* Add PhraseStripContext

* Add PhraseStrip component and bundle props

* Set static control pill location relative to phrase box, clean up comments, consolidate tw classes

* Add regions

* Add tokenDocOrder prop, fix Set mutation, correct test assertions, extend makePhraseLink fixture, add JSDoc and region markers

- Lift tokenDocOrder computation out of SegmentView into callers; add prop to SegmentView and SegmentView tests
- Clone incoming Set in handleSplitHoverChange (usePhraseHoverState) so React sees a new reference on every update
- Fix two ArcOverlay test assertions that used invalid two-argument not.toHaveBeenCalledWith on a single-argument handler; replace with not.toHaveBeenCalled()
- Extend makePhraseLink with an optional surfaceTexts parameter so tests can supply TokenSnapshot.surfaceText values independently of tokenRef
- Add JSDoc blocks for handleSplitHoverEnter and handleSplitHoverLeave in ArcOverlay
- Update useArcPaths @returns JSDoc to describe ArcPathsResult including requiredRowGapPx
- Correct PhraseBox JSDoc: one token remaining (not two) triggers phrase deletion; add region markers to ArcOverlay, PhraseBox, usePhraseHoverState, useArcPaths

* Delete empty PhraseAnalysis automatically, tokenDocOrder only considers words, memoize PhraseGroup

* Eliminate unnecessary abstraction, simplify computeAllArcPaths

* Move edit/unlink controls to new confirm bar, fix edit mode bug, re-derive phrase surface text from new tokens

* Fix SegmentView split button not working, improve gap calculation, clean up comments

* Prevent 1-token phrases being created from edit mode

* Minor adjustments

* Rework arc drawing and related things

* Cleanup

* Minor adjustments

* Minor adjustments

* Hardenings for #91 (#93)

* Harden ContinuousView scroll effect isInternal detection

Replace the heuristic with an explicit lastDisplayUpdateWasInternalRef flag set
at decision time in the focusedTokenRef effect. The scroll effect now reads only
refs, removing the stale closure reads and the eslint-disable comment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add updatePhrase and setPhraseMode to revert effect deps

Both are referentially stable (memoized dispatch and React state setter), so
adding them costs nothing at runtime but removes the implicit reliance on that
stability guarantee. Update the eslint-disable comment to explain why phraseMode
itself remains intentionally omitted.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Document groupRef closure churn and its safe ref-cycling behavior

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* Do minor phrase-arc cleanup (#95)

* Do minor phrase-arc cleanup

* Complete JSDocs

* Refactor describeBoxPair into two locally specific function (#96)

* Refactor describeBoxPair into two locally specific function

* Fix mis-match

* Fix onFocusPhrase breaking PhraseBox memoization

* Improve visibility in light mode

* Add toggles for phrase controls and options dropdown, subdivide components folder, add tooltip for disabled links

* Fix punctuation rendering issues

* Make SegmentView background clickable, shore up test coverage

* Reposition options dropdown on resize

* Minor adjustments

* Minor adjustments

* Improve AGENTS.md

* Rename window* in ContinuousView to renderWindow*, improve render window in ContinuousView, improve handling of settings in InterlinearizerLoader, add dedicated mergePhrases reducer to analysisSlice, emit data-last-token-read on all render paths

* Standardize spelling

* Add REVIEW.md

* Suggested cleanup on #91 (#99)

* Merge mostly redundant tests

* Update settings for VSCode to match our stylelint use

* Add JSDocs

* Fix pump() overwriting observerCallback via stub constructor

new ResizeObserver(() => {}) inside pump() was invoking the stub
constructor, replacing the stored observerCallback with a no-op on
every call after the first. Replace with a pre-declared stubObserver
plain object so pump() never touches the constructor again.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Sort cspell words

* Fix brit spelling now covered in AGENTS.md

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix arc misalignment issue when out-of-segment links hidden

* Add animation to make things smoother when out-of-segment links are hidden

* Fix issue with inputs not focusing their token, add animation to SegmentView

* Minor adjustment

* Minor adjustments

* Fix arc alignment, punctuation routing, and link-button polish; extract test helpers and reorganize types (#100)

* Reserve link-slot space when hidden to prevent arc re-alignment

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>

* Fix punctuation jumping when link icon is hidden

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>

* Fix in-phrase punctuation duplicating on every focus change

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>

* Fix crash on punctuation after the last token of a phrase group

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>

* Cut 'yet'

* Tune link button and arc contrast

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>

* Fix punctuation layout and reduce arc contrast gap

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>

* Guard mergePhrases reducer against targetPhraseId === absorbedPhraseId

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>

* Move controls/modals tests into matching subdirectories

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>

* Disable pointer events and hide from a11y tree when link icon is suppressed

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>

* Import defaultScrRef from test-helpers instead of redefining it

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>

* Extract makeWordToken fixture factory into test-helpers

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>

* Extract emptyAnalysis factory into src/types/emptyFactories.ts

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>

* Reorganize types: rename to kebab-case, add @file headers, move token-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>

* Unexport ArcStrokeProps — no consumers outside phrase-arc.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: extract makeStubProject and reuse project fixtures

* Fix idempotent punctuation routing in buildRenderUnits

* Update comments

* Make common withAnalysisStore test-helper

* Extract in-file project setting helpers

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* Minor adjustments

* bugfix and cleanup: opacity hide, doc cleanup, helper functions (#101)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix test failure

---------

Co-authored-by: D. Ror. <imnasnainaec@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai coderabbitai Bot mentioned this pull request Jul 7, 2026
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