Coding conventions
Each repository documents its own rules in CLAUDE.md or README.md; this page is the cross-repo summary. When a rule here disagrees with the repo file, the repo file wins and this page needs updating.
Everywhere
- Branch from
develop, pull request intodevelop, mergedevelopintomainfor a release (Repositories). - Never commit a secret. Env var names in docs; values in
.env, Portainer, Key Vault or GitHub secrets (Secrets management). - Never log or put in a URL: emails, tokens, codes, passwords, request bodies.
- Say which environment a URL or command is for.
- Tests ship with the change where a test runner exists (backend, Admin CRM).
Backend (emaw-django-app)
From CLAUDE.md:
- Use
.venv\Scripts\python.exefor every command. - DRF denies by default; public views set
AllowAnyexplicitly. Throttles inEMAW/throttles.py. - Staff views:
CrmAccessViewMixin/HasCrmAccess("<capability>"), andscope_contacts()on any queryset exposing contact data. Do not hand-roll staff checks. - Security events through
EMAW.security_log.audit(event, request, user=..., **fields); log user ids only;%slazy formatting, no f-strings in log calls. - One
LOGGINGbuilt bylogging_utils.py; no per-environment logging dicts. - Verification codes via
VerificationCode.verify(code). - Any bulk write that bypasses model signals on contacts must call
invalidate_contact_lists(). - Tests in
<app>/tests/test_*.py, factories in<app>/tests/factories.py, against the throwaway Postgres. - New env vars: add to
docker-compose.ymlx-app-env,CLAUDE.md, and the production stack. - Changing a staff endpoint: update
docs/CRM_FRONTEND_GUIDE.md.
Website (emaw-website)
From CLAUDE.md:
- TypeScript strict-ish:
verbatimModuleSyntax,noUnusedLocals,noUnusedParameters,erasableSyntaxOnly.import typefor types. @/alias over deep relative paths.- Internal links through
LocalizedLink/LocalizedNavLink, neverreact-router-dom'sLink. - New i18n namespace: update
src/lib/i18n/index.tsand both locale folders. - Sanity through
getClient(preview)anduseSanityQuery;sanity.types.tsis generated, do not edit. LayoutfetchessiteSettingsonce.- Prettier: no semicolons, single quotes, no bracket spacing, width 100. ESLint flat config.
- No lockfile (gitignored on purpose); CI uses
npm install. - Checkout stays in Stripe Elements mode (server-side shipping).
Web app (emaw-web-app)
From README.md:
- Every API call is a function in
src/utils/using the shared axios instance. - Sign-in requests pass
{skipAuthRefresh: true}. - Tokens only through
AuthContextProvider; refresh token in the body. .env.developmentand.env.productionare committed; override with.env.development.local.- Keep curriculum JSON in sync with the backend export and the mobile app.
Admin CRM (emaw-crm-app)
From README.md and src/api/README.md:
- Env vars only in
src/config.ts. - Access token in memory; refresh via the HttpOnly cookie through the proxy; nothing auth-related in
localStorage. - No emails in URLs except
q; never log bodies; showX-Request-IDon error toasts;no-consoleis an ESLint error. - CSV exports only on click, never cached.
- Dev sends only to group 36 (
devGuard.ts). - One folder per feature with
api.ts, types, screens,routes.tsx(lazyComponent), tests. - Trailing slashes on API paths.
- Run
npm testandnpm run lintbefore a pull request.
Mobile app (emaw-mobile-app)
From CLAUDE.md:
- Tokens only through
store/tokenStorage.js; never log passwords, tokens orerror.responseof auth requests. - Every API call is a function in
utils/usingstore/AxiosInstance.js. - Styles from
constants/styles.js, not inline. - Native changes go in config plugins or
app.json, never in the gitignoredandroid/andios/. - Bump versions with
npm run version(it also resets the backend URL to production). - Do not commit a dev backend URL in
constants/backendIp.js.