8.6 KiB
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
- React hooks best practices (
Import Organization
Order:
- React imports and hooks -
import { useState } from "react" - Third-party libraries -
import { useQuery } from "@tanstack/react-query" - Supabase imports -
import { supabase } from "@/lib/supabase" - Internal types -
import type { Category } from "@/lib/types" - Internal utilities -
import { cn } from "@/lib/utils" - Components -
import { Button } from "@/components/ui/button" - Other imports - hooks, constants, etc.
Path Aliases:
@/*resolves to./src/*(configured intsconfig.app.json)- Relative imports used only for co-located files
- All absolute imports use the
@/prefix
Example pattern from QuickAddPicker.tsx:
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 errorpattern - 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 (
canSaveboolean pattern)
Example from useCategories.ts:
const { data, error } = await supabase.from("categories").select("*")
if (error) throw error
return data as Category[]
Example from LoginPage.tsx:
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:
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:
/**
* 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:
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:
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()anduseBudgetDetail()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
anytypes 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
noUnusedLocalsandnoUnusedParametersenforced- Path aliases configured for
@/*
React Patterns
Hooks:
useStatefor local component stateuseEffectfor side effects with proper cleanup- Custom hooks for data fetching and logic reuse
useTranslationfrom react-i18next for i18nuseQueryClientfrom @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 Typeafter 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:
const canSave =
Boolean(categoryId) &&
Boolean(amount) &&
!isNaN(parseFloat(amount)) &&
parseFloat(amount) >= 0
Convention analysis: 2026-03-16