Addresses the screenshot-review feedback in #26. No framework or build
step added — all of this stays within the existing plain HTML/CSS/
vanilla-JS + go:embed architecture.
- Nav: re-enable the bottom tab bar that was already built and
switched off (Queue/On-call/Alerts/Team + a "More" sheet for
Stats/Admin/Account), replacing the hamburger on phone width.
- Queue: chip counts, a scroll fade on the filter row, a
"Triggered Xh ago" + severity label per row, a chevron on the team
switcher so it reads as a dropdown.
- On-call: collapse repeated same-person days into shift bars (week
view and "your shifts" both), show the week as a date range with
the ISO week number as secondary text, split "Current shift" out
from "Next shifts" with "ends in Nd", a pill badge + row highlight
for "you".
- Incident detail: fix the actual bug behind the duplicate
"acknowledged" timeline entries (acknowledgeIncident's UPDATE had no
guard on the incident's current status, so acknowledging an
already-acknowledged incident silently re-logged the event — now
idempotent, with regression tests on both the authenticated route
and the ntfy ack-button route). Relabel escalation re-pages so they
don't look like the same page landing twice. Copy the primary action
up near the top. Label the "···" button. Group the timeline by
phase (triggered/acknowledged/resolved). Add an "at a glance"
summary row (duration/severity/responsible) and collapse the group
labels by default.
- Team overview: reword the vague copy ("One owner." etc.) into plain
labels.
- Empty states: fill in missing icons/one-liners across queue,
alerts, stats and the incident timeline.
- CSS: fix card padding bugs, verify link contrast already passes AA,
introduce a --fs-* type-scale token set and migrate the few
genuinely isolated cases onto it (left sizes tied to a fixed shape,
a deliberately prominent display, or a non-negotiable constraint
like the iOS-zoom-prevention input size as documented exceptions
rather than guess at a render this change can't see).
Verified with the full fmt/lint/test/helm-lint gate, plus a live
instance against the test DB with seeded incidents and schedule data
to trace the on-call grouping and timeline phase-splitting logic
against real API responses.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
state.js's currentTeam() was hard-coded to teams[0] and never really meant
"the team currently selected" — team.js's settings page and queue.js's
filter chips each kept their own separate, unsynchronized notion of "which
team" instead, so picking one on one page had no effect on the other.
Replaces both with a single state.selectedTeamID, set only through the new
setSelectedTeam (persisted in localStorage, unlike the queue's old per-tab
sessionStorage filter) and broadcast to listeners via onTeamChange. A new
teamselector.js control — a coloured dot plus the team's name, or "All
teams" — sits at the top of both the desktop sidebar and the mobile topbar,
opening the existing bottom-sheet menu to switch. Shown only once someone is
in more than one team, matching every other team-aware control in this app.
Colours come from a new teamColorClass() in format.js, hashing a team's id
into the six-colour rc1..rc6 palette already used for the rota's per-person
chips, so no schema or API change is needed. The queue's team filter chips
pick up the same colours.