From 5c194cc9b93fabbb87cc78b96b6edc4a470c972a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 11 Sep 2026 10:54:56 +0200 Subject: [PATCH] fix(settings): a fresh managed connection reads as connected again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The managed-record integration projection mapped the ledger's new `unknown` state to `disconnected`, but that mapping runs after the connection map has already established a real credential. A guardian who connected Withings on a child's record saw "Not connected" until the first hourly sync landed, beside a row whose own `connected` field said true — an invitation to revoke and redo the grant they had just made. `state` on this projection reports that a pipe exists, not that it is live; liveness is the sync verdict the card beside the pill already reads. With no ledger row and a credential present, the pipe exists. --- .../record-settings-contract.test.ts | 27 +++++++++++++++++++ src/lib/record-settings/integrations.ts | 20 +++++++++----- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/__tests__/record-settings-contract.test.ts b/src/__tests__/record-settings-contract.test.ts index d1af128c7..43f80ea02 100644 --- a/src/__tests__/record-settings-contract.test.ts +++ b/src/__tests__/record-settings-contract.test.ts @@ -171,6 +171,33 @@ describe("record settings contract", () => { ).toBe("error_reauth"); }); + it("reports a real credential with no ledger row as connected", () => { + // The connection map is the credential: this branch is only reached once + // it says a pipe exists. `unknown` on top of it means the sync ledger + // holds no row yet — an hourly provider connected minutes ago — and this + // projection's `state` is documented as "a pipe exists", not as liveness. + // Calling it "Not connected" invites a guardian to revoke the grant they + // just made. + const connected = { + withings: true, + whoop: false, + fitbit: false, + nightscout: false, + polar: false, + oura: false, + "google-health": false, + strava: false, + } as const; + + expect( + resolveManagedIntegrationState("unknown", connected, "withings"), + ).toBe("connected"); + // No credential still outranks it. + expect(resolveManagedIntegrationState("unknown", connected, "oura")).toBe( + "disconnected", + ); + }); + it("rejects a late response for another record in a second tab", () => { expect(() => assertRecordSettingsResponseForRecord( diff --git a/src/lib/record-settings/integrations.ts b/src/lib/record-settings/integrations.ts index cadea4616..50331d854 100644 --- a/src/lib/record-settings/integrations.ts +++ b/src/lib/record-settings/integrations.ts @@ -5,9 +5,14 @@ import type { /** * A status-ledger row alone cannot prove a connection: integrations without a - * row synthesize a healthy ledger state for the owner Settings dashboard. The + * row synthesize a ledger state the owner Settings dashboard reads. The * managed projection must instead report those as disconnected until its * record carries a real credential or connection row. + * + * This projection's `state` says whether a pipe exists, never whether it is + * live — liveness is `syncHealth.verdict`, and the card beside this pill + * reads that. So the connection map decides first, and only then does the + * ledger state refine the answer. */ export function resolveManagedIntegrationState( state: IntegrationState, @@ -15,9 +20,12 @@ export function resolveManagedIntegrationState( integration: IntegrationKey, ): IntegrationState { if (!connected[integration]) return "disconnected"; - // v1.38.19 — `unknown` is the ledger saying it holds no row. The managed - // projection publishes a closed vocabulary its card paints from, and - // "no history" is not one of its states, so it reads as `disconnected` - // here exactly as it does everywhere else. - return state === "unknown" ? "disconnected" : state; + // v1.38.19 — `unknown` is the ledger saying it holds no row, and past the + // gate above that means a real credential exists whose first sync has not + // landed yet. "A pipe exists" is exactly what this field reports, so it + // reads `connected`. Reporting "Not connected" over a credential the + // guardian created minutes ago invites them to revoke and redo the grant, + // and it would contradict the `connected: true` the same row carries. The + // closed vocabulary this projection publishes stays closed either way. + return state === "unknown" ? "connected" : state; }