Thanks for your interest! Bug reports, commissioning war stories, and doc fixes are always welcome as issues.
LoopCheck is open-core (ADR 0003): the core is AGPL forever, and the maintainer holds sole copyright, which is what keeps future licensing decisions possible. To preserve that, code contributions require either a CLA or a DCO before they can be merged:
- CLA ("contributor license agreement") — a short agreement granting the maintainer broad rights to your patch.
- DCO ("developer certificate of origin") — a
Signed-off-by:line in your commits asserting you have the right to submit the code.
Which of the two LoopCheck will use is not decided yet. Until it is, please open an issue before writing a substantial patch — small fixes can usually wait for the decision; large ones deserve a conversation first so your work doesn't stall on paperwork.
- Read CLAUDE.md — the hard constraints (no-install field tier, append-only records, no build step, derived status, no forms designer, tag numbers as the project's language) are load-bearing walls, and a patch that violates one will be declined regardless of quality.
- Schema changes ship as migrations in
pb_migrations/, never as hand-edits, and need a short ADR indocs/adr/— written before the migration. - Before landing a change that adds a migration or a module, the combined
stack must pass
./scripts/smoke_test.sh— it boots a fresh database, applies every migration, runs the full demo seed, and asserts the stack is healthy (all collections present, each module's seed populated, the append-only / close-once rules intact, the clean-URL routes serving). Modules are built and verified one per session, in isolation, where each looks fine on its own; this gate is what catches the breakage that only appears when everything runs together.scripts/setup.shwires it in as a pre-push hook (git config core.hooksPath .githooks), so a push runs it automatically; a doc-only or emergency push can bypass it withSKIP_SMOKE=1 git push.
The same script is the gate in both places (DECISIONS.md D15):
There are two suites, and they cover different failure modes:
| Suite | What it proves |
|---|---|
scripts/smoke_test.sh |
The REST surface: every migration applies, each module's seed populates, append-only and close-once rules hold, and the ADR 0011 access matrix behaves across guest / user / superuser. 94 assertions. |
scripts/browser_test.sh |
The rendered field path: /t/{tag} serves a populated page, the LOTO honesty line and freshness stamp are present (ADR 0007), an accountless punch flag reads back, and a logged check appears in the ledger. 7 assertions. The REST suite cannot see any of this — a broken Alpine binding would leave it green while the field tier is dead. |
| What runs them | Behaviour | |
|---|---|---|
| Local | .githooks/pre-push |
Runs the REST suite on every push; runs the browser suite too when its tooling is installed, and says so when it isn't. Either failing blocks the push. Bypass with SKIP_SMOKE=1 git push. |
| Remote | .github/workflows/ci.yml |
Runs both on every push and PR to main. The backstop for a bypassed, absent, or skipped local hook. |
Run them yourself exactly as CI does:
./scripts/setup.sh # once — fetches the PocketBase binary
LC_TEST_PORT=8399 sh scripts/smoke_test.sh # REST suitecd scripts/browser && npm ci && npx playwright install chromium && cd ../..
sh scripts/browser_test.sh # browser suite (boots its own seeded instance)Both boot their own throwaway database from pb_migrations/ and delete it on
exit — they never touch your pb_data/. Point the browser suite at an instance
that is already running with LC_BASE=https://host sh scripts/browser_test.sh.
The browser tooling is dev-only (D16):
Playwright and its browser download are git-ignored, never vendored into
pb_public/, and never a runtime dependency. Hard constraint #3's no-build-step
and page-weight budget govern the shipped app, not the test harness.
CI pins PB_VERSION to the version docs/API.md names as the tested one,
rather than tracking latest — a PocketBase upgrade that changes REST behaviour is
a breaking change to the published contract, so moving the pin is a deliberate
act, not drift.
- The collections are a published API surface (docs/API.md) — sixteen published as v1, 24 committed in total (the extra eight marked delta). Breaking changes need an ADR and a contract version bump, not just a migration.
seed/is the maintainer-authored domain library (real water/wastewater checkout practice). Content changes there need domain review by the maintainer — checklist wording is engineering, not copy-editing.- Anything touching the QR/URL format (
/t/{tag_number}) is a breaking change to physical printed labels and needs explicit maintainer sign-off. - Comment the "why," not the "what" — especially PocketBase API calls (filter syntax, expand, create-then-derive sequences). Boring, readable code wins.
- Update the affected docs in the same change — see the docs-as-code definition of done in CLAUDE.md.