diff --git a/src/components/module-list/module-settings-frame.tsx b/src/components/module-list/module-settings-frame.tsx
index 12c6887d1..4d2959b19 100644
--- a/src/components/module-list/module-settings-frame.tsx
+++ b/src/components/module-list/module-settings-frame.tsx
@@ -3,21 +3,18 @@
import type { ReactNode } from "react";
import { SettingsHubBackLink } from "@/components/settings/settings-hub-back-link";
+import { SettingsSectionFrame } from "@/components/settings/settings-section-frame";
/**
* v1.18.6 (W8 / MOD-03) — heading frame for the per-module settings pages
* (Vorsorge / Illness / Labs).
*
- * FRAME-ADOPTION SEAM: a parallel phase is introducing a shared
- * `SettingsSectionFrame` primitive under `src/components/settings/*` (which
- * this phase must not edit). Until that lands, this renders the canonical
- * settings heading markup verbatim — `h1.text-2xl.font-bold.tracking-tight`
- * + `p.text-muted-foreground.text-sm` — so the three new pages already
- * conform to the standard and can be swapped to the shared frame at merge by
- * replacing this single wrapper. The back-link reuses the existing
- * `SettingsHubBackLink` (read-only import) so the pages have a consistent
- * "return to the hub" affordance even though they are not listed in the
- * settings-shell sidebar.
+ * Delegates to the shared `SettingsSectionFrame` so every settings page — the
+ * 19 shell sections and these three per-module pages — renders one identical
+ * heading frame. These pages stay out of the slug registry (their static
+ * routes win over `/settings/[section]`), so they drive the frame's explicit
+ * title/subtitle mode and supply the "return to the module" back-link through
+ * the frame's `topSlot`, which sits above the heading.
*/
export function ModuleSettingsFrame({
title,
@@ -34,13 +31,12 @@ export function ModuleSettingsFrame({
children: ReactNode;
}) {
return (
-
-
-
-
{title}
-
{description}
-
+
}
+ >
{children}
-
+
);
}
diff --git a/src/components/settings/settings-section-frame.tsx b/src/components/settings/settings-section-frame.tsx
index 1191d1cb0..067bd3b17 100644
--- a/src/components/settings/settings-section-frame.tsx
+++ b/src/components/settings/settings-section-frame.tsx
@@ -33,8 +33,7 @@ import * as React from "react";
import { useTranslations } from "@/lib/i18n/context";
import type { SettingsSectionSlug } from "./section-slugs";
-export interface SettingsSectionFrameProps {
- slug: SettingsSectionSlug;
+interface SettingsSectionFrameBaseProps {
/** Optional content rendered above the heading (e.g. a back-to-hub link). */
topSlot?: React.ReactNode;
/** Optional content rendered inline at the top-right of the heading row
@@ -43,14 +42,57 @@ export interface SettingsSectionFrameProps {
children: React.ReactNode;
}
-export function SettingsSectionFrame({
- slug,
- topSlot,
- headingAccessory,
- children,
-}: SettingsSectionFrameProps) {
+/**
+ * Slug mode (the 19 shell sections): title + subtitle come from
+ * `settings.sections..{title,subtitle}` and the `` keeps the
+ * historic `settings-section--title` id for `aria-labelledby` and
+ * spotlight-tour anchors.
+ */
+interface SettingsSectionFrameSlugProps extends SettingsSectionFrameBaseProps {
+ slug: SettingsSectionSlug;
+}
+
+/**
+ * Explicit mode (the per-module settings pages — Vorsorge / Illness / Labs —
+ * which deliberately stay out of the slug registry because their static
+ * routes win over `/settings/[section]`): caller passes resolved strings and
+ * an optional heading id. This lets `ModuleSettingsFrame` delegate here so
+ * every settings page shares one heading frame.
+ */
+interface SettingsSectionFrameExplicitProps
+ extends SettingsSectionFrameBaseProps {
+ title: string;
+ subtitle: string;
+ /** Heading element id (defaults to a stable slug-less id). */
+ titleId?: string;
+}
+
+export type SettingsSectionFrameProps =
+ | SettingsSectionFrameSlugProps
+ | SettingsSectionFrameExplicitProps;
+
+function isSlugProps(
+ props: SettingsSectionFrameProps,
+): props is SettingsSectionFrameSlugProps {
+ return "slug" in props;
+}
+
+export function SettingsSectionFrame(props: SettingsSectionFrameProps) {
const { t } = useTranslations();
- const titleId = `settings-section-${slug}-title`;
+ const { topSlot, headingAccessory, children } = props;
+
+ let title: string;
+ let subtitle: string;
+ let titleId: string;
+ if (isSlugProps(props)) {
+ titleId = `settings-section-${props.slug}-title`;
+ title = t(`settings.sections.${props.slug}.title`);
+ subtitle = t(`settings.sections.${props.slug}.subtitle`);
+ } else {
+ titleId = props.titleId ?? "settings-section-title";
+ title = props.title;
+ subtitle = props.subtitle;
+ }
return (
@@ -58,11 +100,9 @@ export function SettingsSectionFrame({
- {t(`settings.sections.${slug}.title`)}
+ {title}
-
- {t(`settings.sections.${slug}.subtitle`)}
-
+
{subtitle}
{headingAccessory ? (
{headingAccessory}