diff --git a/messages/de.json b/messages/de.json
index 52c9e2293..2a3d40ac2 100644
--- a/messages/de.json
+++ b/messages/de.json
@@ -6855,7 +6855,14 @@
"detail": "Das Token wird nur einmal angezeigt.",
"scopeNote": "Es kann ausschließlich neue Werte in deine eigene Akte eintragen. Es kann deine Daten nicht lesen, nichts ändern oder löschen und kein weiteres Token erstellen. Werte werden als von einem externen Gerät gekennzeichnet, damit du sie später erkennst, und lassen sich jederzeit korrigieren.",
"createFailed": "Das Token konnte nicht erstellt werden. Bitte versuche es erneut.",
- "namePlaceholder": "Name, z. B. Home Assistant"
+ "namePlaceholder": "Name, z. B. Home Assistant",
+ "appleHealth": {
+ "label": "Messwerte in Apple Health schreiben",
+ "createHint": "Die HealthLog-App auf deinem iPhone trägt die Werte dieses Tokens dann in Apple Health ein. Du kannst das später in der Token-Liste unten ändern.",
+ "toggleAria": "Werte von {name} in Apple Health schreiben",
+ "updateFailed": "Die Apple-Health-Einstellung konnte nicht geändert werden. Bitte versuche es erneut.",
+ "unattributed": "Werte, die vor dieser Einstellung gesendet wurden, lassen sich keinem Token zuordnen und bleiben deshalb außerhalb von Apple Health."
+ }
},
"workoutsToken": {
"title": "Training-Tokens",
diff --git a/messages/en.json b/messages/en.json
index 00826c3db..97234ceaf 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -6855,7 +6855,14 @@
"detail": "The token is shown once.",
"scopeNote": "It can only add new readings to your own record. It cannot read your data, change or delete anything, or create another token. Readings are marked as coming from an external device, so you can tell them apart later, and you can still correct them yourself.",
"createFailed": "The token could not be created. Please try again.",
- "namePlaceholder": "Name, e.g. Home Assistant"
+ "namePlaceholder": "Name, e.g. Home Assistant",
+ "appleHealth": {
+ "label": "Write readings to Apple Health",
+ "createHint": "The HealthLog app on your iPhone then adds this token's readings to Apple Health. You can change this later in the token list below.",
+ "toggleAria": "Write readings from {name} to Apple Health",
+ "updateFailed": "The Apple Health setting could not be changed. Please try again.",
+ "unattributed": "Readings sent before this setting existed can't be matched to a token, so they stay out of Apple Health."
+ }
},
"workoutsToken": {
"title": "Workout tokens",
diff --git a/messages/es.json b/messages/es.json
index ecc6d0e7c..3ca0cf9fa 100644
--- a/messages/es.json
+++ b/messages/es.json
@@ -6855,7 +6855,14 @@
"detail": "El token se muestra una sola vez.",
"scopeNote": "Solo puede añadir mediciones nuevas a tu propio historial: no puede leer tus datos, ni modificar o borrar nada, ni crear otro token. Las mediciones se marcan como procedentes de un dispositivo externo, así las distingues más tarde, y puedes corregirlas igualmente.",
"createFailed": "No se pudo crear el token. Inténtalo de nuevo.",
- "namePlaceholder": "Nombre, p. ej. Home Assistant"
+ "namePlaceholder": "Nombre, p. ej. Home Assistant",
+ "appleHealth": {
+ "label": "Escribir mediciones en Apple Health",
+ "createHint": "La app HealthLog de tu iPhone añadirá entonces las mediciones de este token a Apple Health. Puedes cambiarlo más tarde en la lista de tokens de abajo.",
+ "toggleAria": "Escribir las mediciones de {name} en Apple Health",
+ "updateFailed": "No se pudo cambiar el ajuste de Apple Health. Inténtalo de nuevo.",
+ "unattributed": "Las mediciones enviadas antes de que existiera este ajuste no se pueden asociar a ningún token, así que quedan fuera de Apple Health."
+ }
},
"workoutsToken": {
"title": "Tokens de entrenamientos",
diff --git a/messages/fr.json b/messages/fr.json
index 69fbcdfb8..106d1d321 100644
--- a/messages/fr.json
+++ b/messages/fr.json
@@ -6855,7 +6855,14 @@
"detail": "Le jeton n'est affiché qu'une fois.",
"scopeNote": "Il peut uniquement ajouter de nouvelles mesures à votre propre dossier : il ne peut pas lire vos données, ni modifier ou supprimer quoi que ce soit, ni créer un autre jeton. Les mesures sont signalées comme provenant d'un appareil externe, ce qui vous permet de les reconnaître plus tard, et vous pouvez toujours les corriger.",
"createFailed": "Le jeton n'a pas pu être créé. Veuillez réessayer.",
- "namePlaceholder": "Nom, p. ex. Home Assistant"
+ "namePlaceholder": "Nom, p. ex. Home Assistant",
+ "appleHealth": {
+ "label": "Écrire les mesures dans Apple Health",
+ "createHint": "L'app HealthLog de votre iPhone ajoutera alors les mesures de ce jeton à Apple Health. Vous pourrez modifier ce choix plus tard dans la liste des jetons ci-dessous.",
+ "toggleAria": "Écrire les mesures de {name} dans Apple Health",
+ "updateFailed": "Le réglage Apple Health n'a pas pu être modifié. Veuillez réessayer.",
+ "unattributed": "Les mesures envoyées avant l'existence de ce réglage ne peuvent être rattachées à aucun jeton : elles restent donc hors d'Apple Health."
+ }
},
"workoutsToken": {
"title": "Jetons d'entraînements",
diff --git a/messages/it.json b/messages/it.json
index 211211b5a..ea4d45364 100644
--- a/messages/it.json
+++ b/messages/it.json
@@ -6855,7 +6855,14 @@
"detail": "Il token viene mostrato una sola volta.",
"scopeNote": "Può soltanto aggiungere nuove misurazioni alla tua cartella: non può leggere i tuoi dati, modificare o eliminare nulla, né creare un altro token. Le misurazioni vengono contrassegnate come provenienti da un dispositivo esterno, così le riconosci in seguito, e puoi comunque correggerle.",
"createFailed": "Non è stato possibile creare il token. Riprova.",
- "namePlaceholder": "Nome, ad es. Home Assistant"
+ "namePlaceholder": "Nome, ad es. Home Assistant",
+ "appleHealth": {
+ "label": "Scrivi le misurazioni in Apple Health",
+ "createHint": "L'app HealthLog sul tuo iPhone aggiungerà quindi le misurazioni di questo token ad Apple Health. Puoi cambiarlo più tardi nell'elenco dei token qui sotto.",
+ "toggleAria": "Scrivi le misurazioni di {name} in Apple Health",
+ "updateFailed": "Non è stato possibile modificare l'impostazione di Apple Health. Riprova.",
+ "unattributed": "Le misurazioni inviate prima che esistesse questa impostazione non si possono associare a un token, quindi restano fuori da Apple Health."
+ }
},
"workoutsToken": {
"title": "Token per allenamenti",
diff --git a/messages/ko.json b/messages/ko.json
index 5069c1d45..6ec545b8e 100644
--- a/messages/ko.json
+++ b/messages/ko.json
@@ -6855,7 +6855,14 @@
"detail": "토큰은 한 번만 표시돼요.",
"scopeNote": "본인 기록에 새 측정값을 추가하는 것만 할 수 있어요. 데이터를 읽거나, 무언가를 고치거나 지우거나, 다른 토큰을 만들 수는 없어요. 측정값은 외부 기기에서 온 것으로 표시되니 나중에 구분할 수 있고, 언제든 고칠 수 있어요.",
"createFailed": "토큰을 만들지 못했어요. 다시 시도해 주세요.",
- "namePlaceholder": "이름 (예: Home Assistant)"
+ "namePlaceholder": "이름 (예: Home Assistant)",
+ "appleHealth": {
+ "label": "측정값을 Apple Health에 기록",
+ "createHint": "그러면 iPhone의 HealthLog 앱이 이 토큰의 측정값을 Apple Health에 추가해요. 나중에 아래 토큰 목록에서 바꿀 수 있어요.",
+ "toggleAria": "{name}의 측정값을 Apple Health에 기록",
+ "updateFailed": "Apple Health 설정을 바꾸지 못했어요. 다시 시도해 주세요.",
+ "unattributed": "이 설정이 생기기 전에 보낸 측정값은 토큰과 연결할 수 없어서 Apple Health에 기록되지 않아요."
+ }
},
"workoutsToken": {
"title": "운동 토큰",
diff --git a/messages/nl.json b/messages/nl.json
index f091c3c1d..6415f4489 100644
--- a/messages/nl.json
+++ b/messages/nl.json
@@ -6855,7 +6855,14 @@
"detail": "Het token wordt één keer getoond.",
"scopeNote": "Het kan alleen nieuwe metingen aan je eigen dossier toevoegen. Het kan je gegevens niet lezen, niets wijzigen of verwijderen en geen ander token aanmaken. Metingen worden gemarkeerd als afkomstig van een extern apparaat, zodat je ze later kunt onderscheiden, en je kunt ze nog steeds zelf corrigeren.",
"createFailed": "Het token kon niet worden aangemaakt. Probeer het opnieuw.",
- "namePlaceholder": "Naam, bijv. Home Assistant"
+ "namePlaceholder": "Naam, bijv. Home Assistant",
+ "appleHealth": {
+ "label": "Metingen naar Apple Health schrijven",
+ "createHint": "De HealthLog-app op je iPhone zet de metingen van dit token dan in Apple Health. Je kunt dit later wijzigen in de tokenlijst hieronder.",
+ "toggleAria": "Metingen van {name} naar Apple Health schrijven",
+ "updateFailed": "De Apple Health-instelling kon niet worden gewijzigd. Probeer het opnieuw.",
+ "unattributed": "Metingen die zijn verstuurd voordat deze instelling bestond, kunnen niet aan een token worden gekoppeld en blijven daarom buiten Apple Health."
+ }
},
"workoutsToken": {
"title": "Trainingstokens",
diff --git a/messages/pl.json b/messages/pl.json
index 947424424..cb5dc32b7 100644
--- a/messages/pl.json
+++ b/messages/pl.json
@@ -6855,7 +6855,14 @@
"detail": "Token pokazujemy tylko raz.",
"scopeNote": "Może wyłącznie dodawać nowe pomiary do Twojej dokumentacji: nie odczyta danych, niczego nie zmieni ani nie usunie i nie utworzy kolejnego tokenu. Pomiary są oznaczane jako pochodzące z urządzenia zewnętrznego, dzięki czemu później je rozpoznasz, i nadal możesz je poprawić.",
"createFailed": "Nie udało się utworzyć tokenu. Spróbuj ponownie.",
- "namePlaceholder": "Nazwa, np. Home Assistant"
+ "namePlaceholder": "Nazwa, np. Home Assistant",
+ "appleHealth": {
+ "label": "Zapisuj pomiary w Apple Health",
+ "createHint": "Aplikacja HealthLog na Twoim iPhonie doda wtedy pomiary z tego tokenu do Apple Health. Możesz to później zmienić na liście tokenów poniżej.",
+ "toggleAria": "Zapisuj pomiary z {name} w Apple Health",
+ "updateFailed": "Nie udało się zmienić ustawienia Apple Health. Spróbuj ponownie.",
+ "unattributed": "Pomiarów wysłanych, zanim pojawiło się to ustawienie, nie da się przypisać do żadnego tokenu, więc nie trafiają do Apple Health."
+ }
},
"workoutsToken": {
"title": "Tokeny treningów",
diff --git a/messages/pt-BR.json b/messages/pt-BR.json
index 277297c97..aa102f900 100644
--- a/messages/pt-BR.json
+++ b/messages/pt-BR.json
@@ -6855,7 +6855,14 @@
"detail": "O token é exibido uma vez.",
"scopeNote": "Ele só pode adicionar novas leituras ao seu próprio registro. Não pode ler seus dados, alterar ou excluir nada, nem criar outro token. As leituras são marcadas como vindas de um dispositivo externo, para você distingui-las depois, e você ainda pode corrigi-las você mesmo.",
"createFailed": "O token não pôde ser criado. Tente novamente.",
- "namePlaceholder": "Nome, ex.: Home Assistant"
+ "namePlaceholder": "Nome, ex.: Home Assistant",
+ "appleHealth": {
+ "label": "Gravar medições no Apple Health",
+ "createHint": "O app HealthLog no seu iPhone passa a adicionar as medições deste token ao Apple Health. Você pode mudar isso depois na lista de tokens abaixo.",
+ "toggleAria": "Gravar as medições de {name} no Apple Health",
+ "updateFailed": "Não foi possível alterar a configuração do Apple Health. Tente novamente.",
+ "unattributed": "As medições enviadas antes de esta configuração existir não podem ser associadas a um token, por isso ficam fora do Apple Health."
+ }
},
"workoutsToken": {
"title": "Tokens de treino",
diff --git a/src/__tests__/baselines/source-function-lines.json b/src/__tests__/baselines/source-function-lines.json
index 4dcbcf91c..8c6ac48ff 100644
--- a/src/__tests__/baselines/source-function-lines.json
+++ b/src/__tests__/baselines/source-function-lines.json
@@ -1,7 +1,7 @@
{
"$comment": "Production functions over 200 lines, keyed \"file :: name\", and their length. Maintained by src/__tests__/source-size-ratchet.test.ts.",
"count": 355,
- "total": 114773,
+ "total": 114676,
"entries": {
"app/achievements/page.tsx :: AchievementsPage": 206,
"app/api/admin/backups/upload/route.ts :: apiHandler(arg)": 314,
@@ -200,7 +200,7 @@
"components/settings/ai/codex-provider-form.tsx :: CodexProviderForm": 280,
"components/settings/ai/fallback-chain-card.tsx :: FallbackChainCard": 228,
"components/settings/ai/runtime-actions-row.tsx :: RuntimeActionsRow": 282,
- "components/settings/api-section.tsx :: ApiTokensCard": 366,
+ "components/settings/api-section.tsx :: ApiTokensCard": 269,
"components/settings/coach-nudge-card.tsx :: CoachNudgeCard": 251,
"components/settings/coach-prefs-section.tsx :: CoachPrefsSection": 350,
"components/settings/environment-section.tsx :: EnvironmentSection": 666,
diff --git a/src/__tests__/baselines/version-stamp-comments.json b/src/__tests__/baselines/version-stamp-comments.json
index 32b18fa49..0e84d0c5f 100644
--- a/src/__tests__/baselines/version-stamp-comments.json
+++ b/src/__tests__/baselines/version-stamp-comments.json
@@ -1,7 +1,7 @@
{
"$comment": "Comment lines that open with a release version, per file under src/. Maintained by src/__tests__/version-stamp-ratchet.test.ts.",
"count": 2898,
- "total": 9297,
+ "total": 9296,
"entries": {
"__tests__/account-payload-consumer-guard.test.ts": 2,
"__tests__/acting-account-boundary-guard.test.ts": 7,
@@ -1359,7 +1359,7 @@
"components/settings/ai/response-timeout-card.tsx": 1,
"components/settings/ai/shared.ts": 4,
"components/settings/anamnesis-section.tsx": 2,
- "components/settings/api-section.tsx": 2,
+ "components/settings/api-section.tsx": 1,
"components/settings/coach-memory-section.tsx": 3,
"components/settings/coach-nudge-card.tsx": 4,
"components/settings/coach-prefs-section.tsx": 2,
diff --git a/src/components/settings/__tests__/apple-health-mirror-token.test.tsx b/src/components/settings/__tests__/apple-health-mirror-token.test.tsx
new file mode 100644
index 000000000..c06a93b50
--- /dev/null
+++ b/src/components/settings/__tests__/apple-health-mirror-token.test.tsx
@@ -0,0 +1,124 @@
+/**
+ * The Apple Health write-back controls on the API settings (#1272).
+ *
+ * SSR-only, as the rest of this directory: the render pins which token rows
+ * carry the switch and in which state, when the notice about readings that
+ * belong to no token shows, and the write the switch sends is pinned against
+ * a stubbed `fetch`.
+ */
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+import { renderToStaticMarkup } from "react-dom/server";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+
+import { I18nProvider } from "@/lib/i18n/context";
+import { queryKeys } from "@/lib/query-keys";
+
+vi.mock("@/hooks/use-auth", () => ({
+ useAuth: () => ({ isAuthenticated: true }),
+}));
+
+import {
+ AppleHealthMirrorOptIn,
+ AppleHealthMirrorSwitch,
+ UnattributedReadingsNotice,
+ patchTokenMirror,
+} from "../apple-health-mirror-token";
+
+function render(node: React.ReactNode, client = new QueryClient()) {
+ return renderToStaticMarkup(
+
+ {t("settings.apiEndpointAuth")}
+
+ {t("settings.apiEndpointExample")}
+
- {t("settings.apiEndpointAuth")}
-
- {t("settings.apiEndpointExample")}
-
+
+
+
+
+
+
+ {endpoints.map((endpoint) => (
+
+ {t("settings.apiEndpointMethod")}
+
+
+ {t("settings.apiEndpointPath")}
+
+
+ {t("settings.apiEndpointAuth")}
+
+
+ {t("settings.apiEndpointExample")}
+
+
+
+ ))}
+
+ {endpoint.method}
+ {endpoint.path}
+ {endpoint.auth}
+ {endpoint.example}
+
+ {endpoints.map((endpoint) => (
+
+ {endpoint.path}
+ {endpoint.auth}
+
+ {endpoint.example}
+
+
-
-
-
-
-
-
- {endpoints.map((endpoint) => (
-
- {t("settings.apiEndpointMethod")}
-
-
- {t("settings.apiEndpointPath")}
-
-
- {t("settings.apiEndpointAuth")}
-
-
- {t("settings.apiEndpointExample")}
-
-
-
- ))}
-
- {endpoint.method}
- {endpoint.path}
- {endpoint.auth}
- {endpoint.example}
-
- {endpoints.map((endpoint) => (
-
- {endpoint.path}
- {endpoint.auth}
-
- {endpoint.example}
-
- {tok.name}
+
+ {tok.name}
+
{tok.permissions.join(", ")}
@@ -660,6 +567,7 @@ function ApiTokensCard() {
? formatDateTime(tok.lastUsedAt)
: t("settings.tokenNeverUsed")}
| - {t("settings.tokenTableName")} - | -- {t("settings.tokenTablePermissions")} - | -- {t("settings.tokenTableCreated")} - | -- {t("settings.tokenTableLastUsed")} - | -
|---|---|---|---|
| - {tok.name} - | -- {tok.permissions.join(", ")} - | -- {formatDate(tok.createdAt)} - | -- {tok.lastUsedAt - ? formatDateTime(tok.lastUsedAt) - : t("settings.tokenNeverUsed")} - | -
- {tok.name} -
-- - {t("settings.tokenTablePermissions")}: - {" "} - {tok.permissions.join(", ")} -
-- - {t("settings.tokenTableCreated")}: - {" "} - {formatDate(tok.createdAt)} -
-- - {t("settings.tokenTableLastUsed")}: - {" "} - {tok.lastUsedAt - ? formatDateTime(tok.lastUsedAt) - : t("settings.tokenNeverUsed")} -
-| + {t("settings.tokenTableName")} + | ++ {t("settings.tokenTablePermissions")} + | ++ {t("settings.tokenTableCreated")} + | ++ {t("settings.tokenTableLastUsed")} + | +
|---|---|---|---|
|
+ {tok.name}
+ |
+ + {tok.permissions.join(", ")} + | ++ {formatDate(tok.createdAt)} + | ++ {tok.lastUsedAt + ? formatDateTime(tok.lastUsedAt) + : t("settings.tokenNeverUsed")} + | +
{tok.name}
++ + {t("settings.tokenTablePermissions")}: + {" "} + {tok.permissions.join(", ")} +
++ + {t("settings.tokenTableCreated")}: + {" "} + {formatDate(tok.createdAt)} +
++ + {t("settings.tokenTableLastUsed")}: + {" "} + {tok.lastUsedAt + ? formatDateTime(tok.lastUsedAt) + : t("settings.tokenNeverUsed")} +
++ {t("settings.measurementsToken.appleHealth.unattributed")} +
+ ); +} diff --git a/src/lib/query-keys/integrations.ts b/src/lib/query-keys/integrations.ts index 11c0e3b90..732617e1f 100644 --- a/src/lib/query-keys/integrations.ts +++ b/src/lib/query-keys/integrations.ts @@ -5,6 +5,9 @@ */ export const integrationKeys = { tokens: () => ["tokens"] as const, + // How many EXTERNAL readings belong to no measurement token (#1272). Under + // the `["tokens"]` prefix, so a mint or a revoke refreshes it too. + measurementTokenSummary: () => ["tokens", "measurements", "summary"] as const, // v1.22.0 — MCP connector tokens (the dedicated `health:read` scope minted // for the remote MCP endpoint). Distinct key from `tokens()` so the MCP // settings card's mint / revoke mutations invalidate only this list.