chore(lint): enforce ESLint in CI + type-aware rules (#414, #415) - #426
Conversation
#414 — add a blocking Lint job to test.yml (`npx turbo run lint` across web/mobile/shared) so error-level rules actually gate PRs, plus a .turbo cache step. #415 — enable typescript-eslint recommendedTypeChecked + sonarjs across web, shared, and mobile (projectService/tsconfigRootDir; disableTypeChecked for loose JS). Fix ~471 violations behavior-preservingly: floating/misused promises (await/void + the house `() => void fn()` handler idiom), no-unsafe-* boundary typing, redundant type assertions, require-await, and 3 cognitive-complexity extractions. Graduate the two rules that are unsafe to batch-fix to `warn` with tracked follow-ups: no-unnecessary-condition (#424) and cognitive-complexity (#425). Closes #414 Closes #415 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
|
There was a problem hiding this comment.
/pr-review — PR #426
Scope: Bundles #414 (ESLint as a required CI job) + #415 (type-aware ESLint rules + cognitive-complexity cap), with ~190 files of behavior-preserving lint fixes across apps/web, apps/mobile, packages/shared.
Dimensions checked
| Dimension | Result |
|---|---|
| Correctness (behavior-preservation of the fixes) | ✅ Spot-checked all non-trivial hunks (see below) — no behavior drift found |
| Cross-platform parity | ✅ PAIRED — parity-checker subagent confirmed mirrored web/mobile files received equivalent fixes; eslint.config changes landed in all three workspaces (web/mobile/shared) |
| Design (DESIGN.md token/AI-slop/scene-sentence) | ✅ PASS — design-reviewer subagent confirmed the 4 flagged extraction sites use only semantic tokens, no new hex literals/radii, no logic drift |
| Security | ✅ N/A — no auth, input-validation, or trust-boundary code touched; changes are typing/promise-handling only |
| Contract-aligner / backward-compat guard | packages/shared/src/types/* or packages/shared/src/api/endpoints.ts, so no contract surface changed |
| i18n sync | ✅ N/A — no user-facing strings added/changed |
CI/config changes (test.yml, eslint.config.*, package.json) |
✅ New Lint job mirrors the existing type-check job's checkout/setup/cache pattern; no-unsafe-*/unbound-method are disabled only in test-file overrides (__tests__/**, *.test.*, *.spec.*), not in production code — reasonable scoping, not a gate violation |
Manual verification performed
Reviewed the diff directly (git diff origin/main...HEAD, 192 files / 852+/505−) and hand-checked the largest/riskiest hunks for behavior drift:
apps/mobile/app/streak-sections.tsx—resolveNumeralColor/buildRunBandStyleextractions preserve identical branching and token usage.apps/mobile/components/habits/habit-row-trailing.tsx— extractedrunParentProgressAction/resolveParentRingColor/resolveParentRingTrackColorpreserve the exact same precedence and color logic.apps/mobile/components/onboarding/onboarding-actions-context.tsx—async→sync + explicitPromise.resolve()conversions (require-await fix) are behavior-equivalent.apps/web/.../friend-row.tsx+ mobile mirror —void (async () => {...})()IIFE wraps preserve the original await/setState sequencing.apps/mobile/components/goals/goal-detail-drawer.tsx— confirmedrefetchDetail: () => void refetchDetail()matchesuseGoalStatusActions's actual() => voidparameter type (not fire-and-forgetting a value the callee awaits).packages/shared/src/{stores/ui-store,utils/error-utils,utils/drill-navigation}.ts— redundantas Xcasts removed after narrowing already guarantees the type;packages/shared/src/validation/habit-form.ts'svalidateTimesextraction preserves the original short-circuit order.
No Critical or High findings. No Medium findings that are concretely actionable beyond what's already tracked (the two rules graduated to warn — #424, #425 — are called out and tracked by the PR author already, not new debt this review needs to add).
Note (informational, not gating)
SonarCloud's automated PR comment shows the Quality Gate failing on New Code Coverage (70.8% < 80%) and Duplication (5.1% > 3%) — expected for a mechanical fix-up PR with no test additions and repeated void-wrap/extraction patterns across many files. This is a separate required check per this review's CI adaptations, not part of this rubric's pass/fail — flagging for visibility only.
Recommendation: APPROVE
Clean, well-scoped, behavior-preserving lint-hardening PR. Both automated dimension checks (parity, design) passed, and manual spot-checks of every non-trivial hunk found no drift.


Bundles #414 + #415 (correlated lint hardening).
#414 — ESLint as a blocking CI job
Adds a
Lintjob to.github/workflows/test.ymlrunningnpx turbo run lintacross web/mobile/shared (previously lint ran in no workflow, so error-level rules were unenforced). Includes a.turbocache step. RegisterLintas a required check after merge (documented in #420).#415 — type-aware rules + cognitive-complexity cap
Enables typescript-eslint
recommendedTypeChecked+ explicitno-unnecessary-condition+sonarjs/cognitive-complexity(15) across web, shared, and mobile, wired viaparserOptions.projectService. 777 raw findings → 0 errors, fixed behavior-preservingly:await/void; misused → house() => void fn()idiom / sync-wrap)no-unsafe-*→ honest boundary typing atres.json()/JSON seams (noas any)require-await,unbound-method, miscTwo rules graduated to
warn(unsafe to batch-fix; tracked follow-ups):no-unnecessary-condition→ #424 (guard-deletion risk at untyped boundaries),cognitive-complexity→ #425 (large core-screen refactors need visual verification). 229 warnings remain, all from these two graduated rules.Validated:
turbo run type-check3/3,turbo run lint3/3 (0 errors),turbo run test3/3 (web 2150, shared 1411, mobile green). Parity maintained across web↔mobile fixes.🤖 Generated with Claude Code