# Coding Conventions **Analysis Date:** 2026-03-16 ## Naming Patterns **Files:** - React components: PascalCase with .tsx extension - `QuickAddPicker.tsx`, `LoginPage.tsx` - Custom hooks: camelCase starting with "use" with .ts extension - `useAuth.ts`, `useCategories.ts`, `useBudgets.ts` - Utilities and type files: camelCase with .ts extension - `utils.ts`, `types.ts`, `format.ts`, `palette.ts` - UI components: lowercase with hyphens for compound names - `button.tsx`, `dropdown-menu.tsx`, `alert-dialog.tsx` - Directories: lowercase with hyphens for multi-word names - `components/ui`, `pages`, `hooks`, `lib` **Functions:** - React components: PascalCase - `QuickAddPicker`, `LoginPage`, `AppLayout` - Hook functions: camelCase with "use" prefix - `useAuth()`, `useIsMobile()`, `useBudgets()` - Utility functions: camelCase - `cn()`, `formatCurrency()`, `monthBounds()` - Event handlers: camelCase starting with "handle" - `handlePickItem()`, `handleSave()`, `handleDialogClose()` - Private/internal helpers: lowercase with underscore prefix when needed or nested as sub-functions **Variables:** - State variables: camelCase - `session`, `user`, `loading`, `popoverOpen`, `selectedItem` - Constants: UPPER_SNAKE_CASE - `MOBILE_BREAKPOINT`, `CATEGORY_TYPES`, `BUDGETS_KEY` - Query keys: lowercase with underscores - `budgets`, `categories`, `templates` - Boolean variables: descriptive names - `isMobile`, `canSave`, `loading`, `isLoading` **Types:** - Interfaces: PascalCase - `Profile`, `Category`, `Budget`, `BudgetItem` - Type unions: PascalCase or vertical bar notation - `type CategoryType = "income" | "bill" | ...` - Generic parameters: single uppercase letter or descriptive PascalCase - `T`, `Data` ## Code Style **Formatting:** - No explicit formatter configured, but code follows consistent patterns - 2-space indentation (standard TypeScript/JavaScript practice) - Multiline imports organized by source type - Trailing commas in multiline arrays and objects - Semicolons at end of statements **Linting:** - Tool: ESLint (version 9.39.4) with Flat Config - Config: `eslint.config.js` - Extends: `@eslint/js`, `typescript-eslint`, `eslint-plugin-react-hooks`, `eslint-plugin-react-refresh` - Key rules enforced: - React hooks best practices (`react-hooks/rules-of-hooks`) - React refresh compatibility checks - TypeScript recommended rules ## Import Organization **Order:** 1. React imports and hooks - `import { useState } from "react"` 2. Third-party libraries - `import { useQuery } from "@tanstack/react-query"` 3. Supabase imports - `import { supabase } from "@/lib/supabase"` 4. Internal types - `import type { Category } from "@/lib/types"` 5. Internal utilities - `import { cn } from "@/lib/utils"` 6. Components - `import { Button } from "@/components/ui/button"` 7. Other imports - hooks, constants, etc. **Path Aliases:** - `@/*` resolves to `./src/*` (configured in `tsconfig.app.json`) - Relative imports used only for co-located files - All absolute imports use the `@/` prefix **Example pattern from `QuickAddPicker.tsx`:** ```typescript import { useState } from "react" import { useTranslation } from "react-i18next" import { Zap } from "lucide-react" import { toast } from "sonner" import { useQuickAdd } from "@/hooks/useQuickAdd" import { useCategories } from "@/hooks/useCategories" import { useBudgets } from "@/hooks/useBudgets" import type { QuickAddItem, CategoryType } from "@/lib/types" import { categoryColors } from "@/lib/palette" import { Button } from "@/components/ui/button" ``` ## Error Handling **Patterns:** - Supabase errors checked with `if (error) throw error` pattern - Async/await used for promises with try/catch at caller level - User-facing errors converted to toast notifications using `sonner` - Authentication errors throw and are caught in component try/catch blocks - Validation errors prevented via state checks before action (`canSave` boolean pattern) **Example from `useCategories.ts`:** ```typescript const { data, error } = await supabase.from("categories").select("*") if (error) throw error return data as Category[] ``` **Example from `LoginPage.tsx`:** ```typescript try { await signIn(email, password) navigate("/") } catch (err) { setError(err instanceof Error ? err.message : t("common.error")) } ``` ## Logging **Framework:** No structured logging library. Uses standard browser console methods implicitly. **Patterns:** - No verbose console.log calls in code - Relies on error boundaries and try/catch for debugging - Toast notifications (via `sonner`) for user feedback on async operations - Translation keys used for all user-facing messages **Example from `QuickAddPicker.tsx`:** ```typescript catch { toast.error(t("common.error")) } ``` ## Comments **When to Comment:** - Complex business logic documented with block comments - Multi-step mutations explained with numbered sections - Props documented with JSDoc-style comments on interfaces - Section separators used to organize large components (see 80+ line files) **JSDoc/TSDoc:** - Used on exported functions and interfaces - Describes purpose, parameters, and return values - Example from `useBudgets.ts`: ```typescript /** * Given a 1-based month and a full year, return ISO date strings for the * first and last day of that month. */ function monthBounds( month: number, year: number ): { start_date: string; end_date: string } ``` **Documentation Comments on Props:** ```typescript interface QuickAddPickerProps { /** The id of the current month's budget to add the item to. */ budgetId: string } ``` ## Function Design **Size:** Functions kept under 100 lines; larger components organized with comment sections **Parameters:** - Single simple types preferred - Object destructuring for multiple related parameters - Type annotations always present for parameters - Optional parameters marked with `?:` **Return Values:** - Hooks return objects with destructurable properties - Components return JSX.Element - Explicit return type annotations on typed functions - null/undefined handled explicitly in return statements **Example pattern from `useBudgets.ts`:** ```typescript return { budgets: budgetsQuery.data ?? [], loading: budgetsQuery.isLoading, getBudget, createBudget, generateFromTemplate, updateItem, createItem, deleteItem, deleteBudget, } ``` ## Module Design **Exports:** - Named exports for utility functions - `export function cn(...)` - Default exports for React components - `export default function QuickAddPicker` - Type-only exports for TypeScript types - `export type CategoryType = ...` - Multiple related hooks exported from single file - `useBudgets()` and `useBudgetDetail()` from same file **Barrel Files:** - Not used; imports reference specific files directly - Example: `import { Button } from "@/components/ui/button"` not from `@/components/ui` **File Organization:** - One main export per file - Related utilities and helpers in same file with clear section comments - Query key constants defined at top of custom hooks before the hook itself ## TypeScript Configuration **Strict Mode:** Enabled (`"strict": true`) - All implicit `any` types caught - Null and undefined checking enforced - Type assertions allowed but discouraged **Key Options (`tsconfig.app.json`):** - Target: ES2023 - Module: ESNext - JSX: react-jsx (React 17+ transform) - Strict mode enabled - `noUnusedLocals` and `noUnusedParameters` enforced - Path aliases configured for `@/*` ## React Patterns **Hooks:** - `useState` for local component state - `useEffect` for side effects with proper cleanup - Custom hooks for data fetching and logic reuse - `useTranslation` from react-i18next for i18n - `useQueryClient` from @tanstack/react-query for cache invalidation **Component Structure:** - Functional components only - Props typed with TypeScript interfaces - Event handlers defined as functions (not inline) - Section comments separate concerns (Constants, Props, Render) **Forms:** - Controlled inputs with onChange handlers - Form submission prevents default with `e.preventDefault()` - Validation done before mutation execution - Error state displayed to user via toast or error field ## Data Validation **Approach:** Type safety first with TypeScript - All data from Supabase cast with `as Type` after type-safe query - Input validation in component state checks before action - Zod not used; relies on TypeScript types and Supabase schema - Client-side checks prevent invalid mutations from firing **Example from `QuickAddPicker.tsx`:** ```typescript const canSave = Boolean(categoryId) && Boolean(amount) && !isNaN(parseFloat(amount)) && parseFloat(amount) >= 0 ``` --- *Convention analysis: 2026-03-16*