Skip to content

chore(studio): react-doctor cleanup (66 → 84/100)#146

Merged
iipanda merged 2 commits into
mainfrom
chore/react-doctor-cleanup-studio
May 16, 2026
Merged

chore(studio): react-doctor cleanup (66 → 84/100)#146
iipanda merged 2 commits into
mainfrom
chore/react-doctor-cleanup-studio

Conversation

@iipanda
Copy link
Copy Markdown
Collaborator

@iipanda iipanda commented May 16, 2026

Summary

  • Internal cleanup of @mdcms/studio against the react-doctor ruleset: score 66 → 84, 302 → 76 warnings, 1 → 0 errors.
  • No behaviour change on the published surface. One internal-only rename: renderReadyMdxPropsEditorReadyMdxPropsEditor (not re-exported from src/index.ts, only used inside the package and its tests).
  • New packages/studio/knip.json teaches knip about the two-tier runtime architecture (the runtime-ui/** tree is bundled at build-time by build-runtime.ts via dynamic string imports), dropping ~117 false-positive warnings.

Highlights

  • Workspace tsconfig.base.json bumped target/lib to ES2023 (enables Array#toSorted, etc.).
  • React 19 use() migration across context modules (session-context, mount-info-context, next-themes, toast, navigation, capabilities-context, mdx-component-collapse, assistant-context).
  • proposal-card.tsx: removed the prop-mirroring useState/useEffect pattern in 5 sub-cards; reject panel now opens via rejecting || openLocally.
  • settings-page.tsx API-keys table: formatClientDate helper + client-mounted ApiKeyStatusBadge to avoid SSR/CSR locale and clock hydration mismatches. api-key-create-dialog.tsx: min date attr set after mount.
  • Render-in-render extractions to real components: ContentCardGrid/ContentTypeCard, RetryButton, SchemaKindChip/SchemaConstraintFlags, ReadyMdxPropsEditor/AutoFormFieldControl, RouteContent, RowActions, TrashRowActions.
  • Login page: SSO state collapsed into a single discriminated-union state, removing the cascading two-setState effect.
  • studio-component.tsx + mdx-component-node-view.tsx: documented the two intentional dangerouslySetInnerHTML sites inline.
  • Mechanical sweeps: toSorted, flatMap over map().filter(), ES2023 length-check shape, hoisted EMPTY_BREADCRUMBS, SVG d decimal truncation, label htmlFor associations, redundant role="navigation" removed, Promise.all for independent awaits, gap-y over space-y on flex children, action-named button labels, stable list keys, autoFocus removed from non-critical surfaces, useEffectEvent on the save shortcut.

Documented skips

  • react-compiler-destructure-method (8): React Compiler not installed; rule is moot until adopted.
  • react/no-children-prop (4): all in test fixtures.
  • async-await-in-loop (9): all sequential by design (retry, deterministic hash, ordered file copy, pagination).
  • prefer-useReducer (4), no-giant-component (9), no-cascading-set-state (most): substantial design-level refactors. Better as a focused follow-up so individual components can be reviewed in isolation.
  • no-prevent-default, no-dynamic-import-path, nextjs-no-client-side-redirect, nextjs-no-native-script, async-defer-await, rendering-usetransition-loading, query-mutation-missing-invalidation, rerender-state-only-in-handlers: per-site false-positives; rationale in the plan file under .ai/plans/.

Test plan

  • bun run check (build + typecheck across 6 projects) — green locally.
  • bun test --cwd packages/studio — 603 pass, 1 pre-existing unrelated failure in untracked button.test.tsx (not part of this PR).
  • bun run ci:required to be run in CI.
  • Manual smoke (browser):
    • API keys table renders dates without hydration warnings in the console.
    • Proposal card accept / reject still works end-to-end (heavy edits to proposal-card.tsx).
    • Environments promote dialog opens and the include-unpublished switch toggles.
    • Content/[type] page mutations (publish / unpublish / duplicate / delete) still refresh the list immediately.
    • Tiptap toolbar buttons still trigger their actions (replaced the wrapping <div onClick> with onClick on ToolbarButton).

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Fixed SSR hydration mismatches for API key date rendering
    • Improved login state handling and removed unnecessary auto-focus behaviors
  • New Features

    • Enhanced accessibility with improved form control labeling and ARIA attributes
  • Refactor

    • Modernized to ES2023 JavaScript features for improved performance
    • Optimized build processes through concurrent operations
    • Updated UI components for consistency and visual refinement across all pages
  • Style

    • Standardized icon sizing utilities for visual consistency
    • Refined typography and spacing throughout the interface

Internal-only cleanup of `@mdcms/studio` against the react-doctor ruleset.
No published-surface contract change beyond an internal rename
(`renderReadyMdxPropsEditor` → `ReadyMdxPropsEditor`, not in `src/index.ts`).

- ES2023 lib bump so `Array#toSorted` compiles workspace-wide
- React 19 `use()` migration for context reads
- Hydration handling on the API-keys table (client-mounted badge + date helper)
- Render-in-render extractions (ContentCardGrid, RouteContent, RowActions, …)
- Mirror-prop bug fix in proposal-card.tsx (local-override OR rejecting)
- Knip config describing the runtime-ui build-time bundling (drops ~117 false-positive warnings)
- Mechanical sweeps: toSorted, flatMap-over-map+filter, length-check shape,
  module-level EMPTY_*, SVG d-attr decimal truncation, label htmlFor, …
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 16, 2026

Warning

Rate limit exceeded

@iipanda has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 27 minutes and 14 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9e76d947-a591-4088-b5b4-4958f57e6747

📥 Commits

Reviewing files that changed from the base of the PR and between 4052b90 and 94ce59f.

📒 Files selected for processing (4)
  • packages/studio/src/lib/runtime-ui/app/admin/settings-page.tsx
  • packages/studio/src/lib/runtime-ui/components/api-key-create-dialog.tsx
  • packages/studio/src/lib/runtime-ui/components/layout/page-header.tsx
  • packages/studio/src/lib/studio-component.tsx
📝 Walkthrough

Walkthrough

This PR refactors the @mdcms/studio package as an internal cleanup pass targeting React 19 adoption (use() hook migration), ES2023 features (Array.prototype.toSorted), component extraction from helper functions, UI consistency standardization, and build parallelization. Over 300+ ranges across 60+ files are consolidated into a single modernization theme with no breaking changes to exported APIs.

Changes

React 19 & ES2023 Modernization Cleanup

Layer / File(s) Summary
Configuration and Build Infrastructure
tsconfig.base.json, packages/studio/tsconfig.lib.json, packages/studio/knip.json
TypeScript targets upgraded from es2022 to es2023 enabling Array.prototype.toSorted() support. Knip configuration added to declare runtime bundling entry points so runtime-ui/** is not reported as unused.
React use() Hook Migration
packages/studio/src/lib/runtime-ui/**/{adapters,app,components,pages,navigation.tsx}
All context consumers across runtime-ui modules migrate from useContext() to React's use() hook: theme adapter, admin capabilities/session/mount-info, settings/settings status badge, assistant contexts, MDX component collapse, toast, and navigation hooks.
Array Processing Refactoring (toSorted & flatMap)
packages/studio/src/lib/{content-overview-state,document-route-schema,document-version-diff,mdx-component-extension,mdx-props-editor-host,runtime-registry}.ts, packages/studio/src/lib/runtime-ui/**
ES2023 Array.prototype.toSorted() replaces [...items].sort(...) for non-mutating sorts. flatMap() consolidates map().filter() patterns: resolveEnvironmentFieldTargets, serializeMdxJsxAttributes, normalizeStudioRoutePath, getScopeLabel, and locale/proposal filtering throughout pages and components.
Render Helper to Component Extraction
packages/studio/src/lib/{mdx-props-editor-host,remote-studio-app}.tsx, packages/studio/src/lib/runtime-ui/app/admin/{content/[type]/page,environments-page,trash-page}.tsx
Render-function helpers converted to React components: ReadyMdxPropsEditor, RouteContent, RowActions, TrashRowActions, RetryButton, ContentTypeCard, ContentCardGrid. Internal helpers like renderAutoFormFieldControl, renderRetryButton, renderRowActions refactored for composition.
Form State and Input Consolidation
packages/studio/src/lib/runtime-ui/app/admin/{login-page,api-key-create-dialog}.tsx, packages/studio/src/lib/runtime-ui/app/invite/invite-accept-page.tsx
Login SSO state refactored to single union (ssoState: { type: 'loading' | 'ready'; providers: ... }). Form date inputs use todayMinDate state initialized on mount. autoFocus attributes removed from email, label, and name inputs. Invite form updates use functional setInviteData(prev => ...) for email/role/pathPrefix.
UI Consistency and Icon Sizing Standardization
packages/studio/src/lib/runtime-ui/**
Icon sizing consolidated from h-/w- to size-* Tailwind utility across 100+ components and pages. Typography adjusted: font-bold→font-semibold on headers/titles. Padding normalized: px-3 py-3→p-3. Breadcrumb constants extracted (EMPTY_BREADCRUMBS). SVG logo path data updated.
Hydration and Server-Side Rendering Improvements
packages/studio/src/lib/runtime-ui/app/admin/settings-page.tsx
API key dates rendered via formatClientDate helper with suppressHydrationWarning. ApiKeyStatusBadge component uses local now timestamp initialized via useEffect. Date columns display "Never" for missing expirations and avoid direct toLocaleDateString().
Text Updates and Punctuation Normalization
packages/studio/src/lib/runtime-ui/**
Loading indicators changed from "Loading..." to "Loading…" (ellipsis). Error/status messages use colon delimiters instead of em dashes (e.g., "Proposal ready: ..." vs "Proposal ready — ..."). Copy strings refined for consistency.
Build and Test Parallelization
packages/studio/src/lib/{build-runtime.ts,build-runtime.test.ts,studio-loader.test.ts}
buildStudioRuntimeArtifacts now bundles and compiles runtime concurrently via Promise.all. Determinism and integrity-retry tests create active/fallback fixtures in parallel instead of sequentially.
Form Validation, State Management, and List Rendering
packages/studio/src/lib/runtime-ui/components/assistant/proposal-card.tsx, packages/studio/src/lib/runtime-ui/components/editor/inline-ai-panel.tsx
Validation error/check lists use stable React keys (label, code:message) instead of indices. Reject feedback state refactored to local openLocally flag with derived showReject = rejecting || openLocally. AppliedLogLine separators updated.
Schema Field Processing and Route Matching
packages/studio/src/lib/{document-route-schema,remote-studio-app}.ts, packages/studio/src/lib/runtime-ui/app/admin/schema-page.tsx
Schema constraints aggregated via flatMap. Route matching combined into single boolean expression inside routes.find. Document equality checks refactored to length+every pattern. MDX component picker padding standardized (p-2).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • mdcms-ai/mdcms#145: Modifies packages/studio/src/lib/runtime-ui/components/assistant/assistant-context.tsx request construction logic with chat message assembly refactors.
  • mdcms-ai/mdcms#134: Updates runtime build pipeline in packages/studio/src/lib/build-runtime.ts and determinism tests with bundling changes.
  • mdcms-ai/mdcms#100: Refactors auto-form rendering in packages/studio/src/lib/mdx-props-editor-host.tsx targeting the same host control logic.

Poem

🐰 The docs say "use" and "toSorted" now,
No spread-copy spreads for the modern pow.
Icons shrink to size-*, breadcrumbs collapse,
React nineteen's here—let's close the gaps! 🎉

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/react-doctor-cleanup-studio

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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 `@packages/studio/src/lib/runtime-ui/app/admin/settings-page.tsx`:
- Around line 39-45: The badge's "now" value is captured once at mount (useState
now + useEffect that calls setNow once), so ApiKeyStatusBadge's isExpired
(computed from now, expiresAt) never updates; change the useEffect in the
component (the code that declares now, setNow, setNow(Date.now()), and
isExpired) to start a timer (setInterval) that updates now periodically (e.g.,
every 1s or 1m depending on required granularity), compute isExpired from that
updated now, and clear the interval in the effect cleanup to avoid leaks.

In `@packages/studio/src/lib/runtime-ui/components/api-key-create-dialog.tsx`:
- Around line 110-116: The effect that computes todayMinDate runs only on mount
(useEffect with []); change it to recompute whenever the create dialog is opened
by adding the dialog open state (e.g., open or isOpen) to the effect dependency
array and only computing/setTodayMinDate when that state is true; update the
useEffect that sets todayMinDate (and any other place setting the min value,
e.g., where todayMinDate is used at lines ~254-255) so the min date is
recalculated each time the dialog opens instead of only once on mount.

In `@packages/studio/src/lib/runtime-ui/components/layout/page-header.tsx`:
- Line 113: Breadcrumb keys using "crumb.href ?? crumb.label" can collide when
multiple crumbs share the same label and no href; update the key to include a
collision-safe suffix (e.g., append the map index or a unique identifier) where
the crumbs are rendered in the PageHeader component (the crumbs.map callback
that produces elements using key={crumb.href ?? crumb.label}) so that keys
become unique like `${crumb.href ?? crumb.label}-${index}` or use a unique id
field on the crumb objects.

In `@packages/studio/src/lib/studio-component.tsx`:
- Around line 723-726: The comment for the SHELL_THEME_INLINE_SCRIPT constant is
outdated: it says the script reads theme preference from a cookie while the
implementation actually reads from window.localStorage; update the inline
comment near the SHELL_THEME_INLINE_SCRIPT declaration in studio-component.tsx
to state that it reads the dark/light preference from localStorage (not
cookies), that it applies the theme class before hydration to prevent FOUC, and
that the payload contains no user input and does not touch network data.
🪄 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 Plus

Run ID: e977daab-945f-4352-9c1c-d415e24f032f

📥 Commits

Reviewing files that changed from the base of the PR and between b53c355 and 4052b90.

📒 Files selected for processing (53)
  • .changeset/react-doctor-studio-cleanup.md
  • packages/studio/knip.json
  • packages/studio/src/lib/build-runtime.test.ts
  • packages/studio/src/lib/build-runtime.ts
  • packages/studio/src/lib/content-overview-state.ts
  • packages/studio/src/lib/document-route-schema.test.ts
  • packages/studio/src/lib/document-route-schema.ts
  • packages/studio/src/lib/document-version-diff.ts
  • packages/studio/src/lib/mdx-component-extension.ts
  • packages/studio/src/lib/mdx-props-editor-host.test.tsx
  • packages/studio/src/lib/mdx-props-editor-host.tsx
  • packages/studio/src/lib/remote-studio-app.tsx
  • packages/studio/src/lib/runtime-registry.ts
  • packages/studio/src/lib/runtime-ui/adapters/next-themes.tsx
  • packages/studio/src/lib/runtime-ui/app/admin/capabilities-context.tsx
  • packages/studio/src/lib/runtime-ui/app/admin/content/[type]/page.tsx
  • packages/studio/src/lib/runtime-ui/app/admin/environments-page.tsx
  • packages/studio/src/lib/runtime-ui/app/admin/layout.tsx
  • packages/studio/src/lib/runtime-ui/app/admin/login-page.tsx
  • packages/studio/src/lib/runtime-ui/app/admin/mount-info-context.tsx
  • packages/studio/src/lib/runtime-ui/app/admin/page.tsx
  • packages/studio/src/lib/runtime-ui/app/admin/schema-page.tsx
  • packages/studio/src/lib/runtime-ui/app/admin/session-context.tsx
  • packages/studio/src/lib/runtime-ui/app/admin/settings-page.tsx
  • packages/studio/src/lib/runtime-ui/app/admin/trash-page.tsx
  • packages/studio/src/lib/runtime-ui/app/admin/users-page.tsx
  • packages/studio/src/lib/runtime-ui/app/invite/invite-accept-page.tsx
  • packages/studio/src/lib/runtime-ui/components/api-key-create-dialog.tsx
  • packages/studio/src/lib/runtime-ui/components/assistant/assistant-context.tsx
  • packages/studio/src/lib/runtime-ui/components/assistant/assistant-panel.tsx
  • packages/studio/src/lib/runtime-ui/components/assistant/proposal-card.tsx
  • packages/studio/src/lib/runtime-ui/components/assistant/send-stop-button.tsx
  • packages/studio/src/lib/runtime-ui/components/coming-soon.tsx
  • packages/studio/src/lib/runtime-ui/components/create-document-dialog.tsx
  • packages/studio/src/lib/runtime-ui/components/editor/inline-ai-bubble.tsx
  • packages/studio/src/lib/runtime-ui/components/editor/inline-ai-panel.test.tsx
  • packages/studio/src/lib/runtime-ui/components/editor/inline-ai-panel.tsx
  • packages/studio/src/lib/runtime-ui/components/editor/mdx-component-collapse.tsx
  • packages/studio/src/lib/runtime-ui/components/editor/mdx-component-node-view.tsx
  • packages/studio/src/lib/runtime-ui/components/editor/mdx-component-picker.tsx
  • packages/studio/src/lib/runtime-ui/components/editor/tiptap-editor.tsx
  • packages/studio/src/lib/runtime-ui/components/layout/app-sidebar.tsx
  • packages/studio/src/lib/runtime-ui/components/layout/page-header.tsx
  • packages/studio/src/lib/runtime-ui/components/mdcms-logo.tsx
  • packages/studio/src/lib/runtime-ui/components/toast.tsx
  • packages/studio/src/lib/runtime-ui/components/ui/pagination.tsx
  • packages/studio/src/lib/runtime-ui/navigation.tsx
  • packages/studio/src/lib/runtime-ui/pages/content-document-page.tsx
  • packages/studio/src/lib/runtime-ui/pages/content-page.tsx
  • packages/studio/src/lib/studio-component.tsx
  • packages/studio/src/lib/studio-loader.test.ts
  • packages/studio/tsconfig.lib.json
  • tsconfig.base.json
💤 Files with no reviewable changes (3)
  • packages/studio/src/lib/runtime-ui/app/invite/invite-accept-page.tsx
  • packages/studio/src/lib/runtime-ui/components/create-document-dialog.tsx
  • packages/studio/src/lib/runtime-ui/components/ui/pagination.tsx

Comment thread packages/studio/src/lib/runtime-ui/app/admin/settings-page.tsx
Comment thread packages/studio/src/lib/runtime-ui/components/layout/page-header.tsx Outdated
Comment thread packages/studio/src/lib/studio-component.tsx
- ApiKeyStatusBadge refreshes `now` every 60s so a key crossing its
  expiry while the page is open flips Active→Expired without a reload.
- api-key-create-dialog recomputes `todayMinDate` on each dialog open so
  the date input's `min` doesn't get stuck on the first-mount day.
- BreadcrumbTrail key now includes the index suffix (`href ?? label`-`index`)
  to disambiguate breadcrumbs that share a label and have no href.
- SHELL_THEME_INLINE_SCRIPT JSX comment corrected: the script reads from
  `window.localStorage`, not a cookie.
@iipanda iipanda merged commit 30d496b into main May 16, 2026
6 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.

1 participant