From 78b76d2ac5f3d9b6357d116ca3bf5c4babac8b6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Tue, 2 Jun 2026 13:21:38 +0200 Subject: [PATCH] feat(settings): split targets and sources, rename AI Insights section Three related settings changes: - Split the combined Targets & Sources page back into two pages: Targets (/settings/thresholds) owns the per-metric target-range editor and Sources (/settings/sources) owns the source-priority and device-type ladders. Each is its own left-nav entry and route, served by the dynamic [section] route. The former /settings/sources permanent redirect is removed since the route now renders the page directly. - Rename the AI Insights settings section and nav label so the AI nature is explicit: 'AI Insights' (EN) / 'KI-Auswertungen' (DE), replacing the bare 'Insights' / 'Auswertungen'. - Each page renders its own h1 heading consistent with sibling settings pages. Drop the now-unused embedded mode from SourcesSection. Update the section titles and descriptions across all six locale bundles. Co-Authored-By: Claude Opus 4.8 (1M context) (cherry picked from commit 5d8842934b985eb8ce037993a63bb0736ac0c7bd) --- messages/de.json | 6 +- messages/en.json | 6 +- messages/es.json | 10 +-- messages/fr.json | 8 +-- messages/it.json | 10 +-- messages/pl.json | 10 +-- src/app/settings/[section]/page.tsx | 10 +-- .../settings/__tests__/sections.test.tsx | 39 +++++------ .../__tests__/settings-shell.test.tsx | 42 ++++++------ .../settings/section-placeholder.tsx | 2 + src/components/settings/section-slugs.ts | 16 ++--- src/components/settings/settings-shell.tsx | 18 ++--- src/components/settings/sources-section.tsx | 66 +++++++------------ .../settings/thresholds-section.tsx | 46 ++++--------- 14 files changed, 122 insertions(+), 167 deletions(-) diff --git a/messages/de.json b/messages/de.json index dd3303220..911d740e2 100644 --- a/messages/de.json +++ b/messages/de.json @@ -2632,8 +2632,8 @@ "description": "Kachel-Layout und Reihenfolge." }, "thresholds": { - "title": "Zielwerte & Quellen", - "description": "Pro Metrik: eigener Zielbereich und welche Quelle führt, wenn mehrere am selben Tag denselben Wert liefern." + "title": "Zielwerte", + "description": "Pro Metrik dein eigener Zielbereich für gesunde Werte." }, "sources": { "title": "Quellen", @@ -2688,7 +2688,7 @@ } }, "ai": { - "title": "Auswertungen", + "title": "KI-Auswertungen", "description": "Provider, Modell, API-Key." }, "api": { diff --git a/messages/en.json b/messages/en.json index 94ec0d550..fdcd403c2 100644 --- a/messages/en.json +++ b/messages/en.json @@ -2632,8 +2632,8 @@ "description": "Tile layout and order." }, "thresholds": { - "title": "Targets & Sources", - "description": "Per metric: your own target range and which source wins when more than one records the same value on the same day." + "title": "Targets", + "description": "Per metric: your own target range for healthy values." }, "sources": { "title": "Sources", @@ -2688,7 +2688,7 @@ } }, "ai": { - "title": "Insights", + "title": "AI Insights", "description": "Provider, model, key." }, "api": { diff --git a/messages/es.json b/messages/es.json index 4953bba1d..e719fe090 100644 --- a/messages/es.json +++ b/messages/es.json @@ -2632,12 +2632,12 @@ "description": "Tile layout and order." }, "thresholds": { - "title": "Objetivos y fuentes", - "description": "Por métrica: tu propio rango objetivo y qué fuente prevalece cuando varias registran el mismo valor el mismo día." + "title": "Objetivos", + "description": "Por métrica: tu propio rango objetivo de valores saludables." }, "sources": { - "title": "Sources", - "description": "Pick which ingest source wins when more than one records the same metric for the same day.", + "title": "Fuentes", + "description": "Elige qué fuente prevalece cuando varias registran la misma métrica el mismo día.", "redirectNotice": "Las fuentes ahora forman parte de «Objetivos y fuentes».", "cardTitle": "Source priority by metric", "help": "When more than one source delivers the same value on the same day (e.g. Withings and Apple Health both report 1,200 steps), the top source counts as the canonical reading. Other sources stay stored for the audit trail.", @@ -2688,7 +2688,7 @@ } }, "ai": { - "title": "Insights", + "title": "AI Insights", "description": "Provider, model, key." }, "api": { diff --git a/messages/fr.json b/messages/fr.json index d79dcf777..5904ceff4 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -2632,12 +2632,12 @@ "description": "Tile layout and order." }, "thresholds": { - "title": "Objectifs et sources", - "description": "Par métrique : votre propre plage cible et quelle source prévaut quand plusieurs enregistrent la même valeur le même jour." + "title": "Objectifs", + "description": "Par métrique : votre propre plage cible pour des valeurs saines." }, "sources": { "title": "Sources", - "description": "Pick which ingest source wins when more than one records the same metric for the same day.", + "description": "Choisissez quelle source prévaut quand plusieurs enregistrent la même métrique le même jour.", "redirectNotice": "Les sources font désormais partie de « Objectifs et sources ».", "cardTitle": "Source priority by metric", "help": "When more than one source delivers the same value on the same day (e.g. Withings and Apple Health both report 1,200 steps), the top source counts as the canonical reading. Other sources stay stored for the audit trail.", @@ -2688,7 +2688,7 @@ } }, "ai": { - "title": "Insights", + "title": "AI Insights", "description": "Provider, model, key." }, "api": { diff --git a/messages/it.json b/messages/it.json index 528df33c0..2d58c19a3 100644 --- a/messages/it.json +++ b/messages/it.json @@ -2632,12 +2632,12 @@ "description": "Tile layout and order." }, "thresholds": { - "title": "Obiettivi e fonti", - "description": "Per metrica: il tuo intervallo target e quale fonte prevale quando più di una registra lo stesso valore lo stesso giorno." + "title": "Obiettivi", + "description": "Per metrica: il tuo intervallo target per valori sani." }, "sources": { - "title": "Sources", - "description": "Pick which ingest source wins when more than one records the same metric for the same day.", + "title": "Fonti", + "description": "Scegli quale fonte prevale quando più di una registra la stessa metrica lo stesso giorno.", "redirectNotice": "Le fonti ora fanno parte di «Obiettivi e fonti».", "cardTitle": "Source priority by metric", "help": "When more than one source delivers the same value on the same day (e.g. Withings and Apple Health both report 1,200 steps), the top source counts as the canonical reading. Other sources stay stored for the audit trail.", @@ -2688,7 +2688,7 @@ } }, "ai": { - "title": "Insights", + "title": "AI Insights", "description": "Provider, model, key." }, "api": { diff --git a/messages/pl.json b/messages/pl.json index 1f43336f1..7030787dd 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -2632,12 +2632,12 @@ "description": "Tile layout and order." }, "thresholds": { - "title": "Cele i źródła", - "description": "Dla każdej metryki: własny zakres docelowy i które źródło wygrywa, gdy kilka rejestruje tę samą wartość tego samego dnia." + "title": "Cele", + "description": "Dla każdej metryki: własny zakres docelowy zdrowych wartości." }, "sources": { - "title": "Sources", - "description": "Pick which ingest source wins when more than one records the same metric for the same day.", + "title": "Źródła", + "description": "Wybierz, które źródło wygrywa, gdy kilka rejestruje tę samą metrykę tego samego dnia.", "redirectNotice": "Źródła są teraz częścią „Cele i źródła”.", "cardTitle": "Source priority by metric", "help": "When more than one source delivers the same value on the same day (e.g. Withings and Apple Health both report 1,200 steps), the top source counts as the canonical reading. Other sources stay stored for the audit trail.", @@ -2688,7 +2688,7 @@ } }, "ai": { - "title": "Insights", + "title": "AI Insights", "description": "Provider, model, key." }, "api": { diff --git a/src/app/settings/[section]/page.tsx b/src/app/settings/[section]/page.tsx index 81b86cce4..38e5fadae 100644 --- a/src/app/settings/[section]/page.tsx +++ b/src/app/settings/[section]/page.tsx @@ -11,11 +11,12 @@ import { ExportSection } from "@/components/settings/export-section"; import { IntegrationsSection } from "@/components/settings/integrations-section"; import { NotificationsSection } from "@/components/settings/notifications-section"; import { SectionPlaceholder } from "@/components/settings/section-placeholder"; -// v1.4.34 IW-D — `sources` slug was retired (merged into `thresholds`). -// The combined "Targets & Sources" component lives in -// `thresholds-section.tsx`; `/settings/sources` is now a -// `permanentRedirect` handled by the dedicated route file. +// v1.8.7.1 — `thresholds` (Targets) and `sources` (Sources) are two +// separate sections again. `ThresholdsSection` renders the target-range +// editor; `SourcesSection` (standalone mode) renders the source-priority +// ladders. Both are served by this dynamic route. import { ThresholdsSection } from "@/components/settings/thresholds-section"; +import { SourcesSection } from "@/components/settings/sources-section"; import { SETTINGS_SECTION_SLUGS, isSettingsSectionSlug, @@ -49,6 +50,7 @@ const SECTION_COMPONENTS: Record< notifications: NotificationsSection, dashboard: DashboardSection, thresholds: ThresholdsSection, + sources: () => , api: ApiSection, export: ExportSection, advanced: AdvancedSection, diff --git a/src/components/settings/__tests__/sections.test.tsx b/src/components/settings/__tests__/sections.test.tsx index 88300feb2..5a915c73e 100644 --- a/src/components/settings/__tests__/sections.test.tsx +++ b/src/components/settings/__tests__/sections.test.tsx @@ -79,15 +79,6 @@ vi.mock("@/components/settings/dashboard-layout-section", () => ({ vi.mock("@/components/settings/thresholds-editor-section", () => ({ ThresholdsEditorSection: () =>
, })); -// v1.4.34 IW-D — `` now also embeds -// `` so the combined "Targets & -// Sources" page reads as one shelf. The SourcesSection itself owns -// React Query and TanStack mutation wiring; stub it at the import -// boundary to keep the SSR smoke test focused on the wrapper. -vi.mock("@/components/settings/sources-section", () => ({ - SourcesSection: () =>
, -})); - import { I18nProvider } from "@/lib/i18n/context"; import { AboutSection } from "../about-section"; import { AccountSection } from "../account-section"; @@ -169,12 +160,12 @@ describe("settings sections — SSR smoke", () => { expect(html).toContain("Tour ansehen"); }); - it(" renders the Insights card", () => { + it(" renders the AI Insights heading", () => { const html = render(); - // v1.4.33 IW7 — section renamed from "AI Insights" to "Insights" - // per the Marc-Voice rule. The card title is replaced with a sparkles - // icon + status badges, and the section H1 carries the only heading. - expect(html).toContain("Insights"); + // v1.8.7.1 — section renamed to "AI Insights" so the AI nature of the + // surface is explicit (was the bare "Insights"). The section H1 carries + // the only heading. + expect(html).toContain("AI Insights"); expect(html).toContain("settings-section-ai-title"); }); @@ -230,26 +221,26 @@ describe("settings sections — SSR smoke", () => { expect(html).not.toContain(">Jetzt prüfen<"); }); - it(" renders heading and embeds both editors (v1.4.34 IW-D merge)", () => { + it(" renders the Targets heading and the threshold editor only", () => { // v1.4.16 phase B6 contract: the route wrapper is named // `` (was ``), lives // at `thresholds-section.tsx`, and embeds the inner editor under // `` (was ``). - // v1.4.34 IW-D — the wrapper now also embeds - // `` so the page is the merged - // "Targets & Sources" surface. Both editor mounts must be present. + // v1.8.7.1 — Sources split back onto its own page, so this wrapper + // renders the target-range editor only. const html = render(); - // v1.4.34 IW-D: section renamed to "Targets & Sources". - expect(html).toContain("Targets & Sources"); + // v1.8.7.1: section renamed to "Targets". + expect(html).toContain("Targets"); expect(html).toContain('data-testid="thresholds-editor"'); - expect(html).toContain('data-testid="sources-section"'); + expect(html).not.toContain('data-testid="sources-section"'); expect(html).not.toContain("settings.sections.thresholds."); }); - it(" resolves the German title (B6 rename + IW-D merge)", () => { + it(" resolves the German title", () => { const html = render(, "de"); - expect(html).toContain("Zielwerte & Quellen"); + // v1.8.7.1 — German title is "Zielwerte" (Targets only). + expect(html).toContain("Zielwerte"); expect(html).toContain('data-testid="thresholds-editor"'); - expect(html).toContain('data-testid="sources-section"'); + expect(html).not.toContain('data-testid="sources-section"'); }); }); diff --git a/src/components/settings/__tests__/settings-shell.test.tsx b/src/components/settings/__tests__/settings-shell.test.tsx index c5f61ea90..2a2752820 100644 --- a/src/components/settings/__tests__/settings-shell.test.tsx +++ b/src/components/settings/__tests__/settings-shell.test.tsx @@ -35,7 +35,7 @@ function renderShell(props: { } describe("SETTINGS_SECTION_SLUGS", () => { - it("declares the ten sections agreed for v1.4.34", () => { + it("declares the eleven sections", () => { // Order matters — `generateStaticParams()` and the sidebar derive their // ordering from this constant, so a reorder is a behaviour change. // v1.4.3 split the dashboard panel: layout stays under `dashboard`, @@ -44,19 +44,17 @@ describe("SETTINGS_SECTION_SLUGS", () => { // v1.4.16 phase B7 added the consolidated `export` section between // `api` and `advanced` so every "give me my data out" path lives in // one place. - // v1.4.25 W5e added `sources` between `thresholds` and `ai` so the - // per-metric-class source-priority surface lives next to the other - // analytics-shaping controls (thresholds → ranges; sources → - // canonical source per metric). - // v1.4.34 IW-D — `sources` slug was retired (merged into - // `thresholds`, now "Targets & Sources"). `/settings/sources` stays - // alive as a `permanentRedirect`. Section count: 10 (was 11). + // v1.4.25 W5e added `sources` between `thresholds` and `ai`; v1.4.34 + // IW-D merged it into `thresholds`. + // v1.8.7.1 — `sources` (Sources) is its own slug again, sitting + // between `thresholds` (Targets) and `ai`. Section count: 11. expect([...SETTINGS_SECTION_SLUGS]).toEqual([ "account", "integrations", "notifications", "dashboard", "thresholds", + "sources", "ai", "api", "export", @@ -164,11 +162,11 @@ describe("", () => { // in the sidebar user-card dropdown. Route `/settings/about` is // still alive for direct links. expect(html).not.toContain('href="/settings/about"'); - // v1.4.34 IW-D — Sources slug retired; merged into Targets & Sources - // under `/settings/thresholds`. The Sources nav entry must not - // appear; the route stays alive via permanentRedirect. - expect(html).not.toContain('href="/settings/sources"'); - expect(html).toContain("Targets & Sources"); + // v1.8.7.1 — Targets and Sources are two separate nav entries again. + expect(html).toContain('href="/settings/thresholds"'); + expect(html).toContain('href="/settings/sources"'); + expect(html).toContain("Targets"); + expect(html).toContain("Sources"); }); it("resolves every section title via the i18n provider — German", () => { @@ -184,21 +182,21 @@ describe("", () => { // per-metric overrides moved out into their own "Persönliche Zielwerte" // section, which is the new entry below the Dashboard one. expect(html).toContain("Dashboard"); - // v1.4.34 IW-D — section renamed from "Persönliche Zielwerte" to - // "Zielwerte & Quellen" (combined with the former Sources section). - expect(html).toContain("Zielwerte & Quellen"); - // v1.4.33 IW7 — "KI-Auswertungen" renamed to "Auswertungen" per - // the Marc-Voice rule (no "KI"/"AI" prefix in user-facing copy). - expect(html).toContain("Auswertungen"); + // v1.8.7.1 — Targets and Sources are two separate German nav + // entries again: "Zielwerte" and "Quellen". + expect(html).toContain("Zielwerte"); + expect(html).toContain("Quellen"); + // v1.8.7.1 — the AI Insights section is named "KI-Auswertungen" in + // German (the "KI" prefix makes the AI nature explicit). + expect(html).toContain("KI-Auswertungen"); // API & Tokens is identical in both locales (proper noun + ampersand) expect(html).toContain("API & Tokens"); expect(html).toContain("Erweitert"); // v1.4.33 IW7 — "Über" (About) section removed from the in-shell // nav, folded into the sidebar user-card dropdown ("Über HealthLog"). expect(html).not.toContain('href="/settings/about"'); - // v1.4.34 IW-D — Sources nav entry retired (merged into Targets & - // Sources). - expect(html).not.toContain('href="/settings/sources"'); + // v1.8.7.1 — both Targets and Sources nav entries are present. + expect(html).toContain('href="/settings/sources"'); }); it("does NOT surface the raw key when a translation resolves — guards against missing JSON entries", () => { diff --git a/src/components/settings/section-placeholder.tsx b/src/components/settings/section-placeholder.tsx index 0a8681a54..3f05ea25d 100644 --- a/src/components/settings/section-placeholder.tsx +++ b/src/components/settings/section-placeholder.tsx @@ -16,6 +16,7 @@ import { Download, Info, KeyRound, + Layers, LayoutDashboard, Link2, Settings2, @@ -35,6 +36,7 @@ const SLUG_ICON: Record = { notifications: Bell, dashboard: LayoutDashboard, thresholds: SlidersHorizontal, + sources: Layers, ai: Sparkles, api: KeyRound, export: Download, diff --git a/src/components/settings/section-slugs.ts b/src/components/settings/section-slugs.ts index e242d9891..76f010ef5 100644 --- a/src/components/settings/section-slugs.ts +++ b/src/components/settings/section-slugs.ts @@ -6,20 +6,20 @@ * graph. */ -// v1.4.34 IW-D — `sources` slug was retired. The two former sections -// (`/settings/thresholds` and `/settings/sources`) merged into a single -// "Targets & Sources" page under `/settings/thresholds`. The historic -// `/settings/sources` URL stays alive as a `permanentRedirect` (see -// `src/app/settings/sources/page.tsx`) so external bookmarks (iOS, -// docs) follow through unchanged. Per -// `.planning/research/v1434-r-2-carryover-scope.md` §7 and -// `.planning/round-v1433-audit-menu.md` §7.1 (item 3). +// v1.8.7.1 — `thresholds` (Targets) and `sources` (Sources) are again two +// separate settings pages, each its own left-nav entry and route. They were +// merged into one "Targets & Sources" page in v1.4.34 IW-D, but the two +// editors drive distinct mutation flows (`/api/user/thresholds` vs +// `/api/auth/me/source-priority`) and reading them as one page made the +// surface long; splitting them back keeps each concern self-contained. +// Both slugs are served by the dynamic `[section]` route below. export const SETTINGS_SECTION_SLUGS = [ "account", "integrations", "notifications", "dashboard", "thresholds", + "sources", "ai", "api", "export", diff --git a/src/components/settings/settings-shell.tsx b/src/components/settings/settings-shell.tsx index 4e40e80d7..22043e34a 100644 --- a/src/components/settings/settings-shell.tsx +++ b/src/components/settings/settings-shell.tsx @@ -21,6 +21,7 @@ import { Bell, Download, KeyRound, + Layers, LayoutDashboard, Link2, Settings2, @@ -74,14 +75,10 @@ interface SettingsSection { * navigation. Direct links (the user-card dropdown, the public * `/about` permalink) continue to resolve to the same component. * - * v1.4.34 IW-D — `sources` was merged into `thresholds` (now titled - * "Targets & Sources" / "Zielwerte & Quellen"). The combined section - * surfaces per-metric source priority alongside the threshold inputs - * on a single page. `/settings/sources` stays alive as a - * `permanentRedirect` so external bookmarks keep working; the slug - * was dropped from `SETTINGS_SECTION_SLUGS` because the dynamic - * `[section]` route no longer serves it. Per - * `.planning/research/v1434-r-2-carryover-scope.md` §7. + * v1.8.7.1 — `thresholds` (Targets) and `sources` (Sources) are two + * separate sidebar entries again (merged into a single "Targets & + * Sources" page in v1.4.34 IW-D, split back here). Both are served by + * the dynamic `[section]` route. */ export const SETTINGS_SECTIONS: readonly SettingsSection[] = [ { slug: "account", titleKey: "settings.sections.account.title", icon: User }, @@ -105,6 +102,11 @@ export const SETTINGS_SECTIONS: readonly SettingsSection[] = [ titleKey: "settings.sections.thresholds.title", icon: SlidersHorizontal, }, + { + slug: "sources", + titleKey: "settings.sections.sources.title", + icon: Layers, + }, { slug: "ai", titleKey: "settings.sections.ai.title", icon: Sparkles }, { slug: "api", titleKey: "settings.sections.api.title", icon: KeyRound }, { diff --git a/src/components/settings/sources-section.tsx b/src/components/settings/sources-section.tsx index 1e2089d1b..8a44aa099 100644 --- a/src/components/settings/sources-section.tsx +++ b/src/components/settings/sources-section.tsx @@ -99,25 +99,15 @@ const DEVICE_TYPE_LABEL_KEYS: Record = { }; /** - * v1.4.34 IW-D — section presentation mode. - * - `"standalone"` (default) renders the page-level header (h1 + - * subtitle) and keeps the section's own aria-labelledby wiring. - * - `"embedded"` drops the header because a parent surface - * (`` on `/settings/thresholds`) already names - * the page. The section becomes a `
` instead of a - * `
` so the assistive-tech reading order stays one - * combined surface. + * `` — route-level wrapper for `/settings/sources` + * (the "Sources" / "Quellen" page). * - * `/settings/sources` no longer renders this component directly — it - * permanently redirects to `/settings/thresholds`, which uses the - * `embedded` mode. The default keeps backward compatibility for any - * test/storybook that still mounts `` standalone. + * v1.8.7.1 — Sources is its own settings page again (it was merged into + * the combined "Targets & Sources" page under `/settings/thresholds` in + * v1.4.34 IW-D). The section renders its own page header (h1 + subtitle) + * and owns the per-metric source-priority + device-type ladders. */ -export interface SourcesSectionProps { - mode?: "standalone" | "embedded"; -} - -export function SourcesSection({ mode = "standalone" }: SourcesSectionProps = {}) { +export function SourcesSection() { const { t } = useTranslations(); const queryClient = useQueryClient(); @@ -292,32 +282,22 @@ export function SourcesSection({ mode = "standalone" }: SourcesSectionProps = {} return Object.keys(priority.deviceTypePriority).length; }, [priority]); - // v1.4.34 IW-D — `embedded` mode skips the section header because the - // parent surface (``) already names the page. - const isStandalone = mode === "standalone"; - const Wrapper = isStandalone ? "section" : "div"; - const wrapperProps = isStandalone - ? { - "aria-labelledby": "settings-section-sources-title", - className: "space-y-6", - } - : { className: "space-y-6" }; - return ( - - {isStandalone && ( -
-

- {t("settings.sections.sources.title")} -

-

- {t("settings.sections.sources.description")} -

-
- )} +
+
+

+ {t("settings.sections.sources.title")} +

+

+ {t("settings.sections.sources.description")} +

+
@@ -599,7 +579,7 @@ export function SourcesSection({ mode = "standalone" }: SourcesSectionProps = {}
)}
- +
); } diff --git a/src/components/settings/thresholds-section.tsx b/src/components/settings/thresholds-section.tsx index 096045b09..ae913008d 100644 --- a/src/components/settings/thresholds-section.tsx +++ b/src/components/settings/thresholds-section.tsx @@ -1,37 +1,24 @@ "use client"; -import { SourcesSection } from "@/components/settings/sources-section"; import { ThresholdsEditorSection } from "@/components/settings/thresholds-editor-section"; import { useTranslations } from "@/lib/i18n/context"; /** - * `` — route-level wrapper for `/settings/thresholds`. + * `` — route-level wrapper for `/settings/thresholds` + * (the "Targets" / "Zielwerte" page). * - * v1.4.34 IW-D — section was renamed "Zielwerte & Quellen" / "Targets & - * Sources" and absorbs the former `/settings/sources` content. The two - * editors stay distinct mutation flows (`/api/user/thresholds` for - * ranges + `/api/auth/me/source-priority` for source ladders) so a save - * on one half never disturbs the other; presenting them on a single - * page collapses two adjacent sidebar slots into one and matches the - * Settings nav audit (§7.1 item 3 in - * `.planning/round-v1433-audit-menu.md` — both screens were per-metric - * configuration shelves and reading them together matches how a user - * actually thinks about a metric: "for this metric, which sources do - * I trust AND what range counts as healthy?"). - * - * `/settings/sources` stays alive as a `permanentRedirect` (see - * `src/app/settings/sources/page.tsx`) so external bookmarks (iOS - * Settings deep-links, docs) keep resolving. + * v1.8.7.1 — Targets and Sources are two separate settings pages again. + * This page owns only the per-metric target-range editor + * (`/api/user/thresholds`); the source-priority ladders moved back onto + * their own `/settings/sources` page (``). The two were + * merged into one "Targets & Sources" page in v1.4.34 IW-D, but the + * combined surface ran long and the two editors drive distinct mutation + * flows, so each concern is self-contained on its own page. * * Visual structure on this page: - * 1. Section header (combined title + description). - * 2. `` — per-metric range inputs (top). - * The editor card kept its own affordance row (icon, reset-all - * button) so the action surface stays compact. - * 3. `` body — per-metric source priority + the - * two-axis device-type expander (bottom). The inner section's - * header was suppressed in `mode="embedded"` because the page - * header above already names the surface. + * 1. Section header (title + description). + * 2. `` — per-metric range inputs. The editor + * card keeps its own affordance row (icon, reset-all button). * * v1.4.16 phase B6: file renamed from the historic * `thresholds-settings-section.tsx` (``) @@ -63,15 +50,8 @@ export function ThresholdsSection() {

- {/* Per-metric threshold ranges — top card. */} + {/* Per-metric threshold ranges. */} - - {/* Per-metric source priority + two-axis device-type ladder — - bottom card. `mode="embedded"` suppresses the inner h1 + - subtitle (the page header above owns the surface name) and - drops the inner `
` aria-label so the assistive-tech - reading order stays one combined surface, not two. */} -
); }