Conventions and tests¶
The conventions below are not style preferences; each one is load-bearing for a security or operational
property PEART commits to. A change that breaks one of them breaks a guarantee, so make check enforces the
ones that can be enforced mechanically, and review enforces the rest.
The frontend has no build step, and no innerHTML¶
The interface is static pages served by go:embed, with Lit vendored under web/vendor/. There is no Node,
no bundler, no transpile step. A contribution to the frontend is plain JavaScript and Lit templates that run
in the browser as written. Keep it that way: the absence of a build pipeline is what lets the whole
interface ship inside a single Go binary and be audited by reading the files that are actually served.
The Content-Security-Policy is strict, with no unsafe-inline and no unsafe-eval. That has a direct
consequence for how you write components: no inline event handlers, no inline <style> or style=
injection of untrusted values, and no innerHTML or Lit unsafeHTML in our own code. Render through Lit's
templating, which escapes by construction. A component that needs to show user-supplied text binds it as
text, never as markup. This is the single most important frontend rule, because it is what closes stored
XSS at the rendering layer.
Migrations are forward-only¶
Database migrations are numbered and forward-only; there is no down-migration path. A schema change is a new migration file that moves the schema forward and is safe to apply to a live database. This keeps upgrades predictable and irreversible in the direction that matters, and it means you design a migration to be correct on the first apply rather than counting on a rollback that does not exist.
Errors are explicit, resources are deferred¶
Handle errors explicitly at the point they occur; do not paper over them to make a function read cleanly.
Release every resource with defer at the point of acquisition. Avoid mutable package-level state. These are
the ordinary Go disciplines, and the codebase holds to them so that behaviour under failure is the behaviour
you can read off the code.
Tests are table-driven and parallel to the source¶
Every unit of code has tests, and complex logic uses table-driven tests covering the success and the failure
paths. Test files sit beside the code they test (foo_test.go for foo.go). The browser tests drive the
real interface in a headless Chrome and are part of the suite, not a separate optional tier; a change to a
component is not done until its browser test is green. make check runs all of it.
Formatting and documentation are not optional¶
Code is gofmt-clean and stays within 120 columns. Every package has a package comment above the package
declaration, and every exported identifier has a doc comment that is a full sentence beginning with the
identifier's name. Comments explain why, in English, not what. make check fails on a gofmt diff, on an
over-long line, and on the i18n-check gate that keeps the English and Portuguese message catalogues in
lockstep, so run it before you send anything.