`AppSettings.telegramGlobal`, `.ntfyGlobal` and `.webPushGlobal` were read
in two places, and neither of them was the one that matters: the setup-card
visibility on Settings -> Integrations and an echo in the preferences
payload. The dispatcher never looked at them. An operator who switched a
channel off instance-wide lost the setup card and kept every existing
delivery on that channel, indefinitely.
Gate each of the three in the dispatcher, in the shape
`isApiGloballyEnabled()` already uses for the twelve API routes. The read
sits above the per-user channel row and below the per-event preference, it
happens at most once per dispatch, and only when a switchable channel is
actually in the cascade. A skip writes a `globally_disabled` row to the
push-attempt ledger so the admin diagnostics name the cause, and does not
count as an attempt, so `dispatched` keeps telling the truth. The account's
own channel row is left enabled: the switch is the operator's, and flipping
it back has to restore delivery without every user re-enabling anything.
APNs, the generic webhook and email carry no instance-wide switch and are
not gated here; they are turned off at their source instead.
The unit test drives the real dispatcher over a stubbed settings row; the
integration test writes the actual singleton and reads the ledger back out
of Postgres, which is the layer that proves the column the admin route
writes is the column the dispatcher reads.
Every background path that resolved a user row's locale collapsed a
NULL column to English: the nightly briefing warm, the status crons,
the period-narrative warm, reaction lines, the morning refresh, the
briefing push, coach nudges, the reminder workers, and the document and
workout summaries each carried their own copy of
`locales.includes(x) ? x : defaultLocale` or called
`normalizeLocale(user.locale)`. None consulted the instance's configured
default language, so an account that never touched the language picker
got English prose and English pushes on a German-language instance.
Add resolveJobLocale (src/lib/i18n/job-locale.ts): stored user locale
when valid, else the operator's app_settings.default_locale when valid,
else English. The settings read lives beside the other app-settings
accessors. Every job and daily path resolves through it once, where it
has the row; the template helpers keep coerceLocale for a value that is
already resolved, and queue payloads keep normalizeLocale.
The briefing push hands its resolved locale down to the digest, and the
digest to the snapshot read, so the push copy and the digest lines are
in the same language instead of two different fallbacks.
A structural test walks src/lib/jobs and src/lib/daily and fails on a
hand-rolled coercion, a validator applied to a row column, or a row
reader that does not import the helper. It was red against the previous
tree on all three counts.
Mood has been a first-class module here for a long time, with entries,
structured tags, rated factors and notes. The bridge to the external moodLog
service had nothing left to add, and its Settings card was withdrawn in
a56e6fe61 on the promise of a dedicated removal. This delivers it.
Removed: the hourly `moodlog-sync` queue, cron and handler binding; the
inbound webhook and its shared-secret handling; the reverse push and its four
call sites; the pull-sync module; the per-user credential routes and the
instance-wide toggle; the entry on the `/api/integrations/status` envelope,
the `IntegrationKey` union, the cadence map and the display-name table; the
boot-time secret rotation pass; 22 message keys per locale.
Migration 0271 drops the five `users` credential columns and
`app_settings.mood_log_global`, clears the ledger row for the provider, and
deletes the retired pg-boss schedule so no cron keeps enqueuing work no
handler drains.
Mood data is untouched. `mood_entries` is not named anywhere in the migration
and cannot be reached from it: the dropped columns carry no index, constraint
or foreign key, and `mood_entries.user_id` references `users(id)`, not any of
them. Rows the bridge imported keep `source = 'MOODLOG'`, and every read path
- list filter, source badge, exports, backups - still resolves that value.
`src/lib/validations/moodlog.ts` held the native mood-entry schemas alongside
the bridge's own; it is now `validations/mood.ts` and carries only the former.
Long-overdue prettier sweep; prettier had drifted vs. checked-in code
across ~130 files (mostly long-argument line-break wrapping in TS and
table-padding in markdown). Pure formatting — no semantic changes —
and required to clear the v1.4.6 release gate's `pnpm format:check`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
HealthLog — personal health-tracking PWA with weight, blood pressure,
pulse, mood tracking, medication compliance, Withings integration,
doctor report PDF export, and Dracula-themed UI.