fix(settings): finish the D0-D8 settings/admin frame cleanup

- Drop the leading blurb paragraph from the Channels + Integrations
  connection panels so each body starts at its first card (D0 alignment).
- Correct three stale CORE/always-on comments: medications has been a
  fail-open toggleable module since D3; CORE is now weight/BP/pulse only.
- Remove the dead variant/isSubtab branch from SourcesSection — no callers
  after the D4 split; render the standalone header unconditionally.
- Render the admin AI-quality card frame + header once and branch only the
  body across loading/error/empty/data, matching the coach-feedback card.
- Rename the Coach activation card's section id from the legacy
  settings-ai-* namespace to settings-coach-activate-* after the S5 move.
- Gate the medication low-stock reminder card on the medications module so
  it hides when the account turns medications off, mirroring mood/coach.
This commit is contained in:
Marc-André Bombeck committed 2026-06-16 23:12:36 +02:00
1 parent a3217ff767
commit dadb6e2d34
8 files changed
+99 -122

No files matched your search

@@ -3,6 +3,10 @@
/**
* v1.4.47 W3 — Coach activation toggle card.
*
* v1.18.0 (S5) — moved out of Settings → AI into the dedicated Coach
* section; its `<section>` id namespace is `settings-coach-activate-*`
* accordingly. (The file path stays under `ai/` as pre-existing drift.)
*
* v1.18.1 (D7) — polarity flip. The card now reads "Activate Coach" with the
* Switch ON by default (the Coach is on for everyone unless the user opts
* out). The persisted state is still the single `User.disableCoach` field —
@@ -137,14 +141,14 @@ export function DisableCoachCard({
return (
<section
aria-labelledby="settings-ai-disable-coach-title"
aria-labelledby="settings-coach-activate-title"
data-testid="settings-disable-coach-card"
className="bg-card border-border rounded-xl border p-4 sm:p-6"
>
<SettingsCardHeader
icon={MessageCircleHeart}
title={t("settings.coach.activate.title")}
titleId="settings-ai-disable-coach-title"
titleId="settings-coach-activate-title"
description={t("settings.coach.activate.description")}
status={
<Switch
@@ -242,10 +242,6 @@ export function ConnectionsPanel() {
return (
<div className="space-y-6" data-slot="connections-panel">
<p className="text-muted-foreground text-sm">
{t("settings.sections.integrations.connections.description")}
</p>
<WithingsCard viewModel={withingsViewModel} />
<WhoopCard viewModel={whoopViewModel} />
<FitbitCard viewModel={fitbitViewModel} />
@@ -18,7 +18,6 @@
import { useQuery } from "@tanstack/react-query";
import { useAuth } from "@/hooks/use-auth";
import { useTranslations } from "@/lib/i18n/context";
import { queryKeys } from "@/lib/query-keys";
import { NotificationStatusCard } from "@/components/settings/notification-status-card";
import { NtfyCard } from "@/components/settings/ntfy-card";
@@ -37,7 +36,6 @@ interface GlobalServiceAvailability {
}
export function NotificationChannelsPanel() {
const { t } = useTranslations();
const { isAuthenticated } = useAuth();
const { data: globalServices } = useQuery({
@@ -54,10 +52,6 @@ export function NotificationChannelsPanel() {
return (
<div className="space-y-6" data-slot="notification-channels-panel">
<p className="text-muted-foreground text-sm">
{t("settings.sections.integrations.channels.description")}
</p>
<NotificationStatusCard />
{/* v1.4.27 MB3 — `scroll-mt-28` keeps each channel card's anchor
@@ -19,9 +19,11 @@ import { useMedicationListLayout } from "@/lib/queries/use-medication-list-layou
/**
* v1.16.10 — the "Medikamente" settings section. v1.18.0 (S5) promoted it
* from a Layout-hub child to its own standalone nav entry (always shown —
* medications is a CORE domain) and gathered the medication-specific
* preferences here. Hosts three blocks:
* from a Layout-hub child to its own standalone nav entry and gathered the
* medication-specific preferences here. v1.18.1 (D3) made medications a
* toggleable fail-open module, so this nav entry hides when the account
* turns the module off (the medication data routes stay live). Hosts three
* blocks:
* 1. The list VIEW preference — cards vs table, written optimistically
* through the same `PUT /api/medications/layout` the page header
* toggle uses (the toggle component is shared).
@@ -17,9 +17,9 @@ import { MoodReminderCard } from "@/components/settings/mood-reminder-card";
*
* Each reminder-type card is shown ONLY when its module is enabled, read from
* the resolved `useAuth().user.modules` map (the same `/auth/me` map nav and
* Insights pills gate off). Mood → `mood`, Coach nudge → `coach`. Low-stock
* runway maps to medications, which fails open (a missing key reads as
* enabled) so a stale `/me` payload never blanks a card.
* Insights pills gate off). Mood → `mood`, Coach nudge → `coach`, low-stock
* runway → `medications` (a toggleable fail-open module since D3, so a missing
* key reads as enabled and a stale `/me` payload never blanks a card).
*
* v1.18.1 (D5) — the page is intentionally lean: the section blurb, the
* channels / inbox cross-links, and the embedded Vorsorge (preventive-care)
@@ -35,6 +35,7 @@ export function NotificationsSection() {
// so a stale `/me` payload without the module map keeps every card visible.
const moodEnabled = user?.modules?.mood !== false;
const coachEnabled = user?.modules?.coach !== false;
const medsEnabled = user?.modules?.medications !== false;
return (
<section
@@ -61,11 +62,13 @@ export function NotificationsSection() {
</div>
) : null}
{/* Medication low-stock runway — medications is a CORE domain, so this
is always shown. */}
<div id="low-stock" className="scroll-mt-28">
<LowStockCard isAuthenticated={isAuthenticated} />
</div>
{/* Medication low-stock runway — only when the medications module is
enabled (toggleable, fail-open, since D3). */}
{medsEnabled ? (
<div id="low-stock" className="scroll-mt-28">
<LowStockCard isAuthenticated={isAuthenticated} />
</div>
) : null}
{/* Proactive Coach nudge — only when the coach module is enabled. */}
{coachEnabled ? (
+22 -39
View File
@@ -111,31 +111,16 @@ const DEVICE_TYPE_LABEL_KEYS: Record<DeviceType, string> = {
unknown: "settings.sections.sources.deviceLabels.unknown",
};
export interface SourcesSectionProps {
/**
* `"standalone"` (the default) renders the full page header + the
* cross-link back to Integrations — the shape used while Sources had its
* own `/settings/sources` route. `"subtab"` (v1.18.0 S3) suppresses both:
* Sources now renders inside the Integrations → Sources sub-tab, so the
* page header is the parent section's and a cross-link back to the page
* that already contains it would be circular.
*/
variant?: "standalone" | "subtab";
}
/**
* `<SourcesSection>` — the per-metric source-priority + device-type ladders.
*
* v1.18.1 (D4) — Sources is a standalone `/settings/sources` left-side entry
* again (split back out of the Integrations sub-tabs). The default
* `variant="standalone"` renders the section header + the cross-link back to
* Integrationen; `variant="subtab"` is retained for any embedded use and
* suppresses the header.
* again (split back out of the Integrations sub-tabs). It renders its own
* section header + the cross-link back to Integrationen.
*/
export function SourcesSection({ variant = "standalone" }: SourcesSectionProps = {}) {
export function SourcesSection() {
const { t } = useTranslations();
const queryClient = useQueryClient();
const isSubtab = variant === "subtab";
const { data: remote, isLoading } = useQuery({
queryKey: queryKeys.sourcePriority(),
@@ -298,27 +283,25 @@ export function SourcesSection({ variant = "standalone" }: SourcesSectionProps =
aria-labelledby="settings-section-sources-title"
className="space-y-6"
>
{isSubtab ? null : (
// v1.18.1 (D0/D4) — standalone left-side entry again. The section
// blurb is dropped for consistent top alignment; the cross-link back
// to Integrationen (where the sources this ladder ranks are added /
// removed) stays as a quiet pointer.
<header>
<h1 id="settings-section-sources-title" className="sr-only">
{t("settings.sections.sources.title")}
</h1>
<p className="text-muted-foreground text-xs">
{t("settings.sections.sources.integrationsHint")}{" "}
<Link
href="/settings/integrations"
className="text-primary underline underline-offset-2"
data-slot="sources-integrations-cross-link"
>
{t("settings.sections.sources.integrationsHintLink")}
</Link>
</p>
</header>
)}
{/* v1.18.1 (D0/D4) — standalone left-side entry again. The section
blurb is dropped for consistent top alignment; the cross-link back
to Integrationen (where the sources this ladder ranks are added /
removed) stays as a quiet pointer. */}
<header>
<h1 id="settings-section-sources-title" className="sr-only">
{t("settings.sections.sources.title")}
</h1>
<p className="text-muted-foreground text-xs">
{t("settings.sections.sources.integrationsHint")}{" "}
<Link
href="/settings/integrations"
className="text-primary underline underline-offset-2"
data-slot="sources-integrations-cross-link"
>
{t("settings.sections.sources.integrationsHintLink")}
</Link>
</p>
</header>
<div className="bg-card border-border space-y-4 rounded-xl border p-4 sm:p-6">
<SettingsCardHeader