mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-08-31 08:11:27 +02:00
Adds a matrix entry that starts a SQLite-backed Francis runtime next to Pocket ID and points FRANCIS_HOST at it, so the same Playwright suite runs with the actor state, alarms, and placement owned by the runtime instead of embedded in Pocket ID. The suite needs no changes to work in that topology: the E2E reset seeds every actor through actors.Service() and deliberately leaves the actor store alone, so it behaves the same whichever side owns it. The CLI spec is the exception, since export and import are the two commands whose behaviour genuinely differs. It now picks the right Compose file, expects an export to carry no francis.bin, feeds the import an archive without one, and gains a case asserting that an archive that does carry one is refused. The runtime is reached over the Compose network on its UDP port, so nothing is published to the host, and the cluster CA is left unpinned, which exercises the same trust-on-first-use path an operator gets without FRANCIS_CA. Pinning is covered by a unit test instead.
5.0 KiB
5.0 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 intobackend/frontend/distand is embedded viago: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
# docker-compose-francis.yml runs the same suite against a standalone Francis runtime instead of the embedded one
cd ../.. && pnpm test # = playwright test in tests/
Critical gotchas
exclude_frontendandunitbuild tags. Without them plaingo run/go test/golangci-lintfail to run. Always pass-tags exclude_frontend,unitfor backend dev/test/lint.- Never edit generated files:
frontend/src/lib/paraglide/**(Paraglide i18n output) andbackend/frontend/dist/**. For i18n, only editfrontend/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*_FILEvariant. - Logging: stdlib
log/slogonly (bridged to OpenTelemetry). No zerolog/logrus in app code. go.modpins a fork of fosite (replace github.com/ory/fosite => github.com/pocket-id/fosite).
Frontend (SvelteKit)
- Svelte 5 runes only:
$state,$derived,$props,$bindable. Noexport let. Event modifiers are gone — usepreventDefaultfrom$lib/utils/event-util(onsubmit={preventDefault(fn)}). - Forms: use the custom
createForm(schema, initial)from$lib/utils/form-util.tswithform-input.svelte. The vendored shadcn formsnap/superforms wrappers exist but app forms don't use them — match the surrounding file. Import zod asimport { 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: