Merge branch 'fix/v1437-review' into release/v1.43.7

# Conflicts:
#	src/app/api/analytics/route.ts
This commit is contained in:
Marc-André Bombeck committed 2026-10-20 06:20:42 +02:00
commit 45c027aee2
217 files changed
+3636 -2127

No files matched your search

@@ -40,7 +40,7 @@ import {
export function GrantsGivenCard() {
const { t } = useTranslations();
const { data, isLoading, isError, refetch } = useAccountGrants();
const { data, isLoading, isError, error, refetch } = useAccountGrants();
const revoke = useRevokeGrant();
const given = data?.given ?? [];
@@ -62,6 +62,7 @@ export function GrantsGivenCard() {
)}
{isError && (
<QueryErrorCard
error={error}
title={t("recordSharing.given.loadError")}
onRetry={() => void refetch()}
/>
@@ -54,7 +54,7 @@ import {
*/
export function GrantsReceivedCard() {
const { t } = useTranslations();
const { data, isLoading, isError, refetch } = useAccountGrants();
const { data, isLoading, isError, error, refetch } = useAccountGrants();
const accept = useAcceptGrant();
const renounce = useRenounceGrant();
const switchAccount = useAccountSwitch();
@@ -80,6 +80,7 @@ export function GrantsReceivedCard() {
)}
{isError && (
<QueryErrorCard
error={error}
title={t("recordSharing.received.loadError")}
onRetry={() => void refetch()}
/>
@@ -229,6 +229,7 @@ function ManagedProfileRow({
shows no destructive control for it. */}
{guardians.isError && (
<QueryErrorCard
error={guardians.error}
title={t("recordSharing.managed.rosterLoadError")}
onRetry={() => void guardians.refetch()}
/>
@@ -81,6 +81,7 @@ export function ManagedProfileEditForm({
if (profile.isError) {
return (
<QueryErrorCard
error={profile.error}
title={t("recordSharing.managed.editLoadError")}
onRetry={() => void profile.refetch()}
/>
@@ -88,6 +88,7 @@ export function ManagedProfileHandover({
if (status.isError) {
return (
<QueryErrorCard
error={status.error}
title={t("recordSharing.managed.handover.loadError")}
onRetry={() => void status.refetch()}
/>
@@ -39,7 +39,7 @@ import {
*/
export function RecordActivityCard() {
const { t } = useTranslations();
const { data, isLoading, isError, refetch } = useRecordActivity();
const { data, isLoading, isError, error, refetch } = useRecordActivity();
const entries = data?.entries ?? [];
@@ -88,6 +88,7 @@ export function RecordActivityCard() {
)}
{isError && (
<QueryErrorCard
error={error}
title={t("recordSharing.activity.loadError")}
onRetry={() => void refetch()}
/>
+11 -20
View File
@@ -26,7 +26,7 @@ import { NativeSelect } from "@/components/ui/native-select";
import { QueryErrorCard } from "@/components/ui/query-error-card";
import { SettingsCard } from "@/components/settings/settings-card";
import { SettingsCardHeader } from "@/components/settings/_card-header";
import { apiFetchRaw } from "@/lib/api/api-fetch";
import { apiGet } from "@/lib/api/api-fetch";
import { useTranslations } from "@/lib/i18n/context";
import { queryKeys } from "@/lib/query-keys";
@@ -71,15 +71,11 @@ export function AiInsightsCard({
data: insightsSettings,
isPending: settingsPending,
isError: settingsError,
error: settingsFailure,
refetch: refetchSettings,
} = useQuery({
queryKey: queryKeys.insightsSettings(),
queryFn: async () => {
const res = await apiFetchRaw("/api/insights/settings");
if (!res.ok) throw new Error("insights settings read failed");
const json = await res.json();
return json.data as InsightsSettings;
},
queryFn: () => apiGet<InsightsSettings>("/api/insights/settings"),
enabled: isAuthenticated,
});
@@ -87,15 +83,11 @@ export function AiInsightsCard({
data: userProvider,
isPending: providerPending,
isError: providerError,
error: providerFailure,
refetch: refetchProvider,
} = useQuery({
queryKey: queryKeys.userAiProvider(),
queryFn: async () => {
const res = await apiFetchRaw("/api/user/ai-provider");
if (!res.ok) throw new Error("user AI provider read failed");
const json = await res.json();
return json.data as UserAIProvider;
},
queryFn: () => apiGet<UserAIProvider>("/api/user/ai-provider"),
enabled: isAuthenticated,
});
@@ -103,15 +95,11 @@ export function AiInsightsCard({
data: chainData,
isPending: chainPending,
isError: chainError,
error: chainFailure,
refetch: refetchChain,
} = useQuery({
queryKey: queryKeys.insightsProviderChain(),
queryFn: async () => {
const res = await apiFetchRaw("/api/insights/provider-chain");
if (!res.ok) throw new Error("insights provider chain read failed");
const json = await res.json();
return json.data as ProviderChainData;
},
queryFn: () => apiGet<ProviderChainData>("/api/insights/provider-chain"),
enabled: isAuthenticated,
});
@@ -185,7 +173,10 @@ export function AiInsightsCard({
}
/>
{hasReadError ? (
<QueryErrorCard onRetry={retryReads} />
<QueryErrorCard
error={settingsFailure ?? providerFailure ?? chainFailure}
onRetry={retryReads}
/>
) : readsPending ? (
<Skeleton className="h-40 w-full rounded-lg" />
) : (
+2 -1
View File
@@ -440,6 +440,7 @@ function ApiTokensCard() {
const {
data: tokens,
isError,
error,
isLoading,
refetch,
} = useQuery({
@@ -511,7 +512,7 @@ function ApiTokensCard() {
{isError ? (
// A read failure must not read as "no active tokens" on this
// security surface — the §6 fall-through the token list must avoid.
<QueryErrorCard onRetry={() => refetch()} />
<QueryErrorCard error={error} onRetry={() => refetch()} />
) : isLoading ? (
<div data-testid="settings-api-tokens-loading">
<Skeleton className="h-40 w-full rounded-lg" />
@@ -79,7 +79,10 @@ export function CoachConversationsMemoryCard({
description={t("settings.ai.coachConversations.description")}
/>
{history.isError ? (
<QueryErrorRow onRetry={() => void history.refetch()} />
<QueryErrorRow
error={history.error}
onRetry={() => void history.refetch()}
/>
) : visible.length === 0 ? (
<p className="text-muted-foreground text-sm">
{history.isLoading
@@ -326,6 +326,7 @@ export function CoachMemorySection({
{query.isError && (
<QueryErrorRow
error={query.error}
message={t("settings.ai.coachMemory.loadError")}
onRetry={() => query.refetch()}
/>
@@ -188,6 +188,7 @@ export function CoachRemindersSection({
{query.isError && (
<QueryErrorRow
error={query.error}
message={t("settings.ai.coachReminders.loadError")}
onRetry={() => query.refetch()}
/>
@@ -10,6 +10,7 @@
import { useQuery } from "@tanstack/react-query";
import { apiGet } from "@/lib/api/api-fetch";
import type { GoogleHealthBackfillEndedEntry } from "@/lib/google-health/backfill-progress";
import type { ConnectionHealth } from "@/lib/integrations/connection-health";
import type { MetricFreshnessEntry } from "@/lib/integrations/sync-verdict";
import { queryKeys } from "@/lib/query-keys";
@@ -58,11 +59,7 @@ export interface IntegrationStatusViewModel {
* Google Health: data types whose history backfill ended before the start
* of their history, with where and why.
*/
backfillEnded?: Array<{
type: string;
endsAt: string;
reason: "horizon" | "no_progress";
}>;
backfillEnded?: GoogleHealthBackfillEndedEntry[];
/**
* Polar / Oura / Strava: usable credentials resolve (BYO or the shared
* app). Greys out "Connect" when false.
@@ -73,7 +73,12 @@ export function ManagedIntegrationStatus() {
});
if (statusQuery.isError) {
return <QueryErrorCard onRetry={() => void statusQuery.refetch()} />;
return (
<QueryErrorCard
error={statusQuery.error}
onRetry={() => void statusQuery.refetch()}
/>
);
}
const loading = statusQuery.isLoading || !statusQuery.data;
@@ -1115,7 +1115,12 @@ export function ManagedRecordSettingsSection({
});
if (query.isError) {
return <QueryErrorCard onRetry={() => void query.refetch()} />;
return (
<QueryErrorCard
error={query.error}
onRetry={() => void query.refetch()}
/>
);
}
return (
+4 -2
View File
@@ -93,6 +93,7 @@ function McpConnectionsCard() {
const {
data: connections,
isError,
error,
isLoading,
refetch,
} = useQuery({
@@ -134,7 +135,7 @@ function McpConnectionsCard() {
<div className="space-y-4">
{isError ? (
<QueryErrorCard onRetry={() => refetch()} />
<QueryErrorCard error={error} onRetry={() => refetch()} />
) : isLoading ? (
<Skeleton className="h-24 w-full rounded-lg" />
) : list.length === 0 ? (
@@ -285,6 +286,7 @@ function McpTokensCard() {
const {
data: tokens,
isError,
error,
isLoading,
refetch,
} = useQuery({
@@ -457,7 +459,7 @@ function McpTokensCard() {
{t("settings.activeTokensTitle")}
</p>
{isError ? (
<QueryErrorCard onRetry={() => refetch()} />
<QueryErrorCard error={error} onRetry={() => refetch()} />
) : isLoading ? (
<Skeleton className="h-24 w-full rounded-lg" />
) : activeTokens.length === 0 ? (
@@ -354,6 +354,7 @@ export function ScoreSection() {
{configQuery.isError ? (
<QueryErrorCard
error={configQuery.error}
title={t("settings.sections.score.loadFailed")}
onRetry={() => void configQuery.refetch()}
/>
@@ -444,6 +445,7 @@ export function ScoreSection() {
{analyticsQuery.isError ? (
<QueryErrorRow
error={analyticsQuery.error}
slot="score-config-eligibility-error"
message={t("settings.sections.score.eligibilityFailed")}
onRetry={() => void analyticsQuery.refetch()}
@@ -68,7 +68,7 @@ export function SecurityActivityCard({
const [open, setOpen] = useState(false);
const regionId = useId();
const { data, isLoading, isError, refetch } = useQuery({
const { data, isLoading, isError, error, refetch } = useQuery({
queryKey: queryKeys.securityActivity(),
queryFn: () =>
apiGet<{ events: ActivityRow[] }>("/api/auth/me/security-activity"),
@@ -110,6 +110,7 @@ export function SecurityActivityCard({
{isError && (
<QueryErrorRow
error={error}
message={t("settings.security.activityLoadError")}
onRetry={() => refetch()}
/>
@@ -76,7 +76,7 @@ export function SecuritySessionsCard({
const [keptGrants, setKeptGrants] = useState<KeptGrant[]>([]);
const revokeGrant = useRevokeGrant();
const { data, isLoading, isError, refetch } = useQuery({
const { data, isLoading, isError, error, refetch } = useQuery({
queryKey: queryKeys.sessions(),
queryFn: () => apiGet<{ sessions: SessionRow[] }>("/api/auth/me/sessions"),
enabled: isAuthenticated,
@@ -165,6 +165,7 @@ export function SecuritySessionsCard({
{isError && (
<QueryErrorRow
error={error}
message={t("settings.security.sessionsLoadError")}
onRetry={() => refetch()}
/>
@@ -154,6 +154,7 @@ export function ShareDocumentPicker({
</div>
) : list.isError ? (
<QueryErrorRow
error={list.error}
message={t("documents.list.loadError")}
onRetry={() => list.refetch()}
/>
+2 -1
View File
@@ -111,6 +111,7 @@ function ShareLinksCard() {
const {
data: links,
isError,
error,
isLoading,
refetch,
} = useQuery({
@@ -168,7 +169,7 @@ function ShareLinksCard() {
{isError ? (
// A read failure must not read as "no active links" on a clinical
// sharing surface — that is the §6 fall-through this card must avoid.
<QueryErrorCard onRetry={() => refetch()} />
<QueryErrorCard error={error} onRetry={() => refetch()} />
) : isLoading ? (
<div className="space-y-2" data-testid="share-links-loading">
<Skeleton className="h-24 w-full rounded-lg" />
@@ -40,7 +40,7 @@ export function TrustedDevicesCard({
const queryClient = useQueryClient();
const [status, setStatus] = useState<string | null>(null);
const { data, isLoading, isError, refetch } = useQuery({
const { data, isLoading, isError, error, refetch } = useQuery({
queryKey: queryKeys.trustedDevices(),
queryFn: () =>
apiGet<{ devices: TrustedDeviceRow[] }>("/api/auth/me/trusted-devices"),
@@ -105,6 +105,7 @@ export function TrustedDevicesCard({
{isError && (
<QueryErrorRow
error={error}
message={t("settings.security.trustedDevices.loadError")}
onRetry={() => refetch()}
/>