Keep product modules under maintainability budgets by moving LiveView and domain helpers into focused files. Split config/runtime.exs so secrets, roles, and feature flags are not mixed in one file, and add schema dump/load plus per-role boot smokes so a slim-module release can be proven locally. Also route conversations by table ownership, hide empty activity counts, and fail-close CI now that mix test is green.
7.2 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, about 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: false- a failingmix testblocks the deploy.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 you mark work done. Format, compile, and credo must pass.
After you edit 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 you finish, 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 and consistency must pass for the whole project.mix check- the full CI gate (all of the above plus generated-artifact and 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. 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 and LiveView code inapps/elektrine_web. Do not reach across app boundaries casually. - Two exceptions exist 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 was 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. Freshness is judged against arrival, not against when the job runs.
Elektrine.ActivityPub.InboundDeliveryPolicy.permit/4is the one audience gate. Any new inbox entrance applies it. It stops a peer from 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. Each has its own TLS
entry point. Both 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 you change either.