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.
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.
Apply review feedback:
- Extract makeStatusPage from the two test files that duplicated it (test/unit/services/statusPageService.test.ts and test/unit/controllers/statusPageThemeFlag.test.ts) into test/helpers/makeStatusPage.ts. Both files now import the shared helper, matching the existing test/helpers/ convention used for repository fakes and provider contracts
- Add a blank line in statusPageController.getStatusPageByUrl between the normalizedMonitors map and the themesEnabled lookup so the response-shaping section breathes a bit
- Drop the obsolete localStorage references from the plan doc that survived from an earlier draft of the design — the public page never persisted a per-visitor mode choice in localStorage, so the test bullet under 2.11 and the risk row both pointed at functionality that does not exist
Address review follow-ups on PR #3568:
- Extract the controller's withoutThemeFields and applyDefaultTheme helpers from a private class field into a standalone module (controllers/statusPageThemeFlag.ts) so they can be tested without a controller harness; the controller is now a one-line consumer
- Add 9 unit tests covering both helpers (test/unit/controllers/statusPageThemeFlag.test.ts): no-mutation guarantees, partial inputs, all-fields-preserved verbatim, override-with-defaults semantics, and "page has no theme set yet" — the flag-off path is now exercised in both directions
- Expand the comment over withoutThemeFields to spell out why the strip is silent rather than a 400: this is an ops kill switch, not API semantics; clients shouldn't need to know whether themes are disabled today
Two caveats called out in review that this commit does NOT change:
- Mongoose `default:` directives only inject during hydration, not on partial updates. Legacy documents updated with `findOneAndUpdate` still won't have `theme` / `themeMode` written to Mongo. They render as defaults on read because the schema injects them — functionally equivalent, but the DB will hold a mix of present and absent fields indefinitely. Acceptable per the original commit intent.
- docs/plans/status-page-themes.md (304 lines) covers both backend and frontend work for the themes feature. It lives in this PR as the design artefact that drove both branches; moving it for purity would break the "where did this come from?" trail. Leaving it in.
- StatusPage Mongoose model: import DEFAULT_STATUS_PAGE_THEME / DEFAULT_STATUS_PAGE_THEME_MODE from types/statusPage so the four sources of the default literal stay locked together
- openapi.json: add explicit "default" alongside the enum on StatusPageTheme and StatusPageThemeMode component schemas; documentation already mentioned the defaults but the JSON didn't carry them
- statusPageController: add JSDoc-style comments above withoutThemeFields and applyDefaultTheme spelling out the asymmetric strip-on-write / force-on-read semantics so the next reviewer doesn't have to re-derive the design
Server-side, return 200 with success: false and a friendly message when sendTestNotification reports failure (the underlying call already swallows the error); reserve real 500s for unexpected exceptions.
Client-side, teach usePost to honour the response body's success flag so success:false flips the toast to error without throwing through axios. Also tidy the toast container: drop icons, no wrapping, single-line ellipsis with a sensible max-width that hugs short messages.
Mongoose 8 logs a reserved-key warning at boot because the Check schema
defines an `errors` field, which collides with Mongoose's internal
document `errors` accessor. The field is intentional and safe to keep,
so opt out of the warning via `suppressReservedKeysWarning: true`.
Closes#1539
## Changes
- Add `theme` (refined | modern | bold | editorial, default "refined") and
`themeMode` (auto | light | dark, default "auto") fields to the StatusPage
Mongoose model and the shared `StatusPage` type.
- Extend the create/update Zod validator to accept both fields optionally.
- Map both fields in the repository's `toEntity` so GET responses include them.
- Document both fields in the create and update endpoints in `openapi.json`.
- Add `STATUS_PAGE_THEMES_ENABLED` env var (default `true`) wired through
`SettingsService` for ops-level rollback. When disabled, the controller
strips theme fields on write and forces defaults on read.
## Notes
- No migration needed; Mongoose applies defaults for legacy documents.
- Controller short-circuits to zero-cost when the feature flag is on.
- `DEFAULT_STATUS_PAGE_THEME` / `DEFAULT_STATUS_PAGE_THEME_MODE` constants
exported so the frontend PR can consume the same values.
Updated both MongoDB and TimescaleDB repositories to map the new
Twilio-specific fields (accountSid, twilioPhoneNumber) in create,
update, and toEntity methods.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Added `accountSid` and `twilioPhoneNumber` fields to Notification
type, MongoDB model, and frontend type instead of reusing
`address` and `homeserverUrl`
- Updated provider, validation, form hook, and form UI to use new fields
- Added Pushover and Twilio to notificationsService routing tests
(both handleNotifications and sendTestNotification)
- All 59 tests passing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds Twilio SMS as a new notification channel, allowing users to
receive SMS alerts via the Twilio API.
Backend: provider using HTTP Basic Auth, type registration, validation,
service wiring. Uses existing fields: address (Account SID),
accessToken (Auth Token), homeserverUrl (From number), phone (To number).
Frontend: form UI with 4 fields, validation, i18n keys.
Tests: 18 provider tests + updated service test constructor.
Closes#3095
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds Pushover as a new notification channel, allowing users to receive
push notifications on their devices via the Pushover API.
Backend: provider, type registration, validation, service wiring
Frontend: form UI with app token and user key fields, validation, i18n
Tests: full unit test suite (15 tests, 100% coverage on provider)
Closes#3445
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>