Selection never showed. getRowSx set a background on the <tr>, but every
<td> paints background.paper over it, and the hover rule targets cells -
so hover won and selection was invisible. Selection and hover are now
both cell-level rules in DataTable, with selection first so hover layers
on top, and rows carry an `is-selected` class from a new isRowSelected
prop. Uptime, Docker and Infrastructure tables all had the same broken
copy of this; they now share the one implementation, and any future table
gets it for free.
action.selected/selectedHover are defined explicitly and tinted with the
brand, so a selected row reads as chosen rather than dirty. Kept low
(6-11%) so the highlight does not swamp the row, especially in dark mode.
From code review:
- Dropped the Google Fonts @import at the top of index.css. Nothing has
used Inter since the switch to Geist, and it was a render-blocking
third-party request on every load - exactly what self-hosting the font
was meant to avoid on air-gapped installs.
- TextInput's autofill override painted background.default, which used
to equal paper and no longer does, so Chrome-autofilled fields showed
a grey block on white cards. Now paper.
- Sidebar background moved from sx to the bgcolor prop (convention rule
1, and it was a new lint warning), and the backdropFilter dropped -
the background is opaque, so the blur only cost a compositing layer.
- Tab no longer bolds the selected label. That is the same reflow the
ToggleButton change removed, and worse here because the indicator
animates toward a target that moves as the label widens.
- Corrected the StatBox gradient comment: contrastText is white in light
mode but near-black in dark, so the constraint is staying near `main`,
not staying dark.
Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
The sidebar set no background, so it inherited background.default. Once
that became an off-white ground rather than plain white, the nav read as
a grey slab against the content.
It now sits on background.paper, which also gives the layout a real
figure/ground: white nav, slightly darker content area behind it. The
existing right border and the mobile Backdrop still do their jobs, and
an opaque panel is the correct behaviour over that backdrop.
Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
The group labels sat 11px left of the icon column and rendered at the
same 13px as the nav items, so the groups read as indented blocks rather
than headings over a list.
The label used paddingLeft LAYOUT.LG (16px) while NavItem uses
theme.spacing(5) (10px); matching NavItem puts the label flush with the
icons. The eyebrow variant is 13px, so the size is overridden to
typographyLevels.s (11px) to sit below the item text. Top padding goes
from XS to SM for a little more separation above each group.
Measured in the browser: label and first icon both start at x=12, label
renders 11px against the items' 13px.
Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
The group wrapper was rendering as `<li>`, but the enclosing `List` is
`component="nav"` and so renders a `<nav>`, not a `<ul>`. That put list items
directly inside a nav — invalid HTML, and a screen reader announces orphaned
list items with no enclosing list or item count.
`NavItem` renders a `div` via `ListItemButton`, so there is no list structure
for the group wrapper to join. Makes it a plain `Box` and drops the
now-redundant `listStyle: "none"`.
Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
The sidebar listed twelve items in a flat column ordered roughly by when each
feature was added, so nothing signalled that Uptime, Pagespeed and
Infrastructure are the same kind of thing, or that Tags and Proxies are
configuration rather than views. Adding a new monitor type made this worse:
the natural place for a new entry is the end of the list, three rows below the
item it most resembles.
Groups the menu into Monitoring, Alerting, Reporting and Configuration.
`getMenu` now returns groups of items rather than a flat array; the sidebar
renders an eyebrow label per group.
When collapsed to 64px there is no room for a label, so the labels are
replaced by dividers between groups — the grouping survives as rhythm.
Settings moves from the middle of the list to the end of Configuration.
Adds four `components.sidebar.menu.groups.*` keys across all 19 locales.
Also corrects the i18n note in CLAUDE.md, which claimed translations are
managed through PoEditor.
Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
- New PageHeader component (variant D — grouped icon-pill toolbar):
big title + muted description on the left, Support / Docs /
Changelog as a 3-icon segmented pill on the right with tooltips.
External links open in a new tab.
- BasePage / BasePageWithStates / MonitorBasePageWithStates accept
a headerKey prop; when set, render PageHeader pulling
pages.<headerKey>.header.title and .description from i18n.
When unset, falls back to the existing breadcrumb (no behavior
change for not-yet-migrated pages).
- All top-level pages (uptime, infrastructure, pageSpeed,
notifications, maintenance, statusPages, incidents, logs, checks,
account, settings) now pass their headerKey.
- Sidebar: removed the bottom menu (Support / Discussions / Docs /
Changelog) since those links now live in the page header.