diff --git a/src/app/notifications/page.tsx b/src/app/notifications/page.tsx
index db199f853..ba17b08c5 100644
--- a/src/app/notifications/page.tsx
+++ b/src/app/notifications/page.tsx
@@ -8,6 +8,7 @@ import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button";
import { PageHeader } from "@/components/ui/page-header";
import { Skeleton } from "@/components/ui/skeleton";
+import { EmptyState } from "@/components/ui/empty-state";
import { QueryErrorCard } from "@/components/ui/query-error-card";
import Link from "next/link";
import { Bell, Settings, AlertCircle } from "lucide-react";
@@ -201,22 +202,21 @@ export default function NotificationsPage() {
description={t("notifications.subtitle")}
actions={headerActions}
/>
-
-
-
-
- {t("notifications.noChannels")}
-
- {/* Channel setup lives in Settings → Integrations since v1.25.7;
- point the CTA at that anchor, not the reminder-types screen. */}
+ }
+ title={t("notifications.noChannels")}
+ /* Channel setup lives in Settings → Integrations since v1.25.7;
+ point the CTA at that anchor, not the reminder-types screen. */
+ action={
-
-
+ }
+ />
);
}
diff --git a/src/components/dashboard/recent-workouts-tile.tsx b/src/components/dashboard/recent-workouts-tile.tsx
index cd4eab1ab..cf77c7eeb 100644
--- a/src/components/dashboard/recent-workouts-tile.tsx
+++ b/src/components/dashboard/recent-workouts-tile.tsx
@@ -6,6 +6,7 @@ import { Activity } from "lucide-react";
import { useTranslations } from "@/lib/i18n/context";
import { useWorkouts, type WorkoutListEntry } from "@/hooks/use-workouts";
import { cn } from "@/lib/utils";
+import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { TileHeader } from "@/components/insights/tile-header";
import { getDateTimeFormat } from "@/lib/intl/formatter-cache";
import { Skeleton } from "@/components/ui/skeleton";
@@ -78,90 +79,90 @@ export function RecentWorkoutsTile() {
const workouts = data?.workouts ?? [];
return (
-
- {/* Canonical tile header (foreground icon + title) — the same
- `TileHeader` contract the Insights tiles use, so the dashboard
- stops speaking a separate header language. `size="sm"` matches the
- compact dashboard tile. */}
-
- {t("dashboard.recentWorkouts.viewAll")}
-
- }
- />
-
- {showLoading ? (
- // v1.4.43 W11-L6 — reserve roughly the loaded tile height
- // (3 workouts × ~3 rem rows + spacing) so the surrounding
- // dashboard layout doesn't reflow once the data lands.
- // v1.29.x — row-shaped skeletons instead of a "loading…" text
- // line, matching the sport-icon + label + day + duration shape
- // the loaded rows render.
-
- {[0, 1, 2].map((i) => (
-
-
-
-
-
-
- ))}
-
- ) : isError ? (
- refetch()}
- className="border-0 bg-transparent shadow-none"
+
+
+ {/* Canonical tile header (foreground icon + title) — the same
+ `TileHeader` contract the Insights tiles use, so the dashboard
+ stops speaking a separate header language. `size="sm"` matches the
+ compact dashboard tile. */}
+
+ {t("dashboard.recentWorkouts.viewAll")}
+
+ }
/>
- ) : workouts.length === 0 ? (
-
+ )}
+
+
);
}
diff --git a/src/components/settings/account-section/index.tsx b/src/components/settings/account-section/index.tsx
index 5d223b5fd..795852dd0 100644
--- a/src/components/settings/account-section/index.tsx
+++ b/src/components/settings/account-section/index.tsx
@@ -285,10 +285,18 @@ export function AccountSection() {
}
if (!mounted || isLoading) {
+ // §13 — a loading section paints its card + header, not a bare centered
+ // spinner (reference: admin/coach-feedback-section).
return (
-
-
-
+
+
+
+
+
+ {t("common.loading")}
+
+
+
);
}
diff --git a/src/components/settings/ai/runtime-actions-row.tsx b/src/components/settings/ai/runtime-actions-row.tsx
index 4bdc3e181..ddc043a4f 100644
--- a/src/components/settings/ai/runtime-actions-row.tsx
+++ b/src/components/settings/ai/runtime-actions-row.tsx
@@ -10,6 +10,7 @@ import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Switch } from "@/components/ui/switch";
+import { SettingsCardActions } from "@/components/settings/_card-actions";
import { apiFetchRaw, apiPut } from "@/lib/api/api-fetch";
import { formatDateTime } from "@/lib/format";
import { useTranslations } from "@/lib/i18n/context";
@@ -199,8 +200,54 @@ export function RuntimeActionsRow({
void userProvider;
return (
+ // §12 — the action row is the LAST thing in the block. The raw-data
+ // setting and every status line (last-generated, test / regenerate
+ // result) sit ABOVE it, never under it.