From c6566759783b44068721587ad952f93823829754 Mon Sep 17 00:00:00 2001 From: Christopher Rodriguez Date: Tue, 27 Oct 2026 18:34:49 -0700 Subject: [PATCH] feat: say when a summary is longer than the toast will show MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dashboard shows an alert in a toast, not the banner this repository's README still described, and it clamps the summary to three lines on desktop and two on a phone. Everything else — the alert panel, the "Report a Bug" interstitial, the overlay's support panel and the card on this site — shows it whole, so a long summary is correct everywhere and under-read in the one place most people meet an incident. Nothing enforces that, and nothing should: prose length must never hold up a status page mid-incident. So the build prints a note past 200 characters and still exits 0, and the README says what the surfaces actually are, to lead with the symptom, and to leave the workaround to its own field, since every surface that shows the summary renders that directly beneath it. Also drops a doubled article from the missing-field errors, which read "needs a an id". Co-authored-by: Claude Opus 5 --- README.md | 28 ++++++++++++++++++++++++++-- build.mjs | 38 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c0e8110..6edbdfa 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ build, so the template never becomes an incident. id: controller-saving-down # stable, and what dismissal is keyed on — see below severity: outage # info | warning | outage title: Controller saving is down -summary: One or two sentences. Shown in the dashboard banner and the overlay's support panel. +summary: The symptom first, in a sentence that stands alone — see below workaround: One line on what the reader can do right now. Optional. links: # optional, up to 5, all https - label: PlanetScale status @@ -50,6 +50,25 @@ against the PR — has to pass before it can merge. Merging then runs the **Depl the build fails, nothing is deployed and the previous version stays live** — which is also why the build is worth running before you push. +### Writing a summary + +The summary travels further than anything else in the file, and the surface that reaches the most +people shows the least of it. In the dashboard it is a toast: the title, then about three lines of +summary on desktop and two on a phone, clamped, with the rest a press away. The alert panel, the +"Report a Bug" interstitial, the card on this site and the overlay's support panel all show it +whole. + +So lead with the symptom, in one sentence that stands on its own — what breaks, and where someone +sees it. Use the rest for what that sentence earns. Past about 200 characters the build says so, +as a note rather than a failure, because nothing about prose length should hold up a status page +mid-incident. + +Two things don't belong in it: + +- **The workaround.** Every surface that shows the summary renders `workaround` directly beneath + it, so a fix written into both reads twice. +- **The long version.** That is what the body is for, and it is the one field with no limit. + ### When something is actually on fire Repository admins can still push straight to `main`, and that is deliberate: this site exists to @@ -106,10 +125,15 @@ Don't bother putting a CDN in front of this to speed it up; there is nothing to | Reader | What it shows | | --- | --- | -| Dashboard banner | The loudest active alert, with its workaround and first link | +| Dashboard toast | The loudest active alert: its title, and the first two or three lines of its summary | +| Dashboard alert panel | That same alert whole — summary, workaround and every link — one press from the toast | | Dashboard "Report a Bug" | Every active alert, before the bug tracker opens | | Overlay support panel | Every active alert, as health checks, and in the copied report | +A phone's toast opens the panel, since sending someone to another tab mid-task is too much. On +desktop it links here instead when the incident has a page of its own, and opens the panel when it +does not. + `alerts.json` is `{ "schema": "gpv-status/1", "alerts": [...] }`, and it carries only what a reader could act on: every incident still open or still to come, plus anything resolved in the last 30 days. The archive grows on the index page instead, which keeps every incident forever — no reason to make diff --git a/build.mjs b/build.mjs index da00901..4d55f5f 100644 --- a/build.mjs +++ b/build.mjs @@ -47,6 +47,16 @@ const SEVERITY_LABEL = { info: 'Info', warning: 'Warning', outage: 'Outage' }; const LIMITS = { title: 120, summary: 500, workaround: 200, id: 64, label: 60, links: 5 }; +/** + * Roughly how much of a summary the dashboard's toast shows: it clamps to three lines on desktop + * and two on a phone. Past this a summary is still shown whole everywhere else — the alert panel, + * the "Report a Bug" interstitial, the overlay's support panel and the card on this site — but the + * toast is where most people meet an incident, so a summary that needs its second half to make + * sense is one most readers will only half read. A note rather than a failure: nothing about prose + * length should hold up a status page mid-incident. + */ +const SUMMARY_CLAMPED_AT = 200; + /** * How long a resolved incident stays in alerts.json. Nothing reading the feed ever shows a resolved * alert — the dashboard and the overlay both check the window themselves — so this is slack for a @@ -59,10 +69,21 @@ const FEED_RESOLVED_DAYS = 30; const MAX_FEED_ALERTS = 50; const problems = []; +const notes = []; /** Collects every problem rather than stopping at the first, so one run tells you all of them. */ const fail = (file, message) => problems.push(`${file}: ${message}`); +/** Worth saying, but never worth refusing to publish over. Printed once the run is done. */ +const note = (file, message) => notes.push(`${file}: ${message}`); + +const printNotes = () => { + if (notes.length === 0) return; + console.warn('\nWorth a look, though none of this stopped the build:\n'); + for (const message of notes) console.warn(` - ${message}`); + console.warn(''); +}; + const isHttps = (value) => { try { return new URL(value).protocol === 'https:'; @@ -92,7 +113,7 @@ function validate(file, data, body) { const text = (value, field, max, required) => { if (value === undefined || value === null || value === '') { - if (required) fail(file, `needs a ${field}`); + if (required) fail(file, `needs ${field}`); return undefined; } if (typeof value !== 'string') { @@ -129,7 +150,7 @@ function validate(file, data, body) { } } - return { + const alert = { id: text(id, 'an id', LIMITS.id, true), severity, title: text(title, 'a title', LIMITS.title, true), @@ -140,6 +161,17 @@ function validate(file, data, body) { endsAt: endsAt === '' || endsAt === undefined ? null : endsAt, body }; + + if (alert.summary && alert.summary.length > SUMMARY_CLAMPED_AT) { + note( + file, + `the summary is ${alert.summary.length} characters and the dashboard's toast shows about ` + + `${SUMMARY_CLAMPED_AT}. Lead with the symptom in a sentence that stands on its own, and ` + + 'leave the workaround to its own field and the long version to the body.' + ); + } + + return alert; } async function readAnnouncements() { @@ -392,6 +424,7 @@ async function main() { if (checkOnly) { console.log(`${announcements.length} announcement(s) look fine.`); + printNotes(); return; } @@ -423,6 +456,7 @@ async function main() { console.log( `Built ${announcements.length} announcement(s), ${active} active, ${feed.length} in the feed, into dist/` ); + printNotes(); } main().catch((error) => {