Skip to content

Status Page 02

The status page read during diligence rather than during an outage: ninety days of uptime, the latency curve, and every incident carrying its post-mortem — including the bad one.

Take 2 of 27 blocks237 lineslucide-reactAdded 13 Sept 2026

What's included

  • app/status-page-02.tsx
  • 7 blocks it is built from, installed with it
  • Needs lucide-react

Works with

  • React
  • Next.js
  • Tailwind CSS
  • TypeScript

npx hoverlab add status-page-02

Or over MCP, from your editor's agent — no account needed.

License

Free to read, copy and install, for personal and non-commercial projects. Shipping it in client work or a paid product needs Pro ($79 once). The source lands in your repo and stops being ours — no attribution, nothing to upgrade.

Was this useful?

Preview

Ninety days of record

Everything is operational right now, which is the least useful thing this page can tell you. If you are reading it during diligence rather than during an outage, the numbers below are the ones that matter — including the November incident, which was ours and which was bad.

99.94%
Uptime, trailing 90 days
SLA commitment is 99.9%
4
Incidents, trailing 90 days
One of them was serious
41 min
Longest single outage
14 November, sync workers
4 of 4
Post-mortems published
Within 5 business days each

API response time, seven months

p50 and p99 on the reconciliation endpoint. The November spike is the sync stall; the p99 line is the honest one and it is why that month is listed as an incident rather than as degraded performance.

  • p50 (ms)
  • p99 (ms)
  • 3000 ms
  • 1000 ms
  • -1000 ms
  • Jun
  • Aug
  • Oct
  • Dec
API response time, seven months
Monthp50 (ms)p99 (ms)
Jun82 ms410 ms
Jul79 ms395 ms
Aug84 ms428 ms
Sep81 ms402 ms
Oct88 ms447 ms
Nov143 ms1980 ms
Dec86 ms436 ms

Every incident in the window

Four, all of them, newest first — including the upstream one that was not our fault and the maintenance window that overran, because a history containing only green is read as a history that gets edited.

Raw event log

This month

  • Platform deployed ledger-worker v4.22.1

  • Platform completed maintenance on EU read replicas

December

  • Platform deployed retry policy with jitter — the Nov 14 fix

  • Platform published post-mortem for 14 Nov sync stall

Hear about the next one first

Four channels, all free, none of them attached to a marketing list. The email option is the one most customers pick and it sends at most three messages per incident.

The real page, rendered in your current theme — every section below is a live block, not a screenshot.

The other take

Same page type, different bet about what the reader came for. Neither is the “correct” one — pick the layout whose opening section matches the question your visitors arrive with.

Take 1 of 2
Open the full page for this page

Status Page

Written for the person refreshing it during an outage: current state above the fold, the maintenance window as a real block with an end time, and a component list that names what still works.

System Pages6 blocks155 lines

Built from

All blocks
  1. Responsive NavbarBrand, links and CTAs with a mobile panel that unmounts when closed — so its links are never tabbable while invisible — plus escape-to-close and focus return.
  2. Hairline Stats BandA four-up metrics strip with 1px hairline dividers, built from a single gap-px grid so the rules stay crisp on any display.
  3. Dual-Series Line ChartA year-over-year trend in inline SVG with a printed axis, no charting dependency and the same data repeated as a screen-reader table.
  4. Metric Alert ListAlert thresholds with their current distance from firing, so a quiet alert is visibly armed rather than possibly broken.
  5. Audit Log TimelineEvents grouped by day on a vertical rail, icons varying by type and one entry expanded into a detail card — what keeps forty events scannable instead of forty gray dots.
  6. Notification Channel ListWhich channel carries which event, so the loud ones can be moved rather than muted entirely.
  7. App Footer with StatusThe footer of a signed-in product rather than a marketing site: a live status line with words as well as colour, plus locale and theme as native selects.

Only want one section? Open it and copy that block instead — browse Navigation.

Source

app/status-page-02.tsx
/**
 * The status page, take 02 — the one read when nothing is wrong.
 *
 *   record      uptime over 90 days, as the headline number
 *   trend       the latency curve, because "up" and "usable" differ
 *   incidents   every one of them, with the post-mortem attached
 *   history     the raw event log, newest first
 *   subscribe   how to hear about the next one before you notice it
 *
 * Take 01 is written for the person refreshing during an outage: current
 * state above the fold, the maintenance window as a real block with an end
 * time, and a component list naming what still works. Under those
 * conditions everything except "is it me or is it you" is noise, and take 01
 * is correctly ruthless about it.
 *
 * This take serves the other visitor entirely — a procurement reviewer or a
 * prospect's engineer doing diligence, weeks before signing, on a day when
 * the system is fine. For them the current banner is the least interesting
 * thing on the page. They want the record: how often, how long, and whether
 * anyone wrote down what happened.
 *
 * So the ordering inverts. Uptime and latency lead; the incident list is the
 * body of the page rather than a footnote; and every incident row carries a
 * post-mortem rather than a one-line "resolved". The four incidents are all
 * shown, including the bad one — a status page whose history contains only
 * green is read as a status page that deletes things.
 *
 * `line-chart-panel` earns its place here and would be actively harmful on
 * take 01: during an outage a latency chart invites the reader to diagnose
 * instead of telling them what to do. On a diligence read it is the whole
 * question, because a service that is technically up at nine seconds is not
 * up.
 */

import * as React from 'react'
import { NavbarSimple } from '@/components/navbar-simple'
import { StatsBand } from '@/components/stats-band'
import { LineChartPanel } from '@/components/line-chart-panel'
import { MetricAlertList } from '@/components/metric-alert-list'
import { ActivityTimeline } from '@/components/activity-timeline'
import { NotificationChannelList } from '@/components/notification-channel-list'
import { FooterStatusLocale } from '@/components/footer-status-locale'

const RECORD = [
  { value: '99.94%', label: 'Uptime, trailing 90 days', caption: 'SLA commitment is 99.9%' },
  { value: '4', label: 'Incidents, trailing 90 days', caption: 'One of them was serious' },
  { value: '41 min', label: 'Longest single outage', caption: '14 November, sync workers' },
  { value: '4 of 4', label: 'Post-mortems published', caption: 'Within 5 business days each' },
]

const LATENCY_LABELS = [
  'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',
]

const LATENCY_SERIES = [
  { name: 'p50 (ms)', values: [82, 79, 84, 81, 88, 143, 86], area: true },
  { name: 'p99 (ms)', values: [410, 395, 428, 402, 447, 1980, 436] },
]

const INCIDENTS = [
  {
    id: 'nov-14',
    label: '14 Nov — Sync workers stalled for 41 minutes',
    detail:
      'A NetSuite governance change we had not accounted for caused every ledger worker to back off simultaneously. Reconciliation was unavailable; nothing was lost and nothing double-posted. Root cause was a retry policy with no jitter, which is an embarrassing thing to find in 2025. Post-mortem published 19 Nov.',
    tone: 'critical' as const,
    status: '41 min',
  },
  {
    id: 'oct-02',
    label: '2 Oct — Elevated latency, 2h 10m',
    detail:
      'p99 above 2 seconds for just over two hours during a database failover that took longer than the runbook said it would. The service stayed up. We count this as an incident anyway, because a nine-second page load is an outage to the person waiting for it.',
    tone: 'warning' as const,
    status: '2h 10m',
  },
  {
    id: 'sep-19',
    label: '19 Sep — Stripe connector degraded, 26 min',
    detail:
      'Upstream. Stripe had a partial API outage; our connector queued correctly and drained without loss once it cleared. Listed here because it affected you, not because it was ours.',
    tone: 'neutral' as const,
    status: '26 min',
  },
  {
    id: 'jul-08',
    label: '8 Jul — Scheduled maintenance overran by 18 minutes',
    detail:
      'Announced window was 60 minutes; it took 78. We publish overruns as incidents because a maintenance window that quietly doubles is indistinguishable from an outage on your side of it.',
    tone: 'neutral' as const,
    status: '+18 min',
  },
]

const EVENT_LOG = [
  {
    label: 'This month',
    events: [
      {
        id: 'e1',
        kind: 'deploy' as const,
        actor: 'Platform',
        action: 'deployed',
        target: 'ledger-worker v4.22.1',
        at: '2026-01-09T09:14:00Z',
        timeLabel: '9 Jan, 09:14 UTC',
      },
      {
        id: 'e2',
        kind: 'settings' as const,
        actor: 'Platform',
        action: 'completed maintenance on',
        target: 'EU read replicas',
        at: '2026-01-04T02:00:00Z',
        timeLabel: '4 Jan, 02:00 UTC',
      },
    ],
  },
  {
    label: 'December',
    events: [
      {
        id: 'e3',
        kind: 'deploy' as const,
        actor: 'Platform',
        action: 'deployed',
        target: 'retry policy with jitter — the Nov 14 fix',
        at: '2025-12-11T10:02:00Z',
        timeLabel: '11 Dec, 10:02 UTC',
      },
      {
        id: 'e4',
        kind: 'incident' as const,
        actor: 'Platform',
        action: 'published post-mortem for',
        target: '14 Nov sync stall',
        at: '2025-12-01T16:30:00Z',
        timeLabel: '1 Dec, 16:30 UTC',
      },
    ],
  },
]

const SUBSCRIBE = [
  {
    id: 'email',
    label: 'Email',
    detail: 'One message when an incident opens, one when it closes, one when the post-mortem lands. Never anything else — this list is not used for product news.',
    tone: 'positive' as const,
    status: 'Recommended',
  },
  {
    id: 'webhook',
    label: 'Webhook',
    detail: 'POST to your endpoint on every state change, signed. The payload matches the public JSON feed.',
    tone: 'neutral' as const,
    status: 'JSON',
  },
  {
    id: 'rss',
    label: 'RSS / Atom',
    detail: 'Both, unauthenticated, including resolved incidents and post-mortem links.',
    tone: 'neutral' as const,
    status: 'Open',
  },
  {
    id: 'slack',
    label: 'Slack',
    detail: 'A channel app. It posts to one channel and it cannot read anything in your workspace.',
    tone: 'neutral' as const,
    status: 'Write-only',
  },
]

export default function StatusPage02() {
  return (
    <div className="min-h-screen bg-background text-foreground">
      <NavbarSimple brand="Acme" activeLabel="Status" ctaLabel="Subscribe" ctaHref="#status-page-02-subscribe" />

      <main>
        <div className="mx-auto w-full max-w-6xl px-4 pt-12 sm:px-6 lg:px-8">
          <h1 className="text-3xl font-bold tracking-tight sm:text-4xl">
            Ninety days of record
          </h1>
          <p className="mt-3 max-w-2xl text-pretty text-muted-foreground">
            Everything is operational right now, which is the least useful
            thing this page can tell you. If you are reading it during
            diligence rather than during an outage, the numbers below are the
            ones that matter — including the November incident, which was
            ours and which was bad.
          </p>
        </div>

        <StatsBand stats={RECORD} />

        {/*
          A latency chart would be harmful on take 01 — during an outage it
          invites the reader to diagnose instead of telling them what to do.
          On a diligence read it is the whole question, because a service
          that is technically up at nine seconds is not up.
        */}
        <div className="mx-auto w-full max-w-5xl px-4 py-10 sm:px-6 lg:px-8">
          <LineChartPanel
            heading="API response time, seven months"
            description="p50 and p99 on the reconciliation endpoint. The November spike is the sync stall; the p99 line is the honest one and it is why that month is listed as an incident rather than as degraded performance."
            labels={LATENCY_LABELS}
            series={LATENCY_SERIES}
            format={(v) => `${v} ms`}
          />
        </div>

        <MetricAlertList
          heading="Every incident in the window"
          intro="Four, all of them, newest first — including the upstream one that was not our fault and the maintenance window that overran, because a history containing only green is read as a history that gets edited."
          rows={INCIDENTS}
        />

        <ActivityTimeline heading="Raw event log" groups={EVENT_LOG} />

        <div id="status-page-02-subscribe">
          <NotificationChannelList
            heading="Hear about the next one first"
            intro="Four channels, all free, none of them attached to a marketing list. The email option is the one most customers pick and it sends at most three messages per incident."
            rows={SUBSCRIBE}
          />
        </div>
      </main>

      <FooterStatusLocale
        productName="Acme"
        status="operational"
        version="v4.22.1"
      />
    </div>
  )
}

How to use it

  1. 1. Copy each of the 7 blocks above into components/ — each block page has its own copy button.
  2. 2. Drop this file at app/status-page-02.tsx. The imports already point at @/components/…, so they resolve with no edits.
  3. 3. Delete the sections you do not want. Every block takes props, so the copy changes without the layout moving.

For AI

The component, its props, the design tokens it expects and the command that installs it — as one prompt. Paste it into Claude, Cursor, v0 or ChatGPT and what they build around it will match the rest of the catalog instead of inventing its own system.

See the prompt

More System Pages

Open the full page for this page

404 Page

Drops straight into app/not-found.tsx, and keeps the site header and footer — on a 404 the navigation is the most useful thing on screen.

System Pages1 blocks62 lines
Open the full page for this page

Server Error

Retry first, because a 500 is our failure and offering navigation implies the rest of the site works — which is exactly what is in doubt.

System Pages2 blocks44 lines
Open the full page for this page

Scheduled Maintenance

Planned downtime with an actual end time on it — "back soon" is not a time, and a maintenance page without one looks like an outage nobody has noticed.

System Pages2 blocks35 lines
Open the full page for this page

Permission Denied

A 403 that names who can grant what was refused, because the next question is always "then who can" and a support link is the wrong answer to it.

System Pages2 blocks42 lines
Take 1 of 2
Open the full page for this page

Legal Document

Terms, privacy, DPA and cookies in one frame — the docs layout, because people arrive at a legal page looking for a specific clause and a wall of text makes that a Ctrl-F expedition.

System Pages3 blocks50 lines
Take 2 of 2
Open the full page for this page

Legal Document 02

One document with a plain-language column beside each clause, for the reader who cannot name the clause they need — and the cookie control inline, because a banner is unreachable the moment it has been dismissed.

System Pages6 blocks200 lines