[READ-ONLY] Mirror of https://github.com/openstatusHQ/openstatus. 🫖 Status page with uptime monitoring & API monitoring as code 🫖 openstatus.dev
bun drizzle-orm monitoring monitoring-as-code nextjs observability on-call open-source shadcn-ui status-page statuspage synthetic-monitoring tinybird turso uptime uptime-checker uptime-monitor
3.6 kB
Markdown

AGENTS.md — apps/status-page #

Public, unauthenticated, multi-tenant: the page is resolved from the request host or the [domain] segment. Assume every response you touch is served to anonymous visitors and cached.

Gated content #

Pages can be password-protected. Any new surface that renders page content — route, API handler, feed, embed — must apply the same gate the HTML route applies. The existing public representations to mirror are apps/status-page/src/app/api/markdown/[[...path]] and apps/status-page/src/app/api/status/[[...path]].

The gate decision lives in packages/services/src/page-access, so every transport shares it. The statusPage tRPC procedures enforce it themselves: get/getLight return chrome only to a denied caller — login, layout and OG still render from them — and the detail procedures throw. A new procedure that returns page content must call assertPageAccess from packages/api/src/lib/page-access.ts. guardTRPCSource filters on a spoofable header and is not a security boundary.

Never log or report tRPC input — it carries page passwords and subscriber tokens. sentryLoggerLink attaches the operation path only.

Caching #

Markdown and JSON representations negotiate on Accept at the same URL, so every response sets Vary: Accept plus a strong ETag. Cache-control depends on the page's access type — a gated page must not inherit a public TTL.

Theming #

Themes come from @openstatus/theme-store as OKLCH CSS variables. Add or edit a theme in that package; do not hard-code colours in a component.

--radius is themed too, and this app overrides it to 0rem. The scale in packages/ui/src/globals.css is proportional to it (xs 0.2, rounded 0.4, sm 0.6, md 0.8, lg 1.0, xl 1.4), so every step collapses with the theme — use the tokens, never a hard-coded rounded-[4px], and prefer rounded-lg over the equivalent rounded-(--radius). A pill that stays a pill under a square theme is the bug. rounded-full is only for a chip that wraps a glyph (StatusIcon); a coloured status marker takes rounded-lg, matching the bar it describes. The .rounded-full override in globals.css exists only so Tailwind's 9999px doesn't defeat --radius: 0 — never add more of those, fix the component (see packages/ui/AGENTS.md).

Impact labels #

Status-page impact labels are coloured text only — no dots, no chevrons. The hover affordance is a dashed muted underline, never one tinted with the impact colour.

Theme explorer #

/ renders the theme explorer, and it is also where any request resolving to no page row ends up. The proxy rewrites those to a 404 (resolveUnresolvedHostAction) so an unknown slug or a custom domain missing from the DB never answers with the explorer or its OG image. Only themes.openstatus.dev is indexable — see lib/theme-explorer-host.ts.

This is NOT the Next.js you know #

This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in node_modules/next/dist/docs/ (resolved from this file's directory; in monorepos the next package may not be visible from the repo root) before writing any code. Heed deprecation notices.

This block is written and re-added by next dev — verify at node_modules/next/dist/server/lib/generate-agent-files.js. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.