This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Use the Svelte MCP server for any Svelte, SvelteKit, or .svelte/.svelte.ts work:
list-sections— call first to discover docs sections; always start Svelte tasks here.get-documentation— afterlist-sections, inspectuse_casesand fetch all relevant sections at once when possible.svelte-autofixer— use whenever writing or editing Svelte code; iterate until clean.playground-link— only after the user explicitly asks for one; never for code already written to the repo.
Tracktor is a self-hosted vehicle management app (fuel, maintenance, insurance, PUCC/pollution certs, reminders) built with SvelteKit + Svelte 5, Tailwind CSS, SQLite (via @libsql/client), and Drizzle ORM. i18n uses inlang/Paraglide. TypeScript is strict throughout.
- Install:
pnpm install - Dev server:
pnpm dev(host mode) /pnpm local(localhost only) - Build:
pnpm build/ Preview build:pnpm preview - Type/svelte check:
pnpm check(watch:pnpm check:watch) - Lint:
pnpm lint(eslint + prettier check) / Autofix:pnpm format - Test:
pnpm test(watch:pnpm test:watch, coverage:pnpm test:coverage) - DB:
pnpm db:generate(drizzle migration from schema changes),pnpm db:migrate,pnpm db:seed - Clean:
pnpm clean(removes build artifacts, db file, node_modules, etc.)
Always run pnpm check and pnpm lint before considering a change finished. ESLint fails the build on unused imports/vars.
- Run one file:
pnpm vitest --run path/to/file.test.ts - Run by test name:
pnpm vitest --run -t "test name" - Run a folder:
pnpm vitest --run src/__tests__/feature
Note: test coverage is currently minimal (essentially a placeholder in src/__tests__/index.test.ts) — don't assume extensive existing test patterns exist for a given module.
$lib → src/lib, $ui → src/lib/components/ui, $appui → src/lib/components/app, $layout → src/lib/components/layout, $feature → src/lib/components/feature, $stores → src/lib/stores, $services → src/lib/services, $helper → src/lib/helper, $dashboard → src/lib/components/dashboard, $server → src/server. Prefer these aliases over long relative paths.
src/lib/services/*.service.ts— browser/client-side code. Calls the app's own/api/*REST endpoints via$lib/helper/api.helper(apiClient) and returns aResponse<T>shape ({ status: 'OK' | 'ERROR', data?, error? }). Used from.sveltepages/components.src/server/services/*Service.ts— server-only code. Talks directly to the Drizzle DB (src/server/db), does business logic, and is called from+server.tsroute handlers (or+page.server.ts). Never import these from client-facing.sveltecode.
src/lib/domain/* holds shared types/models and pure business rules (e.g. domain/fuel/mileage.ts mileage math) usable from both client and server code.
src/hooks.server.ts runs one-time app init (ensure directories, initializeDatabase() — runs Drizzle migrations, seeding, then patches — and starts the notification scheduler cron) and wires a MiddlewareChain (src/server/middlewares, chain-of-responsibility pattern via BaseMiddleware/setNext): CorsMiddleware → AuthMiddleware → RateLimitMiddleware → LoggingMiddleware. AuthMiddleware checks session cookie/Bearer token against authService, bypassing /api/auth, /api/health, /api/config/branding, and everything when TRACKTOR_DISABLE_AUTH/env.DISABLE_AUTH is set.
- Drizzle schema lives in
src/server/db/schema/*.ts(one file per domain entity:vehicle,fuel-log,insurance,maintenance-logs,pucc,reminder,notification,notification-provider,config,audit,auth). - SQLite dialect,
snake_casecolumn casing, migrations generated tosrc/server/db/migrationsviapnpm db:generate— never hand-edit generated migrations. - One-off data fixups live under
src/server/db/patchand run viaapplyPatches()at startup, after migrations/seeding.
src/routes/(app)/*— authenticated app pages (dashboard, fuel, maintenance, insurance, pollution, reminders, vehicles, expenses, reports, settings), sharing the app shell (AppSidebar,+layout.svelte).src/routes/(auth)/*— login/register, outside the app shell.src/routes/api/*— REST endpoints as+server.tsfiles; vehicle-scoped resources nest underapi/vehicles/[id]/....
The app recently moved from a /dashboard/* nested-route structure to top-level feature routes (/fuel, /insurance, /maintenance, /pollution, /reminders) with a single sidebar app shell — the old dashboard/(feature) routes are being removed in favor of this flatter structure with fleet-wide/vehicle-selector support baked into each page.
src/lib/components/ui is a shadcn-svelte install (components.json, baseColor zinc, registry shadcn-svelte.com) built on bits-ui + tailwind-variants — treat it as generated/vendored (it's excluded from lint) and prefer composing it from $feature/$dashboard/$appui rather than editing it directly. Charts use layerchart/d3-*.
Features (Fuel Log, Maintenance, PUCC, Reminders, Insurance, Overview) are stored as string 'true'/'false' values in the configs table under keys like featureFuelLog, and gated in the UI with the FeatureGate component (feature="fuelLog" or requireAll={[...]}). See docs/feature-toggles.md.
- ESM only (
"type": "module"); usepnpm, not rawnpm/yarn/npx. camelCasefor values/functions,PascalCasefor components/types,SCREAMING_SNAKE_CASEfor constants.- Group imports: external, then aliases, then local relative.
- Assume Svelte 5 runes mode where a file already uses it; keep props/events simple; prefer derived values/helpers over complex template logic; split busy
.sveltemarkup into smaller components. - Fail fast on invalid inputs; prefer existing response/error helpers (
$server/exceptions/AppError,service-response.helper.ts) over ad hoc shapes; narrowunknownbefore accessing API/request/JSON values. - Respect the boundaries between
src/lib/domain,src/lib/services,src/server/services, andsrc/lib/componentsdescribed above. - Keep translation changes in
messages/aligned with inlang; regenerate via the Paraglide vite plugin (runs automatically throughvite dev/vite build). - Add comments only when something is genuinely non-obvious.