| author | |
| committer | |
| log | d10621de5f8f7e3af4831be04c0564a4ae65eade |
| tree | d9d4a0149fb3206c709c258441b4e0648a2e8599 |
| parent | 5fc1c5cb4cfa562552fb0a5fc8278b176a765256 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>6 files changed, 61 insertions(+), 9 deletions(-)
apps/mobile/src/features/cloud/cloudEnvironmentPresentation.test.ts+1-1| ... | @@ -48,7 +48,7 @@ describe("available cloud environment presentation", () => { | ... | @@ -48,7 +48,7 @@ describe("available cloud environment presentation", () => { |
| 48 | }), | 48 | }), |
| 49 | ).toMatchObject({ | 49 | ).toMatchObject({ |
| 50 | connectionState: "unsupported", | 50 | connectionState: "unsupported", |
| 51 | statusText: "Client not supported", | 51 | statusText: expect.stringContaining("Update your app"), |
| 52 | }); | 52 | }); |
| 53 | }); | 53 | }); |
| 54 | 54 |
apps/mobile/src/features/cloud/cloudEnvironmentPresentation.ts+1-1| ... | @@ -27,7 +27,7 @@ export function availableCloudEnvironmentPresentation(input: { | ... | @@ -27,7 +27,7 @@ export function availableCloudEnvironmentPresentation(input: { |
| 27 | connectionError: compatibilityError.message, | 27 | connectionError: compatibilityError.message, |
| 28 | connectionErrorTraceId: null, | 28 | connectionErrorTraceId: null, |
| 29 | connectionState: "unsupported", | 29 | connectionState: "unsupported", |
| 30 | statusText: "Client not supported", | 30 | statusText: compatibilityError.message, |
| 31 | }; | 31 | }; |
| 32 | } | 32 | } |
| 33 | if (input.status?.status === "online") { | 33 | if (input.status?.status === "online") { |
apps/mobile/src/features/connection/CloudEnvironmentRows.tsx+3-5| ... | @@ -2,10 +2,7 @@ import { AppActivityIndicator } from "../../components/AppActivityIndicator"; | ... | @@ -2,10 +2,7 @@ import { AppActivityIndicator } from "../../components/AppActivityIndicator"; |
| 2 | import { ConnectionTraceId } from "./ConnectionTraceId"; | 2 | import { ConnectionTraceId } from "./ConnectionTraceId"; |
| 3 | import { useAuth } from "@clerk/expo"; | 3 | import { useAuth } from "@clerk/expo"; |
| 4 | import { SymbolView } from "../../components/AppSymbol"; | 4 | import { SymbolView } from "../../components/AppSymbol"; |
| 5 | import { | 5 | import { type EnvironmentConnectionPhase } from "@t3tools/client-runtime/connection"; |
| 6 | connectionStatusText, | ||
| 7 | type EnvironmentConnectionPhase, | ||
| 8 | } from "@t3tools/client-runtime/connection"; | ||
| 9 | import { | 6 | import { |
| 10 | type EnvironmentId, | 7 | type EnvironmentId, |
| 11 | type EnvironmentMachineKind, | 8 | type EnvironmentMachineKind, |
| ... | @@ -26,6 +23,7 @@ import { serverEnvironment } from "../../state/server"; | ... | @@ -26,6 +23,7 @@ import { serverEnvironment } from "../../state/server"; |
| 26 | import { availableCloudEnvironmentPresentation } from "../cloud/cloudEnvironmentPresentation"; | 23 | import { availableCloudEnvironmentPresentation } from "../cloud/cloudEnvironmentPresentation"; |
| 27 | import { hasCloudPublicConfig } from "../cloud/publicConfig"; | 24 | import { hasCloudPublicConfig } from "../cloud/publicConfig"; |
| 28 | import { ConnectionStatusDot } from "./ConnectionStatusDot"; | 25 | import { ConnectionStatusDot } from "./ConnectionStatusDot"; |
| 26 | import { environmentStatusText } from "./environmentStatusText"; | ||
| 29 | import { type RelayEnvironmentView, useConnectionController } from "./useConnectionController"; | 27 | import { type RelayEnvironmentView, useConnectionController } from "./useConnectionController"; |
| 30 | 28 | ||
| 31 | interface CloudEnvironmentRowsProps { | 29 | interface CloudEnvironmentRowsProps { |
| ... | @@ -307,7 +305,7 @@ function CloudEnvironmentRowShell(props: { | ... | @@ -307,7 +305,7 @@ function CloudEnvironmentRowShell(props: { |
| 307 | const shouldPulse = isRetrying; | 305 | const shouldPulse = isRetrying; |
| 308 | const statusText = | 306 | const statusText = |
| 309 | props.statusText ?? | 307 | props.statusText ?? |
| 310 | connectionStatusText({ | 308 | environmentStatusText({ |
| 311 | phase: props.connectionState, | 309 | phase: props.connectionState, |
| 312 | error: props.connectionError, | 310 | error: props.connectionError, |
| 313 | traceId: props.connectionErrorTraceId, | 311 | traceId: props.connectionErrorTraceId, |
apps/mobile/src/features/connection/ConnectionEnvironmentRow.tsx+2-2| ... | @@ -1,6 +1,5 @@ | ... | @@ -1,6 +1,5 @@ |
| 1 | import { ConnectionTraceId } from "./ConnectionTraceId"; | 1 | import { ConnectionTraceId } from "./ConnectionTraceId"; |
| 2 | import { SymbolView } from "../../components/AppSymbol"; | 2 | import { SymbolView } from "../../components/AppSymbol"; |
| 3 | import { connectionStatusText } from "@t3tools/client-runtime/connection"; | ||
| 4 | import type { AtomCommandResult } from "@t3tools/client-runtime/state/runtime"; | 3 | import type { AtomCommandResult } from "@t3tools/client-runtime/state/runtime"; |
| 5 | import { type EnvironmentId, resolveEnvironmentMachineKind } from "@t3tools/contracts"; | 4 | import { type EnvironmentId, resolveEnvironmentMachineKind } from "@t3tools/contracts"; |
| 6 | import { useAtomValue } from "@effect/atom-react"; | 5 | import { useAtomValue } from "@effect/atom-react"; |
| ... | @@ -20,12 +19,13 @@ import type { ConnectedEnvironmentSummary } from "../../state/remote-runtime-typ | ... | @@ -20,12 +19,13 @@ import type { ConnectedEnvironmentSummary } from "../../state/remote-runtime-typ |
| 20 | import { serverEnvironment } from "../../state/server"; | 19 | import { serverEnvironment } from "../../state/server"; |
| 21 | import { ConnectionFormField } from "./ConnectionFormField"; | 20 | import { ConnectionFormField } from "./ConnectionFormField"; |
| 22 | import { ConnectionStatusDot } from "./ConnectionStatusDot"; | 21 | import { ConnectionStatusDot } from "./ConnectionStatusDot"; |
| 22 | import { environmentStatusText } from "./environmentStatusText"; | ||
| 23 | 23 | ||
| 24 | function connectionStatusLabel(environment: ConnectedEnvironmentSummary): string | null { | 24 | function connectionStatusLabel(environment: ConnectedEnvironmentSummary): string | null { |
| 25 | if (!environment.isEnabled && environment.connectionState !== "unsupported") { | 25 | if (!environment.isEnabled && environment.connectionState !== "unsupported") { |
| 26 | return "Off"; | 26 | return "Off"; |
| 27 | } | 27 | } |
| 28 | return connectionStatusText({ | 28 | return environmentStatusText({ |
| 29 | phase: environment.connectionState, | 29 | phase: environment.connectionState, |
| 30 | error: environment.connectionError, | 30 | error: environment.connectionError, |
| 31 | traceId: environment.connectionErrorTraceId, | 31 | traceId: environment.connectionErrorTraceId, |
apps/mobile/src/features/connection/environmentStatusText.test.ts created+39| ... | @@ -0,0 +1,39 @@ | ||
| 1 | import { EnvironmentId, ORCHESTRATION_PROTOCOL_VERSION } from "@t3tools/contracts"; | ||
| 2 | import { describe, expect, it } from "vite-plus/test"; | ||
| 3 | |||
| 4 | import { orchestrationProtocolCompatibilityError } from "@t3tools/client-runtime/connection"; | ||
| 5 | |||
| 6 | import { environmentStatusText } from "./environmentStatusText"; | ||
| 7 | |||
| 8 | function compatibilityMessage(serverProtocolVersion: number): string { | ||
| 9 | const error = orchestrationProtocolCompatibilityError({ | ||
| 10 | environmentId: EnvironmentId.make("environment-preview"), | ||
| 11 | label: "Studio Mac", | ||
| 12 | platform: { os: "darwin", arch: "arm64" }, | ||
| 13 | serverVersion: "1.0.0", | ||
| 14 | orchestrationProtocolVersion: serverProtocolVersion, | ||
| 15 | capabilities: { repositoryIdentity: true }, | ||
| 16 | }); | ||
| 17 | if (error === null) throw new Error("expected an incompatible protocol"); | ||
| 18 | return error.message; | ||
| 19 | } | ||
| 20 | |||
| 21 | describe("environment status text", () => { | ||
| 22 | it.each([ | ||
| 23 | ["a newer server", ORCHESTRATION_PROTOCOL_VERSION + 1, "Update your app"], | ||
| 24 | ["an older server", ORCHESTRATION_PROTOCOL_VERSION - 1, "Update T3 Code on Studio Mac"], | ||
| 25 | ])("tells the user what to update for %s", (_, serverProtocolVersion, guidance) => { | ||
| 26 | const text = environmentStatusText({ | ||
| 27 | phase: "unsupported", | ||
| 28 | error: compatibilityMessage(serverProtocolVersion), | ||
| 29 | traceId: null, | ||
| 30 | }); | ||
| 31 | expect(text).toContain(guidance); | ||
| 32 | }); | ||
| 33 | |||
| 34 | it("falls back to the generic label when there is no detail", () => { | ||
| 35 | expect(environmentStatusText({ phase: "unsupported", error: null, traceId: null })).toBe( | ||
| 36 | "Client not supported", | ||
| 37 | ); | ||
| 38 | }); | ||
| 39 | }); | ||
apps/mobile/src/features/connection/environmentStatusText.ts created+15| ... | @@ -0,0 +1,15 @@ | ||
| 1 | import { | ||
| 2 | connectionStatusText, | ||
| 3 | type EnvironmentConnectionPresentation, | ||
| 4 | } from "@t3tools/client-runtime/connection"; | ||
| 5 | |||
| 6 | /** | ||
| 7 | * Status line for mobile environment rows. Mobile has no tooltip like web, so | ||
| 8 | * an unsupported environment shows its compatibility detail, which says | ||
| 9 | * whether to update the app or the host. | ||
| 10 | */ | ||
| 11 | export function environmentStatusText(connection: EnvironmentConnectionPresentation): string { | ||
| 12 | return connection.phase === "unsupported" && connection.error !== null | ||
| 13 | ? connection.error | ||
| 14 | : connectionStatusText(connection); | ||
| 15 | } | ||