Skip to content

refactor: resolve all react-doctor findings (score 100) - #78

Merged
Wifsimster merged 1 commit into
mainfrom
claude/react-doctor-score-100-CCmOX
May 30, 2026
Merged

refactor: resolve all react-doctor findings (score 100)#78
Wifsimster merged 1 commit into
mainfrom
claude/react-doctor-score-100-CCmOX

Conversation

@Wifsimster

Copy link
Copy Markdown
Owner

Summary

Brings both React packages to a 100 / 100 react-doctor score (npx react-doctor@latest), up from 83 — 76 issues → 0, with no intended behavior change.

Project Before After
@koe/dashboard 83 (2 errors, 59 warnings) 100
@wifsimster/koe 95 (15 warnings) 100

Every fix follows the rule's canonical recipe rather than suppressing it.

Dashboard

  • Import cycle (deslop/circular-dependency)router.tsx imported the pages/AppShell that imported it back. Extracted the shared pieces into dependency-free modules: lib/searchParams.ts (search-param types + INBOX_DEFAULT_SEARCH), RouteFallbacks.tsx (404 / error views) and AppLayout.tsx (route layout components). Pages now read search params via getRouteApi, so router.tsx is pure route config — which also clears only-export-components / no-multi-comp.
  • prefer-useReducer / rules-of-hooks / no-cascading-set-stateOnboardingPage hooks moved above its early return and folded into a reducer; InboxPage bulk-action state and TicketDetailPage load/mutation state likewise.
  • State & effectsSearchBox / NotesPanel drop their prop-sync effects for render-phase reconciliation + lazy init (focus preserved while typing); login() calls refresh() directly instead of via a loading state hop; abort controllers are copied before cleanup; useContextuse(); the mobile media query is lazy-initialised instead of set in an effect.
  • A11y / design / dead code — associated <label>s, removed em dashes from UI copy, size-* / p-* shorthands, explicit <button type>, split cva recipes into *-variants.ts, deleted unreachable ui/{avatar,dialog,scroll-area,sonner}.tsx, and dropped the unused @tanstack/router-devtools + sonner deps.

Widget

  • Form values+errors folded into reducers; focus effects replaced with autoFocus; abort controllers copied before cleanup; post-await early returns deferred; visual-viewport listeners marked { passive: true }; explicit <button type>.
  • The Panel renders as a native <dialog> and the IntentPicker as a <fieldset> for built-in semantics. Tailwind preflight is off in the widget, so zero-specificity :where() UA-style resets keep the rendered output identical to the previous <div>s.

react-doctor config

react-doctor.config.json suppresses the unused-file false positive for packages/widget/src/standalone-entry.ts — it's the IIFE/CDN build entry (vite.config.ts), which static analysis from the library entry can't reach. Deleting it would break the standalone build.

Verification

  • npx react-doctor@latest100 / 100, 0 issues
  • pnpm turbo run build → green (dashboard tsc --noEmit + both widget build targets)
  • pnpm test → 89/89 pass
  • pnpm install --frozen-lockfile → clean

(The widget lint failure is the pre-existing "eslint referenced but no flat config installed" issue noted in CLAUDE.md; CI runs it continue-on-error.)

https://claude.ai/code/session_01VY4ViqYDvx6q1qGpTubXwQ


Generated by Claude Code

Bring @koe/dashboard and @wifsimster/koe to a 100/100 react-doctor score
by fixing every reported correctness, architecture, state/effect,
dead-code, accessibility, and performance issue (76 issues → 0).

Dashboard:
- Break the router.tsx <-> pages/AppShell import cycle by extracting the
  search-param types/defaults (lib/searchParams.ts), the route fallbacks
  (RouteFallbacks.tsx) and the route layout components (AppLayout.tsx);
  router.tsx is now pure route config. Pages read search via getRouteApi.
- Split the cva recipes into button-variants.ts / badge-variants.ts so
  the component files export only components (Fast Refresh boundaries).
- Move OnboardingPage's conditionally-called hooks above the early
  return and fold its state into a reducer (rules-of-hooks +
  prefer-useReducer); same reducer treatment for InboxPage bulk actions
  and TicketDetailPage load/mutation state.
- Replace prop-sync effects with render-phase reconciliation / lazy init
  (SearchBox, NotesPanel), call refresh() directly from the login
  handler, copy the abort controller before cleanup, useContext -> use(),
  lazy-init the mobile media query.
- A11y/design: associate labels, drop em dashes in JSX copy, size-/p-
  shorthands, explicit button types.
- Delete unreachable ui files (avatar/dialog/scroll-area/sonner) and drop
  the unused @tanstack/router-devtools and sonner dependencies.

Widget:
- Fold form values+errors into reducers, replace focus effects with
  autoFocus, copy abort controllers before cleanup, defer post-await
  early returns, mark visual-viewport listeners passive, explicit button
  type.
- Render the Panel as a native <dialog> and the IntentPicker as a
  <fieldset> for built-in semantics, with zero-specificity UA-style
  resets so the rendered output is unchanged (Tailwind preflight is off).

A repo-level react-doctor.config.json suppresses the unused-file false
positive for the IIFE build entry (standalone-entry.ts), which static
analysis from the library entry cannot see.

https://claude.ai/code/session_01VY4ViqYDvx6q1qGpTubXwQ
@Wifsimster
Wifsimster marked this pull request as ready for review May 30, 2026 11:05
@Wifsimster
Wifsimster merged commit c8916d6 into main May 30, 2026
1 check 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