The controller writes the service's return value into the response, but
createMonitor was typed Promise<void> and returned nothing, so every
successful POST /monitors answered data: null and callers had no way to
read the new monitor's id.
Fixes#3917
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D7eViZ2Np5ZpWUYNoRhmAt
Swap Docker and Pagespeed positions in the monitoring section of the
sidebar so Docker appears before Infrastructure and Pagespeed comes last.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Buttons, inputs, selects, pickers, toggles and tabs all declared 34 while
IconButton and the PageHeader action sat at 32, so anything mixing them
in a row was 2px out of alignment.
Everything now reads CONTROL_HEIGHT (32) from constants, so the number
lives in one place instead of nine.
Two content-box fixes were needed to make 32 safe, both measured in the
browser rather than assumed:
- MUI's button line-height is 1.75 - 22.75px at our 13px base - which
with 6px padding needs 34.75px. It was already over its own box at 34
and would have clipped at 32. Now 1.2, which leaves headroom for the
taller scripts we ship (ar, th, vi, ja, zh).
- MUI pads the inner input 8.5px top and bottom, so an input's content
needed 35.7px inside a 32px shell and was being cut off by
overflow:hidden. Padding is zeroed in the theme for both the input and
the Select's div, which centres instead.
Verified in the DOM: button, select and input all render at exactly 32
with scrollHeight equal to height, so nothing is clipped.
Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
The sidebar regression is the real fix here. NavItem paints the selected
item with action.selected but hovers unselected items with MUI's default
action.hover. Once the sidebar moved onto `paper`, selected composited to
rgb(31,38,41) - darker than the ground - while hover came out lighter at
rgb(46,46,51), so hovering any other item made it read as more selected
than the actual selection. Both states now come from the same family
(selectedHover / rowHover), so selected always reads stronger.
Also from review:
- Dropped "Inter" from the status page SANS_STACK. Its webfont went with
the Google Fonts import, so on Windows and Linux the stack was silently
falling through to Segoe UI / Roboto on pages users publish.
- Button focus rings now use the same dark-mode green as input focus
rings. They were still on primary.main at 2.86:1, which is the exact
problem primary.light was added to solve.
- Removed 13 size={16} props made redundant by the Icon default change.
The load-bearing pins stay: size={20} on the public status page, and
the header pins that were relying on the old default.
- Documented that alpha() replaces rather than multiplies the alpha
channel in StatusBox, so the grid is coupled to `divider` being opaque.
- Noted on the isRowSelected JSDoc that the small-screen card branch does
not style selection, same limitation getRowSx already had.
Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
Inputs disagreed on both hover and focus. The theme's MuiOutlinedInput
hovered to text.primary - near-black in light mode - while Select,
TextInput and DatePicker each overrode hover back to `divider`, which is
the resting colour, so those had no hover cue at all. Four behaviours
across four components.
Now defined once in the theme and inherited by every outlined input:
rest divider 1.55:1 light / 1.73:1 dark
hover text.disabled 3.64:1 light / 4.66:1 dark
focus primary, 2px 5.93:1 light / 6.36:1 dark
error error.main
The per-component overrides in Select, TextInput and DatePicker are gone,
so there is one place to change this next time.
Focus in dark mode takes primary.light. The brand green is only 2.86:1 on
the dark ground, which made a focused input dimmer than a hovered one.
`main` is deliberately left alone: it is the fill for contained buttons,
and lightening it would flip their label to dark text across 31 sites.
Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
Third time this bug has surfaced, so this fixes the cause. Icon defaulted
to 20px, but 17 call sites already asked for 16 explicitly against 2 for
20, and the sidebar clamps to 16 in CSS - so 16 was the house size and
the 20 default was one nobody chose. Anything bare rendered above the cap
height of the 13px text beside it.
The default is now 16. Five sites that were relying on the old default
and sit next to labels are pinned explicitly anyway (team header, status
page controls, status page delete, theme toggle), and the public status
page's warning icon is pinned at 20 so the default change does not alter
a page users publish.
Sidebar icons are unaffected either way - their CSS already forced 16.
Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
The offline banner used error.main, which in dark mode is light enough
that MUI derives near-black contrastText - a pale salmon bar with dark
text on the one surface where urgency is the point. It now uses
error.dark with explicit white text: 11.12:1 in light mode, 5.04:1 in
dark, and a solid red bar in both.
Darkening error.main itself was the other option, but roughly 30 places
use it as text or a chart mark on a dark ground, where the lighter value
is correct - it would have cost all of them to fix one banner.
The incident summary icons rendered at the shared Icon default of 20px
in lucide's currentColor, so they sat larger and darker than the 13px
label beside them. Now 16px, matching the sidebar and the uptime header
buttons, and coloured from SummaryItem so the icon is a supporting mark
rather than competing with its own label. The 32px status icon on the
active-incidents card keeps its size and semantic colour; it is a focal
indicator, not a label decoration.
Claude-Session: https://claude.ai/code/session_01AbYLvwpEaZFXktHVcqrFRd
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