refactor: resolve all react-doctor findings (score 100) - #78
Merged
Conversation
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
marked this pull request as ready for review
May 30, 2026 11:05
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.@koe/dashboard@wifsimster/koeEvery fix follows the rule's canonical recipe rather than suppressing it.
Dashboard
deslop/circular-dependency) —router.tsximported the pages/AppShellthat 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) andAppLayout.tsx(route layout components). Pages now read search params viagetRouteApi, sorouter.tsxis pure route config — which also clearsonly-export-components/no-multi-comp.prefer-useReducer/rules-of-hooks/no-cascading-set-state—OnboardingPagehooks moved above its early return and folded into a reducer;InboxPagebulk-action state andTicketDetailPageload/mutation state likewise.SearchBox/NotesPaneldrop their prop-sync effects for render-phase reconciliation + lazy init (focus preserved while typing);login()callsrefresh()directly instead of via aloadingstate hop; abort controllers are copied before cleanup;useContext→use(); the mobile media query is lazy-initialised instead of set in an effect.<label>s, removed em dashes from UI copy,size-*/p-*shorthands, explicit<button type>, splitcvarecipes into*-variants.ts, deleted unreachableui/{avatar,dialog,scroll-area,sonner}.tsx, and dropped the unused@tanstack/router-devtools+sonnerdeps.Widget
values+errorsfolded into reducers; focus effects replaced withautoFocus; abort controllers copied before cleanup; post-awaitearly returns deferred; visual-viewport listeners marked{ passive: true }; explicit<button type>.<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.jsonsuppresses theunused-filefalse positive forpackages/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@latest→ 100 / 100, 0 issuespnpm turbo run build→ green (dashboardtsc --noEmit+ both widget build targets)pnpm test→ 89/89 passpnpm install --frozen-lockfile→ clean(The widget
lintfailure is the pre-existing "eslint referenced but no flat config installed" issue noted in CLAUDE.md; CI runs itcontinue-on-error.)https://claude.ai/code/session_01VY4ViqYDvx6q1qGpTubXwQ
Generated by Claude Code