mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 21:10:59 +00:00
chore(release): v1.4.15
Bumps version to 1.4.15 and adds the corresponding CHANGELOG entry covering backup completeness, achievements UI, onboarding tour, doctor-report v2, auto-deploy on GHCR push, empty-states sweep, notification + integration status UI, AI hallucination guardrails, mobile UX fixes, and the BD-Zielbereich percentage fix. Includes the formatting drift left over by the v1.4.15 development phases (Prettier sweep on touched files) and a typecheck fix in `dashboard-layout.test.ts` so `pnpm typecheck`, `pnpm lint`, `pnpm format:check`, `pnpm test`, and `pnpm test:integration` all pass green on the release tag. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
1b1214bcb3
commit
4dcaa08b42
32 files changed
+639
-295
No files matched your search
+42
-42
@@ -53,21 +53,21 @@ Reset einplanen.
|
||||
|
||||
### Release
|
||||
|
||||
| SHA | Summary |
|
||||
| --------- | ------------------------------------ |
|
||||
| `e5fae9b` | chore(release): v1.4.14 |
|
||||
| SHA | Summary |
|
||||
| --------- | --------------------------------------------------------------- |
|
||||
| `e5fae9b` | chore(release): v1.4.14 |
|
||||
| `f0d53fb` | chore(release): rebrand artifacts as v1.4.14 (patch, not minor) |
|
||||
|
||||
### Codex-OAuth verification (Phase 1)
|
||||
|
||||
| SHA | Summary |
|
||||
| --------- | ---------------------------------------------------------------------- |
|
||||
| SHA | Summary |
|
||||
| --------- | ------------------------------------------------------------------------ |
|
||||
| `5df74f7` | fix(ai): default Codex model to `gpt-5.3-codex` for ChatGPT-account auth |
|
||||
|
||||
### v1.4.6 deferred bugfix backlog (Phase 2)
|
||||
|
||||
| SHA | Summary |
|
||||
| --------- | -------------------------------------------------------------------- |
|
||||
| SHA | Summary |
|
||||
| --------- | -------------------------------------------------------------------------------------------- |
|
||||
| `512a6a6` | fix(admin): wipe-all-data clears notification channels, push subs, telegram deletions (T2.1) |
|
||||
| `cb6a59a` | fix(insights): correct DST handling in cross-metric daily bucket pairing (T2.2) |
|
||||
| `5403821` | fix(insights): map AI provider errors to 422/503 instead of 500 (T2.3) |
|
||||
@@ -75,17 +75,17 @@ Reset einplanen.
|
||||
|
||||
### Admin Panel refactor (Phase 4b — folds T2.6 + T2.7)
|
||||
|
||||
| SHA | Summary |
|
||||
| --------- | ------------------------------------------------------------------------ |
|
||||
| SHA | Summary |
|
||||
| --------- | ----------------------------------------------------------------------------------- |
|
||||
| `d8b71b3` | feat(admin): scaffold per-section dynamic routes (admin-shell + [section]/page.tsx) |
|
||||
| `12b280b` | refactor(admin): move sections to dynamic-route pattern + reduce overview |
|
||||
| `69c5225` | feat(admin): backups view + manual backup trigger (T2.6 deferred) |
|
||||
| `5957e18` | feat(admin): users management with filters + force-logout action (T2.7 deferred) |
|
||||
| `a34cd8c` | refactor(i18n): namespace admin keys under `admin.section.<slug>.*` |
|
||||
| `73965cd` | feat(admin): expandable sidebar nav for admin sections |
|
||||
| `961e2a9` | fix(admin): status-card CTAs use real routes (not `#anchor`) |
|
||||
| `7a47532` | feat(admin): redirect legacy section-anchor paths to dynamic routes |
|
||||
| `1a7e3d6` | test(admin): tighten status-card-grid + add per-section render smoke |
|
||||
| `12b280b` | refactor(admin): move sections to dynamic-route pattern + reduce overview |
|
||||
| `69c5225` | feat(admin): backups view + manual backup trigger (T2.6 deferred) |
|
||||
| `5957e18` | feat(admin): users management with filters + force-logout action (T2.7 deferred) |
|
||||
| `a34cd8c` | refactor(i18n): namespace admin keys under `admin.section.<slug>.*` |
|
||||
| `73965cd` | feat(admin): expandable sidebar nav for admin sections |
|
||||
| `961e2a9` | fix(admin): status-card CTAs use real routes (not `#anchor`) |
|
||||
| `7a47532` | feat(admin): redirect legacy section-anchor paths to dynamic routes |
|
||||
| `1a7e3d6` | test(admin): tighten status-card-grid + add per-section render smoke |
|
||||
|
||||
### UX polish (Phase 5)
|
||||
|
||||
@@ -100,44 +100,44 @@ Reset einplanen.
|
||||
|
||||
### Multi-agent QA reconcile (Phase 6)
|
||||
|
||||
| SHA | Summary |
|
||||
| --------- | ------------------------------------------------------------------------ |
|
||||
| `88c8db1` | refactor: apply simplify-review safe-to-apply suggestions (11 of 11) |
|
||||
| `b3f282a` | fix(security): block force-logout from targeting the admin's own session |
|
||||
| `8e17f22` | fix(admin): rate-limit `/api/admin/backups/run` to 3/min per admin |
|
||||
| `f0cfd26` | fix(security): redact standalone `hlk_` / `hlr_` native API tokens in logs |
|
||||
| `bd24f13` | fix(a11y): drop duplicate h2 inside single-section `/admin/<slug>` routes |
|
||||
| SHA | Summary |
|
||||
| --------- | ---------------------------------------------------------------------------------- |
|
||||
| `88c8db1` | refactor: apply simplify-review safe-to-apply suggestions (11 of 11) |
|
||||
| `b3f282a` | fix(security): block force-logout from targeting the admin's own session |
|
||||
| `8e17f22` | fix(admin): rate-limit `/api/admin/backups/run` to 3/min per admin |
|
||||
| `f0cfd26` | fix(security): redact standalone `hlk_` / `hlr_` native API tokens in logs |
|
||||
| `bd24f13` | fix(a11y): drop duplicate h2 inside single-section `/admin/<slug>` routes |
|
||||
| `499cfad` | fix(a11y): bump `/admin/users` + `/admin/login-overview` tap targets to WCAG 2.5.5 |
|
||||
| `ef96e87` | fix(admin): default api-tokens + login-overview routes to expanded |
|
||||
| `ef96e87` | fix(admin): default api-tokens + login-overview routes to expanded |
|
||||
|
||||
### End-to-end test coverage (Phase 3)
|
||||
|
||||
| SHA | Summary |
|
||||
| --------- | -------------------------------------------------------- |
|
||||
| `535465c` | test(e2e): seeded test user + auth storageState fixture |
|
||||
| `3999ea7` | test(e2e): authenticated dashboard + add-measurement flow |
|
||||
| `2b6f959` | test(e2e): mocked Codex device-flow + insights regenerate specs |
|
||||
| `a106547` | test(e2e): doctor-report PDF + mobile-viewport smoke |
|
||||
| SHA | Summary |
|
||||
| --------- | --------------------------------------------------------------------- |
|
||||
| `535465c` | test(e2e): seeded test user + auth storageState fixture |
|
||||
| `3999ea7` | test(e2e): authenticated dashboard + add-measurement flow |
|
||||
| `2b6f959` | test(e2e): mocked Codex device-flow + insights regenerate specs |
|
||||
| `a106547` | test(e2e): doctor-report PDF + mobile-viewport smoke |
|
||||
| `ca3c599` | test(e2e): extend axe-core to authenticated surfaces; fix login regex |
|
||||
| `7af1f59` | fix(a11y): close violations the new e2e axe-core sweep surfaced |
|
||||
| `7af1f59` | fix(a11y): close violations the new e2e axe-core sweep surfaced |
|
||||
|
||||
### Performance (Phase 4)
|
||||
|
||||
| SHA | Summary |
|
||||
| --------- | -------------------------------------------------------- |
|
||||
| `bb2b1de` | perf(insights): defer Recharts ScatterChart imports via `next/dynamic` |
|
||||
| SHA | Summary |
|
||||
| --------- | ----------------------------------------------------------------------- |
|
||||
| `bb2b1de` | perf(insights): defer Recharts ScatterChart imports via `next/dynamic` |
|
||||
| `519e36e` | perf(dashboard): skip checklist API fetches once onboarding is complete |
|
||||
| `1a85da0` | fix(insights): preserve chart visual parity in deferred bundle |
|
||||
| `41fa203` | docs(audit): v1.4.14 performance audit report |
|
||||
| `1a85da0` | fix(insights): preserve chart visual parity in deferred bundle |
|
||||
| `41fa203` | docs(audit): v1.4.14 performance audit report |
|
||||
|
||||
### CI / housekeeping
|
||||
|
||||
| SHA | Summary |
|
||||
| --------- | -------------------------------------------------------- |
|
||||
| SHA | Summary |
|
||||
| --------- | ----------------------------------------------------------------- |
|
||||
| `3cacdf2` | fix(tests): share next/headers cookieJar across integration suite |
|
||||
| `c847b80` | chore(format): trend-card test file prettier sweep |
|
||||
| `badd380` | style(admin): apply prettier to phase-6 reconcile changes |
|
||||
| `6b88e56` | chore(format): prettier sweep over phase-2 touched files |
|
||||
| `c847b80` | chore(format): trend-card test file prettier sweep |
|
||||
| `badd380` | style(admin): apply prettier to phase-6 reconcile changes |
|
||||
| `6b88e56` | chore(format): prettier sweep over phase-2 touched files |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -42,18 +42,18 @@ The current application config (UUID `pg8wggwogo8c4gc4ks0kk4ss`,
|
||||
mode for `dockercompose`-typed apps.
|
||||
- `manual_webhook_secret_github: null` — no webhook secret configured today.
|
||||
|
||||
What Coolify *does* support natively (per the Coolify docs):
|
||||
What Coolify _does_ support natively (per the Coolify docs):
|
||||
|
||||
1. **GitHub App auto-deploy**: pushes to `main` trigger an immediate redeploy.
|
||||
For us this would deploy from a SHA *before* GHCR finishes building, since
|
||||
For us this would deploy from a SHA _before_ GHCR finishes building, since
|
||||
GHCR runs in the same push event in parallel. Race-prone.
|
||||
2. **Deploy webhook URL**: every Coolify resource exposes
|
||||
`GET https://<coolify-fqdn>/api/v1/deploy?uuid=<resource-uuid>` with
|
||||
Bearer-token auth. This is a *trigger-only* surface — it queues a
|
||||
Bearer-token auth. This is a _trigger-only_ surface — it queues a
|
||||
redeploy. Coolify treats this as an authenticated remote-control command;
|
||||
the recipe in the docs is "GitHub Actions builds the image, pushes to
|
||||
GHCR, then `curl --request GET '${{ secrets.COOLIFY_WEBHOOK }}'
|
||||
--header 'Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}'`."
|
||||
--header 'Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}'`."
|
||||
3. **Notification webhook (outgoing)**: Coolify can POST a deployment-event
|
||||
payload to a configured URL on success and failure. This is how we get a
|
||||
notification back into the app for audit logging + admin alerts.
|
||||
@@ -86,8 +86,8 @@ proves unreliable):** Watchtower with a per-image label
|
||||
step, add a final `Trigger Coolify deploy` step that runs only on the
|
||||
publish path (push to `main` or to a `v*` tag — never on PRs). It uses
|
||||
`secrets.COOLIFY_WEBHOOK` (the full Bearer-protected URL with `?uuid=…`)
|
||||
+ `secrets.COOLIFY_TOKEN`. Failure of the deploy trigger does **not**
|
||||
fail the build (the image is already published; deploy is a follow-up).
|
||||
- `secrets.COOLIFY_TOKEN`. Failure of the deploy trigger does **not**
|
||||
fail the build (the image is already published; deploy is a follow-up).
|
||||
- Document required secrets in `.env.example`.
|
||||
|
||||
2. **`feat(deploy): admin notification + audit log on deploy success/failure`**
|
||||
|
||||
@@ -24,12 +24,12 @@ more than coverage breadth.
|
||||
|
||||
## Per-workflow pass-rate
|
||||
|
||||
| Workflow | Total | Success | Failure | Cancelled | Completed pass-rate |
|
||||
| ------------------------------ | ----: | ------: | ------: | --------: | ------------------: |
|
||||
| Integration tests | 47 | 47 | 0 | 0 | 100 % |
|
||||
| Security & Quality | 46 | 43 | 3 | 0 | 93 % |
|
||||
| Build & Publish Docker Image | 60 | 30 | 3 | 26 | 91 % |
|
||||
| e2e | 47 | 0 | 34 | 13 | 0 % |
|
||||
| Workflow | Total | Success | Failure | Cancelled | Completed pass-rate |
|
||||
| ---------------------------- | ----: | ------: | ------: | --------: | ------------------: |
|
||||
| Integration tests | 47 | 47 | 0 | 0 | 100 % |
|
||||
| Security & Quality | 46 | 43 | 3 | 0 | 93 % |
|
||||
| Build & Publish Docker Image | 60 | 30 | 3 | 26 | 91 % |
|
||||
| e2e | 47 | 0 | 34 | 13 | 0 % |
|
||||
|
||||
`Completed pass-rate = success / (success + failure)` — strips out
|
||||
the "cancelled" entries that come from the `concurrency.cancel-in-progress`
|
||||
@@ -89,6 +89,7 @@ in 17 minutes. Both runs wrote to the same gha cache scope
|
||||
(`buildkit` default) with `mode=max`, deadlocking the exporter.
|
||||
|
||||
**Fix shipped (`249c42b`)**:
|
||||
|
||||
- `cache-to: type=gha,mode=max,scope=build-${{ github.ref_name }}`
|
||||
- `cache-from:` reads BOTH `build-${ref}` AND `build-main` so
|
||||
tag/PR runs warm-start off main without contending on writes.
|
||||
@@ -117,12 +118,12 @@ fragility. No action needed beyond what's already in the workflow.
|
||||
|
||||
- [x] **e2e dark colorScheme**: `41945b2`. Unblocks the 0 % gate.
|
||||
- [x] **docker-publish per-ref cache + timeout**: `249c42b`. Bounds
|
||||
deadlocks at 30 min and prevents the v1.4.14 hang from recurring.
|
||||
deadlocks at 30 min and prevents the v1.4.14 hang from recurring.
|
||||
- [x] **post-publish-verify workflow**: `ffa4aac`. Pulls the just-
|
||||
published image, boots it against an ephemeral Postgres, probes
|
||||
`/api/version` + `/api/health`. Informational (no `continue-on-error`
|
||||
blocking), surfaces "manifest pushed but image won't boot" failures
|
||||
the build step alone can't see.
|
||||
published image, boots it against an ephemeral Postgres, probes
|
||||
`/api/version` + `/api/health`. Informational (no `continue-on-error`
|
||||
blocking), surfaces "manifest pushed but image won't boot" failures
|
||||
the build step alone can't see.
|
||||
|
||||
### Defer to v1.4.16 (out of C3 scope, owned elsewhere)
|
||||
|
||||
|
||||
@@ -12,21 +12,21 @@ not a single grey sentence floating in a card.
|
||||
|
||||
## Findings
|
||||
|
||||
| Route / surface | Component | State before | What was wrong | What changed |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `/admin/users` | `UserManagementSection` | Loading-only — when `users` resolved to `[]` the table simply rendered an empty `<tbody>` with the header still visible. | Empty case never specified; on a fresh deployment with one admin self-row it never reproduced for Marc, but the `admin/user` filter did regularly. | New `<EmptyState>` mounts inside the card when `filteredUsers.length === 0`, with `Users` icon, filter-aware copy, and a "Reset filter" CTA when a filter is active. |
|
||||
| `/admin/backups` | `BackupsSection` | Plain `<p>No backups recorded yet.</p>`. | No icon, no CTA, no signal that the "Run backup now" button at the top is what to click. | Replaced with `<EmptyState>` (`Database` icon) that includes a primary "Backup now" button — duplicates the header CTA but inside the empty card so the new admin doesn't need to look up. |
|
||||
| `/admin/login-overview` | `LoginOverviewSection` | Plain `<p>No entries found.</p>` (filter-dependent). | When "Failed only" is selected on a healthy installation the user sees nothing — looked like a bug. | `<EmptyState>` with `ScrollText` icon, filter-aware description, and a "Show all events" CTA when the failed-only filter is active. |
|
||||
| `/admin/api-tokens` | `ApiTokenOverviewSection` | Plain `<p>No API tokens found.</p>`. | Doesn't tell admins that tokens are issued at login from native clients — they wonder if they need to create them somewhere. | `<EmptyState>` with `Key` icon and a description pointing to native sign-in / Settings → Account API tokens. |
|
||||
| `/admin/feedback` | `FeedbackInboxSection` | Plain `<p>No feedback in this category.</p>` per tab. | OK as text but the tab strip already shows `0` badges; the inner panel deserves the same compactness as other admin sections. | `<EmptyState size="compact">` with `Inbox` icon. Per-tab description. |
|
||||
| `/admin` overview | `RecentAuditPreview` | Plain `<p>No recent audit entries.</p>`. | Compact but icon-less — visually inconsistent with the audit-table fixes. | `<EmptyState size="compact" variant="plain">` with `ScrollText` icon. |
|
||||
| `/measurements` | `MeasurementList` | `<div class="border-dashed">{t("measurements.noMeasurements")}</div>`. | No CTA and the top-level "Add measurement" button is the only way out. | `<EmptyState>` with `Activity` icon, filter-aware description, "Add your first measurement" CTA. |
|
||||
| `/mood` | `MoodList` | `<div class="border-dashed">{t("mood.noEntries")}</div>`. | No CTA. | `<EmptyState>` with `Smile` icon, filter-aware description, "Log your first mood" CTA. |
|
||||
| `/medications` | `MedicationsPage` | Custom inline icon + button block. | Already friendly but didn't share the new primitive — diverging styles. | Refactored to `<EmptyState>` with `Pill` icon, "Add first medication" CTA. Visual parity with the rest. |
|
||||
| `/insights` (top-level) | `InsightsPage` | Plain `<div class="text-muted-foreground py-20 text-center">{t("common.noData")}</div>` when `data` is undefined. | Looked like a hung page. | `<EmptyState>` with `TrendingUp` icon, "Log a measurement" CTA → `/measurements`. |
|
||||
| `/insights` (BMI no-height) | Inline `<p>` | "no data" when `user.heightCm == null`. | The user might wonder why the BMI section is empty. | `<EmptyState size="compact" variant="plain">` with `Ruler` icon, copy points to `/settings/account` and a "Set height" CTA. |
|
||||
| `/achievements` | `AchievementsPage` (no-grouped state) | Plain `<p>No achievements unlocked yet.</p>` inside a card. | Icon-less. The card after summary tiles needs the same visual cue we use elsewhere. | `<EmptyState>` with `Trophy` icon — keeps the same `noneUnlockedYet` string to avoid C4 i18n churn. |
|
||||
| Dashboard tile-strip / charts | `DashboardPage` | Tile strip + chart row both hidden when every metric has zero data; only `<GettingStartedChecklist>` plus the welcome banner show. The strip itself stays a 0-px row. | Brand-new users see a near-blank page below the welcome banner — feels broken even though the checklist is technically present. | New `<EmptyState>` block rendered when `trendCards.length === 0 && charts.length === 0`. Routed to "Add your first measurement" via the existing quick-entry dialog (no nav). Does not race the checklist (which has its own self-gating). |
|
||||
| Route / surface | Component | State before | What was wrong | What changed |
|
||||
| ----------------------------- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `/admin/users` | `UserManagementSection` | Loading-only — when `users` resolved to `[]` the table simply rendered an empty `<tbody>` with the header still visible. | Empty case never specified; on a fresh deployment with one admin self-row it never reproduced for Marc, but the `admin/user` filter did regularly. | New `<EmptyState>` mounts inside the card when `filteredUsers.length === 0`, with `Users` icon, filter-aware copy, and a "Reset filter" CTA when a filter is active. |
|
||||
| `/admin/backups` | `BackupsSection` | Plain `<p>No backups recorded yet.</p>`. | No icon, no CTA, no signal that the "Run backup now" button at the top is what to click. | Replaced with `<EmptyState>` (`Database` icon) that includes a primary "Backup now" button — duplicates the header CTA but inside the empty card so the new admin doesn't need to look up. |
|
||||
| `/admin/login-overview` | `LoginOverviewSection` | Plain `<p>No entries found.</p>` (filter-dependent). | When "Failed only" is selected on a healthy installation the user sees nothing — looked like a bug. | `<EmptyState>` with `ScrollText` icon, filter-aware description, and a "Show all events" CTA when the failed-only filter is active. |
|
||||
| `/admin/api-tokens` | `ApiTokenOverviewSection` | Plain `<p>No API tokens found.</p>`. | Doesn't tell admins that tokens are issued at login from native clients — they wonder if they need to create them somewhere. | `<EmptyState>` with `Key` icon and a description pointing to native sign-in / Settings → Account API tokens. |
|
||||
| `/admin/feedback` | `FeedbackInboxSection` | Plain `<p>No feedback in this category.</p>` per tab. | OK as text but the tab strip already shows `0` badges; the inner panel deserves the same compactness as other admin sections. | `<EmptyState size="compact">` with `Inbox` icon. Per-tab description. |
|
||||
| `/admin` overview | `RecentAuditPreview` | Plain `<p>No recent audit entries.</p>`. | Compact but icon-less — visually inconsistent with the audit-table fixes. | `<EmptyState size="compact" variant="plain">` with `ScrollText` icon. |
|
||||
| `/measurements` | `MeasurementList` | `<div class="border-dashed">{t("measurements.noMeasurements")}</div>`. | No CTA and the top-level "Add measurement" button is the only way out. | `<EmptyState>` with `Activity` icon, filter-aware description, "Add your first measurement" CTA. |
|
||||
| `/mood` | `MoodList` | `<div class="border-dashed">{t("mood.noEntries")}</div>`. | No CTA. | `<EmptyState>` with `Smile` icon, filter-aware description, "Log your first mood" CTA. |
|
||||
| `/medications` | `MedicationsPage` | Custom inline icon + button block. | Already friendly but didn't share the new primitive — diverging styles. | Refactored to `<EmptyState>` with `Pill` icon, "Add first medication" CTA. Visual parity with the rest. |
|
||||
| `/insights` (top-level) | `InsightsPage` | Plain `<div class="text-muted-foreground py-20 text-center">{t("common.noData")}</div>` when `data` is undefined. | Looked like a hung page. | `<EmptyState>` with `TrendingUp` icon, "Log a measurement" CTA → `/measurements`. |
|
||||
| `/insights` (BMI no-height) | Inline `<p>` | "no data" when `user.heightCm == null`. | The user might wonder why the BMI section is empty. | `<EmptyState size="compact" variant="plain">` with `Ruler` icon, copy points to `/settings/account` and a "Set height" CTA. |
|
||||
| `/achievements` | `AchievementsPage` (no-grouped state) | Plain `<p>No achievements unlocked yet.</p>` inside a card. | Icon-less. The card after summary tiles needs the same visual cue we use elsewhere. | `<EmptyState>` with `Trophy` icon — keeps the same `noneUnlockedYet` string to avoid C4 i18n churn. |
|
||||
| Dashboard tile-strip / charts | `DashboardPage` | Tile strip + chart row both hidden when every metric has zero data; only `<GettingStartedChecklist>` plus the welcome banner show. The strip itself stays a 0-px row. | Brand-new users see a near-blank page below the welcome banner — feels broken even though the checklist is technically present. | New `<EmptyState>` block rendered when `trendCards.length === 0 && charts.length === 0`. Routed to "Add your first measurement" via the existing quick-entry dialog (no nav). Does not race the checklist (which has its own self-gating). |
|
||||
|
||||
## Out of scope (deferred)
|
||||
|
||||
@@ -43,7 +43,7 @@ not a single grey sentence floating in a card.
|
||||
with height but no weight is intentionally minimal — the chart
|
||||
itself shows the empty axis.
|
||||
- Tab strip `0` badges on `/admin/feedback` use the same `<EmptyState
|
||||
size="compact">` per panel; not duplicating in this audit.
|
||||
size="compact">` per panel; not duplicating in this audit.
|
||||
|
||||
## Tests added
|
||||
|
||||
|
||||
@@ -59,12 +59,12 @@ These appeared in the grep but are deliberately excluded:
|
||||
|
||||
5 entries where both locales return the key's last segment unchanged:
|
||||
|
||||
| Path | Value | Reason |
|
||||
| ------------------------------- | --------- | --------------------------------------------------- |
|
||||
| `settings.ntfy` | `ntfy` | brand name, not translated |
|
||||
| `classifications.bp.Optimal` | `Optimal` | identical word in EN and DE |
|
||||
| `classifications.bp.Normal` | `Normal` | identical |
|
||||
| `classifications.pulse.Normal` | `Normal` | identical |
|
||||
| Path | Value | Reason |
|
||||
| --------------------------------- | --------- | --------------------------------------------------- |
|
||||
| `settings.ntfy` | `ntfy` | brand name, not translated |
|
||||
| `classifications.bp.Optimal` | `Optimal` | identical word in EN and DE |
|
||||
| `classifications.bp.Normal` | `Normal` | identical |
|
||||
| `classifications.pulse.Normal` | `Normal` | identical |
|
||||
| `classifications.bodyFat.Fitness` | `Fitness` | identical (anglicism in DE medical/fitness context) |
|
||||
|
||||
A further 25 entries are EN==key but DE has an actual translation
|
||||
@@ -73,16 +73,16 @@ fine.
|
||||
|
||||
### v1.4.15 new namespaces — coverage check
|
||||
|
||||
| Namespace | Origin | EN keys | DE keys | Drift | Empty |
|
||||
| ------------------------------- | -------------- | ------: | ------: | ----: | ----: |
|
||||
| `admin.section.backups.*` | B1 backups | 35 | 35 | 0 | 0 |
|
||||
| `achievements.*` | B4 | 106 | 106 | 0 | 0 |
|
||||
| `onboarding.tour.*` | B5 | 18 | 18 | 0 | 0 |
|
||||
| `admin.overview.*` | A2 | 16 | 16 | 0 | 0 |
|
||||
| `settings.notificationStatus.*` | B3 | 13 | 13 | 0 | 0 |
|
||||
| `settings.integrationStatus.*` | B2 | 6 | 6 | 0 | 0 |
|
||||
| `doctorReport.*` | B6 | 90 | 90 | 0 | 0 |
|
||||
| `quickAdd.*` (`common.add` etc) | A3 | (`common`) | — | 0 | 0 |
|
||||
| Namespace | Origin | EN keys | DE keys | Drift | Empty |
|
||||
| ------------------------------- | ---------- | ---------: | ------: | ----: | ----: |
|
||||
| `admin.section.backups.*` | B1 backups | 35 | 35 | 0 | 0 |
|
||||
| `achievements.*` | B4 | 106 | 106 | 0 | 0 |
|
||||
| `onboarding.tour.*` | B5 | 18 | 18 | 0 | 0 |
|
||||
| `admin.overview.*` | A2 | 16 | 16 | 0 | 0 |
|
||||
| `settings.notificationStatus.*` | B3 | 13 | 13 | 0 | 0 |
|
||||
| `settings.integrationStatus.*` | B2 | 6 | 6 | 0 | 0 |
|
||||
| `doctorReport.*` | B6 | 90 | 90 | 0 | 0 |
|
||||
| `quickAdd.*` (`common.add` etc) | A3 | (`common`) | — | 0 | 0 |
|
||||
|
||||
### Naming consistency
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ NOT done in v1.4.15 (deliberately deferred):
|
||||
- The `/api/insights/generate` route does NOT yet adopt
|
||||
`generateInsight()` — the existing UI consumes the rich legacy
|
||||
shape (`{summary, classification, findings, correlations,
|
||||
dataQuality, ...}`). Migrating the consumer is v1.4.16 work because
|
||||
dataQuality, ...}`). Migrating the consumer is v1.4.16 work because
|
||||
it requires UI changes to `<InsightAdvisorCard />` and
|
||||
`<InsightsCard />` to render the new strict shape.
|
||||
- The scope-hardened prompt at `src/lib/ai/prompts/insight-generator.ts`
|
||||
@@ -61,9 +61,9 @@ guideline document, with a rendered footnote in the UI.
|
||||
plain language for the German UI.
|
||||
- DGE healthy-weight (BMI bands).
|
||||
- AASM 2015 sleep duration consensus.
|
||||
Each excerpt: `{ id, source, citation, excerpt, locale, validity }`.
|
||||
Curated by hand (NOT scraped) — no quote longer than 200 characters
|
||||
to stay inside fair-use.
|
||||
Each excerpt: `{ id, source, citation, excerpt, locale, validity }`.
|
||||
Curated by hand (NOT scraped) — no quote longer than 200 characters
|
||||
to stay inside fair-use.
|
||||
|
||||
2. **Inject relevant excerpts as system context per request.** A
|
||||
pre-flight step `selectRelevantReferences(features)` looks at the
|
||||
@@ -76,7 +76,7 @@ guideline document, with a rendered footnote in the UI.
|
||||
`citations[]` entry gains an optional `referenceId` field pointing
|
||||
into the bundle — the model uses an excerpt's `id` to back its
|
||||
citation. The UI then renders a footnote `[1] ESH/ESC 2024,
|
||||
p. 17 — "..."` next to the recommendation.
|
||||
p. 17 — "..."` next to the recommendation.
|
||||
|
||||
4. **Hallucination guard for references.** Schema rejects any
|
||||
`referenceId` not in the bundle. The model cannot fabricate a
|
||||
@@ -112,7 +112,7 @@ Goal: a single provider's outage no longer breaks insights generation.
|
||||
that). Per-request total wall-clock budget: 60 s.
|
||||
|
||||
4. **Telemetry.** Wide-Event `ai.cascade.attempts: ["codex","admin-key"]`
|
||||
+ `ai.cascade.outcome: "success" | "all-failed"`.
|
||||
- `ai.cascade.outcome: "success" | "all-failed"`.
|
||||
|
||||
## v1.4.16 — Per-recommendation explainability
|
||||
|
||||
|
||||
+12
-12
@@ -646,9 +646,9 @@ To eliminate that risk class, HealthLog's `CodexClient` walks an ordered chain o
|
||||
```ts
|
||||
const DEFAULT_SLUG_FALLBACK_CHAIN = [
|
||||
"gpt-5.3-codex", // current allow-list entry (verified 2026-05-09)
|
||||
"gpt-5-codex", // historical default (rejected as of 2026-05 but safe to retry — backend may flip)
|
||||
"gpt-5", // bare slug — rejected on ChatGPT-auth as of 2026-05 but lives on api-key auth
|
||||
"gpt-4o", // ladder-rung for last-ditch capability
|
||||
"gpt-5-codex", // historical default (rejected as of 2026-05 but safe to retry — backend may flip)
|
||||
"gpt-5", // bare slug — rejected on ChatGPT-auth as of 2026-05 but lives on api-key auth
|
||||
"gpt-4o", // ladder-rung for last-ditch capability
|
||||
];
|
||||
```
|
||||
|
||||
@@ -656,15 +656,15 @@ The chain is overridable via the env var `CODEX_MODEL_FALLBACK_CHAIN` (comma-sep
|
||||
|
||||
**Trigger condition** (when to walk to the next slug):
|
||||
|
||||
| Status | Action |
|
||||
| ------------------------------------------------------------------------------------- | ----------- |
|
||||
| `400` body matches `/not supported when using Codex with a ChatGPT account/i` | walk |
|
||||
| `400` body contains `model_not_found` | walk |
|
||||
| `400` body contains `does not exist` AND mentions a model name | walk |
|
||||
| `404` (any body) | walk |
|
||||
| `401` first time | refresh and re-try same slug; do NOT walk |
|
||||
| `401` after refresh, `403`, `429`, `5xx` | propagate (don't walk) |
|
||||
| `200` with SSE `response.failed.error.code === "invalid_prompt"` | propagate (request shape is wrong, slug is fine) |
|
||||
| Status | Action |
|
||||
| ----------------------------------------------------------------------------- | ------------------------------------------------ |
|
||||
| `400` body matches `/not supported when using Codex with a ChatGPT account/i` | walk |
|
||||
| `400` body contains `model_not_found` | walk |
|
||||
| `400` body contains `does not exist` AND mentions a model name | walk |
|
||||
| `404` (any body) | walk |
|
||||
| `401` first time | refresh and re-try same slug; do NOT walk |
|
||||
| `401` after refresh, `403`, `429`, `5xx` | propagate (don't walk) |
|
||||
| `200` with SSE `response.failed.error.code === "invalid_prompt"` | propagate (request shape is wrong, slug is fine) |
|
||||
|
||||
**Positive cache** — when a slug is observed working (HTTP 200 with at least one `response.completed` event), record `(slug, timestamp)` in a process-local Map. On the next request, jump straight to the cached slug. TTL: 1 hour. Cache scope: per-process (no DB write).
|
||||
|
||||
|
||||
Reference in new issue
Block a user