Created
May 15, 2026 23:17
-
-
Save kulterryan/dcfe031a209cddbeec902f79f630ae68 to your computer and use it in GitHub Desktop.
React Doctor audit — repository-wide summary (issue #2568)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| react-doctor v0.1.6 | |
| ✔ Select projects to scan › certgen, dashboard, mailer, marketing, web, @10xlms/common, @10xlms/realtime, @10xlms/tiptap, @10xlms/ui, @10xlms/webplayer | |
| Scanning /Users/kulterryan/work/10xlms-platform/apps/certgen... | |
| - Detecting framework. Found Next.js. | |
| ✔ Detecting framework. Found Next.js. | |
| - Detecting React version. Found React 19.2.4. | |
| ✔ Detecting React version. Found React 19.2.4. | |
| - Detecting language. Found TypeScript. | |
| ✔ Detecting language. Found TypeScript. | |
| - Detecting React Compiler. Not found. | |
| ✔ Detecting React Compiler. Not found. | |
| - Found 10 source files. | |
| ✔ Found 10 source files. | |
| - Running lint checks... | |
| - Running lint checks. | |
| ✔ Running lint checks. | |
| - Detecting dead code... | |
| ✔ Detecting dead code. | |
| Architecture 3 issues | |
| ⚠ Inline exhaustive style ×3 | |
| 9 inline style properties — extract to a CSS class, CSS module, or styled component | |
| for maintainability and reuse | |
| Move styles to a CSS class, CSS module, Tailwind utilities, or a styled component — | |
| inline objects with many properties hurt readability and create new references | |
| every render | |
| src/lib/certificate-template.tsx:27 | |
| Dead Code 2 issues | |
| ⚠ Exports | |
| Unused export: redisCertgen | |
| ⚠ Types | |
| Unused type: PreviewParams | |
| Next.js 1 issue | |
| ⚠ No img element | |
| Use next/image instead of <img> — provides automatic optimization, lazy loading, | |
| and responsive srcset | |
| `import Image from 'next/image'` — provides automatic WebP/AVIF, lazy loading, and | |
| responsive srcset | |
| src/lib/certificate-template.tsx:76 | |
| ┌─────┐ 97 / 100 Great | |
| │ ◠ ◠ │ █████████████████████████████████████████████████░ | |
| │ ▽ │ React Doctor (www.react.doctor) | |
| └─────┘ | |
| 6 issues across 3/10 files in 305ms | |
| Full diagnostics written to /var/folders/d7/hlx5rgg91rl6s5rwbqm2s94w0000gn/T/react-doctor-7a8724a6-90f9-476f-b438-0faeed4e21bc | |
| → Share your results: https://www.react.doctor/share?p=certgen&s=97&w=6&f=3 | |
| Scanning /Users/kulterryan/work/10xlms-platform/apps/dashboard... | |
| - Detecting framework. Found Next.js. | |
| ✔ Detecting framework. Found Next.js. | |
| - Detecting React version. Found React 19.2.4. | |
| ✔ Detecting React version. Found React 19.2.4. | |
| - Detecting language. Found TypeScript. | |
| ✔ Detecting language. Found TypeScript. | |
| - Detecting React Compiler. Not found. | |
| ✔ Detecting React Compiler. Not found. | |
| - Found 868 source files. | |
| ✔ Found 868 source files. | |
| - Running lint checks... | |
| - Detecting dead code. | |
| ✔ Detecting dead code. | |
| - Running lint checks... | |
| ✔ Running lint checks. | |
| [react-doctor] Score API returned 413 Request Entity Too Large — using local scoring | |
| Server 393 issues | |
| ✗ Server auth actions ×156 | |
| Server action "createBulkPartnerCoupons" — add auth check (auth(), getSession(), | |
| etc.) at the top | |
| Add `const session = await auth()` at the top and throw/redirect if unauthorized | |
| before any data access | |
| src/actions/offers/createBulkPartnerCoupons.ts:23 | |
| ✗ Server no mutable module state ×2 | |
| Module-scoped const "ALLOWED_ROLES = new Set()" in a "use server" file — the | |
| container itself is shared across requests; move per-request data into the action | |
| body | |
| Move per-request data into the action body, headers/cookies, or a request-scope | |
| (React.cache, AsyncLocalStorage). Module-scope `let`/`var` is shared across | |
| requests. | |
| src/actions/live-session/exportAttendanceErrors.ts:19 | |
| ⚠ Server sequential independent await ×203 | |
| Sequential `await` without a data dependency on the previous result — wrap the | |
| independent calls in `Promise.all([...])` so they race instead of waterfalling | |
| Wrap independent awaits in `Promise.all([...])` so they race instead of | |
| waterfalling — second call doesn't depend on the first | |
| src/app/(dashboard)/lms/[id]/export-batch/page.tsx:24 | |
| Correctness 326 issues | |
| ✗ Nested component definition ×9 | |
| Component "SortableItem" defined inside "AddTrackForm" — creates new instance every | |
| render, destroying state | |
| Move to a separate file or to module scope above the parent component | |
| src/components/form/coursetrack/add-track-form.tsx:127 | |
| ✗ Rules of hooks ×8 | |
| React Hook "useState" is called in function "__FilterSelector" that is neither a | |
| React function component nor a custom React Hook function. React component names | |
| must start with an uppercase letter. React Hook names must start with the word | |
| "use". | |
| https://oxc.rs/docs/guide/usage/linter/rules/react/rules-of-hooks.html | |
| src/components/data-table-filter/components/filter-selector.tsx:57 | |
| ⚠ Rendering hydration mismatch time ×242 | |
| new Date() reachable from JSX renders differently on server vs client — wrap in | |
| useEffect+useState (client-only) or add suppressHydrationWarning to the parent if | |
| intentional | |
| Wrap dynamic time/random values in useEffect+useState (client-only) or add | |
| suppressHydrationWarning to the parent if intentional | |
| src/components/form/coursebatch/add-coursebatch-form.tsx:291 | |
| State & Effects 207 issues | |
| ✗ Effect needs cleanup ×3 | |
| useEffect subscribes via `watch(...)` but never returns a cleanup — leaks the | |
| registration on every re-run and on unmount. Return a cleanup function that calls | |
| the matching remove/unsubscribe call | |
| Return a cleanup function that releases the subscription / timer: `return () => | |
| target.removeEventListener(name, handler)` for listeners, `return () => | |
| clearInterval(id)` / `clearTimeout(id)` for timers, or `return unsubscribe` if the | |
| subscribe call already returned one | |
| src/components/notifications/wati/sendWatiForm.tsx:143 | |
| ⚠ UseReducer ×69 | |
| Component "ModernThemeForm" has 6 useState calls — consider useReducer for related | |
| state | |
| Group related state: `const [state, dispatch] = useReducer(reducer, { field1, | |
| field2, ... })` | |
| src/components/form/theme/modern-theme-form.tsx:87 | |
| ⚠ Cascading set state ×58 | |
| 6 setState calls in a single useEffect — consider using useReducer or deriving state | |
| Combine into useReducer: `const [state, dispatch] = useReducer(reducer, | |
| initialState)` | |
| src/components/form/theme/modern-theme-form.tsx:262 | |
| Accessibility 75 issues | |
| ✗ Role has required aria props ×16 | |
| `combobox` role is missing required aria props `aria-controls`. | |
| Add missing aria props `aria-controls` to the element with `combobox` role. | |
| https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/role-has-required-aria-props.html | |
| src/components/form/supports/create-support-ticket.tsx:312 | |
| ✗ Reduced motion | |
| Project uses a motion library but has no prefers-reduced-motion handling — required | |
| for accessibility (WCAG 2.3.3) | |
| Add `useReducedMotion()` from your animation library, or a `@media | |
| (prefers-reduced-motion: reduce)` CSS query | |
| ⚠ Click events have key events ×21 | |
| Enforce a clickable non-interactive element has at least one keyboard event | |
| listener. | |
| Visible, non-interactive elements with click handlers must have one of `keyup`, | |
| `keydown`, or `keypress` listener. | |
| https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/click-events-have-key-events.html | |
| src/components/showcase/showcase-media-carousel.tsx:144 | |
| Next.js 48 issues | |
| ✗ Async client component ×28 | |
| Async client component "DeleteCategory" — client components cannot be async | |
| Fetch data in a parent Server Component and pass it as props, or use | |
| useQuery/useSWR in the client component | |
| src/app/(dashboard)/lms/[id]/courses/column.tsx:78 | |
| ⚠ No img element ×8 | |
| Use next/image instead of <img> — provides automatic optimization, lazy loading, | |
| and responsive srcset | |
| `import Image from 'next/image'` — provides automatic WebP/AVIF, lazy loading, and | |
| responsive srcset | |
| src/components/showcase/showcase-media-carousel.tsx:117 | |
| ⚠ No redirect in try catch ×5 | |
| notFound() inside try-catch — this throws a special error Next.js handles | |
| internally. Move it outside the try block or use unstable_rethrow() in the catch | |
| Move the redirect/notFound call outside the try block, or add | |
| `unstable_rethrow(error)` in the catch | |
| src/app/(dashboard)/lms/[id]/offers/[offerId]/page.tsx:47 | |
| ⚠ 4564 more warnings | |
| Run `npx react-doctor@latest . --verbose` to get all details | |
| ┌─────┐ 39 / 100 Critical | |
| │ x x │ ████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ | |
| │ ▽ │ React Doctor (www.react.doctor) | |
| └─────┘ | |
| 5393 issues across 712/868 files in 1.3s | |
| Full diagnostics written to /var/folders/d7/hlx5rgg91rl6s5rwbqm2s94w0000gn/T/react-doctor-c929b874-7403-4dcb-8d2b-16548f7e2e53 | |
| → Share your results: https://www.react.doctor/share?p=dashboard&s=39&e=223&w=5170&f=712 | |
| Scanning /Users/kulterryan/work/10xlms-platform/apps/mailer... | |
| - Detecting framework. Found Next.js. | |
| ✔ Detecting framework. Found Next.js. | |
| - Detecting React version. Found React 19.2.4. | |
| ✔ Detecting React version. Found React 19.2.4. | |
| - Detecting language. Found TypeScript. | |
| ✔ Detecting language. Found TypeScript. | |
| - Detecting React Compiler. Not found. | |
| ✔ Detecting React Compiler. Not found. | |
| - Found 46 source files. | |
| ✔ Found 46 source files. | |
| - Running lint checks... | |
| - Running lint checks. | |
| ✔ Running lint checks. | |
| - Detecting dead code... | |
| ✔ Detecting dead code. | |
| Dead Code 4 issues | |
| ⚠ Files ×2 | |
| Unused file | |
| This file is not imported by any other file in the project. | |
| ⚠ Exports | |
| Unused export: BATCH_SUPPORTED_TEMPLATES | |
| ⚠ Types | |
| Unused type: BulkReceiver | |
| Architecture 1 issue | |
| ⚠ Design no em dash in JSX text | |
| Em dash (—) in JSX text reads as model output — replace with comma, colon, | |
| semicolon, or parentheses | |
| Replace em dashes in JSX text with commas, colons, semicolons, periods, or | |
| parentheses — em dashes read as model-output filler | |
| src/emails/LoginOTPEmail.tsx:43 | |
| Bundle Size 1 issue | |
| ⚠ Full lodash import | |
| Importing entire lodash library — import from 'lodash/functionName' instead | |
| Import the specific function: `import debounce from 'lodash/debounce'` — saves ~70kb | |
| src/utils/watiService/getTemplates.ts:3 | |
| Performance 1 issue | |
| ⚠ Async await in loop | |
| await inside a for-loop runs the calls sequentially — for independent operations, | |
| collect them and use `await Promise.all(items.map(...))` to run them concurrently | |
| Collect the items and use `await Promise.all(items.map(...))` to run independent | |
| operations concurrently | |
| src/utils/enqueueBatchEmailJob.ts:255 | |
| ┌─────┐ 96 / 100 Great | |
| │ ◠ ◠ │ ████████████████████████████████████████████████░░ | |
| │ ▽ │ React Doctor (www.react.doctor) | |
| └─────┘ | |
| 7 issues across 7/46 files in 290ms | |
| Full diagnostics written to /var/folders/d7/hlx5rgg91rl6s5rwbqm2s94w0000gn/T/react-doctor-957b6657-20d1-498d-adaa-f6069006fd75 | |
| → Share your results: https://www.react.doctor/share?p=mailer&s=96&w=7&f=7 | |
| Scanning /Users/kulterryan/work/10xlms-platform/apps/marketing... | |
| - Detecting framework. Found Next.js. | |
| ✔ Detecting framework. Found Next.js. | |
| - Detecting React version. Found React 19.2.4. | |
| ✔ Detecting React version. Found React 19.2.4. | |
| - Detecting language. Found TypeScript. | |
| ✔ Detecting language. Found TypeScript. | |
| - Detecting React Compiler. Not found. | |
| ✔ Detecting React Compiler. Not found. | |
| - Found 18 source files. | |
| ✔ Found 18 source files. | |
| - Running lint checks... | |
| - Detecting dead code. | |
| ✔ Detecting dead code. | |
| - Running lint checks... | |
| ✔ Running lint checks. | |
| Architecture 16 issues | |
| ⚠ Design no default tailwind palette ×5 | |
| text-indigo-600 reads as the Tailwind template default — use your project's brand | |
| color or zinc/neutral/stone | |
| Replace `indigo-*` / `gray-*` / `slate-*` with project tokens, your brand color, or | |
| a less-default neutral (`zinc`, `neutral`, `stone`) | |
| src/components/sections/bentogrid.tsx:75 | |
| ⚠ Design no space on flex children ×5 | |
| space-y-12 on a flex/grid parent — use gap-y-12 instead. Per-sibling margins | |
| phantom-gap on conditional render and don't mirror in RTL | |
| Use `gap-*` on the flex/grid parent. `space-x-*` / `space-y-*` produce phantom gaps | |
| when a sibling is conditionally rendered, lose vertical spacing on wrapped lines, | |
| and don't mirror in RTL | |
| src/components/sections/bentogrid.tsx:208 | |
| ⚠ Design no em dash in JSX text ×2 | |
| Em dash (—) in JSX text reads as model output — replace with comma, colon, | |
| semicolon, or parentheses | |
| Replace em dashes in JSX text with commas, colons, semicolons, periods, or | |
| parentheses — em dashes read as model-output filler | |
| src/components/sections/bentogrid.tsx:230 | |
| Performance 11 issues | |
| ⚠ Rendering SVG precision ×9 | |
| SVG d attribute uses 4+ decimal precision — truncate to 1–2 decimals to shrink | |
| markup with no visible difference | |
| Truncate path/points/transform decimals to 1–2 digits — sub-pixel precision adds | |
| bytes with no visible difference | |
| src/components/sections/bentogrid.tsx:22 | |
| ⚠ Advanced event handler refs | |
| useEffect re-subscribes a "onScroll" listener every time the handler identity | |
| changes — store the handler in a ref and have the listener read | |
| `handlerRef.current()`, then drop it from the deps | |
| Store the handler in a ref and have the listener read `handlerRef.current()` — the | |
| subscription stays put while the latest handler is always called | |
| src/components/hooks/use-scroll.ts:10 | |
| ⚠ Client passive event listeners | |
| "scroll" listener without { passive: true } — blocks scrolling performance. Only | |
| add { passive: true } if the handler does NOT call event.preventDefault() (passive | |
| listeners silently ignore preventDefault()) | |
| Add `{ passive: true }` as the third argument: `addEventListener('scroll', handler, | |
| { passive: true })`. Only do this if the handler does NOT call | |
| `event.preventDefault()` — passive listeners silently ignore `preventDefault()`, | |
| which breaks features like pull-to-refresh suppression, custom gestures, and | |
| nested-scroll containment. | |
| src/components/hooks/use-scroll.ts:11 | |
| Correctness 2 issues | |
| ⚠ Rendering hydration mismatch time | |
| new Date() reachable from JSX renders differently on server vs client — wrap in | |
| useEffect+useState (client-only) or add suppressHydrationWarning to the parent if | |
| intentional | |
| Wrap dynamic time/random values in useEffect+useState (client-only) or add | |
| suppressHydrationWarning to the parent if intentional | |
| src/components/layout/site-footer.tsx:12 | |
| ⚠ Array index as key | |
| Array index "index" used as key — causes bugs when list is reordered or filtered | |
| Use a stable unique identifier: `key={item.id}` or `key={item.slug}` — index keys | |
| break on reorder/filter | |
| src/components/sections/info-landing.tsx:33 | |
| Dead Code 1 issue | |
| ⚠ Files | |
| Unused file | |
| This file is not imported by any other file in the project. | |
| ⚠ 4 more warnings | |
| Run `npx react-doctor@latest . --verbose` to get all details | |
| ┌─────┐ 90 / 100 Great | |
| │ ◠ ◠ │ █████████████████████████████████████████████░░░░░ | |
| │ ▽ │ React Doctor (www.react.doctor) | |
| └─────┘ | |
| 30 issues across 7/18 files in 240ms | |
| Full diagnostics written to /var/folders/d7/hlx5rgg91rl6s5rwbqm2s94w0000gn/T/react-doctor-f092a95d-2b09-43a8-b386-c7aaa8c7fa1e | |
| → Share your results: https://www.react.doctor/share?p=marketing&s=90&w=30&f=7 | |
| Scanning /Users/kulterryan/work/10xlms-platform/apps/web... | |
| - Detecting framework. Found Next.js. | |
| ✔ Detecting framework. Found Next.js. | |
| - Detecting React version. Found React 19.2.4. | |
| ✔ Detecting React version. Found React 19.2.4. | |
| - Detecting language. Found TypeScript. | |
| ✔ Detecting language. Found TypeScript. | |
| - Detecting React Compiler. Not found. | |
| ✔ Detecting React Compiler. Not found. | |
| - Found 478 source files. | |
| ✔ Found 478 source files. | |
| - Running lint checks... | |
| - Detecting dead code. | |
| ✔ Detecting dead code. | |
| - Running lint checks... | |
| ✔ Running lint checks. | |
| Correctness 161 issues | |
| ✗ Rules of hooks ×48 | |
| React Hook "useMemo" is called conditionally. React Hooks must be called in the | |
| exact same order in every component render. | |
| Move the Hook call before the condition, or call it unconditionally and branch | |
| inside the Hook/effect instead. | |
| https://oxc.rs/docs/guide/usage/linter/rules/react/rules-of-hooks.html | |
| components/breadcrumb/AutoBreadcrumbs.tsx:30 | |
| ⚠ Rendering hydration mismatch time ×52 | |
| new Date() reachable from JSX renders differently on server vs client — wrap in | |
| useEffect+useState (client-only) or add suppressHydrationWarning to the parent if | |
| intentional | |
| Wrap dynamic time/random values in useEffect+useState (client-only) or add | |
| suppressHydrationWarning to the parent if intentional | |
| components/frontend/showcasePost/CommentItem.tsx:237 | |
| ⚠ Array index as key ×40 | |
| Array index "i" used as key — causes bugs when list is reordered or filtered | |
| Use a stable unique identifier: `key={item.id}` or `key={item.slug}` — index keys | |
| break on reorder/filter | |
| components/frontend/liveSession/liveSessionSection.tsx:333 | |
| Server 119 issues | |
| ✗ Server auth actions ×69 | |
| Server action "updateAttempt" — add auth check (auth(), getSession(), etc.) at the | |
| top | |
| Add `const session = await auth()` at the top and throw/redirect if unauthorized | |
| before any data access | |
| actions/frontend/quiz/updateAttempt.ts:8 | |
| ✗ Server no mutable module state ×3 | |
| Module-scoped const "AVAILABLE_COLORS = []" in a "use server" file — the container | |
| itself is shared across requests; move per-request data into the action body | |
| Move per-request data into the action body, headers/cookies, or a request-scope | |
| (React.cache, AsyncLocalStorage). Module-scope `let`/`var` is shared across | |
| requests. | |
| actions/frontend/calendar/getCalendarEvents.ts:32 | |
| ⚠ Server sequential independent await ×32 | |
| Sequential `await` without a data dependency on the previous result — wrap the | |
| independent calls in `Promise.all([...])` so they race instead of waterfalling | |
| Wrap independent awaits in `Promise.all([...])` so they race instead of | |
| waterfalling — second call doesn't depend on the first | |
| app/[domain]/(loggedin)/assignment/[assignmentid]/page.tsx:59 | |
| State & Effects 95 issues | |
| ✗ Effect needs cleanup | |
| useEffect schedules `setTimeout(...)` but never returns a cleanup — leaks the | |
| registration on every re-run and on unmount. Return a cleanup function that calls | |
| clearTimeout(...) | |
| Return a cleanup function that releases the subscription / timer: `return () => | |
| target.removeEventListener(name, handler)` for listeners, `return () => | |
| clearInterval(id)` / `clearTimeout(id)` for timers, or `return unsubscribe` if the | |
| subscribe call already returned one | |
| components/frontend/chat/chat-component.tsx:533 | |
| ⚠ UseReducer ×28 | |
| Component "CoursesProvider" has 11 useState calls — consider useReducer for related | |
| state | |
| Group related state: `const [state, dispatch] = useReducer(reducer, { field1, | |
| field2, ... })` | |
| components/frontend/course/CoursesContext.tsx:54 | |
| ⚠ Cascading set state ×27 | |
| 6 setState calls in a single useEffect — consider using useReducer or deriving state | |
| Combine into useReducer: `const [state, dispatch] = useReducer(reducer, | |
| initialState)` | |
| hooks/agents-ui/use-agent-audio-visualizer-bar.ts:29 | |
| Security 1 issue | |
| ✗ No side effect in get handler | |
| GET handler on "/logout" route — use POST to prevent CSRF and unintended prefetch | |
| triggers | |
| Move the side effect to a POST handler and use a <form> or fetch with method POST — | |
| GET requests can be triggered by prefetching and are vulnerable to CSRF | |
| app/api/logout/route.ts:4 | |
| Architecture 1430 issues | |
| ⚠ Design no default tailwind palette ×624 | |
| border-gray-200 reads as the Tailwind template default — use zinc (true neutral), | |
| neutral (warmer), or stone (warmest) | |
| Replace `indigo-*` / `gray-*` / `slate-*` with project tokens, your brand color, or | |
| a less-default neutral (`zinc`, `neutral`, `stone`) | |
| components/frontend/knowledgebase/kb-navigation.tsx:22 | |
| ⚠ Design no redundant size axes ×471 | |
| w-5 h-5 → use the shorthand size-5 (Tailwind v3.4+) | |
| Collapse `w-N h-N` to `size-N` (Tailwind v3.4+) when both axes match | |
| components/frontend/knowledgebase/kb-navigation.tsx:25 | |
| ⚠ React compiler destructure method ×87 | |
| Destructure for clarity: `const { push } = useRouter()` then call `push(...)` | |
| directly — easier for React Compiler to memoize and clearer about which methods | |
| this component depends on | |
| Destructure the method up front: `const { push } = useRouter()` then call | |
| `push(...)` directly — clearer dependency graph and easier for React Compiler to | |
| memoize | |
| components/frontend/liveSession/newSessionCard.tsx:90 | |
| ⚠ 1084 more warnings | |
| Run `npx react-doctor@latest . --verbose` to get all details | |
| ┌─────┐ 36 / 100 Critical | |
| │ x x │ ██████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ | |
| │ ▽ │ React Doctor (www.react.doctor) | |
| └─────┘ | |
| 2567 issues across 355/478 files in 876ms | |
| Full diagnostics written to /var/folders/d7/hlx5rgg91rl6s5rwbqm2s94w0000gn/T/react-doctor-14689d95-16c7-47a5-bd23-d01db6905519 | |
| → Share your results: https://www.react.doctor/share?p=web&s=36&e=122&w=2445&f=355 | |
| Scanning /Users/kulterryan/work/10xlms-platform/packages/common... | |
| - Detecting framework. Found Next.js. | |
| ✔ Detecting framework. Found Next.js. | |
| - Detecting React version. Found React 19.2.4. | |
| ✔ Detecting React version. Found React 19.2.4. | |
| - Detecting language. Found TypeScript. | |
| ✔ Detecting language. Found TypeScript. | |
| - Detecting React Compiler. Not found. | |
| ✔ Detecting React Compiler. Not found. | |
| - Found 84 source files. | |
| ✔ Found 84 source files. | |
| - Running lint checks... | |
| - Running lint checks. | |
| ✔ Running lint checks. | |
| - Detecting dead code... | |
| ✔ Detecting dead code. | |
| Accessibility 1 issue | |
| ✗ Reduced motion | |
| Project uses a motion library but has no prefers-reduced-motion handling — required | |
| for accessibility (WCAG 2.3.3) | |
| Add `useReducedMotion()` from your animation library, or a `@media | |
| (prefers-reduced-motion: reduce)` CSS query | |
| Architecture 17 issues | |
| ⚠ Design no redundant size axes ×14 | |
| w-4 h-4 → use the shorthand size-4 (Tailwind v3.4+) | |
| Collapse `w-N h-N` to `size-N` (Tailwind v3.4+) when both axes match | |
| src/components/calendar/date-navigation.tsx:113 | |
| ⚠ Design no three period ellipsis | |
| Three-period ellipsis ("...") in JSX text — use the actual ellipsis character "…" | |
| (or `…`) | |
| Use the typographic ellipsis "…" (or `…`) instead of three periods — pairs | |
| with action-with-followup labels ("Rename…", "Loading…") | |
| src/components/calendar/dialogs/event-list-dialog.tsx:42 | |
| ⚠ React19 deprecated apis | |
| useContext is superseded by `use()` on React 19+ — `use()` reads context | |
| conditionally inside hooks, branches, and loops; switch to `import { use } from | |
| 'react'` | |
| Pass `ref` as a regular prop on function components — `forwardRef` is no longer | |
| needed in React 19+. Replace `useContext(X)` with `use(X)` for branch-aware context | |
| reads. Only enabled on projects detected as React 19+. | |
| src/components/calendar/calendar-provider.tsx:4 | |
| Performance 9 issues | |
| ⚠ Async await in loop ×2 | |
| await inside a for…of loop runs the calls sequentially — for independent | |
| operations, collect them and use `await Promise.all(items.map(...))` to run them | |
| concurrently | |
| Collect the items and use `await Promise.all(items.map(...))` to run independent | |
| operations concurrently | |
| src/lib/batch-promise.ts:34 | |
| ⚠ Advanced event handler refs | |
| useEffect re-subscribes a "updateNetworkStatus" listener every time the handler | |
| identity changes — store the handler in a ref and have the listener read | |
| `handlerRef.current()`, then drop it from the deps | |
| Store the handler in a ref and have the listener read `handlerRef.current()` — the | |
| subscription stays put while the latest handler is always called | |
| src/hooks/use-network-status.ts:18 | |
| ⚠ Js combine iterations | |
| .filter().map() iterates the array twice — combine into a single loop with | |
| .reduce() or for...of | |
| Combine `.map().filter()` (or similar chains) into a single pass with `.reduce()` | |
| or a `for...of` loop to avoid iterating the array twice | |
| src/lib/calendar/helper.ts:317 | |
| Bundle Size 7 issues | |
| ⚠ Lazy motion ×7 | |
| Import "m" with LazyMotion instead of "motion" — saves ~30kb in bundle size | |
| Use `import { LazyMotion, m } from "framer-motion"` with `domAnimation` features — | |
| saves ~30kb | |
| src/components/calendar/date-navigation.tsx:6 | |
| State & Effects 6 issues | |
| ⚠ Cascading set state ×2 | |
| 3 setState calls in a single useEffect — consider using useReducer or deriving state | |
| Combine into useReducer: `const [state, dispatch] = useReducer(reducer, | |
| initialState)` | |
| src/hooks/useVerified.hooks.ts:29 | |
| ⚠ Derived useState ×2 | |
| useState initialized from prop "events" — if this value should stay in sync with | |
| the prop, derive it during render instead | |
| Remove useState and compute the value inline: `const value = transform(propName)` | |
| src/components/calendar/calendar-provider.tsx:96 | |
| ⚠ UseReducer | |
| Component "CalendarProvider" has 8 useState calls — consider useReducer for related | |
| state | |
| Group related state: `const [state, dispatch] = useReducer(reducer, { field1, | |
| field2, ... })` | |
| src/components/calendar/calendar-provider.tsx:60 | |
| ⚠ 10 more warnings | |
| Run `npx react-doctor@latest . --verbose` to get all details | |
| ┌─────┐ 84 / 100 Great | |
| │ ◠ ◠ │ ██████████████████████████████████████████░░░░░░░░ | |
| │ ▽ │ React Doctor (www.react.doctor) | |
| └─────┘ | |
| 43 issues across 19/84 files in 1.0s | |
| Full diagnostics written to /var/folders/d7/hlx5rgg91rl6s5rwbqm2s94w0000gn/T/react-doctor-fbd76ce7-3a81-40e1-8e75-0fbbd9f526da | |
| → Share your results: https://www.react.doctor/share?p=%4010xlms%2Fcommon&s=84&e=1&w=42&f=19 | |
| Scanning /Users/kulterryan/work/10xlms-platform/packages/realtime... | |
| - Detecting framework. Found Next.js. | |
| ✔ Detecting framework. Found Next.js. | |
| - Detecting React version. Found React 19.2.4. | |
| ✔ Detecting React version. Found React 19.2.4. | |
| - Detecting language. Found TypeScript. | |
| ✔ Detecting language. Found TypeScript. | |
| - Detecting React Compiler. Not found. | |
| ✔ Detecting React Compiler. Not found. | |
| - Found 36 source files. | |
| ✔ Found 36 source files. | |
| - Running lint checks... | |
| - Running lint checks. | |
| ✔ Running lint checks. | |
| - Detecting dead code... | |
| ✔ Detecting dead code. | |
| Server 17 issues | |
| ✗ Server auth actions ×16 | |
| Server action "createMeeting" — add auth check (auth(), getSession(), etc.) at the | |
| top | |
| Add `const session = await auth()` at the top and throw/redirect if unauthorized | |
| before any data access | |
| actions/meeting-actions.ts:103 | |
| ⚠ Server after nonblocking | |
| console.warn() in server action — wrap in `after(() => console.warn(...))` so it | |
| doesn't delay the user-visible response | |
| `import { after } from 'next/server'` then wrap: `after(() => | |
| analytics.track(...))` — response isn't blocked | |
| actions/meeting-actions.ts:453 | |
| Performance 43 issues | |
| ⚠ Js batch dom CSS ×16 | |
| Multiple sequential element.style assignments — batch with cssText or classList for | |
| fewer reflows | |
| Batch DOM/CSS reads and writes — interleaving them inside a loop causes layout | |
| thrashing. Read first, then write | |
| MeetingDocumentPip.tsx:579 | |
| ⚠ Js flatmap filter ×7 | |
| .map().filter(Boolean) iterates twice — use .flatMap() to transform and filter in a | |
| single pass | |
| Use `.flatMap(item => condition ? [value] : [])` — transforms and filters in a | |
| single pass instead of creating an intermediate array | |
| messaging/PollsProvider.tsx:337 | |
| ⚠ Js set map lookups ×4 | |
| array.includes() in a loop is O(n) per call — convert to a Set for O(1) lookups | |
| Use a `Set` or `Map` for repeated membership tests / keyed lookups — | |
| `Array.includes`/`find` is O(n) per call | |
| meetingDomChrome.ts:205 | |
| State & Effects 26 issues | |
| ⚠ Cascading set state ×12 | |
| 3 setState calls in a single useEffect — consider using useReducer or deriving state | |
| Combine into useReducer: `const [state, dispatch] = useReducer(reducer, | |
| initialState)` | |
| messaging/NotificationToast.tsx:66 | |
| ⚠ UseReducer ×6 | |
| Component "PollsProvider" has 5 useState calls — consider useReducer for related | |
| state | |
| Group related state: `const [state, dispatch] = useReducer(reducer, { field1, | |
| field2, ... })` | |
| messaging/PollsProvider.tsx:127 | |
| ⚠ Use effect event ×5 | |
| "onNewPoll" is read only inside `on` — wrap it with useEffectEvent and remove it | |
| from the dep array so the effect doesn't re-synchronize on every parent render | |
| Wrap the callback with `useEffectEvent(callback)` (React 19+) and call the | |
| resulting binding from inside the sub-handler. The Effect Event captures the latest | |
| props/state without being a reactive dep, so the effect doesn't re-subscribe on | |
| every parent render. See https://react.dev/reference/react/useEffectEvent | |
| messaging/PollsProvider.tsx:204 | |
| Architecture 24 issues | |
| ⚠ React compiler destructure method ×9 | |
| Destructure for clarity: `const { get } = useSearchParams()` then call `get(...)` | |
| directly — easier for React Compiler to memoize and clearer about which methods | |
| this component depends on | |
| Destructure the method up front: `const { push } = useRouter()` then call | |
| `push(...)` directly — clearer dependency graph and easier for React Compiler to | |
| memoize | |
| MeetingClient.tsx:148 | |
| ⚠ Giant component ×8 | |
| Component "PollsProvider" is 311 lines — consider breaking it into smaller focused | |
| components | |
| Extract logical sections into focused components: `<UserHeader />`, `<UserActions | |
| />`, etc. | |
| messaging/PollsProvider.tsx:112 | |
| ⚠ React19 deprecated apis ×2 | |
| useContext is superseded by `use()` on React 19+ — `use()` reads context | |
| conditionally inside hooks, branches, and loops; switch to `import { use } from | |
| 'react'` | |
| Pass `ref` as a regular prop on function components — `forwardRef` is no longer | |
| needed in React 19+. Replace `useContext(X)` with `use(X)` for branch-aware context | |
| reads. Only enabled on projects detected as React 19+. | |
| messaging/PollsProvider.tsx:9 | |
| Dead Code 16 issues | |
| ⚠ Types ×12 | |
| Unused type: TenxHandRaisedListOptions | |
| ⚠ Exports ×4 | |
| Unused export: initTenxHandRaisedListStage | |
| ⚠ 35 more warnings | |
| Run `npx react-doctor@latest . --verbose` to get all details | |
| ┌─────┐ 75 / 100 Great | |
| │ ◠ ◠ │ ██████████████████████████████████████░░░░░░░░░░░░ | |
| │ ▽ │ React Doctor (www.react.doctor) | |
| └─────┘ | |
| 137 issues across 15/36 files in 728ms | |
| Full diagnostics written to /var/folders/d7/hlx5rgg91rl6s5rwbqm2s94w0000gn/T/react-doctor-36a281a9-9beb-4aac-b5db-80d3a211f877 | |
| → Share your results: https://www.react.doctor/share?p=%4010xlms%2Frealtime&s=75&e=16&w=121&f=15 | |
| Scanning /Users/kulterryan/work/10xlms-platform/packages/tiptap... | |
| - Detecting framework. Found Next.js. | |
| ✔ Detecting framework. Found Next.js. | |
| - Detecting React version. Found React ^18.3.1 || ^19.0.0. | |
| ✔ Detecting React version. Found React ^18.3.1 || ^19.0.0. | |
| - Detecting language. Found TypeScript. | |
| ✔ Detecting language. Found TypeScript. | |
| - Detecting React Compiler. Not found. | |
| ✔ Detecting React Compiler. Not found. | |
| - Found 35 source files. | |
| ✔ Found 35 source files. | |
| - Running lint checks... | |
| - Running lint checks. | |
| ✔ Running lint checks. | |
| - Detecting dead code... | |
| ✔ Detecting dead code. | |
| Architecture 87 issues | |
| ⚠ Design no redundant size axes ×55 | |
| w-4 h-4 → use the shorthand size-4 (Tailwind v3.4+) | |
| Collapse `w-N h-N` to `size-N` (Tailwind v3.4+) when both axes match | |
| src/toolbars/blockquote.tsx:37 | |
| ⚠ React19 deprecated apis ×16 | |
| forwardRef is no longer needed on React 19+ — refs are regular props on function | |
| components; remove forwardRef and pass ref directly | |
| Pass `ref` as a regular prop on function components — `forwardRef` is no longer | |
| needed in React 19+. Replace `useContext(X)` with `use(X)` for branch-aware context | |
| reads. Only enabled on projects detected as React 19+. | |
| src/toolbars/blockquote.tsx:14 | |
| ⚠ Design no default tailwind palette ×11 | |
| border-gray-200 reads as the Tailwind template default — use zinc (true neutral), | |
| neutral (warmer), or stone (warmest) | |
| Replace `indigo-*` / `gray-*` / `slate-*` with project tokens, your brand color, or | |
| a less-default neutral (`zinc`, `neutral`, `stone`) | |
| src/toolbars/table.tsx:53 | |
| Dead Code 13 issues | |
| ⚠ Files ×10 | |
| Unused file | |
| This file is not imported by any other file in the project. | |
| ⚠ Exports ×3 | |
| Unused export: ImageExtensionBase | |
| State & Effects 12 issues | |
| ⚠ UseReducer ×3 | |
| Component "TiptapImage" has 5 useState calls — consider useReducer for related state | |
| Group related state: `const [state, dispatch] = useReducer(reducer, { field1, | |
| field2, ... })` | |
| src/extensions/image.tsx:71 | |
| ⚠ Derived state effect ×3 | |
| Derived state in useEffect — wrap the calculation in useMemo([deps]) (or compute it | |
| directly during render if it isn't expensive) | |
| For derived state, compute inline: `const x = fn(dep)`. For state resets on prop | |
| change, use a key prop: `<Component key={prop} />`. See | |
| https://react.dev/learn/you-might-not-need-an-effect | |
| src/toolbars/link.tsx:43 | |
| ⚠ Effect chain ×3 | |
| useEffect reacts to "searchText" which is set by another useEffect — chains of | |
| effects add an extra render per link and become rigid as code evolves. Compute what | |
| you can during render and write all related state inside the event handler that | |
| originally fires the chain | |
| Compute as much as possible during render (e.g. `const isGameOver = round > 5`) and | |
| write all related state inside the event handler that originally fires the chain. | |
| Each effect link adds an extra render and makes the code rigid as requirements | |
| evolve | |
| src/toolbars/search-and-replace-toolbar.tsx:43 | |
| Performance 10 issues | |
| ⚠ Rerender state only in handlers ×5 | |
| useState "resizingPosition" is updated but never read in the component's return — | |
| use useRef so updates don't trigger re-renders | |
| Replace useState with useRef when the value is only mutated and never read in | |
| render — `ref.current = ...` updates without re-rendering the component | |
| src/extensions/image.tsx:76 | |
| ⚠ Client passive event listeners ×2 | |
| "touchmove" listener without { passive: true } — blocks scrolling performance. Only | |
| add { passive: true } if the handler does NOT call event.preventDefault() (passive | |
| listeners silently ignore preventDefault()) | |
| Add `{ passive: true }` as the third argument: `addEventListener('scroll', handler, | |
| { passive: true })`. Only do this if the handler does NOT call | |
| `event.preventDefault()` — passive listeners silently ignore `preventDefault()`, | |
| which breaks features like pull-to-refresh suppression, custom gestures, and | |
| nested-scroll containment. | |
| src/extensions/image.tsx:179 | |
| ⚠ Js combine iterations ×2 | |
| .map().map() iterates the array twice — combine into a single loop with .reduce() | |
| or for...of | |
| Combine `.map().filter()` (or similar chains) into a single pass with `.reduce()` | |
| or a `for...of` loop to avoid iterating the array twice | |
| src/lib/tiptap-utils.ts:76 | |
| Accessibility 4 issues | |
| ⚠ Click events have key events ×2 | |
| Enforce a clickable non-interactive element has at least one keyboard event | |
| listener. | |
| Visible, non-interactive elements with click handlers must have one of `keyup`, | |
| `keydown`, or `keypress` listener. | |
| https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/click-events-have-key-events.html | |
| src/extensions/image-placeholder.tsx:443 | |
| ⚠ Static element interactions ×2 | |
| Static HTML elements with event handlers require a role. | |
| Add a role attribute to this element, or use a semantic HTML element instead. | |
| https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/no-static-element-interactions.html | |
| src/extensions/image-placeholder.tsx:443 | |
| ⚠ 13 more warnings | |
| Run `npx react-doctor@latest . --verbose` to get all details | |
| ┌─────┐ 83 / 100 Great | |
| │ ◠ ◠ │ ██████████████████████████████████████████░░░░░░░░ | |
| │ ▽ │ React Doctor (www.react.doctor) | |
| └─────┘ | |
| 130 issues across 38/35 files in 606ms | |
| Full diagnostics written to /var/folders/d7/hlx5rgg91rl6s5rwbqm2s94w0000gn/T/react-doctor-2053715f-b892-4f40-8c10-15357977453f | |
| → Share your results: https://www.react.doctor/share?p=%4010xlms%2Ftiptap&s=83&w=130&f=38 | |
| Scanning /Users/kulterryan/work/10xlms-platform/packages/ui... | |
| - Detecting framework. Found Next.js. | |
| ✔ Detecting framework. Found Next.js. | |
| - Detecting React version. Found React 19.2.4. | |
| ✔ Detecting React version. Found React 19.2.4. | |
| - Detecting language. Found TypeScript. | |
| ✔ Detecting language. Found TypeScript. | |
| - Detecting React Compiler. Not found. | |
| ✔ Detecting React Compiler. Not found. | |
| - Found 78 source files. | |
| ✔ Found 78 source files. | |
| - Running lint checks... | |
| - Running lint checks. | |
| ✔ Running lint checks. | |
| - Detecting dead code... | |
| ✔ Detecting dead code. | |
| Accessibility 3 issues | |
| ✗ Role has required aria props ×2 | |
| `combobox` role is missing required aria props `aria-controls`. | |
| Add missing aria props `aria-controls` to the element with `combobox` role. | |
| https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/role-has-required-aria-props.html | |
| src/components/enhanced-combobox.tsx:71 | |
| ⚠ Label has associated control | |
| A form label must have accessible text. | |
| Ensure the label either has text inside it or is accessibly labelled using an | |
| attribute such as `aria-label`, or `aria-labelledby`. You can mark more attributes | |
| as accessible labels by configuring the `labelAttributes` option. | |
| https://oxc.rs/docs/guide/usage/linter/rules/jsx_a11y/label-has-associated-control.html | |
| src/components/upload-button.tsx:27 | |
| Architecture 82 issues | |
| ⚠ React19 deprecated apis ×30 | |
| forwardRef is no longer needed on React 19+ — refs are regular props on function | |
| components; remove forwardRef and pass ref directly | |
| Pass `ref` as a regular prop on function components — `forwardRef` is no longer | |
| needed in React 19+. Replace `useContext(X)` with `use(X)` for branch-aware context | |
| reads. Only enabled on projects detected as React 19+. | |
| src/components/multiselect.tsx:139 | |
| ⚠ Design no redundant size axes ×26 | |
| w-4 h-4 → use the shorthand size-4 (Tailwind v3.4+) | |
| Collapse `w-N h-N` to `size-N` (Tailwind v3.4+) when both axes match | |
| src/components/enhanced-combobox.tsx:84 | |
| ⚠ Design no default tailwind palette ×24 | |
| from-slate-50 reads as the Tailwind template default — use zinc (true neutral), | |
| neutral (warmer), or stone (warmest) | |
| Replace `indigo-*` / `gray-*` / `slate-*` with project tokens, your brand color, or | |
| a less-default neutral (`zinc`, `neutral`, `stone`) | |
| src/components/gridComp.tsx:9 | |
| State & Effects 9 issues | |
| ⚠ Derived useState ×5 | |
| useState initialized from prop "defaultOpen" — if this value should stay in sync | |
| with the prop, derive it during render instead | |
| Remove useState and compute the value inline: `const value = transform(propName)` | |
| src/components/sidebar.tsx:73 | |
| ⚠ Effect event handler ×3 | |
| useEffect simulating an event handler — move logic to an actual event handler | |
| instead | |
| Move the conditional logic into onClick, onChange, or onSubmit handlers directly | |
| src/lib/components/time-picker/time-picker-input.tsx:50 | |
| ⚠ Prop callback in effect | |
| useEffect calls prop callback "setApi" with local state in deps — this is the "lift | |
| state via callback" anti-pattern; lift state into a shared Provider so both sides | |
| read the same source | |
| Lift the shared state into a Provider so both sides read the same source — no | |
| useEffect-driven sync needed | |
| src/components/carousel.tsx:96 | |
| Correctness 8 issues | |
| ⚠ Rendering hydration mismatch time ×7 | |
| Date.now() reachable from JSX renders differently on server vs client — wrap in | |
| useEffect+useState (client-only) or add suppressHydrationWarning to the parent if | |
| intentional | |
| Wrap dynamic time/random values in useEffect+useState (client-only) or add | |
| suppressHydrationWarning to the parent if intentional | |
| src/components/gridComp.tsx:108 | |
| ⚠ Danger | |
| Do not use `dangerouslySetInnerHTML` prop | |
| `dangerouslySetInnerHTML` is a way to inject HTML into your React component. This | |
| is dangerous because it can easily lead to XSS vulnerabilities. | |
| https://oxc.rs/docs/guide/usage/linter/rules/react/no-danger.html | |
| src/components/chart.tsx:83 | |
| Performance 7 issues | |
| ⚠ Rerender functional setstate ×2 | |
| setColumns({ ...columns, ... }) — use functional update `setColumns(prev => ({ | |
| ...prev, ... }))` to avoid stale closures | |
| Use the callback form: `setState(prev => prev + 1)` to always read the latest value | |
| src/components/sortable.tsx:204 | |
| ⚠ Usememo simple expression | |
| useMemo wrapping a trivially cheap expression — memo overhead exceeds the | |
| computation | |
| Remove useMemo — property access, math, and ternaries are already cheap without | |
| memoization | |
| src/components/sidebar.tsx:613 | |
| ⚠ Advanced event handler refs | |
| useEffect re-subscribes a "onSelect" listener every time the handler identity | |
| changes — store the handler in a ref and have the listener read | |
| `handlerRef.current()`, then drop it from the deps | |
| Store the handler in a ref and have the listener read `handlerRef.current()` — the | |
| subscription stays put while the latest handler is always called | |
| src/components/carousel.tsx:99 | |
| ⚠ 8 more warnings | |
| Run `npx react-doctor@latest . --verbose` to get all details | |
| ┌─────┐ 84 / 100 Great | |
| │ ◠ ◠ │ ██████████████████████████████████████████░░░░░░░░ | |
| │ ▽ │ React Doctor (www.react.doctor) | |
| └─────┘ | |
| 112 issues across 26/78 files in 640ms | |
| Full diagnostics written to /var/folders/d7/hlx5rgg91rl6s5rwbqm2s94w0000gn/T/react-doctor-4ffb56f0-8195-4015-9e1f-737abd80032f | |
| → Share your results: https://www.react.doctor/share?p=%4010xlms%2Fui&s=84&e=2&w=110&f=26 | |
| Scanning /Users/kulterryan/work/10xlms-platform/packages/webplayer... | |
| - Detecting framework. Found Next.js. | |
| ✔ Detecting framework. Found Next.js. | |
| - Detecting React version. Found React 19.2.4. | |
| ✔ Detecting React version. Found React 19.2.4. | |
| - Detecting language. Found TypeScript. | |
| ✔ Detecting language. Found TypeScript. | |
| - Detecting React Compiler. Not found. | |
| ✔ Detecting React Compiler. Not found. | |
| - Found 11 source files. | |
| ✔ Found 11 source files. | |
| - Running lint checks... | |
| - Running lint checks. | |
| ✔ Running lint checks. | |
| - Detecting dead code... | |
| ✔ Detecting dead code. | |
| Architecture 27 issues | |
| ⚠ Inline exhaustive style ×12 | |
| 17 inline style properties — extract to a CSS class, CSS module, or styled | |
| component for maintainability and reuse | |
| Move styles to a CSS class, CSS module, Tailwind utilities, or a styled component — | |
| inline objects with many properties hurt readability and create new references | |
| every render | |
| src/components/NotesOverlay.tsx:206 | |
| ⚠ Design no redundant size axes ×5 | |
| w-4 h-4 → use the shorthand size-4 (Tailwind v3.4+) | |
| Collapse `w-N h-N` to `size-N` (Tailwind v3.4+) when both axes match | |
| src/components/NotesTabContent.tsx:78 | |
| ⚠ Z index 9999 ×3 | |
| z-index: 100 is arbitrarily high — use a deliberate z-index scale (1–50). Extreme | |
| values signal a stacking context problem, not a fix | |
| Define a z-index scale in your design tokens (e.g. dropdown: 10, modal: 20, toast: | |
| 30). Create a new stacking context with `isolation: isolate` instead of escalating | |
| values | |
| src/components/NotesOverlay.tsx:262 | |
| Performance 14 issues | |
| ⚠ Transition all ×7 | |
| transition: "all" animates every property including layout — list only the | |
| properties you animate | |
| List specific properties: `transition: "opacity 200ms, transform 200ms"` — or in | |
| Tailwind use `transition-colors`, `transition-opacity`, or `transition-transform` | |
| src/components/NoteInput.tsx:216 | |
| ⚠ Js batch dom CSS ×6 | |
| Multiple sequential element.style assignments — batch with cssText or classList for | |
| fewer reflows | |
| Batch DOM/CSS reads and writes — interleaving them inside a loop causes layout | |
| thrashing. Read first, then write | |
| src/components/NotesPanel.tsx:146 | |
| ⚠ Rerender memo with default value | |
| Default prop value [] creates a new array reference every render — extract to a | |
| module-level constant | |
| Move to module scope: `const EMPTY_ITEMS: Item[] = []` then use as the default value | |
| src/players/Player.tsx:49 | |
| Dead Code 10 issues | |
| ⚠ Exports ×6 | |
| Unused export: default | |
| ⚠ Duplicates ×4 | |
| Duplicate export: NoteInput|default | |
| State & Effects 7 issues | |
| ⚠ Effect event handler ×2 | |
| useEffect simulating an event handler — move logic to an actual event handler | |
| instead | |
| Move the conditional logic into onClick, onChange, or onSubmit handlers directly | |
| src/components/NotesOverlay.tsx:111 | |
| ⚠ Prop callback in effect ×2 | |
| useEffect calls prop callback "onNotesReady" with local state in deps — this is the | |
| "lift state via callback" anti-pattern; lift state into a shared Provider so both | |
| sides read the same source | |
| Lift the shared state into a Provider so both sides read the same source — no | |
| useEffect-driven sync needed | |
| src/components/NotesOverlay.tsx:113 | |
| ⚠ Mirror prop effect | |
| useState "content" is mirrored from prop "initialContent" via this effect — delete | |
| both the useState and the effect, and read the prop directly in render | |
| Delete both the `useState` and the `useEffect` and read the prop directly during | |
| render. Mirroring a prop into local state forces a stale first render before the | |
| effect re-syncs | |
| src/components/NoteInput.tsx:31 | |
| Accessibility 4 issues | |
| ⚠ Outline none ×2 | |
| outline: none removes keyboard focus visibility — use :focus-visible styling | |
| instead, or provide a box-shadow focus ring | |
| Use `:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px | |
| }` to show focus only for keyboard users while hiding it for mouse clicks | |
| src/components/NoteInput.tsx:183 | |
| ⚠ Tiny text ×2 | |
| Font size 11px is too small — body text should be at least 12px for readability, | |
| 16px is ideal | |
| Use at least 12px for body content, 16px is ideal. Small text is hard to read, | |
| especially on high-DPI mobile screens | |
| src/components/NotesPanel.tsx:279 | |
| ⚠ 11 more warnings | |
| Run `npx react-doctor@latest . --verbose` to get all details | |
| ┌─────┐ 85 / 100 Great | |
| │ ◠ ◠ │ ███████████████████████████████████████████░░░░░░░ | |
| │ ▽ │ React Doctor (www.react.doctor) | |
| └─────┘ | |
| 64 issues across 6/11 files in 572ms | |
| Full diagnostics written to /var/folders/d7/hlx5rgg91rl6s5rwbqm2s94w0000gn/T/react-doctor-168fe2e1-eacd-448c-b21f-d481ed69e642 | |
| → Share your results: https://www.react.doctor/share?p=%4010xlms%2Fwebplayer&s=85&w=64&f=6 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment