Skip to content

Stop every focus move from re-checking the whole page's styles on macOS - #1038

Merged
hardbeat920 merged 1 commit into
hardbeat920:mainfrom
zcy22606:fix/group-focus-within-invalidation
Oct 18, 2026
Merged

hardbeat920 merged 1 commit into
hardbeat920:mainfrom
zcy22606:fix/group-focus-within-invalidation

Conversation

@zcy22606

@zcy22606 zcy22606 commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

What changed

src/styles/index.css redefines the group-focus-within variant as &:where(.group:focus-within *), so the whole group condition sits inside :where(). This is the same form the file already uses for the dark variant. No component changes.

Why

Tailwind v4's built-in group-focus-within compiles to

.group-focus-within\:opacity-100:is(:where(.group):focus-within *) { opacity: 1 }

WebKit's style invalidation can't key this rule on .group, because .group is wrapped in a :where() nested inside the :is(). So whenever focus moves anywhere (clicking a session row, a new composer taking focus, tabbing), WebKit re-checks the style of the whole document. With a long transcript open, the Safari Web Inspector timeline showed about 45 ms of style recalculation per focus move. I measured this in a build of MonoCode that I run day to day, not on a fresh checkout of main.

With &:where(.group:focus-within *) the rule is keyed on .group:focus-within. WebKit then only invalidates the .group elements whose focus-within state changed, and the same focus move costs next to nothing.

This is the same kind of fix as #985, where :has() inside a group-has-* variant made every composer keystroke re-check the whole page.

Styles are unchanged

The selector matches exactly the same elements: :where() / :is() only affect specificity, not matching. There are 5 uses on main (git grep group-focus-within):

  • Sidebar.tsx session card: model name (max-w, pr, opacity) and the archive button (pointer-events, opacity)
  • AutomationsView.tsx "Remove trigger" button (opacity)
  • GitChangesPanel.tsx directory and file row actions (display)

Two things change in the compiled CSS, and neither changes a computed value:

  • Specificity drops from (0,2,0) to (0,1,0). On every one of these elements the property being toggled is set otherwise only by (a) a plain base utility (opacity-0, max-w-0, pr-0, pointer-events-none, hidden) at (0,1,0), which the variant still beats because it comes later in the file (next point), or (b) group-hover:* / focus-visible:opacity-100, which set the same value. No non-utility rule in the built CSS sets these properties on these elements. I scanned every rule in the build output that sets opacity / display / pointer-events / max-width / padding with a selector that isn't a plain utility.
  • Order: as a custom variant, the 5 rules now come after all built-in variants (after dark:*) instead of before group-hover:*, so they still come after the base utilities they override.

I diffed npx vite build output before and after the change (written to /tmp, outside the repo). The only difference is these 5 rules, rewritten and moved. I then loaded both builds in Playwright (WebKit and Chromium) on the exact class lists above and compared the computed opacity, display, pointer-events, max-width, padding-right, background-color and color in idle, group focus-within, group hover, hover + focus-within, self-hover and keyboard-focus states. Both engines gave 24/24 identical states. (Playwright's WebKit is a trunk build, so I used it to check the styles are the same, not to measure the slowdown.)

UI

No visual change. The computed styles match in every state checked (see above).

Validation

  • npm run check:web: vitest 483 files / 5363 tests passed (13 skipped), tsc --noEmit clean. Rust untouched, so check:rust not run.
  • npx prettier --check src/styles/index.css passes.
  • Compiled-CSS diff and the computed-style comparison described above.

Checklist

  • I ran npm run check (check:web; no Rust files changed)
  • This PR is small and focused
  • I did not mix unrelated changes

Summary by CodeRabbit

  • New Features
    • Added support for group-focus-within styling, allowing elements within a focused group to change appearance when the group or one of its descendants receives focus. This makes it easier to provide visible focus feedback for grouped interface elements, such as forms and navigation controls.

Tailwind's group-focus-within compiles to :is(:where(.group):focus-within *),
which WebKit can't key its invalidation on, so every focus move re-checked
the whole document: about 45 ms each with a long transcript open. Keeping
the whole group inside :where(), the form the dark variant already uses,
keys the rule on .group. The rules match the same elements and compute the
same styles.
@coderabbitai

coderabbitai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 23346f5e-1eb0-44a7-9ad8-2568f9696a47
📥 Commits

Reviewing files that changed from the base of the PR and between e49a5ae and b3bf7b4.

📒 Files selected for processing: 1
  • src/styles/index.css

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 4 remain after this review.

📝 Walkthrough

The stylesheet adds a group-focus-within Tailwind custom variant. It matches descendants of a focused .group and wraps the group and descendant selectors in :where().

Changes: Group focus-within variant

Layer / File(s) Summary
Define the custom variant
src/styles/index.css
Adds the group-focus-within variant for descendants of a focused .group, with both selectors wrapped in :where().

Priority: ➖ Normal

Estimated code review effort: 1 (Trivial) | ~5 minutes

Change: Refactor

Suggested reviewers: hardbeat920

Merge Risk: ⚪ Minimal · up to b3bf7

No current usage shows a cascade regression from the selector change, and no other concrete merge risk is established. The change is ready for normal checks.

Pre-merge checks: Passed 5
Check name Status Explanation
Title check Passed The title clearly describes the main objective: preventing full-page style recalculation during focus moves on macOS. It is specific and related to the selector change.
Description check Passed The description includes all required sections, explains the change and motivation, documents UI impact, and reports validation results with the checklist completed.
Docstring Coverage Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
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.
Full details: Docstring Coverage

Explanation

No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0 files. (1 skipped: 1 unsupported.)

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@hardbeat920

Copy link
Copy Markdown
Owner

@zcy22606 looks good! thank you

@hardbeat920
hardbeat920 merged commit eda9cbc into hardbeat920:main Oct 18, 2026
13 checks passed
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