The /settings transport preview shows `from` inside the createTransport
JSON, but the implementation was passing it per-message via sendMail.
Switch to passing it as part of the transport config so what the
preview shows matches what is actually built — no behavioural change
for outgoing emails since nodemailer treats transport-level `from` as
the default for every sendMail call.
Brings back the second pre-block I dropped when consolidating the
preview. The transport JSON shows what's handed to nodemailer
createTransport; the From-header block shows the rendered envelope
recipients will see — they answer different questions and both belong.
Conditioned on systemEmailAddress so empty configs don't render an
empty pre.
Closes#3600. Previously the From header on test emails and notifications
showed only the raw email address. Adds an optional "Sender name" field
so emails appear from a friendly name like "Checkmate notifications"
<user@example.org>.
## Behaviour
- Field is optional. When empty/null/whitespace, the From header falls
back to the raw email address — identical to current behaviour, no
regression for existing installs.
- When set, the email service hands nodemailer { name, address } as the
message from field. Nodemailer handles RFC-5322 encoding (quotes,
commas, non-ASCII) so the display name is safe for any input.
- Unsetting works: clearing the input transforms the value to null in
the Zod parse step, and the settings repository already turns null
values into a $unset operation.
- Both client and server Zod schemas trim and empty-string-to-null,
preventing whitespace-only display names from being stored.
## Surfaces
- AppSettings schema, Settings type, EmailTransportConfig pick, server
Zod validation, client Zod validation, client form defaults, and the
Settings page form all carry the new systemEmailDisplayName field.
- The transport preview pre-block on /settings now includes a `from`
key showing exactly what the email service hands nodemailer — the
string when no display name is set, or { name, address } when it is.
- en.json carries the new label and placeholder. Other locales fall
back until translated (16 locales pending — separate follow-up).
## Conventions conformance (per docs/frontend-conventions.md)
- The pre-block I touched had three rule #1 violations (sx p,
borderRadius, backgroundColor); hoisted those to native props using
theme.shape.borderRadius. Other rule #1 violations elsewhere on this
page are pre-existing and intentionally left alone.
- No new sx-with-native-equivalents introduced.
- No color="text.X" shorthand introduced.
- No theme module import introduced.
Capture the five recurring code-review patterns from the recent PR series
(#3591–#3596) as enforceable rules so they don't have to be raised again
in review.
## Conventions
- Prefer MUI native props over `sx` (e.g. color={...}, mt={...}, width={...}).
- Use the full theme path for colors: theme.palette.text.secondary, never
the "text.secondary" shorthand string. The shorthand is invisible to
grep, which makes future audits and refactors miss callsites.
- No hardcoded numeric or string literals — use LAYOUT, typographyLevels,
theme.shape.borderRadius, theme.palette tokens.
- Use useTheme() inside components; don't import { theme } from
@/Utils/Theme/Theme (factory output, not a live mode-aware instance).
- Pair runtime tuples with derived types so they can't drift.
## Files
- docs/frontend-conventions.md — long-form doc with before/after examples
drawn from the actual review fixes, plus a pre-PR checklist.
- CLAUDE.md — short summary in the Code Conventions section pointing to
the doc, so it's loaded into agent context at session start.
## ESLint enforcement
- client/.eslintrc.cjs — four no-restricted-syntax selectors for each rule:
- color="…" / bgcolor="…" / borderColor="…" with a dotted shorthand value
- sx={{ key: ... }} where key has a native equivalent, only on hoistable
components (Box, Stack, Typography, Grid, Container, Paper, Card*,
Divider, Link, Chip) — Dialog/Drawer/Menu etc. only accept sx.
- import { theme } from "@/Utils/Theme/Theme"
- Typography fontSize={13} (the body default — drop the prop).
- TypeScript files are now actually parsed: added @typescript-eslint/parser
and @typescript-eslint/eslint-plugin and a TS override that extends the
React plugin configs. Several @typescript-eslint/recommended rules
disabled to avoid noise on the existing codebase.
- client/package.json — lint script now covers .ts/.tsx; new lint-strict
script for CI use once the codebase is conformant; --max-warnings 0
removed from `lint` so existing-code warnings don't block the workflow.
## Surface
Running `npm run lint` after this change reports ~280 warnings against
the existing codebase, almost all from rule #1 (sx-vs-native). These are
intentionally warnings, not errors — they surface the work without
blocking. Each warning links to the rule number in the conventions doc
so the reasoning is one click away.
The remove-all-monitors confirmation dialog only had a one-line title
and an empty body, which looked awkward and didn't communicate what was
actually about to be deleted alongside the monitors themselves.
- Adds a paragraph explaining that check history, incidents, scheduled
maintenance windows, and dependent status pages will also be removed,
with an inline tip to export monitors first to keep a backup.
- The Confirm button now uses confirmColor='error' to make the
destructive action visually distinct, with a localized
'Remove monitors' label.
- Translated to all 16 locales, with terminology aligned to each
locale's existing usage (e.g. zh-CN/zh-TW use 监控/監控 to match the
existing button label, vi uses 'trình giám sát', etc.).
When Capture runs inside a container (typical for Docker Desktop), the
only visible interfaces are loopback, so every bytes_sent/bytes_recv
counter is zero and the charts render as flat lines without any
explanation. Users couldn't tell whether Capture was unreachable, the
monitor was broken, or there was just genuinely no traffic.
Detect the case where checks exist (chart configs were built) but every
interface across every check reports zero bytesSentPerSecond and
deltaBytesRecv, and render a NoticeBanner above the chart grid
explaining it and suggesting Capture be run on the host.
Adds pages.infrastructure.charts.labels.netNoTraffic to all 16 locales.
- All uppercase headers (Active Incidents, Latest Incidents, Incident
Statistics, Resolved Incidents, summary card titles, details card
sections) now use the existing 'eyebrow' typography variant. That's
the same style used on the Logs page and lives in Theme.ts so future
tweaks land in one place.
- Latest Incidents and Incident Statistics rows now share a minHeight
so both cards line up vertically; Latest Incidents items dropped
their extra dividers and Box wrappers.
- Body text in the summary cards (and the details dialog) uses
variant=body1 (13px) consistently — previously a mix of body1/body2
and unspecified Typography.
- All lucide icons in CardSummary go through the design-system <Icon>
wrapper so they share the project's strokeWidth=1.5 default rather
than lucide's default 2.
- ControlsIncidentFilter: replaced the missing
pages.incidents.filters.clearFilters key (which was rendering as
raw text) with the existing common.buttons.clearFilters.
- CardDetails: extracted local SectionHeading and Cell helpers; switched
the cards' bare <Typography> cells to variant=body1 + variant=eyebrow
for headings; dropped redundant title-dividers (eyebrow style already
separates visually).
- AddTeamMemberDialog: First name / Last name now share a row, and
Password / Confirm password share a row. Each field uses sx={flex:1}
so the row totals match the width of the email/role rows below.
- InviteTeamMemberDialog: drop the redundant mt={theme.spacing(...)}
workaround on the form Stack (the new Dialog component provides this
spacing).
- Constants conformance: spacing literals replaced with LAYOUT.XS.
- Button.tsx: contained variant's hover now uses HOVER.DARKEN=0.06
instead of MUI's default 16%, which was visibly heavy on the green
primary buttons across the app. Also fixes a subtle bug where the
earlier hover sx merge order silently dropped the boxShadow:'none'
reset. Added a HOVER token group to constants.ts so the darken value
has a single source of truth.
- TextInput.tsx: forward the caller's sx to the wrapper Stack when a
fieldLabel is present (previously it was applied to the inner
TextField, so flex:1 etc. didn't propagate to the visible width).
Inner sx now also explicitly width:100% so wrapped inputs don't
collapse to content width when their parent is a flex row.
- FieldLabel.tsx: drop the hardcoded fontSize:'14px' and use
variant=body1 (=13px from theme) to match the rest of the app's
body typography.
Dialog component:
- Title bar gets a subtle background tint + bottom border. Title is
variant=h2 weight=600. Description tucks under the title with
variant=body1 secondary color (replaces MUI's DialogContentText).
- Action bar gets a matching subtle tint + top border. Cancel is now
outlined (was contained-error, which made every modal's Cancel button
look destructive). Confirm keeps its color (primary by default,
caller can pass error for destructive flows).
- Content area sits between with its own breathing room baked in, so
callers no longer need mt={theme.spacing(...)} workarounds.
- The cancelColor prop is removed; sites that genuinely need a
destructive Cancel can use confirmColor='error' on the Confirm button
instead.
NoticeBanner design-element:
- Extracted from the inline alert pattern in PageSpeedKeyPriorityFallback
into a reusable component with severity=info|warning|error.
- Refactored PageSpeedKeyPriorityFallback to use it.
DialogResolution.tsx: drop the now-removed cancelColor prop and the
mt-Box wrapper that's no longer needed (Dialog provides the spacing).
Fills in machine translations for ar, cs, fi, fr, ja, pt-BR, tr, uk, vi
so every locale now ships a native translation of the game-monitor
description (including the new server-query setup sentence and the
<gamedigLink> placeholder).
The game monitor type relies on each game's native query protocol — many
require a config file change on the game server (e.g. Minecraft Java
needs `enable-query=true` in `server.properties`). Without that, the
monitor silently fails to reach the server.
Append a sentence to the game-type general settings description that:
- tells the user some games need server-side queries enabled
- points them to the GameDig supported-games list for protocol details
Implementation:
- Use <Trans> with a `gamedigLink` slot so the link renders inline
- Loosen ConfigBox `subtitle` from `string` to `React.ReactNode` so the
subtitle can host the <Trans> output
- Update all 16 locale files. Six (de, es, ru, th, zh-CN, zh-TW) get a
full translation of the new sentence; nine empty ones (ar, cs, fi, fr,
ja, pt-BR, tr, uk, vi) fall back to the English string per the locale
gap policy
Four small follow-ups from a code review pass:
1. Drop the redundant `?? THEMED_RENDERERS[DEFAULT_STATUS_PAGE_THEME]`
fallback in Status/index.tsx — the map is now a total Record, so the
first lookup cannot return undefined.
2. Extract the literal "/status/public" into PUBLIC_STATUS_PAGE_PREFIX in
Types/StatusPage.ts and reuse from the three runtime call sites
(Status/index.tsx, HeaderStatusPageControls.tsx, StatusPagesTable.tsx).
The Routes/index.tsx route definition keeps its string literal — the
route table is otherwise all literals and the prefix only matters when
it's interpolated.
3. Delete two narrating comments above the public/admin branches in
Status/index.tsx — the JSX is self-describing.
4. Move the inline `style` object construction in StatusPageThemeProvider
into the existing useMemo so the wrapper div receives a stable style
reference. The previous spread defeated the surrounding memoization on
every render.
The 4 themed renderers (refined, modern, bold, editorial) cover every
StatusPageTheme value and the model defaults to "refined", so the legacy
fallback path in Status/index.tsx was unreachable in practice.
Removing it lets us delete:
- theme-overrides.css (the only remaining CSS file in StatusPage)
- StatusBar / MonitorsList / InfrastructureMetrics (only the legacy
fallback consumed them)
- The .sp-* className hooks that wired CSS rules into the components
Pure sx via the per-theme styles.ts recipes is now the single source of
truth for every status page surface. Net diff is -801 / +42.
Status/index.tsx now resolves a renderer from the theme map (falling back
to the default theme) and keeps the public-route and admin-preview
branches it already had.