diff --git a/HISTORY.md b/HISTORY.md
index ba87ef5..d400c7b 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -3,6 +3,7 @@
### π QA Fixes
+- **[Privacy] The version check is now opt-out-able** β the privacy policy described the external version check as "optional", but there was no way to disable it (it hit a hardcoded upstream host whenever the About/Status/version page loaded). Added an **admin toggle**: an `update_check_enabled` setting gates the request in `services/updateCheckService.js` (default on β when off, **no external request is made**), exposed via `GET`/`PUT /api/about-admin/update-check-setting` and a switch on the admin **System Status** page. Privacy policy updated to state an admin can disable it. Test: `tests/updateCheckOptOut.test.js`. (was QA-B16-01)
- **[Security] Bill name could inject HTML into reminder emails** β `buildEmailHtml` (`services/notificationService.js`) escaped the bill name in the detail table but interpolated it **raw** into the reminder message line (`${bill.name} is dueβ¦`), so a bill named `` landed unescaped in the email HTML. Self-XSS (reminder emails go to the bill's owner), but a clear inconsistent-escaping bug β now escaped everywhere. Covered by `tests/notificationDelivery.test.js`. (was QA-B14-04)
- **[Notifications] "Send test push" was completely broken** β `services/notificationService.js` attached its `_push` export (the ntfy/Gotify/Discord/Telegram helpers) *before* the final `module.exports = {β¦}`, which clobbered it, so `require('β¦/notificationService')._push` was `undefined`. `routes/notifications.js` (`const { sendTestPush } = require(β¦)._push || {}`) therefore always hit `throw 'Push service not initialised'` β **`POST /api/notifications/test-push` always returned 500** for every user testing their push channel. Scheduled reminders were unaffected (they call `sendPushToUser` in-scope). Moved the `_push` assignment after the reassignment. Covered by `tests/notificationDelivery.test.js` (per-channel payloads, dispatch, error handling, and a check that the auth token never leaks into the message body). (was QA-B10-01)
- **[Summary/Analytics] Non-monthly bills were counted in every month** β the Summary expense list/total and the Analytics "expected vs actual" line both counted annual (and off-month quarterly) bills for months they weren't due, over-stating the obligation and disagreeing with the Tracker (e.g. a yearly insurance bill inflated every month). Both `routes/summary.js` and `services/analyticsService.js` now gate bills by `resolveDueDate` β the same occurrence check the Tracker uses. Guarded by TrackerβSummary and TrackerβAnalytics reconciliation checks in `e2e/api.probe.spec.js`. The SimpleFIN bank-tracking `unpaid_this_month` metric had the same gap and is fixed the same way (fetch + JS `resolveDueDate` filter, since SQL can't call it), covered by `tests/summaryBankTracking.test.js`. (was QA-B5-01, QA-B5-02, QA-B5-03)
diff --git a/client/api.js b/client/api.js
index 81b2be2..091eb17 100644
--- a/client/api.js
+++ b/client/api.js
@@ -344,6 +344,8 @@ export const api = {
roadmap: (refresh = false) => get(`/about-admin/roadmap${refresh ? '?refresh=1' : ''}`),
updateStatus: () => get('/version/update-status'),
checkForUpdates: () => post('/about-admin/check-updates'),
+ getUpdateCheckSetting: () => get('/about-admin/update-check-setting'),
+ setUpdateCheckSetting: (enabled) => put('/about-admin/update-check-setting', { enabled }),
devLog: () => get('/about-admin/dev-log'),
version: () => get('/version'),
releaseHistory: () => get('/version/history'),
diff --git a/client/pages/StatusPage.jsx b/client/pages/StatusPage.jsx
index 8ce7814..018bc52 100644
--- a/client/pages/StatusPage.jsx
+++ b/client/pages/StatusPage.jsx
@@ -9,6 +9,7 @@ import { toast } from 'sonner';
import { api } from '@/api';
import { cn, fmtUptime, fmtBytes } from '@/lib/utils';
import { Button } from '@/components/ui/button';
+import { Switch } from '@/components/ui/switch';
import { MarkdownText } from '@/components/MarkdownText';
// βββ Helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@@ -128,7 +129,7 @@ function SkeletonCard() {
const CATEGORY_ORDER = ['Added', 'Changed', 'Fixed', 'Removed', 'Deprecated', 'Security'];
-function UpdateCard({ update, onCheckNow, checking }) {
+function UpdateCard({ update, onCheckNow, checking, enabled = true, onToggle }) {
const hasUpdate = !!update.has_update;
const isKnown = update.up_to_date !== null && update.up_to_date !== undefined;
const hasError = !!update.error;
@@ -173,8 +174,15 @@ function UpdateCard({ update, onCheckNow, checking }) {
{update.error && (
{update.error}
)}
+
+
+ Automatic version check
+ External request; off = no phone-home
+
+
+