Skip to content

Developer Handbook

This handbook is the engineering counterpart to the rest of this site. Everything else here is written for the people who use BrightBlur; this section is written for the people who build it. It is deliberately kept together in one place (apps/docs/src/content/docs/internals/) so it can be lifted out into a separate internal site later without untangling it from the user-facing docs.

If you are reading this on your first day: start here, then read Architecture, then pick the chapter closest to whatever you have been asked to touch. You do not need to read all of it before writing code — but you should read Conventions before opening your first pull request.

BrightBlur is a privacy-first photo-sharing app — a SvelteKit PWA (apps/web) and a native iOS/Android app (apps/mobile, Expo/React Native), both running detection, recognition and encryption on-device and sharing the crypto/recognition/wire logic through workspace packages. The core idea: when you share a photo, the faces in it are permanently pixelated in the base image and each face is encrypted separately to the people authorised to see it. The server can read that mosaicked scene and the metadata needed to serve it, but it never receives an unblurred face, raw seed, private key or readable biometric data. Viewers fetch the sealed face entities they are authorised to receive, decrypt them on-device, and composite them back over the pixelated photo in real time.

Two capabilities make this more than a toy:

  • On-device face detection and recognition. Faces are detected, aligned, and embedded entirely in the browser (TFLite + WASM in a Web Worker). The app learns the people you tag so it can suggest matches on future uploads — without a single pixel leaving the device for inference.
  • Per-group end-to-end encryption with key rotation. Every group is a person-group (the access boundary around one individual’s faces) and has an epoch keypair. Content is encrypted to the group’s current epoch public key; members hold wrapped private keys. Removing someone rotates the epoch, so they keep nothing created after they left.

The product goals, in priority order: privacy by construction (the server is untrusted), it must actually work offline-ish and feel instant (PWA, optimistic UI, on-device ML), and it must be approachable (people-first sharing, automatic recognition, batch upload).

┌────────────────────────── browser (untrusted server below) ──────────────────────────┐
│ │
│ UPLOAD VIEW │
│ ────── ──── │
│ decode image fetch encrypted photo + the │
│ → detect faces (SCRFD, Web Worker) face slices you hold keys for │
│ → pixelate faces (destructive 5×5 mosaic) → decrypt locally (KeyRing) │
│ → align + embed (ArcFace/Umeyama, w600k) → composite faces over the │
│ → match to people (template pools, raw cosine) pixelated base, watermark │
│ → seal: │
│ • redacted base → readable privacy floor, served by photo ACL │
│ • face entities → person-group epoch keys │
│ • embeddings → person-group epoch keys │
│ • captions → audience content key │
│ → POST redacted carrier + ciphertext + wrapped keys │
└────────────────────────────────────────────────────────────────────────────────────────┘
│ no unredacted face, raw seed or private key crosses this line │
┌──────────────────────────────────────── Cloudflare ──────────────────────────────────────┐
│ Worker (SvelteKit) · D1 (SQLite, Drizzle) · R2 (blob storage) │
│ stores: photos, circles, face_slices, person_embeddings, epoch/wrapped keys, sessions… │
│ can read: mosaicked scene + service metadata · cannot decrypt sealed face/content data │
└────────────────────────────────────────────────────────────────────────────────────────────┘

The single most important invariant in the whole codebase: the line between the browser and the server is a trust boundary. Plaintext images, face pixels, embeddings, and private keys live only on the left of that line. If you find yourself sending any of those to the server, stop — you have a bug.

Chapter What it covers
Architecture & Tooling Monorepo layout, the stack, the request lifecycle, where code lives, local dev, build, deploy, and CI.
Data Model The Drizzle schema table-by-table, the entity relationships, the D1 client, and migrations.
Encryption & Keys The trust model, epoch keys, key wrapping/rotation, the KeyRing abstractions, and recovery — from an engineer’s point of view.
Recognition Pipeline On-device detection, alignment, embedding, template pools and matching, the TFLite runtime, and the service-worker model cache.
The Publish Pipeline What happens between “user hits publish” and “ciphertext is on the server”, end to end.
API Contracts The HTTP surface: endpoint inventory, request/response shapes, auth, and the cross-cutting middleware.
Frontend Architecture SvelteKit structure, Svelte 5 runes and the house state patterns, shared components, and styling tokens.
Conventions & Process TDD, testing, commit style, British English, the design system, and the current merge-gate policy. Read this first.

The five-minute conventions you cannot skip

Section titled “The five-minute conventions you cannot skip”

These are expanded in Conventions, but if you read nothing else:

  • Server logic lives in src/lib/server/api/; the files in src/routes/api/ are thin wrappers. Put real logic in the API module, not the route.
  • Red–green TDD, two commits. Commit a failing test first (red), then the implementation that makes it pass (green), so a reviewer can validate the bug from the red commit.
  • British English everywhere — code comments, docs, UI copy — with correct diacritics. Conventional-commit messages. Named imports, never namespace imports.
  • $env/dynamic/private for server env vars, never $env/static/private.
  • Run .svelte files through the Svelte MCP autofixer before finalising them.
  • Merge gate: CI (.github/workflows/ci.yml) runs typecheck + unit + build on every PR, plus a mobile typecheck, and applies D1 migrations to production on merge to main. Run the local gate before pushing, and apply destructive/NOT NULL migrations by hand before merging. Full procedure: Operational Status & Gates.

The visual design system (colour tokens, type scale, spacing, radii) is the single source of truth in docs/design.md at the repo root. Read it before touching UI and update it in the same change when you add a token.