Commit Graph
580 Commits
Author SHA1 Message Date
Marc-André BombeckandClaude Opus 4.7 9873363fa3 feat(insights): add Daily Briefing schema + prompt v4.20.0
Extends `aiInsightResponseSchema` with an optional `dailyBriefing`
block (paragraph + 0-5 keyFindings, each with tone / headline / detail
/ delta / sourceWindow / sourceMetric). Nullable + optional so v1.4.19
cached payloads still parse cleanly.

Bumps PROMPT_VERSION 4.19.0 → 4.20.0 and adds GROUND RULE 8 in both
EN and DE system prompts, instructing the model to emit a briefing
block grounded in the user's own snapshot numbers (no extrapolation,
no medical advice claims, hard-cap at 5 findings).

TDD: new daily-briefing-schema.test.ts covers legacy back-compat,
well-formed parsing, > 5 findings rejection, empty-paragraph
rejection, finding-field validation, and the PROMPT_VERSION 4.20.x
match. Existing medical-reference-prompt.test.ts assertion stays
broad enough to ride the bump.

+31 tests (1672 → 1703); typecheck clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:35:26 +02:00
Marc-André BombeckandClaude Opus 4.7 81a40f7955 docs(planning): F6 docs accuracy audit recorded
- STATE.md: F6 ticked complete with the two healthlog-docs commits
  (19eb8de S3 + arm64 + 3d3ea21 production-state alignment)
- phase-F6-report.md: 38 pages checked, 2 CRIT + 10 HIGH inline,
  9 MED + 5 LOW deferred, 2 flagged-but-uncertain items captured
- v1421-backlog.md: F6 audit findings appended (co-edited with F5)

Foundation phases F0/F1/F2/F5/F6/FX complete; develop ready for B1
dispatch (Insights redesign hero + Daily Briefing).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:28:36 +02:00
Marc-André BombeckandClaude Opus 4.7 a2d959c7c1 docs(planning): F5 best-practice audit recorded, v1.4.21 backlog seeded
- STATE.md: F5 ticked complete with the four atomic commits referenced
- phase-F5-report.md: scope + per-commit justification + deferral list
- v1421-backlog.md: new backlog file for F5 LOW carry-over plus the
  FX cleanup carry-over (source-comment sweep, bilingual CHANGELOG,
  CLAUDE.md/AGENTS.md filenames)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:24:19 +02:00
Marc-André BombeckandClaude Opus 4.7 f7977d113e chore(package): add description, repository, license metadata
Fills the npm package metadata fields that GitHub package-cards,
package.json scrapers, and SBOM tooling pick up: description, license
(AGPL-3.0-only), homepage, repository, bugs, keywords. No runtime
behaviour change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:23:20 +02:00
Marc-André BombeckandClaude Opus 4.7 779e5a36ca chore(github): expand dependabot to GitHub Actions + Docker
Existing npm group covered runtime deps. Adds weekly bumps for the
.github/workflows/* action versions and the base image referenced in
Dockerfile so neither falls behind silently.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:23:17 +02:00
Marc-André BombeckandClaude Opus 4.7 072941bae6 chore(github): add issue + PR templates
- bug_report.yml: structured fields for version, deployment, repro
  steps, logs; routes security to SECURITY.md
- feature_request.yml: problem-then-proposal template, surface
  multi-select aligned with the app's actual touch points
- config.yml: disables blank issues, points usage questions to docs
  + Discussions, points security to private advisories
- PULL_REQUEST_TEMPLATE.md: reminds the contributor to target develop
  (not main), checklist mirrors local CI, no-PII reminder

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:23:15 +02:00
Marc-André BombeckandClaude Opus 4.7 e7b6b27451 chore(community): add Code of Conduct (Contributor Covenant 2.1)
Adopts the canonical Contributor Covenant 2.1 (verbatim, with
conduct@bombeck.io as the enforcement contact). CONTRIBUTING.md links
to it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:23:07 +02:00
Marc-André BombeckandClaude Opus 4.7 fe660d407c docs(contributing): document develop / main branch model
CONTRIBUTING.md gains a "Branch Model" section with an ASCII flow
diagram and the two-rule summary (develop = daily target, main =
release-only). Hotfix flow + end-user-vs-contributor guidance
included. Mirror page on docs.healthlog.dev landed in healthlog-docs
5d96861.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:19:05 +02:00
Marc-André BombeckandClaude Opus 4.7 77f9741672 docs(planning): F1 develop branch + FX user-facing cleanup recorded
- STATE.md: F1 ticked complete, F3+F4 collapsed into FX (combined
  cleanup wave), all bullets reflect what shipped on origin/main
- phase-F1-report.md: GHCR workflow audit + branch model contract
- phase-FX-report.md: 3-repo PII/jargon/German scrub (HealthLog,
  healthlog-docs, healthlog-landing) with carry-overs and the
  pre-release PII grep gate spec

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:17:03 +02:00
Marc-André BombeckandClaude Opus 4.7 a1cf9bcb37 docs: scrub PII + internal jargon + German leaks from user-facing artifacts
Retroactive cleanup of CHANGELOG.md and docs/audit/v1.4.X-summary.md
files. Removes maintainer's real name, specific health figures tied to
the live tenant, internal release-process jargon, and stray German
strings in English-only release notes. Pre-release PII-grep gate added
to the v1.4.20 release pipeline. No git history rewrite.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:14:28 +02:00
Marc-André BombeckandClaude Opus 4.7 f80492a5a3 docs(planning): bootstrap v1.4.20 STATE+ROADMAP, archive v1.4.19
- STATE.md replaced with v1.4.20 scaffold (F0/FX foundation, B1-B5
  Insights redesign per phase-D-v1419-product-lead-review.md, Wave D
  QA, Phase E release)
- ROADMAP.md: v1.4.20 milestone at top, v1.4.19 archived as previous
  milestone summary
- phase-E1-report.md + phase-E3-report.md: dangling v1.4.19 release
  artefact updates committed

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:55:27 +02:00
Marc-André BombeckandClaude Opus 4.7 3fa07ad54b docs(planning): v1.4.20 marathon handoff document
Captures the next-session brief: foundation tasks F1-F6 (develop branch
model, CONTRIBUTING docs, retroactive jargon cleanup, release-notes
language audit, repo best-practice audit, doc-hallucination QA), then
the 5-phase Insights redesign + AI Coach work, then standard QA + release
on the new branch flow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:46:46 +02:00
Marc-André BombeckandClaude Opus 4.7 00daf962ea docs(audit): v1.4.19 release summary
Marc-Brief plus what-landed / deferred / CI / production-deploy /
v1.4.20-preview sections. Mirrors the v1.4.18 report shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:43:24 +02:00
Marc-André BombeckandClaude Opus 4.7 89f00cf832 chore(release): v1.4.19
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v1.4.19
2026-05-10 14:34:01 +02:00
Marc-André BombeckandClaude Opus 4.7 1f5ad68033 docs(planning): record Wave D reconcile + reviewer findings
STATE update + reconcile report captures the six reviewer briefs and
the inline triage outcome (one CRITICAL fixed, seven HIGH applied,
three HIGH carried over). Backlogs updated: the v1.4.20 file gains a
"Phase D reconcile carry-over" block with file:line references; the
v1.5 file inherits the strategic risk watchlist (conversation
persistence eviction, correlation FDR control, streaming UX,
prompt-injection refusal, safeParse legacy-payload audit pattern).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:30:57 +02:00
Marc-André BombeckandClaude Opus 4.7 1f0d9ad1f7 style: prettier on touched admin section
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:30:10 +02:00
Marc-André BombeckandClaude Opus 4.7 977f124b9e fix(ui): widen TabsList padding so the focus ring fits inside the clamp
`overflow-y-hidden` on the tabs strip clipped the bottom of the
focus-visible ring (`ring-[3px]`) on triggers because the parent
padding was only 3 px — the 3 px ring ended exactly at the
content-box edge. Bump the padding to `p-1` (4 px) so keyboard users
see the full ring while the y-axis clamp still hides the painted
scrollbar Marc reported.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:26:18 +02:00
Marc-André BombeckandClaude Opus 4.7 5a8ad3d895 fix(charts): drop the redundant resize sync from useViewportWidth
The useState lazy initialiser already runs on the client during
hydration and reads the live `window.innerWidth`, so the post-mount
handler() call was a no-op that triggered a setState inside useEffect.
The eslint react-hooks/set-state-in-effect rule flags this pattern;
removing it lets the resize + orientationchange listeners alone drive
updates without a hydration-time double set.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:25:50 +02:00
Marc-André BombeckandClaude Opus 4.7 1258b24864 fix(admin): render token-name timestamps in Berlin time
The auto-issued token-name viewer rebuilt the dd.MM.yyyy HH:mm suffix
from `Date.getUTC*` accessors, producing 19:46 instead of the 21:46 a
Berlin-time admin would expect for a token issued at 19:46 UTC. Route
the parsed Date through `Intl.DateTimeFormat("de-DE", { timeZone:
"Europe/Berlin", … })` so the embedded timestamp matches every other
display surface in the app.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:25:28 +02:00
Marc-André BombeckandClaude Opus 4.7 6b35cad079 refactor(v1.4.19): apply simplify-review safe suggestions
Mechanical clean-up sweep across five hot spots flagged by the simplify
audit:

- Lift the 11-key auth.* audit-action label map out of the two admin
  components that rebuilt it on every render. The shared
  useAuthActionLabels() hook in _shared.tsx is now the single home for
  new entries (login-overview + recent-audit-preview consume it).
- Drop the dead chipClass branches in IntegrationStatusPill: only the
  connected state actually paints a Dracula-green chip; error and
  disconnected fall back to the badge variant.
- Add thin DateInput / DateTimeInput wrappers under components/ui that
  bake in the lang={locale} hint so 14 callsites across 9 files no
  longer have to remember the prop. A future date input cannot
  silently regress.
- Remove the leading icon-only flex wrappers left behind when the
  duplicate card titles were dropped on /admin/feedback,
  /admin/danger-zone, and the thresholds editor.
- Strip the five "v1.4.19 X removed because…" narration comments from
  insights/page.tsx and the dashboard. git blame already answers what
  changed and the comments aged into history.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:24:35 +02:00
Marc-André BombeckandClaude Opus 4.7 ef74241a68 fix(measurements): paint Sys/Dia badge on mobile blood-pressure rows
The mobile card list checked m.type against the short forms BP_SYS /
BP_DIA, which never appear in API payloads — the canonical enum is
BLOOD_PRESSURE_SYS / BLOOD_PRESSURE_DIA. The badge condition was
therefore permanently false and the disambiguation tag never rendered
on Pixel 5. Route the label through MEASUREMENT_TYPE_LABEL_KEYS so the
mobile branch shares the desktop table's single source of truth, and
add a regression test that asserts both labels paint in both branches
under EN and DE.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:15:56 +02:00
Marc-André BombeckandClaude Opus 4.7 14eff96474 docs(planning): Phase B (A8 application) report + v1.4.20 backlog
Wave B completed: 6 / 6 CRITICAL applied, 21 / 25 HIGH
applied, 4 HIGH deferred (3 not-an-issue, 1 owned by v1.4.20
Insights redesign), 31 MED + 16 LOW seeded as v1.4.20
backlog. Final verification clean. STATE.md status block
appended; A8 raw findings added to repo for future reference.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 14:01:38 +02:00
Marc-André BombeckandClaude Opus 4.7 ae2f671321 style: prettier on touched test + e2e files
Run prettier on the test, e2e, and component files this v1.4.19
A8 wave touched. The .planning/* and docs/audit/* prettier
warnings are pre-existing baseline noise — left alone to keep
this commit narrow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:58:30 +02:00
Marc-André BombeckandClaude Opus 4.7 713b494f04 fix(admin): humanise auto-issued token names in viewer
The native auto-login flow names tokens by suffixing the issuing
call's `Date.now().toISOString()`, so the admin viewer rendered
"iOS auto-login 2026-05-05T19:46:20.603Z" — readable as debug
output, not as something a person picked. Added a renderer-side
formatter that detects the trailing ISO timestamp and rewrites
the cell to "iOS auto-login · 05.05.2026 19:46". Manual token
names without an ISO suffix pass through unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:57:04 +02:00
Marc-André BombeckandClaude Opus 4.7 bc81fd7db2 fix(admin): translate native-token audit actions, link recent rows
Two related improvements to the admin recent-activity surface:

- New audit actions `auth.token.autoissue.native`,
  `auth.token.refresh`, and `auth.token.revoke` were leaking
  through both the dashboard preview and the Login Overview
  viewer as raw event keys. Added translations and wired the
  three keys through the action-label map shared by both
  components.
- Recent-activity rows on /admin were a dead-end: a failed
  login row showed "Unknown" + "Failed" with no link, no IP,
  no way to drill in. Each row is now a Link into
  /admin/login-overview, with the IP shown inline at sm+.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:55:34 +02:00
Marc-André BombeckandClaude Opus 4.7 876e074fd8 fix(measurements,settings): visual disambiguation polish
Two related polish items:

- Mobile measurement list rendered systolic and diastolic BP
  rows with the same heart icon, so a 117 mmHg row and a
  79 mmHg row underneath looked identical until you read the
  number. Mirror the desktop "Sys" / "Dia" badges on the
  mobile cards too.
- Telegram settings card was showing two separate badges side
  by side ("Configured" green + "Enabled" outline). They
  carried no visual hierarchy and made the difference between
  the two unclear. Collapsed into a single status pill — green
  "Active" when both bits are set, neutral
  "Configured · Disabled" when the channel is paused.

Also brought a stale settings-sections SSR-smoke test in line
with the F-07 threshold-naming change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:54:18 +02:00
Marc-André BombeckandClaude Opus 4.7 98cd13c8e0 i18n(achievements,admin): drop redundant labels and grammar nits
A few small quality wins bundled together:

- The 1-day achievement descriptions read "for 1 consecutive
  days" / "1 Tage in Folge" — singular/plural mismatch that
  was awkward in both languages. Hand-tuned the five 1-day
  variants to "for 1 day" / "an einem Tag"; 7d / 30d copies
  unchanged.
- The two "Two hundred …" / "Zweihundert …" badges now use
  the numeral 200 to match every other count-based badge.
- /admin/integrations toggle rows ("Umami" / "GlitchTip" lines
  that duplicated the card header) drop the literal product
  name and just show the action: the existing
  `umamiEnabled` / `glitchtipEnabled` description copy is
  promoted to the toggle's primary label. SettingsToggle's
  description prop becomes optional to support that.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:52:31 +02:00
Marc-André BombeckandClaude Opus 4.7 70ebe32b27 fix(admin): drop duplicate card title on single-card pages
`/admin/danger-zone` and `/admin/feedback` are single-card admin
pages where the page-level header already names the section.
The card-level h2 ("Danger Zone" / "Feedback Inbox") was a verbatim
copy of that header taking up vertical space without adding info.
Removed the duplicate headers — the section icon stays so the
card still has a visible affordance.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:49:58 +02:00
Marc-André BombeckandClaude Opus 4.7 3f6e670894 i18n(admin,settings): copy consistency sweep
Several smaller A8 copy nits in one pass:

- Notifications settings subtitle no longer hard-codes "Telegram,
  ntfy, Web Push" — the channel-reliability board only shows
  configured channels, so naming all three made one of them look
  missing.
- Admin /feedback page subtitle widened to "feedback, bug reports,
  and feature requests" so it matches what the inbox actually
  contains; the duplicate card-level title + description on
  /admin/feedback is gone (page header already provides them).
- /admin/services subtitle now mentions integrations alongside
  notification channels (the API and moodLog toggles live there
  too); per-row "system-wide" suffixes dropped — the section
  subtitle already establishes the scope.
- /admin/ai-quality subtitle reads as a sentence instead of the
  jargon "(severity x provider)" expression.
- /admin/feedback empty state is "All caught up" / "Alles erledigt"
  instead of the English-idiom "Inbox zero".
- /achievements subtitle rewritten in both EN + DE to drop the
  dev-speak "from feature rollout onward".
- /admin/api-tokens permission badges show "All" (with a tooltip)
  instead of the bare regex glob `*`.
- Brand spelling normalized: every Glitchtip → GlitchTip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:47:51 +02:00
Marc-André BombeckandClaude Opus 4.7 1f4d6a28a5 fix(settings): unify threshold page naming
The thresholds settings route was carrying three different
names: nav label "Personal targets", page header "Personal
targets", inner card header "Personal thresholds". The URL
slug is `/settings/thresholds`, so settled on "Personal
thresholds" everywhere in EN; DE keeps the established
"Persönliche Zielwerte". While here, dropped the duplicate
title + subtitle inside the editor card — the page-level
header already provides them and the second copy was just
visual noise.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:43:04 +02:00
Marc-André BombeckandClaude Opus 4.7 42b0f110ba fix(admin): explicit role-change button label
The user-management role-change button rendered a bare arrow
followed by "User" / "Admin" — direction was ambiguous
("does the arrow mean 'now they are User' or 'send them down
to User'?") and the label carried no aria-label. Replaced the
arrow text with explicit "Make user" / "Make admin"
("Zum Benutzer" / "Zum Admin"), and added the same string as
aria-label so screen readers announce the action verb instead
of the visible-only arrow glyph.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:41:52 +02:00
Marc-André BombeckandClaude Opus 4.7 1ae9b9dcad fix(admin,measurements): humanise raw enum badges
Two badges were rendering raw enum values: backups showed
`WEEKLY_AUTO` / `MANUAL` and the measurement list showed
`WITHINGS` instead of `Withings`. Backup type now uses two new
i18n keys (`Weekly (auto)` / `Manuell`); measurement source
reuses the existing `measurements.source*` keys. Unknown
enum values still fall through to the raw string so any
schema additions remain visible.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:40:52 +02:00
Marc-André BombeckandClaude Opus 4.7 d2631253a4 fix(insights): match legacy-payload CTA button to its banner copy
The legacy-payload yellow banner read "Insights updated —
regenerate for new explainability features" but the CTA button
underneath said "Start analysis", so the verb in the help text
and the verb on the button disagreed. Switch the legacy CTA
button to the existing `insights.heroRegenerate` key so both
banner and button now talk about regeneration. The empty-state
"Start analysis" CTA stays untouched — that one's never had
analysis to start from.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:38:19 +02:00
Marc-André BombeckandClaude Opus 4.7 4feacad088 fix(admin): drop sidebar reference from welcome subtitle
The admin overview welcome copy read "Pick a section from the
sidebar to manage HealthLog." but the mobile admin shell uses a
horizontal pill strip, not a sidebar — so the instruction was
literally wrong on the screen size where it mattered most. Drop
the surface reference and let the copy work on every layout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:36:49 +02:00
Marc-André BombeckandClaude Opus 4.7 ff6e18473a fix(forms): hint browser locale on date and datetime inputs
Native `<input type="date">` and `<input type="datetime-local">`
were rendering `mm/dd/yyyy` even when the German locale was
active, because Chromium picks the input's locale from the
nearest `lang` attribute on the element rather than from
`<html lang>`. Pass `lang={locale}` on every date/datetime input
in the app — onboarding DOB, account DOB, export filters,
doctor-report range, measurement / medication / mood timestamp
fields — so DE users now see `TT.MM.JJJJ`. Display formatting
already routed through `useFormatters()`; this closes the form
half of the same gap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:36:23 +02:00
Marc-André BombeckandClaude Opus 4.7 7a6bc812c9 i18n(achievements): replace insulting badge titles
The over-intake and skipped-intake achievements shipped with the
literal titles "Idiot" / "Lazy Boy" in both EN and DE bundles —
the German file never translated them. For a personal health app
the user gets called an idiot any time they double-dose because
they forgot they already took it. Replaced with neutral,
descriptive titles ("Double take" / "Stepped back" — "Doppelt
genommen" / "Pause eingelegt"). Locked in by a regression suite
that scans every badge title against a banned-word list.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:32:19 +02:00
Marc-André BombeckandClaude Opus 4.7 dc75fe846a fix(admin): restrict login overview to auth events
The Login Overview page promised "authentication and admin events"
in its subtitle but the underlying audit-log query never set
`filter=auth`, so the table mixed insights.* / admin.* / data.*
rows in with passkey + password logins. Default the page request
to `filter=auth` (an explicit action picker still overrides), and
restrict the action dropdown to actions starting with `auth.` so
the filter UI stays in sync with what the page actually shows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:30:51 +02:00
Marc-André BombeckandClaude Opus 4.7 180f46c24e fix(medications): localise time-window range string
`formatTimeWindowRange` used to hard-code the German "bis ... Uhr"
suffix, so an English-locale user saw "Today, 19:00 bis 23:00 Uhr"
on the medication card and the medication form. Pass the active
locale through and emit "19:00 – 23:00" for English while keeping
the existing German output for DE users (and unchanged for legacy
call sites that still don't pass a locale).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:29:07 +02:00
Marc-André BombeckandClaude Opus 4.7 79ad5c47a9 docs(planning): A7 report + STATE update for admin polish
Five atomic commits landed for A7:

- 088832a — feedback tab strip mini-scrollbar removed
- dd8212e — api-tokens table truncate-with-tooltip (4th attempt)
- 7a70db6 — api-tokens Einklappen button removed
- 6507646 — Zielwerte page whitespace tightened
- 90a109d — Zielwerte status labels translated to DE

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:24:05 +02:00
Marc-André BombeckandClaude Opus 4.7 90a109d0b4 i18n(admin): translate Zielwerte status labels (Low/On Target/Stable/Moderate to DE)
Marc reported the Zielwerte page (`/targets`) still surfaces English
status pills even when the rest of the UI runs in German: "Low /
On Target / Stable / Moderate / Normal" stayed in source language
because the server emits stable English category strings and the
page rendered them verbatim. Card titles ("Resting pulse", "Mood
stability", "Medication compliance" …) had the same issue.

Add two i18n key blocks under `targets.*`:

- `targets.label.<TYPE>` — one entry per measurement type, used by
  `<TargetCard>` to localise the card title. The server keeps
  emitting English labels (so logs / external consumers don't
  churn); the page swaps them in via `t("targets.label." + target.type)`.
- `targets.status.<key>` — every classification category from
  `lib/analytics/classifications.ts`, `pulse-targets.ts`, and the
  inline maps in `targets/route.ts`. A small `STATUS_CATEGORY_KEY`
  map on the page normalises the human-readable English category
  to the i18n key.

Glucose targets keep their existing key path (the server already
emits `targets.glucoseFasting` etc. as the label, resolved at the
top of `<TargetsPage>` before cards mount).

Fall-through is graceful: if a future server adds a category we
have not translated yet, the verbatim English string still
renders — the page never blows up.

Also adds `targets.bmi`, `targets.label.BMI`, `targets.status.normal`,
`targets.status.optimal`, `targets.status.fitness` to the i18n
integrity test's PLACEHOLDER_ALLOWLIST: those terms are identical
in EN and DE (BMI is a global acronym; "Normal" / "Optimal" /
"Fitness" are technical terms German clinics use verbatim).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:21:36 +02:00
Marc-André BombeckandClaude Opus 4.7 6507646a90 style(admin): reduce excessive vertical whitespace on Zielwerte page
Marc reported "relativ viel Platz" wasted between the overview
header and the first cards on `/targets` (Zielwerte). The page
used `space-y-8` (32 px) on the outer wrapper, which leaves an
oversized gap between the intro paragraph and the first card.

Tighten to `space-y-6` (24 px) — same rhythm the admin and
settings pages use, so the whole app shares one Dracula-spacing
beat.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:21:36 +02:00
Marc-André BombeckandClaude Opus 4.7 7a70db6c86 fix(admin): hide collapse button when page has only one section
`/admin/api-tokens` carried a Collapse / Expand toggle inherited
from the v1.4 shared admin page where 13 sections lived together.
On the v1.5 dedicated route the toggle just hides the only card on
the page — Marc reported it as "sinnlos" (pointless): the page
title already says "API Tokens", the only thing below it is the
tokens card, and collapsing it leaves an empty surface with the
section heading staring back at you.

Drop the local `expanded` state and the toggle button from the
section. The card always renders, the query always fetches, and the
header is now a plain icon + title row. The shared
`settings.collapse` / `settings.expand` i18n keys stay in messages
because `<LoginOverviewSection>` still uses them.

The matching test asserts no Collapse / Expand / Einklappen /
Ausklappen string appears in either locale, and that no
`aria-expanded` button shows up on the section header.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:21:36 +02:00
Marc-André BombeckandClaude Opus 4.7 dd8212e202 fix(admin): api-tokens table truncate-with-tooltip (no scrollbar 4th attempt)
Marc has now reported a residual scrollbar on `/admin/api-tokens`
four times running. Earlier fixes addressed the obvious overflow
culprits (column-hide in v1.4.15, mobile card-list in v1.4.16,
admin-shell mobile-strip `no-scrollbar` in v1.4.18), but the
section itself still leaked a few quirks:

- The mobile token-name span carried `truncate` and `break-all` on
  the same element. `truncate` sets `white-space: nowrap`, which
  beats `word-break: break-all` per CSS spec — `break-all` was dead
  code AND it was misleading. Remove it.
- The desktop table had no per-column upper bound. A long token
  name could expand the row past the card on 768-1024 px viewports.
  Lock the layout with `table-fixed` + `<colgroup>` widths.
- Permission badges had no `max-width` cap. A single long
  permission string (`measurements:write:audit:legacy:extended`)
  could push the cell past the card. Cap each badge with
  `max-w-full truncate` and a native `title` attribute.

Wrap every potentially-long string (token name, username, permission
badge) in a small `<TruncatedCell>` helper that pairs the visual
truncation with a radix tooltip. The tooltip is the discoverable
hover/long-press path so the full value is still readable; the
native `title` attribute is a graceful fallback for screen readers
and right-click → "view full name" on desktop.

Adds an e2e regression that walks every element inside the mobile
card-list and asserts none of them carry `scrollWidth > clientWidth`,
exercised against a worst-case payload (long token name + long
username + long permission string).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:21:36 +02:00
Marc-André BombeckandClaude Opus 4.7 088832aa39 fix(admin): remove spurious scrollbar from feedback tab strip
Marc reported a stray "mini scroll-button" on the right side of the
admin feedback inbox tab strip (Open / Acknowledged / Resolved /
Archived). The four short tabs do not horizontally overflow the
viewport, so the painted bar surprised him every time he opened the
inbox.

Root cause is a CSS overflow coupling: setting only `overflow-x: auto`
silently forces `overflow-y` to `auto` too, because a single axis
cannot be `visible` while the other clips. The strip is locked at
`h-9` (36 CSS px) but its badge children (`py-0.5` + text-xs digits)
ride 1-2 px taller than the strip on some glyph stacks, so the
implicit `overflow-y: auto` paints a tiny vertical scrollbar on the
right edge.

Fix is a single class on `tabsListVariants`: `overflow-y-hidden`
(plus `overflow-y-visible` in the vertical-orientation override so
vertical tab columns are not affected). Horizontal swipe and
keyboard-arrow scrolling still work — only the implicit y-axis bar
is suppressed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:21:36 +02:00
Marc-André BombeckandClaude Opus 4.7 ef67da5570 docs(planning): A3 report + STATE update for /insights polish
Marks Wave A bucket A3 complete. Six commits on origin/main
(60a91af → f2b21a4) covered all six concerns Marc raised about the
/insights page on 2026-05-10. Detailed walkthrough in
.planning/phase-A3-report.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:20:00 +02:00
Marc-André BombeckandClaude Opus 4.7 f2b21a47db test(insights): add insights-polish.test.ts (followup to 5360d3c)
Adds the actual test file that 5360d3c's message described — the
previous commit only landed the .planning/* files for A6 because a
parallel agent's `git add` interleaved with mine and reordered the
working-tree state mid-commit.

The test suite itself was already passing locally (9/9) and against
the 1646-test full suite; this commit lands the file so CI sees the
guards.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:16:57 +02:00
Marc-André BombeckandClaude Opus 4.7 5360d3c0ce test(insights): coverage for v1.4.19 polish changes
Source-grep guards for the four /insights polish concerns Marc
flagged on 2026-05-10 plus drift guards for the chart-token leak
fix:

  1. Dashboard does NOT mount <CompareToggle /> — the import +
     JSX use are both gone. Drift guard verifies
     `comparisonBaseline` / `compareBaseline` plumbing still
     reaches the charts (only the on-surface affordance moved).
  2. /insights renders exactly ONE `onRegenerate={advisor.regenerate}`
     prop — the page hero. The advisor card's duplicate handlers
     are gone.
  3. /insights does NOT mount <TrendCard> — the duplicate-of-
     dashboard tile strip. Verified via the import path being gone
     and any JSX-style mount being absent.
  4. /insights does NOT pass `t("insights.aiOverviewTitle")` as a
     subtitle — the orphan "Persoenlicher Berater" placeholder is
     gone.
  5. `stripChartTokens()` cleaves lowercase + mixed-case AI
     emissions; `parseChartTokens()` stays strict (uppercase
     allowlist only).

Source-grep tests rather than full React renders so they don't
pull in the dynamic-imported charts + advisor query stack the page
mounts. The cost is a slight false-positive risk if a future
refactor renames the JSX literal we're matching, but the comments
on each test point at the load-bearing assertion so a renamer can
update the guard without losing the contract.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:16:19 +02:00
Marc-André BombeckandClaude Opus 4.7 e0c007a8af docs(planning): A2 report + STATE update for charts mobile audit
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:15:14 +02:00
Marc-André BombeckandClaude Opus 4.7 fa91a73aef fix(insights): strip lowercase chart tokens from AI prose
Marc reported a raw "metric: blood_pressure_sweet" string at the
bottom of /insights — looked like a debug code or unrendered
template. Root cause: the v1.4.16 strip regex
`/metric:[A-Z_]+/g` only matches uppercase letters and underscores,
but the model occasionally emits lowercase / snake_case forms like
`metric:blood_pressure` or `metric:weight_trend`. Those slipped
through `stripChartTokens()` unchanged and the literal token text
landed in the DOM.

Split the regex in two:
 - `STRIP_TOKEN_REGEX` (`[A-Za-z0-9_]+`) — permissive; cleaves any
   case from prose so unrenderable / hallucinated tokens never
   surface as text.
 - `PARSE_TOKEN_REGEX` (`[A-Z_]+`) — strict; the render path still
   only mounts charts for the uppercase allowlisted vocabulary.

Allowlist semantics are unchanged — `parseChartTokens()` still
returns only entries from `ALLOWED_CHART_TOKENS`. The change is
purely about defending the visible-prose surface from the wider
class of token shapes the LLM occasionally emits.

XSS-style trailing junk (`metric:WEIGHT' onclick='alert(1)'`)
still cleaves cleanly: the apostrophe terminates either class.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:14:53 +02:00
Marc-André BombeckandClaude Opus 4.7 335f288d69 refactor(insights): drop duplicate metric tile strip
The /insights page used to lead with a 5-column grid of TrendCards
(weight, BP-sys, BP-dia, pulse, plus body-fat and mood when
present) — exact duplicates of the dashboard tile-strip. Marc said
the tiles "duplizieren Dashboard, nehmen nur Platz weg" and asked
for them gone.

Drop the strip, the helper functions that supported only it
(`getRangeColorClass`, `getRangeHint`, `RangeDisplayConfig`), and
the bookkeeping locals (`bf`, `showBodyFatCard`, `moodSummary`,
`showMoodCard`, `fmt`). Remaining mood references on the page read
`data?.moodSummary` inline; every other helper was used only by the
removed strip.

Net: -157 lines, no behaviour change anywhere except the missing
duplicate strip. Recommendations grid + per-section status / chart
cards below stay intact — those are the actual value of /insights.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:14:05 +02:00