Files
SimpleFinanceDash/.planning/codebase/CONVENTIONS.md

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

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:

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:

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() 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:

const canSave =
  Boolean(categoryId) &&
  Boolean(amount) &&
  !isNaN(parseFloat(amount)) &&
  parseFloat(amount) >= 0

Convention analysis: 2026-03-16