docs: map existing codebase

This commit is contained in:
2026-03-11 18:20:14 +01:00
parent 04cbb846d1
commit b9e4d82d1a
7 changed files with 1121 additions and 0 deletions

View File

@@ -0,0 +1,266 @@
# Coding Conventions
**Analysis Date:** 2026-03-11
## Naming Patterns
**Files:**
- React components: PascalCase (e.g., `AppLayout.tsx`, `LoginPage.tsx`)
- TypeScript utilities/libraries: camelCase (e.g., `useAuth.ts`, `api.ts`, `utils.ts`)
- Go packages: lowercase, no underscore (e.g., `auth`, `db`, `models`, `api`)
- Go files: descriptive lowercase (e.g., `handlers.go`, `queries.go`, `auth.go`)
- Test files: None currently present (testing infrastructure not yet established)
**Functions:**
- React hooks: `use` prefix in camelCase (e.g., `useAuth()`, `useBudgets()`)
- Go receiver methods: ExportedName style on receiver (e.g., `(h *Handlers) Register`)
- Go internal functions: camelCase for unexported, PascalCase for exported (e.g., `writeJSON()`, `ListCategories()`)
- TypeScript functions: camelCase (e.g., `request<T>()`, `cn()`)
**Variables:**
- Local variables: camelCase (e.g., `userID`, `displayName`, `carryover`)
- Constants: UPPER_SNAKE_CASE in Go (e.g., `CategoryBill`, `userIDKey`), camelCase in TypeScript (e.g., `API_BASE`)
- React state: camelCase for state and setter (e.g., `[user, setUser]`, `[loading, setLoading]`)
**Types:**
- TypeScript interfaces: PascalCase, no `I` prefix (e.g., `User`, `Category`, `Budget`, `BudgetItem`)
- TypeScript type aliases: PascalCase (e.g., `CategoryType` = 'bill' | 'variable_expense' | ...)
- Go structs: PascalCase (e.g., `User`, `Category`, `Budget`)
- Go type enums: PascalCase with prefixed constants (e.g., `CategoryType`, `CategoryBill`, `CategoryIncome`)
## Code Style
**Formatting:**
- TypeScript: Vite default (2-space indentation implied by no explicit prettier config)
- Go: `gofmt` standard (1-tab indentation)
- Language-specific linters enforced via `npm run lint` (ESLint) and `go vet`
**Linting:**
- Frontend: ESLint with TypeScript support (`eslint.config.js`)
- Extends: `@eslint/js`, `typescript-eslint`, `eslint-plugin-react-hooks`, `eslint-plugin-react-refresh`
- Targets: All `.ts` and `.tsx` files
- Rules enforced: React hooks rules, React refresh rules
- Go: Standard `go vet` (no additional linter configured)
**TypeScript Compiler Settings:**
- `strict: true` - Full strict type checking enabled
- `noUnusedLocals: true` - Unused variables are errors
- `noUnusedParameters: true` - Unused parameters are errors
- `noFallthroughCasesInSwitch: true` - Switch cases must explicitly break or return
- `noUncheckedSideEffectImports: true` - Side-effect imports must be explicit
- Target: ES2022, Module: ESNext
## Import Organization
**TypeScript Order:**
1. Third-party React/UI imports (e.g., `from 'react'`, `from 'react-router-dom'`)
2. Component/utility imports from `@/` alias
3. Type imports (e.g., `type User`)
4. CSS/global imports (e.g., `'@/i18n'`)
Example from `src/components/AppLayout.tsx`:
```typescript
import { useTranslation } from 'react-i18next'
import { Link, useLocation } from 'react-router-dom'
import { LayoutDashboard, Tags, Settings, LogOut } from 'lucide-react'
import { Sidebar, ... } from '@/components/ui/sidebar'
import { Button } from '@/components/ui/button'
import type { AuthContext } from '@/hooks/useAuth'
```
**Go Import Order:**
1. Standard library imports (e.g., `"context"`, `"encoding/json"`)
2. Third-party imports (e.g., `"github.com/go-chi/chi/v5"`)
3. Internal imports (e.g., `"simplefinancedash/backend/internal/..."`)
Example from `backend/internal/api/handlers.go`:
```go
import (
"encoding/json"
"net/http"
"time"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"simplefinancedash/backend/internal/auth"
)
```
**Path Aliases:**
- TypeScript: `@/*` maps to `./src/*` (defined in `tsconfig.json`)
- Go: Full module paths from module name `simplefinancedash/backend`
## Error Handling
**TypeScript Patterns:**
- Silent error suppression with fallback (preferred for non-critical operations):
```typescript
try {
const u = await auth.me()
setUser(u)
} catch {
setUser(null) // Fallback without logging
}
```
- Custom error class for API errors:
```typescript
export class ApiError extends Error {
status: number
constructor(status: number, message: string) {
super(message)
this.name = 'ApiError'
this.status = status
}
}
```
- HTTP response error handling in `request<T>()`:
```typescript
if (!res.ok) {
const body = await res.json().catch(() => ({}))
throw new ApiError(res.status, body.error || res.statusText)
}
```
**Go Patterns:**
- Wrap errors with context using `fmt.Errorf("context: %w", err)` for traceability:
```go
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", fmt.Errorf("hashing password: %w", err)
}
```
- Return early on error in handlers:
```go
if err != nil {
writeError(w, http.StatusBadRequest, "invalid request body")
return
}
```
- Silent errors in non-critical flows with zero-value fallback:
```go
var exists bool
err := pool.QueryRow(...).Scan(&exists)
if err != nil {
exists = false // Assume false on error
}
```
## Logging
**Framework:** No structured logging library. Uses `log` package in Go, `console` in TypeScript.
**Patterns:**
- Go: `log.Printf()` for informational messages (e.g., "Server starting on :8080"), `log.Fatalf()` for fatal errors during startup
- Located in `backend/cmd/server/main.go`
- TypeScript: No logging in production code. Errors silently caught and handled with UI fallbacks.
## Comments
**When to Comment:**
- Go: Comments for exported types/functions (standard Go convention)
- Comment each exported function above its definition
- Used in `backend/internal/auth/auth.go` for public auth functions
- TypeScript: Minimal comments; code clarity preferred
- JSDoc comments not currently used
- Comments only for non-obvious logic or integration details
**Current Usage:**
- Go handler comments: "// Auth Handlers", "// Helpers" as section dividers in `api/handlers.go`
- TypeScript code: Inline comments absent (code is self-documenting with clear naming)
## Function Design
**Size:**
- Go handler functions: 10-50 lines (moderate size with early returns for validation)
- Go query functions: Variable (from 5 lines for simple queries to 30+ for complex operations like `GetBudgetWithItems()`)
- TypeScript hooks: 20-40 lines per hook (following React patterns with useState/useEffect)
**Parameters:**
- Go: Receiver pattern for struct methods, context as first parameter in database functions
- Example: `func (q *Queries) CreateUser(ctx context.Context, email, passwordHash, displayName, locale string) (*models.User, error)`
- TypeScript: Props as single typed object, destructured in function signature
- Example: `export function AppLayout({ auth, children }: Props)`
- React hooks: No parameters passed to hooks; they use internal state
**Return Values:**
- Go: Multiple returns with error as last return (e.g., `(*models.User, error)`)
- TypeScript: Single return object containing multiple pieces of state/functions
- Example: `useBudgets()` returns `{ list, current, loading, fetchList, selectBudget, setCurrent }`
## Module Design
**Exports:**
- Go: Exported types/functions start with capital letter (PascalCase)
- Non-exported functions use camelCase
- Pattern: Define helpers after main exports in same file
- TypeScript: Named exports for components/utilities, default exports for page components
- Example page export: `export default function App()`
- Example utility export: `export class ApiError extends Error`
- Example hook export: `export function useAuth()`
**Barrel Files:**
- No barrel files currently used (no `index.ts` re-exports in `src/lib`, `src/hooks`, etc.)
- Each utility/hook imported directly from its file
- Components from shadcn/ui imported from `@/components/ui/[component-name]`
## API Client Pattern
**Location:** `frontend/src/lib/api.ts`
**Structure:**
```typescript
// 1. Helper function for all requests
async function request<T>(path: string, options?: RequestInit): Promise<T> { ... }
// 2. Custom error class
export class ApiError extends Error { ... }
// 3. Type definitions
export interface User { ... }
export interface Category { ... }
// 4. Namespace objects for endpoint groups
export const auth = {
register: (...) => request<User>(...),
login: (...) => request<User>(...),
...
}
export const categories = {
list: () => request<Category[]>(...),
create: (...) => request<Category>(...),
...
}
```
This pattern provides:
- Type safety with generic `request<T>`
- Automatic error handling and JSON parsing
- Organized endpoint grouping by resource
- Consistent authentication (cookies via `credentials: 'include'`)
## React Component Pattern
**Props Pattern:**
- Define interface above component: `interface Props { ... }`
- Destructure in function signature: `export function Component({ prop1, prop2 }: Props)`
- Type children explicitly: `children: React.ReactNode`
Example from `AppLayout.tsx`:
```typescript
interface Props {
auth: AuthContext
children: React.ReactNode
}
export function AppLayout({ auth, children }: Props) { ... }
```
**Hook Usage:**
- Always call hooks at top level of component
- Use `useCallback` for memoized functions passed to child components
- Combine related state with custom hooks (e.g., `useAuth()`, `useBudgets()`)
---
*Convention analysis: 2026-03-11*