Architecture
Principles
- Serverless and usage-based. Every service scales to zero: no idle cost when nobody draws. Lambda, API Gateway HTTP, DynamoDB on-demand, S3, CloudFront, Cognito.
- Multiple sites, one product. Canvas app, marketing site, docs, API, and auth each live on their own hostname, all defined in configuration.
- Multi-account. dev, stage, prod, and a CI/CD account. Pipelines deploy cross-account; developers never hold prod credentials.
- Local-first app. The canvas works offline; cloud sync is an enhancement, not a dependency.
Component diagram
┌────────────────────────── AWS (per environment account) ──────────────────────────┐ │ │ Browser │ CloudFront + WAF S3 (static sites) API Gateway + WAF │ ┌───────────────┐ │ ┌──────────────────┐ ┌───────────────────┐ ┌──────────────────────┐ │ │ canvas app │──────┼──▶│ app.<domain> │────▶│ app assets │ │ │ │ │ (engine+UI) │ │ │ www.<domain> │────▶│ www assets │ │ /documents/* (JWT) │ │ │ config.json │ │ │ docs.<domain> │────▶│ docs assets │ │ /healthz (public) │ │ └──────┬────────┘ │ └──────────────────┘ └───────────────────┘ │ /published/{t} (pub)│ │ │ │ └──────────┬───────────┘ │ │ OAuth PKCE │ Cognito │ │ ├───────────────┼──▶┌─────────────────────────────────────────────┐ │ │ │ │ │ user pool (MFA, adaptive auth) │ │ │ │ │ │ hosted UI @ auth.<domain> │ │ │ │ │ └─────────────────────────────────────────────┘ │ │ │ presigned │ S3 documents bucket (KMS, versioned) ◀──── Lambda (documents API)│ │ │ PUT/GET │ DynamoDB metadata (PITR, GSIs) ◀────────────┘ │ └───────────────┼──────────────────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────────────────────────────────┘
GitHub ──push main, dev*──▶ CodePipeline (CI/CD account) ──▶ dev account (auto) └── approval ──▶ stage account └── approval ──▶ prod accountData flow
- The app fetches
/config.jsonfrom its own origin (injected per environment at deploy time) — no build-time or hardcoded endpoints. - Sign-in uses Cognito hosted UI with PKCE (public client, no secret).
The ID token is presented to the API (
Authorization: Bearer …). - Document metadata lives in DynamoDB (
idPK;owner-createdandpublished-tokenGSIs). The body lives in S3 underprivate/{ownerId}/{id}/{revision}.frond. - Uploads/downloads use short-lived presigned S3 URLs (10 min), so document bytes never transit the Lambda; committing a revision verifies the object (HEAD) and bumps metadata.
- Publishing copies the current revision to
published/{token}/latest.frond— public viewers never see owner identifiers — andGET /published/{token}serves the read-only view.
Why this stays fast
- The canvas is a quadtree of raster-cached vector cells (see the engine); visible content is O(screen), not O(document).
- Static sites are on CloudFront edge caches; the API is a single Lambda with millisecond cold starts; DynamoDB on-demand absorbs spikes.
- The app bundle is ~70 kB gzipped; everything after first load is local.
Security architecture
- Least privilege IAM per component; presigned-URL access to S3.
- KMS customer-managed keys for documents, DynamoDB, and logs (with rotation); TLS-only bucket policies; S3 Block Public Access everywhere.
- WAF (managed rule sets + IP rate limits) on CloudFront and API Gateway.
- Cognito: strong password policy, optional MFA (TOTP), adaptive auth,
token revocation,
preventUserExistenceErrors. - GuardDuty, Config managed rules, Security Hub, CloudTrail per account.
- Security headers (CSP, HSTS,
frame-ancestors 'none') on all web origins. - See Security and the compliance guides for the full control set.