- Collapse 5-size type scale to 4 by removing 12px from page scale;
annotate text-xs as badge-system token scoped to .ui-badge only
- Metadata (file size, date) stays at 14px, differentiated by slate-500
- Update CTAs to verb+noun: Upload File, Delete File, Download File
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Production compose stack with postgres, web, worker, caddy services (D-01..D-04, D-08)
- postgres service has no host ports binding (internal network only, T-07-09 mitigated)
- web and worker use same image with different command: values (/app/web, /app/worker)
- Both web and worker depend_on postgres with service_healthy condition (T-07-12 mitigated)
- Caddy handles TLS via Let's Encrypt with persistent caddy_data and caddy_config volumes (D-04)
- Caddyfile uses {$DOMAIN} env var interpolation for the site block (RESEARCH Pattern 6)
- Caddyfile includes Let's Encrypt staging note to avoid rate limits (RESEARCH Pitfall 4)
- Add S3_ENDPOINT, S3_BUCKET, S3_REGION, S3_ACCESS_KEY, S3_SECRET_KEY with MinIO dev defaults
- Add S3_USE_PATH_STYLE (true for MinIO, false for R2 virtual-hosted)
- Add MAX_UPLOAD_SIZE_MB=25 with default note
- Add commented DOMAIN=app.yourdomain.com for Caddy TLS in docker-compose.prod.yaml (D-04)
- Clarify TEST_DATABASE_URL is dev/test only and must not appear in .env.prod
- All original vars (DATABASE_URL, SESSION_SECRET, PORT, ENV) preserved
- backend/internal/web/router.go: staticDir string -> staticFS fs.FS; /healthz uses HealthzHandler(); /readyz registered with ReadyzHandler(pinger); embedded FS served via fs.Sub()
- backend/cmd/web/main.go: import assets "backend"; db.RunMigrations(ctx, pool, assets.Migrations) before router; web.NewRouter now receives assets.Static
- All *_test.go NewRouter call sites updated from "./static" to os.DirFS("./static"); "os" import added where missing
- TestHealthz_OK now calls HealthzHandler() with no args (liveness, no db field)
- TestHealthz_Down deleted (new HealthzHandler has no failure mode)
- TestReadyz_OK added: ReadyzHandler(stubPinger{err: nil}) -> 200 + db:ok
- TestReadyz_Down added: ReadyzHandler(stubPinger{err: ...}) -> 503 + degraded
ROADMAP SC-3 (list-failed-jobs CLI) and SC-4 (web binary enqueue) struck
through as deferred per CONTEXT.md decisions D-03 and D-06. VERIFICATION.md
status updated to complete (3/3 active success criteria).
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Closes SC-3 (WORK-04) with a list-failed-jobs subcommand in cmd/worker and
SC-4 (WORK-01) by wiring a river insert-only client into cmd/web with a
/debug/enqueue-test handler that proves the web→worker enqueue path.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
All WORK-01 through WORK-04 requirements verified live against Postgres + MinIO.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- rivermigrate at startup (idempotent, before client construction)
- S3 store init from env vars (S3_ENDPOINT/S3_BUCKET/S3_ACCESS_KEY/S3_SECRET_KEY/S3_REGION/S3_USE_PATH_STYLE)
- signal.NotifyContext created AFTER all startup I/O (PATTERNS.md critical ordering)
- HeartbeatWorker + OrphanCleanupWorker registered via river.AddWorker
- river.Client with slog.Default() Logger, SlogErrorHandler, MaxWorkers:10
- HeartbeatArgs periodic every 1 min (RunOnStart:true), OrphanCleanupArgs every 1 hr
- StopAndCancel(10s timeout) on shutdown; pool.Close after StopAndCancel
- HeartbeatArgs + HeartbeatWorker (logs slog.Info on each tick)
- OrphanCleanupArgs + OrphanCleanupWorker (S3 delete then DB delete loop)
- NewOrphanCleanupWorker constructor with pool + FileStorer injection
- SlogErrorHandler implementing river.ErrorHandler (HandleError + HandlePanic)
- fileQuerier interface for test injection without real DB
- Unit tests: 7 tests pass (pure mock-based, no DB required)
- go build ./... exits 0
Three plans: Wave 1 adds river dependency + internal/jobs package with unit
tests; Wave 2 wires cmd/worker/main.go with full river runtime + justfile
target + README; Wave 3 is human-verify checkpoint.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>