Skip to content

perf: lazy-load Motion and fix React Doctor findings (CHAT-46) - #91

Merged
rpvilo merged 1 commit into
mainfrom
perf/lazy-motion
Oct 14, 2026
Merged

rpvilo merged 1 commit into
mainfrom
perf/lazy-motion

Conversation

@rpvilo

@rpvilo rpvilo commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

Resolves CHAT-46.

Lazy-load Motion (app)

  • The app uses m.* from motion/react-m inside one strict LazyMotion provider (components/providers.tsx). It loads domMax on demand from lib/motion-features.ts; domMax rather than domAnimation, because attachment chips use layout.
  • strict makes a stray motion.* throw instead of quietly pulling the engine back into the first load.
  • MotionConfig reducedMotion="user" snaps the remaining height reveals under reduced motion.
  • Bundle: about 24 kB gzipped less first-load JS on the playground and docs, as measured when this change was first made. Not re-measured in this PR.

React Doctor fixes

Full scan (react-doctor . --no-score --no-supply-chain): 23 errors / 66 warnings → 18 / 57.

Fixed:

  • No ref writes during render (no-ref-current-in-render, 5 of 10):
    • AskUser.Options and AskUser.Option mirror their latest values in insertion effects. These run before the options' own layout effects, which seed the initial highlight; a layout-effect mirror would overwrite that seed with null.
    • Composer.Command's closing flag and the playground's ask-user panel cache are now state that changes only when they flip.
  • Memoized context values (6): AskUser ×3, the shared collapsible (Reasoning, Steps), the anchored Tabs surface, SidebarViews.
  • Playground and docs:
    • the seen-messages set is built once, not per stream chunk
    • the steps chevron transitions only opacity and rotate (checked: Tailwind v4's rotate-180 compiles to the rotate property)
    • the docs search input gets aria-label
  • Chat id: minted with a lazy useState initializer. useMemo may recompute, which would swap the chat out from under the user.

Left as is, triaged:

  • Errors (18):
    • height reveals (9): a transform can't animate the space an element takes
    • useRenderElement's conditional hooks (3): the same intentional pattern as Base UI
    • the pending-jump observer (1): it's disconnected on unmount
    • exit-animation content freezes (3) and run-once init guards (2)
  • Warnings (57):
    • ref dependencies flagged by exhaustive-deps (8)
    • append-only or derived index keys (14)
    • Fast Refresh export hygiene (15)
    • deliberately self-contained docs demos (5)
    • tiny-array lookups (3)
    • build-script awaits (2)
    • 6 single false positives
    • 4 complexity warnings, left for a later refactor

Testing

  • packages/chat: typecheck and 518 tests pass, including the ask-user a11y suite.
  • App typecheck and Biome lint pass.

Notes

  • Overlaps with feat(thread): time smooth scrolls from CSS (CHAT-44) #90 (CHAT-44) in components/chat.tsx, components/playground-settings.tsx and components/ai/thread.tsx. In chat.tsx both edit the same motion/react import line, so whichever merges second resolves a small, mechanical conflict.
  • Not included: the uncommitted React Doctor CI setup (the doctor script in package.json, .github/workflows/react-doctor.yml). It's still undecided; see CHAT-46.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved stability in chat controls, including user prompts, command lists, reasoning panels, steps, and tabs, particularly during updates and closing animations.
    • Chat and interface state updates more consistently, reducing unnecessary re-renders.
  • Accessibility
    • Documentation search now has a clear accessible name for assistive technologies.
    • Animations respect the reduced-motion preference configured on the user’s device.

@vercel

vercel Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
intentface-chat Ready Ready Preview Oct 14, 2026 6:03pm UTC

Request Review

@coderabbitai

coderabbitai Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: 990f1ee7-ad44-40e6-b2e8-5732205c426f
📥 Commits

Reviewing files that changed from the base of the PR and between 24d5eb2 and 79753c0.

📒 Files selected for processing: 1
  • packages/chat/src/tabs/anchored.tsx

Included review availability: This review used your included allowance. 2 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 5 reviews per hour.

📜 Recent review details
⏰ Context from checks skipped due to timeout: 4
  • GitHub Check: verify
  • GitHub Check: react-doctor
  • GitHub Check: Analyze (actions)
  • GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📚 Code guidelines: 1
.changeset/README.md — auto-discovered
📓 Path-based instructions: 1
Source excerpt: This folder is managed by [changesets](https://github.com/changesets/changesets).

📄 CodeRabbit inference engine (.changeset/README.md)

Files:

  • packages/chat/src/tabs/anchored.tsx
🔇 Additional comments: 1
packages/chat/src/tabs/anchored.tsx: 1

4-4: LGTM!

Also applies to: 88-91

📝 Walkthrough

The changes update state and context handling in chat components, move animated elements to motion/react-m, and configure dynamic Motion feature loading with reduced-motion support. The documentation search input also receives an accessible name.

Changes: Render and state behavior

Layer / File(s) Summary
Stabilize context values and AskUser refs
packages/chat/src/ask-user/ask-user.tsx, packages/chat/src/internal/collapsible.tsx, packages/chat/src/tabs/anchored.tsx, components/ui/sidebar.tsx, .changeset/react-doctor-fixes.md
AskUser, CollapsibleRoot, TabsPortal, and SidebarViews memoize context values. AskUser updates its highlight and registration refs in insertion effects. A patch changeset records the package changes.
Update chat and command state
app/(chat)/playground/page.tsx, components/chat.tsx, packages/chat/src/composer/command-list.tsx
The playground initializes its chat ID with lazy state. Chat stores initial message IDs and AskUser panel state in state. ComposerCommand tracks prior activity with state.

Motion feature loading and component migration

Layer / File(s) Summary
Configure Motion feature loading
components/providers.tsx, lib/motion-features.ts
Providers use strict LazyMotion loading and configure Motion to respect the user’s reduced-motion setting. The feature module exports domMax.
Migrate chat animations
components/ai/*, components/chat.tsx
Chat animated elements use m components in place of motion components. The timeline disclosure chevron now transitions only opacity and rotation.
Migrate shared UI animations
components/docs/component-preview-frame.tsx, components/docs/props-table.tsx, components/playground-settings.tsx, components/ui/progressive-blur.tsx, components/ui/sidebar.tsx, components/ui/text-loop.tsx
The listed documentation, playground, and UI components use m components. Their existing animation settings remain in place.
Name the documentation search input
components/docs/docs-search.tsx
The search input receives the accessible name “Search documentation.”

Priority: ⬇️ Low

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

Change: Bug fix

Suggested reviewers: jonijuup

Merge Risk: ⚪ Minimal · up to 79753

This change stabilizes the TabsPortal context value without an identified user-visible regression; no merge-blocking risk is evident.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 20 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly summarizes both primary changes: lazy-loading Motion and addressing React Doctor findings. It is concise, specific, and relevant to the changeset.
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.
  • Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

Motion's animation engine shipped in every route's first load. The app now
uses `m.*` from motion/react-m inside one strict LazyMotion provider that
loads domMax on demand from lib/motion-features.ts, cutting ~24 kB gzipped
from the playground and docs first-load JS. Strict mode makes a stray
`motion.*` throw instead of quietly pulling the engine back in, and
MotionConfig reducedMotion="user" snaps the remaining height reveals.

Fixes from a React Doctor review (23 errors / 66 warnings -> 18 / 57; the
rest are triaged as intentional or false positives):

- Refs are no longer written during render where React may discard it.
  AskUser mirrors its latest highlight and register callback in insertion
  effects, which run before the options' own layout effects that seed the
  highlight. Composer.Command's closing flag and the playground's ask-user
  panel cache are state that changes only when they flip.
- AskUser, the shared collapsible (Reasoning, Steps), the anchored Tabs
  surface and SidebarViews memoize their context values.
- The playground builds its seen-messages set once instead of per stream
  chunk, the steps chevron transitions only opacity and rotate, and the
  docs search input gets an accessible name.
- The playground mints its chat id with a lazy useState initializer:
  useMemo may recompute, which would swap the chat out from under the user.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

React Doctor found no new issues. 🎉

Reviewed by React Doctor for commit 79753c0.

@rpvilo
rpvilo merged commit f35826c into main Oct 14, 2026
9 checks passed
@rpvilo
rpvilo deleted the perf/lazy-motion branch October 14, 2026 18:21

This branch was successfully deployed

1 active deployment
Preview — 79753c04 Deployed Oct 14, 2026 by vercel[bot]
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