flowchart LR
subgraph CLIENT["Clients"]
E1[errors] --> S[(Sentry)]
H[app_started health event<br/>Core Web Vitals] --> EV[(events)]
G[event guard drops] --> EV
end
subgraph SERVER["API and jobs"]
E2[errors] --> S
M[Prometheus metrics] --> FLY[Fly metrics]
J[job_runs] --> SZ["/statusz"]
end
UP[uptime checks] --> HZ["/healthz · /readyz"]
S & FLY & UP --> AL[alerts]
EV --> VIEWS["build_health · build_adoption · jobs"]
| Signal | Source | Answers |
|---|---|---|
| Errors with readable stack traces | Sentry (web, API), source maps uploaded on deploy | What broke, in which build, for how many |
| Request rate, latency, status codes | Prometheus metrics on an internal port | Is the API healthy and fast? |
| Cold start and content time | app_started health event | Is startup getting slower? Did content come from the network or the device? |
| Core Web Vitals | First-party events | Is the web app fast for real users? |
| Event guard drops | Health event | Is something stuck in a render loop? |
| Job runs | job_runs, /statusz, jobs view | Did every job run on time? |
| Build adoption and health | build_adoption, build_health views | Are learners on new builds, and are those builds healthy? |
| Uptime | External checks on health endpoints | Is the site up from outside? |
/statusz and in the jobs view.note Client errors and health events are tagged with the build number, so a bad build shows up as a step change in
build_health, and the fix can be a content switch (raisemin, change a remote switch) instead of waiting for store review.
| Symptom | First look |
|---|---|
| App won't load new content | Manifest signature and key id; edge cache of the manifest |
| Sync errors spike | Sentry for /v1/sync/push; rejected mutation types from an old or new build |
| Reminders stop | /statusz for the nudges job; push provider responses |
| Slow API | Database pool saturation; slow queries; Fly machine health |
${sch.nav()}