258 lines
8.6 KiB
Markdown
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*
|