Pad the settings cards, and make status and timeline readable at a glance

The Team settings cards (Escalation, Sources, Members, Switches, Single
sign-on) printed their text flush against the border with the button in the
corner: .card has never had padding and these never added any. A card with
a header row now pads itself, with the title left, the button right and a
divider before the content.

The incident page no longer carries the primary action twice. The copy up
by the status is gone and the sticky bar keeps it; Note is the timeline's
link, with Copy in the bar for a resolved incident, and the phone's More
sheet drops what the bar already shows. In the queue, a row's title wraps
to two lines so the namespace that tells rows apart is no longer cut off,
zero counts on the filter chips are dimmed, and a row omits the status the
filter already states and the team once the queue is narrowed to one.

Status colours failed 4.5:1 against their own fill in the light theme
(warning 3.6, info 4.1, critical 4.4, ok 4.45, snooze 4.49), so the light
tokens are darker; the dark theme already passed and is unchanged. Severity
badges now carry a shape as well as a colour, and each timeline event has an
icon. An escalation that ran out of levels, a failed notification and a
silent heartbeat stand out in amber or red.

Web UI only: no endpoint or JSON shape changed, so nothing to mirror in
terdut-tui.
This commit is contained in:
Niklas Ye
2026-10-08 14:07:24 +02:00
parent 065b557860
commit 7412456c5a
5 changed files with 104 additions and 54 deletions
+42 -22
View File
@@ -20,23 +20,23 @@
--accent-text: #ffffff;
--accent-soft: #e8eefc;
--crit: #d0342c;
--crit: #c22d26;
--crit-soft: #fdecea;
--warn: #b86e00;
--warn: #8f5500;
--warn-soft: #fdf3e1;
--info: #2f6fdf;
--info: #245fc7;
--info-soft: #e9f0fd;
--ok: #1d7f4c;
--ok: #1a7445;
--ok-soft: #e6f5ec;
--snooze: #6b5bd2;
--snooze: #6050c8;
--snooze-soft: #efedfb;
/* Two hues that mean nothing on their own. The rota needs six colours to
tell six people apart and the palette above only has four that are not
already an alarm. */
--teal: #0f7d8c;
--teal: #0c7180;
--teal-soft: #e3f4f6;
--pink: #b3427e;
--pink: #a63b73;
--pink-soft: #fbe8f2;
--radius: 10px;
@@ -387,6 +387,8 @@ input:focus, textarea:focus { outline: none; border-color: var(--accent); box-sh
.chip[aria-selected="true"], .chip[aria-checked="true"] { background: var(--text); border-color: var(--text); color: var(--bg); }
.theme-picker { padding: 0 0 8px; }
.chip .count { margin-left: 4px; opacity: 0.7; }
/* Nothing in it: step back so the chips that have something stand out. */
.chip .count.zero { opacity: 0.4; }
/* An overlay, not a flex item: absolute against .chips' own (non-scrolling)
box stays flush with its real right edge regardless of scroll position,
which turned out not to be true of position:sticky here — as a flex
@@ -422,7 +424,10 @@ input:focus, textarea:focus { outline: none; border-color: var(--accent); box-sh
background: var(--sev, var(--border-strong));
}
.row-title {
font-weight: 650; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
font-weight: 650; overflow: hidden; overflow-wrap: anywhere;
/* Two lines, not one: titles differ at the end ("PodRestarting
(namespace=...") and an ellipsis cut exactly the part that tells rows apart. */
display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2;
}
.row-age { color: var(--faint); font-size: 13px; text-align: right; white-space: nowrap; }
.row-meta {
@@ -481,6 +486,7 @@ input:focus, textarea:focus { outline: none; border-color: var(--accent); box-sh
}
.badge::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.badge.plain::before { display: none; }
.badge .badge-icon { width: 12px; height: 12px; stroke-width: 2.4; }
.badge.st-triggered, .badge.st-firing { background: var(--crit-soft); color: var(--crit); }
.badge.st-acknowledged { background: var(--warn-soft); color: var(--warn); }
.badge.st-snoozed { background: var(--snooze-soft); color: var(--snooze); }
@@ -516,10 +522,6 @@ input:focus, textarea:focus { outline: none; border-color: var(--accent); box-sh
.detail-head .crumb { font-weight: 600; color: var(--muted); font-size: 14px; }
.detail-title { font-size: var(--fs-xl); font-weight: 750; letter-spacing: -0.01em; margin: 16px 0 8px; overflow-wrap: anywhere; }
.detail-badges { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px; }
/* A copy of the sticky actionbar's primary button, right under the status
it responds to — see quickActions() in incident.js. */
.detail-quick-actions { margin-bottom: 14px; }
.detail-quick-actions .btn-primary { font-size: 16px; min-height: 44px; }
.card {
background: var(--surface);
@@ -588,18 +590,26 @@ details[open] > summary { margin-bottom: 8px; }
.tl-item::before {
content: ""; position: absolute; left: 23px; top: 0; bottom: 0; width: 2px; background: var(--border);
}
.tl-item:first-child::before { top: 16px; }
.tl-item:last-child::before { bottom: calc(100% - 16px); }
.tl-item:first-child::before { top: 18px; }
.tl-item:last-child::before { bottom: calc(100% - 18px); }
.tl-dot {
position: relative; z-index: 1;
width: 10px; height: 10px; margin: 5px 0 0 5px; border-radius: 50%;
background: var(--surface); border: 2px solid var(--faint);
display: grid; place-items: center;
width: 20px; height: 20px; border-radius: 50%;
background: var(--surface); border: 2px solid var(--border-strong); color: var(--muted);
}
.tl-triggered .tl-dot, .tl-notify_failed .tl-dot, .tl-deadman_silent .tl-dot { border-color: var(--crit); background: var(--crit); }
.tl-acknowledged .tl-dot { border-color: var(--warn); background: var(--warn); }
.tl-resolved .tl-dot { border-color: var(--ok); background: var(--ok); }
.tl-snoozed .tl-dot { border-color: var(--snooze); }
.tl-note .tl-dot { border-color: var(--accent); background: var(--accent); }
/* An event with no icon keeps the old plain dot. */
.tl-dot:empty { width: 10px; height: 10px; margin: 5px 0 0 5px; }
.tl-icon { width: 11px; height: 11px; stroke-width: 2.6; }
.tl-triggered .tl-dot, .tl-notify_failed .tl-dot, .tl-deadman_silent .tl-dot { border-color: var(--crit); color: var(--crit); }
.tl-acknowledged .tl-dot { border-color: var(--warn); color: var(--warn); }
.tl-resolved .tl-dot { border-color: var(--ok); color: var(--ok); }
.tl-snoozed .tl-dot { border-color: var(--snooze); color: var(--snooze); }
.tl-note .tl-dot, .tl-resolution_note .tl-dot { border-color: var(--accent); color: var(--accent); }
.tl-problem .tl-dot { border-color: var(--warn); background: var(--warn-soft); color: var(--warn); }
.tl-problem.tl-notify_failed .tl-dot, .tl-problem.tl-deadman_silent .tl-dot { border-color: var(--crit); background: var(--crit-soft); color: var(--crit); }
.tl-problem .tl-text { color: var(--warn); font-weight: 600; }
.tl-problem.tl-notify_failed .tl-text, .tl-problem.tl-deadman_silent .tl-text { color: var(--crit); }
.tl-body { min-width: 0; font-size: 14px; }
.tl-text { overflow-wrap: anywhere; }
.tl-text .who { font-weight: 650; }
@@ -906,7 +916,17 @@ kbd {
Six columns do not fit a phone, so the table scrolls inside its card rather
than the page. A heartbeat under a switch with several is indented, the way
the escalation ladder indents its levels. */
.card-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
/* A card with a header row (the Team and Admin settings cards) pads itself:
plain .card has no padding, so these used to print their text flush against
the border with the button jammed in the corner. The divider separates the
title row from the content below it. */
.card:has(> .card-head) { padding: 20px; }
@media (min-width: 900px) { .card:has(> .card-head) { padding: 24px; } }
.card-head {
display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap;
padding-bottom: 14px; margin-bottom: 14px; border-bottom: 1px solid var(--border);
}
.card-head h2 { margin: 0; font-size: 17px; font-weight: 650; }
.table-scroll { overflow-x: auto; margin-top: 12px; }
.status-table th, .status-table td { white-space: nowrap; }
.status-table td.wrap { white-space: normal; min-width: 12em; }