Files
pocket-id-pocket-id/AGENTS.md
2026-07-11 16:16:05 +02:00

4.9 KiB

AGENTS.md

Pocket ID — a passkey-only OIDC provider. Go backend serves a SvelteKit SPA (embedded in the binary for production). This file lists what isn't obvious from reading the code.

Layout

  • backend/ — Go module (gin, GORM, ory/fosite fork). Its own toolchain, not part of the repository.
  • frontend/ — SvelteKit 5 SPA. Builds into backend/frontend/dist and is embedded via go:embed.
  • tests/ — Playwright end-to-end tests (drives a Dockerized full stack).

Build / test / lint

# backend/   — the exclude_frontend and unit tags are mandatory locally; CI uses them too
go test -tags=exclude_frontend,unit ./...                          # unit/integration tests
go test -tags=exclude_frontend,unit -run TestName ./internal/...   # a single test
golangci-lint run  # lint (config: backend/.golangci.yml - includes build tags)

# frontend/  (or root)
pnpm check        # svelte-check — the ONLY frontend type gate (no unit tests exist)
pnpm lint         # prettier --check && eslint  (note: not enforced by CI)
pnpm format       # prettier --write — REQUIRED before opening a PR

End-to-end (needs Docker; stop any local backend on :1411 first — see gotchas):

cd tests/setup && docker compose up -d --build   # rebuild after ANY code change, or you test stale code
cd ../.. && pnpm test                            # = playwright test in tests/

Critical gotchas

  • exclude_frontend and unit build tags. Without them plain go run/go test/golangci-lint fail to run. Always pass -tags exclude_frontend,unit for backend dev/test/lint.
  • Never edit generated files: frontend/src/lib/paraglide/** (Paraglide i18n output) and backend/frontend/dist/**. For i18n, only edit frontend/messages/en.json; other locales come from Crowdin.
  • Migrations are split by DB. Raw SQL via golang-migrate in backend/resources/migrations/{sqlite,postgres}/ — separate files and separate version timelines. Add a matching up/down pair to both. Not GORM AutoMigrate. SQLite migrations are not auto-wrapped in a transaction (NoTxWrap); wrap multi-statement ones manually (PRAGMA foreign_keys=OFF; BEGIN; … COMMIT; PRAGMA foreign_keys=ON;).

Backend (Go)

  • Config: global common.EnvConfig (caarlos0/env); any secret var supports a *_FILE variant.
  • Logging: stdlib log/slog only (bridged to OpenTelemetry). No zerolog/logrus in app code.
  • go.mod pins a fork of fosite (replace github.com/ory/fosite => github.com/pocket-id/fosite).

Frontend (SvelteKit)

  • Svelte 5 runes only: $state, $derived, $props, $bindable. No export let. Event modifiers are gone — use preventDefault from $lib/utils/event-util (onsubmit={preventDefault(fn)}).
  • Forms: use the custom createForm(schema, initial) from $lib/utils/form-util.ts with form-input.svelte. The vendored shadcn formsnap/superforms wrappers exist but app forms don't use them — match the surrounding file. Import zod as import { z } from 'zod/v4'.

Coding Style Guidelines

Comments

  • Exactly one sentence per line
  • There is NO maximum line width: never wrap a single sentence across multiple comment lines, no matter how long that sentence is
  • A new line in a comment means a new sentence; a wrapped line does not exist
  • No trailing period on single-line comments
  • Prefer comments that explain intent, invariants, or why a branch exists
  • Avoid comments that simply restate the next line of code
  • For multi-step logic, use short section comments to separate the steps and explain why each step exists
  • Inside a function, put a one-sentence comment above each major action; the comments double as visual separators between sections and should say what the step does and why, not how
  • Favor a few well-placed section comments over a wall of code; a reader should be able to skim the comments and understand the method's flow
// Wrong — one sentence wrapped across multiple lines
// This function performs the main validation logic. It checks
// the input against the schema and returns an error if the
// input is invalid.

// Wrong — trailing period on single-line comment
// Validate the input.

// Right — one sentence per line, each line as long as it needs to be
// This function performs the main validation logic
// It checks the input against the schema and returns an error if the input is invalid

// Right
// Validate the input

// Right
// Normalize the request host so callers can pass either Host or X-Forwarded-Host values

// Right
// Browsers do not accept a cookie Domain attribute set to an IP address
// Returning an empty domain tells the caller to set a host-only cookie instead

// Wrong — restates the code
// Trim whitespace and lowercase the host
host = strings.TrimSpace(strings.ToLower(host))

Section comments inside a function — one sentence per major action, describing what and why, acting as visual separators: