Output of `node scripts/strip-planning-tags.mjs --write`: 3,157 comment
lines in 1,308 files lose the work-item identifier at their head. Release
versions and issue references stay; nothing outside comment text
changes, which the script checks per file against a comment-free
printout. healthlog/no-planning-tags is enabled at error level with this
commit, so the tree it lands on is clean.
Introduce a shared SettingsSectionFrame (the settings twin of the admin
renderer's SectionFrame) that emits the canonical visible heading +
subheading, and route all 19 settings sections through it. Every settings
page now shows a real h1 + subtitle in the reference Übersicht style
instead of an invisible sr-only title; the section bodies drop their own
headings and off-style intro paragraphs (the explanatory copy folds into
the frame subtitle).
- Route the admin About page through SectionFrame so it carries the same
visible heading as every other admin section.
- Rename the AI settings section from KI-Auswertung to KI-Anbieter across
six locales: the page configures the provider and key, not an analysis.
- Add a heading-block spacer to the settings and admin sidebars so the
first menu item lines up with the top of the first card.
- Remove the always-on core-domains card from Module: a tracking area that
can't be turned off doesn't need listing.
- Move the proactive-coach control out of Benachrichtigungen into Coach.
- Move admin module availability out of the Coach section into its own
page: it gates every module, not just the coach.
- Give the Layout Insights editor a proper Insights heading.
Adds settings.sections.<slug>.subtitle and the new admin keys across all
six locales; removes the now-unused module core-domain strings.
Apply the v1.18.0 walkthrough notes across the settings surface:
- Drop the explanatory top-text block from every settings section so
content starts at one consistent vertical height (the sr-only heading
stays for the accessibility tree).
- Move "Restart onboarding tour" out of Account into Advanced.
- Module hub: drop the explanatory line above the first card.
- Split the Integrations page into three left-side entries — the
Connections content is now the Integrationen page itself, with
Channels and Sources (source weighting) as their own entries. Drop the
/settings/sources redirect (it is a real route again); Channels is a
new entry.
- Notifications: drop the top text, the channels / inbox cross-links,
and the embedded Vorsorge editor (it has its own page). Keep the mood,
low-stock, and proactive-coach reminder cards.
- Rename the Layout hub entry to "Layout".
- Flip the Coach toggle to "Activate Coach" (default on, opt-out) over
the same single disableCoach column — one source of truth.
- Move the AI "About me" context into Account → Profil, before
cycle tracking.
- Gesundheitsakte: replace the gradient hero with a normal card.
i18n across all six locales for the renamed / added labels; settings
tests updated for the new structure.
The four Layout personalization editors (dashboard, insights, medications,
mood) are reached through the Layout & Personalization hub or a page-header
cog, and the left rail highlights the hub while the body shows the child.
The only return path was tapping the highlighted rail entry — no explicit
up-affordance, sharper on mobile where the rail collapses to a chip strip.
Add a shared back-to-hub link at the top of each child so the hub → child
→ hub loop reads clearly.
Also fix the stale doc-comment on the settings section route: the slug set
is 16 (12 nav-visible plus the four routable-but-hidden Layout children),
not eight.
The dashboard snapshot fires the moment login succeeds instead of
waiting for the page chunk (start ~670 to ~200 ms throttled). Guided
follow-ups are personalised from the user's snapshot and an answer gets
the coach's streamed reaction before the next question. Archived
schedule eras can be corrected through a superseding revision that
keeps the original as an audit trail, era writes serialise per
medication, and duplicate dose times collapse in validation. A rolling
medication whose next dose is tomorrow can no longer read as heavily
overdue — the card status derives strictly from the served next-due —
and the adherence bars keep a fixed geometry in every state. The
server-wide assistant key gains its admin card, the one duplicated
coach preference collapses onto a single owner, twenty-seven admin and
settings card headers move onto the shared pattern with sixty
containers normalised, and the remaining hydration warnings are gone.
A night with coarse apple-health samples next to granular watch data
showed only awake and asleep — the per-session source pick now prefers
the source that knows its stages. Tour popovers clamp into the
viewport, wrap their footer and fall back to a bottom sheet on narrow
screens. Settings and admin sections share one alignment rule — icon
column left, everything else flush on the content column — with neutral
header icons, no duplicated section titles under the shell heading, and
every select on the account-style idiom with a visible chevron. The
dashboard's customize icon matches insights, the target-settings link
leaves the home charts, and the mood heatmap adopts the damped fills
the other mood charts already use.
The unit-system selector and the app-wide injection-site exclusions are
personal account preferences, not dashboard-layout controls. Move both
cards from the Dashboard settings section to the Account section, next to
the profile. The persistence path is unchanged — same endpoints, query
keys, mutation hooks, and labels; only the render location moves. The
Dashboard section keeps the layout/widget controls.
The injection_site column on intake events was read across several
surfaces but never written, so site tracking was effectively dead. Wire
it end-to-end as a per-medication opt-in, gated on an INJECTION delivery
form and default off.
- Schema: add medications.track_injection_sites + allowed_injection_sites
and users.global_excluded_injection_sites (migrations 0099, 0100;
additive, defaulted, no backfill).
- Intake write path: accept an optional injectionSite on the per-med,
status-toggle, and bulk intake routes. Persist it only on a taken
injection with tracking enabled, validated against the medication's
effective allowed set (per-med allowed sites minus the user-level
global exclusion, deny wins) — a disallowed site returns 422 (the bulk
route skips the entry).
- Rotation: extend the least-recently-used recommender with an allowed
filter; the picker disables sites outside the effective set.
- UI: per-medication tracking toggle + allowed-sites editor in the dose
wizard step, a skippable post-dose site prompt on both medication
cards, and a Settings card for the user-level global exclusion.
- Contract: injectionSite is additive on the intake request; OpenAPI
regenerated. New GET/PATCH /api/auth/me/injection-site-prefs.
The site capture is always optional and never blocks logging a dose.
Bucket A of the v1.4.3 polish pass.
Tile-strip width drifted past the chart-strip width by 16px on >=md
because the strip used `-mx-2 px-2` to bleed two pixels past the
parent for a snap-scroll feel. The bleed is real on mobile — the
horizontal-scroll affordance only reads as a scroll if the first/last
tiles touch the viewport edge — but on tablet/desktop where the row
fits without scrolling it just made the tile group visibly wider than
the charts below. Now `md:mx-0 md:px-0` drops the bleed once charts
fit inline, so the two strips share the same content box.
Tile heights varied because the blood-pressure tile rendered as two
stacked TrendCards (sys + dia) inside one tile slot while every
other tile was a single TrendCard. The cleaner fix is one TrendCard
that displays the paired value as `117/79` — TrendCard now accepts an
optional `secondary: { latest, avg7, avg30 }` and the dashboard pushes
ONE BP entry instead of a Fragment of two. Tile count drops from 6
to 5, the strip stops wrapping awkwardly, and the BP card carries
the systolic sparkline + traffic-light tooltip while the diastolic
value rides along under the same heading. Each tile wrapper got
`flex` and the card itself `flex h-full flex-col`, so flex-stretch
inside the strip can do its job and the avg7/avg30 row pins to the
bottom.
Settings nav split: the section formerly titled "Übersicht" was
renamed to "Dashboard" (the term every user already knows from the
main nav), and the embedded Persönliche Zielwerte panel moved out
into its own top-level settings section under `/settings/thresholds`.
Two reasons: the panel is heavy (one card per metric) and was
visually buried behind the layout customizer, and "Übersicht" leaked
internal vocabulary — neither the route slug nor the surrounding nav
entries used it. Tests guarding slug order + section count updated
in lockstep.
Touched:
- messages/de.json, messages/en.json
- src/app/page.tsx (dashboard tile strip)
- src/app/settings/[section]/page.tsx (route wiring)
- src/components/charts/trend-card.tsx (secondary-metric prop)
- src/components/settings/section-slugs.ts
- src/components/settings/settings-shell.tsx (new entry + icon)
- src/components/settings/section-placeholder.tsx
- src/components/settings/dashboard-section.tsx (drop ThresholdsSection)
- src/components/settings/thresholds-settings-section.tsx (new)
- src/components/settings/__tests__/{sections,settings-shell}.test.tsx
Tests + typecheck green (669/669).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the SectionPlaceholder fallbacks for Account, AI, Integrations,
Notifications, Dashboard, API, Advanced, and About with real per-section
components. Every /settings/<slug> URL now renders working content; the
placeholder only fires for unknown slugs.
A2-content extraction
- Account: profile + passkeys + password change (account-section.tsx)
- AI: insights + per-user provider override (ai-section.tsx)
- Integrations: Withings + moodLog (integrations-section.tsx)
- Notifications: Telegram + ntfy + Web Push (split into telegram-card,
ntfy-card, web-push-card to keep each file under ~600 LOC)
- Dashboard: layout + thresholds wrappers (dashboard-section.tsx)
- API: endpoints + tokens (api-section.tsx)
- Advanced: export + danger zone (advanced-section.tsx)
- About (greenfield): version, license, build SHA, repo/changelog/docs
links + "Check for updates" against GitHub releases
A2-about
- Reads /api/version via TanStack Query, surfaces app version, AGPL-3.0
badge, short build SHA, build timestamp, repo/changelog/docs links.
- "Check for updates" button compares against api.github.com latest
release tag with up-to-date / newer-available / failure feedback.
A8-UI test buttons
- Shared TestConnectionButton primitive that POSTs to a test endpoint,
surfaces ok-with-latency or translated meta.errorCode failures.
- Wired into Withings + moodLog (Integrations) and Web Push
(Notifications). Glitchtip + Umami remain admin-only.
i18n
- New settings.about.* and settings.testConnection.* key groups in en.json
and de.json.
Cleanup
- Delete src/app/settings/page.legacy.tsx (3230 LOC monolith).
- Convert sync-from-server patterns to the React-recommended
prev-payload-id render-time pattern so the strict
react-hooks/set-state-in-effect rule passes without a file-level disable.
Tests
- New src/components/settings/__tests__/sections.test.tsx with 9 SSR smoke
tests (one per section) — total suite 514 (was 505).