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) => {