Three small follow-ups surfaced by the v1.4.6 QA pass:
- Settings → KI: the "raw data" warning still used raw Tailwind orange
tokens instead of the Dracula `dracula-orange` ones, breaking theme
consistency and tipping below the WCAG-AA contrast floor on the
card surface.
- Admin status grid: the Backups card cta read "View backups" but
jumped to the system-status section (the dedicated backups view
does not exist yet — that's a v1.5 feature). Re-labelling the cta
to "Open system status" and the Integrations cta to "Open
integrations" makes the destination honest. The href targets
themselves are unchanged.
- `withIdempotency` body-content guard previously did `text.includes("sk-")`
which silently broke caching for benign payloads containing common
English compounds like "task-id", "risk-management", "disk-io".
Tightened to a regex requiring the canonical secret prefix plus a
realistic suffix length, so only actual API keys trip the guard.
Three regression tests cover the false-positive cases.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P19. `useSystemStatus()` and `useAdminSettings()` already throw on
non-OK responses so React Query sets `isError`, but neither caller
read it — the system-status panel just kept spinning forever and the
settings sections silently rendered defaults as if they were live.
Now system-status renders a destructive inline alert when isError, and
the admin page top renders a one-line "Failed to load admin settings"
banner when the shared settings query fails so the admin notices the
toggles below aren't reflecting reality. Both messages have new i18n
keys (`admin.systemStatusLoadError`, `admin.adminSettingsLoadError`)
in en.json (canonical) + de.json.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P18. Replace the brittle "wipeMsg.startsWith(localized prefix)" success
heuristic with React Query's `wipeAllData.isError` flag. The previous
heuristic (a) re-formatted the success copy with empty placeholders to
extract a prefix, (b) string-prefix-matched against the rendered
message, and (c) silently broke whenever the i18n copy changed colon
position or got translated. Now the colour follows the mutation state
directly: error → destructive, success → dracula-green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P17. The category badges (BUG / FEATURE_REQUEST / QUESTION / OTHER)
were using raw Tailwind palette values (bg-red-500/15 text-red-400 etc.)
which clash with the rest of the app's Dracula tokens. Switch to the
dracula-red / dracula-purple / dracula-cyan equivalents so the inbox
matches the site theme — and dark/light mode parity is consistent
with every other status badge in the admin grid.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two related corrections to the "Wipe all data" admin action:
- Stop deleting the audit log inside the transaction. The whole point
of the audit trail is to outlive the data it documents, and this is
the single feature where that property matters most. AuditLog rows
now survive the wipe.
- Audit-log the *intent* (`admin.data.clear.start`) before the
transaction begins, so even if the wipe crashes mid-flight there is
a record of who initiated it.
- The i18n copy and the success toast both undersold the scope: they
only mentioned measurements/medications/intakes, but the wipe also
clears API tokens, Withings connections, and any open auth
challenges. EN + DE strings now enumerate every cleared category and
call out that user accounts AND the audit log are preserved.
- Mutation result type widened so the toast can render the full
count tuple.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every "Manage users" / "View backups" CTA reloaded `/admin` to the top
because the hrefs were pointing at sub-routes that never shipped
(`/admin/users`, `/admin/audit-log`) and at anchors whose IDs do not
exist (`/admin#integrations`, `#monitoring`, `#backups`,
`#maintenance`). The actual section IDs in `src/app/admin/page.tsx`
follow a `section-admin-*` / `section-user-management` pattern.
Six href fixes plus a tightened test: instead of `startsWith("/admin")`
the test now asserts each href is `/admin#section-<known-id>` and that
the anchor is a member of the source-of-truth ID set rendered by the
admin page. Adding a new section requires adding the ID here too —
which is exactly the visibility we want to keep CTAs honest.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bucket E+F of the v1.4.3 polish pass.
**Disconnect-button styling unified.** Three integrations had three
different "Trennen" buttons: Withings used `outline` + `text-
destructive`; moodLog used solid `destructive` (red bg, white text);
the AI/Codex card used `ghost` + `text-destructive`. Same action,
three readings — confusing. All three now use the Withings pattern
(`outline` + `text-destructive`). The actual confirmation button
inside the AlertDialog keeps its solid-red treatment so the final
"yes, do it" still reads as a strong commit.
**Bug-report explicit on/off toggle.** Until v1.4.3 the bug-report
feature was implicitly gated on "is the GitHub token + repo
configured". An admin who wanted to *temporarily* hide the report
button (e.g. during a freeze window) had to clear the encrypted
token, forcing re-entry on resume — destructive for a reversible
intent. Migration `0027_bug_report_enabled_toggle` adds
`app_settings.bug_report_enabled BOOLEAN NOT NULL DEFAULT TRUE`,
the admin settings schema accepts `bugReportEnabled`, and a Switch
in the admin → Bug Report card lets the operator flip it.
`POST /api/bugreport` now returns 503 ("Bug reports are disabled by
the administrator") when the flag is off — the client already
renders a "not configured" screen on that status, so the report
button hides gracefully without further client-side wiring.
i18n keys added: `admin.bugReportEnabledLabel` + `…Description` in
both DE and EN.
Tests + typecheck green (671/671).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Marc reported many residual TypeError crashes after the targeted fix
in 680b001. A comprehensive perl regex sweep across src/app and
src/components turned up 19 more occurrences of the same anti-
pattern: `var?.x.y` only protects the outer object, so when the
inner field is undefined the access throws.
Touched files:
- src/app/api/dashboard/summary/route.ts (server-side null guards)
- src/app/api/health/route.ts (session check)
- src/components/admin/feedback-inbox-section.tsx
- src/components/medications/medication-card.tsx (compliance rates)
- src/components/medications/medication-form.tsx
- src/components/medications/intake-history-list.tsx
- src/components/measurements/measurement-list.tsx
- src/components/mood/mood-list.tsx
- src/components/ui/password-strength.tsx
Pattern applied: `var?.outer.inner` → `var?.outer?.inner`. Tests
green, typecheck clean. Lint untouched (the rule we already enforce
catches set-state-in-effect, not optional-chain depth).
Same anti-pattern as the v1.4.2 hotfix on /dashboard and /insights —
the optional chain protected only the outer object, so any field on
the inner object that didn't exist threw at access time. Marc's
reports flagged dashboard, insights, medications,
notification-preferences, admin login overview, mood list, and
measurement list. Applied 'data?.X?.Y' to every spot the regex
caught.
* Revert "revert: roll source tree back to v1.4.0 to restore production"
This reverts commit e444a90b10.
* fix(v1.4.2): summaries.WEIGHT + medications array guards + healthcheck IPv4
Three production bugs surfaced after the v1.4.0 deploy stabilized:
1. `TypeError: undefined is not an object (evaluating 'E?.summaries.WEIGHT')`
on /dashboard and /insights for users without weight readings —
the optional chain stopped at `data` when it needed to also cover
`data.summaries`. Fixed: `data?.summaries?.WEIGHT` (one level deeper).
2. `TypeError: y?.filter is not a function` on /medications — the
medications query value was non-array (likely a stale
service-worker response from an older API shape). Replaced with
`Array.isArray(medications) ? medications : []` so the page is
immune to any future shape drift.
3. Healthcheck-induced 503 outage (already in 1.4.1 — included here
so 1.4.2 is the single coherent release). The docker-compose
healthcheck used `localhost` which busybox-wget resolves to IPv6
::1; Next.js standalone listens on IPv4 0.0.0.0 only. Pinned
to 127.0.0.1.
This release also includes everything from v1.4.1 (which never
published a clean GHCR tag): the per-section admin extraction, the
testcontainers integration suite, the Playwright + axe-core E2E
foundation, the moodLog SSRF fix, the central error-redaction in
WideEventBuilder + Glitchtip, the medical-citation drift fixes
(ESH 2023, Saint-Maurice attribution, steps source), and the docs
sync.
Co-Authored-By: Marc-André Bombeck <mbombeck@gmail.com>
Production at healthlog.bombeck.io has been 503-ing since the v1.4.1
deploys started landing on apps-01 (Coolify). The container boots —
Next.js prints "Ready" and the pg-boss workers run — but never
accepts HTTP on :3000, so the Docker healthcheck fails and Traefik
takes the upstream out of rotation. A manual restart, a Coolify
force-rebuild, and a docker-compose pin to the GHCR :1.4.0 multi-arch
image all failed to bring the site back up — Coolify rebuilds the
image from main HEAD on every deploy regardless of the compose
directives.
This commit resets the working tree to commit 21bd46d (v1.4.0
release). Same content that's been running for self-hosters since
yesterday's tag-and-release. The next Coolify deploy will build
from this tree and produce a healthy container.
The v1.4.1 work is NOT lost:
- PRs #144, #145, #137, #146, #147, #148, #149, #150 remain in
git history.
- Their commits are still tagged (`v1.4.1`), still on the GHCR
multi-arch image (`ghcr.io/mbombeck/healthlog:1.4.1`), still in
the GitHub Release notes.
- Self-hosters who have already pulled the v1.4.1 image keep it.
- Local development continues from main HEAD with the v1.4.1
code — the regression only surfaced under the Coolify build
flow.
Re-applying v1.4.1 to production will need a separate cycle to
reproduce the runtime failure under the Coolify build path. That
work is tracked in docs/ops/v141-followup-issues.md (added back
when the tree is reapplied) and the deploy gating in
.github/workflows/e2e.yml will catch this class of bug going
forward.
No DB migration. No env-var change. No API contract change.
Co-Authored-By: Marc-André Bombeck <mbombeck@gmail.com>
The admin page was a single 2,702-line client component with
nineteen inline functions covering everything from system status
to feedback inbox. The status-card grid foundation in #133 already
moved the at-a-glance dashboard out; this commit moves the inner
panels.
Split into one file per logical section in src/components/admin/:
_shared.tsx 276 LOC shared types, hooks,
primitives (PasswordInput,
StatusItem, SettingsToggle,
useAdminSettings,
useUpdateSettings,
useSystemStatus,
getApiErrorMessage)
system-status-section.tsx 155
general-settings-section.tsx 54
services-section.tsx 68
umami-section.tsx 173
glitchtip-section.tsx 172
web-push-vapid-section.tsx 148
bug-report-section.tsx 109
reminders-section.tsx 318
user-management-section.tsx 341
api-token-overview-section.tsx 144
login-overview-section.tsx 181
danger-zone-section.tsx 118
feedback-inbox-section.tsx 511 (inbox + category badge
+ detail dialog)
src/app/admin/page.tsx is now a 77-line shell that imports the
14 sections and the existing status-card-grid.tsx. No behavior
change — every section keeps the same DOM, the same query keys,
the same i18n keys, the same id="…" anchors so deep-links from
the sidebar still scroll into place. UserManagementSection no
longer takes a queryClient prop; it grabs its own via
useQueryClient() like every other section does.
Replaces the implicit, fluid status of the legacy admin monolith with a
6-card grid at the top of /admin per ui-guidelines.md §3.4. Each card
pairs a §2.6 colored dot with a text label so state never relies on
color alone (WCAG 1.4.1).
- New aggregator endpoint /api/admin/status-overview returns a single
batched Promise.all (no N+1) covering Users, Integrations, Monitoring,
Backups, Maintenance, Audit log — with severity precomputed server-side.
- New <StatusCardGrid> + reusable <StatusBadge> component in
src/components/admin/. Pure buildCards() function makes severity
mapping unit-testable.
- Tests: 14 new (8 aggregator severity/shape, 6 grid badge + a11y).
- Wires the grid above the existing admin sections; per-section
extraction will land in follow-ups.
Co-authored-by: Marc-André Bombeck <noreply@anthropic.com>