267 lines
9.2 KiB
Markdown
267 lines
9.2 KiB
Markdown
# 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*
|