Files
SimpleFinanceDash/.planning/codebase/CONVENTIONS.md

258 lines
8.6 KiB
Markdown

# 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*