Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
Commit873f13cunknown_key

Polish status page — incident history, subscribe-to-alerts, service uptime badges

Polish status page — incident history, subscribe-to-alerts, service uptime badges

- Migration 0077: creates `incidents` and `status_subscribers` tables
- Schema: adds `incidents` + `statusSubscribers` Drizzle table definitions with indexes
- Status route: full rewrite with production-grade sections:
  - Hero banner derives label from active incident severity (All systems operational / Degraded performance / Major outage)
  - Per-service table with 30-day uptime % column for Git Push, AI Review, CI Runner, API, Database, Autopilot
  - Incident history table (last 10) with date, title, severity badge, status pill, resolved-at columns
  - Subscribe-to-alerts form (POST /status/subscribe) with confirmation email via existing sendEmail helper; GET /status/confirm and /status/unsubscribe token flows; post-redirect notice banners
  - Synthetic monitor alert list (last 24h) retains existing recentRedChecks data
  - Dark-theme scoped CSS with severity badges (minor/major/critical), new .status-pill-warn, table styles, notice banner styles, subscribe section gradient

https://claude.ai/code/session_01DzJMTFASjMHt2f5ze4cNLR
Claude committed on June 6, 2026Parent: 5c7b43d
3 files changed+793140873f13c0a5fa7743c660767a7e6807459fbedda1
3 changed files+793−140
Addeddrizzle/0077_incidents_and_status_subscribers.sql+39−0View fileUnifiedSplit
1-- 0077 — Status page: incident history + subscriber list.
2--
3-- incidents: manually-filed or autopilot-detected outage records shown on
4-- the public /status page. severity: 'minor' | 'major' | 'critical'.
5-- status: 'investigating' | 'identified' | 'monitoring' | 'resolved'.
6--
7-- status_subscribers: email addresses that have opted-in to receive alerts
8-- when a new incident is filed. Confirmation token is single-use; once
9-- the user clicks the verify link confirmed_at is set.
10
11CREATE TABLE IF NOT EXISTS "incidents" (
12 "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
13 "title" text NOT NULL,
14 "severity" text NOT NULL DEFAULT 'minor',
15 "status" text NOT NULL DEFAULT 'resolved',
16 "started_at" timestamptz NOT NULL DEFAULT now(),
17 "resolved_at" timestamptz,
18 "body" text,
19 "created_at" timestamptz NOT NULL DEFAULT now(),
20 "updated_at" timestamptz NOT NULL DEFAULT now()
21);
22
23CREATE INDEX IF NOT EXISTS "idx_incidents_started_at"
24 ON "incidents" ("started_at" DESC);
25
26CREATE INDEX IF NOT EXISTS "idx_incidents_status"
27 ON "incidents" ("status");
28
29CREATE TABLE IF NOT EXISTS "status_subscribers" (
30 "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
31 "email" text NOT NULL UNIQUE,
32 "confirmed_at" timestamptz,
33 "confirm_token" text UNIQUE,
34 "unsubscribe_token" text UNIQUE,
35 "created_at" timestamptz NOT NULL DEFAULT now()
36);
37
38CREATE INDEX IF NOT EXISTS "idx_status_subscribers_email"
39 ON "status_subscribers" ("email");
Modifiedsrc/db/schema.ts+58−0View fileUnifiedSplit
40194019);
40204020
40214021export type ClaudeWebMessage = typeof claudeWebMessages.$inferSelect;
4022
4023// ---------------------------------------------------------------------------
4024// 0077 — Status page: incident history + subscriber list.
4025//
4026// incidents: manually-filed or autopilot-detected outage records shown on
4027// the public /status page. severity: 'minor' | 'major' | 'critical'.
4028// status: 'investigating' | 'identified' | 'monitoring' | 'resolved'.
4029//
4030// status_subscribers: email addresses that have opted-in to receive alerts
4031// when a new incident is filed.
4032// ---------------------------------------------------------------------------
4033export const incidents = pgTable(
4034 "incidents",
4035 {
4036 id: uuid("id").primaryKey().defaultRandom(),
4037 title: text("title").notNull(),
4038 severity: text("severity").notNull().default("minor"),
4039 status: text("status").notNull().default("resolved"),
4040 startedAt: timestamp("started_at", { withTimezone: true })
4041 .defaultNow()
4042 .notNull(),
4043 resolvedAt: timestamp("resolved_at", { withTimezone: true }),
4044 body: text("body"),
4045 createdAt: timestamp("created_at", { withTimezone: true })
4046 .defaultNow()
4047 .notNull(),
4048 updatedAt: timestamp("updated_at", { withTimezone: true })
4049 .defaultNow()
4050 .notNull(),
4051 },
4052 (table) => [
4053 index("idx_incidents_started_at").on(table.startedAt),
4054 index("idx_incidents_status").on(table.status),
4055 ]
4056);
4057
4058export type Incident = typeof incidents.$inferSelect;
4059export type NewIncident = typeof incidents.$inferInsert;
4060
4061export const statusSubscribers = pgTable(
4062 "status_subscribers",
4063 {
4064 id: uuid("id").primaryKey().defaultRandom(),
4065 email: text("email").notNull().unique(),
4066 confirmedAt: timestamp("confirmed_at", { withTimezone: true }),
4067 confirmToken: text("confirm_token").unique(),
4068 unsubscribeToken: text("unsubscribe_token").unique(),
4069 createdAt: timestamp("created_at", { withTimezone: true })
4070 .defaultNow()
4071 .notNull(),
4072 },
4073 (table) => [
4074 index("idx_status_subscribers_email").on(table.email),
4075 ]
4076);
4077
4078export type StatusSubscriber = typeof statusSubscribers.$inferSelect;
4079export type NewStatusSubscriber = typeof statusSubscribers.$inferInsert;
40224080export type NewClaudeWebMessage = typeof claudeWebMessages.$inferInsert;
Modifiedsrc/routes/status.tsx+696−140View fileUnifiedSplit
Large file (1,034 lines). Load full file