Mostly table column normalisation under docs/audit/ + planning, object- literal expansion in src/lib/ai/coach/snapshot.ts after the W4 F6 Apple Health metric block landed, and small wrap/unwrap line adjustments in the device + APNs routes / tests touched across W3/W4/W5 sessions. No behavioural change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
18 KiB
v1.4.21 backlog
Carry-over from v1.4.20 marathon foundation phases. Pick from this list when v1.4.21 starts.
Closed in v1.4.22
The Wave-4 cleanup pass closed the following items. See
.planning/phase-W4-report.md for full SHAs + reasoning.
- C1 Zielwerte sparkline + Δ-vs-last-month — landed in commit 1
- C2 api-tokens 5th-attempt scrollbar fix — landed in commit 2
- C3 Coolify
?force=true+ howto for the UI checkbox — landed in commit 3 - C4 AuthShell post-hydration redirect → moved to proxy.ts — landed in commit 4
- C5 node-26-alpine — PR #162 closed (no Dockerfile bump; Next.js 16 still pins node 22 LTS and node:26-alpine ships without corepack)
- F-32 triple help blocks on /settings/dashboard — landed in commit 5
- F-33 mood subtitle ampersand → "und" / "and" — landed in commit 5
- F-34 measurements subtitle ampersand → "und" / "and" — landed in commit 5
- F-35 Send-test button label inconsistency — unified on the shorter "Send test" / "Test senden" — landed in commit 5
- F-44
LOKI_ENDPOINTenv-var leakage in user-facing copy — landed in commit 5 - F-45 admin/users column header "Users" → "Name" (new
admin.userNamekey) — landed in commit 5 - F-47 /notifications event-column header collision with the page
title — new
notifications.eventColumnkey — landed in commit 5 - F-50 DE error-toast tone — "Bitte erneut versuchen" → "Bitte versuche es erneut" across six keys — landed in commit 5
- F-58 admin/reminders icon swap (Clock → Bell to match the admin-shell nav) — landed in commit 5
- F-62 DE dashboard
greetingSalutation("Hi" → "Hallo, {name}") — landed in commit 5 - D-CR-M-03 formatTokenName regex broadened to non-Z ISO offsets — landed in commit 5
- D-DSGN-M-04 moodLog "Copy" button uses
common.copyinstead of stripping!offcommon.copied— landed in commit 5 - FX 1 191
Marcsource-comment references swept (test fixtures kept) — landed in commit 6 - FX 2 DE+EN bilingual CHANGELOG entries (v1.4.14 + v1.4.15) normalised to English-only — landed in commit 6
- FX 3
CLAUDE.mdrenamed toCONTRIBUTING-AI.md;AGENTS.mdretained for multi-agent compatibility — landed in commit 6
The remaining MED + LOW items below either deferred to v1.4.23 (most of the heavy reviewer findings) or stay open as a hygiene PR.
Phase D — v1.4.20 reconcile carry-over
Items raised by the six-reviewer Phase-D pass that were either too
large for an inline reconcile or otherwise judgment-call-deferred.
HIGH and CRITICAL items all landed inline (fix counts in
.planning/phase-D-v1420-reconcile-report.md).
Code review (MED + LOW carry-over)
- Streaming auto-scroll deps brittleness (Code-MED-01). MessageThread
effect lists
[messages.length, streaming?.content]; a "" → "" flip on a fresh stream won't fire. Addstreaming?.inProgressor a turn counter to the dep list. bandFromInterval"high" label on near-zero r (Code-MED-02). A tight CI of[-0.05, 0.10]reads as "high confidence" alongside near-zero r. Demote to moderate when CI straddles zero, or rename the chip to "tight CI" / "wide CI".- Pearson p-value normal approximation (Code-MED-03). At df=12 the normal approx can put true p≈0.04 at p≈0.025. Either pull in a tiny incomplete-beta implementation (~30 LOC) or raise the surfacing gate to df ≥ 20.
weightTrendAlignment±2 kg target band (Code-MED-04). ReusebuildWeightRangeFromHeight()so the score awards 100 across the user's actual band, not an arbitrary ±2 kg.bpInTargetPctheld constant for "vs last week" delta (Code-MED-05). Recompute the prior-snapshot BP rate or relabel the delta line so the user knows BP is held constant in the comparison.- WeeklyReportView print spinner gate (Code-MED-06). The 300 ms print timer resets on every advisor.isLoading flip. Pin the timer via a ref so a second-render isLoading change doesn't restart it.
<TrendsRow>confidence prop dead (Code-MED-07). Either wireanalytics.correlationsconfidence into the page invocation or drop the prop until B3.x lands the wiring.provenanceFromJsonunsafewindowsfilter cast (Code-MED-08). Validate against theWINDOW_KEYSallow-list shared withsource-chips.tsxso a poisoned legacy row can't surface ast(undefined).- Storyboard category fallback paints purple (Code-MED-09). Run
advisor.payload.insights.storyboardAnnotationsthroughstoryboardAnnotationsSchema.safeParse— same shape asdailyBriefingandtrendAnnotationsalready use. <HeroStrip>greeting-key + relativetime per-renderDate()(Code-LOW-01). Memoise viauseMemoso a chatty parent doesn't re-allocate on every TanStack tick.- Nested
<Sheet>portals on mobile rail trays (Code-LOW-03). Promote the trays to siblings of the outer drawer to avoid the dom-focus-trap fight on iOS Safari. - bpInTargetRate-constant test gap (Code-LOW-05). Add a test that holds BP constant across both snapshots and verifies the delta reflects only the moving pillars.
streamRefusalbypasses budget meter (Code-LOW-06). BumpmessageCount(withtokens: 0) for refusals so the surface meter is honest, or rate-limit the refusal path separately.
Security review (MED + LOW carry-over)
- Refusal path persists messages without budget accounting
(Sec-M-2). Either call
checkRateLimit()at the top of the POST handler before refusal scanning, or bumpmessageCountinsidestreamRefusal(). Same broader gap covered by Senior-MED-3. - Refusal patterns miss common injection synonyms (Sec-M-3).
Extend
INJECTION_PATTERNSwith(forget|abandon|drop|skip|toss| bypass|circumvent)paired with(instructions?|rules?|prompt| guidelines?|directives?|system\s+message), plus a "what (were|are)\s+your.*(instructions|prompt|rules)" probe. Add a zero-width-stripping normalisation pass before regex. assistantMessagepersisted beforerecordSpend()(Sec-M-4). Wrap theappendMessage(assistant)+recordSpend()pair in a Prisma$transactionso the spend ledger stays atomically consistent with the persisted reply.coachChatRequestSchema.conversationIdaccepts any string up to 64 chars (Sec-L-1). Tighten to.regex(/^c[a-z0-9]{24}$/)or a generic[A-Za-z0-9_-]{8,64}regex.- Refusal-injection rail pollution (Sec-L-3). Either skip the
updatedAtbump for refusal appends or hide refusal-only conversations in the rail UI. - Smoke-test
request.clone()against Next.js 16 (Sec-U-1). Add a Vitest case posting a new-conversation body and asserting the persisted user message matches the input. (Largely moot now that the chat route no longer wraps in withIdempotency, but still useful paranoia.) - Race window on
enforceBudgetbetween read + write (Sec-U-2). Acceptable in practice (cap is per-day, not per-second). Annotate the race window in the route header so the next maintainer knows.
Design review (MED + LOW carry-over)
- Hero strip greeting clock is wall-clock, not Berlin (Design-M1).
Pass the current Berlin hour through
Intl.DateTimeFormat("en-CA", { timeZone: "Europe/Berlin", hour: "numeric" })so a traveller sees the Berlin bucket the rest of the UI uses. - Health Score panel mobile order inversion (Design-M3). On
<lgpromote the Health Score panel between the meta row and the weekly banner, or render a compact "score chip" inline with the meta row. - Coach drawer focus trap when both rail trays open (Design-M5).
Set the rail-tray Portal container to the parent SheetContent's ref,
or use a
<Drawer>-style stacked-modals primitive (vaulis already in deps). - Tone-bar visual clip at rounded corner (Design-M6). Inset by
left-1/top-2 bottom-2, or addoverflow-hiddento the parent card, so the bar's straight left edge doesn't poke into the card's rounded corner. - Source chip "n=N" reads as engineering output (Design-M8). Use
{count} samples/{count} Werte, or drop the count on mobile and surface it in a tooltip. - Hero "personal-baseline" copy (Design-L1). Either drop the generic line or surface the actual sample size ("From 187 readings over 90 days").
- Health Score sub-bars paint at composite band (Design-L2). Each sub-bar should derive its own band per component for visual fidelity.
- Storyboard 24-char truncation has no ellipsis (Design-L5). Add
…suffix whenannotation.truncatedLabelis shorter than the source label. - Coach composer keyboard-hint locale drift (Design-L6).
Reconsider the German
Umschalt+Entertranslation — the rest of the app usesShift+Enterliteral even in German.
Senior-dev review (HIGH + MED + LOW carry-over)
- Duplicated maths layer (Pearson + linear regression) (Sr-HIGH-2).
Consolidate into
src/lib/analytics/correlation.ts(singular) +src/lib/analytics/regression.ts. Add p-value + CI as optional outputs on the existingpearsonCorrelation(). MigratecorrelateBpCompliance/correlateMoodPulse/correlateWeightWeekdayto import from analytics. The hypothesis-specific runners stay inlib/insights/. <CoachDrawer key={prefill}>weaponises React keys for state reset (Sr-HIGH-4). Makeprefilla fully-controlled prop on<CoachDrawer>. Drop thekey=re-mount. The drawer reads prefill into the textarea'svalue=and notifies up viaonPrefillConsumedafter submit.- Coach tables not in
cascade-delete.test.ts(Sr-MED-1). Add threeexpect(...count(...)).toBe(0)lines forcoachConversation,coachMessage,coachUsage. Five-line patch. weeklyReport+storyboardAnnotationslifted withoutsafeParse(Sr-MED-2). Lift throughweeklyReportSchema.safeParseandstoryboardAnnotationsSchema.safeParseinuseInsightsAdvisorQuery, the same waydailyBriefingandtrendAnnotationsalready do.- No rate-limit on
/api/insights/chat(Sr-MED-3). WraphandleChatRequestinenforceRateLimit({ key: \coach.chat:${userId}`, max: 30, windowMs: 60_000 })`. - Fake streaming via
tokeniseForStreaming(Sr-MED-4). Wire the provider runner to a true streaming variant with anonTokencallback. Unlocks "stop generating" as a real feature later. medication_schedules.days_of_weekschema drift (Sr-MED-5). Either land the column with a backfill default or drop the field fromschema.prisma. v1.4.20 worked around it twice.apiHandler-vs-withIdempotencystacking (Sr-MED-6). Largely moot now that the chat route doesn't wrap in idempotency, but still worth offeringwithIdempotency(handler, { skipWhen?: (req) => boolean })for the next caller that wants to opt out conditionally.coach_usageredundant index (Sr-LOW-1). Drop the@@indexline — the@@uniquealready builds an index.metricSourceJsonstored asString(Sr-LOW-2). Migrate toJsonb. Cheap to fix while no consumers exist.- Coach off-topic detector locale-mixed (Sr-LOW-3). Tighten the
allow + deny lists with a
defaultAllow=falsebias. v1.5 work. weeklyReport.weekISOregex doesn't validate week 53/00 (Sr-LOW-4). Tighten to^\d{4}-W(0[1-9]|[1-4]\d|5[0-3])$. The route page is already strict.
Simplify review (apply-maybe carry-over)
<CoachDrawer>slot props (historyRail/sourcesRail/composer) are dead (S-06). The only production caller never passes these slots;<CoachDrawerBody>already exposes equivalent test seams. Remove the props.<CoachInput>inputIdprop never overridden (S-07). Drop the prop and inline the default id.cloneForCheckvariable dead in chat POST handler (S-08). Largely moot now (the route no longer peeks at the body before dispatching), but worth a quick re-read.health-score-card.tsxdelta-arrow chooser (S-09). Compress three sibling&&blocks into adeltaIconlookup. Borderline — judgement call for the file's owner.
F5 — Best-practice repo audit (deferred MED + LOW)
- README badges (LOW). Add a small badge row: build status (GitHub Actions), license (AGPL-3.0), latest GHCR tag, container size, package-version. Today the README is content-rich but visually unanchored. Pure cosmetic.
- FUNDING.yml (optional). Skipped because no GitHub Sponsors / Ko-fi / OpenCollective profile is configured. Add when the maintainer enables one.
- Repo-root tidy (LOW). The root currently carries
coverage/,playwright-report/,test-results/directories which are listed in.gitignorebut render as empty entries duringls -la. No action needed unless a future audit re-flags. .gitignoreaudit (LOW). Inherits Next.js + standard Node patterns. Spot-check did not surface gaps.- Discussions enablement (LOW). The new
.github/ISSUE_TEMPLATE/config.ymllinks tohttps://github.com/MBombeck/HealthLog/discussions; verify that Discussions is actually enabled on the repository before v1.4.21 ships, or remove the link.
FX (cleanup) carry-over
- Source-comment sweep (LOW).
src/has 191Marcreferences in maintainer-internal source comments. Visible to anyone who clones the repo. Lower priority than user-rendered surfaces; schedule for a hygiene PR. - DE+EN bilingual CHANGELOG entries (MED). v1.4.15-era entries pair a German sentence with its English translation by historical design. English-only normalisation is a larger rewrite — defer.
CLAUDE.md+AGENTS.mdfilenames (MED). Visible at repo root. These document AI-agent conventions; renaming is a structural change with downstream link impact. Defer until a hygiene PR.
v1.4.19 backlog passthrough (from v1420-backlog.md)
The v1.4.19 quality-of-life audit deferred 31 MED + 16 LOW + 4 HIGH +
4 reconcile-HIGH items into .planning/v1420-backlog.md. v1.4.20 picks
selectively (the redesign owns its own scope); the rest moves here on
v1.4.21 kickoff. See that file for the full list.
Docs site audit (v1.4.19 cross-reference, deferred MED + LOW)
Audit walked all 38 pages under healthlog-docs/src/content/docs/**
against the live app (v1.4.19, image digest sha256:b48f93874cdb…),
the CHANGELOG, .env.example, Dockerfile, docker-compose.yml, and
.github/workflows/docker-publish.yml. CRITICAL + HIGH already landed
on healthlog-docs/main (commits 19eb8de, 3d3ea21). Items below
are MED + LOW and were deliberately left for a hygiene pass.
MED — stale or imprecise but not load-bearing
- Stale version pins in installation / docker / admin examples.
getting-started/installation.mdxandself-hosting/docker.mdxshow:1.2.0as the pin example;api/admin.mdxreturns"version": "1.2.1"in its sample response. Bump all three to a current1.4.xreference on the next docs sweep. architecture/database.mdxmodel count + measurement-type list. Page claims 22 models; current schema has 26. Measurement table also stops at the v1.2 catalogue (WEIGHT / BP_SYS / BP_DIA / PULSE / BODY_FAT / SLEEP_DURATION / ACTIVITY_STEPS) — needs the v1.3 additions (BLOOD_GLUCOSE, OXYGEN_SATURATION, TOTAL_BODY_WATER, BONE_MASS) so the API page and the architecture page agree.architecture/background-jobs.mdxschedule timezone. ListsSunday 03:00 UTCand02:00–02:30 UTCfor the data-backup and insights queues. Actual schedule is Europe/Berlin (CLAUDE.md +admin/backups.mdxalready document it correctly). Same drift infeatures/export-import.mdx(Schedule: Sunday 03:00 UTCblock on line 96) — keep both in sync.architecture/background-jobs.mdxjob catalogue is incomplete. Missinghost-metric-sampler(v1.4.16, host CPU/mem/IO every minute) andfeedback-aggregator(v1.4.16, daily 04:00 Europe/Berlin AI feedback rollup). Add rows for both.- API token hashing wording.
architecture/overview.mdxandsecurity/overview.mdxdescribe the token store as "SHA-256 + HMAC" / "stored as SHA-256 + HMAC hashes". The actual algorithm is keyed HMAC-SHA-256 (API_TOKEN_HMAC_KEYin.env.example,src/lib/auth/hmac.ts). The other API/integrations/external-ingest pages already use the correct phrasing — bring these two into line. api/insights.mdxprovider claim. Now generic ("user's configured provider chain") after the v1.4.19 docs fix; the per- provider request/response examples could still be tightened to show Anthropic / Codex shapes alongside the OpenAI one. Cosmetic.api/mood.mdx"primary UI language is German" note. Wording carried over from the v1.2 era. Already corrected infeatures/mood-tracking.mdx; mirror the same locale-neutral phrasing in the API page.- Architecture overview i18n line (
overview.mdxline 105). Lists "Supported languages: German and English" without flagging that English is the codebase default. Tighten to match CLAUDE.md. features/export-import.mdxSunday-03:00-UTC block. See above — same Berlin/UTC drift; the surrounding paragraph already hints at pg-boss without naming the timezone explicitly.
LOW — wording and polish
api/overview.mdxexample response includesemailfield. The/api/auth/meroute returnsgravatarUrland the user shape; the example could be refreshed to include the gravatar URL pattern that the/auth/mehandler now ships.getting-started/installation.mdx"Pin a specific version". After the version-bump pass (see MED above), tighten the surrounding paragraph to mention SLSA + SBOM verification commands once instead of twice.security/overview.mdxrate-limit table polish. Now shows10 req/h (configurable …)for AI insights — apply the same configurability hint to the data-ingest row and the bug-report row for consistency, if either is configurable.api/insights.mdxprovider example responses. Add a short Anthropic and Codex sample alongside the existing OpenAI shape so client implementers know what to expect. Pure DX polish.- Cross-link hygiene. A handful of pages still link to
/settings/adminor/settings/administrationstyle anchors that were rewritten — search the tree for any remaining instances after the next set of edits.
Total deferred from this audit: 9 MED + 5 LOW. CRITICAL + HIGH
fixed inline at healthlog-docs commits 19eb8de and 3d3ea21.