Planning โ
Ordered. Unsequenced ideas live in backlog.md.
What is already done? โ
1. Server errors surfaced from Cloud Logging. Done 2026-08-27. โ
getServerErrorsMetricsqueriesseverity>=ERROR, groups by source, returns a recent feed. Rendered on the System Health Errors tab.- Auth is
GoogleAuth, so it resolves the metadata server when deployed and ADC locally, which makes it verifiable off-GCP.
2. Uptime derived from the service registry. Done 2026-08-27. โ
- Nine endpoints instead of four, with a 12s cold-start allowance for APIs set from measurement.
3. The orphaned scheduler job deleted. Done 2026-08-27. โ
analytics-daily-aggregation, 20 of the 34 errors. Its route was retired and the job was not.
What is next? โ
4. Capture client errors, because right now nothing does. โ
This is the task she asked to have scoped on 2026-08-27. It is the largest remaining hole: a user hitting a crash produces nothing anyone can see.
What exists today, verified rather than assumed. โ
- No
window.onerror, nounhandledrejectionlistener, no Sentry, nocaptureExceptionanywhere inapps/web/srcorapps/admin/src. apps/admin/src/shared/components/ErrorBoundary.jsxis the only boundary in either app, wraps the whole admin tree, and itscomponentDidCatchonly callsconsole.error.apps/web, the PWA users actually run, has no boundary at all. A render error there is a white screen with nothing recorded.- The
clientErrorsFirestore collection has zero documents and no writer.
The open decision: where captured errors go. โ
Recommendation is B, because it makes client and server errors answer the same question in the same place, which is the whole reason the Errors tab was untrustworthy.
| Option | What it does | What it affects | Risks and implications |
|---|---|---|---|
A. Write to the clientErrors collection | An endpoint writes the docs the existing panel already reads | Smallest change. The panel starts working as originally intended | A second error store with no grouping and no resolution state, so it repeats the problem Error Reporting already solves. Firestore write costs scale with client crashes, which is exactly when you least control volume |
| B. Report into Cloud Error Reporting (recommended) | An endpoint forwards to the Error Reporting API, so client errors join the same grouping and resolutionStatus model as server errors | One error model across both halves. "Is this resolved" works for client errors for free | Needs a correct payload shape for grouping to work. Source maps are a separate piece before stack traces are readable |
| C. Sentry | The option #164 names | Best-in-class grouping, release tracking, source maps | A third-party processor receiving user error payloads, which needs a privacy read before anything else. New vendor and cost |
Option C is not a straight pick here. Lantern's axiom is to prefer losing data over leaking it, and an error payload can carry user content. Anything sent to an outside processor needs the privacy-architecture skill consulted first, so B keeps the data inside infrastructure that is already trusted with it.
What the task covers. โ
- An
ErrorBoundaryinapps/web, matching the admin one's shape, so a render error is a recoverable card rather than a white screen. - Global
window.onerrorandunhandledrejectionhandlers in both apps. - A reporting endpoint on an existing Cloud Run service. API first, not a Cloud Function, per rule 9.
- Rate limiting and payload capping on that endpoint, because it is unauthenticated by necessity and a crash loop is a write amplifier.
- A privacy pass on what gets sent. An error message can contain user content, a phone number, or a venue. Decide what is stripped before it leaves the device, not after.
What it explicitly does NOT cover. โ
- Source map upload, performance monitoring, and alerting. All are in
#164and all are separate tasks.
5. Surface Cloud Error Reporting groups on the Errors tab. โ
- It already tracks
resolutionStatus,firstSeenTime,lastSeenTimeandcount, and five groups are OPEN right now with nothing showing them. - It is complementary to the Logging feed, not a replacement. Error Reporting only catches things shaped like application exceptions, which is why it holds five groups where Logging held thirty-four entries. Logging is the catch-all with no memory; Error Reporting is the stateful subset.
resolutionStatusis writable viaPUT v1beta1/{group.name}, so a Resolve control in the portal is possible rather than deep-linking to the GCP console.- The signal worth building for: a group whose
lastSeenTimemoves after it was marked RESOLVED. That is the fix that did not hold, and it comes free with the model.
6. Work the five OPEN Error Reporting groups. โ
merchants-apiZodError,auth-api500,auth-apiinvalid documentPath,analytics-apimissing@lantern/shared,assistant-apiZodError.- Sequenced after 5 so there is somewhere to see whether fixing one actually closed it.
What is out of scope for this project? โ
- Alerting routes (PagerDuty, Slack). They belong to
#164but need somewhere trustworthy to alert FROM first, which is what tasks 4 and 5 build. - Performance monitoring and Core Web Vitals. Same issue, different deliverable.