Pure-history medication detail page, legacy step consolidation, and the
finish of the outbound-fetch migration.
- /medications/[id] becomes a read-only history surface; every setting
moves into a new advanced-settings sheet, and the edit trigger becomes
a two-option picker. The list page is unchanged.
- A pg-boss job folds pre-v1.5.0 granular step rows into one canonical
daily total per user per day and soft-deletes the originals; migration
0087 adds a partial index for the converging discovery scan.
- Every remaining server-side fetch routes through safeFetch (Withings,
Codex OAuth + SSE, bug-report, Umami, GlitchTip, Loki); a new
healthlog/safe-fetch-required ESLint rule guards the boundary.
- Fixes: intake-edit dialog seeded from real values, consolidation skips
a unique-constraint day instead of stranding it, avatar upload reads
the body once under a bounded cap, egress lint rejects absolute URLs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes issue 218. Flips the convention from "remember to opt in to
redirect: manual" to "remember to opt out". Every outbound fetch that
doesn't pin a hard-coded host now routes through safeFetch and inherits
AbortSignal.timeout(15_000) so a tar-pit upstream can no longer pin a
worker indefinitely. The local AI client also gates on
requirePublicHost: true since its host is user-supplied; operators
running self-hosted Ollama / LM Studio on an RFC1918 address keep their
escape hatch via ALLOW_LOCAL_AI_PRIVATE_HOSTS=true (also enforced at
write-time in /api/user/ai-provider).
14 outbound call sites migrated. The four that already pinned manual
redirect (the historic templates) keep their semantics; the wrapper
preserves an explicit init.redirect. Adds undici as an explicit
dependency so the connect-time pin landing alongside this can rely on
the documented API rather than a transitive hop.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Eleven touch-disjoint waves landed on develop plus a six-axis QA
reconcile round before tagging. See CHANGELOG.md for the user-facing
prose; this commit message documents the wave + report inventory for
release-management traceability.
Waves shipped (each with a phase report under .planning/):
- phase-W-POOL-v1440-report.md — p-limit(4) wrapper around the
analytics 15-way fan-out + pg.Pool max raised 10 -> 20. Closes the
v1.4.39 empirical-trace finding #1 (Prisma pool starvation).
- phase-W-DELETED-v1440-report.md — 11 reader-tier helpers + 3
integration contracts close audit Critical Finding #3 (soft-delete
invisibility) across dashboard, analytics, summaries, rollup tiers,
and the Coach snapshot path.
- phase-W-INSIGHTS-v1440-report.md — mood-rollup swap on the three
/api/insights/{features,targets,cards} routes plus the
compliance-rollup hook gap closure for bulk-projection mints; the
post-QA reconcile patch closes the six remaining measurement.findMany
sites in the cluster that still leaked tombstoned rows.
- phase-W-INFRA-v1440-report.md — rollup helper umbrella consolidation
under src/lib/rollups/* with zero orphan re-exports; lint, typecheck,
knip, and 93/93 rollup tests green post-restructure.
- phase-W-RSC-v1440-report.md — per-tile Suspense boundaries plus the
queryKey factory-bypass walker test (extended to comparison/ in the
reconcile pass).
- phase-W-PRIVACY-v1440-report.md — bilingual /privacy page with 17
paired-section tests pinning the 9 SB-3 disclosure requirements.
- phase-W-AASA-v1440-report.md — /.well-known/apple-app-site-association
endpoint pinned to S8WDX4W5KX.dev.healthlog.app with app-ID parity
assertions guarding against split rotation.
- phase-W-APNS-NOTIFY-v1440-report.md — MEDICATION_REMINDER
time-sensitive APNs payload (priority 10 + interruption-level)
scoped via a parameterised test that pins all 6 other event-types
do not bypass Focus. AP-2 .p8 install gates real delivery.
- phase-W-CONSENT-v1440-report.md — AI consent receipts CRUD with
append-only invariant pinned in test; revoke-then-regrant leaves
both rows; idempotent DELETE returns 200 + receipt: null.
- phase-W-WMY-WIRE-v1440-report.md — three previously-dead WMY readers
(week/month/year) wired through summaries-slice and
health-score-fast-path with a linear-composability parity test
pinning the cross-consumer routing contract.
- phase-W-GHOSTS-v1440-report.md — ~1 177 lines of dead code removed
across 9 atomic commits; startOfUtcDay + wallClockInTz consolidation
eliminates a sibling-drift risk; knip whitelist trimmed to three
files pending follow-up.
QA reconcile patches applied per the v1.4.40 QA round (six reports
under .planning/round-v1440-QA-*-findings.md):
- senior C1 — dashboard-suspense-boundaries regex aligned with the
post-W-INFRA Thread 2 userTimezone-local lift.
- senior H1 — soft-delete filter added to the six remaining
insights measurement.findMany sites.
- specialist H1 — Promise.allSettled around bulk-projection
compliance recompute on both /api/medications/intake and
/api/dashboard/summary.
- ux H1 — compare-toggle migrated to queryKeys.dashboardWidgets
factory; walker test extended to src/components/comparison/.
- security M1 — consent artefact 64 KB cap enforced via
Buffer.byteLength (UTF-8 bytes, not UTF-16 code units).
Quality gates green at tag: pnpm typecheck, pnpm lint, pnpm knip
--include files,dependencies,binaries,unlisted, pnpm test --run
(4726 passing / 1 skipped). Tests 4525 -> 4726 unit + 1 skipped over
the v1.4.39 -> v1.4.40 span.
User-facing prose: see CHANGELOG.md.
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>
Adds vitest unit tests for five previously zero-coverage routes:
- /api/admin/users (GET) — admin gate, safe select shape regression, no
password-hash leak.
- /api/admin/users/[id] (PUT) — Zod validation, 404 for unknown id,
last-admin demotion guard, audit-log call shape.
- /api/admin/settings (GET / PUT) — singleton row regression, encrypted
blob never reaches the client, AES encrypt() called for VAPID +
GitHub-issue tokens, clear flags wipe encrypted columns, every
schema-allowed field round-trips through upsert.
- /api/admin/status (GET) — integration health summary aggregates umami
/ glitchtip / web-push / bug-report flags, only non-revoked tokens
count as active, encrypted blobs never appear in the envelope.
- /api/monitoring/glitchtip (POST) — IP rate-limit gate, short-circuit
when DSN is null, message/stack size caps (2000 / 20 000 chars),
upstream 5xx mapped to 502, environment fallback to "production".
All five files now land at >=80% line coverage:
- admin/users/route.ts: 100%
- admin/users/[id]/route.ts: 100%
- admin/settings/route.ts: 100%
- admin/status/route.ts: 84.21%
- monitoring/glitchtip/route.ts: 100%
Test count: 469 -> 523 (+54; my files contribute 46, the other 8 were
unblocked by re-running prisma generate in the worktree).
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(api): test endpoints for Withings, moodLog, Web Push, Glitchtip, Umami
Five POST /test endpoints modelled on /api/ai/test (apiHandler +
requireAuth/requireAdmin + checkRateLimit 5/60s + annotate() +
sanitised error mapping):
- /api/integrations/withings/test — pings wbsapi.withings.net /measure
with the decrypted user access token
- /api/integrations/moodlog/test — HEAD against the user's decrypted
moodLog URL (5 s timeout, isPublicUrl
SSRF guard)
- /api/notifications/web-push/test — sends "Push test" via web-push to
every PushSubscription, returns only
endpoint host (never full URL)
- /api/monitoring/glitchtip/test — admin-only; sends a stub event via
sendGlitchtipEvent() using the saved
AppSettings.glitchtipDsn
- /api/monitoring/umami/test — admin-only; HTTPS-only GET against
AppSettings.umamiScriptUrl, checks
body for "umami" marker
Each route has a vitest suite covering: happy path, rate-limit denial,
missing config (422), and upstream-401 sanitisation (no sk-/Bearer/full
URL leak).
Co-authored-by: Marc-André Bombeck <noreply@anthropic.com>
* fix(integration-tests): address review findings — singleton key, redirect SSRF, token refresh, scoped push
Multi-agent review caught a regression set on the five integration test
endpoints. Fixes here address every CRITICAL + HIGH finding:
- AppSettings is keyed on `id: "singleton"` everywhere else; the Glitchtip
and Umami test routes were querying `id: "default"` and would silently
return 422 in production. Tests now assert the where argument so the
typo can't sneak back.
- moodLog and Umami fetch with `redirect: "manual"` to neutralise the
redirect-follow SSRF (a public host could 302 to 169.254.169.254 or
RFC1918 ranges); 3xx is now treated as a configuration error.
- Withings test goes through the existing `getValidToken()` helper, so an
expired access token refreshes the same way the real sync does instead
of reporting "credentials rejected" until the next scheduled sync.
- Glitchtip DSN is validated as HTTPS + public host before any outbound
call, and the dispatch is wrapped in a 5 s `Promise.race` deadline.
- Withings dispatch picks up an `AbortController` 5 s deadline; the
Withings API-level `status` field (HTTP 200 + `status !== 0`) is now
categorised separately from the HTTP status (e.g. `status: 401` →
credentials_rejected, `status: 601` → rate_limited).
- moodLog test does an authenticated GET against the actual sync
endpoint instead of a bare HEAD on the configured URL — a HEAD on the
bare URL would return 200 from a generic landing page even when the
API key was invalid.
- Web Push test sends to the most recent subscription only (was: every
registered subscription, so a user with three devices got three buzzes
per click); tests assert findFirst+orderBy so this can't regress.
- Web Push response shape is the same shape on success and upstream
failure (errors land in the meta envelope, not in the visible
response body) — the prior dead-code in the 401 sanitisation test is
gone.
- Every error response now carries an `errorCode` in `meta` (e.g.
`credentials_rejected`, `rate_limited`, `dsn_host_not_public`) so the
upcoming UI can localise via `t("settings.testConnection.errors." + code)`
instead of rendering the server's English fallback.
- Outbound error annotations strip `Bearer <token>` and full URLs
before they reach Wide Events / Loki — push providers in particular
embed the subscription endpoint (which is the routing secret) in
410/404 error messages.
- Umami response read is capped at 256 KB so a hostile public host
cannot stream gigabytes through us; the marker check is tightened to
tracker shapes (`window.umami`, `umami.track`, `"umami"`) so a 404
page that says "Powered by Umami" no longer false-positives.
- `apiError` now optionally carries a `meta` object so error responses
can include the `errorCode` without breaking the existing
`{ data: null, error: <string> }` envelope.
477 tests green.
---------
Co-authored-by: Marc-André Bombeck <noreply@anthropic.com>
Switch defaultLocale from "de" to "en" and make English the code-level
language throughout. All ~145 hardcoded German strings translated:
- API error messages (52 routes) now return English
- Zod validation messages in English
- Classification/analytics functions return English categories
- Notification type labels in English
- Component strings use i18n t() calls (heatmap, mood chart,
medication form, intake history, password strength, insights card)
- ~50 new i18n keys in both de.json and en.json
Route migration:
- /stimmung → /mood, /zielwerte → /targets (new pages)
- 301 redirects in proxy.ts for legacy URLs
- Sidebar + bottom nav updated
Bump version to 1.0.1.
HealthLog — personal health-tracking PWA with weight, blood pressure,
pulse, mood tracking, medication compliance, Withings integration,
doctor report PDF export, and Dracula-themed UI.