← Portal
Tech audit · Code Quality & Maintainability 2026-05-19

Code Quality & Maintainability

12 findings
0
Kritikus
1
Magas
5
Közepes
6
Alacsony

Below are all 12 findings recorded in this dimension during the audit, sorted by severity then launch priority. Each card carries the full anatomy: severity / priority / effort, code location, evidence, technical issue, business impact, plain-language explanation, fix steps, related dimensions, and references.

07

Findings — Code Quality & Maintainability

Magas Launch előtt L COD-001 · code-quality
Single test file across 80,000+ lines of application code: regressions cannot be detected by anything except live users
Kód-hely
src/lib/locale-region.test.ts:the only test file in the repo
Evidence
find src -name '*.test.*' returns exactly one file: src/lib/locale-region.test.ts (Vitest unit test for inferRegion fallback). vitest.config.ts is configured and Vitest 2.1.9 is in devDependencies, but no other test file exists. No tests for: coach-chat / voice-coach endpoints (1,245 + 498 LOC), AI tool-call parsers (meal-analysis.ts parseAiToolCall), auth flow, RLS-bypass paths, the 102-module src/server/ surface, the 38 hooks, or any component. 80,000+ LOC of TS/TSX has 1 test -> effective code coverage is well under 1%.
A probléma

A second engineer modifying coach-chat context-loading, meal-analysis JSON parsing, biometric calculations, blueprint generation, or any of the 100 server modules has no automated way to know whether their change broke existing behavior. With AI-generated outputs in critical paths and no contract tests on parseAiToolCall-style helpers, even silent regressions in the AI gateway response shape go undetected.

Üzleti hatás

Every change ships untested. Two-engineer team velocity will collapse the first time a refactor breaks a downstream coach prompt path, because there is no safety net to catch it before users see broken state. In a health-adjacent product, an undetected regression in biometric calculation or coach safety filtering becomes an end-user incident, not a CI failure.

Magyarázat

There is one automated test in the entire codebase. Everything else has been verified only by you clicking through the app. The first time someone else changes the code, there is no machine to tell them whether they broke something -- they will have to test by hand, every time, and they will miss things.

Javaslat

Add a test script in package.json (vitest run). Then prioritize tests for the highest-risk pure functions first: parseAiToolCall in meal-analysis.ts, the safety-filter helpers in _shared/safety-check.ts / medical-safety.ts / mental-health-risk.ts, the longevity-score composition in useDashboardData, and the region inference already covered. Target: 30 unit tests covering pure server-side helpers within the first sprint; defer integration / E2E to later.

Becsült munka

L — 1–2 weeks

Kapcsolódó dimenziók
OPS-014 already notes the missing test script as an ops problem; this finding is the underlying engineering gap.
Közepes Első sprint M COD-002 · code-quality
TanStack Query installed but zero usage: data layer is 510 useState + 304 useEffect + raw Supabase calls
Kód-hely
package.json + repo-wide:package.json:49 declares @tanstack/react-query ^5.83.0
Evidence
@tanstack/react-query ^5.83.0 in package.json:49. grep for useQuery|useMutation|useInfiniteQuery across src: 0 occurrences. grep for QueryClientProvider across src: 0 occurrences. grep for @tanstack/react-query import in src: 0 occurrences. Meanwhile: 510 useState calls, 304 useEffect calls, 130 direct supabase.from(...) calls across src/, 14 component/route files calling supabase.from(...) directly (CoachPage.tsx 9 supabase calls inline; JournalPage.tsx, settings.tsx, CoachChatSheet.tsx, BiometricEditDialog.tsx, MorningCheckInPrompt.tsx, WaterQuickAddSheet.tsx, etc.). All data fetching is hand-rolled with manual loading/error state.
A probléma

TanStack Query is a transitive devDep of TanStack Start but is treated by this codebase as if it didn't exist. Cache invalidation, request deduplication, automatic refetch on focus, optimistic updates, and stale-while-revalidate are all reimplemented (badly) via useEffect + ad-hoc invalidation buses (lib/data-events.ts, data-invalidation-bus.ts) -- or simply not implemented, which is why the dashboard reloads everything on every render path.

Üzleti hatás

Manual data layer is a maintenance tax on every component. Each one redeclares loading + error + retry + invalidation state by hand. A second engineer cannot rely on a consistent data-fetching idiom and will introduce subtle bugs every time they add a new query.

Magyarázat

A standard tool for fetching and caching data in React is already installed in your project, but the project does not actually use it. Instead, every screen re-implements the same fetch / loading / error pattern by hand. That is why simple data changes (like logging a meal) sometimes don't immediately appear elsewhere in the app -- there is no shared cache.

Javaslat

Either remove @tanstack/react-query from package.json to avoid shipping unused code, OR (better) adopt it: wrap the app in a single QueryClientProvider, migrate the top 5 data-fetching hooks (useProfile, useDashboardData, useLifestyleData, useBodyMeasurements, useWorkoutLogs) to useQuery, and let the rest follow. Choose one direction -- do not leave the library installed but unused.

Becsült munka

M — 1–3 days

Kapcsolódó dimenziók
Same duplication is described from the performance angle in SCA-001 and SCA-004.
Közepes Első sprint L COD-003 · code-quality
Five files over 700 lines mix UI, data fetching, and business logic in one component
Kód-hely
src/components/CoachPage.tsx + 4 other oversized files:CoachPage.tsx:1-2096 (47 hook calls, 9 supabase calls in component body)
Evidence
Top oversized source files (excluding generated routeTree.gen.ts and supabase/types.ts): src/components/CoachPage.tsx 2,096 LOC (47 hook calls, 9 supabase.from calls inline, 12 console.* calls), src/routes/api.coach-chat.ts 1,245 LOC (single server route with 16 supabase calls and the full coach prompt pipeline inline), src/components/CoachChatSheet.tsx 1,108 LOC, src/routes/training_.assessment.tsx 1,023 LOC, src/routes/training.tsx 966 LOC, src/components/JournalPage.tsx 942 LOC, src/components/profile/BodyScanSection.tsx 901 LOC, src/server/meal-analysis.ts 841 LOC. CoachPage.tsx alone contains UI rendering, supabase queries, intake completion side-effects, twin-reaction firing, blueprint reveal logic, dashboard tour state, ManualMealSheet integration, and a typewriter stream -- eight responsibilities in one file.
A probléma

Files this large in a codebase with no tests (cross-ref COD-001) become a no-go zone: a second engineer cannot safely change anything without reading 2,000 lines of context. Several of these (CoachPage, JournalPage, training routes) are page components that should be thin layouts composing extracted hooks and sub-components.

Üzleti hatás

Onboarding cost for a second engineer is dominated by these five files. Bug-fix turnaround time on the coach surface -- the product's main feature -- will be 3-5x slower than on a properly factored codebase because any change requires re-reading the whole file.

Magyarázat

Five files in your app are each 700-2,100 lines long and try to do everything in one place: drawing the screen, fetching data, running business logic, talking to Supabase. The largest one is your coach page. A new developer would need a full day just to safely add a button to this page.

Javaslat
  1. data-fetching into hooks in src/hooks/, (.
  2. sub-sections into child components, (.
  3. pure helpers into src/lib/. Target <=300 LOC per component file and <=100 LOC per function. Start with CoachPage.tsx -- extract useCoachIntakeFlow, useCoachChatState, and a CoachPersonaPanel sub-component.
Becsült munka

L — 1–2 weeks

Kapcsolódó dimenziók
Inline supabase queries in these files duplicate the data layer already noted in SCA-001 and SCA-010.
Közepes Első sprint M COD-004 · code-quality
Errors swallowed by catch-and-console pattern: ~105 catch blocks log and silently return without surfacing failures
Kód-hely
Repo-wide (no single file)
Evidence
160 catch blocks in src/ (excluding routeTree.gen.ts). 12 .catch silently-absorb patterns (e.g. CoachKnockCard.tsx:55, CoachChatSheet.tsx:379, CoachPage.tsx:1214, api.coach-chat.ts:865-868). 105 catch blocks pair console.warn or console.error with a silent return. Samples: src/server/_shared/ai-tool-call.ts: console.warn callAITool network error then return null; src/server/north-star.functions.ts: console.error getTodaysNorthStar failed then return null; src/server/bio-twin-active.ts: console.warn then return null. With no Sentry / Honeybadger (cross-ref OPS-005), console.error in a Cloudflare Worker is lost.
A probléma

The dominant error-handling idiom is: try { ... } catch (e) { console.warn(e); return null }. Callers receive a null and have no way to distinguish no-data-found from AI-gateway-threw from Supabase-RLS-denied from network-blew-up. In a Cloudflare Worker without an error tracker, those console.warn lines never reach a human.

Üzleti hatás

Production failures become invisible. The coach silently degrades, the bio-twin avatar silently skips, the north-star quietly returns null -- and the user sees a UI that just does not work right without anyone knowing why. The team will only learn about failures through user complaints, weeks later.

Magyarázat

When something goes wrong in the app, the code mostly just writes a note to a log nobody reads and pretends everything is fine. Combined with the lack of an error-tracking service, this means failures in production are essentially invisible until a user complains.

Javaslat
  1. catch only what you can handle; let everything else bubble; (.
  2. every catch either rethrows, returns a typed Result type, or reports to an error tracker -- never just console.warn + return null; (.
  3. for fire-and-forget calls, name them: .catch(reportNonFatal). Pair this fix with installing Sentry (OPS-005).
Becsült munka

M — 1–3 days

Kapcsolódó dimenziók
Depends on OPS-005 for any of these logs to become observable.
Közepes Első sprint M COD-005 · code-quality
Coach context-loader duplication: text-coach and voice-coach endpoints reimplement the same load helpers in parallel
Kód-hely
src/routes/api.coach-chat.ts + src/routes/api.voice-coach-chat.ts:api.coach-chat.ts:865-868 and api.voice-coach-chat.ts:313 onward
Evidence
Five context-loader function calls appear in both routes: loadCoachMemoryThreads, loadFunctionalAgeBlock, loadRitualSignals, loadSnapshotArcBlock, loadTodaysNorthStar. api.coach-chat.ts is 1,245 LOC; api.voice-coach-chat.ts is 498 LOC; both build a long context block, both call the Lovable AI Gateway, both stream a response, both handle invalidation. The voice route is effectively a transcription-prefixed copy of the text route. Common tables queried by both: profiles, biometrics, lifestyle_logs, coach_messages, daily_intents (also queried by 10+ other files; 16 places query profiles, 14 query lifestyle_logs, 12 query biometrics).
A probléma

Voice coach and text coach are the same conversation pipeline with a different transport. They should share a single buildCoachContext / streamCoachReply core. As written, every time a context loader is added (e.g. new safety filter) the team must remember to wire it in both routes, and they will forget.

Üzleti hatás

Bug fixes drift between the two surfaces. A safety filter added to text-coach will not protect voice-coach users until someone notices the omission. In a mental-health-adjacent product (cross-ref DOM-007), that drift is a domain-compliance risk in addition to a code-quality one.

Magyarázat

Your text coach and voice coach are mostly the same code copied into two files. When you fix a bug in one, you have to remember to fix it in the other. Sooner or later someone will forget, and one of the two coaches will behave differently from the other.

Javaslat
  1. stays in the route file; everything downstream of messages lives in the shared module.
Becsült munka

M — 1–3 days

Kapcsolódó dimenziók
Same code path is the subject of SCA-001 and SCA-002. Refactor unblocks performance work in both.
Közepes Első sprint M COD-007 · code-quality
Server-side input validation absent in 88 of 100 server modules: TypeScript casts stand in for runtime validation
Kód-hely
src/server/ + src/routes/api*.ts:12 of 100 server modules use zod; 0 of the 2 SSE routes validate inputs
Evidence
Server module count: 100 files under src/server/ (top-level + _shared/ + onboarding/). grep for z.object|z.string|z.number|z.array|z.enum in src/server matches 12 files only -- bio-twin-active.functions.ts, bio-twin-avatar.ts, bio-twin-react.functions.ts, bio-twin-reactions.ts, body-measurements.functions.ts, body-progress-compare.ts, coach-event.functions.ts, coach-intake.functions.ts, measurement-prompts.functions.ts, onboarding/body-baseline-analyze.functions.ts, push-send.ts, yesterday-tomorrow-plan.functions.ts. The two SSE routes (api.coach-chat.ts 1,245 LOC, api.voice-coach-chat.ts 498 LOC) have NO zod schema. Meanwhile as-unknown-as casts appear 29 times across 14 files (rough type-cast as substitute for validation), and bare : any appears 17 times.
A probléma

Most server functions trust their input shape based on TypeScript types that the runtime cannot enforce. A malformed client request -- or a deliberately crafted one -- can pass right through into Supabase queries and AI prompts. Cross-ref SEC-010 frames this as a security finding; here it is also a maintainability finding because callers cannot read a schema to know what each function accepts.

Üzleti hatás

Every change to a server function expected input shape is a runtime gamble: TypeScript will not catch a mismatch from the client, only the eventual database error will. In a codebase with no error tracker (OPS-005) and no tests (COD-001), that means the only signal of contract drift is a user-visible failure.

Magyarázat

When the app backend receives data from the browser, it mostly trusts the data is shaped correctly without checking. If anything ever sends a slightly wrong shape -- a bug, a stale cached client, a malicious user -- the failure will happen deep inside the database or the AI call instead of being caught at the front door.

Javaslat

Zod is already in dependencies (zod ^3.24.2). Add a one-screen schema at the top of each server function and parse the input through it before any business logic. Start with the highest-risk endpoints (api.coach-chat.ts, api.voice-coach-chat.ts, body-measurements, meal-analysis). Use zod safeParse so validation failures return a clean 400, not a thrown 500.

Becsült munka

M — 1–3 days

Kapcsolódó dimenziók
SEC-010 already raised this from the security side; this finding documents the maintainability angle.
Alacsony Első sprint S COD-006 · code-quality
36 of 46 shadcn/ui primitives are imported nowhere: ~5,000 LOC of dead component code in the repo
Kód-hely
src/components/ui/:ls src/components/ui = 46 files; only 10 are imported by app code
Evidence
src/components/ui/ contains 46 .tsx files. A grep over src/components + src/routes + src/hooks for from-@/components/ui/<name> finds 10 used: sheet (11), button (10), input (6), dialog (5), label (3), slider (2), tooltip (1), toggle (1), textarea (1), sonner (1). The other 36 are unused: accordion, alert, alert-dialog, aspect-ratio, avatar, badge, breadcrumb, card, carousel, chart, checkbox, collapsible, command, context-menu, drawer, dropdown-menu, form, hover-card, input-otp, menubar, navigation-menu, pagination, progress, radio-group, resizable, scroll-area, select, separator, sidebar, skeleton, switch, table, tabs, toggle-group. sidebar.tsx alone is 744 LOC and is imported by nothing.
A probléma

The shadcn/ui registry was added wholesale (typical Lovable scaffolding) rather than per-component. Because the app declares sideEffects: false in package.json, tree-shaking probably eliminates most of these from the production bundle -- but the source files still bloat the repo, slow grep and IDE indexing, and tempt future developers to import unused primitives.

Üzleti hatás

Repo size and search noise. Not a launch blocker -- tree-shaking probably handles the runtime cost -- but a steady tax on every grep and every code review. Cleaning it up signals discipline to a second engineer.

Magyarázat

Three quarters of the UI building blocks shipped in your project are never used anywhere. They were added by the scaffolding tool, not by anyone who needed them. They make the codebase look bigger than it is, but they do not run.

Javaslat

Delete the 36 unused .tsx files from src/components/ui/. When a future feature needs one, re-add it via npx shadcn add. Keep the 10 that are imported. Verify bun run build still succeeds.

Becsült munka

S — under ½ day

Kapcsolódó dimenziók
Bundle-size aspect overlaps with SCA-013 -- but those primitives are likely already tree-shaken.
Alacsony Első sprint S COD-008 · code-quality
Hard-coded mock trend data ships to signed-in users on the dashboard sparklines
Kód-hely
src/hooks/useDashboardData.ts:493-507
Evidence
src/hooks/useDashboardData.ts:493 comment reads: 7-day mock trend lines (TODO: replace with real history aggregation). Lines 495-507 return hard-coded arrays [54, 58, 61, 57, 63, 60, biometric.hrv-or-62] for hrv, [62, 70, 65, 58, 72, 68, biometric.recovery-or-74] for recovery, [10, 13, 11, 14, 9, 12, biometric.strain-or-12.4] for strain, and [6.8, 7.2, 6.5, 7.8, 7.4, 7.1, biometric.sleep_hours-or-7.6] for sleep -- the first six values of every signed-in user 7-day sparkline are the same constants, only the last value reflects real data.
A probléma

This is half-implemented. The sparklines render and look real to the user, but six of every seven points are fabricated. A user comparing their dashboard to a friend would see identical history for the first six days. In a longevity-claim context (cross-ref DOM-006), showing fabricated trend data is an evidence-quality issue, not just a code TODO.

Üzleti hatás

Users see a polished-looking trend chart that is partly fictional. In a health-adjacent product where data integrity is a feature, that is a credibility risk. The TODO has been in place long enough that the team has stopped seeing it.

Magyarázat

Your dashboard shows mini line charts for HRV, recovery, strain, and sleep over the last seven days. Right now, six of the seven days are made-up numbers and only the most recent day reflects real data. The TODO comment in the code shows this was a stopgap that did not get finished.

Javaslat

Either implement the real 7-day aggregation (a single Supabase query over biometrics joined to user_id with a 7-day window, ordered ascending), or render the sparkline only when 2+ real points exist and otherwise show a collecting-data state. Do not ship synthetic history as if it were real.

Becsült munka

S — under ½ day

Kapcsolódó dimenziók
Adjacent to DOM-006 -- showing fabricated trends in a longevity app weakens evidence-quality posture.
Alacsony Első sprint M COD-009 · code-quality
28 react-hooks/exhaustive-deps lint disables: hook dependency arrays opted out of correctness check
Kód-hely
Repo-wide (no single file)
Evidence
68 eslint-disable directives in src/ (excluding routeTree.gen.ts). Of those, 28 are specifically react-hooks/exhaustive-deps. Hot spots: CoachPage.tsx (6 disables at lines 493, 1106, 1112, 1125, 1161, 1245), CoachChatDialog.tsx:86, CoachKnockCard.tsx:56. Pattern: the developer added a side effect, the linter complained that the dep array was incomplete or wrong, and the linter was silenced rather than the dependency added.
A probléma

react-hooks/exhaustive-deps exists because missing deps cause stale-closure bugs: the effect captures an old value, never re-runs when it should, and produces hard-to-debug bugs. Disabling the rule one occurrence at a time is the dominant idiom in this codebase -- 28 disables in 86,000 LOC indicates a systematic give-up rather than isolated false positives.

Üzleti hatás

Each disable is a latent stale-closure bug waiting for the right interaction sequence. A second engineer touching any of these effects will not know which disables are intentional (genuinely correct to omit deps) and which are I-gave-up. Combined with no tests (COD-001), reverting a wrong disable will silently break things.

Magyarázat

React has a linter rule that catches a common bug pattern. The code disables this rule 28 times. Each disable is a place where the original developer knew about a potential bug but chose to silence the warning instead of fixing it. The next developer will not know which of those silences are safe.

Javaslat

Treat each disable as a small refactor: replace useEffect + disabled deps with useEffectEvent (React 19 has it) or extract the changing value into a ref. For event-handler-style effects, use useEffectEvent. Aim to halve the count in the first sprint and reach zero within two sprints.

Becsült munka

M — 1–3 days

Alacsony Első sprint M COD-010 · code-quality
177 raw console.* calls in production code with no logger abstraction
Kód-hely
Repo-wide (no single file)
Evidence
177 console.{log,error,warn,info,debug} calls across 81 files in src/ (excluding routeTree.gen.ts). Top hot spots: src/server/bio-twin-avatar.ts (12), src/components/CoachPage.tsx (12), src/routes/api.coach-chat.ts (9), src/server/onboarding/daily-block.functions.ts (8), src/server/meal-analysis.ts (7), src/components/body/BodyCheckInSheet.tsx (5), src/components/JournalPage.tsx (5). No logger module exists in src/lib/. ESLint has no-console disabled at the config level (eslint.config.js does not include no-console in rules).
A probléma

On Cloudflare Workers, console.* writes go to the Worker tail log, which is ephemeral and not searchable beyond a short retention window without wrangler tail running. There is no level filtering, no PII redaction, and no structured fields. Combined with COD-004 (catch-and-console as the error-handling idiom), this guarantees that production failures are visible only if someone is actively tailing logs at the moment of failure.

Üzleti hatás

Logs are practically useless for diagnosing production issues. When a user reports the-coach-gave-me-a-weird-response-yesterday, there is no log to look up. The Lovable AI Gateway costs (cross-ref AI-003, AI-006) cannot be reconciled against in-app actions because there is no per-call log.

Magyarázat

The app developers leaned heavily on console.log statements when debugging -- there are 177 of them. They print to Cloudflare short-lived log stream and then vanish. There is no central log that someone could read later to figure out why something went wrong yesterday.

Javaslat
  1. so logs survive longer than a Worker invocation.
Becsült munka

M — 1–3 days

Kapcsolódó dimenziók
OPS-006 already flagged the 177-call console pattern as an ops finding. This finding is the code-quality refactor that enables the ops fix.
Alacsony Első sprint S COD-011 · code-quality
Minor naming inconsistency: one hook uses kebab-case while all 37 others use camelCase
Kód-hely
src/hooks/use-mobile.tsx:filename itself
Evidence
ls src/hooks/ shows 37 files matching the pattern useFoo.ts / useFoo.tsx (e.g. useAuth.tsx, useDashboardData.ts, useBodyMeasurements.ts), and exactly one file matching kebab-case: use-mobile.tsx. Routes folder follows TanStack convention so does not violate. Components folder is uniformly PascalCase.
A probléma

The shadcn scaffolding introduced use-mobile.tsx with a different naming convention than the rest of the hooks directory. A trivial inconsistency on its own, but symptomatic of generator output being merged without normalisation.

Üzleti hatás

Negligible on its own -- a tiny papercut for IDE auto-complete and for grep-by-convention. Mentioned for completeness so the team can decide whether to enforce a hook-naming lint rule.

Magyarázat

One hook in your project is named use-mobile with a dash. Every other hook is named useSomething without a dash. This is a small inconsistency left over from the scaffolding tool -- easy to fix, low priority.

Javaslat

Rename src/hooks/use-mobile.tsx to src/hooks/useMobile.tsx (and update its one importer). Optionally add an ESLint custom rule or eslint-plugin-filename-rules to enforce useCamelCase.tsx for hook files.

Becsült munka

S — under ½ day

Alacsony nice_to_have S COD-012 · code-quality
ESLint config disables @typescript-eslint/no-unused-vars -- unused imports / locals can ship indefinitely
Kód-hely
eslint.config.js:24
Evidence
eslint.config.js:24 sets @typescript-eslint/no-unused-vars to off. tsconfig.json also has noUnusedLocals: false and noUnusedParameters: false. Result: neither the type checker nor the linter flags unused imports, unused local variables, or unused parameters. Two commented-out imports were found by hand (AppProviders.tsx:14: // import { DailyRitualSheet } and integrations/supabase/client.ts:33: // import { supabase }), but the larger problem is unmeasurable -- there could be hundreds of unused symbols and nothing would flag them.
A probléma

Three guard rails for dead-code prevention are disabled at the configuration level. The codebase has no tool actively telling the team this-import-is-unused. Combined with the 36 unused shadcn primitives (COD-006), this signals that dead-code accumulation is unobserved.

Üzleti hatás

Dead imports and dead variables drift in unnoticed. Each one is small; together they make grep noisier and increase the surface area a second engineer must read. Low-severity, but the fix is a one-line config change.

Magyarázat

Your linter is told to ignore unused variables and imports. So if a developer leaves dead code in a file, no tool will warn about it. The cleanup is automatic -- turn the rule on, fix what it finds.

Javaslat

Change eslint.config.js:24 to @typescript-eslint/no-unused-vars at warn level with an argsIgnorePattern of ^_, and set tsconfig.json noUnusedLocals: true and noUnusedParameters: true. Run bun run lint and bun run build; fix what they flag.

Becsült munka

S — under ½ day

Kapcsolódó dimenziók
AI Project Audit · Tech report · Code Quality & Maintainability Charter v0.4 · 2026-05-19