trails/CLAUDE.md
Ullrich Schäfer f2f0bd31ae
Add testing expectations to CLAUDE.md
Explicit instruction for coding agents: write tests alongside code,
run test suites before committing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 12:39:10 +01:00

5 KiB

CLAUDE.md

Project Overview

trails.cool is a federated, self-hostable platform for outdoor enthusiasts with two apps:

  • Planner (apps/planner) — Stateless collaborative route editor. Real-time editing via Yjs, routing via BRouter, no user accounts, sessions are anonymous and ephemeral.
  • Journal (apps/journal) — Federated social platform for routes and activities. User accounts, ActivityPub federation via Fedify, PostgreSQL + PostGIS.

Full architecture: docs/architecture.md Philosophy: docs/philosophy.md OpenSpec change: openspec/changes/phase-1-mvp/

Principles

  • Privacy-first: The Planner collects zero user data. The Journal documents all data collection in a privacy manifest. Never add tracking, analytics, or data collection without updating the manifest.
  • Data ownership: All user data must be exportable in open formats (GPX, JSON). Never create data lock-in.
  • Simplicity: Start with the simplest thing that works. Don't add abstractions, config options, or features unless real users need them.
  • Open standards: Use GPX, ActivityPub, OpenStreetMap, WebFinger. Don't invent proprietary formats.
  • Inclusive language: Use "host" not "master", "allowlist" not "whitelist", etc.

Tech Stack

  • Language: TypeScript (strict mode)
  • Frontend: React + Tailwind CSS + React Router 7 (Remix stack)
  • Maps: Leaflet + OpenStreetMap tiles
  • CRDT: Yjs + y-websocket (Planner only)
  • Federation: Fedify (Journal only, Phase 2)
  • Database: PostgreSQL + PostGIS
  • Media storage: S3-compatible (Garage)
  • Routing engine: BRouter (Java, runs as separate Docker container)
  • i18n: react-i18next (English + German)
  • Monorepo: pnpm workspaces + Turborepo

Repository Structure

apps/
  planner/          — Planner app (React Router 7)
  journal/          — Journal app (React Router 7 + Fedify)
packages/
  types/            — Shared TypeScript interfaces (Route, Activity, Waypoint)
  ui/               — Shared React components (Tailwind)
  map/              — Leaflet map wrappers and tile layer configs
  gpx/              — GPX parsing, generation, validation
  i18n/             — react-i18next config + translations
infrastructure/     — Terraform + Docker Compose
openspec/           — OpenSpec specs and changes
docs/               — Architecture, philosophy, tooling docs
docker/brouter/     — BRouter Docker image

Development Commands

pnpm install          # Install dependencies
turbo dev             # Start both apps in dev mode
turbo build           # Build all packages and apps
turbo typecheck       # Type-check all packages
turbo lint            # Lint all packages
pnpm test             # Run unit tests (vitest)
pnpm test:watch       # Run unit tests in watch mode
pnpm test:e2e         # Run E2E tests (playwright, requires dev servers)
pnpm test:e2e:ui      # Run E2E tests with Playwright UI

Testing Strategy

  • Unit tests (Vitest + jsdom): For packages, components, utilities, and app logic. Place test files next to source: foo.tsfoo.test.ts. Uses @testing-library/react for component tests.
  • E2E tests (Playwright): For browser behavior across both apps. Tests live in e2e/ at repo root. Scoped per app via testMatch in playwright.config.ts. Playwright auto-starts dev servers if not already running.

Important: Write tests alongside implementation, not as an afterthought. When implementing a package or utility, add a co-located *.test.ts file. When implementing a user-facing feature, add or update E2E tests. Run pnpm test and pnpm test:e2e before committing.

Code Conventions

  • All user-facing strings must use i18n (useTranslation() hook, never hardcode strings)
  • Use @trails-cool/types for shared interfaces — don't duplicate type definitions
  • Map components go in @trails-cool/map, not in individual apps
  • GPX parsing/generation goes in @trails-cool/gpx
  • Database schemas: planner.* for Planner data, journal.* for Journal data
  • Route geometry must be stored as PostGIS LineString (extracted from GPX on save)

Key Architecture Decisions

  • Planner is stateless: No user accounts, no persistent user data. Sessions are anonymous.
  • Journal is the source of truth: Routes live on the owner's Journal instance.
  • Routing host pattern: One client per Planner session talks to BRouter (elected via Yjs awareness).
  • JWT callbacks: Planner saves back to Journal via scoped JWT tokens in callback URLs.
  • Sequential versioning: Route versions are v1, v2, v3. Yjs state vectors enable conflict-free merging.
  • Single domain: Each instance uses one domain for both web UI and ActivityPub handles.
  • Simple permissions: View + Edit only. No fine-grained permissions.

OpenSpec Workflow

Specs live in openspec/. Use these slash commands:

  • /opsx:propose — Create a new change with proposal, design, specs, and tasks
  • /opsx:apply — Implement tasks from an existing change
  • /opsx:explore — Think through ideas before proposing
  • /opsx:archive — Archive a completed change