Skip to content

feat: new hook useModalFocus - #186

Merged
johlju merged 3 commits into
viscalyx:mainfrom
johlju:f/b3-april
May 8, 2026
Merged

feat: new hook useModalFocus#186
johlju merged 3 commits into
viscalyx:mainfrom
johlju:f/b3-april

Conversation

@johlju

@johlju johlju commented May 8, 2026

Copy link
Copy Markdown
Member

Description

Screenshots (if applicable)

Related Issues

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)
  • Performance improvement (improves performance without changing functionality)
  • Dependency update (updating libraries or tools)

Testing

  • npm run check passes locally
  • All existing tests still pass
  • Manual testing completed
  • UI tested on desktop and mobile (if applicable)

Checklist

  • Documentation updated as needed

Checklist

  • Code follows the project style guidelines (Biome)
  • Tests added/updated as needed
  • Self-review of code completed
  • Comments added for complex logic
  • No hardcoded strings (use translations if i18n is added)

This change is Reviewable

johlju added 2 commits May 7, 2026 19:11
…ents.

- Introduced `sync-ai-instructions` skill with a script to copy AI instruction files from `.github/instructions/` to `.agents/rules/`.
- Added `sync-ai-skills` skill to copy local repository skills from `.github/skills` to AI skill target directories.
- Created scripts for both skills to handle file copying and verification.
- Updated `.gitignore` to exclude `.agents` and `.agent` directories.
- Added a new hook `useModalFocus` for managing focus within modal components.
- Refactored modal components to utilize the new focus management hook.
- Updated tests for modal components to ensure focus behavior is correct.
- Removed deprecated `sync-codex-skills` skill and its associated files.
- Updated `biome.json` schema version.
@coderabbitai

coderabbitai Bot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1a644268-6d7b-4777-a7a2-3de8c2f6c3c2

📥 Commits

Reviewing files that changed from the base of the PR and between 369c550 and 63e07cd.

📒 Files selected for processing (7)
  • .github/skills/sync-ai-skills/scripts/sync_ai_skills.sh
  • components/DeviationPill.tsx
  • hooks/useModalFocus.ts
  • tests/unit/deviation-pill.test.tsx
  • tests/unit/requirement-detail-client.test.tsx
  • tests/unit/use-modal-focus.test.tsx
  • tests/unit/version-history.test.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/unit/use-modal-focus.test.tsx
  • .github/skills/sync-ai-skills/scripts/sync_ai_skills.sh
  • hooks/useModalFocus.ts

Hidden review stack artifact:

Walkthrough

This PR centralizes modal focus/keyboard handling into a shared useModalFocus hook (with component and test updates), adds two sync workflows/scripts for AI instructions and skills (docs + agent configs + scripts), replaces hardcoded date renderings with next-intl formatting and updates tests, and updates .gitignore and Biome schema.

Changes

Modal Focus Refactoring

Layer / File(s) Summary
Hook Definition
hooks/useModalFocus.ts
Defines useModalFocus that captures/restores prior focus, schedules initial focus via RAF, cancels RAF on close, handles Escape (optional), and traps Tab/Shift+Tab.
Component Integration
components/ConfirmModal.tsx, components/DeviationDecisionModal.tsx, components/DeviationFormModal.tsx, components/SuggestionFormModal.tsx, components/SuggestionResolutionModal.tsx
Modal components now use useModalFocus (modalRef, initialFocusRef, closeDisabled, onClose, open) and attach the hook-provided handleKeyDown instead of inline handlers.
Tests
tests/unit/use-modal-focus.test.tsx, tests/unit/confirm-modal.test.tsx
Adds hook unit tests for initial focus, Escape close and restore, Escape suppression, Tab/Shift+Tab trapping and RAF cancellation; updates ConfirmModal tests for focus restoration and tab wrap assertions.

Skill Scripts Infrastructure

Layer / File(s) Summary
Sync AI Instructions Skill
.github/skills/sync-ai-instructions/*
Adds SKILL.md, agent config, and sync_ai_instructions.sh to copy .github/instructions/.agents/rules/ with CLI handling, source validation, deterministic file discovery, metadata-preserving copy, and per-file plus summary output.
Sync AI Skills Skill
.github/skills/sync-ai-skills/*
Adds SKILL.md, agent config, and sync_ai_skills.sh to copy .github/skills/ → Codex and .agent/skills with recursive copy, sha256 verification (sha256sum/shasum fallback), per-skill verify, conditional Codex sync, and final summary.
Sync Codex Skills Removal
.github/skills/sync-codex-skills/*
Removes former Codex-only SKILL.md and agent interface fields; functionality consolidated into sync-ai-skills.

Localization and Date Formatting

Layer / File(s) Summary
Date Formatting Updates
components/DeviationPill.tsx, components/VersionHistory.tsx
Switches date rendering to next-intl useFormatter() and format.dateTime(..., { dateStyle: 'short' }) instead of hardcoded or runtime locale toLocaleDateString.
Tests
tests/unit/version-history.test.tsx, tests/unit/deviation-pill.test.tsx, tests/unit/requirement-detail-client.test.tsx
Mocks updated to provide useFormatter().dateTime; tests updated to assert locale-formatted short dates.

Configuration Updates

Layer / File(s) Summary
Gitignore and Biome
.gitignore, biome.json
Append .agents, .agent, and devcontainer-lock.json to .gitignore; bump Biome $schema to 2.4.14.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is entirely template boilerplate with all sections incomplete, checkboxes unchecked, and no actual implementation details, related issues, or testing results provided. Complete the description template by filling in the actual changes made, specify related issues, select the appropriate change type(s), document testing performed, and check off completed items.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: introducing a new React hook named useModalFocus for managing modal focus behavior.
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.

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


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.

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

🧹 Nitpick comments (1)
components/DeviationPill.tsx (1)

4-4: ⚡ Quick win

Use useFormatter() instead of useLocale() + bare toLocaleDateString to prevent hydration mismatches.

Date formatting in a user's locale which doesn't match the server is a documented cause of Next.js hydration errors. useFormatter() from next-intl is specifically designed to avoid hydration mismatches by ensuring that locale, timeZone, and now are shared across the entire app — server and client alike. Using raw toLocaleDateString(locale) bypasses that infrastructure.

VersionHistory.tsx in this same PR already uses the correct pattern. Align DeviationPill.tsx to match:

♻️ Proposed fix
-import { useLocale, useTranslations } from 'next-intl'
+import { useFormatter, useTranslations } from 'next-intl'
-  const locale = useLocale()
+  const format = useFormatter()
-          <span>
-            {new Date(deviation.createdAt).toLocaleDateString(locale)}
-          </span>
+          <span>
+            {format.dateTime(new Date(deviation.createdAt), { dateStyle: 'short' })}
+          </span>
-              <span>
-                {new Date(deviation.decidedAt).toLocaleDateString(locale)}
-              </span>
+              <span>
+                {format.dateTime(new Date(deviation.decidedAt), { dateStyle: 'short' })}
+              </span>

Also applies to: 31-31, 100-103, 129-132

🤖 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 `@components/DeviationPill.tsx` at line 4, The component DeviationPill
currently imports and uses useLocale and calls toLocaleDateString(locale), which
can cause hydration mismatches; replace useLocale with next-intl's
useFormatter() and use its formatDate (e.g., const { formatDate } =
useFormatter()) to format dates consistently across server and client, updating
every place in DeviationPill that calls date.toLocaleDateString(locale) (and
similar bare toLocale* usages) to call formatDate(date, options) instead; adjust
the imports (remove useLocale, import useFormatter) and ensure the values passed
are Date objects or ISO strings acceptable to formatDate so the formatting
matches VersionHistory.tsx's pattern.
🤖 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 @.github/skills/sync-ai-skills/scripts/sync_ai_skills.sh:
- Line 86: The parameter expansion ${source_file#$source_skill_dir/} can treat
$source_skill_dir as a glob; update the pattern operand to quote the inner
variable so the shell treats the directory literally (i.e., modify the expansion
that sets relative_path to use a quoted $source_skill_dir inside the removal
pattern), keeping the assignment to relative_path and using the same symbols
source_file and source_skill_dir.

In `@hooks/useModalFocus.ts`:
- Around line 50-63: The effect in useModalFocus schedules focus via
requestAnimationFrame but never stores or cancels the RAF handle, so a pending
callback can run after the modal begins exit and steal focus; fix by storing the
requestAnimationFrame id (e.g., rafIdRef) when calling requestAnimationFrame in
the open branch and cancel it in the cleanup (and on unmount) using
cancelAnimationFrame; update the useEffect so that initialFocusRef scheduling
assigns the id and the returned cleanup always cancels rafIdRef.current before
restoring previousFocusRef.current.
- Around line 25-26: The FOCUSABLE selector constant is including disabled
controls and missing native links, causing the focus-trap in useModalFocus to
treat disabled buttons as tabbable; update the FOCUSABLE string used by the hook
(FOCUSABLE) to include a[href] and to exclude disabled elements (e.g., 'a[href],
input:not([disabled]), textarea:not([disabled]), button:not([disabled]),
select:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled])') so
querySelectorAll returns only keyboard-reachable elements and the focus-wrap
logic (used by the hook's focus handling) correctly detects first/last enabled
elements.

In `@tests/unit/use-modal-focus.test.tsx`:
- Around line 62-68: The test file stubs globals in beforeEach
(vi.stubGlobal('requestAnimationFrame', ...) and
vi.stubGlobal('cancelAnimationFrame', ...)) but never restores them; add an
afterEach that unstubs those globals (either
vi.unstubGlobal('requestAnimationFrame') and
vi.unstubGlobal('cancelAnimationFrame') or vi.unstubAllGlobals()) so the
synchronous RAF stub does not leak into other tests; place the afterEach
alongside the existing beforeEach to ensure restoration after each test.

In `@tests/unit/version-history.test.tsx`:
- Around line 96-98: The three hardcoded expects
(expect(screen.getByText('3/3/26')) etc.) are timezone-sensitive; instead
compute the expected strings from the same Date objects used in the test by
creating new Date('2026-03-03'), new Date('2026-03-02'), new Date('2026-03-01')
and calling toLocaleDateString('en', { dateStyle: 'short' }) and assert those
computed strings with getByText; apply the same dynamic derivation pattern to
the other date assertions mentioned (lines 360–361) so all date expectations use
the same locale/dateStyle formatting as the component under test.

---

Nitpick comments:
In `@components/DeviationPill.tsx`:
- Line 4: The component DeviationPill currently imports and uses useLocale and
calls toLocaleDateString(locale), which can cause hydration mismatches; replace
useLocale with next-intl's useFormatter() and use its formatDate (e.g., const {
formatDate } = useFormatter()) to format dates consistently across server and
client, updating every place in DeviationPill that calls
date.toLocaleDateString(locale) (and similar bare toLocale* usages) to call
formatDate(date, options) instead; adjust the imports (remove useLocale, import
useFormatter) and ensure the values passed are Date objects or ISO strings
acceptable to formatDate so the formatting matches VersionHistory.tsx's pattern.
🪄 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: CHILL

Plan: Pro

Run ID: f54b3984-0ac5-422b-adda-4456620f17cd

📥 Commits

Reviewing files that changed from the base of the PR and between 87b04ca and 369c550.

📒 Files selected for processing (21)
  • .github/skills/sync-ai-instructions/SKILL.md
  • .github/skills/sync-ai-instructions/agents/openai.yaml
  • .github/skills/sync-ai-instructions/scripts/sync_ai_instructions.sh
  • .github/skills/sync-ai-skills/SKILL.md
  • .github/skills/sync-ai-skills/agents/openai.yaml
  • .github/skills/sync-ai-skills/scripts/sync_ai_skills.sh
  • .github/skills/sync-codex-skills/SKILL.md
  • .github/skills/sync-codex-skills/agents/openai.yaml
  • .gitignore
  • biome.json
  • components/ConfirmModal.tsx
  • components/DeviationDecisionModal.tsx
  • components/DeviationFormModal.tsx
  • components/DeviationPill.tsx
  • components/SuggestionFormModal.tsx
  • components/SuggestionResolutionModal.tsx
  • components/VersionHistory.tsx
  • hooks/useModalFocus.ts
  • tests/unit/confirm-modal.test.tsx
  • tests/unit/use-modal-focus.test.tsx
  • tests/unit/version-history.test.tsx
💤 Files with no reviewable changes (2)
  • .github/skills/sync-codex-skills/agents/openai.yaml
  • .github/skills/sync-codex-skills/SKILL.md

Comment thread .github/skills/sync-ai-skills/scripts/sync_ai_skills.sh Outdated
Comment thread hooks/useModalFocus.ts Outdated
Comment thread hooks/useModalFocus.ts
Comment thread tests/unit/use-modal-focus.test.tsx
Comment thread tests/unit/version-history.test.tsx Outdated
@codecov

codecov Bot commented May 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 60.48%. Comparing base (87b04ca) to head (63e07cd).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #186      +/-   ##
==========================================
+ Coverage   60.38%   60.48%   +0.09%     
==========================================
  Files         293      293              
  Lines       17294    17239      -55     
  Branches     6653     6635      -18     
==========================================
- Hits        10443    10427      -16     
+ Misses       6710     6671      -39     
  Partials      141      141              
Files with missing lines Coverage Δ
components/ConfirmModal.tsx 96.84% <100.00%> (+2.92%) ⬆️
components/DeviationDecisionModal.tsx 45.00% <100.00%> (+5.00%) ⬆️
components/DeviationFormModal.tsx 38.63% <100.00%> (+3.94%) ⬆️
components/DeviationPill.tsx 100.00% <100.00%> (ø)
components/SuggestionFormModal.tsx 44.73% <100.00%> (+6.10%) ⬆️
components/SuggestionResolutionModal.tsx 45.00% <100.00%> (+12.21%) ⬆️
components/VersionHistory.tsx 93.75% <100.00%> (+0.04%) ⬆️
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@johlju

johlju commented May 8, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 8, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@johlju
johlju merged commit b88d907 into viscalyx:main May 8, 2026
9 checks passed
@johlju
johlju deleted the f/b3-april branch May 8, 2026 09:31
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.

1 participant