|
| 1 | +# Agent Guidelines for Annum |
| 2 | + |
| 3 | +This document provides coding agents with essential information about the Annum codebase structure, conventions, and workflows. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +Annum is a SvelteKit application that visualizes Trakt.tv watch history. It uses: |
| 8 | +- **SvelteKit** (v5) with TypeScript |
| 9 | +- **Vite** for bundling |
| 10 | +- **Vitest** for testing |
| 11 | +- **pnpm** as package manager |
| 12 | +- **Auth.js** (@auth/sveltekit) for authentication |
| 13 | +- **Netlify** for deployment |
| 14 | + |
| 15 | +## Build, Test & Lint Commands |
| 16 | + |
| 17 | +```bash |
| 18 | +# Development |
| 19 | +pnpm dev # Start dev server on port 5173 |
| 20 | +pnpm build # Create production build |
| 21 | +pnpm preview # Preview production build locally |
| 22 | + |
| 23 | +# Type Checking & Linting |
| 24 | +pnpm check # Run svelte-check for type errors |
| 25 | +pnpm check:watch # Run svelte-check in watch mode |
| 26 | +pnpm typecheck # Run TypeScript compiler without emitting files |
| 27 | +pnpm lint # Run ESLint |
| 28 | +pnpm lint:fix # Run ESLint with auto-fix |
| 29 | + |
| 30 | +# Testing |
| 31 | +pnpm test # Run tests in watch mode |
| 32 | +pnpm test:ci # Run tests once (for CI) |
| 33 | +pnpm test:coverage # Run tests with coverage report |
| 34 | + |
| 35 | +# Run a single test file |
| 36 | +pnpm vitest src/lib/utils/__tests__/index.ts |
| 37 | + |
| 38 | +# Run a single test by name pattern |
| 39 | +pnpm vitest -t "chunks" |
| 40 | +``` |
| 41 | + |
| 42 | +## Code Style Guidelines |
| 43 | + |
| 44 | +### ESLint Configuration |
| 45 | + |
| 46 | +The project uses `@antfu/eslint-config` with custom overrides: |
| 47 | + |
| 48 | +- **Indentation**: Tabs (not spaces) |
| 49 | +- **Quotes**: Single quotes (avoid escape when necessary) |
| 50 | +- **Semicolons**: No semicolons |
| 51 | +- **Array Types**: Use generic syntax `Array<T>` not `T[]` |
| 52 | + |
| 53 | +### TypeScript |
| 54 | + |
| 55 | +- **Strict mode enabled**: All strict TypeScript checks are on |
| 56 | +- **No `{}` or `object` types**: Use `Record<string, unknown>` instead |
| 57 | +- **Unused variables**: Prefix with underscore `_` to indicate intentionally unused (e.g., `_variable`, `_arg`) |
| 58 | +- **Type imports**: Use `import type` for type-only imports |
| 59 | +- **No `any`**: Avoid using `any` type; use proper types or `unknown` |
| 60 | + |
| 61 | +### Naming Conventions |
| 62 | + |
| 63 | +- **Files**: Use kebab-case for files (e.g., `user-stats.ts`) |
| 64 | +- **Components**: PascalCase for Svelte components (e.g., `Primary.svelte`) |
| 65 | +- **Functions**: camelCase for functions (e.g., `normalizeItem`) |
| 66 | +- **Constants**: SCREAMING_SNAKE_CASE for top-level constants (e.g., `TRAKT_BASE_URL`) |
| 67 | +- **Types/Interfaces**: PascalCase (e.g., `TraktHistoryItem`) |
| 68 | + |
| 69 | +### Imports |
| 70 | + |
| 71 | +Order imports by: |
| 72 | +1. Type imports (using `import type`) |
| 73 | +2. External dependencies |
| 74 | +3. Internal modules (using SvelteKit aliases) |
| 75 | + |
| 76 | +Example: |
| 77 | + |
| 78 | +```typescript |
| 79 | +import type { Language, NormalizedItemResponse } from '$lib/types' |
| 80 | +import { page } from '$app/state' |
| 81 | +import { normalizeItem } from '$lib/utils' |
| 82 | +import { signIn } from '@auth/sveltekit/client' |
| 83 | +``` |
| 84 | + |
| 85 | +### SvelteKit Path Aliases |
| 86 | + |
| 87 | +- `$lib/*` → `src/lib/*` |
| 88 | +- `$assets` → `src/assets` |
| 89 | +- `$const` → `src/const.ts` |
| 90 | +- `$app/*` → SvelteKit internals (state, navigation, stores, etc.) |
| 91 | + |
| 92 | +### Environment Variables |
| 93 | + |
| 94 | +- All private environment variables must use `PRIVATE_` prefix (configured in `svelte.config.js`) |
| 95 | +- Example: `PRIVATE_TRAKT_CLIENT_ID`, `PRIVATE_AUTH_SECRET` |
| 96 | + |
| 97 | +## Function Documentation |
| 98 | + |
| 99 | +All utility functions should include JSDoc comments with: |
| 100 | +- Description of what the function does |
| 101 | +- `@example` tag showing usage |
| 102 | + |
| 103 | +Example: |
| 104 | + |
| 105 | +```typescript |
| 106 | +/** |
| 107 | + * Split an array into chunks of a given size |
| 108 | + * @example chunks([1, 2, 3, 4, 5], 2) => [[1, 2], [3, 4], [5]] |
| 109 | + */ |
| 110 | +export function chunks<T>(array: Array<T>, number: number | string): Array<Array<T>> |
| 111 | +``` |
| 112 | + |
| 113 | +## Error Handling |
| 114 | + |
| 115 | +- Use SvelteKit's `error()` helper for HTTP errors |
| 116 | +- Include helpful error messages with context |
| 117 | +- Log warnings to console for non-critical issues (e.g., missing TMDB IDs) |
| 118 | + |
| 119 | +Example: |
| 120 | +```typescript |
| 121 | +if (!session?.user) |
| 122 | + error(401, 'You must sign in to access this route.') |
| 123 | +``` |
| 124 | + |
| 125 | +## Testing Patterns |
| 126 | + |
| 127 | +- **Test files**: Located in `__tests__/` directories alongside source files |
| 128 | +- **Fixtures**: Store test data in `__fixtures__/` directories |
| 129 | +- **File pattern**: `src/**/__tests__/*.ts` |
| 130 | +- **Coverage**: Includes `src/lib/utils/*.ts` and `src/lib/actions.ts` |
| 131 | + |
| 132 | +Test structure: |
| 133 | + |
| 134 | +```typescript |
| 135 | +import { describe, expect, it } from 'vitest' |
| 136 | +
|
| 137 | +describe('functionName', () => { |
| 138 | + it('should describe expected behavior', () => { |
| 139 | + const result = functionName(input) |
| 140 | + expect(result).toBe(expected) |
| 141 | + }) |
| 142 | +}) |
| 143 | +``` |
| 144 | + |
| 145 | +## Svelte 5 Patterns |
| 146 | + |
| 147 | +- Use Svelte 5 runes: `$state`, `$derived`, `$effect`, `$props` |
| 148 | +- Access page store with `page` from `$app/state` |
| 149 | +- Use `<script lang='ts'>` for TypeScript in Svelte files |
| 150 | +- For PostCSS in styles: `<style lang='postcss'>` |
| 151 | + |
| 152 | +## API Routes |
| 153 | + |
| 154 | +- API routes in `src/routes/api/` |
| 155 | +- Use `RequestHandler` type from `./$types` |
| 156 | +- Set cache headers using `setHeaders()` |
| 157 | +- Return responses with `json()` helper |
| 158 | +- Check authentication with `await locals.auth()` |
| 159 | + |
| 160 | +Example structure: |
| 161 | + |
| 162 | +```typescript |
| 163 | +export const GET: RequestHandler = async ({ locals, url, fetch, setHeaders }) => { |
| 164 | + const session = await locals.auth() |
| 165 | + if (!session?.user) |
| 166 | + error(401, 'You must sign in to access this route.') |
| 167 | +
|
| 168 | + setHeaders({ ...DEFAULT_CACHE_HEADER }) |
| 169 | +
|
| 170 | + // Implementation |
| 171 | + return json({ data }) |
| 172 | +} |
| 173 | +``` |
| 174 | + |
| 175 | +## Constants |
| 176 | + |
| 177 | +Define all constants in `src/const.ts` using: |
| 178 | +- `as const` for literal types |
| 179 | +- `satisfies` for type checking without widening |
| 180 | + |
| 181 | +Example: |
| 182 | + |
| 183 | +```typescript |
| 184 | +export const TMDB_FETCH_DEFAULTS = { |
| 185 | + method: 'GET', |
| 186 | + headers: { 'user-agent': 'annum' }, |
| 187 | +} satisfies RequestInit |
| 188 | +``` |
| 189 | + |
| 190 | +## Common Patterns |
| 191 | + |
| 192 | +### Type Guards |
| 193 | + |
| 194 | +```typescript |
| 195 | +function isTraktWatchedItem(item: TraktHistoryItem | TraktWatchedItem): item is TraktWatchedItem { |
| 196 | + return !('type' in item) |
| 197 | +} |
| 198 | +``` |
| 199 | + |
| 200 | +### Generic Utility Types |
| 201 | + |
| 202 | +```typescript |
| 203 | +type Filter<T> = MapValuesToKeysIfAllowed<T>[keyof T] |
| 204 | +export function groupBy<T extends Record<PropertyKey, any>, Key extends Filter<T>>( |
| 205 | + arr: Array<T>, |
| 206 | + key: Key, |
| 207 | +): Record<T[Key], Array<T>> |
| 208 | +``` |
| 209 | + |
| 210 | +## Git & Deployment |
| 211 | + |
| 212 | +- Deployed on Netlify (adapter: `@sveltejs/adapter-netlify`) |
| 213 | +- Redirects configured in `_redirects` file |
| 214 | +- Site manifest: `static/site.webmanifest` |
| 215 | + |
| 216 | +## Additional Notes |
| 217 | + |
| 218 | +- Use `enhancedImages()` from `@sveltejs/enhanced-img` for optimized images |
| 219 | +- Custom media queries defined in `src/styles/custom-media-queries.css` |
| 220 | +- CSS variables in `src/styles/variables.css` |
| 221 | +- Reset styles in `src/styles/reset.css` |
0 commit comments