7.3 KiB
Working in this repo
Elektrine is an Elixir umbrella app. See README.md for the app layout and
what each umbrella app under apps/ owns.
Git commits
Shared agent commit policy. Keep this section identical in every repo that uses it.
- Do not add AI attribution of any kind: no
Co-Authored-By: Claude ..., noClaude-Session:/Codex-Session:/ similar trailers, no "🤖 Generated with ..." lines, in commit messages or PR bodies. - Use Conventional Commits:
type(scope): subject.- Types:
feat,fix,refactor,docs,test,chore,ci,build,perf. - Scope (optional): short area name for what changed (app, package, or feature). Omit when the change is repo-wide.
- Types:
- Subject in the imperative mood, no trailing period, ~72 chars or less.
Examples:
fix(auth): treat blank env as unset,feat(ui): full-size image modal lightbox,ci: wire multi-arch release artifacts. - Body (when needed) explains why, not a restatement of the diff. Use complete sentences.
- Prefer focused commits. Each commit should leave the tree buildable and tests passable on its own.
- Do not commit or push unless the user asks.
Agent defaults
Shared agent hygiene. Keep this section identical in every repo that uses it.
- Prefer existing project tools and lockfiles; do not invent new package managers, frameworks, or large dependency upgrades unless asked.
- Do not commit, push, force-push, amend published history, or deploy unless the user explicitly asks.
- Never invent, print, or commit secrets (
.env, API keys, private keys, tokens, production passwords). Use example env files only. - Do not run destructive git or filesystem commands (
reset --hard,clean -fdx,rm -rfof real data) unless the user clearly requests it. - Prefer absolute paths in tool calls; do not create files the user did not ask for (especially drive-by markdown docs).
- Match surrounding code style. Keep changes scoped to the request.
- When fixing a bug, prefer a regression test if the repo already has a test harness and the failure is easy to pin.
Before you push
Pushing main to the github remote deploys to production
(.github/workflows/docker-deploy.yml runs on push to main).
allow_format_failure: false— unformatted code blocks the deploy.allow_test_failure: true— failing tests do not block it. Runmix testyourself; CI will not stop a broken suite from shipping.maintracksorigin/main, a local forge atssh://localhost:2222. Thegithubremote is the one that deploys. A baregit pushis not a deploy.
Verifying changes
Always run the checks that CI runs before considering a change done — do not report work as finished until format, compile, and credo pass.
After editing a file, run the fast per-file checks first:
mix format <file>— format the file.mix credo --strict <file>— lint just that file (fast).
Then, before finishing, run the broader gate:
mix format --check-formatted— CI fails if anything is unformatted.mix compile --warnings-as-errors— warnings fail the build; fix them.mix credo --strict— style/consistency must pass (whole project).mix check— the full CI gate (all of the above plus generated-artifact + legacy-marker checks, asset check, dep/hex audits, andtest). Run this for anything non-trivial.
Three budget guards fail in ways that are easy to misread:
scripts/check_maintainability_budgets.sherrors when a budgeted path no longer exists, so moving or deleting a budgeted file must update the budget in the same commit.scripts/check_design_consistency.shcaps rawbtn/cardmarkup, arbitrary type sizes, off-convention radii, raw hex, and inline styles — per app, for every*_webtree. Lower the budget when you remove instances, so the surface cannot grow back. A new*_webapp fails the check until it gets acheck_appline.scripts/check_legacy_marker_budget.shcapslegacy|backward|compat| deprecatedmarkers acrossapps,config,scripts, anddeploy.
Tests
- Run the whole suite from the umbrella root:
mix test. - Run one app's tests from its dir:
cd apps/elektrine_web && mix test. - Run a single file/line:
cd apps/<app> && mix test test/path/to_test.exs:42. - Prefer adding a regression test with any bug fix.
- Running one app's tests can poison
_build/testfor the umbrella; if the root suite then behaves oddly,rm -rf _build/test/lib/elektrine.
Conventions
- Match the style and idioms of the surrounding code in each app.
- Keep domain logic in
apps/elektrine; keep web/LiveView code inapps/elektrine_web. Don't reach across app boundaries casually. - Two exceptions, both because
apps/elektrineis the only app every other app depends on: UI components shared by more than one product app live inapps/elektrine/lib/elektrine/components(avatar, skeleton, badges), and the frontend assets live inapps/elektrine/assets. Import those modules directly — do not add a re-export shim inapps/elektrine_web. - The
ElektrineWeb.Components.UIkit is the design system and stays inapps/elektrine_web. Buttons and cards come from it, not hand-written markup; use thetext-3xs/text-2xsand radius tokens rather than arbitrary values. - Shared text formatting lives in
Elektrine.TextHelpers—truncate/2,truncate_to/2,abbreviate_count/1,time_ago_compact/1. Call those rather than re-deriving them privately, so the same value renders the same way in every app.
Federation security
Elektrine.ActivityPub.SignatureVerificationis the only inbound HTTP signature verifier. Every entrance — the inbox plug and the signature retry queue — goes throughverify/2; callers differ only in the options they pass. Never add a second verifier or a path that checks the signature without also checking required signed headers, timestamp freshness, digest against the raw body, and replay. A partial verifier reachable from anywhere becomes the way in.Elektrine.ActivityPub.HTTPSignaturesigns outbound requests only.- Only key-resolution failures are retryable
(
SignatureVerification.retryable_error?/1). A signature that verified but broke a constraint has been rejected on the merits — do not queue it. - Retried requests store the raw body and the arrival time. The activity is re-parsed from those bytes after the digest check, so what gets processed is what the signature covered, and freshness is judged against arrival rather than when the job happens to run.
Elektrine.ActivityPub.InboundDeliveryPolicy.permit/4is the one audience gate. Any new inbox entrance applies it — it is what stops a peer pushing content at an inbox it has no relationship with.
Mail and Haraka
Haraka is a separate deployment (elektrine-haraka) that owns inbound SMTP on
port 25 and outbound delivery; Elektrine owns mailboxes, JMAP, submission, and
the webhooks Haraka calls. Two layouts are supported and each has its own TLS
entry point, which write the same compose.override.yml — use exactly one per
Haraka deployment:
- same host:
scripts/deploy/configure_haraka_wildcard_tls.sh(the default;docker_deploy.shkeeps it configured) - separate host:
scripts/acme/setup_haraka_mail_tls.sh
See docs/self-hosting/mail.md before changing either.