Files
Sandbar/docs/design.md
T
VeillaxandClaude Sonnet 5.5 4ea4fe5289 Show 90-day uptime bars on the status page
Every check is also counted in a per-day table that outlives the raw checks, so the public page
can draw one bar per UTC day with a tooltip, as most hosted status pages do. Existing databases
are backfilled once from the checks they still have.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 04:22:54 -06:00

4.5 KiB

Sandbar design language

Sandbar's pages follow the Lightwell design language, as Hammerhead does. The references are docs/design.md in the Lightwell repository and in the Hammerhead repository. This page records how that language is applied here. When they disagree, Lightwell's document wins.

What is shared

  • Tokens. Colors, spacing (4px scale), radius (6px and 10px), type sizes (12 to 28px), the single shadow and the dark theme are the Lightwell values, copied from Hammerhead. The accent is Lightwell's teal, #1f5c5c, with white text on it.
  • Principles. Quiet neutrals with one accent, plain and specific wording, one way to do each thing, calm density, and WCAG 2.1 AA.
  • Voice. No emoji, no em or en dashes, no exclamation marks, sentence case, buttons that name the result, and errors that say what happened and what to do next. Errors from the server are capitalized in one place (bad in api.go).
  • Status carries text. A status is a badge with a label ("Firing", "Down", "Resolved"), never a colored dot on its own.

Where it lives

File What it holds
internal/sandbar/web/index.html The app. The token block sits between tokens:begin and tokens:end; raw colors are allowed only there.
internal/sandbar/web/status.html The public status page. Same token block, repeated.

Both are single files with no build step. Change a token in both, and in Hammerhead when the language changes.

Components

The helpers at the top of the script in index.html are the same as Hammerhead's: card, pageHeader, field, chk, badge, emptyState, modal, dialogForm, confirmDialog, ask, toast, skeleton. Feature code uses these and does not style one-off. Sandbar adds three, built only from tokens:

Helper Use
table(head, rows) A table inside a flush card, with column headers
rowList(addLabel, make, initial) A list the person adds to and removes from, such as people in a rotation or steps in a policy
timeline(events) Dated entries for an alert or an incident

Status page bars

  • Uptime bars on the public page are one bar per UTC day, 90 of them. Green is no failures, amber is at least 99% passing, red is below that, grey is no data. Color is never the only signal: the row carries a text status badge and the uptime percentage, each bar's tooltip says the date and the numbers in words, and the whole strip has an aria-label summary.

Icons

Lucide style, outline only, stroke 1.75, round caps, currentColor. Sizes are 16, 18 and 22 pixels. They are inline in the ICONS table. An icon goes with a text label, and icon-only buttons carry an aria-label.

Approved names: sandbar, alerts, incidents, oncall, monitors, topics, channels, status, users, account, out, menu, close, add, check, back, light, dark, alert. Add a name here and in ICONS together.

Patterns

  • One primary action per view. It sits in the page header ("Declare incident", "Add monitor").
  • List pages show the page header, a filter or tab row where there is one, then the table in a card with no second title.
  • Detail pages (an incident) use a back link, the title with its status badge, then cards, most important first.
  • Dialogs and forms end with Cancel and then the primary action, at the bottom right. Labels sit above fields, and rules such as "At least 10 characters" are hints below them.
  • Destructive actions always use confirmDialog. The title names the object, the body says what is lost and whether it can be undone.
  • Dates have the month written out ("Nov 2, 2026"), with fmtDate and fmtTime.
  • Empty states say why the list is empty and what will appear there.
  • Secrets such as a channel's address are never sent back to the browser. The form says it is stored encrypted and hidden.

Layout

  • Content is capped at 1120px. Below 860px the sidebar becomes a slide-in drawer with a top bar. Phones keep a 16px side gutter.
  • On touch screens controls are at least 44px high and inputs are 16px so the page does not zoom.
  • Motion is minimal and turns off under prefers-reduced-motion. The theme follows the system and can be switched from the sidebar.
  • The public status page is a single column, 720px wide, and has no navigation.

Checking a change

node scripts/lint-design.mjs

It fails on em and en dashes, emoji, and raw colors outside the token block, in the pages, the docs and the Go source. Then check the page in light and dark, at phone and desktop width, using only the keyboard.