History

Merge upstream/main: Effect-service refactors

Resolve conflicts from upstream's Effect-service normalization batch while preserving the WSL parallel-backend feature set. Key resolutions: - ipc/methods/window.ts: adopt the DesktopIpc.makeIpcMethod / DesktopIpc.makeSyncIpcMethod namespace convention; keep our plural getLocalEnvironmentBootstraps and the getLocalEnvironmentBearerToken method. - app/DesktopState.ts: adopt upstream's Context.Service class with backendReady + quitting. The window keeps its own backendReadyRef, so state.backendReady is unused but harmless. - app/DesktopObservability.ts: keep our per-instance DesktopBackendOutputLogFactory (per-instance backend logs are required for parallel backends). Drop upstream's newly-extracted single-instance DesktopBackendOutputLog.ts: zero consumers in this tree, fully superseded by the factory (primary maps to forInstance(primary), same server-child.log path). - backend/DesktopBackendManager.test.ts: keep our factory-based harness. Upstream's side provisioned the removed DesktopBackendManager.layer service model (DesktopState / DesktopWindow / DesktopBackendConfiguration deps), which no longer exists in this tree. - Adapt DesktopObservability.ts + ipc/methods/wsl.test.ts to upstream's removal of the standalone DesktopEnvironmentShape export: use DesktopEnvironment.DesktopEnvironment["Service"] (the normalized form).

Josh Gratton committed 4 months ago

Commit 8e3012d2 parents 2a3cdd5 9544e72181 files · +3745 −2756Browse files

Unified · Split

181 files
  1. .github/workflows/mobile-eas-preview.yml +2 −0
  2. apps/desktop/src/app/DesktopAppIdentity.test.ts +2 −2
  3. apps/desktop/src/app/DesktopAppIdentity.ts +4 −6
  4. apps/desktop/src/app/DesktopAssets.ts +7 −8
  5. apps/desktop/src/app/DesktopClerk.test.ts +8 −9
  6. apps/desktop/src/app/DesktopClerk.ts +10 −11
  7. apps/desktop/src/app/DesktopEnvironment.ts +50 −55
  8. apps/desktop/src/app/DesktopLifecycle.ts +89 −120
  9. apps/desktop/src/app/DesktopObservability.ts +3 −3
  10. apps/desktop/src/app/DesktopShutdown.ts +35 −0
  11. apps/desktop/src/app/DesktopState.ts +12 −12
  12. apps/desktop/src/ipc/DesktopIpc.ts +16 −14
  13. apps/desktop/src/ipc/methods/clientSettings.ts +3 −3
  14. apps/desktop/src/ipc/methods/connectionCatalog.ts +4 −4
  15. apps/desktop/src/ipc/methods/preview.ts +23 −23
  16. apps/desktop/src/ipc/methods/serverExposure.ts +5 −5
  17. apps/desktop/src/ipc/methods/sshEnvironment.ts +9 −9
  18. apps/desktop/src/ipc/methods/updates.ts +6 −6
  19. apps/desktop/src/ipc/methods/window.ts +9 −9
  20. apps/desktop/src/ipc/methods/wsl.test.ts +1 −1
  21. apps/desktop/src/main.ts +3 −4
  22. apps/desktop/src/preview/Manager.test.ts +2 −2
  23. apps/desktop/src/shell/DesktopShellEnvironment.test.ts +1 −1
  24. apps/desktop/src/updates/DesktopUpdates.ts +1 −1
  25. apps/desktop/src/window/DesktopWindow.test.ts +6 −6
  26. apps/mobile/.swiftlint.yml +1 −0
  27. apps/mobile/global.css +21 −1
  28. apps/mobile/modules/t3-composer-editor/ios/T3ComposerEditorView.swift +27 −4
  29. apps/mobile/src/app/+not-found.tsx +2 −2
  30. apps/mobile/src/app/connections/index.tsx +1 −1
  31. apps/mobile/src/app/connections/new.tsx +5 −5
  32. apps/mobile/src/app/new/index.tsx +5 −7
  33. apps/mobile/src/app/settings/environments.tsx +11 −11
  34. apps/mobile/src/app/settings/index.tsx +7 −7
  35. apps/mobile/src/components/AppText.tsx +1 −1
  36. apps/mobile/src/components/BrandMark.tsx +3 −6
  37. apps/mobile/src/components/ComposerToolbarTrigger.tsx +1 −1
  38. apps/mobile/src/components/ControlPill.tsx +1 −1
  39. apps/mobile/src/components/EmptyState.tsx +1 −3
  40. apps/mobile/src/components/ErrorBanner.tsx +1 −1
  41. apps/mobile/src/components/StatusPill.tsx +1 −1
  42. apps/mobile/src/features/archive/ArchivedThreadsScreen.tsx +11 −9
  43. apps/mobile/src/features/cloud/CloudWaitlistEnrollment.tsx +8 −12
  44. apps/mobile/src/features/connection/ConnectionEnvironmentRow.tsx +9 −12
  45. apps/mobile/src/features/connection/ConnectionSheetButton.tsx +1 −1
  46. apps/mobile/src/features/connection/EnvironmentConnectionNotice.tsx +3 −3
  47. apps/mobile/src/features/files/FileMarkdownPreview.tsx +3 −4
  48. apps/mobile/src/features/files/FileTreeBrowser.tsx +6 −8
  49. apps/mobile/src/features/files/SourceFileSurface.tsx +15 −9
  50. apps/mobile/src/features/files/ThreadFilesRouteScreen.tsx +8 −7
  51. apps/mobile/src/features/files/WorkspaceFileImagePreview.tsx +2 −2
  52. apps/mobile/src/features/files/WorkspaceFileWebPreview.tsx +3 −5
  53. apps/mobile/src/features/files/nativeSourceFileAdapter.test.ts +20 −0
  54. apps/mobile/src/features/files/nativeSourceFileAdapter.ts +10 −8
  55. apps/mobile/src/features/home/HomeHeader.tsx +3 −2
  56. apps/mobile/src/features/home/HomeScreen.tsx +7 −7
  57. apps/mobile/src/features/home/thread-swipe-actions.tsx +1 −1
  58. apps/mobile/src/features/projects/AddProjectScreen.tsx +11 −11
  59. apps/mobile/src/features/review/ReviewCommentComposerSheet.tsx +8 −8
  60. apps/mobile/src/features/review/ReviewSheet.tsx +19 −18
  61. apps/mobile/src/features/review/nativeReviewDiffAdapter.ts +7 −6
  62. apps/mobile/src/features/review/reviewDiffRendering.tsx +14 −5
  63. apps/mobile/src/features/terminal/NativeTerminalSurface.tsx +6 −5
  64. apps/mobile/src/features/terminal/ThreadTerminalPanel.tsx +3 −3
  65. apps/mobile/src/features/terminal/ThreadTerminalRouteScreen.tsx +3 −2
  66. apps/mobile/src/features/threads/ComposerCommandPopover.tsx +13 −4
  67. apps/mobile/src/features/threads/GitActionProgressOverlay.tsx +2 −2
  68. apps/mobile/src/features/threads/NewTaskDraftScreen.tsx +2 −1
  69. apps/mobile/src/features/threads/PendingApprovalCard.tsx +1 −1
  70. apps/mobile/src/features/threads/PendingUserInputCard.tsx +4 −4
  71. apps/mobile/src/features/threads/ThreadComposer.tsx +5 −6
  72. apps/mobile/src/features/threads/ThreadFeed.tsx +12 −14
  73. apps/mobile/src/features/threads/ThreadNavigationDrawer.tsx +5 −7
  74. apps/mobile/src/features/threads/ThreadRouteScreen.tsx +3 −2
  75. apps/mobile/src/features/threads/git/GitBranchesSheet.tsx +10 −12
  76. apps/mobile/src/features/threads/git/GitCommitSheet.tsx +18 −18
  77. apps/mobile/src/features/threads/git/GitConfirmSheet.tsx +3 −3
  78. apps/mobile/src/features/threads/git/GitOverviewSheet.tsx +3 −3
  79. apps/mobile/src/features/threads/git/gitSheetComponents.tsx +5 −5
  80. apps/mobile/src/features/threads/thread-work-log.tsx +3 −3
  81. apps/mobile/src/lib/typography.test.ts +23 −0
  82. apps/mobile/src/lib/typography.ts +22 −0
  83. apps/mobile/src/native/T3ComposerEditor.ios.tsx +9 −2
  84. apps/mobile/src/native/T3ComposerEditor.tsx +2 −2
  85. apps/server/src/auth/EnvironmentAuth.test.ts +10 −9
  86. apps/server/src/auth/EnvironmentAuthAdmin.test.ts +6 −7
  87. apps/server/src/auth/EnvironmentAuthPolicy.test.ts +7 −6
  88. apps/server/src/auth/PairingGrantStore.test.ts +6 −7
  89. apps/server/src/auth/SessionStore.test.ts +8 −9
  90. apps/server/src/bin.test.ts +11 −14
  91. apps/server/src/cli/auth.ts +3 −3
  92. apps/server/src/cli/config.ts +12 −20
  93. apps/server/src/cli/connect.ts +9 −9
  94. apps/server/src/cli/project.ts +21 −14
  95. apps/server/src/config.ts +83 −76
  96. apps/server/src/environment/Layers/ServerEnvironment.test.ts +8 −10
  97. apps/server/src/keybindings.test.ts +48 −55
  98. apps/server/src/keybindings.ts +63 −67
  99. apps/server/src/mcp/McpHttpServer.test.ts +9 −1
  100. apps/server/src/mcp/PreviewAutomationBroker.test.ts +122 −20
  101. apps/server/src/mcp/PreviewAutomationBroker.ts +40 −25
  102. apps/server/src/provider/Layers/ProviderRegistry.test.ts +116 −76
  103. apps/server/src/provider/providerUpdateSettings.ts +2 −2
  104. apps/server/src/server.test.ts +95 −100
  105. apps/server/src/server.ts +17 −17
  106. apps/server/src/serverLifecycleEvents.test.ts +3 −3
  107. apps/server/src/serverLifecycleEvents.ts +34 −37
  108. apps/server/src/serverRuntimeStartup.test.ts +32 −42
  109. apps/server/src/serverRuntimeStartup.ts +53 −48
  110. apps/server/src/serverRuntimeState.ts +3 −3
  111. apps/server/src/serverSettings.test.ts +19 −17
  112. apps/server/src/serverSettings.ts +56 −57
  113. apps/server/src/vcs/GitVcsDriverCore.test.ts +73 −0
  114. apps/server/src/vcs/GitVcsDriverCore.ts +25 −6
  115. apps/server/src/ws.ts +2 −2
  116. apps/web/src/components/ChatView.tsx +13 −120
  117. apps/web/src/components/DiffPanel.tsx +436 −345
  118. apps/web/src/components/DiffPanelShell.tsx +2 −8
  119. apps/web/src/components/diffs/AnnotatableFileDiff.tsx +209 −2
  120. apps/web/src/components/preview/PreviewAutomationOwner.test.ts +2 −2
  121. apps/web/src/components/preview/PreviewAutomationOwner.tsx +86 −99
  122. apps/web/src/components/preview/previewAutomationRequestConsumer.test.ts +81 −0
  123. apps/web/src/components/preview/previewAutomationRequestConsumer.ts +83 −0
  124. apps/web/src/diffPanelStore.test.ts +68 −0
  125. apps/web/src/diffPanelStore.ts +139 −0
  126. apps/web/src/diffRouteSearch.test.ts +0 −74
  127. apps/web/src/diffRouteSearch.ts +0 −39
  128. apps/web/src/environments/primary/httpClient.ts +5 −8
  129. apps/web/src/index.css +2 −1
  130. apps/web/src/lib/baseRefChoices.test.ts +52 −0
  131. apps/web/src/lib/baseRefChoices.ts +61 −0
  132. apps/web/src/lib/diffRendering.test.ts +54 −1
  133. apps/web/src/lib/diffRendering.ts +41 −1
  134. apps/web/src/lib/runtime.ts +6 −8
  135. apps/web/src/rightPanelStore.test.ts +0 −11
  136. apps/web/src/rightPanelStore.ts +0 −10
  137. apps/web/src/routes/_chat.$environmentId.$threadId.tsx +1 −6
  138. infra/relay/alchemy.run.ts +3 −3
  139. infra/relay/src/Config.ts +19 −14
  140. infra/relay/src/agentActivity/AgentActivityPublisher.test.ts +2 −2
  141. infra/relay/src/agentActivity/AgentActivityRows.ts +2 −2
  142. infra/relay/src/agentActivity/ApnsClient.ts +6 −6
  143. infra/relay/src/agentActivity/ApnsDeliveries.test.ts +2 −2
  144. infra/relay/src/agentActivity/DeliveryAttempts.test.ts +13 −13
  145. infra/relay/src/agentActivity/DeliveryAttempts.ts +2 −2
  146. infra/relay/src/agentActivity/Devices.test.ts +13 −7
  147. infra/relay/src/agentActivity/Devices.ts +2 −2
  148. infra/relay/src/agentActivity/LiveActivities.test.ts +9 −5
  149. infra/relay/src/agentActivity/LiveActivities.ts +2 −2
  150. infra/relay/src/agentActivity/MobileRegistrations.test.ts +3 −3
  151. infra/relay/src/auth/DpopProofs.test.ts +9 −5
  152. infra/relay/src/auth/DpopProofs.ts +25 −28
  153. infra/relay/src/auth/DpopProofs.verifyAndConsume.test.ts +3 −3
  154. infra/relay/src/auth/RelayTokens.test.ts +1 −3
  155. infra/relay/src/auth/RelayTokens.ts +36 −37
  156. infra/relay/src/db.ts +6 −5
  157. infra/relay/src/environments/EnvironmentConnector.test.ts +5 −5
  158. infra/relay/src/environments/EnvironmentConnector.ts +15 −15
  159. infra/relay/src/environments/EnvironmentCredentials.test.ts +5 −5
  160. infra/relay/src/environments/EnvironmentCredentials.ts +18 −20
  161. infra/relay/src/environments/EnvironmentLinker.test.ts +3 −3
  162. infra/relay/src/environments/EnvironmentLinker.ts +19 −20
  163. infra/relay/src/environments/EnvironmentLinks.test.ts +16 −8
  164. infra/relay/src/environments/EnvironmentLinks.ts +40 −41
  165. infra/relay/src/environments/EnvironmentPublishSignatures.test.ts +2 −2
  166. infra/relay/src/environments/EnvironmentPublishSignatures.ts +8 −10
  167. infra/relay/src/environments/ManagedEndpointAllocations.ts +26 −28
  168. infra/relay/src/environments/ManagedEndpointProvider.test.ts +3 −3
  169. infra/relay/src/environments/ManagedEndpointProvider.ts +69 −63
  170. infra/relay/src/http/Api.test.ts +2 −2
  171. infra/relay/src/http/Api.ts +8 −5
  172. infra/relay/src/observability.test.ts +2 −2
  173. infra/relay/src/worker.ts +4 −9
  174. packages/client-runtime/src/state/preview.ts +4 −0
  175. packages/contracts/src/git.ts +2 −0
  176. packages/contracts/src/ipc.ts +2 −1
  177. packages/contracts/src/previewAutomation.ts +6 −1
  178. packages/contracts/src/relay.ts +11 −15
  179. packages/contracts/src/review.ts +1 −0
  180. packages/contracts/src/rpc.ts +3 −2
  181. pnpm-lock.yaml +250 −252
.github/workflows/mobile-eas-preview.yml+2−0
@@ -2,10 +2,12 @@ name: Mobile EAS Preview · 1 unmodified line
22
33 on:
44 pull_request:
5+ types: [opened, reopened, synchronize, labeled, unlabeled]
56
67 jobs:
78 preview:
89 name: EAS Preview
10+ if: contains(github.event.pull_request.labels.*.name, '🚀 Mobile Continuous Deployment')
911 runs-on: blacksmith-8vcpu-ubuntu-2404
1012 permissions:
1113 contents: read
apps/desktop/src/app/DesktopAppIdentity.test.ts+2−2
@@ -63,7 +63,7 @@ const makeElectronAppLayer = (calls: ElectronAppCalls) => · 62 unmodified lines
6363 }),
6464 appendCommandLineSwitch: () => Effect.void,
6565 on: () => Effect.void,
66− } satisfies ElectronApp.ElectronAppShape);
66+ } satisfies ElectronApp.ElectronApp["Service"]);
6767
6868 const makeAssetsLayer = (png: Option.Option<string>) =>
6969 Layer.succeed(DesktopAssets.DesktopAssets, {
@@ -73,7 +73,7 @@ const makeAssetsLayer = (png: Option.Option<string>) => · 3 unmodified lines
7373 png,
7474 }),
7575 resolveResourcePath: () => Effect.succeed(Option.none()),
76− } satisfies DesktopAssets.DesktopAssetsShape);
76+ } satisfies DesktopAssets.DesktopAssets["Service"]);
7777
7878 const makeEnvironmentLayer = (overrides: TestEnvironmentInput = {}) => {
7979 const { env, ...environmentOverrides } = overrides;
apps/desktop/src/app/DesktopAppIdentity.ts+4−6
@@ -18,14 +18,12 @@ const AppPackageMetadata = Schema.Struct({ · 17 unmodified lines
1818 });
1919 const decodeAppPackageMetadata = Schema.decodeEffect(Schema.fromJsonString(AppPackageMetadata));
2020
21−export interface DesktopAppIdentityShape {
22− readonly resolveUserDataPath: Effect.Effect<string>;
23− readonly configure: Effect.Effect<void>;
24−}
25−
2621 export class DesktopAppIdentity extends Context.Service<
2722 DesktopAppIdentity,
28− DesktopAppIdentityShape
23+ {
24+ readonly resolveUserDataPath: Effect.Effect<string>;
25+ readonly configure: Effect.Effect<void>;
26+ }
2927 >()("@t3tools/desktop/app/DesktopAppIdentity") {}
3028
3129 const normalizeCommitHash = (value: string): Option.Option<string> => {
apps/desktop/src/app/DesktopAssets.ts+7−8
@@ -12,14 +12,13 @@ export interface DesktopIconPaths { · 11 unmodified lines
1212 readonly png: Option.Option<string>;
1313 }
1414
15−export interface DesktopAssetsShape {
16− readonly iconPaths: Effect.Effect<DesktopIconPaths>;
17− readonly resolveResourcePath: (fileName: string) => Effect.Effect<Option.Option<string>>;
18−}
19−
20−export class DesktopAssets extends Context.Service<DesktopAssets, DesktopAssetsShape>()(
21− "@t3tools/desktop/app/DesktopAssets",
22−) {}
15+export class DesktopAssets extends Context.Service<
16+ DesktopAssets,
17+ {
18+ readonly iconPaths: Effect.Effect<DesktopIconPaths>;
19+ readonly resolveResourcePath: (fileName: string) => Effect.Effect<Option.Option<string>>;
20+ }
21+>()("@t3tools/desktop/app/DesktopAssets") {}
2322
2423 const resolveResourcePath = Effect.fn("desktop.assets.resolveResourcePath")(function* (
2524 fileName: string,
apps/desktop/src/app/DesktopClerk.test.ts+8−9
@@ -21,10 +21,6 @@ vi.mock("@clerk/electron/storage", () => ({ · 20 unmodified lines
2121 storage: storageMock,
2222 }));
2323
24−import {
25− createDesktopClerkBridge,
26− resolveDesktopClerkFrontendApiHostname,
27−} from "./DesktopClerk.ts";
2824 import * as DesktopClerk from "./DesktopClerk.ts";
2925 import * as DesktopEnvironment from "./DesktopEnvironment.ts";
3026
@@ -32,9 +28,12 @@ describe("DesktopClerk", () => { · 1 unmodified line
3228 it("derives the Clerk Frontend API hostname used by the desktop CSP", () => {
3329 const publishableKey = `pk_test_${btoa("clerk.t3.codes$")}`;
3430
35− assert.equal(resolveDesktopClerkFrontendApiHostname(publishableKey), "clerk.t3.codes");
36− assert.equal(resolveDesktopClerkFrontendApiHostname(""), undefined);
37− assert.equal(resolveDesktopClerkFrontendApiHostname("invalid"), undefined);
31+ assert.equal(
32+ DesktopClerk.resolveDesktopClerkFrontendApiHostname(publishableKey),
33+ "clerk.t3.codes",
34+ );
35+ assert.equal(DesktopClerk.resolveDesktopClerkFrontendApiHostname(""), undefined);
36+ assert.equal(DesktopClerk.resolveDesktopClerkFrontendApiHostname("invalid"), undefined);
3837 });
3938
4039 it.effect("acquires and releases the SDK bridge with the layer", () => {
@@ -44,7 +43,7 @@ describe("DesktopClerk", () => { · 3 unmodified lines
4443 const environment = DesktopEnvironment.DesktopEnvironment.of({
4544 stateDir: "/tmp/t3-state",
4645 isDevelopment: true,
47− } as unknown as DesktopEnvironment.DesktopEnvironmentShape);
46+ } as unknown as DesktopEnvironment.DesktopEnvironment["Service"]);
4847
4948 return Effect.gen(function* () {
5049 yield* Effect.scoped(
@@ -78,7 +77,7 @@ describe("DesktopClerk", () => { · 27 unmodified lines
7877 storageMock.mockReturnValue(storageAdapter);
7978 createClerkBridgeMock.mockReturnValue(bridge);
8079
81− assert.equal(createDesktopClerkBridge("/tmp/t3-state", isDevelopment), bridge);
80+ assert.equal(DesktopClerk.createDesktopClerkBridge("/tmp/t3-state", isDevelopment), bridge);
8281 assert.deepEqual(storageMock.mock.calls, [[{ path: "/tmp/t3-state" }]]);
8382 assert.deepEqual(createClerkBridgeMock.mock.calls, [
8483 [
apps/desktop/src/app/DesktopClerk.ts+10−11
@@ -14,17 +14,16 @@ import * as DesktopEnvironment from "./DesktopEnvironment.ts"; · 13 unmodified lines
1414
1515 declare const __T3CODE_BUILD_CLERK_PUBLISHABLE_KEY__: string | undefined;
1616
17−export interface DesktopClerkShape {
18− readonly configure: Effect.Effect<
19− void,
20− never,
21− ElectronApp.ElectronApp | ElectronWindow.ElectronWindow | Scope.Scope
22− >;
23−}
24−
25−export class DesktopClerk extends Context.Service<DesktopClerk, DesktopClerkShape>()(
26− "@t3tools/desktop/app/DesktopClerk",
27−) {}
17+export class DesktopClerk extends Context.Service<
18+ DesktopClerk,
19+ {
20+ readonly configure: Effect.Effect<
21+ void,
22+ never,
23+ ElectronApp.ElectronApp | ElectronWindow.ElectronWindow | Scope.Scope
24+ >;
25+ }
26+>()("@t3tools/desktop/app/DesktopClerk") {}
2827
2928 export function resolveDesktopClerkFrontendApiHostname(
3029 publishableKey: string | undefined,
apps/desktop/src/app/DesktopEnvironment.ts+50−55
@@ -11,10 +11,7 @@ import * as Layer from "effect/Layer"; · 10 unmodified lines
1111 import * as Option from "effect/Option";
1212 import * as Path from "effect/Path";
1313
14−import {
15− type DesktopSettings,
16− resolveDefaultDesktopSettings,
17−} from "../settings/DesktopAppSettings.ts";
14+import * as DesktopAppSettings from "../settings/DesktopAppSettings.ts";
1815 import * as DesktopConfig from "./DesktopConfig.ts";
1916 import { isNightlyDesktopVersion } from "../updates/updateChannels.ts";
2017
@@ -30,55 +27,53 @@ export interface MakeDesktopEnvironmentInput { · 9 unmodified lines
3027 readonly runningUnderArm64Translation: boolean;
3128 }
3229
33−export interface DesktopEnvironmentShape {
34− readonly path: Path.Path;
35− readonly dirname: string;
36− readonly platform: NodeJS.Platform;
37− readonly processArch: string;
38− readonly isPackaged: boolean;
39− readonly isDevelopment: boolean;
40− readonly appVersion: string;
41− readonly appPath: string;
42− readonly resourcesPath: string;
43− readonly homeDirectory: string;
44− readonly appDataDirectory: string;
45− readonly baseDir: string;
46− readonly stateDir: string;
47− readonly desktopSettingsPath: string;
48− readonly clientSettingsPath: string;
49− readonly savedEnvironmentRegistryPath: string;
50− readonly serverSettingsPath: string;
51− readonly logDir: string;
52− readonly browserArtifactsDir: string;
53− readonly rootDir: string;
54− readonly appRoot: string;
55− readonly backendEntryPath: string;
56− readonly backendCwd: string;
57− readonly preloadPath: string;
58− readonly appUpdateYmlPath: string;
59− readonly devServerUrl: Option.Option<URL>;
60− readonly devRemoteT3ServerEntryPath: Option.Option<string>;
61− readonly configuredBackendPort: Option.Option<number>;
62− readonly commitHashOverride: Option.Option<string>;
63− readonly otlpTracesUrl: Option.Option<string>;
64− readonly otlpExportIntervalMs: number;
65− readonly branding: DesktopAppBranding;
66− readonly displayName: string;
67− readonly appUserModelId: string;
68− readonly linuxDesktopEntryName: string;
69− readonly linuxWmClass: string;
70− readonly userDataDirName: string;
71− readonly legacyUserDataDirName: string;
72− readonly defaultDesktopSettings: DesktopSettings;
73− readonly runtimeInfo: DesktopRuntimeInfo;
74− readonly resolvePickFolderDefaultPath: (rawOptions: unknown) => Option.Option<string>;
75− readonly resolveResourcePathCandidates: (fileName: string) => readonly string[];
76− readonly developmentDockIconPath: string;
77−}
78−
7930 export class DesktopEnvironment extends Context.Service<
8031 DesktopEnvironment,
81− DesktopEnvironmentShape
32+ {
33+ readonly path: Path.Path;
34+ readonly dirname: string;
35+ readonly platform: NodeJS.Platform;
36+ readonly processArch: string;
37+ readonly isPackaged: boolean;
38+ readonly isDevelopment: boolean;
39+ readonly appVersion: string;
40+ readonly appPath: string;
41+ readonly resourcesPath: string;
42+ readonly homeDirectory: string;
43+ readonly appDataDirectory: string;
44+ readonly baseDir: string;
45+ readonly stateDir: string;
46+ readonly desktopSettingsPath: string;
47+ readonly clientSettingsPath: string;
48+ readonly savedEnvironmentRegistryPath: string;
49+ readonly serverSettingsPath: string;
50+ readonly logDir: string;
51+ readonly browserArtifactsDir: string;
52+ readonly rootDir: string;
53+ readonly appRoot: string;
54+ readonly backendEntryPath: string;
55+ readonly backendCwd: string;
56+ readonly preloadPath: string;
57+ readonly appUpdateYmlPath: string;
58+ readonly devServerUrl: Option.Option<URL>;
59+ readonly devRemoteT3ServerEntryPath: Option.Option<string>;
60+ readonly configuredBackendPort: Option.Option<number>;
61+ readonly commitHashOverride: Option.Option<string>;
62+ readonly otlpTracesUrl: Option.Option<string>;
63+ readonly otlpExportIntervalMs: number;
64+ readonly branding: DesktopAppBranding;
65+ readonly displayName: string;
66+ readonly appUserModelId: string;
67+ readonly linuxDesktopEntryName: string;
68+ readonly linuxWmClass: string;
69+ readonly userDataDirName: string;
70+ readonly legacyUserDataDirName: string;
71+ readonly defaultDesktopSettings: DesktopAppSettings.DesktopSettings;
72+ readonly runtimeInfo: DesktopRuntimeInfo;
73+ readonly resolvePickFolderDefaultPath: (rawOptions: unknown) => Option.Option<string>;
74+ readonly resolveResourcePathCandidates: (fileName: string) => readonly string[];
75+ readonly developmentDockIconPath: string;
76+ }
8277 >()("@t3tools/desktop/app/DesktopEnvironment") {}
8378
8479 const APP_BASE_NAME = "T3 Code";
@@ -136,9 +131,9 @@ function resolveDesktopRuntimeInfo(input: { · 51 unmodified lines
136131 };
137132 }
138133
139−const makeDesktopEnvironment = Effect.fn("desktop.environment.make")(function* (
134+const make = Effect.fn("desktop.environment.make")(function* (
140135 input: MakeDesktopEnvironmentInput,
141−): Effect.fn.Return<DesktopEnvironmentShape, Config.ConfigError, Path.Path> {
136+): Effect.fn.Return<DesktopEnvironment["Service"], Config.ConfigError, Path.Path> {
142137 const path = yield* Path.Path;
143138 const config = yield* DesktopConfig.DesktopConfig;
144139 const homeDirectory = input.homeDirectory;
@@ -208,7 +203,7 @@ const makeDesktopEnvironment = Effect.fn("desktop.environment.make")(function* ( · 63 unmodified lines
208203 linuxWmClass: isDevelopment ? "t3code-dev" : "t3code",
209204 userDataDirName,
210205 legacyUserDataDirName,
211− defaultDesktopSettings: resolveDefaultDesktopSettings(input.appVersion),
206+ defaultDesktopSettings: DesktopAppSettings.resolveDefaultDesktopSettings(input.appVersion),
212207 runtimeInfo: resolveDesktopRuntimeInfo({
213208 platform: input.platform,
214209 processArch: input.processArch,
@@ -250,4 +245,4 @@ const makeDesktopEnvironment = Effect.fn("desktop.environment.make")(function* ( · 35 unmodified lines
250245 });
251246
252247 export const layer = (input: MakeDesktopEnvironmentInput) =>
253− Layer.effect(DesktopEnvironment, makeDesktopEnvironment(input));
248+ Layer.effect(DesktopEnvironment, make(input));
apps/desktop/src/app/DesktopLifecycle.ts+89−120
@@ -1,7 +1,6 @@
11 import * as Cause from "effect/Cause";
22 import * as Context from "effect/Context";
33 import * as Effect from "effect/Effect";
4−import * as Deferred from "effect/Deferred";
54 import * as Layer from "effect/Layer";
65 import * as Ref from "effect/Ref";
76 import * as Scope from "effect/Scope";
@@ -10,63 +9,34 @@ import type * as Electron from "electron"; · 2 unmodified lines
109
1110 import * as DesktopEnvironment from "./DesktopEnvironment.ts";
1211 import * as DesktopObservability from "./DesktopObservability.ts";
12+import * as DesktopShutdownModule from "./DesktopShutdown.ts";
1313 import * as ElectronApp from "../electron/ElectronApp.ts";
1414 import * as ElectronTheme from "../electron/ElectronTheme.ts";
1515 import * as DesktopState from "./DesktopState.ts";
1616 import * as DesktopWindow from "../window/DesktopWindow.ts";
1717
18−export interface DesktopShutdownShape {
19− readonly request: Effect.Effect<void>;
20− readonly awaitRequest: Effect.Effect<void>;
21− readonly markComplete: Effect.Effect<void>;
22− readonly awaitComplete: Effect.Effect<void>;
23− readonly isComplete: Effect.Effect<boolean>;
24−}
25−
26−export class DesktopShutdown extends Context.Service<DesktopShutdown, DesktopShutdownShape>()(
27− "@t3tools/desktop/app/DesktopLifecycle/DesktopShutdown",
28−) {}
29−
30−const makeShutdown = Effect.gen(function* () {
31− const requested = yield* Deferred.make<void>();
32− const completed = yield* Deferred.make<void>();
33− const completedRef = yield* Ref.make(false);
34−
35− return DesktopShutdown.of({
36− request: Deferred.succeed(requested, undefined).pipe(Effect.asVoid),
37− awaitRequest: Deferred.await(requested),
38− markComplete: Ref.set(completedRef, true).pipe(
39− Effect.andThen(Deferred.succeed(completed, undefined)),
40− Effect.asVoid,
41− ),
42− awaitComplete: Deferred.await(completed),
43− isComplete: Ref.get(completedRef),
44− });
45−});
46−
47−export const layerShutdown = Layer.effect(DesktopShutdown, makeShutdown);
18+export { DesktopShutdown, layer as layerShutdown } from "./DesktopShutdown.ts";
4819
4920 export type DesktopLifecycleRuntimeServices =
5021 | DesktopEnvironment.DesktopEnvironment
51− | DesktopShutdown
22+ | DesktopShutdownModule.DesktopShutdown
5223 | DesktopState.DesktopState
5324 | DesktopWindow.DesktopWindow
5425 | ElectronApp.ElectronApp
5526 | ElectronTheme.ElectronTheme;
5627
57−export interface DesktopLifecycleShape {
58− readonly relaunch: (
59− reason: string,
60− ) => Effect.Effect<void, never, DesktopLifecycleRuntimeServices>;
61− readonly register: Effect.Effect<void, never, Scope.Scope | DesktopLifecycleRuntimeServices>;
62−}
63−
6428 /**
6529 * @effect-expect-leaking DesktopEnvironment | DesktopShutdown | DesktopState | DesktopWindow | ElectronApp | ElectronTheme
6630 */
67−export class DesktopLifecycle extends Context.Service<DesktopLifecycle, DesktopLifecycleShape>()(
68− "@t3tools/desktop/app/DesktopLifecycle",
69−) {}
31+export class DesktopLifecycle extends Context.Service<
32+ DesktopLifecycle,
33+ {
34+ readonly relaunch: (
35+ reason: string,
36+ ) => Effect.Effect<void, never, DesktopLifecycleRuntimeServices>;
37+ readonly register: Effect.Effect<void, never, Scope.Scope | DesktopLifecycleRuntimeServices>;
38+ }
39+>()("@t3tools/desktop/app/DesktopLifecycle") {}
7040
7141 const { logInfo: logLifecycleInfo, logError: logLifecycleError } =
7242 DesktopObservability.makeComponentLogger("desktop-lifecycle");
@@ -93,8 +63,8 @@ function addScopedListener<Args extends ReadonlyArray<unknown>>( · 20 unmodified lines
9363 }
9464
9565 const requestDesktopShutdownAndWait = Effect.fn("desktop.lifecycle.requestShutdownAndWait")(
96− function* (): Effect.fn.Return<void, never, DesktopShutdown> {
97− const shutdown = yield* DesktopShutdown;
66+ function* (): Effect.fn.Return<void, never, DesktopShutdownModule.DesktopShutdown> {
67+ const shutdown = yield* DesktopShutdownModule.DesktopShutdown;
9868 yield* shutdown.request;
9969 yield* shutdown.awaitComplete;
10070 },
@@ -154,83 +124,82 @@ function quitFromSignal( · 53 unmodified lines
154124 );
155125 }
156126
157−export const layer = Layer.succeed(
158− DesktopLifecycle,
159− DesktopLifecycle.of({
160− relaunch: Effect.fn("desktop.lifecycle.relaunch")(function* (reason) {
161− const electronApp = yield* ElectronApp.ElectronApp;
162− const environment = yield* DesktopEnvironment.DesktopEnvironment;
163− const state = yield* DesktopState.DesktopState;
164− yield* logLifecycleInfo("desktop relaunch requested", { reason });
165− yield* Effect.gen(function* () {
166− yield* Effect.yieldNow;
167− yield* Ref.set(state.quitting, true);
168− yield* requestDesktopShutdownAndWait();
169− if (environment.isDevelopment) {
170− yield* electronApp.exit(75);
171− return;
172− }
173− yield* electronApp.relaunch({
174− execPath: process.execPath,
175− args: process.argv.slice(1),
176− });
177− yield* electronApp.exit(0);
178− }).pipe(
179− Effect.catchCause((cause) =>
180− logLifecycleError("desktop relaunch failed", {
181− cause: Cause.pretty(cause),
182− }),
183− ),
184− Effect.forkDetach,
185− Effect.asVoid,
186− );
187− }),
188− register: Effect.gen(function* () {
189− const desktopWindow = yield* DesktopWindow.DesktopWindow;
190− const electronApp = yield* ElectronApp.ElectronApp;
191− const electronTheme = yield* ElectronTheme.ElectronTheme;
192− const environment = yield* DesktopEnvironment.DesktopEnvironment;
193− const context = yield* Effect.context<DesktopLifecycleRuntimeServices>();
194− const runEffect = Effect.runPromiseWith(context);
195− let quitAllowed = false;
196− yield* electronTheme.onUpdated(() => {
197− void runEffect(
198− desktopWindow.syncAppearance.pipe(Effect.withSpan("desktop.lifecycle.themeUpdated")),
199− );
200− });
201− yield* electronApp.on("before-quit", (event: Electron.Event) => {
202− handleBeforeQuit(
203− event,
204− runEffect,
205− () => quitAllowed,
206− () => {
207− quitAllowed = true;
208− },
209− );
127+const make = DesktopLifecycle.of({
128+ relaunch: Effect.fn("desktop.lifecycle.relaunch")(function* (reason) {
129+ const electronApp = yield* ElectronApp.ElectronApp;
130+ const environment = yield* DesktopEnvironment.DesktopEnvironment;
131+ const state = yield* DesktopState.DesktopState;
132+ yield* logLifecycleInfo("desktop relaunch requested", { reason });
133+ yield* Effect.gen(function* () {
134+ yield* Effect.yieldNow;
135+ yield* Ref.set(state.quitting, true);
136+ yield* requestDesktopShutdownAndWait();
137+ if (environment.isDevelopment) {
138+ yield* electronApp.exit(75);
139+ return;
140+ }
141+ yield* electronApp.relaunch({
142+ execPath: process.execPath,
143+ args: process.argv.slice(1),
210144 });
211− yield* electronApp.on("activate", () => {
212− void runEffect(desktopWindow.activate.pipe(Effect.withSpan("desktop.lifecycle.activate")));
145+ yield* electronApp.exit(0);
146+ }).pipe(
147+ Effect.catchCause((cause) =>
148+ logLifecycleError("desktop relaunch failed", {
149+ cause: Cause.pretty(cause),
150+ }),
151+ ),
152+ Effect.forkDetach,
153+ Effect.asVoid,
154+ );
155+ }),
156+ register: Effect.gen(function* () {
157+ const desktopWindow = yield* DesktopWindow.DesktopWindow;
158+ const electronApp = yield* ElectronApp.ElectronApp;
159+ const electronTheme = yield* ElectronTheme.ElectronTheme;
160+ const environment = yield* DesktopEnvironment.DesktopEnvironment;
161+ const context = yield* Effect.context<DesktopLifecycleRuntimeServices>();
162+ const runEffect = Effect.runPromiseWith(context);
163+ let quitAllowed = false;
164+ yield* electronTheme.onUpdated(() => {
165+ void runEffect(
166+ desktopWindow.syncAppearance.pipe(Effect.withSpan("desktop.lifecycle.themeUpdated")),
167+ );
168+ });
169+ yield* electronApp.on("before-quit", (event: Electron.Event) => {
170+ handleBeforeQuit(
171+ event,
172+ runEffect,
173+ () => quitAllowed,
174+ () => {
175+ quitAllowed = true;
176+ },
177+ );
178+ });
179+ yield* electronApp.on("activate", () => {
180+ void runEffect(desktopWindow.activate.pipe(Effect.withSpan("desktop.lifecycle.activate")));
181+ });
182+ yield* electronApp.on("window-all-closed", () => {
183+ void runEffect(
184+ Effect.gen(function* () {
185+ const app = yield* ElectronApp.ElectronApp;
186+ const state = yield* DesktopState.DesktopState;
187+ if (environment.platform !== "darwin" && !(yield* Ref.get(state.quitting))) {
188+ yield* app.quit;
189+ }
190+ }).pipe(Effect.withSpan("desktop.lifecycle.windowAllClosed")),
191+ );
192+ });
193+
194+ if (environment.platform !== "win32") {
195+ yield* addScopedListener(process, "SIGINT", () => {
196+ quitFromSignal("SIGINT", runEffect);
213197 });
214− yield* electronApp.on("window-all-closed", () => {
215− void runEffect(
216− Effect.gen(function* () {
217− const app = yield* ElectronApp.ElectronApp;
218− const state = yield* DesktopState.DesktopState;
219− if (environment.platform !== "darwin" && !(yield* Ref.get(state.quitting))) {
220− yield* app.quit;
221− }
222− }).pipe(Effect.withSpan("desktop.lifecycle.windowAllClosed")),
223− );
198+ yield* addScopedListener(process, "SIGTERM", () => {
199+ quitFromSignal("SIGTERM", runEffect);
224200 });
201+ }
202+ }).pipe(Effect.withSpan("desktop.lifecycle.register")),
203+});
225204
226− if (environment.platform !== "win32") {
227− yield* addScopedListener(process, "SIGINT", () => {
228− quitFromSignal("SIGINT", runEffect);
229− });
230− yield* addScopedListener(process, "SIGTERM", () => {
231− quitFromSignal("SIGTERM", runEffect);
232− });
233− }
234− }).pipe(Effect.withSpan("desktop.lifecycle.register")),
235− }),
236−);
205+export const layer = Layer.succeed(DesktopLifecycle, make);
apps/desktop/src/app/DesktopObservability.ts+3−3
@@ -314,7 +314,7 @@ const PRIMARY_BACKEND_LOG_INSTANCE_ID = PRIMARY_LOCAL_ENVIRONMENT_ID; · 313 unmodified lines
314314 const sanitizeInstanceIdForFileName = (id: string): string => id.replace(/[^a-zA-Z0-9._-]+/g, "_");
315315
316316 const backendLogFilePathForInstance = (
317− environment: DesktopEnvironment.DesktopEnvironmentShape,
317+ environment: DesktopEnvironment.DesktopEnvironment["Service"],
318318 id: string,
319319 ): string => {
320320 // Primary keeps the historical "server-child.log" path so ops scripts
@@ -332,7 +332,7 @@ const backendLogFilePathForInstance = ( · 11 unmodified lines
332332 // per-call shape lets the shape annotate writes with the *caller's*
333333 // id rather than whatever id created the cached writer first.
334334 const makeBackendOutputSinkForInstance = (
335− environment: DesktopEnvironment.DesktopEnvironmentShape,
335+ environment: DesktopEnvironment.DesktopEnvironment["Service"],
336336 id: string,
337337 ): Effect.Effect<
338338 Option.Option<RotatingLogFileWriter>,
@@ -344,7 +344,7 @@ const makeBackendOutputSinkForInstance = ( · 5 unmodified lines
344344 }).pipe(Effect.option);
345345
346346 const makeBackendOutputLogShape = (
347− environment: DesktopEnvironment.DesktopEnvironmentShape,
347+ environment: DesktopEnvironment.DesktopEnvironment["Service"],
348348 id: string,
349349 sink: Option.Option<RotatingLogFileWriter>,
350350 ): DesktopBackendOutputLogShape =>
apps/desktop/src/app/DesktopShutdown.ts+35−0
@@ -0,0 +1,35 @@
1+import * as Context from "effect/Context";
2+import * as Deferred from "effect/Deferred";
3+import * as Effect from "effect/Effect";
4+import * as Layer from "effect/Layer";
5+import * as Ref from "effect/Ref";
6+
7+export class DesktopShutdown extends Context.Service<
8+ DesktopShutdown,
9+ {
10+ readonly request: Effect.Effect<void>;
11+ readonly awaitRequest: Effect.Effect<void>;
12+ readonly markComplete: Effect.Effect<void>;
13+ readonly awaitComplete: Effect.Effect<void>;
14+ readonly isComplete: Effect.Effect<boolean>;
15+ }
16+>()("@t3tools/desktop/app/DesktopShutdown") {}
17+
18+const make = Effect.gen(function* () {
19+ const requested = yield* Deferred.make<void>();
20+ const completed = yield* Deferred.make<void>();
21+ const completedRef = yield* Ref.make(false);
22+
23+ return DesktopShutdown.of({
24+ request: Deferred.succeed(requested, undefined).pipe(Effect.asVoid),
25+ awaitRequest: Deferred.await(requested),
26+ markComplete: Ref.set(completedRef, true).pipe(
27+ Effect.andThen(Deferred.succeed(completed, undefined)),
28+ Effect.asVoid,
29+ ),
30+ awaitComplete: Deferred.await(completed),
31+ isComplete: Ref.get(completedRef),
32+ });
33+});
34+
35+export const layer = Layer.effect(DesktopShutdown, make);
apps/desktop/src/app/DesktopState.ts+12−12
@@ -3,17 +3,17 @@ import * as Effect from "effect/Effect"; · 2 unmodified lines
33 import * as Layer from "effect/Layer";
44 import * as Ref from "effect/Ref";
55
6−export interface DesktopStateShape {
7− readonly quitting: Ref.Ref<boolean>;
8−}
6+export class DesktopState extends Context.Service<
7+ DesktopState,
8+ {
9+ readonly backendReady: Ref.Ref<boolean>;
10+ readonly quitting: Ref.Ref<boolean>;
11+ }
12+>()("@t3tools/desktop/app/DesktopState") {}
913
10−export class DesktopState extends Context.Service<DesktopState, DesktopStateShape>()(
11− "@t3tools/desktop/app/DesktopState",
12−) {}
14+const make = Effect.all({
15+ backendReady: Ref.make(false),
16+ quitting: Ref.make(false),
17+});
1318
14−export const layer = Layer.effect(
15− DesktopState,
16− Effect.all({
17− quitting: Ref.make(false),
18− }),
19−);
19+export const layer = Layer.effect(DesktopState, make);
apps/desktop/src/ipc/DesktopIpc.ts+16−14
@@ -1,5 +1,6 @@
11 import * as Context from "effect/Context";
22 import * as Effect from "effect/Effect";
3+import * as Layer from "effect/Layer";
34 import * as Schema from "effect/Schema";
45 import * as Scope from "effect/Scope";
56
@@ -33,20 +34,19 @@ export interface DesktopSyncIpcMethod<E, R> { · 27 unmodified lines
3334 readonly handler: () => Effect.Effect<unknown, E, R>;
3435 }
3536
36−export interface DesktopIpcShape {
37− readonly handle: <E, R>(
38− input: DesktopIpcMethod<E, R>,
39− ) => Effect.Effect<void, never, R | Scope.Scope>;
40− readonly handleSync: <E, R>(
41− input: DesktopSyncIpcMethod<E, R>,
42− ) => Effect.Effect<void, never, R | Scope.Scope>;
43−}
44−
45−export class DesktopIpc extends Context.Service<DesktopIpc, DesktopIpcShape>()(
46− "@t3tools/desktop/ipc/DesktopIpc",
47−) {}
48−
49−export const make = (ipcMain: DesktopIpcMain): DesktopIpcShape =>
37+export class DesktopIpc extends Context.Service<
38+ DesktopIpc,
39+ {
40+ readonly handle: <E, R>(
41+ input: DesktopIpcMethod<E, R>,
42+ ) => Effect.Effect<void, never, R | Scope.Scope>;
43+ readonly handleSync: <E, R>(
44+ input: DesktopSyncIpcMethod<E, R>,
45+ ) => Effect.Effect<void, never, R | Scope.Scope>;
46+ }
47+>()("@t3tools/desktop/ipc/DesktopIpc") {}
48+
49+export const make = (ipcMain: DesktopIpcMain): DesktopIpc["Service"] =>
5050 DesktopIpc.of({
5151 handle: Effect.fn("desktop.ipc.registerInvoke")(function* <E, R>({
5252 channel,
@@ -97,6 +97,8 @@ export const make = (ipcMain: DesktopIpcMain): DesktopIpcShape => · 44 unmodified lines
9797 }),
9898 });
9999
100+export const layer = (ipcMain: DesktopIpcMain) => Layer.succeed(DesktopIpc, make(ipcMain));
101+
100102 /**
101103 * Convenience helpers for creating IPC methods
102104 */
apps/desktop/src/ipc/methods/clientSettings.ts+3−3
@@ -5,9 +5,9 @@ import * as Schema from "effect/Schema"; · 4 unmodified lines
55
66 import * as DesktopClientSettings from "../../settings/DesktopClientSettings.ts";
77 import * as IpcChannels from "../channels.ts";
8−import { makeIpcMethod } from "../DesktopIpc.ts";
8+import * as DesktopIpc from "../DesktopIpc.ts";
99
10−export const getClientSettings = makeIpcMethod({
10+export const getClientSettings = DesktopIpc.makeIpcMethod({
1111 channel: IpcChannels.GET_CLIENT_SETTINGS_CHANNEL,
1212 payload: Schema.Void,
1313 result: Schema.NullOr(ClientSettingsSchema),
@@ -17,7 +17,7 @@ export const getClientSettings = makeIpcMethod({ · 3 unmodified lines
1717 }),
1818 });
1919
20−export const setClientSettings = makeIpcMethod({
20+export const setClientSettings = DesktopIpc.makeIpcMethod({
2121 channel: IpcChannels.SET_CLIENT_SETTINGS_CHANNEL,
2222 payload: ClientSettingsSchema,
2323 result: Schema.Void,
apps/desktop/src/ipc/methods/connectionCatalog.ts+4−4
@@ -4,9 +4,9 @@ import * as Schema from "effect/Schema"; · 3 unmodified lines
44
55 import * as DesktopConnectionCatalogStore from "../../app/DesktopConnectionCatalogStore.ts";
66 import * as IpcChannels from "../channels.ts";
7−import { makeIpcMethod } from "../DesktopIpc.ts";
7+import * as DesktopIpc from "../DesktopIpc.ts";
88
9−export const getConnectionCatalog = makeIpcMethod({
9+export const getConnectionCatalog = DesktopIpc.makeIpcMethod({
1010 channel: IpcChannels.GET_CONNECTION_CATALOG_CHANNEL,
1111 payload: Schema.Void,
1212 result: Schema.NullOr(Schema.String),
@@ -16,7 +16,7 @@ export const getConnectionCatalog = makeIpcMethod({ · 3 unmodified lines
1616 }),
1717 });
1818
19−export const setConnectionCatalog = makeIpcMethod({
19+export const setConnectionCatalog = DesktopIpc.makeIpcMethod({
2020 channel: IpcChannels.SET_CONNECTION_CATALOG_CHANNEL,
2121 payload: Schema.String,
2222 result: Schema.Boolean,
@@ -26,7 +26,7 @@ export const setConnectionCatalog = makeIpcMethod({ · 3 unmodified lines
2626 }),
2727 });
2828
29−export const clearConnectionCatalog = makeIpcMethod({
29+export const clearConnectionCatalog = DesktopIpc.makeIpcMethod({
3030 channel: IpcChannels.CLEAR_CONNECTION_CATALOG_CHANNEL,
3131 payload: Schema.Void,
3232 result: Schema.Void,
apps/desktop/src/ipc/methods/preview.ts+23−23
@@ -27,7 +27,7 @@ import { pathToFileURL } from "node:url"; · 26 unmodified lines
2727 import * as PreviewManager from "../../preview/Manager.ts";
2828 import { PREVIEW_WEBVIEW_PREFERENCES } from "../../preview/WebviewPreferences.ts";
2929 import * as IpcChannels from "../channels.ts";
30−import { makeIpcMethod } from "../DesktopIpc.ts";
30+import * as DesktopIpc from "../DesktopIpc.ts";
3131
3232 const broadcast = (channel: string, ...args: ReadonlyArray<unknown>): void => {
3333 for (const window of BrowserWindow.getAllWindows()) {
@@ -52,7 +52,7 @@ export const installPreviewEventForwarding = Effect.fn( · 18 unmodified lines
5252 });
5353 });
5454
55−export const createTab = makeIpcMethod({
55+export const createTab = DesktopIpc.makeIpcMethod({
5656 channel: IpcChannels.PREVIEW_CREATE_TAB_CHANNEL,
5757 payload: DesktopPreviewTabInputSchema,
5858 result: Schema.Void,
@@ -62,7 +62,7 @@ export const createTab = makeIpcMethod({ · 3 unmodified lines
6262 }),
6363 });
6464
65−export const closeTab = makeIpcMethod({
65+export const closeTab = DesktopIpc.makeIpcMethod({
6666 channel: IpcChannels.PREVIEW_CLOSE_TAB_CHANNEL,
6767 payload: DesktopPreviewTabInputSchema,
6868 result: Schema.Void,
@@ -72,7 +72,7 @@ export const closeTab = makeIpcMethod({ · 3 unmodified lines
7272 }),
7373 });
7474
75−export const registerWebview = makeIpcMethod({
75+export const registerWebview = DesktopIpc.makeIpcMethod({
7676 channel: IpcChannels.PREVIEW_REGISTER_WEBVIEW_CHANNEL,
7777 payload: DesktopPreviewRegisterWebviewInputSchema,
7878 result: Schema.Void,
@@ -82,7 +82,7 @@ export const registerWebview = makeIpcMethod({ · 3 unmodified lines
8282 }),
8383 });
8484
85−export const navigate = makeIpcMethod({
85+export const navigate = DesktopIpc.makeIpcMethod({
8686 channel: IpcChannels.PREVIEW_NAVIGATE_CHANNEL,
8787 payload: DesktopPreviewNavigateInputSchema,
8888 result: Schema.Void,
@@ -100,7 +100,7 @@ const tabMethod = ( · 11 unmodified lines
100100 tabId: string,
101101 ) => Effect.Effect<void, PreviewManager.PreviewManagerError>,
102102 ) =>
103− makeIpcMethod({
103+ DesktopIpc.makeIpcMethod({
104104 channel,
105105 payload: DesktopPreviewTabInputSchema,
106106 result: Schema.Void,
@@ -166,7 +166,7 @@ export const stopRecording = tabMethod( · 59 unmodified lines
166166 (manager, tabId) => manager.stopRecording(tabId),
167167 );
168168
169−export const clearCookies = makeIpcMethod({
169+export const clearCookies = DesktopIpc.makeIpcMethod({
170170 channel: IpcChannels.PREVIEW_CLEAR_COOKIES_CHANNEL,
171171 payload: Schema.Void,
172172 result: Schema.Void,
@@ -176,7 +176,7 @@ export const clearCookies = makeIpcMethod({ · 3 unmodified lines
176176 }),
177177 });
178178
179−export const clearCache = makeIpcMethod({
179+export const clearCache = DesktopIpc.makeIpcMethod({
180180 channel: IpcChannels.PREVIEW_CLEAR_CACHE_CHANNEL,
181181 payload: Schema.Void,
182182 result: Schema.Void,
@@ -186,7 +186,7 @@ export const clearCache = makeIpcMethod({ · 3 unmodified lines
186186 }),
187187 });
188188
189−export const getPreviewConfig = makeIpcMethod({
189+export const getPreviewConfig = DesktopIpc.makeIpcMethod({
190190 channel: IpcChannels.PREVIEW_GET_CONFIG_CHANNEL,
191191 payload: DesktopPreviewConfigInputSchema,
192192 result: DesktopPreviewWebviewConfigSchema,
@@ -201,7 +201,7 @@ export const getPreviewConfig = makeIpcMethod({ · 8 unmodified lines
201201 }),
202202 });
203203
204−export const setAnnotationTheme = makeIpcMethod({
204+export const setAnnotationTheme = DesktopIpc.makeIpcMethod({
205205 channel: IpcChannels.PREVIEW_SET_ANNOTATION_THEME_CHANNEL,
206206 payload: DesktopPreviewAnnotationThemeInputSchema,
207207 result: Schema.Void,
@@ -211,7 +211,7 @@ export const setAnnotationTheme = makeIpcMethod({ · 3 unmodified lines
211211 }),
212212 });
213213
214−export const pickElement = makeIpcMethod({
214+export const pickElement = DesktopIpc.makeIpcMethod({
215215 channel: IpcChannels.PREVIEW_PICK_ELEMENT_CHANNEL,
216216 payload: DesktopPreviewTabInputSchema,
217217 result: Schema.NullOr(PreviewAnnotationPayloadSchema),
@@ -221,7 +221,7 @@ export const pickElement = makeIpcMethod({ · 3 unmodified lines
221221 }),
222222 });
223223
224−export const captureScreenshot = makeIpcMethod({
224+export const captureScreenshot = DesktopIpc.makeIpcMethod({
225225 channel: IpcChannels.PREVIEW_CAPTURE_SCREENSHOT_CHANNEL,
226226 payload: DesktopPreviewTabInputSchema,
227227 result: DesktopPreviewScreenshotArtifactSchema,
@@ -231,7 +231,7 @@ export const captureScreenshot = makeIpcMethod({ · 3 unmodified lines
231231 }),
232232 });
233233
234−export const revealArtifact = makeIpcMethod({
234+export const revealArtifact = DesktopIpc.makeIpcMethod({
235235 channel: IpcChannels.PREVIEW_REVEAL_ARTIFACT_CHANNEL,
236236 payload: DesktopPreviewArtifactInputSchema,
237237 result: Schema.Void,
@@ -241,7 +241,7 @@ export const revealArtifact = makeIpcMethod({ · 3 unmodified lines
241241 }),
242242 });
243243
244−export const copyArtifactToClipboard = makeIpcMethod({
244+export const copyArtifactToClipboard = DesktopIpc.makeIpcMethod({
245245 channel: IpcChannels.PREVIEW_COPY_ARTIFACT_CHANNEL,
246246 payload: DesktopPreviewArtifactInputSchema,
247247 result: Schema.Void,
@@ -251,7 +251,7 @@ export const copyArtifactToClipboard = makeIpcMethod({ · 3 unmodified lines
251251 }),
252252 });
253253
254−export const automationStatus = makeIpcMethod({
254+export const automationStatus = DesktopIpc.makeIpcMethod({
255255 channel: IpcChannels.PREVIEW_AUTOMATION_STATUS_CHANNEL,
256256 payload: DesktopPreviewTabInputSchema,
257257 result: PreviewAutomationStatus,
@@ -261,7 +261,7 @@ export const automationStatus = makeIpcMethod({ · 3 unmodified lines
261261 }),
262262 });
263263
264−export const automationSnapshot = makeIpcMethod({
264+export const automationSnapshot = DesktopIpc.makeIpcMethod({
265265 channel: IpcChannels.PREVIEW_AUTOMATION_SNAPSHOT_CHANNEL,
266266 payload: DesktopPreviewTabInputSchema,
267267 result: PreviewAutomationSnapshot,
@@ -271,7 +271,7 @@ export const automationSnapshot = makeIpcMethod({ · 3 unmodified lines
271271 }),
272272 });
273273
274−export const automationClick = makeIpcMethod({
274+export const automationClick = DesktopIpc.makeIpcMethod({
275275 channel: IpcChannels.PREVIEW_AUTOMATION_CLICK_CHANNEL,
276276 payload: DesktopPreviewAutomationClickInputSchema,
277277 result: Schema.Void,
@@ -281,7 +281,7 @@ export const automationClick = makeIpcMethod({ · 3 unmodified lines
281281 }),
282282 });
283283
284−export const automationType = makeIpcMethod({
284+export const automationType = DesktopIpc.makeIpcMethod({
285285 channel: IpcChannels.PREVIEW_AUTOMATION_TYPE_CHANNEL,
286286 payload: DesktopPreviewAutomationTypeInputSchema,
287287 result: Schema.Void,
@@ -291,7 +291,7 @@ export const automationType = makeIpcMethod({ · 3 unmodified lines
291291 }),
292292 });
293293
294−export const automationPress = makeIpcMethod({
294+export const automationPress = DesktopIpc.makeIpcMethod({
295295 channel: IpcChannels.PREVIEW_AUTOMATION_PRESS_CHANNEL,
296296 payload: DesktopPreviewAutomationPressInputSchema,
297297 result: Schema.Void,
@@ -301,7 +301,7 @@ export const automationPress = makeIpcMethod({ · 3 unmodified lines
301301 }),
302302 });
303303
304−export const automationScroll = makeIpcMethod({
304+export const automationScroll = DesktopIpc.makeIpcMethod({
305305 channel: IpcChannels.PREVIEW_AUTOMATION_SCROLL_CHANNEL,
306306 payload: DesktopPreviewAutomationScrollInputSchema,
307307 result: Schema.Void,
@@ -311,7 +311,7 @@ export const automationScroll = makeIpcMethod({ · 3 unmodified lines
311311 }),
312312 });
313313
314−export const automationEvaluate = makeIpcMethod({
314+export const automationEvaluate = DesktopIpc.makeIpcMethod({
315315 channel: IpcChannels.PREVIEW_AUTOMATION_EVALUATE_CHANNEL,
316316 payload: DesktopPreviewAutomationEvaluateInputSchema,
317317 result: Schema.Unknown,
@@ -321,7 +321,7 @@ export const automationEvaluate = makeIpcMethod({ · 3 unmodified lines
321321 }),
322322 });
323323
324−export const automationWaitFor = makeIpcMethod({
324+export const automationWaitFor = DesktopIpc.makeIpcMethod({
325325 channel: IpcChannels.PREVIEW_AUTOMATION_WAIT_FOR_CHANNEL,
326326 payload: DesktopPreviewAutomationWaitForInputSchema,
327327 result: Schema.Void,
@@ -331,7 +331,7 @@ export const automationWaitFor = makeIpcMethod({ · 3 unmodified lines
331331 }),
332332 });
333333
334−export const saveRecording = makeIpcMethod({
334+export const saveRecording = DesktopIpc.makeIpcMethod({
335335 channel: IpcChannels.PREVIEW_RECORDING_SAVE_CHANNEL,
336336 payload: DesktopPreviewRecordingSaveInputSchema,
337337 result: DesktopPreviewRecordingArtifactSchema,
apps/desktop/src/ipc/methods/serverExposure.ts+5−5
@@ -9,14 +9,14 @@ import * as Schema from "effect/Schema"; · 8 unmodified lines
99 import * as DesktopLifecycle from "../../app/DesktopLifecycle.ts";
1010 import * as DesktopServerExposure from "../../backend/DesktopServerExposure.ts";
1111 import * as IpcChannels from "../channels.ts";
12−import { makeIpcMethod } from "../DesktopIpc.ts";
12+import * as DesktopIpc from "../DesktopIpc.ts";
1313
1414 const SetTailscaleServeEnabledInput = Schema.Struct({
1515 enabled: Schema.Boolean,
1616 port: Schema.optionalKey(Schema.Number),
1717 });
1818
19−export const getServerExposureState = makeIpcMethod({
19+export const getServerExposureState = DesktopIpc.makeIpcMethod({
2020 channel: IpcChannels.GET_SERVER_EXPOSURE_STATE_CHANNEL,
2121 payload: Schema.Void,
2222 result: DesktopServerExposureStateSchema,
@@ -26,7 +26,7 @@ export const getServerExposureState = makeIpcMethod({ · 3 unmodified lines
2626 }),
2727 });
2828
29−export const setServerExposureMode = makeIpcMethod({
29+export const setServerExposureMode = DesktopIpc.makeIpcMethod({
3030 channel: IpcChannels.SET_SERVER_EXPOSURE_MODE_CHANNEL,
3131 payload: DesktopServerExposureModeSchema,
3232 result: DesktopServerExposureStateSchema,
@@ -41,7 +41,7 @@ export const setServerExposureMode = makeIpcMethod({ · 8 unmodified lines
4141 }),
4242 });
4343
44−export const setTailscaleServeEnabled = makeIpcMethod({
44+export const setTailscaleServeEnabled = DesktopIpc.makeIpcMethod({
4545 channel: IpcChannels.SET_TAILSCALE_SERVE_ENABLED_CHANNEL,
4646 payload: SetTailscaleServeEnabledInput,
4747 result: DesktopServerExposureStateSchema,
@@ -58,7 +58,7 @@ export const setTailscaleServeEnabled = makeIpcMethod({ · 10 unmodified lines
5858 }),
5959 });
6060
61−export const getAdvertisedEndpoints = makeIpcMethod({
61+export const getAdvertisedEndpoints = DesktopIpc.makeIpcMethod({
6262 channel: IpcChannels.GET_ADVERTISED_ENDPOINTS_CHANNEL,
6363 payload: Schema.Void,
6464 result: Schema.Array(AdvertisedEndpoint),
apps/desktop/src/ipc/methods/sshEnvironment.ts+9−9
@@ -33,7 +33,7 @@ import * as Effect from "effect/Effect"; · 32 unmodified lines
3333 import * as Schema from "effect/Schema";
3434
3535 import * as IpcChannels from "../channels.ts";
36−import { makeIpcMethod } from "../DesktopIpc.ts";
36+import * as DesktopIpc from "../DesktopIpc.ts";
3737 import * as DesktopSshEnvironment from "../../ssh/DesktopSshEnvironment.ts";
3838 import * as DesktopSshPasswordPrompts from "../../ssh/DesktopSshPasswordPrompts.ts";
3939
@@ -107,7 +107,7 @@ const withLoopbackSshApi = · 67 unmodified lines
107107 ),
108108 );
109109
110−export const discoverSshHosts = makeIpcMethod({
110+export const discoverSshHosts = DesktopIpc.makeIpcMethod({
111111 channel: IpcChannels.DISCOVER_SSH_HOSTS_CHANNEL,
112112 payload: Schema.Void,
113113 result: Schema.Array(DesktopDiscoveredSshHostSchema),
@@ -117,7 +117,7 @@ export const discoverSshHosts = makeIpcMethod({ · 3 unmodified lines
117117 }),
118118 });
119119
120−export const ensureSshEnvironment = makeIpcMethod({
120+export const ensureSshEnvironment = DesktopIpc.makeIpcMethod({
121121 channel: IpcChannels.ENSURE_SSH_ENVIRONMENT_CHANNEL,
122122 payload: DesktopSshEnvironmentEnsureInputSchema,
123123 result: DesktopSshEnvironmentEnsureResultSchema,
@@ -139,7 +139,7 @@ export const ensureSshEnvironment = makeIpcMethod({ · 15 unmodified lines
139139 }),
140140 });
141141
142−export const disconnectSshEnvironment = makeIpcMethod({
142+export const disconnectSshEnvironment = DesktopIpc.makeIpcMethod({
143143 channel: IpcChannels.DISCONNECT_SSH_ENVIRONMENT_CHANNEL,
144144 payload: DesktopSshEnvironmentTargetSchema,
145145 result: Schema.Void,
@@ -149,7 +149,7 @@ export const disconnectSshEnvironment = makeIpcMethod({ · 3 unmodified lines
149149 }),
150150 });
151151
152−export const fetchSshEnvironmentDescriptor = makeIpcMethod({
152+export const fetchSshEnvironmentDescriptor = DesktopIpc.makeIpcMethod({
153153 channel: IpcChannels.FETCH_SSH_ENVIRONMENT_DESCRIPTOR_CHANNEL,
154154 payload: DesktopSshHttpBaseUrlInputSchema,
155155 result: ExecutionEnvironmentDescriptor,
@@ -160,7 +160,7 @@ export const fetchSshEnvironmentDescriptor = makeIpcMethod({ · 4 unmodified lines
160160 }),
161161 });
162162
163−export const bootstrapSshBearerSession = makeIpcMethod({
163+export const bootstrapSshBearerSession = DesktopIpc.makeIpcMethod({
164164 channel: IpcChannels.BOOTSTRAP_SSH_BEARER_SESSION_CHANNEL,
165165 payload: DesktopSshBearerBootstrapInputSchema,
166166 result: AuthAccessTokenResult,
@@ -177,7 +177,7 @@ export const bootstrapSshBearerSession = makeIpcMethod({ · 10 unmodified lines
177177 }),
178178 });
179179
180−export const fetchSshSessionState = makeIpcMethod({
180+export const fetchSshSessionState = DesktopIpc.makeIpcMethod({
181181 channel: IpcChannels.FETCH_SSH_SESSION_STATE_CHANNEL,
182182 payload: DesktopSshBearerRequestInputSchema,
183183 result: AuthSessionState,
@@ -194,7 +194,7 @@ export const fetchSshSessionState = makeIpcMethod({ · 10 unmodified lines
194194 }),
195195 });
196196
197−export const issueSshWebSocketTicket = makeIpcMethod({
197+export const issueSshWebSocketTicket = DesktopIpc.makeIpcMethod({
198198 channel: IpcChannels.ISSUE_SSH_WEBSOCKET_TOKEN_CHANNEL,
199199 payload: DesktopSshBearerRequestInputSchema,
200200 result: AuthWebSocketTicketResult,
@@ -211,7 +211,7 @@ export const issueSshWebSocketTicket = makeIpcMethod({ · 10 unmodified lines
211211 }),
212212 });
213213
214−export const resolveSshPasswordPrompt = makeIpcMethod({
214+export const resolveSshPasswordPrompt = DesktopIpc.makeIpcMethod({
215215 channel: IpcChannels.RESOLVE_SSH_PASSWORD_PROMPT_CHANNEL,
216216 payload: DesktopSshPasswordPromptResolutionInputSchema,
217217 result: Schema.Void,
apps/desktop/src/ipc/methods/updates.ts+6−6
@@ -9,9 +9,9 @@ import * as Schema from "effect/Schema"; · 8 unmodified lines
99
1010 import * as DesktopUpdates from "../../updates/DesktopUpdates.ts";
1111 import * as IpcChannels from "../channels.ts";
12−import { makeIpcMethod } from "../DesktopIpc.ts";
12+import * as DesktopIpc from "../DesktopIpc.ts";
1313
14−export const getUpdateState = makeIpcMethod({
14+export const getUpdateState = DesktopIpc.makeIpcMethod({
1515 channel: IpcChannels.UPDATE_GET_STATE_CHANNEL,
1616 payload: Schema.Void,
1717 result: DesktopUpdateStateSchema,
@@ -21,7 +21,7 @@ export const getUpdateState = makeIpcMethod({ · 3 unmodified lines
2121 }),
2222 });
2323
24−export const setUpdateChannel = makeIpcMethod({
24+export const setUpdateChannel = DesktopIpc.makeIpcMethod({
2525 channel: IpcChannels.UPDATE_SET_CHANNEL_CHANNEL,
2626 payload: DesktopUpdateChannelSchema,
2727 result: DesktopUpdateStateSchema,
@@ -31,7 +31,7 @@ export const setUpdateChannel = makeIpcMethod({ · 3 unmodified lines
3131 }),
3232 });
3333
34−export const downloadUpdate = makeIpcMethod({
34+export const downloadUpdate = DesktopIpc.makeIpcMethod({
3535 channel: IpcChannels.UPDATE_DOWNLOAD_CHANNEL,
3636 payload: Schema.Void,
3737 result: DesktopUpdateActionResultSchema,
@@ -41,7 +41,7 @@ export const downloadUpdate = makeIpcMethod({ · 3 unmodified lines
4141 }),
4242 });
4343
44−export const installUpdate = makeIpcMethod({
44+export const installUpdate = DesktopIpc.makeIpcMethod({
4545 channel: IpcChannels.UPDATE_INSTALL_CHANNEL,
4646 payload: Schema.Void,
4747 result: DesktopUpdateActionResultSchema,
@@ -51,7 +51,7 @@ export const installUpdate = makeIpcMethod({ · 3 unmodified lines
5151 }),
5252 });
5353
54−export const checkForUpdate = makeIpcMethod({
54+export const checkForUpdate = DesktopIpc.makeIpcMethod({
5555 channel: IpcChannels.UPDATE_CHECK_CHANNEL,
5656 payload: Schema.Void,
5757 result: DesktopUpdateCheckResultSchema,
apps/desktop/src/ipc/methods/window.ts+9−9
@@ -23,7 +23,7 @@ import * as ElectronShell from "../../electron/ElectronShell.ts"; · 22 unmodified lines
2323 import * as ElectronTheme from "../../electron/ElectronTheme.ts";
2424 import * as ElectronWindow from "../../electron/ElectronWindow.ts";
2525 import * as IpcChannels from "../channels.ts";
26−import { makeIpcMethod, makeSyncIpcMethod } from "../DesktopIpc.ts";
26+import * as DesktopIpc from "../DesktopIpc.ts";
2727 import {
2828 extractDistroFromUncPath,
2929 resolveWslPickFolderDefaultPath,
@@ -46,7 +46,7 @@ function toWebSocketBaseUrl(httpBaseUrl: URL): string { · 16 unmodified lines
4646 return url.href;
4747 }
4848
49−export const getAppBranding = makeSyncIpcMethod({
49+export const getAppBranding = DesktopIpc.makeSyncIpcMethod({
5050 channel: IpcChannels.GET_APP_BRANDING_CHANNEL,
5151 result: Schema.NullOr(DesktopAppBrandingSchema),
5252 handler: Effect.fn("desktop.ipc.window.getAppBranding")(function* () {
@@ -55,7 +55,7 @@ export const getAppBranding = makeSyncIpcMethod({ · 2 unmodified lines
5555 }),
5656 });
5757
58−export const getLocalEnvironmentBootstraps = makeSyncIpcMethod({
58+export const getLocalEnvironmentBootstraps = DesktopIpc.makeSyncIpcMethod({
5959 channel: IpcChannels.GET_LOCAL_ENVIRONMENT_BOOTSTRAPS_CHANNEL,
6060 result: Schema.Array(DesktopEnvironmentBootstrapSchema),
6161 handler: Effect.fn("desktop.ipc.window.getLocalEnvironmentBootstraps")(function* () {
@@ -102,7 +102,7 @@ function extractWslDistroFromEnvironmentId(envId: string): string | null { · 40 unmodified lines
102102 return suffix === "default" || suffix.length === 0 ? null : suffix;
103103 }
104104
105−export const getLocalEnvironmentBearerToken = makeIpcMethod({
105+export const getLocalEnvironmentBearerToken = DesktopIpc.makeIpcMethod({
106106 channel: IpcChannels.GET_LOCAL_ENVIRONMENT_BEARER_TOKEN_CHANNEL,
107107 payload: Schema.Void,
108108 result: Schema.String,
@@ -112,7 +112,7 @@ export const getLocalEnvironmentBearerToken = makeIpcMethod({ · 3 unmodified lines
112112 }),
113113 });
114114
115−export const pickFolder = makeIpcMethod({
115+export const pickFolder = DesktopIpc.makeIpcMethod({
116116 channel: IpcChannels.PICK_FOLDER_CHANNEL,
117117 payload: Schema.UndefinedOr(PickFolderOptionsSchema),
118118 result: Schema.NullOr(Schema.String),
@@ -179,7 +179,7 @@ export const pickFolder = makeIpcMethod({ · 60 unmodified lines
179179 }),
180180 });
181181
182−export const confirm = makeIpcMethod({
182+export const confirm = DesktopIpc.makeIpcMethod({
183183 channel: IpcChannels.CONFIRM_CHANNEL,
184184 payload: Schema.String,
185185 result: Schema.Boolean,
@@ -192,7 +192,7 @@ export const confirm = makeIpcMethod({ · 6 unmodified lines
192192 }),
193193 });
194194
195−export const setTheme = makeIpcMethod({
195+export const setTheme = DesktopIpc.makeIpcMethod({
196196 channel: IpcChannels.SET_THEME_CHANNEL,
197197 payload: DesktopThemeSchema,
198198 result: Schema.Void,
@@ -202,7 +202,7 @@ export const setTheme = makeIpcMethod({ · 3 unmodified lines
202202 }),
203203 });
204204
205−export const showContextMenu = makeIpcMethod({
205+export const showContextMenu = DesktopIpc.makeIpcMethod({
206206 channel: IpcChannels.CONTEXT_MENU_CHANNEL,
207207 payload: ContextMenuInput,
208208 result: Schema.NullOr(Schema.String),
@@ -223,7 +223,7 @@ export const showContextMenu = makeIpcMethod({ · 14 unmodified lines
223223 }),
224224 });
225225
226−export const openExternal = makeIpcMethod({
226+export const openExternal = DesktopIpc.makeIpcMethod({
227227 channel: IpcChannels.OPEN_EXTERNAL_CHANNEL,
228228 payload: Schema.String,
229229 result: Schema.Boolean,
apps/desktop/src/ipc/methods/wsl.test.ts+1−1
@@ -49,7 +49,7 @@ const unusedLifecycleRuntimeLayer = Layer.mergeAll( · 48 unmodified lines
4949 DesktopState.layer,
5050 Layer.succeed(
5151 DesktopEnvironment.DesktopEnvironment,
52− DesktopEnvironment.DesktopEnvironment.of({} as DesktopEnvironment.DesktopEnvironmentShape),
52+ DesktopEnvironment.DesktopEnvironment.of({} as DesktopEnvironment.DesktopEnvironment["Service"]),
5353 ),
5454 Layer.succeed(
5555 DesktopWindow.DesktopWindow,
apps/desktop/src/main.ts+3−4
@@ -14,7 +14,6 @@ import { resolveRemoteT3CliPackageSpec } from "@t3tools/ssh/command"; · 13 unmodified lines
1414 import type { RemoteT3RunnerOptions } from "@t3tools/ssh/tunnel";
1515 import serverPackageJson from "../../server/package.json" with { type: "json" };
1616
17−import type { DesktopSettings as DesktopSettingsValue } from "./settings/DesktopAppSettings.ts";
1817 import * as DesktopIpc from "./ipc/DesktopIpc.ts";
1918 import * as ElectronApp from "./electron/ElectronApp.ts";
2019 import * as ElectronDialog from "./electron/ElectronDialog.ts";
@@ -70,8 +69,8 @@ const desktopEnvironmentLayer = Layer.unwrap( · 49 unmodified lines
7069 );
7170
7271 const resolveDesktopSshCliRunner = (
73− environment: DesktopEnvironment.DesktopEnvironmentShape,
74− settings: DesktopSettingsValue,
72+ environment: DesktopEnvironment.DesktopEnvironment["Service"],
73+ settings: DesktopAppSettings.DesktopSettings,
7574 ): RemoteT3RunnerOptions => {
7675 const devRemoteEntryPath = Option.getOrUndefined(environment.devRemoteT3ServerEntryPath);
7776 if (environment.isDevelopment && devRemoteEntryPath !== undefined) {
@@ -112,7 +111,7 @@ const electronLayer = Layer.mergeAll( · 34 unmodified lines
112111 ElectronTheme.layer,
113112 ElectronUpdater.layer,
114113 ElectronWindow.layer,
115− Layer.succeed(DesktopIpc.DesktopIpc, DesktopIpc.make(Electron.ipcMain)),
114+ DesktopIpc.layer(Electron.ipcMain),
116115 );
117116
118117 const desktopFoundationLayer = Layer.mergeAll(
apps/desktop/src/preview/Manager.test.ts+2−2
@@ -59,7 +59,7 @@ const environmentLayer = Layer.succeed( · 58 unmodified lines
5959 DesktopEnvironment.DesktopEnvironment,
6060 DesktopEnvironment.DesktopEnvironment.of({
6161 browserArtifactsDir: "/tmp/t3/dev/browser-artifacts",
62− } as DesktopEnvironment.DesktopEnvironmentShape),
62+ } as DesktopEnvironment.DesktopEnvironment["Service"]),
6363 );
6464
6565 const fileSystemLayer = FileSystem.layerNoop({
@@ -82,7 +82,7 @@ const layer = PreviewManager.layer.pipe( · 16 unmodified lines
8282
8383 const withManager = <A>(
8484 use: (
85− manager: PreviewManager.PreviewManagerShape,
85+ manager: PreviewManager.PreviewManager["Service"],
8686 ) => Effect.Effect<A, PreviewManager.PreviewManagerError, Scope.Scope>,
8787 ) =>
8888 Effect.gen(function* () {
apps/desktop/src/shell/DesktopShellEnvironment.test.ts+1−1
@@ -64,7 +64,7 @@ function runShellEnvironment(input: { · 63 unmodified lines
6464 DesktopEnvironment.DesktopEnvironment,
6565 DesktopEnvironment.DesktopEnvironment.of({
6666 platform: input.platform,
67− } as DesktopEnvironment.DesktopEnvironmentShape),
67+ } as DesktopEnvironment.DesktopEnvironment["Service"]),
6868 );
6969 const spawnerLayer = Layer.succeed(
7070 ChildProcessSpawner.ChildProcessSpawner,
apps/desktop/src/updates/DesktopUpdates.ts+1−1
@@ -127,7 +127,7 @@ function parseAppUpdateYml(raw: string): Effect.Effect<Option.Option<AppUpdateYm · 126 unmodified lines
127127 function createBaseUpdateState(
128128 channel: DesktopUpdateChannel,
129129 enabled: boolean,
130− environment: DesktopEnvironment.DesktopEnvironmentShape,
130+ environment: DesktopEnvironment.DesktopEnvironment["Service"],
131131 ): DesktopUpdateState {
132132 return {
133133 ...createInitialDesktopUpdateState(environment.appVersion, environment.runtimeInfo, channel),
apps/desktop/src/window/DesktopWindow.test.ts+6−6
@@ -91,7 +91,7 @@ const desktopAssetsLayer = Layer.succeed(DesktopAssets.DesktopAssets, { · 90 unmodified lines
9191 png: Option.none<string>(),
9292 }),
9393 resolveResourcePath: () => Effect.succeed(Option.none<string>()),
94−} satisfies DesktopAssets.DesktopAssetsShape);
94+} satisfies DesktopAssets.DesktopAssets["Service"]);
9595
9696 const desktopServerExposureLayer = Layer.succeed(DesktopServerExposure.DesktopServerExposure, {
9797 getState: Effect.die("unexpected getState"),
@@ -106,19 +106,19 @@ const desktopServerExposureLayer = Layer.succeed(DesktopServerExposure.DesktopSe · 8 unmodified lines
106106 setMode: () => Effect.die("unexpected setMode"),
107107 setTailscaleServeEnabled: () => Effect.die("unexpected setTailscaleServeEnabled"),
108108 getAdvertisedEndpoints: Effect.die("unexpected getAdvertisedEndpoints"),
109−} satisfies DesktopServerExposure.DesktopServerExposureShape);
109+} satisfies DesktopServerExposure.DesktopServerExposure["Service"]);
110110
111111 const electronMenuLayer = Layer.succeed(ElectronMenu.ElectronMenu, {
112112 setApplicationMenu: () => Effect.void,
113113 popupTemplate: () => Effect.void,
114114 showContextMenu: () => Effect.succeed(Option.none()),
115−} satisfies ElectronMenu.ElectronMenuShape);
115+} satisfies ElectronMenu.ElectronMenu["Service"]);
116116
117117 const electronThemeLayer = Layer.succeed(ElectronTheme.ElectronTheme, {
118118 shouldUseDarkColors: Effect.succeed(false),
119119 setSource: () => Effect.void,
120120 onUpdated: () => Effect.void,
121−} satisfies ElectronTheme.ElectronThemeShape);
121+} satisfies ElectronTheme.ElectronTheme["Service"]);
122122
123123 const desktopEnvironmentLayer = DesktopEnvironment.layer(environmentInput).pipe(
124124 Layer.provide(
@@ -156,7 +156,7 @@ function makeTestLayer(input: { · 31 unmodified lines
156156 sendAll: () => Effect.void,
157157 destroyAll: Effect.void,
158158 syncAllAppearance: (sync) => sync(input.window),
159− } satisfies ElectronWindow.ElectronWindowShape);
159+ } satisfies ElectronWindow.ElectronWindow["Service"]);
160160
161161 return DesktopWindow.layer.pipe(
162162 Layer.provide(
@@ -172,7 +172,7 @@ function makeTestLayer(input: { · 9 unmodified lines
172172 return true;
173173 }),
174174 copyText: () => Effect.void,
175− } satisfies ElectronShell.ElectronShellShape),
175+ } satisfies ElectronShell.ElectronShell["Service"]),
176176 electronThemeLayer,
177177 electronWindowLayer,
178178 Layer.mock(PreviewManager.PreviewManager)({
apps/mobile/.swiftlint.yml+1−0
@@ -1,5 +1,6 @@
11 included:
22 - ios/T3Code
3+ - modules/t3-composer-editor/ios
34 - modules/t3-terminal/ios
45 - modules/t3-review-diff/ios
56
apps/mobile/global.css+21−1
@@ -192,11 +192,31 @@ · 191 unmodified lines
192192 }
193193 }
194194
195−/* ─── Font family ───────────────────────────────────────────────────── */
195+/* ─── Typography ────────────────────────────────────────────────────── */
196196 @theme {
197197 --font-sans: "DMSans_400Regular";
198198 --font-medium: "DMSans_500Medium";
199199 --font-bold: "DMSans_700Bold";
200+
201+ /* Keep this scale aligned with src/lib/typography.ts for native style props. */
202+ --text-3xs: 10px;
203+ --text-3xs--line-height: 13px;
204+ --text-2xs: 11px;
205+ --text-2xs--line-height: 15px;
206+ --text-xs: 12px;
207+ --text-xs--line-height: 16px;
208+ --text-sm: 13px;
209+ --text-sm--line-height: 18px;
210+ --text-base: 15px;
211+ --text-base--line-height: 22px;
212+ --text-lg: 17px;
213+ --text-lg--line-height: 22px;
214+ --text-xl: 20px;
215+ --text-xl--line-height: 26px;
216+ --text-2xl: 24px;
217+ --text-2xl--line-height: 30px;
218+ --text-3xl: 28px;
219+ --text-3xl--line-height: 34px;
200220 }
201221
202222 /* ─── Custom utilities ──────────────────────────────────────────────── */
apps/mobile/modules/t3-composer-editor/ios/T3ComposerEditorView.swift+27−4
@@ -275,8 +275,8 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate { · 274 unmodified lines
275275 fileTint: "#737373"
276276 )
277277 private var fontFamily = "DMSans_400Regular"
278− private var fontSize: CGFloat = 15
279− private var lineHeight: CGFloat = 22
278+ private var fontSize: CGFloat = 14
279+ private var lineHeight: CGFloat = 20
280280 private var contentInsetVertical: CGFloat = 0
281281 private var shouldAutoFocus = false
282282 private var didAutoFocus = false
@@ -460,9 +460,19 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate { · 177 unmodified lines
460460 guard !isApplyingControlledValue else {
461461 return
462462 }
463+ restoreBaseTypingAttributes()
463464 emitSelection()
464465 }
465466
467+ public func textView(
468+ _ textView: UITextView,
469+ shouldChangeTextIn range: NSRange,
470+ replacementText text: String
471+ ) -> Bool {
472+ restoreBaseTypingAttributes()
473+ return true
474+ }
475+
466476 public func textViewDidBeginEditing(_ textView: UITextView) {
467477 onComposerFocus()
468478 }
@@ -484,6 +494,7 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate { · 15 unmodified lines
484494 let targetSelection = requestedSelection ?? previousSelection
485495 requestedSelection = nil
486496 textView.selectedRange = displayRange(for: targetSelection)
497+ restoreBaseTypingAttributes()
487498 isApplyingControlledValue = false
488499 updatePlaceholderVisibility()
489500 emitContentSizeIfNeeded()
@@ -556,7 +567,12 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate { · 66 unmodified lines
556567 size: image.size,
557568 baselineOffset: baselineOffset
558569 )
559− return NSAttributedString(attachment: attachment)
570+ let attributedAttachment = NSMutableAttributedString(attachment: attachment)
571+ attributedAttachment.addAttributes(
572+ baseAttributes(),
573+ range: NSRange(location: 0, length: attributedAttachment.length)
574+ )
575+ return attributedAttachment
560576 }
561577
562578 private func renderChip(
@@ -660,11 +676,18 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate { · 97 unmodified lines
660676 let font = UIFont(name: fontFamily, size: fontSize)
661677 ?? UIFont.systemFont(ofSize: fontSize)
662678 textView.font = font
663− textView.typingAttributes = baseAttributes()
679+ restoreBaseTypingAttributes()
664680 placeholderLabel.font = font
665681 setNeedsLayout()
666682 }
667683
684+ private func restoreBaseTypingAttributes() {
685+ guard textView.markedTextRange == nil else {
686+ return
687+ }
688+ textView.typingAttributes = baseAttributes()
689+ }
690+
668691 private func applyTheme() {
669692 textView.textColor = UIColor(composerHex: theme.text) ?? .label
670693 placeholderLabel.textColor = UIColor(composerHex: theme.placeholder) ?? .placeholderText
apps/mobile/src/app/+not-found.tsx+2−2
@@ -21,7 +21,7 @@ export default function NotFoundRoute() { · 20 unmodified lines
2121 }}
2222 style={[{ flex: 1 }, screenBgStyle]}
2323 >
24− <Text className="text-[28px] font-t3-bold text-foreground" selectable>
24+ <Text className="text-3xl font-t3-bold text-foreground" selectable>
2525 Route not found
2626 </Text>
2727 <Link href="/" asChild>
@@ -35,7 +35,7 @@ export default function NotFoundRoute() { · 7 unmodified lines
3535 primaryBgStyle,
3636 ]}
3737 >
38− <Text className="text-[16px] font-t3-bold text-primary-foreground">Return home</Text>
38+ <Text className="text-base font-t3-bold text-primary-foreground">Return home</Text>
3939 </Pressable>
4040 </Link>
4141 </ScrollView>
apps/mobile/src/app/connections/index.tsx+1−1
@@ -85,7 +85,7 @@ export default function ConnectionsRouteScreen() { · 84 unmodified lines
8585 type="monochrome"
8686 />
8787 </View>
88− <Text className="text-center text-[14px] leading-[20px] text-foreground-muted">
88+ <Text className="text-center text-sm leading-[20px] text-foreground-muted">
8989 No environments connected yet.{"\n"}Tap{" "}
9090 <Text className="font-t3-bold text-foreground">+</Text> to add one.
9191 </Text>
apps/mobile/src/app/connections/new.tsx+5−5
@@ -171,7 +171,7 @@ export default function ConnectionsNewRouteScreen() { · 170 unmodified lines
171171 className="items-center gap-3 rounded-[24px] bg-card px-5 py-8"
172172 style={{ borderCurve: "continuous" }}
173173 >
174− <Text className="text-center text-[14px] leading-[20px] text-foreground-muted">
174+ <Text className="text-center text-sm leading-[20px] text-foreground-muted">
175175 Camera permission is required to scan a QR code.
176176 </Text>
177177 <ConnectionSheetButton
@@ -189,7 +189,7 @@ export default function ConnectionsNewRouteScreen() { · 11 unmodified lines
189189 <View collapsable={false} className="gap-4 rounded-[24px] bg-card p-4">
190190 <View collapsable={false} className="gap-1.5">
191191 <Text
192− className="text-[11px] font-t3-bold uppercase text-foreground-muted"
192+ className="text-2xs font-t3-bold uppercase text-foreground-muted"
193193 style={{ letterSpacing: 0.8 }}
194194 >
195195 Host
@@ -202,13 +202,13 @@ export default function ConnectionsNewRouteScreen() { · 6 unmodified lines
202202 placeholderTextColor={placeholderColor}
203203 value={hostInput}
204204 onChangeText={handleHostChange}
205− className="rounded-[14px] border border-input-border bg-input px-4 py-3.5 text-[15px] text-foreground"
205+ className="rounded-[14px] border border-input-border bg-input px-4 py-3.5 text-base text-foreground"
206206 />
207207 </View>
208208
209209 <View collapsable={false} className="gap-1.5">
210210 <Text
211− className="text-[11px] font-t3-bold uppercase text-foreground-muted"
211+ className="text-2xs font-t3-bold uppercase text-foreground-muted"
212212 style={{ letterSpacing: 0.8 }}
213213 >
214214 Pairing code
@@ -220,7 +220,7 @@ export default function ConnectionsNewRouteScreen() { · 5 unmodified lines
220220 placeholderTextColor={placeholderColor}
221221 value={codeInput}
222222 onChangeText={handleCodeChange}
223− className="rounded-[14px] border border-input-border bg-input px-4 py-3.5 text-[15px] text-foreground"
223+ className="rounded-[14px] border border-input-border bg-input px-4 py-3.5 text-base text-foreground"
224224 />
225225 </View>
226226
apps/mobile/src/app/new/index.tsx+5−7
@@ -129,10 +129,10 @@ export default function NewTaskRoute() { · 128 unmodified lines
129129 {items.length === 0 ? (
130130 <View collapsable={false} className="items-center gap-3 rounded-[24px] bg-card px-6 py-8">
131131 {projectEmptyState.loading ? <ActivityIndicator color={accentColor} /> : null}
132− <Text className="text-center text-[17px] font-t3-bold text-foreground">
132+ <Text className="text-center text-lg font-t3-bold text-foreground">
133133 {projectEmptyState.title}
134134 </Text>
135− <Text className="text-center text-[14px] leading-[20px] text-foreground-muted">
135+ <Text className="text-center text-sm leading-[20px] text-foreground-muted">
136136 {projectEmptyState.detail}
137137 </Text>
138138 {!catalogState.hasReadyEnvironment ? (
@@ -140,7 +140,7 @@ export default function NewTaskRoute() { · 1 unmodified line
140140 className="mt-1 rounded-full bg-primary px-4 py-2.5 active:opacity-70"
141141 onPress={() => router.push("/connections/new")}
142142 >
143− <Text className="text-[13px] font-t3-bold text-primary-foreground">
143+ <Text className="text-sm font-t3-bold text-primary-foreground">
144144 Add environment
145145 </Text>
146146 </Pressable>
@@ -149,7 +149,7 @@ export default function NewTaskRoute() { · 2 unmodified lines
149149 className="mt-1 rounded-full bg-primary px-4 py-2.5 active:opacity-70"
150150 onPress={() => router.push("/new/add-project")}
151151 >
152− <Text className="text-[13px] font-t3-bold text-primary-foreground">
152+ <Text className="text-sm font-t3-bold text-primary-foreground">
153153 Add new project
154154 </Text>
155155 </Pressable>
@@ -197,9 +197,7 @@ export default function NewTaskRoute() { · 41 unmodified lines
197197 />
198198 </View>
199199 <View className="flex-1">
200− <Text className="text-[16px] leading-[21px] font-t3-bold">
201− {item.title}
202− </Text>
200+ <Text className="text-base leading-[21px] font-t3-bold">{item.title}</Text>
203201 </View>
204202 <SymbolView
205203 name="chevron.right"
apps/mobile/src/app/settings/environments.tsx+11−11
@@ -111,7 +111,7 @@ export default function SettingsEnvironmentsRouteScreen() { · 110 unmodified lines
111111 type="monochrome"
112112 />
113113 </View>
114− <Text className="text-center text-[14px] leading-[20px] text-foreground-muted">
114+ <Text className="text-center text-sm leading-[20px] text-foreground-muted">
115115 No environments connected yet.{"\n"}Tap{" "}
116116 <Text className="font-t3-bold text-foreground">+</Text> to add one.
117117 </Text>
@@ -160,7 +160,7 @@ function ConfiguredCloudEnvironmentRows(props: { · 42 unmodified lines
160160 return (
161161 <View collapsable={false} className="mt-5 gap-3">
162162 <View className="flex-row items-center justify-between px-1">
163− <Text className="text-[13px] font-t3-bold uppercase text-foreground-muted">T3 Cloud</Text>
163+ <Text className="text-sm font-t3-bold uppercase text-foreground-muted">T3 Cloud</Text>
164164 <Pressable
165165 accessibilityRole="button"
166166 disabled={controller.relayDiscovery.isRefreshing}
@@ -204,16 +204,16 @@ function ConfiguredCloudEnvironmentRows(props: { · 37 unmodified lines
204204 ) : controller.relayDiscovery.isRefreshing ? (
205205 <View collapsable={false} className="items-center gap-3 rounded-[24px] bg-card p-6">
206206 <ActivityIndicator color={iconColor} />
207− <Text className="text-center text-[14px] leading-[20px] text-foreground-muted">
207+ <Text className="text-center text-sm leading-[20px] text-foreground-muted">
208208 Loading linked cloud environments.
209209 </Text>
210210 </View>
211211 ) : controller.relayDiscovery.error ? (
212212 <View collapsable={false} className="gap-3 rounded-[24px] bg-card p-5">
213− <Text className="text-[15px] font-t3-bold text-foreground">
213+ <Text className="text-base font-t3-bold text-foreground">
214214 Could not load T3 Cloud environments
215215 </Text>
216− <Text className="text-[13px] leading-[18px] text-foreground-muted">
216+ <Text className="text-sm leading-[18px] text-foreground-muted">
217217 {controller.relayDiscovery.error}
218218 </Text>
219219 {controller.relayDiscovery.errorTraceId ? (
@@ -222,7 +222,7 @@ function ConfiguredCloudEnvironmentRows(props: { · 2 unmodified lines
222222 </View>
223223 ) : (
224224 <View collapsable={false} className="rounded-[24px] bg-card p-5">
225− <Text className="text-[14px] leading-[20px] text-foreground-muted">
225+ <Text className="text-sm leading-[20px] text-foreground-muted">
226226 No additional linked cloud environments.
227227 </Text>
228228 </View>
@@ -361,7 +361,7 @@ function CloudEnvironmentRowShell(props: { · 132 unmodified lines
361361 <View className="min-w-0 flex-row items-center gap-2">
362362 <ConnectionStatusDot state={props.connectionState} pulse={shouldPulse} size={7} />
363363 <Text
364− className="min-w-0 flex-shrink text-[16px] font-t3-bold leading-[21px] text-foreground"
364+ className="min-w-0 flex-shrink text-base font-t3-bold leading-[21px] text-foreground"
365365 numberOfLines={1}
366366 >
367367 {props.label}
@@ -370,7 +370,7 @@ function CloudEnvironmentRowShell(props: { · 2 unmodified lines
370370 {props.connectionError ? (
371371 <Text
372372 aria-hidden
373− className={cn("absolute left-0 right-0 text-[12px] leading-[16px]", statusClassName)}
373+ className={cn("absolute left-0 right-0 text-xs leading-[16px]", statusClassName)}
374374 onTextLayout={onMeasuredErrorTextLayout}
375375 style={{ opacity: 0, zIndex: -1 }}
376376 >
@@ -384,7 +384,7 @@ function CloudEnvironmentRowShell(props: { · 7 unmodified lines
384384 className="min-w-0 flex-row items-start gap-1"
385385 >
386386 <Text
387− className={cn("min-w-0 flex-1 text-[12px] leading-[16px]", statusClassName)}
387+ className={cn("min-w-0 flex-1 text-xs leading-[16px]", statusClassName)}
388388 numberOfLines={isErrorExpanded ? undefined : 1}
389389 >
390390 {statusText}
@@ -394,7 +394,7 @@ function CloudEnvironmentRowShell(props: { · 3 unmodified lines
394394 <Text
395395 accessibilityHint="Copies the trace ID"
396396 accessibilityRole="button"
397− className={cn("text-[12px] leading-[16px] underline", statusClassName)}
397+ className={cn("text-xs leading-[16px] underline", statusClassName)}
398398 onLongPress={(event) => {
399399 event.stopPropagation();
400400 copyTextWithHaptic(errorTraceId);
@@ -446,7 +446,7 @@ function CopyTraceIdButton(props: { readonly traceId: string }) { · 45 unmodified lines
446446 className="self-start flex-row items-center gap-1.5 rounded-full bg-subtle px-3 py-2 active:opacity-70"
447447 >
448448 <SymbolView name="doc.on.doc" size={12} tintColor={iconColor} type="monochrome" />
449− <Text className="text-[12px] font-t3-bold text-foreground">Copy trace ID</Text>
449+ <Text className="text-xs font-t3-bold text-foreground">Copy trace ID</Text>
450450 </Pressable>
451451 );
452452 }
apps/mobile/src/app/settings/index.tsx+7−7
@@ -346,7 +346,7 @@ function ConfiguredSettingsRouteScreen() { · 345 unmodified lines
346346 onPress={openAccount}
347347 />
348348 </SettingsSection>
349− <Text className="px-2 text-[13px] leading-[18px] text-foreground-muted">
349+ <Text className="px-2 text-sm leading-[18px] text-foreground-muted">
350350 T3 Code works locally without signing in. Cloud features are optional.
351351 </Text>
352352 </View>
@@ -389,7 +389,7 @@ type SymbolName = ComponentProps<typeof SymbolView>["name"]; · 36 unmodified lines
389389 function SettingsSection(props: { readonly title: string; readonly children: ReactNode }) {
390390 return (
391391 <View className="gap-2">
392− <Text className="px-2 text-[13px] font-t3-medium text-foreground-muted">{props.title}</Text>
392+ <Text className="px-2 text-sm font-t3-medium text-foreground-muted">{props.title}</Text>
393393 <View
394394 className="overflow-hidden rounded-[28px] bg-card"
395395 style={{ borderCurve: "continuous" }}
@@ -413,8 +413,8 @@ function AppSettingsSection() { · 17 unmodified lines
413413 type="monochrome"
414414 weight="regular"
415415 />
416− <Text className="flex-1 text-[17px] text-foreground">Version</Text>
417− <Text className="text-[17px] text-foreground-muted">Alpha</Text>
416+ <Text className="flex-1 text-lg text-foreground">Version</Text>
417+ <Text className="text-lg text-foreground-muted">Alpha</Text>
418418 </View>
419419 </SettingsSection>
420420 );
@@ -444,13 +444,13 @@ function SettingsRow(props: { · 23 unmodified lines
444444 style={{ opacity: props.disabled ? 0.45 : 1 }}
445445 >
446446 <SymbolView name={props.icon} size={22} tintColor={icon} type="monochrome" weight="regular" />
447− <Text className="shrink-0 text-[17px] text-foreground" numberOfLines={1}>
447+ <Text className="shrink-0 text-lg text-foreground" numberOfLines={1}>
448448 {props.label}
449449 </Text>
450450 <View className="min-w-0 flex-1 items-end">
451451 {props.value ? (
452452 <Text
453− className="max-w-[180px] text-right text-[16px] text-foreground-muted"
453+ className="max-w-[180px] text-right text-base text-foreground-muted"
454454 ellipsizeMode="middle"
455455 numberOfLines={1}
456456 >
@@ -502,7 +502,7 @@ function SettingsSwitchRow(props: { · 45 unmodified lines
502502 style={{ opacity: props.disabled ? 0.45 : 1 }}
503503 >
504504 <SymbolView name={props.icon} size={22} tintColor={icon} type="monochrome" weight="regular" />
505− <Text className="flex-1 text-[17px] text-foreground">{props.label}</Text>
505+ <Text className="flex-1 text-lg text-foreground">{props.label}</Text>
506506 <Switch
507507 disabled={props.disabled}
508508 ios_backgroundColor={track}
apps/mobile/src/components/AppText.tsx+1−1
@@ -40,7 +40,7 @@ export function AppTextInput({ · 39 unmodified lines
4040 <RNTextInput
4141 ref={ref}
4242 className={cn(
43− "min-h-[54px] rounded-2xl border border-input-border bg-input px-3.5 py-3 font-sans text-[15px] text-foreground",
43+ "min-h-[54px] rounded-2xl border border-input-border bg-input px-3.5 py-3 font-sans text-base text-foreground",
4444 className,
4545 )}
4646 placeholderTextColor={placeholderTextColor ?? placeholderColor}
apps/mobile/src/components/BrandMark.tsx+3−6
@@ -22,15 +22,12 @@ export function BrandMark(props: { readonly compact?: boolean; readonly stageLab · 21 unmodified lines
2222 />
2323 <View className="gap-1">
2424 <View className="flex-row items-center gap-2">
25− <Text
26− className="text-[17px] font-t3-bold text-foreground"
27− style={{ letterSpacing: -0.4 }}
28− >
25+ <Text className="text-lg font-t3-bold text-foreground" style={{ letterSpacing: -0.4 }}>
2926 T3 Code
3027 </Text>
3128 <View className="rounded-full bg-subtle px-2 py-1">
3229 <Text
33− className="text-[10px] font-t3-bold uppercase text-foreground-muted"
30+ className="text-3xs font-t3-bold uppercase text-foreground-muted"
3431 style={{ letterSpacing: 1.1 }}
3532 >
3633 {stageLabel}
@@ -38,7 +35,7 @@ export function BrandMark(props: { readonly compact?: boolean; readonly stageLab · 1 unmodified line
3835 </View>
3936 </View>
4037 {!compact ? (
41− <Text className="text-[12px] font-medium text-foreground-muted">
38+ <Text className="text-xs font-medium text-foreground-muted">
4239 Mobile control surface for your live coding environments
4340 </Text>
4441 ) : null}
apps/mobile/src/components/ComposerToolbarTrigger.tsx+1−1
@@ -223,7 +223,7 @@ export function ComposerToolbarButton(props: { · 222 unmodified lines
223223 {props.label ? (
224224 <Text
225225 className={cn(
226− "shrink text-center text-[13px] font-t3-bold",
226+ "shrink text-center text-sm font-t3-bold",
227227 variant === "primary"
228228 ? props.disabled
229229 ? "text-foreground-muted"
apps/mobile/src/components/ControlPill.tsx+1−1
@@ -48,7 +48,7 @@ export function ControlPill(props: { · 47 unmodified lines
4848 : "bg-subtle",
4949 );
5050 const labelClassName = cn(
51− "text-center text-[12px] font-t3-bold",
51+ "text-center text-xs font-t3-bold",
5252 variant === "primary"
5353 ? props.disabled
5454 ? "text-foreground-muted"
apps/mobile/src/components/EmptyState.tsx+1−3
@@ -19,9 +19,7 @@ export function EmptyState(props: { · 18 unmodified lines
1919 className="mt-4 self-start rounded-full bg-primary px-4 py-2.5 active:opacity-70"
2020 onPress={props.onAction}
2121 >
22− <Text className="text-[13px] font-t3-bold text-primary-foreground">
23− {props.actionLabel}
24− </Text>
22+ <Text className="text-sm font-t3-bold text-primary-foreground">{props.actionLabel}</Text>
2523 </Pressable>
2624 ) : null}
2725 </View>
apps/mobile/src/components/ErrorBanner.tsx+1−1
@@ -4,7 +4,7 @@ import { AppText as Text } from "./AppText"; · 3 unmodified lines
44 export function ErrorBanner(props: { readonly message: string }) {
55 return (
66 <View className="rounded-2xl border border-rose-300/70 bg-rose-100/80 px-3.5 py-3 dark:border-rose-400/28 dark:bg-rose-500/12">
7− <Text className="font-t3-medium text-[13px] leading-[18px] text-rose-700 dark:text-rose-300">
7+ <Text className="font-t3-medium text-sm leading-[18px] text-rose-700 dark:text-rose-300">
88 {props.message}
99 </Text>
1010 </View>
apps/mobile/src/components/StatusPill.tsx+1−1
@@ -26,7 +26,7 @@ export function StatusPill( · 25 unmodified lines
2626 <Text
2727 className={cn(
2828 "font-t3-bold",
29− size === "compact" ? "text-[11px]" : "text-xs",
29+ size === "compact" ? "text-2xs" : "text-xs",
3030 props.textClassName,
3131 )}
3232 >
apps/mobile/src/features/archive/ArchivedThreadsScreen.tsx+11−9
@@ -147,14 +147,14 @@ function ProjectGroupLabel(props: { · 146 unmodified lines
147147 workspaceRoot={props.project.workspaceRoot}
148148 />
149149 <Text
150− className="flex-1 text-[12px] font-t3-medium uppercase text-foreground-muted"
150+ className="flex-1 text-xs font-t3-medium uppercase text-foreground-muted"
151151 numberOfLines={1}
152152 style={{ letterSpacing: 0.5 }}
153153 >
154154 {props.project.title}
155155 </Text>
156156 {props.environmentLabel ? (
157− <Text className="max-w-[42%] text-[11px] text-foreground-tertiary" numberOfLines={1}>
157+ <Text className="max-w-[42%] text-2xs text-foreground-tertiary" numberOfLines={1}>
158158 {props.environmentLabel}
159159 </Text>
160160 ) : null}
@@ -265,13 +265,13 @@ function ArchivedThreadRow(props: { · 104 unmodified lines
265265 <View className="min-w-0 flex-1 gap-1">
266266 <View className="flex-row items-center gap-2">
267267 <Text
268− className="min-w-0 flex-1 text-[15px] font-t3-bold leading-[20px] text-foreground"
268+ className="min-w-0 flex-1 text-base font-t3-bold leading-[20px] text-foreground"
269269 numberOfLines={1}
270270 >
271271 {props.thread.title}
272272 </Text>
273273 <Text
274− className="text-[12px] text-foreground-tertiary"
274+ className="text-xs text-foreground-tertiary"
275275 style={{ fontVariant: ["tabular-nums"] }}
276276 >
277277 {timestamp}
@@ -286,7 +286,7 @@ function ArchivedThreadRow(props: { · 8 unmodified lines
286286 type="monochrome"
287287 />
288288 <Text
289− className="min-w-0 flex-1 text-[11px] text-foreground-tertiary"
289+ className="min-w-0 flex-1 text-2xs text-foreground-tertiary"
290290 numberOfLines={1}
291291 style={{ fontFamily: "monospace" }}
292292 >
@@ -314,10 +314,12 @@ function ArchivedThreadRow(props: { · 21 unmodified lines
314314 function ArchiveError(props: { readonly message: string; readonly onRetry: () => void }) {
315315 return (
316316 <View className="rounded-[20px] border border-danger-border bg-danger p-4">
317− <Text className="font-t3-bold text-danger-foreground">Could not load every archive</Text>
318− <Text className="mt-1 text-[13px] leading-[18px] text-foreground-muted">{props.message}</Text>
317+ <Text className="text-base font-t3-bold text-danger-foreground">
318+ Could not load every archive
319+ </Text>
320+ <Text className="mt-1 text-sm leading-[18px] text-foreground-muted">{props.message}</Text>
319321 <Pressable className="mt-3 self-start active:opacity-60" onPress={props.onRetry}>
320− <Text className="text-[13px] font-t3-bold text-danger-foreground">Try again</Text>
322+ <Text className="text-sm font-t3-bold text-danger-foreground">Try again</Text>
321323 </Pressable>
322324 </View>
323325 );
@@ -386,7 +388,7 @@ export function ArchivedThreadsScreen(props: { · 62 unmodified lines
386388 {isInitialLoad ? (
387389 <View className="items-center py-16">
388390 <ActivityIndicator color={refreshTint} />
389− <Text className="mt-3 text-[13px] text-foreground-muted">Loading archive…</Text>
391+ <Text className="mt-3 text-sm text-foreground-muted">Loading archive…</Text>
390392 </View>
391393 ) : props.groups.length === 0 ? (
392394 <EmptyState
apps/mobile/src/features/cloud/CloudWaitlistEnrollment.tsx+8−12
@@ -2,6 +2,7 @@ import { useWaitlist } from "@clerk/expo"; · 1 unmodified line
22 import { ActivityIndicator, Pressable, StyleSheet, Text, TextInput, View } from "react-native";
33 import { useState } from "react";
44
5+import { MOBILE_TYPOGRAPHY } from "../../lib/typography";
56 import { useThemeColor } from "../../lib/useThemeColor";
67
78 export function CloudWaitlistEnrollment(props: { readonly onSignIn: () => void }) {
@@ -141,12 +142,11 @@ function useCloudWaitlistColors() { · 133 unmodified lines
141142 const styles = StyleSheet.create({
142143 body: {
143144 fontFamily: "DMSans_400Regular",
144− fontSize: 15,
145− lineHeight: 21,
145+ ...MOBILE_TYPOGRAPHY.body,
146146 },
147147 buttonText: {
148148 fontFamily: "DMSans_700Bold",
149− fontSize: 16,
149+ fontSize: MOBILE_TYPOGRAPHY.body.fontSize,
150150 },
151151 content: {
152152 gap: 18,
@@ -156,8 +156,7 @@ const styles = StyleSheet.create({ · 3 unmodified lines
156156 },
157157 error: {
158158 fontFamily: "DMSans_400Regular",
159− fontSize: 13,
160− lineHeight: 18,
159+ ...MOBILE_TYPOGRAPHY.footnote,
161160 },
162161 field: {
163162 gap: 8,
@@ -167,15 +166,14 @@ const styles = StyleSheet.create({ · 3 unmodified lines
167166 borderRadius: 16,
168167 borderWidth: 1,
169168 fontFamily: "DMSans_400Regular",
170− fontSize: 17,
169+ fontSize: MOBILE_TYPOGRAPHY.headline.fontSize,
171170 minHeight: 54,
172171 paddingHorizontal: 16,
173172 paddingVertical: 14,
174173 },
175174 label: {
176175 fontFamily: "DMSans_700Bold",
177− fontSize: 13,
178− lineHeight: 18,
176+ ...MOBILE_TYPOGRAPHY.footnote,
179177 },
180178 primaryButton: {
181179 alignItems: "center",
@@ -196,13 +194,11 @@ const styles = StyleSheet.create({ · 14 unmodified lines
196194 },
197195 signInText: {
198196 fontFamily: "DMSans_700Bold",
199− fontSize: 15,
200− lineHeight: 21,
197+ ...MOBILE_TYPOGRAPHY.body,
201198 },
202199 title: {
203200 fontFamily: "DMSans_700Bold",
204− fontSize: 20,
205− lineHeight: 26,
201+ ...MOBILE_TYPOGRAPHY.title,
206202 textAlign: "center",
207203 },
208204 });
apps/mobile/src/features/connection/ConnectionEnvironmentRow.tsx+9−12
@@ -76,19 +76,16 @@ export function ConnectionEnvironmentRow(props: { · 75 unmodified lines
7676 />
7777
7878 <View className="flex-1 gap-0.5">
79− <Text
80− className="text-[16px] font-t3-bold leading-[21px] text-foreground"
81− numberOfLines={1}
82− >
79+ <Text className="text-base font-t3-bold leading-[21px] text-foreground" numberOfLines={1}>
8380 {props.environment.environmentLabel}
8481 </Text>
85− <Text className="text-[12px] leading-[16px] text-foreground-muted" numberOfLines={1}>
82+ <Text className="text-xs leading-[16px] text-foreground-muted" numberOfLines={1}>
8683 {props.environment.displayUrl}
8784 </Text>
8885 {statusLabel ? (
8986 <Text
9087 className={cn(
91− "text-[12px] leading-[16px]",
88+ "text-xs leading-[16px]",
9289 hasConnectionFailure ? "text-rose-500 dark:text-rose-400" : "text-foreground-muted",
9390 )}
9491 numberOfLines={props.expanded ? undefined : 1}
@@ -137,14 +134,14 @@ export function ConnectionEnvironmentRow(props: { · 42 unmodified lines
137134 className="gap-3 px-4 pb-4"
138135 >
139136 {props.environment.isRelayManaged ? (
140− <Text className="text-[13px] leading-[18px] text-foreground-muted">
137+ <Text className="text-sm leading-[18px] text-foreground-muted">
141138 Managed by T3 Cloud. Tunnel details update automatically.
142139 </Text>
143140 ) : (
144141 <>
145142 <View className="gap-1.5">
146143 <Text
147− className="text-[11px] font-t3-bold uppercase text-foreground-muted"
144+ className="text-2xs font-t3-bold uppercase text-foreground-muted"
148145 style={{ letterSpacing: 0.8 }}
149146 >
150147 Label
@@ -156,13 +153,13 @@ export function ConnectionEnvironmentRow(props: { · 5 unmodified lines
156153 placeholderTextColor={placeholderColor}
157154 value={label}
158155 onChangeText={setLabel}
159− className="rounded-[14px] border border-input-border bg-input px-4 py-3 text-[15px] text-foreground"
156+ className="rounded-[14px] border border-input-border bg-input px-4 py-3 text-base text-foreground"
160157 />
161158 </View>
162159
163160 <View className="gap-1.5">
164161 <Text
165− className="text-[11px] font-t3-bold uppercase text-foreground-muted"
162+ className="text-2xs font-t3-bold uppercase text-foreground-muted"
166163 style={{ letterSpacing: 0.8 }}
167164 >
168165 URL
@@ -175,7 +172,7 @@ export function ConnectionEnvironmentRow(props: { · 6 unmodified lines
175172 placeholderTextColor={placeholderColor}
176173 value={url}
177174 onChangeText={setUrl}
178− className="rounded-[14px] border border-input-border bg-input px-4 py-3 text-[15px] text-foreground"
175+ className="rounded-[14px] border border-input-border bg-input px-4 py-3 text-base text-foreground"
179176 />
180177 </View>
181178 </>
@@ -189,7 +186,7 @@ export function ConnectionEnvironmentRow(props: { · 7 unmodified lines
189186 >
190187 <SymbolView name="checkmark" size={13} tintColor={primaryFg} type="monochrome" />
191188 <Text
192− className="text-[12px] font-t3-bold uppercase text-primary-foreground"
189+ className="text-xs font-t3-bold uppercase text-primary-foreground"
193190 style={{ letterSpacing: 0.8 }}
194191 >
195192 Save
apps/mobile/src/features/connection/ConnectionSheetButton.tsx+1−1
@@ -104,7 +104,7 @@ export function ConnectionSheetButton(props: { · 103 unmodified lines
104104 type="monochrome"
105105 />
106106 <Text
107− className="text-[12px] font-t3-bold uppercase"
107+ className="text-xs font-t3-bold uppercase"
108108 style={{ color: colors.textColor, letterSpacing: 0.8 }}
109109 >
110110 {props.label}
apps/mobile/src/features/connection/EnvironmentConnectionNotice.tsx+3−3
@@ -73,10 +73,10 @@ export function EnvironmentConnectionNotice(props: { · 72 unmodified lines
7373 />
7474 )}
7575
76− <Text className="text-center text-[17px] font-t3-bold leading-[22px] text-foreground">
76+ <Text className="text-center text-lg font-t3-bold leading-[22px] text-foreground">
7777 {noticeTitle(props.connection.phase, props.environmentLabel)}
7878 </Text>
79− <Text className="text-center text-[14px] leading-[20px] text-foreground-muted">
79+ <Text className="text-center text-sm leading-[20px] text-foreground-muted">
8080 {noticeDetail(props.connection.phase, props.resourceName, props.connection.error)}
8181 {props.connection.traceId ? (
8282 <>
@@ -99,7 +99,7 @@ export function EnvironmentConnectionNotice(props: { · 16 unmodified lines
9999 className="mt-1 rounded-full bg-subtle px-4 py-2.5 active:opacity-70"
100100 onPress={props.onRetry}
101101 >
102− <Text className="text-[13px] font-t3-bold text-foreground">Retry now</Text>
102+ <Text className="text-sm font-t3-bold text-foreground">Retry now</Text>
103103 </Pressable>
104104 ) : null}
105105 </View>
apps/mobile/src/features/files/FileMarkdownPreview.tsx+3−4
@@ -7,6 +7,7 @@ import { · 6 unmodified lines
77 } from "react-native-nitro-markdown";
88 import { Linking, ScrollView, Text as NativeText, View } from "react-native";
99
10+import { MOBILE_TYPOGRAPHY } from "../../lib/typography";
1011 import { useThemeColor } from "../../lib/useThemeColor";
1112 import {
1213 hasNativeSelectableMarkdownText,
@@ -72,8 +73,7 @@ function useMarkdownPreviewStyles(): MarkdownPreviewStyles { · 59 unmodified lines
7273 text: {
7374 color: body,
7475 fontFamily: "DMSans_400Regular",
75− fontSize: 15,
76− lineHeight: 22,
76+ ...MOBILE_TYPOGRAPHY.body,
7777 },
7878 heading: {
7979 color: strong,
@@ -123,8 +123,7 @@ function useMarkdownPreviewStyles(): MarkdownPreviewStyles { · 43 unmodified lines
123123 skillTextColor: codeText,
124124 quoteMarkerColor: blockquoteBorder,
125125 dividerColor: horizontalRule,
126− fontSize: 15,
127− lineHeight: 22,
126+ ...MOBILE_TYPOGRAPHY.body,
128127 fontFamily: "DMSans_400Regular",
129128 headingFontFamily: "DMSans_700Bold",
130129 boldFontFamily: "DMSans_700Bold",
apps/mobile/src/features/files/FileTreeBrowser.tsx+6−8
@@ -64,7 +64,7 @@ const FileTreeRow = memo(function FileTreeRow(props: { · 63 unmodified lines
6464 <PierreEntryIcon path={node.path} kind={node.kind} size={17} />
6565 <Text
6666 className={cn(
67− "min-w-0 flex-1 text-[14px] leading-[19px]",
67+ "min-w-0 flex-1 text-sm leading-[19px]",
6868 selected ? "font-t3-bold text-foreground" : "font-t3-medium text-foreground-secondary",
6969 )}
7070 numberOfLines={1}
@@ -72,7 +72,7 @@ const FileTreeRow = memo(function FileTreeRow(props: { · 1 unmodified line
7272 {node.name}
7373 </Text>
7474 {node.kind === "directory" ? (
75− <Text className="text-[11px] font-t3-medium text-foreground-tertiary">
75+ <Text className="text-2xs font-t3-medium text-foreground-tertiary">
7676 {node.children.length}
7777 </Text>
7878 ) : null}
@@ -142,10 +142,8 @@ export function FileTreeBrowser(props: { · 63 unmodified lines
142142 <View className="flex-1 bg-sheet">
143143 {props.error && props.entries.length === 0 ? (
144144 <View className="px-4 py-5">
145− <Text className="text-[13px] font-t3-bold text-foreground">Files unavailable</Text>
146− <Text className="mt-1 text-[12px] leading-[18px] text-foreground-muted">
147− {props.error}
148− </Text>
145+ <Text className="text-sm font-t3-bold text-foreground">Files unavailable</Text>
146+ <Text className="mt-1 text-xs leading-[18px] text-foreground-muted">{props.error}</Text>
149147 </View>
150148 ) : (
151149 <FlatList
@@ -174,8 +172,8 @@ export function FileTreeBrowser(props: { · 22 unmodified lines
174172 <ActivityIndicator size="small" />
175173 ) : (
176174 <>
177− <Text className="text-[13px] font-t3-bold text-foreground">No files found</Text>
178− <Text className="mt-1 text-[12px] leading-[18px] text-foreground-muted">
175+ <Text className="text-sm font-t3-bold text-foreground">No files found</Text>
176+ <Text className="mt-1 text-xs leading-[18px] text-foreground-muted">
179177 {props.searchQuery.trim().length > 0
180178 ? "Try a different search."
181179 : "The workspace file index is empty."}
apps/mobile/src/features/files/SourceFileSurface.tsx+15−9
@@ -18,6 +18,7 @@ import { · 17 unmodified lines
1818 } from "../review/reviewDiffRendering";
1919 import type { ReviewHighlightedToken } from "../review/shikiReviewHighlighter";
2020 import { cn } from "../../lib/cn";
21+import { MOBILE_CODE_SURFACE } from "../../lib/typography";
2122 import {
2223 buildNativeSourceRows,
2324 buildNativeSourceTokens,
@@ -28,8 +29,8 @@ import { · 4 unmodified lines
2829 } from "./nativeSourceFileAdapter";
2930 import { sourceHighlightAtom } from "./sourceHighlightingState";
3031
31−const SOURCE_LINE_HEIGHT = 24;
32−const SOURCE_LINE_NUMBER_WIDTH = 58;
32+const SOURCE_LINE_HEIGHT = MOBILE_CODE_SURFACE.rowHeight;
33+const SOURCE_LINE_NUMBER_WIDTH = MOBILE_CODE_SURFACE.gutterWidth;
3334 const NATIVE_SOURCE_STYLE_JSON = JSON.stringify(NATIVE_SOURCE_STYLE);
3435
3536 interface SourceFileSurfaceProps {
@@ -56,10 +57,12 @@ const HighlightedSourceLine = memo(function HighlightedSourceLine(props: { · 20 unmodified lines
5657 style={{ minHeight: SOURCE_LINE_HEIGHT }}
5758 >
5859 <NativeText
59− className="select-none pr-3 text-right text-[11px] leading-[24px] text-foreground-tertiary"
60+ className="select-none pr-3 text-right text-foreground-tertiary"
6061 style={{
6162 width: SOURCE_LINE_NUMBER_WIDTH,
6263 fontFamily: REVIEW_MONO_FONT_FAMILY,
64+ fontSize: MOBILE_CODE_SURFACE.lineNumberFontSize,
65+ lineHeight: MOBILE_CODE_SURFACE.rowHeight,
6366 }}
6467 >
6568 {props.index + 1}
@@ -67,8 +70,13 @@ const HighlightedSourceLine = memo(function HighlightedSourceLine(props: { · 1 unmodified line
6770 <NativeText
6871 selectable
6972 numberOfLines={1}
70− className="text-[13px] font-medium leading-[24px] text-foreground"
71− style={{ fontFamily: REVIEW_MONO_FONT_FAMILY, minWidth: 320 }}
73+ className="font-normal text-foreground"
74+ style={{
75+ fontFamily: REVIEW_MONO_FONT_FAMILY,
76+ fontSize: MOBILE_CODE_SURFACE.fontSize,
77+ lineHeight: MOBILE_CODE_SURFACE.rowHeight,
78+ minWidth: 320,
79+ }}
7280 >
7381 {props.tokens && props.tokens.length > 0
7482 ? (() => {
@@ -80,7 +88,7 @@ const HighlightedSourceLine = memo(function HighlightedSourceLine(props: { · 5 unmodified lines
8088 const fontWeight =
8189 token.fontStyle !== null && (token.fontStyle & 2) === 2
8290 ? ("700" as const)
83− : ("500" as const);
91+ : ("400" as const);
8492 const fontStyle =
8593 token.fontStyle !== null && (token.fontStyle & 1) === 1
8694 ? ("italic" as const)
@@ -142,9 +150,7 @@ function SourceHighlightStatusView(props: { readonly status: SourceHighlightStat · 55 unmodified lines
142150 if (props.status === "error") {
143151 return (
144152 <View className="border-b border-border bg-card px-4 py-2">
145− <Text className="text-[11px] font-t3-medium uppercase text-foreground-muted">
146− Plain text
147− </Text>
153+ <Text className="text-2xs font-t3-medium uppercase text-foreground-muted">Plain text</Text>
148154 </View>
149155 );
150156 }
apps/mobile/src/features/files/ThreadFilesRouteScreen.tsx+8−7
@@ -24,6 +24,7 @@ import { EmptyState } from "../../components/EmptyState"; · 23 unmodified lines
2424 import { LoadingScreen } from "../../components/LoadingScreen";
2525 import { cn } from "../../lib/cn";
2626 import { buildThreadFilesNavigation } from "../../lib/routes";
27+import { MOBILE_TYPOGRAPHY } from "../../lib/typography";
2728 import { useThemeColor } from "../../lib/useThemeColor";
2829 import { useThreadSelection } from "../../state/use-thread-selection";
2930 import { useSelectedThreadWorktree } from "../../state/use-selected-thread-worktree";
@@ -100,7 +101,7 @@ function ModeButton(props: { · 70 unmodified lines
100101 <SymbolView name={props.icon} size={13} tintColor={iconColor} type="monochrome" />
101102 <Text
102103 className={cn(
103− "text-[12px] font-t3-bold",
104+ "text-xs font-t3-bold",
104105 props.active ? "text-primary-foreground" : "text-foreground-muted",
105106 )}
106107 >
@@ -187,7 +188,7 @@ function FileBreadcrumbs(props: { readonly projectName: string; readonly relativ · 80 unmodified lines
187188 ) : null}
188189 <Text
189190 className={cn(
190− "max-w-[180px] px-1 text-[12px]",
191+ "max-w-[180px] px-1 text-xs",
191192 crumb.kind === "file"
192193 ? "font-t3-bold text-foreground"
193194 : "font-t3-medium text-foreground-muted",
@@ -308,7 +309,7 @@ function FileContent(props: { · 114 unmodified lines
308309 return (
309310 <View className="flex-1 items-center justify-center gap-3 bg-card px-6">
310311 <ActivityIndicator />
311− <Text className="text-center text-[13px] text-foreground-muted">Loading file...</Text>
312+ <Text className="text-center text-sm text-foreground-muted">Loading file...</Text>
312313 </View>
313314 );
314315 }
@@ -317,10 +318,10 @@ function FileContent(props: { · 2 unmodified lines
317318 <View className="flex-1 bg-card">
318319 {props.truncated ? (
319320 <View className="border-b border-amber-200 bg-amber-50 px-4 py-2 dark:border-amber-900/60 dark:bg-amber-950/40">
320− <Text className="text-[11px] font-t3-bold uppercase text-amber-700 dark:text-amber-300">
321+ <Text className="text-2xs font-t3-bold uppercase text-amber-700 dark:text-amber-300">
321322 Partial file
322323 </Text>
323− <Text className="text-[12px] leading-[17px] text-amber-800 dark:text-amber-200">
324+ <Text className="text-xs leading-[17px] text-amber-800 dark:text-amber-200">
324325 Preview limited to the first 1 MB of a truncated file.
325326 </Text>
326327 </View>
@@ -389,7 +390,7 @@ function FilesHeaderTitle(props: { readonly projectName: string }) { · 62 unmodified lines
389390 style={{
390391 color: foregroundColor,
391392 fontFamily: "DMSans_700Bold",
392− fontSize: 18,
393+ fontSize: MOBILE_TYPOGRAPHY.headline.fontSize,
393394 fontWeight: "900",
394395 letterSpacing: -0.4,
395396 }}
@@ -401,7 +402,7 @@ function FilesHeaderTitle(props: { readonly projectName: string }) { · 5 unmodified lines
401402 style={{
402403 color: secondaryForegroundColor,
403404 fontFamily: "DMSans_500Medium",
404− fontSize: 12,
405+ fontSize: MOBILE_TYPOGRAPHY.label.fontSize,
405406 fontWeight: "500",
406407 letterSpacing: 0.2,
407408 }}
apps/mobile/src/features/files/WorkspaceFileImagePreview.tsx+2−2
@@ -81,7 +81,7 @@ function CachedWorkspaceFileImagePreview(props: { · 80 unmodified lines
8181 return (
8282 <View className="flex-1 items-center justify-center gap-3 bg-card px-6">
8383 <ActivityIndicator />
84− <Text className="text-center text-[13px] text-foreground-muted">Loading image...</Text>
84+ <Text className="text-center text-sm text-foreground-muted">Loading image...</Text>
8585 </View>
8686 );
8787 }
@@ -102,7 +102,7 @@ export function WorkspaceFileImagePreview(props: { · 14 unmodified lines
102102 return (
103103 <View className="flex-1 items-center justify-center gap-3 bg-card px-6">
104104 <ActivityIndicator />
105− <Text className="text-center text-[13px] text-foreground-muted">
105+ <Text className="text-center text-sm text-foreground-muted">
106106 Preparing image preview...
107107 </Text>
108108 </View>
apps/mobile/src/features/files/WorkspaceFileWebPreview.tsx+3−5
@@ -13,7 +13,7 @@ export function WorkspaceFileWebPreview(props: { readonly uri: string | null }) · 12 unmodified lines
1313 return (
1414 <View className="flex-1 items-center justify-center gap-3 bg-card px-6">
1515 <ActivityIndicator />
16− <Text className="text-center text-[13px] text-foreground-muted">Preparing preview...</Text>
16+ <Text className="text-center text-sm text-foreground-muted">Preparing preview...</Text>
1717 </View>
1818 );
1919 }
@@ -23,10 +23,8 @@ export function WorkspaceFileWebPreview(props: { readonly uri: string | null }) · 3 unmodified lines
2323 {loadProgress > 0 && loadProgress < 1 ? <LoadingStrip progress={loadProgress} /> : null}
2424 {loadError ? (
2525 <View className="border-b border-border bg-card px-4 py-2">
26− <Text className="text-[12px] font-t3-bold text-foreground">Preview failed</Text>
27− <Text className="mt-0.5 text-[12px] leading-[17px] text-foreground-muted">
28− {loadError}
29− </Text>
26+ <Text className="text-xs font-t3-bold text-foreground">Preview failed</Text>
27+ <Text className="mt-0.5 text-xs leading-[17px] text-foreground-muted">{loadError}</Text>
3028 </View>
3129 ) : null}
3230 <WebView
apps/mobile/src/features/files/nativeSourceFileAdapter.test.ts+20−0
@@ -3,10 +3,30 @@ import { describe, expect, it } from "vite-plus/test"; · 2 unmodified lines
33 import {
44 buildNativeSourceRows,
55 buildNativeSourceTokens,
6+ NATIVE_SOURCE_ROW_HEIGHT,
7+ NATIVE_SOURCE_STYLE,
68 nativeSourceRowId,
79 } from "./nativeSourceFileAdapter";
10+import {
11+ NATIVE_REVIEW_DIFF_ROW_HEIGHT,
12+ NATIVE_REVIEW_DIFF_STYLE,
13+} from "../review/nativeReviewDiffAdapter";
814
915 describe("nativeSourceFileAdapter", () => {
16+ it("uses the same compact code typography as the diff viewer", () => {
17+ expect(NATIVE_SOURCE_ROW_HEIGHT).toBe(NATIVE_REVIEW_DIFF_ROW_HEIGHT);
18+ expect(NATIVE_SOURCE_STYLE).toMatchObject({
19+ rowHeight: NATIVE_REVIEW_DIFF_STYLE.rowHeight,
20+ gutterWidth: NATIVE_REVIEW_DIFF_STYLE.gutterWidth,
21+ codePadding: NATIVE_REVIEW_DIFF_STYLE.codePadding,
22+ textVerticalInset: NATIVE_REVIEW_DIFF_STYLE.textVerticalInset,
23+ codeFontSize: NATIVE_REVIEW_DIFF_STYLE.codeFontSize,
24+ codeFontWeight: NATIVE_REVIEW_DIFF_STYLE.codeFontWeight,
25+ lineNumberFontSize: NATIVE_REVIEW_DIFF_STYLE.lineNumberFontSize,
26+ lineNumberFontWeight: NATIVE_REVIEW_DIFF_STYLE.lineNumberFontWeight,
27+ });
28+ });
29+
1030 it("maps plain source lines onto context rows with stable line numbers", () => {
1131 expect(buildNativeSourceRows(["const value = 1;", "\treturn value;"])).toEqual([
1232 {
apps/mobile/src/features/files/nativeSourceFileAdapter.ts+10−8
@@ -3,22 +3,24 @@ import type { · 2 unmodified lines
33 NativeReviewDiffStyle,
44 NativeReviewDiffToken,
55 } from "../diffs/nativeReviewDiffSurface";
6+import { MOBILE_CODE_SURFACE, MOBILE_TYPOGRAPHY } from "../../lib/typography";
67 import type { SourceHighlightTokens } from "./sourceHighlightingState";
78
8−export const NATIVE_SOURCE_ROW_HEIGHT = 24;
9+export const NATIVE_SOURCE_ROW_HEIGHT = MOBILE_CODE_SURFACE.rowHeight;
910 export const NATIVE_SOURCE_CONTENT_WIDTH = 32_000;
1011
1112 export const NATIVE_SOURCE_STYLE: NativeReviewDiffStyle = {
1213 rowHeight: NATIVE_SOURCE_ROW_HEIGHT,
1314 contentWidth: NATIVE_SOURCE_CONTENT_WIDTH,
1415 changeBarWidth: 0,
15− gutterWidth: 58,
16− codePadding: 8,
17− codeFontSize: 13,
18− codeFontWeight: "medium",
19− lineNumberFontSize: 11,
20− lineNumberFontWeight: "medium",
21− emptyStateFontSize: 12,
16+ gutterWidth: MOBILE_CODE_SURFACE.gutterWidth,
17+ codePadding: MOBILE_CODE_SURFACE.codePadding,
18+ textVerticalInset: MOBILE_CODE_SURFACE.textVerticalInset,
19+ codeFontSize: MOBILE_CODE_SURFACE.fontSize,
20+ codeFontWeight: "regular",
21+ lineNumberFontSize: MOBILE_CODE_SURFACE.lineNumberFontSize,
22+ lineNumberFontWeight: "regular",
23+ emptyStateFontSize: MOBILE_TYPOGRAPHY.label.fontSize,
2224 emptyStateFontWeight: "medium",
2325 };
2426
apps/mobile/src/features/home/HomeHeader.tsx+3−2
@@ -12,6 +12,7 @@ import { Stack } from "expo-router"; · 11 unmodified lines
1212 import { Text as RNText, View } from "react-native";
1313
1414 import { useThemeColor } from "../../lib/useThemeColor";
15+import { MOBILE_TYPOGRAPHY } from "../../lib/typography";
1516 import type { HomeProjectSortOrder } from "./homeThreadList";
1617
1718 export interface HomeHeaderEnvironment {
@@ -118,7 +119,7 @@ export function HomeHeader(props: { · 100 unmodified lines
118119 <RNText
119120 style={{
120121 fontFamily: "DMSans_700Bold",
121− fontSize: 17,
122+ fontSize: MOBILE_TYPOGRAPHY.headline.fontSize,
122123 color: iconColor,
123124 letterSpacing: -0.4,
124125 }}
@@ -136,7 +137,7 @@ export function HomeHeader(props: { · 11 unmodified lines
136137 <RNText
137138 style={{
138139 fontFamily: "DMSans_700Bold",
139− fontSize: 10,
140+ fontSize: MOBILE_TYPOGRAPHY.micro.fontSize,
140141 color: mutedColor,
141142 letterSpacing: 1.1,
142143 textTransform: "uppercase",
apps/mobile/src/features/home/HomeScreen.tsx+7−7
@@ -186,7 +186,7 @@ function ProjectGroupLabel(props: { · 185 unmodified lines
186186 workspaceRoot={props.project.workspaceRoot}
187187 />
188188 <Text
189− className="flex-1 text-[12px] font-t3-medium uppercase text-foreground-muted"
189+ className="flex-1 text-xs font-t3-medium uppercase text-foreground-muted"
190190 style={{ letterSpacing: 0.5 }}
191191 numberOfLines={1}
192192 >
@@ -196,7 +196,7 @@ function ProjectGroupLabel(props: { · 3 unmodified lines
196196 {hiddenCount > 0 ? (
197197 <Pressable onPress={props.onToggleExpand} hitSlop={8}>
198198 <Text
199− className="text-[12px] font-t3-medium text-foreground-muted"
199+ className="text-xs font-t3-medium text-foreground-muted"
200200 style={{ letterSpacing: 0.4 }}
201201 >
202202 {props.isExpanded ? "Show less" : `${hiddenCount} more`}
@@ -335,7 +335,7 @@ function ThreadRow(props: { · 132 unmodified lines
335335 <View style={{ flex: 1, gap: 3 }}>
336336 <View className="flex-row items-center justify-between gap-2">
337337 <Text
338− className="flex-1 text-[15px] font-t3-bold leading-[20px] text-foreground"
338+ className="flex-1 text-base font-t3-bold leading-[20px] text-foreground"
339339 numberOfLines={1}
340340 >
341341 {props.thread.title}
@@ -345,12 +345,12 @@ function ThreadRow(props: { · 3 unmodified lines
345345 className={tone.pillClassName}
346346 style={{ borderRadius: 99, paddingHorizontal: 6, paddingVertical: 2 }}
347347 >
348− <Text className={`text-[10px] font-t3-bold ${tone.textClassName}`}>
348+ <Text className={`text-3xs font-t3-bold ${tone.textClassName}`}>
349349 {tone.label}
350350 </Text>
351351 </View>
352352 <Text
353− className="text-[12px] text-foreground-tertiary"
353+ className="text-xs text-foreground-tertiary"
354354 style={{ fontVariant: ["tabular-nums"] }}
355355 >
356356 {timestamp}
@@ -367,7 +367,7 @@ function ThreadRow(props: { · 10 unmodified lines
367367 type="monochrome"
368368 />
369369 <Text
370− className="text-[11px] text-foreground-tertiary"
370+ className="text-2xs text-foreground-tertiary"
371371 numberOfLines={1}
372372 style={{ fontFamily: "monospace" }}
373373 >
@@ -429,7 +429,7 @@ function StaleCatalogStatusPill(props: { · 55 unmodified lines
429429 weight="semibold"
430430 />
431431 )}
432− <Text className="max-w-[260px] text-[13px] font-t3-bold text-foreground" numberOfLines={1}>
432+ <Text className="max-w-[260px] text-sm font-t3-bold text-foreground" numberOfLines={1}>
433433 {label}
434434 </Text>
435435 </Pressable>
apps/mobile/src/features/home/thread-swipe-actions.tsx+1−1
@@ -167,7 +167,7 @@ function SwipeActionButton(props: { · 166 unmodified lines
167167 </Animated.View>
168168 </View>
169169 <Animated.View style={[{ paddingTop: 2 }, labelStyle]}>
170− <Text className="text-[10px] font-t3-medium text-foreground-muted">{props.label}</Text>
170+ <Text className="text-3xs font-t3-medium text-foreground-muted">{props.label}</Text>
171171 </Animated.View>
172172 </Pressable>
173173 </Animated.View>
apps/mobile/src/features/projects/AddProjectScreen.tsx+11−11
@@ -100,7 +100,7 @@ function sourceFromParam(value: string | string[] | undefined): AddProjectRemote · 99 unmodified lines
100100 function SectionTitle(props: { readonly children: string }) {
101101 return (
102102 <Text
103− className="px-1 text-[11px] font-t3-bold uppercase text-foreground-muted"
103+ className="px-1 text-2xs font-t3-bold uppercase text-foreground-muted"
104104 style={{ letterSpacing: 0.7 }}
105105 >
106106 {props.children}
@@ -168,9 +168,9 @@ function ListRow(props: { · 61 unmodified lines
168168 {props.icon}
169169 </View>
170170 <View className="flex-1 gap-0.5">
171− <Text className="text-[16px] leading-[21px] font-t3-bold">{props.title}</Text>
171+ <Text className="text-base leading-[21px] font-t3-bold">{props.title}</Text>
172172 {props.subtitle ? (
173− <Text className="text-[13px] leading-[17px] text-foreground-muted" numberOfLines={2}>
173+ <Text className="text-sm leading-[17px] text-foreground-muted" numberOfLines={2}>
174174 {props.subtitle}
175175 </Text>
176176 ) : null}
@@ -202,7 +202,7 @@ function PrimaryActionButton(props: { · 25 unmodified lines
202202 {props.loading ? (
203203 <ActivityIndicator color={String(primaryForeground)} />
204204 ) : (
205− <Text className="text-[14px] font-t3-bold text-primary-foreground">{props.label}</Text>
205+ <Text className="text-base font-t3-bold text-primary-foreground">{props.label}</Text>
206206 )}
207207 </Pressable>
208208 );
@@ -215,7 +215,7 @@ function ProjectPathInput(props: { · 6 unmodified lines
215215 }) {
216216 return (
217217 <TextInput
218− className="h-12 min-h-12 rounded-[24px] px-4 py-0 text-[15px] leading-[20px]"
218+ className="h-12 min-h-12 rounded-[24px] px-4 py-0 text-base leading-[20px]"
219219 value={props.value}
220220 onChangeText={props.onChangeText}
221221 autoCapitalize="none"
@@ -273,15 +273,15 @@ function EmptyEnvironmentState() { · 51 unmodified lines
273273
274274 return (
275275 <View className="items-center gap-3 rounded-2xl bg-card px-5 py-8">
276− <Text className="text-center text-[17px] font-t3-bold">No environments connected</Text>
277− <Text className="text-center text-[14px] leading-[20px] text-foreground-muted">
276+ <Text className="text-center text-lg font-t3-bold">No environments connected</Text>
277+ <Text className="text-center text-sm leading-[20px] text-foreground-muted">
278278 Add an environment before adding a project.
279279 </Text>
280280 <Pressable
281281 onPress={() => router.replace("/connections/new")}
282282 className="mt-1 rounded-full bg-primary px-4 py-2.5 active:opacity-70"
283283 >
284− <Text className="text-[13px] font-t3-bold text-primary-foreground">Add environment</Text>
284+ <Text className="text-sm font-t3-bold text-primary-foreground">Add environment</Text>
285285 </Pressable>
286286 </View>
287287 );
@@ -565,7 +565,7 @@ export function AddProjectRepositoryScreen() { · 277 unmodified lines
565565 <AddProjectShell>
566566 {error ? <ErrorBanner message={error} /> : null}
567567 <TextInput
568− className="h-12 min-h-12 rounded-[24px] px-4 py-0 text-[15px] leading-[20px]"
568+ className="h-12 min-h-12 rounded-[24px] px-4 py-0 text-base leading-[20px]"
569569 value={repositoryInput}
570570 onChangeText={setRepositoryInput}
571571 autoCapitalize="none"
@@ -806,8 +806,8 @@ export function AddProjectDestinationScreen() { · 234 unmodified lines
806806 {error ? <ErrorBanner message={error} /> : null}
807807 {repositoryTitle ? (
808808 <View className="rounded-[24px] bg-card px-4 py-3">
809− <Text className="text-[14px] font-t3-bold">{repositoryTitle}</Text>
810− <Text className="mt-0.5 text-[12px] text-foreground-muted" numberOfLines={2}>
809+ <Text className="text-base font-t3-bold">{repositoryTitle}</Text>
810+ <Text className="mt-0.5 text-xs text-foreground-muted" numberOfLines={2}>
811811 {remoteUrl}
812812 </Text>
813813 </View>
apps/mobile/src/features/review/ReviewCommentComposerSheet.tsx+8−8
@@ -159,26 +159,26 @@ export function ReviewCommentComposerSheet() { · 158 unmodified lines
159159 <SymbolView name="xmark" size={18} tintColor={iconTint} type="monochrome" />
160160 </Pressable>
161161
162− <Text className="text-[18px] font-t3-bold text-foreground">Add Comment</Text>
162+ <Text className="text-lg font-t3-bold text-foreground">Add Comment</Text>
163163
164164 <View className="h-12 w-12" />
165165 </View>
166166
167167 {!target ? (
168168 <View className="rounded-[22px] border border-border bg-card px-4 py-5">
169− <Text className="text-[15px] font-t3-bold text-foreground">No selection</Text>
170− <Text className="mt-1 text-[13px] leading-[19px] text-foreground-muted">
169+ <Text className="text-base font-t3-bold text-foreground">No selection</Text>
170+ <Text className="mt-1 text-sm leading-[19px] text-foreground-muted">
171171 Select a diff line or range first.
172172 </Text>
173173 </View>
174174 ) : (
175175 <View className="min-h-0 flex-1 gap-4">
176176 <View className="gap-1 px-1">
177− <Text className="text-[11px] font-t3-bold uppercase text-foreground-muted">
177+ <Text className="text-2xs font-t3-bold uppercase text-foreground-muted">
178178 {selectionLabel}
179179 </Text>
180180 <Text
181− className="font-mono text-[12px] leading-[17px] text-foreground-muted"
181+ className="font-mono text-xs leading-[17px] text-foreground-muted"
182182 ellipsizeMode="middle"
183183 numberOfLines={2}
184184 >
@@ -215,7 +215,7 @@ export function ReviewCommentComposerSheet() { · 30 unmodified lines
215215 >
216216 <ReviewChangeBar change={line.change} />
217217 <Text
218− className="w-9 py-1 pr-1 text-right text-[11px] font-t3-medium text-foreground-muted"
218+ className="w-9 py-1 pr-1 text-right text-2xs font-t3-medium text-foreground-muted"
219219 style={{ fontFamily: REVIEW_MONO_FONT_FAMILY }}
220220 >
221221 {lineNumber ?? ""}
@@ -236,7 +236,7 @@ export function ReviewCommentComposerSheet() { · 14 unmodified lines
236236 </View>
237237
238238 <View className="min-h-0 flex-1 gap-2">
239− <Text className="text-[13px] font-t3-bold text-foreground">Comment</Text>
239+ <Text className="text-sm font-t3-bold text-foreground">Comment</Text>
240240 <View className="min-h-[132px] flex-1 overflow-hidden rounded-[20px] border border-border bg-card">
241241 <View className="min-h-0 flex-1 px-4 pt-3.5">
242242 <TextInputWrapper onPaste={handleNativePaste} style={{ flex: 1, minHeight: 0 }}>
@@ -248,7 +248,7 @@ export function ReviewCommentComposerSheet() { · 5 unmodified lines
248248 textAlignVertical="top"
249249 value={commentText}
250250 onChangeText={setCommentText}
251− className="h-full flex-1 border-0 bg-transparent px-0 py-0 font-sans text-[15px]"
251+ className="h-full flex-1 border-0 bg-transparent px-0 py-0 font-sans text-base"
252252 style={{ flex: 1, minHeight: 0 }}
253253 />
254254 </TextInputWrapper>
apps/mobile/src/features/review/ReviewSheet.tsx+19−18
@@ -20,6 +20,7 @@ import { environmentCatalog } from "../../connection/catalog"; · 19 unmodified lines
2020 import { useEnvironmentPresentation } from "../../state/presentation";
2121 import { useAtomCommand } from "../../state/use-atom-command";
2222 import { useThemeColor } from "../../lib/useThemeColor";
23+import { MOBILE_TYPOGRAPHY } from "../../lib/typography";
2324 import { useThreadDraftForThread } from "../../state/use-thread-composer-state";
2425 import { EnvironmentConnectionNotice } from "../connection/EnvironmentConnectionNotice";
2526 import { useReviewCacheForThread } from "./reviewState";
@@ -41,10 +42,10 @@ const REVIEW_HEADER_SPACING = 0; · 15 unmodified lines
4142 const ReviewNotice = memo(function ReviewNotice(props: { readonly notice: string }) {
4243 return (
4344 <View className="border-b border-amber-200 bg-amber-50 px-4 py-3 dark:border-amber-900/60 dark:bg-amber-950/40">
44− <Text className="text-[12px] font-t3-bold uppercase text-amber-700 dark:text-amber-300">
45+ <Text className="text-xs font-t3-bold uppercase text-amber-700 dark:text-amber-300">
4546 Partial diff
4647 </Text>
47− <Text className="text-[12px] leading-[18px] text-amber-800 dark:text-amber-200">
48+ <Text className="text-xs leading-[18px] text-amber-800 dark:text-amber-200">
4849 {props.notice}
4950 </Text>
5051 </View>
@@ -69,7 +70,7 @@ function ReviewSelectionActionBar(props: { · 18 unmodified lines
6970 tintColor="#ffffff"
7071 type="monochrome"
7172 />
72− <Text className="text-[15px] font-t3-bold text-white">{props.title}</Text>
73+ <Text className="text-base font-t3-bold text-white">{props.title}</Text>
7374 </>
7475 );
7576
@@ -218,8 +219,8 @@ export function ReviewSheet() { · 142 unmodified lines
218219 if (error) {
219220 children.push(
220221 <View key="review-error" className="border-b border-border bg-card px-4 py-3">
221− <Text className="text-[13px] font-t3-bold text-foreground">Review unavailable</Text>
222− <Text className="text-[12px] leading-[18px] text-foreground-muted">{error}</Text>
222+ <Text className="text-sm font-t3-bold text-foreground">Review unavailable</Text>
223+ <Text className="text-xs leading-[18px] text-foreground-muted">{error}</Text>
223224 </View>,
224225 );
225226 }
@@ -251,7 +252,7 @@ export function ReviewSheet() { · 25 unmodified lines
251252 numberOfLines={1}
252253 style={{
253254 fontFamily: "DMSans_700Bold",
254− fontSize: 18,
255+ fontSize: MOBILE_TYPOGRAPHY.headline.fontSize,
255256 fontWeight: "900",
256257 color: headerForeground,
257258 letterSpacing: -0.4,
@@ -273,7 +274,7 @@ export function ReviewSheet() { · 15 unmodified lines
273274 <NativeText
274275 style={{
275276 fontFamily: "DMSans_700Bold",
276− fontSize: 12,
277+ fontSize: MOBILE_TYPOGRAPHY.label.fontSize,
277278 fontWeight: "700",
278279 color: "#16a34a",
279280 }}
@@ -283,7 +284,7 @@ export function ReviewSheet() { · 3 unmodified lines
283284 <NativeText
284285 style={{
285286 fontFamily: "DMSans_700Bold",
286− fontSize: 12,
287+ fontSize: MOBILE_TYPOGRAPHY.label.fontSize,
287288 fontWeight: "700",
288289 color: "#e11d48",
289290 }}
@@ -294,7 +295,7 @@ export function ReviewSheet() { · 4 unmodified lines
294295 <NativeText
295296 style={{
296297 fontFamily: "DMSans_700Bold",
297− fontSize: 12,
298+ fontSize: MOBILE_TYPOGRAPHY.label.fontSize,
298299 fontWeight: "700",
299300 color: "#b45309",
300301 }}
@@ -309,7 +310,7 @@ export function ReviewSheet() { · 8 unmodified lines
309310 numberOfLines={1}
310311 style={{
311312 fontFamily: "DMSans_700Bold",
312− fontSize: 12,
313+ fontSize: MOBILE_TYPOGRAPHY.label.fontSize,
313314 fontWeight: "700",
314315 color: headerMuted,
315316 }}
@@ -320,7 +321,7 @@ export function ReviewSheet() { · 4 unmodified lines
320321 <NativeText
321322 style={{
322323 fontFamily: "DMSans_700Bold",
323− fontSize: 12,
324+ fontSize: MOBILE_TYPOGRAPHY.label.fontSize,
324325 fontWeight: "700",
325326 color: "#b45309",
326327 }}
@@ -430,30 +431,30 @@ export function ReviewSheet() { · 103 unmodified lines
430431 {listHeader}
431432 {!selectedSection ? (
432433 <View className="border-b border-border bg-card px-4 py-5">
433− <Text className="text-[14px] font-t3-bold text-foreground">No review diffs</Text>
434− <Text className="text-[12px] leading-[18px] text-foreground-muted">
434+ <Text className="text-sm font-t3-bold text-foreground">No review diffs</Text>
435+ <Text className="text-xs leading-[18px] text-foreground-muted">
435436 This thread has no ready turn diffs and the worktree diff is empty.
436437 </Text>
437438 </View>
438439 ) : selectedSection.isLoading && selectedSection.diff === null ? (
439440 <View className="items-center gap-3 border-b border-border bg-card px-4 py-6">
440441 <ActivityIndicator size="small" />
441− <Text className="text-[12px] text-foreground-muted">Loading diff…</Text>
442+ <Text className="text-xs text-foreground-muted">Loading diff…</Text>
442443 </View>
443444 ) : parsedDiff.kind === "empty" ? (
444445 <View className="border-b border-border bg-card px-4 py-5">
445− <Text className="text-[14px] font-t3-bold text-foreground">No changes</Text>
446− <Text className="text-[12px] leading-[18px] text-foreground-muted">
446+ <Text className="text-sm font-t3-bold text-foreground">No changes</Text>
447+ <Text className="text-xs leading-[18px] text-foreground-muted">
447448 {selectedSection.subtitle ?? "This diff is empty."}
448449 </Text>
449450 </View>
450451 ) : parsedDiff.kind === "raw" ? (
451452 <View className="gap-3 border-b border-border bg-card px-4 py-4">
452− <Text className="text-[12px] leading-[18px] text-foreground-muted">
453+ <Text className="text-xs leading-[18px] text-foreground-muted">
453454 {parsedDiff.reason}
454455 </Text>
455456 <ScrollView horizontal showsHorizontalScrollIndicator={false} bounces={false}>
456− <Text selectable className="font-mono text-[12px] leading-[19px] text-foreground">
457+ <Text selectable className="font-mono text-xs leading-[19px] text-foreground">
457458 {parsedDiff.text}
458459 </Text>
459460 </ScrollView>
apps/mobile/src/features/review/nativeReviewDiffAdapter.ts+7−6
@@ -5,6 +5,7 @@ import type { · 4 unmodified lines
55 } from "../diffs/nativeReviewDiffTypes";
66 import * as Arr from "effect/Array";
77 import { pipe } from "effect/Function";
8+import { MOBILE_CODE_SURFACE } from "../../lib/typography";
89 import { getPierreTerminalTheme, type TerminalAppearanceScheme } from "../terminal/terminalTheme";
910 import { computeWordAltDiffRanges } from "./reviewWordDiffs";
1011 import {
@@ -18,16 +19,16 @@ import type { ReviewInlineComment } from "./reviewCommentSelection"; · 7 unmodified lines
1819 const NATIVE_REVIEW_MAX_WORD_DIFF_RANGE_COUNT = 4;
1920 const NATIVE_REVIEW_MAX_WORD_DIFF_COVERAGE = 0.45;
2021
21−export const NATIVE_REVIEW_DIFF_ROW_HEIGHT = 20;
22+export const NATIVE_REVIEW_DIFF_ROW_HEIGHT = MOBILE_CODE_SURFACE.rowHeight;
2223 export const NATIVE_REVIEW_DIFF_CONTENT_WIDTH = 2_800;
2324
2425 export const NATIVE_REVIEW_DIFF_STYLE = {
2526 rowHeight: NATIVE_REVIEW_DIFF_ROW_HEIGHT,
2627 contentWidth: NATIVE_REVIEW_DIFF_CONTENT_WIDTH,
2728 changeBarWidth: 4,
28− gutterWidth: 46,
29− codePadding: 7,
30− textVerticalInset: 2,
29+ gutterWidth: MOBILE_CODE_SURFACE.gutterWidth,
30+ codePadding: MOBILE_CODE_SURFACE.codePadding,
31+ textVerticalInset: MOBILE_CODE_SURFACE.textVerticalInset,
3132 fileHeaderHeight: 56,
3233 fileHeaderHorizontalMargin: 8,
3334 fileHeaderVerticalMargin: 6,
@@ -36,9 +37,9 @@ export const NATIVE_REVIEW_DIFF_STYLE = { · 2 unmodified lines
3637 fileHeaderPathRightPadding: 118,
3738 fileHeaderCountColumnWidth: 38,
3839 fileHeaderCountGap: 5,
39− codeFontSize: 11,
40+ codeFontSize: MOBILE_CODE_SURFACE.fontSize,
4041 codeFontWeight: "regular",
41− lineNumberFontSize: 10,
42+ lineNumberFontSize: MOBILE_CODE_SURFACE.lineNumberFontSize,
4243 lineNumberFontWeight: "regular",
4344 hunkFontSize: 11,
4445 hunkFontWeight: "medium",
apps/mobile/src/features/review/reviewDiffRendering.tsx+14−5
@@ -1,6 +1,7 @@
11 import { Platform, Text as NativeText, View } from "react-native";
22
33 import { cn } from "../../lib/cn";
4+import { MOBILE_CODE_SURFACE } from "../../lib/typography";
45
56 import type { ReviewRenderableLineRow } from "./reviewModel";
67 import type { ReviewHighlightedToken } from "./shikiReviewHighlighter";
@@ -11,7 +12,7 @@ export const REVIEW_MONO_FONT_FAMILY = Platform.select({ · 4 unmodified lines
1112 default: "monospace",
1213 });
1314
14−export const REVIEW_DIFF_LINE_HEIGHT = 26;
15+export const REVIEW_DIFF_LINE_HEIGHT = MOBILE_CODE_SURFACE.rowHeight;
1516 const REVIEW_DELETE_STRIPE_COUNT = REVIEW_DIFF_LINE_HEIGHT / 2;
1617
1718 export function renderVisibleWhitespace(value: string): string {
@@ -71,8 +72,12 @@ export function DiffTokenText(props: { · 53 unmodified lines
7172 <NativeText
7273 numberOfLines={1}
7374 selectable
74− className={cn("text-[13px] leading-[17px] font-medium text-foreground", props.className)}
75− style={{ fontFamily: REVIEW_MONO_FONT_FAMILY }}
75+ className={cn("font-normal text-foreground", props.className)}
76+ style={{
77+ fontFamily: REVIEW_MONO_FONT_FAMILY,
78+ fontSize: MOBILE_CODE_SURFACE.fontSize,
79+ lineHeight: MOBILE_CODE_SURFACE.rowHeight,
80+ }}
7681 >
7782 {renderVisibleWhitespace(props.fallback || " ")}
7883 </NativeText>
@@ -83,8 +88,12 @@ export function DiffTokenText(props: { · 4 unmodified lines
8388 <NativeText
8489 numberOfLines={1}
8590 selectable
86− className={cn("text-[13px] leading-[17px] font-medium text-foreground", props.className)}
87− style={{ fontFamily: REVIEW_MONO_FONT_FAMILY }}
91+ className={cn("font-normal text-foreground", props.className)}
92+ style={{
93+ fontFamily: REVIEW_MONO_FONT_FAMILY,
94+ fontSize: MOBILE_CODE_SURFACE.fontSize,
95+ lineHeight: MOBILE_CODE_SURFACE.rowHeight,
96+ }}
8897 >
8998 {(() => {
9099 let offset = 0;
apps/mobile/src/features/terminal/NativeTerminalSurface.tsx+6−5
@@ -11,6 +11,7 @@ import { · 10 unmodified lines
1111 } from "react-native";
1212
1313 import { AppText as Text } from "../../components/AppText";
14+import { MOBILE_TYPOGRAPHY } from "../../lib/typography";
1415 import { resolveNativeTerminalSurfaceView } from "./nativeTerminalModule";
1516 import {
1617 buildGhosttyThemeConfig,
@@ -53,7 +54,7 @@ function estimateGridSize(input: { · 36 unmodified lines
5354 }
5455
5556 const FallbackTerminalSurface = memo(function FallbackTerminalSurface(props: TerminalSurfaceProps) {
56− const fontSize = props.fontSize ?? 12;
57+ const fontSize = props.fontSize ?? MOBILE_TYPOGRAPHY.label.fontSize;
5758 const inputRef = useRef<TextInput>(null);
5859 const appearanceScheme = useColorScheme() === "light" ? "light" : "dark";
5960 const theme = props.theme ?? getPierreTerminalTheme(appearanceScheme);
@@ -93,7 +94,7 @@ const FallbackTerminalSurface = memo(function FallbackTerminalSurface(props: Ter · 33 unmodified lines
9394 <Text
9495 style={{
9596 color: theme.mutedForeground,
96− fontSize: 11,
97+ fontSize: MOBILE_TYPOGRAPHY.caption.fontSize,
9798 paddingBottom: 8,
9899 }}
99100 >
@@ -140,7 +141,7 @@ const FallbackTerminalSurface = memo(function FallbackTerminalSurface(props: Ter · 40 unmodified lines
140141 color: theme.foreground,
141142 flex: 1,
142143 fontFamily: "Menlo",
143− fontSize: 13,
144+ fontSize: MOBILE_TYPOGRAPHY.footnote.fontSize,
144145 padding: 0,
145146 }}
146147 onSubmitEditing={(event) => {
@@ -165,7 +166,7 @@ const FallbackTerminalSurface = memo(function FallbackTerminalSurface(props: Ter · 18 unmodified lines
165166 style={{
166167 color: theme.foreground,
167168 fontFamily: "DMSans_700Bold",
168− fontSize: 11,
169+ fontSize: MOBILE_TYPOGRAPHY.caption.fontSize,
169170 }}
170171 >
171172 Ctrl-C
@@ -177,7 +178,7 @@ const FallbackTerminalSurface = memo(function FallbackTerminalSurface(props: Ter · 5 unmodified lines
177178 });
178179
179180 export const TerminalSurface = memo(function TerminalSurface(props: TerminalSurfaceProps) {
180− const fontSize = props.fontSize ?? 12;
181+ const fontSize = props.fontSize ?? MOBILE_TYPOGRAPHY.label.fontSize;
181182 const keyboardInputRef = useRef<TextInput>(null);
182183 const appearanceScheme = useColorScheme() === "light" ? "light" : "dark";
183184 const theme = props.theme ?? getPierreTerminalTheme(appearanceScheme);
apps/mobile/src/features/terminal/ThreadTerminalPanel.tsx+3−3
@@ -127,16 +127,16 @@ export const ThreadTerminalPanel = memo(function ThreadTerminalPanel( · 126 unmodified lines
127127 <View className="absolute inset-x-3 bottom-28 top-28 overflow-hidden rounded-[8px] border border-white/10 bg-neutral-950 shadow-2xl">
128128 <View className="flex-row items-center justify-between border-b border-white/10 px-3 py-2">
129129 <View className="min-w-0 flex-1">
130− <Text className="font-t3-bold text-[13px] text-neutral-100" numberOfLines={1}>
130+ <Text className="font-t3-bold text-sm text-neutral-100" numberOfLines={1}>
131131 Terminal
132132 </Text>
133− <Text className="text-[11px] text-neutral-500" numberOfLines={1}>
133+ <Text className="text-2xs text-neutral-500" numberOfLines={1}>
134134 {nativeTerminalAvailable ? "Native Ghostty surface" : "Text fallback active"}
135135 </Text>
136136 </View>
137137 <View className="flex-row items-center gap-2">
138138 {terminal.error ? (
139− <Text className="max-w-44 text-right text-[11px] text-red-300" numberOfLines={1}>
139+ <Text className="max-w-44 text-right text-2xs text-red-300" numberOfLines={1}>
140140 {terminal.error}
141141 </Text>
142142 ) : null}
apps/mobile/src/features/terminal/ThreadTerminalRouteScreen.tsx+3−2
@@ -25,6 +25,7 @@ import { terminalEnvironment } from "../../state/terminal"; · 24 unmodified lines
2525 import { useAtomCommand } from "../../state/use-atom-command";
2626 import { useWorkspaceState } from "../../state/workspace";
2727 import { buildThreadTerminalNavigation } from "../../lib/routes";
28+import { MOBILE_TYPOGRAPHY } from "../../lib/typography";
2829 import {
2930 useAttachedTerminalSession,
3031 useKnownTerminalSessions,
@@ -920,7 +921,7 @@ export function ThreadTerminalRouteScreen() { · 889 unmodified lines
920921 style={{
921922 color: terminalTheme.foreground,
922923 fontFamily: "DMSans_700Bold",
923− fontSize: 13,
924+ fontSize: MOBILE_TYPOGRAPHY.footnote.fontSize,
924925 lineHeight: 16,
925926 }}
926927 >
@@ -932,7 +933,7 @@ export function ThreadTerminalRouteScreen() { · 5 unmodified lines
932933 style={{
933934 color: terminalTheme.mutedForeground,
934935 fontFamily: "Menlo",
935− fontSize: 11,
936+ fontSize: MOBILE_TYPOGRAPHY.caption.fontSize,
936937 lineHeight: 14,
937938 }}
938939 >
apps/mobile/src/features/threads/ComposerCommandPopover.tsx+13−4
@@ -7,6 +7,7 @@ import { Pressable, ScrollView, useColorScheme, View, type ViewStyle } from "rea · 6 unmodified lines
77
88 import { AppText as Text } from "../../components/AppText";
99 import { PierreEntryIcon } from "../../components/PierreEntryIcon";
10+import { MOBILE_TYPOGRAPHY } from "../../lib/typography";
1011
1112 export type ComposerCommandItem =
1213 | {
@@ -156,14 +157,22 @@ const CommandRow = memo(function CommandRow(props: { · 143 unmodified lines
156157 <SymbolView name={iconName} size={14} tintColor={iconColor} type="monochrome" />
157158 ) : null}
158159 <Text
159− className="text-[14px] font-t3-medium text-foreground"
160+ className="text-base font-t3-medium text-foreground"
160161 numberOfLines={1}
161162 style={{ flexShrink: 0 }}
162163 >
163164 {props.item.label}
164165 </Text>
165166 {props.item.description ? (
166− <Text numberOfLines={1} style={{ flex: 1, minWidth: 0, fontSize: 12, color: "#a1a1aa" }}>
167+ <Text
168+ numberOfLines={1}
169+ style={{
170+ flex: 1,
171+ minWidth: 0,
172+ fontSize: MOBILE_TYPOGRAPHY.label.fontSize,
173+ color: "#a1a1aa",
174+ }}
175+ >
167176 {props.item.description}
168177 </Text>
169178 ) : null}
@@ -182,7 +191,7 @@ export const ComposerCommandPopover = memo(function ComposerCommandPopover( · 12 unmodified lines
182191 {label ? (
183192 <View style={{ paddingHorizontal: 14, paddingTop: 10, paddingBottom: 4 }}>
184193 <Text
185− className="text-[10px] font-t3-bold text-foreground-muted"
194+ className="text-3xs font-t3-bold text-foreground-muted"
186195 style={{ letterSpacing: 0.8, textTransform: "uppercase" }}
187196 >
188197 {label}
@@ -206,7 +215,7 @@ export const ComposerCommandPopover = memo(function ComposerCommandPopover( · 17 unmodified lines
206215 </ScrollView>
207216 ) : (
208217 <View style={{ paddingHorizontal: 14, paddingVertical: 10 }}>
209− <Text className="text-[12px] text-foreground-tertiary">
218+ <Text className="text-xs text-foreground-tertiary">
210219 {emptyText(props.triggerKind, props.isLoading)}
211220 </Text>
212221 </View>
apps/mobile/src/features/threads/GitActionProgressOverlay.tsx+2−2
@@ -73,12 +73,12 @@ function OverlayContent(props: { readonly progress: GitActionProgress }) { · 72 unmodified lines
7373
7474 <View className="flex-1 gap-0.5">
7575 {progress.label ? (
76− <Text className="text-[13px] font-t3-bold text-foreground" numberOfLines={1}>
76+ <Text className="text-sm font-t3-bold text-foreground" numberOfLines={1}>
7777 {progress.label}
7878 </Text>
7979 ) : null}
8080 {progress.description ? (
81− <Text className="text-[11px] text-foreground-muted" numberOfLines={1}>
81+ <Text className="text-2xs text-foreground-muted" numberOfLines={1}>
8282 {progress.description}
8383 </Text>
8484 ) : null}
apps/mobile/src/features/threads/NewTaskDraftScreen.tsx+2−1
@@ -30,6 +30,7 @@ import { · 29 unmodified lines
3030 resolveProviderOptionDescriptors,
3131 } from "../../lib/providerOptions";
3232 import { buildThreadRoutePath } from "../../lib/routes";
33+import { MOBILE_TYPOGRAPHY } from "../../lib/typography";
3334 import { useProjects } from "../../state/entities";
3435 import { branchBadgeLabel, useNewTaskFlow } from "./new-task-flow-provider";
3536 import { useCreateProjectThread } from "./use-project-actions";
@@ -430,7 +431,7 @@ export function NewTaskDraftScreen(props: { · 394 unmodified lines
430431 onPasteImages={(uris) => void handleNativePasteImages(uris)}
431432 placeholder={`Describe a coding task in ${selectedProject.title}`}
432433 style={{ flex: 1, minHeight: 0 }}
433− textStyle={{ fontSize: 18, lineHeight: 28 }}
434+ textStyle={MOBILE_TYPOGRAPHY.composer}
434435 />
435436 </View>
436437
apps/mobile/src/features/threads/PendingApprovalCard.tsx+1−1
@@ -16,7 +16,7 @@ export interface PendingApprovalCardProps { · 15 unmodified lines
1616 export function PendingApprovalCard(props: PendingApprovalCardProps) {
1717 return (
1818 <View className="gap-2.5 rounded-[20px] border border-neutral-200 bg-neutral-100/80 p-4 dark:border-white/6 dark:bg-neutral-900/80">
19− <Text className="font-t3-bold text-[11px] uppercase tracking-[1.1px] text-sky-700 dark:text-sky-300">
19+ <Text className="font-t3-bold text-2xs uppercase tracking-[1.1px] text-sky-700 dark:text-sky-300">
2020 Approval needed
2121 </Text>
2222 <Text className="font-t3-bold text-lg text-neutral-950 dark:text-neutral-50">
apps/mobile/src/features/threads/PendingUserInputCard.tsx+4−4
@@ -26,7 +26,7 @@ export interface PendingUserInputCardProps { · 25 unmodified lines
2626 export function PendingUserInputCard(props: PendingUserInputCardProps) {
2727 return (
2828 <View className="gap-2.5 rounded-[20px] border border-neutral-200 bg-neutral-100/80 p-4 dark:border-white/6 dark:bg-neutral-900/80">
29− <Text className="font-t3-bold text-[11px] uppercase tracking-[1.1px] text-sky-700 dark:text-sky-300">
29+ <Text className="font-t3-bold text-2xs uppercase tracking-[1.1px] text-sky-700 dark:text-sky-300">
3030 User input needed
3131 </Text>
3232 <Text className="font-t3-bold text-lg text-neutral-950 dark:text-neutral-50">
@@ -39,7 +39,7 @@ export function PendingUserInputCard(props: PendingUserInputCardProps) { · 6 unmodified lines
3939 <Text className="font-t3-bold text-xs uppercase tracking-[1px] text-neutral-500 dark:text-neutral-500">
4040 {question.header}
4141 </Text>
42− <Text className="font-sans text-[15px] leading-[21px] text-neutral-950 dark:text-neutral-50">
42+ <Text className="font-sans text-base leading-[21px] text-neutral-950 dark:text-neutral-50">
4343 {question.question}
4444 </Text>
4545 <View className="flex-row flex-wrap gap-2.5">
@@ -65,7 +65,7 @@ export function PendingUserInputCard(props: PendingUserInputCardProps) { · 19 unmodified lines
6565 >
6666 <Text
6767 className={cn(
68− "font-t3-bold text-[13px]",
68+ "font-t3-bold text-sm",
6969 selected
7070 ? "text-sky-700 dark:text-sky-300"
7171 : "text-neutral-600 dark:text-neutral-300",
@@ -83,7 +83,7 @@ export function PendingUserInputCard(props: PendingUserInputCardProps) { · 11 unmodified lines
8383 props.onChangeCustomAnswer(props.pendingUserInput.requestId, question.id, value)
8484 }
8585 placeholder="Or type a custom answer"
86− className="min-h-[54px] rounded-2xl border border-neutral-200 bg-white px-3.5 py-3 font-sans text-[15px] text-neutral-950 dark:border-white/8 dark:bg-neutral-950/70 dark:text-neutral-50"
86+ className="min-h-[54px] rounded-2xl border border-neutral-200 bg-white px-3.5 py-3 font-sans text-base text-neutral-950 dark:border-white/8 dark:bg-neutral-950/70 dark:text-neutral-50"
8787 />
8888 </View>
8989 );
apps/mobile/src/features/threads/ThreadComposer.tsx+5−6
@@ -43,6 +43,7 @@ import { ControlPill, ControlPillMenu } from "../../components/ControlPill"; · 42 unmodified lines
4343 import { ProviderIcon } from "../../components/ProviderIcon";
4444 import type { DraftComposerImageAttachment } from "../../lib/composerImages";
4545 import { buildModelOptions, groupByProvider } from "../../lib/modelOptions";
46+import { MOBILE_TYPOGRAPHY } from "../../lib/typography";
4647 import type { RemoteClientConnectionState } from "../../lib/connection";
4748 import {
4849 insertRankedSearchResult,
@@ -189,7 +190,7 @@ const ComposerConnectionStatusPill = memo(function ComposerConnectionStatusPill( · 140 unmodified lines
189190 <View className="h-2 w-2 rounded-full bg-red-500" />
190191 )}
191192 <Text
192− className="max-w-[260px] text-[13px] font-t3-bold leading-[17px] text-foreground"
193+ className="max-w-[260px] text-sm font-t3-bold leading-[17px] text-foreground"
193194 numberOfLines={1}
194195 >
195196 {props.status.label}
@@ -717,8 +718,7 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer · 521 unmodified lines
717718 }
718719 }
719720 textStyle={{
720− fontSize: 15,
721− lineHeight: isExpanded ? 22 : 20,
721+ ...MOBILE_TYPOGRAPHY.composer,
722722 color: foregroundColor,
723723 fontFamily: "DMSans_400Regular",
724724 }}
@@ -751,7 +751,7 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer · 26 unmodified lines
751751 justifyContent: "center",
752752 }}
753753 >
754− <Text className="text-foreground-muted text-[11px] font-t3-bold">
754+ <Text className="text-foreground-muted text-2xs font-t3-bold">
755755 +{props.draftAttachments.length - 3}
756756 </Text>
757757 </View>
@@ -831,8 +831,7 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer · 73 unmodified lines
831831 <Text
832832 className="text-foreground-muted"
833833 style={{
834− fontSize: 12,
835− lineHeight: 18,
834+ ...MOBILE_TYPOGRAPHY.label,
836835 paddingTop: 8,
837836 }}
838837 >
apps/mobile/src/features/threads/ThreadFeed.tsx+12−14
@@ -56,6 +56,7 @@ import { buildReviewParsedDiff } from "../review/reviewModel"; · 55 unmodified lines
5656 import { cn } from "../../lib/cn";
5757 import type { LayoutVariant } from "../../lib/layout";
5858 import { buildThreadFilesNavigation } from "../../lib/routes";
59+import { MOBILE_CODE_SURFACE, MOBILE_TYPOGRAPHY } from "../../lib/typography";
5960 import { markdownFileIconSource } from "@t3tools/mobile-markdown-text/file-icons";
6061 import { resolveMarkdownLinkPresentation } from "@t3tools/mobile-markdown-text/links";
6162 import {
@@ -444,8 +445,7 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe · 382 unmodified lines
444445 marginRight: 5,
445446 color: inlineTextColor,
446447 fontFamily: "DMSans_400Regular",
447− fontSize: 15,
448− lineHeight: 22,
448+ ...MOBILE_TYPOGRAPHY.body,
449449 textAlign: ordered ? "right" : "center",
450450 }}
451451 >
@@ -466,7 +466,7 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe · 14 unmodified lines
466466 style={{
467467 color: inlineCodeTextColor,
468468 fontFamily: "ui-monospace",
469− fontSize: 12,
469+ fontSize: MOBILE_TYPOGRAPHY.label.fontSize,
470470 lineHeight: 22,
471471 }}
472472 >
@@ -503,7 +503,7 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe · 30 unmodified lines
503503 style={{
504504 color: markdownBodyColor,
505505 fontFamily: "ui-monospace",
506− fontSize: 12,
506+ fontSize: MOBILE_TYPOGRAPHY.label.fontSize,
507507 opacity: 0.7,
508508 textTransform: "uppercase",
509509 }}
@@ -523,7 +523,7 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe · 13 unmodified lines
523523 style={{
524524 color: blockTextColor,
525525 fontFamily: "ui-monospace",
526− fontSize: 12,
526+ fontSize: MOBILE_TYPOGRAPHY.label.fontSize,
527527 lineHeight: 18,
528528 }}
529529 >
@@ -603,8 +603,7 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe · 73 unmodified lines
603603 skillTextColor: "#f0abfc",
604604 quoteMarkerColor: markdownUserBodyColor,
605605 dividerColor: markdownUserBodyColor,
606− fontSize: 15,
607− lineHeight: 22,
606+ ...MOBILE_TYPOGRAPHY.body,
608607 fontFamily: "DMSans_400Regular",
609608 headingFontFamily: "DMSans_700Bold",
610609 boldFontFamily: "DMSans_700Bold",
@@ -633,8 +632,7 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe · 22 unmodified lines
633632 skillTextColor: inlineSkillForeground,
634633 quoteMarkerColor: markdownBlockquoteBorder,
635634 dividerColor: markdownHrColor,
636− fontSize: 15,
637− lineHeight: 22,
635+ ...MOBILE_TYPOGRAPHY.body,
638636 fontFamily: "DMSans_400Regular",
639637 headingFontFamily: "DMSans_700Bold",
640638 boldFontFamily: "DMSans_700Bold",
@@ -821,7 +819,7 @@ function renderFeedEntry( · 180 unmodified lines
821819 className="max-w-[85%] gap-2 rounded-[22px] rounded-br-[6px] px-3.5 py-2.5 opacity-60"
822820 style={{ backgroundColor: userBubbleColor }}
823821 >
824− <Text className="font-sans text-[15px] leading-[22px] text-white">
822+ <Text className="font-sans text-base leading-[22px] text-white">
825823 {entry.queuedMessage.text}
826824 </Text>
827825 {entry.queuedMessage.attachments.length > 0 ? (
@@ -995,7 +993,7 @@ const ReviewCommentCard = memo(function ReviewCommentCard(props: { · 167 unmodified lines
995993 </View>
996994 <View className="min-w-0 flex-1">
997995 <Text
998− className="font-mono text-[12px] leading-[16px]"
996+ className="font-mono text-xs leading-[16px]"
999997 numberOfLines={1}
1000998 style={{ color: props.colors.text }}
1001999 >
@@ -1040,8 +1038,8 @@ const ReviewCommentCard = memo(function ReviewCommentCard(props: { · 38 unmodified lines
10401038 style={{
10411039 color: props.colors.text,
10421040 fontFamily: "ui-monospace",
1043− fontSize: 12,
1044− lineHeight: 18,
1041+ fontSize: MOBILE_CODE_SURFACE.fontSize,
1042+ lineHeight: MOBILE_CODE_SURFACE.rowHeight,
10451043 }}
10461044 >
10471045 {props.comment.diff.trim()}
@@ -1052,7 +1050,7 @@ const ReviewCommentCard = memo(function ReviewCommentCard(props: { · 4 unmodified lines
10521050 <View className="border-t px-3 py-3" style={{ borderColor: props.colors.border }}>
10531051 <Text
10541052 selectable
1055− className="text-[15px] leading-[21px]"
1053+ className="text-base leading-[21px]"
10561054 style={{ color: props.colors.text }}
10571055 >
10581056 {props.comment.text}
apps/mobile/src/features/threads/ThreadNavigationDrawer.tsx+5−7
@@ -152,7 +152,7 @@ export function ThreadNavigationDrawer(props: { · 151 unmodified lines
152152 ]}
153153 >
154154 <View className="flex-row items-center justify-between px-4 pb-5">
155− <Text className="text-[26px] font-t3-bold">Threads</Text>
155+ <Text className="text-2xl font-t3-bold">Threads</Text>
156156 <Pressable
157157 onPress={() => {
158158 props.onClose();
@@ -224,7 +224,7 @@ function ThreadNavigationDrawerContent(props: { · 65 unmodified lines
224224 {groupedThreads.map((group) => (
225225 <View key={group.key} className="gap-3">
226226 <Text
227− className="px-1 text-[15px] font-t3-bold text-foreground-muted"
227+ className="px-1 text-base font-t3-bold text-foreground-muted"
228228 style={{ letterSpacing: -0.2 }}
229229 >
230230 {group.title}
@@ -233,9 +233,7 @@ function ThreadNavigationDrawerContent(props: { · 2 unmodified lines
233233 <View className="overflow-hidden rounded-[22px] bg-card">
234234 {group.threads.length === 0 ? (
235235 <View className="px-4 py-4">
236− <Text className="text-[14px] font-medium text-foreground-tertiary">
237− No threads yet
238− </Text>
236+ <Text className="text-sm font-medium text-foreground-tertiary">No threads yet</Text>
239237 </View>
240238 ) : (
241239 group.threads.map((thread, index) => {
@@ -260,11 +258,11 @@ function ThreadNavigationDrawerContent(props: { · 18 unmodified lines
260258 >
261259 <View className="flex-row items-start justify-between gap-3">
262260 <View className="flex-1 gap-1">
263− <Text className="text-[16px] font-t3-bold" numberOfLines={1}>
261+ <Text className="text-base font-t3-bold" numberOfLines={1}>
264262 {thread.title}
265263 </Text>
266264 <Text
267− className="text-[13px] font-medium text-foreground-muted"
265+ className="text-sm font-medium text-foreground-muted"
268266 numberOfLines={1}
269267 >
270268 {relativeTime(thread.updatedAt ?? thread.createdAt)}
apps/mobile/src/features/threads/ThreadRouteScreen.tsx+3−2
@@ -20,6 +20,7 @@ import { EmptyState } from "../../components/EmptyState"; · 19 unmodified lines
2020 import { LoadingScreen } from "../../components/LoadingScreen";
2121 import { buildThreadRoutePath, buildThreadTerminalNavigation } from "../../lib/routes";
2222 import { scopedThreadKey } from "../../lib/scopedEntities";
23+import { MOBILE_TYPOGRAPHY } from "../../lib/typography";
2324 import { connectionTone } from "../connection/connectionTone";
2425
2526 import {
@@ -390,7 +391,7 @@ export function ThreadRouteScreen() { · 364 unmodified lines
390391 numberOfLines={1}
391392 style={{
392393 fontFamily: "DMSans_700Bold",
393− fontSize: 18,
394+ fontSize: MOBILE_TYPOGRAPHY.headline.fontSize,
394395 fontWeight: "900",
395396 color: foregroundColor,
396397 letterSpacing: -0.4,
@@ -402,7 +403,7 @@ export function ThreadRouteScreen() { · 5 unmodified lines
402403 numberOfLines={1}
403404 style={{
404405 fontFamily: "DMSans_700Bold",
405− fontSize: 12,
406+ fontSize: MOBILE_TYPOGRAPHY.label.fontSize,
406407 fontWeight: "700",
407408 color: secondaryFg,
408409 letterSpacing: 0.3,
apps/mobile/src/features/threads/git/GitBranchesSheet.tsx+10−12
@@ -69,7 +69,7 @@ export function GitBranchesSheet() { · 68 unmodified lines
6969 >
7070 <View className="gap-2 rounded-[18px] border border-border bg-card px-4 py-4">
7171 <Text
72− className="text-foreground-secondary text-[11px] font-t3-bold uppercase"
72+ className="text-foreground-secondary text-2xs font-t3-bold uppercase"
7373 style={{ letterSpacing: 1 }}
7474 >
7575 New branch
@@ -78,7 +78,7 @@ export function GitBranchesSheet() { · 2 unmodified lines
7878 value={newBranchName}
7979 onChangeText={setNewBranchName}
8080 placeholder="feature/mobile-polish"
81− className="rounded-[18px] px-3.5 py-3 font-sans text-[15px]"
81+ className="rounded-[18px] px-3.5 py-3 font-sans text-base"
8282 style={{
8383 borderWidth: 1,
8484 borderColor: inputBorderColor,
@@ -104,7 +104,7 @@ export function GitBranchesSheet() { · 19 unmodified lines
104104
105105 <View className="gap-2 rounded-[18px] border border-border bg-card px-4 py-4">
106106 <Text
107− className="text-foreground-secondary text-[11px] font-t3-bold uppercase"
107+ className="text-foreground-secondary text-2xs font-t3-bold uppercase"
108108 style={{ letterSpacing: 1 }}
109109 >
110110 New worktree
@@ -113,7 +113,7 @@ export function GitBranchesSheet() { · 2 unmodified lines
113113 value={worktreeBaseBranch}
114114 onChangeText={setWorktreeBaseBranch}
115115 placeholder="main"
116− className="rounded-[18px] px-3.5 py-3 font-sans text-[15px]"
116+ className="rounded-[18px] px-3.5 py-3 font-sans text-base"
117117 style={{
118118 borderWidth: 1,
119119 borderColor: inputBorderColor,
@@ -125,7 +125,7 @@ export function GitBranchesSheet() { · 5 unmodified lines
125125 value={worktreeBranchName}
126126 onChangeText={setWorktreeBranchName}
127127 placeholder="feature/mobile-thread"
128− className="rounded-[18px] px-3.5 py-3 font-sans text-[15px]"
128+ className="rounded-[18px] px-3.5 py-3 font-sans text-base"
129129 style={{
130130 borderWidth: 1,
131131 borderColor: inputBorderColor,
@@ -154,18 +154,16 @@ export function GitBranchesSheet() { · 22 unmodified lines
154154
155155 <View className="gap-2">
156156 <Text
157− className="text-foreground-secondary text-[11px] font-t3-bold uppercase"
157+ className="text-foreground-secondary text-2xs font-t3-bold uppercase"
158158 style={{ letterSpacing: 1 }}
159159 >
160160 Existing branches
161161 </Text>
162162 {branchesLoading ? (
163− <Text className="text-foreground-secondary text-[13px] font-medium">
164− Loading branches...
165− </Text>
163+ <Text className="text-foreground-secondary text-sm font-medium">Loading branches...</Text>
166164 ) : null}
167165 {!branchesLoading && availableBranches.length === 0 ? (
168− <Text className="text-foreground-secondary text-[13px] font-medium">
166+ <Text className="text-foreground-secondary text-sm font-medium">
169167 No local branches found.
170168 </Text>
171169 ) : null}
@@ -195,8 +193,8 @@ export function GitBranchesSheet() { · 23 unmodified lines
195193 }}
196194 >
197195 <View className="absolute inset-0 rounded-[18px] bg-card" />
198− <Text className="text-foreground text-[15px] font-t3-bold">{branch.name}</Text>
199− <Text className="text-foreground-secondary text-[12px] font-medium">{subtitle}</Text>
196+ <Text className="text-foreground text-base font-t3-bold">{branch.name}</Text>
197+ <Text className="text-foreground-secondary text-xs font-medium">{subtitle}</Text>
200198 </Pressable>
201199 );
202200 })}
apps/mobile/src/features/threads/git/GitCommitSheet.tsx+18−18
@@ -79,14 +79,14 @@ export function GitCommitSheet() { · 78 unmodified lines
7979 >
8080 <View className="gap-3 rounded-[22px] border border-border bg-card px-4 py-4">
8181 <View className="flex-row items-center justify-between gap-3">
82− <Text className="text-foreground-muted text-[13px] font-medium">Branch</Text>
83− <Text className="text-foreground text-[15px] font-t3-bold">
82+ <Text className="text-foreground-muted text-sm font-medium">Branch</Text>
83+ <Text className="text-foreground text-base font-t3-bold">
8484 {gitStatus.data?.refName ?? "(detached HEAD)"}
8585 </Text>
8686 </View>
8787 {isDefaultRef ? (
8888 <Text
89− className="text-[12px] leading-[18px]"
89+ className="text-xs leading-[18px]"
9090 style={{ color: isDarkMode ? "#fbbf24" : "#b45309" }}
9191 >
9292 Warning: this is the default branch.
@@ -97,8 +97,8 @@ export function GitCommitSheet() { · 4 unmodified lines
9797 <View className="gap-3 rounded-[22px] border border-border bg-card px-4 py-4">
9898 <View className="flex-row items-center justify-between gap-3">
9999 <View className="gap-1">
100− <Text className="text-foreground text-[16px] font-t3-bold">Files</Text>
101− <Text className="text-foreground-muted text-[12px] leading-[18px]">
100+ <Text className="text-foreground text-base font-t3-bold">Files</Text>
101+ <Text className="text-foreground-muted text-xs leading-[18px]">
102102 {selectedFiles.length} selected · +{selectedInsertions} / -{selectedDeletions}
103103 </Text>
104104 </View>
@@ -108,14 +108,14 @@ export function GitCommitSheet() { · 3 unmodified lines
108108 className="bg-subtle rounded-full px-3 py-2"
109109 onPress={() => setExcludedFiles(new Set())}
110110 >
111− <Text className="text-foreground text-[11px] font-t3-bold uppercase">Reset</Text>
111+ <Text className="text-foreground text-2xs font-t3-bold uppercase">Reset</Text>
112112 </Pressable>
113113 ) : null}
114114 <Pressable
115115 className="bg-subtle rounded-full px-3 py-2"
116116 onPress={() => setIsEditingFiles((current) => !current)}
117117 >
118− <Text className="text-foreground text-[11px] font-t3-bold uppercase">
118+ <Text className="text-foreground text-2xs font-t3-bold uppercase">
119119 {isEditingFiles ? "Done" : "Edit"}
120120 </Text>
121121 </Pressable>
@@ -123,26 +123,26 @@ export function GitCommitSheet() { · 1 unmodified line
123123 </View>
124124
125125 {allFiles.length === 0 ? (
126− <Text className="text-foreground-secondary text-[13px] leading-[19px]">
126+ <Text className="text-foreground-secondary text-sm leading-[19px]">
127127 No changed files are available to commit.
128128 </Text>
129129 ) : !isEditingFiles ? (
130130 <View className="gap-2">
131131 {selectedFilePreview.map((file) => (
132132 <View key={file.path} className="flex-row items-center justify-between gap-3">
133− <Text className="text-foreground flex-1 text-[13px] font-medium" numberOfLines={1}>
133+ <Text className="text-foreground flex-1 text-sm font-medium" numberOfLines={1}>
134134 {file.path}
135135 </Text>
136− <Text className="text-[12px] font-t3-bold" style={{ color: "#10b981" }}>
136+ <Text className="text-xs font-t3-bold" style={{ color: "#10b981" }}>
137137 +{file.insertions}
138138 </Text>
139− <Text className="text-[12px] font-t3-bold" style={{ color: "#f43f5e" }}>
139+ <Text className="text-xs font-t3-bold" style={{ color: "#f43f5e" }}>
140140 -{file.deletions}
141141 </Text>
142142 </View>
143143 ))}
144144 {selectedFiles.length > selectedFilePreview.length ? (
145− <Text className="text-foreground-muted text-[12px] leading-[17px]">
145+ <Text className="text-foreground-muted text-xs leading-[17px]">
146146 +{selectedFiles.length - selectedFilePreview.length} more files
147147 </Text>
148148 ) : null}
@@ -177,21 +177,21 @@ export function GitCommitSheet() { · 28 unmodified lines
177177 <View className="flex-1 gap-1">
178178 <Text
179179 selectable
180− className={`text-[13px] font-t3-bold ${included ? "text-foreground" : "text-foreground-muted"}`}
180+ className={`text-sm font-t3-bold ${included ? "text-foreground" : "text-foreground-muted"}`}
181181 >
182182 {file.path}
183183 </Text>
184184 {!included ? (
185− <Text className="text-foreground-muted text-[11px] leading-[16px]">
185+ <Text className="text-foreground-muted text-2xs leading-[16px]">
186186 Excluded from this commit
187187 </Text>
188188 ) : null}
189189 </View>
190190 <View className="items-end gap-1">
191− <Text className="text-[12px] font-t3-bold" style={{ color: "#10b981" }}>
191+ <Text className="text-xs font-t3-bold" style={{ color: "#10b981" }}>
192192 +{file.insertions}
193193 </Text>
194− <Text className="text-[12px] font-t3-bold" style={{ color: "#f43f5e" }}>
194+ <Text className="text-xs font-t3-bold" style={{ color: "#f43f5e" }}>
195195 -{file.deletions}
196196 </Text>
197197 </View>
@@ -204,14 +204,14 @@ export function GitCommitSheet() { · 6 unmodified lines
204204 </View>
205205
206206 <View className="gap-2">
207− <Text className="text-foreground text-[13px] font-t3-bold">Commit message</Text>
207+ <Text className="text-foreground text-sm font-t3-bold">Commit message</Text>
208208 <TextInput
209209 multiline
210210 value={dialogCommitMessage}
211211 onChangeText={setDialogCommitMessage}
212212 placeholder="Leave empty to auto-generate"
213213 textAlignVertical="top"
214− className="min-h-[128px] rounded-[20px] px-4 py-3.5 font-sans text-[15px]"
214+ className="min-h-[128px] rounded-[20px] px-4 py-3.5 font-sans text-base"
215215 style={{
216216 minHeight: 128,
217217 borderWidth: 1,
apps/mobile/src/features/threads/git/GitConfirmSheet.tsx+3−3
@@ -90,15 +90,15 @@ export function GitConfirmSheet() { · 89 unmodified lines
9090
9191 <View className="items-center gap-1 px-5 pb-3 pt-4">
9292 <Text
93− className="text-[12px] font-t3-bold uppercase text-foreground-muted"
93+ className="text-xs font-t3-bold uppercase text-foreground-muted"
9494 style={{ letterSpacing: 1 }}
9595 >
9696 Confirm
9797 </Text>
98− <Text className="text-center text-[28px] font-t3-bold">
98+ <Text className="text-center text-3xl font-t3-bold">
9999 {copy?.title ?? "Run action on default branch?"}
100100 </Text>
101− <Text className="text-center text-foreground-secondary text-[13px] font-medium leading-[19px]">
101+ <Text className="text-center text-foreground-secondary text-sm font-medium leading-[19px]">
102102 {copy?.description ?? "Choose how to continue."}
103103 </Text>
104104 </View>
apps/mobile/src/features/threads/git/GitOverviewSheet.tsx+3−3
@@ -175,13 +175,13 @@ export function GitOverviewSheet() { · 174 unmodified lines
175175 />
176176 </Pressable>
177177 <Text
178− className="text-[12px] font-t3-bold uppercase text-foreground-muted"
178+ className="text-xs font-t3-bold uppercase text-foreground-muted"
179179 style={{ letterSpacing: 1 }}
180180 >
181181 Branch
182182 </Text>
183− <Text className="text-[28px] font-t3-bold">{currentBranchLabel}</Text>
184− <Text className="text-foreground-secondary text-[13px] font-medium leading-[19px]">
183+ <Text className="text-3xl font-t3-bold">{currentBranchLabel}</Text>
184+ <Text className="text-foreground-secondary text-sm font-medium leading-[19px]">
185185 {statusSummary(gitStatus.data)}
186186 </Text>
187187 </View>
apps/mobile/src/features/threads/git/gitSheetComponents.tsx+5−5
@@ -56,7 +56,7 @@ export function SheetActionButton(props: { · 55 unmodified lines
5656 >
5757 <SymbolView name={props.icon} size={16} tintColor={colors.textColor} type="monochrome" />
5858 <Text
59− className="text-[12px] font-t3-bold uppercase"
59+ className="text-xs font-t3-bold uppercase"
6060 style={{ color: colors.textColor, letterSpacing: 0.9 }}
6161 >
6262 {props.label}
@@ -69,12 +69,12 @@ export function MetaCard(props: { readonly label: string; readonly value: string · 6 unmodified lines
6969 return (
7070 <View className="rounded-[18px] border border-border bg-card px-4 py-3">
7171 <Text
72− className="text-foreground-muted text-[11px] font-t3-bold uppercase"
72+ className="text-foreground-muted text-2xs font-t3-bold uppercase"
7373 style={{ letterSpacing: 0.9 }}
7474 >
7575 {props.label}
7676 </Text>
77− <Text selectable className="text-foreground text-[13px] font-medium" numberOfLines={1}>
77+ <Text selectable className="text-foreground text-sm font-medium" numberOfLines={1}>
7878 {props.value}
7979 </Text>
8080 </View>
@@ -102,9 +102,9 @@ export function SheetListRow(props: { · 21 unmodified lines
102102 <SymbolView name={props.icon} size={16} tintColor={iconColor} type="monochrome" />
103103 </View>
104104 <View className="flex-1 gap-0.5">
105− <Text className="text-foreground text-[16px] font-t3-bold">{props.title}</Text>
105+ <Text className="text-foreground text-base font-t3-bold">{props.title}</Text>
106106 {props.subtitle ? (
107− <Text className="text-foreground-muted text-[12px] leading-[17px]">{props.subtitle}</Text>
107+ <Text className="text-foreground-muted text-xs leading-[17px]">{props.subtitle}</Text>
108108 ) : null}
109109 </View>
110110 <SymbolView name="chevron.right" size={13} tintColor={iconSubtleColor} type="monochrome" />
apps/mobile/src/features/threads/thread-work-log.tsx+3−3
@@ -98,7 +98,7 @@ export function ThreadWorkLog(props: { · 97 unmodified lines
9898 return (
9999 <View className="-mx-1 mb-3 px-1 py-0.5">
100100 {!onlyToolRows ? (
101− <Text className="px-0.5 pb-0.5 font-t3-medium text-[11px] text-foreground-muted opacity-60">
101+ <Text className="px-0.5 pb-0.5 font-t3-medium text-2xs text-foreground-muted opacity-60">
102102 work log
103103 </Text>
104104 ) : null}
@@ -164,7 +164,7 @@ export function ThreadWorkLog(props: { · 59 unmodified lines
164164
165165 <View className="shrink-0 flex-row items-center gap-px">
166166 {props.copiedRowId === row.id ? (
167− <Text className="pr-1 font-t3-medium text-[10px] text-emerald-600 dark:text-emerald-400">
167+ <Text className="pr-1 font-t3-medium text-3xs text-emerald-600 dark:text-emerald-400">
168168 Copied
169169 </Text>
170170 ) : null}
@@ -209,7 +209,7 @@ export function ThreadWorkLog(props: { · 38 unmodified lines
209209 >
210210 <Text
211211 selectable
212− className="text-[11px] leading-[17px] text-foreground-muted"
212+ className="text-2xs leading-[17px] text-foreground-muted"
213213 style={{ fontFamily: "ui-monospace" }}
214214 >
215215 {row.fullDetail}
apps/mobile/src/lib/typography.test.ts+23−0
@@ -0,0 +1,23 @@
1+import { describe, expect, it } from "vite-plus/test";
2+
3+import { MOBILE_CODE_SURFACE, MOBILE_TYPOGRAPHY } from "./typography";
4+
5+describe("mobile typography", () => {
6+ it("uses the intentional compact mobile font scale", () => {
7+ expect(Object.values(MOBILE_TYPOGRAPHY).map(({ fontSize }) => fontSize)).toEqual([
8+ 10, 11, 12, 13, 14, 15, 17, 20, 24, 28,
9+ ]);
10+ });
11+
12+ it("uses a compact shared style for editable composer text", () => {
13+ expect(MOBILE_TYPOGRAPHY.composer).toEqual({ fontSize: 14, lineHeight: 20 });
14+ });
15+
16+ it("uses caption-sized code with a compact readable row height", () => {
17+ expect(MOBILE_CODE_SURFACE).toMatchObject({
18+ fontSize: MOBILE_TYPOGRAPHY.caption.fontSize,
19+ lineNumberFontSize: MOBILE_TYPOGRAPHY.micro.fontSize,
20+ rowHeight: 20,
21+ });
22+ });
23+});
apps/mobile/src/lib/typography.ts+22−0
@@ -0,0 +1,22 @@
1+export const MOBILE_TYPOGRAPHY = {
2+ micro: { fontSize: 10, lineHeight: 13 },
3+ caption: { fontSize: 11, lineHeight: 15 },
4+ label: { fontSize: 12, lineHeight: 16 },
5+ footnote: { fontSize: 13, lineHeight: 18 },
6+ composer: { fontSize: 14, lineHeight: 20 },
7+ body: { fontSize: 15, lineHeight: 22 },
8+ headline: { fontSize: 17, lineHeight: 22 },
9+ title: { fontSize: 20, lineHeight: 26 },
10+ largeTitle: { fontSize: 24, lineHeight: 30 },
11+ display: { fontSize: 28, lineHeight: 34 },
12+} as const;
13+
14+/** Shared geometry for dense, horizontally scrolling code surfaces. */
15+export const MOBILE_CODE_SURFACE = {
16+ rowHeight: 20,
17+ gutterWidth: 46,
18+ codePadding: 7,
19+ textVerticalInset: 2,
20+ fontSize: MOBILE_TYPOGRAPHY.caption.fontSize,
21+ lineNumberFontSize: MOBILE_TYPOGRAPHY.micro.fontSize,
22+} as const;
apps/mobile/src/native/T3ComposerEditor.ios.tsx+9−2
@@ -6,6 +6,7 @@ import { Image, StyleSheet } from "react-native"; · 5 unmodified lines
66
77 import { markdownFileIconSource } from "@t3tools/mobile-markdown-text/file-icons";
88 import { resolveMarkdownFileIcon } from "@t3tools/mobile-markdown-text/links";
9+import { MOBILE_TYPOGRAPHY } from "../lib/typography";
910 import { useThemeColor } from "../lib/useThemeColor";
1011 import type { ComposerEditorProps, ComposerEditorSelection } from "./T3ComposerEditor.types";
1112
@@ -150,9 +151,15 @@ export function ComposerEditor({ · 138 unmodified lines
150151 ? resolvedTextStyle.fontFamily
151152 : "DMSans_400Regular"
152153 }
153− fontSize={typeof resolvedTextStyle.fontSize === "number" ? resolvedTextStyle.fontSize : 15}
154+ fontSize={
155+ typeof resolvedTextStyle.fontSize === "number"
156+ ? resolvedTextStyle.fontSize
157+ : MOBILE_TYPOGRAPHY.composer.fontSize
158+ }
154159 lineHeight={
155− typeof resolvedTextStyle.lineHeight === "number" ? resolvedTextStyle.lineHeight : 22
160+ typeof resolvedTextStyle.lineHeight === "number"
161+ ? resolvedTextStyle.lineHeight
162+ : MOBILE_TYPOGRAPHY.composer.lineHeight
156163 }
157164 contentInsetVertical={contentInsetVertical}
158165 editable={props.editable ?? true}
apps/mobile/src/native/T3ComposerEditor.tsx+2−2
@@ -2,6 +2,7 @@ import { TextInputWrapper } from "expo-paste-input"; · 1 unmodified line
22 import { useImperativeHandle, useRef } from "react";
33 import { TextInput, type TextInput as RNTextInput } from "react-native";
44
5+import { MOBILE_TYPOGRAPHY } from "../lib/typography";
56 import { useThemeColor } from "../lib/useThemeColor";
67 import { useNativePaste } from "../lib/useNativePaste";
78 import type { ComposerEditorProps } from "./T3ComposerEditor.types";
@@ -47,8 +48,7 @@ export function ComposerEditor({ · 39 unmodified lines
4748 minHeight: 0,
4849 color: foregroundColor,
4950 fontFamily: "DMSans_400Regular",
50− fontSize: 15,
51− lineHeight: 22,
51+ ...MOBILE_TYPOGRAPHY.composer,
5252 paddingVertical: contentInsetVertical,
5353 },
5454 textStyle,
apps/server/src/auth/EnvironmentAuth.test.ts+10−9
@@ -4,27 +4,26 @@ import { expect, it } from "@effect/vitest"; · 3 unmodified lines
44 import * as Effect from "effect/Effect";
55 import * as Layer from "effect/Layer";
66
7−import type { ServerConfigShape } from "../config.ts";
8−import { ServerConfig } from "../config.ts";
7+import * as ServerConfig from "../config.ts";
98 import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
109 import * as PairingGrantStore from "./PairingGrantStore.ts";
1110 import * as EnvironmentAuth from "./EnvironmentAuth.ts";
1211
1312 import * as ServerSecretStore from "./ServerSecretStore.ts";
1413
15−const makeServerConfigLayer = (overrides?: Partial<ServerConfigShape>) =>
14+const makeServerConfigLayer = (overrides?: Partial<ServerConfig.ServerConfig["Service"]>) =>
1615 Layer.effect(
17− ServerConfig,
16+ ServerConfig.ServerConfig,
1817 Effect.gen(function* () {
19− const config = yield* ServerConfig;
18+ const config = yield* ServerConfig.ServerConfig;
2019 return {
2120 ...config,
2221 ...overrides,
23− } satisfies ServerConfigShape;
22+ } satisfies ServerConfig.ServerConfig["Service"];
2423 }),
2524 ).pipe(Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-auth-server-test-" })));
2625
27−const makeEnvironmentAuthLayer = (overrides?: Partial<ServerConfigShape>) =>
26+const makeEnvironmentAuthLayer = (overrides?: Partial<ServerConfig.ServerConfig["Service"]>) =>
2827 EnvironmentAuth.layer.pipe(
2928 Layer.provide(SqlitePersistenceMemory),
3029 Layer.provide(ServerSecretStore.layer),
@@ -33,13 +32,15 @@ const makeEnvironmentAuthLayer = (overrides?: Partial<ServerConfigShape>) => · 2 unmodified lines
3332
3433 const makeCookieRequest = (
3534 sessionToken: string,
36−): Parameters<EnvironmentAuth.EnvironmentAuthShape["authenticateHttpRequest"]>[0] =>
35+): Parameters<EnvironmentAuth.EnvironmentAuth["Service"]["authenticateHttpRequest"]>[0] =>
3736 ({
3837 cookies: {
3938 t3_session: sessionToken,
4039 },
4140 headers: {},
42− }) as unknown as Parameters<EnvironmentAuth.EnvironmentAuthShape["authenticateHttpRequest"]>[0];
41+ }) as unknown as Parameters<
42+ EnvironmentAuth.EnvironmentAuth["Service"]["authenticateHttpRequest"]
43+ >[0];
4344
4445 const requestMetadata = {
4546 deviceType: "desktop" as const,
apps/server/src/auth/EnvironmentAuthAdmin.test.ts+6−7
@@ -3,31 +3,30 @@ import { expect, it } from "@effect/vitest"; · 2 unmodified lines
33 import * as Effect from "effect/Effect";
44 import * as Layer from "effect/Layer";
55
6−import type { ServerConfigShape } from "../config.ts";
7−import { ServerConfig } from "../config.ts";
6+import * as ServerConfig from "../config.ts";
87 import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
98 import * as EnvironmentAuth from "./EnvironmentAuth.ts";
109 import * as ServerSecretStore from "./ServerSecretStore.ts";
1110 import * as SessionStore from "./SessionStore.ts";
1211
1312 const makeServerConfigLayer = (
14− overrides?: Partial<Pick<ServerConfigShape, "desktopBootstrapToken">>,
13+ overrides?: Partial<Pick<ServerConfig.ServerConfig["Service"], "desktopBootstrapToken">>,
1514 ) =>
1615 Layer.effect(
17− ServerConfig,
16+ ServerConfig.ServerConfig,
1817 Effect.gen(function* () {
19− const config = yield* ServerConfig;
18+ const config = yield* ServerConfig.ServerConfig;
2019 return {
2120 ...config,
2221 ...overrides,
23− } satisfies ServerConfigShape;
22+ } satisfies ServerConfig.ServerConfig["Service"];
2423 }),
2524 ).pipe(
2625 Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-auth-control-plane-test-" })),
2726 );
2827
2928 const makeEnvironmentAuthLayer = (
30− overrides?: Partial<Pick<ServerConfigShape, "desktopBootstrapToken">>,
29+ overrides?: Partial<Pick<ServerConfig.ServerConfig["Service"], "desktopBootstrapToken">>,
3130 ) =>
3231 EnvironmentAuth.layer.pipe(
3332 Layer.provideMerge(ServerSecretStore.layer),
apps/server/src/auth/EnvironmentAuthPolicy.test.ts+7−6
@@ -3,21 +3,22 @@ import { expect, it } from "@effect/vitest"; · 2 unmodified lines
33 import * as Effect from "effect/Effect";
44 import * as Layer from "effect/Layer";
55
6−import type { ServerConfigShape } from "../config.ts";
7−import { ServerConfig } from "../config.ts";
6+import * as ServerConfig from "../config.ts";
87 import * as EnvironmentAuthPolicy from "./EnvironmentAuthPolicy.ts";
98
10−const makeEnvironmentAuthPolicyLayer = (overrides?: Partial<ServerConfigShape>) =>
9+const makeEnvironmentAuthPolicyLayer = (
10+ overrides?: Partial<ServerConfig.ServerConfig["Service"]>,
11+) =>
1112 EnvironmentAuthPolicy.layer.pipe(
1213 Layer.provide(
1314 Layer.effect(
14− ServerConfig,
15+ ServerConfig.ServerConfig,
1516 Effect.gen(function* () {
16− const config = yield* ServerConfig;
17+ const config = yield* ServerConfig.ServerConfig;
1718 return {
1819 ...config,
1920 ...overrides,
20− } satisfies ServerConfigShape;
21+ } satisfies ServerConfig.ServerConfig["Service"];
2122 }),
2223 ).pipe(
2324 Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-auth-policy-test-" })),
apps/server/src/auth/PairingGrantStore.test.ts+6−7
@@ -5,29 +5,28 @@ import * as Effect from "effect/Effect"; · 4 unmodified lines
55 import * as Layer from "effect/Layer";
66 import * as TestClock from "effect/testing/TestClock";
77
8−import type { ServerConfigShape } from "../config.ts";
9−import { ServerConfig } from "../config.ts";
8+import * as ServerConfig from "../config.ts";
109 import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
1110 import * as PairingGrantStore from "./PairingGrantStore.ts";
1211
1312 const makeServerConfigLayer = (
14− overrides?: Partial<Pick<ServerConfigShape, "desktopBootstrapToken">>,
13+ overrides?: Partial<Pick<ServerConfig.ServerConfig["Service"], "desktopBootstrapToken">>,
1514 ) =>
1615 Layer.effect(
17− ServerConfig,
16+ ServerConfig.ServerConfig,
1817 Effect.gen(function* () {
19− const config = yield* ServerConfig;
18+ const config = yield* ServerConfig.ServerConfig;
2019 return {
2120 ...config,
2221 ...overrides,
23− } satisfies ServerConfigShape;
22+ } satisfies ServerConfig.ServerConfig["Service"];
2423 }),
2524 ).pipe(
2625 Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-auth-bootstrap-test-" })),
2726 );
2827
2928 const makePairingGrantStoreLayer = (
30− overrides?: Partial<Pick<ServerConfigShape, "desktopBootstrapToken">>,
29+ overrides?: Partial<Pick<ServerConfig.ServerConfig["Service"], "desktopBootstrapToken">>,
3130 ) =>
3231 PairingGrantStore.layer.pipe(
3332 Layer.provide(SqlitePersistenceMemory),
apps/server/src/auth/SessionStore.test.ts+8−9
@@ -5,30 +5,29 @@ import * as Effect from "effect/Effect"; · 4 unmodified lines
55 import * as Layer from "effect/Layer";
66 import * as TestClock from "effect/testing/TestClock";
77
8−import type { ServerConfigShape } from "../config.ts";
9−import { ServerConfig } from "../config.ts";
8+import * as ServerConfig from "../config.ts";
109 import { PersistenceSqlError } from "../persistence/Errors.ts";
1110 import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts";
12−import { AuthSessionRepository } from "../persistence/Services/AuthSessions.ts";
11+import * as AuthSessions from "../persistence/Services/AuthSessions.ts";
1312 import * as SessionStore from "./SessionStore.ts";
1413 import * as ServerSecretStore from "./ServerSecretStore.ts";
1514
1615 const makeServerConfigLayer = (
17− overrides?: Partial<Pick<ServerConfigShape, "desktopBootstrapToken">>,
16+ overrides?: Partial<Pick<ServerConfig.ServerConfig["Service"], "desktopBootstrapToken">>,
1817 ) =>
1918 Layer.effect(
20− ServerConfig,
19+ ServerConfig.ServerConfig,
2120 Effect.gen(function* () {
22− const config = yield* ServerConfig;
21+ const config = yield* ServerConfig.ServerConfig;
2322 return {
2423 ...config,
2524 ...overrides,
26− } satisfies ServerConfigShape;
25+ } satisfies ServerConfig.ServerConfig["Service"];
2726 }),
2827 ).pipe(Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-auth-session-test-" })));
2928
3029 const makeSessionStoreLayer = (
31− overrides?: Partial<Pick<ServerConfigShape, "desktopBootstrapToken">>,
30+ overrides?: Partial<Pick<ServerConfig.ServerConfig["Service"], "desktopBootstrapToken">>,
3231 ) =>
3332 SessionStore.layer.pipe(
3433 Layer.provide(SqlitePersistenceMemory),
@@ -41,7 +40,7 @@ const repositoryFailure = new PersistenceSqlError({ · 6 unmodified lines
4140 detail: "sqlite is unavailable",
4241 });
4342
44−const failingSessionLookupRepositoryLayer = Layer.succeed(AuthSessionRepository, {
43+const failingSessionLookupRepositoryLayer = Layer.succeed(AuthSessions.AuthSessionRepository, {
4544 create: () => Effect.void,
4645 getById: () => Effect.fail(repositoryFailure),
4746 listActive: () => Effect.succeed([]),
apps/server/src/bin.test.ts+11−14
@@ -20,8 +20,8 @@ import * as TestConsole from "effect/testing/TestConsole"; · 19 unmodified lines
2020 import { Command } from "effect/unstable/cli";
2121
2222 import { cli, makeCli } from "./bin.ts";
23−import { deriveServerPaths, ServerConfig, type ServerConfigShape } from "./config.ts";
24−import { ProjectionSnapshotQuery } from "./orchestration/Services/ProjectionSnapshotQuery.ts";
23+import * as ServerConfig from "./config.ts";
24+import * as ProjectionSnapshotQuery from "./orchestration/Services/ProjectionSnapshotQuery.ts";
2525 import { OrchestrationLayerLive } from "./orchestration/runtimeLayer.ts";
2626 import { orchestrationHttpApiLayer } from "./orchestration/http.ts";
2727 import { layerConfig as SqlitePersistenceLayerLive } from "./persistence/Layers/Sqlite.ts";
@@ -57,7 +57,7 @@ const captureStdout = <A, E, R>(effect: Effect.Effect<A, E, R>) => · 29 unmodified lines
5757
5858 const makeCliTestServerConfig = (baseDir: string) =>
5959 Effect.gen(function* () {
60− const derivedPaths = yield* deriveServerPaths(baseDir, undefined);
60+ const derivedPaths = yield* ServerConfig.deriveServerPaths(baseDir, undefined);
6161 return {
6262 logLevel: "Info",
6363 traceMinLevel: "Info",
@@ -84,26 +84,23 @@ const makeCliTestServerConfig = (baseDir: string) => · 20 unmodified lines
8484 logWebSocketEvents: false,
8585 tailscaleServeEnabled: false,
8686 tailscaleServePort: 443,
87− } satisfies ServerConfigShape;
87+ } satisfies ServerConfig.ServerConfig["Service"];
8888 });
8989
90−const makeProjectPersistenceLayer = (config: ServerConfigShape) =>
90+const makeProjectPersistenceLayer = (config: ServerConfig.ServerConfig["Service"]) =>
9191 Layer.mergeAll(
9292 OrchestrationLayerLive.pipe(
9393 Layer.provideMerge(RepositoryIdentityResolverLive),
9494 Layer.provideMerge(SqlitePersistenceLayerLive),
9595 ),
9696 WorkspacePathsLive,
97− ).pipe(
98− Layer.provideMerge(NodeServices.layer),
99− Layer.provide(Layer.succeed(ServerConfig, config)),
100− );
97+ ).pipe(Layer.provideMerge(NodeServices.layer), Layer.provide(ServerConfig.layer(config)));
10198
10299 const readPersistedSnapshot = (baseDir: string) =>
103100 Effect.gen(function* () {
104101 const config = yield* makeCliTestServerConfig(baseDir);
105102 return yield* Effect.gen(function* () {
106− const projectionSnapshotQuery = yield* ProjectionSnapshotQuery;
103+ const projectionSnapshotQuery = yield* ProjectionSnapshotQuery.ProjectionSnapshotQuery;
107104 return yield* projectionSnapshotQuery.getSnapshot();
108105 }).pipe(Effect.provide(makeProjectPersistenceLayer(config)));
109106 });
@@ -133,7 +130,7 @@ const withLiveProjectCliServer = <A, E, R>(baseDir: string, run: () => Effect.Ef · 23 unmodified lines
133130 }),
134131 ),
135132 Layer.provideMerge(NodeServices.layer),
136− Layer.provide(Layer.succeed(ServerConfig, config)),
133+ Layer.provide(ServerConfig.layer(config)),
137134 );
138135
139136 return yield* Effect.scoped(
@@ -238,7 +235,7 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => { · 98 unmodified lines
238235 it.effect("logs in to headless connect without enabling access", () =>
239236 Effect.gen(function* () {
240237 const baseDir = mkdtempSync(join(tmpdir(), "t3-cli-cloud-login-test-"));
241− const { secretsDir } = yield* deriveServerPaths(baseDir, undefined);
238+ const { secretsDir } = yield* ServerConfig.deriveServerPaths(baseDir, undefined);
242239 mkdirSync(secretsDir, { recursive: true });
243240 writeFileSync(
244241 join(secretsDir, "cloud-cli-oauth-token.bin"),
@@ -282,7 +279,7 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => { · 37 unmodified lines
282279 it.effect("logs out of headless connect and removes the stored CLI authorization", () =>
283280 Effect.gen(function* () {
284281 const baseDir = mkdtempSync(join(tmpdir(), "t3-cli-cloud-logout-test-"));
285− const { secretsDir } = yield* deriveServerPaths(baseDir, undefined);
282+ const { secretsDir } = yield* ServerConfig.deriveServerPaths(baseDir, undefined);
286283 const tokenPath = join(secretsDir, "cloud-cli-oauth-token.bin");
287284 mkdirSync(secretsDir, { recursive: true });
288285 writeFileSync(tokenPath, "invalid persisted token");
@@ -461,7 +458,7 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => { · 172 unmodified lines
461458 "--base-dir",
462459 baseDir,
463460 ]);
464− const projectionSnapshotQuery = yield* ProjectionSnapshotQuery;
461+ const projectionSnapshotQuery = yield* ProjectionSnapshotQuery.ProjectionSnapshotQuery;
465462 const readModel = yield* projectionSnapshotQuery.getSnapshot();
466463 const addedProject = readModel.projects.find(
467464 (project) => project.workspaceRoot === workspaceRoot && project.deletedAt === null,
apps/server/src/cli/auth.ts+3−3
@@ -18,7 +18,7 @@ import { · 17 unmodified lines
1818 formatPairingCredentialList,
1919 formatSessionList,
2020 } from "../cliAuthFormat.ts";
21−import { ServerConfig } from "../config.ts";
21+import * as ServerConfig from "../config.ts";
2222 import {
2323 authLocationFlags,
2424 type CliAuthLocationFlags,
@@ -28,7 +28,7 @@ import { · 3 unmodified lines
2828
2929 const runWithEnvironmentAuth = <A, E>(
3030 flags: CliAuthLocationFlags,
31− run: (environmentAuth: EnvironmentAuth.EnvironmentAuthShape) => Effect.Effect<A, E>,
31+ run: (environmentAuth: EnvironmentAuth.EnvironmentAuth["Service"]) => Effect.Effect<A, E>,
3232 options?: {
3333 readonly quietLogs?: boolean;
3434 },
@@ -43,7 +43,7 @@ const runWithEnvironmentAuth = <A, E>( · 8 unmodified lines
4343 }).pipe(
4444 Effect.provide(
4545 Layer.mergeAll(EnvironmentAuth.runtimeLayer).pipe(
46− Layer.provide(Layer.succeed(ServerConfig, config)),
46+ Layer.provide(ServerConfig.layer(config)),
4747 Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)),
4848 ),
4949 ),
apps/server/src/cli/config.ts+12−20
@@ -14,18 +14,10 @@ import * as SchemaTransformation from "effect/SchemaTransformation"; · 13 unmodified lines
1414 import { Argument, Flag } from "effect/unstable/cli";
1515
1616 import { readBootstrapEnvelope } from "../bootstrap.ts";
17−import {
18− DEFAULT_PORT,
19− deriveServerPaths,
20− ensureServerDirectories,
21− resolveStaticDir,
22− RuntimeMode,
23− type ServerConfigShape,
24− type StartupPresentation,
25−} from "../config.ts";
17+import * as ServerConfig from "../config.ts";
2618 import { expandHomePath, resolveBaseDir } from "../os-jank.ts";
2719
28−export const modeFlag = Flag.choice("mode", RuntimeMode.literals).pipe(
20+export const modeFlag = Flag.choice("mode", ServerConfig.RuntimeMode.literals).pipe(
2921 Flag.withDescription("Runtime mode. `desktop` keeps loopback defaults unless overridden."),
3022 Flag.optional,
3123 );
@@ -104,7 +96,7 @@ const EnvServerConfig = Config.all({ · 72 unmodified lines
10496 Config.withDefault(10_000),
10597 ),
10698 otlpServiceName: Config.string("T3CODE_OTLP_SERVICE_NAME").pipe(Config.withDefault("t3-server")),
107− mode: Config.schema(RuntimeMode, "T3CODE_MODE").pipe(
99+ mode: Config.schema(ServerConfig.RuntimeMode, "T3CODE_MODE").pipe(
108100 Config.option,
109101 Config.map(Option.getOrUndefined),
110102 ),
@@ -139,7 +131,7 @@ const EnvServerConfig = Config.all({ · 28 unmodified lines
139131 });
140132
141133 export interface CliServerFlags {
142− readonly mode: Option.Option<RuntimeMode>;
134+ readonly mode: Option.Option<ServerConfig.RuntimeMode>;
143135 readonly port: Option.Option<number>;
144136 readonly host: Option.Option<string>;
145137 readonly baseDir: Option.Option<string>;
@@ -208,7 +200,7 @@ export const resolveServerConfig = ( · 62 unmodified lines
208200 flags: CliServerFlags,
209201 cliLogLevel: Option.Option<LogLevel.LogLevel>,
210202 options?: {
211− readonly startupPresentation?: StartupPresentation;
203+ readonly startupPresentation?: ServerConfig.StartupPresentation;
212204 readonly forceAutoBootstrapProjectFromCwd?: boolean;
213205 },
214206 ) =>
@@ -238,7 +230,7 @@ export const resolveServerConfig = ( · 23 unmodified lines
238230 : Option.none();
239231 const bootstrap = Option.getOrUndefined(bootstrapEnvelope);
240232
241− const mode: RuntimeMode = Option.getOrElse(
233+ const mode: ServerConfig.RuntimeMode = Option.getOrElse(
242234 resolveOptionPrecedence(
243235 normalizedFlags.mode,
244236 Option.fromUndefinedOr(env.mode),
@@ -257,9 +249,9 @@ export const resolveServerConfig = ( · 12 unmodified lines
257249 onSome: (value) => Effect.succeed(value),
258250 onNone: () => {
259251 if (mode === "desktop") {
260− return Effect.succeed(DEFAULT_PORT);
252+ return Effect.succeed(ServerConfig.DEFAULT_PORT);
261253 }
262− return findAvailablePort(DEFAULT_PORT);
254+ return findAvailablePort(ServerConfig.DEFAULT_PORT);
263255 },
264256 },
265257 );
@@ -279,8 +271,8 @@ export const resolveServerConfig = ( · 13 unmodified lines
279271 const rawCwd = Option.getOrElse(normalizedFlags.cwd, () => process.cwd());
280272 const cwd = path.resolve(yield* expandHomePath(rawCwd.trim()));
281273 yield* fs.makeDirectory(cwd, { recursive: true });
282− const derivedPaths = yield* deriveServerPaths(baseDir, devUrl);
283− yield* ensureServerDirectories(derivedPaths);
274+ const derivedPaths = yield* ServerConfig.deriveServerPaths(baseDir, devUrl);
275+ yield* ServerConfig.ensureServerDirectories(derivedPaths);
284276 const persistedObservabilitySettings = yield* loadPersistedObservabilitySettings(
285277 derivedPaths.settingsPath,
286278 );
@@ -330,7 +322,7 @@ export const resolveServerConfig = ( · 43 unmodified lines
330322 ),
331323 () => 443,
332324 );
333− const staticDir = devUrl ? undefined : yield* resolveStaticDir();
325+ const staticDir = devUrl ? undefined : yield* ServerConfig.resolveStaticDir();
334326 const host = Option.getOrElse(
335327 resolveOptionPrecedence(
336328 normalizedFlags.host,
@@ -341,7 +333,7 @@ export const resolveServerConfig = ( · 4 unmodified lines
341333 );
342334 const logLevel = Option.getOrElse(cliLogLevel, () => env.logLevel);
343335
344− const config: ServerConfigShape = {
336+ const config: ServerConfig.ServerConfig["Service"] = {
345337 logLevel,
346338 traceMinLevel: env.traceMinLevel,
347339 traceTimingEnabled: env.traceTimingEnabled,
apps/server/src/cli/connect.ts+9−9
@@ -31,9 +31,9 @@ import * as CliTokenManager from "../cloud/CliTokenManager.ts"; · 30 unmodified lines
3131 import { CLOUD_LINKED_USER_ID, RELAY_URL_SECRET } from "../cloud/config.ts";
3232 import { relayUrlConfig } from "../cloud/publicConfig.ts";
3333 import { headlessRelayClientTracingLayer } from "../cloud/relayTracing.ts";
34−import { ServerConfig } from "../config.ts";
34+import * as ServerConfig from "../config.ts";
3535 import { ServerEnvironmentLive } from "../environment/Layers/ServerEnvironment.ts";
36−import { ServerEnvironment } from "../environment/Services/ServerEnvironment.ts";
36+import * as ServerEnvironment from "../environment/Services/ServerEnvironment.ts";
3737 import { readPersistedServerRuntimeState } from "../serverRuntimeState.ts";
3838 import { projectLocationFlags, resolveCliAuthConfig } from "./config.ts";
3939
@@ -145,7 +145,7 @@ const reportRelayClientInstallProgress = (event: RelayClientInstallProgressEvent · 105 unmodified lines
145145
146146 export const acquireRelayClientForLink = Effect.fn("cloud.cli.acquire_relay_client_for_link")(
147147 function* <ConfirmError, ConfirmContext>(
148− relayClient: RelayClient.RelayClientShape,
148+ relayClient: RelayClient.RelayClient["Service"],
149149 confirmInstall: (version: string) => Effect.Effect<boolean, ConfirmError, ConfirmContext>,
150150 reportProgress: (event: RelayClientInstallProgressEvent) => Effect.Effect<void>,
151151 ) {
@@ -164,7 +164,7 @@ export const acquireRelayClientForLink = Effect.fn("cloud.cli.acquire_relay_clie · 12 unmodified lines
164164 );
165165
166166 const withCloudCliSessionToken = <A, E, R>(
167− environmentAuth: EnvironmentAuth.EnvironmentAuthShape,
167+ environmentAuth: EnvironmentAuth.EnvironmentAuth["Service"],
168168 run: (token: string) => Effect.Effect<A, E, R>,
169169 ) =>
170170 Effect.acquireUseRelease(
@@ -183,7 +183,7 @@ type LiveCloudActionResult = · 12 unmodified lines
183183 | { readonly status: "failed"; readonly cause: unknown };
184184
185185 const runLiveCloudUnlink = Effect.fn("cloud.cli.run_live_unlink")(function* () {
186− const config = yield* ServerConfig;
186+ const config = yield* ServerConfig.ServerConfig;
187187 const runtimeState = yield* readPersistedServerRuntimeState(config.serverRuntimeStatePath);
188188 if (Option.isNone(runtimeState)) {
189189 return { status: "not-running" } satisfies LiveCloudActionResult;
@@ -219,7 +219,7 @@ const unlinkRelayEnvironment = Effect.fn("cloud.cli.unlink_relay_environment")(f · 29 unmodified lines
219219 return { status: "not-authenticated" } satisfies RelayUnlinkResult;
220220 }
221221
222− const environment = yield* ServerEnvironment;
222+ const environment = yield* ServerEnvironment.ServerEnvironment;
223223 const environmentId = yield* environment.getEnvironmentId;
224224 const relayUrl = yield* relayUrlConfig;
225225 const httpClient = yield* HttpClient.HttpClient;
@@ -285,8 +285,8 @@ const runCloudCommand = <A, E>( · 59 unmodified lines
285285 | FileSystem.FileSystem
286286 | HttpClient.HttpClient
287287 | Prompt.Environment
288− | ServerConfig
289− | ServerEnvironment
288+ | ServerConfig.ServerConfig
289+ | ServerEnvironment.ServerEnvironment
290290 >,
291291 options?: {
292292 readonly quietLogs?: boolean;
@@ -305,7 +305,7 @@ const runCloudCommand = <A, E>( · 12 unmodified lines
305305 headlessRelayClientTracingLayer,
306306 ).pipe(
307307 Layer.provideMerge(FetchHttpClient.layer),
308− Layer.provideMerge(Layer.succeed(ServerConfig, config)),
308+ Layer.provideMerge(ServerConfig.layer(config)),
309309 Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)),
310310 );
311311 return yield* run.pipe(Effect.provide(runtimeLayer));
apps/server/src/cli/project.ts+21−14
@@ -26,19 +26,19 @@ import * as HttpApiClient from "effect/unstable/httpapi/HttpApiClient"; · 25 unmodified lines
2626
2727 import * as EnvironmentAuth from "../auth/EnvironmentAuth.ts";
2828
29−import { ServerConfig, type ServerConfigShape } from "../config.ts";
30−import { OrchestrationEngineService } from "../orchestration/Services/OrchestrationEngine.ts";
31−import { ProjectionSnapshotQuery } from "../orchestration/Services/ProjectionSnapshotQuery.ts";
29+import * as ServerConfig from "../config.ts";
30+import * as OrchestrationEngine from "../orchestration/Services/OrchestrationEngine.ts";
31+import * as ProjectionSnapshotQuery from "../orchestration/Services/ProjectionSnapshotQuery.ts";
3232 import { OrchestrationLayerLive } from "../orchestration/runtimeLayer.ts";
3333 import { layerConfig as SqlitePersistenceLayerLive } from "../persistence/Layers/Sqlite.ts";
3434 import { RepositoryIdentityResolverLive } from "../project/Layers/RepositoryIdentityResolver.ts";
35−import { getAutoBootstrapDefaultModelSelection } from "../serverRuntimeStartup.ts";
35+import * as ServerRuntimeStartup from "../serverRuntimeStartup.ts";
3636 import {
3737 clearPersistedServerRuntimeState,
3838 readPersistedServerRuntimeState,
3939 } from "../serverRuntimeState.ts";
4040 import { WorkspacePathsLive } from "../workspace/Layers/WorkspacePaths.ts";
41−import { WorkspacePaths } from "../workspace/Services/WorkspacePaths.ts";
41+import * as WorkspacePaths from "../workspace/Services/WorkspacePaths.ts";
4242 import { type CliAuthLocationFlags, projectLocationFlags, resolveCliAuthConfig } from "./config.ts";
4343
4444 type ProjectMutationTarget = {
@@ -78,7 +78,7 @@ const ProjectCliRuntimeLive = Layer.mergeAll( · 33 unmodified lines
7878 const PROJECT_CLI_LIVE_SERVER_TIMEOUT = Duration.seconds(1);
7979 const isEnvironmentHttpCommonError = Schema.is(EnvironmentHttpCommonError);
8080 const withProjectCliSessionToken = <A, E, R>(
81− environmentAuth: EnvironmentAuth.EnvironmentAuthShape,
81+ environmentAuth: EnvironmentAuth.EnvironmentAuth["Service"],
8282 run: (token: string) => Effect.Effect<A, E, R>,
8383 ) =>
8484 Effect.acquireUseRelease(
@@ -123,7 +123,7 @@ const makeLiveServerClient = (origin: string) => · 38 unmodified lines
123123 const normalizeWorkspaceRootForProjectCommand = Effect.fn(
124124 "normalizeWorkspaceRootForProjectCommand",
125125 )(function* (workspaceRoot: string) {
126− const workspacePaths = yield* WorkspacePaths;
126+ const workspacePaths = yield* WorkspacePaths.WorkspacePaths;
127127 return yield* workspacePaths.normalizeWorkspaceRoot(workspaceRoot);
128128 });
129129
@@ -211,12 +211,15 @@ const dispatchLiveOrchestrationCommand = ( · 81 unmodified lines
211211 }).pipe(withProjectCliLiveServerTimeout, Effect.catch(failLiveServerRequest));
212212
213213 const getOfflineSnapshot = Effect.fn("getOfflineSnapshot")(function* () {
214− const projectionSnapshotQuery = yield* ProjectionSnapshotQuery;
214+ const projectionSnapshotQuery = yield* ProjectionSnapshotQuery.ProjectionSnapshotQuery;
215215 return yield* projectionSnapshotQuery.getSnapshot();
216216 });
217217
218218 const tryResolveLiveProjectExecutionMode = Effect.fn("tryResolveLiveProjectExecutionMode")(
219− function* (environmentAuth: EnvironmentAuth.EnvironmentAuthShape, config: ServerConfigShape) {
219+ function* (
220+ environmentAuth: EnvironmentAuth.EnvironmentAuth["Service"],
221+ config: ServerConfig.ServerConfig["Service"],
222+ ) {
220223 const runtimeState = yield* readPersistedServerRuntimeState(config.serverRuntimeStatePath);
221224 if (Option.isNone(runtimeState)) {
222225 return Option.none<{ readonly origin: string }>();
@@ -251,7 +254,11 @@ const runProjectMutation = Effect.fn("runProjectMutation")(function* ( · 28 unmodified lines
251254 }) => Effect.Effect<
252255 string,
253256 Error,
254− Crypto.Crypto | FileSystem.FileSystem | HttpClient.HttpClient | Path.Path | WorkspacePaths
257+ | Crypto.Crypto
258+ | FileSystem.FileSystem
259+ | HttpClient.HttpClient
260+ | Path.Path
261+ | WorkspacePaths.WorkspacePaths
255262 >,
256263 ) {
257264 const logLevel = yield* GlobalFlag.LogLevel;
@@ -278,13 +285,13 @@ const runProjectMutation = Effect.fn("runProjectMutation")(function* ( · 20 unmodified lines
278285 }
279286
280287 const offlineRuntimeLayer = ProjectCliRuntimeLive.pipe(
281− Layer.provide(Layer.succeed(ServerConfig, config)),
288+ Layer.provide(ServerConfig.layer(config)),
282289 Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)),
283290 );
284291
285292 return yield* Effect.gen(function* () {
286293 const snapshot = yield* getOfflineSnapshot();
287− const orchestrationEngine = yield* OrchestrationEngineService;
294+ const orchestrationEngine = yield* OrchestrationEngine.OrchestrationEngineService;
288295 const output = yield* run({
289296 snapshot,
290297 dispatch: (command) => orchestrationEngine.dispatch(command),
@@ -296,7 +303,7 @@ const runProjectMutation = Effect.fn("runProjectMutation")(function* ( · 5 unmodified lines
296303 Effect.provide(
297304 Layer.mergeAll(EnvironmentAuth.runtimeLayer, WorkspacePathsLive).pipe(
298305 Layer.provideMerge(FetchHttpClient.layer),
299− Layer.provide(Layer.succeed(ServerConfig, config)),
306+ Layer.provide(ServerConfig.layer(config)),
300307 Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)),
301308 ),
302309 ),
@@ -341,7 +348,7 @@ const projectAddCommand = Command.make("add", { · 38 unmodified lines
341348 projectId,
342349 title,
343350 workspaceRoot,
344− defaultModelSelection: getAutoBootstrapDefaultModelSelection(),
351+ defaultModelSelection: ServerRuntimeStartup.getAutoBootstrapDefaultModelSelection(),
345352 createdAt: DateTime.formatIso(yield* DateTime.now),
346353 });
347354 return `Added project ${projectId} (${title}) at ${workspaceRoot}.`;
apps/server/src/config.ts+83−76
@@ -6,13 +6,13 @@ · 5 unmodified lines
66 *
77 * @module ServerConfig
88 */
9+import * as Context from "effect/Context";
910 import * as Effect from "effect/Effect";
1011 import * as FileSystem from "effect/FileSystem";
1112 import * as Layer from "effect/Layer";
1213 import * as LogLevel from "effect/LogLevel";
1314 import * as Path from "effect/Path";
1415 import * as Schema from "effect/Schema";
15−import * as Context from "effect/Context";
1616
1717 export const DEFAULT_PORT = 3773;
1818
@@ -46,38 +46,51 @@ export interface ServerDerivedPaths { · 27 unmodified lines
4646 }
4747
4848 /**
49− * ServerConfigShape - Process/runtime configuration required by the server.
49+ * ServerConfig - Service tag for server runtime configuration.
5050 */
51−export interface ServerConfigShape extends ServerDerivedPaths {
52− readonly logLevel: LogLevel.LogLevel;
53− readonly traceMinLevel: LogLevel.LogLevel;
54− readonly traceTimingEnabled: boolean;
55− readonly traceBatchWindowMs: number;
56− readonly traceMaxBytes: number;
57− readonly traceMaxFiles: number;
58− readonly otlpTracesUrl: string | undefined;
59− readonly otlpMetricsUrl: string | undefined;
60− readonly otlpExportIntervalMs: number;
61− readonly otlpServiceName: string;
62− readonly mode: RuntimeMode;
63− readonly port: number;
64− readonly host: string | undefined;
65− readonly cwd: string;
66− readonly baseDir: string;
67− readonly staticDir: string | undefined;
68− readonly devUrl: URL | undefined;
69− readonly noBrowser: boolean;
70− readonly startupPresentation: StartupPresentation;
71− readonly desktopBootstrapToken: string | undefined;
72− readonly autoBootstrapProjectFromCwd: boolean;
73− readonly logWebSocketEvents: boolean;
74− readonly tailscaleServeEnabled: boolean;
75− readonly tailscaleServePort: number;
51+export class ServerConfig extends Context.Service<
52+ ServerConfig,
53+ ServerDerivedPaths & {
54+ readonly logLevel: LogLevel.LogLevel;
55+ readonly traceMinLevel: LogLevel.LogLevel;
56+ readonly traceTimingEnabled: boolean;
57+ readonly traceBatchWindowMs: number;
58+ readonly traceMaxBytes: number;
59+ readonly traceMaxFiles: number;
60+ readonly otlpTracesUrl: string | undefined;
61+ readonly otlpMetricsUrl: string | undefined;
62+ readonly otlpExportIntervalMs: number;
63+ readonly otlpServiceName: string;
64+ readonly mode: RuntimeMode;
65+ readonly port: number;
66+ readonly host: string | undefined;
67+ readonly cwd: string;
68+ readonly baseDir: string;
69+ readonly staticDir: string | undefined;
70+ readonly devUrl: URL | undefined;
71+ readonly noBrowser: boolean;
72+ readonly startupPresentation: StartupPresentation;
73+ readonly desktopBootstrapToken: string | undefined;
74+ readonly autoBootstrapProjectFromCwd: boolean;
75+ readonly logWebSocketEvents: boolean;
76+ readonly tailscaleServeEnabled: boolean;
77+ readonly tailscaleServePort: number;
78+ }
79+>()("t3/config/ServerConfig") {
80+ /** @deprecated Import and use `layerTest` from this module. */
81+ static readonly layerTest = (
82+ cwd: string,
83+ baseDirOrPrefix: string | { readonly prefix: string },
84+ ) => layerTest(cwd, baseDirOrPrefix);
7685 }
7786
87+export const make = (config: ServerConfig["Service"]) => ServerConfig.of(config);
88+
89+export const layer = (config: ServerConfig["Service"]) => Layer.succeed(ServerConfig, make(config));
90+
7891 export const deriveServerPaths = Effect.fn(function* (
79− baseDir: ServerConfigShape["baseDir"],
80− devUrl: ServerConfigShape["devUrl"],
92+ baseDir: ServerConfig["Service"]["baseDir"],
93+ devUrl: ServerConfig["Service"]["devUrl"],
8194 ): Effect.fn.Return<ServerDerivedPaths, never, Path.Path> {
8295 const { join } = yield* Path.Path;
8396 const stateDir = join(baseDir, devUrl !== undefined ? "dev" : "userdata");
@@ -129,56 +142,50 @@ export const ensureServerDirectories = Effect.fn(function* (derivedPaths: Server · 45 unmodified lines
129142 );
130143 });
131144
132−/**
133− * ServerConfig - Service tag for server runtime configuration.
134− */
135−export class ServerConfig extends Context.Service<ServerConfig, ServerConfigShape>()(
136− "t3/config/ServerConfig",
145+const makeTest = Effect.fn("ServerConfig.makeTest")(function* (
146+ cwd: string,
147+ baseDirOrPrefix: string | { readonly prefix: string },
137148 ) {
138− static readonly layerTest = (cwd: string, baseDirOrPrefix: string | { prefix: string }) =>
139− Layer.effect(
140− ServerConfig,
141− Effect.gen(function* () {
142− const devUrl = undefined;
149+ const devUrl = undefined;
150+ const fs = yield* FileSystem.FileSystem;
151+ const baseDir =
152+ typeof baseDirOrPrefix === "string"
153+ ? baseDirOrPrefix
154+ : yield* fs.makeTempDirectoryScoped({ prefix: baseDirOrPrefix.prefix });
155+ const derivedPaths = yield* deriveServerPaths(baseDir, devUrl);
156+ yield* ensureServerDirectories(derivedPaths);
143157
144− const fs = yield* FileSystem.FileSystem;
145− const baseDir =
146− typeof baseDirOrPrefix === "string"
147− ? baseDirOrPrefix
148− : yield* fs.makeTempDirectoryScoped({ prefix: baseDirOrPrefix.prefix });
149− const derivedPaths = yield* deriveServerPaths(baseDir, devUrl);
150− yield* ensureServerDirectories(derivedPaths);
158+ return ServerConfig.of({
159+ logLevel: "Error",
160+ traceMinLevel: "Info",
161+ traceTimingEnabled: true,
162+ traceBatchWindowMs: 200,
163+ traceMaxBytes: 10 * 1024 * 1024,
164+ traceMaxFiles: 10,
165+ otlpTracesUrl: undefined,
166+ otlpMetricsUrl: undefined,
167+ otlpExportIntervalMs: 10_000,
168+ otlpServiceName: "t3-server",
169+ cwd,
170+ baseDir,
171+ ...derivedPaths,
172+ mode: "web",
173+ autoBootstrapProjectFromCwd: false,
174+ logWebSocketEvents: false,
175+ tailscaleServeEnabled: false,
176+ tailscaleServePort: 443,
177+ port: 0,
178+ host: undefined,
179+ desktopBootstrapToken: undefined,
180+ staticDir: undefined,
181+ devUrl,
182+ noBrowser: false,
183+ startupPresentation: "browser",
184+ });
185+});
151186
152− return {
153− logLevel: "Error",
154− traceMinLevel: "Info",
155− traceTimingEnabled: true,
156− traceBatchWindowMs: 200,
157− traceMaxBytes: 10 * 1024 * 1024,
158− traceMaxFiles: 10,
159− otlpTracesUrl: undefined,
160− otlpMetricsUrl: undefined,
161− otlpExportIntervalMs: 10_000,
162− otlpServiceName: "t3-server",
163− cwd,
164− baseDir,
165− ...derivedPaths,
166− mode: "web",
167− autoBootstrapProjectFromCwd: false,
168− logWebSocketEvents: false,
169− tailscaleServeEnabled: false,
170− tailscaleServePort: 443,
171− port: 0,
172− host: undefined,
173− desktopBootstrapToken: undefined,
174− staticDir: undefined,
175− devUrl,
176− noBrowser: false,
177− startupPresentation: "browser",
178− } satisfies ServerConfigShape;
179− }),
180− );
181−}
187+export const layerTest = (cwd: string, baseDirOrPrefix: string | { readonly prefix: string }) =>
188+ Layer.effect(ServerConfig, makeTest(cwd, baseDirOrPrefix));
182189
183190 export const resolveStaticDir = Effect.fn(function* () {
184191 const { join, resolve } = yield* Path.Path;
apps/server/src/environment/Layers/ServerEnvironment.test.ts+8−10
@@ -8,15 +8,15 @@ import * as FileSystem from "effect/FileSystem"; · 7 unmodified lines
88 import * as Layer from "effect/Layer";
99 import * as PlatformError from "effect/PlatformError";
1010
11−import { deriveServerPaths, ServerConfig, type ServerConfigShape } from "../../config.ts";
12−import { ServerEnvironment } from "../Services/ServerEnvironment.ts";
11+import * as ServerConfig from "../../config.ts";
12+import * as ServerEnvironment from "../Services/ServerEnvironment.ts";
1313 import { ServerEnvironmentLive } from "./ServerEnvironment.ts";
1414
1515 const makeServerEnvironmentLayer = (baseDir: string) =>
1616 ServerEnvironmentLive.pipe(Layer.provide(ServerConfig.layerTest(process.cwd(), baseDir)));
1717
1818 const makeServerConfig = Effect.fn(function* (baseDir: string) {
19− const derivedPaths = yield* deriveServerPaths(baseDir, undefined);
19+ const derivedPaths = yield* ServerConfig.deriveServerPaths(baseDir, undefined);
2020
2121 return {
2222 ...derivedPaths,
@@ -44,7 +44,7 @@ const makeServerConfig = Effect.fn(function* (baseDir: string) { · 21 unmodified lines
4444 devUrl: undefined,
4545 noBrowser: false,
4646 startupPresentation: "browser",
47− } satisfies ServerConfigShape;
47+ } satisfies ServerConfig.ServerConfig["Service"];
4848 });
4949
5050 it.layer(NodeServices.layer)("ServerEnvironmentLive", (it) => {
@@ -56,11 +56,11 @@ it.layer(NodeServices.layer)("ServerEnvironmentLive", (it) => { · 5 unmodified lines
5656 });
5757
5858 const first = yield* Effect.gen(function* () {
59− const serverEnvironment = yield* ServerEnvironment;
59+ const serverEnvironment = yield* ServerEnvironment.ServerEnvironment;
6060 return yield* serverEnvironment.getDescriptor;
6161 }).pipe(Effect.provide(makeServerEnvironmentLayer(baseDir)));
6262 const second = yield* Effect.gen(function* () {
63− const serverEnvironment = yield* ServerEnvironment;
63+ const serverEnvironment = yield* ServerEnvironment.ServerEnvironment;
6464 return yield* serverEnvironment.getDescriptor;
6565 }).pipe(Effect.provide(makeServerEnvironmentLayer(baseDir)));
6666
@@ -109,14 +109,12 @@ it.layer(NodeServices.layer)("ServerEnvironmentLive", (it) => { · 42 unmodified lines
109109 });
110110
111111 const exit = yield* Effect.gen(function* () {
112− const serverEnvironment = yield* ServerEnvironment;
112+ const serverEnvironment = yield* ServerEnvironment.ServerEnvironment;
113113 return yield* serverEnvironment.getDescriptor;
114114 }).pipe(
115115 Effect.provide(
116116 ServerEnvironmentLive.pipe(
117− Layer.provide(
118− Layer.merge(Layer.succeed(ServerConfig, serverConfig), failingFileSystemLayer),
119− ),
117+ Layer.provide(Layer.merge(ServerConfig.layer(serverConfig), failingFileSystemLayer)),
120118 ),
121119 ),
122120 Effect.exit,
apps/server/src/keybindings.test.ts+48−55
@@ -9,28 +9,21 @@ import * as Layer from "effect/Layer"; · 8 unmodified lines
99 import * as Logger from "effect/Logger";
1010 import * as Path from "effect/Path";
1111 import * as Schema from "effect/Schema";
12−import { ServerConfig } from "./config.ts";
13−
14−import {
15− DEFAULT_KEYBINDINGS,
16− Keybindings,
17− KeybindingsLive,
18− ResolvedKeybindingFromConfig,
19− compileResolvedKeybindingRule,
20− compileResolvedKeybindingsConfig,
21− parseKeybindingShortcut,
22−} from "./keybindings.ts";
12+import * as ServerConfig from "./config.ts";
13+import * as Keybindings from "./keybindings.ts";
2314 import { KeybindingsConfigError } from "@t3tools/contracts";
2415
2516 const KeybindingsConfigJson = Schema.fromJsonString(KeybindingsConfig);
2617 const encodeKeybindingsConfigJson = Schema.encodeEffect(KeybindingsConfigJson);
2718 const decodeKeybindingsConfigJson = Schema.decodeUnknownEffect(KeybindingsConfigJson);
28−const encodeResolvedKeybindingFromConfig = Schema.encodeEffect(ResolvedKeybindingFromConfig);
19+const encodeResolvedKeybindingFromConfig = Schema.encodeEffect(
20+ Keybindings.ResolvedKeybindingFromConfig,
21+);
2922 const decodeResolvedKeybindingFromConfigExit = Schema.decodeUnknownExit(
30− ResolvedKeybindingFromConfig,
23+ Keybindings.ResolvedKeybindingFromConfig,
3124 );
3225 const makeKeybindingsLayer = () => {
33− return KeybindingsLive.pipe(
26+ return Keybindings.layer.pipe(
3427 Layer.provideMerge(
3528 Layer.fresh(
3629 ServerConfig.layerTest(process.cwd(), {
@@ -66,7 +59,7 @@ const readKeybindingsConfig = (configPath: string) => · 29 unmodified lines
6659 it.layer(NodeServices.layer)("keybindings", (it) => {
6760 it.effect("parses shortcuts including plus key", () =>
6861 Effect.sync(() => {
69− assert.deepEqual(parseKeybindingShortcut("mod+j"), {
62+ assert.deepEqual(Keybindings.parseKeybindingShortcut("mod+j"), {
7063 key: "j",
7164 metaKey: false,
7265 ctrlKey: false,
@@ -74,7 +67,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 1 unmodified line
7467 altKey: false,
7568 modKey: true,
7669 });
77− assert.deepEqual(parseKeybindingShortcut("mod++"), {
70+ assert.deepEqual(Keybindings.parseKeybindingShortcut("mod++"), {
7871 key: "+",
7972 metaKey: false,
8073 ctrlKey: false,
@@ -87,7 +80,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 6 unmodified lines
8780
8881 it.effect("compiles valid rule with parsed when AST", () =>
8982 Effect.sync(() => {
90− const compiled = compileResolvedKeybindingRule({
83+ const compiled = Keybindings.compileResolvedKeybindingRule({
9184 key: "mod+d",
9285 command: "terminal.split",
9386 when: "terminalOpen && !terminalFocus",
@@ -137,14 +130,14 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 43 unmodified lines
137130 it.effect("rejects invalid rules", () =>
138131 Effect.sync(() => {
139132 assert.isNull(
140− compileResolvedKeybindingRule({
133+ Keybindings.compileResolvedKeybindingRule({
141134 key: "mod+shift+d+o",
142135 command: "terminal.new",
143136 }),
144137 );
145138
146139 assert.isNull(
147− compileResolvedKeybindingRule({
140+ Keybindings.compileResolvedKeybindingRule({
148141 key: "mod+d",
149142 command: "terminal.split",
150143 when: "terminalFocus && (",
@@ -152,7 +145,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 1 unmodified line
152145 );
153146
154147 assert.isNull(
155− compileResolvedKeybindingRule({
148+ Keybindings.compileResolvedKeybindingRule({
156149 key: "mod+d",
157150 command: "terminal.split",
158151 when: `${"!".repeat(300)}terminalFocus`,
@@ -181,23 +174,23 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 22 unmodified lines
181174 it.effect("bootstraps default keybindings when config file is missing", () =>
182175 Effect.gen(function* () {
183176 const fs = yield* FileSystem.FileSystem;
184− const { keybindingsConfigPath } = yield* ServerConfig;
177+ const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig;
185178 assert.isFalse(yield* fs.exists(keybindingsConfigPath));
186179
187180 yield* Effect.gen(function* () {
188− const keybindings = yield* Keybindings;
181+ const keybindings = yield* Keybindings.Keybindings;
189182 yield* keybindings.syncDefaultKeybindingsOnStartup;
190183 });
191184
192185 const persisted = yield* readKeybindingsConfig(keybindingsConfigPath);
193− assert.deepEqual(persisted, DEFAULT_KEYBINDINGS);
186+ assert.deepEqual(persisted, Keybindings.DEFAULT_KEYBINDINGS);
194187 }).pipe(Effect.provide(makeKeybindingsLayer())),
195188 );
196189
197190 it.effect("ships configurable thread navigation defaults", () =>
198191 Effect.sync(() => {
199192 const defaultsByCommand = new Map(
200− DEFAULT_KEYBINDINGS.map((binding) => [binding.command, binding.key] as const),
193+ Keybindings.DEFAULT_KEYBINDINGS.map((binding) => [binding.command, binding.key] as const),
201194 );
202195
203196 assert.equal(defaultsByCommand.get("thread.previous"), "mod+shift+[");
@@ -215,17 +208,17 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 11 unmodified lines
215208 it.effect("uses defaults in runtime when config is malformed without overriding file", () =>
216209 Effect.gen(function* () {
217210 const fs = yield* FileSystem.FileSystem;
218− const { keybindingsConfigPath } = yield* ServerConfig;
211+ const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig;
219212 yield* fs.writeFileString(keybindingsConfigPath, "{ not-json");
220213
221214 const configState = yield* Effect.gen(function* () {
222− const keybindings = yield* Keybindings;
215+ const keybindings = yield* Keybindings.Keybindings;
223216 return yield* keybindings.loadConfigState;
224217 });
225218
226219 assert.deepEqual(
227220 configState.keybindings,
228− compileResolvedKeybindingsConfig(DEFAULT_KEYBINDINGS),
221+ Keybindings.compileResolvedKeybindingsConfig(Keybindings.DEFAULT_KEYBINDINGS),
229222 );
230223 assert.deepEqual(configState.issues, [
231224 {
@@ -240,7 +233,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 8 unmodified lines
240233 it.effect("ignores invalid entries in runtime and reports them as issues", () =>
241234 Effect.gen(function* () {
242235 const fs = yield* FileSystem.FileSystem;
243− const { keybindingsConfigPath } = yield* ServerConfig;
236+ const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig;
244237 yield* fs.writeFileString(
245238 keybindingsConfigPath,
246239 // @effect-diagnostics-next-line preferSchemaOverJson:off
@@ -252,7 +245,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 5 unmodified lines
252245 );
253246
254247 const configState = yield* Effect.gen(function* () {
255− const keybindings = yield* Keybindings;
248+ const keybindings = yield* Keybindings.Keybindings;
256249 return yield* keybindings.loadConfigState;
257250 });
258251
@@ -279,14 +272,14 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 20 unmodified lines
279272 "upserts missing default keybindings on startup without overriding existing command rules",
280273 () =>
281274 Effect.gen(function* () {
282− const { keybindingsConfigPath } = yield* ServerConfig;
275+ const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig;
283276 yield* writeKeybindingsConfig(keybindingsConfigPath, [
284277 { key: "mod+shift+t", command: "terminal.toggle" },
285278 { key: "mod+shift+r", command: "script.run-tests.run" },
286279 ]);
287280
288281 yield* Effect.gen(function* () {
289− const keybindings = yield* Keybindings;
282+ const keybindings = yield* Keybindings.Keybindings;
290283 yield* keybindings.syncDefaultKeybindingsOnStartup;
291284 });
292285
@@ -300,7 +293,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 7 unmodified lines
300293 persisted.some((entry) => entry.command === "terminal.toggle" && entry.key === "mod+j"),
301294 );
302295
303− for (const defaultRule of DEFAULT_KEYBINDINGS) {
296+ for (const defaultRule of Keybindings.DEFAULT_KEYBINDINGS) {
304297 assert.isTrue(byCommand.has(defaultRule.command), `expected ${defaultRule.command}`);
305298 }
306299 assert.isTrue(byCommand.has("script.run-tests.run"));
@@ -314,13 +307,13 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 7 unmodified lines
314307 });
315308
316309 return Effect.gen(function* () {
317− const { keybindingsConfigPath } = yield* ServerConfig;
310+ const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig;
318311 yield* writeKeybindingsConfig(keybindingsConfigPath, [
319312 { key: "mod+j", command: "script.custom-action.run" },
320313 ]);
321314
322315 yield* Effect.gen(function* () {
323− const keybindings = yield* Keybindings;
316+ const keybindings = yield* Keybindings.Keybindings;
324317 yield* keybindings.syncDefaultKeybindingsOnStartup;
325318 });
326319
@@ -345,13 +338,13 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 18 unmodified lines
345338
346339 it.effect("upserts custom keybindings to configured path", () =>
347340 Effect.gen(function* () {
348− const { keybindingsConfigPath } = yield* ServerConfig;
341+ const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig;
349342 yield* writeKeybindingsConfig(keybindingsConfigPath, [
350343 { key: "mod+j", command: "terminal.toggle" },
351344 ]);
352345
353346 const resolved = yield* Effect.gen(function* () {
354− const keybindings = yield* Keybindings;
347+ const keybindings = yield* Keybindings.Keybindings;
355348 return yield* keybindings.upsertKeybindingRule({
356349 key: "mod+shift+r",
357350 command: "script.run-tests.run",
@@ -371,12 +364,12 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 13 unmodified lines
371364
372365 it.effect("appends additional custom keybindings for the same command", () =>
373366 Effect.gen(function* () {
374− const { keybindingsConfigPath } = yield* ServerConfig;
367+ const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig;
375368 yield* writeKeybindingsConfig(keybindingsConfigPath, [
376369 { key: "mod+r", command: "script.run-tests.run" },
377370 ]);
378371 yield* Effect.gen(function* () {
379− const keybindings = yield* Keybindings;
372+ const keybindings = yield* Keybindings.Keybindings;
380373 return yield* keybindings.upsertKeybindingRule({
381374 key: "mod+shift+r",
382375 command: "script.run-tests.run",
@@ -394,13 +387,13 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 11 unmodified lines
394387
395388 it.effect("replaces only the targeted custom keybinding", () =>
396389 Effect.gen(function* () {
397− const { keybindingsConfigPath } = yield* ServerConfig;
390+ const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig;
398391 yield* writeKeybindingsConfig(keybindingsConfigPath, [
399392 { key: "mod+r", command: "script.run-tests.run" },
400393 { key: "mod+shift+r", command: "script.run-tests.run" },
401394 ]);
402395 yield* Effect.gen(function* () {
403− const keybindings = yield* Keybindings;
396+ const keybindings = yield* Keybindings.Keybindings;
404397 return yield* keybindings.upsertKeybindingRule({
405398 key: "mod+alt+r",
406399 command: "script.run-tests.run",
@@ -419,13 +412,13 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 12 unmodified lines
419412
420413 it.effect("removes only the targeted custom keybinding", () =>
421414 Effect.gen(function* () {
422− const { keybindingsConfigPath } = yield* ServerConfig;
415+ const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig;
423416 yield* writeKeybindingsConfig(keybindingsConfigPath, [
424417 { key: "mod+r", command: "script.run-tests.run" },
425418 { key: "mod+shift+r", command: "script.run-tests.run" },
426419 ]);
427420 yield* Effect.gen(function* () {
428− const keybindings = yield* Keybindings;
421+ const keybindings = yield* Keybindings.Keybindings;
429422 return yield* keybindings.removeKeybindingRule({
430423 key: "mod+r",
431424 command: "script.run-tests.run",
@@ -441,11 +434,11 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 9 unmodified lines
441434 it.effect("refuses to overwrite malformed keybindings config", () =>
442435 Effect.gen(function* () {
443436 const fs = yield* FileSystem.FileSystem;
444− const { keybindingsConfigPath } = yield* ServerConfig;
437+ const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig;
445438 yield* fs.writeFileString(keybindingsConfigPath, "{ not-json");
446439
447440 const result = yield* Effect.gen(function* () {
448− const keybindings = yield* Keybindings;
441+ const keybindings = yield* Keybindings.Keybindings;
449442 return yield* keybindings.upsertKeybindingRule({
450443 key: "mod+shift+r",
451444 command: "script.run-tests.run",
@@ -461,14 +454,14 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 9 unmodified lines
461454 it.effect("reports non-array config parse errors without duplicate prefix", () =>
462455 Effect.gen(function* () {
463456 const fs = yield* FileSystem.FileSystem;
464− const { keybindingsConfigPath } = yield* ServerConfig;
457+ const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig;
465458 yield* fs.writeFileString(
466459 keybindingsConfigPath,
467460 '{"key":"mod+j","command":"terminal.toggle"}',
468461 );
469462
470463 const firstResult = yield* Effect.gen(function* () {
471− const keybindings = yield* Keybindings;
464+ const keybindings = yield* Keybindings.Keybindings;
472465 return yield* keybindings.upsertKeybindingRule({
473466 key: "mod+shift+r",
474467 command: "script.run-tests.run",
@@ -477,7 +470,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 2 unmodified lines
477470 assertFailure(firstResult, "expected JSON array");
478471
479472 const secondResult = yield* Effect.gen(function* () {
480− const keybindings = yield* Keybindings;
473+ const keybindings = yield* Keybindings.Keybindings;
481474 return yield* keybindings.upsertKeybindingRule({
482475 key: "mod+shift+r",
483476 command: "script.run-tests.run",
@@ -490,7 +483,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 6 unmodified lines
490483 it.effect("fails when config directory is not writable", () =>
491484 Effect.gen(function* () {
492485 const fs = yield* FileSystem.FileSystem;
493− const { keybindingsConfigPath } = yield* ServerConfig;
486+ const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig;
494487 const { dirname } = yield* Path.Path;
495488 yield* writeKeybindingsConfig(keybindingsConfigPath, [
496489 { key: "mod+j", command: "terminal.toggle" },
@@ -498,7 +491,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 1 unmodified line
498491 yield* fs.chmod(dirname(keybindingsConfigPath), 0o500);
499492
500493 const result = yield* Effect.gen(function* () {
501− const keybindings = yield* Keybindings;
494+ const keybindings = yield* Keybindings.Keybindings;
502495 return yield* keybindings.upsertKeybindingRule({
503496 key: "mod+shift+r",
504497 command: "script.run-tests.run",
@@ -516,13 +509,13 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 11 unmodified lines
516509
517510 it.effect("caches loaded resolved config across repeated reads", () =>
518511 Effect.gen(function* () {
519− const { keybindingsConfigPath } = yield* ServerConfig;
512+ const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig;
520513 yield* writeKeybindingsConfig(keybindingsConfigPath, [
521514 { key: "mod+j", command: "terminal.toggle" },
522515 ]);
523516
524517 const [first, second] = yield* Effect.gen(function* () {
525− const keybindings = yield* Keybindings;
518+ const keybindings = yield* Keybindings.Keybindings;
526519 const firstLoad = (yield* keybindings.loadConfigState).keybindings;
527520 const secondLoad = (yield* keybindings.loadConfigState).keybindings;
528521 return [firstLoad, secondLoad] as const;
@@ -535,13 +528,13 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 6 unmodified lines
535528
536529 it.effect("updates cached resolved config after upsert", () =>
537530 Effect.gen(function* () {
538− const { keybindingsConfigPath } = yield* ServerConfig;
531+ const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig;
539532 yield* writeKeybindingsConfig(keybindingsConfigPath, [
540533 { key: "mod+j", command: "terminal.toggle" },
541534 ]);
542535
543536 const loadedAfterUpsert = yield* Effect.gen(function* () {
544− const keybindings = yield* Keybindings;
537+ const keybindings = yield* Keybindings.Keybindings;
545538 yield* keybindings.loadConfigState;
546539 yield* keybindings.upsertKeybindingRule({
547540 key: "mod+shift+r",
@@ -557,7 +550,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 9 unmodified lines
557550
558551 it.effect("serializes concurrent upserts to avoid lost updates", () =>
559552 Effect.gen(function* () {
560− const { keybindingsConfigPath } = yield* ServerConfig;
553+ const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig;
561554 yield* writeKeybindingsConfig(keybindingsConfigPath, []);
562555
563556 const commands = Array.from(
@@ -565,7 +558,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 1 unmodified line
565558 (_, index): KeybindingCommand => `script.concurrent-${index}.run`,
566559 );
567560 yield* Effect.gen(function* () {
568− const keybindings = yield* Keybindings;
561+ const keybindings = yield* Keybindings.Keybindings;
569562 yield* Effect.all(
570563 commands.map((command, index) =>
571564 keybindings.upsertKeybindingRule({
apps/server/src/keybindings.ts+63−67
@@ -41,7 +41,7 @@ import * as Context from "effect/Context"; · 40 unmodified lines
4141 import * as Scope from "effect/Scope";
4242 import * as Stream from "effect/Stream";
4343 import * as Semaphore from "effect/Semaphore";
44−import { ServerConfig } from "./config.ts";
44+import * as ServerConfig from "./config.ts";
4545 import { writeFileStringAtomically } from "./atomicWrite.ts";
4646 import { fromJsonStringPretty, fromLenientJson } from "@t3tools/shared/schemaJson";
4747 import {
@@ -225,74 +225,70 @@ function mergeWithDefaultKeybindings(custom: ResolvedKeybindingsConfig): Resolve · 177 unmodified lines
225225 return merged.slice(-MAX_KEYBINDINGS_COUNT);
226226 }
227227
228−/**
229− * KeybindingsShape - Service API for keybinding configuration operations.
230− */
231−export interface KeybindingsShape {
232− /**
233− * Start the keybindings runtime and attach file watching.
234− *
235− * Safe to call multiple times. The first successful call establishes the
236− * runtime; later calls await the same startup.
237− */
238− readonly start: Effect.Effect<void, KeybindingsConfigError>;
239−
240− /**
241− * Await keybindings runtime readiness.
242− *
243− * Readiness means the config directory exists, the watcher is attached, the
244− * startup sync has completed, and the current snapshot has been loaded.
245− */
246− readonly ready: Effect.Effect<void, KeybindingsConfigError>;
247−
248− /**
249− * Ensure the on-disk keybindings file exists and includes all default
250− * commands so newly-added defaults are backfilled on startup.
251− */
252− readonly syncDefaultKeybindingsOnStartup: Effect.Effect<void, KeybindingsConfigError>;
253−
254− /**
255− * Load runtime keybindings state along with non-fatal configuration issues.
256− */
257− readonly loadConfigState: Effect.Effect<KeybindingsConfigState, KeybindingsConfigError>;
258−
259− /**
260− * Read the latest keybindings snapshot from cache/disk.
261− */
262− readonly getSnapshot: Effect.Effect<KeybindingsConfigState, KeybindingsConfigError>;
263−
264− /**
265− * Stream of keybindings config change events.
266− */
267− readonly streamChanges: Stream.Stream<KeybindingsChangeEvent>;
268−
269− /**
270− * Upsert a keybinding rule and persist the resulting configuration.
271− *
272− * Writes config atomically and enforces the max rule count by truncating
273− * oldest entries when needed.
274− */
275− readonly upsertKeybindingRule: (
276− input: ServerUpsertKeybindingInput,
277− ) => Effect.Effect<ResolvedKeybindingsConfig, KeybindingsConfigError>;
278−
279− /**
280− * Remove a single persisted keybinding rule by exact key/command/when match.
281− */
282− readonly removeKeybindingRule: (
283− input: ServerRemoveKeybindingInput,
284− ) => Effect.Effect<ResolvedKeybindingsConfig, KeybindingsConfigError>;
285−}
286−
287228 /**
288229 * Keybindings - Service tag for keybinding configuration operations.
289230 */
290−export class Keybindings extends Context.Service<Keybindings, KeybindingsShape>()(
291− "t3/keybindings",
292−) {}
231+export class Keybindings extends Context.Service<
232+ Keybindings,
233+ {
234+ /**
235+ * Start the keybindings runtime and attach file watching.
236+ *
237+ * Safe to call multiple times. The first successful call establishes the
238+ * runtime; later calls await the same startup.
239+ */
240+ readonly start: Effect.Effect<void, KeybindingsConfigError>;
241+
242+ /**
243+ * Await keybindings runtime readiness.
244+ *
245+ * Readiness means the config directory exists, the watcher is attached, the
246+ * startup sync has completed, and the current snapshot has been loaded.
247+ */
248+ readonly ready: Effect.Effect<void, KeybindingsConfigError>;
249+
250+ /**
251+ * Ensure the on-disk keybindings file exists and includes all default
252+ * commands so newly-added defaults are backfilled on startup.
253+ */
254+ readonly syncDefaultKeybindingsOnStartup: Effect.Effect<void, KeybindingsConfigError>;
255+
256+ /**
257+ * Load runtime keybindings state along with non-fatal configuration issues.
258+ */
259+ readonly loadConfigState: Effect.Effect<KeybindingsConfigState, KeybindingsConfigError>;
260+
261+ /**
262+ * Read the latest keybindings snapshot from cache/disk.
263+ */
264+ readonly getSnapshot: Effect.Effect<KeybindingsConfigState, KeybindingsConfigError>;
265+
266+ /**
267+ * Stream of keybindings config change events.
268+ */
269+ readonly streamChanges: Stream.Stream<KeybindingsChangeEvent>;
270+
271+ /**
272+ * Upsert a keybinding rule and persist the resulting configuration.
273+ *
274+ * Writes config atomically and enforces the max rule count by truncating
275+ * oldest entries when needed.
276+ */
277+ readonly upsertKeybindingRule: (
278+ input: ServerUpsertKeybindingInput,
279+ ) => Effect.Effect<ResolvedKeybindingsConfig, KeybindingsConfigError>;
280+
281+ /**
282+ * Remove a single persisted keybinding rule by exact key/command/when match.
283+ */
284+ readonly removeKeybindingRule: (
285+ input: ServerRemoveKeybindingInput,
286+ ) => Effect.Effect<ResolvedKeybindingsConfig, KeybindingsConfigError>;
287+ }
288+>()("t3/keybindings") {}
293289
294−const makeKeybindings = Effect.gen(function* () {
295− const { keybindingsConfigPath } = yield* ServerConfig;
290+const make = Effect.gen(function* () {
291+ const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig;
296292 const fs = yield* FileSystem.FileSystem;
297293 const path = yield* Path.Path;
298294 const upsertSemaphore = yield* Semaphore.make(1);
@@ -700,7 +696,7 @@ const makeKeybindings = Effect.gen(function* () { · 401 unmodified lines
700696 return nextResolved;
701697 }),
702698 ),
703− } satisfies KeybindingsShape;
699+ } satisfies Keybindings["Service"];
704700 });
705701
706−export const KeybindingsLive = Layer.effect(Keybindings, makeKeybindings);
702+export const layer = Layer.effect(Keybindings, make);
apps/server/src/mcp/McpHttpServer.test.ts+9−1
@@ -107,7 +107,15 @@ it.effect("registers annotated tools and preserves authenticated request context · 106 unmodified lines
107107 Effect.gen(function* () {
108108 const server = yield* McpServer.McpServer;
109109 const broker = yield* PreviewAutomationBroker.PreviewAutomationBroker;
110− const requests = yield* broker.connect("mcp-test-client");
110+ const requests = yield* broker.connect({
111+ clientId: "mcp-test-client",
112+ environmentId,
113+ threadId,
114+ tabId,
115+ visible: true,
116+ supportsAutomation: true,
117+ focusedAt: "2026-06-11T00:00:00.000Z",
118+ });
111119 yield* Stream.runForEach(requests, (request) =>
112120 broker.respond({
113121 requestId: request.requestId,
apps/server/src/mcp/PreviewAutomationBroker.test.ts+122−20
@@ -2,10 +2,13 @@ import { expect, it } from "@effect/vitest"; · 1 unmodified line
22 import {
33 EnvironmentId,
44 PreviewAutomationNoFocusedOwnerError,
5+ PreviewAutomationUnavailableError,
56 ProviderInstanceId,
67 ThreadId,
8+ type PreviewAutomationOwner,
79 } from "@t3tools/contracts";
810 import * as Effect from "effect/Effect";
11+import * as Fiber from "effect/Fiber";
912 import * as Stream from "effect/Stream";
1013
1114 import * as PreviewAutomationBroker from "./PreviewAutomationBroker.ts";
@@ -20,11 +23,22 @@ const scope = { · 8 unmodified lines
2023 expiresAt: 2,
2124 };
2225
23−it.effect("routes a request to the focused owner and correlates its response", () =>
26+const makeOwner = (overrides: Partial<PreviewAutomationOwner> = {}): PreviewAutomationOwner => ({
27+ clientId: "client-1",
28+ environmentId: scope.environmentId,
29+ threadId: scope.threadId,
30+ tabId: null,
31+ visible: false,
32+ supportsAutomation: true,
33+ focusedAt: "2026-06-11T00:00:00.000Z",
34+ ...overrides,
35+});
36+
37+it.effect("atomically registers a connected owner and correlates its response", () =>
2438 Effect.scoped(
2539 Effect.gen(function* () {
2640 const broker = yield* PreviewAutomationBroker.__testing.make;
27− const requests = yield* broker.connect("client-1");
41+ const requests = yield* broker.connect(makeOwner());
2842 yield* Stream.runForEach(requests, (request) =>
2943 broker.respond({
3044 requestId: request.requestId,
@@ -33,15 +47,6 @@ it.effect("routes a request to the focused owner and correlates its response", ( · 2 unmodified lines
3347 }),
3448 ).pipe(Effect.forkScoped);
3549 yield* Effect.yieldNow;
36− yield* broker.reportOwner({
37− clientId: "client-1",
38− environmentId: scope.environmentId,
39− threadId: scope.threadId,
40− tabId: null,
41− visible: false,
42− supportsAutomation: true,
43− focusedAt: "2026-06-11T00:00:00.000Z",
44− });
4550
4651 const result = yield* broker.invoke<{ available: boolean }>({
4752 scope,
@@ -68,22 +73,119 @@ it.effect("routes interactive commands to a hidden durable browser host", () => · 20 unmodified lines
6873 Effect.scoped(
6974 Effect.gen(function* () {
7075 const broker = yield* PreviewAutomationBroker.__testing.make;
71− const requests = yield* broker.connect("client-hidden");
76+ const requests = yield* broker.connect(
77+ makeOwner({ clientId: "client-hidden", tabId: "tab-hidden" }),
78+ );
79+ yield* Stream.runForEach(requests, (request) =>
80+ broker.respond({ requestId: request.requestId, ok: true }),
81+ ).pipe(Effect.forkScoped);
82+ yield* Effect.yieldNow;
83+
84+ yield* broker.invoke<void>({ scope, operation: "click", input: { x: 10, y: 10 } });
85+ }),
86+ ),
87+);
88+
89+it.effect("lets the browser host resolve an active tab that has not been reported yet", () =>
90+ Effect.scoped(
91+ Effect.gen(function* () {
92+ const broker = yield* PreviewAutomationBroker.__testing.make;
93+ const requests = yield* broker.connect(makeOwner({ tabId: null }));
94+ let routedTabId: string | undefined;
95+ yield* Stream.runForEach(requests, (request) => {
96+ routedTabId = request.tabId;
97+ return broker.respond({ requestId: request.requestId, ok: true });
98+ }).pipe(Effect.forkScoped);
99+ yield* Effect.yieldNow;
100+
101+ yield* broker.invoke<void>({ scope, operation: "click", input: { x: 10, y: 10 } });
102+
103+ expect(routedTabId).toBeUndefined();
104+ }),
105+ ),
106+);
107+
108+it.effect("preserves current owner metadata when its request stream reconnects", () =>
109+ Effect.scoped(
110+ Effect.gen(function* () {
111+ const broker = yield* PreviewAutomationBroker.__testing.make;
112+ const firstRequests = yield* broker.connect(makeOwner());
113+ yield* Stream.runDrain(firstRequests).pipe(Effect.forkScoped);
114+ yield* broker.reportOwner(makeOwner({ tabId: "tab-current", visible: true }));
115+
116+ const reconnectedRequests = yield* broker.connect(makeOwner());
117+ let routedTabId: string | undefined;
118+ yield* Stream.runForEach(reconnectedRequests, (request) => {
119+ routedTabId = request.tabId;
120+ return broker.respond({ requestId: request.requestId, ok: true });
121+ }).pipe(Effect.forkScoped);
122+ yield* Effect.yieldNow;
123+
124+ yield* broker.invoke<void>({ scope, operation: "click", input: { x: 10, y: 10 } });
125+
126+ expect(routedTabId).toBe("tab-current");
127+ }),
128+ ),
129+);
130+
131+it.effect("ignores stale owner cleanup after the client moves to another thread", () =>
132+ Effect.scoped(
133+ Effect.gen(function* () {
134+ const broker = yield* PreviewAutomationBroker.__testing.make;
135+ const requests = yield* broker.connect(makeOwner());
72136 yield* Stream.runForEach(requests, (request) =>
73137 broker.respond({ requestId: request.requestId, ok: true }),
74138 ).pipe(Effect.forkScoped);
75139 yield* Effect.yieldNow;
76− yield* broker.reportOwner({
77− clientId: "client-hidden",
140+
141+ yield* broker.clearOwner({
142+ clientId: "client-1",
78143 environmentId: scope.environmentId,
79− threadId: scope.threadId,
80− tabId: "tab-hidden",
81− visible: false,
82− supportsAutomation: true,
83− focusedAt: "2026-06-11T00:00:00.000Z",
144+ threadId: ThreadId.make("thread-stale"),
84145 });
85146
86− yield* broker.invoke<void>({ scope, operation: "click", input: { x: 10, y: 10 } });
147+ yield* broker.invoke<void>({ scope, operation: "status", input: {} });
148+ }),
149+ ),
150+);
151+
152+it.effect("fails requests assigned to a browser stream when that stream reconnects", () =>
153+ Effect.scoped(
154+ Effect.gen(function* () {
155+ const broker = yield* PreviewAutomationBroker.__testing.make;
156+ const _requests = yield* broker.connect(makeOwner());
157+ const pending = yield* broker
158+ .invoke<void>({ scope, operation: "status", input: {} })
159+ .pipe(Effect.flip, Effect.forkScoped);
160+ yield* Effect.yieldNow;
161+
162+ const _replacementRequests = yield* broker.connect(makeOwner());
163+
164+ const error = yield* Fiber.join(pending);
165+ expect(error).toBeInstanceOf(PreviewAutomationUnavailableError);
166+ }),
167+ ),
168+);
169+
170+it.effect("falls back to an older connected owner when a newer report is not connected", () =>
171+ Effect.scoped(
172+ Effect.gen(function* () {
173+ const broker = yield* PreviewAutomationBroker.__testing.make;
174+ const requests = yield* broker.connect(makeOwner({ clientId: "client-connected" }));
175+ yield* Stream.runForEach(requests, (request) =>
176+ broker.respond({ requestId: request.requestId, ok: true, result: "connected" }),
177+ ).pipe(Effect.forkScoped);
178+ yield* Effect.yieldNow;
179+ yield* broker.reportOwner(
180+ makeOwner({
181+ clientId: "client-report-only",
182+ focusedAt: "2026-06-11T00:00:01.000Z",
183+ }),
184+ );
185+
186+ const result = yield* broker.invoke<string>({ scope, operation: "status", input: {} });
187+
188+ expect(result).toBe("connected");
87189 }),
88190 ),
89191 );
apps/server/src/mcp/PreviewAutomationBroker.ts+40−25
@@ -11,6 +11,7 @@ import { · 10 unmodified lines
1111 type PreviewAutomationError,
1212 type PreviewAutomationOperation,
1313 type PreviewAutomationOwner,
14+ type PreviewAutomationOwnerIdentity,
1415 type PreviewAutomationRequest,
1516 type PreviewAutomationResponse,
1617 type PreviewTabId,
@@ -35,11 +36,13 @@ export interface PreviewAutomationInvokeInput { · 18 unmodified lines
3536 }
3637
3738 export interface PreviewAutomationBrokerShape {
38− readonly connect: (clientId: string) => Effect.Effect<Stream.Stream<PreviewAutomationRequest>>;
39+ readonly connect: (
40+ owner: PreviewAutomationOwner,
41+ ) => Effect.Effect<Stream.Stream<PreviewAutomationRequest>>;
3942 readonly reportOwner: (
4043 owner: PreviewAutomationOwner,
4144 ) => Effect.Effect<void, PreviewAutomationError>;
42− readonly clearOwner: (clientId: string) => Effect.Effect<void>;
45+ readonly clearOwner: (owner: PreviewAutomationOwnerIdentity) => Effect.Effect<void>;
4346 readonly respond: (
4447 response: PreviewAutomationResponse,
4548 ) => Effect.Effect<void, PreviewAutomationError>;
@@ -63,7 +66,7 @@ interface ClientConnection { · 17 unmodified lines
6366 }
6467
6568 interface PendingRequest {
66− readonly clientId: string;
69+ readonly queue: ClientConnection["queue"];
6770 readonly deferred: Deferred.Deferred<unknown, PreviewAutomationError>;
6871 }
6972
@@ -133,17 +136,16 @@ const make = Effect.gen(function* PreviewAutomationBrokerMake() { · 63 unmodified lines
133136 queue: ClientConnection["queue"],
134137 ) {
135138 const toFail = yield* SynchronizedRef.modify(state, (current) => {
136− if (current.clients.get(clientId)?.queue !== queue) {
137− return [[] as ReadonlyArray<PendingRequest>, current] as const;
138− }
139139 const clients = new Map(current.clients);
140140 const owners = new Map(current.owners);
141141 const pending = new Map(current.pending);
142142 const disconnected: PendingRequest[] = [];
143− clients.delete(clientId);
144− owners.delete(clientId);
143+ if (current.clients.get(clientId)?.queue === queue) {
144+ clients.delete(clientId);
145+ owners.delete(clientId);
146+ }
145147 for (const [requestId, entry] of pending) {
146− if (entry.clientId === clientId) {
148+ if (entry.queue === queue) {
147149 pending.delete(requestId);
148150 disconnected.push(entry);
149151 }
@@ -166,12 +168,22 @@ const make = Effect.gen(function* PreviewAutomationBrokerMake() { · 16 unmodified lines
166168
167169 const connect: PreviewAutomationBrokerShape["connect"] = Effect.fn(
168170 "PreviewAutomationBroker.connect",
169− )(function* (clientId) {
171+ )(function* (owner) {
172+ const clientId = owner.clientId;
170173 const queue = yield* Queue.unbounded<import("@t3tools/contracts").PreviewAutomationRequest>();
171174 const previous = yield* SynchronizedRef.modify(state, (current) => {
172175 const clients = new Map(current.clients);
176+ const owners = new Map(current.owners);
177+ const existingOwner = current.owners.get(clientId);
173178 clients.set(clientId, { clientId, queue });
174− return [current.clients.get(clientId), { ...current, clients }] as const;
179+ owners.set(
180+ clientId,
181+ existingOwner?.environmentId === owner.environmentId &&
182+ existingOwner.threadId === owner.threadId
183+ ? { ...existingOwner, supportsAutomation: owner.supportsAutomation }
184+ : owner,
185+ );
186+ return [current.clients.get(clientId), { ...current, clients, owners }] as const;
175187 });
176188 if (previous) yield* disconnect(clientId, previous.queue);
177189 return Stream.fromQueue(queue).pipe(Stream.ensuring(disconnect(clientId, queue)));
@@ -189,10 +201,18 @@ const make = Effect.gen(function* PreviewAutomationBrokerMake() { · 11 unmodified lines
189201
190202 const clearOwner: PreviewAutomationBrokerShape["clearOwner"] = Effect.fn(
191203 "PreviewAutomationBroker.clearOwner",
192− )(function* (clientId) {
204+ )(function* (owner) {
193205 yield* SynchronizedRef.update(state, (current) => {
206+ const currentOwner = current.owners.get(owner.clientId);
207+ if (
208+ !currentOwner ||
209+ currentOwner.environmentId !== owner.environmentId ||
210+ currentOwner.threadId !== owner.threadId
211+ ) {
212+ return current;
213+ }
194214 const owners = new Map(current.owners);
195− owners.delete(clientId);
215+ owners.delete(owner.clientId);
196216 return { ...current, owners };
197217 });
198218 });
@@ -234,8 +254,13 @@ const make = Effect.gen(function* PreviewAutomationBrokerMake() { · 35 unmodified lines
234254 owner.supportsAutomation,
235255 )
236256 .sort((left, right) => right.focusedAt.localeCompare(left.focusedAt));
237− const owner = candidates[0];
257+ const owner = candidates.find((candidate) => current.clients.has(candidate.clientId));
238258 if (!owner) {
259+ if (candidates.length > 0) {
260+ return yield* new PreviewAutomationUnavailableError({
261+ message: "The browser host is not connected.",
262+ });
263+ }
239264 return yield* new PreviewAutomationNoFocusedOwnerError({
240265 message: "No desktop browser host is available for this thread.",
241266 });
@@ -246,22 +271,12 @@ const make = Effect.gen(function* PreviewAutomationBrokerMake() { · 4 unmodified lines
246271 message: "The browser host is not connected.",
247272 });
248273 }
249− if (
250− input.operation !== "open" &&
251− input.operation !== "status" &&
252− !owner.tabId &&
253− !input.tabId
254− ) {
255− return yield* new PreviewAutomationTabNotFoundError({
256− message: "The browser host does not have an active tab.",
257− });
258− }
259274 const timeoutMs = input.timeoutMs ?? 15_000;
260275 const deferred = yield* Deferred.make<unknown, PreviewAutomationError>();
261276 const requestId = yield* SynchronizedRef.modify(state, (next) => {
262277 const requestId = `preview-${next.requestSequence}`;
263278 const pending = new Map(next.pending);
264− pending.set(requestId, { clientId: owner.clientId, deferred });
279+ pending.set(requestId, { queue: connection.queue, deferred });
265280 return [requestId, { ...next, pending, requestSequence: next.requestSequence + 1 }] as const;
266281 });
267282 const removePending = SynchronizedRef.update(state, (next) => {
apps/server/src/provider/Layers/ProviderRegistry.test.ts+116−76
@@ -32,8 +32,8 @@ import { applyServerSettingsPatch } from "@t3tools/shared/serverSettings"; · 31 unmodified lines
3232
3333 import { checkCodexProviderStatus, type CodexAppServerProviderSnapshot } from "./CodexProvider.ts";
3434 import { checkClaudeProviderStatus } from "./ClaudeProvider.ts";
35−import { OpenCodeRuntimeLive } from "../opencodeRuntime.ts";
36−import { NoOpProviderEventLoggers, ProviderEventLoggers } from "./ProviderEventLoggers.ts";
35+import * as OpenCodeRuntime from "../opencodeRuntime.ts";
36+import * as ProviderEventLoggers from "./ProviderEventLoggers.ts";
3737 import { ProviderInstanceRegistryHydrationLive } from "./ProviderInstanceRegistryHydration.ts";
3838 import {
3939 haveProvidersChanged,
@@ -42,12 +42,12 @@ import { · 2 unmodified lines
4242 ProviderRegistryLive,
4343 selectProvidersByKind,
4444 } from "./ProviderRegistry.ts";
45−import { ServerConfig } from "../../config.ts";
46−import { ServerSettingsService, type ServerSettingsShape } from "../../serverSettings.ts";
45+import * as ServerConfig from "../../config.ts";
46+import * as ServerSettingsModule from "../../serverSettings.ts";
4747 import { readProviderStatusCache, resolveProviderStatusCachePath } from "../providerStatusCache.ts";
4848 import type { ProviderInstance } from "../ProviderDriver.ts";
49−import { ProviderInstanceRegistry } from "../Services/ProviderInstanceRegistry.ts";
50−import { ProviderRegistry } from "../Services/ProviderRegistry.ts";
49+import * as ProviderInstanceRegistry from "../Services/ProviderInstanceRegistry.ts";
50+import * as ProviderRegistry from "../Services/ProviderRegistry.ts";
5151 import { makeManualOnlyProviderMaintenanceCapabilities } from "../providerMaintenance.ts";
5252 const decodeServerSettings = Schema.decodeSync(ServerSettings);
5353 const encodeServerSettings = Schema.encodeSync(ServerSettings);
@@ -294,11 +294,11 @@ function makeMutableServerSettingsService( · 240 unmodified lines
294294 get streamChanges() {
295295 return Stream.fromPubSub(changes);
296296 },
297− } satisfies ServerSettingsShape;
297+ } satisfies ServerSettingsModule.ServerSettingsService["Service"];
298298 });
299299 }
300300
301−it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), TestHttpClientLive))(
301+it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), TestHttpClientLive))(
302302 "ProviderRegistry",
303303 (it) => {
304304 describe("checkCodexProviderStatus", () => {
@@ -636,14 +636,17 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 331 unmodified lines
636636 adapter: {} as ProviderInstance["adapter"],
637637 textGeneration: {} as ProviderInstance["textGeneration"],
638638 } satisfies ProviderInstance;
639− const instanceRegistryLayer = Layer.succeed(ProviderInstanceRegistry, {
640− getInstance: (instanceId) =>
641− Effect.succeed(instanceId === codexInstanceId ? instance : undefined),
642− listInstances: Effect.succeed([instance]),
643− listUnavailable: Effect.succeed([]),
644− streamChanges: Stream.empty,
645− subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), PubSub.subscribe),
646− });
639+ const instanceRegistryLayer = Layer.succeed(
640+ ProviderInstanceRegistry.ProviderInstanceRegistry,
641+ {
642+ getInstance: (instanceId) =>
643+ Effect.succeed(instanceId === codexInstanceId ? instance : undefined),
644+ listInstances: Effect.succeed([instance]),
645+ listUnavailable: Effect.succeed([]),
646+ streamChanges: Stream.empty,
647+ subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), PubSub.subscribe),
648+ },
649+ );
647650 const scope = yield* Scope.make();
648651 yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
649652 const runtimeServices = yield* Layer.build(
@@ -658,7 +661,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 8 unmodified lines
658661 ),
659662 ).pipe(Scope.provide(scope));
660663 yield* Effect.gen(function* () {
661− const registry = yield* ProviderRegistry;
664+ const registry = yield* ProviderRegistry.ProviderRegistry;
662665 assert.deepStrictEqual(yield* registry.getProviders, [initialProvider]);
663666 assert.strictEqual(yield* Ref.get(refreshCalls), 0);
664667 }).pipe(Effect.provide(runtimeServices));
@@ -786,16 +789,19 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 121 unmodified lines
786789 adapter: {} as ProviderInstance["adapter"],
787790 textGeneration: {} as ProviderInstance["textGeneration"],
788791 } satisfies ProviderInstance;
789− const instanceRegistryLayer = Layer.succeed(ProviderInstanceRegistry, {
790− getInstance: (instanceId) =>
791− Effect.succeed(instanceId === cursorInstanceId ? instance : undefined),
792− listInstances: Effect.succeed([instance]),
793− listUnavailable: Effect.succeed([]),
794− streamChanges: Stream.empty,
795− subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), (pubsub) =>
796− PubSub.subscribe(pubsub),
797− ),
798− });
792+ const instanceRegistryLayer = Layer.succeed(
793+ ProviderInstanceRegistry.ProviderInstanceRegistry,
794+ {
795+ getInstance: (instanceId) =>
796+ Effect.succeed(instanceId === cursorInstanceId ? instance : undefined),
797+ listInstances: Effect.succeed([instance]),
798+ listUnavailable: Effect.succeed([]),
799+ streamChanges: Stream.empty,
800+ subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), (pubsub) =>
801+ PubSub.subscribe(pubsub),
802+ ),
803+ },
804+ );
799805 const scope = yield* Scope.make();
800806 yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
801807 const runtimeServices = yield* Layer.build(
@@ -811,8 +817,8 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 9 unmodified lines
811817 ).pipe(Scope.provide(scope));
812818
813819 yield* Effect.gen(function* () {
814− const registry = yield* ProviderRegistry;
815− const config = yield* ServerConfig;
820+ const registry = yield* ProviderRegistry.ProviderRegistry;
821+ const config = yield* ServerConfig.ServerConfig;
816822 const filePath = yield* resolveProviderStatusCachePath({
817823 cacheDir: config.providerStatusCacheDir,
818824 instanceId: cursorInstanceId,
@@ -880,16 +886,19 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 61 unmodified lines
880886 adapter: {} as ProviderInstance["adapter"],
881887 textGeneration: {} as ProviderInstance["textGeneration"],
882888 } satisfies ProviderInstance;
883− const instanceRegistryLayer = Layer.succeed(ProviderInstanceRegistry, {
884− getInstance: (instanceId) =>
885− Effect.succeed(instanceId === codexInstanceId ? instance : undefined),
886− listInstances: Effect.succeed([instance]),
887− listUnavailable: Effect.succeed([]),
888− streamChanges: Stream.empty,
889− subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), (pubsub) =>
890− PubSub.subscribe(pubsub),
891− ),
892− });
889+ const instanceRegistryLayer = Layer.succeed(
890+ ProviderInstanceRegistry.ProviderInstanceRegistry,
891+ {
892+ getInstance: (instanceId) =>
893+ Effect.succeed(instanceId === codexInstanceId ? instance : undefined),
894+ listInstances: Effect.succeed([instance]),
895+ listUnavailable: Effect.succeed([]),
896+ streamChanges: Stream.empty,
897+ subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), (pubsub) =>
898+ PubSub.subscribe(pubsub),
899+ ),
900+ },
901+ );
893902 const scope = yield* Scope.make();
894903 yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
895904 const runtimeServices = yield* Layer.build(
@@ -905,7 +914,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 9 unmodified lines
905914 ).pipe(Scope.provide(scope));
906915
907916 yield* Effect.gen(function* () {
908− const registry = yield* ProviderRegistry;
917+ const registry = yield* ProviderRegistry.ProviderRegistry;
909918
910919 assert.deepStrictEqual(yield* registry.getProviders, [cachedProvider]);
911920 assert.deepStrictEqual(yield* registry.refresh(codexDriver), [cachedProvider]);
@@ -975,25 +984,28 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 63 unmodified lines
975984 const instancesRef = yield* Ref.make<ReadonlyArray<ProviderInstance>>([codexInstance]);
976985 const failNextList = yield* Ref.make(false);
977986 const wait = () => Effect.yieldNow;
978− const instanceRegistryLayer = Layer.succeed(ProviderInstanceRegistry, {
979− getInstance: (instanceId) =>
980− Ref.get(instancesRef).pipe(
981− Effect.map((instances) =>
982− instances.find((instance) => instance.instanceId === instanceId),
987+ const instanceRegistryLayer = Layer.succeed(
988+ ProviderInstanceRegistry.ProviderInstanceRegistry,
989+ {
990+ getInstance: (instanceId) =>
991+ Ref.get(instancesRef).pipe(
992+ Effect.map((instances) =>
993+ instances.find((instance) => instance.instanceId === instanceId),
994+ ),
983995 ),
984− ),
985− listInstances: Effect.gen(function* () {
986− const shouldFail = yield* Ref.get(failNextList);
987− if (shouldFail) {
988− yield* Ref.set(failNextList, false);
989− return yield* Effect.die(new Error("simulated registry list failure"));
990− }
991− return yield* Ref.get(instancesRef);
992− }),
993− listUnavailable: Effect.succeed([]),
994− streamChanges: Stream.fromPubSub(changes),
995− subscribeChanges: PubSub.subscribe(changes),
996− });
996+ listInstances: Effect.gen(function* () {
997+ const shouldFail = yield* Ref.get(failNextList);
998+ if (shouldFail) {
999+ yield* Ref.set(failNextList, false);
1000+ return yield* Effect.die(new Error("simulated registry list failure"));
1001+ }
1002+ return yield* Ref.get(instancesRef);
1003+ }),
1004+ listUnavailable: Effect.succeed([]),
1005+ streamChanges: Stream.fromPubSub(changes),
1006+ subscribeChanges: PubSub.subscribe(changes),
1007+ },
1008+ );
9971009 const scope = yield* Scope.make();
9981010 yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
9991011 const runtimeServices = yield* Layer.build(
@@ -1009,7 +1021,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 9 unmodified lines
10091021 ).pipe(Scope.provide(scope));
10101022
10111023 yield* Effect.gen(function* () {
1012− const registry = yield* ProviderRegistry;
1024+ const registry = yield* ProviderRegistry.ProviderRegistry;
10131025 assert.deepStrictEqual(yield* registry.getProviders, [codexProvider]);
10141026
10151027 yield* Ref.set(failNextList, true);
@@ -1092,15 +1104,22 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 76 unmodified lines
10921104 yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
10931105 const providerRegistryLayer = ProviderRegistryLive.pipe(
10941106 Layer.provideMerge(ProviderInstanceRegistryHydrationLive),
1095− Layer.provideMerge(Layer.succeed(ServerSettingsService, serverSettings)),
1107+ Layer.provideMerge(
1108+ Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings),
1109+ ),
10961110 Layer.provideMerge(
10971111 ServerConfig.layerTest(process.cwd(), {
10981112 prefix: "t3-provider-registry-",
10991113 }),
11001114 ),
11011115 Layer.provideMerge(TestHttpClientLive),
1102− Layer.provideMerge(Layer.succeed(ProviderEventLoggers, NoOpProviderEventLoggers)),
1103− Layer.provideMerge(OpenCodeRuntimeLive),
1116+ Layer.provideMerge(
1117+ Layer.succeed(
1118+ ProviderEventLoggers.ProviderEventLoggers,
1119+ ProviderEventLoggers.NoOpProviderEventLoggers,
1120+ ),
1121+ ),
1122+ Layer.provideMerge(OpenCodeRuntime.OpenCodeRuntimeLive),
11041123 // NO spawner mock — `ChildProcessSpawner` is supplied by the
11051124 // outer `NodeServices.layer` on `it.layer(...)` and will
11061125 // genuinely spawn a subprocess. The missing-binary ENOENT is
@@ -1112,7 +1131,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 5 unmodified lines
11121131 );
11131132
11141133 yield* Effect.gen(function* () {
1115− const registry = yield* ProviderRegistry;
1134+ const registry = yield* ProviderRegistry.ProviderRegistry;
11161135 let providers = yield* registry.getProviders;
11171136 for (
11181137 let attempts = 0;
@@ -1177,15 +1196,22 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 58 unmodified lines
11771196 yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
11781197 const providerRegistryLayer = ProviderRegistryLive.pipe(
11791198 Layer.provideMerge(ProviderInstanceRegistryHydrationLive),
1180− Layer.provideMerge(Layer.succeed(ServerSettingsService, serverSettings)),
1199+ Layer.provideMerge(
1200+ Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings),
1201+ ),
11811202 Layer.provideMerge(
11821203 ServerConfig.layerTest(process.cwd(), {
11831204 prefix: "t3-provider-registry-",
11841205 }),
11851206 ),
11861207 Layer.provideMerge(TestHttpClientLive),
1187− Layer.provideMerge(Layer.succeed(ProviderEventLoggers, NoOpProviderEventLoggers)),
1188− Layer.provideMerge(OpenCodeRuntimeLive),
1208+ Layer.provideMerge(
1209+ Layer.succeed(
1210+ ProviderEventLoggers.ProviderEventLoggers,
1211+ ProviderEventLoggers.NoOpProviderEventLoggers,
1212+ ),
1213+ ),
1214+ Layer.provideMerge(OpenCodeRuntime.OpenCodeRuntimeLive),
11891215 Layer.updateService(ChildProcessSpawner.ChildProcessSpawner, (spawner) =>
11901216 ChildProcessSpawner.make((command) => {
11911217 spawnedCommands.push((command as { readonly command: string }).command);
@@ -1199,7 +1225,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 7 unmodified lines
11991225 );
12001226
12011227 yield* Effect.gen(function* () {
1202− const registry = yield* ProviderRegistry;
1228+ const registry = yield* ProviderRegistry.ProviderRegistry;
12031229 // Boot-time probe: the default codex instance is enabled with
12041230 // `firstMissing`, so the real spawner yields ENOENT and the
12051231 // snapshot should be `status: "error"`.
@@ -1291,15 +1317,22 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 85 unmodified lines
12911317 yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
12921318 const providerRegistryLayer = ProviderRegistryLive.pipe(
12931319 Layer.provideMerge(ProviderInstanceRegistryHydrationLive),
1294− Layer.provideMerge(Layer.succeed(ServerSettingsService, serverSettings)),
1320+ Layer.provideMerge(
1321+ Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings),
1322+ ),
12951323 Layer.provideMerge(
12961324 ServerConfig.layerTest(process.cwd(), {
12971325 prefix: "t3-provider-registry-",
12981326 }),
12991327 ),
13001328 Layer.provideMerge(TestHttpClientLive),
1301− Layer.provideMerge(Layer.succeed(ProviderEventLoggers, NoOpProviderEventLoggers)),
1302− Layer.provideMerge(OpenCodeRuntimeLive),
1329+ Layer.provideMerge(
1330+ Layer.succeed(
1331+ ProviderEventLoggers.ProviderEventLoggers,
1332+ ProviderEventLoggers.NoOpProviderEventLoggers,
1333+ ),
1334+ ),
1335+ Layer.provideMerge(OpenCodeRuntime.OpenCodeRuntimeLive),
13031336 Layer.provideMerge(NodeServices.layer),
13041337 );
13051338 const runtimeServices = yield* Layer.build(providerRegistryLayer).pipe(
@@ -1307,7 +1340,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 1 unmodified line
13071340 );
13081341
13091342 yield* Effect.gen(function* () {
1310− const registry = yield* ProviderRegistry;
1343+ const registry = yield* ProviderRegistry.ProviderRegistry;
13111344 const providers = yield* registry.getProviders;
13121345 const ghost = providers.find((provider) => provider.instanceId === "ghost_main");
13131346
@@ -1345,15 +1378,22 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 31 unmodified lines
13451378 yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
13461379 const providerRegistryLayer = ProviderRegistryLive.pipe(
13471380 Layer.provideMerge(ProviderInstanceRegistryHydrationLive),
1348− Layer.provideMerge(Layer.succeed(ServerSettingsService, serverSettings)),
1381+ Layer.provideMerge(
1382+ Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings),
1383+ ),
13491384 Layer.provideMerge(
13501385 ServerConfig.layerTest(process.cwd(), {
13511386 prefix: "t3-provider-registry-",
13521387 }),
13531388 ),
13541389 Layer.provideMerge(TestHttpClientLive),
1355− Layer.provideMerge(Layer.succeed(ProviderEventLoggers, NoOpProviderEventLoggers)),
1356− Layer.provideMerge(OpenCodeRuntimeLive),
1390+ Layer.provideMerge(
1391+ Layer.succeed(
1392+ ProviderEventLoggers.ProviderEventLoggers,
1393+ ProviderEventLoggers.NoOpProviderEventLoggers,
1394+ ),
1395+ ),
1396+ Layer.provideMerge(OpenCodeRuntime.OpenCodeRuntimeLive),
13571397 Layer.provideMerge(
13581398 mockCommandSpawnerLayer((command, args) => {
13591399 if (command === "agent") {
@@ -1380,13 +1420,13 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 20 unmodified lines
13801420 );
13811421 const runtimeServices = yield* Layer.build(
13821422 Layer.mergeAll(
1383− Layer.succeed(ServerSettingsService, serverSettings),
1423+ Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings),
13841424 providerRegistryLayer,
13851425 ),
13861426 ).pipe(Scope.provide(scope));
13871427
13881428 yield* Effect.gen(function* () {
1389− const registry = yield* ProviderRegistry;
1429+ const registry = yield* ProviderRegistry.ProviderRegistry;
13901430 const providers = yield* registry.getProviders;
13911431 const cursorProvider = providers.find(
13921432 (provider) => provider.instanceId === ProviderInstanceId.make("cursor"),
apps/server/src/provider/providerUpdateSettings.ts+2−2
@@ -3,7 +3,7 @@ import * as Effect from "effect/Effect"; · 2 unmodified lines
33 import * as Equal from "effect/Equal";
44 import * as Stream from "effect/Stream";
55
6−import type { ServerSettingsShape } from "../serverSettings.ts";
6+import type * as ServerSettingsModule from "../serverSettings.ts";
77
88 export interface ProviderSnapshotSettings<Settings> {
99 readonly provider: Settings;
@@ -29,7 +29,7 @@ export function haveProviderSnapshotSettingsChanged<Settings>( · 19 unmodified lines
2929
3030 export function makeProviderSnapshotSettingsSource<Settings>(
3131 provider: Settings,
32− serverSettings: ServerSettingsShape,
32+ serverSettings: ServerSettingsModule.ServerSettingsService["Service"],
3333 ): {
3434 readonly getSettings: Effect.Effect<ProviderSnapshotSettings<Settings>, ServerSettingsError>;
3535 readonly streamSettings: Stream.Stream<ProviderSnapshotSettings<Settings>>;
apps/server/src/server.test.ts+95−100
@@ -69,56 +69,30 @@ import { vi } from "vite-plus/test"; · 68 unmodified lines
6969
7070 const TEST_EPOCH = DateTime.makeUnsafe("1970-01-01T00:00:00.000Z");
7171
72−import type { ServerConfigShape } from "./config.ts";
73−import { deriveServerPaths, ServerConfig } from "./config.ts";
72+import * as ServerConfig from "./config.ts";
7473 import { makeRoutesLayer } from "./server.ts";
75−import {
76− CheckpointDiffQuery,
77− type CheckpointDiffQueryShape,
78−} from "./checkpointing/Services/CheckpointDiffQuery.ts";
79−import { GitManager, type GitManagerShape } from "./git/GitManager.ts";
80−import { Keybindings, type KeybindingsShape } from "./keybindings.ts";
74+import * as CheckpointDiffQuery from "./checkpointing/Services/CheckpointDiffQuery.ts";
75+import * as GitManager from "./git/GitManager.ts";
76+import * as Keybindings from "./keybindings.ts";
8177 import * as ExternalLauncher from "./process/externalLauncher.ts";
82−import {
83− OrchestrationEngineService,
84− type OrchestrationEngineShape,
85−} from "./orchestration/Services/OrchestrationEngine.ts";
78+import * as OrchestrationEngine from "./orchestration/Services/OrchestrationEngine.ts";
8679 import { OrchestrationListenerCallbackError } from "./orchestration/Errors.ts";
87−import {
88− ProjectionSnapshotQuery,
89− type ProjectionSnapshotQueryShape,
90−} from "./orchestration/Services/ProjectionSnapshotQuery.ts";
80+import * as ProjectionSnapshotQuery from "./orchestration/Services/ProjectionSnapshotQuery.ts";
9181 import { SqlitePersistenceMemory } from "./persistence/Layers/Sqlite.ts";
9282 import { PersistenceSqlError } from "./persistence/Errors.ts";
93−import {
94− ProviderRegistry,
95− type ProviderRegistryShape,
96−} from "./provider/Services/ProviderRegistry.ts";
83+import * as ProviderRegistry from "./provider/Services/ProviderRegistry.ts";
9784 import { makeManualOnlyProviderMaintenanceCapabilities } from "./provider/providerMaintenance.ts";
98−import { ServerLifecycleEvents, type ServerLifecycleEventsShape } from "./serverLifecycleEvents.ts";
99−import { ServerRuntimeStartup, type ServerRuntimeStartupShape } from "./serverRuntimeStartup.ts";
100−import { ServerSettingsService, type ServerSettingsShape } from "./serverSettings.ts";
101−import { TerminalManager, type TerminalManagerShape } from "./terminal/Services/Manager.ts";
85+import * as ServerLifecycleEvents from "./serverLifecycleEvents.ts";
86+import * as ServerRuntimeStartup from "./serverRuntimeStartup.ts";
87+import * as ServerSettings from "./serverSettings.ts";
88+import * as TerminalManager from "./terminal/Services/Manager.ts";
10289 import * as PreviewManager from "./preview/Manager.ts";
10390 import * as PortScanner from "./preview/PortScanner.ts";
104−import {
105− BrowserTraceCollector,
106− type BrowserTraceCollectorShape,
107−} from "./observability/Services/BrowserTraceCollector.ts";
91+import * as BrowserTraceCollector from "./observability/Services/BrowserTraceCollector.ts";
10892 import { ProjectFaviconResolverLive } from "./project/Layers/ProjectFaviconResolver.ts";
109−import {
110− ProjectSetupScriptRunner,
111− ProjectSetupScriptRunnerError,
112− type ProjectSetupScriptRunnerShape,
113−} from "./project/Services/ProjectSetupScriptRunner.ts";
114−import {
115− RepositoryIdentityResolver,
116− type RepositoryIdentityResolverShape,
117−} from "./project/Services/RepositoryIdentityResolver.ts";
118−import {
119− ServerEnvironment,
120− type ServerEnvironmentShape,
121−} from "./environment/Services/ServerEnvironment.ts";
93+import * as ProjectSetupScriptRunner from "./project/Services/ProjectSetupScriptRunner.ts";
94+import * as RepositoryIdentityResolver from "./project/Services/RepositoryIdentityResolver.ts";
95+import * as ServerEnvironment from "./environment/Services/ServerEnvironment.ts";
12296 import * as WorkspaceEntries from "./workspace/WorkspaceEntries.ts";
12397 import { WorkspaceFileSystemLive } from "./workspace/Layers/WorkspaceFileSystem.ts";
12498 import { WorkspacePathsLive } from "./workspace/Layers/WorkspacePaths.ts";
@@ -132,10 +106,7 @@ import * as ReviewService from "./review/ReviewService.ts"; · 7 unmodified lines
132106 import * as SourceControlRepositoryService from "./sourceControl/SourceControlRepositoryService.ts";
133107 import * as ServerSecretStore from "./auth/ServerSecretStore.ts";
134108 import * as EnvironmentAuth from "./auth/EnvironmentAuth.ts";
135−import {
136− CloudManagedEndpointRuntime,
137− type CloudManagedEndpointRuntimeShape,
138−} from "./cloud/ManagedEndpointRuntime.ts";
109+import * as CloudManagedEndpointRuntime from "./cloud/ManagedEndpointRuntime.ts";
139110 import * as CloudCliTokenManager from "./cloud/CliTokenManager.ts";
140111 import * as ProcessDiagnostics from "./diagnostics/ProcessDiagnostics.ts";
141112 import * as ProcessResourceMonitor from "./diagnostics/ProcessResourceMonitor.ts";
@@ -341,32 +312,40 @@ const makeBrowserOtlpPayload = (spanName: string) => · 199 unmodified lines
341312 });
342313
343314 const buildAppUnderTest = (options?: {
344− config?: Partial<ServerConfigShape>;
315+ config?: Partial<ServerConfig.ServerConfig["Service"]>;
345316 layers?: {
346− keybindings?: Partial<KeybindingsShape>;
347− providerRegistry?: Partial<ProviderRegistryShape>;
348− serverSettings?: Partial<ServerSettingsShape>;
349− externalLauncher?: Partial<ExternalLauncher.ExternalLauncherShape>;
350− vcsDriver?: Partial<VcsDriver.VcsDriverShape>;
351− vcsDriverRegistry?: Partial<VcsDriverRegistry.VcsDriverRegistryShape>;
352− gitVcsDriver?: Partial<GitVcsDriver.GitVcsDriverShape>;
353− gitManager?: Partial<GitManagerShape>;
354− sourceControlRepositoryService?: Partial<SourceControlRepositoryService.SourceControlRepositoryServiceShape>;
355− reviewService?: Partial<ReviewService.ReviewServiceShape>;
356− vcsStatusBroadcaster?: Partial<VcsStatusBroadcaster.VcsStatusBroadcasterShape>;
357− projectSetupScriptRunner?: Partial<ProjectSetupScriptRunnerShape>;
358− terminalManager?: Partial<TerminalManagerShape>;
359− orchestrationEngine?: Partial<OrchestrationEngineShape>;
360− projectionSnapshotQuery?: Partial<ProjectionSnapshotQueryShape>;
361− checkpointDiffQuery?: Partial<CheckpointDiffQueryShape>;
362− browserTraceCollector?: Partial<BrowserTraceCollectorShape>;
363− serverLifecycleEvents?: Partial<ServerLifecycleEventsShape>;
364− serverRuntimeStartup?: Partial<ServerRuntimeStartupShape>;
365− serverEnvironment?: Partial<ServerEnvironmentShape>;
366− repositoryIdentityResolver?: Partial<RepositoryIdentityResolverShape>;
367− cloudManagedEndpointRuntime?: Partial<CloudManagedEndpointRuntimeShape>;
368− relayClient?: Partial<RelayClient.RelayClientShape>;
369− cloudCliTokenManager?: Partial<CloudCliTokenManager.CloudCliTokenManagerShape>;
317+ keybindings?: Partial<Keybindings.Keybindings["Service"]>;
318+ providerRegistry?: Partial<ProviderRegistry.ProviderRegistry["Service"]>;
319+ serverSettings?: Partial<ServerSettings.ServerSettingsService["Service"]>;
320+ externalLauncher?: Partial<ExternalLauncher.ExternalLauncher["Service"]>;
321+ vcsDriver?: Partial<VcsDriver.VcsDriver["Service"]>;
322+ vcsDriverRegistry?: Partial<VcsDriverRegistry.VcsDriverRegistry["Service"]>;
323+ gitVcsDriver?: Partial<GitVcsDriver.GitVcsDriver["Service"]>;
324+ gitManager?: Partial<GitManager.GitManager["Service"]>;
325+ sourceControlRepositoryService?: Partial<
326+ SourceControlRepositoryService.SourceControlRepositoryService["Service"]
327+ >;
328+ reviewService?: Partial<ReviewService.ReviewService["Service"]>;
329+ vcsStatusBroadcaster?: Partial<VcsStatusBroadcaster.VcsStatusBroadcaster["Service"]>;
330+ projectSetupScriptRunner?: Partial<
331+ ProjectSetupScriptRunner.ProjectSetupScriptRunner["Service"]
332+ >;
333+ terminalManager?: Partial<TerminalManager.TerminalManager["Service"]>;
334+ orchestrationEngine?: Partial<OrchestrationEngine.OrchestrationEngineService["Service"]>;
335+ projectionSnapshotQuery?: Partial<ProjectionSnapshotQuery.ProjectionSnapshotQuery["Service"]>;
336+ checkpointDiffQuery?: Partial<CheckpointDiffQuery.CheckpointDiffQuery["Service"]>;
337+ browserTraceCollector?: Partial<BrowserTraceCollector.BrowserTraceCollector["Service"]>;
338+ serverLifecycleEvents?: Partial<ServerLifecycleEvents.ServerLifecycleEvents["Service"]>;
339+ serverRuntimeStartup?: Partial<ServerRuntimeStartup.ServerRuntimeStartup["Service"]>;
340+ serverEnvironment?: Partial<ServerEnvironment.ServerEnvironment["Service"]>;
341+ repositoryIdentityResolver?: Partial<
342+ RepositoryIdentityResolver.RepositoryIdentityResolver["Service"]
343+ >;
344+ cloudManagedEndpointRuntime?: Partial<
345+ CloudManagedEndpointRuntime.CloudManagedEndpointRuntime["Service"]
346+ >;
347+ relayClient?: Partial<RelayClient.RelayClient["Service"]>;
348+ cloudCliTokenManager?: Partial<CloudCliTokenManager.CloudCliTokenManager["Service"]>;
370349 };
371350 }) =>
372351 Effect.gen(function* () {
@@ -374,8 +353,8 @@ const buildAppUnderTest = (options?: { · 1 unmodified line
374353 const tempBaseDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-router-test-" });
375354 const baseDir = options?.config?.baseDir ?? tempBaseDir;
376355 const devUrl = options?.config?.devUrl;
377− const derivedPaths = yield* deriveServerPaths(baseDir, devUrl);
378− const config: ServerConfigShape = {
356+ const derivedPaths = yield* ServerConfig.deriveServerPaths(baseDir, devUrl);
357+ const config: ServerConfig.ServerConfig["Service"] = {
379358 logLevel: "Info",
380359 traceMinLevel: "Info",
381360 traceTimingEnabled: true,
@@ -403,8 +382,8 @@ const buildAppUnderTest = (options?: { · 21 unmodified lines
403382 tailscaleServePort: 443,
404383 ...options?.config,
405384 };
406− const layerConfig = Layer.succeed(ServerConfig, config);
407− const defaultVcsDriver: VcsDriver.VcsDriverShape = {
385+ const layerConfig = ServerConfig.layer(config);
386+ const defaultVcsDriver: VcsDriver.VcsDriver["Service"] = {
408387 capabilities: {
409388 kind: "git",
410389 supportsWorktrees: true,
@@ -502,7 +481,7 @@ const buildAppUnderTest = (options?: { · 91 unmodified lines
502481 const gitVcsDriverLayer = Layer.mock(GitVcsDriver.GitVcsDriver)({
503482 ...options?.layers?.gitVcsDriver,
504483 });
505− const gitManagerLayer = Layer.mock(GitManager)({
484+ const gitManagerLayer = Layer.mock(GitManager.GitManager)({
506485 ...options?.layers?.gitManager,
507486 });
508487 const workspaceEntriesLayer = WorkspaceEntries.layer.pipe(
@@ -545,7 +524,7 @@ const buildAppUnderTest = (options?: { · 36 unmodified lines
545524 disableLogger: true,
546525 }).pipe(
547526 Layer.provide(
548− Layer.mock(Keybindings)({
527+ Layer.mock(Keybindings.Keybindings)({
549528 loadConfigState: Effect.succeed({
550529 keybindings: [],
551530 issues: [],
@@ -555,7 +534,7 @@ const buildAppUnderTest = (options?: { · 3 unmodified lines
555534 }),
556535 ),
557536 Layer.provide(
558− Layer.mock(ProviderRegistry)({
537+ Layer.mock(ProviderRegistry.ProviderRegistry)({
559538 getProviders: Effect.succeed([]),
560539 refresh: () => Effect.succeed([]),
561540 refreshInstance: () => Effect.succeed([]),
@@ -569,7 +548,7 @@ const buildAppUnderTest = (options?: { · 7 unmodified lines
569548 }),
570549 ),
571550 Layer.provide(
572− Layer.mock(ServerSettingsService)({
551+ Layer.mock(ServerSettings.ServerSettingsService)({
573552 start: Effect.void,
574553 ready: Effect.void,
575554 getSettings: Effect.succeed(DEFAULT_SERVER_SETTINGS),
@@ -658,13 +637,13 @@ const buildAppUnderTest = (options?: { · 82 unmodified lines
658637 ),
659638 Layer.provideMerge(vcsStatusBroadcasterLayer),
660639 Layer.provide(
661− Layer.mock(ProjectSetupScriptRunner)({
640+ Layer.mock(ProjectSetupScriptRunner.ProjectSetupScriptRunner)({
662641 runForThread: () => Effect.succeed({ status: "no-script" as const }),
663642 ...options?.layers?.projectSetupScriptRunner,
664643 }),
665644 ),
666645 Layer.provide(
667− Layer.mock(TerminalManager)({
646+ Layer.mock(TerminalManager.TerminalManager)({
668647 ...options?.layers?.terminalManager,
669648 }),
670649 ),
@@ -692,7 +671,7 @@ const buildAppUnderTest = (options?: { · 21 unmodified lines
692671 ),
693672 ),
694673 Layer.provide(
695− Layer.mock(OrchestrationEngineService)({
674+ Layer.mock(OrchestrationEngine.OrchestrationEngineService)({
696675 readEvents: () => Stream.empty,
697676 dispatch: () => Effect.succeed({ sequence: 0 }),
698677 streamDomainEvents: Stream.empty,
@@ -700,7 +679,7 @@ const buildAppUnderTest = (options?: { · 1 unmodified line
700679 }),
701680 ),
702681 Layer.provide(
703− Layer.mock(ProjectionSnapshotQuery)({
682+ Layer.mock(ProjectionSnapshotQuery.ProjectionSnapshotQuery)({
704683 getCommandReadModel: () => Effect.succeed(makeDefaultOrchestrationReadModel()),
705684 getSnapshot: () => Effect.succeed(makeDefaultOrchestrationReadModel()),
706685 getShellSnapshot: () =>
@@ -729,7 +708,7 @@ const buildAppUnderTest = (options?: { · 22 unmodified lines
729708 }),
730709 ),
731710 Layer.provide(
732− Layer.mock(CheckpointDiffQuery)({
711+ Layer.mock(CheckpointDiffQuery.CheckpointDiffQuery)({
733712 getTurnDiff: () =>
734713 Effect.succeed({
735714 threadId: defaultThreadId,
@@ -751,13 +730,13 @@ const buildAppUnderTest = (options?: { · 15 unmodified lines
751730
752731 const appLayer = servedRoutesLayer.pipe(
753732 Layer.provide(
754− Layer.mock(BrowserTraceCollector)({
733+ Layer.mock(BrowserTraceCollector.BrowserTraceCollector)({
755734 record: () => Effect.void,
756735 ...options?.layers?.browserTraceCollector,
757736 }),
758737 ),
759738 Layer.provide(
760− Layer.mock(ServerLifecycleEvents)({
739+ Layer.mock(ServerLifecycleEvents.ServerLifecycleEvents)({
761740 publish: (event) => Effect.succeed({ ...(event as any), sequence: 1 }),
762741 snapshot: Effect.succeed({ sequence: 0, events: [] }),
763742 stream: Stream.empty,
@@ -765,7 +744,7 @@ const buildAppUnderTest = (options?: { · 1 unmodified line
765744 }),
766745 ),
767746 Layer.provide(
768− Layer.mock(ServerRuntimeStartup)({
747+ Layer.mock(ServerRuntimeStartup.ServerRuntimeStartup)({
769748 awaitCommandReady: Effect.void,
770749 markHttpListening: Effect.void,
771750 enqueueCommand: (effect) => effect,
@@ -773,22 +752,22 @@ const buildAppUnderTest = (options?: { · 1 unmodified line
773752 }),
774753 ),
775754 Layer.provide(
776− Layer.mock(ServerEnvironment)({
755+ Layer.mock(ServerEnvironment.ServerEnvironment)({
777756 getEnvironmentId: Effect.succeed(testEnvironmentDescriptor.environmentId),
778757 getDescriptor: Effect.succeed(testEnvironmentDescriptor),
779758 ...options?.layers?.serverEnvironment,
780759 }),
781760 ),
782761 Layer.provide(
783− Layer.mock(RepositoryIdentityResolver)({
762+ Layer.mock(RepositoryIdentityResolver.RepositoryIdentityResolver)({
784763 resolve: () => Effect.succeed(null),
785764 ...options?.layers?.repositoryIdentityResolver,
786765 }),
787766 ),
788767 Layer.provide(
789768 Layer.succeed(
790− CloudManagedEndpointRuntime,
791− CloudManagedEndpointRuntime.of({
769+ CloudManagedEndpointRuntime.CloudManagedEndpointRuntime,
770+ CloudManagedEndpointRuntime.CloudManagedEndpointRuntime.of({
792771 applyConfig: () => Effect.succeed({ status: "disabled" }),
793772 ...options?.layers?.cloudManagedEndpointRuntime,
794773 }),
@@ -5939,14 +5918,14 @@ it.layer(NodeServices.layer)("server router seam", (it) => { · 5144 unmodified lines
59395918 }),
59405919 );
59415920 const fetchRemote = vi.fn(
5942− (_: Parameters<GitVcsDriver.GitVcsDriverShape["fetchRemote"]>[0]) =>
5921+ (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["fetchRemote"]>[0]) =>
59435922 Effect.sync(() => {
59445923 bootstrapGitOperations.push("fetch");
59455924 }),
59465925 );
59475926 const fetchedOriginCommit = "0123456789abcdef0123456789abcdef01234567";
59485927 const resolveRemoteTrackingCommit = vi.fn(
5949− (_: Parameters<GitVcsDriver.GitVcsDriverShape["resolveRemoteTrackingCommit"]>[0]) =>
5928+ (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["resolveRemoteTrackingCommit"]>[0]) =>
59505929 Effect.sync(() => {
59515930 bootstrapGitOperations.push("resolve-remote-commit");
59525931 return {
@@ -5956,7 +5935,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { · 3 unmodified lines
59565935 }),
59575936 );
59585937 const createWorktree = vi.fn(
5959− (_: Parameters<GitVcsDriver.GitVcsDriverShape["createWorktree"]>[0]) =>
5938+ (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["createWorktree"]>[0]) =>
59605939 Effect.sync(() => {
59615940 bootstrapGitOperations.push("create-worktree");
59625941 return {
@@ -5968,7 +5947,11 @@ it.layer(NodeServices.layer)("server router seam", (it) => { · 5 unmodified lines
59685947 }),
59695948 );
59705949 const runForThread = vi.fn(
5971− (_: Parameters<ProjectSetupScriptRunnerShape["runForThread"]>[0]) =>
5950+ (
5951+ _: Parameters<
5952+ ProjectSetupScriptRunner.ProjectSetupScriptRunner["Service"]["runForThread"]
5953+ >[0],
5954+ ) =>
59725955 Effect.succeed({
59735956 status: "started" as const,
59745957 scriptId: "setup",
@@ -6102,7 +6085,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { · 127 unmodified lines
61026085 Effect.gen(function* () {
61036086 const dispatchedCommands: Array<OrchestrationCommand> = [];
61046087 const createWorktree = vi.fn(
6105− (_: Parameters<GitVcsDriver.GitVcsDriverShape["createWorktree"]>[0]) =>
6088+ (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["createWorktree"]>[0]) =>
61066089 Effect.succeed({
61076090 worktree: {
61086091 refName: "t3code/bootstrap-refName",
@@ -6111,8 +6094,16 @@ it.layer(NodeServices.layer)("server router seam", (it) => { · 2 unmodified lines
61116094 }),
61126095 );
61136096 const runForThread = vi.fn(
6114− (_: Parameters<ProjectSetupScriptRunnerShape["runForThread"]>[0]) =>
6115− Effect.fail(new ProjectSetupScriptRunnerError({ message: "pty unavailable" })),
6097+ (
6098+ _: Parameters<
6099+ ProjectSetupScriptRunner.ProjectSetupScriptRunner["Service"]["runForThread"]
6100+ >[0],
6101+ ) =>
6102+ Effect.fail(
6103+ new ProjectSetupScriptRunner.ProjectSetupScriptRunnerError({
6104+ message: "pty unavailable",
6105+ }),
6106+ ),
61166107 );
61176108
61186109 yield* buildAppUnderTest({
@@ -6196,7 +6187,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { · 77 unmodified lines
61966187 Effect.gen(function* () {
61976188 const dispatchedCommands: Array<OrchestrationCommand> = [];
61986189 const createWorktree = vi.fn(
6199− (_: Parameters<GitVcsDriver.GitVcsDriverShape["createWorktree"]>[0]) =>
6190+ (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["createWorktree"]>[0]) =>
62006191 Effect.succeed({
62016192 worktree: {
62026193 refName: "t3code/bootstrap-refName",
@@ -6205,7 +6196,11 @@ it.layer(NodeServices.layer)("server router seam", (it) => { · 2 unmodified lines
62056196 }),
62066197 );
62076198 const runForThread = vi.fn(
6208− (_: Parameters<ProjectSetupScriptRunnerShape["runForThread"]>[0]) =>
6199+ (
6200+ _: Parameters<
6201+ ProjectSetupScriptRunner.ProjectSetupScriptRunner["Service"]["runForThread"]
6202+ >[0],
6203+ ) =>
62096204 Effect.succeed({
62106205 status: "started" as const,
62116206 scriptId: "setup",
@@ -6315,7 +6310,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { · 103 unmodified lines
63156310 Effect.gen(function* () {
63166311 const dispatchedCommands: Array<OrchestrationCommand> = [];
63176312 const createWorktree = vi.fn(
6318− (_: Parameters<GitVcsDriver.GitVcsDriverShape["createWorktree"]>[0]) =>
6313+ (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["createWorktree"]>[0]) =>
63196314 Effect.die(new Error("worktree exploded")),
63206315 );
63216316
apps/server/src/server.ts+17−17
@@ -4,7 +4,7 @@ import * as Layer from "effect/Layer"; · 3 unmodified lines
44 import { FetchHttpClient, HttpRouter, HttpServer } from "effect/unstable/http";
55 import * as HttpApiBuilder from "effect/unstable/httpapi/HttpApiBuilder";
66
7−import { ServerConfig } from "./config.ts";
7+import * as ServerConfig from "./config.ts";
88 import {
99 otlpTracesProxyRouteLayer,
1010 assetRouteLayer,
@@ -16,15 +16,15 @@ import { fixPath } from "./os-jank.ts"; · 5 unmodified lines
1616 import { websocketRpcRouteLayer } from "./ws.ts";
1717 import * as ExternalLauncher from "./process/externalLauncher.ts";
1818 import { layerConfig as SqlitePersistenceLayerLive } from "./persistence/Layers/Sqlite.ts";
19−import { ServerLifecycleEventsLive } from "./serverLifecycleEvents.ts";
19+import * as ServerLifecycleEvents from "./serverLifecycleEvents.ts";
2020 import { AnalyticsServiceLayerLive } from "./telemetry/Layers/AnalyticsService.ts";
2121 import { ProviderSessionDirectoryLive } from "./provider/Layers/ProviderSessionDirectory.ts";
2222 import { ProviderSessionRuntimeRepositoryLive } from "./persistence/Layers/ProviderSessionRuntime.ts";
2323 import { ProviderAdapterRegistryLive } from "./provider/Layers/ProviderAdapterRegistry.ts";
24−import { ProviderEventLoggersLive } from "./provider/Layers/ProviderEventLoggers.ts";
24+import * as ProviderEventLoggers from "./provider/Layers/ProviderEventLoggers.ts";
2525 import { ProviderServiceLive } from "./provider/Layers/ProviderService.ts";
2626 import { ProviderSessionReaperLive } from "./provider/Layers/ProviderSessionReaper.ts";
27−import { OpenCodeRuntimeLive } from "./provider/opencodeRuntime.ts";
27+import * as OpenCodeRuntime from "./provider/opencodeRuntime.ts";
2828 import { CheckpointDiffQueryLive } from "./checkpointing/Layers/CheckpointDiffQuery.ts";
2929 import { CheckpointStoreLive } from "./checkpointing/Layers/CheckpointStore.ts";
3030 import * as AzureDevOpsCli from "./sourceControl/AzureDevOpsCli.ts";
@@ -40,8 +40,8 @@ import * as PreviewManager from "./preview/Manager.ts"; · 9 unmodified lines
4040 import * as PortScanner from "./preview/PortScanner.ts";
4141 import * as ProcessRunner from "./processRunner.ts";
4242 import * as GitManager from "./git/GitManager.ts";
43−import { KeybindingsLive } from "./keybindings.ts";
44−import { ServerRuntimeStartup, ServerRuntimeStartupLive } from "./serverRuntimeStartup.ts";
43+import * as Keybindings from "./keybindings.ts";
44+import * as ServerRuntimeStartup from "./serverRuntimeStartup.ts";
4545 import { OrchestrationReactorLive } from "./orchestration/Layers/OrchestrationReactor.ts";
4646 import { RuntimeReceiptBusLive } from "./orchestration/Layers/RuntimeReceiptBus.ts";
4747 import { ProviderRuntimeIngestionLive } from "./orchestration/Layers/ProviderRuntimeIngestion.ts";
@@ -51,7 +51,7 @@ import { ThreadDeletionReactorLive } from "./orchestration/Layers/ThreadDeletion · 3 unmodified lines
5151 import * as AgentAwarenessRelay from "./relay/AgentAwarenessRelay.ts";
5252 import { hasCloudPublicConfig } from "./cloud/publicConfig.ts";
5353 import { ProviderRegistryLive } from "./provider/Layers/ProviderRegistry.ts";
54−import { ServerSettingsLive } from "./serverSettings.ts";
54+import * as ServerSettings from "./serverSettings.ts";
5555 import { ProjectFaviconResolverLive } from "./project/Layers/ProjectFaviconResolver.ts";
5656 import { RepositoryIdentityResolverLive } from "./project/Layers/RepositoryIdentityResolver.ts";
5757 import * as WorkspaceEntries from "./workspace/WorkspaceEntries.ts";
@@ -112,14 +112,14 @@ const PtyAdapterLive = Layer.unwrap( · 54 unmodified lines
112112
113113 const RelayClientLive = Layer.unwrap(
114114 Effect.gen(function* () {
115− const config = yield* ServerConfig;
115+ const config = yield* ServerConfig.ServerConfig;
116116 return RelayClient.layerCloudflared({ baseDir: config.baseDir });
117117 }),
118118 );
119119
120120 const HttpServerLive = Layer.unwrap(
121121 Effect.gen(function* () {
122− const config = yield* ServerConfig;
122+ const config = yield* ServerConfig.ServerConfig;
123123 if (typeof Bun !== "undefined") {
124124 const BunHttpServer = yield* Effect.promise(
125125 () => import("@effect/platform-bun/BunHttpServer"),
@@ -292,7 +292,7 @@ const RuntimeCoreDependenciesLive = ReactorLayerLive.pipe( · 166 unmodified lines
292292 Layer.provideMerge(ProviderRuntimeLayerLive),
293293 Layer.provideMerge(Layer.mergeAll(TerminalLayerLive, PreviewLayerLive)),
294294 Layer.provideMerge(PersistenceLayerLive),
295− Layer.provideMerge(KeybindingsLive),
295+ Layer.provideMerge(Keybindings.layer),
296296 Layer.provideMerge(ProviderRegistryLive),
297297 // The instance registry is the new routing keystone — text generation,
298298 // adapter lookup, and runtime ingestion all resolve `ProviderInstanceId`
@@ -305,14 +305,14 @@ const RuntimeCoreDependenciesLive = ReactorLayerLive.pipe( · 6 unmodified lines
305305 // `ProviderService` (canonical stream, written after event normalization).
306306 // Provided once at the runtime level so every consumer sees the same
307307 // logger instances.
308− Layer.provideMerge(ProviderEventLoggersLive),
308+ Layer.provideMerge(ProviderEventLoggers.ProviderEventLoggersLive),
309309 // `OpenCodeDriver.create()` yields `OpenCodeRuntime`; previously the old
310310 // `ProviderRegistryLive` pulled `OpenCodeRuntimeLive` in for itself, but
311311 // the rewritten registry reads snapshots off the instance registry and
312312 // no longer transitively provides it. Exposing it at the runtime level
313313 // keeps a single Live for all opencode consumers.
314− Layer.provideMerge(OpenCodeRuntimeLive),
315− Layer.provideMerge(ServerSettingsLive),
314+ Layer.provideMerge(OpenCodeRuntime.OpenCodeRuntimeLive),
315+ Layer.provideMerge(ServerSettings.layer.pipe(Layer.provide(ServerSecretStore.layer))),
316316 Layer.provideMerge(WorkspaceLayerLive),
317317 Layer.provideMerge(ProjectFaviconResolverLayerLive),
318318 Layer.provideMerge(RepositoryIdentityResolverLive),
@@ -334,11 +334,11 @@ const RuntimeDependenciesLive = RuntimeCoreDependenciesLive.pipe( · 15 unmodified lines
334334 Layer.provideMerge(TraceDiagnostics.layer),
335335 Layer.provideMerge(AnalyticsServiceLayerLive),
336336 Layer.provideMerge(ExternalLauncher.layer),
337− Layer.provideMerge(ServerLifecycleEventsLive),
337+ Layer.provideMerge(ServerLifecycleEvents.layer),
338338 Layer.provide(NetService.layer),
339339 );
340340
341−const RuntimeServicesLive = ServerRuntimeStartupLive.pipe(
341+const RuntimeServicesLive = ServerRuntimeStartup.layer.pipe(
342342 Layer.provideMerge(RuntimeDependenciesLive),
343343 );
344344
@@ -361,14 +361,14 @@ export const makeRoutesLayer = Layer.mergeAll( · 16 unmodified lines
361361
362362 export const makeServerLayer = Layer.unwrap(
363363 Effect.gen(function* () {
364− const config = yield* ServerConfig;
364+ const config = yield* ServerConfig.ServerConfig;
365365
366366 yield* fixPath();
367367
368368 const httpListeningLayer = Layer.effectDiscard(
369369 Effect.gen(function* () {
370370 yield* HttpServer.HttpServer;
371− const startup = yield* ServerRuntimeStartup;
371+ const startup = yield* ServerRuntimeStartup.ServerRuntimeStartup;
372372 yield* startup.markHttpListening;
373373 }),
374374 );
apps/server/src/serverLifecycleEvents.test.ts+3−3
@@ -4,13 +4,13 @@ import { assertTrue } from "@effect/vitest/utils"; · 3 unmodified lines
44 import * as Effect from "effect/Effect";
55 import * as Option from "effect/Option";
66
7−import { ServerLifecycleEvents, ServerLifecycleEventsLive } from "./serverLifecycleEvents.ts";
7+import * as ServerLifecycleEvents from "./serverLifecycleEvents.ts";
88
99 it.effect(
1010 "publishes lifecycle events without subscribers and snapshots the latest welcome/ready",
1111 () =>
1212 Effect.gen(function* () {
13− const lifecycleEvents = yield* ServerLifecycleEvents;
13+ const lifecycleEvents = yield* ServerLifecycleEvents.ServerLifecycleEvents;
1414 const environment = {
1515 environmentId: EnvironmentId.make("environment-test"),
1616 label: "Test environment",
@@ -49,5 +49,5 @@ it.effect( · 32 unmodified lines
4949 const snapshot = yield* lifecycleEvents.snapshot;
5050 assert.equal(snapshot.sequence, 2);
5151 assert.deepEqual(snapshot.events.map((event) => event.type).toSorted(), ["ready", "welcome"]);
52− }).pipe(Effect.provide(ServerLifecycleEventsLive)),
52+ }).pipe(Effect.provide(ServerLifecycleEvents.layer)),
5353 );
apps/server/src/serverLifecycleEvents.ts+34−37
@@ -1,9 +1,9 @@
11 import type { ServerLifecycleStreamEvent } from "@t3tools/contracts";
2+import * as Context from "effect/Context";
23 import * as Effect from "effect/Effect";
34 import * as Layer from "effect/Layer";
45 import * as PubSub from "effect/PubSub";
56 import * as Ref from "effect/Ref";
6−import * as Context from "effect/Context";
77 import * as Stream from "effect/Stream";
88
99 type LifecycleEventInput =
@@ -15,44 +15,41 @@ interface SnapshotState { · 5 unmodified lines
1515 readonly events: ReadonlyArray<ServerLifecycleStreamEvent>;
1616 }
1717
18−export interface ServerLifecycleEventsShape {
19− readonly publish: (event: LifecycleEventInput) => Effect.Effect<ServerLifecycleStreamEvent>;
20− readonly snapshot: Effect.Effect<SnapshotState>;
21− readonly stream: Stream.Stream<ServerLifecycleStreamEvent>;
22−}
23−
2418 export class ServerLifecycleEvents extends Context.Service<
2519 ServerLifecycleEvents,
26− ServerLifecycleEventsShape
20+ {
21+ readonly publish: (event: LifecycleEventInput) => Effect.Effect<ServerLifecycleStreamEvent>;
22+ readonly snapshot: Effect.Effect<SnapshotState>;
23+ readonly stream: Stream.Stream<ServerLifecycleStreamEvent>;
24+ }
2725 >()("t3/serverLifecycleEvents") {}
2826
29−export const ServerLifecycleEventsLive = Layer.effect(
30− ServerLifecycleEvents,
31− Effect.gen(function* () {
32− const pubsub = yield* PubSub.unbounded<ServerLifecycleStreamEvent>();
33− const state = yield* Ref.make<SnapshotState>({
34− sequence: 0,
35− events: [],
36− });
27+const make = Effect.gen(function* () {
28+ const pubsub = yield* PubSub.unbounded<ServerLifecycleStreamEvent>();
29+ const state = yield* Ref.make<SnapshotState>({
30+ sequence: 0,
31+ events: [],
32+ });
33+
34+ return {
35+ publish: (event) =>
36+ Ref.modify(state, (current) => {
37+ const nextSequence = current.sequence + 1;
38+ const nextEvent = {
39+ ...event,
40+ sequence: nextSequence,
41+ } satisfies ServerLifecycleStreamEvent;
42+ const nextEvents =
43+ nextEvent.type === "welcome"
44+ ? [nextEvent, ...current.events.filter((entry) => entry.type !== "welcome")]
45+ : [nextEvent, ...current.events.filter((entry) => entry.type !== "ready")];
46+ return [nextEvent, { sequence: nextSequence, events: nextEvents }] as const;
47+ }).pipe(Effect.tap((event) => PubSub.publish(pubsub, event))),
48+ snapshot: Ref.get(state),
49+ get stream() {
50+ return Stream.fromPubSub(pubsub);
51+ },
52+ } satisfies ServerLifecycleEvents["Service"];
53+});
3754
38− return {
39− publish: (event) =>
40− Ref.modify(state, (current) => {
41− const nextSequence = current.sequence + 1;
42− const nextEvent = {
43− ...event,
44− sequence: nextSequence,
45− } satisfies ServerLifecycleStreamEvent;
46− const nextEvents =
47− nextEvent.type === "welcome"
48− ? [nextEvent, ...current.events.filter((entry) => entry.type !== "welcome")]
49− : [nextEvent, ...current.events.filter((entry) => entry.type !== "ready")];
50− return [nextEvent, { sequence: nextSequence, events: nextEvents }] as const;
51− }).pipe(Effect.tap((event) => PubSub.publish(pubsub, event))),
52− snapshot: Ref.get(state),
53− get stream() {
54− return Stream.fromPubSub(pubsub);
55− },
56− } satisfies ServerLifecycleEventsShape;
57− }),
58−);
55+export const layer = Layer.effect(ServerLifecycleEvents, make);
apps/server/src/serverRuntimeStartup.test.ts+32−42
@@ -10,24 +10,14 @@ import * as PlatformError from "effect/PlatformError"; · 9 unmodified lines
1010 import * as Ref from "effect/Ref";
1111 import * as Stream from "effect/Stream";
1212
13−import { ServerConfig } from "./config.ts";
14−import {
15− OrchestrationEngineService,
16− type OrchestrationEngineShape,
17−} from "./orchestration/Services/OrchestrationEngine.ts";
18−import { ProjectionSnapshotQuery } from "./orchestration/Services/ProjectionSnapshotQuery.ts";
19−import { AnalyticsService } from "./telemetry/Services/AnalyticsService.ts";
20−import {
21− getAutoBootstrapDefaultModelSelection,
22− launchStartupHeartbeat,
23− makeCommandGate,
24− resolveAutoBootstrapWelcomeTargets,
25− resolveWelcomeBase,
26− ServerRuntimeStartupError,
27−} from "./serverRuntimeStartup.ts";
13+import * as ServerConfig from "./config.ts";
14+import * as OrchestrationEngine from "./orchestration/Services/OrchestrationEngine.ts";
15+import * as ProjectionSnapshotQuery from "./orchestration/Services/ProjectionSnapshotQuery.ts";
16+import * as AnalyticsService from "./telemetry/Services/AnalyticsService.ts";
17+import * as ServerRuntimeStartup from "./serverRuntimeStartup.ts";
2818
2919 it("uses the canonical Codex default for auto-bootstrapped model selection", () => {
30− assert.deepStrictEqual(getAutoBootstrapDefaultModelSelection(), {
20+ assert.deepStrictEqual(ServerRuntimeStartup.getAutoBootstrapDefaultModelSelection(), {
3121 instanceId: ProviderInstanceId.make("codex"),
3222 model: DEFAULT_MODEL,
3323 });
@@ -37,7 +27,7 @@ it.effect("enqueueCommand waits for readiness and then drains queued work", () = · 3 unmodified lines
3727 Effect.scoped(
3828 Effect.gen(function* () {
3929 const executionCount = yield* Ref.make(0);
40− const commandGate = yield* makeCommandGate;
30+ const commandGate = yield* ServerRuntimeStartup.makeCommandGate;
4131
4232 const queuedCommandFiber = yield* commandGate
4333 .enqueueCommand(Ref.updateAndGet(executionCount, (count) => count + 1))
@@ -58,7 +48,7 @@ it.effect("enqueueCommand waits for readiness and then drains queued work", () = · 14 unmodified lines
5848 it.effect("enqueueCommand fails queued work when readiness fails", () =>
5949 Effect.scoped(
6050 Effect.gen(function* () {
61− const commandGate = yield* makeCommandGate;
51+ const commandGate = yield* ServerRuntimeStartup.makeCommandGate;
6252 const failure = yield* Deferred.make<void, never>();
6353
6454 const queuedCommandFiber = yield* commandGate
@@ -66,13 +56,13 @@ it.effect("enqueueCommand fails queued work when readiness fails", () => · 1 unmodified line
6656 .pipe(Effect.forkScoped);
6757
6858 yield* commandGate.failCommandReady(
69− new ServerRuntimeStartupError({
70− message: "startup failed",
59+ new ServerRuntimeStartup.ServerRuntimeStartupError({
60+ stage: "command-readiness",
7161 }),
7262 );
7363
7464 const error = yield* Effect.flip(Fiber.join(queuedCommandFiber));
75− assert.equal(error.message, "startup failed");
65+ assert.equal(error.message, "Server runtime startup failed before command readiness.");
7666 }),
7767 ),
7868 );
@@ -82,8 +72,8 @@ it.effect("launchStartupHeartbeat does not block the caller while counts are loa · 3 unmodified lines
8272 Effect.gen(function* () {
8373 const releaseCounts = yield* Deferred.make<void, never>();
8474
85− yield* launchStartupHeartbeat.pipe(
86− Effect.provideService(ProjectionSnapshotQuery, {
75+ yield* ServerRuntimeStartup.launchStartupHeartbeat.pipe(
76+ Effect.provideService(ProjectionSnapshotQuery.ProjectionSnapshotQuery, {
8777 getCommandReadModel: () => Effect.die("unused"),
8878 getSnapshot: () => Effect.die("unused"),
8979 getShellSnapshot: () => Effect.die("unused"),
@@ -104,7 +94,7 @@ it.effect("launchStartupHeartbeat does not block the caller while counts are loa · 14 unmodified lines
10494 getThreadShellById: () => Effect.succeed(Option.none()),
10595 getThreadDetailById: () => Effect.succeed(Option.none()),
10696 }),
107− Effect.provideService(AnalyticsService, {
97+ Effect.provideService(AnalyticsService.AnalyticsService, {
10898 record: () => Effect.void,
10999 flush: Effect.void,
110100 }),
@@ -115,8 +105,8 @@ it.effect("launchStartupHeartbeat does not block the caller while counts are loa · 4 unmodified lines
115105
116106 it.effect("resolveWelcomeBase derives cwd and project name from server config", () =>
117107 Effect.gen(function* () {
118− const welcome = yield* resolveWelcomeBase.pipe(
119− Effect.provideService(ServerConfig, {
108+ const welcome = yield* ServerRuntimeStartup.resolveWelcomeBase.pipe(
109+ Effect.provideService(ServerConfig.ServerConfig, {
120110 cwd: "/tmp/startup-project",
121111 } as never),
122112 );
@@ -134,12 +124,12 @@ it.effect("resolveAutoBootstrapWelcomeTargets returns existing project and threa · 11 unmodified lines
134124
135125 return Effect.gen(function* () {
136126 const dispatchCalls = yield* Ref.make<ReadonlyArray<string>>([]);
137− const targets = yield* resolveAutoBootstrapWelcomeTargets.pipe(
138− Effect.provideService(ServerConfig, {
127+ const targets = yield* ServerRuntimeStartup.resolveAutoBootstrapWelcomeTargets.pipe(
128+ Effect.provideService(ServerConfig.ServerConfig, {
139129 cwd: "/tmp/startup-project",
140130 autoBootstrapProjectFromCwd: true,
141131 } as never),
142− Effect.provideService(ProjectionSnapshotQuery, {
132+ Effect.provideService(ProjectionSnapshotQuery.ProjectionSnapshotQuery, {
143133 getCommandReadModel: () => Effect.die("unused"),
144134 getSnapshot: () => Effect.die("unused"),
145135 getShellSnapshot: () => Effect.die("unused"),
@@ -152,7 +142,7 @@ it.effect("resolveAutoBootstrapWelcomeTargets returns existing project and threa · 6 unmodified lines
152142 id: bootstrapProjectId,
153143 title: "Startup Project",
154144 workspaceRoot: "/tmp/startup-project",
155− defaultModelSelection: getAutoBootstrapDefaultModelSelection(),
145+ defaultModelSelection: ServerRuntimeStartup.getAutoBootstrapDefaultModelSelection(),
156146 scripts: [],
157147 createdAt: "2026-01-01T00:00:00.000Z",
158148 updatedAt: "2026-01-01T00:00:00.000Z",
@@ -166,14 +156,14 @@ it.effect("resolveAutoBootstrapWelcomeTargets returns existing project and threa · 7 unmodified lines
166156 getThreadShellById: () => Effect.die("unused"),
167157 getThreadDetailById: () => Effect.die("unused"),
168158 }),
169− Effect.provideService(OrchestrationEngineService, {
159+ Effect.provideService(OrchestrationEngine.OrchestrationEngineService, {
170160 readEvents: () => Stream.empty,
171161 dispatch: (command) =>
172162 Ref.update(dispatchCalls, (calls) => [...calls, command.type]).pipe(
173163 Effect.as({ sequence: 1 }),
174164 ),
175165 streamDomainEvents: Stream.empty,
176− } satisfies OrchestrationEngineShape),
166+ } satisfies OrchestrationEngine.OrchestrationEngineService["Service"]),
177167 Effect.provide(NodeServices.layer),
178168 );
179169
@@ -188,12 +178,12 @@ it.effect("resolveAutoBootstrapWelcomeTargets returns existing project and threa · 8 unmodified lines
188178 it.effect("resolveAutoBootstrapWelcomeTargets creates a project and thread when missing", () =>
189179 Effect.gen(function* () {
190180 const dispatchCalls = yield* Ref.make<ReadonlyArray<string>>([]);
191− const targets = yield* resolveAutoBootstrapWelcomeTargets.pipe(
192− Effect.provideService(ServerConfig, {
181+ const targets = yield* ServerRuntimeStartup.resolveAutoBootstrapWelcomeTargets.pipe(
182+ Effect.provideService(ServerConfig.ServerConfig, {
193183 cwd: "/tmp/startup-project",
194184 autoBootstrapProjectFromCwd: true,
195185 } as never),
196− Effect.provideService(ProjectionSnapshotQuery, {
186+ Effect.provideService(ProjectionSnapshotQuery.ProjectionSnapshotQuery, {
197187 getCommandReadModel: () => Effect.die("unused"),
198188 getSnapshot: () => Effect.die("unused"),
199189 getShellSnapshot: () => Effect.die("unused"),
@@ -208,14 +198,14 @@ it.effect("resolveAutoBootstrapWelcomeTargets creates a project and thread when · 8 unmodified lines
208198 getThreadShellById: () => Effect.die("unused"),
209199 getThreadDetailById: () => Effect.die("unused"),
210200 }),
211− Effect.provideService(OrchestrationEngineService, {
201+ Effect.provideService(OrchestrationEngine.OrchestrationEngineService, {
212202 readEvents: () => Stream.empty,
213203 dispatch: (command) =>
214204 Ref.update(dispatchCalls, (calls) => [...calls, command.type]).pipe(
215205 Effect.as({ sequence: 1 }),
216206 ),
217207 streamDomainEvents: Stream.empty,
218− } satisfies OrchestrationEngineShape),
208+ } satisfies OrchestrationEngine.OrchestrationEngineService["Service"]),
219209 Effect.provide(NodeServices.layer),
220210 );
221211
@@ -236,12 +226,12 @@ it.effect("resolveAutoBootstrapWelcomeTargets preserves typed UUID generation fa · 14 unmodified lines
236226 });
237227 const dispatchCalls = yield* Ref.make<ReadonlyArray<string>>([]);
238228
239− const error = yield* resolveAutoBootstrapWelcomeTargets.pipe(
240− Effect.provideService(ServerConfig, {
229+ const error = yield* ServerRuntimeStartup.resolveAutoBootstrapWelcomeTargets.pipe(
230+ Effect.provideService(ServerConfig.ServerConfig, {
241231 cwd: "/tmp/startup-project",
242232 autoBootstrapProjectFromCwd: true,
243233 } as never),
244− Effect.provideService(ProjectionSnapshotQuery, {
234+ Effect.provideService(ProjectionSnapshotQuery.ProjectionSnapshotQuery, {
245235 getCommandReadModel: () => Effect.die("unused"),
246236 getSnapshot: () => Effect.die("unused"),
247237 getShellSnapshot: () => Effect.die("unused"),
@@ -256,14 +246,14 @@ it.effect("resolveAutoBootstrapWelcomeTargets preserves typed UUID generation fa · 8 unmodified lines
256246 getThreadShellById: () => Effect.die("unused"),
257247 getThreadDetailById: () => Effect.die("unused"),
258248 }),
259− Effect.provideService(OrchestrationEngineService, {
249+ Effect.provideService(OrchestrationEngine.OrchestrationEngineService, {
260250 readEvents: () => Stream.empty,
261251 dispatch: (command) =>
262252 Ref.update(dispatchCalls, (calls) => [...calls, command.type]).pipe(
263253 Effect.as({ sequence: 1 }),
264254 ),
265255 streamDomainEvents: Stream.empty,
266− } satisfies OrchestrationEngineShape),
256+ } satisfies OrchestrationEngine.OrchestrationEngineService["Service"]),
267257 Effect.provideService(Crypto.Crypto, {
268258 ...crypto,
269259 randomUUIDv4: Effect.fail(uuidError),
apps/server/src/serverRuntimeStartup.ts+53−48
@@ -7,8 +7,10 @@ import { · 6 unmodified lines
77 ProviderInstanceId,
88 ThreadId,
99 } from "@t3tools/contracts";
10−import * as Data from "effect/Data";
10+import * as Console from "effect/Console";
11+import * as Context from "effect/Context";
1112 import * as Crypto from "effect/Crypto";
13+import * as DateTime from "effect/DateTime";
1214 import * as Deferred from "effect/Deferred";
1315 import * as Effect from "effect/Effect";
1416 import * as Exit from "effect/Exit";
@@ -17,23 +19,21 @@ import * as Option from "effect/Option"; · 2 unmodified lines
1719 import * as Path from "effect/Path";
1820 import * as Queue from "effect/Queue";
1921 import * as Ref from "effect/Ref";
22+import * as Schema from "effect/Schema";
2023 import * as Scope from "effect/Scope";
21−import * as Context from "effect/Context";
22−import * as Console from "effect/Console";
23−import * as DateTime from "effect/DateTime";
2424
25−import { ServerConfig } from "./config.ts";
26−import { Keybindings } from "./keybindings.ts";
25+import * as ServerConfig from "./config.ts";
26+import * as Keybindings from "./keybindings.ts";
2727 import * as ExternalLauncher from "./process/externalLauncher.ts";
28−import { OrchestrationEngineService } from "./orchestration/Services/OrchestrationEngine.ts";
29−import { ProjectionSnapshotQuery } from "./orchestration/Services/ProjectionSnapshotQuery.ts";
30−import { OrchestrationReactor } from "./orchestration/Services/OrchestrationReactor.ts";
31−import { ServerLifecycleEvents } from "./serverLifecycleEvents.ts";
32−import { ServerSettingsService } from "./serverSettings.ts";
33−import { ServerEnvironment } from "./environment/Services/ServerEnvironment.ts";
34−import { AnalyticsService } from "./telemetry/Services/AnalyticsService.ts";
28+import * as OrchestrationEngine from "./orchestration/Services/OrchestrationEngine.ts";
29+import * as ProjectionSnapshotQuery from "./orchestration/Services/ProjectionSnapshotQuery.ts";
30+import * as OrchestrationReactor from "./orchestration/Services/OrchestrationReactor.ts";
31+import * as ServerLifecycleEvents from "./serverLifecycleEvents.ts";
32+import * as ServerSettings from "./serverSettings.ts";
33+import * as ServerEnvironment from "./environment/Services/ServerEnvironment.ts";
34+import * as AnalyticsService from "./telemetry/Services/AnalyticsService.ts";
3535 import * as EnvironmentAuth from "./auth/EnvironmentAuth.ts";
36−import { ProviderSessionReaper } from "./provider/Services/ProviderSessionReaper.ts";
36+import * as ProviderSessionReaper from "./provider/Services/ProviderSessionReaper.ts";
3737 import {
3838 formatHeadlessServeOutput,
3939 formatHostForUrl,
@@ -41,22 +41,30 @@ import { · 1 unmodified line
4141 issueHeadlessServeAccessInfo,
4242 } from "./startupAccess.ts";
4343
44−export class ServerRuntimeStartupError extends Data.TaggedError("ServerRuntimeStartupError")<{
45− readonly message: string;
46− readonly cause?: unknown;
47−}> {}
48−
49−export interface ServerRuntimeStartupShape {
50− readonly awaitCommandReady: Effect.Effect<void, ServerRuntimeStartupError>;
51− readonly markHttpListening: Effect.Effect<void>;
52− readonly enqueueCommand: <A, E>(
53− effect: Effect.Effect<A, E>,
54− ) => Effect.Effect<A, E | ServerRuntimeStartupError>;
44+export class ServerRuntimeStartupError extends Schema.TaggedErrorClass<ServerRuntimeStartupError>()(
45+ "ServerRuntimeStartupError",
46+ {
47+ stage: Schema.Literal("command-readiness"),
48+ cause: Schema.optional(Schema.Defect()),
49+ },
50+) {
51+ override get message(): string {
52+ switch (this.stage) {
53+ case "command-readiness":
54+ return "Server runtime startup failed before command readiness.";
55+ }
56+ }
5557 }
5658
5759 export class ServerRuntimeStartup extends Context.Service<
5860 ServerRuntimeStartup,
59− ServerRuntimeStartupShape
61+ {
62+ readonly awaitCommandReady: Effect.Effect<void, ServerRuntimeStartupError>;
63+ readonly markHttpListening: Effect.Effect<void>;
64+ readonly enqueueCommand: <A, E>(
65+ effect: Effect.Effect<A, E>,
66+ ) => Effect.Effect<A, E | ServerRuntimeStartupError>;
67+ }
6068 >()("t3/serverRuntimeStartup") {}
6169
6270 interface QueuedCommand {
@@ -124,8 +132,8 @@ export const makeCommandGate = Effect.gen(function* () { · 61 unmodified lines
124132 });
125133
126134 export const recordStartupHeartbeat = Effect.gen(function* () {
127− const analytics = yield* AnalyticsService;
128− const projectionSnapshotQuery = yield* ProjectionSnapshotQuery;
135+ const analytics = yield* AnalyticsService.AnalyticsService;
136+ const projectionSnapshotQuery = yield* ProjectionSnapshotQuery.ProjectionSnapshotQuery;
129137
130138 const { threadCount, projectCount } = yield* projectionSnapshotQuery.getCounts().pipe(
131139 Effect.catch((cause) =>
@@ -160,7 +168,7 @@ export const getAutoBootstrapDefaultModelSelection = (): ModelSelection => ({ · 28 unmodified lines
160168 });
161169
162170 export const resolveWelcomeBase = Effect.gen(function* () {
163− const serverConfig = yield* ServerConfig;
171+ const serverConfig = yield* ServerConfig.ServerConfig;
164172 const segments = serverConfig.cwd.split(/[/\\]/).filter(Boolean);
165173 const projectName = segments[segments.length - 1] ?? "project";
166174
@@ -173,9 +181,9 @@ export const resolveWelcomeBase = Effect.gen(function* () { · 6 unmodified lines
173181 export const resolveAutoBootstrapWelcomeTargets = Effect.gen(function* () {
174182 const crypto = yield* Crypto.Crypto;
175183 const randomUUID = crypto.randomUUIDv4;
176− const serverConfig = yield* ServerConfig;
177− const projectionReadModelQuery = yield* ProjectionSnapshotQuery;
178− const orchestrationEngine = yield* OrchestrationEngineService;
184+ const serverConfig = yield* ServerConfig.ServerConfig;
185+ const projectionReadModelQuery = yield* ProjectionSnapshotQuery.ProjectionSnapshotQuery;
186+ const orchestrationEngine = yield* OrchestrationEngine.OrchestrationEngineService;
179187 const path = yield* Path.Path;
180188
181189 let bootstrapProjectId: ProjectId | undefined;
@@ -243,7 +251,7 @@ export const resolveAutoBootstrapWelcomeTargets = Effect.gen(function* () { · 61 unmodified lines
243251 });
244252
245253 const resolveStartupBrowserTarget = Effect.gen(function* () {
246− const serverConfig = yield* ServerConfig;
254+ const serverConfig = yield* ServerConfig.ServerConfig;
247255 const serverAuth = yield* EnvironmentAuth.EnvironmentAuth;
248256 const localUrl = `http://localhost:${serverConfig.port}`;
249257 const bindUrl =
@@ -260,7 +268,7 @@ const resolveStartupBrowserTarget = Effect.gen(function* () { · 10 unmodified lines
260268
261269 const maybeOpenBrowser = (target: string) =>
262270 Effect.gen(function* () {
263− const serverConfig = yield* ServerConfig;
271+ const serverConfig = yield* ServerConfig.ServerConfig;
264272 if (serverConfig.noBrowser) {
265273 return;
266274 }
@@ -281,14 +289,14 @@ const runStartupPhase = <A, E, R>(phase: string, effect: Effect.Effect<A, E, R>) · 14 unmodified lines
281289 Effect.withSpan(`server.startup.${phase}`),
282290 );
283291
284−export const makeServerRuntimeStartup = Effect.gen(function* () {
285− const serverConfig = yield* ServerConfig;
286− const keybindings = yield* Keybindings;
287− const orchestrationReactor = yield* OrchestrationReactor;
288− const providerSessionReaper = yield* ProviderSessionReaper;
289− const lifecycleEvents = yield* ServerLifecycleEvents;
290− const serverSettings = yield* ServerSettingsService;
291− const serverEnvironment = yield* ServerEnvironment;
292+const make = Effect.gen(function* () {
293+ const serverConfig = yield* ServerConfig.ServerConfig;
294+ const keybindings = yield* Keybindings.Keybindings;
295+ const orchestrationReactor = yield* OrchestrationReactor.OrchestrationReactor;
296+ const providerSessionReaper = yield* ProviderSessionReaper.ProviderSessionReaper;
297+ const lifecycleEvents = yield* ServerLifecycleEvents.ServerLifecycleEvents;
298+ const serverSettings = yield* ServerSettings.ServerSettingsService;
299+ const serverEnvironment = yield* ServerEnvironment.ServerEnvironment;
292300 const crypto = yield* Crypto.Crypto;
293301
294302 const commandGate = yield* makeCommandGate;
@@ -409,7 +417,7 @@ export const makeServerRuntimeStartup = Effect.gen(function* () { · 114 unmodified lines
409417 const startupExit = yield* Effect.exit(startup);
410418 if (Exit.isFailure(startupExit)) {
411419 const error = new ServerRuntimeStartupError({
412− message: "Server runtime startup failed before command readiness.",
420+ stage: "command-readiness",
413421 cause: startupExit.cause,
414422 });
415423 yield* Effect.logError("server runtime startup failed", { cause: startupExit.cause });
@@ -461,10 +469,7 @@ export const makeServerRuntimeStartup = Effect.gen(function* () { · 45 unmodified lines
461469 awaitCommandReady: commandGate.awaitCommandReady,
462470 markHttpListening: Deferred.succeed(httpListening, undefined),
463471 enqueueCommand: commandGate.enqueueCommand,
464− } satisfies ServerRuntimeStartupShape;
472+ } satisfies ServerRuntimeStartup["Service"];
465473 });
466474
467−export const ServerRuntimeStartupLive = Layer.effect(
468− ServerRuntimeStartup,
469− makeServerRuntimeStartup,
470−);
475+export const layer = Layer.effect(ServerRuntimeStartup, make);
apps/server/src/serverRuntimeState.ts+3−3
@@ -5,7 +5,7 @@ import * as Option from "effect/Option"; · 4 unmodified lines
55 import * as Schema from "effect/Schema";
66
77 import { writeFileStringAtomically } from "./atomicWrite.ts";
8−import { type ServerConfigShape } from "./config.ts";
8+import type * as ServerConfig from "./config.ts";
99 import { formatHostForUrl, isWildcardHost } from "./startupAccess.ts";
1010
1111 export const PersistedServerRuntimeState = Schema.Struct({
@@ -23,7 +23,7 @@ const decodePersistedServerRuntimeState = Schema.decodeUnknownEffect( · 11 unmodified lines
2323 );
2424
2525 const runtimeOriginForConfig = (
26− config: Pick<ServerConfigShape, "host">,
26+ config: Pick<ServerConfig.ServerConfig["Service"], "host">,
2727 port: number,
2828 ): PersistedServerRuntimeState["origin"] => {
2929 const hostname =
@@ -32,7 +32,7 @@ const runtimeOriginForConfig = ( · 2 unmodified lines
3232 };
3333
3434 export const makePersistedServerRuntimeState = (input: {
35− readonly config: Pick<ServerConfigShape, "host">;
35+ readonly config: Pick<ServerConfig.ServerConfig["Service"], "host">;
3636 readonly port: number;
3737 }): Effect.Effect<PersistedServerRuntimeState> =>
3838 Effect.map(DateTime.now, (now) => ({
apps/server/src/serverSettings.test.ts+19−17
@@ -13,14 +13,16 @@ import * as Duration from "effect/Duration"; · 12 unmodified lines
1313 import * as FileSystem from "effect/FileSystem";
1414 import * as Layer from "effect/Layer";
1515 import * as Schema from "effect/Schema";
16−import { ServerConfig } from "./config.ts";
17−import { ServerSettingsLive, ServerSettingsService } from "./serverSettings.ts";
16+import * as ServerSecretStore from "./auth/ServerSecretStore.ts";
17+import * as ServerConfig from "./config.ts";
18+import * as ServerSettingsModule from "./serverSettings.ts";
1819
1920 const decodeSettingsPatch = Schema.decodeUnknownEffect(ServerSettingsPatch);
2021 const decodeServerSettings = Schema.decodeUnknownEffect(ServerSettings);
2122
2223 const makeServerSettingsLayer = () =>
23− ServerSettingsLive.pipe(
24+ ServerSettingsModule.layer.pipe(
25+ Layer.provide(ServerSecretStore.layer),
2426 Layer.provideMerge(
2527 Layer.fresh(
2628 ServerConfig.layerTest(process.cwd(), {
@@ -77,7 +79,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { · 50 unmodified lines
7779
7880 it.effect("deep merges nested settings updates without dropping siblings", () =>
7981 Effect.gen(function* () {
80− const serverSettings = yield* ServerSettingsService;
82+ const serverSettings = yield* ServerSettingsModule.ServerSettingsService;
8183
8284 yield* serverSettings.updateSettings({
8385 providers: {
@@ -145,7 +147,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { · 61 unmodified lines
145147
146148 it.effect("preserves model when switching providers via textGenerationModelSelection", () =>
147149 Effect.gen(function* () {
148− const serverSettings = yield* ServerSettingsService;
150+ const serverSettings = yield* ServerSettingsModule.ServerSettingsService;
149151
150152 // Start with Claude text generation selection
151153 yield* serverSettings.updateSettings({
@@ -183,7 +185,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { · 31 unmodified lines
183185
184186 it.effect("preserves custom provider instance text generation selections", () =>
185187 Effect.gen(function* () {
186− const serverSettings = yield* ServerSettingsService;
188+ const serverSettings = yield* ServerSettingsModule.ServerSettingsService;
187189
188190 const next = yield* serverSettings.updateSettings({
189191 providerInstances: {
@@ -210,7 +212,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { · 20 unmodified lines
210212 "uses explicit provider instance enabled state over legacy provider enabled state",
211213 () =>
212214 Effect.gen(function* () {
213− const serverSettings = yield* ServerSettingsService;
215+ const serverSettings = yield* ServerSettingsModule.ServerSettingsService;
214216 const instanceId = ProviderInstanceId.make("claude_openrouter");
215217
216218 const next = yield* serverSettings.updateSettings({
@@ -241,7 +243,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { · 24 unmodified lines
241243
242244 it.effect("preserves enabled text generation selections for non-built-in drivers", () =>
243245 Effect.gen(function* () {
244− const serverSettings = yield* ServerSettingsService;
246+ const serverSettings = yield* ServerSettingsModule.ServerSettingsService;
245247 const instanceId = ProviderInstanceId.make("openrouter_text");
246248
247249 const next = yield* serverSettings.updateSettings({
@@ -267,7 +269,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { · 19 unmodified lines
267269
268270 it.effect("drops stale text generation options when resetting model selection", () =>
269271 Effect.gen(function* () {
270− const serverSettings = yield* ServerSettingsService;
272+ const serverSettings = yield* ServerSettingsModule.ServerSettingsService;
271273
272274 yield* serverSettings.updateSettings({
273275 textGenerationModelSelection: {
@@ -300,7 +302,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { · 26 unmodified lines
300302
301303 it.effect("replaces provider instance maps when clearing optional fields", () =>
302304 Effect.gen(function* () {
303− const serverSettings = yield* ServerSettingsService;
305+ const serverSettings = yield* ServerSettingsModule.ServerSettingsService;
304306 const codexId = ProviderInstanceId.make("codex");
305307
306308 yield* serverSettings.updateSettings({
@@ -337,7 +339,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { · 30 unmodified lines
337339
338340 it.effect("trims provider path settings when updates are applied", () =>
339341 Effect.gen(function* () {
340− const serverSettings = yield* ServerSettingsService;
342+ const serverSettings = yield* ServerSettingsModule.ServerSettingsService;
341343
342344 const next = yield* serverSettings.updateSettings({
343345 providers: {
@@ -382,7 +384,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { · 38 unmodified lines
382384
383385 it.effect("trims observability settings when updates are applied", () =>
384386 Effect.gen(function* () {
385− const serverSettings = yield* ServerSettingsService;
387+ const serverSettings = yield* ServerSettingsModule.ServerSettingsService;
386388
387389 const next = yield* serverSettings.updateSettings({
388390 addProjectBaseDirectory: " ~/Development ",
@@ -402,7 +404,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { · 13 unmodified lines
402404
403405 it.effect("defaults blank binary paths to provider executables", () =>
404406 Effect.gen(function* () {
405− const serverSettings = yield* ServerSettingsService;
407+ const serverSettings = yield* ServerSettingsModule.ServerSettingsService;
406408
407409 const next = yield* serverSettings.updateSettings({
408410 providers: {
@@ -422,8 +424,8 @@ it.layer(NodeServices.layer)("server settings", (it) => { · 13 unmodified lines
422424
423425 it.effect("writes only non-default server settings to disk", () =>
424426 Effect.gen(function* () {
425− const serverSettings = yield* ServerSettingsService;
426− const serverConfig = yield* ServerConfig;
427+ const serverSettings = yield* ServerSettingsModule.ServerSettingsService;
428+ const serverConfig = yield* ServerConfig.ServerConfig;
427429 const fileSystem = yield* FileSystem.FileSystem;
428430 const next = yield* serverSettings.updateSettings({
429431 addProjectBaseDirectory: "~/Development",
@@ -469,8 +471,8 @@ it.layer(NodeServices.layer)("server settings", (it) => { · 39 unmodified lines
469471
470472 it.effect("stores sensitive provider instance environment values outside settings.json", () =>
471473 Effect.gen(function* () {
472− const serverSettings = yield* ServerSettingsService;
473− const serverConfig = yield* ServerConfig;
474+ const serverSettings = yield* ServerSettingsModule.ServerSettingsService;
475+ const serverConfig = yield* ServerConfig.ServerConfig;
474476 const fileSystem = yield* FileSystem.FileSystem;
475477 const instanceId = ProviderInstanceId.make("codex_personal");
476478
apps/server/src/serverSettings.ts+56−57
@@ -26,26 +26,26 @@ import { · 25 unmodified lines
2626 type ServerSettingsPatch,
2727 } from "@t3tools/contracts";
2828 import * as Cache from "effect/Cache";
29+import * as Cause from "effect/Cause";
30+import * as Context from "effect/Context";
2931 import * as Deferred from "effect/Deferred";
3032 import * as Duration from "effect/Duration";
33+import * as Equal from "effect/Equal";
3134 import * as Effect from "effect/Effect";
3235 import * as Exit from "effect/Exit";
3336 import * as FileSystem from "effect/FileSystem";
3437 import * as Layer from "effect/Layer";
3538 import * as Option from "effect/Option";
3639 import * as Path from "effect/Path";
37−import * as Equal from "effect/Equal";
3840 import * as PubSub from "effect/PubSub";
3941 import * as Ref from "effect/Ref";
4042 import * as Schema from "effect/Schema";
4143 import * as SchemaIssue from "effect/SchemaIssue";
44+import * as Semaphore from "effect/Semaphore";
4245 import * as Scope from "effect/Scope";
43−import * as Context from "effect/Context";
4446 import * as Stream from "effect/Stream";
45−import * as Cause from "effect/Cause";
46−import * as Semaphore from "effect/Semaphore";
4747 import { writeFileStringAtomically } from "./atomicWrite.ts";
48−import { ServerConfig } from "./config.ts";
48+import * as ServerConfig from "./config.ts";
4949 import { type DeepPartial, deepMerge } from "@t3tools/shared/Struct";
5050 import { fromJsonStringPretty, fromLenientJson } from "@t3tools/shared/schemaJson";
5151 import { applyServerSettingsPatch } from "@t3tools/shared/serverSettings";
@@ -109,59 +109,60 @@ export function redactServerSettingsForClient(settings: ServerSettings): ServerS · 57 unmodified lines
109109 return { ...settings, providerInstances };
110110 }
111111
112−export interface ServerSettingsShape {
113− /** Start the settings runtime and attach file watching. */
114− readonly start: Effect.Effect<void, ServerSettingsError>;
115−
116− /** Await settings runtime readiness. */
117− readonly ready: Effect.Effect<void, ServerSettingsError>;
112+export class ServerSettingsService extends Context.Service<
113+ ServerSettingsService,
114+ {
115+ /** Start the settings runtime and attach file watching. */
116+ readonly start: Effect.Effect<void, ServerSettingsError>;
118117
119− /** Read the current settings. */
120− readonly getSettings: Effect.Effect<ServerSettings, ServerSettingsError>;
118+ /** Await settings runtime readiness. */
119+ readonly ready: Effect.Effect<void, ServerSettingsError>;
121120
122− /** Patch settings and persist. Returns the new full settings object. */
123− readonly updateSettings: (
124− patch: ServerSettingsPatch,
125− ) => Effect.Effect<ServerSettings, ServerSettingsError>;
121+ /** Read the current settings. */
122+ readonly getSettings: Effect.Effect<ServerSettings, ServerSettingsError>;
126123
127− /** Stream of settings change events. */
128− readonly streamChanges: Stream.Stream<ServerSettings>;
129−}
124+ /** Patch settings and persist. Returns the new full settings object. */
125+ readonly updateSettings: (
126+ patch: ServerSettingsPatch,
127+ ) => Effect.Effect<ServerSettings, ServerSettingsError>;
130128
131−export class ServerSettingsService extends Context.Service<
132− ServerSettingsService,
133− ServerSettingsShape
129+ /** Stream of settings change events. */
130+ readonly streamChanges: Stream.Stream<ServerSettings>;
131+ }
134132 >()("t3/serverSettings/ServerSettingsService") {
135− static readonly layerTest = (overrides: DeepPartial<ServerSettings> = {}) =>
136− Layer.effect(
137− ServerSettingsService,
138− Effect.gen(function* () {
139− const { automaticGitFetchInterval, ...overridesForMerge } = overrides;
140− const merged = deepMerge(DEFAULT_SERVER_SETTINGS, overridesForMerge);
141− const initialSettings = yield* normalizeServerSettings({
142− ...merged,
143− ...(automaticGitFetchInterval !== undefined
144− ? { automaticGitFetchInterval: automaticGitFetchInterval as Duration.Duration }
145− : {}),
146− });
147− const currentSettingsRef = yield* Ref.make<ServerSettings>(initialSettings);
148−
149− return {
150− start: Effect.void,
151− ready: Effect.void,
152− getSettings: Ref.get(currentSettingsRef),
153− updateSettings: (patch) =>
154− Ref.get(currentSettingsRef).pipe(
155− Effect.map((currentSettings) => applyServerSettingsPatch(currentSettings, patch)),
156− Effect.flatMap(normalizeServerSettings),
157− Effect.tap((nextSettings) => Ref.set(currentSettingsRef, nextSettings)),
158− ),
159− streamChanges: Stream.empty,
160− } satisfies ServerSettingsShape;
161− }),
162− );
133+ /** @deprecated Import and use `layerTest` from this module. */
134+ static readonly layerTest = (overrides: DeepPartial<ServerSettings> = {}) => layerTest(overrides);
163135 }
164136
137+const makeTest = (overrides: DeepPartial<ServerSettings> = {}) =>
138+ Effect.gen(function* () {
139+ const { automaticGitFetchInterval, ...overridesForMerge } = overrides;
140+ const merged = deepMerge(DEFAULT_SERVER_SETTINGS, overridesForMerge);
141+ const initialSettings = yield* normalizeServerSettings({
142+ ...merged,
143+ ...(automaticGitFetchInterval !== undefined
144+ ? { automaticGitFetchInterval: automaticGitFetchInterval as Duration.Duration }
145+ : {}),
146+ });
147+ const currentSettingsRef = yield* Ref.make<ServerSettings>(initialSettings);
148+
149+ return {
150+ start: Effect.void,
151+ ready: Effect.void,
152+ getSettings: Ref.get(currentSettingsRef),
153+ updateSettings: (patch) =>
154+ Ref.get(currentSettingsRef).pipe(
155+ Effect.map((currentSettings) => applyServerSettingsPatch(currentSettings, patch)),
156+ Effect.flatMap(normalizeServerSettings),
157+ Effect.tap((nextSettings) => Ref.set(currentSettingsRef, nextSettings)),
158+ ),
159+ streamChanges: Stream.empty,
160+ } satisfies ServerSettingsService["Service"];
161+ });
162+
163+export const layerTest = (overrides: DeepPartial<ServerSettings> = {}) =>
164+ Layer.effect(ServerSettingsService, makeTest(overrides));
165+
165166 const ServerSettingsJson = fromLenientJson(ServerSettings);
166167 const decodeServerSettingsJsonExit = Schema.decodeUnknownExit(ServerSettingsJson);
167168
@@ -255,8 +256,8 @@ function stripDefaultServerSettings(current: unknown, defaults: unknown): unknow · 87 unmodified lines
255256 return Object.is(current, defaults) ? undefined : current;
256257 }
257258
258−const makeServerSettings = Effect.gen(function* () {
259− const { settingsPath } = yield* ServerConfig;
259+const make = Effect.gen(function* () {
260+ const { settingsPath } = yield* ServerConfig.ServerConfig;
260261 const fs = yield* FileSystem.FileSystem;
261262 const pathService = yield* Path.Path;
262263 const secretStore = yield* ServerSecretStore.ServerSecretStore;
@@ -578,9 +579,7 @@ const makeServerSettings = Effect.gen(function* () { · 315 unmodified lines
578579 Stream.map(resolveTextGenerationProvider),
579580 );
580581 },
581− } satisfies ServerSettingsShape;
582+ } satisfies ServerSettingsService["Service"];
582583 });
583584
584−export const ServerSettingsLive = Layer.effect(ServerSettingsService, makeServerSettings).pipe(
585− Layer.provide(ServerSecretStore.layer),
586−);
585+export const layer = Layer.effect(ServerSettingsService, make);
apps/server/src/vcs/GitVcsDriverCore.test.ts+73−0
@@ -100,6 +100,41 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { · 99 unmodified lines
100100 assert.deepStrictEqual(paths, ["complete.txt", "final.txt"]);
101101 }),
102102 );
103+
104+ it.effect("honors whitespace filtering for worktree and branch previews", () =>
105+ Effect.gen(function* () {
106+ const cwd = yield* makeTmpDir();
107+ const { initialBranch } = yield* initRepoWithCommit(cwd);
108+ const driver = yield* GitVcsDriver.GitVcsDriver;
109+ yield* git(cwd, ["checkout", "-b", "feature/whitespace"]);
110+ yield* writeTextFile(cwd, "README.md", "# test\n");
111+ yield* git(cwd, ["add", "README.md"]);
112+ yield* git(cwd, ["commit", "-m", "change whitespace"]);
113+ yield* writeTextFile(cwd, "README.md", "# test\n");
114+
115+ const included = yield* driver.getReviewDiffPreview({
116+ cwd,
117+ baseRef: initialBranch,
118+ ignoreWhitespace: false,
119+ });
120+ const ignored = yield* driver.getReviewDiffPreview({
121+ cwd,
122+ baseRef: initialBranch,
123+ ignoreWhitespace: true,
124+ });
125+
126+ assert.isNotEmpty(included.sources.find((source) => source.kind === "working-tree")?.diff);
127+ assert.isNotEmpty(included.sources.find((source) => source.kind === "branch-range")?.diff);
128+ assert.strictEqual(
129+ ignored.sources.find((source) => source.kind === "working-tree")?.diff,
130+ "",
131+ );
132+ assert.strictEqual(
133+ ignored.sources.find((source) => source.kind === "branch-range")?.diff,
134+ "",
135+ );
136+ }),
137+ );
103138 });
104139
105140 describe("repository status", () => {
@@ -342,6 +377,44 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { · 236 unmodified lines
342377 });
343378
344379 describe("refName operations", () => {
380+ it.effect("optionally includes remote refs that match local branches", () =>
381+ Effect.gen(function* () {
382+ const cwd = yield* makeTmpDir();
383+ const remote = yield* makeTmpDir("git-vcs-driver-remote-");
384+ const { initialBranch } = yield* initRepoWithCommit(cwd);
385+ yield* git(remote, ["init", "--bare"]);
386+ yield* git(cwd, ["remote", "add", "origin", remote]);
387+ yield* git(cwd, ["push", "-u", "origin", initialBranch]);
388+ const driver = yield* GitVcsDriver.GitVcsDriver;
389+
390+ const deduplicated = yield* driver.listRefs({ cwd });
391+ assert.equal(
392+ deduplicated.refs.some((ref) => ref.name === `origin/${initialBranch}`),
393+ false,
394+ );
395+
396+ const complete = yield* driver.listRefs({ cwd, includeMatchingRemoteRefs: true });
397+ assert.equal(
398+ complete.refs.some((ref) => ref.name === initialBranch),
399+ true,
400+ );
401+ assert.equal(
402+ complete.refs.some((ref) => ref.name === `origin/${initialBranch}`),
403+ true,
404+ );
405+
406+ const remoteOnly = yield* driver.listRefs({
407+ cwd,
408+ includeMatchingRemoteRefs: true,
409+ refKind: "remote",
410+ limit: 1,
411+ });
412+ assert.equal(remoteOnly.refs.length, 1);
413+ assert.equal(remoteOnly.refs[0]?.name, `origin/${initialBranch}`);
414+ assert.equal(remoteOnly.refs[0]?.isRemote, true);
415+ }),
416+ );
417+
345418 it.effect("creates, checks out, renames, and lists refs", () =>
346419 Effect.gen(function* () {
347420 const cwd = yield* makeTmpDir();
apps/server/src/vcs/GitVcsDriverCore.ts+25−6
@@ -1817,7 +1817,14 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* · 1816 unmodified lines
18171817 const dirtyTrackedResult = yield* executeGit(
18181818 "GitVcsDriver.getReviewDiffPreview.dirtyTracked",
18191819 input.cwd,
1820− ["diff", "--patch", "--minimal", "HEAD", "--"],
1820+ [
1821+ "diff",
1822+ "--patch",
1823+ "--minimal",
1824+ ...(input.ignoreWhitespace ? ["--ignore-all-space"] : []),
1825+ "HEAD",
1826+ "--",
1827+ ],
18211828 {
18221829 maxOutputBytes: REVIEW_DIFF_PATCH_MAX_OUTPUT_BYTES,
18231830 appendTruncationMarker: true,
@@ -1843,7 +1850,13 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* · 19 unmodified lines
18431850 ? yield* executeGit(
18441851 "GitVcsDriver.getReviewDiffPreview.base",
18451852 input.cwd,
1846− ["diff", "--patch", "--minimal", `${baseRef}...HEAD`],
1853+ [
1854+ "diff",
1855+ "--patch",
1856+ "--minimal",
1857+ ...(input.ignoreWhitespace ? ["--ignore-all-space"] : []),
1858+ `${baseRef}...HEAD`,
1859+ ],
18471860 {
18481861 maxOutputBytes: REVIEW_DIFF_PATCH_MAX_OUTPUT_BYTES,
18491862 appendTruncationMarker: true,
@@ -2127,11 +2140,17 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* · 277 unmodified lines
21272140 })
21282141 : [];
21292142
2143+ const allBranches = input.includeMatchingRemoteRefs
2144+ ? [...localBranches, ...remoteBranches]
2145+ : dedupeRemoteBranchesWithLocalMatches([...localBranches, ...remoteBranches]);
2146+ const branchesForKind =
2147+ input.refKind === "local"
2148+ ? allBranches.filter((ref) => !ref.isRemote)
2149+ : input.refKind === "remote"
2150+ ? allBranches.filter((ref) => ref.isRemote)
2151+ : allBranches;
21302152 const refs = paginateBranches({
2131− refs: filterBranchesForListQuery(
2132− dedupeRemoteBranchesWithLocalMatches([...localBranches, ...remoteBranches]),
2133− input.query,
2134− ),
2153+ refs: filterBranchesForListQuery(branchesForKind, input.query),
21352154 cursor: input.cursor,
21362155 limit: input.limit,
21372156 });
apps/server/src/ws.ts+2−2
@@ -1489,7 +1489,7 @@ const makeWsRpcLayer = (currentSession: AuthenticatedSession) => · 1488 unmodified lines
14891489 [WS_METHODS.previewAutomationConnect]: (input) =>
14901490 observeRpcStreamEffect(
14911491 WS_METHODS.previewAutomationConnect,
1492− previewAutomationBroker.connect(input.clientId),
1492+ previewAutomationBroker.connect(input),
14931493 { "rpc.aggregate": "preview-automation" },
14941494 ),
14951495 [WS_METHODS.previewAutomationRespond]: (input) =>
@@ -1507,7 +1507,7 @@ const makeWsRpcLayer = (currentSession: AuthenticatedSession) => · 11 unmodified lines
15071507 [WS_METHODS.previewAutomationClearOwner]: (input) =>
15081508 observeRpcEffect(
15091509 WS_METHODS.previewAutomationClearOwner,
1510− previewAutomationBroker.clearOwner(input.clientId),
1510+ previewAutomationBroker.clearOwner(input),
15111511 { "rpc.aggregate": "preview-automation" },
15121512 ),
15131513 [WS_METHODS.subscribePreviewEvents]: (_input) =>
apps/web/src/components/ChatView.tsx+13−120
@@ -42,7 +42,7 @@ import { nextTerminalId, resolveTerminalSessionLabel } from "@t3tools/shared/ter · 41 unmodified lines
4242 import { Debouncer } from "@tanstack/react-pacer";
4343 import { useAtomValue } from "@effect/atom-react";
4444 import { lazy, memo, Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react";
45−import { useNavigate, useSearch } from "@tanstack/react-router";
45+import { useNavigate } from "@tanstack/react-router";
4646 import { useShallow } from "zustand/react/shallow";
4747 import {
4848 isAtomCommandInterrupted,
@@ -55,7 +55,7 @@ import * as Cause from "effect/Cause"; · 6 unmodified lines
5555 import { AsyncResult } from "effect/unstable/reactivity";
5656 import { isElectron } from "../env";
5757 import { readLocalApi } from "../localApi";
58−import { parseDiffRouteSearch, stripDiffSearchParams } from "../diffRouteSearch";
58+import { useDiffPanelStore } from "../diffPanelStore";
5959 import {
6060 collapseExpandedComposerCursor,
6161 parseStandaloneComposerSlashCommand,
@@ -104,7 +104,7 @@ import { buildTemporaryWorktreeBranchName } from "@t3tools/shared/git"; · 42 unmodified lines
104104 import { useMediaQuery } from "../hooks/useMediaQuery";
105105 import { RIGHT_PANEL_INLINE_LAYOUT_MEDIA_QUERY } from "../rightPanelLayout";
106106 import {
107− selectActiveRightPanelKindWithUrl,
107+ selectActiveRightPanel,
108108 selectActiveRightPanelSurface,
109109 selectThreadRightPanelState,
110110 type RightPanelSurface,
@@ -1034,10 +1034,6 @@ function ChatViewContent(props: ChatViewProps) { · 923 unmodified lines
10341034 const timestampFormat = settings.timestampFormat;
10351035 const autoOpenPlanSidebar = settings.autoOpenPlanSidebar;
10361036 const navigate = useNavigate();
1037− const rawSearch = useSearch({
1038− strict: false,
1039− select: (params) => parseDiffRouteSearch(params),
1040− });
10411037 const { resolvedTheme } = useTheme();
10421038 // Granular store selectors — avoid subscribing to prompt changes.
10431039 const composerRuntimeMode = useComposerDraftStore(
@@ -1217,7 +1213,6 @@ function ChatViewContent(props: ChatViewProps) { · 173 unmodified lines
12171213 composerInteractionMode ?? activeThread?.interactionMode ?? DEFAULT_INTERACTION_MODE;
12181214 const isLocalDraftThread = !isServerThread && localDraftThread !== undefined;
12191215 const canCheckoutPullRequestIntoThread = isLocalDraftThread;
1220− const diffOpen = rawSearch.diff === "1";
12211216 const activeThreadId = activeThread?.id ?? null;
12221217 const runningTerminalIds = useThreadRunningTerminalIds({
12231218 environmentId: activeThread?.environmentId ?? null,
@@ -1259,8 +1254,9 @@ function ChatViewContent(props: ChatViewProps) { · 35 unmodified lines
12591254 );
12601255 const activeThreadKey = activeThreadRef ? scopedThreadKey(activeThreadRef) : null;
12611256 const activeRightPanelKind = useRightPanelStore((state) =>
1262− selectActiveRightPanelKindWithUrl(state.byThreadKey, activeThreadRef, diffOpen),
1257+ selectActiveRightPanel(state.byThreadKey, activeThreadRef),
12631258 );
1259+ const diffOpen = activeRightPanelKind === "diff";
12641260 const rightPanelState = useRightPanelStore((state) =>
12651261 selectThreadRightPanelState(state.byThreadKey, activeThreadRef),
12661262 );
@@ -1295,11 +1291,6 @@ function ChatViewContent(props: ChatViewProps) { · 28 unmodified lines
12951291
12961292 const planSidebarOpen = activeRightPanelKind === "plan";
12971293
1298− useEffect(() => {
1299− if (!activeThreadRef || !diffOpen) return;
1300− useRightPanelStore.getState().open(activeThreadRef, "diff");
1301− }, [activeThreadRef, diffOpen]);
1302−
13031294 const existingOpenTerminalThreadKeys = useMemo(() => {
13041295 const existingThreadKeys = new Set<string>([...serverThreadKeys, ...draftThreadKeys]);
13051296 return openTerminalThreadKeys.filter((nextThreadKey) => existingThreadKeys.has(nextThreadKey));
@@ -2151,27 +2142,7 @@ function ChatViewContent(props: ChatViewProps) { · 845 unmodified lines
21512142 if (activeThreadRef) {
21522143 useRightPanelStore.getState().toggle(activeThreadRef, "diff");
21532144 }
2154− void navigate({
2155− to: "/$environmentId/$threadId",
2156− params: {
2157− environmentId,
2158− threadId,
2159− },
2160− replace: true,
2161− search: (previous) => {
2162− const rest = stripDiffSearchParams(previous);
2163− return diffOpen ? { ...rest, diff: undefined } : { ...rest, diff: "1" };
2164− },
2165− });
2166− }, [
2167− activeThreadRef,
2168− diffOpen,
2169− environmentId,
2170− isServerThread,
2171− navigate,
2172− onDiffPanelOpen,
2173− threadId,
2174− ]);
2145+ }, [activeThreadRef, diffOpen, isServerThread, onDiffPanelOpen]);
21752146
21762147 const envLocked = Boolean(
21772148 activeThread &&
@@ -2757,21 +2728,7 @@ function ChatViewContent(props: ChatViewProps) { · 579 unmodified lines
27572728 if (!activeThreadRef || !isServerThread || !isGitRepo) return;
27582729 useRightPanelStore.getState().open(activeThreadRef, "diff");
27592730 onDiffPanelOpen?.();
2760− void navigate({
2761− to: "/$environmentId/$threadId",
2762− params: { environmentId, threadId },
2763− replace: true,
2764− search: (previous) => ({ ...stripDiffSearchParams(previous), diff: "1" }),
2765− });
2766− }, [
2767− activeThreadRef,
2768− environmentId,
2769− isGitRepo,
2770− isServerThread,
2771− navigate,
2772− onDiffPanelOpen,
2773− threadId,
2774− ]);
2731+ }, [activeThreadRef, isGitRepo, isServerThread, onDiffPanelOpen]);
27752732 const addFilesSurface = useCallback(() => {
27762733 if (!activeThreadRef || !activeProject) return;
27772734 useRightPanelStore.getState().open(activeThreadRef, "files");
@@ -2789,30 +2746,13 @@ function ChatViewContent(props: ChatViewProps) { · 11 unmodified lines
27892746 useRightPanelStore.getState().close(activeThreadRef);
27902747 return;
27912748 }
2792− if (diffOpen) {
2793− void navigate({
2794− to: "/$environmentId/$threadId",
2795− params: { environmentId, threadId },
2796− replace: true,
2797− search: (previous) => ({ ...stripDiffSearchParams(previous), diff: undefined }),
2798− });
2799− }
28002749 const activeTabId = activePreviewState.activeTabId;
28012750 if (activeTabId) {
28022751 useRightPanelStore.getState().openBrowser(activeThreadRef, activeTabId);
28032752 } else {
28042753 createBrowserSurface();
28052754 }
2806− }, [
2807− activePreviewState.activeTabId,
2808− activeThreadRef,
2809− createBrowserSurface,
2810− diffOpen,
2811− environmentId,
2812− navigate,
2813− previewPanelOpen,
2814− threadId,
2815− ]);
2755+ }, [activePreviewState.activeTabId, activeThreadRef, createBrowserSurface, previewPanelOpen]);
28162756 const closePreviewPanel = useCallback(() => {
28172757 if (activeThreadRef) {
28182758 setMaximizedRightPanelThreadKey(null);
@@ -2936,31 +2876,9 @@ function ChatViewContent(props: ChatViewProps) { · 117 unmodified lines
29362876 }
29372877 if (surface.kind === "diff" && !diffOpen) {
29382878 onDiffPanelOpen?.();
2939− void navigate({
2940− to: "/$environmentId/$threadId",
2941− params: { environmentId, threadId },
2942− replace: true,
2943− search: (previous) => ({ ...stripDiffSearchParams(previous), diff: "1" }),
2944− });
2945− } else if (surface.kind !== "diff" && diffOpen) {
2946− void navigate({
2947− to: "/$environmentId/$threadId",
2948− params: { environmentId, threadId },
2949− replace: true,
2950− search: (previous) => ({ ...stripDiffSearchParams(previous), diff: undefined }),
2951− });
29522879 }
29532880 },
2954− [
2955− activeThreadRef,
2956− diffOpen,
2957− dismissPlanSidebarForCurrentTurn,
2958− environmentId,
2959− navigate,
2960− onDiffPanelOpen,
2961− planSidebarOpen,
2962− threadId,
2963− ],
2881+ [activeThreadRef, diffOpen, dismissPlanSidebarForCurrentTurn, onDiffPanelOpen, planSidebarOpen],
29642882 );
29652883 const toggleRightPanel = useCallback(() => {
29662884 if (!activeThreadRef) return;
@@ -3006,26 +2924,14 @@ function ChatViewContent(props: ChatViewProps) { · 39 unmodified lines
30062924 }
30072925 }
30082926 }
3009− if (diffOpen && surfaces.some((surface) => surface.kind === "diff")) {
3010− void navigate({
3011− to: "/$environmentId/$threadId",
3012− params: { environmentId, threadId },
3013− replace: true,
3014− search: (previous) => ({ ...stripDiffSearchParams(previous), diff: undefined }),
3015− });
3016− }
30172927 },
30182928 [
30192929 activeThreadRef,
30202930 activePreviewState.sessions,
30212931 closePreview,
30222932 closeTerminalMutation,
3023− diffOpen,
30242933 dismissPlanSidebarForCurrentTurn,
3025− environmentId,
3026− navigate,
30272934 storeCloseTerminal,
3028− threadId,
30292935 ],
30302936 );
30312937 const syncActivePreviewSurface = useCallback(() => {
@@ -4631,25 +4537,12 @@ function ChatViewContent(props: ChatViewProps) { · 1599 unmodified lines
46314537 }, []);
46324538 const onOpenTurnDiff = useCallback(
46334539 (turnId: TurnId, filePath?: string) => {
4634− if (!isServerThread) {
4635− return;
4636− }
4540+ if (!isServerThread || !activeThreadRef) return;
4541+ useDiffPanelStore.getState().selectTurn(activeThreadRef, turnId, filePath);
4542+ useRightPanelStore.getState().open(activeThreadRef, "diff");
46374543 onDiffPanelOpen?.();
4638− void navigate({
4639− to: "/$environmentId/$threadId",
4640− params: {
4641− environmentId,
4642− threadId,
4643− },
4644− search: (previous) => {
4645− const rest = stripDiffSearchParams(previous);
4646− return filePath
4647− ? { ...rest, diff: "1", diffTurnId: turnId, diffFilePath: filePath }
4648− : { ...rest, diff: "1", diffTurnId: turnId };
4649− },
4650− });
46514544 },
4652− [environmentId, isServerThread, navigate, onDiffPanelOpen, threadId],
4545+ [activeThreadRef, isServerThread, onDiffPanelOpen],
46534546 );
46544547 // Both the Map and the revert handler are read from refs at call-time so
46554548 // the callback reference is fully stable and never busts context identity.
apps/web/src/components/DiffPanel.tsx+436−345
@@ -1,35 +1,28 @@
11 import { useAtomValue } from "@effect/atom-react";
2−import { Virtualizer } from "@pierre/diffs/react";
3−import { useNavigate, useParams, useSearch } from "@tanstack/react-router";
4−import { scopeThreadRef } from "@t3tools/client-runtime/environment";
2+import { useParams } from "@tanstack/react-router";
53 import {
64 isAtomCommandInterrupted,
75 squashAtomCommandFailure,
86 } from "@t3tools/client-runtime/state/runtime";
97 import type { ScopedThreadRef, TurnId } from "@t3tools/contracts";
108 import {
9+ ArrowRightIcon,
10+ CheckIcon,
1111 ChevronDownIcon,
12− ChevronLeftIcon,
1312 ChevronRightIcon,
1413 Columns2Icon,
1514 PilcrowIcon,
1615 Rows3Icon,
16+ SearchIcon,
1717 TextWrapIcon,
1818 } from "lucide-react";
19−import {
20− type WheelEvent as ReactWheelEvent,
21− useCallback,
22− useEffect,
23− useMemo,
24− useRef,
25− useState,
26−} from "react";
19+import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2720 import { useOpenInPreferredEditor } from "../editorPreferences";
2821 import { type DraftId } from "../composerDraftStore";
2922 import { openDiffFilePrimaryAction } from "../diffFileActions";
3023 import { useCheckpointDiff } from "~/lib/checkpointDiffState";
3124 import { cn } from "~/lib/utils";
32−import { parseDiffRouteSearch, stripDiffSearchParams } from "../diffRouteSearch";
25+import { selectThreadDiffPanelSelection, useDiffPanelStore } from "../diffPanelStore";
3326 import { useTheme } from "../hooks/useTheme";
3427 import {
3528 buildFileDiffRenderKey,
@@ -40,19 +33,41 @@ import { · 4 unmodified lines
4033 } from "../lib/diffRendering";
4134 import { useTurnDiffSummaries } from "../hooks/useTurnDiffSummaries";
4235 import { useProject, useThread } from "../state/entities";
43−import { buildThreadRouteParams, resolveThreadRouteRef } from "../threadRoutes";
36+import { resolveThreadRouteRef } from "../threadRoutes";
4437 import { useSettings } from "../hooks/useSettings";
4538 import { formatShortTimestamp } from "../timestampFormat";
4639 import { DiffPanelLoadingState, DiffPanelShell, type DiffPanelMode } from "./DiffPanelShell";
47−import { AnnotatableFileDiff } from "./diffs/AnnotatableFileDiff";
40+import { AnnotatableCodeView, type AnnotatableCodeViewHandle } from "./diffs/AnnotatableFileDiff";
4841 import { ToggleGroup, Toggle } from "./ui/toggle-group";
42+import { Switch } from "./ui/switch";
43+import {
44+ Combobox,
45+ ComboboxEmpty,
46+ ComboboxInput,
47+ ComboboxItem,
48+ ComboboxList,
49+ ComboboxPopup,
50+ ComboboxTrigger,
51+} from "./ui/combobox";
52+import {
53+ DropdownMenu,
54+ DropdownMenuContent,
55+ DropdownMenuItem,
56+ DropdownMenuSub,
57+ DropdownMenuSubContent,
58+ DropdownMenuSubTrigger,
59+ DropdownMenuTrigger,
60+} from "./ui/menu";
4961 import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";
5062 import { useEnvironmentQuery } from "../state/query";
5163 import { serverEnvironment } from "../state/server";
64+import { reviewEnvironment } from "../state/review";
5265 import { vcsEnvironment } from "../state/vcs";
66+import { buildBaseRefChoices, filterBaseRefChoices } from "../lib/baseRefChoices";
5367
5468 type DiffRenderMode = "stacked" | "split";
5569 type DiffThemeType = "light" | "dark";
70+const AUTOMATIC_BASE_REF = "__automatic_base_ref__";
5671
5772 interface CollapsedDiffFilesState {
5873 readonly scopeKey: string | null;
@@ -167,27 +182,24 @@ interface DiffPanelProps { · 108 unmodified lines
167182 export { DiffWorkerPoolProvider } from "./DiffWorkerPoolProvider";
168183
169184 export default function DiffPanel({ mode = "inline", composerDraftTarget }: DiffPanelProps) {
170− const navigate = useNavigate();
171185 const { resolvedTheme } = useTheme();
172186 const settings = useSettings();
173187 const [diffRenderMode, setDiffRenderMode] = useState<DiffRenderMode>("stacked");
174188 const [diffWordWrap, setDiffWordWrap] = useState(settings.diffWordWrap);
175189 const [diffIgnoreWhitespace, setDiffIgnoreWhitespace] = useState(settings.diffIgnoreWhitespace);
190+ const [baseRefQuery, setBaseRefQuery] = useState("");
176191 const [collapsedDiffFiles, setCollapsedDiffFiles] = useState<CollapsedDiffFilesState>(() => ({
177192 scopeKey: null,
178193 fileKeys: EMPTY_COLLAPSED_DIFF_FILE_KEYS,
179194 }));
180− const patchViewportRef = useRef<HTMLDivElement>(null);
181− const turnStripRef = useRef<HTMLDivElement>(null);
182− const previousDiffOpenRef = useRef(false);
183− const [canScrollTurnStripLeft, setCanScrollTurnStripLeft] = useState(false);
184− const [canScrollTurnStripRight, setCanScrollTurnStripRight] = useState(false);
195+ const codeViewRef = useRef<AnnotatableCodeViewHandle>(null);
185196 const routeThreadRef = useParams({
186197 strict: false,
187198 select: (params) => resolveThreadRouteRef(params),
188199 });
189− const diffSearch = useSearch({ strict: false, select: (search) => parseDiffRouteSearch(search) });
190− const diffOpen = diffSearch.diff === "1";
200+ const diffSelection = useDiffPanelStore((state) =>
201+ selectThreadDiffPanelSelection(state.byThreadKey, routeThreadRef),
202+ );
191203 const activeThreadId = routeThreadRef?.threadId ?? null;
192204 const activeThread = useThread(routeThreadRef);
193205 const activeProjectId = activeThread?.projectId ?? null;
@@ -233,8 +245,20 @@ export default function DiffPanel({ mode = "inline", composerDraftTarget }: Diff · 39 unmodified lines
233245 [inferredCheckpointTurnCountByTurnId, turnDiffSummaries],
234246 );
235247
236− const selectedTurnId = diffSearch.diffTurnId ?? null;
237− const selectedFilePath = selectedTurnId !== null ? (diffSearch.diffFilePath ?? null) : null;
248+ useEffect(() => {
249+ if (!routeThreadRef || diffSelection.kind !== "turn") return;
250+ useDiffPanelStore.getState().reconcileTurnSelection(
251+ routeThreadRef,
252+ orderedTurnDiffSummaries.map((summary) => summary.turnId),
253+ );
254+ }, [diffSelection, orderedTurnDiffSummaries, routeThreadRef]);
255+
256+ const selectedTurnId = diffSelection.kind === "turn" ? diffSelection.turnId : null;
257+ const selectedGitScope = diffSelection.kind === "unstaged" ? "unstaged" : "branch";
258+ const selectedBaseRef = diffSelection.kind === "branch" ? diffSelection.baseRef : null;
259+ const selectedFilePath = diffSelection.kind === "turn" ? diffSelection.filePath : null;
260+ const selectedFileRevealRequestId =
261+ diffSelection.kind === "turn" ? diffSelection.revealRequestId : 0;
238262 const selectedTurn =
239263 selectedTurnId === null
240264 ? undefined
@@ -243,7 +267,16 @@ export default function DiffPanel({ mode = "inline", composerDraftTarget }: Diff · 2 unmodified lines
243267 const selectedCheckpointTurnCount =
244268 selectedTurn &&
245269 (selectedTurn.checkpointTurnCount ?? inferredCheckpointTurnCountByTurnId[selectedTurn.turnId]);
246− const reviewSectionId = selectedTurn ? `turn:${selectedTurn.turnId}` : "conversation";
270+ const latestTurn = orderedTurnDiffSummaries[0];
271+ const selectedScopeLabel =
272+ selectedTurnId === null
273+ ? selectedGitScope === "unstaged"
274+ ? "Working tree"
275+ : "Branch changes"
276+ : selectedTurn?.turnId === latestTurn?.turnId
277+ ? "Latest turn"
278+ : `Turn ${selectedCheckpointTurnCount ?? "?"}`;
279+ const reviewSectionId = selectedTurn ? `turn:${selectedTurn.turnId}` : selectedGitScope;
247280 const collapseScopeKey = routeThreadRef
248281 ? `${routeThreadRef.environmentId}:${routeThreadRef.threadId}:${reviewSectionId}`
249282 : null;
@@ -253,7 +286,9 @@ export default function DiffPanel({ mode = "inline", composerDraftTarget }: Diff · 3 unmodified lines
253286 : EMPTY_COLLAPSED_DIFF_FILE_KEYS;
254287 const reviewSectionTitle = selectedTurn
255288 ? `Turn ${selectedCheckpointTurnCount ?? "?"}`
256− : "All turns";
289+ : selectedGitScope === "unstaged"
290+ ? "Working tree"
291+ : "Branch changes";
257292 const selectedCheckpointRange = useMemo(
258293 () =>
259294 typeof selectedCheckpointTurnCount === "number"
@@ -264,62 +299,116 @@ export default function DiffPanel({ mode = "inline", composerDraftTarget }: Diff · 4 unmodified lines
264299 : null,
265300 [selectedCheckpointTurnCount],
266301 );
267− const conversationCheckpointTurnCount = useMemo(() => {
268− const turnCounts: Array<number> = [];
269− for (const summary of orderedTurnDiffSummaries) {
270− const value =
271− summary.checkpointTurnCount ?? inferredCheckpointTurnCountByTurnId[summary.turnId];
272− if (typeof value === "number") {
273− turnCounts.push(value);
274− }
275− }
276− if (turnCounts.length === 0) {
277− return undefined;
278− }
279− const latest = Math.max(...turnCounts);
280− return latest > 0 ? latest : undefined;
281− }, [inferredCheckpointTurnCountByTurnId, orderedTurnDiffSummaries]);
282− const conversationCheckpointRange = useMemo(
283− () =>
284− !selectedTurn && typeof conversationCheckpointTurnCount === "number"
285− ? {
286− fromTurnCount: 0,
287− toTurnCount: conversationCheckpointTurnCount,
288− }
289− : null,
290− [conversationCheckpointTurnCount, selectedTurn],
291− );
292− const activeCheckpointRange = selectedTurn
293− ? selectedCheckpointRange
294− : conversationCheckpointRange;
295− const conversationCacheScope = useMemo(() => {
296− if (selectedTurn || orderedTurnDiffSummaries.length === 0) {
297− return null;
298− }
299− return `conversation:${orderedTurnDiffSummaries.map((summary) => summary.turnId).join(",")}`;
300− }, [orderedTurnDiffSummaries, selectedTurn]);
301302 const activeCheckpointDiff = useCheckpointDiff(
302303 {
303304 environmentId: activeThread?.environmentId ?? null,
304305 threadId: activeThreadId,
305− fromTurnCount: activeCheckpointRange?.fromTurnCount ?? null,
306− toTurnCount: activeCheckpointRange?.toTurnCount ?? null,
306+ fromTurnCount: selectedCheckpointRange?.fromTurnCount ?? null,
307+ toTurnCount: selectedCheckpointRange?.toTurnCount ?? null,
307308 ignoreWhitespace: diffIgnoreWhitespace,
308− cacheScope: selectedTurn ? `turn:${selectedTurn.turnId}` : conversationCacheScope,
309+ cacheScope: selectedTurn ? `turn:${selectedTurn.turnId}` : null,
309310 },
310− { enabled: isGitRepo },
311+ { enabled: isGitRepo && selectedTurn !== undefined },
311312 );
312− const selectedTurnCheckpointDiff = selectedTurn ? activeCheckpointDiff.data?.diff : undefined;
313− const conversationCheckpointDiff = selectedTurn ? undefined : activeCheckpointDiff.data?.diff;
314− const isLoadingCheckpointDiff = activeCheckpointDiff.isPending;
315− const checkpointDiffError = activeCheckpointDiff.error;
316−
317− const selectedPatch = selectedTurn ? selectedTurnCheckpointDiff : conversationCheckpointDiff;
313+ const primaryBranchDiffPreview = useEnvironmentQuery(
314+ selectedTurnId === null && activeThread && activeCwd
315+ ? reviewEnvironment.diffPreview({
316+ environmentId: activeThread.environmentId,
317+ input: {
318+ cwd: activeCwd,
319+ ...(selectedBaseRef ? { baseRef: selectedBaseRef } : {}),
320+ ignoreWhitespace: diffIgnoreWhitespace,
321+ },
322+ })
323+ : null,
324+ );
325+ const shouldRetryBranchDiffAtEnvironmentCwd =
326+ selectedTurnId === null &&
327+ primaryBranchDiffPreview.error?.includes("configured workspace root") === true &&
328+ serverConfig?.cwd !== undefined &&
329+ serverConfig.cwd !== activeCwd;
330+ const fallbackBranchDiffPreview = useEnvironmentQuery(
331+ shouldRetryBranchDiffAtEnvironmentCwd && activeThread && serverConfig
332+ ? reviewEnvironment.diffPreview({
333+ environmentId: activeThread.environmentId,
334+ input: {
335+ cwd: serverConfig.cwd,
336+ ...(selectedBaseRef ? { baseRef: selectedBaseRef } : {}),
337+ ignoreWhitespace: diffIgnoreWhitespace,
338+ },
339+ })
340+ : null,
341+ );
342+ const branchDiffPreview = shouldRetryBranchDiffAtEnvironmentCwd
343+ ? fallbackBranchDiffPreview
344+ : primaryBranchDiffPreview;
345+ const selectedGitSource = branchDiffPreview.data?.sources.find(
346+ (source) => source.kind === (selectedGitScope === "unstaged" ? "working-tree" : "branch-range"),
347+ );
348+ const localBranchRefs = useEnvironmentQuery(
349+ selectedTurnId === null &&
350+ selectedGitScope === "branch" &&
351+ activeThread &&
352+ branchDiffPreview.data?.cwd
353+ ? vcsEnvironment.listRefs({
354+ environmentId: activeThread.environmentId,
355+ input: {
356+ cwd: branchDiffPreview.data.cwd,
357+ includeMatchingRemoteRefs: true,
358+ refKind: "local",
359+ ...(baseRefQuery.trim().length > 0 ? { query: baseRefQuery.trim() } : {}),
360+ limit: 100,
361+ },
362+ })
363+ : null,
364+ );
365+ const remoteBranchRefs = useEnvironmentQuery(
366+ selectedTurnId === null &&
367+ selectedGitScope === "branch" &&
368+ activeThread &&
369+ branchDiffPreview.data?.cwd
370+ ? vcsEnvironment.listRefs({
371+ environmentId: activeThread.environmentId,
372+ input: {
373+ cwd: branchDiffPreview.data.cwd,
374+ includeMatchingRemoteRefs: true,
375+ refKind: "remote",
376+ ...(baseRefQuery.trim().length > 0 ? { query: baseRefQuery.trim() } : {}),
377+ limit: 100,
378+ },
379+ })
380+ : null,
381+ );
382+ const baseRefChoices = buildBaseRefChoices(
383+ localBranchRefs.data?.refs.filter((ref) => ref.name !== selectedGitSource?.headRef) ?? [],
384+ remoteBranchRefs.data?.refs ?? [],
385+ );
386+ const matchingBaseRefChoices = filterBaseRefChoices(baseRefChoices, baseRefQuery);
387+ const valueForBaseRefChoice = (choice: (typeof baseRefChoices)[number]) =>
388+ selectedBaseRef && selectedBaseRef === choice.remote?.name
389+ ? selectedBaseRef
390+ : (choice.local?.name ?? choice.remote?.name ?? choice.id);
391+ const baseRefItems = [AUTOMATIC_BASE_REF, ...baseRefChoices.map(valueForBaseRefChoice)];
392+ const filteredBaseRefItems = [
393+ ...(baseRefQuery.trim().length === 0 ? [AUTOMATIC_BASE_REF] : []),
394+ ...matchingBaseRefChoices.map(valueForBaseRefChoice),
395+ ];
396+ const gitDiff = selectedGitSource?.diff;
397+
398+ const selectedPatch = selectedTurn ? activeCheckpointDiff.data?.diff : gitDiff;
399+ const isSelectedPatchTruncated = !selectedTurn && selectedGitSource?.truncated === true;
400+ const isLoadingSelectedPatch = selectedTurn
401+ ? activeCheckpointDiff.isPending
402+ : branchDiffPreview.isPending;
403+ const selectedPatchError = selectedTurn ? activeCheckpointDiff.error : branchDiffPreview.error;
318404 const hasResolvedPatch = typeof selectedPatch === "string";
319405 const hasNoNetChanges = hasResolvedPatch && selectedPatch.trim().length === 0;
320406 const renderablePatch = useMemo(
321− () => getRenderablePatch(selectedPatch, `diff-panel:${resolvedTheme}`),
322− [resolvedTheme, selectedPatch],
407+ () =>
408+ getRenderablePatch(selectedPatch, `diff-panel:${resolvedTheme}`, {
409+ compactPartialHunkOffsets: selectedTurnId === null,
410+ }),
411+ [resolvedTheme, selectedPatch, selectedTurnId],
323412 );
324413 const renderableFiles = useMemo(() => {
325414 if (!renderablePatch || renderablePatch.kind !== "files") {
@@ -332,24 +421,26 @@ export default function DiffPanel({ mode = "inline", composerDraftTarget }: Diff · 6 unmodified lines
332421 }),
333422 );
334423 }, [renderablePatch]);
424+ const codeViewFiles = useMemo(
425+ () =>
426+ renderableFiles.map((fileDiff) => {
427+ const fileKey = buildFileDiffRenderKey(fileDiff);
428+ return {
429+ fileDiff,
430+ filePath: resolveFileDiffPath(fileDiff),
431+ fileKey,
432+ collapsed: collapsedDiffFileKeys.has(fileKey),
433+ };
434+ }),
435+ [collapsedDiffFileKeys, renderableFiles],
436+ );
335437
336438 useEffect(() => {
337− if (diffOpen && !previousDiffOpenRef.current) {
338− setDiffWordWrap(settings.diffWordWrap);
339− setDiffIgnoreWhitespace(settings.diffIgnoreWhitespace);
340− }
341− previousDiffOpenRef.current = diffOpen;
342− }, [diffOpen, settings.diffIgnoreWhitespace, settings.diffWordWrap]);
343−
344− useEffect(() => {
345− if (!selectedFilePath || !patchViewportRef.current) {
346− return;
347− }
348− const target = Array.from(
349− patchViewportRef.current.querySelectorAll<HTMLElement>("[data-diff-file-path]"),
350− ).find((element) => element.dataset.diffFilePath === selectedFilePath);
351− target?.scrollIntoView({ block: "nearest" });
352− }, [selectedFilePath, renderableFiles]);
439+ if (!selectedFilePath) return;
440+ const file = codeViewFiles.find((candidate) => candidate.filePath === selectedFilePath);
441+ if (!file) return;
442+ codeViewRef.current?.scrollTo({ type: "item", id: file.fileKey, align: "start" });
443+ }, [codeViewFiles, selectedFilePath, selectedFileRevealRequestId]);
353444
354445 const openDiffFile = useCallback(
355446 (filePath: string) => {
@@ -385,186 +476,190 @@ export default function DiffPanel({ mode = "inline", composerDraftTarget }: Diff · 29 unmodified lines
385476 );
386477
387478 const selectTurn = (turnId: TurnId) => {
388− if (!activeThread) return;
389− void navigate({
390− to: "/$environmentId/$threadId",
391− params: buildThreadRouteParams(scopeThreadRef(activeThread.environmentId, activeThread.id)),
392− search: (previous) => {
393− const rest = stripDiffSearchParams(previous);
394− return { ...rest, diff: "1", diffTurnId: turnId };
395− },
396− });
479+ if (!routeThreadRef) return;
480+ useDiffPanelStore.getState().selectTurn(routeThreadRef, turnId);
397481 };
398− const selectWholeConversation = () => {
399− if (!activeThread) return;
400− void navigate({
401− to: "/$environmentId/$threadId",
402− params: buildThreadRouteParams(scopeThreadRef(activeThread.environmentId, activeThread.id)),
403− search: (previous) => {
404− const rest = stripDiffSearchParams(previous);
405− return { ...rest, diff: "1" };
406− },
407− });
482+ const selectGitScope = (scope: "branch" | "unstaged") => {
483+ if (!routeThreadRef) return;
484+ useDiffPanelStore.getState().selectGitScope(routeThreadRef, scope);
485+ };
486+ const selectBranchBaseRef = (baseRef: string | null) => {
487+ if (!routeThreadRef) return;
488+ useDiffPanelStore.getState().selectBranchBaseRef(routeThreadRef, baseRef);
408489 };
409− const updateTurnStripScrollState = useCallback(() => {
410− const element = turnStripRef.current;
411− if (!element) {
412− setCanScrollTurnStripLeft(false);
413− setCanScrollTurnStripRight(false);
414− return;
415− }
416−
417− const maxScrollLeft = Math.max(0, element.scrollWidth - element.clientWidth);
418− setCanScrollTurnStripLeft(element.scrollLeft > 4);
419− setCanScrollTurnStripRight(element.scrollLeft < maxScrollLeft - 4);
420− }, []);
421− const scrollTurnStripBy = useCallback((offset: number) => {
422− const element = turnStripRef.current;
423− if (!element) return;
424− element.scrollBy({ left: offset, behavior: "smooth" });
425− }, []);
426− const onTurnStripWheel = useCallback((event: ReactWheelEvent<HTMLDivElement>) => {
427− const element = turnStripRef.current;
428− if (!element) return;
429− if (element.scrollWidth <= element.clientWidth + 1) return;
430− if (Math.abs(event.deltaY) <= Math.abs(event.deltaX)) return;
431−
432− event.preventDefault();
433− element.scrollBy({ left: event.deltaY, behavior: "auto" });
434− }, []);
435−
436− useEffect(() => {
437− const element = turnStripRef.current;
438− if (!element) return;
439−
440− const frameId = window.requestAnimationFrame(() => updateTurnStripScrollState());
441− const onScroll = () => updateTurnStripScrollState();
442−
443− element.addEventListener("scroll", onScroll, { passive: true });
444−
445− const resizeObserver = new ResizeObserver(() => updateTurnStripScrollState());
446− resizeObserver.observe(element);
447−
448− return () => {
449− window.cancelAnimationFrame(frameId);
450− element.removeEventListener("scroll", onScroll);
451− resizeObserver.disconnect();
452− };
453− }, [updateTurnStripScrollState]);
454−
455− useEffect(() => {
456− const frameId = window.requestAnimationFrame(() => updateTurnStripScrollState());
457− return () => {
458− window.cancelAnimationFrame(frameId);
459− };
460− }, [orderedTurnDiffSummaries, selectedTurnId, updateTurnStripScrollState]);
461−
462− useEffect(() => {
463− const element = turnStripRef.current;
464− if (!element) return;
465−
466− const selectedChip = element.querySelector<HTMLElement>("[data-turn-chip-selected='true']");
467− selectedChip?.scrollIntoView({ block: "nearest", inline: "nearest", behavior: "smooth" });
468− }, [selectedTurn?.turnId, selectedTurnId]);
469490
470491 const headerRow = (
471492 <>
472− <div className="relative min-w-0 flex-1 [-webkit-app-region:no-drag]">
473− <button
474− type="button"
475− className={cn(
476− "absolute left-0 top-1/2 z-20 inline-flex size-6 -translate-y-1/2 items-center justify-center rounded-md border bg-background/90 text-muted-foreground transition-colors",
477− canScrollTurnStripLeft
478− ? "border-border/70 hover:border-border hover:text-foreground"
479− : "cursor-not-allowed border-border/40 text-muted-foreground/40",
480− )}
481− onClick={() => scrollTurnStripBy(-180)}
482− disabled={!canScrollTurnStripLeft}
483− aria-label="Scroll turn list left"
484− >
485− <ChevronLeftIcon className="size-3.5" />
486− </button>
487− <button
488− type="button"
489− className={cn(
490− "absolute right-0 top-1/2 z-20 inline-flex size-6 -translate-y-1/2 items-center justify-center rounded-md border bg-background/90 text-muted-foreground transition-colors",
491− canScrollTurnStripRight
492− ? "border-border/70 hover:border-border hover:text-foreground"
493− : "cursor-not-allowed border-border/40 text-muted-foreground/40",
494− )}
495− onClick={() => scrollTurnStripBy(180)}
496− disabled={!canScrollTurnStripRight}
497− aria-label="Scroll turn list right"
498− >
499− <ChevronRightIcon className="size-3.5" />
500− </button>
501− <div
502− ref={turnStripRef}
503− className="turn-chip-strip flex gap-1 overflow-x-auto px-8 py-0.5"
504− style={
505− canScrollTurnStripLeft || canScrollTurnStripRight
506− ? {
507− maskImage: `linear-gradient(to right, ${canScrollTurnStripLeft ? "transparent 24px, black 72px" : "black"}, ${canScrollTurnStripRight ? "black calc(100% - 72px), transparent calc(100% - 24px)" : "black"})`,
508− }
509− : undefined
510− }
511− onWheel={onTurnStripWheel}
512− >
513− <button
514− type="button"
515− className="shrink-0 rounded-md"
516− onClick={selectWholeConversation}
517− data-turn-chip-selected={selectedTurnId === null}
493+ <div className="flex min-w-0 flex-1 items-center gap-3 [-webkit-app-region:no-drag]">
494+ <DropdownMenu>
495+ <DropdownMenuTrigger
496+ className="inline-flex h-6 max-w-full items-center gap-1 rounded-md bg-muted/70 px-2 text-xs font-medium text-foreground outline-none transition-colors hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring"
497+ aria-label={`Diff scope: ${selectedScopeLabel}`}
518498 >
519− <div
520− className={cn(
521− "rounded-md border px-2 py-1 text-left transition-colors",
522− selectedTurnId === null
523− ? "border-border bg-accent text-accent-foreground"
524− : "border-border/70 bg-background/70 text-muted-foreground/80 hover:border-border hover:text-foreground/80",
499+ <span className="truncate">{selectedScopeLabel}</span>
500+ <ChevronDownIcon className="size-3.5 shrink-0 text-muted-foreground" />
501+ </DropdownMenuTrigger>
502+ <DropdownMenuContent align="start" className="w-60">
503+ <DropdownMenuItem onClick={() => selectGitScope("unstaged")}>
504+ <span>Working tree</span>
505+ {selectedTurnId === null && selectedGitScope === "unstaged" && (
506+ <CheckIcon className="ml-auto" />
507+ )}
508+ </DropdownMenuItem>
509+ <DropdownMenuItem onClick={() => selectGitScope("branch")}>
510+ <span>Branch changes</span>
511+ {selectedTurnId === null && selectedGitScope === "branch" && (
512+ <CheckIcon className="ml-auto" />
525513 )}
514+ </DropdownMenuItem>
515+ <DropdownMenuItem
516+ onClick={() => {
517+ if (latestTurn) selectTurn(latestTurn.turnId);
518+ }}
526519 >
527− <div className="text-[10px] leading-tight font-medium">All turns</div>
528− </div>
529− </button>
530− {orderedTurnDiffSummaries.map((summary) => (
531− <Tooltip key={summary.turnId}>
532− <TooltipTrigger
533− render={
534− <button
535− type="button"
536− aria-label={summary.turnId}
537− className="shrink-0 rounded-md"
538− onClick={() => selectTurn(summary.turnId)}
539− data-turn-chip-selected={summary.turnId === selectedTurn?.turnId}
540− />
541− }
520+ <span>Latest turn</span>
521+ {selectedTurnId !== null && selectedTurn?.turnId === latestTurn?.turnId && (
522+ <CheckIcon className="ml-auto" />
523+ )}
524+ </DropdownMenuItem>
525+ <DropdownMenuSub>
526+ <DropdownMenuSubTrigger>Turn</DropdownMenuSubTrigger>
527+ <DropdownMenuSubContent className="w-64">
528+ {orderedTurnDiffSummaries.map((summary) => {
529+ const turnCount =
530+ summary.checkpointTurnCount ??
531+ inferredCheckpointTurnCountByTurnId[summary.turnId] ??
532+ "?";
533+ return (
534+ <DropdownMenuItem
535+ key={summary.turnId}
536+ onClick={() => selectTurn(summary.turnId)}
537+ >
538+ <span>Turn {turnCount}</span>
539+ <span className="ml-auto text-xs tabular-nums text-muted-foreground">
540+ {formatShortTimestamp(summary.completedAt, settings.timestampFormat)}
541+ </span>
542+ {summary.turnId === selectedTurn?.turnId && <CheckIcon className="ml-1" />}
543+ </DropdownMenuItem>
544+ );
545+ })}
546+ </DropdownMenuSubContent>
547+ </DropdownMenuSub>
548+ </DropdownMenuContent>
549+ </DropdownMenu>
550+ {selectedTurnId === null && selectedGitScope === "branch" && selectedGitSource?.baseRef && (
551+ <div
552+ className="flex min-w-0 max-w-full items-center gap-2 overflow-hidden text-xs text-muted-foreground"
553+ title={`${selectedGitSource.headRef ?? "HEAD"} → ${selectedGitSource.baseRef}`}
554+ aria-label={`Comparing ${selectedGitSource.headRef ?? "HEAD"} against ${selectedGitSource.baseRef}`}
555+ >
556+ <span className="min-w-0 max-w-48 truncate">{selectedGitSource.headRef ?? "HEAD"}</span>
557+ <ArrowRightIcon className="size-3.5 shrink-0 opacity-70" />
558+ <Combobox
559+ items={baseRefItems}
560+ filteredItems={filteredBaseRefItems}
561+ value={selectedBaseRef ?? AUTOMATIC_BASE_REF}
562+ onOpenChange={(open) => {
563+ if (!open) setBaseRefQuery("");
564+ }}
565+ onValueChange={(value) => {
566+ if (!value) return;
567+ selectBranchBaseRef(value === AUTOMATIC_BASE_REF ? null : value);
568+ }}
569+ >
570+ <ComboboxTrigger
571+ className="inline-flex min-w-0 max-w-48 items-center gap-1 overflow-hidden rounded-md px-1.5 py-1 outline-none transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring"
572+ aria-label={`Change comparison target. Currently ${selectedGitSource.baseRef}`}
573+ >
574+ <span className="min-w-0 truncate">{selectedGitSource.baseRef}</span>
575+ <ChevronDownIcon className="size-3.5 shrink-0 opacity-70" />
576+ </ComboboxTrigger>
577+ <ComboboxPopup
578+ align="start"
579+ className="w-72 min-w-0 max-w-[calc(100vw-1rem)] overflow-hidden [&>[data-slot=combobox-popup]]:min-w-0 [&>[data-slot=combobox-popup]]:overflow-hidden"
542580 >
543− <div
544− className={cn(
545− "rounded-md border px-2 py-1 text-left transition-colors",
546− summary.turnId === selectedTurn?.turnId
547− ? "border-border bg-accent text-accent-foreground"
548− : "border-border/70 bg-background/70 text-muted-foreground/80 hover:border-border hover:text-foreground/80",
549− )}
550− >
551− <div className="flex items-center gap-1">
552− <span className="text-[10px] leading-tight font-medium">
553− Turn{" "}
554− {summary.checkpointTurnCount ??
555− inferredCheckpointTurnCountByTurnId[summary.turnId] ??
556− "?"}
557− </span>
558− <span className="text-[9px] leading-tight opacity-70 tabular-nums">
559− {formatShortTimestamp(summary.completedAt, settings.timestampFormat)}
560− </span>
581+ <div className="min-w-0 shrink-0 px-3 pt-2.5">
582+ <div className="relative -translate-y-px border-b border-border/70 pb-1.5 transition-colors focus-within:border-ring">
583+ <SearchIcon
584+ aria-hidden="true"
585+ className="pointer-events-none absolute top-1.5 left-0 size-4 shrink-0 text-muted-foreground/55"
586+ />
587+ <ComboboxInput
588+ className="[&_input]:h-6.5 [&_input]:ps-5 [&_input]:font-sans [&_input]:leading-6.5"
589+ inputClassName="rounded-none bg-transparent text-sm"
590+ placeholder="Search refs..."
591+ showTrigger={false}
592+ size="sm"
593+ unstyled
594+ value={baseRefQuery}
595+ onChange={(event) => setBaseRefQuery(event.target.value)}
596+ />
561597 </div>
562598 </div>
563− </TooltipTrigger>
564− <TooltipPopup side="top">{summary.turnId}</TooltipPopup>
565− </Tooltip>
566− ))}
567− </div>
599+ <div className="grid shrink-0 grid-cols-[1rem_minmax(0,1fr)] items-center gap-2 border-b border-border/70 ps-3 pe-6.5 pt-2 pb-1.5 font-medium text-[10px] text-muted-foreground uppercase tracking-wide">
600+ <span aria-hidden="true" />
601+ <div className="grid min-w-0 grid-cols-[minmax(0,1fr)_2rem] items-center">
602+ <span>Branch</span>
603+ <span className="text-right">Remote</span>
604+ </div>
605+ </div>
606+ <ComboboxEmpty>No matching refs.</ComboboxEmpty>
607+ <ComboboxList className="max-h-64 min-w-0 overflow-x-hidden">
608+ <ComboboxItem
609+ className="h-8 w-full min-w-0 grid-cols-[1rem_minmax(0,1fr)] py-0"
610+ contentClassName="w-full min-w-0 overflow-hidden"
611+ value={AUTOMATIC_BASE_REF}
612+ >
613+ <span className="block min-w-0 truncate">Automatic</span>
614+ </ComboboxItem>
615+ {baseRefChoices.map((choice) => {
616+ const item = valueForBaseRefChoice(choice);
617+ const hasBoth = choice.local !== null && choice.remote !== null;
618+ const useRemote = choice.remote?.name === item;
619+ return (
620+ <ComboboxItem
621+ key={choice.id}
622+ className="h-8 w-full min-w-0 grid-cols-[1rem_minmax(0,1fr)] py-0"
623+ contentClassName="w-full min-w-0 overflow-hidden"
624+ value={item}
625+ >
626+ <div className="grid w-full min-w-0 grid-cols-[minmax(0,1fr)_2rem] items-center overflow-hidden">
627+ <span className="block min-w-0 truncate pe-2">{choice.label}</span>
628+ {hasBoth ? (
629+ <div
630+ className="flex justify-end"
631+ onClick={(event) => event.stopPropagation()}
632+ onPointerDown={(event) => event.stopPropagation()}
633+ >
634+ <Switch
635+ aria-label={`Use remote version of ${choice.label}`}
636+ checked={useRemote}
637+ className="[--thumb-size:--spacing(3)]"
638+ onCheckedChange={(checked) => {
639+ const nextRef = checked
640+ ? choice.remote?.name
641+ : choice.local?.name;
642+ if (nextRef) selectBranchBaseRef(nextRef);
643+ }}
644+ />
645+ </div>
646+ ) : choice.remote ? (
647+ <span
648+ className="flex justify-end text-muted-foreground"
649+ title="Remote only"
650+ >
651+ <CheckIcon aria-hidden="true" className="size-3" />
652+ </span>
653+ ) : null}
654+ </div>
655+ </ComboboxItem>
656+ );
657+ })}
658+ </ComboboxList>
659+ </ComboboxPopup>
660+ </Combobox>
661+ </div>
662+ )}
568663 </div>
569664 <div className="flex shrink-0 items-center gap-1 [-webkit-app-region:no-drag]">
570665 <ToggleGroup
@@ -644,24 +739,35 @@ export default function DiffPanel({ mode = "inline", composerDraftTarget }: Diff · 73 unmodified lines
644739 <div className="flex flex-1 items-center justify-center px-5 text-center text-xs text-muted-foreground/70">
645740 Turn diffs are unavailable because this project is not a git repository.
646741 </div>
647− ) : orderedTurnDiffSummaries.length === 0 ? (
742+ ) : selectedTurnId !== null && orderedTurnDiffSummaries.length === 0 ? (
648743 <div className="flex flex-1 items-center justify-center px-5 text-center text-xs text-muted-foreground/70">
649744 No completed turns yet.
650745 </div>
651746 ) : (
652747 <>
653− <div
654− ref={patchViewportRef}
655− className="diff-panel-viewport min-h-0 min-w-0 flex-1 overflow-hidden"
656− >
657− {checkpointDiffError && !renderablePatch && (
748+ <div className="diff-panel-viewport flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
749+ {isSelectedPatchTruncated && (
750+ <p className="shrink-0 border-b border-border/70 bg-muted/40 px-3 py-1.5 text-[11px] text-muted-foreground">
751+ This diff was truncated because it exceeded the preview limit. The changes shown are
752+ incomplete.
753+ </p>
754+ )}
755+ {selectedPatchError && !renderablePatch && (
658756 <div className="px-3">
659− <p className="mb-2 text-[11px] text-red-500/80">{checkpointDiffError}</p>
757+ <p className="mb-2 text-[11px] text-red-500/80">{selectedPatchError}</p>
660758 </div>
661759 )}
662760 {!renderablePatch ? (
663− isLoadingCheckpointDiff ? (
664− <DiffPanelLoadingState label="Loading checkpoint diff..." />
761+ isLoadingSelectedPatch ? (
762+ <DiffPanelLoadingState
763+ label={
764+ selectedTurn
765+ ? "Loading checkpoint diff..."
766+ : selectedGitScope === "unstaged"
767+ ? "Loading working tree diff..."
768+ : "Loading branch diff..."
769+ }
770+ />
665771 ) : (
666772 <div className="flex h-full items-center justify-center px-3 py-2 text-xs text-muted-foreground/70">
667773 <p>
@@ -672,88 +778,73 @@ export default function DiffPanel({ mode = "inline", composerDraftTarget }: Diff · 4 unmodified lines
672778 </div>
673779 )
674780 ) : renderablePatch.kind === "files" ? (
675− <Virtualizer
676− className="diff-render-surface h-full min-h-0 overflow-auto px-2 pb-2"
677− config={{
678− overscrollSize: 600,
679− intersectionObserverMargin: 1200,
781+ <div
782+ className="min-h-0 flex-1"
783+ onClickCapture={(event) => {
784+ const composedPath = event.nativeEvent.composedPath?.() ?? [];
785+ const title = composedPath.find(
786+ (node): node is HTMLElement =>
787+ node instanceof HTMLElement && node.hasAttribute("data-title"),
788+ );
789+ const filePath = title?.textContent?.trim();
790+ if (filePath) openDiffFile(filePath);
680791 }}
681792 >
682− {renderableFiles.map((fileDiff) => {
683− const filePath = resolveFileDiffPath(fileDiff);
684− const fileKey = buildFileDiffRenderKey(fileDiff);
685− const themedFileKey = `${fileKey}:${resolvedTheme}`;
686− const collapsed = collapsedDiffFileKeys.has(fileKey);
687− return (
688− <div
689− key={themedFileKey}
690− data-diff-file-path={filePath}
691− className="diff-render-file group/diff-file mb-2 rounded-md first:mt-2 last:mb-0"
692− onClickCapture={(event) => {
693− const nativeEvent = event.nativeEvent as MouseEvent;
694− const composedPath = nativeEvent.composedPath?.() ?? [];
695− const clickedHeader = composedPath.some((node) => {
696− if (!(node instanceof Element)) return false;
697− return node.hasAttribute("data-title");
698− });
699− if (!clickedHeader) return;
700− openDiffFile(filePath);
701− }}
702− >
703− <AnnotatableFileDiff
704− fileDiff={fileDiff}
705− filePath={filePath}
706− sectionId={reviewSectionId}
707− sectionTitle={reviewSectionTitle}
708− composerDraftTarget={composerDraftTarget}
709− renderHeaderPrefix={() => (
710− <Tooltip>
711− <TooltipTrigger
712− render={
713− <button
714− type="button"
715− className={cn(
716− "inline-flex size-5 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 bg-transparent p-0 transition-colors hover:bg-foreground/10 focus-visible:outline-hidden",
717− getDiffCollapseIconClassName(fileDiff),
718− )}
719− aria-label={
720− collapsed ? `Expand ${filePath}` : `Collapse ${filePath}`
721− }
722− aria-expanded={!collapsed}
723− onClick={(event) => {
724− event.stopPropagation();
725− toggleDiffFileCollapsed(fileKey);
726− }}
727− />
728− }
729− >
730− {collapsed ? (
731− <ChevronRightIcon className="size-4" />
732− ) : (
733− <ChevronDownIcon className="size-4" />
793+ <AnnotatableCodeView
794+ viewerRef={codeViewRef}
795+ key={collapseScopeKey ?? reviewSectionId}
796+ className="diff-render-surface h-full min-h-0 overflow-auto"
797+ files={codeViewFiles}
798+ sectionId={reviewSectionId}
799+ sectionTitle={reviewSectionTitle}
800+ composerDraftTarget={composerDraftTarget}
801+ renderHeaderPrefix={(fileDiff, fileKey, collapsed) => {
802+ const filePath = resolveFileDiffPath(fileDiff);
803+ return (
804+ <Tooltip>
805+ <TooltipTrigger
806+ render={
807+ <button
808+ type="button"
809+ className={cn(
810+ "inline-flex size-5 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 bg-transparent p-0 transition-colors hover:bg-foreground/10 focus-visible:outline-hidden",
811+ getDiffCollapseIconClassName(fileDiff),
734812 )}
735− </TooltipTrigger>
736− <TooltipPopup side="top">
737− {collapsed ? "Expand diff" : "Collapse diff"}
738− </TooltipPopup>
739− </Tooltip>
740− )}
741− options={{
742− collapsed,
743− diffStyle: diffRenderMode === "split" ? "split" : "unified",
744− lineDiffType: "none",
745− overflow: diffWordWrap ? "wrap" : "scroll",
746− theme: resolveDiffThemeName(resolvedTheme),
747− themeType: resolvedTheme as DiffThemeType,
748− unsafeCSS: DIFF_PANEL_UNSAFE_CSS,
749− }}
750− />
751− </div>
752− );
753− })}
754− </Virtualizer>
813+ aria-label={collapsed ? `Expand ${filePath}` : `Collapse ${filePath}`}
814+ aria-expanded={!collapsed}
815+ onClick={(event) => {
816+ event.stopPropagation();
817+ toggleDiffFileCollapsed(fileKey);
818+ }}
819+ />
820+ }
821+ >
822+ {collapsed ? (
823+ <ChevronRightIcon className="size-4" />
824+ ) : (
825+ <ChevronDownIcon className="size-4" />
826+ )}
827+ </TooltipTrigger>
828+ <TooltipPopup side="top">
829+ {collapsed ? "Expand diff" : "Collapse diff"}
830+ </TooltipPopup>
831+ </Tooltip>
832+ );
833+ }}
834+ options={{
835+ diffStyle: diffRenderMode === "split" ? "split" : "unified",
836+ lineDiffType: "none",
837+ overflow: diffWordWrap ? "wrap" : "scroll",
838+ theme: resolveDiffThemeName(resolvedTheme),
839+ themeType: resolvedTheme as DiffThemeType,
840+ unsafeCSS: DIFF_PANEL_UNSAFE_CSS,
841+ stickyHeaders: true,
842+ layout: { paddingTop: 8, paddingBottom: 8, gap: 8 },
843+ }}
844+ />
845+ </div>
755846 ) : (
756− <div className="h-full overflow-auto p-2">
847+ <div className="min-h-0 flex-1 overflow-auto p-2">
757848 <div className="space-y-2">
758849 <p className="text-[11px] text-muted-foreground/75">{renderablePatch.reason}</p>
759850 <pre
apps/web/src/components/DiffPanelShell.tsx+2−8
@@ -48,14 +48,8 @@ export function DiffPanelShell(props: { · 47 unmodified lines
4848 export function DiffPanelHeaderSkeleton() {
4949 return (
5050 <>
51− <div className="relative min-w-0 flex-1">
52− <Skeleton className="absolute left-0 top-1/2 size-6 -translate-y-1/2 rounded-md border border-border/50" />
53− <Skeleton className="absolute right-0 top-1/2 size-6 -translate-y-1/2 rounded-md border border-border/50" />
54− <div className="flex gap-1 overflow-hidden px-8 py-0.5">
55− <Skeleton className="h-6 w-16 shrink-0 rounded-md" />
56− <Skeleton className="h-6 w-24 shrink-0 rounded-md" />
57− <Skeleton className="h-6 w-24 shrink-0 rounded-md max-sm:hidden" />
58− </div>
51+ <div className="min-w-0 flex-1">
52+ <Skeleton className="h-8 w-32 rounded-lg" />
5953 </div>
6054 <div className="flex shrink-0 gap-1">
6155 <Skeleton className="size-7 rounded-md" />
apps/web/src/components/diffs/AnnotatableFileDiff.tsx+209−2
@@ -1,14 +1,23 @@
11 import type {
22 AnnotationSide,
3+ CodeViewDiffItem,
4+ CodeViewItem,
35 DiffLineAnnotation,
46 FileDiffMetadata,
57 SelectedLineRange,
68 } from "@pierre/diffs";
7−import { FileDiff, type FileDiffProps } from "@pierre/diffs/react";
9+import {
10+ CodeView,
11+ type CodeViewHandle,
12+ type CodeViewProps,
13+ FileDiff,
14+ type FileDiffProps,
15+} from "@pierre/diffs/react";
816 import type { ScopedThreadRef } from "@t3tools/contracts";
9−import { useCallback, useMemo, useState, type ReactNode } from "react";
17+import { useCallback, useMemo, useState, type ReactNode, type Ref } from "react";
1018
1119 import { type DraftId, useComposerDraftStore } from "~/composerDraftStore";
20+import { fnv1a32 } from "~/lib/diffRendering";
1221 import {
1322 buildDiffReviewComment,
1423 restoreDiffReviewCommentRange,
@@ -31,6 +40,7 @@ interface DiffCommentAnnotationGroup { · 16 unmodified lines
3140 }
3241
3342 type DiffCommentLineAnnotation = DiffLineAnnotation<DiffCommentAnnotationGroup>;
43+export type AnnotatableCodeViewHandle = CodeViewHandle<DiffCommentAnnotationGroup>;
3444 const EMPTY_REVIEW_COMMENTS: ReadonlyArray<ReviewCommentContext> = [];
3545
3646 function annotationSide(range: SelectedLineRange): AnnotationSide {
@@ -237,3 +247,200 @@ export function AnnotatableFileDiff({ · 200 unmodified lines
237247 />
238248 );
239249 }
250+
251+interface AnnotatableCodeViewProps {
252+ files: ReadonlyArray<{
253+ fileDiff: FileDiffMetadata;
254+ filePath: string;
255+ fileKey: string;
256+ collapsed: boolean;
257+ }>;
258+ sectionId: string;
259+ sectionTitle: string;
260+ composerDraftTarget: ScopedThreadRef | DraftId;
261+ options: NonNullable<CodeViewProps<DiffCommentAnnotationGroup>["options"]>;
262+ viewerRef?: Ref<AnnotatableCodeViewHandle>;
263+ className?: string;
264+ renderHeaderPrefix: (
265+ fileDiff: FileDiffMetadata,
266+ fileKey: string,
267+ collapsed: boolean,
268+ ) => ReactNode;
269+}
270+
271+interface DiffSelectionContext {
272+ item: CodeViewItem<DiffCommentAnnotationGroup>;
273+}
274+
275+export function AnnotatableCodeView({
276+ files,
277+ sectionId,
278+ sectionTitle,
279+ composerDraftTarget,
280+ options,
281+ viewerRef,
282+ className,
283+ renderHeaderPrefix,
284+}: AnnotatableCodeViewProps) {
285+ const addReviewComment = useComposerDraftStore((store) => store.addReviewComment);
286+ const removeReviewComment = useComposerDraftStore((store) => store.removeReviewComment);
287+ const reviewComments = useComposerDraftStore(
288+ (store) => store.getComposerDraft(composerDraftTarget)?.reviewComments ?? EMPTY_REVIEW_COMMENTS,
289+ );
290+ const [selectedLines, setSelectedLines] = useState<{
291+ id: string;
292+ range: SelectedLineRange;
293+ } | null>(null);
294+ const [draft, setDraft] = useState<{
295+ fileKey: string;
296+ annotation: DiffCommentLineAnnotation;
297+ } | null>(null);
298+
299+ const filesByKey = useMemo(() => new Map(files.map((file) => [file.fileKey, file])), [files]);
300+ const items = useMemo<CodeViewDiffItem<DiffCommentAnnotationGroup>[]>(
301+ () =>
302+ files.map(({ fileDiff, filePath, fileKey, collapsed }) => {
303+ const persisted = reviewComments
304+ .filter(
305+ (comment) =>
306+ comment.sectionId === sectionId &&
307+ comment.filePath === filePath &&
308+ (comment.fenceLanguage ?? "diff") === "diff",
309+ )
310+ .reduce<DiffCommentLineAnnotation[]>((annotations, comment) => {
311+ const range = restoreDiffReviewCommentRange(fileDiff, comment);
312+ if (!range) return annotations;
313+ return appendAnnotationEntry(annotations, range, {
314+ id: comment.id,
315+ kind: "comment",
316+ range,
317+ rangeLabel: comment.rangeLabel,
318+ text: comment.text,
319+ });
320+ }, []);
321+ const annotations =
322+ draft?.fileKey === fileKey ? [...persisted, draft.annotation] : persisted;
323+ return {
324+ id: fileKey,
325+ type: "diff",
326+ fileDiff,
327+ annotations,
328+ collapsed,
329+ version: fnv1a32(
330+ `${collapsed ? "1" : "0"}:${annotations
331+ .flatMap((annotation) =>
332+ annotation.metadata.entries.map(
333+ (entry) => `${entry.id}:${entry.rangeLabel}:${entry.text}`,
334+ ),
335+ )
336+ .join(":")}`,
337+ ),
338+ };
339+ }),
340+ [draft, files, reviewComments, sectionId],
341+ );
342+
343+ const removeEntry = useCallback(
344+ (entryId: string) => {
345+ setSelectedLines(null);
346+ if (draft?.annotation.metadata.entries.some((entry) => entry.id === entryId)) {
347+ setDraft(null);
348+ } else {
349+ removeReviewComment(composerDraftTarget, entryId);
350+ }
351+ },
352+ [composerDraftTarget, draft, removeReviewComment],
353+ );
354+
355+ const submitEntry = useCallback(
356+ (entryId: string, text: string) => {
357+ const entry = draft?.annotation.metadata.entries.find(
358+ (candidate) => candidate.id === entryId,
359+ );
360+ const file = draft ? filesByKey.get(draft.fileKey) : undefined;
361+ if (!entry || !file) return;
362+ const comment = buildDiffReviewComment({
363+ id: entry.id,
364+ sectionId,
365+ sectionTitle,
366+ filePath: file.filePath,
367+ fileDiff: file.fileDiff,
368+ range: entry.range,
369+ text,
370+ });
371+ if (comment) addReviewComment(composerDraftTarget, comment);
372+ setSelectedLines(null);
373+ setDraft(null);
374+ },
375+ [addReviewComment, composerDraftTarget, draft, filesByKey, sectionId, sectionTitle],
376+ );
377+
378+ const beginComment = useCallback(
379+ (range: SelectedLineRange | null, context: DiffSelectionContext) => {
380+ if (!range) return;
381+ const item = context.item;
382+ if (item.type !== "diff") return;
383+ const file = filesByKey.get(item.id);
384+ if (!file) return;
385+ const id = nextFileCommentId();
386+ const comment = buildDiffReviewComment({
387+ id,
388+ sectionId,
389+ sectionTitle,
390+ filePath: file.filePath,
391+ fileDiff: file.fileDiff,
392+ range,
393+ text: "",
394+ });
395+ if (!comment) return;
396+ setDraft({
397+ fileKey: item.id,
398+ annotation: {
399+ side: annotationSide(range),
400+ lineNumber: range.end,
401+ metadata: {
402+ entries: [{ id, kind: "draft", range, rangeLabel: comment.rangeLabel, text: "" }],
403+ },
404+ },
405+ });
406+ },
407+ [filesByKey, sectionId, sectionTitle],
408+ );
409+
410+ const hasOpenComment = draft !== null;
411+ return (
412+ <CodeView<DiffCommentAnnotationGroup>
413+ {...(viewerRef ? { ref: viewerRef } : {})}
414+ {...(className ? { className } : {})}
415+ items={items}
416+ selectedLines={selectedLines}
417+ onSelectedLinesChange={setSelectedLines}
418+ options={{
419+ ...options,
420+ enableGutterUtility: !hasOpenComment,
421+ enableLineSelection: !hasOpenComment,
422+ onLineSelectionEnd: beginComment,
423+ }}
424+ renderHeaderPrefix={(item) =>
425+ item.type === "diff"
426+ ? renderHeaderPrefix(item.fileDiff, item.id, item.collapsed === true)
427+ : null
428+ }
429+ renderAnnotation={(annotation) => (
430+ <div className="py-1">
431+ {annotation.metadata.entries.map((entry) => (
432+ <LocalCommentAnnotation
433+ key={entry.id}
434+ kind={entry.kind}
435+ rangeLabel={entry.rangeLabel}
436+ text={entry.text}
437+ onCancel={() => removeEntry(entry.id)}
438+ onComment={(text) => submitEntry(entry.id, text)}
439+ onDelete={() => removeEntry(entry.id)}
440+ />
441+ ))}
442+ </div>
443+ )}
444+ />
445+ );
446+}
apps/web/src/components/preview/PreviewAutomationOwner.test.ts+2−2
@@ -3,11 +3,11 @@ import { describe, expect, it } from "vite-plus/test"; · 2 unmodified lines
33 import { observeAutomationOwnerConnectedGeneration } from "./PreviewAutomationOwner";
44
55 describe("observeAutomationOwnerConnectedGeneration", () => {
6− it("re-reports ownership only after a later transport generation connects", () => {
6+ it("reports ownership when the initial transport generation connects", () => {
77 const initial = observeAutomationOwnerConnectedGeneration(null, 1);
88 expect(initial).toEqual({
99 nextGeneration: 1,
10− shouldReport: false,
10+ shouldReport: true,
1111 });
1212
1313 const disconnected = observeAutomationOwnerConnectedGeneration(initial.nextGeneration, null);
apps/web/src/components/preview/PreviewAutomationOwner.tsx+86−99
@@ -1,15 +1,16 @@
11 "use client";
22
3+import { useAtomValue } from "@effect/atom-react";
34 import { squashAtomCommandFailure } from "@t3tools/client-runtime/state/runtime";
45 import type {
56 PreviewAutomationNavigateInput,
67 PreviewAutomationOpenInput,
78 PreviewAutomationRequest,
8− PreviewAutomationResponse,
9+ PreviewAutomationOwner as PreviewAutomationOwnerState,
910 PreviewAutomationStatus,
1011 ScopedThreadRef,
1112 } from "@t3tools/contracts";
12−import { useCallback, useEffect, useId, useRef } from "react";
13+import { useCallback, useEffect, useEffectEvent, useId, useMemo, useRef, useState } from "react";
1314
1415 import {
1516 applyPreviewServerSnapshot,
@@ -20,11 +21,14 @@ import { useRightPanelStore } from "~/rightPanelStore"; · 4 unmodified lines
2021 import { resolveBrowserNavigationTarget } from "~/browser/browserTargetResolver";
2122 import { startBrowserRecording, stopBrowserRecording } from "~/browser/browserRecording";
2223 import { previewEnvironment } from "~/state/preview";
23−import { useEnvironmentQuery } from "~/state/query";
2424 import { useEnvironmentConnectionState } from "~/state/environments";
2525 import { useAtomCommand } from "~/state/use-atom-command";
2626
2727 import { previewBridge } from "./previewBridge";
28+import {
29+ createLatestPreviewAutomationRequestHandler,
30+ createPreviewAutomationRequestConsumerAtom,
31+} from "./previewAutomationRequestConsumer";
2832
2933 export function observeAutomationOwnerConnectedGeneration(
3034 previousGeneration: number | null,
@@ -41,7 +45,7 @@ export function observeAutomationOwnerConnectedGeneration( · 10 unmodified lines
4145 }
4246 return {
4347 nextGeneration: connectedGeneration,
44− shouldReport: previousGeneration !== null && previousGeneration !== connectedGeneration,
48+ shouldReport: previousGeneration !== connectedGeneration,
4549 };
4650 }
4751
@@ -109,24 +113,10 @@ const currentStatus = async ( · 61 unmodified lines
109113 };
110114 };
111115
112−const serializeError = (error: unknown): NonNullable<PreviewAutomationResponse["error"]> => {
113− if (error instanceof Error) {
114− const detail =
115− "detail" in error && (error as { detail?: unknown }).detail !== undefined
116− ? (error as { detail?: unknown }).detail
117− : undefined;
118− return {
119− _tag: error.name.startsWith("PreviewAutomation")
120− ? error.name
121− : "PreviewAutomationExecutionError",
122− message: error.message,
123− ...(detail === undefined ? {} : { detail }),
124− };
125− }
126− return {
127− _tag: "PreviewAutomationExecutionError",
128− message: String(error),
129− };
116+const previewTabNotFoundError = (): Error => {
117+ const error = new Error("Preview tab is not initialized.");
118+ error.name = "PreviewAutomationTabNotFoundError";
119+ return error;
130120 };
131121
132122 export function PreviewAutomationOwner(props: {
@@ -135,12 +125,22 @@ export function PreviewAutomationOwner(props: { · 2 unmodified lines
135125 }) {
136126 const { threadRef, visible } = props;
137127 const automationClientId = useId();
138− const automationRequests = useEnvironmentQuery(
139− previewEnvironment.automationRequests({
128+ const initialAutomationOwner = useMemo<PreviewAutomationOwnerState>(
129+ () => ({
130+ clientId: automationClientId,
140131 environmentId: threadRef.environmentId,
141− input: { clientId: automationClientId },
132+ threadId: threadRef.threadId,
133+ tabId: null,
134+ visible: false,
135+ supportsAutomation: Boolean(previewBridge?.automation),
136+ focusedAt: new Date().toISOString(),
142137 }),
138+ [automationClientId, threadRef.environmentId, threadRef.threadId],
143139 );
140+ const automationRequestsAtom = previewEnvironment.automationRequests({
141+ environmentId: threadRef.environmentId,
142+ input: initialAutomationOwner,
143+ });
144144 const connectionState = useEnvironmentConnectionState(threadRef.environmentId).data;
145145 const connectedGeneration =
146146 connectionState?.phase === "connected" ? connectionState.generation : null;
@@ -159,13 +159,24 @@ export function PreviewAutomationOwner(props: { · 12 unmodified lines
159159 previewEnvironment.clearAutomationOwner,
160160 "preview automation owner clear",
161161 );
162− const ownerStateRef = useRef({ threadRef, visible });
163162 const connectedGenerationRef = useRef<number | null>(null);
164− const handlerRef = useRef<(request: PreviewAutomationRequest) => Promise<unknown>>(
165− async () => undefined,
166− );
163+ const reportCurrentAutomationOwner = useEffectEvent(() => {
164+ const state = readThreadPreviewState(threadRef);
165+ return reportAutomationOwner({
166+ environmentId: threadRef.environmentId,
167+ input: {
168+ clientId: automationClientId,
169+ environmentId: threadRef.environmentId,
170+ threadId: threadRef.threadId,
171+ tabId: state.snapshot?.tabId ?? null,
172+ visible,
173+ supportsAutomation: Boolean(previewBridge?.automation),
174+ focusedAt: new Date().toISOString(),
175+ },
176+ });
177+ });
167178 useEffect(() => {
168− ownerStateRef.current = { threadRef, visible };
179+ void reportCurrentAutomationOwner();
169180 }, [threadRef, visible]);
170181
171182 const handleRequest = useCallback(
@@ -208,7 +219,7 @@ export function PreviewAutomationOwner(props: { · 36 unmodified lines
208219 return currentStatus(threadRef, input.show ?? true);
209220 }
210221 case "navigate": {
211− if (!previewBridge || !tabId) throw new Error("Preview tab is not initialized.");
222+ if (!previewBridge || !tabId) throw previewTabNotFoundError();
212223 const input = request.input as PreviewAutomationNavigateInput;
213224 const resolution = resolveBrowserNavigationTarget(
214225 threadRef.environmentId,
@@ -223,46 +234,46 @@ export function PreviewAutomationOwner(props: { · 8 unmodified lines
223234 return currentStatus(threadRef, visible);
224235 }
225236 case "snapshot":
226− if (!previewBridge || !tabId) throw new Error("Preview tab is not initialized.");
237+ if (!previewBridge || !tabId) throw previewTabNotFoundError();
227238 return previewBridge.automation.snapshot(tabId);
228239 case "click":
229− if (!previewBridge || !tabId) throw new Error("Preview tab is not initialized.");
240+ if (!previewBridge || !tabId) throw previewTabNotFoundError();
230241 return previewBridge.automation.click(
231242 tabId,
232243 request.input as Parameters<typeof previewBridge.automation.click>[1],
233244 );
234245 case "type":
235− if (!previewBridge || !tabId) throw new Error("Preview tab is not initialized.");
246+ if (!previewBridge || !tabId) throw previewTabNotFoundError();
236247 return previewBridge.automation.type(
237248 tabId,
238249 request.input as Parameters<typeof previewBridge.automation.type>[1],
239250 );
240251 case "press":
241− if (!previewBridge || !tabId) throw new Error("Preview tab is not initialized.");
252+ if (!previewBridge || !tabId) throw previewTabNotFoundError();
242253 return previewBridge.automation.press(
243254 tabId,
244255 request.input as Parameters<typeof previewBridge.automation.press>[1],
245256 );
246257 case "scroll":
247− if (!previewBridge || !tabId) throw new Error("Preview tab is not initialized.");
258+ if (!previewBridge || !tabId) throw previewTabNotFoundError();
248259 return previewBridge.automation.scroll(
249260 tabId,
250261 request.input as Parameters<typeof previewBridge.automation.scroll>[1],
251262 );
252263 case "evaluate":
253− if (!previewBridge || !tabId) throw new Error("Preview tab is not initialized.");
264+ if (!previewBridge || !tabId) throw previewTabNotFoundError();
254265 return previewBridge.automation.evaluate(
255266 tabId,
256267 request.input as Parameters<typeof previewBridge.automation.evaluate>[1],
257268 );
258269 case "waitFor":
259− if (!previewBridge || !tabId) throw new Error("Preview tab is not initialized.");
270+ if (!previewBridge || !tabId) throw previewTabNotFoundError();
260271 return previewBridge.automation.waitFor(
261272 tabId,
262273 request.input as Parameters<typeof previewBridge.automation.waitFor>[1],
263274 );
264275 case "recordingStart": {
265− if (!tabId) throw new Error("Preview tab is not initialized.");
276+ if (!tabId) throw previewTabNotFoundError();
266277 const startedAt = await startBrowserRecording(tabId);
267278 return {
268279 tabId,
@@ -271,7 +282,7 @@ export function PreviewAutomationOwner(props: { · 2 unmodified lines
271282 };
272283 }
273284 case "recordingStop": {
274− if (!tabId) throw new Error("Preview tab is not initialized.");
285+ if (!tabId) throw previewTabNotFoundError();
275286 const artifact = await stopBrowserRecording(tabId);
276287 if (!artifact) throw new Error("No active recording exists for this preview tab.");
277288 return artifact;
@@ -280,34 +291,34 @@ export function PreviewAutomationOwner(props: { · 2 unmodified lines
280291 },
281292 [open, threadRef, visible],
282293 );
294+ const [requestHandler] = useState(() =>
295+ createLatestPreviewAutomationRequestHandler(handleRequest),
296+ );
283297 useEffect(() => {
284− handlerRef.current = handleRequest;
285− }, [handleRequest]);
298+ requestHandler.set(handleRequest);
299+ }, [handleRequest, requestHandler]);
286300
287− useEffect(() => {
288− const request = automationRequests.data;
289− if (!request) return;
290− void handlerRef.current(request).then(
291− (result) =>
292− respondToAutomation({
293− environmentId: threadRef.environmentId,
294− input: {
295− requestId: request.requestId,
296− ok: true,
297− ...(result === undefined ? {} : { result }),
298− },
299− }),
300− (error) =>
301− respondToAutomation({
302− environmentId: threadRef.environmentId,
303− input: {
304− requestId: request.requestId,
305− ok: false,
306− error: serializeError(error),
307− },
308− }),
309− );
310− }, [automationRequests.data, respondToAutomation, threadRef.environmentId]);
301+ const automationRequestConsumerAtom = useMemo(
302+ () =>
303+ createPreviewAutomationRequestConsumerAtom({
304+ requestsAtom: automationRequestsAtom,
305+ handleRequest: requestHandler.handle,
306+ respond: (response) =>
307+ respondToAutomation({
308+ environmentId: threadRef.environmentId,
309+ input: response,
310+ }),
311+ label: `preview:automation-request-consumer:${automationClientId}`,
312+ }),
313+ [
314+ automationClientId,
315+ automationRequestsAtom,
316+ requestHandler,
317+ respondToAutomation,
318+ threadRef.environmentId,
319+ ],
320+ );
321+ useAtomValue(automationRequestConsumerAtom);
311322
312323 useEffect(() => {
313324 const observation = observeAutomationOwnerConnectedGeneration(
@@ -317,39 +328,11 @@ export function PreviewAutomationOwner(props: { · 3 unmodified lines
317328 connectedGenerationRef.current = observation.nextGeneration;
318329 if (!observation.shouldReport) return;
319330
320− const ownerState = ownerStateRef.current;
321− const state = readThreadPreviewState(ownerState.threadRef);
322− void reportAutomationOwner({
323− environmentId: ownerState.threadRef.environmentId,
324− input: {
325− clientId: automationClientId,
326− environmentId: ownerState.threadRef.environmentId,
327− threadId: ownerState.threadRef.threadId,
328− tabId: state.snapshot?.tabId ?? null,
329− visible: ownerState.visible,
330− supportsAutomation: Boolean(previewBridge?.automation),
331− focusedAt: new Date().toISOString(),
332− },
333− });
334− }, [automationClientId, connectedGeneration, reportAutomationOwner]);
331+ void reportCurrentAutomationOwner();
332+ }, [connectedGeneration]);
335333
336334 useEffect(() => {
337− const report = () => {
338− const state = readThreadPreviewState(threadRef);
339− void reportAutomationOwner({
340− environmentId: threadRef.environmentId,
341− input: {
342− clientId: automationClientId,
343− environmentId: threadRef.environmentId,
344− threadId: threadRef.threadId,
345− tabId: state.snapshot?.tabId ?? null,
346− visible,
347− supportsAutomation: Boolean(previewBridge?.automation),
348− focusedAt: new Date().toISOString(),
349− },
350− });
351− };
352− report();
335+ const report = () => void reportCurrentAutomationOwner();
353336 window.addEventListener("focus", report);
354337 const unsubscribe = subscribeThreadPreviewState(threadRef, (state, previous) => {
355338 if (state.snapshot?.tabId !== previous.snapshot?.tabId) {
@@ -361,10 +344,14 @@ export function PreviewAutomationOwner(props: { · 5 unmodified lines
361344 unsubscribe();
362345 void clearAutomationOwner({
363346 environmentId: threadRef.environmentId,
364− input: { clientId: automationClientId },
347+ input: {
348+ clientId: automationClientId,
349+ environmentId: threadRef.environmentId,
350+ threadId: threadRef.threadId,
351+ },
365352 });
366353 };
367− }, [automationClientId, clearAutomationOwner, reportAutomationOwner, threadRef, visible]);
354+ }, [automationClientId, clearAutomationOwner, threadRef]);
368355
369356 return null;
370357 }
apps/web/src/components/preview/previewAutomationRequestConsumer.test.ts+81−0
@@ -0,0 +1,81 @@
1+import type { PreviewAutomationRequest, PreviewAutomationResponse } from "@t3tools/contracts";
2+import { ThreadId } from "@t3tools/contracts";
3+import { AsyncResult, Atom, AtomRegistry } from "effect/unstable/reactivity";
4+import { describe, expect, it, vi } from "vite-plus/test";
5+
6+import {
7+ createPreviewAutomationRequestConsumerAtom,
8+ serializePreviewAutomationError,
9+} from "./previewAutomationRequestConsumer";
10+
11+const request = (requestId: string): PreviewAutomationRequest => ({
12+ requestId,
13+ threadId: ThreadId.make("thread-1"),
14+ operation: "status",
15+ input: {},
16+ timeoutMs: 15_000,
17+});
18+
19+describe("previewAutomationRequestConsumer", () => {
20+ it("consumes every request emitted before React can render", async () => {
21+ const requestsAtom = Atom.make<AsyncResult.AsyncResult<PreviewAutomationRequest, Error>>(
22+ AsyncResult.initial<PreviewAutomationRequest, Error>(false),
23+ );
24+ const handleRequest = vi.fn(async (value: PreviewAutomationRequest) => ({
25+ requestId: value.requestId,
26+ }));
27+ const responses: PreviewAutomationResponse[] = [];
28+ const respond = vi.fn(async (response: PreviewAutomationResponse) => {
29+ responses.push(response);
30+ });
31+ const consumerAtom = createPreviewAutomationRequestConsumerAtom({
32+ requestsAtom,
33+ handleRequest,
34+ respond,
35+ label: "test:preview-automation-consumer",
36+ });
37+ const registry = AtomRegistry.make();
38+ registry.mount(consumerAtom);
39+
40+ registry.set(requestsAtom, AsyncResult.success(request("request-1")));
41+ registry.set(requestsAtom, AsyncResult.success(request("request-2")));
42+
43+ await vi.waitFor(() => expect(respond).toHaveBeenCalledTimes(2));
44+ expect(handleRequest.mock.calls.map(([value]) => value.requestId)).toEqual([
45+ "request-1",
46+ "request-2",
47+ ]);
48+ expect(responses.map((response) => response.requestId)).toEqual(["request-1", "request-2"]);
49+ registry.dispose();
50+ });
51+
52+ it("consumes a request that arrived immediately before the consumer mounted", async () => {
53+ const requestsAtom = Atom.make(
54+ AsyncResult.success<PreviewAutomationRequest, Error>(request("request-ready")),
55+ );
56+ const respond = vi.fn(async (_response: PreviewAutomationResponse) => undefined);
57+ const consumerAtom = createPreviewAutomationRequestConsumerAtom({
58+ requestsAtom,
59+ handleRequest: async () => undefined,
60+ respond,
61+ label: "test:preview-automation-initial-request",
62+ });
63+ const registry = AtomRegistry.make();
64+
65+ registry.mount(consumerAtom);
66+
67+ await vi.waitFor(() => expect(respond).toHaveBeenCalledTimes(1));
68+ expect(respond).toHaveBeenCalledWith({ requestId: "request-ready", ok: true });
69+ registry.dispose();
70+ });
71+
72+ it("preserves typed automation errors in responses", () => {
73+ const error = new Error("No preview tab");
74+ error.name = "PreviewAutomationTabNotFoundError";
75+
76+ expect(serializePreviewAutomationError(error)).toEqual({
77+ _tag: "PreviewAutomationTabNotFoundError",
78+ message: "No preview tab",
79+ });
80+ });
81+});
apps/web/src/components/preview/previewAutomationRequestConsumer.ts+83−0
@@ -0,0 +1,83 @@
1+import type { PreviewAutomationRequest, PreviewAutomationResponse } from "@t3tools/contracts";
2+import { AsyncResult, Atom } from "effect/unstable/reactivity";
3+
4+type AutomationRequestResult<E> = AsyncResult.AsyncResult<PreviewAutomationRequest, E>;
5+type AutomationRequestHandler = (request: PreviewAutomationRequest) => Promise<unknown>;
6+
7+export function createLatestPreviewAutomationRequestHandler(initial: AutomationRequestHandler): {
8+ readonly set: (handler: AutomationRequestHandler) => void;
9+ readonly handle: AutomationRequestHandler;
10+} {
11+ let current = initial;
12+ return {
13+ set: (handler) => {
14+ current = handler;
15+ },
16+ handle: (request) => current(request),
17+ };
18+}
19+
20+export function serializePreviewAutomationError(
21+ error: unknown,
22+): NonNullable<PreviewAutomationResponse["error"]> {
23+ if (error instanceof Error) {
24+ const detail =
25+ "detail" in error && (error as { detail?: unknown }).detail !== undefined
26+ ? (error as { detail?: unknown }).detail
27+ : undefined;
28+ return {
29+ _tag: error.name.startsWith("PreviewAutomation")
30+ ? error.name
31+ : "PreviewAutomationExecutionError",
32+ message: error.message,
33+ ...(detail === undefined ? {} : { detail }),
34+ };
35+ }
36+ return {
37+ _tag: "PreviewAutomationExecutionError",
38+ message: String(error),
39+ };
40+}
41+
42+export function createPreviewAutomationRequestConsumerAtom<E>(options: {
43+ readonly requestsAtom: Atom.Atom<AutomationRequestResult<E>>;
44+ readonly handleRequest: (request: PreviewAutomationRequest) => Promise<unknown>;
45+ readonly respond: (response: PreviewAutomationResponse) => Promise<unknown>;
46+ readonly label: string;
47+}): Atom.Atom<void> {
48+ return Atom.make((get) => {
49+ let disposed = false;
50+ let requestsVersion = 0;
51+
52+ const consume = (result: AutomationRequestResult<E>) => {
53+ if (!AsyncResult.isSuccess(result)) return;
54+ const request = result.value;
55+ void options.handleRequest(request).then(
56+ (value) =>
57+ options.respond({
58+ requestId: request.requestId,
59+ ok: true,
60+ ...(value === undefined ? {} : { result: value }),
61+ }),
62+ (error) =>
63+ options.respond({
64+ requestId: request.requestId,
65+ ok: false,
66+ error: serializePreviewAutomationError(error),
67+ }),
68+ );
69+ };
70+
71+ get.addFinalizer(() => {
72+ disposed = true;
73+ });
74+ const initialRequest = get.once(options.requestsAtom);
75+ get.subscribe(options.requestsAtom, (result) => {
76+ requestsVersion += 1;
77+ consume(result);
78+ });
79+ queueMicrotask(() => {
80+ if (!disposed && requestsVersion === 0) consume(initialRequest);
81+ });
82+ }).pipe(Atom.setIdleTTL(0), Atom.withLabel(options.label));
83+}
apps/web/src/diffPanelStore.test.ts+68−0
@@ -0,0 +1,68 @@
1+import { scopeThreadRef } from "@t3tools/client-runtime/environment";
2+import { EnvironmentId, ThreadId, TurnId } from "@t3tools/contracts";
3+import { beforeEach, describe, expect, it } from "vite-plus/test";
4+
5+import { selectThreadDiffPanelSelection, useDiffPanelStore } from "./diffPanelStore";
6+
7+const THREAD_REF = scopeThreadRef(EnvironmentId.make("environment-1"), ThreadId.make("thread-1"));
8+
9+describe("diffPanelStore", () => {
10+ beforeEach(() => useDiffPanelStore.setState({ byThreadKey: {}, branchBaseRefByThreadKey: {} }));
11+
12+ it("defaults each thread to branch changes with automatic base selection", () => {
13+ expect(
14+ selectThreadDiffPanelSelection(useDiffPanelStore.getState().byThreadKey, THREAD_REF),
15+ ).toEqual({ kind: "branch", baseRef: null });
16+ });
17+
18+ it("clears incompatible selection fields when changing scopes", () => {
19+ const store = useDiffPanelStore.getState();
20+ store.selectTurn(THREAD_REF, TurnId.make("turn-1"), "src/app.ts");
21+ store.selectGitScope(THREAD_REF, "unstaged");
22+
23+ expect(
24+ selectThreadDiffPanelSelection(useDiffPanelStore.getState().byThreadKey, THREAD_REF),
25+ ).toEqual({ kind: "unstaged" });
26+
27+ useDiffPanelStore.getState().selectBranchBaseRef(THREAD_REF, " origin/main ");
28+ expect(
29+ selectThreadDiffPanelSelection(useDiffPanelStore.getState().byThreadKey, THREAD_REF),
30+ ).toEqual({ kind: "branch", baseRef: "origin/main" });
31+ });
32+
33+ it("increments the reveal request when opening the same turn file again", () => {
34+ const turnId = TurnId.make("turn-1");
35+ useDiffPanelStore.getState().selectTurn(THREAD_REF, turnId, "src/app.ts");
36+ useDiffPanelStore.getState().selectTurn(THREAD_REF, turnId, "src/app.ts");
37+
38+ expect(
39+ selectThreadDiffPanelSelection(useDiffPanelStore.getState().byThreadKey, THREAD_REF),
40+ ).toEqual({ kind: "turn", turnId, filePath: "src/app.ts", revealRequestId: 2 });
41+ });
42+
43+ it("restores the selected branch base after visiting another scope", () => {
44+ useDiffPanelStore.getState().selectBranchBaseRef(THREAD_REF, "origin/main");
45+ useDiffPanelStore.getState().selectGitScope(THREAD_REF, "unstaged");
46+ useDiffPanelStore.getState().selectGitScope(THREAD_REF, "branch");
47+
48+ expect(
49+ selectThreadDiffPanelSelection(useDiffPanelStore.getState().byThreadKey, THREAD_REF),
50+ ).toEqual({ kind: "branch", baseRef: "origin/main" });
51+ });
52+
53+ it("reconciles a missing turn selection to the latest available turn", () => {
54+ const missingTurnId = TurnId.make("turn-missing");
55+ const latestTurnId = TurnId.make("turn-latest");
56+ useDiffPanelStore.getState().selectTurn(THREAD_REF, missingTurnId, "src/app.ts");
57+ useDiffPanelStore.getState().reconcileTurnSelection(THREAD_REF, [latestTurnId]);
58+
59+ expect(
60+ selectThreadDiffPanelSelection(useDiffPanelStore.getState().byThreadKey, THREAD_REF),
61+ ).toEqual({
62+ kind: "turn",
63+ turnId: latestTurnId,
64+ filePath: "src/app.ts",
65+ revealRequestId: 1,
66+ });
67+ });
68+});
apps/web/src/diffPanelStore.ts+139−0
@@ -0,0 +1,139 @@
1+import { scopedThreadKey } from "@t3tools/client-runtime/environment";
2+import type { ScopedThreadRef, TurnId } from "@t3tools/contracts";
3+import { create } from "zustand";
4+import { createJSONStorage, persist } from "zustand/middleware";
5+
6+import { resolveStorage } from "./lib/storage";
7+
8+export type DiffPanelSelection =
9+ | { kind: "branch"; baseRef: string | null }
10+ | { kind: "unstaged" }
11+ | { kind: "turn"; turnId: TurnId; filePath: string | null; revealRequestId: number };
12+
13+const DEFAULT_SELECTION: DiffPanelSelection = { kind: "branch", baseRef: null };
14+
15+interface DiffPanelStoreState {
16+ byThreadKey: Record<string, DiffPanelSelection>;
17+ branchBaseRefByThreadKey: Record<string, string | null>;
18+ selectGitScope: (ref: ScopedThreadRef, scope: "branch" | "unstaged") => void;
19+ selectBranchBaseRef: (ref: ScopedThreadRef, baseRef: string | null) => void;
20+ selectTurn: (ref: ScopedThreadRef, turnId: TurnId, filePath?: string) => void;
21+ reconcileTurnSelection: (ref: ScopedThreadRef, availableTurnIds: ReadonlyArray<TurnId>) => void;
22+ removeThread: (ref: ScopedThreadRef) => void;
23+}
24+
25+function normalizeBaseRef(baseRef: string | null): string | null {
26+ const normalized = baseRef?.trim();
27+ return normalized ? normalized : null;
28+}
29+
30+export const useDiffPanelStore = create<DiffPanelStoreState>()(
31+ persist(
32+ (set) => ({
33+ byThreadKey: {},
34+ branchBaseRefByThreadKey: {},
35+ selectGitScope: (ref, scope) =>
36+ set((state) => {
37+ const threadKey = scopedThreadKey(ref);
38+ const previous = state.byThreadKey[threadKey];
39+ const previousBaseRef =
40+ previous?.kind === "branch"
41+ ? previous.baseRef
42+ : (state.branchBaseRefByThreadKey[threadKey] ?? null);
43+ return {
44+ byThreadKey: {
45+ ...state.byThreadKey,
46+ [threadKey]:
47+ scope === "branch"
48+ ? { kind: "branch", baseRef: previousBaseRef }
49+ : { kind: "unstaged" },
50+ },
51+ branchBaseRefByThreadKey:
52+ previous?.kind === "branch"
53+ ? { ...state.branchBaseRefByThreadKey, [threadKey]: previous.baseRef }
54+ : state.branchBaseRefByThreadKey,
55+ };
56+ }),
57+ selectBranchBaseRef: (ref, baseRef) =>
58+ set((state) => {
59+ const threadKey = scopedThreadKey(ref);
60+ const normalizedBaseRef = normalizeBaseRef(baseRef);
61+ return {
62+ byThreadKey: {
63+ ...state.byThreadKey,
64+ [threadKey]: { kind: "branch", baseRef: normalizedBaseRef },
65+ },
66+ branchBaseRefByThreadKey: {
67+ ...state.branchBaseRefByThreadKey,
68+ [threadKey]: normalizedBaseRef,
69+ },
70+ };
71+ }),
72+ selectTurn: (ref, turnId, filePath) =>
73+ set((state) => {
74+ const threadKey = scopedThreadKey(ref);
75+ const previous = state.byThreadKey[threadKey];
76+ return {
77+ byThreadKey: {
78+ ...state.byThreadKey,
79+ [threadKey]: {
80+ kind: "turn",
81+ turnId,
82+ filePath: filePath?.trim() || null,
83+ revealRequestId: previous?.kind === "turn" ? previous.revealRequestId + 1 : 1,
84+ },
85+ },
86+ };
87+ }),
88+ reconcileTurnSelection: (ref, availableTurnIds) =>
89+ set((state) => {
90+ const threadKey = scopedThreadKey(ref);
91+ const previous = state.byThreadKey[threadKey];
92+ const latestTurnId = availableTurnIds[0];
93+ if (
94+ previous?.kind !== "turn" ||
95+ latestTurnId === undefined ||
96+ availableTurnIds.includes(previous.turnId)
97+ ) {
98+ return state;
99+ }
100+ return {
101+ byThreadKey: {
102+ ...state.byThreadKey,
103+ [threadKey]: { ...previous, turnId: latestTurnId },
104+ },
105+ };
106+ }),
107+ removeThread: (ref) =>
108+ set((state) => {
109+ const threadKey = scopedThreadKey(ref);
110+ if (!(threadKey in state.byThreadKey) && !(threadKey in state.branchBaseRefByThreadKey)) {
111+ return state;
112+ }
113+ const { [threadKey]: _removed, ...byThreadKey } = state.byThreadKey;
114+ const { [threadKey]: _removedBaseRef, ...branchBaseRefByThreadKey } =
115+ state.branchBaseRefByThreadKey;
116+ return { byThreadKey, branchBaseRefByThreadKey };
117+ }),
118+ }),
119+ {
120+ name: "t3code:diff-panel-state:v1",
121+ version: 1,
122+ storage: createJSONStorage(() =>
123+ resolveStorage(typeof window !== "undefined" ? window.localStorage : undefined),
124+ ),
125+ partialize: (state) => ({
126+ byThreadKey: state.byThreadKey,
127+ branchBaseRefByThreadKey: state.branchBaseRefByThreadKey,
128+ }),
129+ },
130+ ),
131+);
132+
133+export function selectThreadDiffPanelSelection(
134+ byThreadKey: Record<string, DiffPanelSelection>,
135+ ref: ScopedThreadRef | null | undefined,
136+): DiffPanelSelection {
137+ if (!ref) return DEFAULT_SELECTION;
138+ return byThreadKey[scopedThreadKey(ref)] ?? DEFAULT_SELECTION;
139+}
apps/web/src/diffRouteSearch.test.ts+0−74
@@ -1,74 +0,0 @@
1−import { describe, expect, it } from "vite-plus/test";
2−
3−import { parseDiffRouteSearch } from "./diffRouteSearch";
4−
5−describe("parseDiffRouteSearch", () => {
6− it("parses valid diff search values", () => {
7− const parsed = parseDiffRouteSearch({
8− diff: "1",
9− diffTurnId: "turn-1",
10− diffFilePath: "src/app.ts",
11− });
12−
13− expect(parsed).toEqual({
14− diff: "1",
15− diffTurnId: "turn-1",
16− diffFilePath: "src/app.ts",
17− });
18− });
19−
20− it("treats numeric and boolean diff toggles as open", () => {
21− expect(
22− parseDiffRouteSearch({
23− diff: 1,
24− diffTurnId: "turn-1",
25− }),
26− ).toEqual({
27− diff: "1",
28− diffTurnId: "turn-1",
29− });
30−
31− expect(
32− parseDiffRouteSearch({
33− diff: true,
34− diffTurnId: "turn-1",
35− }),
36− ).toEqual({
37− diff: "1",
38− diffTurnId: "turn-1",
39− });
40− });
41−
42− it("drops turn and file values when diff is closed", () => {
43− const parsed = parseDiffRouteSearch({
44− diff: "0",
45− diffTurnId: "turn-1",
46− diffFilePath: "src/app.ts",
47− });
48−
49− expect(parsed).toEqual({});
50− });
51−
52− it("drops file value when turn is not selected", () => {
53− const parsed = parseDiffRouteSearch({
54− diff: "1",
55− diffFilePath: "src/app.ts",
56− });
57−
58− expect(parsed).toEqual({
59− diff: "1",
60− });
61− });
62−
63− it("normalizes whitespace-only values", () => {
64− const parsed = parseDiffRouteSearch({
65− diff: "1",
66− diffTurnId: " ",
67− diffFilePath: " ",
68− });
69−
70− expect(parsed).toEqual({
71− diff: "1",
72− });
73− });
74−});
apps/web/src/diffRouteSearch.ts+0−39
@@ -1,39 +0,0 @@
1−import { TurnId } from "@t3tools/contracts";
2−
3−export interface DiffRouteSearch {
4− diff?: "1" | undefined;
5− diffTurnId?: TurnId | undefined;
6− diffFilePath?: string | undefined;
7−}
8−
9−function isDiffOpenValue(value: unknown): boolean {
10− return value === "1" || value === 1 || value === true;
11−}
12−
13−function normalizeSearchString(value: unknown): string | undefined {
14− if (typeof value !== "string") {
15− return undefined;
16− }
17− const normalized = value.trim();
18− return normalized.length > 0 ? normalized : undefined;
19−}
20−
21−export function stripDiffSearchParams<T extends Record<string, unknown>>(
22− params: T,
23−): Omit<T, "diff" | "diffTurnId" | "diffFilePath"> {
24− const { diff: _diff, diffTurnId: _diffTurnId, diffFilePath: _diffFilePath, ...rest } = params;
25− return rest as Omit<T, "diff" | "diffTurnId" | "diffFilePath">;
26−}
27−
28−export function parseDiffRouteSearch(search: Record<string, unknown>): DiffRouteSearch {
29− const diff = isDiffOpenValue(search.diff) ? "1" : undefined;
30− const diffTurnIdRaw = diff ? normalizeSearchString(search.diffTurnId) : undefined;
31− const diffTurnId = diffTurnIdRaw ? TurnId.make(diffTurnIdRaw) : undefined;
32− const diffFilePath = diff && diffTurnId ? normalizeSearchString(search.diffFilePath) : undefined;
33−
34− return {
35− ...(diff ? { diff } : {}),
36− ...(diffTurnId ? { diffTurnId } : {}),
37− ...(diffFilePath ? { diffFilePath } : {}),
38− };
39−}
apps/web/src/environments/primary/httpClient.ts+5−8
@@ -5,16 +5,13 @@ import * as Layer from "effect/Layer"; · 4 unmodified lines
55
66 import { resolvePrimaryEnvironmentHttpUrl } from "./target";
77
8−export type PrimaryEnvironmentHttpClientShape = Effect.Success<
9− ReturnType<typeof makeEnvironmentHttpApiClient>
10−>;
11−
128 export class PrimaryEnvironmentHttpClient extends Context.Service<
139 PrimaryEnvironmentHttpClient,
14− PrimaryEnvironmentHttpClientShape
10+ Effect.Success<ReturnType<typeof makeEnvironmentHttpApiClient>>
1511 >()("@t3tools/web/environments/primary/httpClient/PrimaryEnvironmentHttpClient") {}
1612
17−export const primaryEnvironmentHttpClientLive = Layer.effect(
18− PrimaryEnvironmentHttpClient,
19− Effect.suspend(() => makeEnvironmentHttpApiClient(resolvePrimaryEnvironmentHttpUrl("/"))),
13+const make = Effect.suspend(() =>
14+ makeEnvironmentHttpApiClient(resolvePrimaryEnvironmentHttpUrl("/")),
2015 );
16+
17+export const layer = Layer.effect(PrimaryEnvironmentHttpClient, make);
apps/web/src/index.css+2−1
@@ -689,7 +689,8 @@ label:has(> select#reasoning-effort) select { · 688 unmodified lines
689689 background: color-mix(in srgb, var(--background) 94%, var(--card));
690690 }
691691
692−.diff-render-file {
692+.diff-render-file,
693+.diff-render-surface > diffs-container {
693694 border: 1px solid var(--border);
694695 border-radius: 0.5rem;
695696 overflow: clip;
apps/web/src/lib/baseRefChoices.test.ts+52−0
@@ -0,0 +1,52 @@
1+import { describe, expect, it } from "vite-plus/test";
2+import type { VcsRef } from "@t3tools/contracts";
3+import { buildBaseRefChoices, filterBaseRefChoices } from "./baseRefChoices";
4+
5+function ref(name: string, remoteName?: string): VcsRef {
6+ return {
7+ name,
8+ current: false,
9+ isDefault: false,
10+ isRemote: remoteName !== undefined,
11+ ...(remoteName ? { remoteName } : {}),
12+ worktreePath: null,
13+ };
14+}
15+
16+describe("buildBaseRefChoices", () => {
17+ it("pairs matching local and remote branches and prefers origin", () => {
18+ const choices = buildBaseRefChoices(
19+ [ref("main")],
20+ [ref("upstream/main", "upstream"), ref("origin/main", "origin")],
21+ );
22+
23+ expect(choices).toEqual([
24+ expect.objectContaining({
25+ label: "main",
26+ local: expect.objectContaining({ name: "main" }),
27+ remote: expect.objectContaining({ name: "origin/main" }),
28+ }),
29+ expect.objectContaining({
30+ label: "upstream/main",
31+ local: null,
32+ remote: expect.objectContaining({ name: "upstream/main" }),
33+ }),
34+ ]);
35+ });
36+});
37+
38+describe("filterBaseRefChoices", () => {
39+ it("filters stale server results against the current query", () => {
40+ const choices = buildBaseRefChoices(
41+ [ref("main"), ref("feature/search")],
42+ [ref("origin/main", "origin"), ref("origin/feature/search", "origin")],
43+ );
44+
45+ expect(filterBaseRefChoices(choices, "SEARCH").map((choice) => choice.label)).toEqual([
46+ "feature/search",
47+ ]);
48+ expect(filterBaseRefChoices(choices, "origin/main").map((choice) => choice.label)).toEqual([
49+ "main",
50+ ]);
51+ });
52+});
apps/web/src/lib/baseRefChoices.ts+61−0
@@ -0,0 +1,61 @@
1+import type { VcsRef } from "@t3tools/contracts";
2+
3+export interface BaseRefChoice {
4+ readonly id: string;
5+ readonly label: string;
6+ readonly local: VcsRef | null;
7+ readonly remote: VcsRef | null;
8+}
9+
10+function remoteBranchName(ref: VcsRef): string {
11+ if (ref.remoteName && ref.name.startsWith(`${ref.remoteName}/`)) {
12+ return ref.name.slice(ref.remoteName.length + 1);
13+ }
14+ return ref.name;
15+}
16+
17+export function buildBaseRefChoices(
18+ localRefs: ReadonlyArray<VcsRef>,
19+ remoteRefs: ReadonlyArray<VcsRef>,
20+): ReadonlyArray<BaseRefChoice> {
21+ const unusedRemoteRefs = new Set(remoteRefs);
22+ const pairedChoices = localRefs.map((local) => {
23+ const matches = remoteRefs.filter(
24+ (remote) => unusedRemoteRefs.has(remote) && remoteBranchName(remote) === local.name,
25+ );
26+ const remote =
27+ matches.find((candidate) => candidate.remoteName === "origin") ?? matches[0] ?? null;
28+ if (remote) unusedRemoteRefs.delete(remote);
29+ return {
30+ id: `local:${local.name}`,
31+ label: local.name,
32+ local,
33+ remote,
34+ };
35+ });
36+
37+ const remoteOnlyChoices = remoteRefs
38+ .filter((remote) => unusedRemoteRefs.has(remote))
39+ .map((remote) => ({
40+ id: `remote:${remote.name}`,
41+ label: remote.name,
42+ local: null,
43+ remote,
44+ }));
45+
46+ return [...pairedChoices, ...remoteOnlyChoices];
47+}
48+
49+export function filterBaseRefChoices(
50+ choices: ReadonlyArray<BaseRefChoice>,
51+ query: string,
52+): ReadonlyArray<BaseRefChoice> {
53+ const normalizedQuery = query.trim().toLocaleLowerCase();
54+ if (normalizedQuery.length === 0) return choices;
55+ return choices.filter(
56+ (choice) =>
57+ choice.label.toLocaleLowerCase().includes(normalizedQuery) ||
58+ choice.local?.name.toLocaleLowerCase().includes(normalizedQuery) === true ||
59+ choice.remote?.name.toLocaleLowerCase().includes(normalizedQuery) === true,
60+ );
61+}
apps/web/src/lib/diffRendering.test.ts+54−1
@@ -1,5 +1,5 @@
11 import { describe, expect, it } from "vite-plus/test";
2−import { buildPatchCacheKey } from "./diffRendering";
2+import { buildPatchCacheKey, getRenderablePatch } from "./diffRendering";
33
44 describe("buildPatchCacheKey", () => {
55 it("returns a stable cache key for identical content", () => {
@@ -29,3 +29,56 @@ describe("buildPatchCacheKey", () => { · 23 unmodified lines
2929 );
3030 });
3131 });
32+
33+describe("getRenderablePatch", () => {
34+ it("compacts partial hunk render offsets for virtualized review diffs", () => {
35+ const patch = [
36+ "diff --git a/example.ts b/example.ts",
37+ "index 1111111..2222222 100644",
38+ "--- a/example.ts",
39+ "+++ b/example.ts",
40+ "@@ -48,4 +48,4 @@",
41+ " context",
42+ "-before",
43+ "+after",
44+ " context",
45+ " context",
46+ "@@ -80,3 +80,4 @@",
47+ " context",
48+ "+added",
49+ " context",
50+ " context",
51+ ].join("\n");
52+
53+ const parsed = getRenderablePatch(patch, "review", {
54+ compactPartialHunkOffsets: true,
55+ });
56+ expect(parsed?.kind).toBe("files");
57+ if (parsed?.kind !== "files") return;
58+
59+ const file = parsed.files[0];
60+ expect(file?.hunks[0]?.collapsedBefore).toBe(47);
61+ expect(file?.hunks[0]?.unifiedLineStart).toBe(0);
62+ expect(file?.hunks[1]?.collapsedBefore).toBeGreaterThan(0);
63+ expect(file?.hunks[1]?.unifiedLineStart).toBe(file?.hunks[0]?.unifiedLineCount);
64+ expect(file?.unifiedLineCount).toBe(
65+ file?.hunks.reduce((total, hunk) => total + hunk.unifiedLineCount, 0),
66+ );
67+ });
68+
69+ it("retains source-file offsets for checkpoint diffs", () => {
70+ const patch = [
71+ "diff --git a/example.ts b/example.ts",
72+ "--- a/example.ts",
73+ "+++ b/example.ts",
74+ "@@ -48,1 +48,1 @@",
75+ "-before",
76+ "+after",
77+ ].join("\n");
78+
79+ const parsed = getRenderablePatch(patch, "checkpoint");
80+ expect(parsed?.kind).toBe("files");
81+ if (parsed?.kind !== "files") return;
82+ expect(parsed.files[0]?.hunks[0]?.unifiedLineStart).toBe(47);
83+ });
84+});
apps/web/src/lib/diffRendering.ts+41−1
@@ -52,9 +52,45 @@ export type RenderablePatch = · 51 unmodified lines
5252 reason: string;
5353 };
5454
55+interface RenderablePatchOptions {
56+ /**
57+ * Pierre's partial-patch parser keeps hunk render starts in source-file
58+ * coordinates. Its virtualizer iterates partial patches as compact rows, so
59+ * review diffs need compact render starts while retaining collapsedBefore
60+ * for the "N unmodified lines" separator.
61+ */
62+ compactPartialHunkOffsets?: boolean;
63+}
64+
65+export function compactPartialHunkOffsets(file: FileDiffMetadata): FileDiffMetadata {
66+ if (!file.isPartial) return file;
67+
68+ let splitLineStart = 0;
69+ let unifiedLineStart = 0;
70+ const hunks = file.hunks.map((hunk) => {
71+ const compactHunk = {
72+ ...hunk,
73+ splitLineStart,
74+ unifiedLineStart,
75+ };
76+ splitLineStart += hunk.splitLineCount;
77+ unifiedLineStart += hunk.unifiedLineCount;
78+ return compactHunk;
79+ });
80+
81+ return {
82+ ...file,
83+ hunks,
84+ splitLineCount: splitLineStart,
85+ unifiedLineCount: unifiedLineStart,
86+ ...(file.cacheKey ? { cacheKey: `${file.cacheKey}:compact-partial` } : {}),
87+ };
88+}
89+
5590 export function getRenderablePatch(
5691 patch: string | undefined,
5792 cacheScope = "diff-panel",
93+ options: RenderablePatchOptions = {},
5894 ): RenderablePatch | null {
5995 if (!patch) return null;
6096 const normalizedPatch = patch.trim();
@@ -65,7 +101,11 @@ export function getRenderablePatch( · 4 unmodified lines
65101 normalizedPatch,
66102 buildPatchCacheKey(normalizedPatch, cacheScope),
67103 );
68− const files = parsedPatches.flatMap((parsedPatch) => parsedPatch.files);
104+ const files = parsedPatches.flatMap((parsedPatch) =>
105+ options.compactPartialHunkOffsets
106+ ? parsedPatch.files.map(compactPartialHunkOffsets)
107+ : parsedPatch.files,
108+ );
69109 if (files.length > 0) {
70110 return { kind: "files", files };
71111 }
apps/web/src/lib/runtime.ts+6−8
@@ -5,10 +5,7 @@ import * as Socket from "effect/unstable/socket/Socket"; · 4 unmodified lines
55
66 import { remoteHttpClientLayer } from "@t3tools/client-runtime/rpc";
77 import { makeRelayClientTracingLayer } from "@t3tools/shared/relayTracing";
8−import {
9− PrimaryEnvironmentHttpClient,
10− primaryEnvironmentHttpClientLive,
11−} from "../environments/primary/httpClient";
8+import * as PrimaryEnvironmentHttpClient from "../environments/primary/httpClient";
129 import { primaryEnvironmentHttpLayer } from "../environments/primary/httpLayer";
1310
1411 import { browserCryptoLayer } from "../cloud/dpop";
@@ -30,11 +27,11 @@ const relayTracingLayer = makeRelayClientTracingLayer(resolveRelayTracingConfig( · 15 unmodified lines
3027 export const remoteHttpRuntime = ManagedRuntime.make(httpClientLayer);
3128
3229 const primaryHttpRuntime = ManagedRuntime.make(
33− primaryEnvironmentHttpClientLive.pipe(Layer.provide(primaryEnvironmentHttpLayer)),
30+ PrimaryEnvironmentHttpClient.layer.pipe(Layer.provide(primaryEnvironmentHttpLayer)),
3431 );
3532
3633 export type PrimaryHttpEffectRunner = <A, E>(
37− effect: Effect.Effect<A, E, PrimaryEnvironmentHttpClient>,
34+ effect: Effect.Effect<A, E, PrimaryEnvironmentHttpClient.PrimaryEnvironmentHttpClient>,
3835 ) => Promise<A>;
3936
4037 const livePrimaryHttpRunner: PrimaryHttpEffectRunner = (effect) =>
@@ -42,8 +39,9 @@ const livePrimaryHttpRunner: PrimaryHttpEffectRunner = (effect) => · 1 unmodified line
4239
4340 let primaryHttpRunner = livePrimaryHttpRunner;
4441
45−export const runPrimaryHttp = <A, E>(effect: Effect.Effect<A, E, PrimaryEnvironmentHttpClient>) =>
46− primaryHttpRunner(effect);
42+export const runPrimaryHttp = <A, E>(
43+ effect: Effect.Effect<A, E, PrimaryEnvironmentHttpClient.PrimaryEnvironmentHttpClient>,
44+) => primaryHttpRunner(effect);
4745
4846 export function __setPrimaryHttpRunnerForTests(runner?: PrimaryHttpEffectRunner): void {
4947 primaryHttpRunner = runner ?? livePrimaryHttpRunner;
apps/web/src/rightPanelStore.test.ts+0−11
@@ -6,7 +6,6 @@ import { · 5 unmodified lines
66 migratePersistedRightPanelState,
77 selectActiveRightPanel,
88 selectActiveRightPanelSurface,
9− selectActiveRightPanelKindWithUrl,
109 selectThreadRightPanelState,
1110 useRightPanelStore,
1211 } from "./rightPanelStore";
@@ -254,16 +253,6 @@ describe("rightPanelStore", () => { · 241 unmodified lines
254253 expect(selectActiveRightPanel(useRightPanelStore.getState().byThreadKey, refA)).toBe("plan");
255254 });
256255
257− it("?diff=1 always wins over persisted state", () => {
258− useRightPanelStore.getState().open(refA, "preview");
259− expect(
260− selectActiveRightPanelKindWithUrl(useRightPanelStore.getState().byThreadKey, refA, true),
261− ).toBe("diff");
262− expect(
263− selectActiveRightPanelKindWithUrl(useRightPanelStore.getState().byThreadKey, refA, false),
264− ).toBe("preview");
265− });
266−
267256 it("removeThread clears persisted state", () => {
268257 useRightPanelStore.getState().open(refA, "plan");
269258 useRightPanelStore.getState().removeThread(refA);
apps/web/src/rightPanelStore.ts+0−10
@@ -550,13 +550,3 @@ export function selectActiveRightPanelSurface( · 549 unmodified lines
550550 if (!state.isOpen) return null;
551551 return state.surfaces.find((surface) => surface.id === state.activeSurfaceId) ?? null;
552552 }
553−
554−export function selectActiveRightPanelKindWithUrl(
555− byThreadKey: Record<string, ThreadRightPanelState>,
556− ref: ScopedThreadRef | null | undefined,
557− diffSearchActive: boolean,
558−): RightPanelKind | null {
559− if (!selectThreadRightPanelState(byThreadKey, ref).isOpen) return null;
560− if (diffSearchActive) return "diff";
561− return selectActiveRightPanel(byThreadKey, ref);
562−}
apps/web/src/routes/_chat.$environmentId.$threadId.tsx+1−6
@@ -1,10 +1,9 @@
1−import { createFileRoute, retainSearchParams, useNavigate } from "@tanstack/react-router";
1+import { createFileRoute, useNavigate } from "@tanstack/react-router";
22 import { useEffect } from "react";
33
44 import ChatView from "../components/ChatView";
55 import { threadHasStarted } from "../components/ChatView.logic";
66 import { finalizePromotedDraftThreadByRef, useComposerDraftStore } from "../composerDraftStore";
7−import { type DiffRouteSearch, parseDiffRouteSearch } from "../diffRouteSearch";
87 import { resolveThreadRouteRef } from "../threadRoutes";
98 import { SidebarInset } from "~/components/ui/sidebar";
109 import { useEnvironmentThreadRefs, useThreadDetail, useThreadShell } from "../state/entities";
@@ -74,9 +73,5 @@ function ChatThreadRouteView() { · 63 unmodified lines
7473 }
7574
7675 export const Route = createFileRoute("/_chat/$environmentId/$threadId")({
77− validateSearch: (search) => parseDiffRouteSearch(search),
78− search: {
79− middlewares: [retainSearchParams<DiffRouteSearch>(["diff"])],
80− },
8176 component: ChatThreadRouteView,
8277 });
infra/relay/alchemy.run.ts+3−3
@@ -7,7 +7,7 @@ import * as Effect from "effect/Effect"; · 6 unmodified lines
77 import * as Layer from "effect/Layer";
88 import * as Planetscale from "alchemy/Planetscale";
99
10−import { PlanetscaleDatabase, RelayHyperdrive } from "./src/db.ts";
10+import * as RelayDb from "./src/db.ts";
1111 import { RelayObservability } from "./src/observability.ts";
1212 import { ManagedEndpointZone, RelayApiZone } from "./src/zone.ts";
1313 import Api from "./src/worker.ts";
@@ -24,8 +24,8 @@ export default Alchemy.Stack( · 10 unmodified lines
2424 state: Cloudflare.state(),
2525 },
2626 Effect.gen(function* () {
27− const db = yield* PlanetscaleDatabase;
28− const hyperdrive = yield* RelayHyperdrive;
27+ const db = yield* RelayDb.PlanetscaleDatabase;
28+ const hyperdrive = yield* RelayDb.RelayHyperdrive;
2929 const managedEndpointZone = yield* ManagedEndpointZone.pipe(Effect.orDie);
3030 const relayApiZone = yield* RelayApiZone.pipe(Effect.orDie);
3131 const observability = yield* RelayObservability;
infra/relay/src/Config.ts+19−14
@@ -1,4 +1,5 @@
11 import * as Context from "effect/Context";
2+import * as Layer from "effect/Layer";
23 import * as Redacted from "effect/Redacted";
34 import * as Schema from "effect/Schema";
45
@@ -13,20 +14,24 @@ export interface ApnsCredentials { · 8 unmodified lines
1314 readonly environment: ApnsEnvironment;
1415 }
1516
16−export interface RelayConfigurationShape {
17− readonly relayIssuer: string;
18− readonly apns: ApnsCredentials;
19− readonly clerkSecretKey: Redacted.Redacted<string>;
20− readonly clerkPublishableKey: string;
21− readonly clerkJwtAudience: string;
22− readonly apnsDeliveryJobSigningSecret: Redacted.Redacted<string>;
23− readonly cloudMintPrivateKey: Redacted.Redacted<string>;
24− readonly cloudMintPublicKey: string;
25− readonly managedEndpointBaseDomain: string | undefined;
26− readonly managedEndpointNamespace: string | undefined;
27−}
28−
2917 export class RelayConfiguration extends Context.Service<
3018 RelayConfiguration,
31− RelayConfigurationShape
19+ {
20+ readonly relayIssuer: string;
21+ readonly apns: ApnsCredentials;
22+ readonly clerkSecretKey: Redacted.Redacted<string>;
23+ readonly clerkPublishableKey: string;
24+ readonly clerkJwtAudience: string;
25+ readonly apnsDeliveryJobSigningSecret: Redacted.Redacted<string>;
26+ readonly cloudMintPrivateKey: Redacted.Redacted<string>;
27+ readonly cloudMintPublicKey: string;
28+ readonly managedEndpointBaseDomain: string | undefined;
29+ readonly managedEndpointNamespace: string | undefined;
30+ }
3231 >()("t3code-relay/Config/RelayConfiguration") {}
32+
33+export const make = (configuration: RelayConfiguration["Service"]) =>
34+ RelayConfiguration.of(configuration);
35+
36+export const layer = (configuration: RelayConfiguration["Service"]) =>
37+ Layer.succeed(RelayConfiguration, make(configuration));
infra/relay/src/agentActivity/AgentActivityPublisher.test.ts+2−2
@@ -66,8 +66,8 @@ function makeAgentActivityRows( · 65 unmodified lines
6666 }
6767
6868 function makeEnvironmentLinks(
69− overrides: Partial<EnvironmentLinks.EnvironmentLinksShape> = {},
70−): EnvironmentLinks.EnvironmentLinksShape {
69+ overrides: Partial<EnvironmentLinks.EnvironmentLinks["Service"]> = {},
70+): EnvironmentLinks.EnvironmentLinks["Service"] {
7171 return {
7272 upsert: () => Effect.void,
7373 listUsersForEnvironment: () => Effect.succeed(["dev:julius"]),
infra/relay/src/agentActivity/AgentActivityRows.ts+2−2
@@ -9,7 +9,7 @@ import * as Option from "effect/Option"; · 8 unmodified lines
99 import * as Schema from "effect/Schema";
1010 import { and, desc, eq, isNull } from "drizzle-orm";
1111
12−import { RelayDb } from "../db.ts";
12+import * as RelayDb from "../db.ts";
1313 import { relayAgentActivityRows, relayEnvironmentLinks } from "../persistence/schema.ts";
1414
1515 export class AgentActivityRowUpsertPersistenceError extends Schema.TaggedErrorClass<AgentActivityRowUpsertPersistenceError>()(
@@ -70,7 +70,7 @@ const decodeRelayAgentActivityStateJson = Schema.decodeUnknownOption( · 54 unmodified lines
7070 );
7171
7272 const make = Effect.gen(function* () {
73− const db = yield* RelayDb;
73+ const db = yield* RelayDb.RelayDb;
7474
7575 return AgentActivityRows.of({
7676 upsert: Effect.fn("relay.agent_activity_rows.upsert")(
infra/relay/src/agentActivity/ApnsClient.ts+6−6
@@ -9,7 +9,7 @@ import * as Option from "effect/Option"; · 8 unmodified lines
99 import * as Redacted from "effect/Redacted";
1010 import * as Schema from "effect/Schema";
1111 import { Headers, HttpClient, HttpClientRequest } from "effect/unstable/http";
12−import type { ApnsCredentials } from "../Config.ts";
12+import * as RelayConfiguration from "../Config.ts";
1313 import type { ApnsNotificationPayload } from "./apnsDeliveryJobs.ts";
1414
1515 const LIVE_ACTIVITY_NAME = "AgentActivity";
@@ -99,9 +99,9 @@ const encodeApnsJwtPayloadJson = Schema.encodeEffect( · 83 unmodified lines
9999 );
100100
101101 const makeApnsJwt = Effect.fn("relay.apns.make_jwt")(function* (input: {
102− readonly teamId: ApnsCredentials["teamId"];
103− readonly keyId: ApnsCredentials["keyId"];
104− readonly privateKey: ApnsCredentials["privateKey"];
102+ readonly teamId: RelayConfiguration.ApnsCredentials["teamId"];
103+ readonly keyId: RelayConfiguration.ApnsCredentials["keyId"];
104+ readonly privateKey: RelayConfiguration.ApnsCredentials["privateKey"];
105105 readonly issuedAtUnixSeconds: number;
106106 }) {
107107 const headerJson = yield* encodeApnsJwtHeaderJson({ alg: "ES256", kid: input.keyId }).pipe(
@@ -235,12 +235,12 @@ export interface ApnsClientShape { · 127 unmodified lines
235235 readonly makeLiveActivityRequest: typeof makeLiveActivityRequest;
236236 readonly makePushNotificationRequest: typeof makePushNotificationRequest;
237237 readonly sendLiveActivityRequest: (input: {
238− readonly credentials: ApnsCredentials;
238+ readonly credentials: RelayConfiguration.ApnsCredentials;
239239 readonly request: ApnsLiveActivityRequest;
240240 readonly issuedAtUnixSeconds: number;
241241 }) => Effect.Effect<ApnsDeliveryResult, ApnsError>;
242242 readonly sendPushNotificationRequest: (input: {
243− readonly credentials: ApnsCredentials;
243+ readonly credentials: RelayConfiguration.ApnsCredentials;
244244 readonly request: ApnsPushNotificationRequest;
245245 readonly issuedAtUnixSeconds: number;
246246 }) => Effect.Effect<ApnsDeliveryResult, ApnsError>;
infra/relay/src/agentActivity/ApnsDeliveries.test.ts+2−2
@@ -153,7 +153,7 @@ function makeLayer(input: { · 152 unmodified lines
153153 Parameters<LiveActivities.LiveActivitiesShape["invalidateDeliveryToken"]>[0]
154154 >;
155155 readonly currentTargets?: ReadonlyArray<LiveActivities.TargetRow>;
156− readonly config?: RelayConfiguration.RelayConfigurationShape;
156+ readonly config?: RelayConfiguration.RelayConfiguration["Service"];
157157 readonly execute?: (
158158 request: HttpClientRequest.HttpClientRequest,
159159 ) => Effect.Effect<HttpClientResponse.HttpClientResponse>;
@@ -213,7 +213,7 @@ function makeLayer(input: { · 53 unmodified lines
213213 input.invalidatedTokens?.push(invalidated);
214214 }),
215215 }),
216− Layer.succeed(RelayConfiguration.RelayConfiguration, input.config ?? config),
216+ RelayConfiguration.layer(input.config ?? config),
217217 input.execute
218218 ? Layer.succeed(HttpClient.HttpClient, HttpClient.make(input.execute))
219219 : FetchHttpClient.layer,
infra/relay/src/agentActivity/DeliveryAttempts.test.ts+13−13
@@ -3,7 +3,7 @@ import { describe, expect, it } from "@effect/vitest"; · 2 unmodified lines
33 import * as Effect from "effect/Effect";
44 import * as Layer from "effect/Layer";
55
6−import { RelayDb, type RelayDatabase } from "../db.ts";
6+import * as RelayDb from "../db.ts";
77 import { relayDeliveryAttempts } from "../persistence/schema.ts";
88 import * as DeliveryAttempts from "./DeliveryAttempts.ts";
99
@@ -20,7 +20,7 @@ describe("DeliveryAttempts", () => { · 10 unmodified lines
2020 },
2121 };
2222 },
23− } as unknown as RelayDatabase;
23+ } as unknown as RelayDb.RelayDb["Service"];
2424
2525 return Effect.gen(function* () {
2626 const attempts = yield* DeliveryAttempts.DeliveryAttempts;
@@ -52,7 +52,7 @@ describe("DeliveryAttempts", () => { · 25 unmodified lines
5252 Effect.provide(
5353 DeliveryAttempts.layer.pipe(
5454 Layer.provide(NodeCryptoLayer.layer),
55− Layer.provide(Layer.succeed(RelayDb, fakeDb)),
55+ Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)),
5656 ),
5757 ),
5858 );
@@ -78,7 +78,7 @@ describe("DeliveryAttempts", () => { · 19 unmodified lines
7878 },
7979 };
8080 },
81− } as unknown as RelayDatabase;
81+ } as unknown as RelayDb.RelayDb["Service"];
8282
8383 return Effect.gen(function* () {
8484 const attempts = yield* DeliveryAttempts.DeliveryAttempts;
@@ -104,7 +104,7 @@ describe("DeliveryAttempts", () => { · 19 unmodified lines
104104 Effect.provide(
105105 DeliveryAttempts.layer.pipe(
106106 Layer.provide(NodeCryptoLayer.layer),
107− Layer.provide(Layer.succeed(RelayDb, fakeDb)),
107+ Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)),
108108 ),
109109 ),
110110 );
@@ -135,7 +135,7 @@ describe("DeliveryAttempts", () => { · 24 unmodified lines
135135 }),
136136 }),
137137 }),
138− } as unknown as RelayDatabase;
138+ } as unknown as RelayDb.RelayDb["Service"];
139139
140140 return Effect.gen(function* () {
141141 const attempts = yield* DeliveryAttempts.DeliveryAttempts;
@@ -154,7 +154,7 @@ describe("DeliveryAttempts", () => { · 12 unmodified lines
154154 Effect.provide(
155155 DeliveryAttempts.layer.pipe(
156156 Layer.provide(NodeCryptoLayer.layer),
157− Layer.provide(Layer.succeed(RelayDb, fakeDb)),
157+ Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)),
158158 ),
159159 ),
160160 );
@@ -185,7 +185,7 @@ describe("DeliveryAttempts", () => { · 24 unmodified lines
185185 }),
186186 }),
187187 }),
188− } as unknown as RelayDatabase;
188+ } as unknown as RelayDb.RelayDb["Service"];
189189
190190 return Effect.gen(function* () {
191191 const attempts = yield* DeliveryAttempts.DeliveryAttempts;
@@ -204,7 +204,7 @@ describe("DeliveryAttempts", () => { · 12 unmodified lines
204204 Effect.provide(
205205 DeliveryAttempts.layer.pipe(
206206 Layer.provide(NodeCryptoLayer.layer),
207− Layer.provide(Layer.succeed(RelayDb, fakeDb)),
207+ Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)),
208208 ),
209209 ),
210210 );
@@ -246,7 +246,7 @@ describe("DeliveryAttempts", () => { · 35 unmodified lines
246246 };
247247 },
248248 }),
249− } as unknown as RelayDatabase;
249+ } as unknown as RelayDb.RelayDb["Service"];
250250
251251 return Effect.gen(function* () {
252252 const attempts = yield* DeliveryAttempts.DeliveryAttempts;
@@ -266,7 +266,7 @@ describe("DeliveryAttempts", () => { · 13 unmodified lines
266266 Effect.provide(
267267 DeliveryAttempts.layer.pipe(
268268 Layer.provide(NodeCryptoLayer.layer),
269− Layer.provide(Layer.succeed(RelayDb, fakeDb)),
269+ Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)),
270270 ),
271271 ),
272272 );
@@ -290,7 +290,7 @@ describe("DeliveryAttempts", () => { · 17 unmodified lines
290290 },
291291 };
292292 },
293− } as unknown as RelayDatabase;
293+ } as unknown as RelayDb.RelayDb["Service"];
294294
295295 return Effect.gen(function* () {
296296 const attempts = yield* DeliveryAttempts.DeliveryAttempts;
@@ -315,7 +315,7 @@ describe("DeliveryAttempts", () => { · 18 unmodified lines
315315 Effect.provide(
316316 DeliveryAttempts.layer.pipe(
317317 Layer.provide(NodeCryptoLayer.layer),
318− Layer.provide(Layer.succeed(RelayDb, fakeDb)),
318+ Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)),
319319 ),
320320 ),
321321 );
infra/relay/src/agentActivity/DeliveryAttempts.ts+2−2
@@ -7,7 +7,7 @@ import { and, eq, isNull } from "drizzle-orm"; · 6 unmodified lines
77 import * as Crypto from "effect/Crypto";
88 import * as Schema from "effect/Schema";
99
10−import { RelayDb } from "../db.ts";
10+import * as RelayDb from "../db.ts";
1111 import { relayDeliveryAttempts } from "../persistence/schema.ts";
1212
1313 export class DeliveryAttemptRecordPersistenceError extends Schema.TaggedErrorClass<DeliveryAttemptRecordPersistenceError>()(
@@ -84,7 +84,7 @@ function insertValues( · 70 unmodified lines
8484 }
8585
8686 const make = Effect.gen(function* () {
87− const db = yield* RelayDb;
87+ const db = yield* RelayDb.RelayDb;
8888 const crypto = yield* Crypto.Crypto;
8989
9090 const isExpiredClaim = (claimedAt: string | null, now: DateTime.DateTime) => {
infra/relay/src/agentActivity/Devices.test.ts+13−7
@@ -5,7 +5,7 @@ import { PgDialect } from "drizzle-orm/pg-core"; · 4 unmodified lines
55 import * as Effect from "effect/Effect";
66 import * as Layer from "effect/Layer";
77
8−import { RelayDb, type RelayDatabase } from "../db.ts";
8+import * as RelayDb from "../db.ts";
99 import { relayLiveActivities, relayMobileDevices } from "../persistence/schema.ts";
1010 import * as Devices from "./Devices.ts";
1111
@@ -71,7 +71,7 @@ describe("Devices", () => { · 59 unmodified lines
7171 },
7272 };
7373 },
74− } as unknown as RelayDatabase;
74+ } as unknown as RelayDb.RelayDb["Service"];
7575
7676 return Effect.gen(function* () {
7777 const devices = yield* Devices.Devices;
@@ -110,7 +110,9 @@ describe("Devices", () => { · 32 unmodified lines
110110 pushToStartToken: "push-to-start-token",
111111 }),
112112 ]);
113− }).pipe(Effect.provide(Devices.layer.pipe(Layer.provide(Layer.succeed(RelayDb, fakeDb)))));
113+ }).pipe(
114+ Effect.provide(Devices.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)))),
115+ );
114116 });
115117
116118 it.effect("unregisters APNs state only for the current user device", () => {
@@ -130,7 +132,7 @@ describe("Devices", () => { · 13 unmodified lines
130132 },
131133 };
132134 },
133− } as unknown as RelayDatabase;
135+ } as unknown as RelayDb.RelayDb["Service"];
134136
135137 return Effect.gen(function* () {
136138 const devices = yield* Devices.Devices;
@@ -156,7 +158,9 @@ describe("Devices", () => { · 19 unmodified lines
156158 params: ["user-2", "device-1"],
157159 },
158160 ]);
159− }).pipe(Effect.provide(Devices.layer.pipe(Layer.provide(Layer.succeed(RelayDb, fakeDb)))));
161+ }).pipe(
162+ Effect.provide(Devices.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)))),
163+ );
160164 });
161165
162166 it.effect("lists safe notification state without exposing APNs tokens", () => {
@@ -184,7 +188,7 @@ describe("Devices", () => { · 21 unmodified lines
184188 };
185189 },
186190 }),
187− } as unknown as RelayDatabase;
191+ } as unknown as RelayDb.RelayDb["Service"];
188192
189193 return Effect.gen(function* () {
190194 const devices = yield* Devices.Devices;
@@ -215,6 +219,8 @@ describe("Devices", () => { · 24 unmodified lines
215219 updatedAt: "2026-06-01T00:00:00.000Z",
216220 },
217221 ]);
218− }).pipe(Effect.provide(Devices.layer.pipe(Layer.provide(Layer.succeed(RelayDb, fakeDb)))));
222+ }).pipe(
223+ Effect.provide(Devices.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)))),
224+ );
219225 });
220226 });
infra/relay/src/agentActivity/Devices.ts+2−2
@@ -10,7 +10,7 @@ import * as Schema from "effect/Schema"; · 9 unmodified lines
1010 import { and, eq } from "drizzle-orm";
1111 import { sql } from "drizzle-orm";
1212
13−import { RelayDb } from "../db.ts";
13+import * as RelayDb from "../db.ts";
1414 import { relayLiveActivities, relayMobileDevices } from "../persistence/schema.ts";
1515
1616 export class DeviceRegistrationPersistenceError extends Schema.TaggedErrorClass<DeviceRegistrationPersistenceError>()(
@@ -59,7 +59,7 @@ export class Devices extends Context.Service<Devices, DevicesShape>()( · 42 unmodified lines
5959 ) {}
6060
6161 const make = Effect.gen(function* () {
62− const db = yield* RelayDb;
62+ const db = yield* RelayDb.RelayDb;
6363
6464 return Devices.of({
6565 register: Effect.fn("relay.devices.register")(
infra/relay/src/agentActivity/LiveActivities.test.ts+9−5
@@ -8,7 +8,7 @@ import { PgDialect } from "drizzle-orm/pg-core"; · 7 unmodified lines
88 import * as Effect from "effect/Effect";
99 import * as Layer from "effect/Layer";
1010
11−import { RelayDb, type RelayDatabase } from "../db.ts";
11+import * as RelayDb from "../db.ts";
1212 import { relayLiveActivities } from "../persistence/schema.ts";
1313 import * as LiveActivities from "./LiveActivities.ts";
1414
@@ -88,7 +88,7 @@ describe("LiveActivities", () => { · 73 unmodified lines
8888 },
8989 };
9090 },
91− } as unknown as RelayDatabase;
91+ } as unknown as RelayDb.RelayDb["Service"];
9292
9393 return Effect.gen(function* () {
9494 const liveActivities = yield* LiveActivities.LiveActivities;
@@ -138,7 +138,9 @@ describe("LiveActivities", () => { · 43 unmodified lines
138138 }),
139139 );
140140 }).pipe(
141− Effect.provide(LiveActivities.layer.pipe(Layer.provide(Layer.succeed(RelayDb, fakeDb)))),
141+ Effect.provide(
142+ LiveActivities.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb))),
143+ ),
142144 );
143145 },
144146 );
@@ -164,7 +166,7 @@ describe("LiveActivities", () => { · 19 unmodified lines
164166 },
165167 };
166168 },
167− } as unknown as RelayDatabase;
169+ } as unknown as RelayDb.RelayDb["Service"];
168170
169171 return Effect.gen(function* () {
170172 const liveActivities = yield* LiveActivities.LiveActivities;
@@ -190,7 +192,9 @@ describe("LiveActivities", () => { · 19 unmodified lines
190192 }),
191193 );
192194 }).pipe(
193− Effect.provide(LiveActivities.layer.pipe(Layer.provide(Layer.succeed(RelayDb, fakeDb)))),
195+ Effect.provide(
196+ LiveActivities.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb))),
197+ ),
194198 );
195199 });
196200 });
infra/relay/src/agentActivity/LiveActivities.ts+2−2
@@ -12,7 +12,7 @@ import * as Layer from "effect/Layer"; · 11 unmodified lines
1212 import * as Schema from "effect/Schema";
1313 import { and, eq, sql } from "drizzle-orm";
1414
15−import { RelayDb } from "../db.ts";
15+import * as RelayDb from "../db.ts";
1616 import { relayLiveActivities, relayMobileDevices } from "../persistence/schema.ts";
1717
1818 export class LiveActivityRegistrationPersistenceError extends Schema.TaggedErrorClass<LiveActivityRegistrationPersistenceError>()(
@@ -108,7 +108,7 @@ const encodeRelayAgentActivityAggregateStateJson = Schema.encodeEffect( · 89 unmodified lines
108108 );
109109
110110 const make = Effect.gen(function* () {
111− const db = yield* RelayDb;
111+ const db = yield* RelayDb.RelayDb;
112112
113113 return LiveActivities.of({
114114 register: Effect.fn("relay.live_activities.register")(
infra/relay/src/agentActivity/MobileRegistrations.test.ts+3−3
@@ -86,8 +86,8 @@ function makeAgentActivityRows( · 85 unmodified lines
8686 }
8787
8888 function makeEnvironmentLinks(
89− overrides: Partial<EnvironmentLinks.EnvironmentLinksShape> = {},
90−): EnvironmentLinks.EnvironmentLinksShape {
89+ overrides: Partial<EnvironmentLinks.EnvironmentLinks["Service"]> = {},
90+): EnvironmentLinks.EnvironmentLinks["Service"] {
9191 return {
9292 upsert: () => Effect.void,
9393 listUsersForEnvironment: () => Effect.succeed(["dev:julius"]),
@@ -153,7 +153,7 @@ function makeRegistrationReplayLayer(input: { · 59 unmodified lines
153153 Layer.succeed(EnvironmentLinks.EnvironmentLinks, makeEnvironmentLinks()),
154154 Layer.succeed(LiveActivities.LiveActivities, input.liveActivities),
155155 Layer.succeed(DeliveryAttempts.DeliveryAttempts, makeDeliveryAttempts()),
156− Layer.succeed(RelayConfiguration.RelayConfiguration, config),
156+ RelayConfiguration.layer(config),
157157 Layer.succeed(ApnsDeliveryQueue.ApnsDeliveryQueueSender, {
158158 send: (body) =>
159159 Effect.sync(() => {
infra/relay/src/auth/DpopProofs.test.ts+9−5
@@ -4,7 +4,7 @@ import * as Effect from "effect/Effect"; · 3 unmodified lines
44 import * as Layer from "effect/Layer";
55 import * as Option from "effect/Option";
66
7−import { RelayDb, type RelayDatabase } from "../db.ts";
7+import * as RelayDb from "../db.ts";
88 import { relayDpopProofs } from "../persistence/schema.ts";
99 import * as DpopProofs from "./DpopProofs.ts";
1010
@@ -41,7 +41,7 @@ describe("DpopProofReplay", () => { · 30 unmodified lines
4141 },
4242 };
4343 },
44− } as unknown as RelayDatabase;
44+ } as unknown as RelayDb.RelayDb["Service"];
4545
4646 return Effect.gen(function* () {
4747 const replay = yield* DpopProofs.DpopProofReplay;
@@ -67,7 +67,9 @@ describe("DpopProofReplay", () => { · 19 unmodified lines
6767 expiresAt: "2026-05-25T12:00:00.000Z",
6868 },
6969 ]);
70− }).pipe(Effect.provide(DpopProofs.layer.pipe(Layer.provide(Layer.succeed(RelayDb, fakeDb)))));
70+ }).pipe(
71+ Effect.provide(DpopProofs.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)))),
72+ );
7173 });
7274
7375 it.effect("prunes expired proof rows from the maintenance path", () => {
@@ -84,12 +86,14 @@ describe("DpopProofReplay", () => { · 10 unmodified lines
8486 },
8587 };
8688 },
87− } as unknown as RelayDatabase;
89+ } as unknown as RelayDb.RelayDb["Service"];
8890
8991 return Effect.gen(function* () {
9092 const replay = yield* DpopProofs.DpopProofReplay;
9193 yield* replay.pruneExpired;
9294 expect(calls).toEqual(["delete", "delete.where"]);
93− }).pipe(Effect.provide(DpopProofs.layer.pipe(Layer.provide(Layer.succeed(RelayDb, fakeDb)))));
95+ }).pipe(
96+ Effect.provide(DpopProofs.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)))),
97+ );
9498 });
9599 });
infra/relay/src/auth/DpopProofs.ts+25−28
@@ -7,7 +7,7 @@ import * as HttpApiError from "effect/unstable/httpapi/HttpApiError"; · 6 unmodified lines
77 import { lt } from "drizzle-orm";
88
99 import { verifyDpopProof } from "@t3tools/shared/dpop";
10−import { RelayDb } from "../db.ts";
10+import * as RelayDb from "../db.ts";
1111 import { relayDpopProofs } from "../persistence/schema.ts";
1212
1313 export class DpopProofReplayPersistenceError extends Schema.TaggedErrorClass<DpopProofReplayPersistenceError>()(
@@ -21,34 +21,31 @@ export class DpopProofReplayPersistenceError extends Schema.TaggedErrorClass<Dpo · 7 unmodified lines
2121 }
2222 }
2323
24−export interface DpopProofReplayShape {
25− readonly verifyAndConsume: (input: {
26− readonly proof: string | undefined;
27− readonly method: string;
28− readonly url: string;
29− readonly expectedThumbprint?: string;
30− readonly expectedAccessToken?: string;
31− readonly now: DateTime.DateTime;
32− }) => Effect.Effect<string, HttpApiError.Unauthorized | DpopProofReplayPersistenceError>;
33−
34− readonly consume: (input: {
35− readonly thumbprint: string;
36− readonly jti: string;
37− readonly iat: number;
38− readonly expiresAt: DateTime.DateTime;
39− }) => Effect.Effect<boolean, DpopProofReplayPersistenceError>;
40−
41− readonly pruneExpired: Effect.Effect<void, DpopProofReplayPersistenceError>;
42−}
43−
44−export class DpopProofReplay extends Context.Service<DpopProofReplay, DpopProofReplayShape>()(
45− "t3code-relay/auth/DpopProofs/DpopProofReplay",
46−) {}
24+export class DpopProofReplay extends Context.Service<
25+ DpopProofReplay,
26+ {
27+ readonly verifyAndConsume: (input: {
28+ readonly proof: string | undefined;
29+ readonly method: string;
30+ readonly url: string;
31+ readonly expectedThumbprint?: string;
32+ readonly expectedAccessToken?: string;
33+ readonly now: DateTime.DateTime;
34+ }) => Effect.Effect<string, HttpApiError.Unauthorized | DpopProofReplayPersistenceError>;
35+ readonly consume: (input: {
36+ readonly thumbprint: string;
37+ readonly jti: string;
38+ readonly iat: number;
39+ readonly expiresAt: DateTime.DateTime;
40+ }) => Effect.Effect<boolean, DpopProofReplayPersistenceError>;
41+ readonly pruneExpired: Effect.Effect<void, DpopProofReplayPersistenceError>;
42+ }
43+>()("t3code-relay/auth/DpopProofs/DpopProofReplay") {}
4744
4845 const make = Effect.gen(function* () {
49− const db = yield* RelayDb;
46+ const db = yield* RelayDb.RelayDb;
5047
51− const consume: DpopProofReplayShape["consume"] = Effect.fn("relay.dpop_proofs.consume")(
48+ const consume: DpopProofReplay["Service"]["consume"] = Effect.fn("relay.dpop_proofs.consume")(
5249 function* (input) {
5350 const createdAt = DateTime.formatIso(yield* DateTime.now);
5451 const inserted = yield* db
@@ -67,7 +64,7 @@ const make = Effect.gen(function* () { · 12 unmodified lines
6764 Effect.mapError((cause) => new DpopProofReplayPersistenceError({ cause })),
6865 );
6966
70− const verifyAndConsume: DpopProofReplayShape["verifyAndConsume"] = Effect.fn(
67+ const verifyAndConsume: DpopProofReplay["Service"]["verifyAndConsume"] = Effect.fn(
7168 "relay.dpop_proofs.verify_and_consume",
7269 )(function* (input) {
7370 yield* Effect.annotateCurrentSpan({
@@ -114,7 +111,7 @@ const make = Effect.gen(function* () { · 40 unmodified lines
114111 return result.thumbprint;
115112 });
116113
117− const pruneExpired: DpopProofReplayShape["pruneExpired"] = Effect.gen(function* () {
114+ const pruneExpired: DpopProofReplay["Service"]["pruneExpired"] = Effect.gen(function* () {
118115 const now = DateTime.formatIso(yield* DateTime.now);
119116 yield* Effect.annotateCurrentSpan({ "relay.dpop_prune.before": now });
120117 yield* db.delete(relayDpopProofs).where(lt(relayDpopProofs.expiresAt, now));
infra/relay/src/auth/DpopProofs.verifyAndConsume.test.ts+3−3
@@ -10,7 +10,7 @@ import * as DateTime from "effect/DateTime"; · 9 unmodified lines
1010 import * as Effect from "effect/Effect";
1111 import * as Layer from "effect/Layer";
1212
13−import { RelayDb, type RelayDatabase } from "../db.ts";
13+import * as RelayDb from "../db.ts";
1414 import { relayDpopProofs } from "../persistence/schema.ts";
1515 import * as DpopProofs from "./DpopProofs.ts";
1616
@@ -78,8 +78,8 @@ function layer( · 61 unmodified lines
7878 }),
7979 };
8080 },
81− } as unknown as RelayDatabase;
82− return DpopProofs.layer.pipe(Layer.provide(Layer.succeed(RelayDb, fakeDb)));
81+ } as unknown as RelayDb.RelayDb["Service"];
82+ return DpopProofs.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)));
8383 }
8484
8585 function consumeEachProofOnce() {
infra/relay/src/auth/RelayTokens.test.ts+1−3
@@ -33,9 +33,7 @@ const config = RelayConfiguration.RelayConfiguration.of({ · 32 unmodified lines
3333 managedEndpointNamespace: undefined,
3434 });
3535
36−const layer = RelayTokens.layer.pipe(
37− Layer.provide(Layer.succeed(RelayConfiguration.RelayConfiguration, config)),
38−);
36+const layer = RelayTokens.layer.pipe(Layer.provide(RelayConfiguration.layer(config)));
3937
4038 describe("RelayTokens", () => {
4139 it.effect("issues a user-bound environment link challenge", () =>
infra/relay/src/auth/RelayTokens.ts+36−37
@@ -93,45 +93,44 @@ function resolveDpopAccessTokenScopes(input: { · 92 unmodified lines
9393 });
9494 }
9595
96−export interface RelayTokensShape {
97− readonly resolveDpopAccessTokenScopes: typeof resolveDpopAccessTokenScopes;
98− readonly issueLinkChallenge: (input: {
99− readonly userId: string;
100− readonly request: RelayEnvironmentLinkChallengeRequest;
101− readonly jti: string;
102− readonly issuedAtEpochSeconds: number;
103− readonly expiresAtEpochSeconds: number;
104− }) => Effect.Effect<string, RelayJwtError>;
105− readonly verifyLinkChallenge: (input: {
106− readonly token: string;
107− readonly userId: string;
108− readonly request: RelayEnvironmentLinkChallengeRequest;
109− readonly nowEpochSeconds: number;
110− }) => Effect.Effect<LinkChallengeClaims | null>;
111− readonly issueDpopAccessToken: (input: {
112− readonly userId: string;
113− readonly proofKeyThumbprint: string;
114− readonly jti: string;
115− readonly issuedAtEpochSeconds: number;
116− readonly expiresAtEpochSeconds: number;
117− readonly clientId: RelayPublicClientId;
118− readonly scopes: ReadonlyArray<RelayDpopAccessTokenScope>;
119− }) => Effect.Effect<string, RelayJwtError>;
120− readonly verifyDpopAccessToken: (input: {
121− readonly token: string;
122− readonly nowEpochSeconds: number;
123− }) => Effect.Effect<RelayDpopAccessTokenClaims | null>;
124−}
125−
126−export class RelayTokens extends Context.Service<RelayTokens, RelayTokensShape>()(
127− "t3code-relay/auth/RelayTokens",
128−) {}
96+export class RelayTokens extends Context.Service<
97+ RelayTokens,
98+ {
99+ readonly resolveDpopAccessTokenScopes: typeof resolveDpopAccessTokenScopes;
100+ readonly issueLinkChallenge: (input: {
101+ readonly userId: string;
102+ readonly request: RelayEnvironmentLinkChallengeRequest;
103+ readonly jti: string;
104+ readonly issuedAtEpochSeconds: number;
105+ readonly expiresAtEpochSeconds: number;
106+ }) => Effect.Effect<string, RelayJwtError>;
107+ readonly verifyLinkChallenge: (input: {
108+ readonly token: string;
109+ readonly userId: string;
110+ readonly request: RelayEnvironmentLinkChallengeRequest;
111+ readonly nowEpochSeconds: number;
112+ }) => Effect.Effect<LinkChallengeClaims | null>;
113+ readonly issueDpopAccessToken: (input: {
114+ readonly userId: string;
115+ readonly proofKeyThumbprint: string;
116+ readonly jti: string;
117+ readonly issuedAtEpochSeconds: number;
118+ readonly expiresAtEpochSeconds: number;
119+ readonly clientId: RelayPublicClientId;
120+ readonly scopes: ReadonlyArray<RelayDpopAccessTokenScope>;
121+ }) => Effect.Effect<string, RelayJwtError>;
122+ readonly verifyDpopAccessToken: (input: {
123+ readonly token: string;
124+ readonly nowEpochSeconds: number;
125+ }) => Effect.Effect<RelayDpopAccessTokenClaims | null>;
126+ }
127+>()("t3code-relay/auth/RelayTokens") {}
129128
130129 const make = Effect.gen(function* () {
131130 const config = yield* RelayConfiguration.RelayConfiguration;
132131 const issuer = normalizeRelayIssuer(config.relayIssuer);
133132
134− const issueLinkChallenge: RelayTokensShape["issueLinkChallenge"] = Effect.fn(
133+ const issueLinkChallenge: RelayTokens["Service"]["issueLinkChallenge"] = Effect.fn(
135134 "relay.tokens.issue_link_challenge",
136135 )(function* (input) {
137136 return yield* signRelayJwt({
@@ -150,7 +149,7 @@ const make = Effect.gen(function* () { · 12 unmodified lines
150149 });
151150 });
152151
153− const verifyLinkChallenge: RelayTokensShape["verifyLinkChallenge"] = Effect.fn(
152+ const verifyLinkChallenge: RelayTokens["Service"]["verifyLinkChallenge"] = Effect.fn(
154153 "relay.tokens.verify_link_challenge",
155154 )((input) =>
156155 verifyRelayJwt({
@@ -177,7 +176,7 @@ const make = Effect.gen(function* () { · 20 unmodified lines
177176 ),
178177 );
179178
180− const issueDpopAccessToken: RelayTokensShape["issueDpopAccessToken"] = Effect.fn(
179+ const issueDpopAccessToken: RelayTokens["Service"]["issueDpopAccessToken"] = Effect.fn(
181180 "relay.tokens.issue_dpop_access_token",
182181 )(function* (input) {
183182 return yield* signRelayJwt({
@@ -197,7 +196,7 @@ const make = Effect.gen(function* () { · 13 unmodified lines
197196 });
198197 });
199198
200− const verifyDpopAccessToken: RelayTokensShape["verifyDpopAccessToken"] = Effect.fn(
199+ const verifyDpopAccessToken: RelayTokens["Service"]["verifyDpopAccessToken"] = Effect.fn(
201200 "relay.tokens.verify_dpop_access_token",
202201 )((input) =>
203202 verifyRelayJwt({
infra/relay/src/db.ts+6−5
@@ -10,11 +10,12 @@ import * as Effect from "effect/Effect"; · 9 unmodified lines
1010
1111 import { relayDatabaseMode } from "./dbConfig.ts";
1212
13−export interface RelayDatabase extends EffectPgDatabase {
14− readonly $client: PgClient;
15−}
16−
17−export class RelayDb extends Context.Service<RelayDb, RelayDatabase>()("t3code-relay/db/RelayDb") {}
13+export class RelayDb extends Context.Service<
14+ RelayDb,
15+ EffectPgDatabase & {
16+ readonly $client: PgClient;
17+ }
18+>()("t3code-relay/db/RelayDb") {}
1819
1920 export const PlanetscaleDatabase = Effect.gen(function* () {
2021 const { stage } = yield* Alchemy.Stack;
infra/relay/src/environments/EnvironmentConnector.test.ts+5−5
@@ -161,8 +161,8 @@ function connectorTestLayer( · 160 unmodified lines
161161 request: HttpClientRequest.HttpClientRequest,
162162 ) => Effect.Effect<HttpClientResponse.HttpClientResponse>,
163163 options?: {
164− readonly links?: EnvironmentLinks.EnvironmentLinksShape;
165− readonly allocations?: ManagedEndpointAllocations.ManagedEndpointAllocationsShape;
164+ readonly links?: EnvironmentLinks.EnvironmentLinks["Service"];
165+ readonly allocations?: ManagedEndpointAllocations.ManagedEndpointAllocations["Service"];
166166 },
167167 ) {
168168 return EnvironmentConnector.layer.pipe(
@@ -174,7 +174,7 @@ function connectorTestLayer( · 5 unmodified lines
174174 options?.allocations ?? makeAllocations(),
175175 ),
176176 ),
177− Layer.provide(Layer.succeed(RelayConfiguration.RelayConfiguration, settings)),
177+ Layer.provide(RelayConfiguration.layer(settings)),
178178 Layer.provide(Layer.succeed(HttpClient.HttpClient, HttpClient.make(execute))),
179179 );
180180 }
@@ -189,7 +189,7 @@ function makeAllocations( · 8 unmodified lines
189189 dnsRecordId: "dns-record-id",
190190 readyAt: "2026-05-25T00:00:00.000Z",
191191 },
192−): ManagedEndpointAllocations.ManagedEndpointAllocationsShape {
192+): ManagedEndpointAllocations.ManagedEndpointAllocations["Service"] {
193193 return {
194194 get: () => Effect.succeed(allocation),
195195 reserve: () => Effect.die("unused"),
@@ -202,7 +202,7 @@ function makeAllocations( · 6 unmodified lines
202202
203203 function makeLinks(
204204 overrides: Partial<EnvironmentLinks.RelayLinkedEnvironmentRecord> = {},
205−): EnvironmentLinks.EnvironmentLinksShape {
205+): EnvironmentLinks.EnvironmentLinks["Service"] {
206206 return {
207207 upsert: () => Effect.void,
208208 listUsersForEnvironment: () => Effect.succeed([]),
infra/relay/src/environments/EnvironmentConnector.ts+15−15
@@ -35,7 +35,9 @@ import * as Layer from "effect/Layer"; · 34 unmodified lines
3535 import * as Option from "effect/Option";
3636 import * as Redacted from "effect/Redacted";
3737 import * as Schema from "effect/Schema";
38−import { FetchHttpClient, HttpClient, HttpClientError } from "effect/unstable/http";
38+import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
39+import * as HttpClient from "effect/unstable/http/HttpClient";
40+import * as HttpClientError from "effect/unstable/http/HttpClientError";
3941
4042 import * as EnvironmentLinks from "./EnvironmentLinks.ts";
4143 import * as ManagedEndpointAllocations from "./ManagedEndpointAllocations.ts";
@@ -139,22 +141,20 @@ export type EnvironmentConnectorError = · 97 unmodified lines
139141 export const ENVIRONMENT_MINT_REQUEST_TIMEOUT_MS = 10_000;
140142 const ENVIRONMENT_HEALTH_CLOCK_SKEW_MILLIS = 60 * 1_000;
141143
142−export interface EnvironmentConnectorShape {
143− readonly connect: (input: {
144− readonly userId: string;
145− readonly environmentId: string;
146− readonly clientProofKeyThumbprint: string;
147− readonly deviceId?: string;
148− }) => Effect.Effect<RelayEnvironmentConnectResponse, EnvironmentConnectorError>;
149− readonly status: (input: {
150− readonly userId: string;
151− readonly environmentId: string;
152− }) => Effect.Effect<RelayEnvironmentStatusResponse, EnvironmentConnectorError>;
153−}
154−
155144 export class EnvironmentConnector extends Context.Service<
156145 EnvironmentConnector,
157− EnvironmentConnectorShape
146+ {
147+ readonly connect: (input: {
148+ readonly userId: string;
149+ readonly environmentId: string;
150+ readonly clientProofKeyThumbprint: string;
151+ readonly deviceId?: string;
152+ }) => Effect.Effect<RelayEnvironmentConnectResponse, EnvironmentConnectorError>;
153+ readonly status: (input: {
154+ readonly userId: string;
155+ readonly environmentId: string;
156+ }) => Effect.Effect<RelayEnvironmentStatusResponse, EnvironmentConnectorError>;
157+ }
158158 >()("t3code-relay/environments/EnvironmentConnector") {}
159159
160160 const decodeMintResponseProof = Schema.decodeUnknownEffect(
infra/relay/src/environments/EnvironmentCredentials.test.ts+5−5
@@ -4,7 +4,7 @@ import { PgDialect, QueryBuilder } from "drizzle-orm/pg-core"; · 3 unmodified lines
44 import * as Effect from "effect/Effect";
55 import * as Layer from "effect/Layer";
66
7−import { RelayDb, type RelayDatabase } from "../db.ts";
7+import * as RelayDb from "../db.ts";
88 import { relayEnvironmentCredentials } from "../persistence/schema.ts";
99 import * as EnvironmentCredentials from "./EnvironmentCredentials.ts";
1010
@@ -47,7 +47,7 @@ describe("EnvironmentCredentials", () => { · 36 unmodified lines
4747 }),
4848 };
4949 },
50− } as unknown as RelayDatabase;
50+ } as unknown as RelayDb.RelayDb["Service"];
5151
5252 return Effect.gen(function* () {
5353 const credentials = yield* EnvironmentCredentials.EnvironmentCredentials;
@@ -87,7 +87,7 @@ describe("EnvironmentCredentials", () => { · 33 unmodified lines
8787 Effect.provide(
8888 EnvironmentCredentials.layer.pipe(
8989 Layer.provide(NodeCryptoLayer.layer),
90− Layer.provide(Layer.succeed(RelayDb, fakeDb)),
90+ Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)),
9191 ),
9292 ),
9393 );
@@ -118,7 +118,7 @@ describe("EnvironmentCredentials", () => { · 24 unmodified lines
118118 },
119119 };
120120 },
121− } as unknown as RelayDatabase;
121+ } as unknown as RelayDb.RelayDb["Service"];
122122
123123 return Effect.gen(function* () {
124124 const credentials = yield* EnvironmentCredentials.EnvironmentCredentials;
@@ -150,7 +150,7 @@ describe("EnvironmentCredentials", () => { · 25 unmodified lines
150150 Effect.provide(
151151 EnvironmentCredentials.layer.pipe(
152152 Layer.provide(NodeCryptoLayer.layer),
153− Layer.provide(Layer.succeed(RelayDb, fakeDb)),
153+ Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)),
154154 ),
155155 ),
156156 );
infra/relay/src/environments/EnvironmentCredentials.ts+18−20
@@ -8,7 +8,7 @@ import * as Option from "effect/Option"; · 7 unmodified lines
88 import * as Schema from "effect/Schema";
99 import { and, eq, isNull, ne, notExists } from "drizzle-orm";
1010
11−import { RelayDb } from "../db.ts";
11+import * as RelayDb from "../db.ts";
1212 import { relayEnvironmentCredentials, relayEnvironmentLinks } from "../persistence/schema.ts";
1313
1414 export class EnvironmentCredentialCreatePersistenceError extends Schema.TaggedErrorClass<EnvironmentCredentialCreatePersistenceError>()(
@@ -44,30 +44,28 @@ export interface EnvironmentCredentialPrincipal { · 29 unmodified lines
4444 readonly environmentPublicKey: string;
4545 }
4646
47−export interface EnvironmentCredentialsShape {
48− readonly create: (input: {
49− readonly environmentId: string;
50− readonly environmentPublicKey: string;
51− }) => Effect.Effect<string, EnvironmentCredentialCreatePersistenceError>;
52− readonly authenticate: (
53− token: string,
54− ) => Effect.Effect<
55− Option.Option<EnvironmentCredentialPrincipal>,
56− EnvironmentCredentialAuthenticatePersistenceError
57− >;
58− readonly revokeForEnvironmentPublicKey: (input: {
59− readonly environmentId: string;
60− readonly environmentPublicKey: string;
61− }) => Effect.Effect<boolean, EnvironmentCredentialRevokePersistenceError>;
62−}
63−
6447 export class EnvironmentCredentials extends Context.Service<
6548 EnvironmentCredentials,
66− EnvironmentCredentialsShape
49+ {
50+ readonly create: (input: {
51+ readonly environmentId: string;
52+ readonly environmentPublicKey: string;
53+ }) => Effect.Effect<string, EnvironmentCredentialCreatePersistenceError>;
54+ readonly authenticate: (
55+ token: string,
56+ ) => Effect.Effect<
57+ Option.Option<EnvironmentCredentialPrincipal>,
58+ EnvironmentCredentialAuthenticatePersistenceError
59+ >;
60+ readonly revokeForEnvironmentPublicKey: (input: {
61+ readonly environmentId: string;
62+ readonly environmentPublicKey: string;
63+ }) => Effect.Effect<boolean, EnvironmentCredentialRevokePersistenceError>;
64+ }
6765 >()("t3code-relay/environments/EnvironmentCredentials") {}
6866
6967 const make = Effect.gen(function* () {
70− const db = yield* RelayDb;
68+ const db = yield* RelayDb.RelayDb;
7169 const crypto = yield* Crypto.Crypto;
7270 const hashToken = (token: string) =>
7371 crypto
infra/relay/src/environments/EnvironmentLinker.test.ts+3−3
@@ -105,14 +105,14 @@ const makeRequest = Effect.gen(function* () { · 104 unmodified lines
105105 });
106106
107107 function testLayer(input?: {
108− readonly upsert?: EnvironmentLinks.EnvironmentLinksShape["upsert"];
109− readonly consume?: DpopProofs.DpopProofReplayShape["consume"];
108+ readonly upsert?: EnvironmentLinks.EnvironmentLinks["Service"]["upsert"];
109+ readonly consume?: DpopProofs.DpopProofReplay["Service"]["consume"];
110110 }) {
111111 return EnvironmentLinker.layer.pipe(
112112 Layer.provideMerge(RelayTokens.layer),
113113 Layer.provide(
114114 Layer.mergeAll(
115− Layer.succeed(RelayConfiguration.RelayConfiguration, config),
115+ RelayConfiguration.layer(config),
116116 Layer.succeed(DpopProofs.DpopProofReplay, {
117117 verifyAndConsume: () => Effect.die("unexpected DPoP proof verification"),
118118 consume: input?.consume ?? (() => Effect.succeed(true)),
infra/relay/src/environments/EnvironmentLinker.ts+19−20
@@ -53,26 +53,25 @@ export type EnvironmentLinkError = · 52 unmodified lines
5353 | EnvironmentCredentials.EnvironmentCredentialCreatePersistenceError
5454 | ManagedEndpointProvider.ManagedEndpointProviderError;
5555
56−export interface EnvironmentLinkerShape {
57− readonly link: (input: {
58− readonly userId: string;
59− readonly request: RelayEnvironmentLinkRequest;
60− }) => Effect.Effect<
61− {
62− readonly environmentId: RelayEnvironmentLinkProofPayload["environmentId"];
63− readonly endpoint: RelayEnvironmentLinkProofPayload["endpoint"];
64− readonly endpointRuntime:
65− | ManagedEndpointProvider.ManagedEndpointProvisioningResult["runtime"]
66− | null;
67− readonly environmentCredential: string;
68− },
69− EnvironmentLinkError
70− >;
71−}
72−
73−export class EnvironmentLinker extends Context.Service<EnvironmentLinker, EnvironmentLinkerShape>()(
74− "t3code-relay/environments/EnvironmentLinker",
75−) {}
56+export class EnvironmentLinker extends Context.Service<
57+ EnvironmentLinker,
58+ {
59+ readonly link: (input: {
60+ readonly userId: string;
61+ readonly request: RelayEnvironmentLinkRequest;
62+ }) => Effect.Effect<
63+ {
64+ readonly environmentId: RelayEnvironmentLinkProofPayload["environmentId"];
65+ readonly endpoint: RelayEnvironmentLinkProofPayload["endpoint"];
66+ readonly endpointRuntime:
67+ | ManagedEndpointProvider.ManagedEndpointProvisioningResult["runtime"]
68+ | null;
69+ readonly environmentCredential: string;
70+ },
71+ EnvironmentLinkError
72+ >;
73+ }
74+>()("t3code-relay/environments/EnvironmentLinker") {}
7675
7776 const decodeProof = Schema.decodeUnknownEffect(RelayEnvironmentLinkProofPayload);
7877
infra/relay/src/environments/EnvironmentLinks.test.ts+16−8
@@ -3,9 +3,9 @@ import * as Effect from "effect/Effect"; · 2 unmodified lines
33 import * as Layer from "effect/Layer";
44 import { PgDialect } from "drizzle-orm/pg-core";
55
6−import { RelayDb, type RelayDatabase } from "../db.ts";
6+import * as RelayDb from "../db.ts";
77 import { relayEnvironmentLinks } from "../persistence/schema.ts";
8−import { EnvironmentLinks, layer } from "./EnvironmentLinks.ts";
8+import * as EnvironmentLinks from "./EnvironmentLinks.ts";
99
1010 describe("EnvironmentLinks", () => {
1111 it.effect("selects users when either notifications or Live Activities are enabled", () => {
@@ -25,10 +25,10 @@ describe("EnvironmentLinks", () => { · 13 unmodified lines
2525 },
2626 };
2727 },
28− } as unknown as RelayDatabase;
28+ } as unknown as RelayDb.RelayDb["Service"];
2929
3030 return Effect.gen(function* () {
31− const links = yield* EnvironmentLinks;
31+ const links = yield* EnvironmentLinks.EnvironmentLinks;
3232 expect(yield* links.listUsersForEnvironment({ environmentId: "env-1" })).toEqual([]);
3333 expect(whereConditions).toHaveLength(1);
3434
@@ -39,7 +39,11 @@ describe("EnvironmentLinks", () => { · 4 unmodified lines
3939 expect(query.sql).toContain('"relay_environment_links"."live_activities_enabled" = $3');
4040 expect(query.sql).toContain(" or ");
4141 expect(query.params).toEqual(["env-1", true, true]);
42− }).pipe(Effect.provide(layer.pipe(Layer.provide(Layer.succeed(RelayDb, fakeDb)))));
42+ }).pipe(
43+ Effect.provide(
44+ EnvironmentLinks.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb))),
45+ ),
46+ );
4347 });
4448
4549 it.effect("revokes only the active link owned by the requesting user", () => {
@@ -65,10 +69,10 @@ describe("EnvironmentLinks", () => { · 19 unmodified lines
6569 },
6670 };
6771 },
68− } as unknown as RelayDatabase;
72+ } as unknown as RelayDb.RelayDb["Service"];
6973
7074 return Effect.gen(function* () {
71− const links = yield* EnvironmentLinks;
75+ const links = yield* EnvironmentLinks.EnvironmentLinks;
7276 const revoked = yield* links.revokeForUser({
7377 userId: "user-1",
7478 environmentId: "env-1",
@@ -86,6 +90,10 @@ describe("EnvironmentLinks", () => { · 11 unmodified lines
8690 expect(query.sql).toContain('"relay_environment_links"."environment_id" = $2');
8791 expect(query.sql).toContain('"relay_environment_links"."revoked_at" is null');
8892 expect(query.params).toEqual(["user-1", "env-1"]);
89− }).pipe(Effect.provide(layer.pipe(Layer.provide(Layer.succeed(RelayDb, fakeDb)))));
93+ }).pipe(
94+ Effect.provide(
95+ EnvironmentLinks.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb))),
96+ ),
97+ );
9098 });
9199 });
infra/relay/src/environments/EnvironmentLinks.ts+40−41
@@ -11,7 +11,7 @@ import * as Layer from "effect/Layer"; · 10 unmodified lines
1111 import * as Schema from "effect/Schema";
1212 import { and, eq, isNull, or } from "drizzle-orm";
1313
14−import { RelayDb } from "../db.ts";
14+import * as RelayDb from "../db.ts";
1515 import { relayEnvironmentLinks } from "../persistence/schema.ts";
1616
1717 export interface RelayLinkedEnvironmentRecord extends RelayClientEnvironmentRecord {
@@ -78,45 +78,44 @@ export class EnvironmentLinkRevokePersistenceError extends Schema.TaggedErrorCla · 60 unmodified lines
7878 }
7979 }
8080
81−export interface EnvironmentLinksShape {
82− readonly upsert: (input: {
83− readonly userId: string;
84− readonly request: RelayEnvironmentLinkRequest;
85− readonly proof: RelayEnvironmentLinkProofPayload;
86− readonly endpoint: RelayManagedEndpoint;
87− }) => Effect.Effect<void, EnvironmentLinkUpsertPersistenceError>;
88− readonly listUsersForEnvironment: (input: {
89− readonly environmentId: string;
90− }) => Effect.Effect<ReadonlyArray<string>, EnvironmentLinkUserListPersistenceError>;
91− readonly listDeliveryUsersForEnvironment: (input: {
92− readonly environmentId: string;
93− readonly environmentPublicKey: string;
94− }) => Effect.Effect<
95− ReadonlyArray<AgentAwarenessDeliveryUserRecord>,
96− EnvironmentLinkUserListPersistenceError
97− >;
98− readonly listPublicKeysForEnvironment: (input: {
99− readonly environmentId: string;
100− }) => Effect.Effect<ReadonlyArray<string>, EnvironmentPublicKeyListPersistenceError>;
101− readonly listForUser: (input: {
102− readonly userId: string;
103− }) => Effect.Effect<
104− ReadonlyArray<RelayClientEnvironmentRecord>,
105− EnvironmentLinkListPersistenceError
106− >;
107− readonly getForUser: (input: {
108− readonly userId: string;
109− readonly environmentId: string;
110− }) => Effect.Effect<RelayLinkedEnvironmentRecord | null, EnvironmentLinkLookupPersistenceError>;
111− readonly revokeForUser: (input: {
112− readonly userId: string;
113− readonly environmentId: string;
114− }) => Effect.Effect<boolean, EnvironmentLinkRevokePersistenceError>;
115−}
116−
117−export class EnvironmentLinks extends Context.Service<EnvironmentLinks, EnvironmentLinksShape>()(
118− "t3code-relay/environments/EnvironmentLinks",
119−) {}
81+export class EnvironmentLinks extends Context.Service<
82+ EnvironmentLinks,
83+ {
84+ readonly upsert: (input: {
85+ readonly userId: string;
86+ readonly request: RelayEnvironmentLinkRequest;
87+ readonly proof: RelayEnvironmentLinkProofPayload;
88+ readonly endpoint: RelayManagedEndpoint;
89+ }) => Effect.Effect<void, EnvironmentLinkUpsertPersistenceError>;
90+ readonly listUsersForEnvironment: (input: {
91+ readonly environmentId: string;
92+ }) => Effect.Effect<ReadonlyArray<string>, EnvironmentLinkUserListPersistenceError>;
93+ readonly listDeliveryUsersForEnvironment: (input: {
94+ readonly environmentId: string;
95+ readonly environmentPublicKey: string;
96+ }) => Effect.Effect<
97+ ReadonlyArray<AgentAwarenessDeliveryUserRecord>,
98+ EnvironmentLinkUserListPersistenceError
99+ >;
100+ readonly listPublicKeysForEnvironment: (input: {
101+ readonly environmentId: string;
102+ }) => Effect.Effect<ReadonlyArray<string>, EnvironmentPublicKeyListPersistenceError>;
103+ readonly listForUser: (input: {
104+ readonly userId: string;
105+ }) => Effect.Effect<
106+ ReadonlyArray<RelayClientEnvironmentRecord>,
107+ EnvironmentLinkListPersistenceError
108+ >;
109+ readonly getForUser: (input: {
110+ readonly userId: string;
111+ readonly environmentId: string;
112+ }) => Effect.Effect<RelayLinkedEnvironmentRecord | null, EnvironmentLinkLookupPersistenceError>;
113+ readonly revokeForUser: (input: {
114+ readonly userId: string;
115+ readonly environmentId: string;
116+ }) => Effect.Effect<boolean, EnvironmentLinkRevokePersistenceError>;
117+ }
118+>()("t3code-relay/environments/EnvironmentLinks") {}
120119
121120 function agentAwarenessDeliveryUserCondition(environmentId: string) {
122121 return and(
@@ -140,7 +139,7 @@ function agentAwarenessDeliveryUserKeyCondition(input: { · 17 unmodified lines
140139 }
141140
142141 const make = Effect.gen(function* () {
143− const db = yield* RelayDb;
142+ const db = yield* RelayDb.RelayDb;
144143
145144 return EnvironmentLinks.of({
146145 upsert: Effect.fn("relay.environment_links.upsert")(
infra/relay/src/environments/EnvironmentPublishSignatures.test.ts+2−2
@@ -80,11 +80,11 @@ const freshRequest = Effect.gen(function* () { · 79 unmodified lines
8080 } satisfies RelayAgentActivityPublishRequest;
8181 });
8282
83−function layer(replay?: Partial<DpopProofs.DpopProofReplayShape>) {
83+function layer(replay?: Partial<DpopProofs.DpopProofReplay["Service"]>) {
8484 return EnvironmentPublishSignatures.layer.pipe(
8585 Layer.provide(
8686 Layer.merge(
87− Layer.succeed(RelayConfiguration.RelayConfiguration, config),
87+ RelayConfiguration.layer(config),
8888 Layer.succeed(DpopProofs.DpopProofReplay, {
8989 verifyAndConsume:
9090 replay?.verifyAndConsume ?? (() => Effect.die("unexpected DPoP proof verification")),
infra/relay/src/environments/EnvironmentPublishSignatures.ts+8−10
@@ -59,18 +59,16 @@ export type EnvironmentPublishSignatureError = · 58 unmodified lines
5959 | EnvironmentPublishPublicKeyMissing
6060 | DpopProofs.DpopProofReplayPersistenceError;
6161
62−export interface EnvironmentPublishSignaturesShape {
63− readonly verify: (input: {
64− readonly environmentId: string;
65− readonly environmentPublicKey: string;
66− readonly threadId: string;
67− readonly request: RelayAgentActivityPublishRequest;
68− }) => Effect.Effect<void, EnvironmentPublishSignatureError>;
69−}
70−
7162 export class EnvironmentPublishSignatures extends Context.Service<
7263 EnvironmentPublishSignatures,
73− EnvironmentPublishSignaturesShape
64+ {
65+ readonly verify: (input: {
66+ readonly environmentId: string;
67+ readonly environmentPublicKey: string;
68+ readonly threadId: string;
69+ readonly request: RelayAgentActivityPublishRequest;
70+ }) => Effect.Effect<void, EnvironmentPublishSignatureError>;
71+ }
7472 >()("t3code-relay/environments/EnvironmentPublishSignatures") {}
7573
7674 const decodeProof = Schema.decodeUnknownEffect(RelayAgentActivityPublishProofPayload);
infra/relay/src/environments/ManagedEndpointAllocations.ts+26−28
@@ -6,7 +6,7 @@ import * as Effect from "effect/Effect"; · 5 unmodified lines
66 import * as Layer from "effect/Layer";
77 import * as Schema from "effect/Schema";
88
9−import { RelayDb } from "../db.ts";
9+import * as RelayDb from "../db.ts";
1010 import { isManagedEndpointHostname, managedEndpointForHostname } from "../deploymentConfig.ts";
1111 import { relayManagedEndpointAllocations } from "../persistence/schema.ts";
1212
@@ -66,26 +66,29 @@ interface RecordManagedEndpointDnsInput extends ManagedEndpointAllocationKey { · 53 unmodified lines
6666 readonly dnsRecordId: string;
6767 }
6868
69−export interface ManagedEndpointAllocationsShape {
70− readonly get: (
71− input: ManagedEndpointAllocationKey,
72− ) => Effect.Effect<ManagedEndpointAllocation | null, ManagedEndpointAllocationPersistenceError>;
73− readonly reserve: (
74− input: ReserveManagedEndpointAllocationInput,
75− ) => Effect.Effect<ManagedEndpointAllocation, ManagedEndpointAllocationPersistenceError>;
76− readonly recordTunnel: (
77− input: RecordManagedEndpointTunnelInput,
78− ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>;
79− readonly recordDns: (
80− input: RecordManagedEndpointDnsInput,
81− ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>;
82− readonly markReady: (
83− input: ManagedEndpointAllocationKey,
84− ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>;
85− readonly remove: (
86− input: ManagedEndpointAllocationKey,
87− ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>;
88−}
69+export class ManagedEndpointAllocations extends Context.Service<
70+ ManagedEndpointAllocations,
71+ {
72+ readonly get: (
73+ input: ManagedEndpointAllocationKey,
74+ ) => Effect.Effect<ManagedEndpointAllocation | null, ManagedEndpointAllocationPersistenceError>;
75+ readonly reserve: (
76+ input: ReserveManagedEndpointAllocationInput,
77+ ) => Effect.Effect<ManagedEndpointAllocation, ManagedEndpointAllocationPersistenceError>;
78+ readonly recordTunnel: (
79+ input: RecordManagedEndpointTunnelInput,
80+ ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>;
81+ readonly recordDns: (
82+ input: RecordManagedEndpointDnsInput,
83+ ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>;
84+ readonly markReady: (
85+ input: ManagedEndpointAllocationKey,
86+ ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>;
87+ readonly remove: (
88+ input: ManagedEndpointAllocationKey,
89+ ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>;
90+ }
91+>()("t3code-relay/environments/ManagedEndpointAllocations") {}
8992
9093 const allocationSelection = {
9194 userId: relayManagedEndpointAllocations.userId,
@@ -109,7 +112,7 @@ const persistenceError = (cause: unknown) => · 17 unmodified lines
109112 : new ManagedEndpointAllocationPersistenceError({ cause });
110113
111114 const make = Effect.gen(function* () {
112− const db = yield* RelayDb;
115+ const db = yield* RelayDb.RelayDb;
113116
114117 return ManagedEndpointAllocations.of({
115118 get: Effect.fn("relay.managed_endpoint_allocations.get")(function* (
@@ -198,9 +201,4 @@ const make = Effect.gen(function* () { · 82 unmodified lines
198201 });
199202 });
200203
201−export class ManagedEndpointAllocations extends Context.Service<
202− ManagedEndpointAllocations,
203− ManagedEndpointAllocationsShape
204−>()("t3code-relay/environments/ManagedEndpointAllocations") {
205− static readonly layer = Layer.effect(this, make);
206−}
204+export const layer = Layer.effect(ManagedEndpointAllocations, make);
infra/relay/src/environments/ManagedEndpointProvider.test.ts+3−3
@@ -204,9 +204,9 @@ function providerLayer( · 203 unmodified lines
204204 ) {
205205 return ManagedEndpointProvider.layer.pipe(
206206 Layer.provideMerge(NodeServices.layer),
207− Layer.provide(Layer.succeed(RelayConfiguration.RelayConfiguration, config)),
208− Layer.provide(Layer.succeed(ManagedEndpointProvider.ManagedEndpointTunnelClient, tunnelClient)),
209− Layer.provide(Layer.succeed(ManagedEndpointProvider.ManagedEndpointDnsClient, dnsClient)),
207+ Layer.provide(RelayConfiguration.layer(config)),
208+ Layer.provide(ManagedEndpointProvider.layerTunnelClient(tunnelClient)),
209+ Layer.provide(ManagedEndpointProvider.layerDnsClient(dnsClient)),
210210 Layer.provide(
211211 Layer.succeed(ManagedEndpointAllocations.ManagedEndpointAllocations, allocations),
212212 ),
infra/relay/src/environments/ManagedEndpointProvider.ts+69−63
@@ -23,7 +23,7 @@ import { · 22 unmodified lines
2323 managedEndpointHostname,
2424 managedEndpointTunnelName,
2525 } from "../deploymentConfig.ts";
26−import { ManagedEndpointAllocations } from "./ManagedEndpointAllocations.ts";
26+import * as ManagedEndpointAllocations from "./ManagedEndpointAllocations.ts";
2727
2828 export class ManagedEndpointProvisioningNotConfigured extends Schema.TaggedErrorClass<ManagedEndpointProvisioningNotConfigured>()(
2929 "ManagedEndpointProvisioningNotConfigured",
@@ -74,21 +74,19 @@ export interface ManagedEndpointProvisioningResult { · 44 unmodified lines
7474 readonly runtime: RelayManagedEndpointRuntimeConfig;
7575 }
7676
77−export interface ManagedEndpointProviderShape {
78− readonly provision: (input: {
79− readonly userId: string;
80− readonly environmentId: string;
81− readonly origin: RelayManagedEndpointOrigin;
82− }) => Effect.Effect<ManagedEndpointProvisioningResult, ManagedEndpointProviderError>;
83− readonly deprovision: (input: {
84− readonly userId: string;
85− readonly environmentId: string;
86− }) => Effect.Effect<void, ManagedEndpointDeprovisioningFailed>;
87−}
88−
8977 export class ManagedEndpointProvider extends Context.Service<
9078 ManagedEndpointProvider,
91− ManagedEndpointProviderShape
79+ {
80+ readonly provision: (input: {
81+ readonly userId: string;
82+ readonly environmentId: string;
83+ readonly origin: RelayManagedEndpointOrigin;
84+ }) => Effect.Effect<ManagedEndpointProvisioningResult, ManagedEndpointProviderError>;
85+ readonly deprovision: (input: {
86+ readonly userId: string;
87+ readonly environmentId: string;
88+ }) => Effect.Effect<void, ManagedEndpointDeprovisioningFailed>;
89+ }
9290 >()("t3code-relay/environments/ManagedEndpointProvider") {}
9391
9492 interface ManagedEndpointTunnel {
@@ -105,36 +103,42 @@ export class ManagedEndpointTunnelClientError extends Schema.TaggedErrorClass<Ma · 10 unmodified lines
105103 }
106104 }
107105
108−export interface ManagedEndpointTunnelClientShape {
109− readonly list: (request: {
110− readonly name: string;
111− readonly isDeleted: false;
112− }) => Effect.Effect<
113− { readonly result: ReadonlyArray<ManagedEndpointTunnel> },
114− ManagedEndpointTunnelClientError
115− >;
116− readonly create: (request: {
117− readonly name: string;
118− readonly configSrc: "cloudflare";
119− }) => Effect.Effect<ManagedEndpointTunnel, ManagedEndpointTunnelClientError>;
120− readonly putConfiguration: (
121− tunnelId: string,
122− config: {
123− readonly ingress: Array<{
124− readonly hostname?: string;
125− readonly service: string;
126− }>;
127− },
128− ) => Effect.Effect<unknown, ManagedEndpointTunnelClientError>;
129− readonly getToken: (tunnelId: string) => Effect.Effect<string, ManagedEndpointTunnelClientError>;
130− readonly delete: (tunnelId: string) => Effect.Effect<unknown, ManagedEndpointTunnelClientError>;
131−}
132−
133106 export class ManagedEndpointTunnelClient extends Context.Service<
134107 ManagedEndpointTunnelClient,
135− ManagedEndpointTunnelClientShape
108+ {
109+ readonly list: (request: {
110+ readonly name: string;
111+ readonly isDeleted: false;
112+ }) => Effect.Effect<
113+ { readonly result: ReadonlyArray<ManagedEndpointTunnel> },
114+ ManagedEndpointTunnelClientError
115+ >;
116+ readonly create: (request: {
117+ readonly name: string;
118+ readonly configSrc: "cloudflare";
119+ }) => Effect.Effect<ManagedEndpointTunnel, ManagedEndpointTunnelClientError>;
120+ readonly putConfiguration: (
121+ tunnelId: string,
122+ config: {
123+ readonly ingress: Array<{
124+ readonly hostname?: string;
125+ readonly service: string;
126+ }>;
127+ },
128+ ) => Effect.Effect<unknown, ManagedEndpointTunnelClientError>;
129+ readonly getToken: (
130+ tunnelId: string,
131+ ) => Effect.Effect<string, ManagedEndpointTunnelClientError>;
132+ readonly delete: (tunnelId: string) => Effect.Effect<unknown, ManagedEndpointTunnelClientError>;
133+ }
136134 >()("t3code-relay/environments/ManagedEndpointProvider/ManagedEndpointTunnelClient") {}
137135
136+export const makeTunnelClient = (client: ManagedEndpointTunnelClient["Service"]) =>
137+ ManagedEndpointTunnelClient.of(client);
138+
139+export const layerTunnelClient = (client: ManagedEndpointTunnelClient["Service"]) =>
140+ Layer.succeed(ManagedEndpointTunnelClient, makeTunnelClient(client));
141+
138142 interface ManagedEndpointCnameRecordInput {
139143 readonly type: "CNAME";
140144 readonly name: string;
@@ -152,29 +156,33 @@ export class ManagedEndpointDnsClientError extends Schema.TaggedErrorClass<Manag · 11 unmodified lines
152156 }
153157 }
154158
155−export interface ManagedEndpointDnsClientShape {
156− readonly listRecords: (
157− hostname: string,
158− ) => Effect.Effect<ReadonlyArray<{ readonly id: string }>, ManagedEndpointDnsClientError>;
159− readonly createRecord: (
160− request: ManagedEndpointCnameRecordInput,
161− ) => Effect.Effect<{ readonly id: string }, ManagedEndpointDnsClientError>;
162− readonly updateRecord: (
163− dnsRecordId: string,
164− request: ManagedEndpointCnameRecordInput,
165− ) => Effect.Effect<unknown, ManagedEndpointDnsClientError>;
166− readonly deleteRecord: (
167− dnsRecordId: string,
168− ) => Effect.Effect<unknown, ManagedEndpointDnsClientError>;
169−}
170−
171159 export class ManagedEndpointDnsClient extends Context.Service<
172160 ManagedEndpointDnsClient,
173− ManagedEndpointDnsClientShape
161+ {
162+ readonly listRecords: (
163+ hostname: string,
164+ ) => Effect.Effect<ReadonlyArray<{ readonly id: string }>, ManagedEndpointDnsClientError>;
165+ readonly createRecord: (
166+ request: ManagedEndpointCnameRecordInput,
167+ ) => Effect.Effect<{ readonly id: string }, ManagedEndpointDnsClientError>;
168+ readonly updateRecord: (
169+ dnsRecordId: string,
170+ request: ManagedEndpointCnameRecordInput,
171+ ) => Effect.Effect<unknown, ManagedEndpointDnsClientError>;
172+ readonly deleteRecord: (
173+ dnsRecordId: string,
174+ ) => Effect.Effect<unknown, ManagedEndpointDnsClientError>;
175+ }
174176 >()("t3code-relay/environments/ManagedEndpointProvider/ManagedEndpointDnsClient") {}
175177
178+export const makeDnsClient = (client: ManagedEndpointDnsClient["Service"]) =>
179+ ManagedEndpointDnsClient.of(client);
180+
181+export const layerDnsClient = (client: ManagedEndpointDnsClient["Service"]) =>
182+ Layer.succeed(ManagedEndpointDnsClient, makeDnsClient(client));
183+
176184 const requireCloudflareSettings = Effect.fnUntraced(function* (
177− settings: RelayConfiguration.RelayConfigurationShape,
185+ settings: RelayConfiguration.RelayConfiguration["Service"],
178186 ) {
179187 if (!settings.managedEndpointBaseDomain || !settings.managedEndpointNamespace) {
180188 return yield* new ManagedEndpointProvisioningNotConfigured();
@@ -234,7 +242,7 @@ const make = Effect.gen(function* () { · 53 unmodified lines
234242 const crypto = yield* Crypto.Crypto;
235243 const tunnels = yield* ManagedEndpointTunnelClient;
236244 const dns = yield* ManagedEndpointDnsClient;
237− const allocations = yield* ManagedEndpointAllocations;
245+ const allocations = yield* ManagedEndpointAllocations.ManagedEndpointAllocations;
238246
239247 const updateExistingDnsRecords = Effect.fnUntraced(function* (
240248 records: ReadonlyArray<{ readonly id: string }>,
@@ -452,8 +460,7 @@ export const layerCloudflareBindings = ( · 211 unmodified lines
452460 layer.pipe(
453461 Layer.provide(
454462 Layer.mergeAll(
455− Layer.succeed(
456− ManagedEndpointTunnelClient,
463+ layerTunnelClient(
457464 ManagedEndpointTunnelClient.of({
458465 list: (request) =>
459466 tunnelClient.list(request).pipe(
@@ -482,8 +489,7 @@ export const layerCloudflareBindings = ( · 22 unmodified lines
482489 ),
483490 }),
484491 ),
485− Layer.succeed(
486− ManagedEndpointDnsClient,
492+ layerDnsClient(
487493 ManagedEndpointDnsClient.of({
488494 listRecords: (hostname) =>
489495 dnsClient.listDnsRecords({ search: hostname }).pipe(
infra/relay/src/http/Api.test.ts+2−2
@@ -30,7 +30,7 @@ vi.mock("@clerk/backend", () => ({ · 29 unmodified lines
3030 verifyToken: vi.fn(),
3131 }));
3232
33−const relaySettings: RelayConfiguration.RelayConfigurationShape = {
33+const relaySettings: RelayConfiguration.RelayConfiguration["Service"] = {
3434 relayIssuer: "https://relay.example.test",
3535 apns: {
3636 teamId: "apns-team",
@@ -110,7 +110,7 @@ describe("relay environment authentication", () => { · 73 unmodified lines
110110 const failure = new EnvironmentCredentials.EnvironmentCredentialAuthenticatePersistenceError({
111111 cause: "database unavailable",
112112 });
113− const credentials: EnvironmentCredentials.EnvironmentCredentialsShape = {
113+ const credentials: EnvironmentCredentials.EnvironmentCredentials["Service"] = {
114114 create: () => Effect.die("unused create"),
115115 authenticate: () => Effect.fail(failure),
116116 revokeForEnvironmentPublicKey: () => Effect.die("unused revoke"),
infra/relay/src/http/Api.ts+8−5
@@ -66,7 +66,7 @@ import * as ManagedEndpointAllocations from "../environments/ManagedEndpointAllo · 65 unmodified lines
6666 import * as EnvironmentPublishSignatures from "../environments/EnvironmentPublishSignatures.ts";
6767 import * as MobileRegistrations from "../agentActivity/MobileRegistrations.ts";
6868 import { withSpanAttributes } from "../observability.ts";
69−import { RelayDb } from "../db.ts";
69+import * as RelayDb from "../db.ts";
7070
7171 const relayCorsAllowedMethods = ["GET", "POST", "DELETE", "OPTIONS"] as const;
7272 const relayCorsAllowedHeaders = [
@@ -347,7 +347,7 @@ export const healthApi = HttpApiBuilder.group( · 274 unmodified lines
347347 RelayApi,
348348 "health",
349349 Effect.fnUntraced(function* (handlers) {
350− const db = yield* RelayDb;
350+ const db = yield* RelayDb.RelayDb;
351351 return handlers.handle(
352352 "health",
353353 Effect.fn("relay.api.health")(
@@ -985,7 +985,10 @@ function hasExpectedClerkAudience(audience: unknown, expectedAudience: string): · 631 unmodified lines
985985 audience.some((entry) => typeof entry === "string" && entry === expectedAudience);
986986 }
987987
988−function verifyClerkBearerToken(config: RelayConfiguration.RelayConfigurationShape, token: string) {
988+function verifyClerkBearerToken(
989+ config: RelayConfiguration.RelayConfiguration["Service"],
990+ token: string,
991+) {
989992 return Effect.tryPromise({
990993 try: () =>
991994 verifyToken(token, {
@@ -1001,7 +1004,7 @@ function verifyClerkBearerToken(config: RelayConfiguration.RelayConfigurationSha · 9 unmodified lines
10011004 }
10021005
10031006 function verifyClerkOAuthBearerToken(
1004− config: RelayConfiguration.RelayConfigurationShape,
1007+ config: RelayConfiguration.RelayConfiguration["Service"],
10051008 token: string,
10061009 ) {
10071010 return Effect.tryPromise({
@@ -1027,7 +1030,7 @@ function verifyClerkOAuthBearerToken( · 19 unmodified lines
10271030 }
10281031
10291032 export function verifyRelayClientBearerToken(
1030− config: RelayConfiguration.RelayConfigurationShape,
1033+ config: RelayConfiguration.RelayConfiguration["Service"],
10311034 token: string,
10321035 ) {
10331036 return verifyClerkBearerToken(config, token).pipe(
infra/relay/src/observability.test.ts+2−2
@@ -9,7 +9,7 @@ import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest"; · 8 unmodified lines
99 import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
1010 import type { OtlpTracer } from "effect/unstable/observability";
1111
12−import { EnvironmentConnectNotAuthorized } from "./environments/EnvironmentConnector.ts";
12+import * as EnvironmentConnector from "./environments/EnvironmentConnector.ts";
1313 import { makeRelayTraceLayer } from "./observability.ts";
1414
1515 interface ExportedRequest {
@@ -43,7 +43,7 @@ it.effect("exports schema error fields as span attributes", () => · 27 unmodified lines
4343 );
4444
4545 yield* Effect.fail(
46− new EnvironmentConnectNotAuthorized({
46+ new EnvironmentConnector.EnvironmentConnectNotAuthorized({
4747 environmentId: "environment-1",
4848 operation: "connect",
4949 reason: "managed_endpoint_allocation_not_ready",
infra/relay/src/worker.ts+4−9
@@ -42,7 +42,7 @@ import * as EnvironmentCredentials from "./environments/EnvironmentCredentials.t · 41 unmodified lines
4242 import * as EnvironmentLinks from "./environments/EnvironmentLinks.ts";
4343 import * as ManagedEndpointAllocations from "./environments/ManagedEndpointAllocations.ts";
4444 import * as LiveActivities from "./agentActivity/LiveActivities.ts";
45−import { RelayDb, RelayHyperdrive } from "./db.ts";
45+import * as RelayDb from "./db.ts";
4646 import { RelayApnsDeliveryDeadLetterQueue, RelayApnsDeliveryQueue } from "./queues.ts";
4747 import * as RelayConfiguration from "./Config.ts";
4848 import * as AgentActivityPublisher from "./agentActivity/AgentActivityPublisher.ts";
@@ -138,7 +138,7 @@ export default class Api extends Cloudflare.Worker<Api>()( · 89 unmodified lines
138138
139139 const cloudMintPrivateKey = yield* cloudMintKeyPair.privateKey;
140140 const cloudMintPublicKey = yield* cloudMintKeyPair.publicKey;
141− const hyperdrive = yield* Cloudflare.Hyperdrive.bind(yield* RelayHyperdrive);
141+ const hyperdrive = yield* Cloudflare.Hyperdrive.bind(yield* RelayDb.RelayHyperdrive);
142142 const db = yield* Drizzle.postgres(hyperdrive.connectionString);
143143
144144 const managedEndpointTunnelBinding = yield* Cloudflare.TunnelReadWrite.bind();
@@ -203,16 +203,11 @@ export default class Api extends Cloudflare.Worker<Api>()( · 58 unmodified lines
203203 Layer.provideMerge(AgentActivityRows.layer),
204204 Layer.provideMerge(Devices.layer),
205205 Layer.provideMerge(EnvironmentCredentials.layer),
206− Layer.provideMerge(
207− Layer.mergeAll(
208− EnvironmentLinks.layer,
209− ManagedEndpointAllocations.ManagedEndpointAllocations.layer,
210− ),
211− ),
206+ Layer.provideMerge(Layer.mergeAll(EnvironmentLinks.layer, ManagedEndpointAllocations.layer)),
212207 Layer.provideMerge(LiveActivities.layer),
213208 Layer.provideMerge(DeliveryAttempts.layer),
214209 Layer.provideMerge(RelayTokens.layer),
215− Layer.provideMerge(Layer.succeed(RelayDb, db)),
210+ Layer.provideMerge(Layer.succeed(RelayDb.RelayDb, db)),
216211 Layer.provideMerge(Layer.effect(RelayConfiguration.RelayConfiguration, loadSettings)),
217212 Layer.provideMerge(webcryptoLayer),
218213 );
packages/client-runtime/src/state/preview.ts+4−0
@@ -37,6 +37,10 @@ export function createPreviewEnvironmentAtoms<R, E>( · 36 unmodified lines
3737 automationRequests: createEnvironmentRpcSubscriptionAtomFamily(runtime, {
3838 label: "environment-data:preview:automation-requests",
3939 tag: WS_METHODS.previewAutomationConnect,
40+ // Automation requests are commands, not cached query data. Dispose the
41+ // stream immediately with its owner so stale requests cannot replay when
42+ // a thread remounts and the server can clear disconnected hosts promptly.
43+ idleTtlMs: 0,
4044 }),
4145 open: createEnvironmentRpcCommand(runtime, {
4246 label: "environment-data:preview:open",
packages/contracts/src/git.ts+2−0
@@ -125,6 +125,8 @@ export const VcsListRefsInput = Schema.Struct({ · 124 unmodified lines
125125 cwd: TrimmedNonEmptyStringSchema,
126126 query: Schema.optional(TrimmedNonEmptyStringSchema.check(Schema.isMaxLength(256))),
127127 cursor: Schema.optional(NonNegativeInt),
128+ includeMatchingRemoteRefs: Schema.optional(Schema.Boolean),
129+ refKind: Schema.optional(Schema.Literals(["all", "local", "remote"])),
128130 limit: Schema.optional(
129131 PositiveInt.check(Schema.isLessThanOrEqualTo(GIT_LIST_BRANCHES_MAX_LIMIT)),
130132 ),
packages/contracts/src/ipc.ts+2−1
@@ -75,6 +75,7 @@ import { · 74 unmodified lines
7575 PreviewAutomationClickInput,
7676 PreviewAutomationEvaluateInput,
7777 PreviewAutomationOwner,
78+ PreviewAutomationOwnerIdentity,
7879 PreviewAutomationPressInput,
7980 PreviewAutomationRequest,
8081 PreviewAutomationResponse,
@@ -1232,7 +1233,7 @@ export interface EnvironmentApi { · 1151 unmodified lines
12321233 ) => () => void;
12331234 respond: (response: PreviewAutomationResponse) => Promise<void>;
12341235 reportOwner: (owner: PreviewAutomationOwner) => Promise<void>;
1235− clearOwner: (input: { clientId: string }) => Promise<void>;
1236+ clearOwner: (input: PreviewAutomationOwnerIdentity) => Promise<void>;
12361237 };
12371238 onEvent: (
12381239 callback: (event: PreviewEvent) => void,
packages/contracts/src/previewAutomation.ts+6−1
@@ -410,10 +410,15 @@ export const PreviewAutomationRecordingArtifact = Schema.Struct({ · 409 unmodified lines
410410 });
411411 export type PreviewAutomationRecordingArtifact = typeof PreviewAutomationRecordingArtifact.Type;
412412
413−export const PreviewAutomationOwner = Schema.Struct({
413+export const PreviewAutomationOwnerIdentity = Schema.Struct({
414414 clientId: TrimmedNonEmptyString,
415415 environmentId: EnvironmentId,
416416 threadId: ThreadId,
417+});
418+export type PreviewAutomationOwnerIdentity = typeof PreviewAutomationOwnerIdentity.Type;
419+
420+export const PreviewAutomationOwner = Schema.Struct({
421+ ...PreviewAutomationOwnerIdentity.fields,
417422 tabId: Schema.NullOr(PreviewTabId),
418423 visible: Schema.Boolean,
419424 supportsAutomation: Schema.Boolean,
packages/contracts/src/relay.ts+11−15
@@ -1,5 +1,5 @@
1−import * as Schema from "effect/Schema";
21 import * as Context from "effect/Context";
2+import * as Schema from "effect/Schema";
33 import * as HttpApi from "effect/unstable/httpapi/HttpApi";
44 import * as HttpApiEndpoint from "effect/unstable/httpapi/HttpApiEndpoint";
55 import * as HttpApiGroup from "effect/unstable/httpapi/HttpApiGroup";
@@ -512,26 +512,22 @@ const RelayAgentActivityPublishErrors = [ · 506 unmodified lines
512512 RelayInternalError,
513513 ] as const;
514514
515−export interface RelayClientPrincipalShape {
516− readonly userId: string;
517− readonly token: string;
518− readonly proofKeyThumbprint?: string;
519− readonly dpopScopes?: ReadonlyArray<RelayDpopAccessTokenScope>;
520−}
521−
522515 export class RelayClientPrincipal extends Context.Service<
523516 RelayClientPrincipal,
524− RelayClientPrincipalShape
517+ {
518+ readonly userId: string;
519+ readonly token: string;
520+ readonly proofKeyThumbprint?: string;
521+ readonly dpopScopes?: ReadonlyArray<RelayDpopAccessTokenScope>;
522+ }
525523 >()("@t3tools/contracts/relay/RelayClientPrincipal") {}
526524
527−export interface RelayEnvironmentPrincipalShape {
528− readonly environmentId: string;
529− readonly environmentPublicKey: string;
530−}
531−
532525 export class RelayEnvironmentPrincipal extends Context.Service<
533526 RelayEnvironmentPrincipal,
534− RelayEnvironmentPrincipalShape
527+ {
528+ readonly environmentId: string;
529+ readonly environmentPublicKey: string;
530+ }
535531 >()("@t3tools/contracts/relay/RelayEnvironmentPrincipal") {}
536532
537533 const RelayClientBearerAuthorization = HttpApiSecurity.http({ scheme: "bearer" }).pipe(
packages/contracts/src/review.ts+1−0
@@ -6,6 +6,7 @@ import { VcsError } from "./vcs.ts"; · 5 unmodified lines
66 export const ReviewDiffPreviewInput = Schema.Struct({
77 cwd: TrimmedNonEmptyString,
88 baseRef: Schema.optional(TrimmedNonEmptyString),
9+ ignoreWhitespace: Schema.optionalKey(Schema.Boolean),
910 });
1011 export type ReviewDiffPreviewInput = typeof ReviewDiffPreviewInput.Type;
1112
packages/contracts/src/rpc.ts+3−2
@@ -108,6 +108,7 @@ import { · 107 unmodified lines
108108 import {
109109 PreviewAutomationError,
110110 PreviewAutomationOwner,
111+ PreviewAutomationOwnerIdentity,
111112 PreviewAutomationRequest,
112113 PreviewAutomationResponse,
113114 } from "./previewAutomation.ts";
@@ -549,7 +550,7 @@ export const WsPreviewReportStatusRpc = Rpc.make(WS_METHODS.previewReportStatus, · 435 unmodified lines
549550 });
550551
551552 export const WsPreviewAutomationConnectRpc = Rpc.make(WS_METHODS.previewAutomationConnect, {
552− payload: Schema.Struct({ clientId: Schema.String }),
553+ payload: PreviewAutomationOwner,
553554 success: PreviewAutomationRequest,
554555 error: Schema.Union([PreviewAutomationError, EnvironmentAuthorizationError]),
555556 stream: true,
@@ -566,7 +567,7 @@ export const WsPreviewAutomationReportOwnerRpc = Rpc.make(WS_METHODS.previewAuto · 10 unmodified lines
566567 });
567568
568569 export const WsPreviewAutomationClearOwnerRpc = Rpc.make(WS_METHODS.previewAutomationClearOwner, {
569− payload: Schema.Struct({ clientId: Schema.String }),
570+ payload: PreviewAutomationOwnerIdentity,
570571 error: Schema.Union([PreviewAutomationError, EnvironmentAuthorizationError]),
571572 });
572573
pnpm-lock.yaml+250−252
@@ -194,10 +194,10 @@ importers: · 193 unmodified lines
194194 dependencies:
195195 '@callstack/liquid-glass':
196196 specifier: ^0.7.1
197− version: 0.7.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
197+ version: 0.7.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
198198 '@clerk/expo':
199199 specifier: 3.4.8-snapshot.v20260619001138
200− version: 3.4.8-snapshot.v20260619001138(expo-auth-session@56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(expo-constants@56.0.16)(expo-crypto@56.0.4(expo@56.0.8))(expo-secure-store@56.0.4(expo@56.0.8))(expo-web-browser@56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)))(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
200+ version: 3.4.8-snapshot.v20260619001138(expo-auth-session@56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(expo-constants@56.0.16)(expo-crypto@56.0.4(expo@56.0.8))(expo-secure-store@56.0.4(expo@56.0.8))(expo-web-browser@56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)))(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
201201 '@effect/atom-react':
202202 specifier: 4.0.0-beta.78
203203 version: 4.0.0-beta.78(effect@4.0.0-beta.78(patch_hash=c502bc684210b707dfceb87d8fe6ad6843395af6e19cfc02cd65854898bde2c5))(react@19.2.3)(scheduler@0.27.0)
@@ -206,10 +206,10 @@ importers: · 2 unmodified lines
206206 version: 0.4.2
207207 '@expo/ui':
208208 specifier: ~56.0.8
209− version: 56.0.15(961c4aa6f32829b318e3c87ef20ad401)
209+ version: 56.0.15(ab3f255d102c60ba0fd2bbe4e47ba584)
210210 '@legendapp/list':
211211 specifier: 3.0.0-beta.44
212− version: 3.0.0-beta.44(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
212+ version: 3.0.0-beta.44(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
213213 '@noble/curves':
214214 specifier: 'catalog:'
215215 version: 1.9.1
@@ -221,7 +221,7 @@ importers: · 5 unmodified lines
221221 version: 1.3.0-beta.5(patch_hash=7cb6da88544119adda056b2f46f43956f99326227732da0b345081e285a6c53a)(@shikijs/themes@4.2.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
222222 '@react-native-menu/menu':
223223 specifier: ^2.0.0
224− version: 2.0.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
224+ version: 2.0.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
225225 '@shikijs/core':
226226 specifier: 4.2.0
227227 version: 4.2.0
@@ -242,7 +242,7 @@ importers: · 14 unmodified lines
242242 version: link:../../packages/contracts
243243 '@t3tools/mobile-markdown-text':
244244 specifier: file:./modules/t3-markdown-text
245− version: file:apps/mobile/modules/t3-markdown-text(f0ff241d48c23db461fff5d47e450578)
245+ version: file:apps/mobile/modules/t3-markdown-text(e909ee9a8f7fbe234da80c739fca4984)
246246 '@t3tools/mobile-review-diff-native':
247247 specifier: file:./modules/t3-review-diff
248248 version: file:apps/mobile/modules/t3-review-diff
@@ -263,40 +263,40 @@ importers: · 14 unmodified lines
263263 version: 4.0.0-beta.78(patch_hash=c502bc684210b707dfceb87d8fe6ad6843395af6e19cfc02cd65854898bde2c5)
264264 expo:
265265 specifier: ^56.0.0
266− version: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
266+ version: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
267267 expo-asset:
268268 specifier: ~56.0.15
269− version: 56.0.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)
269+ version: 56.0.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)
270270 expo-auth-session:
271271 specifier: ~56.0.12
272− version: 56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
272+ version: 56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
273273 expo-build-properties:
274274 specifier: ~56.0.15
275275 version: 56.0.16(expo@56.0.8)
276276 expo-camera:
277277 specifier: ~56.0.7
278− version: 56.0.7(@types/emscripten@1.41.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
278+ version: 56.0.7(@types/emscripten@1.41.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
279279 expo-clipboard:
280280 specifier: ~56.0.3
281− version: 56.0.3(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
281+ version: 56.0.3(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
282282 expo-constants:
283283 specifier: ~56.0.16
284− version: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
284+ version: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
285285 expo-crypto:
286286 specifier: ~56.0.4
287287 version: 56.0.4(expo@56.0.8)
288288 expo-dev-client:
289289 specifier: ~56.0.16
290− version: 56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
290+ version: 56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
291291 expo-file-system:
292292 specifier: ~56.0.7
293− version: 56.0.7(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
293+ version: 56.0.7(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
294294 expo-font:
295295 specifier: ~56.0.5
296− version: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
296+ version: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
297297 expo-glass-effect:
298298 specifier: ~56.0.4
299− version: 56.0.4(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
299+ version: 56.0.4(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
300300 expo-haptics:
301301 specifier: ~56.0.3
302302 version: 56.0.3(expo@56.0.8)
@@ -305,19 +305,19 @@ importers: · 2 unmodified lines
305305 version: 56.0.15(expo@56.0.8)
306306 expo-linking:
307307 specifier: ~56.0.12
308− version: 56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
308+ version: 56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
309309 expo-network:
310310 specifier: ~56.0.5
311311 version: 56.0.5(expo@56.0.8)(react@19.2.3)
312312 expo-notifications:
313313 specifier: ~56.0.14
314− version: 56.0.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)
314+ version: 56.0.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)
315315 expo-paste-input:
316316 specifier: ^0.1.15
317− version: 0.1.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
317+ version: 0.1.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
318318 expo-router:
319319 specifier: ~56.2.7
320− version: 56.2.8(5bfdf39b8f760e4dc2d5c3acffc97310)
320+ version: 56.2.8(c021de11d02907bd585610408f5252e8)
321321 expo-secure-store:
322322 specifier: ~56.0.4
323323 version: 56.0.4(expo@56.0.8)
@@ -326,16 +326,16 @@ importers: · 2 unmodified lines
326326 version: 56.0.10(expo@56.0.8)(typescript@6.0.3)
327327 expo-symbols:
328328 specifier: ~56.0.5
329− version: 56.0.5(expo-font@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
329+ version: 56.0.5(expo-font@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
330330 expo-updates:
331331 specifier: ~56.0.17
332− version: 56.0.17(expo-dev-client@56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)))(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
332+ version: 56.0.17(expo-dev-client@56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)))(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
333333 expo-web-browser:
334334 specifier: ~56.0.5
335− version: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
335+ version: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
336336 expo-widgets:
337337 specifier: ~56.0.15
338− version: 56.0.16(961c4aa6f32829b318e3c87ef20ad401)
338+ version: 56.0.16(ab3f255d102c60ba0fd2bbe4e47ba584)
339339 punycode:
340340 specifier: ^2.3.1
341341 version: 2.3.1
@@ -347,43 +347,43 @@ importers: · 5 unmodified lines
347347 version: 19.2.3(react@19.2.3)
348348 react-native:
349349 specifier: 0.85.3
350− version: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
350+ version: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
351351 react-native-gesture-handler:
352352 specifier: ~2.31.1
353− version: 2.31.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
353+ version: 2.31.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
354354 react-native-image-viewing:
355355 specifier: ^0.2.2
356− version: 0.2.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
356+ version: 0.2.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
357357 react-native-keyboard-controller:
358358 specifier: 1.21.6
359− version: 1.21.6(react-native-reanimated@4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
359+ version: 1.21.6(react-native-reanimated@4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
360360 react-native-nitro-markdown:
361361 specifier: ^0.5.0
362− version: 0.5.8(react-native-nitro-modules@0.35.9(patch_hash=825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-svg@15.15.4(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
362+ version: 0.5.8(react-native-nitro-modules@0.35.9(patch_hash=825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-svg@15.15.4(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
363363 react-native-nitro-modules:
364364 specifier: ^0.35.4
365− version: 0.35.9(patch_hash=825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
365+ version: 0.35.9(patch_hash=825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
366366 react-native-reanimated:
367367 specifier: 4.3.1
368− version: 4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
368+ version: 4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
369369 react-native-safe-area-context:
370370 specifier: ~5.7.0
371− version: 5.7.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
371+ version: 5.7.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
372372 react-native-screens:
373373 specifier: 4.25.2
374− version: 4.25.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
374+ version: 4.25.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
375375 react-native-shiki-engine:
376376 specifier: ^0.3.12
377− version: 0.3.12(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
377+ version: 0.3.12(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
378378 react-native-svg:
379379 specifier: 15.15.4
380− version: 15.15.4(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
380+ version: 15.15.4(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
381381 react-native-webview:
382382 specifier: ^13.16.1
383− version: 13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
383+ version: 13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
384384 react-native-worklets:
385385 specifier: 0.8.3
386− version: 0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
386+ version: 0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
387387 shiki:
388388 specifier: 4.2.0
389389 version: 4.2.0
@@ -392,7 +392,7 @@ importers: · 2 unmodified lines
392392 version: 3.6.0
393393 uniwind:
394394 specifier: ^1.6.2
395− version: 1.7.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(tailwindcss@4.3.0)
395+ version: 1.7.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(tailwindcss@4.3.0)
396396 devDependencies:
397397 '@effect/vitest':
398398 specifier: 4.0.0-beta.78
@@ -10839,10 +10839,10 @@ snapshots: · 10440 unmodified lines
1083910839 optionalDependencies:
1084010840 '@types/react': 19.2.16
1084110841
10842− '@callstack/liquid-glass@0.7.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
10842+ '@callstack/liquid-glass@0.7.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
1084310843 dependencies:
1084410844 react: 19.2.3
10845− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
10845+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1084610846
1084710847 '@capsizecss/unpack@4.0.0':
1084810848 dependencies:
@@ -10946,23 +10946,23 @@ snapshots: · 97 unmodified lines
1094610946 electron-store: 8.2.0
1094710947 react-dom: 19.2.6(react@19.2.6)
1094810948
10949− '@clerk/expo@3.4.8-snapshot.v20260619001138(expo-auth-session@56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(expo-constants@56.0.16)(expo-crypto@56.0.4(expo@56.0.8))(expo-secure-store@56.0.4(expo@56.0.8))(expo-web-browser@56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)))(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
10949+ '@clerk/expo@3.4.8-snapshot.v20260619001138(expo-auth-session@56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(expo-constants@56.0.16)(expo-crypto@56.0.4(expo@56.0.8))(expo-secure-store@56.0.4(expo@56.0.8))(expo-web-browser@56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)))(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
1095010950 dependencies:
1095110951 '@clerk/clerk-js': 6.18.2-snapshot.v20260619001138(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
1095210952 '@clerk/react': 6.10.4-snapshot.v20260619001138(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
1095310953 '@clerk/shared': 4.19.2-snapshot.v20260619001138(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
1095410954 base-64: 1.0.0
10955− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
10955+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1095610956 react: 19.2.3
10957− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
10958− react-native-url-polyfill: 2.0.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
10957+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
10958+ react-native-url-polyfill: 2.0.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
1095910959 tslib: 2.8.1
1096010960 optionalDependencies:
10961− expo-auth-session: 56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
10962− expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
10961+ expo-auth-session: 56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
10962+ expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
1096310963 expo-crypto: 56.0.4(expo@56.0.8)
1096410964 expo-secure-store: 56.0.4(expo@56.0.8)
10965− expo-web-browser: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
10965+ expo-web-browser: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
1096610966 react-dom: 19.2.3(react@19.2.3)
1096710967
1096810968 '@clerk/react@6.10.4-snapshot.v20260619001138(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
@@ -11529,7 +11529,7 @@ snapshots: · 560 unmodified lines
1152911529
1153011530 '@expo-google-fonts/material-symbols@0.4.38': {}
1153111531
11532− '@expo/cli@56.1.13(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-constants@56.0.16)(expo-font@56.0.5)(expo-router@56.2.8)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)':
11532+ '@expo/cli@56.1.13(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-constants@56.0.16)(expo-font@56.0.5)(expo-router@56.2.8)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)':
1153311533 dependencies:
1153411534 '@expo/code-signing-certificates': 0.0.6
1153511535 '@expo/config': 56.0.9(typescript@6.0.3)
@@ -11539,7 +11539,7 @@ snapshots: · 3 unmodified lines
1153911539 '@expo/image-utils': 0.10.1(typescript@6.0.3)
1154011540 '@expo/inline-modules': 0.0.10(typescript@6.0.3)
1154111541 '@expo/json-file': 10.2.0
11542− '@expo/log-box': 56.0.12(@expo/dom-webview@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
11542+ '@expo/log-box': 56.0.12(@expo/dom-webview@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1154311543 '@expo/metro': 56.0.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)
1154411544 '@expo/metro-config': 56.0.13(patch_hash=8cb08b5bb7051ed9d2dbe46a2c293c5a1e17f1bd6ddf30de27909e18c921ff46)(bufferutil@4.1.0)(expo@56.0.8)(typescript@6.0.3)(utf-8-validate@6.0.6)
1154511545 '@expo/metro-file-map': 56.0.3
@@ -11564,7 +11564,7 @@ snapshots: · 18 unmodified lines
1156411564 connect: 3.7.0
1156511565 debug: 4.4.3
1156611566 dnssd-advertise: 1.1.4
11567− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
11567+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1156811568 expo-server: 56.0.4
1156911569 fetch-nodeshim: 0.4.10
1157011570 getenv: 2.0.0
@@ -11590,8 +11590,8 @@ snapshots: · 19 unmodified lines
1159011590 ws: 8.21.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)
1159111591 zod: 3.25.76
1159211592 optionalDependencies:
11593− expo-router: 56.2.8(5bfdf39b8f760e4dc2d5c3acffc97310)
11594− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
11593+ expo-router: 56.2.8(c021de11d02907bd585610408f5252e8)
11594+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1159511595 transitivePeerDependencies:
1159611596 - '@expo/dom-webview'
1159711597 - '@expo/metro-runtime'
@@ -11653,18 +11653,18 @@ snapshots: · 55 unmodified lines
1165311653 transitivePeerDependencies:
1165411654 - supports-color
1165511655
11656− '@expo/devtools@56.0.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
11656+ '@expo/devtools@56.0.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
1165711657 dependencies:
1165811658 chalk: 4.1.2
1165911659 optionalDependencies:
1166011660 react: 19.2.3
11661− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
11661+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1166211662
11663− '@expo/dom-webview@56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
11663+ '@expo/dom-webview@56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
1166411664 dependencies:
11665− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
11665+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1166611666 react: 19.2.3
11667− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
11667+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1166811668
1166911669 '@expo/env@2.3.0':
1167011670 dependencies:
@@ -11725,13 +11725,13 @@ snapshots: · 54 unmodified lines
1172511725 - supports-color
1172611726 - typescript
1172711727
11728− '@expo/log-box@56.0.12(@expo/dom-webview@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
11728+ '@expo/log-box@56.0.12(@expo/dom-webview@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
1172911729 dependencies:
11730− '@expo/dom-webview': 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
11730+ '@expo/dom-webview': 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1173111731 anser: 1.4.10
11732− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
11732+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1173311733 react: 19.2.3
11734− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
11734+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1173511735 stacktrace-parser: 0.1.11
1173611736
1173711737 '@expo/metro-config@56.0.13(patch_hash=8cb08b5bb7051ed9d2dbe46a2c293c5a1e17f1bd6ddf30de27909e18c921ff46)(bufferutil@4.1.0)(expo@56.0.8)(typescript@6.0.3)(utf-8-validate@6.0.6)':
@@ -11761,7 +11761,7 @@ snapshots: · 23 unmodified lines
1176111761 postcss: 8.5.15
1176211762 resolve-from: 5.0.0
1176311763 optionalDependencies:
11764− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
11764+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1176511765 transitivePeerDependencies:
1176611766 - bufferutil
1176711767 - supports-color
@@ -11779,14 +11779,14 @@ snapshots: · 11 unmodified lines
1177911779 transitivePeerDependencies:
1178011780 - supports-color
1178111781
11782− '@expo/metro-runtime@56.0.13(@expo/log-box@56.0.12)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
11782+ '@expo/metro-runtime@56.0.13(@expo/log-box@56.0.12)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
1178311783 dependencies:
11784− '@expo/log-box': 56.0.12(@expo/dom-webview@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
11784+ '@expo/log-box': 56.0.12(@expo/dom-webview@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1178511785 anser: 1.4.10
11786− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
11786+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1178711787 pretty-format: 29.7.0
1178811788 react: 19.2.3
11789− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
11789+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1179011790 stacktrace-parser: 0.1.11
1179111791 whatwg-fetch: 3.6.20
1179211792 optionalDependencies:
@@ -11861,14 +11861,14 @@ snapshots: · 68 unmodified lines
1186111861 '@expo/router-server@56.0.12(@expo/metro-runtime@56.0.13)(expo-constants@56.0.16)(expo-font@56.0.5)(expo-router@56.2.8)(expo-server@56.0.4)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
1186211862 dependencies:
1186311863 debug: 4.4.3
11864− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
11865− expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
11866− expo-font: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
11864+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
11865+ expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
11866+ expo-font: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1186711867 expo-server: 56.0.4
1186811868 react: 19.2.3
1186911869 optionalDependencies:
11870− '@expo/metro-runtime': 56.0.13(@expo/log-box@56.0.12)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
11871− expo-router: 56.2.8(5bfdf39b8f760e4dc2d5c3acffc97310)
11870+ '@expo/metro-runtime': 56.0.13(@expo/log-box@56.0.12)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
11871+ expo-router: 56.2.8(c021de11d02907bd585610408f5252e8)
1187211872 react-dom: 19.2.3(react@19.2.3)
1187311873 transitivePeerDependencies:
1187411874 - supports-color
@@ -11883,18 +11883,18 @@ snapshots: · 8 unmodified lines
1188311883
1188411884 '@expo/sudo-prompt@9.3.2': {}
1188511885
11886− '@expo/ui@56.0.15(961c4aa6f32829b318e3c87ef20ad401)':
11886+ '@expo/ui@56.0.15(ab3f255d102c60ba0fd2bbe4e47ba584)':
1188711887 dependencies:
11888− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
11888+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1188911889 react: 19.2.3
11890− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
11890+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1189111891 sf-symbols-typescript: 2.2.0
1189211892 vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
1189311893 optionalDependencies:
1189411894 '@babel/core': 7.29.7
1189511895 react-dom: 19.2.3(react@19.2.3)
11896− react-native-reanimated: 4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
11897− react-native-worklets: 0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
11896+ react-native-reanimated: 4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
11897+ react-native-worklets: 0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1189811898 transitivePeerDependencies:
1189911899 - '@types/react'
1190011900 - '@types/react-dom'
@@ -12155,13 +12155,13 @@ snapshots: · 254 unmodified lines
1215512155 dependencies:
1215612156 jsbi: 4.3.2
1215712157
12158− '@legendapp/list@3.0.0-beta.44(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
12158+ '@legendapp/list@3.0.0-beta.44(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
1215912159 dependencies:
1216012160 react: 19.2.3
1216112161 use-sync-external-store: 1.6.0(react@19.2.3)
1216212162 optionalDependencies:
1216312163 react-dom: 19.2.3(react@19.2.3)
12164− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
12164+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1216512165
1216612166 '@legendapp/list@3.0.0-beta.44(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
1216712167 dependencies:
@@ -12984,15 +12984,15 @@ snapshots: · 816 unmodified lines
1298412984 prompts: 2.4.2
1298512985 tinyexec: 1.2.4
1298612986
12987− '@react-native-masked-view/masked-view@0.3.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
12987+ '@react-native-masked-view/masked-view@0.3.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
1298812988 dependencies:
1298912989 react: 19.2.3
12990− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
12990+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1299112991
12992− '@react-native-menu/menu@2.0.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
12992+ '@react-native-menu/menu@2.0.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
1299312993 dependencies:
1299412994 react: 19.2.3
12995− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
12995+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1299612996
1299712997 '@react-native/assets-registry@0.85.3': {}
1299812998
@@ -13052,7 +13052,7 @@ snapshots: · 53 unmodified lines
1305213052 tinyglobby: 0.2.17
1305313053 yargs: 17.7.2
1305413054
13055− '@react-native/community-cli-plugin@0.85.3(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(bufferutil@4.1.0)(utf-8-validate@6.0.6)':
13055+ '@react-native/community-cli-plugin@0.85.3(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(bufferutil@4.1.0)(utf-8-validate@6.0.6)':
1305613056 dependencies:
1305713057 '@react-native/dev-middleware': 0.85.3(bufferutil@4.1.0)(utf-8-validate@6.0.6)
1305813058 debug: 4.4.3
@@ -13062,7 +13062,7 @@ snapshots: · 3 unmodified lines
1306213062 metro-core: 0.84.4
1306313063 semver: 7.8.1
1306413064 optionalDependencies:
13065− '@react-native/metro-config': 0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6)
13065+ '@react-native/metro-config': 0.85.3(@babel/core@7.29.7)
1306613066 transitivePeerDependencies:
1306713067 - bufferutil
1306813068 - supports-color
@@ -13110,7 +13110,7 @@ snapshots: · 41 unmodified lines
1311013110 transitivePeerDependencies:
1311113111 - supports-color
1311213112
13113− '@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6)':
13113+ '@react-native/metro-config@0.85.3(@babel/core@7.29.7)':
1311413114 dependencies:
1311513115 '@react-native/js-polyfills': 0.85.3
1311613116 '@react-native/metro-babel-transformer': 0.85.3(@babel/core@7.29.7)
@@ -13118,18 +13118,16 @@ snapshots: · 1 unmodified line
1311813118 metro-runtime: 0.84.4
1311913119 transitivePeerDependencies:
1312013120 - '@babel/core'
13121− - bufferutil
1312213121 - supports-color
13123− - utf-8-validate
1312413122
1312513123 '@react-native/normalize-colors@0.85.3': {}
1312613124
13127− '@react-native/virtualized-lists@0.85.3(@types/react@19.2.16)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
13125+ '@react-native/virtualized-lists@0.85.3(@types/react@19.2.16)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)':
1312813126 dependencies:
1312913127 invariant: 2.2.4
1313013128 nullthrows: 1.1.1
1313113129 react: 19.2.3
13132− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
13130+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1313313131 optionalDependencies:
1313413132 '@types/react': 19.2.16
1313513133
@@ -13542,15 +13540,15 @@ snapshots: · 406 unmodified lines
1354213540 dependencies:
1354313541 defer-to-connect: 2.0.1
1354413542
13545− '@t3tools/mobile-markdown-text@file:apps/mobile/modules/t3-markdown-text(f0ff241d48c23db461fff5d47e450578)':
13543+ '@t3tools/mobile-markdown-text@file:apps/mobile/modules/t3-markdown-text(e909ee9a8f7fbe234da80c739fca4984)':
1354613544 dependencies:
13547− expo-asset: 56.0.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)
13548− expo-clipboard: 56.0.3(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
13545+ expo-asset: 56.0.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)
13546+ expo-clipboard: 56.0.3(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1354913547 expo-haptics: 56.0.3(expo@56.0.8)
13550− expo-symbols: 56.0.5(expo-font@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
13548+ expo-symbols: 56.0.5(expo-font@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1355113549 react: 19.2.3
13552− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
13553− react-native-nitro-markdown: 0.5.8(react-native-nitro-modules@0.35.9(patch_hash=825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-svg@15.15.4(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
13550+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
13551+ react-native-nitro-markdown: 0.5.8(react-native-nitro-modules@0.35.9(patch_hash=825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-svg@15.15.4(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1355413552
1355513553 '@t3tools/mobile-review-diff-native@file:apps/mobile/modules/t3-review-diff': {}
1355613554
@@ -14626,8 +14624,8 @@ snapshots: · 1069 unmodified lines
1462614624 react-refresh: 0.14.2
1462714625 optionalDependencies:
1462814626 '@babel/runtime': 7.29.7
14629− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
14630− expo-widgets: 56.0.16(961c4aa6f32829b318e3c87ef20ad401)
14627+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
14628+ expo-widgets: 56.0.16(ab3f255d102c60ba0fd2bbe4e47ba584)
1463114629 transitivePeerDependencies:
1463214630 - '@babel/core'
1463314631 - supports-color
@@ -15518,29 +15516,29 @@ snapshots: · 884 unmodified lines
1551815516
1551915517 expo-application@56.0.3(expo@56.0.8):
1552015518 dependencies:
15521− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15519+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1552215520
15523− expo-asset@56.0.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3):
15521+ expo-asset@56.0.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3):
1552415522 dependencies:
1552515523 '@expo/image-utils': 0.10.1(typescript@6.0.3)
15526− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15527− expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15524+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15525+ expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
1552815526 react: 19.2.3
15529− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15527+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1553015528 transitivePeerDependencies:
1553115529 - supports-color
1553215530 - typescript
1553315531
15534− expo-auth-session@56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
15532+ expo-auth-session@56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1553515533 dependencies:
1553615534 expo-application: 56.0.3(expo@56.0.8)
15537− expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15535+ expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
1553815536 expo-crypto: 56.0.4(expo@56.0.8)
15539− expo-linking: 56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15540− expo-web-browser: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15537+ expo-linking: 56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15538+ expo-web-browser: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
1554115539 invariant: 2.2.4
1554215540 react: 19.2.3
15543− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15541+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1554415542 transitivePeerDependencies:
1554515543 - expo
1554615544 - supports-color
@@ -15548,119 +15546,119 @@ snapshots: · 1 unmodified line
1554815546 expo-build-properties@56.0.16(expo@56.0.8):
1554915547 dependencies:
1555015548 '@expo/schema-utils': 56.0.1
15551− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15549+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1555215550 resolve-from: 5.0.0
1555315551 semver: 7.8.1
1555415552
15555− expo-camera@56.0.7(@types/emscripten@1.41.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
15553+ expo-camera@56.0.7(@types/emscripten@1.41.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1555615554 dependencies:
1555715555 barcode-detector: 3.2.0(@types/emscripten@1.41.5)
15558− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15556+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1555915557 react: 19.2.3
15560− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15558+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1556115559 transitivePeerDependencies:
1556215560 - '@types/emscripten'
1556315561
15564− expo-clipboard@56.0.3(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
15562+ expo-clipboard@56.0.3(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1556515563 dependencies:
15566− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15564+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1556715565 react: 19.2.3
15568− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15566+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1556915567
15570− expo-constants@56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
15568+ expo-constants@56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
1557115569 dependencies:
1557215570 '@expo/env': 2.3.0
15573− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15574− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15571+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15572+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1557515573 transitivePeerDependencies:
1557615574 - supports-color
1557715575
1557815576 expo-crypto@56.0.4(expo@56.0.8):
1557915577 dependencies:
15580− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15578+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1558115579
15582− expo-dev-client@56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
15580+ expo-dev-client@56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
1558315581 dependencies:
15584− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15585− expo-dev-launcher: 56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15586− expo-dev-menu: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15582+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15583+ expo-dev-launcher: 56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15584+ expo-dev-menu: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
1558715585 expo-dev-menu-interface: 56.0.1(expo@56.0.8)
1558815586 expo-manifests: 56.0.4(expo@56.0.8)
1558915587 expo-updates-interface: 56.0.2(expo@56.0.8)
1559015588 transitivePeerDependencies:
1559115589 - react-native
1559215590
15593− expo-dev-launcher@56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
15591+ expo-dev-launcher@56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
1559415592 dependencies:
1559515593 '@expo/schema-utils': 56.0.1
15596− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15597− expo-dev-menu: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15594+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15595+ expo-dev-menu: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
1559815596 expo-manifests: 56.0.4(expo@56.0.8)
15599− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15597+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1560015598
1560115599 expo-dev-menu-interface@56.0.1(expo@56.0.8):
1560215600 dependencies:
15603− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15601+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1560415602
15605− expo-dev-menu@56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
15603+ expo-dev-menu@56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
1560615604 dependencies:
15607− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15605+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1560815606 expo-dev-menu-interface: 56.0.1(expo@56.0.8)
15609− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15607+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1561015608
1561115609 expo-eas-client@56.0.1: {}
1561215610
15613− expo-file-system@56.0.7(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
15611+ expo-file-system@56.0.7(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
1561415612 dependencies:
15615− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15616− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15613+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15614+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1561715615
15618− expo-font@56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
15616+ expo-font@56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1561915617 dependencies:
15620− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15618+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1562115619 fontfaceobserver: 2.3.0
1562215620 react: 19.2.3
15623− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15621+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1562415622
15625− expo-glass-effect@56.0.4(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
15623+ expo-glass-effect@56.0.4(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1562615624 dependencies:
15627− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15625+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1562815626 react: 19.2.3
15629− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15627+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1563015628
1563115629 expo-haptics@56.0.3(expo@56.0.8):
1563215630 dependencies:
15633− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15631+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1563415632
1563515633 expo-image-loader@56.0.3(expo@56.0.8):
1563615634 dependencies:
15637− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15635+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1563815636
1563915637 expo-image-picker@56.0.15(expo@56.0.8):
1564015638 dependencies:
15641− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15639+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1564215640 expo-image-loader: 56.0.3(expo@56.0.8)
1564315641
1564415642 expo-json-utils@56.0.0: {}
1564515643
1564615644 expo-keep-awake@56.0.3(expo@56.0.8)(react@19.2.3):
1564715645 dependencies:
15648− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15646+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1564915647 react: 19.2.3
1565015648
15651− expo-linking@56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
15649+ expo-linking@56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1565215650 dependencies:
15653− expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15651+ expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
1565415652 invariant: 2.2.4
1565515653 react: 19.2.3
15656− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15654+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1565715655 transitivePeerDependencies:
1565815656 - expo
1565915657 - supports-color
1566015658
1566115659 expo-manifests@56.0.4(expo@56.0.8):
1566215660 dependencies:
15663− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15661+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1566415662 expo-json-utils: 56.0.0
1566515663
1566615664 expo-modules-autolinking@56.0.14(typescript@6.0.3):
@@ -15673,66 +15671,66 @@ snapshots: · 6 unmodified lines
1567315671 - supports-color
1567415672 - typescript
1567515673
15676− expo-modules-core@56.0.14(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
15674+ expo-modules-core@56.0.14(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1567715675 dependencies:
1567815676 '@expo/expo-modules-macros-plugin': 0.0.9
15679− expo-modules-jsi: 56.0.7(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15677+ expo-modules-jsi: 56.0.7(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
1568015678 invariant: 2.2.4
1568115679 react: 19.2.3
15682− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15680+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1568315681 optionalDependencies:
15684− react-native-worklets: 0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15682+ react-native-worklets: 0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1568515683
15686− expo-modules-jsi@56.0.7(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
15684+ expo-modules-jsi@56.0.7(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
1568715685 dependencies:
15688− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15686+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1568915687
1569015688 expo-network@56.0.5(expo@56.0.8)(react@19.2.3):
1569115689 dependencies:
15692− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15690+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1569315691 react: 19.2.3
1569415692
15695− expo-notifications@56.0.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3):
15693+ expo-notifications@56.0.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3):
1569615694 dependencies:
1569715695 '@expo/image-utils': 0.10.1(typescript@6.0.3)
1569815696 abort-controller: 3.0.0
1569915697 badgin: 1.2.3
15700− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15698+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1570115699 expo-application: 56.0.3(expo@56.0.8)
15702− expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15700+ expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
1570315701 react: 19.2.3
15704− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15702+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1570515703 transitivePeerDependencies:
1570615704 - supports-color
1570715705 - typescript
1570815706
15709− expo-paste-input@0.1.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
15707+ expo-paste-input@0.1.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1571015708 dependencies:
15711− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15709+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1571215710 react: 19.2.3
15713− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15711+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1571415712
15715− expo-router@56.2.8(5bfdf39b8f760e4dc2d5c3acffc97310):
15713+ expo-router@56.2.8(c021de11d02907bd585610408f5252e8):
1571615714 dependencies:
15717− '@expo/log-box': 56.0.12(@expo/dom-webview@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15718− '@expo/metro-runtime': 56.0.13(@expo/log-box@56.0.12)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15715+ '@expo/log-box': 56.0.12(@expo/dom-webview@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15716+ '@expo/metro-runtime': 56.0.13(@expo/log-box@56.0.12)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1571915717 '@expo/schema-utils': 56.0.1
15720− '@expo/ui': 56.0.15(961c4aa6f32829b318e3c87ef20ad401)
15718+ '@expo/ui': 56.0.15(ab3f255d102c60ba0fd2bbe4e47ba584)
1572115719 '@radix-ui/react-slot': 1.2.4(@types/react@19.2.16)(react@19.2.3)
1572215720 '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
15723− '@react-native-masked-view/masked-view': 0.3.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15721+ '@react-native-masked-view/masked-view': 0.3.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1572415722 '@testing-library/jest-dom': 6.9.1
1572515723 '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1)
1572615724 client-only: 0.0.1
1572715725 color: 4.2.3
1572815726 debug: 4.4.3
1572915727 escape-string-regexp: 4.0.0
15730− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15731− expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15732− expo-glass-effect: 56.0.4(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15733− expo-linking: 56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15728+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15729+ expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15730+ expo-glass-effect: 56.0.4(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15731+ expo-linking: 56.0.13(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1573415732 expo-server: 56.0.4
15735− expo-symbols: 56.0.5(expo-font@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15733+ expo-symbols: 56.0.5(expo-font@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1573615734 fast-deep-equal: 3.1.3
1573715735 invariant: 2.2.4
1573815736 nanoid: 3.3.12
@@ -15740,18 +15738,18 @@ snapshots: · 1 unmodified line
1574015738 react: 19.2.3
1574115739 react-fast-compare: 3.2.2
1574215740 react-is: 19.2.7
15743− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15744− react-native-drawer-layout: 4.2.4(0e9729601f58a7a7ae26c76fe6017455)
15745− react-native-safe-area-context: 5.7.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15746− react-native-screens: 4.25.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15741+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15742+ react-native-drawer-layout: 4.2.4(react-native-gesture-handler@2.31.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-reanimated@4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15743+ react-native-safe-area-context: 5.7.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15744+ react-native-screens: 4.25.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1574715745 server-only: 0.0.1
1574815746 sf-symbols-typescript: 2.2.0
1574915747 shallowequal: 1.1.0
1575015748 vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.16))(@types/react@19.2.16)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
1575115749 optionalDependencies:
1575215750 react-dom: 19.2.3(react@19.2.3)
15753− react-native-gesture-handler: 2.31.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15754− react-native-reanimated: 4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15751+ react-native-gesture-handler: 2.31.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15752+ react-native-reanimated: 4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1575515753 transitivePeerDependencies:
1575615754 - '@babel/core'
1575715755 - '@testing-library/dom'
@@ -15763,7 +15761,7 @@ snapshots: · 5 unmodified lines
1576315761
1576415762 expo-secure-store@56.0.4(expo@56.0.8):
1576515763 dependencies:
15766− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15764+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1576715765
1576815766 expo-server@56.0.4: {}
1576915767
@@ -15771,7 +15769,7 @@ snapshots: · 1 unmodified line
1577115769 dependencies:
1577215770 '@expo/config-plugins': 56.0.8(typescript@6.0.3)
1577315771 '@expo/image-utils': 0.10.1(typescript@6.0.3)
15774− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15772+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1577515773 xml2js: 0.6.0
1577615774 transitivePeerDependencies:
1577715775 - supports-color
@@ -15779,20 +15777,20 @@ snapshots: · 1 unmodified line
1577915777
1578015778 expo-structured-headers@56.0.0: {}
1578115779
15782− expo-symbols@56.0.5(expo-font@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
15780+ expo-symbols@56.0.5(expo-font@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1578315781 dependencies:
1578415782 '@expo-google-fonts/material-symbols': 0.4.38
15785− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15786− expo-font: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15783+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15784+ expo-font: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1578715785 react: 19.2.3
15788− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15786+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1578915787 sf-symbols-typescript: 2.2.0
1579015788
1579115789 expo-updates-interface@56.0.2(expo@56.0.8):
1579215790 dependencies:
15793− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15791+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1579415792
15795− expo-updates@56.0.17(expo-dev-client@56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)))(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
15793+ expo-updates@56.0.17(expo-dev-client@56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)))(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1579615794 dependencies:
1579715795 '@expo/code-signing-certificates': 0.0.6
1579815796 '@expo/plist': 0.7.0
@@ -15800,7 +15798,7 @@ snapshots: · 1 unmodified line
1580015798 arg: 4.1.3
1580115799 chalk: 4.1.2
1580215800 debug: 4.4.3
15803− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15801+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1580415802 expo-eas-client: 56.0.1
1580515803 expo-manifests: 56.0.4(expo@56.0.8)
1580615804 expo-structured-headers: 56.0.0
@@ -15810,25 +15808,25 @@ snapshots: · 3 unmodified lines
1581015808 ignore: 5.3.2
1581115809 nullthrows: 1.1.1
1581215810 react: 19.2.3
15813− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15811+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1581415812 resolve-from: 5.0.0
1581515813 optionalDependencies:
15816− expo-dev-client: 56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15814+ expo-dev-client: 56.0.18(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
1581715815 transitivePeerDependencies:
1581815816 - supports-color
1581915817
15820− expo-web-browser@56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
15818+ expo-web-browser@56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
1582115819 dependencies:
15822− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15823− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15820+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15821+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1582415822
15825− expo-widgets@56.0.16(961c4aa6f32829b318e3c87ef20ad401):
15823+ expo-widgets@56.0.16(ab3f255d102c60ba0fd2bbe4e47ba584):
1582615824 dependencies:
1582715825 '@expo/plist': 0.7.0
15828− '@expo/ui': 56.0.15(961c4aa6f32829b318e3c87ef20ad401)
15829− expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d)
15826+ '@expo/ui': 56.0.15(ab3f255d102c60ba0fd2bbe4e47ba584)
15827+ expo: 56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1583015828 react: 19.2.3
15831− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15829+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1583215830 transitivePeerDependencies:
1583315831 - '@babel/core'
1583415832 - '@types/react'
@@ -15837,37 +15835,37 @@ snapshots: · 2 unmodified lines
1583715835 - react-native-reanimated
1583815836 - react-native-worklets
1583915837
15840− expo@56.0.8(63f7aade424ad9e7b1154b679fa2a14d):
15838+ expo@56.0.8(@babel/core@7.29.7)(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-router@56.2.8)(expo-widgets@56.0.16)(react-dom@19.2.3(react@19.2.3))(react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6):
1584115839 dependencies:
1584215840 '@babel/runtime': 7.29.7
15843− '@expo/cli': 56.1.13(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-constants@56.0.16)(expo-font@56.0.5)(expo-router@56.2.8)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
15841+ '@expo/cli': 56.1.13(@expo/dom-webview@56.0.5)(@expo/metro-runtime@56.0.13)(bufferutil@4.1.0)(expo-constants@56.0.16)(expo-font@56.0.5)(expo-router@56.2.8)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)(utf-8-validate@6.0.6)
1584415842 '@expo/config': 56.0.9(typescript@6.0.3)
1584515843 '@expo/config-plugins': 56.0.8(typescript@6.0.3)
15846− '@expo/devtools': 56.0.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15844+ '@expo/devtools': 56.0.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1584715845 '@expo/fingerprint': 0.19.3
1584815846 '@expo/local-build-cache-provider': 56.0.8(typescript@6.0.3)
15849− '@expo/log-box': 56.0.12(@expo/dom-webview@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15847+ '@expo/log-box': 56.0.12(@expo/dom-webview@56.0.5)(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1585015848 '@expo/metro': 56.0.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)
1585115849 '@expo/metro-config': 56.0.13(patch_hash=8cb08b5bb7051ed9d2dbe46a2c293c5a1e17f1bd6ddf30de27909e18c921ff46)(bufferutil@4.1.0)(expo@56.0.8)(typescript@6.0.3)(utf-8-validate@6.0.6)
1585215850 '@ungap/structured-clone': 1.3.1
1585315851 babel-preset-expo: 56.0.14(@babel/core@7.29.7)(@babel/runtime@7.29.7)(expo-widgets@56.0.16)(expo@56.0.8)(react-refresh@0.14.2)
15854− expo-asset: 56.0.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)
15855− expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15856− expo-file-system: 56.0.7(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15857− expo-font: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15852+ expo-asset: 56.0.15(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(typescript@6.0.3)
15853+ expo-constants: 56.0.16(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15854+ expo-file-system: 56.0.7(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))
15855+ expo-font: 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1585815856 expo-keep-awake: 56.0.3(expo@56.0.8)(react@19.2.3)
1585915857 expo-modules-autolinking: 56.0.14(typescript@6.0.3)
15860− expo-modules-core: 56.0.14(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15858+ expo-modules-core: 56.0.14(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1586115859 pretty-format: 29.7.0
1586215860 react: 19.2.3
15863− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
15861+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1586415862 react-refresh: 0.14.2
1586515863 whatwg-url-minimum: 0.1.2
1586615864 optionalDependencies:
15867− '@expo/dom-webview': 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15868− '@expo/metro-runtime': 56.0.13(@expo/log-box@56.0.12)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15865+ '@expo/dom-webview': 56.0.5(expo@56.0.8)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15866+ '@expo/metro-runtime': 56.0.13(@expo/log-box@56.0.12)(expo@56.0.8)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1586915867 react-dom: 19.2.3(react@19.2.3)
15870− react-native-webview: 13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
15868+ react-native-webview: 13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1587115869 transitivePeerDependencies:
1587215870 - '@babel/core'
1587315871 - bufferutil
@@ -18285,102 +18283,102 @@ snapshots: · 2411 unmodified lines
1828518283 transitivePeerDependencies:
1828618284 - supports-color
1828718285
18288− react-native-drawer-layout@4.2.4(0e9729601f58a7a7ae26c76fe6017455):
18286+ react-native-drawer-layout@4.2.4(react-native-gesture-handler@2.31.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-reanimated@4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1828918287 dependencies:
1829018288 color: 4.2.3
1829118289 react: 19.2.3
18292− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18293− react-native-gesture-handler: 2.31.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
18294− react-native-reanimated: 4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
18290+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18291+ react-native-gesture-handler: 2.31.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
18292+ react-native-reanimated: 4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1829518293 use-latest-callback: 0.2.6(react@19.2.3)
1829618294
18297− react-native-gesture-handler@2.31.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
18295+ react-native-gesture-handler@2.31.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1829818296 dependencies:
1829918297 '@egjs/hammerjs': 2.0.17
1830018298 '@types/react-test-renderer': 19.1.0
1830118299 hoist-non-react-statics: 3.3.2
1830218300 invariant: 2.2.4
1830318301 react: 19.2.3
18304− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18302+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1830518303
18306− react-native-image-viewing@0.2.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
18304+ react-native-image-viewing@0.2.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1830718305 dependencies:
1830818306 react: 19.2.3
18309− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18307+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1831018308
18311− react-native-is-edge-to-edge@1.3.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
18309+ react-native-is-edge-to-edge@1.3.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1831218310 dependencies:
1831318311 react: 19.2.3
18314− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18312+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1831518313
18316− react-native-keyboard-controller@1.21.6(react-native-reanimated@4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
18314+ react-native-keyboard-controller@1.21.6(react-native-reanimated@4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1831718315 dependencies:
1831818316 react: 19.2.3
18319− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18320− react-native-is-edge-to-edge: 1.3.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
18321− react-native-reanimated: 4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
18317+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18318+ react-native-is-edge-to-edge: 1.3.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
18319+ react-native-reanimated: 4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1832218320
18323− react-native-nitro-markdown@0.5.8(react-native-nitro-modules@0.35.9(patch_hash=825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-svg@15.15.4(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
18321+ react-native-nitro-markdown@0.5.8(react-native-nitro-modules@0.35.9(patch_hash=825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native-svg@15.15.4(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1832418322 dependencies:
1832518323 react: 19.2.3
18326− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18327− react-native-nitro-modules: 0.35.9(patch_hash=825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
18324+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18325+ react-native-nitro-modules: 0.35.9(patch_hash=825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1832818326 optionalDependencies:
18329− react-native-svg: 15.15.4(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
18327+ react-native-svg: 15.15.4(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1833018328
18331− react-native-nitro-modules@0.35.9(patch_hash=825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
18329+ react-native-nitro-modules@0.35.9(patch_hash=825622aae63a8fb5b904f3c77908a0e216261d727ea171709f2c0b6088422675)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1833218330 dependencies:
1833318331 react: 19.2.3
18334− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18332+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1833518333
18336− react-native-reanimated@4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
18334+ react-native-reanimated@4.3.1(react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1833718335 dependencies:
1833818336 react: 19.2.3
18339− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18340− react-native-is-edge-to-edge: 1.3.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
18341− react-native-worklets: 0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
18337+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18338+ react-native-is-edge-to-edge: 1.3.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
18339+ react-native-worklets: 0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1834218340 semver: 7.8.1
1834318341
18344− react-native-safe-area-context@5.7.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
18342+ react-native-safe-area-context@5.7.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1834518343 dependencies:
1834618344 react: 19.2.3
18347− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18345+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1834818346
18349− react-native-screens@4.25.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
18347+ react-native-screens@4.25.2(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1835018348 dependencies:
1835118349 react: 19.2.3
1835218350 react-freeze: 1.0.4(react@19.2.3)
18353− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18351+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1835418352 warn-once: 0.1.1
1835518353
18356− react-native-shiki-engine@0.3.12(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
18354+ react-native-shiki-engine@0.3.12(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1835718355 dependencies:
1835818356 '@shikijs/types': 4.2.0
1835918357 '@shikijs/vscode-textmate': 10.0.2
1836018358 react: 19.2.3
18361− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18359+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1836218360
18363− react-native-svg@15.15.4(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
18361+ react-native-svg@15.15.4(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1836418362 dependencies:
1836518363 css-select: 5.2.2
1836618364 css-tree: 1.1.3
1836718365 react: 19.2.3
18368− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18366+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1836918367 warn-once: 0.1.1
1837018368
18371− react-native-url-polyfill@2.0.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
18369+ react-native-url-polyfill@2.0.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)):
1837218370 dependencies:
18373− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18371+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1837418372 whatwg-url-without-unicode: 8.0.0-3
1837518373
18376− react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
18374+ react-native-webview@13.16.1(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1837718375 dependencies:
1837818376 escape-string-regexp: 4.0.0
1837918377 invariant: 2.2.4
1838018378 react: 19.2.3
18381− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18379+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1838218380
18383− react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
18381+ react-native-worklets@0.8.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3):
1838418382 dependencies:
1838518383 '@babel/core': 7.29.7
1838618384 '@babel/plugin-transform-arrow-functions': 7.29.7(@babel/core@7.29.7)
@@ -18392,23 +18390,23 @@ snapshots: · 5 unmodified lines
1839218390 '@babel/plugin-transform-template-literals': 7.29.7(@babel/core@7.29.7)
1839318391 '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7)
1839418392 '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7)
18395− '@react-native/metro-config': 0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6)
18393+ '@react-native/metro-config': 0.85.3(@babel/core@7.29.7)
1839618394 convert-source-map: 2.0.0
1839718395 react: 19.2.3
18398− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
18396+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1839918397 semver: 7.8.1
1840018398 transitivePeerDependencies:
1840118399 - supports-color
1840218400
18403− react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6):
18401+ react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6):
1840418402 dependencies:
1840518403 '@react-native/assets-registry': 0.85.3
1840618404 '@react-native/codegen': 0.85.3(@babel/core@7.29.7)
18407− '@react-native/community-cli-plugin': 0.85.3(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(bufferutil@4.1.0)(utf-8-validate@6.0.6)
18405+ '@react-native/community-cli-plugin': 0.85.3(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(bufferutil@4.1.0)(utf-8-validate@6.0.6)
1840818406 '@react-native/gradle-plugin': 0.85.3
1840918407 '@react-native/js-polyfills': 0.85.3
1841018408 '@react-native/normalize-colors': 0.85.3
18411− '@react-native/virtualized-lists': 0.85.3(@types/react@19.2.16)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
18409+ '@react-native/virtualized-lists': 0.85.3(@types/react@19.2.16)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)
1841218410 abort-controller: 3.0.0
1841318411 anser: 1.4.10
1841418412 ansi-regex: 5.0.1
@@ -19437,14 +19435,14 @@ snapshots: · 1022 unmodified lines
1943719435
1943819436 universalify@2.0.1: {}
1943919437
19440− uniwind@1.7.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(tailwindcss@4.3.0):
19438+ uniwind@1.7.0(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)(tailwindcss@4.3.0):
1944119439 dependencies:
1944219440 '@tailwindcss/node': 4.2.1
1944319441 '@tailwindcss/oxide': 4.2.1
1944419442 culori: 4.0.2
1944519443 lightningcss: 1.30.1
1944619444 react: 19.2.3
19447− react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
19445+ react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)
1944819446 tailwindcss: 4.3.0
1944919447
1945019448 unpipe@1.0.0: {}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t