# Code Review Checklist — Property Match > Use this checklist before every PR review and before considering any implementation "done". > For architecture context see [ARCHITECTURE.md](./ARCHITECTURE.md). > For coding rules see [CLAUDE.md](./CLAUDE.md). --- ## A. Component Size & Structure - [ ] **Is the component too large?** - Page component: hard limit 300 lines, target 150–200 - Feature component: hard limit 250 lines, target 100–150 - Atom/shared component: hard limit 150 lines - If over limit: extract sub-components, move constants to `*Constants.ts`, move helpers to `*Utils.ts` (`.tsx` if JSX) - [ ] **Is business logic inside JSX?** - `.filter()`, `.sort()`, `.reduce()`, conditional rendering based on computed values — these belong in `useMemo`, not inline in the return - The render body should read computed values, not compute them - [ ] **Are hardcoded strings present?** - Status labels (`'ACTIVE'`, `'Aktiv'`, `'PROPERTY_MANAGER'`) must come from `enums.ts` or `constants.ts` - Route paths must come from `ROUTES` constants, not string literals - German UI labels for enums live in `constants.ts` (e.g., `ASSET_TYPE_LABELS`) - [ ] **Are hardcoded colors or thresholds present?** - Colors like `'#1a7a4a'`, `'#dc2626'` directly in `sx` props → use `DS_COLORS` from `lib/ds.ts` - Score thresholds like `score >= 80` → use `SCORE_STRONG` from `constants.ts` - Confidence thresholds like `conf >= 0.75` → use `CONF_HIGH` from `constants.ts` - [ ] **Are duplicate result type labels/colors defined locally?** - `RESULT_TYPE_META` in `lib/ds.ts` is the single source of truth - `RESULT_TYPE_LABEL` / `RESULT_TYPE_COLOR` inline objects anywhere else → delete them --- ## B. State & Data - [ ] **Is server data stored in Zustand?** - Any `useEffect(() => setFoo(data), [data])` pattern that mirrors React Query data into local/Zustand state → remove it, read directly from the query - Server data lives exclusively in the React Query cache - [ ] **Is derived state stored in state?** - `const [count, setCount] = useState(0)` + `useEffect` to keep it in sync → compute inline or use `useMemo` - Exception: expensive computations may use `useMemo` - [ ] **Is the full Zustand store subscribed to without a selector?** - `const { a, b, c } = useMyStore()` — re-renders on every store mutation - Use `useMyStore(s => s.a)` for single fields, `useShallow` for multiple fields - [ ] **Is there a side effect in the render body?** - `queryClient.invalidateQueries(...)` called during render → must be in `useEffect` - Any `console.log`, analytics call, or mutation triggered directly in render body → `useEffect` - [ ] **Are all hooks called before conditional early returns?** - React rules of hooks: all `useState`, `useMemo`, `useCallback`, `useQuery` calls must appear before any `if (isLoading) return ...` --- ## C. Type Safety - [ ] **Are there `any` casts?** - `as any`, `: any`, `// @ts-ignore` — fix the type or use `unknown` with a type guard - One exception: bridging untyped third-party code — must be isolated in a named adapter - [ ] **Are domain types re-defined locally?** - Any interface or type that duplicates something in `src/domain/` → import from domain instead - [ ] **Are response types used?** - Service functions should return `ListResponse` or `ItemResponse` from `services/types.ts` - [ ] **Do `.tsx` files contain JSX and `.ts` files not?** - Any file with JSX (``, ``, ``) must have `.tsx` extension - Plain TypeScript utilities use `.ts` --- ## D. Layering & Architecture - [ ] **Is business logic in a component?** - Scoring calculations, ranking, filtering rules, AI calls, data transformation → move to service or `features/` layer - Components only render and dispatch - [ ] **Is a provider called directly from a component or hook?** - `MockupPropertyProvider.getAll()` called in a hook → must go through `propertyService` - All provider access is mediated by a service - [ ] **Is an LLM API called directly?** - Any `fetch` to an AI API, or direct `OpenAI` / `openrouter` SDK calls outside `src/services/ai/` → move behind `IAIService` - [ ] **Are there cross-workspace component imports?** - A supply component importing from `components/demand/` or vice versa → use `components/shared/` or `components/ui/` instead - [ ] **Are the import direction rules respected?** - `services/` must not import React hooks - `domain/` must not import anything from `src/` - `lib/constants.ts` must not import from components or services - No circular imports --- ## E. Loading, Error & Empty States - [ ] **Does every data-dependent view handle loading?** - Every `useQuery` that drives a list or table must show a skeleton or spinner while `isLoading` - Use the appropriate skeleton component (`FeedSkeleton`, `ReminderSkeleton`, etc.) - [ ] **Does every data-dependent view handle error?** - `isError` from `useQuery` must show `` - Never leave an error state blank or silently swallowed - [ ] **Does every list handle the empty state?** - Empty results after filtering → `` with a reset action - Empty because no data exists → `` --- ## F. Performance - [ ] **Are list items wrapped in `React.memo`?** - Components rendered inside `.map()` in feeds, tables, or grids → should be `memo()` - Without memo, every parent state change (e.g., filter toggle, `selectedId`) re-renders the entire list - [ ] **Are callbacks to memoized children wrapped in `useCallback`?** - Passing `() => setSelectedId(id)` inline as a prop to a `memo()`-wrapped child → the memo is broken - Wrap with `useCallback` and explicit dependency array - [ ] **Are multiple aggregations over the same list collapsed into one pass?** - Three separate `useMemo` calls that each `.filter()` the same array → collapse into one `useMemo` with a `for` loop - [ ] **Is `useMemo` used for expensive filters and sorts?** - `.filter()` + `.sort()` over 25+ items in the render body → `useMemo` - Decision aggregates (matchReady count, criticalGaps count, etc.) in `Properties.tsx` → already in `useMemo` - [ ] **Are routes lazy-loaded?** - All page imports in `App.tsx` must use `React.lazy()` — never static imports for route components --- ## G. Explainability & Trust - [ ] **Does every score shown have a breakdown path?** - A match score badge with no way to see *why* it's that score → add `ScoreBreakdownPanel` or link to MatchDetail - [ ] **Are confidence levels shown proactively?** - Data with `confidenceScore < CONF_MEDIUM` or `freshness === 'STALE'` → must be visually flagged - `ConfidenceBadge`, `FreshnessIndicator`, `DataQualityBar` exist for this purpose - [ ] **Are Future Availability signals clearly labeled as probabilistic?** - `FUTURE_AVAILABILITY` results must always show `probabilityScore` and `signalBasis` - They must never look identical to confirmed availability listings - [ ] **Does AI output show its confidence?** - AI-extracted need criteria → shown with confidence indicator - AI match suggestions → shown with `MAISON_WORK` badge and probability --- ## H. Security - [ ] **Is the React Query cache cleared on logout?** - Logout flow must call `queryClient.clear()` — otherwise stale data from previous user persists - [ ] **Are role checks using `permissions.ts` helpers?** - `user.role === 'PROPERTY_MANAGER'` inline → use `canViewMatchCenter(user)` from `lib/permissions.ts` - [ ] **Are workspace route guards in place?** - Every supply/demand/ops route wrapped in `` - Never guard with `if (user.workspace !== ...)` inside a component body - [ ] **Is sensitive data stored in Zustand?** - PII, tokens, and sensitive fields beyond current-user identity should not persist in client state --- ## I. Backend-Readiness - [ ] **Is the provider interface complete?** - Any new entity that creates a mock shortcut (array mutation inline, no interface) → add `I[Entity]Provider` and `Mockup[Entity]Provider` - [ ] **Are async patterns consistent?** - All provider methods are `async` — if a new provider method is synchronous, fix it before it gets called from a service - [ ] **Are query keys stable and specific?** - Query key `['properties']` is fine for list; `['properties', id]` for single item - Keys that include derived or computed objects (not primitives) will break cache lookup - [ ] **Are stale times centralized?** - No `staleTime: 60_000` or `staleTime: 5 * 60 * 1000` inline in hook files - Use `STALE_PROPERTIES`, `STALE_MATCHES`, etc. from `src/lib/constants.ts` --- ## J. AI & Model Governance - [ ] **Is the AI call going through `IAIService`?** - Direct fetch to AI API anywhere outside `src/services/ai/` → move it - [ ] **Is the AI response validated with Zod before use?** - Raw LLM string parsed inline with `JSON.parse()` without a schema → add Zod validation - [ ] **Is the AI output logged via `aiMonitoringService`?** - Any AI call that influences a business decision (need parsing, match recommendation, market signal) → log it - [ ] **Does the AI flow have a fallback?** - If `aiService` throws or returns `confidence < threshold`, is there a manual entry path? - AI failures must never block the user workflow --- ## Quick Checklist — Pre-Commit Before every commit, verify: ``` [ ] npx tsc --noEmit → zero errors [ ] No new hardcoded colors, routes, or role strings [ ] No business logic added to a JSX return block [ ] No React Query data mirrored into Zustand or useState [ ] All hooks called before early returns [ ] New list-item components wrapped in React.memo [ ] New provider methods are async and match the interface [ ] New feature goes through Provider → Service → Hook → Component [ ] Loading, error, and empty states handled for any new data-fetching view [ ] File with JSX has .tsx extension ```