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).
Unified · Split
| @@ -2,10 +2,12 @@ name: Mobile EAS Preview · 1 unmodified line | |||
| 2 | 2 | ||
| 3 | on: | 3 | on: |
| 4 | pull_request: | 4 | pull_request: |
| 5 | types: [opened, reopened, synchronize, labeled, unlabeled] | ||
| 5 | 6 | ||
| 6 | jobs: | 7 | jobs: |
| 7 | preview: | 8 | preview: |
| 8 | name: EAS Preview | 9 | name: EAS Preview |
| 10 | if: contains(github.event.pull_request.labels.*.name, '🚀 Mobile Continuous Deployment') | ||
| 9 | runs-on: blacksmith-8vcpu-ubuntu-2404 | 11 | runs-on: blacksmith-8vcpu-ubuntu-2404 |
| 10 | permissions: | 12 | permissions: |
| 11 | contents: read | 13 | contents: read |
| @@ -63,7 +63,7 @@ const makeElectronAppLayer = (calls: ElectronAppCalls) => · 62 unmodified lines | |||
| 63 | }), | 63 | }), |
| 64 | appendCommandLineSwitch: () => Effect.void, | 64 | appendCommandLineSwitch: () => Effect.void, |
| 65 | on: () => Effect.void, | 65 | on: () => Effect.void, |
| 66 | } satisfies ElectronApp.ElectronAppShape); | 66 | } satisfies ElectronApp.ElectronApp["Service"]); |
| 67 | 67 | ||
| 68 | const makeAssetsLayer = (png: Option.Option<string>) => | 68 | const makeAssetsLayer = (png: Option.Option<string>) => |
| 69 | Layer.succeed(DesktopAssets.DesktopAssets, { | 69 | Layer.succeed(DesktopAssets.DesktopAssets, { |
| @@ -73,7 +73,7 @@ const makeAssetsLayer = (png: Option.Option<string>) => · 3 unmodified lines | |||
| 73 | png, | 73 | png, |
| 74 | }), | 74 | }), |
| 75 | resolveResourcePath: () => Effect.succeed(Option.none()), | 75 | resolveResourcePath: () => Effect.succeed(Option.none()), |
| 76 | } satisfies DesktopAssets.DesktopAssetsShape); | 76 | } satisfies DesktopAssets.DesktopAssets["Service"]); |
| 77 | 77 | ||
| 78 | const makeEnvironmentLayer = (overrides: TestEnvironmentInput = {}) => { | 78 | const makeEnvironmentLayer = (overrides: TestEnvironmentInput = {}) => { |
| 79 | const { env, ...environmentOverrides } = overrides; | 79 | const { env, ...environmentOverrides } = overrides; |
| @@ -18,14 +18,12 @@ const AppPackageMetadata = Schema.Struct({ · 17 unmodified lines | |||
| 18 | }); | 18 | }); |
| 19 | const decodeAppPackageMetadata = Schema.decodeEffect(Schema.fromJsonString(AppPackageMetadata)); | 19 | const decodeAppPackageMetadata = Schema.decodeEffect(Schema.fromJsonString(AppPackageMetadata)); |
| 20 | 20 | ||
| 21 | export interface DesktopAppIdentityShape { | ||
| 22 | readonly resolveUserDataPath: Effect.Effect<string>; | ||
| 23 | readonly configure: Effect.Effect<void>; | ||
| 24 | } | ||
| 25 | |||
| 26 | export class DesktopAppIdentity extends Context.Service< | 21 | export class DesktopAppIdentity extends Context.Service< |
| 27 | DesktopAppIdentity, | 22 | DesktopAppIdentity, |
| 28 | DesktopAppIdentityShape | 23 | { |
| 24 | readonly resolveUserDataPath: Effect.Effect<string>; | ||
| 25 | readonly configure: Effect.Effect<void>; | ||
| 26 | } | ||
| 29 | >()("@t3tools/desktop/app/DesktopAppIdentity") {} | 27 | >()("@t3tools/desktop/app/DesktopAppIdentity") {} |
| 30 | 28 | ||
| 31 | const normalizeCommitHash = (value: string): Option.Option<string> => { | 29 | const normalizeCommitHash = (value: string): Option.Option<string> => { |
| @@ -12,14 +12,13 @@ export interface DesktopIconPaths { · 11 unmodified lines | |||
| 12 | readonly png: Option.Option<string>; | 12 | readonly png: Option.Option<string>; |
| 13 | } | 13 | } |
| 14 | 14 | ||
| 15 | export interface DesktopAssetsShape { | 15 | export class DesktopAssets extends Context.Service< |
| 16 | readonly iconPaths: Effect.Effect<DesktopIconPaths>; | 16 | DesktopAssets, |
| 17 | readonly resolveResourcePath: (fileName: string) => Effect.Effect<Option.Option<string>>; | 17 | { |
| 18 | } | 18 | readonly iconPaths: Effect.Effect<DesktopIconPaths>; |
| 19 | 19 | readonly resolveResourcePath: (fileName: string) => Effect.Effect<Option.Option<string>>; | |
| 20 | export class DesktopAssets extends Context.Service<DesktopAssets, DesktopAssetsShape>()( | 20 | } |
| 21 | "@t3tools/desktop/app/DesktopAssets", | 21 | >()("@t3tools/desktop/app/DesktopAssets") {} |
| 22 | ) {} | ||
| 23 | 22 | ||
| 24 | const resolveResourcePath = Effect.fn("desktop.assets.resolveResourcePath")(function* ( | 23 | const resolveResourcePath = Effect.fn("desktop.assets.resolveResourcePath")(function* ( |
| 25 | fileName: string, | 24 | fileName: string, |
| @@ -21,10 +21,6 @@ vi.mock("@clerk/electron/storage", () => ({ · 20 unmodified lines | |||
| 21 | storage: storageMock, | 21 | storage: storageMock, |
| 22 | })); | 22 | })); |
| 23 | 23 | ||
| 24 | import { | ||
| 25 | createDesktopClerkBridge, | ||
| 26 | resolveDesktopClerkFrontendApiHostname, | ||
| 27 | } from "./DesktopClerk.ts"; | ||
| 28 | import * as DesktopClerk from "./DesktopClerk.ts"; | 24 | import * as DesktopClerk from "./DesktopClerk.ts"; |
| 29 | import * as DesktopEnvironment from "./DesktopEnvironment.ts"; | 25 | import * as DesktopEnvironment from "./DesktopEnvironment.ts"; |
| 30 | 26 | ||
| @@ -32,9 +28,12 @@ describe("DesktopClerk", () => { · 1 unmodified line | |||
| 32 | it("derives the Clerk Frontend API hostname used by the desktop CSP", () => { | 28 | it("derives the Clerk Frontend API hostname used by the desktop CSP", () => { |
| 33 | const publishableKey = `pk_test_${btoa("clerk.t3.codes$")}`; | 29 | const publishableKey = `pk_test_${btoa("clerk.t3.codes$")}`; |
| 34 | 30 | ||
| 35 | assert.equal(resolveDesktopClerkFrontendApiHostname(publishableKey), "clerk.t3.codes"); | 31 | assert.equal( |
| 36 | assert.equal(resolveDesktopClerkFrontendApiHostname(""), undefined); | 32 | DesktopClerk.resolveDesktopClerkFrontendApiHostname(publishableKey), |
| 37 | assert.equal(resolveDesktopClerkFrontendApiHostname("invalid"), undefined); | 33 | "clerk.t3.codes", |
| 34 | ); | ||
| 35 | assert.equal(DesktopClerk.resolveDesktopClerkFrontendApiHostname(""), undefined); | ||
| 36 | assert.equal(DesktopClerk.resolveDesktopClerkFrontendApiHostname("invalid"), undefined); | ||
| 38 | }); | 37 | }); |
| 39 | 38 | ||
| 40 | it.effect("acquires and releases the SDK bridge with the layer", () => { | 39 | it.effect("acquires and releases the SDK bridge with the layer", () => { |
| @@ -44,7 +43,7 @@ describe("DesktopClerk", () => { · 3 unmodified lines | |||
| 44 | const environment = DesktopEnvironment.DesktopEnvironment.of({ | 43 | const environment = DesktopEnvironment.DesktopEnvironment.of({ |
| 45 | stateDir: "/tmp/t3-state", | 44 | stateDir: "/tmp/t3-state", |
| 46 | isDevelopment: true, | 45 | isDevelopment: true, |
| 47 | } as unknown as DesktopEnvironment.DesktopEnvironmentShape); | 46 | } as unknown as DesktopEnvironment.DesktopEnvironment["Service"]); |
| 48 | 47 | ||
| 49 | return Effect.gen(function* () { | 48 | return Effect.gen(function* () { |
| 50 | yield* Effect.scoped( | 49 | yield* Effect.scoped( |
| @@ -78,7 +77,7 @@ describe("DesktopClerk", () => { · 27 unmodified lines | |||
| 78 | storageMock.mockReturnValue(storageAdapter); | 77 | storageMock.mockReturnValue(storageAdapter); |
| 79 | createClerkBridgeMock.mockReturnValue(bridge); | 78 | createClerkBridgeMock.mockReturnValue(bridge); |
| 80 | 79 | ||
| 81 | assert.equal(createDesktopClerkBridge("/tmp/t3-state", isDevelopment), bridge); | 80 | assert.equal(DesktopClerk.createDesktopClerkBridge("/tmp/t3-state", isDevelopment), bridge); |
| 82 | assert.deepEqual(storageMock.mock.calls, [[{ path: "/tmp/t3-state" }]]); | 81 | assert.deepEqual(storageMock.mock.calls, [[{ path: "/tmp/t3-state" }]]); |
| 83 | assert.deepEqual(createClerkBridgeMock.mock.calls, [ | 82 | assert.deepEqual(createClerkBridgeMock.mock.calls, [ |
| 84 | [ | 83 | [ |
| @@ -14,17 +14,16 @@ import * as DesktopEnvironment from "./DesktopEnvironment.ts"; · 13 unmodified lines | |||
| 14 | 14 | ||
| 15 | declare const __T3CODE_BUILD_CLERK_PUBLISHABLE_KEY__: string | undefined; | 15 | declare const __T3CODE_BUILD_CLERK_PUBLISHABLE_KEY__: string | undefined; |
| 16 | 16 | ||
| 17 | export interface DesktopClerkShape { | 17 | export class DesktopClerk extends Context.Service< |
| 18 | readonly configure: Effect.Effect< | 18 | DesktopClerk, |
| 19 | void, | 19 | { |
| 20 | never, | 20 | readonly configure: Effect.Effect< |
| 21 | ElectronApp.ElectronApp | ElectronWindow.ElectronWindow | Scope.Scope | 21 | void, |
| 22 | >; | 22 | never, |
| 23 | } | 23 | ElectronApp.ElectronApp | ElectronWindow.ElectronWindow | Scope.Scope |
| 24 | 24 | >; | |
| 25 | export class DesktopClerk extends Context.Service<DesktopClerk, DesktopClerkShape>()( | 25 | } |
| 26 | "@t3tools/desktop/app/DesktopClerk", | 26 | >()("@t3tools/desktop/app/DesktopClerk") {} |
| 27 | ) {} | ||
| 28 | 27 | ||
| 29 | export function resolveDesktopClerkFrontendApiHostname( | 28 | export function resolveDesktopClerkFrontendApiHostname( |
| 30 | publishableKey: string | undefined, | 29 | publishableKey: string | undefined, |
| @@ -11,10 +11,7 @@ import * as Layer from "effect/Layer"; · 10 unmodified lines | |||
| 11 | import * as Option from "effect/Option"; | 11 | import * as Option from "effect/Option"; |
| 12 | import * as Path from "effect/Path"; | 12 | import * as Path from "effect/Path"; |
| 13 | 13 | ||
| 14 | import { | 14 | import * as DesktopAppSettings from "../settings/DesktopAppSettings.ts"; |
| 15 | type DesktopSettings, | ||
| 16 | resolveDefaultDesktopSettings, | ||
| 17 | } from "../settings/DesktopAppSettings.ts"; | ||
| 18 | import * as DesktopConfig from "./DesktopConfig.ts"; | 15 | import * as DesktopConfig from "./DesktopConfig.ts"; |
| 19 | import { isNightlyDesktopVersion } from "../updates/updateChannels.ts"; | 16 | import { isNightlyDesktopVersion } from "../updates/updateChannels.ts"; |
| 20 | 17 | ||
| @@ -30,55 +27,53 @@ export interface MakeDesktopEnvironmentInput { · 9 unmodified lines | |||
| 30 | readonly runningUnderArm64Translation: boolean; | 27 | readonly runningUnderArm64Translation: boolean; |
| 31 | } | 28 | } |
| 32 | 29 | ||
| 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 | |||
| 79 | export class DesktopEnvironment extends Context.Service< | 30 | export class DesktopEnvironment extends Context.Service< |
| 80 | DesktopEnvironment, | 31 | 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 | } | ||
| 82 | >()("@t3tools/desktop/app/DesktopEnvironment") {} | 77 | >()("@t3tools/desktop/app/DesktopEnvironment") {} |
| 83 | 78 | ||
| 84 | const APP_BASE_NAME = "T3 Code"; | 79 | const APP_BASE_NAME = "T3 Code"; |
| @@ -136,9 +131,9 @@ function resolveDesktopRuntimeInfo(input: { · 51 unmodified lines | |||
| 136 | }; | 131 | }; |
| 137 | } | 132 | } |
| 138 | 133 | ||
| 139 | const makeDesktopEnvironment = Effect.fn("desktop.environment.make")(function* ( | 134 | const make = Effect.fn("desktop.environment.make")(function* ( |
| 140 | input: MakeDesktopEnvironmentInput, | 135 | input: MakeDesktopEnvironmentInput, |
| 141 | ): Effect.fn.Return<DesktopEnvironmentShape, Config.ConfigError, Path.Path> { | 136 | ): Effect.fn.Return<DesktopEnvironment["Service"], Config.ConfigError, Path.Path> { |
| 142 | const path = yield* Path.Path; | 137 | const path = yield* Path.Path; |
| 143 | const config = yield* DesktopConfig.DesktopConfig; | 138 | const config = yield* DesktopConfig.DesktopConfig; |
| 144 | const homeDirectory = input.homeDirectory; | 139 | const homeDirectory = input.homeDirectory; |
| @@ -208,7 +203,7 @@ const makeDesktopEnvironment = Effect.fn("desktop.environment.make")(function* ( · 63 unmodified lines | |||
| 208 | linuxWmClass: isDevelopment ? "t3code-dev" : "t3code", | 203 | linuxWmClass: isDevelopment ? "t3code-dev" : "t3code", |
| 209 | userDataDirName, | 204 | userDataDirName, |
| 210 | legacyUserDataDirName, | 205 | legacyUserDataDirName, |
| 211 | defaultDesktopSettings: resolveDefaultDesktopSettings(input.appVersion), | 206 | defaultDesktopSettings: DesktopAppSettings.resolveDefaultDesktopSettings(input.appVersion), |
| 212 | runtimeInfo: resolveDesktopRuntimeInfo({ | 207 | runtimeInfo: resolveDesktopRuntimeInfo({ |
| 213 | platform: input.platform, | 208 | platform: input.platform, |
| 214 | processArch: input.processArch, | 209 | processArch: input.processArch, |
| @@ -250,4 +245,4 @@ const makeDesktopEnvironment = Effect.fn("desktop.environment.make")(function* ( · 35 unmodified lines | |||
| 250 | }); | 245 | }); |
| 251 | 246 | ||
| 252 | export const layer = (input: MakeDesktopEnvironmentInput) => | 247 | export const layer = (input: MakeDesktopEnvironmentInput) => |
| 253 | Layer.effect(DesktopEnvironment, makeDesktopEnvironment(input)); | 248 | Layer.effect(DesktopEnvironment, make(input)); |
| @@ -1,7 +1,6 @@ | |||
| 1 | import * as Cause from "effect/Cause"; | 1 | import * as Cause from "effect/Cause"; |
| 2 | import * as Context from "effect/Context"; | 2 | import * as Context from "effect/Context"; |
| 3 | import * as Effect from "effect/Effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as Deferred from "effect/Deferred"; | ||
| 5 | import * as Layer from "effect/Layer"; | 4 | import * as Layer from "effect/Layer"; |
| 6 | import * as Ref from "effect/Ref"; | 5 | import * as Ref from "effect/Ref"; |
| 7 | import * as Scope from "effect/Scope"; | 6 | import * as Scope from "effect/Scope"; |
| @@ -10,63 +9,34 @@ import type * as Electron from "electron"; · 2 unmodified lines | |||
| 10 | 9 | ||
| 11 | import * as DesktopEnvironment from "./DesktopEnvironment.ts"; | 10 | import * as DesktopEnvironment from "./DesktopEnvironment.ts"; |
| 12 | import * as DesktopObservability from "./DesktopObservability.ts"; | 11 | import * as DesktopObservability from "./DesktopObservability.ts"; |
| 12 | import * as DesktopShutdownModule from "./DesktopShutdown.ts"; | ||
| 13 | import * as ElectronApp from "../electron/ElectronApp.ts"; | 13 | import * as ElectronApp from "../electron/ElectronApp.ts"; |
| 14 | import * as ElectronTheme from "../electron/ElectronTheme.ts"; | 14 | import * as ElectronTheme from "../electron/ElectronTheme.ts"; |
| 15 | import * as DesktopState from "./DesktopState.ts"; | 15 | import * as DesktopState from "./DesktopState.ts"; |
| 16 | import * as DesktopWindow from "../window/DesktopWindow.ts"; | 16 | import * as DesktopWindow from "../window/DesktopWindow.ts"; |
| 17 | 17 | ||
| 18 | export interface DesktopShutdownShape { | 18 | export { DesktopShutdown, layer as layerShutdown } from "./DesktopShutdown.ts"; |
| 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); | ||
| 48 | 19 | ||
| 49 | export type DesktopLifecycleRuntimeServices = | 20 | export type DesktopLifecycleRuntimeServices = |
| 50 | | DesktopEnvironment.DesktopEnvironment | 21 | | DesktopEnvironment.DesktopEnvironment |
| 51 | | DesktopShutdown | 22 | | DesktopShutdownModule.DesktopShutdown |
| 52 | | DesktopState.DesktopState | 23 | | DesktopState.DesktopState |
| 53 | | DesktopWindow.DesktopWindow | 24 | | DesktopWindow.DesktopWindow |
| 54 | | ElectronApp.ElectronApp | 25 | | ElectronApp.ElectronApp |
| 55 | | ElectronTheme.ElectronTheme; | 26 | | ElectronTheme.ElectronTheme; |
| 56 | 27 | ||
| 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 | |||
| 64 | /** | 28 | /** |
| 65 | * @effect-expect-leaking DesktopEnvironment | DesktopShutdown | DesktopState | DesktopWindow | ElectronApp | ElectronTheme | 29 | * @effect-expect-leaking DesktopEnvironment | DesktopShutdown | DesktopState | DesktopWindow | ElectronApp | ElectronTheme |
| 66 | */ | 30 | */ |
| 67 | export class DesktopLifecycle extends Context.Service<DesktopLifecycle, DesktopLifecycleShape>()( | 31 | export class DesktopLifecycle extends Context.Service< |
| 68 | "@t3tools/desktop/app/DesktopLifecycle", | 32 | DesktopLifecycle, |
| 69 | ) {} | 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") {} | ||
| 70 | 40 | ||
| 71 | const { logInfo: logLifecycleInfo, logError: logLifecycleError } = | 41 | const { logInfo: logLifecycleInfo, logError: logLifecycleError } = |
| 72 | DesktopObservability.makeComponentLogger("desktop-lifecycle"); | 42 | DesktopObservability.makeComponentLogger("desktop-lifecycle"); |
| @@ -93,8 +63,8 @@ function addScopedListener<Args extends ReadonlyArray<unknown>>( · 20 unmodified lines | |||
| 93 | } | 63 | } |
| 94 | 64 | ||
| 95 | const requestDesktopShutdownAndWait = Effect.fn("desktop.lifecycle.requestShutdownAndWait")( | 65 | const requestDesktopShutdownAndWait = Effect.fn("desktop.lifecycle.requestShutdownAndWait")( |
| 96 | function* (): Effect.fn.Return<void, never, DesktopShutdown> { | 66 | function* (): Effect.fn.Return<void, never, DesktopShutdownModule.DesktopShutdown> { |
| 97 | const shutdown = yield* DesktopShutdown; | 67 | const shutdown = yield* DesktopShutdownModule.DesktopShutdown; |
| 98 | yield* shutdown.request; | 68 | yield* shutdown.request; |
| 99 | yield* shutdown.awaitComplete; | 69 | yield* shutdown.awaitComplete; |
| 100 | }, | 70 | }, |
| @@ -154,83 +124,82 @@ function quitFromSignal( · 53 unmodified lines | |||
| 154 | ); | 124 | ); |
| 155 | } | 125 | } |
| 156 | 126 | ||
| 157 | export const layer = Layer.succeed( | 127 | const make = DesktopLifecycle.of({ |
| 158 | DesktopLifecycle, | 128 | relaunch: Effect.fn("desktop.lifecycle.relaunch")(function* (reason) { |
| 159 | DesktopLifecycle.of({ | 129 | const electronApp = yield* ElectronApp.ElectronApp; |
| 160 | relaunch: Effect.fn("desktop.lifecycle.relaunch")(function* (reason) { | 130 | const environment = yield* DesktopEnvironment.DesktopEnvironment; |
| 161 | const electronApp = yield* ElectronApp.ElectronApp; | 131 | const state = yield* DesktopState.DesktopState; |
| 162 | const environment = yield* DesktopEnvironment.DesktopEnvironment; | 132 | yield* logLifecycleInfo("desktop relaunch requested", { reason }); |
| 163 | const state = yield* DesktopState.DesktopState; | 133 | yield* Effect.gen(function* () { |
| 164 | yield* logLifecycleInfo("desktop relaunch requested", { reason }); | 134 | yield* Effect.yieldNow; |
| 165 | yield* Effect.gen(function* () { | 135 | yield* Ref.set(state.quitting, true); |
| 166 | yield* Effect.yieldNow; | 136 | yield* requestDesktopShutdownAndWait(); |
| 167 | yield* Ref.set(state.quitting, true); | 137 | if (environment.isDevelopment) { |
| 168 | yield* requestDesktopShutdownAndWait(); | 138 | yield* electronApp.exit(75); |
| 169 | if (environment.isDevelopment) { | 139 | return; |
| 170 | yield* electronApp.exit(75); | 140 | } |
| 171 | return; | 141 | yield* electronApp.relaunch({ |
| 172 | } | 142 | execPath: process.execPath, |
| 173 | yield* electronApp.relaunch({ | 143 | args: process.argv.slice(1), |
| 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 | ); | ||
| 210 | }); | 144 | }); |
| 211 | yield* electronApp.on("activate", () => { | 145 | yield* electronApp.exit(0); |
| 212 | void runEffect(desktopWindow.activate.pipe(Effect.withSpan("desktop.lifecycle.activate"))); | 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); | ||
| 213 | }); | 197 | }); |
| 214 | yield* electronApp.on("window-all-closed", () => { | 198 | yield* addScopedListener(process, "SIGTERM", () => { |
| 215 | void runEffect( | 199 | quitFromSignal("SIGTERM", 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 | ); | ||
| 224 | }); | 200 | }); |
| 201 | } | ||
| 202 | }).pipe(Effect.withSpan("desktop.lifecycle.register")), | ||
| 203 | }); | ||
| 225 | 204 | ||
| 226 | if (environment.platform !== "win32") { | 205 | export const layer = Layer.succeed(DesktopLifecycle, make); |
| 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 | ); | ||
| @@ -314,7 +314,7 @@ const PRIMARY_BACKEND_LOG_INSTANCE_ID = PRIMARY_LOCAL_ENVIRONMENT_ID; · 313 unmodified lines | |||
| 314 | const sanitizeInstanceIdForFileName = (id: string): string => id.replace(/[^a-zA-Z0-9._-]+/g, "_"); | 314 | const sanitizeInstanceIdForFileName = (id: string): string => id.replace(/[^a-zA-Z0-9._-]+/g, "_"); |
| 315 | 315 | ||
| 316 | const backendLogFilePathForInstance = ( | 316 | const backendLogFilePathForInstance = ( |
| 317 | environment: DesktopEnvironment.DesktopEnvironmentShape, | 317 | environment: DesktopEnvironment.DesktopEnvironment["Service"], |
| 318 | id: string, | 318 | id: string, |
| 319 | ): string => { | 319 | ): string => { |
| 320 | // Primary keeps the historical "server-child.log" path so ops scripts | 320 | // Primary keeps the historical "server-child.log" path so ops scripts |
| @@ -332,7 +332,7 @@ const backendLogFilePathForInstance = ( · 11 unmodified lines | |||
| 332 | // per-call shape lets the shape annotate writes with the *caller's* | 332 | // per-call shape lets the shape annotate writes with the *caller's* |
| 333 | // id rather than whatever id created the cached writer first. | 333 | // id rather than whatever id created the cached writer first. |
| 334 | const makeBackendOutputSinkForInstance = ( | 334 | const makeBackendOutputSinkForInstance = ( |
| 335 | environment: DesktopEnvironment.DesktopEnvironmentShape, | 335 | environment: DesktopEnvironment.DesktopEnvironment["Service"], |
| 336 | id: string, | 336 | id: string, |
| 337 | ): Effect.Effect< | 337 | ): Effect.Effect< |
| 338 | Option.Option<RotatingLogFileWriter>, | 338 | Option.Option<RotatingLogFileWriter>, |
| @@ -344,7 +344,7 @@ const makeBackendOutputSinkForInstance = ( · 5 unmodified lines | |||
| 344 | }).pipe(Effect.option); | 344 | }).pipe(Effect.option); |
| 345 | 345 | ||
| 346 | const makeBackendOutputLogShape = ( | 346 | const makeBackendOutputLogShape = ( |
| 347 | environment: DesktopEnvironment.DesktopEnvironmentShape, | 347 | environment: DesktopEnvironment.DesktopEnvironment["Service"], |
| 348 | id: string, | 348 | id: string, |
| 349 | sink: Option.Option<RotatingLogFileWriter>, | 349 | sink: Option.Option<RotatingLogFileWriter>, |
| 350 | ): DesktopBackendOutputLogShape => | 350 | ): DesktopBackendOutputLogShape => |
| @@ -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); | ||
| @@ -3,17 +3,17 @@ import * as Effect from "effect/Effect"; · 2 unmodified lines | |||
| 3 | import * as Layer from "effect/Layer"; | 3 | import * as Layer from "effect/Layer"; |
| 4 | import * as Ref from "effect/Ref"; | 4 | import * as Ref from "effect/Ref"; |
| 5 | 5 | ||
| 6 | export interface DesktopStateShape { | 6 | export class DesktopState extends Context.Service< |
| 7 | readonly quitting: Ref.Ref<boolean>; | 7 | DesktopState, |
| 8 | } | 8 | { |
| 9 | readonly backendReady: Ref.Ref<boolean>; | ||
| 10 | readonly quitting: Ref.Ref<boolean>; | ||
| 11 | } | ||
| 12 | >()("@t3tools/desktop/app/DesktopState") {} | ||
| 9 | 13 | ||
| 10 | export class DesktopState extends Context.Service<DesktopState, DesktopStateShape>()( | 14 | const make = Effect.all({ |
| 11 | "@t3tools/desktop/app/DesktopState", | 15 | backendReady: Ref.make(false), |
| 12 | ) {} | 16 | quitting: Ref.make(false), |
| 17 | }); | ||
| 13 | 18 | ||
| 14 | export const layer = Layer.effect( | 19 | export const layer = Layer.effect(DesktopState, make); |
| 15 | DesktopState, | ||
| 16 | Effect.all({ | ||
| 17 | quitting: Ref.make(false), | ||
| 18 | }), | ||
| 19 | ); | ||
| @@ -1,5 +1,6 @@ | |||
| 1 | import * as Context from "effect/Context"; | 1 | import * as Context from "effect/Context"; |
| 2 | import * as Effect from "effect/Effect"; | 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Layer from "effect/Layer"; | ||
| 3 | import * as Schema from "effect/Schema"; | 4 | import * as Schema from "effect/Schema"; |
| 4 | import * as Scope from "effect/Scope"; | 5 | import * as Scope from "effect/Scope"; |
| 5 | 6 | ||
| @@ -33,20 +34,19 @@ export interface DesktopSyncIpcMethod<E, R> { · 27 unmodified lines | |||
| 33 | readonly handler: () => Effect.Effect<unknown, E, R>; | 34 | readonly handler: () => Effect.Effect<unknown, E, R>; |
| 34 | } | 35 | } |
| 35 | 36 | ||
| 36 | export interface DesktopIpcShape { | 37 | export class DesktopIpc extends Context.Service< |
| 37 | readonly handle: <E, R>( | 38 | DesktopIpc, |
| 38 | input: DesktopIpcMethod<E, R>, | 39 | { |
| 39 | ) => Effect.Effect<void, never, R | Scope.Scope>; | 40 | readonly handle: <E, R>( |
| 40 | readonly handleSync: <E, R>( | 41 | input: DesktopIpcMethod<E, R>, |
| 41 | input: DesktopSyncIpcMethod<E, R>, | 42 | ) => Effect.Effect<void, never, R | Scope.Scope>; |
| 42 | ) => Effect.Effect<void, never, R | Scope.Scope>; | 43 | readonly handleSync: <E, R>( |
| 43 | } | 44 | input: DesktopSyncIpcMethod<E, R>, |
| 44 | 45 | ) => Effect.Effect<void, never, R | Scope.Scope>; | |
| 45 | export class DesktopIpc extends Context.Service<DesktopIpc, DesktopIpcShape>()( | 46 | } |
| 46 | "@t3tools/desktop/ipc/DesktopIpc", | 47 | >()("@t3tools/desktop/ipc/DesktopIpc") {} |
| 47 | ) {} | 48 | |
| 48 | 49 | export const make = (ipcMain: DesktopIpcMain): DesktopIpc["Service"] => | |
| 49 | export const make = (ipcMain: DesktopIpcMain): DesktopIpcShape => | ||
| 50 | DesktopIpc.of({ | 50 | DesktopIpc.of({ |
| 51 | handle: Effect.fn("desktop.ipc.registerInvoke")(function* <E, R>({ | 51 | handle: Effect.fn("desktop.ipc.registerInvoke")(function* <E, R>({ |
| 52 | channel, | 52 | channel, |
| @@ -97,6 +97,8 @@ export const make = (ipcMain: DesktopIpcMain): DesktopIpcShape => · 44 unmodified lines | |||
| 97 | }), | 97 | }), |
| 98 | }); | 98 | }); |
| 99 | 99 | ||
| 100 | export const layer = (ipcMain: DesktopIpcMain) => Layer.succeed(DesktopIpc, make(ipcMain)); | ||
| 101 | |||
| 100 | /** | 102 | /** |
| 101 | * Convenience helpers for creating IPC methods | 103 | * Convenience helpers for creating IPC methods |
| 102 | */ | 104 | */ |
| @@ -5,9 +5,9 @@ import * as Schema from "effect/Schema"; · 4 unmodified lines | |||
| 5 | 5 | ||
| 6 | import * as DesktopClientSettings from "../../settings/DesktopClientSettings.ts"; | 6 | import * as DesktopClientSettings from "../../settings/DesktopClientSettings.ts"; |
| 7 | import * as IpcChannels from "../channels.ts"; | 7 | import * as IpcChannels from "../channels.ts"; |
| 8 | import { makeIpcMethod } from "../DesktopIpc.ts"; | 8 | import * as DesktopIpc from "../DesktopIpc.ts"; |
| 9 | 9 | ||
| 10 | export const getClientSettings = makeIpcMethod({ | 10 | export const getClientSettings = DesktopIpc.makeIpcMethod({ |
| 11 | channel: IpcChannels.GET_CLIENT_SETTINGS_CHANNEL, | 11 | channel: IpcChannels.GET_CLIENT_SETTINGS_CHANNEL, |
| 12 | payload: Schema.Void, | 12 | payload: Schema.Void, |
| 13 | result: Schema.NullOr(ClientSettingsSchema), | 13 | result: Schema.NullOr(ClientSettingsSchema), |
| @@ -17,7 +17,7 @@ export const getClientSettings = makeIpcMethod({ · 3 unmodified lines | |||
| 17 | }), | 17 | }), |
| 18 | }); | 18 | }); |
| 19 | 19 | ||
| 20 | export const setClientSettings = makeIpcMethod({ | 20 | export const setClientSettings = DesktopIpc.makeIpcMethod({ |
| 21 | channel: IpcChannels.SET_CLIENT_SETTINGS_CHANNEL, | 21 | channel: IpcChannels.SET_CLIENT_SETTINGS_CHANNEL, |
| 22 | payload: ClientSettingsSchema, | 22 | payload: ClientSettingsSchema, |
| 23 | result: Schema.Void, | 23 | result: Schema.Void, |
| @@ -4,9 +4,9 @@ import * as Schema from "effect/Schema"; · 3 unmodified lines | |||
| 4 | 4 | ||
| 5 | import * as DesktopConnectionCatalogStore from "../../app/DesktopConnectionCatalogStore.ts"; | 5 | import * as DesktopConnectionCatalogStore from "../../app/DesktopConnectionCatalogStore.ts"; |
| 6 | import * as IpcChannels from "../channels.ts"; | 6 | import * as IpcChannels from "../channels.ts"; |
| 7 | import { makeIpcMethod } from "../DesktopIpc.ts"; | 7 | import * as DesktopIpc from "../DesktopIpc.ts"; |
| 8 | 8 | ||
| 9 | export const getConnectionCatalog = makeIpcMethod({ | 9 | export const getConnectionCatalog = DesktopIpc.makeIpcMethod({ |
| 10 | channel: IpcChannels.GET_CONNECTION_CATALOG_CHANNEL, | 10 | channel: IpcChannels.GET_CONNECTION_CATALOG_CHANNEL, |
| 11 | payload: Schema.Void, | 11 | payload: Schema.Void, |
| 12 | result: Schema.NullOr(Schema.String), | 12 | result: Schema.NullOr(Schema.String), |
| @@ -16,7 +16,7 @@ export const getConnectionCatalog = makeIpcMethod({ · 3 unmodified lines | |||
| 16 | }), | 16 | }), |
| 17 | }); | 17 | }); |
| 18 | 18 | ||
| 19 | export const setConnectionCatalog = makeIpcMethod({ | 19 | export const setConnectionCatalog = DesktopIpc.makeIpcMethod({ |
| 20 | channel: IpcChannels.SET_CONNECTION_CATALOG_CHANNEL, | 20 | channel: IpcChannels.SET_CONNECTION_CATALOG_CHANNEL, |
| 21 | payload: Schema.String, | 21 | payload: Schema.String, |
| 22 | result: Schema.Boolean, | 22 | result: Schema.Boolean, |
| @@ -26,7 +26,7 @@ export const setConnectionCatalog = makeIpcMethod({ · 3 unmodified lines | |||
| 26 | }), | 26 | }), |
| 27 | }); | 27 | }); |
| 28 | 28 | ||
| 29 | export const clearConnectionCatalog = makeIpcMethod({ | 29 | export const clearConnectionCatalog = DesktopIpc.makeIpcMethod({ |
| 30 | channel: IpcChannels.CLEAR_CONNECTION_CATALOG_CHANNEL, | 30 | channel: IpcChannels.CLEAR_CONNECTION_CATALOG_CHANNEL, |
| 31 | payload: Schema.Void, | 31 | payload: Schema.Void, |
| 32 | result: Schema.Void, | 32 | result: Schema.Void, |
| @@ -27,7 +27,7 @@ import { pathToFileURL } from "node:url"; · 26 unmodified lines | |||
| 27 | import * as PreviewManager from "../../preview/Manager.ts"; | 27 | import * as PreviewManager from "../../preview/Manager.ts"; |
| 28 | import { PREVIEW_WEBVIEW_PREFERENCES } from "../../preview/WebviewPreferences.ts"; | 28 | import { PREVIEW_WEBVIEW_PREFERENCES } from "../../preview/WebviewPreferences.ts"; |
| 29 | import * as IpcChannels from "../channels.ts"; | 29 | import * as IpcChannels from "../channels.ts"; |
| 30 | import { makeIpcMethod } from "../DesktopIpc.ts"; | 30 | import * as DesktopIpc from "../DesktopIpc.ts"; |
| 31 | 31 | ||
| 32 | const broadcast = (channel: string, ...args: ReadonlyArray<unknown>): void => { | 32 | const broadcast = (channel: string, ...args: ReadonlyArray<unknown>): void => { |
| 33 | for (const window of BrowserWindow.getAllWindows()) { | 33 | for (const window of BrowserWindow.getAllWindows()) { |
| @@ -52,7 +52,7 @@ export const installPreviewEventForwarding = Effect.fn( · 18 unmodified lines | |||
| 52 | }); | 52 | }); |
| 53 | }); | 53 | }); |
| 54 | 54 | ||
| 55 | export const createTab = makeIpcMethod({ | 55 | export const createTab = DesktopIpc.makeIpcMethod({ |
| 56 | channel: IpcChannels.PREVIEW_CREATE_TAB_CHANNEL, | 56 | channel: IpcChannels.PREVIEW_CREATE_TAB_CHANNEL, |
| 57 | payload: DesktopPreviewTabInputSchema, | 57 | payload: DesktopPreviewTabInputSchema, |
| 58 | result: Schema.Void, | 58 | result: Schema.Void, |
| @@ -62,7 +62,7 @@ export const createTab = makeIpcMethod({ · 3 unmodified lines | |||
| 62 | }), | 62 | }), |
| 63 | }); | 63 | }); |
| 64 | 64 | ||
| 65 | export const closeTab = makeIpcMethod({ | 65 | export const closeTab = DesktopIpc.makeIpcMethod({ |
| 66 | channel: IpcChannels.PREVIEW_CLOSE_TAB_CHANNEL, | 66 | channel: IpcChannels.PREVIEW_CLOSE_TAB_CHANNEL, |
| 67 | payload: DesktopPreviewTabInputSchema, | 67 | payload: DesktopPreviewTabInputSchema, |
| 68 | result: Schema.Void, | 68 | result: Schema.Void, |
| @@ -72,7 +72,7 @@ export const closeTab = makeIpcMethod({ · 3 unmodified lines | |||
| 72 | }), | 72 | }), |
| 73 | }); | 73 | }); |
| 74 | 74 | ||
| 75 | export const registerWebview = makeIpcMethod({ | 75 | export const registerWebview = DesktopIpc.makeIpcMethod({ |
| 76 | channel: IpcChannels.PREVIEW_REGISTER_WEBVIEW_CHANNEL, | 76 | channel: IpcChannels.PREVIEW_REGISTER_WEBVIEW_CHANNEL, |
| 77 | payload: DesktopPreviewRegisterWebviewInputSchema, | 77 | payload: DesktopPreviewRegisterWebviewInputSchema, |
| 78 | result: Schema.Void, | 78 | result: Schema.Void, |
| @@ -82,7 +82,7 @@ export const registerWebview = makeIpcMethod({ · 3 unmodified lines | |||
| 82 | }), | 82 | }), |
| 83 | }); | 83 | }); |
| 84 | 84 | ||
| 85 | export const navigate = makeIpcMethod({ | 85 | export const navigate = DesktopIpc.makeIpcMethod({ |
| 86 | channel: IpcChannels.PREVIEW_NAVIGATE_CHANNEL, | 86 | channel: IpcChannels.PREVIEW_NAVIGATE_CHANNEL, |
| 87 | payload: DesktopPreviewNavigateInputSchema, | 87 | payload: DesktopPreviewNavigateInputSchema, |
| 88 | result: Schema.Void, | 88 | result: Schema.Void, |
| @@ -100,7 +100,7 @@ const tabMethod = ( · 11 unmodified lines | |||
| 100 | tabId: string, | 100 | tabId: string, |
| 101 | ) => Effect.Effect<void, PreviewManager.PreviewManagerError>, | 101 | ) => Effect.Effect<void, PreviewManager.PreviewManagerError>, |
| 102 | ) => | 102 | ) => |
| 103 | makeIpcMethod({ | 103 | DesktopIpc.makeIpcMethod({ |
| 104 | channel, | 104 | channel, |
| 105 | payload: DesktopPreviewTabInputSchema, | 105 | payload: DesktopPreviewTabInputSchema, |
| 106 | result: Schema.Void, | 106 | result: Schema.Void, |
| @@ -166,7 +166,7 @@ export const stopRecording = tabMethod( · 59 unmodified lines | |||
| 166 | (manager, tabId) => manager.stopRecording(tabId), | 166 | (manager, tabId) => manager.stopRecording(tabId), |
| 167 | ); | 167 | ); |
| 168 | 168 | ||
| 169 | export const clearCookies = makeIpcMethod({ | 169 | export const clearCookies = DesktopIpc.makeIpcMethod({ |
| 170 | channel: IpcChannels.PREVIEW_CLEAR_COOKIES_CHANNEL, | 170 | channel: IpcChannels.PREVIEW_CLEAR_COOKIES_CHANNEL, |
| 171 | payload: Schema.Void, | 171 | payload: Schema.Void, |
| 172 | result: Schema.Void, | 172 | result: Schema.Void, |
| @@ -176,7 +176,7 @@ export const clearCookies = makeIpcMethod({ · 3 unmodified lines | |||
| 176 | }), | 176 | }), |
| 177 | }); | 177 | }); |
| 178 | 178 | ||
| 179 | export const clearCache = makeIpcMethod({ | 179 | export const clearCache = DesktopIpc.makeIpcMethod({ |
| 180 | channel: IpcChannels.PREVIEW_CLEAR_CACHE_CHANNEL, | 180 | channel: IpcChannels.PREVIEW_CLEAR_CACHE_CHANNEL, |
| 181 | payload: Schema.Void, | 181 | payload: Schema.Void, |
| 182 | result: Schema.Void, | 182 | result: Schema.Void, |
| @@ -186,7 +186,7 @@ export const clearCache = makeIpcMethod({ · 3 unmodified lines | |||
| 186 | }), | 186 | }), |
| 187 | }); | 187 | }); |
| 188 | 188 | ||
| 189 | export const getPreviewConfig = makeIpcMethod({ | 189 | export const getPreviewConfig = DesktopIpc.makeIpcMethod({ |
| 190 | channel: IpcChannels.PREVIEW_GET_CONFIG_CHANNEL, | 190 | channel: IpcChannels.PREVIEW_GET_CONFIG_CHANNEL, |
| 191 | payload: DesktopPreviewConfigInputSchema, | 191 | payload: DesktopPreviewConfigInputSchema, |
| 192 | result: DesktopPreviewWebviewConfigSchema, | 192 | result: DesktopPreviewWebviewConfigSchema, |
| @@ -201,7 +201,7 @@ export const getPreviewConfig = makeIpcMethod({ · 8 unmodified lines | |||
| 201 | }), | 201 | }), |
| 202 | }); | 202 | }); |
| 203 | 203 | ||
| 204 | export const setAnnotationTheme = makeIpcMethod({ | 204 | export const setAnnotationTheme = DesktopIpc.makeIpcMethod({ |
| 205 | channel: IpcChannels.PREVIEW_SET_ANNOTATION_THEME_CHANNEL, | 205 | channel: IpcChannels.PREVIEW_SET_ANNOTATION_THEME_CHANNEL, |
| 206 | payload: DesktopPreviewAnnotationThemeInputSchema, | 206 | payload: DesktopPreviewAnnotationThemeInputSchema, |
| 207 | result: Schema.Void, | 207 | result: Schema.Void, |
| @@ -211,7 +211,7 @@ export const setAnnotationTheme = makeIpcMethod({ · 3 unmodified lines | |||
| 211 | }), | 211 | }), |
| 212 | }); | 212 | }); |
| 213 | 213 | ||
| 214 | export const pickElement = makeIpcMethod({ | 214 | export const pickElement = DesktopIpc.makeIpcMethod({ |
| 215 | channel: IpcChannels.PREVIEW_PICK_ELEMENT_CHANNEL, | 215 | channel: IpcChannels.PREVIEW_PICK_ELEMENT_CHANNEL, |
| 216 | payload: DesktopPreviewTabInputSchema, | 216 | payload: DesktopPreviewTabInputSchema, |
| 217 | result: Schema.NullOr(PreviewAnnotationPayloadSchema), | 217 | result: Schema.NullOr(PreviewAnnotationPayloadSchema), |
| @@ -221,7 +221,7 @@ export const pickElement = makeIpcMethod({ · 3 unmodified lines | |||
| 221 | }), | 221 | }), |
| 222 | }); | 222 | }); |
| 223 | 223 | ||
| 224 | export const captureScreenshot = makeIpcMethod({ | 224 | export const captureScreenshot = DesktopIpc.makeIpcMethod({ |
| 225 | channel: IpcChannels.PREVIEW_CAPTURE_SCREENSHOT_CHANNEL, | 225 | channel: IpcChannels.PREVIEW_CAPTURE_SCREENSHOT_CHANNEL, |
| 226 | payload: DesktopPreviewTabInputSchema, | 226 | payload: DesktopPreviewTabInputSchema, |
| 227 | result: DesktopPreviewScreenshotArtifactSchema, | 227 | result: DesktopPreviewScreenshotArtifactSchema, |
| @@ -231,7 +231,7 @@ export const captureScreenshot = makeIpcMethod({ · 3 unmodified lines | |||
| 231 | }), | 231 | }), |
| 232 | }); | 232 | }); |
| 233 | 233 | ||
| 234 | export const revealArtifact = makeIpcMethod({ | 234 | export const revealArtifact = DesktopIpc.makeIpcMethod({ |
| 235 | channel: IpcChannels.PREVIEW_REVEAL_ARTIFACT_CHANNEL, | 235 | channel: IpcChannels.PREVIEW_REVEAL_ARTIFACT_CHANNEL, |
| 236 | payload: DesktopPreviewArtifactInputSchema, | 236 | payload: DesktopPreviewArtifactInputSchema, |
| 237 | result: Schema.Void, | 237 | result: Schema.Void, |
| @@ -241,7 +241,7 @@ export const revealArtifact = makeIpcMethod({ · 3 unmodified lines | |||
| 241 | }), | 241 | }), |
| 242 | }); | 242 | }); |
| 243 | 243 | ||
| 244 | export const copyArtifactToClipboard = makeIpcMethod({ | 244 | export const copyArtifactToClipboard = DesktopIpc.makeIpcMethod({ |
| 245 | channel: IpcChannels.PREVIEW_COPY_ARTIFACT_CHANNEL, | 245 | channel: IpcChannels.PREVIEW_COPY_ARTIFACT_CHANNEL, |
| 246 | payload: DesktopPreviewArtifactInputSchema, | 246 | payload: DesktopPreviewArtifactInputSchema, |
| 247 | result: Schema.Void, | 247 | result: Schema.Void, |
| @@ -251,7 +251,7 @@ export const copyArtifactToClipboard = makeIpcMethod({ · 3 unmodified lines | |||
| 251 | }), | 251 | }), |
| 252 | }); | 252 | }); |
| 253 | 253 | ||
| 254 | export const automationStatus = makeIpcMethod({ | 254 | export const automationStatus = DesktopIpc.makeIpcMethod({ |
| 255 | channel: IpcChannels.PREVIEW_AUTOMATION_STATUS_CHANNEL, | 255 | channel: IpcChannels.PREVIEW_AUTOMATION_STATUS_CHANNEL, |
| 256 | payload: DesktopPreviewTabInputSchema, | 256 | payload: DesktopPreviewTabInputSchema, |
| 257 | result: PreviewAutomationStatus, | 257 | result: PreviewAutomationStatus, |
| @@ -261,7 +261,7 @@ export const automationStatus = makeIpcMethod({ · 3 unmodified lines | |||
| 261 | }), | 261 | }), |
| 262 | }); | 262 | }); |
| 263 | 263 | ||
| 264 | export const automationSnapshot = makeIpcMethod({ | 264 | export const automationSnapshot = DesktopIpc.makeIpcMethod({ |
| 265 | channel: IpcChannels.PREVIEW_AUTOMATION_SNAPSHOT_CHANNEL, | 265 | channel: IpcChannels.PREVIEW_AUTOMATION_SNAPSHOT_CHANNEL, |
| 266 | payload: DesktopPreviewTabInputSchema, | 266 | payload: DesktopPreviewTabInputSchema, |
| 267 | result: PreviewAutomationSnapshot, | 267 | result: PreviewAutomationSnapshot, |
| @@ -271,7 +271,7 @@ export const automationSnapshot = makeIpcMethod({ · 3 unmodified lines | |||
| 271 | }), | 271 | }), |
| 272 | }); | 272 | }); |
| 273 | 273 | ||
| 274 | export const automationClick = makeIpcMethod({ | 274 | export const automationClick = DesktopIpc.makeIpcMethod({ |
| 275 | channel: IpcChannels.PREVIEW_AUTOMATION_CLICK_CHANNEL, | 275 | channel: IpcChannels.PREVIEW_AUTOMATION_CLICK_CHANNEL, |
| 276 | payload: DesktopPreviewAutomationClickInputSchema, | 276 | payload: DesktopPreviewAutomationClickInputSchema, |
| 277 | result: Schema.Void, | 277 | result: Schema.Void, |
| @@ -281,7 +281,7 @@ export const automationClick = makeIpcMethod({ · 3 unmodified lines | |||
| 281 | }), | 281 | }), |
| 282 | }); | 282 | }); |
| 283 | 283 | ||
| 284 | export const automationType = makeIpcMethod({ | 284 | export const automationType = DesktopIpc.makeIpcMethod({ |
| 285 | channel: IpcChannels.PREVIEW_AUTOMATION_TYPE_CHANNEL, | 285 | channel: IpcChannels.PREVIEW_AUTOMATION_TYPE_CHANNEL, |
| 286 | payload: DesktopPreviewAutomationTypeInputSchema, | 286 | payload: DesktopPreviewAutomationTypeInputSchema, |
| 287 | result: Schema.Void, | 287 | result: Schema.Void, |
| @@ -291,7 +291,7 @@ export const automationType = makeIpcMethod({ · 3 unmodified lines | |||
| 291 | }), | 291 | }), |
| 292 | }); | 292 | }); |
| 293 | 293 | ||
| 294 | export const automationPress = makeIpcMethod({ | 294 | export const automationPress = DesktopIpc.makeIpcMethod({ |
| 295 | channel: IpcChannels.PREVIEW_AUTOMATION_PRESS_CHANNEL, | 295 | channel: IpcChannels.PREVIEW_AUTOMATION_PRESS_CHANNEL, |
| 296 | payload: DesktopPreviewAutomationPressInputSchema, | 296 | payload: DesktopPreviewAutomationPressInputSchema, |
| 297 | result: Schema.Void, | 297 | result: Schema.Void, |
| @@ -301,7 +301,7 @@ export const automationPress = makeIpcMethod({ · 3 unmodified lines | |||
| 301 | }), | 301 | }), |
| 302 | }); | 302 | }); |
| 303 | 303 | ||
| 304 | export const automationScroll = makeIpcMethod({ | 304 | export const automationScroll = DesktopIpc.makeIpcMethod({ |
| 305 | channel: IpcChannels.PREVIEW_AUTOMATION_SCROLL_CHANNEL, | 305 | channel: IpcChannels.PREVIEW_AUTOMATION_SCROLL_CHANNEL, |
| 306 | payload: DesktopPreviewAutomationScrollInputSchema, | 306 | payload: DesktopPreviewAutomationScrollInputSchema, |
| 307 | result: Schema.Void, | 307 | result: Schema.Void, |
| @@ -311,7 +311,7 @@ export const automationScroll = makeIpcMethod({ · 3 unmodified lines | |||
| 311 | }), | 311 | }), |
| 312 | }); | 312 | }); |
| 313 | 313 | ||
| 314 | export const automationEvaluate = makeIpcMethod({ | 314 | export const automationEvaluate = DesktopIpc.makeIpcMethod({ |
| 315 | channel: IpcChannels.PREVIEW_AUTOMATION_EVALUATE_CHANNEL, | 315 | channel: IpcChannels.PREVIEW_AUTOMATION_EVALUATE_CHANNEL, |
| 316 | payload: DesktopPreviewAutomationEvaluateInputSchema, | 316 | payload: DesktopPreviewAutomationEvaluateInputSchema, |
| 317 | result: Schema.Unknown, | 317 | result: Schema.Unknown, |
| @@ -321,7 +321,7 @@ export const automationEvaluate = makeIpcMethod({ · 3 unmodified lines | |||
| 321 | }), | 321 | }), |
| 322 | }); | 322 | }); |
| 323 | 323 | ||
| 324 | export const automationWaitFor = makeIpcMethod({ | 324 | export const automationWaitFor = DesktopIpc.makeIpcMethod({ |
| 325 | channel: IpcChannels.PREVIEW_AUTOMATION_WAIT_FOR_CHANNEL, | 325 | channel: IpcChannels.PREVIEW_AUTOMATION_WAIT_FOR_CHANNEL, |
| 326 | payload: DesktopPreviewAutomationWaitForInputSchema, | 326 | payload: DesktopPreviewAutomationWaitForInputSchema, |
| 327 | result: Schema.Void, | 327 | result: Schema.Void, |
| @@ -331,7 +331,7 @@ export const automationWaitFor = makeIpcMethod({ · 3 unmodified lines | |||
| 331 | }), | 331 | }), |
| 332 | }); | 332 | }); |
| 333 | 333 | ||
| 334 | export const saveRecording = makeIpcMethod({ | 334 | export const saveRecording = DesktopIpc.makeIpcMethod({ |
| 335 | channel: IpcChannels.PREVIEW_RECORDING_SAVE_CHANNEL, | 335 | channel: IpcChannels.PREVIEW_RECORDING_SAVE_CHANNEL, |
| 336 | payload: DesktopPreviewRecordingSaveInputSchema, | 336 | payload: DesktopPreviewRecordingSaveInputSchema, |
| 337 | result: DesktopPreviewRecordingArtifactSchema, | 337 | result: DesktopPreviewRecordingArtifactSchema, |
| @@ -9,14 +9,14 @@ import * as Schema from "effect/Schema"; · 8 unmodified lines | |||
| 9 | import * as DesktopLifecycle from "../../app/DesktopLifecycle.ts"; | 9 | import * as DesktopLifecycle from "../../app/DesktopLifecycle.ts"; |
| 10 | import * as DesktopServerExposure from "../../backend/DesktopServerExposure.ts"; | 10 | import * as DesktopServerExposure from "../../backend/DesktopServerExposure.ts"; |
| 11 | import * as IpcChannels from "../channels.ts"; | 11 | import * as IpcChannels from "../channels.ts"; |
| 12 | import { makeIpcMethod } from "../DesktopIpc.ts"; | 12 | import * as DesktopIpc from "../DesktopIpc.ts"; |
| 13 | 13 | ||
| 14 | const SetTailscaleServeEnabledInput = Schema.Struct({ | 14 | const SetTailscaleServeEnabledInput = Schema.Struct({ |
| 15 | enabled: Schema.Boolean, | 15 | enabled: Schema.Boolean, |
| 16 | port: Schema.optionalKey(Schema.Number), | 16 | port: Schema.optionalKey(Schema.Number), |
| 17 | }); | 17 | }); |
| 18 | 18 | ||
| 19 | export const getServerExposureState = makeIpcMethod({ | 19 | export const getServerExposureState = DesktopIpc.makeIpcMethod({ |
| 20 | channel: IpcChannels.GET_SERVER_EXPOSURE_STATE_CHANNEL, | 20 | channel: IpcChannels.GET_SERVER_EXPOSURE_STATE_CHANNEL, |
| 21 | payload: Schema.Void, | 21 | payload: Schema.Void, |
| 22 | result: DesktopServerExposureStateSchema, | 22 | result: DesktopServerExposureStateSchema, |
| @@ -26,7 +26,7 @@ export const getServerExposureState = makeIpcMethod({ · 3 unmodified lines | |||
| 26 | }), | 26 | }), |
| 27 | }); | 27 | }); |
| 28 | 28 | ||
| 29 | export const setServerExposureMode = makeIpcMethod({ | 29 | export const setServerExposureMode = DesktopIpc.makeIpcMethod({ |
| 30 | channel: IpcChannels.SET_SERVER_EXPOSURE_MODE_CHANNEL, | 30 | channel: IpcChannels.SET_SERVER_EXPOSURE_MODE_CHANNEL, |
| 31 | payload: DesktopServerExposureModeSchema, | 31 | payload: DesktopServerExposureModeSchema, |
| 32 | result: DesktopServerExposureStateSchema, | 32 | result: DesktopServerExposureStateSchema, |
| @@ -41,7 +41,7 @@ export const setServerExposureMode = makeIpcMethod({ · 8 unmodified lines | |||
| 41 | }), | 41 | }), |
| 42 | }); | 42 | }); |
| 43 | 43 | ||
| 44 | export const setTailscaleServeEnabled = makeIpcMethod({ | 44 | export const setTailscaleServeEnabled = DesktopIpc.makeIpcMethod({ |
| 45 | channel: IpcChannels.SET_TAILSCALE_SERVE_ENABLED_CHANNEL, | 45 | channel: IpcChannels.SET_TAILSCALE_SERVE_ENABLED_CHANNEL, |
| 46 | payload: SetTailscaleServeEnabledInput, | 46 | payload: SetTailscaleServeEnabledInput, |
| 47 | result: DesktopServerExposureStateSchema, | 47 | result: DesktopServerExposureStateSchema, |
| @@ -58,7 +58,7 @@ export const setTailscaleServeEnabled = makeIpcMethod({ · 10 unmodified lines | |||
| 58 | }), | 58 | }), |
| 59 | }); | 59 | }); |
| 60 | 60 | ||
| 61 | export const getAdvertisedEndpoints = makeIpcMethod({ | 61 | export const getAdvertisedEndpoints = DesktopIpc.makeIpcMethod({ |
| 62 | channel: IpcChannels.GET_ADVERTISED_ENDPOINTS_CHANNEL, | 62 | channel: IpcChannels.GET_ADVERTISED_ENDPOINTS_CHANNEL, |
| 63 | payload: Schema.Void, | 63 | payload: Schema.Void, |
| 64 | result: Schema.Array(AdvertisedEndpoint), | 64 | result: Schema.Array(AdvertisedEndpoint), |
| @@ -33,7 +33,7 @@ import * as Effect from "effect/Effect"; · 32 unmodified lines | |||
| 33 | import * as Schema from "effect/Schema"; | 33 | import * as Schema from "effect/Schema"; |
| 34 | 34 | ||
| 35 | import * as IpcChannels from "../channels.ts"; | 35 | import * as IpcChannels from "../channels.ts"; |
| 36 | import { makeIpcMethod } from "../DesktopIpc.ts"; | 36 | import * as DesktopIpc from "../DesktopIpc.ts"; |
| 37 | import * as DesktopSshEnvironment from "../../ssh/DesktopSshEnvironment.ts"; | 37 | import * as DesktopSshEnvironment from "../../ssh/DesktopSshEnvironment.ts"; |
| 38 | import * as DesktopSshPasswordPrompts from "../../ssh/DesktopSshPasswordPrompts.ts"; | 38 | import * as DesktopSshPasswordPrompts from "../../ssh/DesktopSshPasswordPrompts.ts"; |
| 39 | 39 | ||
| @@ -107,7 +107,7 @@ const withLoopbackSshApi = · 67 unmodified lines | |||
| 107 | ), | 107 | ), |
| 108 | ); | 108 | ); |
| 109 | 109 | ||
| 110 | export const discoverSshHosts = makeIpcMethod({ | 110 | export const discoverSshHosts = DesktopIpc.makeIpcMethod({ |
| 111 | channel: IpcChannels.DISCOVER_SSH_HOSTS_CHANNEL, | 111 | channel: IpcChannels.DISCOVER_SSH_HOSTS_CHANNEL, |
| 112 | payload: Schema.Void, | 112 | payload: Schema.Void, |
| 113 | result: Schema.Array(DesktopDiscoveredSshHostSchema), | 113 | result: Schema.Array(DesktopDiscoveredSshHostSchema), |
| @@ -117,7 +117,7 @@ export const discoverSshHosts = makeIpcMethod({ · 3 unmodified lines | |||
| 117 | }), | 117 | }), |
| 118 | }); | 118 | }); |
| 119 | 119 | ||
| 120 | export const ensureSshEnvironment = makeIpcMethod({ | 120 | export const ensureSshEnvironment = DesktopIpc.makeIpcMethod({ |
| 121 | channel: IpcChannels.ENSURE_SSH_ENVIRONMENT_CHANNEL, | 121 | channel: IpcChannels.ENSURE_SSH_ENVIRONMENT_CHANNEL, |
| 122 | payload: DesktopSshEnvironmentEnsureInputSchema, | 122 | payload: DesktopSshEnvironmentEnsureInputSchema, |
| 123 | result: DesktopSshEnvironmentEnsureResultSchema, | 123 | result: DesktopSshEnvironmentEnsureResultSchema, |
| @@ -139,7 +139,7 @@ export const ensureSshEnvironment = makeIpcMethod({ · 15 unmodified lines | |||
| 139 | }), | 139 | }), |
| 140 | }); | 140 | }); |
| 141 | 141 | ||
| 142 | export const disconnectSshEnvironment = makeIpcMethod({ | 142 | export const disconnectSshEnvironment = DesktopIpc.makeIpcMethod({ |
| 143 | channel: IpcChannels.DISCONNECT_SSH_ENVIRONMENT_CHANNEL, | 143 | channel: IpcChannels.DISCONNECT_SSH_ENVIRONMENT_CHANNEL, |
| 144 | payload: DesktopSshEnvironmentTargetSchema, | 144 | payload: DesktopSshEnvironmentTargetSchema, |
| 145 | result: Schema.Void, | 145 | result: Schema.Void, |
| @@ -149,7 +149,7 @@ export const disconnectSshEnvironment = makeIpcMethod({ · 3 unmodified lines | |||
| 149 | }), | 149 | }), |
| 150 | }); | 150 | }); |
| 151 | 151 | ||
| 152 | export const fetchSshEnvironmentDescriptor = makeIpcMethod({ | 152 | export const fetchSshEnvironmentDescriptor = DesktopIpc.makeIpcMethod({ |
| 153 | channel: IpcChannels.FETCH_SSH_ENVIRONMENT_DESCRIPTOR_CHANNEL, | 153 | channel: IpcChannels.FETCH_SSH_ENVIRONMENT_DESCRIPTOR_CHANNEL, |
| 154 | payload: DesktopSshHttpBaseUrlInputSchema, | 154 | payload: DesktopSshHttpBaseUrlInputSchema, |
| 155 | result: ExecutionEnvironmentDescriptor, | 155 | result: ExecutionEnvironmentDescriptor, |
| @@ -160,7 +160,7 @@ export const fetchSshEnvironmentDescriptor = makeIpcMethod({ · 4 unmodified lines | |||
| 160 | }), | 160 | }), |
| 161 | }); | 161 | }); |
| 162 | 162 | ||
| 163 | export const bootstrapSshBearerSession = makeIpcMethod({ | 163 | export const bootstrapSshBearerSession = DesktopIpc.makeIpcMethod({ |
| 164 | channel: IpcChannels.BOOTSTRAP_SSH_BEARER_SESSION_CHANNEL, | 164 | channel: IpcChannels.BOOTSTRAP_SSH_BEARER_SESSION_CHANNEL, |
| 165 | payload: DesktopSshBearerBootstrapInputSchema, | 165 | payload: DesktopSshBearerBootstrapInputSchema, |
| 166 | result: AuthAccessTokenResult, | 166 | result: AuthAccessTokenResult, |
| @@ -177,7 +177,7 @@ export const bootstrapSshBearerSession = makeIpcMethod({ · 10 unmodified lines | |||
| 177 | }), | 177 | }), |
| 178 | }); | 178 | }); |
| 179 | 179 | ||
| 180 | export const fetchSshSessionState = makeIpcMethod({ | 180 | export const fetchSshSessionState = DesktopIpc.makeIpcMethod({ |
| 181 | channel: IpcChannels.FETCH_SSH_SESSION_STATE_CHANNEL, | 181 | channel: IpcChannels.FETCH_SSH_SESSION_STATE_CHANNEL, |
| 182 | payload: DesktopSshBearerRequestInputSchema, | 182 | payload: DesktopSshBearerRequestInputSchema, |
| 183 | result: AuthSessionState, | 183 | result: AuthSessionState, |
| @@ -194,7 +194,7 @@ export const fetchSshSessionState = makeIpcMethod({ · 10 unmodified lines | |||
| 194 | }), | 194 | }), |
| 195 | }); | 195 | }); |
| 196 | 196 | ||
| 197 | export const issueSshWebSocketTicket = makeIpcMethod({ | 197 | export const issueSshWebSocketTicket = DesktopIpc.makeIpcMethod({ |
| 198 | channel: IpcChannels.ISSUE_SSH_WEBSOCKET_TOKEN_CHANNEL, | 198 | channel: IpcChannels.ISSUE_SSH_WEBSOCKET_TOKEN_CHANNEL, |
| 199 | payload: DesktopSshBearerRequestInputSchema, | 199 | payload: DesktopSshBearerRequestInputSchema, |
| 200 | result: AuthWebSocketTicketResult, | 200 | result: AuthWebSocketTicketResult, |
| @@ -211,7 +211,7 @@ export const issueSshWebSocketTicket = makeIpcMethod({ · 10 unmodified lines | |||
| 211 | }), | 211 | }), |
| 212 | }); | 212 | }); |
| 213 | 213 | ||
| 214 | export const resolveSshPasswordPrompt = makeIpcMethod({ | 214 | export const resolveSshPasswordPrompt = DesktopIpc.makeIpcMethod({ |
| 215 | channel: IpcChannels.RESOLVE_SSH_PASSWORD_PROMPT_CHANNEL, | 215 | channel: IpcChannels.RESOLVE_SSH_PASSWORD_PROMPT_CHANNEL, |
| 216 | payload: DesktopSshPasswordPromptResolutionInputSchema, | 216 | payload: DesktopSshPasswordPromptResolutionInputSchema, |
| 217 | result: Schema.Void, | 217 | result: Schema.Void, |
| @@ -9,9 +9,9 @@ import * as Schema from "effect/Schema"; · 8 unmodified lines | |||
| 9 | 9 | ||
| 10 | import * as DesktopUpdates from "../../updates/DesktopUpdates.ts"; | 10 | import * as DesktopUpdates from "../../updates/DesktopUpdates.ts"; |
| 11 | import * as IpcChannels from "../channels.ts"; | 11 | import * as IpcChannels from "../channels.ts"; |
| 12 | import { makeIpcMethod } from "../DesktopIpc.ts"; | 12 | import * as DesktopIpc from "../DesktopIpc.ts"; |
| 13 | 13 | ||
| 14 | export const getUpdateState = makeIpcMethod({ | 14 | export const getUpdateState = DesktopIpc.makeIpcMethod({ |
| 15 | channel: IpcChannels.UPDATE_GET_STATE_CHANNEL, | 15 | channel: IpcChannels.UPDATE_GET_STATE_CHANNEL, |
| 16 | payload: Schema.Void, | 16 | payload: Schema.Void, |
| 17 | result: DesktopUpdateStateSchema, | 17 | result: DesktopUpdateStateSchema, |
| @@ -21,7 +21,7 @@ export const getUpdateState = makeIpcMethod({ · 3 unmodified lines | |||
| 21 | }), | 21 | }), |
| 22 | }); | 22 | }); |
| 23 | 23 | ||
| 24 | export const setUpdateChannel = makeIpcMethod({ | 24 | export const setUpdateChannel = DesktopIpc.makeIpcMethod({ |
| 25 | channel: IpcChannels.UPDATE_SET_CHANNEL_CHANNEL, | 25 | channel: IpcChannels.UPDATE_SET_CHANNEL_CHANNEL, |
| 26 | payload: DesktopUpdateChannelSchema, | 26 | payload: DesktopUpdateChannelSchema, |
| 27 | result: DesktopUpdateStateSchema, | 27 | result: DesktopUpdateStateSchema, |
| @@ -31,7 +31,7 @@ export const setUpdateChannel = makeIpcMethod({ · 3 unmodified lines | |||
| 31 | }), | 31 | }), |
| 32 | }); | 32 | }); |
| 33 | 33 | ||
| 34 | export const downloadUpdate = makeIpcMethod({ | 34 | export const downloadUpdate = DesktopIpc.makeIpcMethod({ |
| 35 | channel: IpcChannels.UPDATE_DOWNLOAD_CHANNEL, | 35 | channel: IpcChannels.UPDATE_DOWNLOAD_CHANNEL, |
| 36 | payload: Schema.Void, | 36 | payload: Schema.Void, |
| 37 | result: DesktopUpdateActionResultSchema, | 37 | result: DesktopUpdateActionResultSchema, |
| @@ -41,7 +41,7 @@ export const downloadUpdate = makeIpcMethod({ · 3 unmodified lines | |||
| 41 | }), | 41 | }), |
| 42 | }); | 42 | }); |
| 43 | 43 | ||
| 44 | export const installUpdate = makeIpcMethod({ | 44 | export const installUpdate = DesktopIpc.makeIpcMethod({ |
| 45 | channel: IpcChannels.UPDATE_INSTALL_CHANNEL, | 45 | channel: IpcChannels.UPDATE_INSTALL_CHANNEL, |
| 46 | payload: Schema.Void, | 46 | payload: Schema.Void, |
| 47 | result: DesktopUpdateActionResultSchema, | 47 | result: DesktopUpdateActionResultSchema, |
| @@ -51,7 +51,7 @@ export const installUpdate = makeIpcMethod({ · 3 unmodified lines | |||
| 51 | }), | 51 | }), |
| 52 | }); | 52 | }); |
| 53 | 53 | ||
| 54 | export const checkForUpdate = makeIpcMethod({ | 54 | export const checkForUpdate = DesktopIpc.makeIpcMethod({ |
| 55 | channel: IpcChannels.UPDATE_CHECK_CHANNEL, | 55 | channel: IpcChannels.UPDATE_CHECK_CHANNEL, |
| 56 | payload: Schema.Void, | 56 | payload: Schema.Void, |
| 57 | result: DesktopUpdateCheckResultSchema, | 57 | result: DesktopUpdateCheckResultSchema, |
| @@ -23,7 +23,7 @@ import * as ElectronShell from "../../electron/ElectronShell.ts"; · 22 unmodified lines | |||
| 23 | import * as ElectronTheme from "../../electron/ElectronTheme.ts"; | 23 | import * as ElectronTheme from "../../electron/ElectronTheme.ts"; |
| 24 | import * as ElectronWindow from "../../electron/ElectronWindow.ts"; | 24 | import * as ElectronWindow from "../../electron/ElectronWindow.ts"; |
| 25 | import * as IpcChannels from "../channels.ts"; | 25 | import * as IpcChannels from "../channels.ts"; |
| 26 | import { makeIpcMethod, makeSyncIpcMethod } from "../DesktopIpc.ts"; | 26 | import * as DesktopIpc from "../DesktopIpc.ts"; |
| 27 | import { | 27 | import { |
| 28 | extractDistroFromUncPath, | 28 | extractDistroFromUncPath, |
| 29 | resolveWslPickFolderDefaultPath, | 29 | resolveWslPickFolderDefaultPath, |
| @@ -46,7 +46,7 @@ function toWebSocketBaseUrl(httpBaseUrl: URL): string { · 16 unmodified lines | |||
| 46 | return url.href; | 46 | return url.href; |
| 47 | } | 47 | } |
| 48 | 48 | ||
| 49 | export const getAppBranding = makeSyncIpcMethod({ | 49 | export const getAppBranding = DesktopIpc.makeSyncIpcMethod({ |
| 50 | channel: IpcChannels.GET_APP_BRANDING_CHANNEL, | 50 | channel: IpcChannels.GET_APP_BRANDING_CHANNEL, |
| 51 | result: Schema.NullOr(DesktopAppBrandingSchema), | 51 | result: Schema.NullOr(DesktopAppBrandingSchema), |
| 52 | handler: Effect.fn("desktop.ipc.window.getAppBranding")(function* () { | 52 | handler: Effect.fn("desktop.ipc.window.getAppBranding")(function* () { |
| @@ -55,7 +55,7 @@ export const getAppBranding = makeSyncIpcMethod({ · 2 unmodified lines | |||
| 55 | }), | 55 | }), |
| 56 | }); | 56 | }); |
| 57 | 57 | ||
| 58 | export const getLocalEnvironmentBootstraps = makeSyncIpcMethod({ | 58 | export const getLocalEnvironmentBootstraps = DesktopIpc.makeSyncIpcMethod({ |
| 59 | channel: IpcChannels.GET_LOCAL_ENVIRONMENT_BOOTSTRAPS_CHANNEL, | 59 | channel: IpcChannels.GET_LOCAL_ENVIRONMENT_BOOTSTRAPS_CHANNEL, |
| 60 | result: Schema.Array(DesktopEnvironmentBootstrapSchema), | 60 | result: Schema.Array(DesktopEnvironmentBootstrapSchema), |
| 61 | handler: Effect.fn("desktop.ipc.window.getLocalEnvironmentBootstraps")(function* () { | 61 | handler: Effect.fn("desktop.ipc.window.getLocalEnvironmentBootstraps")(function* () { |
| @@ -102,7 +102,7 @@ function extractWslDistroFromEnvironmentId(envId: string): string | null { · 40 unmodified lines | |||
| 102 | return suffix === "default" || suffix.length === 0 ? null : suffix; | 102 | return suffix === "default" || suffix.length === 0 ? null : suffix; |
| 103 | } | 103 | } |
| 104 | 104 | ||
| 105 | export const getLocalEnvironmentBearerToken = makeIpcMethod({ | 105 | export const getLocalEnvironmentBearerToken = DesktopIpc.makeIpcMethod({ |
| 106 | channel: IpcChannels.GET_LOCAL_ENVIRONMENT_BEARER_TOKEN_CHANNEL, | 106 | channel: IpcChannels.GET_LOCAL_ENVIRONMENT_BEARER_TOKEN_CHANNEL, |
| 107 | payload: Schema.Void, | 107 | payload: Schema.Void, |
| 108 | result: Schema.String, | 108 | result: Schema.String, |
| @@ -112,7 +112,7 @@ export const getLocalEnvironmentBearerToken = makeIpcMethod({ · 3 unmodified lines | |||
| 112 | }), | 112 | }), |
| 113 | }); | 113 | }); |
| 114 | 114 | ||
| 115 | export const pickFolder = makeIpcMethod({ | 115 | export const pickFolder = DesktopIpc.makeIpcMethod({ |
| 116 | channel: IpcChannels.PICK_FOLDER_CHANNEL, | 116 | channel: IpcChannels.PICK_FOLDER_CHANNEL, |
| 117 | payload: Schema.UndefinedOr(PickFolderOptionsSchema), | 117 | payload: Schema.UndefinedOr(PickFolderOptionsSchema), |
| 118 | result: Schema.NullOr(Schema.String), | 118 | result: Schema.NullOr(Schema.String), |
| @@ -179,7 +179,7 @@ export const pickFolder = makeIpcMethod({ · 60 unmodified lines | |||
| 179 | }), | 179 | }), |
| 180 | }); | 180 | }); |
| 181 | 181 | ||
| 182 | export const confirm = makeIpcMethod({ | 182 | export const confirm = DesktopIpc.makeIpcMethod({ |
| 183 | channel: IpcChannels.CONFIRM_CHANNEL, | 183 | channel: IpcChannels.CONFIRM_CHANNEL, |
| 184 | payload: Schema.String, | 184 | payload: Schema.String, |
| 185 | result: Schema.Boolean, | 185 | result: Schema.Boolean, |
| @@ -192,7 +192,7 @@ export const confirm = makeIpcMethod({ · 6 unmodified lines | |||
| 192 | }), | 192 | }), |
| 193 | }); | 193 | }); |
| 194 | 194 | ||
| 195 | export const setTheme = makeIpcMethod({ | 195 | export const setTheme = DesktopIpc.makeIpcMethod({ |
| 196 | channel: IpcChannels.SET_THEME_CHANNEL, | 196 | channel: IpcChannels.SET_THEME_CHANNEL, |
| 197 | payload: DesktopThemeSchema, | 197 | payload: DesktopThemeSchema, |
| 198 | result: Schema.Void, | 198 | result: Schema.Void, |
| @@ -202,7 +202,7 @@ export const setTheme = makeIpcMethod({ · 3 unmodified lines | |||
| 202 | }), | 202 | }), |
| 203 | }); | 203 | }); |
| 204 | 204 | ||
| 205 | export const showContextMenu = makeIpcMethod({ | 205 | export const showContextMenu = DesktopIpc.makeIpcMethod({ |
| 206 | channel: IpcChannels.CONTEXT_MENU_CHANNEL, | 206 | channel: IpcChannels.CONTEXT_MENU_CHANNEL, |
| 207 | payload: ContextMenuInput, | 207 | payload: ContextMenuInput, |
| 208 | result: Schema.NullOr(Schema.String), | 208 | result: Schema.NullOr(Schema.String), |
| @@ -223,7 +223,7 @@ export const showContextMenu = makeIpcMethod({ · 14 unmodified lines | |||
| 223 | }), | 223 | }), |
| 224 | }); | 224 | }); |
| 225 | 225 | ||
| 226 | export const openExternal = makeIpcMethod({ | 226 | export const openExternal = DesktopIpc.makeIpcMethod({ |
| 227 | channel: IpcChannels.OPEN_EXTERNAL_CHANNEL, | 227 | channel: IpcChannels.OPEN_EXTERNAL_CHANNEL, |
| 228 | payload: Schema.String, | 228 | payload: Schema.String, |
| 229 | result: Schema.Boolean, | 229 | result: Schema.Boolean, |
| @@ -49,7 +49,7 @@ const unusedLifecycleRuntimeLayer = Layer.mergeAll( · 48 unmodified lines | |||
| 49 | DesktopState.layer, | 49 | DesktopState.layer, |
| 50 | Layer.succeed( | 50 | Layer.succeed( |
| 51 | DesktopEnvironment.DesktopEnvironment, | 51 | DesktopEnvironment.DesktopEnvironment, |
| 52 | DesktopEnvironment.DesktopEnvironment.of({} as DesktopEnvironment.DesktopEnvironmentShape), | 52 | DesktopEnvironment.DesktopEnvironment.of({} as DesktopEnvironment.DesktopEnvironment["Service"]), |
| 53 | ), | 53 | ), |
| 54 | Layer.succeed( | 54 | Layer.succeed( |
| 55 | DesktopWindow.DesktopWindow, | 55 | DesktopWindow.DesktopWindow, |
| @@ -14,7 +14,6 @@ import { resolveRemoteT3CliPackageSpec } from "@t3tools/ssh/command"; · 13 unmodified lines | |||
| 14 | import type { RemoteT3RunnerOptions } from "@t3tools/ssh/tunnel"; | 14 | import type { RemoteT3RunnerOptions } from "@t3tools/ssh/tunnel"; |
| 15 | import serverPackageJson from "../../server/package.json" with { type: "json" }; | 15 | import serverPackageJson from "../../server/package.json" with { type: "json" }; |
| 16 | 16 | ||
| 17 | import type { DesktopSettings as DesktopSettingsValue } from "./settings/DesktopAppSettings.ts"; | ||
| 18 | import * as DesktopIpc from "./ipc/DesktopIpc.ts"; | 17 | import * as DesktopIpc from "./ipc/DesktopIpc.ts"; |
| 19 | import * as ElectronApp from "./electron/ElectronApp.ts"; | 18 | import * as ElectronApp from "./electron/ElectronApp.ts"; |
| 20 | import * as ElectronDialog from "./electron/ElectronDialog.ts"; | 19 | import * as ElectronDialog from "./electron/ElectronDialog.ts"; |
| @@ -70,8 +69,8 @@ const desktopEnvironmentLayer = Layer.unwrap( · 49 unmodified lines | |||
| 70 | ); | 69 | ); |
| 71 | 70 | ||
| 72 | const resolveDesktopSshCliRunner = ( | 71 | const resolveDesktopSshCliRunner = ( |
| 73 | environment: DesktopEnvironment.DesktopEnvironmentShape, | 72 | environment: DesktopEnvironment.DesktopEnvironment["Service"], |
| 74 | settings: DesktopSettingsValue, | 73 | settings: DesktopAppSettings.DesktopSettings, |
| 75 | ): RemoteT3RunnerOptions => { | 74 | ): RemoteT3RunnerOptions => { |
| 76 | const devRemoteEntryPath = Option.getOrUndefined(environment.devRemoteT3ServerEntryPath); | 75 | const devRemoteEntryPath = Option.getOrUndefined(environment.devRemoteT3ServerEntryPath); |
| 77 | if (environment.isDevelopment && devRemoteEntryPath !== undefined) { | 76 | if (environment.isDevelopment && devRemoteEntryPath !== undefined) { |
| @@ -112,7 +111,7 @@ const electronLayer = Layer.mergeAll( · 34 unmodified lines | |||
| 112 | ElectronTheme.layer, | 111 | ElectronTheme.layer, |
| 113 | ElectronUpdater.layer, | 112 | ElectronUpdater.layer, |
| 114 | ElectronWindow.layer, | 113 | ElectronWindow.layer, |
| 115 | Layer.succeed(DesktopIpc.DesktopIpc, DesktopIpc.make(Electron.ipcMain)), | 114 | DesktopIpc.layer(Electron.ipcMain), |
| 116 | ); | 115 | ); |
| 117 | 116 | ||
| 118 | const desktopFoundationLayer = Layer.mergeAll( | 117 | const desktopFoundationLayer = Layer.mergeAll( |
| @@ -59,7 +59,7 @@ const environmentLayer = Layer.succeed( · 58 unmodified lines | |||
| 59 | DesktopEnvironment.DesktopEnvironment, | 59 | DesktopEnvironment.DesktopEnvironment, |
| 60 | DesktopEnvironment.DesktopEnvironment.of({ | 60 | DesktopEnvironment.DesktopEnvironment.of({ |
| 61 | browserArtifactsDir: "/tmp/t3/dev/browser-artifacts", | 61 | browserArtifactsDir: "/tmp/t3/dev/browser-artifacts", |
| 62 | } as DesktopEnvironment.DesktopEnvironmentShape), | 62 | } as DesktopEnvironment.DesktopEnvironment["Service"]), |
| 63 | ); | 63 | ); |
| 64 | 64 | ||
| 65 | const fileSystemLayer = FileSystem.layerNoop({ | 65 | const fileSystemLayer = FileSystem.layerNoop({ |
| @@ -82,7 +82,7 @@ const layer = PreviewManager.layer.pipe( · 16 unmodified lines | |||
| 82 | 82 | ||
| 83 | const withManager = <A>( | 83 | const withManager = <A>( |
| 84 | use: ( | 84 | use: ( |
| 85 | manager: PreviewManager.PreviewManagerShape, | 85 | manager: PreviewManager.PreviewManager["Service"], |
| 86 | ) => Effect.Effect<A, PreviewManager.PreviewManagerError, Scope.Scope>, | 86 | ) => Effect.Effect<A, PreviewManager.PreviewManagerError, Scope.Scope>, |
| 87 | ) => | 87 | ) => |
| 88 | Effect.gen(function* () { | 88 | Effect.gen(function* () { |
| @@ -64,7 +64,7 @@ function runShellEnvironment(input: { · 63 unmodified lines | |||
| 64 | DesktopEnvironment.DesktopEnvironment, | 64 | DesktopEnvironment.DesktopEnvironment, |
| 65 | DesktopEnvironment.DesktopEnvironment.of({ | 65 | DesktopEnvironment.DesktopEnvironment.of({ |
| 66 | platform: input.platform, | 66 | platform: input.platform, |
| 67 | } as DesktopEnvironment.DesktopEnvironmentShape), | 67 | } as DesktopEnvironment.DesktopEnvironment["Service"]), |
| 68 | ); | 68 | ); |
| 69 | const spawnerLayer = Layer.succeed( | 69 | const spawnerLayer = Layer.succeed( |
| 70 | ChildProcessSpawner.ChildProcessSpawner, | 70 | ChildProcessSpawner.ChildProcessSpawner, |
| @@ -127,7 +127,7 @@ function parseAppUpdateYml(raw: string): Effect.Effect<Option.Option<AppUpdateYm · 126 unmodified lines | |||
| 127 | function createBaseUpdateState( | 127 | function createBaseUpdateState( |
| 128 | channel: DesktopUpdateChannel, | 128 | channel: DesktopUpdateChannel, |
| 129 | enabled: boolean, | 129 | enabled: boolean, |
| 130 | environment: DesktopEnvironment.DesktopEnvironmentShape, | 130 | environment: DesktopEnvironment.DesktopEnvironment["Service"], |
| 131 | ): DesktopUpdateState { | 131 | ): DesktopUpdateState { |
| 132 | return { | 132 | return { |
| 133 | ...createInitialDesktopUpdateState(environment.appVersion, environment.runtimeInfo, channel), | 133 | ...createInitialDesktopUpdateState(environment.appVersion, environment.runtimeInfo, channel), |
| @@ -91,7 +91,7 @@ const desktopAssetsLayer = Layer.succeed(DesktopAssets.DesktopAssets, { · 90 unmodified lines | |||
| 91 | png: Option.none<string>(), | 91 | png: Option.none<string>(), |
| 92 | }), | 92 | }), |
| 93 | resolveResourcePath: () => Effect.succeed(Option.none<string>()), | 93 | resolveResourcePath: () => Effect.succeed(Option.none<string>()), |
| 94 | } satisfies DesktopAssets.DesktopAssetsShape); | 94 | } satisfies DesktopAssets.DesktopAssets["Service"]); |
| 95 | 95 | ||
| 96 | const desktopServerExposureLayer = Layer.succeed(DesktopServerExposure.DesktopServerExposure, { | 96 | const desktopServerExposureLayer = Layer.succeed(DesktopServerExposure.DesktopServerExposure, { |
| 97 | getState: Effect.die("unexpected getState"), | 97 | getState: Effect.die("unexpected getState"), |
| @@ -106,19 +106,19 @@ const desktopServerExposureLayer = Layer.succeed(DesktopServerExposure.DesktopSe · 8 unmodified lines | |||
| 106 | setMode: () => Effect.die("unexpected setMode"), | 106 | setMode: () => Effect.die("unexpected setMode"), |
| 107 | setTailscaleServeEnabled: () => Effect.die("unexpected setTailscaleServeEnabled"), | 107 | setTailscaleServeEnabled: () => Effect.die("unexpected setTailscaleServeEnabled"), |
| 108 | getAdvertisedEndpoints: Effect.die("unexpected getAdvertisedEndpoints"), | 108 | getAdvertisedEndpoints: Effect.die("unexpected getAdvertisedEndpoints"), |
| 109 | } satisfies DesktopServerExposure.DesktopServerExposureShape); | 109 | } satisfies DesktopServerExposure.DesktopServerExposure["Service"]); |
| 110 | 110 | ||
| 111 | const electronMenuLayer = Layer.succeed(ElectronMenu.ElectronMenu, { | 111 | const electronMenuLayer = Layer.succeed(ElectronMenu.ElectronMenu, { |
| 112 | setApplicationMenu: () => Effect.void, | 112 | setApplicationMenu: () => Effect.void, |
| 113 | popupTemplate: () => Effect.void, | 113 | popupTemplate: () => Effect.void, |
| 114 | showContextMenu: () => Effect.succeed(Option.none()), | 114 | showContextMenu: () => Effect.succeed(Option.none()), |
| 115 | } satisfies ElectronMenu.ElectronMenuShape); | 115 | } satisfies ElectronMenu.ElectronMenu["Service"]); |
| 116 | 116 | ||
| 117 | const electronThemeLayer = Layer.succeed(ElectronTheme.ElectronTheme, { | 117 | const electronThemeLayer = Layer.succeed(ElectronTheme.ElectronTheme, { |
| 118 | shouldUseDarkColors: Effect.succeed(false), | 118 | shouldUseDarkColors: Effect.succeed(false), |
| 119 | setSource: () => Effect.void, | 119 | setSource: () => Effect.void, |
| 120 | onUpdated: () => Effect.void, | 120 | onUpdated: () => Effect.void, |
| 121 | } satisfies ElectronTheme.ElectronThemeShape); | 121 | } satisfies ElectronTheme.ElectronTheme["Service"]); |
| 122 | 122 | ||
| 123 | const desktopEnvironmentLayer = DesktopEnvironment.layer(environmentInput).pipe( | 123 | const desktopEnvironmentLayer = DesktopEnvironment.layer(environmentInput).pipe( |
| 124 | Layer.provide( | 124 | Layer.provide( |
| @@ -156,7 +156,7 @@ function makeTestLayer(input: { · 31 unmodified lines | |||
| 156 | sendAll: () => Effect.void, | 156 | sendAll: () => Effect.void, |
| 157 | destroyAll: Effect.void, | 157 | destroyAll: Effect.void, |
| 158 | syncAllAppearance: (sync) => sync(input.window), | 158 | syncAllAppearance: (sync) => sync(input.window), |
| 159 | } satisfies ElectronWindow.ElectronWindowShape); | 159 | } satisfies ElectronWindow.ElectronWindow["Service"]); |
| 160 | 160 | ||
| 161 | return DesktopWindow.layer.pipe( | 161 | return DesktopWindow.layer.pipe( |
| 162 | Layer.provide( | 162 | Layer.provide( |
| @@ -172,7 +172,7 @@ function makeTestLayer(input: { · 9 unmodified lines | |||
| 172 | return true; | 172 | return true; |
| 173 | }), | 173 | }), |
| 174 | copyText: () => Effect.void, | 174 | copyText: () => Effect.void, |
| 175 | } satisfies ElectronShell.ElectronShellShape), | 175 | } satisfies ElectronShell.ElectronShell["Service"]), |
| 176 | electronThemeLayer, | 176 | electronThemeLayer, |
| 177 | electronWindowLayer, | 177 | electronWindowLayer, |
| 178 | Layer.mock(PreviewManager.PreviewManager)({ | 178 | Layer.mock(PreviewManager.PreviewManager)({ |
| @@ -1,5 +1,6 @@ | |||
| 1 | included: | 1 | included: |
| 2 | - ios/T3Code | 2 | - ios/T3Code |
| 3 | - modules/t3-composer-editor/ios | ||
| 3 | - modules/t3-terminal/ios | 4 | - modules/t3-terminal/ios |
| 4 | - modules/t3-review-diff/ios | 5 | - modules/t3-review-diff/ios |
| 5 | 6 | ||
| @@ -192,11 +192,31 @@ · 191 unmodified lines | |||
| 192 | } | 192 | } |
| 193 | } | 193 | } |
| 194 | 194 | ||
| 195 | /* ─── Font family ───────────────────────────────────────────────────── */ | 195 | /* ─── Typography ────────────────────────────────────────────────────── */ |
| 196 | @theme { | 196 | @theme { |
| 197 | --font-sans: "DMSans_400Regular"; | 197 | --font-sans: "DMSans_400Regular"; |
| 198 | --font-medium: "DMSans_500Medium"; | 198 | --font-medium: "DMSans_500Medium"; |
| 199 | --font-bold: "DMSans_700Bold"; | 199 | --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; | ||
| 200 | } | 220 | } |
| 201 | 221 | ||
| 202 | /* ─── Custom utilities ──────────────────────────────────────────────── */ | 222 | /* ─── Custom utilities ──────────────────────────────────────────────── */ |
| @@ -275,8 +275,8 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate { · 274 unmodified lines | |||
| 275 | fileTint: "#737373" | 275 | fileTint: "#737373" |
| 276 | ) | 276 | ) |
| 277 | private var fontFamily = "DMSans_400Regular" | 277 | private var fontFamily = "DMSans_400Regular" |
| 278 | private var fontSize: CGFloat = 15 | 278 | private var fontSize: CGFloat = 14 |
| 279 | private var lineHeight: CGFloat = 22 | 279 | private var lineHeight: CGFloat = 20 |
| 280 | private var contentInsetVertical: CGFloat = 0 | 280 | private var contentInsetVertical: CGFloat = 0 |
| 281 | private var shouldAutoFocus = false | 281 | private var shouldAutoFocus = false |
| 282 | private var didAutoFocus = false | 282 | private var didAutoFocus = false |
| @@ -460,9 +460,19 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate { · 177 unmodified lines | |||
| 460 | guard !isApplyingControlledValue else { | 460 | guard !isApplyingControlledValue else { |
| 461 | return | 461 | return |
| 462 | } | 462 | } |
| 463 | restoreBaseTypingAttributes() | ||
| 463 | emitSelection() | 464 | emitSelection() |
| 464 | } | 465 | } |
| 465 | 466 | ||
| 467 | public func textView( | ||
| 468 | _ textView: UITextView, | ||
| 469 | shouldChangeTextIn range: NSRange, | ||
| 470 | replacementText text: String | ||
| 471 | ) -> Bool { | ||
| 472 | restoreBaseTypingAttributes() | ||
| 473 | return true | ||
| 474 | } | ||
| 475 | |||
| 466 | public func textViewDidBeginEditing(_ textView: UITextView) { | 476 | public func textViewDidBeginEditing(_ textView: UITextView) { |
| 467 | onComposerFocus() | 477 | onComposerFocus() |
| 468 | } | 478 | } |
| @@ -484,6 +494,7 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate { · 15 unmodified lines | |||
| 484 | let targetSelection = requestedSelection ?? previousSelection | 494 | let targetSelection = requestedSelection ?? previousSelection |
| 485 | requestedSelection = nil | 495 | requestedSelection = nil |
| 486 | textView.selectedRange = displayRange(for: targetSelection) | 496 | textView.selectedRange = displayRange(for: targetSelection) |
| 497 | restoreBaseTypingAttributes() | ||
| 487 | isApplyingControlledValue = false | 498 | isApplyingControlledValue = false |
| 488 | updatePlaceholderVisibility() | 499 | updatePlaceholderVisibility() |
| 489 | emitContentSizeIfNeeded() | 500 | emitContentSizeIfNeeded() |
| @@ -556,7 +567,12 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate { · 66 unmodified lines | |||
| 556 | size: image.size, | 567 | size: image.size, |
| 557 | baselineOffset: baselineOffset | 568 | baselineOffset: baselineOffset |
| 558 | ) | 569 | ) |
| 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 | ||
| 560 | } | 576 | } |
| 561 | 577 | ||
| 562 | private func renderChip( | 578 | private func renderChip( |
| @@ -660,11 +676,18 @@ public final class T3ComposerEditorView: ExpoView, UITextViewDelegate { · 97 unmodified lines | |||
| 660 | let font = UIFont(name: fontFamily, size: fontSize) | 676 | let font = UIFont(name: fontFamily, size: fontSize) |
| 661 | ?? UIFont.systemFont(ofSize: fontSize) | 677 | ?? UIFont.systemFont(ofSize: fontSize) |
| 662 | textView.font = font | 678 | textView.font = font |
| 663 | textView.typingAttributes = baseAttributes() | 679 | restoreBaseTypingAttributes() |
| 664 | placeholderLabel.font = font | 680 | placeholderLabel.font = font |
| 665 | setNeedsLayout() | 681 | setNeedsLayout() |
| 666 | } | 682 | } |
| 667 | 683 | ||
| 684 | private func restoreBaseTypingAttributes() { | ||
| 685 | guard textView.markedTextRange == nil else { | ||
| 686 | return | ||
| 687 | } | ||
| 688 | textView.typingAttributes = baseAttributes() | ||
| 689 | } | ||
| 690 | |||
| 668 | private func applyTheme() { | 691 | private func applyTheme() { |
| 669 | textView.textColor = UIColor(composerHex: theme.text) ?? .label | 692 | textView.textColor = UIColor(composerHex: theme.text) ?? .label |
| 670 | placeholderLabel.textColor = UIColor(composerHex: theme.placeholder) ?? .placeholderText | 693 | placeholderLabel.textColor = UIColor(composerHex: theme.placeholder) ?? .placeholderText |
| @@ -21,7 +21,7 @@ export default function NotFoundRoute() { · 20 unmodified lines | |||
| 21 | }} | 21 | }} |
| 22 | style={[{ flex: 1 }, screenBgStyle]} | 22 | style={[{ flex: 1 }, screenBgStyle]} |
| 23 | > | 23 | > |
| 24 | <Text className="text-[28px] font-t3-bold text-foreground" selectable> | 24 | <Text className="text-3xl font-t3-bold text-foreground" selectable> |
| 25 | Route not found | 25 | Route not found |
| 26 | </Text> | 26 | </Text> |
| 27 | <Link href="/" asChild> | 27 | <Link href="/" asChild> |
| @@ -35,7 +35,7 @@ export default function NotFoundRoute() { · 7 unmodified lines | |||
| 35 | primaryBgStyle, | 35 | primaryBgStyle, |
| 36 | ]} | 36 | ]} |
| 37 | > | 37 | > |
| 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> |
| 39 | </Pressable> | 39 | </Pressable> |
| 40 | </Link> | 40 | </Link> |
| 41 | </ScrollView> | 41 | </ScrollView> |
| @@ -85,7 +85,7 @@ export default function ConnectionsRouteScreen() { · 84 unmodified lines | |||
| 85 | type="monochrome" | 85 | type="monochrome" |
| 86 | /> | 86 | /> |
| 87 | </View> | 87 | </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"> |
| 89 | No environments connected yet.{"\n"}Tap{" "} | 89 | No environments connected yet.{"\n"}Tap{" "} |
| 90 | <Text className="font-t3-bold text-foreground">+</Text> to add one. | 90 | <Text className="font-t3-bold text-foreground">+</Text> to add one. |
| 91 | </Text> | 91 | </Text> |
| @@ -171,7 +171,7 @@ export default function ConnectionsNewRouteScreen() { · 170 unmodified lines | |||
| 171 | className="items-center gap-3 rounded-[24px] bg-card px-5 py-8" | 171 | className="items-center gap-3 rounded-[24px] bg-card px-5 py-8" |
| 172 | style={{ borderCurve: "continuous" }} | 172 | style={{ borderCurve: "continuous" }} |
| 173 | > | 173 | > |
| 174 | <Text className="text-center text-[14px] leading-[20px] text-foreground-muted"> | 174 | <Text className="text-center text-sm leading-[20px] text-foreground-muted"> |
| 175 | Camera permission is required to scan a QR code. | 175 | Camera permission is required to scan a QR code. |
| 176 | </Text> | 176 | </Text> |
| 177 | <ConnectionSheetButton | 177 | <ConnectionSheetButton |
| @@ -189,7 +189,7 @@ export default function ConnectionsNewRouteScreen() { · 11 unmodified lines | |||
| 189 | <View collapsable={false} className="gap-4 rounded-[24px] bg-card p-4"> | 189 | <View collapsable={false} className="gap-4 rounded-[24px] bg-card p-4"> |
| 190 | <View collapsable={false} className="gap-1.5"> | 190 | <View collapsable={false} className="gap-1.5"> |
| 191 | <Text | 191 | <Text |
| 192 | className="text-[11px] font-t3-bold uppercase text-foreground-muted" | 192 | className="text-2xs font-t3-bold uppercase text-foreground-muted" |
| 193 | style={{ letterSpacing: 0.8 }} | 193 | style={{ letterSpacing: 0.8 }} |
| 194 | > | 194 | > |
| 195 | Host | 195 | Host |
| @@ -202,13 +202,13 @@ export default function ConnectionsNewRouteScreen() { · 6 unmodified lines | |||
| 202 | placeholderTextColor={placeholderColor} | 202 | placeholderTextColor={placeholderColor} |
| 203 | value={hostInput} | 203 | value={hostInput} |
| 204 | onChangeText={handleHostChange} | 204 | 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" |
| 206 | /> | 206 | /> |
| 207 | </View> | 207 | </View> |
| 208 | 208 | ||
| 209 | <View collapsable={false} className="gap-1.5"> | 209 | <View collapsable={false} className="gap-1.5"> |
| 210 | <Text | 210 | <Text |
| 211 | className="text-[11px] font-t3-bold uppercase text-foreground-muted" | 211 | className="text-2xs font-t3-bold uppercase text-foreground-muted" |
| 212 | style={{ letterSpacing: 0.8 }} | 212 | style={{ letterSpacing: 0.8 }} |
| 213 | > | 213 | > |
| 214 | Pairing code | 214 | Pairing code |
| @@ -220,7 +220,7 @@ export default function ConnectionsNewRouteScreen() { · 5 unmodified lines | |||
| 220 | placeholderTextColor={placeholderColor} | 220 | placeholderTextColor={placeholderColor} |
| 221 | value={codeInput} | 221 | value={codeInput} |
| 222 | onChangeText={handleCodeChange} | 222 | 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" |
| 224 | /> | 224 | /> |
| 225 | </View> | 225 | </View> |
| 226 | 226 | ||
| @@ -129,10 +129,10 @@ export default function NewTaskRoute() { · 128 unmodified lines | |||
| 129 | {items.length === 0 ? ( | 129 | {items.length === 0 ? ( |
| 130 | <View collapsable={false} className="items-center gap-3 rounded-[24px] bg-card px-6 py-8"> | 130 | <View collapsable={false} className="items-center gap-3 rounded-[24px] bg-card px-6 py-8"> |
| 131 | {projectEmptyState.loading ? <ActivityIndicator color={accentColor} /> : null} | 131 | {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"> |
| 133 | {projectEmptyState.title} | 133 | {projectEmptyState.title} |
| 134 | </Text> | 134 | </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"> |
| 136 | {projectEmptyState.detail} | 136 | {projectEmptyState.detail} |
| 137 | </Text> | 137 | </Text> |
| 138 | {!catalogState.hasReadyEnvironment ? ( | 138 | {!catalogState.hasReadyEnvironment ? ( |
| @@ -140,7 +140,7 @@ export default function NewTaskRoute() { · 1 unmodified line | |||
| 140 | className="mt-1 rounded-full bg-primary px-4 py-2.5 active:opacity-70" | 140 | className="mt-1 rounded-full bg-primary px-4 py-2.5 active:opacity-70" |
| 141 | onPress={() => router.push("/connections/new")} | 141 | onPress={() => router.push("/connections/new")} |
| 142 | > | 142 | > |
| 143 | <Text className="text-[13px] font-t3-bold text-primary-foreground"> | 143 | <Text className="text-sm font-t3-bold text-primary-foreground"> |
| 144 | Add environment | 144 | Add environment |
| 145 | </Text> | 145 | </Text> |
| 146 | </Pressable> | 146 | </Pressable> |
| @@ -149,7 +149,7 @@ export default function NewTaskRoute() { · 2 unmodified lines | |||
| 149 | className="mt-1 rounded-full bg-primary px-4 py-2.5 active:opacity-70" | 149 | className="mt-1 rounded-full bg-primary px-4 py-2.5 active:opacity-70" |
| 150 | onPress={() => router.push("/new/add-project")} | 150 | onPress={() => router.push("/new/add-project")} |
| 151 | > | 151 | > |
| 152 | <Text className="text-[13px] font-t3-bold text-primary-foreground"> | 152 | <Text className="text-sm font-t3-bold text-primary-foreground"> |
| 153 | Add new project | 153 | Add new project |
| 154 | </Text> | 154 | </Text> |
| 155 | </Pressable> | 155 | </Pressable> |
| @@ -197,9 +197,7 @@ export default function NewTaskRoute() { · 41 unmodified lines | |||
| 197 | /> | 197 | /> |
| 198 | </View> | 198 | </View> |
| 199 | <View className="flex-1"> | 199 | <View className="flex-1"> |
| 200 | <Text className="text-[16px] leading-[21px] font-t3-bold"> | 200 | <Text className="text-base leading-[21px] font-t3-bold">{item.title}</Text> |
| 201 | {item.title} | ||
| 202 | </Text> | ||
| 203 | </View> | 201 | </View> |
| 204 | <SymbolView | 202 | <SymbolView |
| 205 | name="chevron.right" | 203 | name="chevron.right" |
| @@ -111,7 +111,7 @@ export default function SettingsEnvironmentsRouteScreen() { · 110 unmodified lines | |||
| 111 | type="monochrome" | 111 | type="monochrome" |
| 112 | /> | 112 | /> |
| 113 | </View> | 113 | </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"> |
| 115 | No environments connected yet.{"\n"}Tap{" "} | 115 | No environments connected yet.{"\n"}Tap{" "} |
| 116 | <Text className="font-t3-bold text-foreground">+</Text> to add one. | 116 | <Text className="font-t3-bold text-foreground">+</Text> to add one. |
| 117 | </Text> | 117 | </Text> |
| @@ -160,7 +160,7 @@ function ConfiguredCloudEnvironmentRows(props: { · 42 unmodified lines | |||
| 160 | return ( | 160 | return ( |
| 161 | <View collapsable={false} className="mt-5 gap-3"> | 161 | <View collapsable={false} className="mt-5 gap-3"> |
| 162 | <View className="flex-row items-center justify-between px-1"> | 162 | <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> |
| 164 | <Pressable | 164 | <Pressable |
| 165 | accessibilityRole="button" | 165 | accessibilityRole="button" |
| 166 | disabled={controller.relayDiscovery.isRefreshing} | 166 | disabled={controller.relayDiscovery.isRefreshing} |
| @@ -204,16 +204,16 @@ function ConfiguredCloudEnvironmentRows(props: { · 37 unmodified lines | |||
| 204 | ) : controller.relayDiscovery.isRefreshing ? ( | 204 | ) : controller.relayDiscovery.isRefreshing ? ( |
| 205 | <View collapsable={false} className="items-center gap-3 rounded-[24px] bg-card p-6"> | 205 | <View collapsable={false} className="items-center gap-3 rounded-[24px] bg-card p-6"> |
| 206 | <ActivityIndicator color={iconColor} /> | 206 | <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"> |
| 208 | Loading linked cloud environments. | 208 | Loading linked cloud environments. |
| 209 | </Text> | 209 | </Text> |
| 210 | </View> | 210 | </View> |
| 211 | ) : controller.relayDiscovery.error ? ( | 211 | ) : controller.relayDiscovery.error ? ( |
| 212 | <View collapsable={false} className="gap-3 rounded-[24px] bg-card p-5"> | 212 | <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"> |
| 214 | Could not load T3 Cloud environments | 214 | Could not load T3 Cloud environments |
| 215 | </Text> | 215 | </Text> |
| 216 | <Text className="text-[13px] leading-[18px] text-foreground-muted"> | 216 | <Text className="text-sm leading-[18px] text-foreground-muted"> |
| 217 | {controller.relayDiscovery.error} | 217 | {controller.relayDiscovery.error} |
| 218 | </Text> | 218 | </Text> |
| 219 | {controller.relayDiscovery.errorTraceId ? ( | 219 | {controller.relayDiscovery.errorTraceId ? ( |
| @@ -222,7 +222,7 @@ function ConfiguredCloudEnvironmentRows(props: { · 2 unmodified lines | |||
| 222 | </View> | 222 | </View> |
| 223 | ) : ( | 223 | ) : ( |
| 224 | <View collapsable={false} className="rounded-[24px] bg-card p-5"> | 224 | <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"> |
| 226 | No additional linked cloud environments. | 226 | No additional linked cloud environments. |
| 227 | </Text> | 227 | </Text> |
| 228 | </View> | 228 | </View> |
| @@ -361,7 +361,7 @@ function CloudEnvironmentRowShell(props: { · 132 unmodified lines | |||
| 361 | <View className="min-w-0 flex-row items-center gap-2"> | 361 | <View className="min-w-0 flex-row items-center gap-2"> |
| 362 | <ConnectionStatusDot state={props.connectionState} pulse={shouldPulse} size={7} /> | 362 | <ConnectionStatusDot state={props.connectionState} pulse={shouldPulse} size={7} /> |
| 363 | <Text | 363 | <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" |
| 365 | numberOfLines={1} | 365 | numberOfLines={1} |
| 366 | > | 366 | > |
| 367 | {props.label} | 367 | {props.label} |
| @@ -370,7 +370,7 @@ function CloudEnvironmentRowShell(props: { · 2 unmodified lines | |||
| 370 | {props.connectionError ? ( | 370 | {props.connectionError ? ( |
| 371 | <Text | 371 | <Text |
| 372 | aria-hidden | 372 | 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)} |
| 374 | onTextLayout={onMeasuredErrorTextLayout} | 374 | onTextLayout={onMeasuredErrorTextLayout} |
| 375 | style={{ opacity: 0, zIndex: -1 }} | 375 | style={{ opacity: 0, zIndex: -1 }} |
| 376 | > | 376 | > |
| @@ -384,7 +384,7 @@ function CloudEnvironmentRowShell(props: { · 7 unmodified lines | |||
| 384 | className="min-w-0 flex-row items-start gap-1" | 384 | className="min-w-0 flex-row items-start gap-1" |
| 385 | > | 385 | > |
| 386 | <Text | 386 | <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)} |
| 388 | numberOfLines={isErrorExpanded ? undefined : 1} | 388 | numberOfLines={isErrorExpanded ? undefined : 1} |
| 389 | > | 389 | > |
| 390 | {statusText} | 390 | {statusText} |
| @@ -394,7 +394,7 @@ function CloudEnvironmentRowShell(props: { · 3 unmodified lines | |||
| 394 | <Text | 394 | <Text |
| 395 | accessibilityHint="Copies the trace ID" | 395 | accessibilityHint="Copies the trace ID" |
| 396 | accessibilityRole="button" | 396 | accessibilityRole="button" |
| 397 | className={cn("text-[12px] leading-[16px] underline", statusClassName)} | 397 | className={cn("text-xs leading-[16px] underline", statusClassName)} |
| 398 | onLongPress={(event) => { | 398 | onLongPress={(event) => { |
| 399 | event.stopPropagation(); | 399 | event.stopPropagation(); |
| 400 | copyTextWithHaptic(errorTraceId); | 400 | copyTextWithHaptic(errorTraceId); |
| @@ -446,7 +446,7 @@ function CopyTraceIdButton(props: { readonly traceId: string }) { · 45 unmodified lines | |||
| 446 | className="self-start flex-row items-center gap-1.5 rounded-full bg-subtle px-3 py-2 active:opacity-70" | 446 | className="self-start flex-row items-center gap-1.5 rounded-full bg-subtle px-3 py-2 active:opacity-70" |
| 447 | > | 447 | > |
| 448 | <SymbolView name="doc.on.doc" size={12} tintColor={iconColor} type="monochrome" /> | 448 | <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> |
| 450 | </Pressable> | 450 | </Pressable> |
| 451 | ); | 451 | ); |
| 452 | } | 452 | } |
| @@ -346,7 +346,7 @@ function ConfiguredSettingsRouteScreen() { · 345 unmodified lines | |||
| 346 | onPress={openAccount} | 346 | onPress={openAccount} |
| 347 | /> | 347 | /> |
| 348 | </SettingsSection> | 348 | </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"> |
| 350 | T3 Code works locally without signing in. Cloud features are optional. | 350 | T3 Code works locally without signing in. Cloud features are optional. |
| 351 | </Text> | 351 | </Text> |
| 352 | </View> | 352 | </View> |
| @@ -389,7 +389,7 @@ type SymbolName = ComponentProps<typeof SymbolView>["name"]; · 36 unmodified lines | |||
| 389 | function SettingsSection(props: { readonly title: string; readonly children: ReactNode }) { | 389 | function SettingsSection(props: { readonly title: string; readonly children: ReactNode }) { |
| 390 | return ( | 390 | return ( |
| 391 | <View className="gap-2"> | 391 | <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> |
| 393 | <View | 393 | <View |
| 394 | className="overflow-hidden rounded-[28px] bg-card" | 394 | className="overflow-hidden rounded-[28px] bg-card" |
| 395 | style={{ borderCurve: "continuous" }} | 395 | style={{ borderCurve: "continuous" }} |
| @@ -413,8 +413,8 @@ function AppSettingsSection() { · 17 unmodified lines | |||
| 413 | type="monochrome" | 413 | type="monochrome" |
| 414 | weight="regular" | 414 | weight="regular" |
| 415 | /> | 415 | /> |
| 416 | <Text className="flex-1 text-[17px] text-foreground">Version</Text> | 416 | <Text className="flex-1 text-lg text-foreground">Version</Text> |
| 417 | <Text className="text-[17px] text-foreground-muted">Alpha</Text> | 417 | <Text className="text-lg text-foreground-muted">Alpha</Text> |
| 418 | </View> | 418 | </View> |
| 419 | </SettingsSection> | 419 | </SettingsSection> |
| 420 | ); | 420 | ); |
| @@ -444,13 +444,13 @@ function SettingsRow(props: { · 23 unmodified lines | |||
| 444 | style={{ opacity: props.disabled ? 0.45 : 1 }} | 444 | style={{ opacity: props.disabled ? 0.45 : 1 }} |
| 445 | > | 445 | > |
| 446 | <SymbolView name={props.icon} size={22} tintColor={icon} type="monochrome" weight="regular" /> | 446 | <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}> |
| 448 | {props.label} | 448 | {props.label} |
| 449 | </Text> | 449 | </Text> |
| 450 | <View className="min-w-0 flex-1 items-end"> | 450 | <View className="min-w-0 flex-1 items-end"> |
| 451 | {props.value ? ( | 451 | {props.value ? ( |
| 452 | <Text | 452 | <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" |
| 454 | ellipsizeMode="middle" | 454 | ellipsizeMode="middle" |
| 455 | numberOfLines={1} | 455 | numberOfLines={1} |
| 456 | > | 456 | > |
| @@ -502,7 +502,7 @@ function SettingsSwitchRow(props: { · 45 unmodified lines | |||
| 502 | style={{ opacity: props.disabled ? 0.45 : 1 }} | 502 | style={{ opacity: props.disabled ? 0.45 : 1 }} |
| 503 | > | 503 | > |
| 504 | <SymbolView name={props.icon} size={22} tintColor={icon} type="monochrome" weight="regular" /> | 504 | <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> |
| 506 | <Switch | 506 | <Switch |
| 507 | disabled={props.disabled} | 507 | disabled={props.disabled} |
| 508 | ios_backgroundColor={track} | 508 | ios_backgroundColor={track} |
| @@ -40,7 +40,7 @@ export function AppTextInput({ · 39 unmodified lines | |||
| 40 | <RNTextInput | 40 | <RNTextInput |
| 41 | ref={ref} | 41 | ref={ref} |
| 42 | className={cn( | 42 | 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", |
| 44 | className, | 44 | className, |
| 45 | )} | 45 | )} |
| 46 | placeholderTextColor={placeholderTextColor ?? placeholderColor} | 46 | placeholderTextColor={placeholderTextColor ?? placeholderColor} |
| @@ -22,15 +22,12 @@ export function BrandMark(props: { readonly compact?: boolean; readonly stageLab · 21 unmodified lines | |||
| 22 | /> | 22 | /> |
| 23 | <View className="gap-1"> | 23 | <View className="gap-1"> |
| 24 | <View className="flex-row items-center gap-2"> | 24 | <View className="flex-row items-center gap-2"> |
| 25 | <Text | 25 | <Text className="text-lg font-t3-bold text-foreground" style={{ letterSpacing: -0.4 }}> |
| 26 | className="text-[17px] font-t3-bold text-foreground" | ||
| 27 | style={{ letterSpacing: -0.4 }} | ||
| 28 | > | ||
| 29 | T3 Code | 26 | T3 Code |
| 30 | </Text> | 27 | </Text> |
| 31 | <View className="rounded-full bg-subtle px-2 py-1"> | 28 | <View className="rounded-full bg-subtle px-2 py-1"> |
| 32 | <Text | 29 | <Text |
| 33 | className="text-[10px] font-t3-bold uppercase text-foreground-muted" | 30 | className="text-3xs font-t3-bold uppercase text-foreground-muted" |
| 34 | style={{ letterSpacing: 1.1 }} | 31 | style={{ letterSpacing: 1.1 }} |
| 35 | > | 32 | > |
| 36 | {stageLabel} | 33 | {stageLabel} |
| @@ -38,7 +35,7 @@ export function BrandMark(props: { readonly compact?: boolean; readonly stageLab · 1 unmodified line | |||
| 38 | </View> | 35 | </View> |
| 39 | </View> | 36 | </View> |
| 40 | {!compact ? ( | 37 | {!compact ? ( |
| 41 | <Text className="text-[12px] font-medium text-foreground-muted"> | 38 | <Text className="text-xs font-medium text-foreground-muted"> |
| 42 | Mobile control surface for your live coding environments | 39 | Mobile control surface for your live coding environments |
| 43 | </Text> | 40 | </Text> |
| 44 | ) : null} | 41 | ) : null} |
| @@ -223,7 +223,7 @@ export function ComposerToolbarButton(props: { · 222 unmodified lines | |||
| 223 | {props.label ? ( | 223 | {props.label ? ( |
| 224 | <Text | 224 | <Text |
| 225 | className={cn( | 225 | className={cn( |
| 226 | "shrink text-center text-[13px] font-t3-bold", | 226 | "shrink text-center text-sm font-t3-bold", |
| 227 | variant === "primary" | 227 | variant === "primary" |
| 228 | ? props.disabled | 228 | ? props.disabled |
| 229 | ? "text-foreground-muted" | 229 | ? "text-foreground-muted" |
| @@ -48,7 +48,7 @@ export function ControlPill(props: { · 47 unmodified lines | |||
| 48 | : "bg-subtle", | 48 | : "bg-subtle", |
| 49 | ); | 49 | ); |
| 50 | const labelClassName = cn( | 50 | const labelClassName = cn( |
| 51 | "text-center text-[12px] font-t3-bold", | 51 | "text-center text-xs font-t3-bold", |
| 52 | variant === "primary" | 52 | variant === "primary" |
| 53 | ? props.disabled | 53 | ? props.disabled |
| 54 | ? "text-foreground-muted" | 54 | ? "text-foreground-muted" |
| @@ -19,9 +19,7 @@ export function EmptyState(props: { · 18 unmodified lines | |||
| 19 | className="mt-4 self-start rounded-full bg-primary px-4 py-2.5 active:opacity-70" | 19 | className="mt-4 self-start rounded-full bg-primary px-4 py-2.5 active:opacity-70" |
| 20 | onPress={props.onAction} | 20 | onPress={props.onAction} |
| 21 | > | 21 | > |
| 22 | <Text className="text-[13px] font-t3-bold text-primary-foreground"> | 22 | <Text className="text-sm font-t3-bold text-primary-foreground">{props.actionLabel}</Text> |
| 23 | {props.actionLabel} | ||
| 24 | </Text> | ||
| 25 | </Pressable> | 23 | </Pressable> |
| 26 | ) : null} | 24 | ) : null} |
| 27 | </View> | 25 | </View> |
| @@ -4,7 +4,7 @@ import { AppText as Text } from "./AppText"; · 3 unmodified lines | |||
| 4 | export function ErrorBanner(props: { readonly message: string }) { | 4 | export function ErrorBanner(props: { readonly message: string }) { |
| 5 | return ( | 5 | return ( |
| 6 | <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"> | 6 | <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"> |
| 8 | {props.message} | 8 | {props.message} |
| 9 | </Text> | 9 | </Text> |
| 10 | </View> | 10 | </View> |
| @@ -26,7 +26,7 @@ export function StatusPill( · 25 unmodified lines | |||
| 26 | <Text | 26 | <Text |
| 27 | className={cn( | 27 | className={cn( |
| 28 | "font-t3-bold", | 28 | "font-t3-bold", |
| 29 | size === "compact" ? "text-[11px]" : "text-xs", | 29 | size === "compact" ? "text-2xs" : "text-xs", |
| 30 | props.textClassName, | 30 | props.textClassName, |
| 31 | )} | 31 | )} |
| 32 | > | 32 | > |
| @@ -147,14 +147,14 @@ function ProjectGroupLabel(props: { · 146 unmodified lines | |||
| 147 | workspaceRoot={props.project.workspaceRoot} | 147 | workspaceRoot={props.project.workspaceRoot} |
| 148 | /> | 148 | /> |
| 149 | <Text | 149 | <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" |
| 151 | numberOfLines={1} | 151 | numberOfLines={1} |
| 152 | style={{ letterSpacing: 0.5 }} | 152 | style={{ letterSpacing: 0.5 }} |
| 153 | > | 153 | > |
| 154 | {props.project.title} | 154 | {props.project.title} |
| 155 | </Text> | 155 | </Text> |
| 156 | {props.environmentLabel ? ( | 156 | {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}> |
| 158 | {props.environmentLabel} | 158 | {props.environmentLabel} |
| 159 | </Text> | 159 | </Text> |
| 160 | ) : null} | 160 | ) : null} |
| @@ -265,13 +265,13 @@ function ArchivedThreadRow(props: { · 104 unmodified lines | |||
| 265 | <View className="min-w-0 flex-1 gap-1"> | 265 | <View className="min-w-0 flex-1 gap-1"> |
| 266 | <View className="flex-row items-center gap-2"> | 266 | <View className="flex-row items-center gap-2"> |
| 267 | <Text | 267 | <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" |
| 269 | numberOfLines={1} | 269 | numberOfLines={1} |
| 270 | > | 270 | > |
| 271 | {props.thread.title} | 271 | {props.thread.title} |
| 272 | </Text> | 272 | </Text> |
| 273 | <Text | 273 | <Text |
| 274 | className="text-[12px] text-foreground-tertiary" | 274 | className="text-xs text-foreground-tertiary" |
| 275 | style={{ fontVariant: ["tabular-nums"] }} | 275 | style={{ fontVariant: ["tabular-nums"] }} |
| 276 | > | 276 | > |
| 277 | {timestamp} | 277 | {timestamp} |
| @@ -286,7 +286,7 @@ function ArchivedThreadRow(props: { · 8 unmodified lines | |||
| 286 | type="monochrome" | 286 | type="monochrome" |
| 287 | /> | 287 | /> |
| 288 | <Text | 288 | <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" |
| 290 | numberOfLines={1} | 290 | numberOfLines={1} |
| 291 | style={{ fontFamily: "monospace" }} | 291 | style={{ fontFamily: "monospace" }} |
| 292 | > | 292 | > |
| @@ -314,10 +314,12 @@ function ArchivedThreadRow(props: { · 21 unmodified lines | |||
| 314 | function ArchiveError(props: { readonly message: string; readonly onRetry: () => void }) { | 314 | function ArchiveError(props: { readonly message: string; readonly onRetry: () => void }) { |
| 315 | return ( | 315 | return ( |
| 316 | <View className="rounded-[20px] border border-danger-border bg-danger p-4"> | 316 | <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> | 317 | <Text className="text-base font-t3-bold text-danger-foreground"> |
| 318 | <Text className="mt-1 text-[13px] leading-[18px] text-foreground-muted">{props.message}</Text> | 318 | Could not load every archive |
| 319 | </Text> | ||
| 320 | <Text className="mt-1 text-sm leading-[18px] text-foreground-muted">{props.message}</Text> | ||
| 319 | <Pressable className="mt-3 self-start active:opacity-60" onPress={props.onRetry}> | 321 | <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> |
| 321 | </Pressable> | 323 | </Pressable> |
| 322 | </View> | 324 | </View> |
| 323 | ); | 325 | ); |
| @@ -386,7 +388,7 @@ export function ArchivedThreadsScreen(props: { · 62 unmodified lines | |||
| 386 | {isInitialLoad ? ( | 388 | {isInitialLoad ? ( |
| 387 | <View className="items-center py-16"> | 389 | <View className="items-center py-16"> |
| 388 | <ActivityIndicator color={refreshTint} /> | 390 | <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> |
| 390 | </View> | 392 | </View> |
| 391 | ) : props.groups.length === 0 ? ( | 393 | ) : props.groups.length === 0 ? ( |
| 392 | <EmptyState | 394 | <EmptyState |
| @@ -2,6 +2,7 @@ import { useWaitlist } from "@clerk/expo"; · 1 unmodified line | |||
| 2 | import { ActivityIndicator, Pressable, StyleSheet, Text, TextInput, View } from "react-native"; | 2 | import { ActivityIndicator, Pressable, StyleSheet, Text, TextInput, View } from "react-native"; |
| 3 | import { useState } from "react"; | 3 | import { useState } from "react"; |
| 4 | 4 | ||
| 5 | import { MOBILE_TYPOGRAPHY } from "../../lib/typography"; | ||
| 5 | import { useThemeColor } from "../../lib/useThemeColor"; | 6 | import { useThemeColor } from "../../lib/useThemeColor"; |
| 6 | 7 | ||
| 7 | export function CloudWaitlistEnrollment(props: { readonly onSignIn: () => void }) { | 8 | export function CloudWaitlistEnrollment(props: { readonly onSignIn: () => void }) { |
| @@ -141,12 +142,11 @@ function useCloudWaitlistColors() { · 133 unmodified lines | |||
| 141 | const styles = StyleSheet.create({ | 142 | const styles = StyleSheet.create({ |
| 142 | body: { | 143 | body: { |
| 143 | fontFamily: "DMSans_400Regular", | 144 | fontFamily: "DMSans_400Regular", |
| 144 | fontSize: 15, | 145 | ...MOBILE_TYPOGRAPHY.body, |
| 145 | lineHeight: 21, | ||
| 146 | }, | 146 | }, |
| 147 | buttonText: { | 147 | buttonText: { |
| 148 | fontFamily: "DMSans_700Bold", | 148 | fontFamily: "DMSans_700Bold", |
| 149 | fontSize: 16, | 149 | fontSize: MOBILE_TYPOGRAPHY.body.fontSize, |
| 150 | }, | 150 | }, |
| 151 | content: { | 151 | content: { |
| 152 | gap: 18, | 152 | gap: 18, |
| @@ -156,8 +156,7 @@ const styles = StyleSheet.create({ · 3 unmodified lines | |||
| 156 | }, | 156 | }, |
| 157 | error: { | 157 | error: { |
| 158 | fontFamily: "DMSans_400Regular", | 158 | fontFamily: "DMSans_400Regular", |
| 159 | fontSize: 13, | 159 | ...MOBILE_TYPOGRAPHY.footnote, |
| 160 | lineHeight: 18, | ||
| 161 | }, | 160 | }, |
| 162 | field: { | 161 | field: { |
| 163 | gap: 8, | 162 | gap: 8, |
| @@ -167,15 +166,14 @@ const styles = StyleSheet.create({ · 3 unmodified lines | |||
| 167 | borderRadius: 16, | 166 | borderRadius: 16, |
| 168 | borderWidth: 1, | 167 | borderWidth: 1, |
| 169 | fontFamily: "DMSans_400Regular", | 168 | fontFamily: "DMSans_400Regular", |
| 170 | fontSize: 17, | 169 | fontSize: MOBILE_TYPOGRAPHY.headline.fontSize, |
| 171 | minHeight: 54, | 170 | minHeight: 54, |
| 172 | paddingHorizontal: 16, | 171 | paddingHorizontal: 16, |
| 173 | paddingVertical: 14, | 172 | paddingVertical: 14, |
| 174 | }, | 173 | }, |
| 175 | label: { | 174 | label: { |
| 176 | fontFamily: "DMSans_700Bold", | 175 | fontFamily: "DMSans_700Bold", |
| 177 | fontSize: 13, | 176 | ...MOBILE_TYPOGRAPHY.footnote, |
| 178 | lineHeight: 18, | ||
| 179 | }, | 177 | }, |
| 180 | primaryButton: { | 178 | primaryButton: { |
| 181 | alignItems: "center", | 179 | alignItems: "center", |
| @@ -196,13 +194,11 @@ const styles = StyleSheet.create({ · 14 unmodified lines | |||
| 196 | }, | 194 | }, |
| 197 | signInText: { | 195 | signInText: { |
| 198 | fontFamily: "DMSans_700Bold", | 196 | fontFamily: "DMSans_700Bold", |
| 199 | fontSize: 15, | 197 | ...MOBILE_TYPOGRAPHY.body, |
| 200 | lineHeight: 21, | ||
| 201 | }, | 198 | }, |
| 202 | title: { | 199 | title: { |
| 203 | fontFamily: "DMSans_700Bold", | 200 | fontFamily: "DMSans_700Bold", |
| 204 | fontSize: 20, | 201 | ...MOBILE_TYPOGRAPHY.title, |
| 205 | lineHeight: 26, | ||
| 206 | textAlign: "center", | 202 | textAlign: "center", |
| 207 | }, | 203 | }, |
| 208 | }); | 204 | }); |
| @@ -76,19 +76,16 @@ export function ConnectionEnvironmentRow(props: { · 75 unmodified lines | |||
| 76 | /> | 76 | /> |
| 77 | 77 | ||
| 78 | <View className="flex-1 gap-0.5"> | 78 | <View className="flex-1 gap-0.5"> |
| 79 | <Text | 79 | <Text className="text-base font-t3-bold leading-[21px] text-foreground" numberOfLines={1}> |
| 80 | className="text-[16px] font-t3-bold leading-[21px] text-foreground" | ||
| 81 | numberOfLines={1} | ||
| 82 | > | ||
| 83 | {props.environment.environmentLabel} | 80 | {props.environment.environmentLabel} |
| 84 | </Text> | 81 | </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}> |
| 86 | {props.environment.displayUrl} | 83 | {props.environment.displayUrl} |
| 87 | </Text> | 84 | </Text> |
| 88 | {statusLabel ? ( | 85 | {statusLabel ? ( |
| 89 | <Text | 86 | <Text |
| 90 | className={cn( | 87 | className={cn( |
| 91 | "text-[12px] leading-[16px]", | 88 | "text-xs leading-[16px]", |
| 92 | hasConnectionFailure ? "text-rose-500 dark:text-rose-400" : "text-foreground-muted", | 89 | hasConnectionFailure ? "text-rose-500 dark:text-rose-400" : "text-foreground-muted", |
| 93 | )} | 90 | )} |
| 94 | numberOfLines={props.expanded ? undefined : 1} | 91 | numberOfLines={props.expanded ? undefined : 1} |
| @@ -137,14 +134,14 @@ export function ConnectionEnvironmentRow(props: { · 42 unmodified lines | |||
| 137 | className="gap-3 px-4 pb-4" | 134 | className="gap-3 px-4 pb-4" |
| 138 | > | 135 | > |
| 139 | {props.environment.isRelayManaged ? ( | 136 | {props.environment.isRelayManaged ? ( |
| 140 | <Text className="text-[13px] leading-[18px] text-foreground-muted"> | 137 | <Text className="text-sm leading-[18px] text-foreground-muted"> |
| 141 | Managed by T3 Cloud. Tunnel details update automatically. | 138 | Managed by T3 Cloud. Tunnel details update automatically. |
| 142 | </Text> | 139 | </Text> |
| 143 | ) : ( | 140 | ) : ( |
| 144 | <> | 141 | <> |
| 145 | <View className="gap-1.5"> | 142 | <View className="gap-1.5"> |
| 146 | <Text | 143 | <Text |
| 147 | className="text-[11px] font-t3-bold uppercase text-foreground-muted" | 144 | className="text-2xs font-t3-bold uppercase text-foreground-muted" |
| 148 | style={{ letterSpacing: 0.8 }} | 145 | style={{ letterSpacing: 0.8 }} |
| 149 | > | 146 | > |
| 150 | Label | 147 | Label |
| @@ -156,13 +153,13 @@ export function ConnectionEnvironmentRow(props: { · 5 unmodified lines | |||
| 156 | placeholderTextColor={placeholderColor} | 153 | placeholderTextColor={placeholderColor} |
| 157 | value={label} | 154 | value={label} |
| 158 | onChangeText={setLabel} | 155 | 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" |
| 160 | /> | 157 | /> |
| 161 | </View> | 158 | </View> |
| 162 | 159 | ||
| 163 | <View className="gap-1.5"> | 160 | <View className="gap-1.5"> |
| 164 | <Text | 161 | <Text |
| 165 | className="text-[11px] font-t3-bold uppercase text-foreground-muted" | 162 | className="text-2xs font-t3-bold uppercase text-foreground-muted" |
| 166 | style={{ letterSpacing: 0.8 }} | 163 | style={{ letterSpacing: 0.8 }} |
| 167 | > | 164 | > |
| 168 | URL | 165 | URL |
| @@ -175,7 +172,7 @@ export function ConnectionEnvironmentRow(props: { · 6 unmodified lines | |||
| 175 | placeholderTextColor={placeholderColor} | 172 | placeholderTextColor={placeholderColor} |
| 176 | value={url} | 173 | value={url} |
| 177 | onChangeText={setUrl} | 174 | 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" |
| 179 | /> | 176 | /> |
| 180 | </View> | 177 | </View> |
| 181 | </> | 178 | </> |
| @@ -189,7 +186,7 @@ export function ConnectionEnvironmentRow(props: { · 7 unmodified lines | |||
| 189 | > | 186 | > |
| 190 | <SymbolView name="checkmark" size={13} tintColor={primaryFg} type="monochrome" /> | 187 | <SymbolView name="checkmark" size={13} tintColor={primaryFg} type="monochrome" /> |
| 191 | <Text | 188 | <Text |
| 192 | className="text-[12px] font-t3-bold uppercase text-primary-foreground" | 189 | className="text-xs font-t3-bold uppercase text-primary-foreground" |
| 193 | style={{ letterSpacing: 0.8 }} | 190 | style={{ letterSpacing: 0.8 }} |
| 194 | > | 191 | > |
| 195 | Save | 192 | Save |
| @@ -104,7 +104,7 @@ export function ConnectionSheetButton(props: { · 103 unmodified lines | |||
| 104 | type="monochrome" | 104 | type="monochrome" |
| 105 | /> | 105 | /> |
| 106 | <Text | 106 | <Text |
| 107 | className="text-[12px] font-t3-bold uppercase" | 107 | className="text-xs font-t3-bold uppercase" |
| 108 | style={{ color: colors.textColor, letterSpacing: 0.8 }} | 108 | style={{ color: colors.textColor, letterSpacing: 0.8 }} |
| 109 | > | 109 | > |
| 110 | {props.label} | 110 | {props.label} |
| @@ -73,10 +73,10 @@ export function EnvironmentConnectionNotice(props: { · 72 unmodified lines | |||
| 73 | /> | 73 | /> |
| 74 | )} | 74 | )} |
| 75 | 75 | ||
| 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"> |
| 77 | {noticeTitle(props.connection.phase, props.environmentLabel)} | 77 | {noticeTitle(props.connection.phase, props.environmentLabel)} |
| 78 | </Text> | 78 | </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"> |
| 80 | {noticeDetail(props.connection.phase, props.resourceName, props.connection.error)} | 80 | {noticeDetail(props.connection.phase, props.resourceName, props.connection.error)} |
| 81 | {props.connection.traceId ? ( | 81 | {props.connection.traceId ? ( |
| 82 | <> | 82 | <> |
| @@ -99,7 +99,7 @@ export function EnvironmentConnectionNotice(props: { · 16 unmodified lines | |||
| 99 | className="mt-1 rounded-full bg-subtle px-4 py-2.5 active:opacity-70" | 99 | className="mt-1 rounded-full bg-subtle px-4 py-2.5 active:opacity-70" |
| 100 | onPress={props.onRetry} | 100 | onPress={props.onRetry} |
| 101 | > | 101 | > |
| 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> |
| 103 | </Pressable> | 103 | </Pressable> |
| 104 | ) : null} | 104 | ) : null} |
| 105 | </View> | 105 | </View> |
| @@ -7,6 +7,7 @@ import { · 6 unmodified lines | |||
| 7 | } from "react-native-nitro-markdown"; | 7 | } from "react-native-nitro-markdown"; |
| 8 | import { Linking, ScrollView, Text as NativeText, View } from "react-native"; | 8 | import { Linking, ScrollView, Text as NativeText, View } from "react-native"; |
| 9 | 9 | ||
| 10 | import { MOBILE_TYPOGRAPHY } from "../../lib/typography"; | ||
| 10 | import { useThemeColor } from "../../lib/useThemeColor"; | 11 | import { useThemeColor } from "../../lib/useThemeColor"; |
| 11 | import { | 12 | import { |
| 12 | hasNativeSelectableMarkdownText, | 13 | hasNativeSelectableMarkdownText, |
| @@ -72,8 +73,7 @@ function useMarkdownPreviewStyles(): MarkdownPreviewStyles { · 59 unmodified lines | |||
| 72 | text: { | 73 | text: { |
| 73 | color: body, | 74 | color: body, |
| 74 | fontFamily: "DMSans_400Regular", | 75 | fontFamily: "DMSans_400Regular", |
| 75 | fontSize: 15, | 76 | ...MOBILE_TYPOGRAPHY.body, |
| 76 | lineHeight: 22, | ||
| 77 | }, | 77 | }, |
| 78 | heading: { | 78 | heading: { |
| 79 | color: strong, | 79 | color: strong, |
| @@ -123,8 +123,7 @@ function useMarkdownPreviewStyles(): MarkdownPreviewStyles { · 43 unmodified lines | |||
| 123 | skillTextColor: codeText, | 123 | skillTextColor: codeText, |
| 124 | quoteMarkerColor: blockquoteBorder, | 124 | quoteMarkerColor: blockquoteBorder, |
| 125 | dividerColor: horizontalRule, | 125 | dividerColor: horizontalRule, |
| 126 | fontSize: 15, | 126 | ...MOBILE_TYPOGRAPHY.body, |
| 127 | lineHeight: 22, | ||
| 128 | fontFamily: "DMSans_400Regular", | 127 | fontFamily: "DMSans_400Regular", |
| 129 | headingFontFamily: "DMSans_700Bold", | 128 | headingFontFamily: "DMSans_700Bold", |
| 130 | boldFontFamily: "DMSans_700Bold", | 129 | boldFontFamily: "DMSans_700Bold", |
| @@ -64,7 +64,7 @@ const FileTreeRow = memo(function FileTreeRow(props: { · 63 unmodified lines | |||
| 64 | <PierreEntryIcon path={node.path} kind={node.kind} size={17} /> | 64 | <PierreEntryIcon path={node.path} kind={node.kind} size={17} /> |
| 65 | <Text | 65 | <Text |
| 66 | className={cn( | 66 | className={cn( |
| 67 | "min-w-0 flex-1 text-[14px] leading-[19px]", | 67 | "min-w-0 flex-1 text-sm leading-[19px]", |
| 68 | selected ? "font-t3-bold text-foreground" : "font-t3-medium text-foreground-secondary", | 68 | selected ? "font-t3-bold text-foreground" : "font-t3-medium text-foreground-secondary", |
| 69 | )} | 69 | )} |
| 70 | numberOfLines={1} | 70 | numberOfLines={1} |
| @@ -72,7 +72,7 @@ const FileTreeRow = memo(function FileTreeRow(props: { · 1 unmodified line | |||
| 72 | {node.name} | 72 | {node.name} |
| 73 | </Text> | 73 | </Text> |
| 74 | {node.kind === "directory" ? ( | 74 | {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"> |
| 76 | {node.children.length} | 76 | {node.children.length} |
| 77 | </Text> | 77 | </Text> |
| 78 | ) : null} | 78 | ) : null} |
| @@ -142,10 +142,8 @@ export function FileTreeBrowser(props: { · 63 unmodified lines | |||
| 142 | <View className="flex-1 bg-sheet"> | 142 | <View className="flex-1 bg-sheet"> |
| 143 | {props.error && props.entries.length === 0 ? ( | 143 | {props.error && props.entries.length === 0 ? ( |
| 144 | <View className="px-4 py-5"> | 144 | <View className="px-4 py-5"> |
| 145 | <Text className="text-[13px] font-t3-bold text-foreground">Files unavailable</Text> | 145 | <Text className="text-sm font-t3-bold text-foreground">Files unavailable</Text> |
| 146 | <Text className="mt-1 text-[12px] leading-[18px] text-foreground-muted"> | 146 | <Text className="mt-1 text-xs leading-[18px] text-foreground-muted">{props.error}</Text> |
| 147 | {props.error} | ||
| 148 | </Text> | ||
| 149 | </View> | 147 | </View> |
| 150 | ) : ( | 148 | ) : ( |
| 151 | <FlatList | 149 | <FlatList |
| @@ -174,8 +172,8 @@ export function FileTreeBrowser(props: { · 22 unmodified lines | |||
| 174 | <ActivityIndicator size="small" /> | 172 | <ActivityIndicator size="small" /> |
| 175 | ) : ( | 173 | ) : ( |
| 176 | <> | 174 | <> |
| 177 | <Text className="text-[13px] font-t3-bold text-foreground">No files found</Text> | 175 | <Text className="text-sm font-t3-bold text-foreground">No files found</Text> |
| 178 | <Text className="mt-1 text-[12px] leading-[18px] text-foreground-muted"> | 176 | <Text className="mt-1 text-xs leading-[18px] text-foreground-muted"> |
| 179 | {props.searchQuery.trim().length > 0 | 177 | {props.searchQuery.trim().length > 0 |
| 180 | ? "Try a different search." | 178 | ? "Try a different search." |
| 181 | : "The workspace file index is empty."} | 179 | : "The workspace file index is empty."} |
| @@ -18,6 +18,7 @@ import { · 17 unmodified lines | |||
| 18 | } from "../review/reviewDiffRendering"; | 18 | } from "../review/reviewDiffRendering"; |
| 19 | import type { ReviewHighlightedToken } from "../review/shikiReviewHighlighter"; | 19 | import type { ReviewHighlightedToken } from "../review/shikiReviewHighlighter"; |
| 20 | import { cn } from "../../lib/cn"; | 20 | import { cn } from "../../lib/cn"; |
| 21 | import { MOBILE_CODE_SURFACE } from "../../lib/typography"; | ||
| 21 | import { | 22 | import { |
| 22 | buildNativeSourceRows, | 23 | buildNativeSourceRows, |
| 23 | buildNativeSourceTokens, | 24 | buildNativeSourceTokens, |
| @@ -28,8 +29,8 @@ import { · 4 unmodified lines | |||
| 28 | } from "./nativeSourceFileAdapter"; | 29 | } from "./nativeSourceFileAdapter"; |
| 29 | import { sourceHighlightAtom } from "./sourceHighlightingState"; | 30 | import { sourceHighlightAtom } from "./sourceHighlightingState"; |
| 30 | 31 | ||
| 31 | const SOURCE_LINE_HEIGHT = 24; | 32 | const SOURCE_LINE_HEIGHT = MOBILE_CODE_SURFACE.rowHeight; |
| 32 | const SOURCE_LINE_NUMBER_WIDTH = 58; | 33 | const SOURCE_LINE_NUMBER_WIDTH = MOBILE_CODE_SURFACE.gutterWidth; |
| 33 | const NATIVE_SOURCE_STYLE_JSON = JSON.stringify(NATIVE_SOURCE_STYLE); | 34 | const NATIVE_SOURCE_STYLE_JSON = JSON.stringify(NATIVE_SOURCE_STYLE); |
| 34 | 35 | ||
| 35 | interface SourceFileSurfaceProps { | 36 | interface SourceFileSurfaceProps { |
| @@ -56,10 +57,12 @@ const HighlightedSourceLine = memo(function HighlightedSourceLine(props: { · 20 unmodified lines | |||
| 56 | style={{ minHeight: SOURCE_LINE_HEIGHT }} | 57 | style={{ minHeight: SOURCE_LINE_HEIGHT }} |
| 57 | > | 58 | > |
| 58 | <NativeText | 59 | <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" |
| 60 | style={{ | 61 | style={{ |
| 61 | width: SOURCE_LINE_NUMBER_WIDTH, | 62 | width: SOURCE_LINE_NUMBER_WIDTH, |
| 62 | fontFamily: REVIEW_MONO_FONT_FAMILY, | 63 | fontFamily: REVIEW_MONO_FONT_FAMILY, |
| 64 | fontSize: MOBILE_CODE_SURFACE.lineNumberFontSize, | ||
| 65 | lineHeight: MOBILE_CODE_SURFACE.rowHeight, | ||
| 63 | }} | 66 | }} |
| 64 | > | 67 | > |
| 65 | {props.index + 1} | 68 | {props.index + 1} |
| @@ -67,8 +70,13 @@ const HighlightedSourceLine = memo(function HighlightedSourceLine(props: { · 1 unmodified line | |||
| 67 | <NativeText | 70 | <NativeText |
| 68 | selectable | 71 | selectable |
| 69 | numberOfLines={1} | 72 | numberOfLines={1} |
| 70 | className="text-[13px] font-medium leading-[24px] text-foreground" | 73 | className="font-normal text-foreground" |
| 71 | style={{ fontFamily: REVIEW_MONO_FONT_FAMILY, minWidth: 320 }} | 74 | style={{ |
| 75 | fontFamily: REVIEW_MONO_FONT_FAMILY, | ||
| 76 | fontSize: MOBILE_CODE_SURFACE.fontSize, | ||
| 77 | lineHeight: MOBILE_CODE_SURFACE.rowHeight, | ||
| 78 | minWidth: 320, | ||
| 79 | }} | ||
| 72 | > | 80 | > |
| 73 | {props.tokens && props.tokens.length > 0 | 81 | {props.tokens && props.tokens.length > 0 |
| 74 | ? (() => { | 82 | ? (() => { |
| @@ -80,7 +88,7 @@ const HighlightedSourceLine = memo(function HighlightedSourceLine(props: { · 5 unmodified lines | |||
| 80 | const fontWeight = | 88 | const fontWeight = |
| 81 | token.fontStyle !== null && (token.fontStyle & 2) === 2 | 89 | token.fontStyle !== null && (token.fontStyle & 2) === 2 |
| 82 | ? ("700" as const) | 90 | ? ("700" as const) |
| 83 | : ("500" as const); | 91 | : ("400" as const); |
| 84 | const fontStyle = | 92 | const fontStyle = |
| 85 | token.fontStyle !== null && (token.fontStyle & 1) === 1 | 93 | token.fontStyle !== null && (token.fontStyle & 1) === 1 |
| 86 | ? ("italic" as const) | 94 | ? ("italic" as const) |
| @@ -142,9 +150,7 @@ function SourceHighlightStatusView(props: { readonly status: SourceHighlightStat · 55 unmodified lines | |||
| 142 | if (props.status === "error") { | 150 | if (props.status === "error") { |
| 143 | return ( | 151 | return ( |
| 144 | <View className="border-b border-border bg-card px-4 py-2"> | 152 | <View className="border-b border-border bg-card px-4 py-2"> |
| 145 | <Text className="text-[11px] font-t3-medium uppercase text-foreground-muted"> | 153 | <Text className="text-2xs font-t3-medium uppercase text-foreground-muted">Plain text</Text> |
| 146 | Plain text | ||
| 147 | </Text> | ||
| 148 | </View> | 154 | </View> |
| 149 | ); | 155 | ); |
| 150 | } | 156 | } |
| @@ -24,6 +24,7 @@ import { EmptyState } from "../../components/EmptyState"; · 23 unmodified lines | |||
| 24 | import { LoadingScreen } from "../../components/LoadingScreen"; | 24 | import { LoadingScreen } from "../../components/LoadingScreen"; |
| 25 | import { cn } from "../../lib/cn"; | 25 | import { cn } from "../../lib/cn"; |
| 26 | import { buildThreadFilesNavigation } from "../../lib/routes"; | 26 | import { buildThreadFilesNavigation } from "../../lib/routes"; |
| 27 | import { MOBILE_TYPOGRAPHY } from "../../lib/typography"; | ||
| 27 | import { useThemeColor } from "../../lib/useThemeColor"; | 28 | import { useThemeColor } from "../../lib/useThemeColor"; |
| 28 | import { useThreadSelection } from "../../state/use-thread-selection"; | 29 | import { useThreadSelection } from "../../state/use-thread-selection"; |
| 29 | import { useSelectedThreadWorktree } from "../../state/use-selected-thread-worktree"; | 30 | import { useSelectedThreadWorktree } from "../../state/use-selected-thread-worktree"; |
| @@ -100,7 +101,7 @@ function ModeButton(props: { · 70 unmodified lines | |||
| 100 | <SymbolView name={props.icon} size={13} tintColor={iconColor} type="monochrome" /> | 101 | <SymbolView name={props.icon} size={13} tintColor={iconColor} type="monochrome" /> |
| 101 | <Text | 102 | <Text |
| 102 | className={cn( | 103 | className={cn( |
| 103 | "text-[12px] font-t3-bold", | 104 | "text-xs font-t3-bold", |
| 104 | props.active ? "text-primary-foreground" : "text-foreground-muted", | 105 | props.active ? "text-primary-foreground" : "text-foreground-muted", |
| 105 | )} | 106 | )} |
| 106 | > | 107 | > |
| @@ -187,7 +188,7 @@ function FileBreadcrumbs(props: { readonly projectName: string; readonly relativ · 80 unmodified lines | |||
| 187 | ) : null} | 188 | ) : null} |
| 188 | <Text | 189 | <Text |
| 189 | className={cn( | 190 | className={cn( |
| 190 | "max-w-[180px] px-1 text-[12px]", | 191 | "max-w-[180px] px-1 text-xs", |
| 191 | crumb.kind === "file" | 192 | crumb.kind === "file" |
| 192 | ? "font-t3-bold text-foreground" | 193 | ? "font-t3-bold text-foreground" |
| 193 | : "font-t3-medium text-foreground-muted", | 194 | : "font-t3-medium text-foreground-muted", |
| @@ -308,7 +309,7 @@ function FileContent(props: { · 114 unmodified lines | |||
| 308 | return ( | 309 | return ( |
| 309 | <View className="flex-1 items-center justify-center gap-3 bg-card px-6"> | 310 | <View className="flex-1 items-center justify-center gap-3 bg-card px-6"> |
| 310 | <ActivityIndicator /> | 311 | <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> |
| 312 | </View> | 313 | </View> |
| 313 | ); | 314 | ); |
| 314 | } | 315 | } |
| @@ -317,10 +318,10 @@ function FileContent(props: { · 2 unmodified lines | |||
| 317 | <View className="flex-1 bg-card"> | 318 | <View className="flex-1 bg-card"> |
| 318 | {props.truncated ? ( | 319 | {props.truncated ? ( |
| 319 | <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 | <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"> |
| 321 | Partial file | 322 | Partial file |
| 322 | </Text> | 323 | </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"> |
| 324 | Preview limited to the first 1 MB of a truncated file. | 325 | Preview limited to the first 1 MB of a truncated file. |
| 325 | </Text> | 326 | </Text> |
| 326 | </View> | 327 | </View> |
| @@ -389,7 +390,7 @@ function FilesHeaderTitle(props: { readonly projectName: string }) { · 62 unmodified lines | |||
| 389 | style={{ | 390 | style={{ |
| 390 | color: foregroundColor, | 391 | color: foregroundColor, |
| 391 | fontFamily: "DMSans_700Bold", | 392 | fontFamily: "DMSans_700Bold", |
| 392 | fontSize: 18, | 393 | fontSize: MOBILE_TYPOGRAPHY.headline.fontSize, |
| 393 | fontWeight: "900", | 394 | fontWeight: "900", |
| 394 | letterSpacing: -0.4, | 395 | letterSpacing: -0.4, |
| 395 | }} | 396 | }} |
| @@ -401,7 +402,7 @@ function FilesHeaderTitle(props: { readonly projectName: string }) { · 5 unmodified lines | |||
| 401 | style={{ | 402 | style={{ |
| 402 | color: secondaryForegroundColor, | 403 | color: secondaryForegroundColor, |
| 403 | fontFamily: "DMSans_500Medium", | 404 | fontFamily: "DMSans_500Medium", |
| 404 | fontSize: 12, | 405 | fontSize: MOBILE_TYPOGRAPHY.label.fontSize, |
| 405 | fontWeight: "500", | 406 | fontWeight: "500", |
| 406 | letterSpacing: 0.2, | 407 | letterSpacing: 0.2, |
| 407 | }} | 408 | }} |
| @@ -81,7 +81,7 @@ function CachedWorkspaceFileImagePreview(props: { · 80 unmodified lines | |||
| 81 | return ( | 81 | return ( |
| 82 | <View className="flex-1 items-center justify-center gap-3 bg-card px-6"> | 82 | <View className="flex-1 items-center justify-center gap-3 bg-card px-6"> |
| 83 | <ActivityIndicator /> | 83 | <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> |
| 85 | </View> | 85 | </View> |
| 86 | ); | 86 | ); |
| 87 | } | 87 | } |
| @@ -102,7 +102,7 @@ export function WorkspaceFileImagePreview(props: { · 14 unmodified lines | |||
| 102 | return ( | 102 | return ( |
| 103 | <View className="flex-1 items-center justify-center gap-3 bg-card px-6"> | 103 | <View className="flex-1 items-center justify-center gap-3 bg-card px-6"> |
| 104 | <ActivityIndicator /> | 104 | <ActivityIndicator /> |
| 105 | <Text className="text-center text-[13px] text-foreground-muted"> | 105 | <Text className="text-center text-sm text-foreground-muted"> |
| 106 | Preparing image preview... | 106 | Preparing image preview... |
| 107 | </Text> | 107 | </Text> |
| 108 | </View> | 108 | </View> |
| @@ -13,7 +13,7 @@ export function WorkspaceFileWebPreview(props: { readonly uri: string | null }) · 12 unmodified lines | |||
| 13 | return ( | 13 | return ( |
| 14 | <View className="flex-1 items-center justify-center gap-3 bg-card px-6"> | 14 | <View className="flex-1 items-center justify-center gap-3 bg-card px-6"> |
| 15 | <ActivityIndicator /> | 15 | <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> |
| 17 | </View> | 17 | </View> |
| 18 | ); | 18 | ); |
| 19 | } | 19 | } |
| @@ -23,10 +23,8 @@ export function WorkspaceFileWebPreview(props: { readonly uri: string | null }) · 3 unmodified lines | |||
| 23 | {loadProgress > 0 && loadProgress < 1 ? <LoadingStrip progress={loadProgress} /> : null} | 23 | {loadProgress > 0 && loadProgress < 1 ? <LoadingStrip progress={loadProgress} /> : null} |
| 24 | {loadError ? ( | 24 | {loadError ? ( |
| 25 | <View className="border-b border-border bg-card px-4 py-2"> | 25 | <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> | 26 | <Text className="text-xs font-t3-bold text-foreground">Preview failed</Text> |
| 27 | <Text className="mt-0.5 text-[12px] leading-[17px] text-foreground-muted"> | 27 | <Text className="mt-0.5 text-xs leading-[17px] text-foreground-muted">{loadError}</Text> |
| 28 | {loadError} | ||
| 29 | </Text> | ||
| 30 | </View> | 28 | </View> |
| 31 | ) : null} | 29 | ) : null} |
| 32 | <WebView | 30 | <WebView |
| @@ -3,10 +3,30 @@ import { describe, expect, it } from "vite-plus/test"; · 2 unmodified lines | |||
| 3 | import { | 3 | import { |
| 4 | buildNativeSourceRows, | 4 | buildNativeSourceRows, |
| 5 | buildNativeSourceTokens, | 5 | buildNativeSourceTokens, |
| 6 | NATIVE_SOURCE_ROW_HEIGHT, | ||
| 7 | NATIVE_SOURCE_STYLE, | ||
| 6 | nativeSourceRowId, | 8 | nativeSourceRowId, |
| 7 | } from "./nativeSourceFileAdapter"; | 9 | } from "./nativeSourceFileAdapter"; |
| 10 | import { | ||
| 11 | NATIVE_REVIEW_DIFF_ROW_HEIGHT, | ||
| 12 | NATIVE_REVIEW_DIFF_STYLE, | ||
| 13 | } from "../review/nativeReviewDiffAdapter"; | ||
| 8 | 14 | ||
| 9 | describe("nativeSourceFileAdapter", () => { | 15 | 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 | |||
| 10 | it("maps plain source lines onto context rows with stable line numbers", () => { | 30 | it("maps plain source lines onto context rows with stable line numbers", () => { |
| 11 | expect(buildNativeSourceRows(["const value = 1;", "\treturn value;"])).toEqual([ | 31 | expect(buildNativeSourceRows(["const value = 1;", "\treturn value;"])).toEqual([ |
| 12 | { | 32 | { |
| @@ -3,22 +3,24 @@ import type { · 2 unmodified lines | |||
| 3 | NativeReviewDiffStyle, | 3 | NativeReviewDiffStyle, |
| 4 | NativeReviewDiffToken, | 4 | NativeReviewDiffToken, |
| 5 | } from "../diffs/nativeReviewDiffSurface"; | 5 | } from "../diffs/nativeReviewDiffSurface"; |
| 6 | import { MOBILE_CODE_SURFACE, MOBILE_TYPOGRAPHY } from "../../lib/typography"; | ||
| 6 | import type { SourceHighlightTokens } from "./sourceHighlightingState"; | 7 | import type { SourceHighlightTokens } from "./sourceHighlightingState"; |
| 7 | 8 | ||
| 8 | export const NATIVE_SOURCE_ROW_HEIGHT = 24; | 9 | export const NATIVE_SOURCE_ROW_HEIGHT = MOBILE_CODE_SURFACE.rowHeight; |
| 9 | export const NATIVE_SOURCE_CONTENT_WIDTH = 32_000; | 10 | export const NATIVE_SOURCE_CONTENT_WIDTH = 32_000; |
| 10 | 11 | ||
| 11 | export const NATIVE_SOURCE_STYLE: NativeReviewDiffStyle = { | 12 | export const NATIVE_SOURCE_STYLE: NativeReviewDiffStyle = { |
| 12 | rowHeight: NATIVE_SOURCE_ROW_HEIGHT, | 13 | rowHeight: NATIVE_SOURCE_ROW_HEIGHT, |
| 13 | contentWidth: NATIVE_SOURCE_CONTENT_WIDTH, | 14 | contentWidth: NATIVE_SOURCE_CONTENT_WIDTH, |
| 14 | changeBarWidth: 0, | 15 | changeBarWidth: 0, |
| 15 | gutterWidth: 58, | 16 | gutterWidth: MOBILE_CODE_SURFACE.gutterWidth, |
| 16 | codePadding: 8, | 17 | codePadding: MOBILE_CODE_SURFACE.codePadding, |
| 17 | codeFontSize: 13, | 18 | textVerticalInset: MOBILE_CODE_SURFACE.textVerticalInset, |
| 18 | codeFontWeight: "medium", | 19 | codeFontSize: MOBILE_CODE_SURFACE.fontSize, |
| 19 | lineNumberFontSize: 11, | 20 | codeFontWeight: "regular", |
| 20 | lineNumberFontWeight: "medium", | 21 | lineNumberFontSize: MOBILE_CODE_SURFACE.lineNumberFontSize, |
| 21 | emptyStateFontSize: 12, | 22 | lineNumberFontWeight: "regular", |
| 23 | emptyStateFontSize: MOBILE_TYPOGRAPHY.label.fontSize, | ||
| 22 | emptyStateFontWeight: "medium", | 24 | emptyStateFontWeight: "medium", |
| 23 | }; | 25 | }; |
| 24 | 26 | ||
| @@ -12,6 +12,7 @@ import { Stack } from "expo-router"; · 11 unmodified lines | |||
| 12 | import { Text as RNText, View } from "react-native"; | 12 | import { Text as RNText, View } from "react-native"; |
| 13 | 13 | ||
| 14 | import { useThemeColor } from "../../lib/useThemeColor"; | 14 | import { useThemeColor } from "../../lib/useThemeColor"; |
| 15 | import { MOBILE_TYPOGRAPHY } from "../../lib/typography"; | ||
| 15 | import type { HomeProjectSortOrder } from "./homeThreadList"; | 16 | import type { HomeProjectSortOrder } from "./homeThreadList"; |
| 16 | 17 | ||
| 17 | export interface HomeHeaderEnvironment { | 18 | export interface HomeHeaderEnvironment { |
| @@ -118,7 +119,7 @@ export function HomeHeader(props: { · 100 unmodified lines | |||
| 118 | <RNText | 119 | <RNText |
| 119 | style={{ | 120 | style={{ |
| 120 | fontFamily: "DMSans_700Bold", | 121 | fontFamily: "DMSans_700Bold", |
| 121 | fontSize: 17, | 122 | fontSize: MOBILE_TYPOGRAPHY.headline.fontSize, |
| 122 | color: iconColor, | 123 | color: iconColor, |
| 123 | letterSpacing: -0.4, | 124 | letterSpacing: -0.4, |
| 124 | }} | 125 | }} |
| @@ -136,7 +137,7 @@ export function HomeHeader(props: { · 11 unmodified lines | |||
| 136 | <RNText | 137 | <RNText |
| 137 | style={{ | 138 | style={{ |
| 138 | fontFamily: "DMSans_700Bold", | 139 | fontFamily: "DMSans_700Bold", |
| 139 | fontSize: 10, | 140 | fontSize: MOBILE_TYPOGRAPHY.micro.fontSize, |
| 140 | color: mutedColor, | 141 | color: mutedColor, |
| 141 | letterSpacing: 1.1, | 142 | letterSpacing: 1.1, |
| 142 | textTransform: "uppercase", | 143 | textTransform: "uppercase", |
| @@ -186,7 +186,7 @@ function ProjectGroupLabel(props: { · 185 unmodified lines | |||
| 186 | workspaceRoot={props.project.workspaceRoot} | 186 | workspaceRoot={props.project.workspaceRoot} |
| 187 | /> | 187 | /> |
| 188 | <Text | 188 | <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" |
| 190 | style={{ letterSpacing: 0.5 }} | 190 | style={{ letterSpacing: 0.5 }} |
| 191 | numberOfLines={1} | 191 | numberOfLines={1} |
| 192 | > | 192 | > |
| @@ -196,7 +196,7 @@ function ProjectGroupLabel(props: { · 3 unmodified lines | |||
| 196 | {hiddenCount > 0 ? ( | 196 | {hiddenCount > 0 ? ( |
| 197 | <Pressable onPress={props.onToggleExpand} hitSlop={8}> | 197 | <Pressable onPress={props.onToggleExpand} hitSlop={8}> |
| 198 | <Text | 198 | <Text |
| 199 | className="text-[12px] font-t3-medium text-foreground-muted" | 199 | className="text-xs font-t3-medium text-foreground-muted" |
| 200 | style={{ letterSpacing: 0.4 }} | 200 | style={{ letterSpacing: 0.4 }} |
| 201 | > | 201 | > |
| 202 | {props.isExpanded ? "Show less" : `${hiddenCount} more`} | 202 | {props.isExpanded ? "Show less" : `${hiddenCount} more`} |
| @@ -335,7 +335,7 @@ function ThreadRow(props: { · 132 unmodified lines | |||
| 335 | <View style={{ flex: 1, gap: 3 }}> | 335 | <View style={{ flex: 1, gap: 3 }}> |
| 336 | <View className="flex-row items-center justify-between gap-2"> | 336 | <View className="flex-row items-center justify-between gap-2"> |
| 337 | <Text | 337 | <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" |
| 339 | numberOfLines={1} | 339 | numberOfLines={1} |
| 340 | > | 340 | > |
| 341 | {props.thread.title} | 341 | {props.thread.title} |
| @@ -345,12 +345,12 @@ function ThreadRow(props: { · 3 unmodified lines | |||
| 345 | className={tone.pillClassName} | 345 | className={tone.pillClassName} |
| 346 | style={{ borderRadius: 99, paddingHorizontal: 6, paddingVertical: 2 }} | 346 | style={{ borderRadius: 99, paddingHorizontal: 6, paddingVertical: 2 }} |
| 347 | > | 347 | > |
| 348 | <Text className={`text-[10px] font-t3-bold ${tone.textClassName}`}> | 348 | <Text className={`text-3xs font-t3-bold ${tone.textClassName}`}> |
| 349 | {tone.label} | 349 | {tone.label} |
| 350 | </Text> | 350 | </Text> |
| 351 | </View> | 351 | </View> |
| 352 | <Text | 352 | <Text |
| 353 | className="text-[12px] text-foreground-tertiary" | 353 | className="text-xs text-foreground-tertiary" |
| 354 | style={{ fontVariant: ["tabular-nums"] }} | 354 | style={{ fontVariant: ["tabular-nums"] }} |
| 355 | > | 355 | > |
| 356 | {timestamp} | 356 | {timestamp} |
| @@ -367,7 +367,7 @@ function ThreadRow(props: { · 10 unmodified lines | |||
| 367 | type="monochrome" | 367 | type="monochrome" |
| 368 | /> | 368 | /> |
| 369 | <Text | 369 | <Text |
| 370 | className="text-[11px] text-foreground-tertiary" | 370 | className="text-2xs text-foreground-tertiary" |
| 371 | numberOfLines={1} | 371 | numberOfLines={1} |
| 372 | style={{ fontFamily: "monospace" }} | 372 | style={{ fontFamily: "monospace" }} |
| 373 | > | 373 | > |
| @@ -429,7 +429,7 @@ function StaleCatalogStatusPill(props: { · 55 unmodified lines | |||
| 429 | weight="semibold" | 429 | weight="semibold" |
| 430 | /> | 430 | /> |
| 431 | )} | 431 | )} |
| 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}> |
| 433 | {label} | 433 | {label} |
| 434 | </Text> | 434 | </Text> |
| 435 | </Pressable> | 435 | </Pressable> |
| @@ -167,7 +167,7 @@ function SwipeActionButton(props: { · 166 unmodified lines | |||
| 167 | </Animated.View> | 167 | </Animated.View> |
| 168 | </View> | 168 | </View> |
| 169 | <Animated.View style={[{ paddingTop: 2 }, labelStyle]}> | 169 | <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> |
| 171 | </Animated.View> | 171 | </Animated.View> |
| 172 | </Pressable> | 172 | </Pressable> |
| 173 | </Animated.View> | 173 | </Animated.View> |
| @@ -100,7 +100,7 @@ function sourceFromParam(value: string | string[] | undefined): AddProjectRemote · 99 unmodified lines | |||
| 100 | function SectionTitle(props: { readonly children: string }) { | 100 | function SectionTitle(props: { readonly children: string }) { |
| 101 | return ( | 101 | return ( |
| 102 | <Text | 102 | <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" |
| 104 | style={{ letterSpacing: 0.7 }} | 104 | style={{ letterSpacing: 0.7 }} |
| 105 | > | 105 | > |
| 106 | {props.children} | 106 | {props.children} |
| @@ -168,9 +168,9 @@ function ListRow(props: { · 61 unmodified lines | |||
| 168 | {props.icon} | 168 | {props.icon} |
| 169 | </View> | 169 | </View> |
| 170 | <View className="flex-1 gap-0.5"> | 170 | <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> |
| 172 | {props.subtitle ? ( | 172 | {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}> |
| 174 | {props.subtitle} | 174 | {props.subtitle} |
| 175 | </Text> | 175 | </Text> |
| 176 | ) : null} | 176 | ) : null} |
| @@ -202,7 +202,7 @@ function PrimaryActionButton(props: { · 25 unmodified lines | |||
| 202 | {props.loading ? ( | 202 | {props.loading ? ( |
| 203 | <ActivityIndicator color={String(primaryForeground)} /> | 203 | <ActivityIndicator color={String(primaryForeground)} /> |
| 204 | ) : ( | 204 | ) : ( |
| 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> |
| 206 | )} | 206 | )} |
| 207 | </Pressable> | 207 | </Pressable> |
| 208 | ); | 208 | ); |
| @@ -215,7 +215,7 @@ function ProjectPathInput(props: { · 6 unmodified lines | |||
| 215 | }) { | 215 | }) { |
| 216 | return ( | 216 | return ( |
| 217 | <TextInput | 217 | <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]" |
| 219 | value={props.value} | 219 | value={props.value} |
| 220 | onChangeText={props.onChangeText} | 220 | onChangeText={props.onChangeText} |
| 221 | autoCapitalize="none" | 221 | autoCapitalize="none" |
| @@ -273,15 +273,15 @@ function EmptyEnvironmentState() { · 51 unmodified lines | |||
| 273 | 273 | ||
| 274 | return ( | 274 | return ( |
| 275 | <View className="items-center gap-3 rounded-2xl bg-card px-5 py-8"> | 275 | <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> | 276 | <Text className="text-center text-lg font-t3-bold">No environments connected</Text> |
| 277 | <Text className="text-center text-[14px] leading-[20px] text-foreground-muted"> | 277 | <Text className="text-center text-sm leading-[20px] text-foreground-muted"> |
| 278 | Add an environment before adding a project. | 278 | Add an environment before adding a project. |
| 279 | </Text> | 279 | </Text> |
| 280 | <Pressable | 280 | <Pressable |
| 281 | onPress={() => router.replace("/connections/new")} | 281 | onPress={() => router.replace("/connections/new")} |
| 282 | className="mt-1 rounded-full bg-primary px-4 py-2.5 active:opacity-70" | 282 | className="mt-1 rounded-full bg-primary px-4 py-2.5 active:opacity-70" |
| 283 | > | 283 | > |
| 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> |
| 285 | </Pressable> | 285 | </Pressable> |
| 286 | </View> | 286 | </View> |
| 287 | ); | 287 | ); |
| @@ -565,7 +565,7 @@ export function AddProjectRepositoryScreen() { · 277 unmodified lines | |||
| 565 | <AddProjectShell> | 565 | <AddProjectShell> |
| 566 | {error ? <ErrorBanner message={error} /> : null} | 566 | {error ? <ErrorBanner message={error} /> : null} |
| 567 | <TextInput | 567 | <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]" |
| 569 | value={repositoryInput} | 569 | value={repositoryInput} |
| 570 | onChangeText={setRepositoryInput} | 570 | onChangeText={setRepositoryInput} |
| 571 | autoCapitalize="none" | 571 | autoCapitalize="none" |
| @@ -806,8 +806,8 @@ export function AddProjectDestinationScreen() { · 234 unmodified lines | |||
| 806 | {error ? <ErrorBanner message={error} /> : null} | 806 | {error ? <ErrorBanner message={error} /> : null} |
| 807 | {repositoryTitle ? ( | 807 | {repositoryTitle ? ( |
| 808 | <View className="rounded-[24px] bg-card px-4 py-3"> | 808 | <View className="rounded-[24px] bg-card px-4 py-3"> |
| 809 | <Text className="text-[14px] font-t3-bold">{repositoryTitle}</Text> | 809 | <Text className="text-base font-t3-bold">{repositoryTitle}</Text> |
| 810 | <Text className="mt-0.5 text-[12px] text-foreground-muted" numberOfLines={2}> | 810 | <Text className="mt-0.5 text-xs text-foreground-muted" numberOfLines={2}> |
| 811 | {remoteUrl} | 811 | {remoteUrl} |
| 812 | </Text> | 812 | </Text> |
| 813 | </View> | 813 | </View> |
| @@ -159,26 +159,26 @@ export function ReviewCommentComposerSheet() { · 158 unmodified lines | |||
| 159 | <SymbolView name="xmark" size={18} tintColor={iconTint} type="monochrome" /> | 159 | <SymbolView name="xmark" size={18} tintColor={iconTint} type="monochrome" /> |
| 160 | </Pressable> | 160 | </Pressable> |
| 161 | 161 | ||
| 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> |
| 163 | 163 | ||
| 164 | <View className="h-12 w-12" /> | 164 | <View className="h-12 w-12" /> |
| 165 | </View> | 165 | </View> |
| 166 | 166 | ||
| 167 | {!target ? ( | 167 | {!target ? ( |
| 168 | <View className="rounded-[22px] border border-border bg-card px-4 py-5"> | 168 | <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> | 169 | <Text className="text-base font-t3-bold text-foreground">No selection</Text> |
| 170 | <Text className="mt-1 text-[13px] leading-[19px] text-foreground-muted"> | 170 | <Text className="mt-1 text-sm leading-[19px] text-foreground-muted"> |
| 171 | Select a diff line or range first. | 171 | Select a diff line or range first. |
| 172 | </Text> | 172 | </Text> |
| 173 | </View> | 173 | </View> |
| 174 | ) : ( | 174 | ) : ( |
| 175 | <View className="min-h-0 flex-1 gap-4"> | 175 | <View className="min-h-0 flex-1 gap-4"> |
| 176 | <View className="gap-1 px-1"> | 176 | <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"> |
| 178 | {selectionLabel} | 178 | {selectionLabel} |
| 179 | </Text> | 179 | </Text> |
| 180 | <Text | 180 | <Text |
| 181 | className="font-mono text-[12px] leading-[17px] text-foreground-muted" | 181 | className="font-mono text-xs leading-[17px] text-foreground-muted" |
| 182 | ellipsizeMode="middle" | 182 | ellipsizeMode="middle" |
| 183 | numberOfLines={2} | 183 | numberOfLines={2} |
| 184 | > | 184 | > |
| @@ -215,7 +215,7 @@ export function ReviewCommentComposerSheet() { · 30 unmodified lines | |||
| 215 | > | 215 | > |
| 216 | <ReviewChangeBar change={line.change} /> | 216 | <ReviewChangeBar change={line.change} /> |
| 217 | <Text | 217 | <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" |
| 219 | style={{ fontFamily: REVIEW_MONO_FONT_FAMILY }} | 219 | style={{ fontFamily: REVIEW_MONO_FONT_FAMILY }} |
| 220 | > | 220 | > |
| 221 | {lineNumber ?? ""} | 221 | {lineNumber ?? ""} |
| @@ -236,7 +236,7 @@ export function ReviewCommentComposerSheet() { · 14 unmodified lines | |||
| 236 | </View> | 236 | </View> |
| 237 | 237 | ||
| 238 | <View className="min-h-0 flex-1 gap-2"> | 238 | <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> |
| 240 | <View className="min-h-[132px] flex-1 overflow-hidden rounded-[20px] border border-border bg-card"> | 240 | <View className="min-h-[132px] flex-1 overflow-hidden rounded-[20px] border border-border bg-card"> |
| 241 | <View className="min-h-0 flex-1 px-4 pt-3.5"> | 241 | <View className="min-h-0 flex-1 px-4 pt-3.5"> |
| 242 | <TextInputWrapper onPaste={handleNativePaste} style={{ flex: 1, minHeight: 0 }}> | 242 | <TextInputWrapper onPaste={handleNativePaste} style={{ flex: 1, minHeight: 0 }}> |
| @@ -248,7 +248,7 @@ export function ReviewCommentComposerSheet() { · 5 unmodified lines | |||
| 248 | textAlignVertical="top" | 248 | textAlignVertical="top" |
| 249 | value={commentText} | 249 | value={commentText} |
| 250 | onChangeText={setCommentText} | 250 | 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" |
| 252 | style={{ flex: 1, minHeight: 0 }} | 252 | style={{ flex: 1, minHeight: 0 }} |
| 253 | /> | 253 | /> |
| 254 | </TextInputWrapper> | 254 | </TextInputWrapper> |
| @@ -20,6 +20,7 @@ import { environmentCatalog } from "../../connection/catalog"; · 19 unmodified lines | |||
| 20 | import { useEnvironmentPresentation } from "../../state/presentation"; | 20 | import { useEnvironmentPresentation } from "../../state/presentation"; |
| 21 | import { useAtomCommand } from "../../state/use-atom-command"; | 21 | import { useAtomCommand } from "../../state/use-atom-command"; |
| 22 | import { useThemeColor } from "../../lib/useThemeColor"; | 22 | import { useThemeColor } from "../../lib/useThemeColor"; |
| 23 | import { MOBILE_TYPOGRAPHY } from "../../lib/typography"; | ||
| 23 | import { useThreadDraftForThread } from "../../state/use-thread-composer-state"; | 24 | import { useThreadDraftForThread } from "../../state/use-thread-composer-state"; |
| 24 | import { EnvironmentConnectionNotice } from "../connection/EnvironmentConnectionNotice"; | 25 | import { EnvironmentConnectionNotice } from "../connection/EnvironmentConnectionNotice"; |
| 25 | import { useReviewCacheForThread } from "./reviewState"; | 26 | import { useReviewCacheForThread } from "./reviewState"; |
| @@ -41,10 +42,10 @@ const REVIEW_HEADER_SPACING = 0; · 15 unmodified lines | |||
| 41 | const ReviewNotice = memo(function ReviewNotice(props: { readonly notice: string }) { | 42 | const ReviewNotice = memo(function ReviewNotice(props: { readonly notice: string }) { |
| 42 | return ( | 43 | return ( |
| 43 | <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 | <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"> |
| 45 | Partial diff | 46 | Partial diff |
| 46 | </Text> | 47 | </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"> |
| 48 | {props.notice} | 49 | {props.notice} |
| 49 | </Text> | 50 | </Text> |
| 50 | </View> | 51 | </View> |
| @@ -69,7 +70,7 @@ function ReviewSelectionActionBar(props: { · 18 unmodified lines | |||
| 69 | tintColor="#ffffff" | 70 | tintColor="#ffffff" |
| 70 | type="monochrome" | 71 | type="monochrome" |
| 71 | /> | 72 | /> |
| 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> |
| 73 | </> | 74 | </> |
| 74 | ); | 75 | ); |
| 75 | 76 | ||
| @@ -218,8 +219,8 @@ export function ReviewSheet() { · 142 unmodified lines | |||
| 218 | if (error) { | 219 | if (error) { |
| 219 | children.push( | 220 | children.push( |
| 220 | <View key="review-error" className="border-b border-border bg-card px-4 py-3"> | 221 | <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-sm font-t3-bold text-foreground">Review unavailable</Text> |
| 222 | <Text className="text-[12px] leading-[18px] text-foreground-muted">{error}</Text> | 223 | <Text className="text-xs leading-[18px] text-foreground-muted">{error}</Text> |
| 223 | </View>, | 224 | </View>, |
| 224 | ); | 225 | ); |
| 225 | } | 226 | } |
| @@ -251,7 +252,7 @@ export function ReviewSheet() { · 25 unmodified lines | |||
| 251 | numberOfLines={1} | 252 | numberOfLines={1} |
| 252 | style={{ | 253 | style={{ |
| 253 | fontFamily: "DMSans_700Bold", | 254 | fontFamily: "DMSans_700Bold", |
| 254 | fontSize: 18, | 255 | fontSize: MOBILE_TYPOGRAPHY.headline.fontSize, |
| 255 | fontWeight: "900", | 256 | fontWeight: "900", |
| 256 | color: headerForeground, | 257 | color: headerForeground, |
| 257 | letterSpacing: -0.4, | 258 | letterSpacing: -0.4, |
| @@ -273,7 +274,7 @@ export function ReviewSheet() { · 15 unmodified lines | |||
| 273 | <NativeText | 274 | <NativeText |
| 274 | style={{ | 275 | style={{ |
| 275 | fontFamily: "DMSans_700Bold", | 276 | fontFamily: "DMSans_700Bold", |
| 276 | fontSize: 12, | 277 | fontSize: MOBILE_TYPOGRAPHY.label.fontSize, |
| 277 | fontWeight: "700", | 278 | fontWeight: "700", |
| 278 | color: "#16a34a", | 279 | color: "#16a34a", |
| 279 | }} | 280 | }} |
| @@ -283,7 +284,7 @@ export function ReviewSheet() { · 3 unmodified lines | |||
| 283 | <NativeText | 284 | <NativeText |
| 284 | style={{ | 285 | style={{ |
| 285 | fontFamily: "DMSans_700Bold", | 286 | fontFamily: "DMSans_700Bold", |
| 286 | fontSize: 12, | 287 | fontSize: MOBILE_TYPOGRAPHY.label.fontSize, |
| 287 | fontWeight: "700", | 288 | fontWeight: "700", |
| 288 | color: "#e11d48", | 289 | color: "#e11d48", |
| 289 | }} | 290 | }} |
| @@ -294,7 +295,7 @@ export function ReviewSheet() { · 4 unmodified lines | |||
| 294 | <NativeText | 295 | <NativeText |
| 295 | style={{ | 296 | style={{ |
| 296 | fontFamily: "DMSans_700Bold", | 297 | fontFamily: "DMSans_700Bold", |
| 297 | fontSize: 12, | 298 | fontSize: MOBILE_TYPOGRAPHY.label.fontSize, |
| 298 | fontWeight: "700", | 299 | fontWeight: "700", |
| 299 | color: "#b45309", | 300 | color: "#b45309", |
| 300 | }} | 301 | }} |
| @@ -309,7 +310,7 @@ export function ReviewSheet() { · 8 unmodified lines | |||
| 309 | numberOfLines={1} | 310 | numberOfLines={1} |
| 310 | style={{ | 311 | style={{ |
| 311 | fontFamily: "DMSans_700Bold", | 312 | fontFamily: "DMSans_700Bold", |
| 312 | fontSize: 12, | 313 | fontSize: MOBILE_TYPOGRAPHY.label.fontSize, |
| 313 | fontWeight: "700", | 314 | fontWeight: "700", |
| 314 | color: headerMuted, | 315 | color: headerMuted, |
| 315 | }} | 316 | }} |
| @@ -320,7 +321,7 @@ export function ReviewSheet() { · 4 unmodified lines | |||
| 320 | <NativeText | 321 | <NativeText |
| 321 | style={{ | 322 | style={{ |
| 322 | fontFamily: "DMSans_700Bold", | 323 | fontFamily: "DMSans_700Bold", |
| 323 | fontSize: 12, | 324 | fontSize: MOBILE_TYPOGRAPHY.label.fontSize, |
| 324 | fontWeight: "700", | 325 | fontWeight: "700", |
| 325 | color: "#b45309", | 326 | color: "#b45309", |
| 326 | }} | 327 | }} |
| @@ -430,30 +431,30 @@ export function ReviewSheet() { · 103 unmodified lines | |||
| 430 | {listHeader} | 431 | {listHeader} |
| 431 | {!selectedSection ? ( | 432 | {!selectedSection ? ( |
| 432 | <View className="border-b border-border bg-card px-4 py-5"> | 433 | <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-sm font-t3-bold text-foreground">No review diffs</Text> |
| 434 | <Text className="text-[12px] leading-[18px] text-foreground-muted"> | 435 | <Text className="text-xs leading-[18px] text-foreground-muted"> |
| 435 | This thread has no ready turn diffs and the worktree diff is empty. | 436 | This thread has no ready turn diffs and the worktree diff is empty. |
| 436 | </Text> | 437 | </Text> |
| 437 | </View> | 438 | </View> |
| 438 | ) : selectedSection.isLoading && selectedSection.diff === null ? ( | 439 | ) : selectedSection.isLoading && selectedSection.diff === null ? ( |
| 439 | <View className="items-center gap-3 border-b border-border bg-card px-4 py-6"> | 440 | <View className="items-center gap-3 border-b border-border bg-card px-4 py-6"> |
| 440 | <ActivityIndicator size="small" /> | 441 | <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> |
| 442 | </View> | 443 | </View> |
| 443 | ) : parsedDiff.kind === "empty" ? ( | 444 | ) : parsedDiff.kind === "empty" ? ( |
| 444 | <View className="border-b border-border bg-card px-4 py-5"> | 445 | <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-sm font-t3-bold text-foreground">No changes</Text> |
| 446 | <Text className="text-[12px] leading-[18px] text-foreground-muted"> | 447 | <Text className="text-xs leading-[18px] text-foreground-muted"> |
| 447 | {selectedSection.subtitle ?? "This diff is empty."} | 448 | {selectedSection.subtitle ?? "This diff is empty."} |
| 448 | </Text> | 449 | </Text> |
| 449 | </View> | 450 | </View> |
| 450 | ) : parsedDiff.kind === "raw" ? ( | 451 | ) : parsedDiff.kind === "raw" ? ( |
| 451 | <View className="gap-3 border-b border-border bg-card px-4 py-4"> | 452 | <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"> |
| 453 | {parsedDiff.reason} | 454 | {parsedDiff.reason} |
| 454 | </Text> | 455 | </Text> |
| 455 | <ScrollView horizontal showsHorizontalScrollIndicator={false} bounces={false}> | 456 | <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"> |
| 457 | {parsedDiff.text} | 458 | {parsedDiff.text} |
| 458 | </Text> | 459 | </Text> |
| 459 | </ScrollView> | 460 | </ScrollView> |
| @@ -5,6 +5,7 @@ import type { · 4 unmodified lines | |||
| 5 | } from "../diffs/nativeReviewDiffTypes"; | 5 | } from "../diffs/nativeReviewDiffTypes"; |
| 6 | import * as Arr from "effect/Array"; | 6 | import * as Arr from "effect/Array"; |
| 7 | import { pipe } from "effect/Function"; | 7 | import { pipe } from "effect/Function"; |
| 8 | import { MOBILE_CODE_SURFACE } from "../../lib/typography"; | ||
| 8 | import { getPierreTerminalTheme, type TerminalAppearanceScheme } from "../terminal/terminalTheme"; | 9 | import { getPierreTerminalTheme, type TerminalAppearanceScheme } from "../terminal/terminalTheme"; |
| 9 | import { computeWordAltDiffRanges } from "./reviewWordDiffs"; | 10 | import { computeWordAltDiffRanges } from "./reviewWordDiffs"; |
| 10 | import { | 11 | import { |
| @@ -18,16 +19,16 @@ import type { ReviewInlineComment } from "./reviewCommentSelection"; · 7 unmodified lines | |||
| 18 | const NATIVE_REVIEW_MAX_WORD_DIFF_RANGE_COUNT = 4; | 19 | const NATIVE_REVIEW_MAX_WORD_DIFF_RANGE_COUNT = 4; |
| 19 | const NATIVE_REVIEW_MAX_WORD_DIFF_COVERAGE = 0.45; | 20 | const NATIVE_REVIEW_MAX_WORD_DIFF_COVERAGE = 0.45; |
| 20 | 21 | ||
| 21 | export const NATIVE_REVIEW_DIFF_ROW_HEIGHT = 20; | 22 | export const NATIVE_REVIEW_DIFF_ROW_HEIGHT = MOBILE_CODE_SURFACE.rowHeight; |
| 22 | export const NATIVE_REVIEW_DIFF_CONTENT_WIDTH = 2_800; | 23 | export const NATIVE_REVIEW_DIFF_CONTENT_WIDTH = 2_800; |
| 23 | 24 | ||
| 24 | export const NATIVE_REVIEW_DIFF_STYLE = { | 25 | export const NATIVE_REVIEW_DIFF_STYLE = { |
| 25 | rowHeight: NATIVE_REVIEW_DIFF_ROW_HEIGHT, | 26 | rowHeight: NATIVE_REVIEW_DIFF_ROW_HEIGHT, |
| 26 | contentWidth: NATIVE_REVIEW_DIFF_CONTENT_WIDTH, | 27 | contentWidth: NATIVE_REVIEW_DIFF_CONTENT_WIDTH, |
| 27 | changeBarWidth: 4, | 28 | changeBarWidth: 4, |
| 28 | gutterWidth: 46, | 29 | gutterWidth: MOBILE_CODE_SURFACE.gutterWidth, |
| 29 | codePadding: 7, | 30 | codePadding: MOBILE_CODE_SURFACE.codePadding, |
| 30 | textVerticalInset: 2, | 31 | textVerticalInset: MOBILE_CODE_SURFACE.textVerticalInset, |
| 31 | fileHeaderHeight: 56, | 32 | fileHeaderHeight: 56, |
| 32 | fileHeaderHorizontalMargin: 8, | 33 | fileHeaderHorizontalMargin: 8, |
| 33 | fileHeaderVerticalMargin: 6, | 34 | fileHeaderVerticalMargin: 6, |
| @@ -36,9 +37,9 @@ export const NATIVE_REVIEW_DIFF_STYLE = { · 2 unmodified lines | |||
| 36 | fileHeaderPathRightPadding: 118, | 37 | fileHeaderPathRightPadding: 118, |
| 37 | fileHeaderCountColumnWidth: 38, | 38 | fileHeaderCountColumnWidth: 38, |
| 38 | fileHeaderCountGap: 5, | 39 | fileHeaderCountGap: 5, |
| 39 | codeFontSize: 11, | 40 | codeFontSize: MOBILE_CODE_SURFACE.fontSize, |
| 40 | codeFontWeight: "regular", | 41 | codeFontWeight: "regular", |
| 41 | lineNumberFontSize: 10, | 42 | lineNumberFontSize: MOBILE_CODE_SURFACE.lineNumberFontSize, |
| 42 | lineNumberFontWeight: "regular", | 43 | lineNumberFontWeight: "regular", |
| 43 | hunkFontSize: 11, | 44 | hunkFontSize: 11, |
| 44 | hunkFontWeight: "medium", | 45 | hunkFontWeight: "medium", |
| @@ -1,6 +1,7 @@ | |||
| 1 | import { Platform, Text as NativeText, View } from "react-native"; | 1 | import { Platform, Text as NativeText, View } from "react-native"; |
| 2 | 2 | ||
| 3 | import { cn } from "../../lib/cn"; | 3 | import { cn } from "../../lib/cn"; |
| 4 | import { MOBILE_CODE_SURFACE } from "../../lib/typography"; | ||
| 4 | 5 | ||
| 5 | import type { ReviewRenderableLineRow } from "./reviewModel"; | 6 | import type { ReviewRenderableLineRow } from "./reviewModel"; |
| 6 | import type { ReviewHighlightedToken } from "./shikiReviewHighlighter"; | 7 | import type { ReviewHighlightedToken } from "./shikiReviewHighlighter"; |
| @@ -11,7 +12,7 @@ export const REVIEW_MONO_FONT_FAMILY = Platform.select({ · 4 unmodified lines | |||
| 11 | default: "monospace", | 12 | default: "monospace", |
| 12 | }); | 13 | }); |
| 13 | 14 | ||
| 14 | export const REVIEW_DIFF_LINE_HEIGHT = 26; | 15 | export const REVIEW_DIFF_LINE_HEIGHT = MOBILE_CODE_SURFACE.rowHeight; |
| 15 | const REVIEW_DELETE_STRIPE_COUNT = REVIEW_DIFF_LINE_HEIGHT / 2; | 16 | const REVIEW_DELETE_STRIPE_COUNT = REVIEW_DIFF_LINE_HEIGHT / 2; |
| 16 | 17 | ||
| 17 | export function renderVisibleWhitespace(value: string): string { | 18 | export function renderVisibleWhitespace(value: string): string { |
| @@ -71,8 +72,12 @@ export function DiffTokenText(props: { · 53 unmodified lines | |||
| 71 | <NativeText | 72 | <NativeText |
| 72 | numberOfLines={1} | 73 | numberOfLines={1} |
| 73 | selectable | 74 | selectable |
| 74 | className={cn("text-[13px] leading-[17px] font-medium text-foreground", props.className)} | 75 | className={cn("font-normal text-foreground", props.className)} |
| 75 | style={{ fontFamily: REVIEW_MONO_FONT_FAMILY }} | 76 | style={{ |
| 77 | fontFamily: REVIEW_MONO_FONT_FAMILY, | ||
| 78 | fontSize: MOBILE_CODE_SURFACE.fontSize, | ||
| 79 | lineHeight: MOBILE_CODE_SURFACE.rowHeight, | ||
| 80 | }} | ||
| 76 | > | 81 | > |
| 77 | {renderVisibleWhitespace(props.fallback || " ")} | 82 | {renderVisibleWhitespace(props.fallback || " ")} |
| 78 | </NativeText> | 83 | </NativeText> |
| @@ -83,8 +88,12 @@ export function DiffTokenText(props: { · 4 unmodified lines | |||
| 83 | <NativeText | 88 | <NativeText |
| 84 | numberOfLines={1} | 89 | numberOfLines={1} |
| 85 | selectable | 90 | selectable |
| 86 | className={cn("text-[13px] leading-[17px] font-medium text-foreground", props.className)} | 91 | className={cn("font-normal text-foreground", props.className)} |
| 87 | style={{ fontFamily: REVIEW_MONO_FONT_FAMILY }} | 92 | style={{ |
| 93 | fontFamily: REVIEW_MONO_FONT_FAMILY, | ||
| 94 | fontSize: MOBILE_CODE_SURFACE.fontSize, | ||
| 95 | lineHeight: MOBILE_CODE_SURFACE.rowHeight, | ||
| 96 | }} | ||
| 88 | > | 97 | > |
| 89 | {(() => { | 98 | {(() => { |
| 90 | let offset = 0; | 99 | let offset = 0; |
| @@ -11,6 +11,7 @@ import { · 10 unmodified lines | |||
| 11 | } from "react-native"; | 11 | } from "react-native"; |
| 12 | 12 | ||
| 13 | import { AppText as Text } from "../../components/AppText"; | 13 | import { AppText as Text } from "../../components/AppText"; |
| 14 | import { MOBILE_TYPOGRAPHY } from "../../lib/typography"; | ||
| 14 | import { resolveNativeTerminalSurfaceView } from "./nativeTerminalModule"; | 15 | import { resolveNativeTerminalSurfaceView } from "./nativeTerminalModule"; |
| 15 | import { | 16 | import { |
| 16 | buildGhosttyThemeConfig, | 17 | buildGhosttyThemeConfig, |
| @@ -53,7 +54,7 @@ function estimateGridSize(input: { · 36 unmodified lines | |||
| 53 | } | 54 | } |
| 54 | 55 | ||
| 55 | const FallbackTerminalSurface = memo(function FallbackTerminalSurface(props: TerminalSurfaceProps) { | 56 | const FallbackTerminalSurface = memo(function FallbackTerminalSurface(props: TerminalSurfaceProps) { |
| 56 | const fontSize = props.fontSize ?? 12; | 57 | const fontSize = props.fontSize ?? MOBILE_TYPOGRAPHY.label.fontSize; |
| 57 | const inputRef = useRef<TextInput>(null); | 58 | const inputRef = useRef<TextInput>(null); |
| 58 | const appearanceScheme = useColorScheme() === "light" ? "light" : "dark"; | 59 | const appearanceScheme = useColorScheme() === "light" ? "light" : "dark"; |
| 59 | const theme = props.theme ?? getPierreTerminalTheme(appearanceScheme); | 60 | const theme = props.theme ?? getPierreTerminalTheme(appearanceScheme); |
| @@ -93,7 +94,7 @@ const FallbackTerminalSurface = memo(function FallbackTerminalSurface(props: Ter · 33 unmodified lines | |||
| 93 | <Text | 94 | <Text |
| 94 | style={{ | 95 | style={{ |
| 95 | color: theme.mutedForeground, | 96 | color: theme.mutedForeground, |
| 96 | fontSize: 11, | 97 | fontSize: MOBILE_TYPOGRAPHY.caption.fontSize, |
| 97 | paddingBottom: 8, | 98 | paddingBottom: 8, |
| 98 | }} | 99 | }} |
| 99 | > | 100 | > |
| @@ -140,7 +141,7 @@ const FallbackTerminalSurface = memo(function FallbackTerminalSurface(props: Ter · 40 unmodified lines | |||
| 140 | color: theme.foreground, | 141 | color: theme.foreground, |
| 141 | flex: 1, | 142 | flex: 1, |
| 142 | fontFamily: "Menlo", | 143 | fontFamily: "Menlo", |
| 143 | fontSize: 13, | 144 | fontSize: MOBILE_TYPOGRAPHY.footnote.fontSize, |
| 144 | padding: 0, | 145 | padding: 0, |
| 145 | }} | 146 | }} |
| 146 | onSubmitEditing={(event) => { | 147 | onSubmitEditing={(event) => { |
| @@ -165,7 +166,7 @@ const FallbackTerminalSurface = memo(function FallbackTerminalSurface(props: Ter · 18 unmodified lines | |||
| 165 | style={{ | 166 | style={{ |
| 166 | color: theme.foreground, | 167 | color: theme.foreground, |
| 167 | fontFamily: "DMSans_700Bold", | 168 | fontFamily: "DMSans_700Bold", |
| 168 | fontSize: 11, | 169 | fontSize: MOBILE_TYPOGRAPHY.caption.fontSize, |
| 169 | }} | 170 | }} |
| 170 | > | 171 | > |
| 171 | Ctrl-C | 172 | Ctrl-C |
| @@ -177,7 +178,7 @@ const FallbackTerminalSurface = memo(function FallbackTerminalSurface(props: Ter · 5 unmodified lines | |||
| 177 | }); | 178 | }); |
| 178 | 179 | ||
| 179 | export const TerminalSurface = memo(function TerminalSurface(props: TerminalSurfaceProps) { | 180 | export const TerminalSurface = memo(function TerminalSurface(props: TerminalSurfaceProps) { |
| 180 | const fontSize = props.fontSize ?? 12; | 181 | const fontSize = props.fontSize ?? MOBILE_TYPOGRAPHY.label.fontSize; |
| 181 | const keyboardInputRef = useRef<TextInput>(null); | 182 | const keyboardInputRef = useRef<TextInput>(null); |
| 182 | const appearanceScheme = useColorScheme() === "light" ? "light" : "dark"; | 183 | const appearanceScheme = useColorScheme() === "light" ? "light" : "dark"; |
| 183 | const theme = props.theme ?? getPierreTerminalTheme(appearanceScheme); | 184 | const theme = props.theme ?? getPierreTerminalTheme(appearanceScheme); |
| @@ -127,16 +127,16 @@ export const ThreadTerminalPanel = memo(function ThreadTerminalPanel( · 126 unmodified lines | |||
| 127 | <View className="absolute inset-x-3 bottom-28 top-28 overflow-hidden rounded-[8px] border border-white/10 bg-neutral-950 shadow-2xl"> | 127 | <View className="absolute inset-x-3 bottom-28 top-28 overflow-hidden rounded-[8px] border border-white/10 bg-neutral-950 shadow-2xl"> |
| 128 | <View className="flex-row items-center justify-between border-b border-white/10 px-3 py-2"> | 128 | <View className="flex-row items-center justify-between border-b border-white/10 px-3 py-2"> |
| 129 | <View className="min-w-0 flex-1"> | 129 | <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}> |
| 131 | Terminal | 131 | Terminal |
| 132 | </Text> | 132 | </Text> |
| 133 | <Text className="text-[11px] text-neutral-500" numberOfLines={1}> | 133 | <Text className="text-2xs text-neutral-500" numberOfLines={1}> |
| 134 | {nativeTerminalAvailable ? "Native Ghostty surface" : "Text fallback active"} | 134 | {nativeTerminalAvailable ? "Native Ghostty surface" : "Text fallback active"} |
| 135 | </Text> | 135 | </Text> |
| 136 | </View> | 136 | </View> |
| 137 | <View className="flex-row items-center gap-2"> | 137 | <View className="flex-row items-center gap-2"> |
| 138 | {terminal.error ? ( | 138 | {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}> |
| 140 | {terminal.error} | 140 | {terminal.error} |
| 141 | </Text> | 141 | </Text> |
| 142 | ) : null} | 142 | ) : null} |
| @@ -25,6 +25,7 @@ import { terminalEnvironment } from "../../state/terminal"; · 24 unmodified lines | |||
| 25 | import { useAtomCommand } from "../../state/use-atom-command"; | 25 | import { useAtomCommand } from "../../state/use-atom-command"; |
| 26 | import { useWorkspaceState } from "../../state/workspace"; | 26 | import { useWorkspaceState } from "../../state/workspace"; |
| 27 | import { buildThreadTerminalNavigation } from "../../lib/routes"; | 27 | import { buildThreadTerminalNavigation } from "../../lib/routes"; |
| 28 | import { MOBILE_TYPOGRAPHY } from "../../lib/typography"; | ||
| 28 | import { | 29 | import { |
| 29 | useAttachedTerminalSession, | 30 | useAttachedTerminalSession, |
| 30 | useKnownTerminalSessions, | 31 | useKnownTerminalSessions, |
| @@ -920,7 +921,7 @@ export function ThreadTerminalRouteScreen() { · 889 unmodified lines | |||
| 920 | style={{ | 921 | style={{ |
| 921 | color: terminalTheme.foreground, | 922 | color: terminalTheme.foreground, |
| 922 | fontFamily: "DMSans_700Bold", | 923 | fontFamily: "DMSans_700Bold", |
| 923 | fontSize: 13, | 924 | fontSize: MOBILE_TYPOGRAPHY.footnote.fontSize, |
| 924 | lineHeight: 16, | 925 | lineHeight: 16, |
| 925 | }} | 926 | }} |
| 926 | > | 927 | > |
| @@ -932,7 +933,7 @@ export function ThreadTerminalRouteScreen() { · 5 unmodified lines | |||
| 932 | style={{ | 933 | style={{ |
| 933 | color: terminalTheme.mutedForeground, | 934 | color: terminalTheme.mutedForeground, |
| 934 | fontFamily: "Menlo", | 935 | fontFamily: "Menlo", |
| 935 | fontSize: 11, | 936 | fontSize: MOBILE_TYPOGRAPHY.caption.fontSize, |
| 936 | lineHeight: 14, | 937 | lineHeight: 14, |
| 937 | }} | 938 | }} |
| 938 | > | 939 | > |
| @@ -7,6 +7,7 @@ import { Pressable, ScrollView, useColorScheme, View, type ViewStyle } from "rea · 6 unmodified lines | |||
| 7 | 7 | ||
| 8 | import { AppText as Text } from "../../components/AppText"; | 8 | import { AppText as Text } from "../../components/AppText"; |
| 9 | import { PierreEntryIcon } from "../../components/PierreEntryIcon"; | 9 | import { PierreEntryIcon } from "../../components/PierreEntryIcon"; |
| 10 | import { MOBILE_TYPOGRAPHY } from "../../lib/typography"; | ||
| 10 | 11 | ||
| 11 | export type ComposerCommandItem = | 12 | export type ComposerCommandItem = |
| 12 | | { | 13 | | { |
| @@ -156,14 +157,22 @@ const CommandRow = memo(function CommandRow(props: { · 143 unmodified lines | |||
| 156 | <SymbolView name={iconName} size={14} tintColor={iconColor} type="monochrome" /> | 157 | <SymbolView name={iconName} size={14} tintColor={iconColor} type="monochrome" /> |
| 157 | ) : null} | 158 | ) : null} |
| 158 | <Text | 159 | <Text |
| 159 | className="text-[14px] font-t3-medium text-foreground" | 160 | className="text-base font-t3-medium text-foreground" |
| 160 | numberOfLines={1} | 161 | numberOfLines={1} |
| 161 | style={{ flexShrink: 0 }} | 162 | style={{ flexShrink: 0 }} |
| 162 | > | 163 | > |
| 163 | {props.item.label} | 164 | {props.item.label} |
| 164 | </Text> | 165 | </Text> |
| 165 | {props.item.description ? ( | 166 | {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 | > | ||
| 167 | {props.item.description} | 176 | {props.item.description} |
| 168 | </Text> | 177 | </Text> |
| 169 | ) : null} | 178 | ) : null} |
| @@ -182,7 +191,7 @@ export const ComposerCommandPopover = memo(function ComposerCommandPopover( · 12 unmodified lines | |||
| 182 | {label ? ( | 191 | {label ? ( |
| 183 | <View style={{ paddingHorizontal: 14, paddingTop: 10, paddingBottom: 4 }}> | 192 | <View style={{ paddingHorizontal: 14, paddingTop: 10, paddingBottom: 4 }}> |
| 184 | <Text | 193 | <Text |
| 185 | className="text-[10px] font-t3-bold text-foreground-muted" | 194 | className="text-3xs font-t3-bold text-foreground-muted" |
| 186 | style={{ letterSpacing: 0.8, textTransform: "uppercase" }} | 195 | style={{ letterSpacing: 0.8, textTransform: "uppercase" }} |
| 187 | > | 196 | > |
| 188 | {label} | 197 | {label} |
| @@ -206,7 +215,7 @@ export const ComposerCommandPopover = memo(function ComposerCommandPopover( · 17 unmodified lines | |||
| 206 | </ScrollView> | 215 | </ScrollView> |
| 207 | ) : ( | 216 | ) : ( |
| 208 | <View style={{ paddingHorizontal: 14, paddingVertical: 10 }}> | 217 | <View style={{ paddingHorizontal: 14, paddingVertical: 10 }}> |
| 209 | <Text className="text-[12px] text-foreground-tertiary"> | 218 | <Text className="text-xs text-foreground-tertiary"> |
| 210 | {emptyText(props.triggerKind, props.isLoading)} | 219 | {emptyText(props.triggerKind, props.isLoading)} |
| 211 | </Text> | 220 | </Text> |
| 212 | </View> | 221 | </View> |
| @@ -73,12 +73,12 @@ function OverlayContent(props: { readonly progress: GitActionProgress }) { · 72 unmodified lines | |||
| 73 | 73 | ||
| 74 | <View className="flex-1 gap-0.5"> | 74 | <View className="flex-1 gap-0.5"> |
| 75 | {progress.label ? ( | 75 | {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}> |
| 77 | {progress.label} | 77 | {progress.label} |
| 78 | </Text> | 78 | </Text> |
| 79 | ) : null} | 79 | ) : null} |
| 80 | {progress.description ? ( | 80 | {progress.description ? ( |
| 81 | <Text className="text-[11px] text-foreground-muted" numberOfLines={1}> | 81 | <Text className="text-2xs text-foreground-muted" numberOfLines={1}> |
| 82 | {progress.description} | 82 | {progress.description} |
| 83 | </Text> | 83 | </Text> |
| 84 | ) : null} | 84 | ) : null} |
| @@ -30,6 +30,7 @@ import { · 29 unmodified lines | |||
| 30 | resolveProviderOptionDescriptors, | 30 | resolveProviderOptionDescriptors, |
| 31 | } from "../../lib/providerOptions"; | 31 | } from "../../lib/providerOptions"; |
| 32 | import { buildThreadRoutePath } from "../../lib/routes"; | 32 | import { buildThreadRoutePath } from "../../lib/routes"; |
| 33 | import { MOBILE_TYPOGRAPHY } from "../../lib/typography"; | ||
| 33 | import { useProjects } from "../../state/entities"; | 34 | import { useProjects } from "../../state/entities"; |
| 34 | import { branchBadgeLabel, useNewTaskFlow } from "./new-task-flow-provider"; | 35 | import { branchBadgeLabel, useNewTaskFlow } from "./new-task-flow-provider"; |
| 35 | import { useCreateProjectThread } from "./use-project-actions"; | 36 | import { useCreateProjectThread } from "./use-project-actions"; |
| @@ -430,7 +431,7 @@ export function NewTaskDraftScreen(props: { · 394 unmodified lines | |||
| 430 | onPasteImages={(uris) => void handleNativePasteImages(uris)} | 431 | onPasteImages={(uris) => void handleNativePasteImages(uris)} |
| 431 | placeholder={`Describe a coding task in ${selectedProject.title}`} | 432 | placeholder={`Describe a coding task in ${selectedProject.title}`} |
| 432 | style={{ flex: 1, minHeight: 0 }} | 433 | style={{ flex: 1, minHeight: 0 }} |
| 433 | textStyle={{ fontSize: 18, lineHeight: 28 }} | 434 | textStyle={MOBILE_TYPOGRAPHY.composer} |
| 434 | /> | 435 | /> |
| 435 | </View> | 436 | </View> |
| 436 | 437 | ||
| @@ -16,7 +16,7 @@ export interface PendingApprovalCardProps { · 15 unmodified lines | |||
| 16 | export function PendingApprovalCard(props: PendingApprovalCardProps) { | 16 | export function PendingApprovalCard(props: PendingApprovalCardProps) { |
| 17 | return ( | 17 | return ( |
| 18 | <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"> | 18 | <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"> |
| 20 | Approval needed | 20 | Approval needed |
| 21 | </Text> | 21 | </Text> |
| 22 | <Text className="font-t3-bold text-lg text-neutral-950 dark:text-neutral-50"> | 22 | <Text className="font-t3-bold text-lg text-neutral-950 dark:text-neutral-50"> |
| @@ -26,7 +26,7 @@ export interface PendingUserInputCardProps { · 25 unmodified lines | |||
| 26 | export function PendingUserInputCard(props: PendingUserInputCardProps) { | 26 | export function PendingUserInputCard(props: PendingUserInputCardProps) { |
| 27 | return ( | 27 | return ( |
| 28 | <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"> | 28 | <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"> |
| 30 | User input needed | 30 | User input needed |
| 31 | </Text> | 31 | </Text> |
| 32 | <Text className="font-t3-bold text-lg text-neutral-950 dark:text-neutral-50"> | 32 | <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 | |||
| 39 | <Text className="font-t3-bold text-xs uppercase tracking-[1px] text-neutral-500 dark:text-neutral-500"> | 39 | <Text className="font-t3-bold text-xs uppercase tracking-[1px] text-neutral-500 dark:text-neutral-500"> |
| 40 | {question.header} | 40 | {question.header} |
| 41 | </Text> | 41 | </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"> |
| 43 | {question.question} | 43 | {question.question} |
| 44 | </Text> | 44 | </Text> |
| 45 | <View className="flex-row flex-wrap gap-2.5"> | 45 | <View className="flex-row flex-wrap gap-2.5"> |
| @@ -65,7 +65,7 @@ export function PendingUserInputCard(props: PendingUserInputCardProps) { · 19 unmodified lines | |||
| 65 | > | 65 | > |
| 66 | <Text | 66 | <Text |
| 67 | className={cn( | 67 | className={cn( |
| 68 | "font-t3-bold text-[13px]", | 68 | "font-t3-bold text-sm", |
| 69 | selected | 69 | selected |
| 70 | ? "text-sky-700 dark:text-sky-300" | 70 | ? "text-sky-700 dark:text-sky-300" |
| 71 | : "text-neutral-600 dark:text-neutral-300", | 71 | : "text-neutral-600 dark:text-neutral-300", |
| @@ -83,7 +83,7 @@ export function PendingUserInputCard(props: PendingUserInputCardProps) { · 11 unmodified lines | |||
| 83 | props.onChangeCustomAnswer(props.pendingUserInput.requestId, question.id, value) | 83 | props.onChangeCustomAnswer(props.pendingUserInput.requestId, question.id, value) |
| 84 | } | 84 | } |
| 85 | placeholder="Or type a custom answer" | 85 | 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" |
| 87 | /> | 87 | /> |
| 88 | </View> | 88 | </View> |
| 89 | ); | 89 | ); |
| @@ -43,6 +43,7 @@ import { ControlPill, ControlPillMenu } from "../../components/ControlPill"; · 42 unmodified lines | |||
| 43 | import { ProviderIcon } from "../../components/ProviderIcon"; | 43 | import { ProviderIcon } from "../../components/ProviderIcon"; |
| 44 | import type { DraftComposerImageAttachment } from "../../lib/composerImages"; | 44 | import type { DraftComposerImageAttachment } from "../../lib/composerImages"; |
| 45 | import { buildModelOptions, groupByProvider } from "../../lib/modelOptions"; | 45 | import { buildModelOptions, groupByProvider } from "../../lib/modelOptions"; |
| 46 | import { MOBILE_TYPOGRAPHY } from "../../lib/typography"; | ||
| 46 | import type { RemoteClientConnectionState } from "../../lib/connection"; | 47 | import type { RemoteClientConnectionState } from "../../lib/connection"; |
| 47 | import { | 48 | import { |
| 48 | insertRankedSearchResult, | 49 | insertRankedSearchResult, |
| @@ -189,7 +190,7 @@ const ComposerConnectionStatusPill = memo(function ComposerConnectionStatusPill( · 140 unmodified lines | |||
| 189 | <View className="h-2 w-2 rounded-full bg-red-500" /> | 190 | <View className="h-2 w-2 rounded-full bg-red-500" /> |
| 190 | )} | 191 | )} |
| 191 | <Text | 192 | <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" |
| 193 | numberOfLines={1} | 194 | numberOfLines={1} |
| 194 | > | 195 | > |
| 195 | {props.status.label} | 196 | {props.status.label} |
| @@ -717,8 +718,7 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer · 521 unmodified lines | |||
| 717 | } | 718 | } |
| 718 | } | 719 | } |
| 719 | textStyle={{ | 720 | textStyle={{ |
| 720 | fontSize: 15, | 721 | ...MOBILE_TYPOGRAPHY.composer, |
| 721 | lineHeight: isExpanded ? 22 : 20, | ||
| 722 | color: foregroundColor, | 722 | color: foregroundColor, |
| 723 | fontFamily: "DMSans_400Regular", | 723 | fontFamily: "DMSans_400Regular", |
| 724 | }} | 724 | }} |
| @@ -751,7 +751,7 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer · 26 unmodified lines | |||
| 751 | justifyContent: "center", | 751 | justifyContent: "center", |
| 752 | }} | 752 | }} |
| 753 | > | 753 | > |
| 754 | <Text className="text-foreground-muted text-[11px] font-t3-bold"> | 754 | <Text className="text-foreground-muted text-2xs font-t3-bold"> |
| 755 | +{props.draftAttachments.length - 3} | 755 | +{props.draftAttachments.length - 3} |
| 756 | </Text> | 756 | </Text> |
| 757 | </View> | 757 | </View> |
| @@ -831,8 +831,7 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer · 73 unmodified lines | |||
| 831 | <Text | 831 | <Text |
| 832 | className="text-foreground-muted" | 832 | className="text-foreground-muted" |
| 833 | style={{ | 833 | style={{ |
| 834 | fontSize: 12, | 834 | ...MOBILE_TYPOGRAPHY.label, |
| 835 | lineHeight: 18, | ||
| 836 | paddingTop: 8, | 835 | paddingTop: 8, |
| 837 | }} | 836 | }} |
| 838 | > | 837 | > |
| @@ -56,6 +56,7 @@ import { buildReviewParsedDiff } from "../review/reviewModel"; · 55 unmodified lines | |||
| 56 | import { cn } from "../../lib/cn"; | 56 | import { cn } from "../../lib/cn"; |
| 57 | import type { LayoutVariant } from "../../lib/layout"; | 57 | import type { LayoutVariant } from "../../lib/layout"; |
| 58 | import { buildThreadFilesNavigation } from "../../lib/routes"; | 58 | import { buildThreadFilesNavigation } from "../../lib/routes"; |
| 59 | import { MOBILE_CODE_SURFACE, MOBILE_TYPOGRAPHY } from "../../lib/typography"; | ||
| 59 | import { markdownFileIconSource } from "@t3tools/mobile-markdown-text/file-icons"; | 60 | import { markdownFileIconSource } from "@t3tools/mobile-markdown-text/file-icons"; |
| 60 | import { resolveMarkdownLinkPresentation } from "@t3tools/mobile-markdown-text/links"; | 61 | import { resolveMarkdownLinkPresentation } from "@t3tools/mobile-markdown-text/links"; |
| 61 | import { | 62 | import { |
| @@ -444,8 +445,7 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe · 382 unmodified lines | |||
| 444 | marginRight: 5, | 445 | marginRight: 5, |
| 445 | color: inlineTextColor, | 446 | color: inlineTextColor, |
| 446 | fontFamily: "DMSans_400Regular", | 447 | fontFamily: "DMSans_400Regular", |
| 447 | fontSize: 15, | 448 | ...MOBILE_TYPOGRAPHY.body, |
| 448 | lineHeight: 22, | ||
| 449 | textAlign: ordered ? "right" : "center", | 449 | textAlign: ordered ? "right" : "center", |
| 450 | }} | 450 | }} |
| 451 | > | 451 | > |
| @@ -466,7 +466,7 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe · 14 unmodified lines | |||
| 466 | style={{ | 466 | style={{ |
| 467 | color: inlineCodeTextColor, | 467 | color: inlineCodeTextColor, |
| 468 | fontFamily: "ui-monospace", | 468 | fontFamily: "ui-monospace", |
| 469 | fontSize: 12, | 469 | fontSize: MOBILE_TYPOGRAPHY.label.fontSize, |
| 470 | lineHeight: 22, | 470 | lineHeight: 22, |
| 471 | }} | 471 | }} |
| 472 | > | 472 | > |
| @@ -503,7 +503,7 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe · 30 unmodified lines | |||
| 503 | style={{ | 503 | style={{ |
| 504 | color: markdownBodyColor, | 504 | color: markdownBodyColor, |
| 505 | fontFamily: "ui-monospace", | 505 | fontFamily: "ui-monospace", |
| 506 | fontSize: 12, | 506 | fontSize: MOBILE_TYPOGRAPHY.label.fontSize, |
| 507 | opacity: 0.7, | 507 | opacity: 0.7, |
| 508 | textTransform: "uppercase", | 508 | textTransform: "uppercase", |
| 509 | }} | 509 | }} |
| @@ -523,7 +523,7 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe · 13 unmodified lines | |||
| 523 | style={{ | 523 | style={{ |
| 524 | color: blockTextColor, | 524 | color: blockTextColor, |
| 525 | fontFamily: "ui-monospace", | 525 | fontFamily: "ui-monospace", |
| 526 | fontSize: 12, | 526 | fontSize: MOBILE_TYPOGRAPHY.label.fontSize, |
| 527 | lineHeight: 18, | 527 | lineHeight: 18, |
| 528 | }} | 528 | }} |
| 529 | > | 529 | > |
| @@ -603,8 +603,7 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe · 73 unmodified lines | |||
| 603 | skillTextColor: "#f0abfc", | 603 | skillTextColor: "#f0abfc", |
| 604 | quoteMarkerColor: markdownUserBodyColor, | 604 | quoteMarkerColor: markdownUserBodyColor, |
| 605 | dividerColor: markdownUserBodyColor, | 605 | dividerColor: markdownUserBodyColor, |
| 606 | fontSize: 15, | 606 | ...MOBILE_TYPOGRAPHY.body, |
| 607 | lineHeight: 22, | ||
| 608 | fontFamily: "DMSans_400Regular", | 607 | fontFamily: "DMSans_400Regular", |
| 609 | headingFontFamily: "DMSans_700Bold", | 608 | headingFontFamily: "DMSans_700Bold", |
| 610 | boldFontFamily: "DMSans_700Bold", | 609 | boldFontFamily: "DMSans_700Bold", |
| @@ -633,8 +632,7 @@ function useMarkdownStyles(onLinkPress: (href: string) => void): MarkdownStyleSe · 22 unmodified lines | |||
| 633 | skillTextColor: inlineSkillForeground, | 632 | skillTextColor: inlineSkillForeground, |
| 634 | quoteMarkerColor: markdownBlockquoteBorder, | 633 | quoteMarkerColor: markdownBlockquoteBorder, |
| 635 | dividerColor: markdownHrColor, | 634 | dividerColor: markdownHrColor, |
| 636 | fontSize: 15, | 635 | ...MOBILE_TYPOGRAPHY.body, |
| 637 | lineHeight: 22, | ||
| 638 | fontFamily: "DMSans_400Regular", | 636 | fontFamily: "DMSans_400Regular", |
| 639 | headingFontFamily: "DMSans_700Bold", | 637 | headingFontFamily: "DMSans_700Bold", |
| 640 | boldFontFamily: "DMSans_700Bold", | 638 | boldFontFamily: "DMSans_700Bold", |
| @@ -821,7 +819,7 @@ function renderFeedEntry( · 180 unmodified lines | |||
| 821 | className="max-w-[85%] gap-2 rounded-[22px] rounded-br-[6px] px-3.5 py-2.5 opacity-60" | 819 | className="max-w-[85%] gap-2 rounded-[22px] rounded-br-[6px] px-3.5 py-2.5 opacity-60" |
| 822 | style={{ backgroundColor: userBubbleColor }} | 820 | style={{ backgroundColor: userBubbleColor }} |
| 823 | > | 821 | > |
| 824 | <Text className="font-sans text-[15px] leading-[22px] text-white"> | 822 | <Text className="font-sans text-base leading-[22px] text-white"> |
| 825 | {entry.queuedMessage.text} | 823 | {entry.queuedMessage.text} |
| 826 | </Text> | 824 | </Text> |
| 827 | {entry.queuedMessage.attachments.length > 0 ? ( | 825 | {entry.queuedMessage.attachments.length > 0 ? ( |
| @@ -995,7 +993,7 @@ const ReviewCommentCard = memo(function ReviewCommentCard(props: { · 167 unmodified lines | |||
| 995 | </View> | 993 | </View> |
| 996 | <View className="min-w-0 flex-1"> | 994 | <View className="min-w-0 flex-1"> |
| 997 | <Text | 995 | <Text |
| 998 | className="font-mono text-[12px] leading-[16px]" | 996 | className="font-mono text-xs leading-[16px]" |
| 999 | numberOfLines={1} | 997 | numberOfLines={1} |
| 1000 | style={{ color: props.colors.text }} | 998 | style={{ color: props.colors.text }} |
| 1001 | > | 999 | > |
| @@ -1040,8 +1038,8 @@ const ReviewCommentCard = memo(function ReviewCommentCard(props: { · 38 unmodified lines | |||
| 1040 | style={{ | 1038 | style={{ |
| 1041 | color: props.colors.text, | 1039 | color: props.colors.text, |
| 1042 | fontFamily: "ui-monospace", | 1040 | fontFamily: "ui-monospace", |
| 1043 | fontSize: 12, | 1041 | fontSize: MOBILE_CODE_SURFACE.fontSize, |
| 1044 | lineHeight: 18, | 1042 | lineHeight: MOBILE_CODE_SURFACE.rowHeight, |
| 1045 | }} | 1043 | }} |
| 1046 | > | 1044 | > |
| 1047 | {props.comment.diff.trim()} | 1045 | {props.comment.diff.trim()} |
| @@ -1052,7 +1050,7 @@ const ReviewCommentCard = memo(function ReviewCommentCard(props: { · 4 unmodified lines | |||
| 1052 | <View className="border-t px-3 py-3" style={{ borderColor: props.colors.border }}> | 1050 | <View className="border-t px-3 py-3" style={{ borderColor: props.colors.border }}> |
| 1053 | <Text | 1051 | <Text |
| 1054 | selectable | 1052 | selectable |
| 1055 | className="text-[15px] leading-[21px]" | 1053 | className="text-base leading-[21px]" |
| 1056 | style={{ color: props.colors.text }} | 1054 | style={{ color: props.colors.text }} |
| 1057 | > | 1055 | > |
| 1058 | {props.comment.text} | 1056 | {props.comment.text} |
| @@ -152,7 +152,7 @@ export function ThreadNavigationDrawer(props: { · 151 unmodified lines | |||
| 152 | ]} | 152 | ]} |
| 153 | > | 153 | > |
| 154 | <View className="flex-row items-center justify-between px-4 pb-5"> | 154 | <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> |
| 156 | <Pressable | 156 | <Pressable |
| 157 | onPress={() => { | 157 | onPress={() => { |
| 158 | props.onClose(); | 158 | props.onClose(); |
| @@ -224,7 +224,7 @@ function ThreadNavigationDrawerContent(props: { · 65 unmodified lines | |||
| 224 | {groupedThreads.map((group) => ( | 224 | {groupedThreads.map((group) => ( |
| 225 | <View key={group.key} className="gap-3"> | 225 | <View key={group.key} className="gap-3"> |
| 226 | <Text | 226 | <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" |
| 228 | style={{ letterSpacing: -0.2 }} | 228 | style={{ letterSpacing: -0.2 }} |
| 229 | > | 229 | > |
| 230 | {group.title} | 230 | {group.title} |
| @@ -233,9 +233,7 @@ function ThreadNavigationDrawerContent(props: { · 2 unmodified lines | |||
| 233 | <View className="overflow-hidden rounded-[22px] bg-card"> | 233 | <View className="overflow-hidden rounded-[22px] bg-card"> |
| 234 | {group.threads.length === 0 ? ( | 234 | {group.threads.length === 0 ? ( |
| 235 | <View className="px-4 py-4"> | 235 | <View className="px-4 py-4"> |
| 236 | <Text className="text-[14px] font-medium text-foreground-tertiary"> | 236 | <Text className="text-sm font-medium text-foreground-tertiary">No threads yet</Text> |
| 237 | No threads yet | ||
| 238 | </Text> | ||
| 239 | </View> | 237 | </View> |
| 240 | ) : ( | 238 | ) : ( |
| 241 | group.threads.map((thread, index) => { | 239 | group.threads.map((thread, index) => { |
| @@ -260,11 +258,11 @@ function ThreadNavigationDrawerContent(props: { · 18 unmodified lines | |||
| 260 | > | 258 | > |
| 261 | <View className="flex-row items-start justify-between gap-3"> | 259 | <View className="flex-row items-start justify-between gap-3"> |
| 262 | <View className="flex-1 gap-1"> | 260 | <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}> |
| 264 | {thread.title} | 262 | {thread.title} |
| 265 | </Text> | 263 | </Text> |
| 266 | <Text | 264 | <Text |
| 267 | className="text-[13px] font-medium text-foreground-muted" | 265 | className="text-sm font-medium text-foreground-muted" |
| 268 | numberOfLines={1} | 266 | numberOfLines={1} |
| 269 | > | 267 | > |
| 270 | {relativeTime(thread.updatedAt ?? thread.createdAt)} | 268 | {relativeTime(thread.updatedAt ?? thread.createdAt)} |
| @@ -20,6 +20,7 @@ import { EmptyState } from "../../components/EmptyState"; · 19 unmodified lines | |||
| 20 | import { LoadingScreen } from "../../components/LoadingScreen"; | 20 | import { LoadingScreen } from "../../components/LoadingScreen"; |
| 21 | import { buildThreadRoutePath, buildThreadTerminalNavigation } from "../../lib/routes"; | 21 | import { buildThreadRoutePath, buildThreadTerminalNavigation } from "../../lib/routes"; |
| 22 | import { scopedThreadKey } from "../../lib/scopedEntities"; | 22 | import { scopedThreadKey } from "../../lib/scopedEntities"; |
| 23 | import { MOBILE_TYPOGRAPHY } from "../../lib/typography"; | ||
| 23 | import { connectionTone } from "../connection/connectionTone"; | 24 | import { connectionTone } from "../connection/connectionTone"; |
| 24 | 25 | ||
| 25 | import { | 26 | import { |
| @@ -390,7 +391,7 @@ export function ThreadRouteScreen() { · 364 unmodified lines | |||
| 390 | numberOfLines={1} | 391 | numberOfLines={1} |
| 391 | style={{ | 392 | style={{ |
| 392 | fontFamily: "DMSans_700Bold", | 393 | fontFamily: "DMSans_700Bold", |
| 393 | fontSize: 18, | 394 | fontSize: MOBILE_TYPOGRAPHY.headline.fontSize, |
| 394 | fontWeight: "900", | 395 | fontWeight: "900", |
| 395 | color: foregroundColor, | 396 | color: foregroundColor, |
| 396 | letterSpacing: -0.4, | 397 | letterSpacing: -0.4, |
| @@ -402,7 +403,7 @@ export function ThreadRouteScreen() { · 5 unmodified lines | |||
| 402 | numberOfLines={1} | 403 | numberOfLines={1} |
| 403 | style={{ | 404 | style={{ |
| 404 | fontFamily: "DMSans_700Bold", | 405 | fontFamily: "DMSans_700Bold", |
| 405 | fontSize: 12, | 406 | fontSize: MOBILE_TYPOGRAPHY.label.fontSize, |
| 406 | fontWeight: "700", | 407 | fontWeight: "700", |
| 407 | color: secondaryFg, | 408 | color: secondaryFg, |
| 408 | letterSpacing: 0.3, | 409 | letterSpacing: 0.3, |
| @@ -69,7 +69,7 @@ export function GitBranchesSheet() { · 68 unmodified lines | |||
| 69 | > | 69 | > |
| 70 | <View className="gap-2 rounded-[18px] border border-border bg-card px-4 py-4"> | 70 | <View className="gap-2 rounded-[18px] border border-border bg-card px-4 py-4"> |
| 71 | <Text | 71 | <Text |
| 72 | className="text-foreground-secondary text-[11px] font-t3-bold uppercase" | 72 | className="text-foreground-secondary text-2xs font-t3-bold uppercase" |
| 73 | style={{ letterSpacing: 1 }} | 73 | style={{ letterSpacing: 1 }} |
| 74 | > | 74 | > |
| 75 | New branch | 75 | New branch |
| @@ -78,7 +78,7 @@ export function GitBranchesSheet() { · 2 unmodified lines | |||
| 78 | value={newBranchName} | 78 | value={newBranchName} |
| 79 | onChangeText={setNewBranchName} | 79 | onChangeText={setNewBranchName} |
| 80 | placeholder="feature/mobile-polish" | 80 | 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" |
| 82 | style={{ | 82 | style={{ |
| 83 | borderWidth: 1, | 83 | borderWidth: 1, |
| 84 | borderColor: inputBorderColor, | 84 | borderColor: inputBorderColor, |
| @@ -104,7 +104,7 @@ export function GitBranchesSheet() { · 19 unmodified lines | |||
| 104 | 104 | ||
| 105 | <View className="gap-2 rounded-[18px] border border-border bg-card px-4 py-4"> | 105 | <View className="gap-2 rounded-[18px] border border-border bg-card px-4 py-4"> |
| 106 | <Text | 106 | <Text |
| 107 | className="text-foreground-secondary text-[11px] font-t3-bold uppercase" | 107 | className="text-foreground-secondary text-2xs font-t3-bold uppercase" |
| 108 | style={{ letterSpacing: 1 }} | 108 | style={{ letterSpacing: 1 }} |
| 109 | > | 109 | > |
| 110 | New worktree | 110 | New worktree |
| @@ -113,7 +113,7 @@ export function GitBranchesSheet() { · 2 unmodified lines | |||
| 113 | value={worktreeBaseBranch} | 113 | value={worktreeBaseBranch} |
| 114 | onChangeText={setWorktreeBaseBranch} | 114 | onChangeText={setWorktreeBaseBranch} |
| 115 | placeholder="main" | 115 | 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" |
| 117 | style={{ | 117 | style={{ |
| 118 | borderWidth: 1, | 118 | borderWidth: 1, |
| 119 | borderColor: inputBorderColor, | 119 | borderColor: inputBorderColor, |
| @@ -125,7 +125,7 @@ export function GitBranchesSheet() { · 5 unmodified lines | |||
| 125 | value={worktreeBranchName} | 125 | value={worktreeBranchName} |
| 126 | onChangeText={setWorktreeBranchName} | 126 | onChangeText={setWorktreeBranchName} |
| 127 | placeholder="feature/mobile-thread" | 127 | 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" |
| 129 | style={{ | 129 | style={{ |
| 130 | borderWidth: 1, | 130 | borderWidth: 1, |
| 131 | borderColor: inputBorderColor, | 131 | borderColor: inputBorderColor, |
| @@ -154,18 +154,16 @@ export function GitBranchesSheet() { · 22 unmodified lines | |||
| 154 | 154 | ||
| 155 | <View className="gap-2"> | 155 | <View className="gap-2"> |
| 156 | <Text | 156 | <Text |
| 157 | className="text-foreground-secondary text-[11px] font-t3-bold uppercase" | 157 | className="text-foreground-secondary text-2xs font-t3-bold uppercase" |
| 158 | style={{ letterSpacing: 1 }} | 158 | style={{ letterSpacing: 1 }} |
| 159 | > | 159 | > |
| 160 | Existing branches | 160 | Existing branches |
| 161 | </Text> | 161 | </Text> |
| 162 | {branchesLoading ? ( | 162 | {branchesLoading ? ( |
| 163 | <Text className="text-foreground-secondary text-[13px] font-medium"> | 163 | <Text className="text-foreground-secondary text-sm font-medium">Loading branches...</Text> |
| 164 | Loading branches... | ||
| 165 | </Text> | ||
| 166 | ) : null} | 164 | ) : null} |
| 167 | {!branchesLoading && availableBranches.length === 0 ? ( | 165 | {!branchesLoading && availableBranches.length === 0 ? ( |
| 168 | <Text className="text-foreground-secondary text-[13px] font-medium"> | 166 | <Text className="text-foreground-secondary text-sm font-medium"> |
| 169 | No local branches found. | 167 | No local branches found. |
| 170 | </Text> | 168 | </Text> |
| 171 | ) : null} | 169 | ) : null} |
| @@ -195,8 +193,8 @@ export function GitBranchesSheet() { · 23 unmodified lines | |||
| 195 | }} | 193 | }} |
| 196 | > | 194 | > |
| 197 | <View className="absolute inset-0 rounded-[18px] bg-card" /> | 195 | <View className="absolute inset-0 rounded-[18px] bg-card" /> |
| 198 | <Text className="text-foreground text-[15px] font-t3-bold">{branch.name}</Text> | 196 | <Text className="text-foreground text-base font-t3-bold">{branch.name}</Text> |
| 199 | <Text className="text-foreground-secondary text-[12px] font-medium">{subtitle}</Text> | 197 | <Text className="text-foreground-secondary text-xs font-medium">{subtitle}</Text> |
| 200 | </Pressable> | 198 | </Pressable> |
| 201 | ); | 199 | ); |
| 202 | })} | 200 | })} |
| @@ -79,14 +79,14 @@ export function GitCommitSheet() { · 78 unmodified lines | |||
| 79 | > | 79 | > |
| 80 | <View className="gap-3 rounded-[22px] border border-border bg-card px-4 py-4"> | 80 | <View className="gap-3 rounded-[22px] border border-border bg-card px-4 py-4"> |
| 81 | <View className="flex-row items-center justify-between gap-3"> | 81 | <View className="flex-row items-center justify-between gap-3"> |
| 82 | <Text className="text-foreground-muted text-[13px] font-medium">Branch</Text> | 82 | <Text className="text-foreground-muted text-sm font-medium">Branch</Text> |
| 83 | <Text className="text-foreground text-[15px] font-t3-bold"> | 83 | <Text className="text-foreground text-base font-t3-bold"> |
| 84 | {gitStatus.data?.refName ?? "(detached HEAD)"} | 84 | {gitStatus.data?.refName ?? "(detached HEAD)"} |
| 85 | </Text> | 85 | </Text> |
| 86 | </View> | 86 | </View> |
| 87 | {isDefaultRef ? ( | 87 | {isDefaultRef ? ( |
| 88 | <Text | 88 | <Text |
| 89 | className="text-[12px] leading-[18px]" | 89 | className="text-xs leading-[18px]" |
| 90 | style={{ color: isDarkMode ? "#fbbf24" : "#b45309" }} | 90 | style={{ color: isDarkMode ? "#fbbf24" : "#b45309" }} |
| 91 | > | 91 | > |
| 92 | Warning: this is the default branch. | 92 | Warning: this is the default branch. |
| @@ -97,8 +97,8 @@ export function GitCommitSheet() { · 4 unmodified lines | |||
| 97 | <View className="gap-3 rounded-[22px] border border-border bg-card px-4 py-4"> | 97 | <View className="gap-3 rounded-[22px] border border-border bg-card px-4 py-4"> |
| 98 | <View className="flex-row items-center justify-between gap-3"> | 98 | <View className="flex-row items-center justify-between gap-3"> |
| 99 | <View className="gap-1"> | 99 | <View className="gap-1"> |
| 100 | <Text className="text-foreground text-[16px] font-t3-bold">Files</Text> | 100 | <Text className="text-foreground text-base font-t3-bold">Files</Text> |
| 101 | <Text className="text-foreground-muted text-[12px] leading-[18px]"> | 101 | <Text className="text-foreground-muted text-xs leading-[18px]"> |
| 102 | {selectedFiles.length} selected · +{selectedInsertions} / -{selectedDeletions} | 102 | {selectedFiles.length} selected · +{selectedInsertions} / -{selectedDeletions} |
| 103 | </Text> | 103 | </Text> |
| 104 | </View> | 104 | </View> |
| @@ -108,14 +108,14 @@ export function GitCommitSheet() { · 3 unmodified lines | |||
| 108 | className="bg-subtle rounded-full px-3 py-2" | 108 | className="bg-subtle rounded-full px-3 py-2" |
| 109 | onPress={() => setExcludedFiles(new Set())} | 109 | onPress={() => setExcludedFiles(new Set())} |
| 110 | > | 110 | > |
| 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> |
| 112 | </Pressable> | 112 | </Pressable> |
| 113 | ) : null} | 113 | ) : null} |
| 114 | <Pressable | 114 | <Pressable |
| 115 | className="bg-subtle rounded-full px-3 py-2" | 115 | className="bg-subtle rounded-full px-3 py-2" |
| 116 | onPress={() => setIsEditingFiles((current) => !current)} | 116 | onPress={() => setIsEditingFiles((current) => !current)} |
| 117 | > | 117 | > |
| 118 | <Text className="text-foreground text-[11px] font-t3-bold uppercase"> | 118 | <Text className="text-foreground text-2xs font-t3-bold uppercase"> |
| 119 | {isEditingFiles ? "Done" : "Edit"} | 119 | {isEditingFiles ? "Done" : "Edit"} |
| 120 | </Text> | 120 | </Text> |
| 121 | </Pressable> | 121 | </Pressable> |
| @@ -123,26 +123,26 @@ export function GitCommitSheet() { · 1 unmodified line | |||
| 123 | </View> | 123 | </View> |
| 124 | 124 | ||
| 125 | {allFiles.length === 0 ? ( | 125 | {allFiles.length === 0 ? ( |
| 126 | <Text className="text-foreground-secondary text-[13px] leading-[19px]"> | 126 | <Text className="text-foreground-secondary text-sm leading-[19px]"> |
| 127 | No changed files are available to commit. | 127 | No changed files are available to commit. |
| 128 | </Text> | 128 | </Text> |
| 129 | ) : !isEditingFiles ? ( | 129 | ) : !isEditingFiles ? ( |
| 130 | <View className="gap-2"> | 130 | <View className="gap-2"> |
| 131 | {selectedFilePreview.map((file) => ( | 131 | {selectedFilePreview.map((file) => ( |
| 132 | <View key={file.path} className="flex-row items-center justify-between gap-3"> | 132 | <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}> |
| 134 | {file.path} | 134 | {file.path} |
| 135 | </Text> | 135 | </Text> |
| 136 | <Text className="text-[12px] font-t3-bold" style={{ color: "#10b981" }}> | 136 | <Text className="text-xs font-t3-bold" style={{ color: "#10b981" }}> |
| 137 | +{file.insertions} | 137 | +{file.insertions} |
| 138 | </Text> | 138 | </Text> |
| 139 | <Text className="text-[12px] font-t3-bold" style={{ color: "#f43f5e" }}> | 139 | <Text className="text-xs font-t3-bold" style={{ color: "#f43f5e" }}> |
| 140 | -{file.deletions} | 140 | -{file.deletions} |
| 141 | </Text> | 141 | </Text> |
| 142 | </View> | 142 | </View> |
| 143 | ))} | 143 | ))} |
| 144 | {selectedFiles.length > selectedFilePreview.length ? ( | 144 | {selectedFiles.length > selectedFilePreview.length ? ( |
| 145 | <Text className="text-foreground-muted text-[12px] leading-[17px]"> | 145 | <Text className="text-foreground-muted text-xs leading-[17px]"> |
| 146 | +{selectedFiles.length - selectedFilePreview.length} more files | 146 | +{selectedFiles.length - selectedFilePreview.length} more files |
| 147 | </Text> | 147 | </Text> |
| 148 | ) : null} | 148 | ) : null} |
| @@ -177,21 +177,21 @@ export function GitCommitSheet() { · 28 unmodified lines | |||
| 177 | <View className="flex-1 gap-1"> | 177 | <View className="flex-1 gap-1"> |
| 178 | <Text | 178 | <Text |
| 179 | selectable | 179 | 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"}`} |
| 181 | > | 181 | > |
| 182 | {file.path} | 182 | {file.path} |
| 183 | </Text> | 183 | </Text> |
| 184 | {!included ? ( | 184 | {!included ? ( |
| 185 | <Text className="text-foreground-muted text-[11px] leading-[16px]"> | 185 | <Text className="text-foreground-muted text-2xs leading-[16px]"> |
| 186 | Excluded from this commit | 186 | Excluded from this commit |
| 187 | </Text> | 187 | </Text> |
| 188 | ) : null} | 188 | ) : null} |
| 189 | </View> | 189 | </View> |
| 190 | <View className="items-end gap-1"> | 190 | <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" }}> |
| 192 | +{file.insertions} | 192 | +{file.insertions} |
| 193 | </Text> | 193 | </Text> |
| 194 | <Text className="text-[12px] font-t3-bold" style={{ color: "#f43f5e" }}> | 194 | <Text className="text-xs font-t3-bold" style={{ color: "#f43f5e" }}> |
| 195 | -{file.deletions} | 195 | -{file.deletions} |
| 196 | </Text> | 196 | </Text> |
| 197 | </View> | 197 | </View> |
| @@ -204,14 +204,14 @@ export function GitCommitSheet() { · 6 unmodified lines | |||
| 204 | </View> | 204 | </View> |
| 205 | 205 | ||
| 206 | <View className="gap-2"> | 206 | <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> |
| 208 | <TextInput | 208 | <TextInput |
| 209 | multiline | 209 | multiline |
| 210 | value={dialogCommitMessage} | 210 | value={dialogCommitMessage} |
| 211 | onChangeText={setDialogCommitMessage} | 211 | onChangeText={setDialogCommitMessage} |
| 212 | placeholder="Leave empty to auto-generate" | 212 | placeholder="Leave empty to auto-generate" |
| 213 | textAlignVertical="top" | 213 | 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" |
| 215 | style={{ | 215 | style={{ |
| 216 | minHeight: 128, | 216 | minHeight: 128, |
| 217 | borderWidth: 1, | 217 | borderWidth: 1, |
| @@ -90,15 +90,15 @@ export function GitConfirmSheet() { · 89 unmodified lines | |||
| 90 | 90 | ||
| 91 | <View className="items-center gap-1 px-5 pb-3 pt-4"> | 91 | <View className="items-center gap-1 px-5 pb-3 pt-4"> |
| 92 | <Text | 92 | <Text |
| 93 | className="text-[12px] font-t3-bold uppercase text-foreground-muted" | 93 | className="text-xs font-t3-bold uppercase text-foreground-muted" |
| 94 | style={{ letterSpacing: 1 }} | 94 | style={{ letterSpacing: 1 }} |
| 95 | > | 95 | > |
| 96 | Confirm | 96 | Confirm |
| 97 | </Text> | 97 | </Text> |
| 98 | <Text className="text-center text-[28px] font-t3-bold"> | 98 | <Text className="text-center text-3xl font-t3-bold"> |
| 99 | {copy?.title ?? "Run action on default branch?"} | 99 | {copy?.title ?? "Run action on default branch?"} |
| 100 | </Text> | 100 | </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]"> |
| 102 | {copy?.description ?? "Choose how to continue."} | 102 | {copy?.description ?? "Choose how to continue."} |
| 103 | </Text> | 103 | </Text> |
| 104 | </View> | 104 | </View> |
| @@ -175,13 +175,13 @@ export function GitOverviewSheet() { · 174 unmodified lines | |||
| 175 | /> | 175 | /> |
| 176 | </Pressable> | 176 | </Pressable> |
| 177 | <Text | 177 | <Text |
| 178 | className="text-[12px] font-t3-bold uppercase text-foreground-muted" | 178 | className="text-xs font-t3-bold uppercase text-foreground-muted" |
| 179 | style={{ letterSpacing: 1 }} | 179 | style={{ letterSpacing: 1 }} |
| 180 | > | 180 | > |
| 181 | Branch | 181 | Branch |
| 182 | </Text> | 182 | </Text> |
| 183 | <Text className="text-[28px] font-t3-bold">{currentBranchLabel}</Text> | 183 | <Text className="text-3xl font-t3-bold">{currentBranchLabel}</Text> |
| 184 | <Text className="text-foreground-secondary text-[13px] font-medium leading-[19px]"> | 184 | <Text className="text-foreground-secondary text-sm font-medium leading-[19px]"> |
| 185 | {statusSummary(gitStatus.data)} | 185 | {statusSummary(gitStatus.data)} |
| 186 | </Text> | 186 | </Text> |
| 187 | </View> | 187 | </View> |
| @@ -56,7 +56,7 @@ export function SheetActionButton(props: { · 55 unmodified lines | |||
| 56 | > | 56 | > |
| 57 | <SymbolView name={props.icon} size={16} tintColor={colors.textColor} type="monochrome" /> | 57 | <SymbolView name={props.icon} size={16} tintColor={colors.textColor} type="monochrome" /> |
| 58 | <Text | 58 | <Text |
| 59 | className="text-[12px] font-t3-bold uppercase" | 59 | className="text-xs font-t3-bold uppercase" |
| 60 | style={{ color: colors.textColor, letterSpacing: 0.9 }} | 60 | style={{ color: colors.textColor, letterSpacing: 0.9 }} |
| 61 | > | 61 | > |
| 62 | {props.label} | 62 | {props.label} |
| @@ -69,12 +69,12 @@ export function MetaCard(props: { readonly label: string; readonly value: string · 6 unmodified lines | |||
| 69 | return ( | 69 | return ( |
| 70 | <View className="rounded-[18px] border border-border bg-card px-4 py-3"> | 70 | <View className="rounded-[18px] border border-border bg-card px-4 py-3"> |
| 71 | <Text | 71 | <Text |
| 72 | className="text-foreground-muted text-[11px] font-t3-bold uppercase" | 72 | className="text-foreground-muted text-2xs font-t3-bold uppercase" |
| 73 | style={{ letterSpacing: 0.9 }} | 73 | style={{ letterSpacing: 0.9 }} |
| 74 | > | 74 | > |
| 75 | {props.label} | 75 | {props.label} |
| 76 | </Text> | 76 | </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}> |
| 78 | {props.value} | 78 | {props.value} |
| 79 | </Text> | 79 | </Text> |
| 80 | </View> | 80 | </View> |
| @@ -102,9 +102,9 @@ export function SheetListRow(props: { · 21 unmodified lines | |||
| 102 | <SymbolView name={props.icon} size={16} tintColor={iconColor} type="monochrome" /> | 102 | <SymbolView name={props.icon} size={16} tintColor={iconColor} type="monochrome" /> |
| 103 | </View> | 103 | </View> |
| 104 | <View className="flex-1 gap-0.5"> | 104 | <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> |
| 106 | {props.subtitle ? ( | 106 | {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> |
| 108 | ) : null} | 108 | ) : null} |
| 109 | </View> | 109 | </View> |
| 110 | <SymbolView name="chevron.right" size={13} tintColor={iconSubtleColor} type="monochrome" /> | 110 | <SymbolView name="chevron.right" size={13} tintColor={iconSubtleColor} type="monochrome" /> |
| @@ -98,7 +98,7 @@ export function ThreadWorkLog(props: { · 97 unmodified lines | |||
| 98 | return ( | 98 | return ( |
| 99 | <View className="-mx-1 mb-3 px-1 py-0.5"> | 99 | <View className="-mx-1 mb-3 px-1 py-0.5"> |
| 100 | {!onlyToolRows ? ( | 100 | {!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"> |
| 102 | work log | 102 | work log |
| 103 | </Text> | 103 | </Text> |
| 104 | ) : null} | 104 | ) : null} |
| @@ -164,7 +164,7 @@ export function ThreadWorkLog(props: { · 59 unmodified lines | |||
| 164 | 164 | ||
| 165 | <View className="shrink-0 flex-row items-center gap-px"> | 165 | <View className="shrink-0 flex-row items-center gap-px"> |
| 166 | {props.copiedRowId === row.id ? ( | 166 | {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"> |
| 168 | Copied | 168 | Copied |
| 169 | </Text> | 169 | </Text> |
| 170 | ) : null} | 170 | ) : null} |
| @@ -209,7 +209,7 @@ export function ThreadWorkLog(props: { · 38 unmodified lines | |||
| 209 | > | 209 | > |
| 210 | <Text | 210 | <Text |
| 211 | selectable | 211 | selectable |
| 212 | className="text-[11px] leading-[17px] text-foreground-muted" | 212 | className="text-2xs leading-[17px] text-foreground-muted" |
| 213 | style={{ fontFamily: "ui-monospace" }} | 213 | style={{ fontFamily: "ui-monospace" }} |
| 214 | > | 214 | > |
| 215 | {row.fullDetail} | 215 | {row.fullDetail} |
| @@ -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 | }); | ||
| @@ -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; | ||
| @@ -6,6 +6,7 @@ import { Image, StyleSheet } from "react-native"; · 5 unmodified lines | |||
| 6 | 6 | ||
| 7 | import { markdownFileIconSource } from "@t3tools/mobile-markdown-text/file-icons"; | 7 | import { markdownFileIconSource } from "@t3tools/mobile-markdown-text/file-icons"; |
| 8 | import { resolveMarkdownFileIcon } from "@t3tools/mobile-markdown-text/links"; | 8 | import { resolveMarkdownFileIcon } from "@t3tools/mobile-markdown-text/links"; |
| 9 | import { MOBILE_TYPOGRAPHY } from "../lib/typography"; | ||
| 9 | import { useThemeColor } from "../lib/useThemeColor"; | 10 | import { useThemeColor } from "../lib/useThemeColor"; |
| 10 | import type { ComposerEditorProps, ComposerEditorSelection } from "./T3ComposerEditor.types"; | 11 | import type { ComposerEditorProps, ComposerEditorSelection } from "./T3ComposerEditor.types"; |
| 11 | 12 | ||
| @@ -150,9 +151,15 @@ export function ComposerEditor({ · 138 unmodified lines | |||
| 150 | ? resolvedTextStyle.fontFamily | 151 | ? resolvedTextStyle.fontFamily |
| 151 | : "DMSans_400Regular" | 152 | : "DMSans_400Regular" |
| 152 | } | 153 | } |
| 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 | } | ||
| 154 | lineHeight={ | 159 | lineHeight={ |
| 155 | typeof resolvedTextStyle.lineHeight === "number" ? resolvedTextStyle.lineHeight : 22 | 160 | typeof resolvedTextStyle.lineHeight === "number" |
| 161 | ? resolvedTextStyle.lineHeight | ||
| 162 | : MOBILE_TYPOGRAPHY.composer.lineHeight | ||
| 156 | } | 163 | } |
| 157 | contentInsetVertical={contentInsetVertical} | 164 | contentInsetVertical={contentInsetVertical} |
| 158 | editable={props.editable ?? true} | 165 | editable={props.editable ?? true} |
| @@ -2,6 +2,7 @@ import { TextInputWrapper } from "expo-paste-input"; · 1 unmodified line | |||
| 2 | import { useImperativeHandle, useRef } from "react"; | 2 | import { useImperativeHandle, useRef } from "react"; |
| 3 | import { TextInput, type TextInput as RNTextInput } from "react-native"; | 3 | import { TextInput, type TextInput as RNTextInput } from "react-native"; |
| 4 | 4 | ||
| 5 | import { MOBILE_TYPOGRAPHY } from "../lib/typography"; | ||
| 5 | import { useThemeColor } from "../lib/useThemeColor"; | 6 | import { useThemeColor } from "../lib/useThemeColor"; |
| 6 | import { useNativePaste } from "../lib/useNativePaste"; | 7 | import { useNativePaste } from "../lib/useNativePaste"; |
| 7 | import type { ComposerEditorProps } from "./T3ComposerEditor.types"; | 8 | import type { ComposerEditorProps } from "./T3ComposerEditor.types"; |
| @@ -47,8 +48,7 @@ export function ComposerEditor({ · 39 unmodified lines | |||
| 47 | minHeight: 0, | 48 | minHeight: 0, |
| 48 | color: foregroundColor, | 49 | color: foregroundColor, |
| 49 | fontFamily: "DMSans_400Regular", | 50 | fontFamily: "DMSans_400Regular", |
| 50 | fontSize: 15, | 51 | ...MOBILE_TYPOGRAPHY.composer, |
| 51 | lineHeight: 22, | ||
| 52 | paddingVertical: contentInsetVertical, | 52 | paddingVertical: contentInsetVertical, |
| 53 | }, | 53 | }, |
| 54 | textStyle, | 54 | textStyle, |
| @@ -4,27 +4,26 @@ import { expect, it } from "@effect/vitest"; · 3 unmodified lines | |||
| 4 | import * as Effect from "effect/Effect"; | 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as Layer from "effect/Layer"; | 5 | import * as Layer from "effect/Layer"; |
| 6 | 6 | ||
| 7 | import type { ServerConfigShape } from "../config.ts"; | 7 | import * as ServerConfig from "../config.ts"; |
| 8 | import { ServerConfig } from "../config.ts"; | ||
| 9 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | 8 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; |
| 10 | import * as PairingGrantStore from "./PairingGrantStore.ts"; | 9 | import * as PairingGrantStore from "./PairingGrantStore.ts"; |
| 11 | import * as EnvironmentAuth from "./EnvironmentAuth.ts"; | 10 | import * as EnvironmentAuth from "./EnvironmentAuth.ts"; |
| 12 | 11 | ||
| 13 | import * as ServerSecretStore from "./ServerSecretStore.ts"; | 12 | import * as ServerSecretStore from "./ServerSecretStore.ts"; |
| 14 | 13 | ||
| 15 | const makeServerConfigLayer = (overrides?: Partial<ServerConfigShape>) => | 14 | const makeServerConfigLayer = (overrides?: Partial<ServerConfig.ServerConfig["Service"]>) => |
| 16 | Layer.effect( | 15 | Layer.effect( |
| 17 | ServerConfig, | 16 | ServerConfig.ServerConfig, |
| 18 | Effect.gen(function* () { | 17 | Effect.gen(function* () { |
| 19 | const config = yield* ServerConfig; | 18 | const config = yield* ServerConfig.ServerConfig; |
| 20 | return { | 19 | return { |
| 21 | ...config, | 20 | ...config, |
| 22 | ...overrides, | 21 | ...overrides, |
| 23 | } satisfies ServerConfigShape; | 22 | } satisfies ServerConfig.ServerConfig["Service"]; |
| 24 | }), | 23 | }), |
| 25 | ).pipe(Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-auth-server-test-" }))); | 24 | ).pipe(Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-auth-server-test-" }))); |
| 26 | 25 | ||
| 27 | const makeEnvironmentAuthLayer = (overrides?: Partial<ServerConfigShape>) => | 26 | const makeEnvironmentAuthLayer = (overrides?: Partial<ServerConfig.ServerConfig["Service"]>) => |
| 28 | EnvironmentAuth.layer.pipe( | 27 | EnvironmentAuth.layer.pipe( |
| 29 | Layer.provide(SqlitePersistenceMemory), | 28 | Layer.provide(SqlitePersistenceMemory), |
| 30 | Layer.provide(ServerSecretStore.layer), | 29 | Layer.provide(ServerSecretStore.layer), |
| @@ -33,13 +32,15 @@ const makeEnvironmentAuthLayer = (overrides?: Partial<ServerConfigShape>) => · 2 unmodified lines | |||
| 33 | 32 | ||
| 34 | const makeCookieRequest = ( | 33 | const makeCookieRequest = ( |
| 35 | sessionToken: string, | 34 | sessionToken: string, |
| 36 | ): Parameters<EnvironmentAuth.EnvironmentAuthShape["authenticateHttpRequest"]>[0] => | 35 | ): Parameters<EnvironmentAuth.EnvironmentAuth["Service"]["authenticateHttpRequest"]>[0] => |
| 37 | ({ | 36 | ({ |
| 38 | cookies: { | 37 | cookies: { |
| 39 | t3_session: sessionToken, | 38 | t3_session: sessionToken, |
| 40 | }, | 39 | }, |
| 41 | headers: {}, | 40 | headers: {}, |
| 42 | }) as unknown as Parameters<EnvironmentAuth.EnvironmentAuthShape["authenticateHttpRequest"]>[0]; | 41 | }) as unknown as Parameters< |
| 42 | EnvironmentAuth.EnvironmentAuth["Service"]["authenticateHttpRequest"] | ||
| 43 | >[0]; | ||
| 43 | 44 | ||
| 44 | const requestMetadata = { | 45 | const requestMetadata = { |
| 45 | deviceType: "desktop" as const, | 46 | deviceType: "desktop" as const, |
| @@ -3,31 +3,30 @@ import { expect, it } from "@effect/vitest"; · 2 unmodified lines | |||
| 3 | import * as Effect from "effect/Effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as Layer from "effect/Layer"; | 4 | import * as Layer from "effect/Layer"; |
| 5 | 5 | ||
| 6 | import type { ServerConfigShape } from "../config.ts"; | 6 | import * as ServerConfig from "../config.ts"; |
| 7 | import { ServerConfig } from "../config.ts"; | ||
| 8 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | 7 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; |
| 9 | import * as EnvironmentAuth from "./EnvironmentAuth.ts"; | 8 | import * as EnvironmentAuth from "./EnvironmentAuth.ts"; |
| 10 | import * as ServerSecretStore from "./ServerSecretStore.ts"; | 9 | import * as ServerSecretStore from "./ServerSecretStore.ts"; |
| 11 | import * as SessionStore from "./SessionStore.ts"; | 10 | import * as SessionStore from "./SessionStore.ts"; |
| 12 | 11 | ||
| 13 | const makeServerConfigLayer = ( | 12 | const makeServerConfigLayer = ( |
| 14 | overrides?: Partial<Pick<ServerConfigShape, "desktopBootstrapToken">>, | 13 | overrides?: Partial<Pick<ServerConfig.ServerConfig["Service"], "desktopBootstrapToken">>, |
| 15 | ) => | 14 | ) => |
| 16 | Layer.effect( | 15 | Layer.effect( |
| 17 | ServerConfig, | 16 | ServerConfig.ServerConfig, |
| 18 | Effect.gen(function* () { | 17 | Effect.gen(function* () { |
| 19 | const config = yield* ServerConfig; | 18 | const config = yield* ServerConfig.ServerConfig; |
| 20 | return { | 19 | return { |
| 21 | ...config, | 20 | ...config, |
| 22 | ...overrides, | 21 | ...overrides, |
| 23 | } satisfies ServerConfigShape; | 22 | } satisfies ServerConfig.ServerConfig["Service"]; |
| 24 | }), | 23 | }), |
| 25 | ).pipe( | 24 | ).pipe( |
| 26 | Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-auth-control-plane-test-" })), | 25 | Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-auth-control-plane-test-" })), |
| 27 | ); | 26 | ); |
| 28 | 27 | ||
| 29 | const makeEnvironmentAuthLayer = ( | 28 | const makeEnvironmentAuthLayer = ( |
| 30 | overrides?: Partial<Pick<ServerConfigShape, "desktopBootstrapToken">>, | 29 | overrides?: Partial<Pick<ServerConfig.ServerConfig["Service"], "desktopBootstrapToken">>, |
| 31 | ) => | 30 | ) => |
| 32 | EnvironmentAuth.layer.pipe( | 31 | EnvironmentAuth.layer.pipe( |
| 33 | Layer.provideMerge(ServerSecretStore.layer), | 32 | Layer.provideMerge(ServerSecretStore.layer), |
| @@ -3,21 +3,22 @@ import { expect, it } from "@effect/vitest"; · 2 unmodified lines | |||
| 3 | import * as Effect from "effect/Effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as Layer from "effect/Layer"; | 4 | import * as Layer from "effect/Layer"; |
| 5 | 5 | ||
| 6 | import type { ServerConfigShape } from "../config.ts"; | 6 | import * as ServerConfig from "../config.ts"; |
| 7 | import { ServerConfig } from "../config.ts"; | ||
| 8 | import * as EnvironmentAuthPolicy from "./EnvironmentAuthPolicy.ts"; | 7 | import * as EnvironmentAuthPolicy from "./EnvironmentAuthPolicy.ts"; |
| 9 | 8 | ||
| 10 | const makeEnvironmentAuthPolicyLayer = (overrides?: Partial<ServerConfigShape>) => | 9 | const makeEnvironmentAuthPolicyLayer = ( |
| 10 | overrides?: Partial<ServerConfig.ServerConfig["Service"]>, | ||
| 11 | ) => | ||
| 11 | EnvironmentAuthPolicy.layer.pipe( | 12 | EnvironmentAuthPolicy.layer.pipe( |
| 12 | Layer.provide( | 13 | Layer.provide( |
| 13 | Layer.effect( | 14 | Layer.effect( |
| 14 | ServerConfig, | 15 | ServerConfig.ServerConfig, |
| 15 | Effect.gen(function* () { | 16 | Effect.gen(function* () { |
| 16 | const config = yield* ServerConfig; | 17 | const config = yield* ServerConfig.ServerConfig; |
| 17 | return { | 18 | return { |
| 18 | ...config, | 19 | ...config, |
| 19 | ...overrides, | 20 | ...overrides, |
| 20 | } satisfies ServerConfigShape; | 21 | } satisfies ServerConfig.ServerConfig["Service"]; |
| 21 | }), | 22 | }), |
| 22 | ).pipe( | 23 | ).pipe( |
| 23 | Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-auth-policy-test-" })), | 24 | Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-auth-policy-test-" })), |
| @@ -5,29 +5,28 @@ import * as Effect from "effect/Effect"; · 4 unmodified lines | |||
| 5 | import * as Layer from "effect/Layer"; | 5 | import * as Layer from "effect/Layer"; |
| 6 | import * as TestClock from "effect/testing/TestClock"; | 6 | import * as TestClock from "effect/testing/TestClock"; |
| 7 | 7 | ||
| 8 | import type { ServerConfigShape } from "../config.ts"; | 8 | import * as ServerConfig from "../config.ts"; |
| 9 | import { ServerConfig } from "../config.ts"; | ||
| 10 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | 9 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; |
| 11 | import * as PairingGrantStore from "./PairingGrantStore.ts"; | 10 | import * as PairingGrantStore from "./PairingGrantStore.ts"; |
| 12 | 11 | ||
| 13 | const makeServerConfigLayer = ( | 12 | const makeServerConfigLayer = ( |
| 14 | overrides?: Partial<Pick<ServerConfigShape, "desktopBootstrapToken">>, | 13 | overrides?: Partial<Pick<ServerConfig.ServerConfig["Service"], "desktopBootstrapToken">>, |
| 15 | ) => | 14 | ) => |
| 16 | Layer.effect( | 15 | Layer.effect( |
| 17 | ServerConfig, | 16 | ServerConfig.ServerConfig, |
| 18 | Effect.gen(function* () { | 17 | Effect.gen(function* () { |
| 19 | const config = yield* ServerConfig; | 18 | const config = yield* ServerConfig.ServerConfig; |
| 20 | return { | 19 | return { |
| 21 | ...config, | 20 | ...config, |
| 22 | ...overrides, | 21 | ...overrides, |
| 23 | } satisfies ServerConfigShape; | 22 | } satisfies ServerConfig.ServerConfig["Service"]; |
| 24 | }), | 23 | }), |
| 25 | ).pipe( | 24 | ).pipe( |
| 26 | Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-auth-bootstrap-test-" })), | 25 | Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-auth-bootstrap-test-" })), |
| 27 | ); | 26 | ); |
| 28 | 27 | ||
| 29 | const makePairingGrantStoreLayer = ( | 28 | const makePairingGrantStoreLayer = ( |
| 30 | overrides?: Partial<Pick<ServerConfigShape, "desktopBootstrapToken">>, | 29 | overrides?: Partial<Pick<ServerConfig.ServerConfig["Service"], "desktopBootstrapToken">>, |
| 31 | ) => | 30 | ) => |
| 32 | PairingGrantStore.layer.pipe( | 31 | PairingGrantStore.layer.pipe( |
| 33 | Layer.provide(SqlitePersistenceMemory), | 32 | Layer.provide(SqlitePersistenceMemory), |
| @@ -5,30 +5,29 @@ import * as Effect from "effect/Effect"; · 4 unmodified lines | |||
| 5 | import * as Layer from "effect/Layer"; | 5 | import * as Layer from "effect/Layer"; |
| 6 | import * as TestClock from "effect/testing/TestClock"; | 6 | import * as TestClock from "effect/testing/TestClock"; |
| 7 | 7 | ||
| 8 | import type { ServerConfigShape } from "../config.ts"; | 8 | import * as ServerConfig from "../config.ts"; |
| 9 | import { ServerConfig } from "../config.ts"; | ||
| 10 | import { PersistenceSqlError } from "../persistence/Errors.ts"; | 9 | import { PersistenceSqlError } from "../persistence/Errors.ts"; |
| 11 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; | 10 | import { SqlitePersistenceMemory } from "../persistence/Layers/Sqlite.ts"; |
| 12 | import { AuthSessionRepository } from "../persistence/Services/AuthSessions.ts"; | 11 | import * as AuthSessions from "../persistence/Services/AuthSessions.ts"; |
| 13 | import * as SessionStore from "./SessionStore.ts"; | 12 | import * as SessionStore from "./SessionStore.ts"; |
| 14 | import * as ServerSecretStore from "./ServerSecretStore.ts"; | 13 | import * as ServerSecretStore from "./ServerSecretStore.ts"; |
| 15 | 14 | ||
| 16 | const makeServerConfigLayer = ( | 15 | const makeServerConfigLayer = ( |
| 17 | overrides?: Partial<Pick<ServerConfigShape, "desktopBootstrapToken">>, | 16 | overrides?: Partial<Pick<ServerConfig.ServerConfig["Service"], "desktopBootstrapToken">>, |
| 18 | ) => | 17 | ) => |
| 19 | Layer.effect( | 18 | Layer.effect( |
| 20 | ServerConfig, | 19 | ServerConfig.ServerConfig, |
| 21 | Effect.gen(function* () { | 20 | Effect.gen(function* () { |
| 22 | const config = yield* ServerConfig; | 21 | const config = yield* ServerConfig.ServerConfig; |
| 23 | return { | 22 | return { |
| 24 | ...config, | 23 | ...config, |
| 25 | ...overrides, | 24 | ...overrides, |
| 26 | } satisfies ServerConfigShape; | 25 | } satisfies ServerConfig.ServerConfig["Service"]; |
| 27 | }), | 26 | }), |
| 28 | ).pipe(Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-auth-session-test-" }))); | 27 | ).pipe(Layer.provide(ServerConfig.layerTest(process.cwd(), { prefix: "t3-auth-session-test-" }))); |
| 29 | 28 | ||
| 30 | const makeSessionStoreLayer = ( | 29 | const makeSessionStoreLayer = ( |
| 31 | overrides?: Partial<Pick<ServerConfigShape, "desktopBootstrapToken">>, | 30 | overrides?: Partial<Pick<ServerConfig.ServerConfig["Service"], "desktopBootstrapToken">>, |
| 32 | ) => | 31 | ) => |
| 33 | SessionStore.layer.pipe( | 32 | SessionStore.layer.pipe( |
| 34 | Layer.provide(SqlitePersistenceMemory), | 33 | Layer.provide(SqlitePersistenceMemory), |
| @@ -41,7 +40,7 @@ const repositoryFailure = new PersistenceSqlError({ · 6 unmodified lines | |||
| 41 | detail: "sqlite is unavailable", | 40 | detail: "sqlite is unavailable", |
| 42 | }); | 41 | }); |
| 43 | 42 | ||
| 44 | const failingSessionLookupRepositoryLayer = Layer.succeed(AuthSessionRepository, { | 43 | const failingSessionLookupRepositoryLayer = Layer.succeed(AuthSessions.AuthSessionRepository, { |
| 45 | create: () => Effect.void, | 44 | create: () => Effect.void, |
| 46 | getById: () => Effect.fail(repositoryFailure), | 45 | getById: () => Effect.fail(repositoryFailure), |
| 47 | listActive: () => Effect.succeed([]), | 46 | listActive: () => Effect.succeed([]), |
| @@ -20,8 +20,8 @@ import * as TestConsole from "effect/testing/TestConsole"; · 19 unmodified lines | |||
| 20 | import { Command } from "effect/unstable/cli"; | 20 | import { Command } from "effect/unstable/cli"; |
| 21 | 21 | ||
| 22 | import { cli, makeCli } from "./bin.ts"; | 22 | import { cli, makeCli } from "./bin.ts"; |
| 23 | import { deriveServerPaths, ServerConfig, type ServerConfigShape } from "./config.ts"; | 23 | import * as ServerConfig from "./config.ts"; |
| 24 | import { ProjectionSnapshotQuery } from "./orchestration/Services/ProjectionSnapshotQuery.ts"; | 24 | import * as ProjectionSnapshotQuery from "./orchestration/Services/ProjectionSnapshotQuery.ts"; |
| 25 | import { OrchestrationLayerLive } from "./orchestration/runtimeLayer.ts"; | 25 | import { OrchestrationLayerLive } from "./orchestration/runtimeLayer.ts"; |
| 26 | import { orchestrationHttpApiLayer } from "./orchestration/http.ts"; | 26 | import { orchestrationHttpApiLayer } from "./orchestration/http.ts"; |
| 27 | import { layerConfig as SqlitePersistenceLayerLive } from "./persistence/Layers/Sqlite.ts"; | 27 | 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 | |||
| 57 | 57 | ||
| 58 | const makeCliTestServerConfig = (baseDir: string) => | 58 | const makeCliTestServerConfig = (baseDir: string) => |
| 59 | Effect.gen(function* () { | 59 | Effect.gen(function* () { |
| 60 | const derivedPaths = yield* deriveServerPaths(baseDir, undefined); | 60 | const derivedPaths = yield* ServerConfig.deriveServerPaths(baseDir, undefined); |
| 61 | return { | 61 | return { |
| 62 | logLevel: "Info", | 62 | logLevel: "Info", |
| 63 | traceMinLevel: "Info", | 63 | traceMinLevel: "Info", |
| @@ -84,26 +84,23 @@ const makeCliTestServerConfig = (baseDir: string) => · 20 unmodified lines | |||
| 84 | logWebSocketEvents: false, | 84 | logWebSocketEvents: false, |
| 85 | tailscaleServeEnabled: false, | 85 | tailscaleServeEnabled: false, |
| 86 | tailscaleServePort: 443, | 86 | tailscaleServePort: 443, |
| 87 | } satisfies ServerConfigShape; | 87 | } satisfies ServerConfig.ServerConfig["Service"]; |
| 88 | }); | 88 | }); |
| 89 | 89 | ||
| 90 | const makeProjectPersistenceLayer = (config: ServerConfigShape) => | 90 | const makeProjectPersistenceLayer = (config: ServerConfig.ServerConfig["Service"]) => |
| 91 | Layer.mergeAll( | 91 | Layer.mergeAll( |
| 92 | OrchestrationLayerLive.pipe( | 92 | OrchestrationLayerLive.pipe( |
| 93 | Layer.provideMerge(RepositoryIdentityResolverLive), | 93 | Layer.provideMerge(RepositoryIdentityResolverLive), |
| 94 | Layer.provideMerge(SqlitePersistenceLayerLive), | 94 | Layer.provideMerge(SqlitePersistenceLayerLive), |
| 95 | ), | 95 | ), |
| 96 | WorkspacePathsLive, | 96 | WorkspacePathsLive, |
| 97 | ).pipe( | 97 | ).pipe(Layer.provideMerge(NodeServices.layer), Layer.provide(ServerConfig.layer(config))); |
| 98 | Layer.provideMerge(NodeServices.layer), | ||
| 99 | Layer.provide(Layer.succeed(ServerConfig, config)), | ||
| 100 | ); | ||
| 101 | 98 | ||
| 102 | const readPersistedSnapshot = (baseDir: string) => | 99 | const readPersistedSnapshot = (baseDir: string) => |
| 103 | Effect.gen(function* () { | 100 | Effect.gen(function* () { |
| 104 | const config = yield* makeCliTestServerConfig(baseDir); | 101 | const config = yield* makeCliTestServerConfig(baseDir); |
| 105 | return yield* Effect.gen(function* () { | 102 | return yield* Effect.gen(function* () { |
| 106 | const projectionSnapshotQuery = yield* ProjectionSnapshotQuery; | 103 | const projectionSnapshotQuery = yield* ProjectionSnapshotQuery.ProjectionSnapshotQuery; |
| 107 | return yield* projectionSnapshotQuery.getSnapshot(); | 104 | return yield* projectionSnapshotQuery.getSnapshot(); |
| 108 | }).pipe(Effect.provide(makeProjectPersistenceLayer(config))); | 105 | }).pipe(Effect.provide(makeProjectPersistenceLayer(config))); |
| 109 | }); | 106 | }); |
| @@ -133,7 +130,7 @@ const withLiveProjectCliServer = <A, E, R>(baseDir: string, run: () => Effect.Ef · 23 unmodified lines | |||
| 133 | }), | 130 | }), |
| 134 | ), | 131 | ), |
| 135 | Layer.provideMerge(NodeServices.layer), | 132 | Layer.provideMerge(NodeServices.layer), |
| 136 | Layer.provide(Layer.succeed(ServerConfig, config)), | 133 | Layer.provide(ServerConfig.layer(config)), |
| 137 | ); | 134 | ); |
| 138 | 135 | ||
| 139 | return yield* Effect.scoped( | 136 | return yield* Effect.scoped( |
| @@ -238,7 +235,7 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => { · 98 unmodified lines | |||
| 238 | it.effect("logs in to headless connect without enabling access", () => | 235 | it.effect("logs in to headless connect without enabling access", () => |
| 239 | Effect.gen(function* () { | 236 | Effect.gen(function* () { |
| 240 | const baseDir = mkdtempSync(join(tmpdir(), "t3-cli-cloud-login-test-")); | 237 | 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); |
| 242 | mkdirSync(secretsDir, { recursive: true }); | 239 | mkdirSync(secretsDir, { recursive: true }); |
| 243 | writeFileSync( | 240 | writeFileSync( |
| 244 | join(secretsDir, "cloud-cli-oauth-token.bin"), | 241 | join(secretsDir, "cloud-cli-oauth-token.bin"), |
| @@ -282,7 +279,7 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => { · 37 unmodified lines | |||
| 282 | it.effect("logs out of headless connect and removes the stored CLI authorization", () => | 279 | it.effect("logs out of headless connect and removes the stored CLI authorization", () => |
| 283 | Effect.gen(function* () { | 280 | Effect.gen(function* () { |
| 284 | const baseDir = mkdtempSync(join(tmpdir(), "t3-cli-cloud-logout-test-")); | 281 | 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); |
| 286 | const tokenPath = join(secretsDir, "cloud-cli-oauth-token.bin"); | 283 | const tokenPath = join(secretsDir, "cloud-cli-oauth-token.bin"); |
| 287 | mkdirSync(secretsDir, { recursive: true }); | 284 | mkdirSync(secretsDir, { recursive: true }); |
| 288 | writeFileSync(tokenPath, "invalid persisted token"); | 285 | writeFileSync(tokenPath, "invalid persisted token"); |
| @@ -461,7 +458,7 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => { · 172 unmodified lines | |||
| 461 | "--base-dir", | 458 | "--base-dir", |
| 462 | baseDir, | 459 | baseDir, |
| 463 | ]); | 460 | ]); |
| 464 | const projectionSnapshotQuery = yield* ProjectionSnapshotQuery; | 461 | const projectionSnapshotQuery = yield* ProjectionSnapshotQuery.ProjectionSnapshotQuery; |
| 465 | const readModel = yield* projectionSnapshotQuery.getSnapshot(); | 462 | const readModel = yield* projectionSnapshotQuery.getSnapshot(); |
| 466 | const addedProject = readModel.projects.find( | 463 | const addedProject = readModel.projects.find( |
| 467 | (project) => project.workspaceRoot === workspaceRoot && project.deletedAt === null, | 464 | (project) => project.workspaceRoot === workspaceRoot && project.deletedAt === null, |
| @@ -18,7 +18,7 @@ import { · 17 unmodified lines | |||
| 18 | formatPairingCredentialList, | 18 | formatPairingCredentialList, |
| 19 | formatSessionList, | 19 | formatSessionList, |
| 20 | } from "../cliAuthFormat.ts"; | 20 | } from "../cliAuthFormat.ts"; |
| 21 | import { ServerConfig } from "../config.ts"; | 21 | import * as ServerConfig from "../config.ts"; |
| 22 | import { | 22 | import { |
| 23 | authLocationFlags, | 23 | authLocationFlags, |
| 24 | type CliAuthLocationFlags, | 24 | type CliAuthLocationFlags, |
| @@ -28,7 +28,7 @@ import { · 3 unmodified lines | |||
| 28 | 28 | ||
| 29 | const runWithEnvironmentAuth = <A, E>( | 29 | const runWithEnvironmentAuth = <A, E>( |
| 30 | flags: CliAuthLocationFlags, | 30 | flags: CliAuthLocationFlags, |
| 31 | run: (environmentAuth: EnvironmentAuth.EnvironmentAuthShape) => Effect.Effect<A, E>, | 31 | run: (environmentAuth: EnvironmentAuth.EnvironmentAuth["Service"]) => Effect.Effect<A, E>, |
| 32 | options?: { | 32 | options?: { |
| 33 | readonly quietLogs?: boolean; | 33 | readonly quietLogs?: boolean; |
| 34 | }, | 34 | }, |
| @@ -43,7 +43,7 @@ const runWithEnvironmentAuth = <A, E>( · 8 unmodified lines | |||
| 43 | }).pipe( | 43 | }).pipe( |
| 44 | Effect.provide( | 44 | Effect.provide( |
| 45 | Layer.mergeAll(EnvironmentAuth.runtimeLayer).pipe( | 45 | Layer.mergeAll(EnvironmentAuth.runtimeLayer).pipe( |
| 46 | Layer.provide(Layer.succeed(ServerConfig, config)), | 46 | Layer.provide(ServerConfig.layer(config)), |
| 47 | Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)), | 47 | Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)), |
| 48 | ), | 48 | ), |
| 49 | ), | 49 | ), |
| @@ -14,18 +14,10 @@ import * as SchemaTransformation from "effect/SchemaTransformation"; · 13 unmodified lines | |||
| 14 | import { Argument, Flag } from "effect/unstable/cli"; | 14 | import { Argument, Flag } from "effect/unstable/cli"; |
| 15 | 15 | ||
| 16 | import { readBootstrapEnvelope } from "../bootstrap.ts"; | 16 | import { readBootstrapEnvelope } from "../bootstrap.ts"; |
| 17 | import { | 17 | import * as ServerConfig from "../config.ts"; |
| 18 | DEFAULT_PORT, | ||
| 19 | deriveServerPaths, | ||
| 20 | ensureServerDirectories, | ||
| 21 | resolveStaticDir, | ||
| 22 | RuntimeMode, | ||
| 23 | type ServerConfigShape, | ||
| 24 | type StartupPresentation, | ||
| 25 | } from "../config.ts"; | ||
| 26 | import { expandHomePath, resolveBaseDir } from "../os-jank.ts"; | 18 | import { expandHomePath, resolveBaseDir } from "../os-jank.ts"; |
| 27 | 19 | ||
| 28 | export const modeFlag = Flag.choice("mode", RuntimeMode.literals).pipe( | 20 | export const modeFlag = Flag.choice("mode", ServerConfig.RuntimeMode.literals).pipe( |
| 29 | Flag.withDescription("Runtime mode. `desktop` keeps loopback defaults unless overridden."), | 21 | Flag.withDescription("Runtime mode. `desktop` keeps loopback defaults unless overridden."), |
| 30 | Flag.optional, | 22 | Flag.optional, |
| 31 | ); | 23 | ); |
| @@ -104,7 +96,7 @@ const EnvServerConfig = Config.all({ · 72 unmodified lines | |||
| 104 | Config.withDefault(10_000), | 96 | Config.withDefault(10_000), |
| 105 | ), | 97 | ), |
| 106 | otlpServiceName: Config.string("T3CODE_OTLP_SERVICE_NAME").pipe(Config.withDefault("t3-server")), | 98 | 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( |
| 108 | Config.option, | 100 | Config.option, |
| 109 | Config.map(Option.getOrUndefined), | 101 | Config.map(Option.getOrUndefined), |
| 110 | ), | 102 | ), |
| @@ -139,7 +131,7 @@ const EnvServerConfig = Config.all({ · 28 unmodified lines | |||
| 139 | }); | 131 | }); |
| 140 | 132 | ||
| 141 | export interface CliServerFlags { | 133 | export interface CliServerFlags { |
| 142 | readonly mode: Option.Option<RuntimeMode>; | 134 | readonly mode: Option.Option<ServerConfig.RuntimeMode>; |
| 143 | readonly port: Option.Option<number>; | 135 | readonly port: Option.Option<number>; |
| 144 | readonly host: Option.Option<string>; | 136 | readonly host: Option.Option<string>; |
| 145 | readonly baseDir: Option.Option<string>; | 137 | readonly baseDir: Option.Option<string>; |
| @@ -208,7 +200,7 @@ export const resolveServerConfig = ( · 62 unmodified lines | |||
| 208 | flags: CliServerFlags, | 200 | flags: CliServerFlags, |
| 209 | cliLogLevel: Option.Option<LogLevel.LogLevel>, | 201 | cliLogLevel: Option.Option<LogLevel.LogLevel>, |
| 210 | options?: { | 202 | options?: { |
| 211 | readonly startupPresentation?: StartupPresentation; | 203 | readonly startupPresentation?: ServerConfig.StartupPresentation; |
| 212 | readonly forceAutoBootstrapProjectFromCwd?: boolean; | 204 | readonly forceAutoBootstrapProjectFromCwd?: boolean; |
| 213 | }, | 205 | }, |
| 214 | ) => | 206 | ) => |
| @@ -238,7 +230,7 @@ export const resolveServerConfig = ( · 23 unmodified lines | |||
| 238 | : Option.none(); | 230 | : Option.none(); |
| 239 | const bootstrap = Option.getOrUndefined(bootstrapEnvelope); | 231 | const bootstrap = Option.getOrUndefined(bootstrapEnvelope); |
| 240 | 232 | ||
| 241 | const mode: RuntimeMode = Option.getOrElse( | 233 | const mode: ServerConfig.RuntimeMode = Option.getOrElse( |
| 242 | resolveOptionPrecedence( | 234 | resolveOptionPrecedence( |
| 243 | normalizedFlags.mode, | 235 | normalizedFlags.mode, |
| 244 | Option.fromUndefinedOr(env.mode), | 236 | Option.fromUndefinedOr(env.mode), |
| @@ -257,9 +249,9 @@ export const resolveServerConfig = ( · 12 unmodified lines | |||
| 257 | onSome: (value) => Effect.succeed(value), | 249 | onSome: (value) => Effect.succeed(value), |
| 258 | onNone: () => { | 250 | onNone: () => { |
| 259 | if (mode === "desktop") { | 251 | if (mode === "desktop") { |
| 260 | return Effect.succeed(DEFAULT_PORT); | 252 | return Effect.succeed(ServerConfig.DEFAULT_PORT); |
| 261 | } | 253 | } |
| 262 | return findAvailablePort(DEFAULT_PORT); | 254 | return findAvailablePort(ServerConfig.DEFAULT_PORT); |
| 263 | }, | 255 | }, |
| 264 | }, | 256 | }, |
| 265 | ); | 257 | ); |
| @@ -279,8 +271,8 @@ export const resolveServerConfig = ( · 13 unmodified lines | |||
| 279 | const rawCwd = Option.getOrElse(normalizedFlags.cwd, () => process.cwd()); | 271 | const rawCwd = Option.getOrElse(normalizedFlags.cwd, () => process.cwd()); |
| 280 | const cwd = path.resolve(yield* expandHomePath(rawCwd.trim())); | 272 | const cwd = path.resolve(yield* expandHomePath(rawCwd.trim())); |
| 281 | yield* fs.makeDirectory(cwd, { recursive: true }); | 273 | yield* fs.makeDirectory(cwd, { recursive: true }); |
| 282 | const derivedPaths = yield* deriveServerPaths(baseDir, devUrl); | 274 | const derivedPaths = yield* ServerConfig.deriveServerPaths(baseDir, devUrl); |
| 283 | yield* ensureServerDirectories(derivedPaths); | 275 | yield* ServerConfig.ensureServerDirectories(derivedPaths); |
| 284 | const persistedObservabilitySettings = yield* loadPersistedObservabilitySettings( | 276 | const persistedObservabilitySettings = yield* loadPersistedObservabilitySettings( |
| 285 | derivedPaths.settingsPath, | 277 | derivedPaths.settingsPath, |
| 286 | ); | 278 | ); |
| @@ -330,7 +322,7 @@ export const resolveServerConfig = ( · 43 unmodified lines | |||
| 330 | ), | 322 | ), |
| 331 | () => 443, | 323 | () => 443, |
| 332 | ); | 324 | ); |
| 333 | const staticDir = devUrl ? undefined : yield* resolveStaticDir(); | 325 | const staticDir = devUrl ? undefined : yield* ServerConfig.resolveStaticDir(); |
| 334 | const host = Option.getOrElse( | 326 | const host = Option.getOrElse( |
| 335 | resolveOptionPrecedence( | 327 | resolveOptionPrecedence( |
| 336 | normalizedFlags.host, | 328 | normalizedFlags.host, |
| @@ -341,7 +333,7 @@ export const resolveServerConfig = ( · 4 unmodified lines | |||
| 341 | ); | 333 | ); |
| 342 | const logLevel = Option.getOrElse(cliLogLevel, () => env.logLevel); | 334 | const logLevel = Option.getOrElse(cliLogLevel, () => env.logLevel); |
| 343 | 335 | ||
| 344 | const config: ServerConfigShape = { | 336 | const config: ServerConfig.ServerConfig["Service"] = { |
| 345 | logLevel, | 337 | logLevel, |
| 346 | traceMinLevel: env.traceMinLevel, | 338 | traceMinLevel: env.traceMinLevel, |
| 347 | traceTimingEnabled: env.traceTimingEnabled, | 339 | traceTimingEnabled: env.traceTimingEnabled, |
| @@ -31,9 +31,9 @@ import * as CliTokenManager from "../cloud/CliTokenManager.ts"; · 30 unmodified lines | |||
| 31 | import { CLOUD_LINKED_USER_ID, RELAY_URL_SECRET } from "../cloud/config.ts"; | 31 | import { CLOUD_LINKED_USER_ID, RELAY_URL_SECRET } from "../cloud/config.ts"; |
| 32 | import { relayUrlConfig } from "../cloud/publicConfig.ts"; | 32 | import { relayUrlConfig } from "../cloud/publicConfig.ts"; |
| 33 | import { headlessRelayClientTracingLayer } from "../cloud/relayTracing.ts"; | 33 | import { headlessRelayClientTracingLayer } from "../cloud/relayTracing.ts"; |
| 34 | import { ServerConfig } from "../config.ts"; | 34 | import * as ServerConfig from "../config.ts"; |
| 35 | import { ServerEnvironmentLive } from "../environment/Layers/ServerEnvironment.ts"; | 35 | import { ServerEnvironmentLive } from "../environment/Layers/ServerEnvironment.ts"; |
| 36 | import { ServerEnvironment } from "../environment/Services/ServerEnvironment.ts"; | 36 | import * as ServerEnvironment from "../environment/Services/ServerEnvironment.ts"; |
| 37 | import { readPersistedServerRuntimeState } from "../serverRuntimeState.ts"; | 37 | import { readPersistedServerRuntimeState } from "../serverRuntimeState.ts"; |
| 38 | import { projectLocationFlags, resolveCliAuthConfig } from "./config.ts"; | 38 | import { projectLocationFlags, resolveCliAuthConfig } from "./config.ts"; |
| 39 | 39 | ||
| @@ -145,7 +145,7 @@ const reportRelayClientInstallProgress = (event: RelayClientInstallProgressEvent · 105 unmodified lines | |||
| 145 | 145 | ||
| 146 | export const acquireRelayClientForLink = Effect.fn("cloud.cli.acquire_relay_client_for_link")( | 146 | export const acquireRelayClientForLink = Effect.fn("cloud.cli.acquire_relay_client_for_link")( |
| 147 | function* <ConfirmError, ConfirmContext>( | 147 | function* <ConfirmError, ConfirmContext>( |
| 148 | relayClient: RelayClient.RelayClientShape, | 148 | relayClient: RelayClient.RelayClient["Service"], |
| 149 | confirmInstall: (version: string) => Effect.Effect<boolean, ConfirmError, ConfirmContext>, | 149 | confirmInstall: (version: string) => Effect.Effect<boolean, ConfirmError, ConfirmContext>, |
| 150 | reportProgress: (event: RelayClientInstallProgressEvent) => Effect.Effect<void>, | 150 | reportProgress: (event: RelayClientInstallProgressEvent) => Effect.Effect<void>, |
| 151 | ) { | 151 | ) { |
| @@ -164,7 +164,7 @@ export const acquireRelayClientForLink = Effect.fn("cloud.cli.acquire_relay_clie · 12 unmodified lines | |||
| 164 | ); | 164 | ); |
| 165 | 165 | ||
| 166 | const withCloudCliSessionToken = <A, E, R>( | 166 | const withCloudCliSessionToken = <A, E, R>( |
| 167 | environmentAuth: EnvironmentAuth.EnvironmentAuthShape, | 167 | environmentAuth: EnvironmentAuth.EnvironmentAuth["Service"], |
| 168 | run: (token: string) => Effect.Effect<A, E, R>, | 168 | run: (token: string) => Effect.Effect<A, E, R>, |
| 169 | ) => | 169 | ) => |
| 170 | Effect.acquireUseRelease( | 170 | Effect.acquireUseRelease( |
| @@ -183,7 +183,7 @@ type LiveCloudActionResult = · 12 unmodified lines | |||
| 183 | | { readonly status: "failed"; readonly cause: unknown }; | 183 | | { readonly status: "failed"; readonly cause: unknown }; |
| 184 | 184 | ||
| 185 | const runLiveCloudUnlink = Effect.fn("cloud.cli.run_live_unlink")(function* () { | 185 | const runLiveCloudUnlink = Effect.fn("cloud.cli.run_live_unlink")(function* () { |
| 186 | const config = yield* ServerConfig; | 186 | const config = yield* ServerConfig.ServerConfig; |
| 187 | const runtimeState = yield* readPersistedServerRuntimeState(config.serverRuntimeStatePath); | 187 | const runtimeState = yield* readPersistedServerRuntimeState(config.serverRuntimeStatePath); |
| 188 | if (Option.isNone(runtimeState)) { | 188 | if (Option.isNone(runtimeState)) { |
| 189 | return { status: "not-running" } satisfies LiveCloudActionResult; | 189 | return { status: "not-running" } satisfies LiveCloudActionResult; |
| @@ -219,7 +219,7 @@ const unlinkRelayEnvironment = Effect.fn("cloud.cli.unlink_relay_environment")(f · 29 unmodified lines | |||
| 219 | return { status: "not-authenticated" } satisfies RelayUnlinkResult; | 219 | return { status: "not-authenticated" } satisfies RelayUnlinkResult; |
| 220 | } | 220 | } |
| 221 | 221 | ||
| 222 | const environment = yield* ServerEnvironment; | 222 | const environment = yield* ServerEnvironment.ServerEnvironment; |
| 223 | const environmentId = yield* environment.getEnvironmentId; | 223 | const environmentId = yield* environment.getEnvironmentId; |
| 224 | const relayUrl = yield* relayUrlConfig; | 224 | const relayUrl = yield* relayUrlConfig; |
| 225 | const httpClient = yield* HttpClient.HttpClient; | 225 | const httpClient = yield* HttpClient.HttpClient; |
| @@ -285,8 +285,8 @@ const runCloudCommand = <A, E>( · 59 unmodified lines | |||
| 285 | | FileSystem.FileSystem | 285 | | FileSystem.FileSystem |
| 286 | | HttpClient.HttpClient | 286 | | HttpClient.HttpClient |
| 287 | | Prompt.Environment | 287 | | Prompt.Environment |
| 288 | | ServerConfig | 288 | | ServerConfig.ServerConfig |
| 289 | | ServerEnvironment | 289 | | ServerEnvironment.ServerEnvironment |
| 290 | >, | 290 | >, |
| 291 | options?: { | 291 | options?: { |
| 292 | readonly quietLogs?: boolean; | 292 | readonly quietLogs?: boolean; |
| @@ -305,7 +305,7 @@ const runCloudCommand = <A, E>( · 12 unmodified lines | |||
| 305 | headlessRelayClientTracingLayer, | 305 | headlessRelayClientTracingLayer, |
| 306 | ).pipe( | 306 | ).pipe( |
| 307 | Layer.provideMerge(FetchHttpClient.layer), | 307 | Layer.provideMerge(FetchHttpClient.layer), |
| 308 | Layer.provideMerge(Layer.succeed(ServerConfig, config)), | 308 | Layer.provideMerge(ServerConfig.layer(config)), |
| 309 | Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)), | 309 | Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)), |
| 310 | ); | 310 | ); |
| 311 | return yield* run.pipe(Effect.provide(runtimeLayer)); | 311 | return yield* run.pipe(Effect.provide(runtimeLayer)); |
| @@ -26,19 +26,19 @@ import * as HttpApiClient from "effect/unstable/httpapi/HttpApiClient"; · 25 unmodified lines | |||
| 26 | 26 | ||
| 27 | import * as EnvironmentAuth from "../auth/EnvironmentAuth.ts"; | 27 | import * as EnvironmentAuth from "../auth/EnvironmentAuth.ts"; |
| 28 | 28 | ||
| 29 | import { ServerConfig, type ServerConfigShape } from "../config.ts"; | 29 | import * as ServerConfig from "../config.ts"; |
| 30 | import { OrchestrationEngineService } from "../orchestration/Services/OrchestrationEngine.ts"; | 30 | import * as OrchestrationEngine from "../orchestration/Services/OrchestrationEngine.ts"; |
| 31 | import { ProjectionSnapshotQuery } from "../orchestration/Services/ProjectionSnapshotQuery.ts"; | 31 | import * as ProjectionSnapshotQuery from "../orchestration/Services/ProjectionSnapshotQuery.ts"; |
| 32 | import { OrchestrationLayerLive } from "../orchestration/runtimeLayer.ts"; | 32 | import { OrchestrationLayerLive } from "../orchestration/runtimeLayer.ts"; |
| 33 | import { layerConfig as SqlitePersistenceLayerLive } from "../persistence/Layers/Sqlite.ts"; | 33 | import { layerConfig as SqlitePersistenceLayerLive } from "../persistence/Layers/Sqlite.ts"; |
| 34 | import { RepositoryIdentityResolverLive } from "../project/Layers/RepositoryIdentityResolver.ts"; | 34 | import { RepositoryIdentityResolverLive } from "../project/Layers/RepositoryIdentityResolver.ts"; |
| 35 | import { getAutoBootstrapDefaultModelSelection } from "../serverRuntimeStartup.ts"; | 35 | import * as ServerRuntimeStartup from "../serverRuntimeStartup.ts"; |
| 36 | import { | 36 | import { |
| 37 | clearPersistedServerRuntimeState, | 37 | clearPersistedServerRuntimeState, |
| 38 | readPersistedServerRuntimeState, | 38 | readPersistedServerRuntimeState, |
| 39 | } from "../serverRuntimeState.ts"; | 39 | } from "../serverRuntimeState.ts"; |
| 40 | import { WorkspacePathsLive } from "../workspace/Layers/WorkspacePaths.ts"; | 40 | import { WorkspacePathsLive } from "../workspace/Layers/WorkspacePaths.ts"; |
| 41 | import { WorkspacePaths } from "../workspace/Services/WorkspacePaths.ts"; | 41 | import * as WorkspacePaths from "../workspace/Services/WorkspacePaths.ts"; |
| 42 | import { type CliAuthLocationFlags, projectLocationFlags, resolveCliAuthConfig } from "./config.ts"; | 42 | import { type CliAuthLocationFlags, projectLocationFlags, resolveCliAuthConfig } from "./config.ts"; |
| 43 | 43 | ||
| 44 | type ProjectMutationTarget = { | 44 | type ProjectMutationTarget = { |
| @@ -78,7 +78,7 @@ const ProjectCliRuntimeLive = Layer.mergeAll( · 33 unmodified lines | |||
| 78 | const PROJECT_CLI_LIVE_SERVER_TIMEOUT = Duration.seconds(1); | 78 | const PROJECT_CLI_LIVE_SERVER_TIMEOUT = Duration.seconds(1); |
| 79 | const isEnvironmentHttpCommonError = Schema.is(EnvironmentHttpCommonError); | 79 | const isEnvironmentHttpCommonError = Schema.is(EnvironmentHttpCommonError); |
| 80 | const withProjectCliSessionToken = <A, E, R>( | 80 | const withProjectCliSessionToken = <A, E, R>( |
| 81 | environmentAuth: EnvironmentAuth.EnvironmentAuthShape, | 81 | environmentAuth: EnvironmentAuth.EnvironmentAuth["Service"], |
| 82 | run: (token: string) => Effect.Effect<A, E, R>, | 82 | run: (token: string) => Effect.Effect<A, E, R>, |
| 83 | ) => | 83 | ) => |
| 84 | Effect.acquireUseRelease( | 84 | Effect.acquireUseRelease( |
| @@ -123,7 +123,7 @@ const makeLiveServerClient = (origin: string) => · 38 unmodified lines | |||
| 123 | const normalizeWorkspaceRootForProjectCommand = Effect.fn( | 123 | const normalizeWorkspaceRootForProjectCommand = Effect.fn( |
| 124 | "normalizeWorkspaceRootForProjectCommand", | 124 | "normalizeWorkspaceRootForProjectCommand", |
| 125 | )(function* (workspaceRoot: string) { | 125 | )(function* (workspaceRoot: string) { |
| 126 | const workspacePaths = yield* WorkspacePaths; | 126 | const workspacePaths = yield* WorkspacePaths.WorkspacePaths; |
| 127 | return yield* workspacePaths.normalizeWorkspaceRoot(workspaceRoot); | 127 | return yield* workspacePaths.normalizeWorkspaceRoot(workspaceRoot); |
| 128 | }); | 128 | }); |
| 129 | 129 | ||
| @@ -211,12 +211,15 @@ const dispatchLiveOrchestrationCommand = ( · 81 unmodified lines | |||
| 211 | }).pipe(withProjectCliLiveServerTimeout, Effect.catch(failLiveServerRequest)); | 211 | }).pipe(withProjectCliLiveServerTimeout, Effect.catch(failLiveServerRequest)); |
| 212 | 212 | ||
| 213 | const getOfflineSnapshot = Effect.fn("getOfflineSnapshot")(function* () { | 213 | const getOfflineSnapshot = Effect.fn("getOfflineSnapshot")(function* () { |
| 214 | const projectionSnapshotQuery = yield* ProjectionSnapshotQuery; | 214 | const projectionSnapshotQuery = yield* ProjectionSnapshotQuery.ProjectionSnapshotQuery; |
| 215 | return yield* projectionSnapshotQuery.getSnapshot(); | 215 | return yield* projectionSnapshotQuery.getSnapshot(); |
| 216 | }); | 216 | }); |
| 217 | 217 | ||
| 218 | const tryResolveLiveProjectExecutionMode = Effect.fn("tryResolveLiveProjectExecutionMode")( | 218 | 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 | ) { | ||
| 220 | const runtimeState = yield* readPersistedServerRuntimeState(config.serverRuntimeStatePath); | 223 | const runtimeState = yield* readPersistedServerRuntimeState(config.serverRuntimeStatePath); |
| 221 | if (Option.isNone(runtimeState)) { | 224 | if (Option.isNone(runtimeState)) { |
| 222 | return Option.none<{ readonly origin: string }>(); | 225 | return Option.none<{ readonly origin: string }>(); |
| @@ -251,7 +254,11 @@ const runProjectMutation = Effect.fn("runProjectMutation")(function* ( · 28 unmodified lines | |||
| 251 | }) => Effect.Effect< | 254 | }) => Effect.Effect< |
| 252 | string, | 255 | string, |
| 253 | Error, | 256 | 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 | ||
| 255 | >, | 262 | >, |
| 256 | ) { | 263 | ) { |
| 257 | const logLevel = yield* GlobalFlag.LogLevel; | 264 | const logLevel = yield* GlobalFlag.LogLevel; |
| @@ -278,13 +285,13 @@ const runProjectMutation = Effect.fn("runProjectMutation")(function* ( · 20 unmodified lines | |||
| 278 | } | 285 | } |
| 279 | 286 | ||
| 280 | const offlineRuntimeLayer = ProjectCliRuntimeLive.pipe( | 287 | const offlineRuntimeLayer = ProjectCliRuntimeLive.pipe( |
| 281 | Layer.provide(Layer.succeed(ServerConfig, config)), | 288 | Layer.provide(ServerConfig.layer(config)), |
| 282 | Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)), | 289 | Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)), |
| 283 | ); | 290 | ); |
| 284 | 291 | ||
| 285 | return yield* Effect.gen(function* () { | 292 | return yield* Effect.gen(function* () { |
| 286 | const snapshot = yield* getOfflineSnapshot(); | 293 | const snapshot = yield* getOfflineSnapshot(); |
| 287 | const orchestrationEngine = yield* OrchestrationEngineService; | 294 | const orchestrationEngine = yield* OrchestrationEngine.OrchestrationEngineService; |
| 288 | const output = yield* run({ | 295 | const output = yield* run({ |
| 289 | snapshot, | 296 | snapshot, |
| 290 | dispatch: (command) => orchestrationEngine.dispatch(command), | 297 | dispatch: (command) => orchestrationEngine.dispatch(command), |
| @@ -296,7 +303,7 @@ const runProjectMutation = Effect.fn("runProjectMutation")(function* ( · 5 unmodified lines | |||
| 296 | Effect.provide( | 303 | Effect.provide( |
| 297 | Layer.mergeAll(EnvironmentAuth.runtimeLayer, WorkspacePathsLive).pipe( | 304 | Layer.mergeAll(EnvironmentAuth.runtimeLayer, WorkspacePathsLive).pipe( |
| 298 | Layer.provideMerge(FetchHttpClient.layer), | 305 | Layer.provideMerge(FetchHttpClient.layer), |
| 299 | Layer.provide(Layer.succeed(ServerConfig, config)), | 306 | Layer.provide(ServerConfig.layer(config)), |
| 300 | Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)), | 307 | Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)), |
| 301 | ), | 308 | ), |
| 302 | ), | 309 | ), |
| @@ -341,7 +348,7 @@ const projectAddCommand = Command.make("add", { · 38 unmodified lines | |||
| 341 | projectId, | 348 | projectId, |
| 342 | title, | 349 | title, |
| 343 | workspaceRoot, | 350 | workspaceRoot, |
| 344 | defaultModelSelection: getAutoBootstrapDefaultModelSelection(), | 351 | defaultModelSelection: ServerRuntimeStartup.getAutoBootstrapDefaultModelSelection(), |
| 345 | createdAt: DateTime.formatIso(yield* DateTime.now), | 352 | createdAt: DateTime.formatIso(yield* DateTime.now), |
| 346 | }); | 353 | }); |
| 347 | return `Added project ${projectId} (${title}) at ${workspaceRoot}.`; | 354 | return `Added project ${projectId} (${title}) at ${workspaceRoot}.`; |
| @@ -6,13 +6,13 @@ · 5 unmodified lines | |||
| 6 | * | 6 | * |
| 7 | * @module ServerConfig | 7 | * @module ServerConfig |
| 8 | */ | 8 | */ |
| 9 | import * as Context from "effect/Context"; | ||
| 9 | import * as Effect from "effect/Effect"; | 10 | import * as Effect from "effect/Effect"; |
| 10 | import * as FileSystem from "effect/FileSystem"; | 11 | import * as FileSystem from "effect/FileSystem"; |
| 11 | import * as Layer from "effect/Layer"; | 12 | import * as Layer from "effect/Layer"; |
| 12 | import * as LogLevel from "effect/LogLevel"; | 13 | import * as LogLevel from "effect/LogLevel"; |
| 13 | import * as Path from "effect/Path"; | 14 | import * as Path from "effect/Path"; |
| 14 | import * as Schema from "effect/Schema"; | 15 | import * as Schema from "effect/Schema"; |
| 15 | import * as Context from "effect/Context"; | ||
| 16 | 16 | ||
| 17 | export const DEFAULT_PORT = 3773; | 17 | export const DEFAULT_PORT = 3773; |
| 18 | 18 | ||
| @@ -46,38 +46,51 @@ export interface ServerDerivedPaths { · 27 unmodified lines | |||
| 46 | } | 46 | } |
| 47 | 47 | ||
| 48 | /** | 48 | /** |
| 49 | * ServerConfigShape - Process/runtime configuration required by the server. | 49 | * ServerConfig - Service tag for server runtime configuration. |
| 50 | */ | 50 | */ |
| 51 | export interface ServerConfigShape extends ServerDerivedPaths { | 51 | export class ServerConfig extends Context.Service< |
| 52 | readonly logLevel: LogLevel.LogLevel; | 52 | ServerConfig, |
| 53 | readonly traceMinLevel: LogLevel.LogLevel; | 53 | ServerDerivedPaths & { |
| 54 | readonly traceTimingEnabled: boolean; | 54 | readonly logLevel: LogLevel.LogLevel; |
| 55 | readonly traceBatchWindowMs: number; | 55 | readonly traceMinLevel: LogLevel.LogLevel; |
| 56 | readonly traceMaxBytes: number; | 56 | readonly traceTimingEnabled: boolean; |
| 57 | readonly traceMaxFiles: number; | 57 | readonly traceBatchWindowMs: number; |
| 58 | readonly otlpTracesUrl: string | undefined; | 58 | readonly traceMaxBytes: number; |
| 59 | readonly otlpMetricsUrl: string | undefined; | 59 | readonly traceMaxFiles: number; |
| 60 | readonly otlpExportIntervalMs: number; | 60 | readonly otlpTracesUrl: string | undefined; |
| 61 | readonly otlpServiceName: string; | 61 | readonly otlpMetricsUrl: string | undefined; |
| 62 | readonly mode: RuntimeMode; | 62 | readonly otlpExportIntervalMs: number; |
| 63 | readonly port: number; | 63 | readonly otlpServiceName: string; |
| 64 | readonly host: string | undefined; | 64 | readonly mode: RuntimeMode; |
| 65 | readonly cwd: string; | 65 | readonly port: number; |
| 66 | readonly baseDir: string; | 66 | readonly host: string | undefined; |
| 67 | readonly staticDir: string | undefined; | 67 | readonly cwd: string; |
| 68 | readonly devUrl: URL | undefined; | 68 | readonly baseDir: string; |
| 69 | readonly noBrowser: boolean; | 69 | readonly staticDir: string | undefined; |
| 70 | readonly startupPresentation: StartupPresentation; | 70 | readonly devUrl: URL | undefined; |
| 71 | readonly desktopBootstrapToken: string | undefined; | 71 | readonly noBrowser: boolean; |
| 72 | readonly autoBootstrapProjectFromCwd: boolean; | 72 | readonly startupPresentation: StartupPresentation; |
| 73 | readonly logWebSocketEvents: boolean; | 73 | readonly desktopBootstrapToken: string | undefined; |
| 74 | readonly tailscaleServeEnabled: boolean; | 74 | readonly autoBootstrapProjectFromCwd: boolean; |
| 75 | readonly tailscaleServePort: number; | 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); | ||
| 76 | } | 85 | } |
| 77 | 86 | ||
| 87 | export const make = (config: ServerConfig["Service"]) => ServerConfig.of(config); | ||
| 88 | |||
| 89 | export const layer = (config: ServerConfig["Service"]) => Layer.succeed(ServerConfig, make(config)); | ||
| 90 | |||
| 78 | export const deriveServerPaths = Effect.fn(function* ( | 91 | export const deriveServerPaths = Effect.fn(function* ( |
| 79 | baseDir: ServerConfigShape["baseDir"], | 92 | baseDir: ServerConfig["Service"]["baseDir"], |
| 80 | devUrl: ServerConfigShape["devUrl"], | 93 | devUrl: ServerConfig["Service"]["devUrl"], |
| 81 | ): Effect.fn.Return<ServerDerivedPaths, never, Path.Path> { | 94 | ): Effect.fn.Return<ServerDerivedPaths, never, Path.Path> { |
| 82 | const { join } = yield* Path.Path; | 95 | const { join } = yield* Path.Path; |
| 83 | const stateDir = join(baseDir, devUrl !== undefined ? "dev" : "userdata"); | 96 | const stateDir = join(baseDir, devUrl !== undefined ? "dev" : "userdata"); |
| @@ -129,56 +142,50 @@ export const ensureServerDirectories = Effect.fn(function* (derivedPaths: Server · 45 unmodified lines | |||
| 129 | ); | 142 | ); |
| 130 | }); | 143 | }); |
| 131 | 144 | ||
| 132 | /** | 145 | const makeTest = Effect.fn("ServerConfig.makeTest")(function* ( |
| 133 | * ServerConfig - Service tag for server runtime configuration. | 146 | cwd: string, |
| 134 | */ | 147 | baseDirOrPrefix: string | { readonly prefix: string }, |
| 135 | export class ServerConfig extends Context.Service<ServerConfig, ServerConfigShape>()( | ||
| 136 | "t3/config/ServerConfig", | ||
| 137 | ) { | 148 | ) { |
| 138 | static readonly layerTest = (cwd: string, baseDirOrPrefix: string | { prefix: string }) => | 149 | const devUrl = undefined; |
| 139 | Layer.effect( | 150 | const fs = yield* FileSystem.FileSystem; |
| 140 | ServerConfig, | 151 | const baseDir = |
| 141 | Effect.gen(function* () { | 152 | typeof baseDirOrPrefix === "string" |
| 142 | const devUrl = undefined; | 153 | ? baseDirOrPrefix |
| 154 | : yield* fs.makeTempDirectoryScoped({ prefix: baseDirOrPrefix.prefix }); | ||
| 155 | const derivedPaths = yield* deriveServerPaths(baseDir, devUrl); | ||
| 156 | yield* ensureServerDirectories(derivedPaths); | ||
| 143 | 157 | ||
| 144 | const fs = yield* FileSystem.FileSystem; | 158 | return ServerConfig.of({ |
| 145 | const baseDir = | 159 | logLevel: "Error", |
| 146 | typeof baseDirOrPrefix === "string" | 160 | traceMinLevel: "Info", |
| 147 | ? baseDirOrPrefix | 161 | traceTimingEnabled: true, |
| 148 | : yield* fs.makeTempDirectoryScoped({ prefix: baseDirOrPrefix.prefix }); | 162 | traceBatchWindowMs: 200, |
| 149 | const derivedPaths = yield* deriveServerPaths(baseDir, devUrl); | 163 | traceMaxBytes: 10 * 1024 * 1024, |
| 150 | yield* ensureServerDirectories(derivedPaths); | 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 | }); | ||
| 151 | 186 | ||
| 152 | return { | 187 | export const layerTest = (cwd: string, baseDirOrPrefix: string | { readonly prefix: string }) => |
| 153 | logLevel: "Error", | 188 | Layer.effect(ServerConfig, makeTest(cwd, baseDirOrPrefix)); |
| 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 | } | ||
| 182 | 189 | ||
| 183 | export const resolveStaticDir = Effect.fn(function* () { | 190 | export const resolveStaticDir = Effect.fn(function* () { |
| 184 | const { join, resolve } = yield* Path.Path; | 191 | const { join, resolve } = yield* Path.Path; |
| @@ -8,15 +8,15 @@ import * as FileSystem from "effect/FileSystem"; · 7 unmodified lines | |||
| 8 | import * as Layer from "effect/Layer"; | 8 | import * as Layer from "effect/Layer"; |
| 9 | import * as PlatformError from "effect/PlatformError"; | 9 | import * as PlatformError from "effect/PlatformError"; |
| 10 | 10 | ||
| 11 | import { deriveServerPaths, ServerConfig, type ServerConfigShape } from "../../config.ts"; | 11 | import * as ServerConfig from "../../config.ts"; |
| 12 | import { ServerEnvironment } from "../Services/ServerEnvironment.ts"; | 12 | import * as ServerEnvironment from "../Services/ServerEnvironment.ts"; |
| 13 | import { ServerEnvironmentLive } from "./ServerEnvironment.ts"; | 13 | import { ServerEnvironmentLive } from "./ServerEnvironment.ts"; |
| 14 | 14 | ||
| 15 | const makeServerEnvironmentLayer = (baseDir: string) => | 15 | const makeServerEnvironmentLayer = (baseDir: string) => |
| 16 | ServerEnvironmentLive.pipe(Layer.provide(ServerConfig.layerTest(process.cwd(), baseDir))); | 16 | ServerEnvironmentLive.pipe(Layer.provide(ServerConfig.layerTest(process.cwd(), baseDir))); |
| 17 | 17 | ||
| 18 | const makeServerConfig = Effect.fn(function* (baseDir: string) { | 18 | const makeServerConfig = Effect.fn(function* (baseDir: string) { |
| 19 | const derivedPaths = yield* deriveServerPaths(baseDir, undefined); | 19 | const derivedPaths = yield* ServerConfig.deriveServerPaths(baseDir, undefined); |
| 20 | 20 | ||
| 21 | return { | 21 | return { |
| 22 | ...derivedPaths, | 22 | ...derivedPaths, |
| @@ -44,7 +44,7 @@ const makeServerConfig = Effect.fn(function* (baseDir: string) { · 21 unmodified lines | |||
| 44 | devUrl: undefined, | 44 | devUrl: undefined, |
| 45 | noBrowser: false, | 45 | noBrowser: false, |
| 46 | startupPresentation: "browser", | 46 | startupPresentation: "browser", |
| 47 | } satisfies ServerConfigShape; | 47 | } satisfies ServerConfig.ServerConfig["Service"]; |
| 48 | }); | 48 | }); |
| 49 | 49 | ||
| 50 | it.layer(NodeServices.layer)("ServerEnvironmentLive", (it) => { | 50 | it.layer(NodeServices.layer)("ServerEnvironmentLive", (it) => { |
| @@ -56,11 +56,11 @@ it.layer(NodeServices.layer)("ServerEnvironmentLive", (it) => { · 5 unmodified lines | |||
| 56 | }); | 56 | }); |
| 57 | 57 | ||
| 58 | const first = yield* Effect.gen(function* () { | 58 | const first = yield* Effect.gen(function* () { |
| 59 | const serverEnvironment = yield* ServerEnvironment; | 59 | const serverEnvironment = yield* ServerEnvironment.ServerEnvironment; |
| 60 | return yield* serverEnvironment.getDescriptor; | 60 | return yield* serverEnvironment.getDescriptor; |
| 61 | }).pipe(Effect.provide(makeServerEnvironmentLayer(baseDir))); | 61 | }).pipe(Effect.provide(makeServerEnvironmentLayer(baseDir))); |
| 62 | const second = yield* Effect.gen(function* () { | 62 | const second = yield* Effect.gen(function* () { |
| 63 | const serverEnvironment = yield* ServerEnvironment; | 63 | const serverEnvironment = yield* ServerEnvironment.ServerEnvironment; |
| 64 | return yield* serverEnvironment.getDescriptor; | 64 | return yield* serverEnvironment.getDescriptor; |
| 65 | }).pipe(Effect.provide(makeServerEnvironmentLayer(baseDir))); | 65 | }).pipe(Effect.provide(makeServerEnvironmentLayer(baseDir))); |
| 66 | 66 | ||
| @@ -109,14 +109,12 @@ it.layer(NodeServices.layer)("ServerEnvironmentLive", (it) => { · 42 unmodified lines | |||
| 109 | }); | 109 | }); |
| 110 | 110 | ||
| 111 | const exit = yield* Effect.gen(function* () { | 111 | const exit = yield* Effect.gen(function* () { |
| 112 | const serverEnvironment = yield* ServerEnvironment; | 112 | const serverEnvironment = yield* ServerEnvironment.ServerEnvironment; |
| 113 | return yield* serverEnvironment.getDescriptor; | 113 | return yield* serverEnvironment.getDescriptor; |
| 114 | }).pipe( | 114 | }).pipe( |
| 115 | Effect.provide( | 115 | Effect.provide( |
| 116 | ServerEnvironmentLive.pipe( | 116 | ServerEnvironmentLive.pipe( |
| 117 | Layer.provide( | 117 | Layer.provide(Layer.merge(ServerConfig.layer(serverConfig), failingFileSystemLayer)), |
| 118 | Layer.merge(Layer.succeed(ServerConfig, serverConfig), failingFileSystemLayer), | ||
| 119 | ), | ||
| 120 | ), | 118 | ), |
| 121 | ), | 119 | ), |
| 122 | Effect.exit, | 120 | Effect.exit, |
| @@ -9,28 +9,21 @@ import * as Layer from "effect/Layer"; · 8 unmodified lines | |||
| 9 | import * as Logger from "effect/Logger"; | 9 | import * as Logger from "effect/Logger"; |
| 10 | import * as Path from "effect/Path"; | 10 | import * as Path from "effect/Path"; |
| 11 | import * as Schema from "effect/Schema"; | 11 | import * as Schema from "effect/Schema"; |
| 12 | import { ServerConfig } from "./config.ts"; | 12 | import * as ServerConfig from "./config.ts"; |
| 13 | 13 | import * as Keybindings from "./keybindings.ts"; | |
| 14 | import { | ||
| 15 | DEFAULT_KEYBINDINGS, | ||
| 16 | Keybindings, | ||
| 17 | KeybindingsLive, | ||
| 18 | ResolvedKeybindingFromConfig, | ||
| 19 | compileResolvedKeybindingRule, | ||
| 20 | compileResolvedKeybindingsConfig, | ||
| 21 | parseKeybindingShortcut, | ||
| 22 | } from "./keybindings.ts"; | ||
| 23 | import { KeybindingsConfigError } from "@t3tools/contracts"; | 14 | import { KeybindingsConfigError } from "@t3tools/contracts"; |
| 24 | 15 | ||
| 25 | const KeybindingsConfigJson = Schema.fromJsonString(KeybindingsConfig); | 16 | const KeybindingsConfigJson = Schema.fromJsonString(KeybindingsConfig); |
| 26 | const encodeKeybindingsConfigJson = Schema.encodeEffect(KeybindingsConfigJson); | 17 | const encodeKeybindingsConfigJson = Schema.encodeEffect(KeybindingsConfigJson); |
| 27 | const decodeKeybindingsConfigJson = Schema.decodeUnknownEffect(KeybindingsConfigJson); | 18 | const decodeKeybindingsConfigJson = Schema.decodeUnknownEffect(KeybindingsConfigJson); |
| 28 | const encodeResolvedKeybindingFromConfig = Schema.encodeEffect(ResolvedKeybindingFromConfig); | 19 | const encodeResolvedKeybindingFromConfig = Schema.encodeEffect( |
| 20 | Keybindings.ResolvedKeybindingFromConfig, | ||
| 21 | ); | ||
| 29 | const decodeResolvedKeybindingFromConfigExit = Schema.decodeUnknownExit( | 22 | const decodeResolvedKeybindingFromConfigExit = Schema.decodeUnknownExit( |
| 30 | ResolvedKeybindingFromConfig, | 23 | Keybindings.ResolvedKeybindingFromConfig, |
| 31 | ); | 24 | ); |
| 32 | const makeKeybindingsLayer = () => { | 25 | const makeKeybindingsLayer = () => { |
| 33 | return KeybindingsLive.pipe( | 26 | return Keybindings.layer.pipe( |
| 34 | Layer.provideMerge( | 27 | Layer.provideMerge( |
| 35 | Layer.fresh( | 28 | Layer.fresh( |
| 36 | ServerConfig.layerTest(process.cwd(), { | 29 | ServerConfig.layerTest(process.cwd(), { |
| @@ -66,7 +59,7 @@ const readKeybindingsConfig = (configPath: string) => · 29 unmodified lines | |||
| 66 | it.layer(NodeServices.layer)("keybindings", (it) => { | 59 | it.layer(NodeServices.layer)("keybindings", (it) => { |
| 67 | it.effect("parses shortcuts including plus key", () => | 60 | it.effect("parses shortcuts including plus key", () => |
| 68 | Effect.sync(() => { | 61 | Effect.sync(() => { |
| 69 | assert.deepEqual(parseKeybindingShortcut("mod+j"), { | 62 | assert.deepEqual(Keybindings.parseKeybindingShortcut("mod+j"), { |
| 70 | key: "j", | 63 | key: "j", |
| 71 | metaKey: false, | 64 | metaKey: false, |
| 72 | ctrlKey: false, | 65 | ctrlKey: false, |
| @@ -74,7 +67,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 1 unmodified line | |||
| 74 | altKey: false, | 67 | altKey: false, |
| 75 | modKey: true, | 68 | modKey: true, |
| 76 | }); | 69 | }); |
| 77 | assert.deepEqual(parseKeybindingShortcut("mod++"), { | 70 | assert.deepEqual(Keybindings.parseKeybindingShortcut("mod++"), { |
| 78 | key: "+", | 71 | key: "+", |
| 79 | metaKey: false, | 72 | metaKey: false, |
| 80 | ctrlKey: false, | 73 | ctrlKey: false, |
| @@ -87,7 +80,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 6 unmodified lines | |||
| 87 | 80 | ||
| 88 | it.effect("compiles valid rule with parsed when AST", () => | 81 | it.effect("compiles valid rule with parsed when AST", () => |
| 89 | Effect.sync(() => { | 82 | Effect.sync(() => { |
| 90 | const compiled = compileResolvedKeybindingRule({ | 83 | const compiled = Keybindings.compileResolvedKeybindingRule({ |
| 91 | key: "mod+d", | 84 | key: "mod+d", |
| 92 | command: "terminal.split", | 85 | command: "terminal.split", |
| 93 | when: "terminalOpen && !terminalFocus", | 86 | when: "terminalOpen && !terminalFocus", |
| @@ -137,14 +130,14 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 43 unmodified lines | |||
| 137 | it.effect("rejects invalid rules", () => | 130 | it.effect("rejects invalid rules", () => |
| 138 | Effect.sync(() => { | 131 | Effect.sync(() => { |
| 139 | assert.isNull( | 132 | assert.isNull( |
| 140 | compileResolvedKeybindingRule({ | 133 | Keybindings.compileResolvedKeybindingRule({ |
| 141 | key: "mod+shift+d+o", | 134 | key: "mod+shift+d+o", |
| 142 | command: "terminal.new", | 135 | command: "terminal.new", |
| 143 | }), | 136 | }), |
| 144 | ); | 137 | ); |
| 145 | 138 | ||
| 146 | assert.isNull( | 139 | assert.isNull( |
| 147 | compileResolvedKeybindingRule({ | 140 | Keybindings.compileResolvedKeybindingRule({ |
| 148 | key: "mod+d", | 141 | key: "mod+d", |
| 149 | command: "terminal.split", | 142 | command: "terminal.split", |
| 150 | when: "terminalFocus && (", | 143 | when: "terminalFocus && (", |
| @@ -152,7 +145,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 1 unmodified line | |||
| 152 | ); | 145 | ); |
| 153 | 146 | ||
| 154 | assert.isNull( | 147 | assert.isNull( |
| 155 | compileResolvedKeybindingRule({ | 148 | Keybindings.compileResolvedKeybindingRule({ |
| 156 | key: "mod+d", | 149 | key: "mod+d", |
| 157 | command: "terminal.split", | 150 | command: "terminal.split", |
| 158 | when: `${"!".repeat(300)}terminalFocus`, | 151 | when: `${"!".repeat(300)}terminalFocus`, |
| @@ -181,23 +174,23 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 22 unmodified lines | |||
| 181 | it.effect("bootstraps default keybindings when config file is missing", () => | 174 | it.effect("bootstraps default keybindings when config file is missing", () => |
| 182 | Effect.gen(function* () { | 175 | Effect.gen(function* () { |
| 183 | const fs = yield* FileSystem.FileSystem; | 176 | const fs = yield* FileSystem.FileSystem; |
| 184 | const { keybindingsConfigPath } = yield* ServerConfig; | 177 | const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig; |
| 185 | assert.isFalse(yield* fs.exists(keybindingsConfigPath)); | 178 | assert.isFalse(yield* fs.exists(keybindingsConfigPath)); |
| 186 | 179 | ||
| 187 | yield* Effect.gen(function* () { | 180 | yield* Effect.gen(function* () { |
| 188 | const keybindings = yield* Keybindings; | 181 | const keybindings = yield* Keybindings.Keybindings; |
| 189 | yield* keybindings.syncDefaultKeybindingsOnStartup; | 182 | yield* keybindings.syncDefaultKeybindingsOnStartup; |
| 190 | }); | 183 | }); |
| 191 | 184 | ||
| 192 | const persisted = yield* readKeybindingsConfig(keybindingsConfigPath); | 185 | const persisted = yield* readKeybindingsConfig(keybindingsConfigPath); |
| 193 | assert.deepEqual(persisted, DEFAULT_KEYBINDINGS); | 186 | assert.deepEqual(persisted, Keybindings.DEFAULT_KEYBINDINGS); |
| 194 | }).pipe(Effect.provide(makeKeybindingsLayer())), | 187 | }).pipe(Effect.provide(makeKeybindingsLayer())), |
| 195 | ); | 188 | ); |
| 196 | 189 | ||
| 197 | it.effect("ships configurable thread navigation defaults", () => | 190 | it.effect("ships configurable thread navigation defaults", () => |
| 198 | Effect.sync(() => { | 191 | Effect.sync(() => { |
| 199 | const defaultsByCommand = new Map( | 192 | 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), |
| 201 | ); | 194 | ); |
| 202 | 195 | ||
| 203 | assert.equal(defaultsByCommand.get("thread.previous"), "mod+shift+["); | 196 | assert.equal(defaultsByCommand.get("thread.previous"), "mod+shift+["); |
| @@ -215,17 +208,17 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 11 unmodified lines | |||
| 215 | it.effect("uses defaults in runtime when config is malformed without overriding file", () => | 208 | it.effect("uses defaults in runtime when config is malformed without overriding file", () => |
| 216 | Effect.gen(function* () { | 209 | Effect.gen(function* () { |
| 217 | const fs = yield* FileSystem.FileSystem; | 210 | const fs = yield* FileSystem.FileSystem; |
| 218 | const { keybindingsConfigPath } = yield* ServerConfig; | 211 | const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig; |
| 219 | yield* fs.writeFileString(keybindingsConfigPath, "{ not-json"); | 212 | yield* fs.writeFileString(keybindingsConfigPath, "{ not-json"); |
| 220 | 213 | ||
| 221 | const configState = yield* Effect.gen(function* () { | 214 | const configState = yield* Effect.gen(function* () { |
| 222 | const keybindings = yield* Keybindings; | 215 | const keybindings = yield* Keybindings.Keybindings; |
| 223 | return yield* keybindings.loadConfigState; | 216 | return yield* keybindings.loadConfigState; |
| 224 | }); | 217 | }); |
| 225 | 218 | ||
| 226 | assert.deepEqual( | 219 | assert.deepEqual( |
| 227 | configState.keybindings, | 220 | configState.keybindings, |
| 228 | compileResolvedKeybindingsConfig(DEFAULT_KEYBINDINGS), | 221 | Keybindings.compileResolvedKeybindingsConfig(Keybindings.DEFAULT_KEYBINDINGS), |
| 229 | ); | 222 | ); |
| 230 | assert.deepEqual(configState.issues, [ | 223 | assert.deepEqual(configState.issues, [ |
| 231 | { | 224 | { |
| @@ -240,7 +233,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 8 unmodified lines | |||
| 240 | it.effect("ignores invalid entries in runtime and reports them as issues", () => | 233 | it.effect("ignores invalid entries in runtime and reports them as issues", () => |
| 241 | Effect.gen(function* () { | 234 | Effect.gen(function* () { |
| 242 | const fs = yield* FileSystem.FileSystem; | 235 | const fs = yield* FileSystem.FileSystem; |
| 243 | const { keybindingsConfigPath } = yield* ServerConfig; | 236 | const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig; |
| 244 | yield* fs.writeFileString( | 237 | yield* fs.writeFileString( |
| 245 | keybindingsConfigPath, | 238 | keybindingsConfigPath, |
| 246 | // @effect-diagnostics-next-line preferSchemaOverJson:off | 239 | // @effect-diagnostics-next-line preferSchemaOverJson:off |
| @@ -252,7 +245,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 5 unmodified lines | |||
| 252 | ); | 245 | ); |
| 253 | 246 | ||
| 254 | const configState = yield* Effect.gen(function* () { | 247 | const configState = yield* Effect.gen(function* () { |
| 255 | const keybindings = yield* Keybindings; | 248 | const keybindings = yield* Keybindings.Keybindings; |
| 256 | return yield* keybindings.loadConfigState; | 249 | return yield* keybindings.loadConfigState; |
| 257 | }); | 250 | }); |
| 258 | 251 | ||
| @@ -279,14 +272,14 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 20 unmodified lines | |||
| 279 | "upserts missing default keybindings on startup without overriding existing command rules", | 272 | "upserts missing default keybindings on startup without overriding existing command rules", |
| 280 | () => | 273 | () => |
| 281 | Effect.gen(function* () { | 274 | Effect.gen(function* () { |
| 282 | const { keybindingsConfigPath } = yield* ServerConfig; | 275 | const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig; |
| 283 | yield* writeKeybindingsConfig(keybindingsConfigPath, [ | 276 | yield* writeKeybindingsConfig(keybindingsConfigPath, [ |
| 284 | { key: "mod+shift+t", command: "terminal.toggle" }, | 277 | { key: "mod+shift+t", command: "terminal.toggle" }, |
| 285 | { key: "mod+shift+r", command: "script.run-tests.run" }, | 278 | { key: "mod+shift+r", command: "script.run-tests.run" }, |
| 286 | ]); | 279 | ]); |
| 287 | 280 | ||
| 288 | yield* Effect.gen(function* () { | 281 | yield* Effect.gen(function* () { |
| 289 | const keybindings = yield* Keybindings; | 282 | const keybindings = yield* Keybindings.Keybindings; |
| 290 | yield* keybindings.syncDefaultKeybindingsOnStartup; | 283 | yield* keybindings.syncDefaultKeybindingsOnStartup; |
| 291 | }); | 284 | }); |
| 292 | 285 | ||
| @@ -300,7 +293,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 7 unmodified lines | |||
| 300 | persisted.some((entry) => entry.command === "terminal.toggle" && entry.key === "mod+j"), | 293 | persisted.some((entry) => entry.command === "terminal.toggle" && entry.key === "mod+j"), |
| 301 | ); | 294 | ); |
| 302 | 295 | ||
| 303 | for (const defaultRule of DEFAULT_KEYBINDINGS) { | 296 | for (const defaultRule of Keybindings.DEFAULT_KEYBINDINGS) { |
| 304 | assert.isTrue(byCommand.has(defaultRule.command), `expected ${defaultRule.command}`); | 297 | assert.isTrue(byCommand.has(defaultRule.command), `expected ${defaultRule.command}`); |
| 305 | } | 298 | } |
| 306 | assert.isTrue(byCommand.has("script.run-tests.run")); | 299 | assert.isTrue(byCommand.has("script.run-tests.run")); |
| @@ -314,13 +307,13 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 7 unmodified lines | |||
| 314 | }); | 307 | }); |
| 315 | 308 | ||
| 316 | return Effect.gen(function* () { | 309 | return Effect.gen(function* () { |
| 317 | const { keybindingsConfigPath } = yield* ServerConfig; | 310 | const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig; |
| 318 | yield* writeKeybindingsConfig(keybindingsConfigPath, [ | 311 | yield* writeKeybindingsConfig(keybindingsConfigPath, [ |
| 319 | { key: "mod+j", command: "script.custom-action.run" }, | 312 | { key: "mod+j", command: "script.custom-action.run" }, |
| 320 | ]); | 313 | ]); |
| 321 | 314 | ||
| 322 | yield* Effect.gen(function* () { | 315 | yield* Effect.gen(function* () { |
| 323 | const keybindings = yield* Keybindings; | 316 | const keybindings = yield* Keybindings.Keybindings; |
| 324 | yield* keybindings.syncDefaultKeybindingsOnStartup; | 317 | yield* keybindings.syncDefaultKeybindingsOnStartup; |
| 325 | }); | 318 | }); |
| 326 | 319 | ||
| @@ -345,13 +338,13 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 18 unmodified lines | |||
| 345 | 338 | ||
| 346 | it.effect("upserts custom keybindings to configured path", () => | 339 | it.effect("upserts custom keybindings to configured path", () => |
| 347 | Effect.gen(function* () { | 340 | Effect.gen(function* () { |
| 348 | const { keybindingsConfigPath } = yield* ServerConfig; | 341 | const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig; |
| 349 | yield* writeKeybindingsConfig(keybindingsConfigPath, [ | 342 | yield* writeKeybindingsConfig(keybindingsConfigPath, [ |
| 350 | { key: "mod+j", command: "terminal.toggle" }, | 343 | { key: "mod+j", command: "terminal.toggle" }, |
| 351 | ]); | 344 | ]); |
| 352 | 345 | ||
| 353 | const resolved = yield* Effect.gen(function* () { | 346 | const resolved = yield* Effect.gen(function* () { |
| 354 | const keybindings = yield* Keybindings; | 347 | const keybindings = yield* Keybindings.Keybindings; |
| 355 | return yield* keybindings.upsertKeybindingRule({ | 348 | return yield* keybindings.upsertKeybindingRule({ |
| 356 | key: "mod+shift+r", | 349 | key: "mod+shift+r", |
| 357 | command: "script.run-tests.run", | 350 | command: "script.run-tests.run", |
| @@ -371,12 +364,12 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 13 unmodified lines | |||
| 371 | 364 | ||
| 372 | it.effect("appends additional custom keybindings for the same command", () => | 365 | it.effect("appends additional custom keybindings for the same command", () => |
| 373 | Effect.gen(function* () { | 366 | Effect.gen(function* () { |
| 374 | const { keybindingsConfigPath } = yield* ServerConfig; | 367 | const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig; |
| 375 | yield* writeKeybindingsConfig(keybindingsConfigPath, [ | 368 | yield* writeKeybindingsConfig(keybindingsConfigPath, [ |
| 376 | { key: "mod+r", command: "script.run-tests.run" }, | 369 | { key: "mod+r", command: "script.run-tests.run" }, |
| 377 | ]); | 370 | ]); |
| 378 | yield* Effect.gen(function* () { | 371 | yield* Effect.gen(function* () { |
| 379 | const keybindings = yield* Keybindings; | 372 | const keybindings = yield* Keybindings.Keybindings; |
| 380 | return yield* keybindings.upsertKeybindingRule({ | 373 | return yield* keybindings.upsertKeybindingRule({ |
| 381 | key: "mod+shift+r", | 374 | key: "mod+shift+r", |
| 382 | command: "script.run-tests.run", | 375 | command: "script.run-tests.run", |
| @@ -394,13 +387,13 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 11 unmodified lines | |||
| 394 | 387 | ||
| 395 | it.effect("replaces only the targeted custom keybinding", () => | 388 | it.effect("replaces only the targeted custom keybinding", () => |
| 396 | Effect.gen(function* () { | 389 | Effect.gen(function* () { |
| 397 | const { keybindingsConfigPath } = yield* ServerConfig; | 390 | const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig; |
| 398 | yield* writeKeybindingsConfig(keybindingsConfigPath, [ | 391 | yield* writeKeybindingsConfig(keybindingsConfigPath, [ |
| 399 | { key: "mod+r", command: "script.run-tests.run" }, | 392 | { key: "mod+r", command: "script.run-tests.run" }, |
| 400 | { key: "mod+shift+r", command: "script.run-tests.run" }, | 393 | { key: "mod+shift+r", command: "script.run-tests.run" }, |
| 401 | ]); | 394 | ]); |
| 402 | yield* Effect.gen(function* () { | 395 | yield* Effect.gen(function* () { |
| 403 | const keybindings = yield* Keybindings; | 396 | const keybindings = yield* Keybindings.Keybindings; |
| 404 | return yield* keybindings.upsertKeybindingRule({ | 397 | return yield* keybindings.upsertKeybindingRule({ |
| 405 | key: "mod+alt+r", | 398 | key: "mod+alt+r", |
| 406 | command: "script.run-tests.run", | 399 | command: "script.run-tests.run", |
| @@ -419,13 +412,13 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 12 unmodified lines | |||
| 419 | 412 | ||
| 420 | it.effect("removes only the targeted custom keybinding", () => | 413 | it.effect("removes only the targeted custom keybinding", () => |
| 421 | Effect.gen(function* () { | 414 | Effect.gen(function* () { |
| 422 | const { keybindingsConfigPath } = yield* ServerConfig; | 415 | const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig; |
| 423 | yield* writeKeybindingsConfig(keybindingsConfigPath, [ | 416 | yield* writeKeybindingsConfig(keybindingsConfigPath, [ |
| 424 | { key: "mod+r", command: "script.run-tests.run" }, | 417 | { key: "mod+r", command: "script.run-tests.run" }, |
| 425 | { key: "mod+shift+r", command: "script.run-tests.run" }, | 418 | { key: "mod+shift+r", command: "script.run-tests.run" }, |
| 426 | ]); | 419 | ]); |
| 427 | yield* Effect.gen(function* () { | 420 | yield* Effect.gen(function* () { |
| 428 | const keybindings = yield* Keybindings; | 421 | const keybindings = yield* Keybindings.Keybindings; |
| 429 | return yield* keybindings.removeKeybindingRule({ | 422 | return yield* keybindings.removeKeybindingRule({ |
| 430 | key: "mod+r", | 423 | key: "mod+r", |
| 431 | command: "script.run-tests.run", | 424 | command: "script.run-tests.run", |
| @@ -441,11 +434,11 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 9 unmodified lines | |||
| 441 | it.effect("refuses to overwrite malformed keybindings config", () => | 434 | it.effect("refuses to overwrite malformed keybindings config", () => |
| 442 | Effect.gen(function* () { | 435 | Effect.gen(function* () { |
| 443 | const fs = yield* FileSystem.FileSystem; | 436 | const fs = yield* FileSystem.FileSystem; |
| 444 | const { keybindingsConfigPath } = yield* ServerConfig; | 437 | const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig; |
| 445 | yield* fs.writeFileString(keybindingsConfigPath, "{ not-json"); | 438 | yield* fs.writeFileString(keybindingsConfigPath, "{ not-json"); |
| 446 | 439 | ||
| 447 | const result = yield* Effect.gen(function* () { | 440 | const result = yield* Effect.gen(function* () { |
| 448 | const keybindings = yield* Keybindings; | 441 | const keybindings = yield* Keybindings.Keybindings; |
| 449 | return yield* keybindings.upsertKeybindingRule({ | 442 | return yield* keybindings.upsertKeybindingRule({ |
| 450 | key: "mod+shift+r", | 443 | key: "mod+shift+r", |
| 451 | command: "script.run-tests.run", | 444 | command: "script.run-tests.run", |
| @@ -461,14 +454,14 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 9 unmodified lines | |||
| 461 | it.effect("reports non-array config parse errors without duplicate prefix", () => | 454 | it.effect("reports non-array config parse errors without duplicate prefix", () => |
| 462 | Effect.gen(function* () { | 455 | Effect.gen(function* () { |
| 463 | const fs = yield* FileSystem.FileSystem; | 456 | const fs = yield* FileSystem.FileSystem; |
| 464 | const { keybindingsConfigPath } = yield* ServerConfig; | 457 | const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig; |
| 465 | yield* fs.writeFileString( | 458 | yield* fs.writeFileString( |
| 466 | keybindingsConfigPath, | 459 | keybindingsConfigPath, |
| 467 | '{"key":"mod+j","command":"terminal.toggle"}', | 460 | '{"key":"mod+j","command":"terminal.toggle"}', |
| 468 | ); | 461 | ); |
| 469 | 462 | ||
| 470 | const firstResult = yield* Effect.gen(function* () { | 463 | const firstResult = yield* Effect.gen(function* () { |
| 471 | const keybindings = yield* Keybindings; | 464 | const keybindings = yield* Keybindings.Keybindings; |
| 472 | return yield* keybindings.upsertKeybindingRule({ | 465 | return yield* keybindings.upsertKeybindingRule({ |
| 473 | key: "mod+shift+r", | 466 | key: "mod+shift+r", |
| 474 | command: "script.run-tests.run", | 467 | command: "script.run-tests.run", |
| @@ -477,7 +470,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 2 unmodified lines | |||
| 477 | assertFailure(firstResult, "expected JSON array"); | 470 | assertFailure(firstResult, "expected JSON array"); |
| 478 | 471 | ||
| 479 | const secondResult = yield* Effect.gen(function* () { | 472 | const secondResult = yield* Effect.gen(function* () { |
| 480 | const keybindings = yield* Keybindings; | 473 | const keybindings = yield* Keybindings.Keybindings; |
| 481 | return yield* keybindings.upsertKeybindingRule({ | 474 | return yield* keybindings.upsertKeybindingRule({ |
| 482 | key: "mod+shift+r", | 475 | key: "mod+shift+r", |
| 483 | command: "script.run-tests.run", | 476 | command: "script.run-tests.run", |
| @@ -490,7 +483,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 6 unmodified lines | |||
| 490 | it.effect("fails when config directory is not writable", () => | 483 | it.effect("fails when config directory is not writable", () => |
| 491 | Effect.gen(function* () { | 484 | Effect.gen(function* () { |
| 492 | const fs = yield* FileSystem.FileSystem; | 485 | const fs = yield* FileSystem.FileSystem; |
| 493 | const { keybindingsConfigPath } = yield* ServerConfig; | 486 | const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig; |
| 494 | const { dirname } = yield* Path.Path; | 487 | const { dirname } = yield* Path.Path; |
| 495 | yield* writeKeybindingsConfig(keybindingsConfigPath, [ | 488 | yield* writeKeybindingsConfig(keybindingsConfigPath, [ |
| 496 | { key: "mod+j", command: "terminal.toggle" }, | 489 | { key: "mod+j", command: "terminal.toggle" }, |
| @@ -498,7 +491,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 1 unmodified line | |||
| 498 | yield* fs.chmod(dirname(keybindingsConfigPath), 0o500); | 491 | yield* fs.chmod(dirname(keybindingsConfigPath), 0o500); |
| 499 | 492 | ||
| 500 | const result = yield* Effect.gen(function* () { | 493 | const result = yield* Effect.gen(function* () { |
| 501 | const keybindings = yield* Keybindings; | 494 | const keybindings = yield* Keybindings.Keybindings; |
| 502 | return yield* keybindings.upsertKeybindingRule({ | 495 | return yield* keybindings.upsertKeybindingRule({ |
| 503 | key: "mod+shift+r", | 496 | key: "mod+shift+r", |
| 504 | command: "script.run-tests.run", | 497 | command: "script.run-tests.run", |
| @@ -516,13 +509,13 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 11 unmodified lines | |||
| 516 | 509 | ||
| 517 | it.effect("caches loaded resolved config across repeated reads", () => | 510 | it.effect("caches loaded resolved config across repeated reads", () => |
| 518 | Effect.gen(function* () { | 511 | Effect.gen(function* () { |
| 519 | const { keybindingsConfigPath } = yield* ServerConfig; | 512 | const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig; |
| 520 | yield* writeKeybindingsConfig(keybindingsConfigPath, [ | 513 | yield* writeKeybindingsConfig(keybindingsConfigPath, [ |
| 521 | { key: "mod+j", command: "terminal.toggle" }, | 514 | { key: "mod+j", command: "terminal.toggle" }, |
| 522 | ]); | 515 | ]); |
| 523 | 516 | ||
| 524 | const [first, second] = yield* Effect.gen(function* () { | 517 | const [first, second] = yield* Effect.gen(function* () { |
| 525 | const keybindings = yield* Keybindings; | 518 | const keybindings = yield* Keybindings.Keybindings; |
| 526 | const firstLoad = (yield* keybindings.loadConfigState).keybindings; | 519 | const firstLoad = (yield* keybindings.loadConfigState).keybindings; |
| 527 | const secondLoad = (yield* keybindings.loadConfigState).keybindings; | 520 | const secondLoad = (yield* keybindings.loadConfigState).keybindings; |
| 528 | return [firstLoad, secondLoad] as const; | 521 | return [firstLoad, secondLoad] as const; |
| @@ -535,13 +528,13 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 6 unmodified lines | |||
| 535 | 528 | ||
| 536 | it.effect("updates cached resolved config after upsert", () => | 529 | it.effect("updates cached resolved config after upsert", () => |
| 537 | Effect.gen(function* () { | 530 | Effect.gen(function* () { |
| 538 | const { keybindingsConfigPath } = yield* ServerConfig; | 531 | const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig; |
| 539 | yield* writeKeybindingsConfig(keybindingsConfigPath, [ | 532 | yield* writeKeybindingsConfig(keybindingsConfigPath, [ |
| 540 | { key: "mod+j", command: "terminal.toggle" }, | 533 | { key: "mod+j", command: "terminal.toggle" }, |
| 541 | ]); | 534 | ]); |
| 542 | 535 | ||
| 543 | const loadedAfterUpsert = yield* Effect.gen(function* () { | 536 | const loadedAfterUpsert = yield* Effect.gen(function* () { |
| 544 | const keybindings = yield* Keybindings; | 537 | const keybindings = yield* Keybindings.Keybindings; |
| 545 | yield* keybindings.loadConfigState; | 538 | yield* keybindings.loadConfigState; |
| 546 | yield* keybindings.upsertKeybindingRule({ | 539 | yield* keybindings.upsertKeybindingRule({ |
| 547 | key: "mod+shift+r", | 540 | key: "mod+shift+r", |
| @@ -557,7 +550,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 9 unmodified lines | |||
| 557 | 550 | ||
| 558 | it.effect("serializes concurrent upserts to avoid lost updates", () => | 551 | it.effect("serializes concurrent upserts to avoid lost updates", () => |
| 559 | Effect.gen(function* () { | 552 | Effect.gen(function* () { |
| 560 | const { keybindingsConfigPath } = yield* ServerConfig; | 553 | const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig; |
| 561 | yield* writeKeybindingsConfig(keybindingsConfigPath, []); | 554 | yield* writeKeybindingsConfig(keybindingsConfigPath, []); |
| 562 | 555 | ||
| 563 | const commands = Array.from( | 556 | const commands = Array.from( |
| @@ -565,7 +558,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => { · 1 unmodified line | |||
| 565 | (_, index): KeybindingCommand => `script.concurrent-${index}.run`, | 558 | (_, index): KeybindingCommand => `script.concurrent-${index}.run`, |
| 566 | ); | 559 | ); |
| 567 | yield* Effect.gen(function* () { | 560 | yield* Effect.gen(function* () { |
| 568 | const keybindings = yield* Keybindings; | 561 | const keybindings = yield* Keybindings.Keybindings; |
| 569 | yield* Effect.all( | 562 | yield* Effect.all( |
| 570 | commands.map((command, index) => | 563 | commands.map((command, index) => |
| 571 | keybindings.upsertKeybindingRule({ | 564 | keybindings.upsertKeybindingRule({ |
| @@ -41,7 +41,7 @@ import * as Context from "effect/Context"; · 40 unmodified lines | |||
| 41 | import * as Scope from "effect/Scope"; | 41 | import * as Scope from "effect/Scope"; |
| 42 | import * as Stream from "effect/Stream"; | 42 | import * as Stream from "effect/Stream"; |
| 43 | import * as Semaphore from "effect/Semaphore"; | 43 | import * as Semaphore from "effect/Semaphore"; |
| 44 | import { ServerConfig } from "./config.ts"; | 44 | import * as ServerConfig from "./config.ts"; |
| 45 | import { writeFileStringAtomically } from "./atomicWrite.ts"; | 45 | import { writeFileStringAtomically } from "./atomicWrite.ts"; |
| 46 | import { fromJsonStringPretty, fromLenientJson } from "@t3tools/shared/schemaJson"; | 46 | import { fromJsonStringPretty, fromLenientJson } from "@t3tools/shared/schemaJson"; |
| 47 | import { | 47 | import { |
| @@ -225,74 +225,70 @@ function mergeWithDefaultKeybindings(custom: ResolvedKeybindingsConfig): Resolve · 177 unmodified lines | |||
| 225 | return merged.slice(-MAX_KEYBINDINGS_COUNT); | 225 | return merged.slice(-MAX_KEYBINDINGS_COUNT); |
| 226 | } | 226 | } |
| 227 | 227 | ||
| 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 | |||
| 287 | /** | 228 | /** |
| 288 | * Keybindings - Service tag for keybinding configuration operations. | 229 | * Keybindings - Service tag for keybinding configuration operations. |
| 289 | */ | 230 | */ |
| 290 | export class Keybindings extends Context.Service<Keybindings, KeybindingsShape>()( | 231 | export class Keybindings extends Context.Service< |
| 291 | "t3/keybindings", | 232 | Keybindings, |
| 292 | ) {} | 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") {} | ||
| 293 | 289 | ||
| 294 | const makeKeybindings = Effect.gen(function* () { | 290 | const make = Effect.gen(function* () { |
| 295 | const { keybindingsConfigPath } = yield* ServerConfig; | 291 | const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig; |
| 296 | const fs = yield* FileSystem.FileSystem; | 292 | const fs = yield* FileSystem.FileSystem; |
| 297 | const path = yield* Path.Path; | 293 | const path = yield* Path.Path; |
| 298 | const upsertSemaphore = yield* Semaphore.make(1); | 294 | const upsertSemaphore = yield* Semaphore.make(1); |
| @@ -700,7 +696,7 @@ const makeKeybindings = Effect.gen(function* () { · 401 unmodified lines | |||
| 700 | return nextResolved; | 696 | return nextResolved; |
| 701 | }), | 697 | }), |
| 702 | ), | 698 | ), |
| 703 | } satisfies KeybindingsShape; | 699 | } satisfies Keybindings["Service"]; |
| 704 | }); | 700 | }); |
| 705 | 701 | ||
| 706 | export const KeybindingsLive = Layer.effect(Keybindings, makeKeybindings); | 702 | export const layer = Layer.effect(Keybindings, make); |
| @@ -107,7 +107,15 @@ it.effect("registers annotated tools and preserves authenticated request context · 106 unmodified lines | |||
| 107 | Effect.gen(function* () { | 107 | Effect.gen(function* () { |
| 108 | const server = yield* McpServer.McpServer; | 108 | const server = yield* McpServer.McpServer; |
| 109 | const broker = yield* PreviewAutomationBroker.PreviewAutomationBroker; | 109 | 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 | }); | ||
| 111 | yield* Stream.runForEach(requests, (request) => | 119 | yield* Stream.runForEach(requests, (request) => |
| 112 | broker.respond({ | 120 | broker.respond({ |
| 113 | requestId: request.requestId, | 121 | requestId: request.requestId, |
| @@ -2,10 +2,13 @@ import { expect, it } from "@effect/vitest"; · 1 unmodified line | |||
| 2 | import { | 2 | import { |
| 3 | EnvironmentId, | 3 | EnvironmentId, |
| 4 | PreviewAutomationNoFocusedOwnerError, | 4 | PreviewAutomationNoFocusedOwnerError, |
| 5 | PreviewAutomationUnavailableError, | ||
| 5 | ProviderInstanceId, | 6 | ProviderInstanceId, |
| 6 | ThreadId, | 7 | ThreadId, |
| 8 | type PreviewAutomationOwner, | ||
| 7 | } from "@t3tools/contracts"; | 9 | } from "@t3tools/contracts"; |
| 8 | import * as Effect from "effect/Effect"; | 10 | import * as Effect from "effect/Effect"; |
| 11 | import * as Fiber from "effect/Fiber"; | ||
| 9 | import * as Stream from "effect/Stream"; | 12 | import * as Stream from "effect/Stream"; |
| 10 | 13 | ||
| 11 | import * as PreviewAutomationBroker from "./PreviewAutomationBroker.ts"; | 14 | import * as PreviewAutomationBroker from "./PreviewAutomationBroker.ts"; |
| @@ -20,11 +23,22 @@ const scope = { · 8 unmodified lines | |||
| 20 | expiresAt: 2, | 23 | expiresAt: 2, |
| 21 | }; | 24 | }; |
| 22 | 25 | ||
| 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", () => | ||
| 24 | Effect.scoped( | 38 | Effect.scoped( |
| 25 | Effect.gen(function* () { | 39 | Effect.gen(function* () { |
| 26 | const broker = yield* PreviewAutomationBroker.__testing.make; | 40 | const broker = yield* PreviewAutomationBroker.__testing.make; |
| 27 | const requests = yield* broker.connect("client-1"); | 41 | const requests = yield* broker.connect(makeOwner()); |
| 28 | yield* Stream.runForEach(requests, (request) => | 42 | yield* Stream.runForEach(requests, (request) => |
| 29 | broker.respond({ | 43 | broker.respond({ |
| 30 | requestId: request.requestId, | 44 | requestId: request.requestId, |
| @@ -33,15 +47,6 @@ it.effect("routes a request to the focused owner and correlates its response", ( · 2 unmodified lines | |||
| 33 | }), | 47 | }), |
| 34 | ).pipe(Effect.forkScoped); | 48 | ).pipe(Effect.forkScoped); |
| 35 | yield* Effect.yieldNow; | 49 | 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 | }); | ||
| 45 | 50 | ||
| 46 | const result = yield* broker.invoke<{ available: boolean }>({ | 51 | const result = yield* broker.invoke<{ available: boolean }>({ |
| 47 | scope, | 52 | scope, |
| @@ -68,22 +73,119 @@ it.effect("routes interactive commands to a hidden durable browser host", () => · 20 unmodified lines | |||
| 68 | Effect.scoped( | 73 | Effect.scoped( |
| 69 | Effect.gen(function* () { | 74 | Effect.gen(function* () { |
| 70 | const broker = yield* PreviewAutomationBroker.__testing.make; | 75 | 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()); | ||
| 72 | yield* Stream.runForEach(requests, (request) => | 136 | yield* Stream.runForEach(requests, (request) => |
| 73 | broker.respond({ requestId: request.requestId, ok: true }), | 137 | broker.respond({ requestId: request.requestId, ok: true }), |
| 74 | ).pipe(Effect.forkScoped); | 138 | ).pipe(Effect.forkScoped); |
| 75 | yield* Effect.yieldNow; | 139 | yield* Effect.yieldNow; |
| 76 | yield* broker.reportOwner({ | 140 | |
| 77 | clientId: "client-hidden", | 141 | yield* broker.clearOwner({ |
| 142 | clientId: "client-1", | ||
| 78 | environmentId: scope.environmentId, | 143 | environmentId: scope.environmentId, |
| 79 | threadId: scope.threadId, | 144 | threadId: ThreadId.make("thread-stale"), |
| 80 | tabId: "tab-hidden", | ||
| 81 | visible: false, | ||
| 82 | supportsAutomation: true, | ||
| 83 | focusedAt: "2026-06-11T00:00:00.000Z", | ||
| 84 | }); | 145 | }); |
| 85 | 146 | ||
| 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"); | ||
| 87 | }), | 189 | }), |
| 88 | ), | 190 | ), |
| 89 | ); | 191 | ); |
| @@ -11,6 +11,7 @@ import { · 10 unmodified lines | |||
| 11 | type PreviewAutomationError, | 11 | type PreviewAutomationError, |
| 12 | type PreviewAutomationOperation, | 12 | type PreviewAutomationOperation, |
| 13 | type PreviewAutomationOwner, | 13 | type PreviewAutomationOwner, |
| 14 | type PreviewAutomationOwnerIdentity, | ||
| 14 | type PreviewAutomationRequest, | 15 | type PreviewAutomationRequest, |
| 15 | type PreviewAutomationResponse, | 16 | type PreviewAutomationResponse, |
| 16 | type PreviewTabId, | 17 | type PreviewTabId, |
| @@ -35,11 +36,13 @@ export interface PreviewAutomationInvokeInput { · 18 unmodified lines | |||
| 35 | } | 36 | } |
| 36 | 37 | ||
| 37 | export interface PreviewAutomationBrokerShape { | 38 | 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>>; | ||
| 39 | readonly reportOwner: ( | 42 | readonly reportOwner: ( |
| 40 | owner: PreviewAutomationOwner, | 43 | owner: PreviewAutomationOwner, |
| 41 | ) => Effect.Effect<void, PreviewAutomationError>; | 44 | ) => Effect.Effect<void, PreviewAutomationError>; |
| 42 | readonly clearOwner: (clientId: string) => Effect.Effect<void>; | 45 | readonly clearOwner: (owner: PreviewAutomationOwnerIdentity) => Effect.Effect<void>; |
| 43 | readonly respond: ( | 46 | readonly respond: ( |
| 44 | response: PreviewAutomationResponse, | 47 | response: PreviewAutomationResponse, |
| 45 | ) => Effect.Effect<void, PreviewAutomationError>; | 48 | ) => Effect.Effect<void, PreviewAutomationError>; |
| @@ -63,7 +66,7 @@ interface ClientConnection { · 17 unmodified lines | |||
| 63 | } | 66 | } |
| 64 | 67 | ||
| 65 | interface PendingRequest { | 68 | interface PendingRequest { |
| 66 | readonly clientId: string; | 69 | readonly queue: ClientConnection["queue"]; |
| 67 | readonly deferred: Deferred.Deferred<unknown, PreviewAutomationError>; | 70 | readonly deferred: Deferred.Deferred<unknown, PreviewAutomationError>; |
| 68 | } | 71 | } |
| 69 | 72 | ||
| @@ -133,17 +136,16 @@ const make = Effect.gen(function* PreviewAutomationBrokerMake() { · 63 unmodified lines | |||
| 133 | queue: ClientConnection["queue"], | 136 | queue: ClientConnection["queue"], |
| 134 | ) { | 137 | ) { |
| 135 | const toFail = yield* SynchronizedRef.modify(state, (current) => { | 138 | const toFail = yield* SynchronizedRef.modify(state, (current) => { |
| 136 | if (current.clients.get(clientId)?.queue !== queue) { | ||
| 137 | return [[] as ReadonlyArray<PendingRequest>, current] as const; | ||
| 138 | } | ||
| 139 | const clients = new Map(current.clients); | 139 | const clients = new Map(current.clients); |
| 140 | const owners = new Map(current.owners); | 140 | const owners = new Map(current.owners); |
| 141 | const pending = new Map(current.pending); | 141 | const pending = new Map(current.pending); |
| 142 | const disconnected: PendingRequest[] = []; | 142 | const disconnected: PendingRequest[] = []; |
| 143 | clients.delete(clientId); | 143 | if (current.clients.get(clientId)?.queue === queue) { |
| 144 | owners.delete(clientId); | 144 | clients.delete(clientId); |
| 145 | owners.delete(clientId); | ||
| 146 | } | ||
| 145 | for (const [requestId, entry] of pending) { | 147 | for (const [requestId, entry] of pending) { |
| 146 | if (entry.clientId === clientId) { | 148 | if (entry.queue === queue) { |
| 147 | pending.delete(requestId); | 149 | pending.delete(requestId); |
| 148 | disconnected.push(entry); | 150 | disconnected.push(entry); |
| 149 | } | 151 | } |
| @@ -166,12 +168,22 @@ const make = Effect.gen(function* PreviewAutomationBrokerMake() { · 16 unmodified lines | |||
| 166 | 168 | ||
| 167 | const connect: PreviewAutomationBrokerShape["connect"] = Effect.fn( | 169 | const connect: PreviewAutomationBrokerShape["connect"] = Effect.fn( |
| 168 | "PreviewAutomationBroker.connect", | 170 | "PreviewAutomationBroker.connect", |
| 169 | )(function* (clientId) { | 171 | )(function* (owner) { |
| 172 | const clientId = owner.clientId; | ||
| 170 | const queue = yield* Queue.unbounded<import("@t3tools/contracts").PreviewAutomationRequest>(); | 173 | const queue = yield* Queue.unbounded<import("@t3tools/contracts").PreviewAutomationRequest>(); |
| 171 | const previous = yield* SynchronizedRef.modify(state, (current) => { | 174 | const previous = yield* SynchronizedRef.modify(state, (current) => { |
| 172 | const clients = new Map(current.clients); | 175 | const clients = new Map(current.clients); |
| 176 | const owners = new Map(current.owners); | ||
| 177 | const existingOwner = current.owners.get(clientId); | ||
| 173 | clients.set(clientId, { clientId, queue }); | 178 | 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; | ||
| 175 | }); | 187 | }); |
| 176 | if (previous) yield* disconnect(clientId, previous.queue); | 188 | if (previous) yield* disconnect(clientId, previous.queue); |
| 177 | return Stream.fromQueue(queue).pipe(Stream.ensuring(disconnect(clientId, queue))); | 189 | return Stream.fromQueue(queue).pipe(Stream.ensuring(disconnect(clientId, queue))); |
| @@ -189,10 +201,18 @@ const make = Effect.gen(function* PreviewAutomationBrokerMake() { · 11 unmodified lines | |||
| 189 | 201 | ||
| 190 | const clearOwner: PreviewAutomationBrokerShape["clearOwner"] = Effect.fn( | 202 | const clearOwner: PreviewAutomationBrokerShape["clearOwner"] = Effect.fn( |
| 191 | "PreviewAutomationBroker.clearOwner", | 203 | "PreviewAutomationBroker.clearOwner", |
| 192 | )(function* (clientId) { | 204 | )(function* (owner) { |
| 193 | yield* SynchronizedRef.update(state, (current) => { | 205 | 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 | } | ||
| 194 | const owners = new Map(current.owners); | 214 | const owners = new Map(current.owners); |
| 195 | owners.delete(clientId); | 215 | owners.delete(owner.clientId); |
| 196 | return { ...current, owners }; | 216 | return { ...current, owners }; |
| 197 | }); | 217 | }); |
| 198 | }); | 218 | }); |
| @@ -234,8 +254,13 @@ const make = Effect.gen(function* PreviewAutomationBrokerMake() { · 35 unmodified lines | |||
| 234 | owner.supportsAutomation, | 254 | owner.supportsAutomation, |
| 235 | ) | 255 | ) |
| 236 | .sort((left, right) => right.focusedAt.localeCompare(left.focusedAt)); | 256 | .sort((left, right) => right.focusedAt.localeCompare(left.focusedAt)); |
| 237 | const owner = candidates[0]; | 257 | const owner = candidates.find((candidate) => current.clients.has(candidate.clientId)); |
| 238 | if (!owner) { | 258 | if (!owner) { |
| 259 | if (candidates.length > 0) { | ||
| 260 | return yield* new PreviewAutomationUnavailableError({ | ||
| 261 | message: "The browser host is not connected.", | ||
| 262 | }); | ||
| 263 | } | ||
| 239 | return yield* new PreviewAutomationNoFocusedOwnerError({ | 264 | return yield* new PreviewAutomationNoFocusedOwnerError({ |
| 240 | message: "No desktop browser host is available for this thread.", | 265 | message: "No desktop browser host is available for this thread.", |
| 241 | }); | 266 | }); |
| @@ -246,22 +271,12 @@ const make = Effect.gen(function* PreviewAutomationBrokerMake() { · 4 unmodified lines | |||
| 246 | message: "The browser host is not connected.", | 271 | message: "The browser host is not connected.", |
| 247 | }); | 272 | }); |
| 248 | } | 273 | } |
| 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 | } | ||
| 259 | const timeoutMs = input.timeoutMs ?? 15_000; | 274 | const timeoutMs = input.timeoutMs ?? 15_000; |
| 260 | const deferred = yield* Deferred.make<unknown, PreviewAutomationError>(); | 275 | const deferred = yield* Deferred.make<unknown, PreviewAutomationError>(); |
| 261 | const requestId = yield* SynchronizedRef.modify(state, (next) => { | 276 | const requestId = yield* SynchronizedRef.modify(state, (next) => { |
| 262 | const requestId = `preview-${next.requestSequence}`; | 277 | const requestId = `preview-${next.requestSequence}`; |
| 263 | const pending = new Map(next.pending); | 278 | const pending = new Map(next.pending); |
| 264 | pending.set(requestId, { clientId: owner.clientId, deferred }); | 279 | pending.set(requestId, { queue: connection.queue, deferred }); |
| 265 | return [requestId, { ...next, pending, requestSequence: next.requestSequence + 1 }] as const; | 280 | return [requestId, { ...next, pending, requestSequence: next.requestSequence + 1 }] as const; |
| 266 | }); | 281 | }); |
| 267 | const removePending = SynchronizedRef.update(state, (next) => { | 282 | const removePending = SynchronizedRef.update(state, (next) => { |
| @@ -32,8 +32,8 @@ import { applyServerSettingsPatch } from "@t3tools/shared/serverSettings"; · 31 unmodified lines | |||
| 32 | 32 | ||
| 33 | import { checkCodexProviderStatus, type CodexAppServerProviderSnapshot } from "./CodexProvider.ts"; | 33 | import { checkCodexProviderStatus, type CodexAppServerProviderSnapshot } from "./CodexProvider.ts"; |
| 34 | import { checkClaudeProviderStatus } from "./ClaudeProvider.ts"; | 34 | import { checkClaudeProviderStatus } from "./ClaudeProvider.ts"; |
| 35 | import { OpenCodeRuntimeLive } from "../opencodeRuntime.ts"; | 35 | import * as OpenCodeRuntime from "../opencodeRuntime.ts"; |
| 36 | import { NoOpProviderEventLoggers, ProviderEventLoggers } from "./ProviderEventLoggers.ts"; | 36 | import * as ProviderEventLoggers from "./ProviderEventLoggers.ts"; |
| 37 | import { ProviderInstanceRegistryHydrationLive } from "./ProviderInstanceRegistryHydration.ts"; | 37 | import { ProviderInstanceRegistryHydrationLive } from "./ProviderInstanceRegistryHydration.ts"; |
| 38 | import { | 38 | import { |
| 39 | haveProvidersChanged, | 39 | haveProvidersChanged, |
| @@ -42,12 +42,12 @@ import { · 2 unmodified lines | |||
| 42 | ProviderRegistryLive, | 42 | ProviderRegistryLive, |
| 43 | selectProvidersByKind, | 43 | selectProvidersByKind, |
| 44 | } from "./ProviderRegistry.ts"; | 44 | } from "./ProviderRegistry.ts"; |
| 45 | import { ServerConfig } from "../../config.ts"; | 45 | import * as ServerConfig from "../../config.ts"; |
| 46 | import { ServerSettingsService, type ServerSettingsShape } from "../../serverSettings.ts"; | 46 | import * as ServerSettingsModule from "../../serverSettings.ts"; |
| 47 | import { readProviderStatusCache, resolveProviderStatusCachePath } from "../providerStatusCache.ts"; | 47 | import { readProviderStatusCache, resolveProviderStatusCachePath } from "../providerStatusCache.ts"; |
| 48 | import type { ProviderInstance } from "../ProviderDriver.ts"; | 48 | import type { ProviderInstance } from "../ProviderDriver.ts"; |
| 49 | import { ProviderInstanceRegistry } from "../Services/ProviderInstanceRegistry.ts"; | 49 | import * as ProviderInstanceRegistry from "../Services/ProviderInstanceRegistry.ts"; |
| 50 | import { ProviderRegistry } from "../Services/ProviderRegistry.ts"; | 50 | import * as ProviderRegistry from "../Services/ProviderRegistry.ts"; |
| 51 | import { makeManualOnlyProviderMaintenanceCapabilities } from "../providerMaintenance.ts"; | 51 | import { makeManualOnlyProviderMaintenanceCapabilities } from "../providerMaintenance.ts"; |
| 52 | const decodeServerSettings = Schema.decodeSync(ServerSettings); | 52 | const decodeServerSettings = Schema.decodeSync(ServerSettings); |
| 53 | const encodeServerSettings = Schema.encodeSync(ServerSettings); | 53 | const encodeServerSettings = Schema.encodeSync(ServerSettings); |
| @@ -294,11 +294,11 @@ function makeMutableServerSettingsService( · 240 unmodified lines | |||
| 294 | get streamChanges() { | 294 | get streamChanges() { |
| 295 | return Stream.fromPubSub(changes); | 295 | return Stream.fromPubSub(changes); |
| 296 | }, | 296 | }, |
| 297 | } satisfies ServerSettingsShape; | 297 | } satisfies ServerSettingsModule.ServerSettingsService["Service"]; |
| 298 | }); | 298 | }); |
| 299 | } | 299 | } |
| 300 | 300 | ||
| 301 | it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), TestHttpClientLive))( | 301 | it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsModule.layerTest(), TestHttpClientLive))( |
| 302 | "ProviderRegistry", | 302 | "ProviderRegistry", |
| 303 | (it) => { | 303 | (it) => { |
| 304 | describe("checkCodexProviderStatus", () => { | 304 | describe("checkCodexProviderStatus", () => { |
| @@ -636,14 +636,17 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 331 unmodified lines | |||
| 636 | adapter: {} as ProviderInstance["adapter"], | 636 | adapter: {} as ProviderInstance["adapter"], |
| 637 | textGeneration: {} as ProviderInstance["textGeneration"], | 637 | textGeneration: {} as ProviderInstance["textGeneration"], |
| 638 | } satisfies ProviderInstance; | 638 | } satisfies ProviderInstance; |
| 639 | const instanceRegistryLayer = Layer.succeed(ProviderInstanceRegistry, { | 639 | const instanceRegistryLayer = Layer.succeed( |
| 640 | getInstance: (instanceId) => | 640 | ProviderInstanceRegistry.ProviderInstanceRegistry, |
| 641 | Effect.succeed(instanceId === codexInstanceId ? instance : undefined), | 641 | { |
| 642 | listInstances: Effect.succeed([instance]), | 642 | getInstance: (instanceId) => |
| 643 | listUnavailable: Effect.succeed([]), | 643 | Effect.succeed(instanceId === codexInstanceId ? instance : undefined), |
| 644 | streamChanges: Stream.empty, | 644 | listInstances: Effect.succeed([instance]), |
| 645 | subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), PubSub.subscribe), | 645 | listUnavailable: Effect.succeed([]), |
| 646 | }); | 646 | streamChanges: Stream.empty, |
| 647 | subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), PubSub.subscribe), | ||
| 648 | }, | ||
| 649 | ); | ||
| 647 | const scope = yield* Scope.make(); | 650 | const scope = yield* Scope.make(); |
| 648 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); | 651 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); |
| 649 | const runtimeServices = yield* Layer.build( | 652 | const runtimeServices = yield* Layer.build( |
| @@ -658,7 +661,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 8 unmodified lines | |||
| 658 | ), | 661 | ), |
| 659 | ).pipe(Scope.provide(scope)); | 662 | ).pipe(Scope.provide(scope)); |
| 660 | yield* Effect.gen(function* () { | 663 | yield* Effect.gen(function* () { |
| 661 | const registry = yield* ProviderRegistry; | 664 | const registry = yield* ProviderRegistry.ProviderRegistry; |
| 662 | assert.deepStrictEqual(yield* registry.getProviders, [initialProvider]); | 665 | assert.deepStrictEqual(yield* registry.getProviders, [initialProvider]); |
| 663 | assert.strictEqual(yield* Ref.get(refreshCalls), 0); | 666 | assert.strictEqual(yield* Ref.get(refreshCalls), 0); |
| 664 | }).pipe(Effect.provide(runtimeServices)); | 667 | }).pipe(Effect.provide(runtimeServices)); |
| @@ -786,16 +789,19 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 121 unmodified lines | |||
| 786 | adapter: {} as ProviderInstance["adapter"], | 789 | adapter: {} as ProviderInstance["adapter"], |
| 787 | textGeneration: {} as ProviderInstance["textGeneration"], | 790 | textGeneration: {} as ProviderInstance["textGeneration"], |
| 788 | } satisfies ProviderInstance; | 791 | } satisfies ProviderInstance; |
| 789 | const instanceRegistryLayer = Layer.succeed(ProviderInstanceRegistry, { | 792 | const instanceRegistryLayer = Layer.succeed( |
| 790 | getInstance: (instanceId) => | 793 | ProviderInstanceRegistry.ProviderInstanceRegistry, |
| 791 | Effect.succeed(instanceId === cursorInstanceId ? instance : undefined), | 794 | { |
| 792 | listInstances: Effect.succeed([instance]), | 795 | getInstance: (instanceId) => |
| 793 | listUnavailable: Effect.succeed([]), | 796 | Effect.succeed(instanceId === cursorInstanceId ? instance : undefined), |
| 794 | streamChanges: Stream.empty, | 797 | listInstances: Effect.succeed([instance]), |
| 795 | subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), (pubsub) => | 798 | listUnavailable: Effect.succeed([]), |
| 796 | PubSub.subscribe(pubsub), | 799 | streamChanges: Stream.empty, |
| 797 | ), | 800 | subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), (pubsub) => |
| 798 | }); | 801 | PubSub.subscribe(pubsub), |
| 802 | ), | ||
| 803 | }, | ||
| 804 | ); | ||
| 799 | const scope = yield* Scope.make(); | 805 | const scope = yield* Scope.make(); |
| 800 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); | 806 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); |
| 801 | const runtimeServices = yield* Layer.build( | 807 | const runtimeServices = yield* Layer.build( |
| @@ -811,8 +817,8 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 9 unmodified lines | |||
| 811 | ).pipe(Scope.provide(scope)); | 817 | ).pipe(Scope.provide(scope)); |
| 812 | 818 | ||
| 813 | yield* Effect.gen(function* () { | 819 | yield* Effect.gen(function* () { |
| 814 | const registry = yield* ProviderRegistry; | 820 | const registry = yield* ProviderRegistry.ProviderRegistry; |
| 815 | const config = yield* ServerConfig; | 821 | const config = yield* ServerConfig.ServerConfig; |
| 816 | const filePath = yield* resolveProviderStatusCachePath({ | 822 | const filePath = yield* resolveProviderStatusCachePath({ |
| 817 | cacheDir: config.providerStatusCacheDir, | 823 | cacheDir: config.providerStatusCacheDir, |
| 818 | instanceId: cursorInstanceId, | 824 | instanceId: cursorInstanceId, |
| @@ -880,16 +886,19 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 61 unmodified lines | |||
| 880 | adapter: {} as ProviderInstance["adapter"], | 886 | adapter: {} as ProviderInstance["adapter"], |
| 881 | textGeneration: {} as ProviderInstance["textGeneration"], | 887 | textGeneration: {} as ProviderInstance["textGeneration"], |
| 882 | } satisfies ProviderInstance; | 888 | } satisfies ProviderInstance; |
| 883 | const instanceRegistryLayer = Layer.succeed(ProviderInstanceRegistry, { | 889 | const instanceRegistryLayer = Layer.succeed( |
| 884 | getInstance: (instanceId) => | 890 | ProviderInstanceRegistry.ProviderInstanceRegistry, |
| 885 | Effect.succeed(instanceId === codexInstanceId ? instance : undefined), | 891 | { |
| 886 | listInstances: Effect.succeed([instance]), | 892 | getInstance: (instanceId) => |
| 887 | listUnavailable: Effect.succeed([]), | 893 | Effect.succeed(instanceId === codexInstanceId ? instance : undefined), |
| 888 | streamChanges: Stream.empty, | 894 | listInstances: Effect.succeed([instance]), |
| 889 | subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), (pubsub) => | 895 | listUnavailable: Effect.succeed([]), |
| 890 | PubSub.subscribe(pubsub), | 896 | streamChanges: Stream.empty, |
| 891 | ), | 897 | subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), (pubsub) => |
| 892 | }); | 898 | PubSub.subscribe(pubsub), |
| 899 | ), | ||
| 900 | }, | ||
| 901 | ); | ||
| 893 | const scope = yield* Scope.make(); | 902 | const scope = yield* Scope.make(); |
| 894 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); | 903 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); |
| 895 | const runtimeServices = yield* Layer.build( | 904 | const runtimeServices = yield* Layer.build( |
| @@ -905,7 +914,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 9 unmodified lines | |||
| 905 | ).pipe(Scope.provide(scope)); | 914 | ).pipe(Scope.provide(scope)); |
| 906 | 915 | ||
| 907 | yield* Effect.gen(function* () { | 916 | yield* Effect.gen(function* () { |
| 908 | const registry = yield* ProviderRegistry; | 917 | const registry = yield* ProviderRegistry.ProviderRegistry; |
| 909 | 918 | ||
| 910 | assert.deepStrictEqual(yield* registry.getProviders, [cachedProvider]); | 919 | assert.deepStrictEqual(yield* registry.getProviders, [cachedProvider]); |
| 911 | assert.deepStrictEqual(yield* registry.refresh(codexDriver), [cachedProvider]); | 920 | assert.deepStrictEqual(yield* registry.refresh(codexDriver), [cachedProvider]); |
| @@ -975,25 +984,28 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 63 unmodified lines | |||
| 975 | const instancesRef = yield* Ref.make<ReadonlyArray<ProviderInstance>>([codexInstance]); | 984 | const instancesRef = yield* Ref.make<ReadonlyArray<ProviderInstance>>([codexInstance]); |
| 976 | const failNextList = yield* Ref.make(false); | 985 | const failNextList = yield* Ref.make(false); |
| 977 | const wait = () => Effect.yieldNow; | 986 | const wait = () => Effect.yieldNow; |
| 978 | const instanceRegistryLayer = Layer.succeed(ProviderInstanceRegistry, { | 987 | const instanceRegistryLayer = Layer.succeed( |
| 979 | getInstance: (instanceId) => | 988 | ProviderInstanceRegistry.ProviderInstanceRegistry, |
| 980 | Ref.get(instancesRef).pipe( | 989 | { |
| 981 | Effect.map((instances) => | 990 | getInstance: (instanceId) => |
| 982 | instances.find((instance) => instance.instanceId === instanceId), | 991 | Ref.get(instancesRef).pipe( |
| 992 | Effect.map((instances) => | ||
| 993 | instances.find((instance) => instance.instanceId === instanceId), | ||
| 994 | ), | ||
| 983 | ), | 995 | ), |
| 984 | ), | 996 | listInstances: Effect.gen(function* () { |
| 985 | listInstances: Effect.gen(function* () { | 997 | const shouldFail = yield* Ref.get(failNextList); |
| 986 | const shouldFail = yield* Ref.get(failNextList); | 998 | if (shouldFail) { |
| 987 | if (shouldFail) { | 999 | yield* Ref.set(failNextList, false); |
| 988 | yield* Ref.set(failNextList, false); | 1000 | return yield* Effect.die(new Error("simulated registry list failure")); |
| 989 | return yield* Effect.die(new Error("simulated registry list failure")); | 1001 | } |
| 990 | } | 1002 | return yield* Ref.get(instancesRef); |
| 991 | return yield* Ref.get(instancesRef); | 1003 | }), |
| 992 | }), | 1004 | listUnavailable: Effect.succeed([]), |
| 993 | listUnavailable: Effect.succeed([]), | 1005 | streamChanges: Stream.fromPubSub(changes), |
| 994 | streamChanges: Stream.fromPubSub(changes), | 1006 | subscribeChanges: PubSub.subscribe(changes), |
| 995 | subscribeChanges: PubSub.subscribe(changes), | 1007 | }, |
| 996 | }); | 1008 | ); |
| 997 | const scope = yield* Scope.make(); | 1009 | const scope = yield* Scope.make(); |
| 998 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); | 1010 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); |
| 999 | const runtimeServices = yield* Layer.build( | 1011 | const runtimeServices = yield* Layer.build( |
| @@ -1009,7 +1021,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 9 unmodified lines | |||
| 1009 | ).pipe(Scope.provide(scope)); | 1021 | ).pipe(Scope.provide(scope)); |
| 1010 | 1022 | ||
| 1011 | yield* Effect.gen(function* () { | 1023 | yield* Effect.gen(function* () { |
| 1012 | const registry = yield* ProviderRegistry; | 1024 | const registry = yield* ProviderRegistry.ProviderRegistry; |
| 1013 | assert.deepStrictEqual(yield* registry.getProviders, [codexProvider]); | 1025 | assert.deepStrictEqual(yield* registry.getProviders, [codexProvider]); |
| 1014 | 1026 | ||
| 1015 | yield* Ref.set(failNextList, true); | 1027 | yield* Ref.set(failNextList, true); |
| @@ -1092,15 +1104,22 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 76 unmodified lines | |||
| 1092 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); | 1104 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); |
| 1093 | const providerRegistryLayer = ProviderRegistryLive.pipe( | 1105 | const providerRegistryLayer = ProviderRegistryLive.pipe( |
| 1094 | Layer.provideMerge(ProviderInstanceRegistryHydrationLive), | 1106 | Layer.provideMerge(ProviderInstanceRegistryHydrationLive), |
| 1095 | Layer.provideMerge(Layer.succeed(ServerSettingsService, serverSettings)), | 1107 | Layer.provideMerge( |
| 1108 | Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings), | ||
| 1109 | ), | ||
| 1096 | Layer.provideMerge( | 1110 | Layer.provideMerge( |
| 1097 | ServerConfig.layerTest(process.cwd(), { | 1111 | ServerConfig.layerTest(process.cwd(), { |
| 1098 | prefix: "t3-provider-registry-", | 1112 | prefix: "t3-provider-registry-", |
| 1099 | }), | 1113 | }), |
| 1100 | ), | 1114 | ), |
| 1101 | Layer.provideMerge(TestHttpClientLive), | 1115 | Layer.provideMerge(TestHttpClientLive), |
| 1102 | Layer.provideMerge(Layer.succeed(ProviderEventLoggers, NoOpProviderEventLoggers)), | 1116 | Layer.provideMerge( |
| 1103 | Layer.provideMerge(OpenCodeRuntimeLive), | 1117 | Layer.succeed( |
| 1118 | ProviderEventLoggers.ProviderEventLoggers, | ||
| 1119 | ProviderEventLoggers.NoOpProviderEventLoggers, | ||
| 1120 | ), | ||
| 1121 | ), | ||
| 1122 | Layer.provideMerge(OpenCodeRuntime.OpenCodeRuntimeLive), | ||
| 1104 | // NO spawner mock — `ChildProcessSpawner` is supplied by the | 1123 | // NO spawner mock — `ChildProcessSpawner` is supplied by the |
| 1105 | // outer `NodeServices.layer` on `it.layer(...)` and will | 1124 | // outer `NodeServices.layer` on `it.layer(...)` and will |
| 1106 | // genuinely spawn a subprocess. The missing-binary ENOENT is | 1125 | // 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 | |||
| 1112 | ); | 1131 | ); |
| 1113 | 1132 | ||
| 1114 | yield* Effect.gen(function* () { | 1133 | yield* Effect.gen(function* () { |
| 1115 | const registry = yield* ProviderRegistry; | 1134 | const registry = yield* ProviderRegistry.ProviderRegistry; |
| 1116 | let providers = yield* registry.getProviders; | 1135 | let providers = yield* registry.getProviders; |
| 1117 | for ( | 1136 | for ( |
| 1118 | let attempts = 0; | 1137 | let attempts = 0; |
| @@ -1177,15 +1196,22 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 58 unmodified lines | |||
| 1177 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); | 1196 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); |
| 1178 | const providerRegistryLayer = ProviderRegistryLive.pipe( | 1197 | const providerRegistryLayer = ProviderRegistryLive.pipe( |
| 1179 | Layer.provideMerge(ProviderInstanceRegistryHydrationLive), | 1198 | Layer.provideMerge(ProviderInstanceRegistryHydrationLive), |
| 1180 | Layer.provideMerge(Layer.succeed(ServerSettingsService, serverSettings)), | 1199 | Layer.provideMerge( |
| 1200 | Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings), | ||
| 1201 | ), | ||
| 1181 | Layer.provideMerge( | 1202 | Layer.provideMerge( |
| 1182 | ServerConfig.layerTest(process.cwd(), { | 1203 | ServerConfig.layerTest(process.cwd(), { |
| 1183 | prefix: "t3-provider-registry-", | 1204 | prefix: "t3-provider-registry-", |
| 1184 | }), | 1205 | }), |
| 1185 | ), | 1206 | ), |
| 1186 | Layer.provideMerge(TestHttpClientLive), | 1207 | Layer.provideMerge(TestHttpClientLive), |
| 1187 | Layer.provideMerge(Layer.succeed(ProviderEventLoggers, NoOpProviderEventLoggers)), | 1208 | Layer.provideMerge( |
| 1188 | Layer.provideMerge(OpenCodeRuntimeLive), | 1209 | Layer.succeed( |
| 1210 | ProviderEventLoggers.ProviderEventLoggers, | ||
| 1211 | ProviderEventLoggers.NoOpProviderEventLoggers, | ||
| 1212 | ), | ||
| 1213 | ), | ||
| 1214 | Layer.provideMerge(OpenCodeRuntime.OpenCodeRuntimeLive), | ||
| 1189 | Layer.updateService(ChildProcessSpawner.ChildProcessSpawner, (spawner) => | 1215 | Layer.updateService(ChildProcessSpawner.ChildProcessSpawner, (spawner) => |
| 1190 | ChildProcessSpawner.make((command) => { | 1216 | ChildProcessSpawner.make((command) => { |
| 1191 | spawnedCommands.push((command as { readonly command: string }).command); | 1217 | spawnedCommands.push((command as { readonly command: string }).command); |
| @@ -1199,7 +1225,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 7 unmodified lines | |||
| 1199 | ); | 1225 | ); |
| 1200 | 1226 | ||
| 1201 | yield* Effect.gen(function* () { | 1227 | yield* Effect.gen(function* () { |
| 1202 | const registry = yield* ProviderRegistry; | 1228 | const registry = yield* ProviderRegistry.ProviderRegistry; |
| 1203 | // Boot-time probe: the default codex instance is enabled with | 1229 | // Boot-time probe: the default codex instance is enabled with |
| 1204 | // `firstMissing`, so the real spawner yields ENOENT and the | 1230 | // `firstMissing`, so the real spawner yields ENOENT and the |
| 1205 | // snapshot should be `status: "error"`. | 1231 | // snapshot should be `status: "error"`. |
| @@ -1291,15 +1317,22 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 85 unmodified lines | |||
| 1291 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); | 1317 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); |
| 1292 | const providerRegistryLayer = ProviderRegistryLive.pipe( | 1318 | const providerRegistryLayer = ProviderRegistryLive.pipe( |
| 1293 | Layer.provideMerge(ProviderInstanceRegistryHydrationLive), | 1319 | Layer.provideMerge(ProviderInstanceRegistryHydrationLive), |
| 1294 | Layer.provideMerge(Layer.succeed(ServerSettingsService, serverSettings)), | 1320 | Layer.provideMerge( |
| 1321 | Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings), | ||
| 1322 | ), | ||
| 1295 | Layer.provideMerge( | 1323 | Layer.provideMerge( |
| 1296 | ServerConfig.layerTest(process.cwd(), { | 1324 | ServerConfig.layerTest(process.cwd(), { |
| 1297 | prefix: "t3-provider-registry-", | 1325 | prefix: "t3-provider-registry-", |
| 1298 | }), | 1326 | }), |
| 1299 | ), | 1327 | ), |
| 1300 | Layer.provideMerge(TestHttpClientLive), | 1328 | Layer.provideMerge(TestHttpClientLive), |
| 1301 | Layer.provideMerge(Layer.succeed(ProviderEventLoggers, NoOpProviderEventLoggers)), | 1329 | Layer.provideMerge( |
| 1302 | Layer.provideMerge(OpenCodeRuntimeLive), | 1330 | Layer.succeed( |
| 1331 | ProviderEventLoggers.ProviderEventLoggers, | ||
| 1332 | ProviderEventLoggers.NoOpProviderEventLoggers, | ||
| 1333 | ), | ||
| 1334 | ), | ||
| 1335 | Layer.provideMerge(OpenCodeRuntime.OpenCodeRuntimeLive), | ||
| 1303 | Layer.provideMerge(NodeServices.layer), | 1336 | Layer.provideMerge(NodeServices.layer), |
| 1304 | ); | 1337 | ); |
| 1305 | const runtimeServices = yield* Layer.build(providerRegistryLayer).pipe( | 1338 | const runtimeServices = yield* Layer.build(providerRegistryLayer).pipe( |
| @@ -1307,7 +1340,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 1 unmodified line | |||
| 1307 | ); | 1340 | ); |
| 1308 | 1341 | ||
| 1309 | yield* Effect.gen(function* () { | 1342 | yield* Effect.gen(function* () { |
| 1310 | const registry = yield* ProviderRegistry; | 1343 | const registry = yield* ProviderRegistry.ProviderRegistry; |
| 1311 | const providers = yield* registry.getProviders; | 1344 | const providers = yield* registry.getProviders; |
| 1312 | const ghost = providers.find((provider) => provider.instanceId === "ghost_main"); | 1345 | const ghost = providers.find((provider) => provider.instanceId === "ghost_main"); |
| 1313 | 1346 | ||
| @@ -1345,15 +1378,22 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 31 unmodified lines | |||
| 1345 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); | 1378 | yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void)); |
| 1346 | const providerRegistryLayer = ProviderRegistryLive.pipe( | 1379 | const providerRegistryLayer = ProviderRegistryLive.pipe( |
| 1347 | Layer.provideMerge(ProviderInstanceRegistryHydrationLive), | 1380 | Layer.provideMerge(ProviderInstanceRegistryHydrationLive), |
| 1348 | Layer.provideMerge(Layer.succeed(ServerSettingsService, serverSettings)), | 1381 | Layer.provideMerge( |
| 1382 | Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings), | ||
| 1383 | ), | ||
| 1349 | Layer.provideMerge( | 1384 | Layer.provideMerge( |
| 1350 | ServerConfig.layerTest(process.cwd(), { | 1385 | ServerConfig.layerTest(process.cwd(), { |
| 1351 | prefix: "t3-provider-registry-", | 1386 | prefix: "t3-provider-registry-", |
| 1352 | }), | 1387 | }), |
| 1353 | ), | 1388 | ), |
| 1354 | Layer.provideMerge(TestHttpClientLive), | 1389 | Layer.provideMerge(TestHttpClientLive), |
| 1355 | Layer.provideMerge(Layer.succeed(ProviderEventLoggers, NoOpProviderEventLoggers)), | 1390 | Layer.provideMerge( |
| 1356 | Layer.provideMerge(OpenCodeRuntimeLive), | 1391 | Layer.succeed( |
| 1392 | ProviderEventLoggers.ProviderEventLoggers, | ||
| 1393 | ProviderEventLoggers.NoOpProviderEventLoggers, | ||
| 1394 | ), | ||
| 1395 | ), | ||
| 1396 | Layer.provideMerge(OpenCodeRuntime.OpenCodeRuntimeLive), | ||
| 1357 | Layer.provideMerge( | 1397 | Layer.provideMerge( |
| 1358 | mockCommandSpawnerLayer((command, args) => { | 1398 | mockCommandSpawnerLayer((command, args) => { |
| 1359 | if (command === "agent") { | 1399 | if (command === "agent") { |
| @@ -1380,13 +1420,13 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest(), T · 20 unmodified lines | |||
| 1380 | ); | 1420 | ); |
| 1381 | const runtimeServices = yield* Layer.build( | 1421 | const runtimeServices = yield* Layer.build( |
| 1382 | Layer.mergeAll( | 1422 | Layer.mergeAll( |
| 1383 | Layer.succeed(ServerSettingsService, serverSettings), | 1423 | Layer.succeed(ServerSettingsModule.ServerSettingsService, serverSettings), |
| 1384 | providerRegistryLayer, | 1424 | providerRegistryLayer, |
| 1385 | ), | 1425 | ), |
| 1386 | ).pipe(Scope.provide(scope)); | 1426 | ).pipe(Scope.provide(scope)); |
| 1387 | 1427 | ||
| 1388 | yield* Effect.gen(function* () { | 1428 | yield* Effect.gen(function* () { |
| 1389 | const registry = yield* ProviderRegistry; | 1429 | const registry = yield* ProviderRegistry.ProviderRegistry; |
| 1390 | const providers = yield* registry.getProviders; | 1430 | const providers = yield* registry.getProviders; |
| 1391 | const cursorProvider = providers.find( | 1431 | const cursorProvider = providers.find( |
| 1392 | (provider) => provider.instanceId === ProviderInstanceId.make("cursor"), | 1432 | (provider) => provider.instanceId === ProviderInstanceId.make("cursor"), |
| @@ -3,7 +3,7 @@ import * as Effect from "effect/Effect"; · 2 unmodified lines | |||
| 3 | import * as Equal from "effect/Equal"; | 3 | import * as Equal from "effect/Equal"; |
| 4 | import * as Stream from "effect/Stream"; | 4 | import * as Stream from "effect/Stream"; |
| 5 | 5 | ||
| 6 | import type { ServerSettingsShape } from "../serverSettings.ts"; | 6 | import type * as ServerSettingsModule from "../serverSettings.ts"; |
| 7 | 7 | ||
| 8 | export interface ProviderSnapshotSettings<Settings> { | 8 | export interface ProviderSnapshotSettings<Settings> { |
| 9 | readonly provider: Settings; | 9 | readonly provider: Settings; |
| @@ -29,7 +29,7 @@ export function haveProviderSnapshotSettingsChanged<Settings>( · 19 unmodified lines | |||
| 29 | 29 | ||
| 30 | export function makeProviderSnapshotSettingsSource<Settings>( | 30 | export function makeProviderSnapshotSettingsSource<Settings>( |
| 31 | provider: Settings, | 31 | provider: Settings, |
| 32 | serverSettings: ServerSettingsShape, | 32 | serverSettings: ServerSettingsModule.ServerSettingsService["Service"], |
| 33 | ): { | 33 | ): { |
| 34 | readonly getSettings: Effect.Effect<ProviderSnapshotSettings<Settings>, ServerSettingsError>; | 34 | readonly getSettings: Effect.Effect<ProviderSnapshotSettings<Settings>, ServerSettingsError>; |
| 35 | readonly streamSettings: Stream.Stream<ProviderSnapshotSettings<Settings>>; | 35 | readonly streamSettings: Stream.Stream<ProviderSnapshotSettings<Settings>>; |
| @@ -69,56 +69,30 @@ import { vi } from "vite-plus/test"; · 68 unmodified lines | |||
| 69 | 69 | ||
| 70 | const TEST_EPOCH = DateTime.makeUnsafe("1970-01-01T00:00:00.000Z"); | 70 | const TEST_EPOCH = DateTime.makeUnsafe("1970-01-01T00:00:00.000Z"); |
| 71 | 71 | ||
| 72 | import type { ServerConfigShape } from "./config.ts"; | 72 | import * as ServerConfig from "./config.ts"; |
| 73 | import { deriveServerPaths, ServerConfig } from "./config.ts"; | ||
| 74 | import { makeRoutesLayer } from "./server.ts"; | 73 | import { makeRoutesLayer } from "./server.ts"; |
| 75 | import { | 74 | import * as CheckpointDiffQuery from "./checkpointing/Services/CheckpointDiffQuery.ts"; |
| 76 | CheckpointDiffQuery, | 75 | import * as GitManager from "./git/GitManager.ts"; |
| 77 | type CheckpointDiffQueryShape, | 76 | import * as Keybindings from "./keybindings.ts"; |
| 78 | } from "./checkpointing/Services/CheckpointDiffQuery.ts"; | ||
| 79 | import { GitManager, type GitManagerShape } from "./git/GitManager.ts"; | ||
| 80 | import { Keybindings, type KeybindingsShape } from "./keybindings.ts"; | ||
| 81 | import * as ExternalLauncher from "./process/externalLauncher.ts"; | 77 | import * as ExternalLauncher from "./process/externalLauncher.ts"; |
| 82 | import { | 78 | import * as OrchestrationEngine from "./orchestration/Services/OrchestrationEngine.ts"; |
| 83 | OrchestrationEngineService, | ||
| 84 | type OrchestrationEngineShape, | ||
| 85 | } from "./orchestration/Services/OrchestrationEngine.ts"; | ||
| 86 | import { OrchestrationListenerCallbackError } from "./orchestration/Errors.ts"; | 79 | import { OrchestrationListenerCallbackError } from "./orchestration/Errors.ts"; |
| 87 | import { | 80 | import * as ProjectionSnapshotQuery from "./orchestration/Services/ProjectionSnapshotQuery.ts"; |
| 88 | ProjectionSnapshotQuery, | ||
| 89 | type ProjectionSnapshotQueryShape, | ||
| 90 | } from "./orchestration/Services/ProjectionSnapshotQuery.ts"; | ||
| 91 | import { SqlitePersistenceMemory } from "./persistence/Layers/Sqlite.ts"; | 81 | import { SqlitePersistenceMemory } from "./persistence/Layers/Sqlite.ts"; |
| 92 | import { PersistenceSqlError } from "./persistence/Errors.ts"; | 82 | import { PersistenceSqlError } from "./persistence/Errors.ts"; |
| 93 | import { | 83 | import * as ProviderRegistry from "./provider/Services/ProviderRegistry.ts"; |
| 94 | ProviderRegistry, | ||
| 95 | type ProviderRegistryShape, | ||
| 96 | } from "./provider/Services/ProviderRegistry.ts"; | ||
| 97 | import { makeManualOnlyProviderMaintenanceCapabilities } from "./provider/providerMaintenance.ts"; | 84 | import { makeManualOnlyProviderMaintenanceCapabilities } from "./provider/providerMaintenance.ts"; |
| 98 | import { ServerLifecycleEvents, type ServerLifecycleEventsShape } from "./serverLifecycleEvents.ts"; | 85 | import * as ServerLifecycleEvents from "./serverLifecycleEvents.ts"; |
| 99 | import { ServerRuntimeStartup, type ServerRuntimeStartupShape } from "./serverRuntimeStartup.ts"; | 86 | import * as ServerRuntimeStartup from "./serverRuntimeStartup.ts"; |
| 100 | import { ServerSettingsService, type ServerSettingsShape } from "./serverSettings.ts"; | 87 | import * as ServerSettings from "./serverSettings.ts"; |
| 101 | import { TerminalManager, type TerminalManagerShape } from "./terminal/Services/Manager.ts"; | 88 | import * as TerminalManager from "./terminal/Services/Manager.ts"; |
| 102 | import * as PreviewManager from "./preview/Manager.ts"; | 89 | import * as PreviewManager from "./preview/Manager.ts"; |
| 103 | import * as PortScanner from "./preview/PortScanner.ts"; | 90 | import * as PortScanner from "./preview/PortScanner.ts"; |
| 104 | import { | 91 | import * as BrowserTraceCollector from "./observability/Services/BrowserTraceCollector.ts"; |
| 105 | BrowserTraceCollector, | ||
| 106 | type BrowserTraceCollectorShape, | ||
| 107 | } from "./observability/Services/BrowserTraceCollector.ts"; | ||
| 108 | import { ProjectFaviconResolverLive } from "./project/Layers/ProjectFaviconResolver.ts"; | 92 | import { ProjectFaviconResolverLive } from "./project/Layers/ProjectFaviconResolver.ts"; |
| 109 | import { | 93 | import * as ProjectSetupScriptRunner from "./project/Services/ProjectSetupScriptRunner.ts"; |
| 110 | ProjectSetupScriptRunner, | 94 | import * as RepositoryIdentityResolver from "./project/Services/RepositoryIdentityResolver.ts"; |
| 111 | ProjectSetupScriptRunnerError, | 95 | import * as ServerEnvironment from "./environment/Services/ServerEnvironment.ts"; |
| 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"; | ||
| 122 | import * as WorkspaceEntries from "./workspace/WorkspaceEntries.ts"; | 96 | import * as WorkspaceEntries from "./workspace/WorkspaceEntries.ts"; |
| 123 | import { WorkspaceFileSystemLive } from "./workspace/Layers/WorkspaceFileSystem.ts"; | 97 | import { WorkspaceFileSystemLive } from "./workspace/Layers/WorkspaceFileSystem.ts"; |
| 124 | import { WorkspacePathsLive } from "./workspace/Layers/WorkspacePaths.ts"; | 98 | import { WorkspacePathsLive } from "./workspace/Layers/WorkspacePaths.ts"; |
| @@ -132,10 +106,7 @@ import * as ReviewService from "./review/ReviewService.ts"; · 7 unmodified lines | |||
| 132 | import * as SourceControlRepositoryService from "./sourceControl/SourceControlRepositoryService.ts"; | 106 | import * as SourceControlRepositoryService from "./sourceControl/SourceControlRepositoryService.ts"; |
| 133 | import * as ServerSecretStore from "./auth/ServerSecretStore.ts"; | 107 | import * as ServerSecretStore from "./auth/ServerSecretStore.ts"; |
| 134 | import * as EnvironmentAuth from "./auth/EnvironmentAuth.ts"; | 108 | import * as EnvironmentAuth from "./auth/EnvironmentAuth.ts"; |
| 135 | import { | 109 | import * as CloudManagedEndpointRuntime from "./cloud/ManagedEndpointRuntime.ts"; |
| 136 | CloudManagedEndpointRuntime, | ||
| 137 | type CloudManagedEndpointRuntimeShape, | ||
| 138 | } from "./cloud/ManagedEndpointRuntime.ts"; | ||
| 139 | import * as CloudCliTokenManager from "./cloud/CliTokenManager.ts"; | 110 | import * as CloudCliTokenManager from "./cloud/CliTokenManager.ts"; |
| 140 | import * as ProcessDiagnostics from "./diagnostics/ProcessDiagnostics.ts"; | 111 | import * as ProcessDiagnostics from "./diagnostics/ProcessDiagnostics.ts"; |
| 141 | import * as ProcessResourceMonitor from "./diagnostics/ProcessResourceMonitor.ts"; | 112 | import * as ProcessResourceMonitor from "./diagnostics/ProcessResourceMonitor.ts"; |
| @@ -341,32 +312,40 @@ const makeBrowserOtlpPayload = (spanName: string) => · 199 unmodified lines | |||
| 341 | }); | 312 | }); |
| 342 | 313 | ||
| 343 | const buildAppUnderTest = (options?: { | 314 | const buildAppUnderTest = (options?: { |
| 344 | config?: Partial<ServerConfigShape>; | 315 | config?: Partial<ServerConfig.ServerConfig["Service"]>; |
| 345 | layers?: { | 316 | layers?: { |
| 346 | keybindings?: Partial<KeybindingsShape>; | 317 | keybindings?: Partial<Keybindings.Keybindings["Service"]>; |
| 347 | providerRegistry?: Partial<ProviderRegistryShape>; | 318 | providerRegistry?: Partial<ProviderRegistry.ProviderRegistry["Service"]>; |
| 348 | serverSettings?: Partial<ServerSettingsShape>; | 319 | serverSettings?: Partial<ServerSettings.ServerSettingsService["Service"]>; |
| 349 | externalLauncher?: Partial<ExternalLauncher.ExternalLauncherShape>; | 320 | externalLauncher?: Partial<ExternalLauncher.ExternalLauncher["Service"]>; |
| 350 | vcsDriver?: Partial<VcsDriver.VcsDriverShape>; | 321 | vcsDriver?: Partial<VcsDriver.VcsDriver["Service"]>; |
| 351 | vcsDriverRegistry?: Partial<VcsDriverRegistry.VcsDriverRegistryShape>; | 322 | vcsDriverRegistry?: Partial<VcsDriverRegistry.VcsDriverRegistry["Service"]>; |
| 352 | gitVcsDriver?: Partial<GitVcsDriver.GitVcsDriverShape>; | 323 | gitVcsDriver?: Partial<GitVcsDriver.GitVcsDriver["Service"]>; |
| 353 | gitManager?: Partial<GitManagerShape>; | 324 | gitManager?: Partial<GitManager.GitManager["Service"]>; |
| 354 | sourceControlRepositoryService?: Partial<SourceControlRepositoryService.SourceControlRepositoryServiceShape>; | 325 | sourceControlRepositoryService?: Partial< |
| 355 | reviewService?: Partial<ReviewService.ReviewServiceShape>; | 326 | SourceControlRepositoryService.SourceControlRepositoryService["Service"] |
| 356 | vcsStatusBroadcaster?: Partial<VcsStatusBroadcaster.VcsStatusBroadcasterShape>; | 327 | >; |
| 357 | projectSetupScriptRunner?: Partial<ProjectSetupScriptRunnerShape>; | 328 | reviewService?: Partial<ReviewService.ReviewService["Service"]>; |
| 358 | terminalManager?: Partial<TerminalManagerShape>; | 329 | vcsStatusBroadcaster?: Partial<VcsStatusBroadcaster.VcsStatusBroadcaster["Service"]>; |
| 359 | orchestrationEngine?: Partial<OrchestrationEngineShape>; | 330 | projectSetupScriptRunner?: Partial< |
| 360 | projectionSnapshotQuery?: Partial<ProjectionSnapshotQueryShape>; | 331 | ProjectSetupScriptRunner.ProjectSetupScriptRunner["Service"] |
| 361 | checkpointDiffQuery?: Partial<CheckpointDiffQueryShape>; | 332 | >; |
| 362 | browserTraceCollector?: Partial<BrowserTraceCollectorShape>; | 333 | terminalManager?: Partial<TerminalManager.TerminalManager["Service"]>; |
| 363 | serverLifecycleEvents?: Partial<ServerLifecycleEventsShape>; | 334 | orchestrationEngine?: Partial<OrchestrationEngine.OrchestrationEngineService["Service"]>; |
| 364 | serverRuntimeStartup?: Partial<ServerRuntimeStartupShape>; | 335 | projectionSnapshotQuery?: Partial<ProjectionSnapshotQuery.ProjectionSnapshotQuery["Service"]>; |
| 365 | serverEnvironment?: Partial<ServerEnvironmentShape>; | 336 | checkpointDiffQuery?: Partial<CheckpointDiffQuery.CheckpointDiffQuery["Service"]>; |
| 366 | repositoryIdentityResolver?: Partial<RepositoryIdentityResolverShape>; | 337 | browserTraceCollector?: Partial<BrowserTraceCollector.BrowserTraceCollector["Service"]>; |
| 367 | cloudManagedEndpointRuntime?: Partial<CloudManagedEndpointRuntimeShape>; | 338 | serverLifecycleEvents?: Partial<ServerLifecycleEvents.ServerLifecycleEvents["Service"]>; |
| 368 | relayClient?: Partial<RelayClient.RelayClientShape>; | 339 | serverRuntimeStartup?: Partial<ServerRuntimeStartup.ServerRuntimeStartup["Service"]>; |
| 369 | cloudCliTokenManager?: Partial<CloudCliTokenManager.CloudCliTokenManagerShape>; | 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"]>; | ||
| 370 | }; | 349 | }; |
| 371 | }) => | 350 | }) => |
| 372 | Effect.gen(function* () { | 351 | Effect.gen(function* () { |
| @@ -374,8 +353,8 @@ const buildAppUnderTest = (options?: { · 1 unmodified line | |||
| 374 | const tempBaseDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-router-test-" }); | 353 | const tempBaseDir = yield* fileSystem.makeTempDirectoryScoped({ prefix: "t3-router-test-" }); |
| 375 | const baseDir = options?.config?.baseDir ?? tempBaseDir; | 354 | const baseDir = options?.config?.baseDir ?? tempBaseDir; |
| 376 | const devUrl = options?.config?.devUrl; | 355 | const devUrl = options?.config?.devUrl; |
| 377 | const derivedPaths = yield* deriveServerPaths(baseDir, devUrl); | 356 | const derivedPaths = yield* ServerConfig.deriveServerPaths(baseDir, devUrl); |
| 378 | const config: ServerConfigShape = { | 357 | const config: ServerConfig.ServerConfig["Service"] = { |
| 379 | logLevel: "Info", | 358 | logLevel: "Info", |
| 380 | traceMinLevel: "Info", | 359 | traceMinLevel: "Info", |
| 381 | traceTimingEnabled: true, | 360 | traceTimingEnabled: true, |
| @@ -403,8 +382,8 @@ const buildAppUnderTest = (options?: { · 21 unmodified lines | |||
| 403 | tailscaleServePort: 443, | 382 | tailscaleServePort: 443, |
| 404 | ...options?.config, | 383 | ...options?.config, |
| 405 | }; | 384 | }; |
| 406 | const layerConfig = Layer.succeed(ServerConfig, config); | 385 | const layerConfig = ServerConfig.layer(config); |
| 407 | const defaultVcsDriver: VcsDriver.VcsDriverShape = { | 386 | const defaultVcsDriver: VcsDriver.VcsDriver["Service"] = { |
| 408 | capabilities: { | 387 | capabilities: { |
| 409 | kind: "git", | 388 | kind: "git", |
| 410 | supportsWorktrees: true, | 389 | supportsWorktrees: true, |
| @@ -502,7 +481,7 @@ const buildAppUnderTest = (options?: { · 91 unmodified lines | |||
| 502 | const gitVcsDriverLayer = Layer.mock(GitVcsDriver.GitVcsDriver)({ | 481 | const gitVcsDriverLayer = Layer.mock(GitVcsDriver.GitVcsDriver)({ |
| 503 | ...options?.layers?.gitVcsDriver, | 482 | ...options?.layers?.gitVcsDriver, |
| 504 | }); | 483 | }); |
| 505 | const gitManagerLayer = Layer.mock(GitManager)({ | 484 | const gitManagerLayer = Layer.mock(GitManager.GitManager)({ |
| 506 | ...options?.layers?.gitManager, | 485 | ...options?.layers?.gitManager, |
| 507 | }); | 486 | }); |
| 508 | const workspaceEntriesLayer = WorkspaceEntries.layer.pipe( | 487 | const workspaceEntriesLayer = WorkspaceEntries.layer.pipe( |
| @@ -545,7 +524,7 @@ const buildAppUnderTest = (options?: { · 36 unmodified lines | |||
| 545 | disableLogger: true, | 524 | disableLogger: true, |
| 546 | }).pipe( | 525 | }).pipe( |
| 547 | Layer.provide( | 526 | Layer.provide( |
| 548 | Layer.mock(Keybindings)({ | 527 | Layer.mock(Keybindings.Keybindings)({ |
| 549 | loadConfigState: Effect.succeed({ | 528 | loadConfigState: Effect.succeed({ |
| 550 | keybindings: [], | 529 | keybindings: [], |
| 551 | issues: [], | 530 | issues: [], |
| @@ -555,7 +534,7 @@ const buildAppUnderTest = (options?: { · 3 unmodified lines | |||
| 555 | }), | 534 | }), |
| 556 | ), | 535 | ), |
| 557 | Layer.provide( | 536 | Layer.provide( |
| 558 | Layer.mock(ProviderRegistry)({ | 537 | Layer.mock(ProviderRegistry.ProviderRegistry)({ |
| 559 | getProviders: Effect.succeed([]), | 538 | getProviders: Effect.succeed([]), |
| 560 | refresh: () => Effect.succeed([]), | 539 | refresh: () => Effect.succeed([]), |
| 561 | refreshInstance: () => Effect.succeed([]), | 540 | refreshInstance: () => Effect.succeed([]), |
| @@ -569,7 +548,7 @@ const buildAppUnderTest = (options?: { · 7 unmodified lines | |||
| 569 | }), | 548 | }), |
| 570 | ), | 549 | ), |
| 571 | Layer.provide( | 550 | Layer.provide( |
| 572 | Layer.mock(ServerSettingsService)({ | 551 | Layer.mock(ServerSettings.ServerSettingsService)({ |
| 573 | start: Effect.void, | 552 | start: Effect.void, |
| 574 | ready: Effect.void, | 553 | ready: Effect.void, |
| 575 | getSettings: Effect.succeed(DEFAULT_SERVER_SETTINGS), | 554 | getSettings: Effect.succeed(DEFAULT_SERVER_SETTINGS), |
| @@ -658,13 +637,13 @@ const buildAppUnderTest = (options?: { · 82 unmodified lines | |||
| 658 | ), | 637 | ), |
| 659 | Layer.provideMerge(vcsStatusBroadcasterLayer), | 638 | Layer.provideMerge(vcsStatusBroadcasterLayer), |
| 660 | Layer.provide( | 639 | Layer.provide( |
| 661 | Layer.mock(ProjectSetupScriptRunner)({ | 640 | Layer.mock(ProjectSetupScriptRunner.ProjectSetupScriptRunner)({ |
| 662 | runForThread: () => Effect.succeed({ status: "no-script" as const }), | 641 | runForThread: () => Effect.succeed({ status: "no-script" as const }), |
| 663 | ...options?.layers?.projectSetupScriptRunner, | 642 | ...options?.layers?.projectSetupScriptRunner, |
| 664 | }), | 643 | }), |
| 665 | ), | 644 | ), |
| 666 | Layer.provide( | 645 | Layer.provide( |
| 667 | Layer.mock(TerminalManager)({ | 646 | Layer.mock(TerminalManager.TerminalManager)({ |
| 668 | ...options?.layers?.terminalManager, | 647 | ...options?.layers?.terminalManager, |
| 669 | }), | 648 | }), |
| 670 | ), | 649 | ), |
| @@ -692,7 +671,7 @@ const buildAppUnderTest = (options?: { · 21 unmodified lines | |||
| 692 | ), | 671 | ), |
| 693 | ), | 672 | ), |
| 694 | Layer.provide( | 673 | Layer.provide( |
| 695 | Layer.mock(OrchestrationEngineService)({ | 674 | Layer.mock(OrchestrationEngine.OrchestrationEngineService)({ |
| 696 | readEvents: () => Stream.empty, | 675 | readEvents: () => Stream.empty, |
| 697 | dispatch: () => Effect.succeed({ sequence: 0 }), | 676 | dispatch: () => Effect.succeed({ sequence: 0 }), |
| 698 | streamDomainEvents: Stream.empty, | 677 | streamDomainEvents: Stream.empty, |
| @@ -700,7 +679,7 @@ const buildAppUnderTest = (options?: { · 1 unmodified line | |||
| 700 | }), | 679 | }), |
| 701 | ), | 680 | ), |
| 702 | Layer.provide( | 681 | Layer.provide( |
| 703 | Layer.mock(ProjectionSnapshotQuery)({ | 682 | Layer.mock(ProjectionSnapshotQuery.ProjectionSnapshotQuery)({ |
| 704 | getCommandReadModel: () => Effect.succeed(makeDefaultOrchestrationReadModel()), | 683 | getCommandReadModel: () => Effect.succeed(makeDefaultOrchestrationReadModel()), |
| 705 | getSnapshot: () => Effect.succeed(makeDefaultOrchestrationReadModel()), | 684 | getSnapshot: () => Effect.succeed(makeDefaultOrchestrationReadModel()), |
| 706 | getShellSnapshot: () => | 685 | getShellSnapshot: () => |
| @@ -729,7 +708,7 @@ const buildAppUnderTest = (options?: { · 22 unmodified lines | |||
| 729 | }), | 708 | }), |
| 730 | ), | 709 | ), |
| 731 | Layer.provide( | 710 | Layer.provide( |
| 732 | Layer.mock(CheckpointDiffQuery)({ | 711 | Layer.mock(CheckpointDiffQuery.CheckpointDiffQuery)({ |
| 733 | getTurnDiff: () => | 712 | getTurnDiff: () => |
| 734 | Effect.succeed({ | 713 | Effect.succeed({ |
| 735 | threadId: defaultThreadId, | 714 | threadId: defaultThreadId, |
| @@ -751,13 +730,13 @@ const buildAppUnderTest = (options?: { · 15 unmodified lines | |||
| 751 | 730 | ||
| 752 | const appLayer = servedRoutesLayer.pipe( | 731 | const appLayer = servedRoutesLayer.pipe( |
| 753 | Layer.provide( | 732 | Layer.provide( |
| 754 | Layer.mock(BrowserTraceCollector)({ | 733 | Layer.mock(BrowserTraceCollector.BrowserTraceCollector)({ |
| 755 | record: () => Effect.void, | 734 | record: () => Effect.void, |
| 756 | ...options?.layers?.browserTraceCollector, | 735 | ...options?.layers?.browserTraceCollector, |
| 757 | }), | 736 | }), |
| 758 | ), | 737 | ), |
| 759 | Layer.provide( | 738 | Layer.provide( |
| 760 | Layer.mock(ServerLifecycleEvents)({ | 739 | Layer.mock(ServerLifecycleEvents.ServerLifecycleEvents)({ |
| 761 | publish: (event) => Effect.succeed({ ...(event as any), sequence: 1 }), | 740 | publish: (event) => Effect.succeed({ ...(event as any), sequence: 1 }), |
| 762 | snapshot: Effect.succeed({ sequence: 0, events: [] }), | 741 | snapshot: Effect.succeed({ sequence: 0, events: [] }), |
| 763 | stream: Stream.empty, | 742 | stream: Stream.empty, |
| @@ -765,7 +744,7 @@ const buildAppUnderTest = (options?: { · 1 unmodified line | |||
| 765 | }), | 744 | }), |
| 766 | ), | 745 | ), |
| 767 | Layer.provide( | 746 | Layer.provide( |
| 768 | Layer.mock(ServerRuntimeStartup)({ | 747 | Layer.mock(ServerRuntimeStartup.ServerRuntimeStartup)({ |
| 769 | awaitCommandReady: Effect.void, | 748 | awaitCommandReady: Effect.void, |
| 770 | markHttpListening: Effect.void, | 749 | markHttpListening: Effect.void, |
| 771 | enqueueCommand: (effect) => effect, | 750 | enqueueCommand: (effect) => effect, |
| @@ -773,22 +752,22 @@ const buildAppUnderTest = (options?: { · 1 unmodified line | |||
| 773 | }), | 752 | }), |
| 774 | ), | 753 | ), |
| 775 | Layer.provide( | 754 | Layer.provide( |
| 776 | Layer.mock(ServerEnvironment)({ | 755 | Layer.mock(ServerEnvironment.ServerEnvironment)({ |
| 777 | getEnvironmentId: Effect.succeed(testEnvironmentDescriptor.environmentId), | 756 | getEnvironmentId: Effect.succeed(testEnvironmentDescriptor.environmentId), |
| 778 | getDescriptor: Effect.succeed(testEnvironmentDescriptor), | 757 | getDescriptor: Effect.succeed(testEnvironmentDescriptor), |
| 779 | ...options?.layers?.serverEnvironment, | 758 | ...options?.layers?.serverEnvironment, |
| 780 | }), | 759 | }), |
| 781 | ), | 760 | ), |
| 782 | Layer.provide( | 761 | Layer.provide( |
| 783 | Layer.mock(RepositoryIdentityResolver)({ | 762 | Layer.mock(RepositoryIdentityResolver.RepositoryIdentityResolver)({ |
| 784 | resolve: () => Effect.succeed(null), | 763 | resolve: () => Effect.succeed(null), |
| 785 | ...options?.layers?.repositoryIdentityResolver, | 764 | ...options?.layers?.repositoryIdentityResolver, |
| 786 | }), | 765 | }), |
| 787 | ), | 766 | ), |
| 788 | Layer.provide( | 767 | Layer.provide( |
| 789 | Layer.succeed( | 768 | Layer.succeed( |
| 790 | CloudManagedEndpointRuntime, | 769 | CloudManagedEndpointRuntime.CloudManagedEndpointRuntime, |
| 791 | CloudManagedEndpointRuntime.of({ | 770 | CloudManagedEndpointRuntime.CloudManagedEndpointRuntime.of({ |
| 792 | applyConfig: () => Effect.succeed({ status: "disabled" }), | 771 | applyConfig: () => Effect.succeed({ status: "disabled" }), |
| 793 | ...options?.layers?.cloudManagedEndpointRuntime, | 772 | ...options?.layers?.cloudManagedEndpointRuntime, |
| 794 | }), | 773 | }), |
| @@ -5939,14 +5918,14 @@ it.layer(NodeServices.layer)("server router seam", (it) => { · 5144 unmodified lines | |||
| 5939 | }), | 5918 | }), |
| 5940 | ); | 5919 | ); |
| 5941 | const fetchRemote = vi.fn( | 5920 | const fetchRemote = vi.fn( |
| 5942 | (_: Parameters<GitVcsDriver.GitVcsDriverShape["fetchRemote"]>[0]) => | 5921 | (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["fetchRemote"]>[0]) => |
| 5943 | Effect.sync(() => { | 5922 | Effect.sync(() => { |
| 5944 | bootstrapGitOperations.push("fetch"); | 5923 | bootstrapGitOperations.push("fetch"); |
| 5945 | }), | 5924 | }), |
| 5946 | ); | 5925 | ); |
| 5947 | const fetchedOriginCommit = "0123456789abcdef0123456789abcdef01234567"; | 5926 | const fetchedOriginCommit = "0123456789abcdef0123456789abcdef01234567"; |
| 5948 | const resolveRemoteTrackingCommit = vi.fn( | 5927 | const resolveRemoteTrackingCommit = vi.fn( |
| 5949 | (_: Parameters<GitVcsDriver.GitVcsDriverShape["resolveRemoteTrackingCommit"]>[0]) => | 5928 | (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["resolveRemoteTrackingCommit"]>[0]) => |
| 5950 | Effect.sync(() => { | 5929 | Effect.sync(() => { |
| 5951 | bootstrapGitOperations.push("resolve-remote-commit"); | 5930 | bootstrapGitOperations.push("resolve-remote-commit"); |
| 5952 | return { | 5931 | return { |
| @@ -5956,7 +5935,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { · 3 unmodified lines | |||
| 5956 | }), | 5935 | }), |
| 5957 | ); | 5936 | ); |
| 5958 | const createWorktree = vi.fn( | 5937 | const createWorktree = vi.fn( |
| 5959 | (_: Parameters<GitVcsDriver.GitVcsDriverShape["createWorktree"]>[0]) => | 5938 | (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["createWorktree"]>[0]) => |
| 5960 | Effect.sync(() => { | 5939 | Effect.sync(() => { |
| 5961 | bootstrapGitOperations.push("create-worktree"); | 5940 | bootstrapGitOperations.push("create-worktree"); |
| 5962 | return { | 5941 | return { |
| @@ -5968,7 +5947,11 @@ it.layer(NodeServices.layer)("server router seam", (it) => { · 5 unmodified lines | |||
| 5968 | }), | 5947 | }), |
| 5969 | ); | 5948 | ); |
| 5970 | const runForThread = vi.fn( | 5949 | const runForThread = vi.fn( |
| 5971 | (_: Parameters<ProjectSetupScriptRunnerShape["runForThread"]>[0]) => | 5950 | ( |
| 5951 | _: Parameters< | ||
| 5952 | ProjectSetupScriptRunner.ProjectSetupScriptRunner["Service"]["runForThread"] | ||
| 5953 | >[0], | ||
| 5954 | ) => | ||
| 5972 | Effect.succeed({ | 5955 | Effect.succeed({ |
| 5973 | status: "started" as const, | 5956 | status: "started" as const, |
| 5974 | scriptId: "setup", | 5957 | scriptId: "setup", |
| @@ -6102,7 +6085,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { · 127 unmodified lines | |||
| 6102 | Effect.gen(function* () { | 6085 | Effect.gen(function* () { |
| 6103 | const dispatchedCommands: Array<OrchestrationCommand> = []; | 6086 | const dispatchedCommands: Array<OrchestrationCommand> = []; |
| 6104 | const createWorktree = vi.fn( | 6087 | const createWorktree = vi.fn( |
| 6105 | (_: Parameters<GitVcsDriver.GitVcsDriverShape["createWorktree"]>[0]) => | 6088 | (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["createWorktree"]>[0]) => |
| 6106 | Effect.succeed({ | 6089 | Effect.succeed({ |
| 6107 | worktree: { | 6090 | worktree: { |
| 6108 | refName: "t3code/bootstrap-refName", | 6091 | refName: "t3code/bootstrap-refName", |
| @@ -6111,8 +6094,16 @@ it.layer(NodeServices.layer)("server router seam", (it) => { · 2 unmodified lines | |||
| 6111 | }), | 6094 | }), |
| 6112 | ); | 6095 | ); |
| 6113 | const runForThread = vi.fn( | 6096 | const runForThread = vi.fn( |
| 6114 | (_: Parameters<ProjectSetupScriptRunnerShape["runForThread"]>[0]) => | 6097 | ( |
| 6115 | Effect.fail(new ProjectSetupScriptRunnerError({ message: "pty unavailable" })), | 6098 | _: Parameters< |
| 6099 | ProjectSetupScriptRunner.ProjectSetupScriptRunner["Service"]["runForThread"] | ||
| 6100 | >[0], | ||
| 6101 | ) => | ||
| 6102 | Effect.fail( | ||
| 6103 | new ProjectSetupScriptRunner.ProjectSetupScriptRunnerError({ | ||
| 6104 | message: "pty unavailable", | ||
| 6105 | }), | ||
| 6106 | ), | ||
| 6116 | ); | 6107 | ); |
| 6117 | 6108 | ||
| 6118 | yield* buildAppUnderTest({ | 6109 | yield* buildAppUnderTest({ |
| @@ -6196,7 +6187,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { · 77 unmodified lines | |||
| 6196 | Effect.gen(function* () { | 6187 | Effect.gen(function* () { |
| 6197 | const dispatchedCommands: Array<OrchestrationCommand> = []; | 6188 | const dispatchedCommands: Array<OrchestrationCommand> = []; |
| 6198 | const createWorktree = vi.fn( | 6189 | const createWorktree = vi.fn( |
| 6199 | (_: Parameters<GitVcsDriver.GitVcsDriverShape["createWorktree"]>[0]) => | 6190 | (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["createWorktree"]>[0]) => |
| 6200 | Effect.succeed({ | 6191 | Effect.succeed({ |
| 6201 | worktree: { | 6192 | worktree: { |
| 6202 | refName: "t3code/bootstrap-refName", | 6193 | refName: "t3code/bootstrap-refName", |
| @@ -6205,7 +6196,11 @@ it.layer(NodeServices.layer)("server router seam", (it) => { · 2 unmodified lines | |||
| 6205 | }), | 6196 | }), |
| 6206 | ); | 6197 | ); |
| 6207 | const runForThread = vi.fn( | 6198 | const runForThread = vi.fn( |
| 6208 | (_: Parameters<ProjectSetupScriptRunnerShape["runForThread"]>[0]) => | 6199 | ( |
| 6200 | _: Parameters< | ||
| 6201 | ProjectSetupScriptRunner.ProjectSetupScriptRunner["Service"]["runForThread"] | ||
| 6202 | >[0], | ||
| 6203 | ) => | ||
| 6209 | Effect.succeed({ | 6204 | Effect.succeed({ |
| 6210 | status: "started" as const, | 6205 | status: "started" as const, |
| 6211 | scriptId: "setup", | 6206 | scriptId: "setup", |
| @@ -6315,7 +6310,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { · 103 unmodified lines | |||
| 6315 | Effect.gen(function* () { | 6310 | Effect.gen(function* () { |
| 6316 | const dispatchedCommands: Array<OrchestrationCommand> = []; | 6311 | const dispatchedCommands: Array<OrchestrationCommand> = []; |
| 6317 | const createWorktree = vi.fn( | 6312 | const createWorktree = vi.fn( |
| 6318 | (_: Parameters<GitVcsDriver.GitVcsDriverShape["createWorktree"]>[0]) => | 6313 | (_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["createWorktree"]>[0]) => |
| 6319 | Effect.die(new Error("worktree exploded")), | 6314 | Effect.die(new Error("worktree exploded")), |
| 6320 | ); | 6315 | ); |
| 6321 | 6316 | ||
| @@ -4,7 +4,7 @@ import * as Layer from "effect/Layer"; · 3 unmodified lines | |||
| 4 | import { FetchHttpClient, HttpRouter, HttpServer } from "effect/unstable/http"; | 4 | import { FetchHttpClient, HttpRouter, HttpServer } from "effect/unstable/http"; |
| 5 | import * as HttpApiBuilder from "effect/unstable/httpapi/HttpApiBuilder"; | 5 | import * as HttpApiBuilder from "effect/unstable/httpapi/HttpApiBuilder"; |
| 6 | 6 | ||
| 7 | import { ServerConfig } from "./config.ts"; | 7 | import * as ServerConfig from "./config.ts"; |
| 8 | import { | 8 | import { |
| 9 | otlpTracesProxyRouteLayer, | 9 | otlpTracesProxyRouteLayer, |
| 10 | assetRouteLayer, | 10 | assetRouteLayer, |
| @@ -16,15 +16,15 @@ import { fixPath } from "./os-jank.ts"; · 5 unmodified lines | |||
| 16 | import { websocketRpcRouteLayer } from "./ws.ts"; | 16 | import { websocketRpcRouteLayer } from "./ws.ts"; |
| 17 | import * as ExternalLauncher from "./process/externalLauncher.ts"; | 17 | import * as ExternalLauncher from "./process/externalLauncher.ts"; |
| 18 | import { layerConfig as SqlitePersistenceLayerLive } from "./persistence/Layers/Sqlite.ts"; | 18 | import { layerConfig as SqlitePersistenceLayerLive } from "./persistence/Layers/Sqlite.ts"; |
| 19 | import { ServerLifecycleEventsLive } from "./serverLifecycleEvents.ts"; | 19 | import * as ServerLifecycleEvents from "./serverLifecycleEvents.ts"; |
| 20 | import { AnalyticsServiceLayerLive } from "./telemetry/Layers/AnalyticsService.ts"; | 20 | import { AnalyticsServiceLayerLive } from "./telemetry/Layers/AnalyticsService.ts"; |
| 21 | import { ProviderSessionDirectoryLive } from "./provider/Layers/ProviderSessionDirectory.ts"; | 21 | import { ProviderSessionDirectoryLive } from "./provider/Layers/ProviderSessionDirectory.ts"; |
| 22 | import { ProviderSessionRuntimeRepositoryLive } from "./persistence/Layers/ProviderSessionRuntime.ts"; | 22 | import { ProviderSessionRuntimeRepositoryLive } from "./persistence/Layers/ProviderSessionRuntime.ts"; |
| 23 | import { ProviderAdapterRegistryLive } from "./provider/Layers/ProviderAdapterRegistry.ts"; | 23 | import { ProviderAdapterRegistryLive } from "./provider/Layers/ProviderAdapterRegistry.ts"; |
| 24 | import { ProviderEventLoggersLive } from "./provider/Layers/ProviderEventLoggers.ts"; | 24 | import * as ProviderEventLoggers from "./provider/Layers/ProviderEventLoggers.ts"; |
| 25 | import { ProviderServiceLive } from "./provider/Layers/ProviderService.ts"; | 25 | import { ProviderServiceLive } from "./provider/Layers/ProviderService.ts"; |
| 26 | import { ProviderSessionReaperLive } from "./provider/Layers/ProviderSessionReaper.ts"; | 26 | import { ProviderSessionReaperLive } from "./provider/Layers/ProviderSessionReaper.ts"; |
| 27 | import { OpenCodeRuntimeLive } from "./provider/opencodeRuntime.ts"; | 27 | import * as OpenCodeRuntime from "./provider/opencodeRuntime.ts"; |
| 28 | import { CheckpointDiffQueryLive } from "./checkpointing/Layers/CheckpointDiffQuery.ts"; | 28 | import { CheckpointDiffQueryLive } from "./checkpointing/Layers/CheckpointDiffQuery.ts"; |
| 29 | import { CheckpointStoreLive } from "./checkpointing/Layers/CheckpointStore.ts"; | 29 | import { CheckpointStoreLive } from "./checkpointing/Layers/CheckpointStore.ts"; |
| 30 | import * as AzureDevOpsCli from "./sourceControl/AzureDevOpsCli.ts"; | 30 | import * as AzureDevOpsCli from "./sourceControl/AzureDevOpsCli.ts"; |
| @@ -40,8 +40,8 @@ import * as PreviewManager from "./preview/Manager.ts"; · 9 unmodified lines | |||
| 40 | import * as PortScanner from "./preview/PortScanner.ts"; | 40 | import * as PortScanner from "./preview/PortScanner.ts"; |
| 41 | import * as ProcessRunner from "./processRunner.ts"; | 41 | import * as ProcessRunner from "./processRunner.ts"; |
| 42 | import * as GitManager from "./git/GitManager.ts"; | 42 | import * as GitManager from "./git/GitManager.ts"; |
| 43 | import { KeybindingsLive } from "./keybindings.ts"; | 43 | import * as Keybindings from "./keybindings.ts"; |
| 44 | import { ServerRuntimeStartup, ServerRuntimeStartupLive } from "./serverRuntimeStartup.ts"; | 44 | import * as ServerRuntimeStartup from "./serverRuntimeStartup.ts"; |
| 45 | import { OrchestrationReactorLive } from "./orchestration/Layers/OrchestrationReactor.ts"; | 45 | import { OrchestrationReactorLive } from "./orchestration/Layers/OrchestrationReactor.ts"; |
| 46 | import { RuntimeReceiptBusLive } from "./orchestration/Layers/RuntimeReceiptBus.ts"; | 46 | import { RuntimeReceiptBusLive } from "./orchestration/Layers/RuntimeReceiptBus.ts"; |
| 47 | import { ProviderRuntimeIngestionLive } from "./orchestration/Layers/ProviderRuntimeIngestion.ts"; | 47 | import { ProviderRuntimeIngestionLive } from "./orchestration/Layers/ProviderRuntimeIngestion.ts"; |
| @@ -51,7 +51,7 @@ import { ThreadDeletionReactorLive } from "./orchestration/Layers/ThreadDeletion · 3 unmodified lines | |||
| 51 | import * as AgentAwarenessRelay from "./relay/AgentAwarenessRelay.ts"; | 51 | import * as AgentAwarenessRelay from "./relay/AgentAwarenessRelay.ts"; |
| 52 | import { hasCloudPublicConfig } from "./cloud/publicConfig.ts"; | 52 | import { hasCloudPublicConfig } from "./cloud/publicConfig.ts"; |
| 53 | import { ProviderRegistryLive } from "./provider/Layers/ProviderRegistry.ts"; | 53 | import { ProviderRegistryLive } from "./provider/Layers/ProviderRegistry.ts"; |
| 54 | import { ServerSettingsLive } from "./serverSettings.ts"; | 54 | import * as ServerSettings from "./serverSettings.ts"; |
| 55 | import { ProjectFaviconResolverLive } from "./project/Layers/ProjectFaviconResolver.ts"; | 55 | import { ProjectFaviconResolverLive } from "./project/Layers/ProjectFaviconResolver.ts"; |
| 56 | import { RepositoryIdentityResolverLive } from "./project/Layers/RepositoryIdentityResolver.ts"; | 56 | import { RepositoryIdentityResolverLive } from "./project/Layers/RepositoryIdentityResolver.ts"; |
| 57 | import * as WorkspaceEntries from "./workspace/WorkspaceEntries.ts"; | 57 | import * as WorkspaceEntries from "./workspace/WorkspaceEntries.ts"; |
| @@ -112,14 +112,14 @@ const PtyAdapterLive = Layer.unwrap( · 54 unmodified lines | |||
| 112 | 112 | ||
| 113 | const RelayClientLive = Layer.unwrap( | 113 | const RelayClientLive = Layer.unwrap( |
| 114 | Effect.gen(function* () { | 114 | Effect.gen(function* () { |
| 115 | const config = yield* ServerConfig; | 115 | const config = yield* ServerConfig.ServerConfig; |
| 116 | return RelayClient.layerCloudflared({ baseDir: config.baseDir }); | 116 | return RelayClient.layerCloudflared({ baseDir: config.baseDir }); |
| 117 | }), | 117 | }), |
| 118 | ); | 118 | ); |
| 119 | 119 | ||
| 120 | const HttpServerLive = Layer.unwrap( | 120 | const HttpServerLive = Layer.unwrap( |
| 121 | Effect.gen(function* () { | 121 | Effect.gen(function* () { |
| 122 | const config = yield* ServerConfig; | 122 | const config = yield* ServerConfig.ServerConfig; |
| 123 | if (typeof Bun !== "undefined") { | 123 | if (typeof Bun !== "undefined") { |
| 124 | const BunHttpServer = yield* Effect.promise( | 124 | const BunHttpServer = yield* Effect.promise( |
| 125 | () => import("@effect/platform-bun/BunHttpServer"), | 125 | () => import("@effect/platform-bun/BunHttpServer"), |
| @@ -292,7 +292,7 @@ const RuntimeCoreDependenciesLive = ReactorLayerLive.pipe( · 166 unmodified lines | |||
| 292 | Layer.provideMerge(ProviderRuntimeLayerLive), | 292 | Layer.provideMerge(ProviderRuntimeLayerLive), |
| 293 | Layer.provideMerge(Layer.mergeAll(TerminalLayerLive, PreviewLayerLive)), | 293 | Layer.provideMerge(Layer.mergeAll(TerminalLayerLive, PreviewLayerLive)), |
| 294 | Layer.provideMerge(PersistenceLayerLive), | 294 | Layer.provideMerge(PersistenceLayerLive), |
| 295 | Layer.provideMerge(KeybindingsLive), | 295 | Layer.provideMerge(Keybindings.layer), |
| 296 | Layer.provideMerge(ProviderRegistryLive), | 296 | Layer.provideMerge(ProviderRegistryLive), |
| 297 | // The instance registry is the new routing keystone — text generation, | 297 | // The instance registry is the new routing keystone — text generation, |
| 298 | // adapter lookup, and runtime ingestion all resolve `ProviderInstanceId` | 298 | // adapter lookup, and runtime ingestion all resolve `ProviderInstanceId` |
| @@ -305,14 +305,14 @@ const RuntimeCoreDependenciesLive = ReactorLayerLive.pipe( · 6 unmodified lines | |||
| 305 | // `ProviderService` (canonical stream, written after event normalization). | 305 | // `ProviderService` (canonical stream, written after event normalization). |
| 306 | // Provided once at the runtime level so every consumer sees the same | 306 | // Provided once at the runtime level so every consumer sees the same |
| 307 | // logger instances. | 307 | // logger instances. |
| 308 | Layer.provideMerge(ProviderEventLoggersLive), | 308 | Layer.provideMerge(ProviderEventLoggers.ProviderEventLoggersLive), |
| 309 | // `OpenCodeDriver.create()` yields `OpenCodeRuntime`; previously the old | 309 | // `OpenCodeDriver.create()` yields `OpenCodeRuntime`; previously the old |
| 310 | // `ProviderRegistryLive` pulled `OpenCodeRuntimeLive` in for itself, but | 310 | // `ProviderRegistryLive` pulled `OpenCodeRuntimeLive` in for itself, but |
| 311 | // the rewritten registry reads snapshots off the instance registry and | 311 | // the rewritten registry reads snapshots off the instance registry and |
| 312 | // no longer transitively provides it. Exposing it at the runtime level | 312 | // no longer transitively provides it. Exposing it at the runtime level |
| 313 | // keeps a single Live for all opencode consumers. | 313 | // keeps a single Live for all opencode consumers. |
| 314 | Layer.provideMerge(OpenCodeRuntimeLive), | 314 | Layer.provideMerge(OpenCodeRuntime.OpenCodeRuntimeLive), |
| 315 | Layer.provideMerge(ServerSettingsLive), | 315 | Layer.provideMerge(ServerSettings.layer.pipe(Layer.provide(ServerSecretStore.layer))), |
| 316 | Layer.provideMerge(WorkspaceLayerLive), | 316 | Layer.provideMerge(WorkspaceLayerLive), |
| 317 | Layer.provideMerge(ProjectFaviconResolverLayerLive), | 317 | Layer.provideMerge(ProjectFaviconResolverLayerLive), |
| 318 | Layer.provideMerge(RepositoryIdentityResolverLive), | 318 | Layer.provideMerge(RepositoryIdentityResolverLive), |
| @@ -334,11 +334,11 @@ const RuntimeDependenciesLive = RuntimeCoreDependenciesLive.pipe( · 15 unmodified lines | |||
| 334 | Layer.provideMerge(TraceDiagnostics.layer), | 334 | Layer.provideMerge(TraceDiagnostics.layer), |
| 335 | Layer.provideMerge(AnalyticsServiceLayerLive), | 335 | Layer.provideMerge(AnalyticsServiceLayerLive), |
| 336 | Layer.provideMerge(ExternalLauncher.layer), | 336 | Layer.provideMerge(ExternalLauncher.layer), |
| 337 | Layer.provideMerge(ServerLifecycleEventsLive), | 337 | Layer.provideMerge(ServerLifecycleEvents.layer), |
| 338 | Layer.provide(NetService.layer), | 338 | Layer.provide(NetService.layer), |
| 339 | ); | 339 | ); |
| 340 | 340 | ||
| 341 | const RuntimeServicesLive = ServerRuntimeStartupLive.pipe( | 341 | const RuntimeServicesLive = ServerRuntimeStartup.layer.pipe( |
| 342 | Layer.provideMerge(RuntimeDependenciesLive), | 342 | Layer.provideMerge(RuntimeDependenciesLive), |
| 343 | ); | 343 | ); |
| 344 | 344 | ||
| @@ -361,14 +361,14 @@ export const makeRoutesLayer = Layer.mergeAll( · 16 unmodified lines | |||
| 361 | 361 | ||
| 362 | export const makeServerLayer = Layer.unwrap( | 362 | export const makeServerLayer = Layer.unwrap( |
| 363 | Effect.gen(function* () { | 363 | Effect.gen(function* () { |
| 364 | const config = yield* ServerConfig; | 364 | const config = yield* ServerConfig.ServerConfig; |
| 365 | 365 | ||
| 366 | yield* fixPath(); | 366 | yield* fixPath(); |
| 367 | 367 | ||
| 368 | const httpListeningLayer = Layer.effectDiscard( | 368 | const httpListeningLayer = Layer.effectDiscard( |
| 369 | Effect.gen(function* () { | 369 | Effect.gen(function* () { |
| 370 | yield* HttpServer.HttpServer; | 370 | yield* HttpServer.HttpServer; |
| 371 | const startup = yield* ServerRuntimeStartup; | 371 | const startup = yield* ServerRuntimeStartup.ServerRuntimeStartup; |
| 372 | yield* startup.markHttpListening; | 372 | yield* startup.markHttpListening; |
| 373 | }), | 373 | }), |
| 374 | ); | 374 | ); |
| @@ -4,13 +4,13 @@ import { assertTrue } from "@effect/vitest/utils"; · 3 unmodified lines | |||
| 4 | import * as Effect from "effect/Effect"; | 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as Option from "effect/Option"; | 5 | import * as Option from "effect/Option"; |
| 6 | 6 | ||
| 7 | import { ServerLifecycleEvents, ServerLifecycleEventsLive } from "./serverLifecycleEvents.ts"; | 7 | import * as ServerLifecycleEvents from "./serverLifecycleEvents.ts"; |
| 8 | 8 | ||
| 9 | it.effect( | 9 | it.effect( |
| 10 | "publishes lifecycle events without subscribers and snapshots the latest welcome/ready", | 10 | "publishes lifecycle events without subscribers and snapshots the latest welcome/ready", |
| 11 | () => | 11 | () => |
| 12 | Effect.gen(function* () { | 12 | Effect.gen(function* () { |
| 13 | const lifecycleEvents = yield* ServerLifecycleEvents; | 13 | const lifecycleEvents = yield* ServerLifecycleEvents.ServerLifecycleEvents; |
| 14 | const environment = { | 14 | const environment = { |
| 15 | environmentId: EnvironmentId.make("environment-test"), | 15 | environmentId: EnvironmentId.make("environment-test"), |
| 16 | label: "Test environment", | 16 | label: "Test environment", |
| @@ -49,5 +49,5 @@ it.effect( · 32 unmodified lines | |||
| 49 | const snapshot = yield* lifecycleEvents.snapshot; | 49 | const snapshot = yield* lifecycleEvents.snapshot; |
| 50 | assert.equal(snapshot.sequence, 2); | 50 | assert.equal(snapshot.sequence, 2); |
| 51 | assert.deepEqual(snapshot.events.map((event) => event.type).toSorted(), ["ready", "welcome"]); | 51 | assert.deepEqual(snapshot.events.map((event) => event.type).toSorted(), ["ready", "welcome"]); |
| 52 | }).pipe(Effect.provide(ServerLifecycleEventsLive)), | 52 | }).pipe(Effect.provide(ServerLifecycleEvents.layer)), |
| 53 | ); | 53 | ); |
| @@ -1,9 +1,9 @@ | |||
| 1 | import type { ServerLifecycleStreamEvent } from "@t3tools/contracts"; | 1 | import type { ServerLifecycleStreamEvent } from "@t3tools/contracts"; |
| 2 | import * as Context from "effect/Context"; | ||
| 2 | import * as Effect from "effect/Effect"; | 3 | import * as Effect from "effect/Effect"; |
| 3 | import * as Layer from "effect/Layer"; | 4 | import * as Layer from "effect/Layer"; |
| 4 | import * as PubSub from "effect/PubSub"; | 5 | import * as PubSub from "effect/PubSub"; |
| 5 | import * as Ref from "effect/Ref"; | 6 | import * as Ref from "effect/Ref"; |
| 6 | import * as Context from "effect/Context"; | ||
| 7 | import * as Stream from "effect/Stream"; | 7 | import * as Stream from "effect/Stream"; |
| 8 | 8 | ||
| 9 | type LifecycleEventInput = | 9 | type LifecycleEventInput = |
| @@ -15,44 +15,41 @@ interface SnapshotState { · 5 unmodified lines | |||
| 15 | readonly events: ReadonlyArray<ServerLifecycleStreamEvent>; | 15 | readonly events: ReadonlyArray<ServerLifecycleStreamEvent>; |
| 16 | } | 16 | } |
| 17 | 17 | ||
| 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 | |||
| 24 | export class ServerLifecycleEvents extends Context.Service< | 18 | export class ServerLifecycleEvents extends Context.Service< |
| 25 | ServerLifecycleEvents, | 19 | 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 | } | ||
| 27 | >()("t3/serverLifecycleEvents") {} | 25 | >()("t3/serverLifecycleEvents") {} |
| 28 | 26 | ||
| 29 | export const ServerLifecycleEventsLive = Layer.effect( | 27 | const make = Effect.gen(function* () { |
| 30 | ServerLifecycleEvents, | 28 | const pubsub = yield* PubSub.unbounded<ServerLifecycleStreamEvent>(); |
| 31 | Effect.gen(function* () { | 29 | const state = yield* Ref.make<SnapshotState>({ |
| 32 | const pubsub = yield* PubSub.unbounded<ServerLifecycleStreamEvent>(); | 30 | sequence: 0, |
| 33 | const state = yield* Ref.make<SnapshotState>({ | 31 | events: [], |
| 34 | sequence: 0, | 32 | }); |
| 35 | events: [], | 33 | |
| 36 | }); | 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 | }); | ||
| 37 | 54 | ||
| 38 | return { | 55 | export const layer = Layer.effect(ServerLifecycleEvents, make); |
| 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 | ); | ||
| @@ -10,24 +10,14 @@ import * as PlatformError from "effect/PlatformError"; · 9 unmodified lines | |||
| 10 | import * as Ref from "effect/Ref"; | 10 | import * as Ref from "effect/Ref"; |
| 11 | import * as Stream from "effect/Stream"; | 11 | import * as Stream from "effect/Stream"; |
| 12 | 12 | ||
| 13 | import { ServerConfig } from "./config.ts"; | 13 | import * as ServerConfig from "./config.ts"; |
| 14 | import { | 14 | import * as OrchestrationEngine from "./orchestration/Services/OrchestrationEngine.ts"; |
| 15 | OrchestrationEngineService, | 15 | import * as ProjectionSnapshotQuery from "./orchestration/Services/ProjectionSnapshotQuery.ts"; |
| 16 | type OrchestrationEngineShape, | 16 | import * as AnalyticsService from "./telemetry/Services/AnalyticsService.ts"; |
| 17 | } from "./orchestration/Services/OrchestrationEngine.ts"; | 17 | import * as ServerRuntimeStartup from "./serverRuntimeStartup.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"; | ||
| 28 | 18 | ||
| 29 | it("uses the canonical Codex default for auto-bootstrapped model selection", () => { | 19 | it("uses the canonical Codex default for auto-bootstrapped model selection", () => { |
| 30 | assert.deepStrictEqual(getAutoBootstrapDefaultModelSelection(), { | 20 | assert.deepStrictEqual(ServerRuntimeStartup.getAutoBootstrapDefaultModelSelection(), { |
| 31 | instanceId: ProviderInstanceId.make("codex"), | 21 | instanceId: ProviderInstanceId.make("codex"), |
| 32 | model: DEFAULT_MODEL, | 22 | model: DEFAULT_MODEL, |
| 33 | }); | 23 | }); |
| @@ -37,7 +27,7 @@ it.effect("enqueueCommand waits for readiness and then drains queued work", () = · 3 unmodified lines | |||
| 37 | Effect.scoped( | 27 | Effect.scoped( |
| 38 | Effect.gen(function* () { | 28 | Effect.gen(function* () { |
| 39 | const executionCount = yield* Ref.make(0); | 29 | const executionCount = yield* Ref.make(0); |
| 40 | const commandGate = yield* makeCommandGate; | 30 | const commandGate = yield* ServerRuntimeStartup.makeCommandGate; |
| 41 | 31 | ||
| 42 | const queuedCommandFiber = yield* commandGate | 32 | const queuedCommandFiber = yield* commandGate |
| 43 | .enqueueCommand(Ref.updateAndGet(executionCount, (count) => count + 1)) | 33 | .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 | |||
| 58 | it.effect("enqueueCommand fails queued work when readiness fails", () => | 48 | it.effect("enqueueCommand fails queued work when readiness fails", () => |
| 59 | Effect.scoped( | 49 | Effect.scoped( |
| 60 | Effect.gen(function* () { | 50 | Effect.gen(function* () { |
| 61 | const commandGate = yield* makeCommandGate; | 51 | const commandGate = yield* ServerRuntimeStartup.makeCommandGate; |
| 62 | const failure = yield* Deferred.make<void, never>(); | 52 | const failure = yield* Deferred.make<void, never>(); |
| 63 | 53 | ||
| 64 | const queuedCommandFiber = yield* commandGate | 54 | const queuedCommandFiber = yield* commandGate |
| @@ -66,13 +56,13 @@ it.effect("enqueueCommand fails queued work when readiness fails", () => · 1 unmodified line | |||
| 66 | .pipe(Effect.forkScoped); | 56 | .pipe(Effect.forkScoped); |
| 67 | 57 | ||
| 68 | yield* commandGate.failCommandReady( | 58 | yield* commandGate.failCommandReady( |
| 69 | new ServerRuntimeStartupError({ | 59 | new ServerRuntimeStartup.ServerRuntimeStartupError({ |
| 70 | message: "startup failed", | 60 | stage: "command-readiness", |
| 71 | }), | 61 | }), |
| 72 | ); | 62 | ); |
| 73 | 63 | ||
| 74 | const error = yield* Effect.flip(Fiber.join(queuedCommandFiber)); | 64 | 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."); |
| 76 | }), | 66 | }), |
| 77 | ), | 67 | ), |
| 78 | ); | 68 | ); |
| @@ -82,8 +72,8 @@ it.effect("launchStartupHeartbeat does not block the caller while counts are loa · 3 unmodified lines | |||
| 82 | Effect.gen(function* () { | 72 | Effect.gen(function* () { |
| 83 | const releaseCounts = yield* Deferred.make<void, never>(); | 73 | const releaseCounts = yield* Deferred.make<void, never>(); |
| 84 | 74 | ||
| 85 | yield* launchStartupHeartbeat.pipe( | 75 | yield* ServerRuntimeStartup.launchStartupHeartbeat.pipe( |
| 86 | Effect.provideService(ProjectionSnapshotQuery, { | 76 | Effect.provideService(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { |
| 87 | getCommandReadModel: () => Effect.die("unused"), | 77 | getCommandReadModel: () => Effect.die("unused"), |
| 88 | getSnapshot: () => Effect.die("unused"), | 78 | getSnapshot: () => Effect.die("unused"), |
| 89 | getShellSnapshot: () => Effect.die("unused"), | 79 | getShellSnapshot: () => Effect.die("unused"), |
| @@ -104,7 +94,7 @@ it.effect("launchStartupHeartbeat does not block the caller while counts are loa · 14 unmodified lines | |||
| 104 | getThreadShellById: () => Effect.succeed(Option.none()), | 94 | getThreadShellById: () => Effect.succeed(Option.none()), |
| 105 | getThreadDetailById: () => Effect.succeed(Option.none()), | 95 | getThreadDetailById: () => Effect.succeed(Option.none()), |
| 106 | }), | 96 | }), |
| 107 | Effect.provideService(AnalyticsService, { | 97 | Effect.provideService(AnalyticsService.AnalyticsService, { |
| 108 | record: () => Effect.void, | 98 | record: () => Effect.void, |
| 109 | flush: Effect.void, | 99 | flush: Effect.void, |
| 110 | }), | 100 | }), |
| @@ -115,8 +105,8 @@ it.effect("launchStartupHeartbeat does not block the caller while counts are loa · 4 unmodified lines | |||
| 115 | 105 | ||
| 116 | it.effect("resolveWelcomeBase derives cwd and project name from server config", () => | 106 | it.effect("resolveWelcomeBase derives cwd and project name from server config", () => |
| 117 | Effect.gen(function* () { | 107 | Effect.gen(function* () { |
| 118 | const welcome = yield* resolveWelcomeBase.pipe( | 108 | const welcome = yield* ServerRuntimeStartup.resolveWelcomeBase.pipe( |
| 119 | Effect.provideService(ServerConfig, { | 109 | Effect.provideService(ServerConfig.ServerConfig, { |
| 120 | cwd: "/tmp/startup-project", | 110 | cwd: "/tmp/startup-project", |
| 121 | } as never), | 111 | } as never), |
| 122 | ); | 112 | ); |
| @@ -134,12 +124,12 @@ it.effect("resolveAutoBootstrapWelcomeTargets returns existing project and threa · 11 unmodified lines | |||
| 134 | 124 | ||
| 135 | return Effect.gen(function* () { | 125 | return Effect.gen(function* () { |
| 136 | const dispatchCalls = yield* Ref.make<ReadonlyArray<string>>([]); | 126 | const dispatchCalls = yield* Ref.make<ReadonlyArray<string>>([]); |
| 137 | const targets = yield* resolveAutoBootstrapWelcomeTargets.pipe( | 127 | const targets = yield* ServerRuntimeStartup.resolveAutoBootstrapWelcomeTargets.pipe( |
| 138 | Effect.provideService(ServerConfig, { | 128 | Effect.provideService(ServerConfig.ServerConfig, { |
| 139 | cwd: "/tmp/startup-project", | 129 | cwd: "/tmp/startup-project", |
| 140 | autoBootstrapProjectFromCwd: true, | 130 | autoBootstrapProjectFromCwd: true, |
| 141 | } as never), | 131 | } as never), |
| 142 | Effect.provideService(ProjectionSnapshotQuery, { | 132 | Effect.provideService(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { |
| 143 | getCommandReadModel: () => Effect.die("unused"), | 133 | getCommandReadModel: () => Effect.die("unused"), |
| 144 | getSnapshot: () => Effect.die("unused"), | 134 | getSnapshot: () => Effect.die("unused"), |
| 145 | getShellSnapshot: () => Effect.die("unused"), | 135 | getShellSnapshot: () => Effect.die("unused"), |
| @@ -152,7 +142,7 @@ it.effect("resolveAutoBootstrapWelcomeTargets returns existing project and threa · 6 unmodified lines | |||
| 152 | id: bootstrapProjectId, | 142 | id: bootstrapProjectId, |
| 153 | title: "Startup Project", | 143 | title: "Startup Project", |
| 154 | workspaceRoot: "/tmp/startup-project", | 144 | workspaceRoot: "/tmp/startup-project", |
| 155 | defaultModelSelection: getAutoBootstrapDefaultModelSelection(), | 145 | defaultModelSelection: ServerRuntimeStartup.getAutoBootstrapDefaultModelSelection(), |
| 156 | scripts: [], | 146 | scripts: [], |
| 157 | createdAt: "2026-01-01T00:00:00.000Z", | 147 | createdAt: "2026-01-01T00:00:00.000Z", |
| 158 | updatedAt: "2026-01-01T00:00:00.000Z", | 148 | updatedAt: "2026-01-01T00:00:00.000Z", |
| @@ -166,14 +156,14 @@ it.effect("resolveAutoBootstrapWelcomeTargets returns existing project and threa · 7 unmodified lines | |||
| 166 | getThreadShellById: () => Effect.die("unused"), | 156 | getThreadShellById: () => Effect.die("unused"), |
| 167 | getThreadDetailById: () => Effect.die("unused"), | 157 | getThreadDetailById: () => Effect.die("unused"), |
| 168 | }), | 158 | }), |
| 169 | Effect.provideService(OrchestrationEngineService, { | 159 | Effect.provideService(OrchestrationEngine.OrchestrationEngineService, { |
| 170 | readEvents: () => Stream.empty, | 160 | readEvents: () => Stream.empty, |
| 171 | dispatch: (command) => | 161 | dispatch: (command) => |
| 172 | Ref.update(dispatchCalls, (calls) => [...calls, command.type]).pipe( | 162 | Ref.update(dispatchCalls, (calls) => [...calls, command.type]).pipe( |
| 173 | Effect.as({ sequence: 1 }), | 163 | Effect.as({ sequence: 1 }), |
| 174 | ), | 164 | ), |
| 175 | streamDomainEvents: Stream.empty, | 165 | streamDomainEvents: Stream.empty, |
| 176 | } satisfies OrchestrationEngineShape), | 166 | } satisfies OrchestrationEngine.OrchestrationEngineService["Service"]), |
| 177 | Effect.provide(NodeServices.layer), | 167 | Effect.provide(NodeServices.layer), |
| 178 | ); | 168 | ); |
| 179 | 169 | ||
| @@ -188,12 +178,12 @@ it.effect("resolveAutoBootstrapWelcomeTargets returns existing project and threa · 8 unmodified lines | |||
| 188 | it.effect("resolveAutoBootstrapWelcomeTargets creates a project and thread when missing", () => | 178 | it.effect("resolveAutoBootstrapWelcomeTargets creates a project and thread when missing", () => |
| 189 | Effect.gen(function* () { | 179 | Effect.gen(function* () { |
| 190 | const dispatchCalls = yield* Ref.make<ReadonlyArray<string>>([]); | 180 | const dispatchCalls = yield* Ref.make<ReadonlyArray<string>>([]); |
| 191 | const targets = yield* resolveAutoBootstrapWelcomeTargets.pipe( | 181 | const targets = yield* ServerRuntimeStartup.resolveAutoBootstrapWelcomeTargets.pipe( |
| 192 | Effect.provideService(ServerConfig, { | 182 | Effect.provideService(ServerConfig.ServerConfig, { |
| 193 | cwd: "/tmp/startup-project", | 183 | cwd: "/tmp/startup-project", |
| 194 | autoBootstrapProjectFromCwd: true, | 184 | autoBootstrapProjectFromCwd: true, |
| 195 | } as never), | 185 | } as never), |
| 196 | Effect.provideService(ProjectionSnapshotQuery, { | 186 | Effect.provideService(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { |
| 197 | getCommandReadModel: () => Effect.die("unused"), | 187 | getCommandReadModel: () => Effect.die("unused"), |
| 198 | getSnapshot: () => Effect.die("unused"), | 188 | getSnapshot: () => Effect.die("unused"), |
| 199 | getShellSnapshot: () => Effect.die("unused"), | 189 | getShellSnapshot: () => Effect.die("unused"), |
| @@ -208,14 +198,14 @@ it.effect("resolveAutoBootstrapWelcomeTargets creates a project and thread when · 8 unmodified lines | |||
| 208 | getThreadShellById: () => Effect.die("unused"), | 198 | getThreadShellById: () => Effect.die("unused"), |
| 209 | getThreadDetailById: () => Effect.die("unused"), | 199 | getThreadDetailById: () => Effect.die("unused"), |
| 210 | }), | 200 | }), |
| 211 | Effect.provideService(OrchestrationEngineService, { | 201 | Effect.provideService(OrchestrationEngine.OrchestrationEngineService, { |
| 212 | readEvents: () => Stream.empty, | 202 | readEvents: () => Stream.empty, |
| 213 | dispatch: (command) => | 203 | dispatch: (command) => |
| 214 | Ref.update(dispatchCalls, (calls) => [...calls, command.type]).pipe( | 204 | Ref.update(dispatchCalls, (calls) => [...calls, command.type]).pipe( |
| 215 | Effect.as({ sequence: 1 }), | 205 | Effect.as({ sequence: 1 }), |
| 216 | ), | 206 | ), |
| 217 | streamDomainEvents: Stream.empty, | 207 | streamDomainEvents: Stream.empty, |
| 218 | } satisfies OrchestrationEngineShape), | 208 | } satisfies OrchestrationEngine.OrchestrationEngineService["Service"]), |
| 219 | Effect.provide(NodeServices.layer), | 209 | Effect.provide(NodeServices.layer), |
| 220 | ); | 210 | ); |
| 221 | 211 | ||
| @@ -236,12 +226,12 @@ it.effect("resolveAutoBootstrapWelcomeTargets preserves typed UUID generation fa · 14 unmodified lines | |||
| 236 | }); | 226 | }); |
| 237 | const dispatchCalls = yield* Ref.make<ReadonlyArray<string>>([]); | 227 | const dispatchCalls = yield* Ref.make<ReadonlyArray<string>>([]); |
| 238 | 228 | ||
| 239 | const error = yield* resolveAutoBootstrapWelcomeTargets.pipe( | 229 | const error = yield* ServerRuntimeStartup.resolveAutoBootstrapWelcomeTargets.pipe( |
| 240 | Effect.provideService(ServerConfig, { | 230 | Effect.provideService(ServerConfig.ServerConfig, { |
| 241 | cwd: "/tmp/startup-project", | 231 | cwd: "/tmp/startup-project", |
| 242 | autoBootstrapProjectFromCwd: true, | 232 | autoBootstrapProjectFromCwd: true, |
| 243 | } as never), | 233 | } as never), |
| 244 | Effect.provideService(ProjectionSnapshotQuery, { | 234 | Effect.provideService(ProjectionSnapshotQuery.ProjectionSnapshotQuery, { |
| 245 | getCommandReadModel: () => Effect.die("unused"), | 235 | getCommandReadModel: () => Effect.die("unused"), |
| 246 | getSnapshot: () => Effect.die("unused"), | 236 | getSnapshot: () => Effect.die("unused"), |
| 247 | getShellSnapshot: () => Effect.die("unused"), | 237 | getShellSnapshot: () => Effect.die("unused"), |
| @@ -256,14 +246,14 @@ it.effect("resolveAutoBootstrapWelcomeTargets preserves typed UUID generation fa · 8 unmodified lines | |||
| 256 | getThreadShellById: () => Effect.die("unused"), | 246 | getThreadShellById: () => Effect.die("unused"), |
| 257 | getThreadDetailById: () => Effect.die("unused"), | 247 | getThreadDetailById: () => Effect.die("unused"), |
| 258 | }), | 248 | }), |
| 259 | Effect.provideService(OrchestrationEngineService, { | 249 | Effect.provideService(OrchestrationEngine.OrchestrationEngineService, { |
| 260 | readEvents: () => Stream.empty, | 250 | readEvents: () => Stream.empty, |
| 261 | dispatch: (command) => | 251 | dispatch: (command) => |
| 262 | Ref.update(dispatchCalls, (calls) => [...calls, command.type]).pipe( | 252 | Ref.update(dispatchCalls, (calls) => [...calls, command.type]).pipe( |
| 263 | Effect.as({ sequence: 1 }), | 253 | Effect.as({ sequence: 1 }), |
| 264 | ), | 254 | ), |
| 265 | streamDomainEvents: Stream.empty, | 255 | streamDomainEvents: Stream.empty, |
| 266 | } satisfies OrchestrationEngineShape), | 256 | } satisfies OrchestrationEngine.OrchestrationEngineService["Service"]), |
| 267 | Effect.provideService(Crypto.Crypto, { | 257 | Effect.provideService(Crypto.Crypto, { |
| 268 | ...crypto, | 258 | ...crypto, |
| 269 | randomUUIDv4: Effect.fail(uuidError), | 259 | randomUUIDv4: Effect.fail(uuidError), |
| @@ -7,8 +7,10 @@ import { · 6 unmodified lines | |||
| 7 | ProviderInstanceId, | 7 | ProviderInstanceId, |
| 8 | ThreadId, | 8 | ThreadId, |
| 9 | } from "@t3tools/contracts"; | 9 | } from "@t3tools/contracts"; |
| 10 | import * as Data from "effect/Data"; | 10 | import * as Console from "effect/Console"; |
| 11 | import * as Context from "effect/Context"; | ||
| 11 | import * as Crypto from "effect/Crypto"; | 12 | import * as Crypto from "effect/Crypto"; |
| 13 | import * as DateTime from "effect/DateTime"; | ||
| 12 | import * as Deferred from "effect/Deferred"; | 14 | import * as Deferred from "effect/Deferred"; |
| 13 | import * as Effect from "effect/Effect"; | 15 | import * as Effect from "effect/Effect"; |
| 14 | import * as Exit from "effect/Exit"; | 16 | import * as Exit from "effect/Exit"; |
| @@ -17,23 +19,21 @@ import * as Option from "effect/Option"; · 2 unmodified lines | |||
| 17 | import * as Path from "effect/Path"; | 19 | import * as Path from "effect/Path"; |
| 18 | import * as Queue from "effect/Queue"; | 20 | import * as Queue from "effect/Queue"; |
| 19 | import * as Ref from "effect/Ref"; | 21 | import * as Ref from "effect/Ref"; |
| 22 | import * as Schema from "effect/Schema"; | ||
| 20 | import * as Scope from "effect/Scope"; | 23 | 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"; | ||
| 24 | 24 | ||
| 25 | import { ServerConfig } from "./config.ts"; | 25 | import * as ServerConfig from "./config.ts"; |
| 26 | import { Keybindings } from "./keybindings.ts"; | 26 | import * as Keybindings from "./keybindings.ts"; |
| 27 | import * as ExternalLauncher from "./process/externalLauncher.ts"; | 27 | import * as ExternalLauncher from "./process/externalLauncher.ts"; |
| 28 | import { OrchestrationEngineService } from "./orchestration/Services/OrchestrationEngine.ts"; | 28 | import * as OrchestrationEngine from "./orchestration/Services/OrchestrationEngine.ts"; |
| 29 | import { ProjectionSnapshotQuery } from "./orchestration/Services/ProjectionSnapshotQuery.ts"; | 29 | import * as ProjectionSnapshotQuery from "./orchestration/Services/ProjectionSnapshotQuery.ts"; |
| 30 | import { OrchestrationReactor } from "./orchestration/Services/OrchestrationReactor.ts"; | 30 | import * as OrchestrationReactor from "./orchestration/Services/OrchestrationReactor.ts"; |
| 31 | import { ServerLifecycleEvents } from "./serverLifecycleEvents.ts"; | 31 | import * as ServerLifecycleEvents from "./serverLifecycleEvents.ts"; |
| 32 | import { ServerSettingsService } from "./serverSettings.ts"; | 32 | import * as ServerSettings from "./serverSettings.ts"; |
| 33 | import { ServerEnvironment } from "./environment/Services/ServerEnvironment.ts"; | 33 | import * as ServerEnvironment from "./environment/Services/ServerEnvironment.ts"; |
| 34 | import { AnalyticsService } from "./telemetry/Services/AnalyticsService.ts"; | 34 | import * as AnalyticsService from "./telemetry/Services/AnalyticsService.ts"; |
| 35 | import * as EnvironmentAuth from "./auth/EnvironmentAuth.ts"; | 35 | import * as EnvironmentAuth from "./auth/EnvironmentAuth.ts"; |
| 36 | import { ProviderSessionReaper } from "./provider/Services/ProviderSessionReaper.ts"; | 36 | import * as ProviderSessionReaper from "./provider/Services/ProviderSessionReaper.ts"; |
| 37 | import { | 37 | import { |
| 38 | formatHeadlessServeOutput, | 38 | formatHeadlessServeOutput, |
| 39 | formatHostForUrl, | 39 | formatHostForUrl, |
| @@ -41,22 +41,30 @@ import { · 1 unmodified line | |||
| 41 | issueHeadlessServeAccessInfo, | 41 | issueHeadlessServeAccessInfo, |
| 42 | } from "./startupAccess.ts"; | 42 | } from "./startupAccess.ts"; |
| 43 | 43 | ||
| 44 | export class ServerRuntimeStartupError extends Data.TaggedError("ServerRuntimeStartupError")<{ | 44 | export class ServerRuntimeStartupError extends Schema.TaggedErrorClass<ServerRuntimeStartupError>()( |
| 45 | readonly message: string; | 45 | "ServerRuntimeStartupError", |
| 46 | readonly cause?: unknown; | 46 | { |
| 47 | }> {} | 47 | stage: Schema.Literal("command-readiness"), |
| 48 | 48 | cause: Schema.optional(Schema.Defect()), | |
| 49 | export interface ServerRuntimeStartupShape { | 49 | }, |
| 50 | readonly awaitCommandReady: Effect.Effect<void, ServerRuntimeStartupError>; | 50 | ) { |
| 51 | readonly markHttpListening: Effect.Effect<void>; | 51 | override get message(): string { |
| 52 | readonly enqueueCommand: <A, E>( | 52 | switch (this.stage) { |
| 53 | effect: Effect.Effect<A, E>, | 53 | case "command-readiness": |
| 54 | ) => Effect.Effect<A, E | ServerRuntimeStartupError>; | 54 | return "Server runtime startup failed before command readiness."; |
| 55 | } | ||
| 56 | } | ||
| 55 | } | 57 | } |
| 56 | 58 | ||
| 57 | export class ServerRuntimeStartup extends Context.Service< | 59 | export class ServerRuntimeStartup extends Context.Service< |
| 58 | ServerRuntimeStartup, | 60 | 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 | } | ||
| 60 | >()("t3/serverRuntimeStartup") {} | 68 | >()("t3/serverRuntimeStartup") {} |
| 61 | 69 | ||
| 62 | interface QueuedCommand { | 70 | interface QueuedCommand { |
| @@ -124,8 +132,8 @@ export const makeCommandGate = Effect.gen(function* () { · 61 unmodified lines | |||
| 124 | }); | 132 | }); |
| 125 | 133 | ||
| 126 | export const recordStartupHeartbeat = Effect.gen(function* () { | 134 | export const recordStartupHeartbeat = Effect.gen(function* () { |
| 127 | const analytics = yield* AnalyticsService; | 135 | const analytics = yield* AnalyticsService.AnalyticsService; |
| 128 | const projectionSnapshotQuery = yield* ProjectionSnapshotQuery; | 136 | const projectionSnapshotQuery = yield* ProjectionSnapshotQuery.ProjectionSnapshotQuery; |
| 129 | 137 | ||
| 130 | const { threadCount, projectCount } = yield* projectionSnapshotQuery.getCounts().pipe( | 138 | const { threadCount, projectCount } = yield* projectionSnapshotQuery.getCounts().pipe( |
| 131 | Effect.catch((cause) => | 139 | Effect.catch((cause) => |
| @@ -160,7 +168,7 @@ export const getAutoBootstrapDefaultModelSelection = (): ModelSelection => ({ · 28 unmodified lines | |||
| 160 | }); | 168 | }); |
| 161 | 169 | ||
| 162 | export const resolveWelcomeBase = Effect.gen(function* () { | 170 | export const resolveWelcomeBase = Effect.gen(function* () { |
| 163 | const serverConfig = yield* ServerConfig; | 171 | const serverConfig = yield* ServerConfig.ServerConfig; |
| 164 | const segments = serverConfig.cwd.split(/[/\\]/).filter(Boolean); | 172 | const segments = serverConfig.cwd.split(/[/\\]/).filter(Boolean); |
| 165 | const projectName = segments[segments.length - 1] ?? "project"; | 173 | const projectName = segments[segments.length - 1] ?? "project"; |
| 166 | 174 | ||
| @@ -173,9 +181,9 @@ export const resolveWelcomeBase = Effect.gen(function* () { · 6 unmodified lines | |||
| 173 | export const resolveAutoBootstrapWelcomeTargets = Effect.gen(function* () { | 181 | export const resolveAutoBootstrapWelcomeTargets = Effect.gen(function* () { |
| 174 | const crypto = yield* Crypto.Crypto; | 182 | const crypto = yield* Crypto.Crypto; |
| 175 | const randomUUID = crypto.randomUUIDv4; | 183 | const randomUUID = crypto.randomUUIDv4; |
| 176 | const serverConfig = yield* ServerConfig; | 184 | const serverConfig = yield* ServerConfig.ServerConfig; |
| 177 | const projectionReadModelQuery = yield* ProjectionSnapshotQuery; | 185 | const projectionReadModelQuery = yield* ProjectionSnapshotQuery.ProjectionSnapshotQuery; |
| 178 | const orchestrationEngine = yield* OrchestrationEngineService; | 186 | const orchestrationEngine = yield* OrchestrationEngine.OrchestrationEngineService; |
| 179 | const path = yield* Path.Path; | 187 | const path = yield* Path.Path; |
| 180 | 188 | ||
| 181 | let bootstrapProjectId: ProjectId | undefined; | 189 | let bootstrapProjectId: ProjectId | undefined; |
| @@ -243,7 +251,7 @@ export const resolveAutoBootstrapWelcomeTargets = Effect.gen(function* () { · 61 unmodified lines | |||
| 243 | }); | 251 | }); |
| 244 | 252 | ||
| 245 | const resolveStartupBrowserTarget = Effect.gen(function* () { | 253 | const resolveStartupBrowserTarget = Effect.gen(function* () { |
| 246 | const serverConfig = yield* ServerConfig; | 254 | const serverConfig = yield* ServerConfig.ServerConfig; |
| 247 | const serverAuth = yield* EnvironmentAuth.EnvironmentAuth; | 255 | const serverAuth = yield* EnvironmentAuth.EnvironmentAuth; |
| 248 | const localUrl = `http://localhost:${serverConfig.port}`; | 256 | const localUrl = `http://localhost:${serverConfig.port}`; |
| 249 | const bindUrl = | 257 | const bindUrl = |
| @@ -260,7 +268,7 @@ const resolveStartupBrowserTarget = Effect.gen(function* () { · 10 unmodified lines | |||
| 260 | 268 | ||
| 261 | const maybeOpenBrowser = (target: string) => | 269 | const maybeOpenBrowser = (target: string) => |
| 262 | Effect.gen(function* () { | 270 | Effect.gen(function* () { |
| 263 | const serverConfig = yield* ServerConfig; | 271 | const serverConfig = yield* ServerConfig.ServerConfig; |
| 264 | if (serverConfig.noBrowser) { | 272 | if (serverConfig.noBrowser) { |
| 265 | return; | 273 | return; |
| 266 | } | 274 | } |
| @@ -281,14 +289,14 @@ const runStartupPhase = <A, E, R>(phase: string, effect: Effect.Effect<A, E, R>) · 14 unmodified lines | |||
| 281 | Effect.withSpan(`server.startup.${phase}`), | 289 | Effect.withSpan(`server.startup.${phase}`), |
| 282 | ); | 290 | ); |
| 283 | 291 | ||
| 284 | export const makeServerRuntimeStartup = Effect.gen(function* () { | 292 | const make = Effect.gen(function* () { |
| 285 | const serverConfig = yield* ServerConfig; | 293 | const serverConfig = yield* ServerConfig.ServerConfig; |
| 286 | const keybindings = yield* Keybindings; | 294 | const keybindings = yield* Keybindings.Keybindings; |
| 287 | const orchestrationReactor = yield* OrchestrationReactor; | 295 | const orchestrationReactor = yield* OrchestrationReactor.OrchestrationReactor; |
| 288 | const providerSessionReaper = yield* ProviderSessionReaper; | 296 | const providerSessionReaper = yield* ProviderSessionReaper.ProviderSessionReaper; |
| 289 | const lifecycleEvents = yield* ServerLifecycleEvents; | 297 | const lifecycleEvents = yield* ServerLifecycleEvents.ServerLifecycleEvents; |
| 290 | const serverSettings = yield* ServerSettingsService; | 298 | const serverSettings = yield* ServerSettings.ServerSettingsService; |
| 291 | const serverEnvironment = yield* ServerEnvironment; | 299 | const serverEnvironment = yield* ServerEnvironment.ServerEnvironment; |
| 292 | const crypto = yield* Crypto.Crypto; | 300 | const crypto = yield* Crypto.Crypto; |
| 293 | 301 | ||
| 294 | const commandGate = yield* makeCommandGate; | 302 | const commandGate = yield* makeCommandGate; |
| @@ -409,7 +417,7 @@ export const makeServerRuntimeStartup = Effect.gen(function* () { · 114 unmodified lines | |||
| 409 | const startupExit = yield* Effect.exit(startup); | 417 | const startupExit = yield* Effect.exit(startup); |
| 410 | if (Exit.isFailure(startupExit)) { | 418 | if (Exit.isFailure(startupExit)) { |
| 411 | const error = new ServerRuntimeStartupError({ | 419 | const error = new ServerRuntimeStartupError({ |
| 412 | message: "Server runtime startup failed before command readiness.", | 420 | stage: "command-readiness", |
| 413 | cause: startupExit.cause, | 421 | cause: startupExit.cause, |
| 414 | }); | 422 | }); |
| 415 | yield* Effect.logError("server runtime startup failed", { cause: startupExit.cause }); | 423 | yield* Effect.logError("server runtime startup failed", { cause: startupExit.cause }); |
| @@ -461,10 +469,7 @@ export const makeServerRuntimeStartup = Effect.gen(function* () { · 45 unmodified lines | |||
| 461 | awaitCommandReady: commandGate.awaitCommandReady, | 469 | awaitCommandReady: commandGate.awaitCommandReady, |
| 462 | markHttpListening: Deferred.succeed(httpListening, undefined), | 470 | markHttpListening: Deferred.succeed(httpListening, undefined), |
| 463 | enqueueCommand: commandGate.enqueueCommand, | 471 | enqueueCommand: commandGate.enqueueCommand, |
| 464 | } satisfies ServerRuntimeStartupShape; | 472 | } satisfies ServerRuntimeStartup["Service"]; |
| 465 | }); | 473 | }); |
| 466 | 474 | ||
| 467 | export const ServerRuntimeStartupLive = Layer.effect( | 475 | export const layer = Layer.effect(ServerRuntimeStartup, make); |
| 468 | ServerRuntimeStartup, | ||
| 469 | makeServerRuntimeStartup, | ||
| 470 | ); | ||
| @@ -5,7 +5,7 @@ import * as Option from "effect/Option"; · 4 unmodified lines | |||
| 5 | import * as Schema from "effect/Schema"; | 5 | import * as Schema from "effect/Schema"; |
| 6 | 6 | ||
| 7 | import { writeFileStringAtomically } from "./atomicWrite.ts"; | 7 | import { writeFileStringAtomically } from "./atomicWrite.ts"; |
| 8 | import { type ServerConfigShape } from "./config.ts"; | 8 | import type * as ServerConfig from "./config.ts"; |
| 9 | import { formatHostForUrl, isWildcardHost } from "./startupAccess.ts"; | 9 | import { formatHostForUrl, isWildcardHost } from "./startupAccess.ts"; |
| 10 | 10 | ||
| 11 | export const PersistedServerRuntimeState = Schema.Struct({ | 11 | export const PersistedServerRuntimeState = Schema.Struct({ |
| @@ -23,7 +23,7 @@ const decodePersistedServerRuntimeState = Schema.decodeUnknownEffect( · 11 unmodified lines | |||
| 23 | ); | 23 | ); |
| 24 | 24 | ||
| 25 | const runtimeOriginForConfig = ( | 25 | const runtimeOriginForConfig = ( |
| 26 | config: Pick<ServerConfigShape, "host">, | 26 | config: Pick<ServerConfig.ServerConfig["Service"], "host">, |
| 27 | port: number, | 27 | port: number, |
| 28 | ): PersistedServerRuntimeState["origin"] => { | 28 | ): PersistedServerRuntimeState["origin"] => { |
| 29 | const hostname = | 29 | const hostname = |
| @@ -32,7 +32,7 @@ const runtimeOriginForConfig = ( · 2 unmodified lines | |||
| 32 | }; | 32 | }; |
| 33 | 33 | ||
| 34 | export const makePersistedServerRuntimeState = (input: { | 34 | export const makePersistedServerRuntimeState = (input: { |
| 35 | readonly config: Pick<ServerConfigShape, "host">; | 35 | readonly config: Pick<ServerConfig.ServerConfig["Service"], "host">; |
| 36 | readonly port: number; | 36 | readonly port: number; |
| 37 | }): Effect.Effect<PersistedServerRuntimeState> => | 37 | }): Effect.Effect<PersistedServerRuntimeState> => |
| 38 | Effect.map(DateTime.now, (now) => ({ | 38 | Effect.map(DateTime.now, (now) => ({ |
| @@ -13,14 +13,16 @@ import * as Duration from "effect/Duration"; · 12 unmodified lines | |||
| 13 | import * as FileSystem from "effect/FileSystem"; | 13 | import * as FileSystem from "effect/FileSystem"; |
| 14 | import * as Layer from "effect/Layer"; | 14 | import * as Layer from "effect/Layer"; |
| 15 | import * as Schema from "effect/Schema"; | 15 | import * as Schema from "effect/Schema"; |
| 16 | import { ServerConfig } from "./config.ts"; | 16 | import * as ServerSecretStore from "./auth/ServerSecretStore.ts"; |
| 17 | import { ServerSettingsLive, ServerSettingsService } from "./serverSettings.ts"; | 17 | import * as ServerConfig from "./config.ts"; |
| 18 | import * as ServerSettingsModule from "./serverSettings.ts"; | ||
| 18 | 19 | ||
| 19 | const decodeSettingsPatch = Schema.decodeUnknownEffect(ServerSettingsPatch); | 20 | const decodeSettingsPatch = Schema.decodeUnknownEffect(ServerSettingsPatch); |
| 20 | const decodeServerSettings = Schema.decodeUnknownEffect(ServerSettings); | 21 | const decodeServerSettings = Schema.decodeUnknownEffect(ServerSettings); |
| 21 | 22 | ||
| 22 | const makeServerSettingsLayer = () => | 23 | const makeServerSettingsLayer = () => |
| 23 | ServerSettingsLive.pipe( | 24 | ServerSettingsModule.layer.pipe( |
| 25 | Layer.provide(ServerSecretStore.layer), | ||
| 24 | Layer.provideMerge( | 26 | Layer.provideMerge( |
| 25 | Layer.fresh( | 27 | Layer.fresh( |
| 26 | ServerConfig.layerTest(process.cwd(), { | 28 | ServerConfig.layerTest(process.cwd(), { |
| @@ -77,7 +79,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { · 50 unmodified lines | |||
| 77 | 79 | ||
| 78 | it.effect("deep merges nested settings updates without dropping siblings", () => | 80 | it.effect("deep merges nested settings updates without dropping siblings", () => |
| 79 | Effect.gen(function* () { | 81 | Effect.gen(function* () { |
| 80 | const serverSettings = yield* ServerSettingsService; | 82 | const serverSettings = yield* ServerSettingsModule.ServerSettingsService; |
| 81 | 83 | ||
| 82 | yield* serverSettings.updateSettings({ | 84 | yield* serverSettings.updateSettings({ |
| 83 | providers: { | 85 | providers: { |
| @@ -145,7 +147,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { · 61 unmodified lines | |||
| 145 | 147 | ||
| 146 | it.effect("preserves model when switching providers via textGenerationModelSelection", () => | 148 | it.effect("preserves model when switching providers via textGenerationModelSelection", () => |
| 147 | Effect.gen(function* () { | 149 | Effect.gen(function* () { |
| 148 | const serverSettings = yield* ServerSettingsService; | 150 | const serverSettings = yield* ServerSettingsModule.ServerSettingsService; |
| 149 | 151 | ||
| 150 | // Start with Claude text generation selection | 152 | // Start with Claude text generation selection |
| 151 | yield* serverSettings.updateSettings({ | 153 | yield* serverSettings.updateSettings({ |
| @@ -183,7 +185,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { · 31 unmodified lines | |||
| 183 | 185 | ||
| 184 | it.effect("preserves custom provider instance text generation selections", () => | 186 | it.effect("preserves custom provider instance text generation selections", () => |
| 185 | Effect.gen(function* () { | 187 | Effect.gen(function* () { |
| 186 | const serverSettings = yield* ServerSettingsService; | 188 | const serverSettings = yield* ServerSettingsModule.ServerSettingsService; |
| 187 | 189 | ||
| 188 | const next = yield* serverSettings.updateSettings({ | 190 | const next = yield* serverSettings.updateSettings({ |
| 189 | providerInstances: { | 191 | providerInstances: { |
| @@ -210,7 +212,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { · 20 unmodified lines | |||
| 210 | "uses explicit provider instance enabled state over legacy provider enabled state", | 212 | "uses explicit provider instance enabled state over legacy provider enabled state", |
| 211 | () => | 213 | () => |
| 212 | Effect.gen(function* () { | 214 | Effect.gen(function* () { |
| 213 | const serverSettings = yield* ServerSettingsService; | 215 | const serverSettings = yield* ServerSettingsModule.ServerSettingsService; |
| 214 | const instanceId = ProviderInstanceId.make("claude_openrouter"); | 216 | const instanceId = ProviderInstanceId.make("claude_openrouter"); |
| 215 | 217 | ||
| 216 | const next = yield* serverSettings.updateSettings({ | 218 | const next = yield* serverSettings.updateSettings({ |
| @@ -241,7 +243,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { · 24 unmodified lines | |||
| 241 | 243 | ||
| 242 | it.effect("preserves enabled text generation selections for non-built-in drivers", () => | 244 | it.effect("preserves enabled text generation selections for non-built-in drivers", () => |
| 243 | Effect.gen(function* () { | 245 | Effect.gen(function* () { |
| 244 | const serverSettings = yield* ServerSettingsService; | 246 | const serverSettings = yield* ServerSettingsModule.ServerSettingsService; |
| 245 | const instanceId = ProviderInstanceId.make("openrouter_text"); | 247 | const instanceId = ProviderInstanceId.make("openrouter_text"); |
| 246 | 248 | ||
| 247 | const next = yield* serverSettings.updateSettings({ | 249 | const next = yield* serverSettings.updateSettings({ |
| @@ -267,7 +269,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { · 19 unmodified lines | |||
| 267 | 269 | ||
| 268 | it.effect("drops stale text generation options when resetting model selection", () => | 270 | it.effect("drops stale text generation options when resetting model selection", () => |
| 269 | Effect.gen(function* () { | 271 | Effect.gen(function* () { |
| 270 | const serverSettings = yield* ServerSettingsService; | 272 | const serverSettings = yield* ServerSettingsModule.ServerSettingsService; |
| 271 | 273 | ||
| 272 | yield* serverSettings.updateSettings({ | 274 | yield* serverSettings.updateSettings({ |
| 273 | textGenerationModelSelection: { | 275 | textGenerationModelSelection: { |
| @@ -300,7 +302,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { · 26 unmodified lines | |||
| 300 | 302 | ||
| 301 | it.effect("replaces provider instance maps when clearing optional fields", () => | 303 | it.effect("replaces provider instance maps when clearing optional fields", () => |
| 302 | Effect.gen(function* () { | 304 | Effect.gen(function* () { |
| 303 | const serverSettings = yield* ServerSettingsService; | 305 | const serverSettings = yield* ServerSettingsModule.ServerSettingsService; |
| 304 | const codexId = ProviderInstanceId.make("codex"); | 306 | const codexId = ProviderInstanceId.make("codex"); |
| 305 | 307 | ||
| 306 | yield* serverSettings.updateSettings({ | 308 | yield* serverSettings.updateSettings({ |
| @@ -337,7 +339,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { · 30 unmodified lines | |||
| 337 | 339 | ||
| 338 | it.effect("trims provider path settings when updates are applied", () => | 340 | it.effect("trims provider path settings when updates are applied", () => |
| 339 | Effect.gen(function* () { | 341 | Effect.gen(function* () { |
| 340 | const serverSettings = yield* ServerSettingsService; | 342 | const serverSettings = yield* ServerSettingsModule.ServerSettingsService; |
| 341 | 343 | ||
| 342 | const next = yield* serverSettings.updateSettings({ | 344 | const next = yield* serverSettings.updateSettings({ |
| 343 | providers: { | 345 | providers: { |
| @@ -382,7 +384,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { · 38 unmodified lines | |||
| 382 | 384 | ||
| 383 | it.effect("trims observability settings when updates are applied", () => | 385 | it.effect("trims observability settings when updates are applied", () => |
| 384 | Effect.gen(function* () { | 386 | Effect.gen(function* () { |
| 385 | const serverSettings = yield* ServerSettingsService; | 387 | const serverSettings = yield* ServerSettingsModule.ServerSettingsService; |
| 386 | 388 | ||
| 387 | const next = yield* serverSettings.updateSettings({ | 389 | const next = yield* serverSettings.updateSettings({ |
| 388 | addProjectBaseDirectory: " ~/Development ", | 390 | addProjectBaseDirectory: " ~/Development ", |
| @@ -402,7 +404,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { · 13 unmodified lines | |||
| 402 | 404 | ||
| 403 | it.effect("defaults blank binary paths to provider executables", () => | 405 | it.effect("defaults blank binary paths to provider executables", () => |
| 404 | Effect.gen(function* () { | 406 | Effect.gen(function* () { |
| 405 | const serverSettings = yield* ServerSettingsService; | 407 | const serverSettings = yield* ServerSettingsModule.ServerSettingsService; |
| 406 | 408 | ||
| 407 | const next = yield* serverSettings.updateSettings({ | 409 | const next = yield* serverSettings.updateSettings({ |
| 408 | providers: { | 410 | providers: { |
| @@ -422,8 +424,8 @@ it.layer(NodeServices.layer)("server settings", (it) => { · 13 unmodified lines | |||
| 422 | 424 | ||
| 423 | it.effect("writes only non-default server settings to disk", () => | 425 | it.effect("writes only non-default server settings to disk", () => |
| 424 | Effect.gen(function* () { | 426 | Effect.gen(function* () { |
| 425 | const serverSettings = yield* ServerSettingsService; | 427 | const serverSettings = yield* ServerSettingsModule.ServerSettingsService; |
| 426 | const serverConfig = yield* ServerConfig; | 428 | const serverConfig = yield* ServerConfig.ServerConfig; |
| 427 | const fileSystem = yield* FileSystem.FileSystem; | 429 | const fileSystem = yield* FileSystem.FileSystem; |
| 428 | const next = yield* serverSettings.updateSettings({ | 430 | const next = yield* serverSettings.updateSettings({ |
| 429 | addProjectBaseDirectory: "~/Development", | 431 | addProjectBaseDirectory: "~/Development", |
| @@ -469,8 +471,8 @@ it.layer(NodeServices.layer)("server settings", (it) => { · 39 unmodified lines | |||
| 469 | 471 | ||
| 470 | it.effect("stores sensitive provider instance environment values outside settings.json", () => | 472 | it.effect("stores sensitive provider instance environment values outside settings.json", () => |
| 471 | Effect.gen(function* () { | 473 | Effect.gen(function* () { |
| 472 | const serverSettings = yield* ServerSettingsService; | 474 | const serverSettings = yield* ServerSettingsModule.ServerSettingsService; |
| 473 | const serverConfig = yield* ServerConfig; | 475 | const serverConfig = yield* ServerConfig.ServerConfig; |
| 474 | const fileSystem = yield* FileSystem.FileSystem; | 476 | const fileSystem = yield* FileSystem.FileSystem; |
| 475 | const instanceId = ProviderInstanceId.make("codex_personal"); | 477 | const instanceId = ProviderInstanceId.make("codex_personal"); |
| 476 | 478 | ||
| @@ -26,26 +26,26 @@ import { · 25 unmodified lines | |||
| 26 | type ServerSettingsPatch, | 26 | type ServerSettingsPatch, |
| 27 | } from "@t3tools/contracts"; | 27 | } from "@t3tools/contracts"; |
| 28 | import * as Cache from "effect/Cache"; | 28 | import * as Cache from "effect/Cache"; |
| 29 | import * as Cause from "effect/Cause"; | ||
| 30 | import * as Context from "effect/Context"; | ||
| 29 | import * as Deferred from "effect/Deferred"; | 31 | import * as Deferred from "effect/Deferred"; |
| 30 | import * as Duration from "effect/Duration"; | 32 | import * as Duration from "effect/Duration"; |
| 33 | import * as Equal from "effect/Equal"; | ||
| 31 | import * as Effect from "effect/Effect"; | 34 | import * as Effect from "effect/Effect"; |
| 32 | import * as Exit from "effect/Exit"; | 35 | import * as Exit from "effect/Exit"; |
| 33 | import * as FileSystem from "effect/FileSystem"; | 36 | import * as FileSystem from "effect/FileSystem"; |
| 34 | import * as Layer from "effect/Layer"; | 37 | import * as Layer from "effect/Layer"; |
| 35 | import * as Option from "effect/Option"; | 38 | import * as Option from "effect/Option"; |
| 36 | import * as Path from "effect/Path"; | 39 | import * as Path from "effect/Path"; |
| 37 | import * as Equal from "effect/Equal"; | ||
| 38 | import * as PubSub from "effect/PubSub"; | 40 | import * as PubSub from "effect/PubSub"; |
| 39 | import * as Ref from "effect/Ref"; | 41 | import * as Ref from "effect/Ref"; |
| 40 | import * as Schema from "effect/Schema"; | 42 | import * as Schema from "effect/Schema"; |
| 41 | import * as SchemaIssue from "effect/SchemaIssue"; | 43 | import * as SchemaIssue from "effect/SchemaIssue"; |
| 44 | import * as Semaphore from "effect/Semaphore"; | ||
| 42 | import * as Scope from "effect/Scope"; | 45 | import * as Scope from "effect/Scope"; |
| 43 | import * as Context from "effect/Context"; | ||
| 44 | import * as Stream from "effect/Stream"; | 46 | import * as Stream from "effect/Stream"; |
| 45 | import * as Cause from "effect/Cause"; | ||
| 46 | import * as Semaphore from "effect/Semaphore"; | ||
| 47 | import { writeFileStringAtomically } from "./atomicWrite.ts"; | 47 | import { writeFileStringAtomically } from "./atomicWrite.ts"; |
| 48 | import { ServerConfig } from "./config.ts"; | 48 | import * as ServerConfig from "./config.ts"; |
| 49 | import { type DeepPartial, deepMerge } from "@t3tools/shared/Struct"; | 49 | import { type DeepPartial, deepMerge } from "@t3tools/shared/Struct"; |
| 50 | import { fromJsonStringPretty, fromLenientJson } from "@t3tools/shared/schemaJson"; | 50 | import { fromJsonStringPretty, fromLenientJson } from "@t3tools/shared/schemaJson"; |
| 51 | import { applyServerSettingsPatch } from "@t3tools/shared/serverSettings"; | 51 | import { applyServerSettingsPatch } from "@t3tools/shared/serverSettings"; |
| @@ -109,59 +109,60 @@ export function redactServerSettingsForClient(settings: ServerSettings): ServerS · 57 unmodified lines | |||
| 109 | return { ...settings, providerInstances }; | 109 | return { ...settings, providerInstances }; |
| 110 | } | 110 | } |
| 111 | 111 | ||
| 112 | export interface ServerSettingsShape { | 112 | export class ServerSettingsService extends Context.Service< |
| 113 | /** Start the settings runtime and attach file watching. */ | 113 | ServerSettingsService, |
| 114 | readonly start: Effect.Effect<void, ServerSettingsError>; | 114 | { |
| 115 | 115 | /** Start the settings runtime and attach file watching. */ | |
| 116 | /** Await settings runtime readiness. */ | 116 | readonly start: Effect.Effect<void, ServerSettingsError>; |
| 117 | readonly ready: Effect.Effect<void, ServerSettingsError>; | ||
| 118 | 117 | ||
| 119 | /** Read the current settings. */ | 118 | /** Await settings runtime readiness. */ |
| 120 | readonly getSettings: Effect.Effect<ServerSettings, ServerSettingsError>; | 119 | readonly ready: Effect.Effect<void, ServerSettingsError>; |
| 121 | 120 | ||
| 122 | /** Patch settings and persist. Returns the new full settings object. */ | 121 | /** Read the current settings. */ |
| 123 | readonly updateSettings: ( | 122 | readonly getSettings: Effect.Effect<ServerSettings, ServerSettingsError>; |
| 124 | patch: ServerSettingsPatch, | ||
| 125 | ) => Effect.Effect<ServerSettings, ServerSettingsError>; | ||
| 126 | 123 | ||
| 127 | /** Stream of settings change events. */ | 124 | /** Patch settings and persist. Returns the new full settings object. */ |
| 128 | readonly streamChanges: Stream.Stream<ServerSettings>; | 125 | readonly updateSettings: ( |
| 129 | } | 126 | patch: ServerSettingsPatch, |
| 127 | ) => Effect.Effect<ServerSettings, ServerSettingsError>; | ||
| 130 | 128 | ||
| 131 | export class ServerSettingsService extends Context.Service< | 129 | /** Stream of settings change events. */ |
| 132 | ServerSettingsService, | 130 | readonly streamChanges: Stream.Stream<ServerSettings>; |
| 133 | ServerSettingsShape | 131 | } |
| 134 | >()("t3/serverSettings/ServerSettingsService") { | 132 | >()("t3/serverSettings/ServerSettingsService") { |
| 135 | static readonly layerTest = (overrides: DeepPartial<ServerSettings> = {}) => | 133 | /** @deprecated Import and use `layerTest` from this module. */ |
| 136 | Layer.effect( | 134 | static readonly layerTest = (overrides: DeepPartial<ServerSettings> = {}) => layerTest(overrides); |
| 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 | ); | ||
| 163 | } | 135 | } |
| 164 | 136 | ||
| 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 | |||
| 165 | const ServerSettingsJson = fromLenientJson(ServerSettings); | 166 | const ServerSettingsJson = fromLenientJson(ServerSettings); |
| 166 | const decodeServerSettingsJsonExit = Schema.decodeUnknownExit(ServerSettingsJson); | 167 | const decodeServerSettingsJsonExit = Schema.decodeUnknownExit(ServerSettingsJson); |
| 167 | 168 | ||
| @@ -255,8 +256,8 @@ function stripDefaultServerSettings(current: unknown, defaults: unknown): unknow · 87 unmodified lines | |||
| 255 | return Object.is(current, defaults) ? undefined : current; | 256 | return Object.is(current, defaults) ? undefined : current; |
| 256 | } | 257 | } |
| 257 | 258 | ||
| 258 | const makeServerSettings = Effect.gen(function* () { | 259 | const make = Effect.gen(function* () { |
| 259 | const { settingsPath } = yield* ServerConfig; | 260 | const { settingsPath } = yield* ServerConfig.ServerConfig; |
| 260 | const fs = yield* FileSystem.FileSystem; | 261 | const fs = yield* FileSystem.FileSystem; |
| 261 | const pathService = yield* Path.Path; | 262 | const pathService = yield* Path.Path; |
| 262 | const secretStore = yield* ServerSecretStore.ServerSecretStore; | 263 | const secretStore = yield* ServerSecretStore.ServerSecretStore; |
| @@ -578,9 +579,7 @@ const makeServerSettings = Effect.gen(function* () { · 315 unmodified lines | |||
| 578 | Stream.map(resolveTextGenerationProvider), | 579 | Stream.map(resolveTextGenerationProvider), |
| 579 | ); | 580 | ); |
| 580 | }, | 581 | }, |
| 581 | } satisfies ServerSettingsShape; | 582 | } satisfies ServerSettingsService["Service"]; |
| 582 | }); | 583 | }); |
| 583 | 584 | ||
| 584 | export const ServerSettingsLive = Layer.effect(ServerSettingsService, makeServerSettings).pipe( | 585 | export const layer = Layer.effect(ServerSettingsService, make); |
| 585 | Layer.provide(ServerSecretStore.layer), | ||
| 586 | ); | ||
| @@ -100,6 +100,41 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { · 99 unmodified lines | |||
| 100 | assert.deepStrictEqual(paths, ["complete.txt", "final.txt"]); | 100 | assert.deepStrictEqual(paths, ["complete.txt", "final.txt"]); |
| 101 | }), | 101 | }), |
| 102 | ); | 102 | ); |
| 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 | ); | ||
| 103 | }); | 138 | }); |
| 104 | 139 | ||
| 105 | describe("repository status", () => { | 140 | describe("repository status", () => { |
| @@ -342,6 +377,44 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { · 236 unmodified lines | |||
| 342 | }); | 377 | }); |
| 343 | 378 | ||
| 344 | describe("refName operations", () => { | 379 | 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 | |||
| 345 | it.effect("creates, checks out, renames, and lists refs", () => | 418 | it.effect("creates, checks out, renames, and lists refs", () => |
| 346 | Effect.gen(function* () { | 419 | Effect.gen(function* () { |
| 347 | const cwd = yield* makeTmpDir(); | 420 | const cwd = yield* makeTmpDir(); |
| @@ -1817,7 +1817,14 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* · 1816 unmodified lines | |||
| 1817 | const dirtyTrackedResult = yield* executeGit( | 1817 | const dirtyTrackedResult = yield* executeGit( |
| 1818 | "GitVcsDriver.getReviewDiffPreview.dirtyTracked", | 1818 | "GitVcsDriver.getReviewDiffPreview.dirtyTracked", |
| 1819 | input.cwd, | 1819 | input.cwd, |
| 1820 | ["diff", "--patch", "--minimal", "HEAD", "--"], | 1820 | [ |
| 1821 | "diff", | ||
| 1822 | "--patch", | ||
| 1823 | "--minimal", | ||
| 1824 | ...(input.ignoreWhitespace ? ["--ignore-all-space"] : []), | ||
| 1825 | "HEAD", | ||
| 1826 | "--", | ||
| 1827 | ], | ||
| 1821 | { | 1828 | { |
| 1822 | maxOutputBytes: REVIEW_DIFF_PATCH_MAX_OUTPUT_BYTES, | 1829 | maxOutputBytes: REVIEW_DIFF_PATCH_MAX_OUTPUT_BYTES, |
| 1823 | appendTruncationMarker: true, | 1830 | appendTruncationMarker: true, |
| @@ -1843,7 +1850,13 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* · 19 unmodified lines | |||
| 1843 | ? yield* executeGit( | 1850 | ? yield* executeGit( |
| 1844 | "GitVcsDriver.getReviewDiffPreview.base", | 1851 | "GitVcsDriver.getReviewDiffPreview.base", |
| 1845 | input.cwd, | 1852 | 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 | ], | ||
| 1847 | { | 1860 | { |
| 1848 | maxOutputBytes: REVIEW_DIFF_PATCH_MAX_OUTPUT_BYTES, | 1861 | maxOutputBytes: REVIEW_DIFF_PATCH_MAX_OUTPUT_BYTES, |
| 1849 | appendTruncationMarker: true, | 1862 | appendTruncationMarker: true, |
| @@ -2127,11 +2140,17 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* · 277 unmodified lines | |||
| 2127 | }) | 2140 | }) |
| 2128 | : []; | 2141 | : []; |
| 2129 | 2142 | ||
| 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; | ||
| 2130 | const refs = paginateBranches({ | 2152 | const refs = paginateBranches({ |
| 2131 | refs: filterBranchesForListQuery( | 2153 | refs: filterBranchesForListQuery(branchesForKind, input.query), |
| 2132 | dedupeRemoteBranchesWithLocalMatches([...localBranches, ...remoteBranches]), | ||
| 2133 | input.query, | ||
| 2134 | ), | ||
| 2135 | cursor: input.cursor, | 2154 | cursor: input.cursor, |
| 2136 | limit: input.limit, | 2155 | limit: input.limit, |
| 2137 | }); | 2156 | }); |
| @@ -1489,7 +1489,7 @@ const makeWsRpcLayer = (currentSession: AuthenticatedSession) => · 1488 unmodified lines | |||
| 1489 | [WS_METHODS.previewAutomationConnect]: (input) => | 1489 | [WS_METHODS.previewAutomationConnect]: (input) => |
| 1490 | observeRpcStreamEffect( | 1490 | observeRpcStreamEffect( |
| 1491 | WS_METHODS.previewAutomationConnect, | 1491 | WS_METHODS.previewAutomationConnect, |
| 1492 | previewAutomationBroker.connect(input.clientId), | 1492 | previewAutomationBroker.connect(input), |
| 1493 | { "rpc.aggregate": "preview-automation" }, | 1493 | { "rpc.aggregate": "preview-automation" }, |
| 1494 | ), | 1494 | ), |
| 1495 | [WS_METHODS.previewAutomationRespond]: (input) => | 1495 | [WS_METHODS.previewAutomationRespond]: (input) => |
| @@ -1507,7 +1507,7 @@ const makeWsRpcLayer = (currentSession: AuthenticatedSession) => · 11 unmodified lines | |||
| 1507 | [WS_METHODS.previewAutomationClearOwner]: (input) => | 1507 | [WS_METHODS.previewAutomationClearOwner]: (input) => |
| 1508 | observeRpcEffect( | 1508 | observeRpcEffect( |
| 1509 | WS_METHODS.previewAutomationClearOwner, | 1509 | WS_METHODS.previewAutomationClearOwner, |
| 1510 | previewAutomationBroker.clearOwner(input.clientId), | 1510 | previewAutomationBroker.clearOwner(input), |
| 1511 | { "rpc.aggregate": "preview-automation" }, | 1511 | { "rpc.aggregate": "preview-automation" }, |
| 1512 | ), | 1512 | ), |
| 1513 | [WS_METHODS.subscribePreviewEvents]: (_input) => | 1513 | [WS_METHODS.subscribePreviewEvents]: (_input) => |
| @@ -42,7 +42,7 @@ import { nextTerminalId, resolveTerminalSessionLabel } from "@t3tools/shared/ter · 41 unmodified lines | |||
| 42 | import { Debouncer } from "@tanstack/react-pacer"; | 42 | import { Debouncer } from "@tanstack/react-pacer"; |
| 43 | import { useAtomValue } from "@effect/atom-react"; | 43 | import { useAtomValue } from "@effect/atom-react"; |
| 44 | import { lazy, memo, Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react"; | 44 | 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"; |
| 46 | import { useShallow } from "zustand/react/shallow"; | 46 | import { useShallow } from "zustand/react/shallow"; |
| 47 | import { | 47 | import { |
| 48 | isAtomCommandInterrupted, | 48 | isAtomCommandInterrupted, |
| @@ -55,7 +55,7 @@ import * as Cause from "effect/Cause"; · 6 unmodified lines | |||
| 55 | import { AsyncResult } from "effect/unstable/reactivity"; | 55 | import { AsyncResult } from "effect/unstable/reactivity"; |
| 56 | import { isElectron } from "../env"; | 56 | import { isElectron } from "../env"; |
| 57 | import { readLocalApi } from "../localApi"; | 57 | import { readLocalApi } from "../localApi"; |
| 58 | import { parseDiffRouteSearch, stripDiffSearchParams } from "../diffRouteSearch"; | 58 | import { useDiffPanelStore } from "../diffPanelStore"; |
| 59 | import { | 59 | import { |
| 60 | collapseExpandedComposerCursor, | 60 | collapseExpandedComposerCursor, |
| 61 | parseStandaloneComposerSlashCommand, | 61 | parseStandaloneComposerSlashCommand, |
| @@ -104,7 +104,7 @@ import { buildTemporaryWorktreeBranchName } from "@t3tools/shared/git"; · 42 unmodified lines | |||
| 104 | import { useMediaQuery } from "../hooks/useMediaQuery"; | 104 | import { useMediaQuery } from "../hooks/useMediaQuery"; |
| 105 | import { RIGHT_PANEL_INLINE_LAYOUT_MEDIA_QUERY } from "../rightPanelLayout"; | 105 | import { RIGHT_PANEL_INLINE_LAYOUT_MEDIA_QUERY } from "../rightPanelLayout"; |
| 106 | import { | 106 | import { |
| 107 | selectActiveRightPanelKindWithUrl, | 107 | selectActiveRightPanel, |
| 108 | selectActiveRightPanelSurface, | 108 | selectActiveRightPanelSurface, |
| 109 | selectThreadRightPanelState, | 109 | selectThreadRightPanelState, |
| 110 | type RightPanelSurface, | 110 | type RightPanelSurface, |
| @@ -1034,10 +1034,6 @@ function ChatViewContent(props: ChatViewProps) { · 923 unmodified lines | |||
| 1034 | const timestampFormat = settings.timestampFormat; | 1034 | const timestampFormat = settings.timestampFormat; |
| 1035 | const autoOpenPlanSidebar = settings.autoOpenPlanSidebar; | 1035 | const autoOpenPlanSidebar = settings.autoOpenPlanSidebar; |
| 1036 | const navigate = useNavigate(); | 1036 | const navigate = useNavigate(); |
| 1037 | const rawSearch = useSearch({ | ||
| 1038 | strict: false, | ||
| 1039 | select: (params) => parseDiffRouteSearch(params), | ||
| 1040 | }); | ||
| 1041 | const { resolvedTheme } = useTheme(); | 1037 | const { resolvedTheme } = useTheme(); |
| 1042 | // Granular store selectors — avoid subscribing to prompt changes. | 1038 | // Granular store selectors — avoid subscribing to prompt changes. |
| 1043 | const composerRuntimeMode = useComposerDraftStore( | 1039 | const composerRuntimeMode = useComposerDraftStore( |
| @@ -1217,7 +1213,6 @@ function ChatViewContent(props: ChatViewProps) { · 173 unmodified lines | |||
| 1217 | composerInteractionMode ?? activeThread?.interactionMode ?? DEFAULT_INTERACTION_MODE; | 1213 | composerInteractionMode ?? activeThread?.interactionMode ?? DEFAULT_INTERACTION_MODE; |
| 1218 | const isLocalDraftThread = !isServerThread && localDraftThread !== undefined; | 1214 | const isLocalDraftThread = !isServerThread && localDraftThread !== undefined; |
| 1219 | const canCheckoutPullRequestIntoThread = isLocalDraftThread; | 1215 | const canCheckoutPullRequestIntoThread = isLocalDraftThread; |
| 1220 | const diffOpen = rawSearch.diff === "1"; | ||
| 1221 | const activeThreadId = activeThread?.id ?? null; | 1216 | const activeThreadId = activeThread?.id ?? null; |
| 1222 | const runningTerminalIds = useThreadRunningTerminalIds({ | 1217 | const runningTerminalIds = useThreadRunningTerminalIds({ |
| 1223 | environmentId: activeThread?.environmentId ?? null, | 1218 | environmentId: activeThread?.environmentId ?? null, |
| @@ -1259,8 +1254,9 @@ function ChatViewContent(props: ChatViewProps) { · 35 unmodified lines | |||
| 1259 | ); | 1254 | ); |
| 1260 | const activeThreadKey = activeThreadRef ? scopedThreadKey(activeThreadRef) : null; | 1255 | const activeThreadKey = activeThreadRef ? scopedThreadKey(activeThreadRef) : null; |
| 1261 | const activeRightPanelKind = useRightPanelStore((state) => | 1256 | const activeRightPanelKind = useRightPanelStore((state) => |
| 1262 | selectActiveRightPanelKindWithUrl(state.byThreadKey, activeThreadRef, diffOpen), | 1257 | selectActiveRightPanel(state.byThreadKey, activeThreadRef), |
| 1263 | ); | 1258 | ); |
| 1259 | const diffOpen = activeRightPanelKind === "diff"; | ||
| 1264 | const rightPanelState = useRightPanelStore((state) => | 1260 | const rightPanelState = useRightPanelStore((state) => |
| 1265 | selectThreadRightPanelState(state.byThreadKey, activeThreadRef), | 1261 | selectThreadRightPanelState(state.byThreadKey, activeThreadRef), |
| 1266 | ); | 1262 | ); |
| @@ -1295,11 +1291,6 @@ function ChatViewContent(props: ChatViewProps) { · 28 unmodified lines | |||
| 1295 | 1291 | ||
| 1296 | const planSidebarOpen = activeRightPanelKind === "plan"; | 1292 | const planSidebarOpen = activeRightPanelKind === "plan"; |
| 1297 | 1293 | ||
| 1298 | useEffect(() => { | ||
| 1299 | if (!activeThreadRef || !diffOpen) return; | ||
| 1300 | useRightPanelStore.getState().open(activeThreadRef, "diff"); | ||
| 1301 | }, [activeThreadRef, diffOpen]); | ||
| 1302 | |||
| 1303 | const existingOpenTerminalThreadKeys = useMemo(() => { | 1294 | const existingOpenTerminalThreadKeys = useMemo(() => { |
| 1304 | const existingThreadKeys = new Set<string>([...serverThreadKeys, ...draftThreadKeys]); | 1295 | const existingThreadKeys = new Set<string>([...serverThreadKeys, ...draftThreadKeys]); |
| 1305 | return openTerminalThreadKeys.filter((nextThreadKey) => existingThreadKeys.has(nextThreadKey)); | 1296 | return openTerminalThreadKeys.filter((nextThreadKey) => existingThreadKeys.has(nextThreadKey)); |
| @@ -2151,27 +2142,7 @@ function ChatViewContent(props: ChatViewProps) { · 845 unmodified lines | |||
| 2151 | if (activeThreadRef) { | 2142 | if (activeThreadRef) { |
| 2152 | useRightPanelStore.getState().toggle(activeThreadRef, "diff"); | 2143 | useRightPanelStore.getState().toggle(activeThreadRef, "diff"); |
| 2153 | } | 2144 | } |
| 2154 | void navigate({ | 2145 | }, [activeThreadRef, diffOpen, isServerThread, onDiffPanelOpen]); |
| 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 | ]); | ||
| 2175 | 2146 | ||
| 2176 | const envLocked = Boolean( | 2147 | const envLocked = Boolean( |
| 2177 | activeThread && | 2148 | activeThread && |
| @@ -2757,21 +2728,7 @@ function ChatViewContent(props: ChatViewProps) { · 579 unmodified lines | |||
| 2757 | if (!activeThreadRef || !isServerThread || !isGitRepo) return; | 2728 | if (!activeThreadRef || !isServerThread || !isGitRepo) return; |
| 2758 | useRightPanelStore.getState().open(activeThreadRef, "diff"); | 2729 | useRightPanelStore.getState().open(activeThreadRef, "diff"); |
| 2759 | onDiffPanelOpen?.(); | 2730 | onDiffPanelOpen?.(); |
| 2760 | void navigate({ | 2731 | }, [activeThreadRef, isGitRepo, isServerThread, onDiffPanelOpen]); |
| 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 | ]); | ||
| 2775 | const addFilesSurface = useCallback(() => { | 2732 | const addFilesSurface = useCallback(() => { |
| 2776 | if (!activeThreadRef || !activeProject) return; | 2733 | if (!activeThreadRef || !activeProject) return; |
| 2777 | useRightPanelStore.getState().open(activeThreadRef, "files"); | 2734 | useRightPanelStore.getState().open(activeThreadRef, "files"); |
| @@ -2789,30 +2746,13 @@ function ChatViewContent(props: ChatViewProps) { · 11 unmodified lines | |||
| 2789 | useRightPanelStore.getState().close(activeThreadRef); | 2746 | useRightPanelStore.getState().close(activeThreadRef); |
| 2790 | return; | 2747 | return; |
| 2791 | } | 2748 | } |
| 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 | } | ||
| 2800 | const activeTabId = activePreviewState.activeTabId; | 2749 | const activeTabId = activePreviewState.activeTabId; |
| 2801 | if (activeTabId) { | 2750 | if (activeTabId) { |
| 2802 | useRightPanelStore.getState().openBrowser(activeThreadRef, activeTabId); | 2751 | useRightPanelStore.getState().openBrowser(activeThreadRef, activeTabId); |
| 2803 | } else { | 2752 | } else { |
| 2804 | createBrowserSurface(); | 2753 | createBrowserSurface(); |
| 2805 | } | 2754 | } |
| 2806 | }, [ | 2755 | }, [activePreviewState.activeTabId, activeThreadRef, createBrowserSurface, previewPanelOpen]); |
| 2807 | activePreviewState.activeTabId, | ||
| 2808 | activeThreadRef, | ||
| 2809 | createBrowserSurface, | ||
| 2810 | diffOpen, | ||
| 2811 | environmentId, | ||
| 2812 | navigate, | ||
| 2813 | previewPanelOpen, | ||
| 2814 | threadId, | ||
| 2815 | ]); | ||
| 2816 | const closePreviewPanel = useCallback(() => { | 2756 | const closePreviewPanel = useCallback(() => { |
| 2817 | if (activeThreadRef) { | 2757 | if (activeThreadRef) { |
| 2818 | setMaximizedRightPanelThreadKey(null); | 2758 | setMaximizedRightPanelThreadKey(null); |
| @@ -2936,31 +2876,9 @@ function ChatViewContent(props: ChatViewProps) { · 117 unmodified lines | |||
| 2936 | } | 2876 | } |
| 2937 | if (surface.kind === "diff" && !diffOpen) { | 2877 | if (surface.kind === "diff" && !diffOpen) { |
| 2938 | onDiffPanelOpen?.(); | 2878 | 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 | }); | ||
| 2952 | } | 2879 | } |
| 2953 | }, | 2880 | }, |
| 2954 | [ | 2881 | [activeThreadRef, diffOpen, dismissPlanSidebarForCurrentTurn, onDiffPanelOpen, planSidebarOpen], |
| 2955 | activeThreadRef, | ||
| 2956 | diffOpen, | ||
| 2957 | dismissPlanSidebarForCurrentTurn, | ||
| 2958 | environmentId, | ||
| 2959 | navigate, | ||
| 2960 | onDiffPanelOpen, | ||
| 2961 | planSidebarOpen, | ||
| 2962 | threadId, | ||
| 2963 | ], | ||
| 2964 | ); | 2882 | ); |
| 2965 | const toggleRightPanel = useCallback(() => { | 2883 | const toggleRightPanel = useCallback(() => { |
| 2966 | if (!activeThreadRef) return; | 2884 | if (!activeThreadRef) return; |
| @@ -3006,26 +2924,14 @@ function ChatViewContent(props: ChatViewProps) { · 39 unmodified lines | |||
| 3006 | } | 2924 | } |
| 3007 | } | 2925 | } |
| 3008 | } | 2926 | } |
| 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 | } | ||
| 3017 | }, | 2927 | }, |
| 3018 | [ | 2928 | [ |
| 3019 | activeThreadRef, | 2929 | activeThreadRef, |
| 3020 | activePreviewState.sessions, | 2930 | activePreviewState.sessions, |
| 3021 | closePreview, | 2931 | closePreview, |
| 3022 | closeTerminalMutation, | 2932 | closeTerminalMutation, |
| 3023 | diffOpen, | ||
| 3024 | dismissPlanSidebarForCurrentTurn, | 2933 | dismissPlanSidebarForCurrentTurn, |
| 3025 | environmentId, | ||
| 3026 | navigate, | ||
| 3027 | storeCloseTerminal, | 2934 | storeCloseTerminal, |
| 3028 | threadId, | ||
| 3029 | ], | 2935 | ], |
| 3030 | ); | 2936 | ); |
| 3031 | const syncActivePreviewSurface = useCallback(() => { | 2937 | const syncActivePreviewSurface = useCallback(() => { |
| @@ -4631,25 +4537,12 @@ function ChatViewContent(props: ChatViewProps) { · 1599 unmodified lines | |||
| 4631 | }, []); | 4537 | }, []); |
| 4632 | const onOpenTurnDiff = useCallback( | 4538 | const onOpenTurnDiff = useCallback( |
| 4633 | (turnId: TurnId, filePath?: string) => { | 4539 | (turnId: TurnId, filePath?: string) => { |
| 4634 | if (!isServerThread) { | 4540 | if (!isServerThread || !activeThreadRef) return; |
| 4635 | return; | 4541 | useDiffPanelStore.getState().selectTurn(activeThreadRef, turnId, filePath); |
| 4636 | } | 4542 | useRightPanelStore.getState().open(activeThreadRef, "diff"); |
| 4637 | onDiffPanelOpen?.(); | 4543 | 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 | }); | ||
| 4651 | }, | 4544 | }, |
| 4652 | [environmentId, isServerThread, navigate, onDiffPanelOpen, threadId], | 4545 | [activeThreadRef, isServerThread, onDiffPanelOpen], |
| 4653 | ); | 4546 | ); |
| 4654 | // Both the Map and the revert handler are read from refs at call-time so | 4547 | // Both the Map and the revert handler are read from refs at call-time so |
| 4655 | // the callback reference is fully stable and never busts context identity. | 4548 | // the callback reference is fully stable and never busts context identity. |
| @@ -1,35 +1,28 @@ | |||
| 1 | import { useAtomValue } from "@effect/atom-react"; | 1 | import { useAtomValue } from "@effect/atom-react"; |
| 2 | import { Virtualizer } from "@pierre/diffs/react"; | 2 | import { useParams } from "@tanstack/react-router"; |
| 3 | import { useNavigate, useParams, useSearch } from "@tanstack/react-router"; | ||
| 4 | import { scopeThreadRef } from "@t3tools/client-runtime/environment"; | ||
| 5 | import { | 3 | import { |
| 6 | isAtomCommandInterrupted, | 4 | isAtomCommandInterrupted, |
| 7 | squashAtomCommandFailure, | 5 | squashAtomCommandFailure, |
| 8 | } from "@t3tools/client-runtime/state/runtime"; | 6 | } from "@t3tools/client-runtime/state/runtime"; |
| 9 | import type { ScopedThreadRef, TurnId } from "@t3tools/contracts"; | 7 | import type { ScopedThreadRef, TurnId } from "@t3tools/contracts"; |
| 10 | import { | 8 | import { |
| 9 | ArrowRightIcon, | ||
| 10 | CheckIcon, | ||
| 11 | ChevronDownIcon, | 11 | ChevronDownIcon, |
| 12 | ChevronLeftIcon, | ||
| 13 | ChevronRightIcon, | 12 | ChevronRightIcon, |
| 14 | Columns2Icon, | 13 | Columns2Icon, |
| 15 | PilcrowIcon, | 14 | PilcrowIcon, |
| 16 | Rows3Icon, | 15 | Rows3Icon, |
| 16 | SearchIcon, | ||
| 17 | TextWrapIcon, | 17 | TextWrapIcon, |
| 18 | } from "lucide-react"; | 18 | } from "lucide-react"; |
| 19 | import { | 19 | import { useCallback, useEffect, useMemo, useRef, useState } from "react"; |
| 20 | type WheelEvent as ReactWheelEvent, | ||
| 21 | useCallback, | ||
| 22 | useEffect, | ||
| 23 | useMemo, | ||
| 24 | useRef, | ||
| 25 | useState, | ||
| 26 | } from "react"; | ||
| 27 | import { useOpenInPreferredEditor } from "../editorPreferences"; | 20 | import { useOpenInPreferredEditor } from "../editorPreferences"; |
| 28 | import { type DraftId } from "../composerDraftStore"; | 21 | import { type DraftId } from "../composerDraftStore"; |
| 29 | import { openDiffFilePrimaryAction } from "../diffFileActions"; | 22 | import { openDiffFilePrimaryAction } from "../diffFileActions"; |
| 30 | import { useCheckpointDiff } from "~/lib/checkpointDiffState"; | 23 | import { useCheckpointDiff } from "~/lib/checkpointDiffState"; |
| 31 | import { cn } from "~/lib/utils"; | 24 | import { cn } from "~/lib/utils"; |
| 32 | import { parseDiffRouteSearch, stripDiffSearchParams } from "../diffRouteSearch"; | 25 | import { selectThreadDiffPanelSelection, useDiffPanelStore } from "../diffPanelStore"; |
| 33 | import { useTheme } from "../hooks/useTheme"; | 26 | import { useTheme } from "../hooks/useTheme"; |
| 34 | import { | 27 | import { |
| 35 | buildFileDiffRenderKey, | 28 | buildFileDiffRenderKey, |
| @@ -40,19 +33,41 @@ import { · 4 unmodified lines | |||
| 40 | } from "../lib/diffRendering"; | 33 | } from "../lib/diffRendering"; |
| 41 | import { useTurnDiffSummaries } from "../hooks/useTurnDiffSummaries"; | 34 | import { useTurnDiffSummaries } from "../hooks/useTurnDiffSummaries"; |
| 42 | import { useProject, useThread } from "../state/entities"; | 35 | import { useProject, useThread } from "../state/entities"; |
| 43 | import { buildThreadRouteParams, resolveThreadRouteRef } from "../threadRoutes"; | 36 | import { resolveThreadRouteRef } from "../threadRoutes"; |
| 44 | import { useSettings } from "../hooks/useSettings"; | 37 | import { useSettings } from "../hooks/useSettings"; |
| 45 | import { formatShortTimestamp } from "../timestampFormat"; | 38 | import { formatShortTimestamp } from "../timestampFormat"; |
| 46 | import { DiffPanelLoadingState, DiffPanelShell, type DiffPanelMode } from "./DiffPanelShell"; | 39 | import { DiffPanelLoadingState, DiffPanelShell, type DiffPanelMode } from "./DiffPanelShell"; |
| 47 | import { AnnotatableFileDiff } from "./diffs/AnnotatableFileDiff"; | 40 | import { AnnotatableCodeView, type AnnotatableCodeViewHandle } from "./diffs/AnnotatableFileDiff"; |
| 48 | import { ToggleGroup, Toggle } from "./ui/toggle-group"; | 41 | 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"; | ||
| 49 | import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip"; | 61 | import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip"; |
| 50 | import { useEnvironmentQuery } from "../state/query"; | 62 | import { useEnvironmentQuery } from "../state/query"; |
| 51 | import { serverEnvironment } from "../state/server"; | 63 | import { serverEnvironment } from "../state/server"; |
| 64 | import { reviewEnvironment } from "../state/review"; | ||
| 52 | import { vcsEnvironment } from "../state/vcs"; | 65 | import { vcsEnvironment } from "../state/vcs"; |
| 66 | import { buildBaseRefChoices, filterBaseRefChoices } from "../lib/baseRefChoices"; | ||
| 53 | 67 | ||
| 54 | type DiffRenderMode = "stacked" | "split"; | 68 | type DiffRenderMode = "stacked" | "split"; |
| 55 | type DiffThemeType = "light" | "dark"; | 69 | type DiffThemeType = "light" | "dark"; |
| 70 | const AUTOMATIC_BASE_REF = "__automatic_base_ref__"; | ||
| 56 | 71 | ||
| 57 | interface CollapsedDiffFilesState { | 72 | interface CollapsedDiffFilesState { |
| 58 | readonly scopeKey: string | null; | 73 | readonly scopeKey: string | null; |
| @@ -167,27 +182,24 @@ interface DiffPanelProps { · 108 unmodified lines | |||
| 167 | export { DiffWorkerPoolProvider } from "./DiffWorkerPoolProvider"; | 182 | export { DiffWorkerPoolProvider } from "./DiffWorkerPoolProvider"; |
| 168 | 183 | ||
| 169 | export default function DiffPanel({ mode = "inline", composerDraftTarget }: DiffPanelProps) { | 184 | export default function DiffPanel({ mode = "inline", composerDraftTarget }: DiffPanelProps) { |
| 170 | const navigate = useNavigate(); | ||
| 171 | const { resolvedTheme } = useTheme(); | 185 | const { resolvedTheme } = useTheme(); |
| 172 | const settings = useSettings(); | 186 | const settings = useSettings(); |
| 173 | const [diffRenderMode, setDiffRenderMode] = useState<DiffRenderMode>("stacked"); | 187 | const [diffRenderMode, setDiffRenderMode] = useState<DiffRenderMode>("stacked"); |
| 174 | const [diffWordWrap, setDiffWordWrap] = useState(settings.diffWordWrap); | 188 | const [diffWordWrap, setDiffWordWrap] = useState(settings.diffWordWrap); |
| 175 | const [diffIgnoreWhitespace, setDiffIgnoreWhitespace] = useState(settings.diffIgnoreWhitespace); | 189 | const [diffIgnoreWhitespace, setDiffIgnoreWhitespace] = useState(settings.diffIgnoreWhitespace); |
| 190 | const [baseRefQuery, setBaseRefQuery] = useState(""); | ||
| 176 | const [collapsedDiffFiles, setCollapsedDiffFiles] = useState<CollapsedDiffFilesState>(() => ({ | 191 | const [collapsedDiffFiles, setCollapsedDiffFiles] = useState<CollapsedDiffFilesState>(() => ({ |
| 177 | scopeKey: null, | 192 | scopeKey: null, |
| 178 | fileKeys: EMPTY_COLLAPSED_DIFF_FILE_KEYS, | 193 | fileKeys: EMPTY_COLLAPSED_DIFF_FILE_KEYS, |
| 179 | })); | 194 | })); |
| 180 | const patchViewportRef = useRef<HTMLDivElement>(null); | 195 | const codeViewRef = useRef<AnnotatableCodeViewHandle>(null); |
| 181 | const turnStripRef = useRef<HTMLDivElement>(null); | ||
| 182 | const previousDiffOpenRef = useRef(false); | ||
| 183 | const [canScrollTurnStripLeft, setCanScrollTurnStripLeft] = useState(false); | ||
| 184 | const [canScrollTurnStripRight, setCanScrollTurnStripRight] = useState(false); | ||
| 185 | const routeThreadRef = useParams({ | 196 | const routeThreadRef = useParams({ |
| 186 | strict: false, | 197 | strict: false, |
| 187 | select: (params) => resolveThreadRouteRef(params), | 198 | select: (params) => resolveThreadRouteRef(params), |
| 188 | }); | 199 | }); |
| 189 | const diffSearch = useSearch({ strict: false, select: (search) => parseDiffRouteSearch(search) }); | 200 | const diffSelection = useDiffPanelStore((state) => |
| 190 | const diffOpen = diffSearch.diff === "1"; | 201 | selectThreadDiffPanelSelection(state.byThreadKey, routeThreadRef), |
| 202 | ); | ||
| 191 | const activeThreadId = routeThreadRef?.threadId ?? null; | 203 | const activeThreadId = routeThreadRef?.threadId ?? null; |
| 192 | const activeThread = useThread(routeThreadRef); | 204 | const activeThread = useThread(routeThreadRef); |
| 193 | const activeProjectId = activeThread?.projectId ?? null; | 205 | const activeProjectId = activeThread?.projectId ?? null; |
| @@ -233,8 +245,20 @@ export default function DiffPanel({ mode = "inline", composerDraftTarget }: Diff · 39 unmodified lines | |||
| 233 | [inferredCheckpointTurnCountByTurnId, turnDiffSummaries], | 245 | [inferredCheckpointTurnCountByTurnId, turnDiffSummaries], |
| 234 | ); | 246 | ); |
| 235 | 247 | ||
| 236 | const selectedTurnId = diffSearch.diffTurnId ?? null; | 248 | useEffect(() => { |
| 237 | const selectedFilePath = selectedTurnId !== null ? (diffSearch.diffFilePath ?? null) : null; | 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; | ||
| 238 | const selectedTurn = | 262 | const selectedTurn = |
| 239 | selectedTurnId === null | 263 | selectedTurnId === null |
| 240 | ? undefined | 264 | ? undefined |
| @@ -243,7 +267,16 @@ export default function DiffPanel({ mode = "inline", composerDraftTarget }: Diff · 2 unmodified lines | |||
| 243 | const selectedCheckpointTurnCount = | 267 | const selectedCheckpointTurnCount = |
| 244 | selectedTurn && | 268 | selectedTurn && |
| 245 | (selectedTurn.checkpointTurnCount ?? inferredCheckpointTurnCountByTurnId[selectedTurn.turnId]); | 269 | (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; | ||
| 247 | const collapseScopeKey = routeThreadRef | 280 | const collapseScopeKey = routeThreadRef |
| 248 | ? `${routeThreadRef.environmentId}:${routeThreadRef.threadId}:${reviewSectionId}` | 281 | ? `${routeThreadRef.environmentId}:${routeThreadRef.threadId}:${reviewSectionId}` |
| 249 | : null; | 282 | : null; |
| @@ -253,7 +286,9 @@ export default function DiffPanel({ mode = "inline", composerDraftTarget }: Diff · 3 unmodified lines | |||
| 253 | : EMPTY_COLLAPSED_DIFF_FILE_KEYS; | 286 | : EMPTY_COLLAPSED_DIFF_FILE_KEYS; |
| 254 | const reviewSectionTitle = selectedTurn | 287 | const reviewSectionTitle = selectedTurn |
| 255 | ? `Turn ${selectedCheckpointTurnCount ?? "?"}` | 288 | ? `Turn ${selectedCheckpointTurnCount ?? "?"}` |
| 256 | : "All turns"; | 289 | : selectedGitScope === "unstaged" |
| 290 | ? "Working tree" | ||
| 291 | : "Branch changes"; | ||
| 257 | const selectedCheckpointRange = useMemo( | 292 | const selectedCheckpointRange = useMemo( |
| 258 | () => | 293 | () => |
| 259 | typeof selectedCheckpointTurnCount === "number" | 294 | typeof selectedCheckpointTurnCount === "number" |
| @@ -264,62 +299,116 @@ export default function DiffPanel({ mode = "inline", composerDraftTarget }: Diff · 4 unmodified lines | |||
| 264 | : null, | 299 | : null, |
| 265 | [selectedCheckpointTurnCount], | 300 | [selectedCheckpointTurnCount], |
| 266 | ); | 301 | ); |
| 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]); | ||
| 301 | const activeCheckpointDiff = useCheckpointDiff( | 302 | const activeCheckpointDiff = useCheckpointDiff( |
| 302 | { | 303 | { |
| 303 | environmentId: activeThread?.environmentId ?? null, | 304 | environmentId: activeThread?.environmentId ?? null, |
| 304 | threadId: activeThreadId, | 305 | threadId: activeThreadId, |
| 305 | fromTurnCount: activeCheckpointRange?.fromTurnCount ?? null, | 306 | fromTurnCount: selectedCheckpointRange?.fromTurnCount ?? null, |
| 306 | toTurnCount: activeCheckpointRange?.toTurnCount ?? null, | 307 | toTurnCount: selectedCheckpointRange?.toTurnCount ?? null, |
| 307 | ignoreWhitespace: diffIgnoreWhitespace, | 308 | ignoreWhitespace: diffIgnoreWhitespace, |
| 308 | cacheScope: selectedTurn ? `turn:${selectedTurn.turnId}` : conversationCacheScope, | 309 | cacheScope: selectedTurn ? `turn:${selectedTurn.turnId}` : null, |
| 309 | }, | 310 | }, |
| 310 | { enabled: isGitRepo }, | 311 | { enabled: isGitRepo && selectedTurn !== undefined }, |
| 311 | ); | 312 | ); |
| 312 | const selectedTurnCheckpointDiff = selectedTurn ? activeCheckpointDiff.data?.diff : undefined; | 313 | const primaryBranchDiffPreview = useEnvironmentQuery( |
| 313 | const conversationCheckpointDiff = selectedTurn ? undefined : activeCheckpointDiff.data?.diff; | 314 | selectedTurnId === null && activeThread && activeCwd |
| 314 | const isLoadingCheckpointDiff = activeCheckpointDiff.isPending; | 315 | ? reviewEnvironment.diffPreview({ |
| 315 | const checkpointDiffError = activeCheckpointDiff.error; | 316 | environmentId: activeThread.environmentId, |
| 316 | 317 | input: { | |
| 317 | const selectedPatch = selectedTurn ? selectedTurnCheckpointDiff : conversationCheckpointDiff; | 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; | ||
| 318 | const hasResolvedPatch = typeof selectedPatch === "string"; | 404 | const hasResolvedPatch = typeof selectedPatch === "string"; |
| 319 | const hasNoNetChanges = hasResolvedPatch && selectedPatch.trim().length === 0; | 405 | const hasNoNetChanges = hasResolvedPatch && selectedPatch.trim().length === 0; |
| 320 | const renderablePatch = useMemo( | 406 | const renderablePatch = useMemo( |
| 321 | () => getRenderablePatch(selectedPatch, `diff-panel:${resolvedTheme}`), | 407 | () => |
| 322 | [resolvedTheme, selectedPatch], | 408 | getRenderablePatch(selectedPatch, `diff-panel:${resolvedTheme}`, { |
| 409 | compactPartialHunkOffsets: selectedTurnId === null, | ||
| 410 | }), | ||
| 411 | [resolvedTheme, selectedPatch, selectedTurnId], | ||
| 323 | ); | 412 | ); |
| 324 | const renderableFiles = useMemo(() => { | 413 | const renderableFiles = useMemo(() => { |
| 325 | if (!renderablePatch || renderablePatch.kind !== "files") { | 414 | if (!renderablePatch || renderablePatch.kind !== "files") { |
| @@ -332,24 +421,26 @@ export default function DiffPanel({ mode = "inline", composerDraftTarget }: Diff · 6 unmodified lines | |||
| 332 | }), | 421 | }), |
| 333 | ); | 422 | ); |
| 334 | }, [renderablePatch]); | 423 | }, [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 | ); | ||
| 335 | 437 | ||
| 336 | useEffect(() => { | 438 | useEffect(() => { |
| 337 | if (diffOpen && !previousDiffOpenRef.current) { | 439 | if (!selectedFilePath) return; |
| 338 | setDiffWordWrap(settings.diffWordWrap); | 440 | const file = codeViewFiles.find((candidate) => candidate.filePath === selectedFilePath); |
| 339 | setDiffIgnoreWhitespace(settings.diffIgnoreWhitespace); | 441 | if (!file) return; |
| 340 | } | 442 | codeViewRef.current?.scrollTo({ type: "item", id: file.fileKey, align: "start" }); |
| 341 | previousDiffOpenRef.current = diffOpen; | 443 | }, [codeViewFiles, selectedFilePath, selectedFileRevealRequestId]); |
| 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]); | ||
| 353 | 444 | ||
| 354 | const openDiffFile = useCallback( | 445 | const openDiffFile = useCallback( |
| 355 | (filePath: string) => { | 446 | (filePath: string) => { |
| @@ -385,186 +476,190 @@ export default function DiffPanel({ mode = "inline", composerDraftTarget }: Diff · 29 unmodified lines | |||
| 385 | ); | 476 | ); |
| 386 | 477 | ||
| 387 | const selectTurn = (turnId: TurnId) => { | 478 | const selectTurn = (turnId: TurnId) => { |
| 388 | if (!activeThread) return; | 479 | if (!routeThreadRef) return; |
| 389 | void navigate({ | 480 | useDiffPanelStore.getState().selectTurn(routeThreadRef, turnId); |
| 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 | }); | ||
| 397 | }; | 481 | }; |
| 398 | const selectWholeConversation = () => { | 482 | const selectGitScope = (scope: "branch" | "unstaged") => { |
| 399 | if (!activeThread) return; | 483 | if (!routeThreadRef) return; |
| 400 | void navigate({ | 484 | useDiffPanelStore.getState().selectGitScope(routeThreadRef, scope); |
| 401 | to: "/$environmentId/$threadId", | 485 | }; |
| 402 | params: buildThreadRouteParams(scopeThreadRef(activeThread.environmentId, activeThread.id)), | 486 | const selectBranchBaseRef = (baseRef: string | null) => { |
| 403 | search: (previous) => { | 487 | if (!routeThreadRef) return; |
| 404 | const rest = stripDiffSearchParams(previous); | 488 | useDiffPanelStore.getState().selectBranchBaseRef(routeThreadRef, baseRef); |
| 405 | return { ...rest, diff: "1" }; | ||
| 406 | }, | ||
| 407 | }); | ||
| 408 | }; | 489 | }; |
| 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]); | ||
| 469 | 490 | ||
| 470 | const headerRow = ( | 491 | const headerRow = ( |
| 471 | <> | 492 | <> |
| 472 | <div className="relative min-w-0 flex-1 [-webkit-app-region:no-drag]"> | 493 | <div className="flex min-w-0 flex-1 items-center gap-3 [-webkit-app-region:no-drag]"> |
| 473 | <button | 494 | <DropdownMenu> |
| 474 | type="button" | 495 | <DropdownMenuTrigger |
| 475 | className={cn( | 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" |
| 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", | 497 | aria-label={`Diff scope: ${selectedScopeLabel}`} |
| 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} | ||
| 518 | > | 498 | > |
| 519 | <div | 499 | <span className="truncate">{selectedScopeLabel}</span> |
| 520 | className={cn( | 500 | <ChevronDownIcon className="size-3.5 shrink-0 text-muted-foreground" /> |
| 521 | "rounded-md border px-2 py-1 text-left transition-colors", | 501 | </DropdownMenuTrigger> |
| 522 | selectedTurnId === null | 502 | <DropdownMenuContent align="start" className="w-60"> |
| 523 | ? "border-border bg-accent text-accent-foreground" | 503 | <DropdownMenuItem onClick={() => selectGitScope("unstaged")}> |
| 524 | : "border-border/70 bg-background/70 text-muted-foreground/80 hover:border-border hover:text-foreground/80", | 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" /> | ||
| 525 | )} | 513 | )} |
| 514 | </DropdownMenuItem> | ||
| 515 | <DropdownMenuItem | ||
| 516 | onClick={() => { | ||
| 517 | if (latestTurn) selectTurn(latestTurn.turnId); | ||
| 518 | }} | ||
| 526 | > | 519 | > |
| 527 | <div className="text-[10px] leading-tight font-medium">All turns</div> | 520 | <span>Latest turn</span> |
| 528 | </div> | 521 | {selectedTurnId !== null && selectedTurn?.turnId === latestTurn?.turnId && ( |
| 529 | </button> | 522 | <CheckIcon className="ml-auto" /> |
| 530 | {orderedTurnDiffSummaries.map((summary) => ( | 523 | )} |
| 531 | <Tooltip key={summary.turnId}> | 524 | </DropdownMenuItem> |
| 532 | <TooltipTrigger | 525 | <DropdownMenuSub> |
| 533 | render={ | 526 | <DropdownMenuSubTrigger>Turn</DropdownMenuSubTrigger> |
| 534 | <button | 527 | <DropdownMenuSubContent className="w-64"> |
| 535 | type="button" | 528 | {orderedTurnDiffSummaries.map((summary) => { |
| 536 | aria-label={summary.turnId} | 529 | const turnCount = |
| 537 | className="shrink-0 rounded-md" | 530 | summary.checkpointTurnCount ?? |
| 538 | onClick={() => selectTurn(summary.turnId)} | 531 | inferredCheckpointTurnCountByTurnId[summary.turnId] ?? |
| 539 | data-turn-chip-selected={summary.turnId === selectedTurn?.turnId} | 532 | "?"; |
| 540 | /> | 533 | return ( |
| 541 | } | 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" | ||
| 542 | > | 580 | > |
| 543 | <div | 581 | <div className="min-w-0 shrink-0 px-3 pt-2.5"> |
| 544 | className={cn( | 582 | <div className="relative -translate-y-px border-b border-border/70 pb-1.5 transition-colors focus-within:border-ring"> |
| 545 | "rounded-md border px-2 py-1 text-left transition-colors", | 583 | <SearchIcon |
| 546 | summary.turnId === selectedTurn?.turnId | 584 | aria-hidden="true" |
| 547 | ? "border-border bg-accent text-accent-foreground" | 585 | className="pointer-events-none absolute top-1.5 left-0 size-4 shrink-0 text-muted-foreground/55" |
| 548 | : "border-border/70 bg-background/70 text-muted-foreground/80 hover:border-border hover:text-foreground/80", | 586 | /> |
| 549 | )} | 587 | <ComboboxInput |
| 550 | > | 588 | className="[&_input]:h-6.5 [&_input]:ps-5 [&_input]:font-sans [&_input]:leading-6.5" |
| 551 | <div className="flex items-center gap-1"> | 589 | inputClassName="rounded-none bg-transparent text-sm" |
| 552 | <span className="text-[10px] leading-tight font-medium"> | 590 | placeholder="Search refs..." |
| 553 | Turn{" "} | 591 | showTrigger={false} |
| 554 | {summary.checkpointTurnCount ?? | 592 | size="sm" |
| 555 | inferredCheckpointTurnCountByTurnId[summary.turnId] ?? | 593 | unstyled |
| 556 | "?"} | 594 | value={baseRefQuery} |
| 557 | </span> | 595 | onChange={(event) => setBaseRefQuery(event.target.value)} |
| 558 | <span className="text-[9px] leading-tight opacity-70 tabular-nums"> | 596 | /> |
| 559 | {formatShortTimestamp(summary.completedAt, settings.timestampFormat)} | ||
| 560 | </span> | ||
| 561 | </div> | 597 | </div> |
| 562 | </div> | 598 | </div> |
| 563 | </TooltipTrigger> | 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"> |
| 564 | <TooltipPopup side="top">{summary.turnId}</TooltipPopup> | 600 | <span aria-hidden="true" /> |
| 565 | </Tooltip> | 601 | <div className="grid min-w-0 grid-cols-[minmax(0,1fr)_2rem] items-center"> |
| 566 | ))} | 602 | <span>Branch</span> |
| 567 | </div> | 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 | )} | ||
| 568 | </div> | 663 | </div> |
| 569 | <div className="flex shrink-0 items-center gap-1 [-webkit-app-region:no-drag]"> | 664 | <div className="flex shrink-0 items-center gap-1 [-webkit-app-region:no-drag]"> |
| 570 | <ToggleGroup | 665 | <ToggleGroup |
| @@ -644,24 +739,35 @@ export default function DiffPanel({ mode = "inline", composerDraftTarget }: Diff · 73 unmodified lines | |||
| 644 | <div className="flex flex-1 items-center justify-center px-5 text-center text-xs text-muted-foreground/70"> | 739 | <div className="flex flex-1 items-center justify-center px-5 text-center text-xs text-muted-foreground/70"> |
| 645 | Turn diffs are unavailable because this project is not a git repository. | 740 | Turn diffs are unavailable because this project is not a git repository. |
| 646 | </div> | 741 | </div> |
| 647 | ) : orderedTurnDiffSummaries.length === 0 ? ( | 742 | ) : selectedTurnId !== null && orderedTurnDiffSummaries.length === 0 ? ( |
| 648 | <div className="flex flex-1 items-center justify-center px-5 text-center text-xs text-muted-foreground/70"> | 743 | <div className="flex flex-1 items-center justify-center px-5 text-center text-xs text-muted-foreground/70"> |
| 649 | No completed turns yet. | 744 | No completed turns yet. |
| 650 | </div> | 745 | </div> |
| 651 | ) : ( | 746 | ) : ( |
| 652 | <> | 747 | <> |
| 653 | <div | 748 | <div className="diff-panel-viewport flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden"> |
| 654 | ref={patchViewportRef} | 749 | {isSelectedPatchTruncated && ( |
| 655 | className="diff-panel-viewport min-h-0 min-w-0 flex-1 overflow-hidden" | 750 | <p className="shrink-0 border-b border-border/70 bg-muted/40 px-3 py-1.5 text-[11px] text-muted-foreground"> |
| 656 | > | 751 | This diff was truncated because it exceeded the preview limit. The changes shown are |
| 657 | {checkpointDiffError && !renderablePatch && ( | 752 | incomplete. |
| 753 | </p> | ||
| 754 | )} | ||
| 755 | {selectedPatchError && !renderablePatch && ( | ||
| 658 | <div className="px-3"> | 756 | <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> |
| 660 | </div> | 758 | </div> |
| 661 | )} | 759 | )} |
| 662 | {!renderablePatch ? ( | 760 | {!renderablePatch ? ( |
| 663 | isLoadingCheckpointDiff ? ( | 761 | isLoadingSelectedPatch ? ( |
| 664 | <DiffPanelLoadingState label="Loading checkpoint diff..." /> | 762 | <DiffPanelLoadingState |
| 763 | label={ | ||
| 764 | selectedTurn | ||
| 765 | ? "Loading checkpoint diff..." | ||
| 766 | : selectedGitScope === "unstaged" | ||
| 767 | ? "Loading working tree diff..." | ||
| 768 | : "Loading branch diff..." | ||
| 769 | } | ||
| 770 | /> | ||
| 665 | ) : ( | 771 | ) : ( |
| 666 | <div className="flex h-full items-center justify-center px-3 py-2 text-xs text-muted-foreground/70"> | 772 | <div className="flex h-full items-center justify-center px-3 py-2 text-xs text-muted-foreground/70"> |
| 667 | <p> | 773 | <p> |
| @@ -672,88 +778,73 @@ export default function DiffPanel({ mode = "inline", composerDraftTarget }: Diff · 4 unmodified lines | |||
| 672 | </div> | 778 | </div> |
| 673 | ) | 779 | ) |
| 674 | ) : renderablePatch.kind === "files" ? ( | 780 | ) : renderablePatch.kind === "files" ? ( |
| 675 | <Virtualizer | 781 | <div |
| 676 | className="diff-render-surface h-full min-h-0 overflow-auto px-2 pb-2" | 782 | className="min-h-0 flex-1" |
| 677 | config={{ | 783 | onClickCapture={(event) => { |
| 678 | overscrollSize: 600, | 784 | const composedPath = event.nativeEvent.composedPath?.() ?? []; |
| 679 | intersectionObserverMargin: 1200, | 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); | ||
| 680 | }} | 791 | }} |
| 681 | > | 792 | > |
| 682 | {renderableFiles.map((fileDiff) => { | 793 | <AnnotatableCodeView |
| 683 | const filePath = resolveFileDiffPath(fileDiff); | 794 | viewerRef={codeViewRef} |
| 684 | const fileKey = buildFileDiffRenderKey(fileDiff); | 795 | key={collapseScopeKey ?? reviewSectionId} |
| 685 | const themedFileKey = `${fileKey}:${resolvedTheme}`; | 796 | className="diff-render-surface h-full min-h-0 overflow-auto" |
| 686 | const collapsed = collapsedDiffFileKeys.has(fileKey); | 797 | files={codeViewFiles} |
| 687 | return ( | 798 | sectionId={reviewSectionId} |
| 688 | <div | 799 | sectionTitle={reviewSectionTitle} |
| 689 | key={themedFileKey} | 800 | composerDraftTarget={composerDraftTarget} |
| 690 | data-diff-file-path={filePath} | 801 | renderHeaderPrefix={(fileDiff, fileKey, collapsed) => { |
| 691 | className="diff-render-file group/diff-file mb-2 rounded-md first:mt-2 last:mb-0" | 802 | const filePath = resolveFileDiffPath(fileDiff); |
| 692 | onClickCapture={(event) => { | 803 | return ( |
| 693 | const nativeEvent = event.nativeEvent as MouseEvent; | 804 | <Tooltip> |
| 694 | const composedPath = nativeEvent.composedPath?.() ?? []; | 805 | <TooltipTrigger |
| 695 | const clickedHeader = composedPath.some((node) => { | 806 | render={ |
| 696 | if (!(node instanceof Element)) return false; | 807 | <button |
| 697 | return node.hasAttribute("data-title"); | 808 | type="button" |
| 698 | }); | 809 | className={cn( |
| 699 | if (!clickedHeader) return; | 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", |
| 700 | openDiffFile(filePath); | 811 | getDiffCollapseIconClassName(fileDiff), |
| 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" /> | ||
| 734 | )} | 812 | )} |
| 735 | </TooltipTrigger> | 813 | aria-label={collapsed ? `Expand ${filePath}` : `Collapse ${filePath}`} |
| 736 | <TooltipPopup side="top"> | 814 | aria-expanded={!collapsed} |
| 737 | {collapsed ? "Expand diff" : "Collapse diff"} | 815 | onClick={(event) => { |
| 738 | </TooltipPopup> | 816 | event.stopPropagation(); |
| 739 | </Tooltip> | 817 | toggleDiffFileCollapsed(fileKey); |
| 740 | )} | 818 | }} |
| 741 | options={{ | 819 | /> |
| 742 | collapsed, | 820 | } |
| 743 | diffStyle: diffRenderMode === "split" ? "split" : "unified", | 821 | > |
| 744 | lineDiffType: "none", | 822 | {collapsed ? ( |
| 745 | overflow: diffWordWrap ? "wrap" : "scroll", | 823 | <ChevronRightIcon className="size-4" /> |
| 746 | theme: resolveDiffThemeName(resolvedTheme), | 824 | ) : ( |
| 747 | themeType: resolvedTheme as DiffThemeType, | 825 | <ChevronDownIcon className="size-4" /> |
| 748 | unsafeCSS: DIFF_PANEL_UNSAFE_CSS, | 826 | )} |
| 749 | }} | 827 | </TooltipTrigger> |
| 750 | /> | 828 | <TooltipPopup side="top"> |
| 751 | </div> | 829 | {collapsed ? "Expand diff" : "Collapse diff"} |
| 752 | ); | 830 | </TooltipPopup> |
| 753 | })} | 831 | </Tooltip> |
| 754 | </Virtualizer> | 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> | ||
| 755 | ) : ( | 846 | ) : ( |
| 756 | <div className="h-full overflow-auto p-2"> | 847 | <div className="min-h-0 flex-1 overflow-auto p-2"> |
| 757 | <div className="space-y-2"> | 848 | <div className="space-y-2"> |
| 758 | <p className="text-[11px] text-muted-foreground/75">{renderablePatch.reason}</p> | 849 | <p className="text-[11px] text-muted-foreground/75">{renderablePatch.reason}</p> |
| 759 | <pre | 850 | <pre |
| @@ -48,14 +48,8 @@ export function DiffPanelShell(props: { · 47 unmodified lines | |||
| 48 | export function DiffPanelHeaderSkeleton() { | 48 | export function DiffPanelHeaderSkeleton() { |
| 49 | return ( | 49 | return ( |
| 50 | <> | 50 | <> |
| 51 | <div className="relative min-w-0 flex-1"> | 51 | <div className="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" /> | 52 | <Skeleton className="h-8 w-32 rounded-lg" /> |
| 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> | ||
| 59 | </div> | 53 | </div> |
| 60 | <div className="flex shrink-0 gap-1"> | 54 | <div className="flex shrink-0 gap-1"> |
| 61 | <Skeleton className="size-7 rounded-md" /> | 55 | <Skeleton className="size-7 rounded-md" /> |
| @@ -1,14 +1,23 @@ | |||
| 1 | import type { | 1 | import type { |
| 2 | AnnotationSide, | 2 | AnnotationSide, |
| 3 | CodeViewDiffItem, | ||
| 4 | CodeViewItem, | ||
| 3 | DiffLineAnnotation, | 5 | DiffLineAnnotation, |
| 4 | FileDiffMetadata, | 6 | FileDiffMetadata, |
| 5 | SelectedLineRange, | 7 | SelectedLineRange, |
| 6 | } from "@pierre/diffs"; | 8 | } 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"; | ||
| 8 | import type { ScopedThreadRef } from "@t3tools/contracts"; | 16 | 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"; |
| 10 | 18 | ||
| 11 | import { type DraftId, useComposerDraftStore } from "~/composerDraftStore"; | 19 | import { type DraftId, useComposerDraftStore } from "~/composerDraftStore"; |
| 20 | import { fnv1a32 } from "~/lib/diffRendering"; | ||
| 12 | import { | 21 | import { |
| 13 | buildDiffReviewComment, | 22 | buildDiffReviewComment, |
| 14 | restoreDiffReviewCommentRange, | 23 | restoreDiffReviewCommentRange, |
| @@ -31,6 +40,7 @@ interface DiffCommentAnnotationGroup { · 16 unmodified lines | |||
| 31 | } | 40 | } |
| 32 | 41 | ||
| 33 | type DiffCommentLineAnnotation = DiffLineAnnotation<DiffCommentAnnotationGroup>; | 42 | type DiffCommentLineAnnotation = DiffLineAnnotation<DiffCommentAnnotationGroup>; |
| 43 | export type AnnotatableCodeViewHandle = CodeViewHandle<DiffCommentAnnotationGroup>; | ||
| 34 | const EMPTY_REVIEW_COMMENTS: ReadonlyArray<ReviewCommentContext> = []; | 44 | const EMPTY_REVIEW_COMMENTS: ReadonlyArray<ReviewCommentContext> = []; |
| 35 | 45 | ||
| 36 | function annotationSide(range: SelectedLineRange): AnnotationSide { | 46 | function annotationSide(range: SelectedLineRange): AnnotationSide { |
| @@ -237,3 +247,200 @@ export function AnnotatableFileDiff({ · 200 unmodified lines | |||
| 237 | /> | 247 | /> |
| 238 | ); | 248 | ); |
| 239 | } | 249 | } |
| 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 | } | ||
| @@ -3,11 +3,11 @@ import { describe, expect, it } from "vite-plus/test"; · 2 unmodified lines | |||
| 3 | import { observeAutomationOwnerConnectedGeneration } from "./PreviewAutomationOwner"; | 3 | import { observeAutomationOwnerConnectedGeneration } from "./PreviewAutomationOwner"; |
| 4 | 4 | ||
| 5 | describe("observeAutomationOwnerConnectedGeneration", () => { | 5 | describe("observeAutomationOwnerConnectedGeneration", () => { |
| 6 | it("re-reports ownership only after a later transport generation connects", () => { | 6 | it("reports ownership when the initial transport generation connects", () => { |
| 7 | const initial = observeAutomationOwnerConnectedGeneration(null, 1); | 7 | const initial = observeAutomationOwnerConnectedGeneration(null, 1); |
| 8 | expect(initial).toEqual({ | 8 | expect(initial).toEqual({ |
| 9 | nextGeneration: 1, | 9 | nextGeneration: 1, |
| 10 | shouldReport: false, | 10 | shouldReport: true, |
| 11 | }); | 11 | }); |
| 12 | 12 | ||
| 13 | const disconnected = observeAutomationOwnerConnectedGeneration(initial.nextGeneration, null); | 13 | const disconnected = observeAutomationOwnerConnectedGeneration(initial.nextGeneration, null); |
| @@ -1,15 +1,16 @@ | |||
| 1 | "use client"; | 1 | "use client"; |
| 2 | 2 | ||
| 3 | import { useAtomValue } from "@effect/atom-react"; | ||
| 3 | import { squashAtomCommandFailure } from "@t3tools/client-runtime/state/runtime"; | 4 | import { squashAtomCommandFailure } from "@t3tools/client-runtime/state/runtime"; |
| 4 | import type { | 5 | import type { |
| 5 | PreviewAutomationNavigateInput, | 6 | PreviewAutomationNavigateInput, |
| 6 | PreviewAutomationOpenInput, | 7 | PreviewAutomationOpenInput, |
| 7 | PreviewAutomationRequest, | 8 | PreviewAutomationRequest, |
| 8 | PreviewAutomationResponse, | 9 | PreviewAutomationOwner as PreviewAutomationOwnerState, |
| 9 | PreviewAutomationStatus, | 10 | PreviewAutomationStatus, |
| 10 | ScopedThreadRef, | 11 | ScopedThreadRef, |
| 11 | } from "@t3tools/contracts"; | 12 | } from "@t3tools/contracts"; |
| 12 | import { useCallback, useEffect, useId, useRef } from "react"; | 13 | import { useCallback, useEffect, useEffectEvent, useId, useMemo, useRef, useState } from "react"; |
| 13 | 14 | ||
| 14 | import { | 15 | import { |
| 15 | applyPreviewServerSnapshot, | 16 | applyPreviewServerSnapshot, |
| @@ -20,11 +21,14 @@ import { useRightPanelStore } from "~/rightPanelStore"; · 4 unmodified lines | |||
| 20 | import { resolveBrowserNavigationTarget } from "~/browser/browserTargetResolver"; | 21 | import { resolveBrowserNavigationTarget } from "~/browser/browserTargetResolver"; |
| 21 | import { startBrowserRecording, stopBrowserRecording } from "~/browser/browserRecording"; | 22 | import { startBrowserRecording, stopBrowserRecording } from "~/browser/browserRecording"; |
| 22 | import { previewEnvironment } from "~/state/preview"; | 23 | import { previewEnvironment } from "~/state/preview"; |
| 23 | import { useEnvironmentQuery } from "~/state/query"; | ||
| 24 | import { useEnvironmentConnectionState } from "~/state/environments"; | 24 | import { useEnvironmentConnectionState } from "~/state/environments"; |
| 25 | import { useAtomCommand } from "~/state/use-atom-command"; | 25 | import { useAtomCommand } from "~/state/use-atom-command"; |
| 26 | 26 | ||
| 27 | import { previewBridge } from "./previewBridge"; | 27 | import { previewBridge } from "./previewBridge"; |
| 28 | import { | ||
| 29 | createLatestPreviewAutomationRequestHandler, | ||
| 30 | createPreviewAutomationRequestConsumerAtom, | ||
| 31 | } from "./previewAutomationRequestConsumer"; | ||
| 28 | 32 | ||
| 29 | export function observeAutomationOwnerConnectedGeneration( | 33 | export function observeAutomationOwnerConnectedGeneration( |
| 30 | previousGeneration: number | null, | 34 | previousGeneration: number | null, |
| @@ -41,7 +45,7 @@ export function observeAutomationOwnerConnectedGeneration( · 10 unmodified lines | |||
| 41 | } | 45 | } |
| 42 | return { | 46 | return { |
| 43 | nextGeneration: connectedGeneration, | 47 | nextGeneration: connectedGeneration, |
| 44 | shouldReport: previousGeneration !== null && previousGeneration !== connectedGeneration, | 48 | shouldReport: previousGeneration !== connectedGeneration, |
| 45 | }; | 49 | }; |
| 46 | } | 50 | } |
| 47 | 51 | ||
| @@ -109,24 +113,10 @@ const currentStatus = async ( · 61 unmodified lines | |||
| 109 | }; | 113 | }; |
| 110 | }; | 114 | }; |
| 111 | 115 | ||
| 112 | const serializeError = (error: unknown): NonNullable<PreviewAutomationResponse["error"]> => { | 116 | const previewTabNotFoundError = (): Error => { |
| 113 | if (error instanceof Error) { | 117 | const error = new Error("Preview tab is not initialized."); |
| 114 | const detail = | 118 | error.name = "PreviewAutomationTabNotFoundError"; |
| 115 | "detail" in error && (error as { detail?: unknown }).detail !== undefined | 119 | return error; |
| 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 | }; | ||
| 130 | }; | 120 | }; |
| 131 | 121 | ||
| 132 | export function PreviewAutomationOwner(props: { | 122 | export function PreviewAutomationOwner(props: { |
| @@ -135,12 +125,22 @@ export function PreviewAutomationOwner(props: { · 2 unmodified lines | |||
| 135 | }) { | 125 | }) { |
| 136 | const { threadRef, visible } = props; | 126 | const { threadRef, visible } = props; |
| 137 | const automationClientId = useId(); | 127 | const automationClientId = useId(); |
| 138 | const automationRequests = useEnvironmentQuery( | 128 | const initialAutomationOwner = useMemo<PreviewAutomationOwnerState>( |
| 139 | previewEnvironment.automationRequests({ | 129 | () => ({ |
| 130 | clientId: automationClientId, | ||
| 140 | environmentId: threadRef.environmentId, | 131 | 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(), | ||
| 142 | }), | 137 | }), |
| 138 | [automationClientId, threadRef.environmentId, threadRef.threadId], | ||
| 143 | ); | 139 | ); |
| 140 | const automationRequestsAtom = previewEnvironment.automationRequests({ | ||
| 141 | environmentId: threadRef.environmentId, | ||
| 142 | input: initialAutomationOwner, | ||
| 143 | }); | ||
| 144 | const connectionState = useEnvironmentConnectionState(threadRef.environmentId).data; | 144 | const connectionState = useEnvironmentConnectionState(threadRef.environmentId).data; |
| 145 | const connectedGeneration = | 145 | const connectedGeneration = |
| 146 | connectionState?.phase === "connected" ? connectionState.generation : null; | 146 | connectionState?.phase === "connected" ? connectionState.generation : null; |
| @@ -159,13 +159,24 @@ export function PreviewAutomationOwner(props: { · 12 unmodified lines | |||
| 159 | previewEnvironment.clearAutomationOwner, | 159 | previewEnvironment.clearAutomationOwner, |
| 160 | "preview automation owner clear", | 160 | "preview automation owner clear", |
| 161 | ); | 161 | ); |
| 162 | const ownerStateRef = useRef({ threadRef, visible }); | ||
| 163 | const connectedGenerationRef = useRef<number | null>(null); | 162 | const connectedGenerationRef = useRef<number | null>(null); |
| 164 | const handlerRef = useRef<(request: PreviewAutomationRequest) => Promise<unknown>>( | 163 | const reportCurrentAutomationOwner = useEffectEvent(() => { |
| 165 | async () => undefined, | 164 | const state = readThreadPreviewState(threadRef); |
| 166 | ); | 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 | }); | ||
| 167 | useEffect(() => { | 178 | useEffect(() => { |
| 168 | ownerStateRef.current = { threadRef, visible }; | 179 | void reportCurrentAutomationOwner(); |
| 169 | }, [threadRef, visible]); | 180 | }, [threadRef, visible]); |
| 170 | 181 | ||
| 171 | const handleRequest = useCallback( | 182 | const handleRequest = useCallback( |
| @@ -208,7 +219,7 @@ export function PreviewAutomationOwner(props: { · 36 unmodified lines | |||
| 208 | return currentStatus(threadRef, input.show ?? true); | 219 | return currentStatus(threadRef, input.show ?? true); |
| 209 | } | 220 | } |
| 210 | case "navigate": { | 221 | case "navigate": { |
| 211 | if (!previewBridge || !tabId) throw new Error("Preview tab is not initialized."); | 222 | if (!previewBridge || !tabId) throw previewTabNotFoundError(); |
| 212 | const input = request.input as PreviewAutomationNavigateInput; | 223 | const input = request.input as PreviewAutomationNavigateInput; |
| 213 | const resolution = resolveBrowserNavigationTarget( | 224 | const resolution = resolveBrowserNavigationTarget( |
| 214 | threadRef.environmentId, | 225 | threadRef.environmentId, |
| @@ -223,46 +234,46 @@ export function PreviewAutomationOwner(props: { · 8 unmodified lines | |||
| 223 | return currentStatus(threadRef, visible); | 234 | return currentStatus(threadRef, visible); |
| 224 | } | 235 | } |
| 225 | case "snapshot": | 236 | case "snapshot": |
| 226 | if (!previewBridge || !tabId) throw new Error("Preview tab is not initialized."); | 237 | if (!previewBridge || !tabId) throw previewTabNotFoundError(); |
| 227 | return previewBridge.automation.snapshot(tabId); | 238 | return previewBridge.automation.snapshot(tabId); |
| 228 | case "click": | 239 | case "click": |
| 229 | if (!previewBridge || !tabId) throw new Error("Preview tab is not initialized."); | 240 | if (!previewBridge || !tabId) throw previewTabNotFoundError(); |
| 230 | return previewBridge.automation.click( | 241 | return previewBridge.automation.click( |
| 231 | tabId, | 242 | tabId, |
| 232 | request.input as Parameters<typeof previewBridge.automation.click>[1], | 243 | request.input as Parameters<typeof previewBridge.automation.click>[1], |
| 233 | ); | 244 | ); |
| 234 | case "type": | 245 | case "type": |
| 235 | if (!previewBridge || !tabId) throw new Error("Preview tab is not initialized."); | 246 | if (!previewBridge || !tabId) throw previewTabNotFoundError(); |
| 236 | return previewBridge.automation.type( | 247 | return previewBridge.automation.type( |
| 237 | tabId, | 248 | tabId, |
| 238 | request.input as Parameters<typeof previewBridge.automation.type>[1], | 249 | request.input as Parameters<typeof previewBridge.automation.type>[1], |
| 239 | ); | 250 | ); |
| 240 | case "press": | 251 | case "press": |
| 241 | if (!previewBridge || !tabId) throw new Error("Preview tab is not initialized."); | 252 | if (!previewBridge || !tabId) throw previewTabNotFoundError(); |
| 242 | return previewBridge.automation.press( | 253 | return previewBridge.automation.press( |
| 243 | tabId, | 254 | tabId, |
| 244 | request.input as Parameters<typeof previewBridge.automation.press>[1], | 255 | request.input as Parameters<typeof previewBridge.automation.press>[1], |
| 245 | ); | 256 | ); |
| 246 | case "scroll": | 257 | case "scroll": |
| 247 | if (!previewBridge || !tabId) throw new Error("Preview tab is not initialized."); | 258 | if (!previewBridge || !tabId) throw previewTabNotFoundError(); |
| 248 | return previewBridge.automation.scroll( | 259 | return previewBridge.automation.scroll( |
| 249 | tabId, | 260 | tabId, |
| 250 | request.input as Parameters<typeof previewBridge.automation.scroll>[1], | 261 | request.input as Parameters<typeof previewBridge.automation.scroll>[1], |
| 251 | ); | 262 | ); |
| 252 | case "evaluate": | 263 | case "evaluate": |
| 253 | if (!previewBridge || !tabId) throw new Error("Preview tab is not initialized."); | 264 | if (!previewBridge || !tabId) throw previewTabNotFoundError(); |
| 254 | return previewBridge.automation.evaluate( | 265 | return previewBridge.automation.evaluate( |
| 255 | tabId, | 266 | tabId, |
| 256 | request.input as Parameters<typeof previewBridge.automation.evaluate>[1], | 267 | request.input as Parameters<typeof previewBridge.automation.evaluate>[1], |
| 257 | ); | 268 | ); |
| 258 | case "waitFor": | 269 | case "waitFor": |
| 259 | if (!previewBridge || !tabId) throw new Error("Preview tab is not initialized."); | 270 | if (!previewBridge || !tabId) throw previewTabNotFoundError(); |
| 260 | return previewBridge.automation.waitFor( | 271 | return previewBridge.automation.waitFor( |
| 261 | tabId, | 272 | tabId, |
| 262 | request.input as Parameters<typeof previewBridge.automation.waitFor>[1], | 273 | request.input as Parameters<typeof previewBridge.automation.waitFor>[1], |
| 263 | ); | 274 | ); |
| 264 | case "recordingStart": { | 275 | case "recordingStart": { |
| 265 | if (!tabId) throw new Error("Preview tab is not initialized."); | 276 | if (!tabId) throw previewTabNotFoundError(); |
| 266 | const startedAt = await startBrowserRecording(tabId); | 277 | const startedAt = await startBrowserRecording(tabId); |
| 267 | return { | 278 | return { |
| 268 | tabId, | 279 | tabId, |
| @@ -271,7 +282,7 @@ export function PreviewAutomationOwner(props: { · 2 unmodified lines | |||
| 271 | }; | 282 | }; |
| 272 | } | 283 | } |
| 273 | case "recordingStop": { | 284 | case "recordingStop": { |
| 274 | if (!tabId) throw new Error("Preview tab is not initialized."); | 285 | if (!tabId) throw previewTabNotFoundError(); |
| 275 | const artifact = await stopBrowserRecording(tabId); | 286 | const artifact = await stopBrowserRecording(tabId); |
| 276 | if (!artifact) throw new Error("No active recording exists for this preview tab."); | 287 | if (!artifact) throw new Error("No active recording exists for this preview tab."); |
| 277 | return artifact; | 288 | return artifact; |
| @@ -280,34 +291,34 @@ export function PreviewAutomationOwner(props: { · 2 unmodified lines | |||
| 280 | }, | 291 | }, |
| 281 | [open, threadRef, visible], | 292 | [open, threadRef, visible], |
| 282 | ); | 293 | ); |
| 294 | const [requestHandler] = useState(() => | ||
| 295 | createLatestPreviewAutomationRequestHandler(handleRequest), | ||
| 296 | ); | ||
| 283 | useEffect(() => { | 297 | useEffect(() => { |
| 284 | handlerRef.current = handleRequest; | 298 | requestHandler.set(handleRequest); |
| 285 | }, [handleRequest]); | 299 | }, [handleRequest, requestHandler]); |
| 286 | 300 | ||
| 287 | useEffect(() => { | 301 | const automationRequestConsumerAtom = useMemo( |
| 288 | const request = automationRequests.data; | 302 | () => |
| 289 | if (!request) return; | 303 | createPreviewAutomationRequestConsumerAtom({ |
| 290 | void handlerRef.current(request).then( | 304 | requestsAtom: automationRequestsAtom, |
| 291 | (result) => | 305 | handleRequest: requestHandler.handle, |
| 292 | respondToAutomation({ | 306 | respond: (response) => |
| 293 | environmentId: threadRef.environmentId, | 307 | respondToAutomation({ |
| 294 | input: { | 308 | environmentId: threadRef.environmentId, |
| 295 | requestId: request.requestId, | 309 | input: response, |
| 296 | ok: true, | 310 | }), |
| 297 | ...(result === undefined ? {} : { result }), | 311 | label: `preview:automation-request-consumer:${automationClientId}`, |
| 298 | }, | 312 | }), |
| 299 | }), | 313 | [ |
| 300 | (error) => | 314 | automationClientId, |
| 301 | respondToAutomation({ | 315 | automationRequestsAtom, |
| 302 | environmentId: threadRef.environmentId, | 316 | requestHandler, |
| 303 | input: { | 317 | respondToAutomation, |
| 304 | requestId: request.requestId, | 318 | threadRef.environmentId, |
| 305 | ok: false, | 319 | ], |
| 306 | error: serializeError(error), | 320 | ); |
| 307 | }, | 321 | useAtomValue(automationRequestConsumerAtom); |
| 308 | }), | ||
| 309 | ); | ||
| 310 | }, [automationRequests.data, respondToAutomation, threadRef.environmentId]); | ||
| 311 | 322 | ||
| 312 | useEffect(() => { | 323 | useEffect(() => { |
| 313 | const observation = observeAutomationOwnerConnectedGeneration( | 324 | const observation = observeAutomationOwnerConnectedGeneration( |
| @@ -317,39 +328,11 @@ export function PreviewAutomationOwner(props: { · 3 unmodified lines | |||
| 317 | connectedGenerationRef.current = observation.nextGeneration; | 328 | connectedGenerationRef.current = observation.nextGeneration; |
| 318 | if (!observation.shouldReport) return; | 329 | if (!observation.shouldReport) return; |
| 319 | 330 | ||
| 320 | const ownerState = ownerStateRef.current; | 331 | void reportCurrentAutomationOwner(); |
| 321 | const state = readThreadPreviewState(ownerState.threadRef); | 332 | }, [connectedGeneration]); |
| 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]); | ||
| 335 | 333 | ||
| 336 | useEffect(() => { | 334 | useEffect(() => { |
| 337 | const report = () => { | 335 | const report = () => void reportCurrentAutomationOwner(); |
| 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(); | ||
| 353 | window.addEventListener("focus", report); | 336 | window.addEventListener("focus", report); |
| 354 | const unsubscribe = subscribeThreadPreviewState(threadRef, (state, previous) => { | 337 | const unsubscribe = subscribeThreadPreviewState(threadRef, (state, previous) => { |
| 355 | if (state.snapshot?.tabId !== previous.snapshot?.tabId) { | 338 | if (state.snapshot?.tabId !== previous.snapshot?.tabId) { |
| @@ -361,10 +344,14 @@ export function PreviewAutomationOwner(props: { · 5 unmodified lines | |||
| 361 | unsubscribe(); | 344 | unsubscribe(); |
| 362 | void clearAutomationOwner({ | 345 | void clearAutomationOwner({ |
| 363 | environmentId: threadRef.environmentId, | 346 | environmentId: threadRef.environmentId, |
| 364 | input: { clientId: automationClientId }, | 347 | input: { |
| 348 | clientId: automationClientId, | ||
| 349 | environmentId: threadRef.environmentId, | ||
| 350 | threadId: threadRef.threadId, | ||
| 351 | }, | ||
| 365 | }); | 352 | }); |
| 366 | }; | 353 | }; |
| 367 | }, [automationClientId, clearAutomationOwner, reportAutomationOwner, threadRef, visible]); | 354 | }, [automationClientId, clearAutomationOwner, threadRef]); |
| 368 | 355 | ||
| 369 | return null; | 356 | return null; |
| 370 | } | 357 | } |
| @@ -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 | }); | ||
| @@ -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 | } | ||
| @@ -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 | }); | ||
| @@ -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 | } | ||
| @@ -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 | }); | ||
| @@ -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 | } | ||
| @@ -5,16 +5,13 @@ import * as Layer from "effect/Layer"; · 4 unmodified lines | |||
| 5 | 5 | ||
| 6 | import { resolvePrimaryEnvironmentHttpUrl } from "./target"; | 6 | import { resolvePrimaryEnvironmentHttpUrl } from "./target"; |
| 7 | 7 | ||
| 8 | export type PrimaryEnvironmentHttpClientShape = Effect.Success< | ||
| 9 | ReturnType<typeof makeEnvironmentHttpApiClient> | ||
| 10 | >; | ||
| 11 | |||
| 12 | export class PrimaryEnvironmentHttpClient extends Context.Service< | 8 | export class PrimaryEnvironmentHttpClient extends Context.Service< |
| 13 | PrimaryEnvironmentHttpClient, | 9 | PrimaryEnvironmentHttpClient, |
| 14 | PrimaryEnvironmentHttpClientShape | 10 | Effect.Success<ReturnType<typeof makeEnvironmentHttpApiClient>> |
| 15 | >()("@t3tools/web/environments/primary/httpClient/PrimaryEnvironmentHttpClient") {} | 11 | >()("@t3tools/web/environments/primary/httpClient/PrimaryEnvironmentHttpClient") {} |
| 16 | 12 | ||
| 17 | export const primaryEnvironmentHttpClientLive = Layer.effect( | 13 | const make = Effect.suspend(() => |
| 18 | PrimaryEnvironmentHttpClient, | 14 | makeEnvironmentHttpApiClient(resolvePrimaryEnvironmentHttpUrl("/")), |
| 19 | Effect.suspend(() => makeEnvironmentHttpApiClient(resolvePrimaryEnvironmentHttpUrl("/"))), | ||
| 20 | ); | 15 | ); |
| 16 | |||
| 17 | export const layer = Layer.effect(PrimaryEnvironmentHttpClient, make); | ||
| @@ -689,7 +689,8 @@ label:has(> select#reasoning-effort) select { · 688 unmodified lines | |||
| 689 | background: color-mix(in srgb, var(--background) 94%, var(--card)); | 689 | background: color-mix(in srgb, var(--background) 94%, var(--card)); |
| 690 | } | 690 | } |
| 691 | 691 | ||
| 692 | .diff-render-file { | 692 | .diff-render-file, |
| 693 | .diff-render-surface > diffs-container { | ||
| 693 | border: 1px solid var(--border); | 694 | border: 1px solid var(--border); |
| 694 | border-radius: 0.5rem; | 695 | border-radius: 0.5rem; |
| 695 | overflow: clip; | 696 | overflow: clip; |
| @@ -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 | }); | ||
| @@ -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 | } | ||
| @@ -1,5 +1,5 @@ | |||
| 1 | import { describe, expect, it } from "vite-plus/test"; | 1 | import { describe, expect, it } from "vite-plus/test"; |
| 2 | import { buildPatchCacheKey } from "./diffRendering"; | 2 | import { buildPatchCacheKey, getRenderablePatch } from "./diffRendering"; |
| 3 | 3 | ||
| 4 | describe("buildPatchCacheKey", () => { | 4 | describe("buildPatchCacheKey", () => { |
| 5 | it("returns a stable cache key for identical content", () => { | 5 | it("returns a stable cache key for identical content", () => { |
| @@ -29,3 +29,56 @@ describe("buildPatchCacheKey", () => { · 23 unmodified lines | |||
| 29 | ); | 29 | ); |
| 30 | }); | 30 | }); |
| 31 | }); | 31 | }); |
| 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 | }); | ||
| @@ -52,9 +52,45 @@ export type RenderablePatch = · 51 unmodified lines | |||
| 52 | reason: string; | 52 | reason: string; |
| 53 | }; | 53 | }; |
| 54 | 54 | ||
| 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 | |||
| 55 | export function getRenderablePatch( | 90 | export function getRenderablePatch( |
| 56 | patch: string | undefined, | 91 | patch: string | undefined, |
| 57 | cacheScope = "diff-panel", | 92 | cacheScope = "diff-panel", |
| 93 | options: RenderablePatchOptions = {}, | ||
| 58 | ): RenderablePatch | null { | 94 | ): RenderablePatch | null { |
| 59 | if (!patch) return null; | 95 | if (!patch) return null; |
| 60 | const normalizedPatch = patch.trim(); | 96 | const normalizedPatch = patch.trim(); |
| @@ -65,7 +101,11 @@ export function getRenderablePatch( · 4 unmodified lines | |||
| 65 | normalizedPatch, | 101 | normalizedPatch, |
| 66 | buildPatchCacheKey(normalizedPatch, cacheScope), | 102 | buildPatchCacheKey(normalizedPatch, cacheScope), |
| 67 | ); | 103 | ); |
| 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 | ); | ||
| 69 | if (files.length > 0) { | 109 | if (files.length > 0) { |
| 70 | return { kind: "files", files }; | 110 | return { kind: "files", files }; |
| 71 | } | 111 | } |
| @@ -5,10 +5,7 @@ import * as Socket from "effect/unstable/socket/Socket"; · 4 unmodified lines | |||
| 5 | 5 | ||
| 6 | import { remoteHttpClientLayer } from "@t3tools/client-runtime/rpc"; | 6 | import { remoteHttpClientLayer } from "@t3tools/client-runtime/rpc"; |
| 7 | import { makeRelayClientTracingLayer } from "@t3tools/shared/relayTracing"; | 7 | import { makeRelayClientTracingLayer } from "@t3tools/shared/relayTracing"; |
| 8 | import { | 8 | import * as PrimaryEnvironmentHttpClient from "../environments/primary/httpClient"; |
| 9 | PrimaryEnvironmentHttpClient, | ||
| 10 | primaryEnvironmentHttpClientLive, | ||
| 11 | } from "../environments/primary/httpClient"; | ||
| 12 | import { primaryEnvironmentHttpLayer } from "../environments/primary/httpLayer"; | 9 | import { primaryEnvironmentHttpLayer } from "../environments/primary/httpLayer"; |
| 13 | 10 | ||
| 14 | import { browserCryptoLayer } from "../cloud/dpop"; | 11 | import { browserCryptoLayer } from "../cloud/dpop"; |
| @@ -30,11 +27,11 @@ const relayTracingLayer = makeRelayClientTracingLayer(resolveRelayTracingConfig( · 15 unmodified lines | |||
| 30 | export const remoteHttpRuntime = ManagedRuntime.make(httpClientLayer); | 27 | export const remoteHttpRuntime = ManagedRuntime.make(httpClientLayer); |
| 31 | 28 | ||
| 32 | const primaryHttpRuntime = ManagedRuntime.make( | 29 | const primaryHttpRuntime = ManagedRuntime.make( |
| 33 | primaryEnvironmentHttpClientLive.pipe(Layer.provide(primaryEnvironmentHttpLayer)), | 30 | PrimaryEnvironmentHttpClient.layer.pipe(Layer.provide(primaryEnvironmentHttpLayer)), |
| 34 | ); | 31 | ); |
| 35 | 32 | ||
| 36 | export type PrimaryHttpEffectRunner = <A, E>( | 33 | export type PrimaryHttpEffectRunner = <A, E>( |
| 37 | effect: Effect.Effect<A, E, PrimaryEnvironmentHttpClient>, | 34 | effect: Effect.Effect<A, E, PrimaryEnvironmentHttpClient.PrimaryEnvironmentHttpClient>, |
| 38 | ) => Promise<A>; | 35 | ) => Promise<A>; |
| 39 | 36 | ||
| 40 | const livePrimaryHttpRunner: PrimaryHttpEffectRunner = (effect) => | 37 | const livePrimaryHttpRunner: PrimaryHttpEffectRunner = (effect) => |
| @@ -42,8 +39,9 @@ const livePrimaryHttpRunner: PrimaryHttpEffectRunner = (effect) => · 1 unmodified line | |||
| 42 | 39 | ||
| 43 | let primaryHttpRunner = livePrimaryHttpRunner; | 40 | let primaryHttpRunner = livePrimaryHttpRunner; |
| 44 | 41 | ||
| 45 | export const runPrimaryHttp = <A, E>(effect: Effect.Effect<A, E, PrimaryEnvironmentHttpClient>) => | 42 | export const runPrimaryHttp = <A, E>( |
| 46 | primaryHttpRunner(effect); | 43 | effect: Effect.Effect<A, E, PrimaryEnvironmentHttpClient.PrimaryEnvironmentHttpClient>, |
| 44 | ) => primaryHttpRunner(effect); | ||
| 47 | 45 | ||
| 48 | export function __setPrimaryHttpRunnerForTests(runner?: PrimaryHttpEffectRunner): void { | 46 | export function __setPrimaryHttpRunnerForTests(runner?: PrimaryHttpEffectRunner): void { |
| 49 | primaryHttpRunner = runner ?? livePrimaryHttpRunner; | 47 | primaryHttpRunner = runner ?? livePrimaryHttpRunner; |
| @@ -6,7 +6,6 @@ import { · 5 unmodified lines | |||
| 6 | migratePersistedRightPanelState, | 6 | migratePersistedRightPanelState, |
| 7 | selectActiveRightPanel, | 7 | selectActiveRightPanel, |
| 8 | selectActiveRightPanelSurface, | 8 | selectActiveRightPanelSurface, |
| 9 | selectActiveRightPanelKindWithUrl, | ||
| 10 | selectThreadRightPanelState, | 9 | selectThreadRightPanelState, |
| 11 | useRightPanelStore, | 10 | useRightPanelStore, |
| 12 | } from "./rightPanelStore"; | 11 | } from "./rightPanelStore"; |
| @@ -254,16 +253,6 @@ describe("rightPanelStore", () => { · 241 unmodified lines | |||
| 254 | expect(selectActiveRightPanel(useRightPanelStore.getState().byThreadKey, refA)).toBe("plan"); | 253 | expect(selectActiveRightPanel(useRightPanelStore.getState().byThreadKey, refA)).toBe("plan"); |
| 255 | }); | 254 | }); |
| 256 | 255 | ||
| 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 | |||
| 267 | it("removeThread clears persisted state", () => { | 256 | it("removeThread clears persisted state", () => { |
| 268 | useRightPanelStore.getState().open(refA, "plan"); | 257 | useRightPanelStore.getState().open(refA, "plan"); |
| 269 | useRightPanelStore.getState().removeThread(refA); | 258 | useRightPanelStore.getState().removeThread(refA); |
| @@ -550,13 +550,3 @@ export function selectActiveRightPanelSurface( · 549 unmodified lines | |||
| 550 | if (!state.isOpen) return null; | 550 | if (!state.isOpen) return null; |
| 551 | return state.surfaces.find((surface) => surface.id === state.activeSurfaceId) ?? null; | 551 | return state.surfaces.find((surface) => surface.id === state.activeSurfaceId) ?? null; |
| 552 | } | 552 | } |
| 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 | } | ||
| @@ -1,10 +1,9 @@ | |||
| 1 | import { createFileRoute, retainSearchParams, useNavigate } from "@tanstack/react-router"; | 1 | import { createFileRoute, useNavigate } from "@tanstack/react-router"; |
| 2 | import { useEffect } from "react"; | 2 | import { useEffect } from "react"; |
| 3 | 3 | ||
| 4 | import ChatView from "../components/ChatView"; | 4 | import ChatView from "../components/ChatView"; |
| 5 | import { threadHasStarted } from "../components/ChatView.logic"; | 5 | import { threadHasStarted } from "../components/ChatView.logic"; |
| 6 | import { finalizePromotedDraftThreadByRef, useComposerDraftStore } from "../composerDraftStore"; | 6 | import { finalizePromotedDraftThreadByRef, useComposerDraftStore } from "../composerDraftStore"; |
| 7 | import { type DiffRouteSearch, parseDiffRouteSearch } from "../diffRouteSearch"; | ||
| 8 | import { resolveThreadRouteRef } from "../threadRoutes"; | 7 | import { resolveThreadRouteRef } from "../threadRoutes"; |
| 9 | import { SidebarInset } from "~/components/ui/sidebar"; | 8 | import { SidebarInset } from "~/components/ui/sidebar"; |
| 10 | import { useEnvironmentThreadRefs, useThreadDetail, useThreadShell } from "../state/entities"; | 9 | import { useEnvironmentThreadRefs, useThreadDetail, useThreadShell } from "../state/entities"; |
| @@ -74,9 +73,5 @@ function ChatThreadRouteView() { · 63 unmodified lines | |||
| 74 | } | 73 | } |
| 75 | 74 | ||
| 76 | export const Route = createFileRoute("/_chat/$environmentId/$threadId")({ | 75 | export const Route = createFileRoute("/_chat/$environmentId/$threadId")({ |
| 77 | validateSearch: (search) => parseDiffRouteSearch(search), | ||
| 78 | search: { | ||
| 79 | middlewares: [retainSearchParams<DiffRouteSearch>(["diff"])], | ||
| 80 | }, | ||
| 81 | component: ChatThreadRouteView, | 76 | component: ChatThreadRouteView, |
| 82 | }); | 77 | }); |
| @@ -7,7 +7,7 @@ import * as Effect from "effect/Effect"; · 6 unmodified lines | |||
| 7 | import * as Layer from "effect/Layer"; | 7 | import * as Layer from "effect/Layer"; |
| 8 | import * as Planetscale from "alchemy/Planetscale"; | 8 | import * as Planetscale from "alchemy/Planetscale"; |
| 9 | 9 | ||
| 10 | import { PlanetscaleDatabase, RelayHyperdrive } from "./src/db.ts"; | 10 | import * as RelayDb from "./src/db.ts"; |
| 11 | import { RelayObservability } from "./src/observability.ts"; | 11 | import { RelayObservability } from "./src/observability.ts"; |
| 12 | import { ManagedEndpointZone, RelayApiZone } from "./src/zone.ts"; | 12 | import { ManagedEndpointZone, RelayApiZone } from "./src/zone.ts"; |
| 13 | import Api from "./src/worker.ts"; | 13 | import Api from "./src/worker.ts"; |
| @@ -24,8 +24,8 @@ export default Alchemy.Stack( · 10 unmodified lines | |||
| 24 | state: Cloudflare.state(), | 24 | state: Cloudflare.state(), |
| 25 | }, | 25 | }, |
| 26 | Effect.gen(function* () { | 26 | Effect.gen(function* () { |
| 27 | const db = yield* PlanetscaleDatabase; | 27 | const db = yield* RelayDb.PlanetscaleDatabase; |
| 28 | const hyperdrive = yield* RelayHyperdrive; | 28 | const hyperdrive = yield* RelayDb.RelayHyperdrive; |
| 29 | const managedEndpointZone = yield* ManagedEndpointZone.pipe(Effect.orDie); | 29 | const managedEndpointZone = yield* ManagedEndpointZone.pipe(Effect.orDie); |
| 30 | const relayApiZone = yield* RelayApiZone.pipe(Effect.orDie); | 30 | const relayApiZone = yield* RelayApiZone.pipe(Effect.orDie); |
| 31 | const observability = yield* RelayObservability; | 31 | const observability = yield* RelayObservability; |
| @@ -1,4 +1,5 @@ | |||
| 1 | import * as Context from "effect/Context"; | 1 | import * as Context from "effect/Context"; |
| 2 | import * as Layer from "effect/Layer"; | ||
| 2 | import * as Redacted from "effect/Redacted"; | 3 | import * as Redacted from "effect/Redacted"; |
| 3 | import * as Schema from "effect/Schema"; | 4 | import * as Schema from "effect/Schema"; |
| 4 | 5 | ||
| @@ -13,20 +14,24 @@ export interface ApnsCredentials { · 8 unmodified lines | |||
| 13 | readonly environment: ApnsEnvironment; | 14 | readonly environment: ApnsEnvironment; |
| 14 | } | 15 | } |
| 15 | 16 | ||
| 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 | |||
| 29 | export class RelayConfiguration extends Context.Service< | 17 | export class RelayConfiguration extends Context.Service< |
| 30 | RelayConfiguration, | 18 | 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 | } | ||
| 32 | >()("t3code-relay/Config/RelayConfiguration") {} | 31 | >()("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)); | ||
| @@ -66,8 +66,8 @@ function makeAgentActivityRows( · 65 unmodified lines | |||
| 66 | } | 66 | } |
| 67 | 67 | ||
| 68 | function makeEnvironmentLinks( | 68 | function makeEnvironmentLinks( |
| 69 | overrides: Partial<EnvironmentLinks.EnvironmentLinksShape> = {}, | 69 | overrides: Partial<EnvironmentLinks.EnvironmentLinks["Service"]> = {}, |
| 70 | ): EnvironmentLinks.EnvironmentLinksShape { | 70 | ): EnvironmentLinks.EnvironmentLinks["Service"] { |
| 71 | return { | 71 | return { |
| 72 | upsert: () => Effect.void, | 72 | upsert: () => Effect.void, |
| 73 | listUsersForEnvironment: () => Effect.succeed(["dev:julius"]), | 73 | listUsersForEnvironment: () => Effect.succeed(["dev:julius"]), |
| @@ -9,7 +9,7 @@ import * as Option from "effect/Option"; · 8 unmodified lines | |||
| 9 | import * as Schema from "effect/Schema"; | 9 | import * as Schema from "effect/Schema"; |
| 10 | import { and, desc, eq, isNull } from "drizzle-orm"; | 10 | import { and, desc, eq, isNull } from "drizzle-orm"; |
| 11 | 11 | ||
| 12 | import { RelayDb } from "../db.ts"; | 12 | import * as RelayDb from "../db.ts"; |
| 13 | import { relayAgentActivityRows, relayEnvironmentLinks } from "../persistence/schema.ts"; | 13 | import { relayAgentActivityRows, relayEnvironmentLinks } from "../persistence/schema.ts"; |
| 14 | 14 | ||
| 15 | export class AgentActivityRowUpsertPersistenceError extends Schema.TaggedErrorClass<AgentActivityRowUpsertPersistenceError>()( | 15 | export class AgentActivityRowUpsertPersistenceError extends Schema.TaggedErrorClass<AgentActivityRowUpsertPersistenceError>()( |
| @@ -70,7 +70,7 @@ const decodeRelayAgentActivityStateJson = Schema.decodeUnknownOption( · 54 unmodified lines | |||
| 70 | ); | 70 | ); |
| 71 | 71 | ||
| 72 | const make = Effect.gen(function* () { | 72 | const make = Effect.gen(function* () { |
| 73 | const db = yield* RelayDb; | 73 | const db = yield* RelayDb.RelayDb; |
| 74 | 74 | ||
| 75 | return AgentActivityRows.of({ | 75 | return AgentActivityRows.of({ |
| 76 | upsert: Effect.fn("relay.agent_activity_rows.upsert")( | 76 | upsert: Effect.fn("relay.agent_activity_rows.upsert")( |
| @@ -9,7 +9,7 @@ import * as Option from "effect/Option"; · 8 unmodified lines | |||
| 9 | import * as Redacted from "effect/Redacted"; | 9 | import * as Redacted from "effect/Redacted"; |
| 10 | import * as Schema from "effect/Schema"; | 10 | import * as Schema from "effect/Schema"; |
| 11 | import { Headers, HttpClient, HttpClientRequest } from "effect/unstable/http"; | 11 | import { Headers, HttpClient, HttpClientRequest } from "effect/unstable/http"; |
| 12 | import type { ApnsCredentials } from "../Config.ts"; | 12 | import * as RelayConfiguration from "../Config.ts"; |
| 13 | import type { ApnsNotificationPayload } from "./apnsDeliveryJobs.ts"; | 13 | import type { ApnsNotificationPayload } from "./apnsDeliveryJobs.ts"; |
| 14 | 14 | ||
| 15 | const LIVE_ACTIVITY_NAME = "AgentActivity"; | 15 | const LIVE_ACTIVITY_NAME = "AgentActivity"; |
| @@ -99,9 +99,9 @@ const encodeApnsJwtPayloadJson = Schema.encodeEffect( · 83 unmodified lines | |||
| 99 | ); | 99 | ); |
| 100 | 100 | ||
| 101 | const makeApnsJwt = Effect.fn("relay.apns.make_jwt")(function* (input: { | 101 | const makeApnsJwt = Effect.fn("relay.apns.make_jwt")(function* (input: { |
| 102 | readonly teamId: ApnsCredentials["teamId"]; | 102 | readonly teamId: RelayConfiguration.ApnsCredentials["teamId"]; |
| 103 | readonly keyId: ApnsCredentials["keyId"]; | 103 | readonly keyId: RelayConfiguration.ApnsCredentials["keyId"]; |
| 104 | readonly privateKey: ApnsCredentials["privateKey"]; | 104 | readonly privateKey: RelayConfiguration.ApnsCredentials["privateKey"]; |
| 105 | readonly issuedAtUnixSeconds: number; | 105 | readonly issuedAtUnixSeconds: number; |
| 106 | }) { | 106 | }) { |
| 107 | const headerJson = yield* encodeApnsJwtHeaderJson({ alg: "ES256", kid: input.keyId }).pipe( | 107 | const headerJson = yield* encodeApnsJwtHeaderJson({ alg: "ES256", kid: input.keyId }).pipe( |
| @@ -235,12 +235,12 @@ export interface ApnsClientShape { · 127 unmodified lines | |||
| 235 | readonly makeLiveActivityRequest: typeof makeLiveActivityRequest; | 235 | readonly makeLiveActivityRequest: typeof makeLiveActivityRequest; |
| 236 | readonly makePushNotificationRequest: typeof makePushNotificationRequest; | 236 | readonly makePushNotificationRequest: typeof makePushNotificationRequest; |
| 237 | readonly sendLiveActivityRequest: (input: { | 237 | readonly sendLiveActivityRequest: (input: { |
| 238 | readonly credentials: ApnsCredentials; | 238 | readonly credentials: RelayConfiguration.ApnsCredentials; |
| 239 | readonly request: ApnsLiveActivityRequest; | 239 | readonly request: ApnsLiveActivityRequest; |
| 240 | readonly issuedAtUnixSeconds: number; | 240 | readonly issuedAtUnixSeconds: number; |
| 241 | }) => Effect.Effect<ApnsDeliveryResult, ApnsError>; | 241 | }) => Effect.Effect<ApnsDeliveryResult, ApnsError>; |
| 242 | readonly sendPushNotificationRequest: (input: { | 242 | readonly sendPushNotificationRequest: (input: { |
| 243 | readonly credentials: ApnsCredentials; | 243 | readonly credentials: RelayConfiguration.ApnsCredentials; |
| 244 | readonly request: ApnsPushNotificationRequest; | 244 | readonly request: ApnsPushNotificationRequest; |
| 245 | readonly issuedAtUnixSeconds: number; | 245 | readonly issuedAtUnixSeconds: number; |
| 246 | }) => Effect.Effect<ApnsDeliveryResult, ApnsError>; | 246 | }) => Effect.Effect<ApnsDeliveryResult, ApnsError>; |
| @@ -153,7 +153,7 @@ function makeLayer(input: { · 152 unmodified lines | |||
| 153 | Parameters<LiveActivities.LiveActivitiesShape["invalidateDeliveryToken"]>[0] | 153 | Parameters<LiveActivities.LiveActivitiesShape["invalidateDeliveryToken"]>[0] |
| 154 | >; | 154 | >; |
| 155 | readonly currentTargets?: ReadonlyArray<LiveActivities.TargetRow>; | 155 | readonly currentTargets?: ReadonlyArray<LiveActivities.TargetRow>; |
| 156 | readonly config?: RelayConfiguration.RelayConfigurationShape; | 156 | readonly config?: RelayConfiguration.RelayConfiguration["Service"]; |
| 157 | readonly execute?: ( | 157 | readonly execute?: ( |
| 158 | request: HttpClientRequest.HttpClientRequest, | 158 | request: HttpClientRequest.HttpClientRequest, |
| 159 | ) => Effect.Effect<HttpClientResponse.HttpClientResponse>; | 159 | ) => Effect.Effect<HttpClientResponse.HttpClientResponse>; |
| @@ -213,7 +213,7 @@ function makeLayer(input: { · 53 unmodified lines | |||
| 213 | input.invalidatedTokens?.push(invalidated); | 213 | input.invalidatedTokens?.push(invalidated); |
| 214 | }), | 214 | }), |
| 215 | }), | 215 | }), |
| 216 | Layer.succeed(RelayConfiguration.RelayConfiguration, input.config ?? config), | 216 | RelayConfiguration.layer(input.config ?? config), |
| 217 | input.execute | 217 | input.execute |
| 218 | ? Layer.succeed(HttpClient.HttpClient, HttpClient.make(input.execute)) | 218 | ? Layer.succeed(HttpClient.HttpClient, HttpClient.make(input.execute)) |
| 219 | : FetchHttpClient.layer, | 219 | : FetchHttpClient.layer, |
| @@ -3,7 +3,7 @@ import { describe, expect, it } from "@effect/vitest"; · 2 unmodified lines | |||
| 3 | import * as Effect from "effect/Effect"; | 3 | import * as Effect from "effect/Effect"; |
| 4 | import * as Layer from "effect/Layer"; | 4 | import * as Layer from "effect/Layer"; |
| 5 | 5 | ||
| 6 | import { RelayDb, type RelayDatabase } from "../db.ts"; | 6 | import * as RelayDb from "../db.ts"; |
| 7 | import { relayDeliveryAttempts } from "../persistence/schema.ts"; | 7 | import { relayDeliveryAttempts } from "../persistence/schema.ts"; |
| 8 | import * as DeliveryAttempts from "./DeliveryAttempts.ts"; | 8 | import * as DeliveryAttempts from "./DeliveryAttempts.ts"; |
| 9 | 9 | ||
| @@ -20,7 +20,7 @@ describe("DeliveryAttempts", () => { · 10 unmodified lines | |||
| 20 | }, | 20 | }, |
| 21 | }; | 21 | }; |
| 22 | }, | 22 | }, |
| 23 | } as unknown as RelayDatabase; | 23 | } as unknown as RelayDb.RelayDb["Service"]; |
| 24 | 24 | ||
| 25 | return Effect.gen(function* () { | 25 | return Effect.gen(function* () { |
| 26 | const attempts = yield* DeliveryAttempts.DeliveryAttempts; | 26 | const attempts = yield* DeliveryAttempts.DeliveryAttempts; |
| @@ -52,7 +52,7 @@ describe("DeliveryAttempts", () => { · 25 unmodified lines | |||
| 52 | Effect.provide( | 52 | Effect.provide( |
| 53 | DeliveryAttempts.layer.pipe( | 53 | DeliveryAttempts.layer.pipe( |
| 54 | Layer.provide(NodeCryptoLayer.layer), | 54 | Layer.provide(NodeCryptoLayer.layer), |
| 55 | Layer.provide(Layer.succeed(RelayDb, fakeDb)), | 55 | Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)), |
| 56 | ), | 56 | ), |
| 57 | ), | 57 | ), |
| 58 | ); | 58 | ); |
| @@ -78,7 +78,7 @@ describe("DeliveryAttempts", () => { · 19 unmodified lines | |||
| 78 | }, | 78 | }, |
| 79 | }; | 79 | }; |
| 80 | }, | 80 | }, |
| 81 | } as unknown as RelayDatabase; | 81 | } as unknown as RelayDb.RelayDb["Service"]; |
| 82 | 82 | ||
| 83 | return Effect.gen(function* () { | 83 | return Effect.gen(function* () { |
| 84 | const attempts = yield* DeliveryAttempts.DeliveryAttempts; | 84 | const attempts = yield* DeliveryAttempts.DeliveryAttempts; |
| @@ -104,7 +104,7 @@ describe("DeliveryAttempts", () => { · 19 unmodified lines | |||
| 104 | Effect.provide( | 104 | Effect.provide( |
| 105 | DeliveryAttempts.layer.pipe( | 105 | DeliveryAttempts.layer.pipe( |
| 106 | Layer.provide(NodeCryptoLayer.layer), | 106 | Layer.provide(NodeCryptoLayer.layer), |
| 107 | Layer.provide(Layer.succeed(RelayDb, fakeDb)), | 107 | Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)), |
| 108 | ), | 108 | ), |
| 109 | ), | 109 | ), |
| 110 | ); | 110 | ); |
| @@ -135,7 +135,7 @@ describe("DeliveryAttempts", () => { · 24 unmodified lines | |||
| 135 | }), | 135 | }), |
| 136 | }), | 136 | }), |
| 137 | }), | 137 | }), |
| 138 | } as unknown as RelayDatabase; | 138 | } as unknown as RelayDb.RelayDb["Service"]; |
| 139 | 139 | ||
| 140 | return Effect.gen(function* () { | 140 | return Effect.gen(function* () { |
| 141 | const attempts = yield* DeliveryAttempts.DeliveryAttempts; | 141 | const attempts = yield* DeliveryAttempts.DeliveryAttempts; |
| @@ -154,7 +154,7 @@ describe("DeliveryAttempts", () => { · 12 unmodified lines | |||
| 154 | Effect.provide( | 154 | Effect.provide( |
| 155 | DeliveryAttempts.layer.pipe( | 155 | DeliveryAttempts.layer.pipe( |
| 156 | Layer.provide(NodeCryptoLayer.layer), | 156 | Layer.provide(NodeCryptoLayer.layer), |
| 157 | Layer.provide(Layer.succeed(RelayDb, fakeDb)), | 157 | Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)), |
| 158 | ), | 158 | ), |
| 159 | ), | 159 | ), |
| 160 | ); | 160 | ); |
| @@ -185,7 +185,7 @@ describe("DeliveryAttempts", () => { · 24 unmodified lines | |||
| 185 | }), | 185 | }), |
| 186 | }), | 186 | }), |
| 187 | }), | 187 | }), |
| 188 | } as unknown as RelayDatabase; | 188 | } as unknown as RelayDb.RelayDb["Service"]; |
| 189 | 189 | ||
| 190 | return Effect.gen(function* () { | 190 | return Effect.gen(function* () { |
| 191 | const attempts = yield* DeliveryAttempts.DeliveryAttempts; | 191 | const attempts = yield* DeliveryAttempts.DeliveryAttempts; |
| @@ -204,7 +204,7 @@ describe("DeliveryAttempts", () => { · 12 unmodified lines | |||
| 204 | Effect.provide( | 204 | Effect.provide( |
| 205 | DeliveryAttempts.layer.pipe( | 205 | DeliveryAttempts.layer.pipe( |
| 206 | Layer.provide(NodeCryptoLayer.layer), | 206 | Layer.provide(NodeCryptoLayer.layer), |
| 207 | Layer.provide(Layer.succeed(RelayDb, fakeDb)), | 207 | Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)), |
| 208 | ), | 208 | ), |
| 209 | ), | 209 | ), |
| 210 | ); | 210 | ); |
| @@ -246,7 +246,7 @@ describe("DeliveryAttempts", () => { · 35 unmodified lines | |||
| 246 | }; | 246 | }; |
| 247 | }, | 247 | }, |
| 248 | }), | 248 | }), |
| 249 | } as unknown as RelayDatabase; | 249 | } as unknown as RelayDb.RelayDb["Service"]; |
| 250 | 250 | ||
| 251 | return Effect.gen(function* () { | 251 | return Effect.gen(function* () { |
| 252 | const attempts = yield* DeliveryAttempts.DeliveryAttempts; | 252 | const attempts = yield* DeliveryAttempts.DeliveryAttempts; |
| @@ -266,7 +266,7 @@ describe("DeliveryAttempts", () => { · 13 unmodified lines | |||
| 266 | Effect.provide( | 266 | Effect.provide( |
| 267 | DeliveryAttempts.layer.pipe( | 267 | DeliveryAttempts.layer.pipe( |
| 268 | Layer.provide(NodeCryptoLayer.layer), | 268 | Layer.provide(NodeCryptoLayer.layer), |
| 269 | Layer.provide(Layer.succeed(RelayDb, fakeDb)), | 269 | Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)), |
| 270 | ), | 270 | ), |
| 271 | ), | 271 | ), |
| 272 | ); | 272 | ); |
| @@ -290,7 +290,7 @@ describe("DeliveryAttempts", () => { · 17 unmodified lines | |||
| 290 | }, | 290 | }, |
| 291 | }; | 291 | }; |
| 292 | }, | 292 | }, |
| 293 | } as unknown as RelayDatabase; | 293 | } as unknown as RelayDb.RelayDb["Service"]; |
| 294 | 294 | ||
| 295 | return Effect.gen(function* () { | 295 | return Effect.gen(function* () { |
| 296 | const attempts = yield* DeliveryAttempts.DeliveryAttempts; | 296 | const attempts = yield* DeliveryAttempts.DeliveryAttempts; |
| @@ -315,7 +315,7 @@ describe("DeliveryAttempts", () => { · 18 unmodified lines | |||
| 315 | Effect.provide( | 315 | Effect.provide( |
| 316 | DeliveryAttempts.layer.pipe( | 316 | DeliveryAttempts.layer.pipe( |
| 317 | Layer.provide(NodeCryptoLayer.layer), | 317 | Layer.provide(NodeCryptoLayer.layer), |
| 318 | Layer.provide(Layer.succeed(RelayDb, fakeDb)), | 318 | Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)), |
| 319 | ), | 319 | ), |
| 320 | ), | 320 | ), |
| 321 | ); | 321 | ); |
| @@ -7,7 +7,7 @@ import { and, eq, isNull } from "drizzle-orm"; · 6 unmodified lines | |||
| 7 | import * as Crypto from "effect/Crypto"; | 7 | import * as Crypto from "effect/Crypto"; |
| 8 | import * as Schema from "effect/Schema"; | 8 | import * as Schema from "effect/Schema"; |
| 9 | 9 | ||
| 10 | import { RelayDb } from "../db.ts"; | 10 | import * as RelayDb from "../db.ts"; |
| 11 | import { relayDeliveryAttempts } from "../persistence/schema.ts"; | 11 | import { relayDeliveryAttempts } from "../persistence/schema.ts"; |
| 12 | 12 | ||
| 13 | export class DeliveryAttemptRecordPersistenceError extends Schema.TaggedErrorClass<DeliveryAttemptRecordPersistenceError>()( | 13 | export class DeliveryAttemptRecordPersistenceError extends Schema.TaggedErrorClass<DeliveryAttemptRecordPersistenceError>()( |
| @@ -84,7 +84,7 @@ function insertValues( · 70 unmodified lines | |||
| 84 | } | 84 | } |
| 85 | 85 | ||
| 86 | const make = Effect.gen(function* () { | 86 | const make = Effect.gen(function* () { |
| 87 | const db = yield* RelayDb; | 87 | const db = yield* RelayDb.RelayDb; |
| 88 | const crypto = yield* Crypto.Crypto; | 88 | const crypto = yield* Crypto.Crypto; |
| 89 | 89 | ||
| 90 | const isExpiredClaim = (claimedAt: string | null, now: DateTime.DateTime) => { | 90 | const isExpiredClaim = (claimedAt: string | null, now: DateTime.DateTime) => { |
| @@ -5,7 +5,7 @@ import { PgDialect } from "drizzle-orm/pg-core"; · 4 unmodified lines | |||
| 5 | import * as Effect from "effect/Effect"; | 5 | import * as Effect from "effect/Effect"; |
| 6 | import * as Layer from "effect/Layer"; | 6 | import * as Layer from "effect/Layer"; |
| 7 | 7 | ||
| 8 | import { RelayDb, type RelayDatabase } from "../db.ts"; | 8 | import * as RelayDb from "../db.ts"; |
| 9 | import { relayLiveActivities, relayMobileDevices } from "../persistence/schema.ts"; | 9 | import { relayLiveActivities, relayMobileDevices } from "../persistence/schema.ts"; |
| 10 | import * as Devices from "./Devices.ts"; | 10 | import * as Devices from "./Devices.ts"; |
| 11 | 11 | ||
| @@ -71,7 +71,7 @@ describe("Devices", () => { · 59 unmodified lines | |||
| 71 | }, | 71 | }, |
| 72 | }; | 72 | }; |
| 73 | }, | 73 | }, |
| 74 | } as unknown as RelayDatabase; | 74 | } as unknown as RelayDb.RelayDb["Service"]; |
| 75 | 75 | ||
| 76 | return Effect.gen(function* () { | 76 | return Effect.gen(function* () { |
| 77 | const devices = yield* Devices.Devices; | 77 | const devices = yield* Devices.Devices; |
| @@ -110,7 +110,9 @@ describe("Devices", () => { · 32 unmodified lines | |||
| 110 | pushToStartToken: "push-to-start-token", | 110 | pushToStartToken: "push-to-start-token", |
| 111 | }), | 111 | }), |
| 112 | ]); | 112 | ]); |
| 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 | ); | ||
| 114 | }); | 116 | }); |
| 115 | 117 | ||
| 116 | it.effect("unregisters APNs state only for the current user device", () => { | 118 | it.effect("unregisters APNs state only for the current user device", () => { |
| @@ -130,7 +132,7 @@ describe("Devices", () => { · 13 unmodified lines | |||
| 130 | }, | 132 | }, |
| 131 | }; | 133 | }; |
| 132 | }, | 134 | }, |
| 133 | } as unknown as RelayDatabase; | 135 | } as unknown as RelayDb.RelayDb["Service"]; |
| 134 | 136 | ||
| 135 | return Effect.gen(function* () { | 137 | return Effect.gen(function* () { |
| 136 | const devices = yield* Devices.Devices; | 138 | const devices = yield* Devices.Devices; |
| @@ -156,7 +158,9 @@ describe("Devices", () => { · 19 unmodified lines | |||
| 156 | params: ["user-2", "device-1"], | 158 | params: ["user-2", "device-1"], |
| 157 | }, | 159 | }, |
| 158 | ]); | 160 | ]); |
| 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 | ); | ||
| 160 | }); | 164 | }); |
| 161 | 165 | ||
| 162 | it.effect("lists safe notification state without exposing APNs tokens", () => { | 166 | it.effect("lists safe notification state without exposing APNs tokens", () => { |
| @@ -184,7 +188,7 @@ describe("Devices", () => { · 21 unmodified lines | |||
| 184 | }; | 188 | }; |
| 185 | }, | 189 | }, |
| 186 | }), | 190 | }), |
| 187 | } as unknown as RelayDatabase; | 191 | } as unknown as RelayDb.RelayDb["Service"]; |
| 188 | 192 | ||
| 189 | return Effect.gen(function* () { | 193 | return Effect.gen(function* () { |
| 190 | const devices = yield* Devices.Devices; | 194 | const devices = yield* Devices.Devices; |
| @@ -215,6 +219,8 @@ describe("Devices", () => { · 24 unmodified lines | |||
| 215 | updatedAt: "2026-06-01T00:00:00.000Z", | 219 | updatedAt: "2026-06-01T00:00:00.000Z", |
| 216 | }, | 220 | }, |
| 217 | ]); | 221 | ]); |
| 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 | ); | ||
| 219 | }); | 225 | }); |
| 220 | }); | 226 | }); |
| @@ -10,7 +10,7 @@ import * as Schema from "effect/Schema"; · 9 unmodified lines | |||
| 10 | import { and, eq } from "drizzle-orm"; | 10 | import { and, eq } from "drizzle-orm"; |
| 11 | import { sql } from "drizzle-orm"; | 11 | import { sql } from "drizzle-orm"; |
| 12 | 12 | ||
| 13 | import { RelayDb } from "../db.ts"; | 13 | import * as RelayDb from "../db.ts"; |
| 14 | import { relayLiveActivities, relayMobileDevices } from "../persistence/schema.ts"; | 14 | import { relayLiveActivities, relayMobileDevices } from "../persistence/schema.ts"; |
| 15 | 15 | ||
| 16 | export class DeviceRegistrationPersistenceError extends Schema.TaggedErrorClass<DeviceRegistrationPersistenceError>()( | 16 | export class DeviceRegistrationPersistenceError extends Schema.TaggedErrorClass<DeviceRegistrationPersistenceError>()( |
| @@ -59,7 +59,7 @@ export class Devices extends Context.Service<Devices, DevicesShape>()( · 42 unmodified lines | |||
| 59 | ) {} | 59 | ) {} |
| 60 | 60 | ||
| 61 | const make = Effect.gen(function* () { | 61 | const make = Effect.gen(function* () { |
| 62 | const db = yield* RelayDb; | 62 | const db = yield* RelayDb.RelayDb; |
| 63 | 63 | ||
| 64 | return Devices.of({ | 64 | return Devices.of({ |
| 65 | register: Effect.fn("relay.devices.register")( | 65 | register: Effect.fn("relay.devices.register")( |
| @@ -8,7 +8,7 @@ import { PgDialect } from "drizzle-orm/pg-core"; · 7 unmodified lines | |||
| 8 | import * as Effect from "effect/Effect"; | 8 | import * as Effect from "effect/Effect"; |
| 9 | import * as Layer from "effect/Layer"; | 9 | import * as Layer from "effect/Layer"; |
| 10 | 10 | ||
| 11 | import { RelayDb, type RelayDatabase } from "../db.ts"; | 11 | import * as RelayDb from "../db.ts"; |
| 12 | import { relayLiveActivities } from "../persistence/schema.ts"; | 12 | import { relayLiveActivities } from "../persistence/schema.ts"; |
| 13 | import * as LiveActivities from "./LiveActivities.ts"; | 13 | import * as LiveActivities from "./LiveActivities.ts"; |
| 14 | 14 | ||
| @@ -88,7 +88,7 @@ describe("LiveActivities", () => { · 73 unmodified lines | |||
| 88 | }, | 88 | }, |
| 89 | }; | 89 | }; |
| 90 | }, | 90 | }, |
| 91 | } as unknown as RelayDatabase; | 91 | } as unknown as RelayDb.RelayDb["Service"]; |
| 92 | 92 | ||
| 93 | return Effect.gen(function* () { | 93 | return Effect.gen(function* () { |
| 94 | const liveActivities = yield* LiveActivities.LiveActivities; | 94 | const liveActivities = yield* LiveActivities.LiveActivities; |
| @@ -138,7 +138,9 @@ describe("LiveActivities", () => { · 43 unmodified lines | |||
| 138 | }), | 138 | }), |
| 139 | ); | 139 | ); |
| 140 | }).pipe( | 140 | }).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 | ), | ||
| 142 | ); | 144 | ); |
| 143 | }, | 145 | }, |
| 144 | ); | 146 | ); |
| @@ -164,7 +166,7 @@ describe("LiveActivities", () => { · 19 unmodified lines | |||
| 164 | }, | 166 | }, |
| 165 | }; | 167 | }; |
| 166 | }, | 168 | }, |
| 167 | } as unknown as RelayDatabase; | 169 | } as unknown as RelayDb.RelayDb["Service"]; |
| 168 | 170 | ||
| 169 | return Effect.gen(function* () { | 171 | return Effect.gen(function* () { |
| 170 | const liveActivities = yield* LiveActivities.LiveActivities; | 172 | const liveActivities = yield* LiveActivities.LiveActivities; |
| @@ -190,7 +192,9 @@ describe("LiveActivities", () => { · 19 unmodified lines | |||
| 190 | }), | 192 | }), |
| 191 | ); | 193 | ); |
| 192 | }).pipe( | 194 | }).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 | ), | ||
| 194 | ); | 198 | ); |
| 195 | }); | 199 | }); |
| 196 | }); | 200 | }); |
| @@ -12,7 +12,7 @@ import * as Layer from "effect/Layer"; · 11 unmodified lines | |||
| 12 | import * as Schema from "effect/Schema"; | 12 | import * as Schema from "effect/Schema"; |
| 13 | import { and, eq, sql } from "drizzle-orm"; | 13 | import { and, eq, sql } from "drizzle-orm"; |
| 14 | 14 | ||
| 15 | import { RelayDb } from "../db.ts"; | 15 | import * as RelayDb from "../db.ts"; |
| 16 | import { relayLiveActivities, relayMobileDevices } from "../persistence/schema.ts"; | 16 | import { relayLiveActivities, relayMobileDevices } from "../persistence/schema.ts"; |
| 17 | 17 | ||
| 18 | export class LiveActivityRegistrationPersistenceError extends Schema.TaggedErrorClass<LiveActivityRegistrationPersistenceError>()( | 18 | export class LiveActivityRegistrationPersistenceError extends Schema.TaggedErrorClass<LiveActivityRegistrationPersistenceError>()( |
| @@ -108,7 +108,7 @@ const encodeRelayAgentActivityAggregateStateJson = Schema.encodeEffect( · 89 unmodified lines | |||
| 108 | ); | 108 | ); |
| 109 | 109 | ||
| 110 | const make = Effect.gen(function* () { | 110 | const make = Effect.gen(function* () { |
| 111 | const db = yield* RelayDb; | 111 | const db = yield* RelayDb.RelayDb; |
| 112 | 112 | ||
| 113 | return LiveActivities.of({ | 113 | return LiveActivities.of({ |
| 114 | register: Effect.fn("relay.live_activities.register")( | 114 | register: Effect.fn("relay.live_activities.register")( |
| @@ -86,8 +86,8 @@ function makeAgentActivityRows( · 85 unmodified lines | |||
| 86 | } | 86 | } |
| 87 | 87 | ||
| 88 | function makeEnvironmentLinks( | 88 | function makeEnvironmentLinks( |
| 89 | overrides: Partial<EnvironmentLinks.EnvironmentLinksShape> = {}, | 89 | overrides: Partial<EnvironmentLinks.EnvironmentLinks["Service"]> = {}, |
| 90 | ): EnvironmentLinks.EnvironmentLinksShape { | 90 | ): EnvironmentLinks.EnvironmentLinks["Service"] { |
| 91 | return { | 91 | return { |
| 92 | upsert: () => Effect.void, | 92 | upsert: () => Effect.void, |
| 93 | listUsersForEnvironment: () => Effect.succeed(["dev:julius"]), | 93 | listUsersForEnvironment: () => Effect.succeed(["dev:julius"]), |
| @@ -153,7 +153,7 @@ function makeRegistrationReplayLayer(input: { · 59 unmodified lines | |||
| 153 | Layer.succeed(EnvironmentLinks.EnvironmentLinks, makeEnvironmentLinks()), | 153 | Layer.succeed(EnvironmentLinks.EnvironmentLinks, makeEnvironmentLinks()), |
| 154 | Layer.succeed(LiveActivities.LiveActivities, input.liveActivities), | 154 | Layer.succeed(LiveActivities.LiveActivities, input.liveActivities), |
| 155 | Layer.succeed(DeliveryAttempts.DeliveryAttempts, makeDeliveryAttempts()), | 155 | Layer.succeed(DeliveryAttempts.DeliveryAttempts, makeDeliveryAttempts()), |
| 156 | Layer.succeed(RelayConfiguration.RelayConfiguration, config), | 156 | RelayConfiguration.layer(config), |
| 157 | Layer.succeed(ApnsDeliveryQueue.ApnsDeliveryQueueSender, { | 157 | Layer.succeed(ApnsDeliveryQueue.ApnsDeliveryQueueSender, { |
| 158 | send: (body) => | 158 | send: (body) => |
| 159 | Effect.sync(() => { | 159 | Effect.sync(() => { |
| @@ -4,7 +4,7 @@ import * as Effect from "effect/Effect"; · 3 unmodified lines | |||
| 4 | import * as Layer from "effect/Layer"; | 4 | import * as Layer from "effect/Layer"; |
| 5 | import * as Option from "effect/Option"; | 5 | import * as Option from "effect/Option"; |
| 6 | 6 | ||
| 7 | import { RelayDb, type RelayDatabase } from "../db.ts"; | 7 | import * as RelayDb from "../db.ts"; |
| 8 | import { relayDpopProofs } from "../persistence/schema.ts"; | 8 | import { relayDpopProofs } from "../persistence/schema.ts"; |
| 9 | import * as DpopProofs from "./DpopProofs.ts"; | 9 | import * as DpopProofs from "./DpopProofs.ts"; |
| 10 | 10 | ||
| @@ -41,7 +41,7 @@ describe("DpopProofReplay", () => { · 30 unmodified lines | |||
| 41 | }, | 41 | }, |
| 42 | }; | 42 | }; |
| 43 | }, | 43 | }, |
| 44 | } as unknown as RelayDatabase; | 44 | } as unknown as RelayDb.RelayDb["Service"]; |
| 45 | 45 | ||
| 46 | return Effect.gen(function* () { | 46 | return Effect.gen(function* () { |
| 47 | const replay = yield* DpopProofs.DpopProofReplay; | 47 | const replay = yield* DpopProofs.DpopProofReplay; |
| @@ -67,7 +67,9 @@ describe("DpopProofReplay", () => { · 19 unmodified lines | |||
| 67 | expiresAt: "2026-05-25T12:00:00.000Z", | 67 | expiresAt: "2026-05-25T12:00:00.000Z", |
| 68 | }, | 68 | }, |
| 69 | ]); | 69 | ]); |
| 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 | ); | ||
| 71 | }); | 73 | }); |
| 72 | 74 | ||
| 73 | it.effect("prunes expired proof rows from the maintenance path", () => { | 75 | it.effect("prunes expired proof rows from the maintenance path", () => { |
| @@ -84,12 +86,14 @@ describe("DpopProofReplay", () => { · 10 unmodified lines | |||
| 84 | }, | 86 | }, |
| 85 | }; | 87 | }; |
| 86 | }, | 88 | }, |
| 87 | } as unknown as RelayDatabase; | 89 | } as unknown as RelayDb.RelayDb["Service"]; |
| 88 | 90 | ||
| 89 | return Effect.gen(function* () { | 91 | return Effect.gen(function* () { |
| 90 | const replay = yield* DpopProofs.DpopProofReplay; | 92 | const replay = yield* DpopProofs.DpopProofReplay; |
| 91 | yield* replay.pruneExpired; | 93 | yield* replay.pruneExpired; |
| 92 | expect(calls).toEqual(["delete", "delete.where"]); | 94 | 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 | ); | ||
| 94 | }); | 98 | }); |
| 95 | }); | 99 | }); |
| @@ -7,7 +7,7 @@ import * as HttpApiError from "effect/unstable/httpapi/HttpApiError"; · 6 unmodified lines | |||
| 7 | import { lt } from "drizzle-orm"; | 7 | import { lt } from "drizzle-orm"; |
| 8 | 8 | ||
| 9 | import { verifyDpopProof } from "@t3tools/shared/dpop"; | 9 | import { verifyDpopProof } from "@t3tools/shared/dpop"; |
| 10 | import { RelayDb } from "../db.ts"; | 10 | import * as RelayDb from "../db.ts"; |
| 11 | import { relayDpopProofs } from "../persistence/schema.ts"; | 11 | import { relayDpopProofs } from "../persistence/schema.ts"; |
| 12 | 12 | ||
| 13 | export class DpopProofReplayPersistenceError extends Schema.TaggedErrorClass<DpopProofReplayPersistenceError>()( | 13 | export class DpopProofReplayPersistenceError extends Schema.TaggedErrorClass<DpopProofReplayPersistenceError>()( |
| @@ -21,34 +21,31 @@ export class DpopProofReplayPersistenceError extends Schema.TaggedErrorClass<Dpo · 7 unmodified lines | |||
| 21 | } | 21 | } |
| 22 | } | 22 | } |
| 23 | 23 | ||
| 24 | export interface DpopProofReplayShape { | 24 | export class DpopProofReplay extends Context.Service< |
| 25 | readonly verifyAndConsume: (input: { | 25 | DpopProofReplay, |
| 26 | readonly proof: string | undefined; | 26 | { |
| 27 | readonly method: string; | 27 | readonly verifyAndConsume: (input: { |
| 28 | readonly url: string; | 28 | readonly proof: string | undefined; |
| 29 | readonly expectedThumbprint?: string; | 29 | readonly method: string; |
| 30 | readonly expectedAccessToken?: string; | 30 | readonly url: string; |
| 31 | readonly now: DateTime.DateTime; | 31 | readonly expectedThumbprint?: string; |
| 32 | }) => Effect.Effect<string, HttpApiError.Unauthorized | DpopProofReplayPersistenceError>; | 32 | readonly expectedAccessToken?: string; |
| 33 | 33 | readonly now: DateTime.DateTime; | |
| 34 | readonly consume: (input: { | 34 | }) => Effect.Effect<string, HttpApiError.Unauthorized | DpopProofReplayPersistenceError>; |
| 35 | readonly thumbprint: string; | 35 | readonly consume: (input: { |
| 36 | readonly jti: string; | 36 | readonly thumbprint: string; |
| 37 | readonly iat: number; | 37 | readonly jti: string; |
| 38 | readonly expiresAt: DateTime.DateTime; | 38 | readonly iat: number; |
| 39 | }) => Effect.Effect<boolean, DpopProofReplayPersistenceError>; | 39 | readonly expiresAt: DateTime.DateTime; |
| 40 | 40 | }) => Effect.Effect<boolean, DpopProofReplayPersistenceError>; | |
| 41 | readonly pruneExpired: Effect.Effect<void, DpopProofReplayPersistenceError>; | 41 | readonly pruneExpired: Effect.Effect<void, DpopProofReplayPersistenceError>; |
| 42 | } | 42 | } |
| 43 | 43 | >()("t3code-relay/auth/DpopProofs/DpopProofReplay") {} | |
| 44 | export class DpopProofReplay extends Context.Service<DpopProofReplay, DpopProofReplayShape>()( | ||
| 45 | "t3code-relay/auth/DpopProofs/DpopProofReplay", | ||
| 46 | ) {} | ||
| 47 | 44 | ||
| 48 | const make = Effect.gen(function* () { | 45 | const make = Effect.gen(function* () { |
| 49 | const db = yield* RelayDb; | 46 | const db = yield* RelayDb.RelayDb; |
| 50 | 47 | ||
| 51 | const consume: DpopProofReplayShape["consume"] = Effect.fn("relay.dpop_proofs.consume")( | 48 | const consume: DpopProofReplay["Service"]["consume"] = Effect.fn("relay.dpop_proofs.consume")( |
| 52 | function* (input) { | 49 | function* (input) { |
| 53 | const createdAt = DateTime.formatIso(yield* DateTime.now); | 50 | const createdAt = DateTime.formatIso(yield* DateTime.now); |
| 54 | const inserted = yield* db | 51 | const inserted = yield* db |
| @@ -67,7 +64,7 @@ const make = Effect.gen(function* () { · 12 unmodified lines | |||
| 67 | Effect.mapError((cause) => new DpopProofReplayPersistenceError({ cause })), | 64 | Effect.mapError((cause) => new DpopProofReplayPersistenceError({ cause })), |
| 68 | ); | 65 | ); |
| 69 | 66 | ||
| 70 | const verifyAndConsume: DpopProofReplayShape["verifyAndConsume"] = Effect.fn( | 67 | const verifyAndConsume: DpopProofReplay["Service"]["verifyAndConsume"] = Effect.fn( |
| 71 | "relay.dpop_proofs.verify_and_consume", | 68 | "relay.dpop_proofs.verify_and_consume", |
| 72 | )(function* (input) { | 69 | )(function* (input) { |
| 73 | yield* Effect.annotateCurrentSpan({ | 70 | yield* Effect.annotateCurrentSpan({ |
| @@ -114,7 +111,7 @@ const make = Effect.gen(function* () { · 40 unmodified lines | |||
| 114 | return result.thumbprint; | 111 | return result.thumbprint; |
| 115 | }); | 112 | }); |
| 116 | 113 | ||
| 117 | const pruneExpired: DpopProofReplayShape["pruneExpired"] = Effect.gen(function* () { | 114 | const pruneExpired: DpopProofReplay["Service"]["pruneExpired"] = Effect.gen(function* () { |
| 118 | const now = DateTime.formatIso(yield* DateTime.now); | 115 | const now = DateTime.formatIso(yield* DateTime.now); |
| 119 | yield* Effect.annotateCurrentSpan({ "relay.dpop_prune.before": now }); | 116 | yield* Effect.annotateCurrentSpan({ "relay.dpop_prune.before": now }); |
| 120 | yield* db.delete(relayDpopProofs).where(lt(relayDpopProofs.expiresAt, now)); | 117 | yield* db.delete(relayDpopProofs).where(lt(relayDpopProofs.expiresAt, now)); |
| @@ -10,7 +10,7 @@ import * as DateTime from "effect/DateTime"; · 9 unmodified lines | |||
| 10 | import * as Effect from "effect/Effect"; | 10 | import * as Effect from "effect/Effect"; |
| 11 | import * as Layer from "effect/Layer"; | 11 | import * as Layer from "effect/Layer"; |
| 12 | 12 | ||
| 13 | import { RelayDb, type RelayDatabase } from "../db.ts"; | 13 | import * as RelayDb from "../db.ts"; |
| 14 | import { relayDpopProofs } from "../persistence/schema.ts"; | 14 | import { relayDpopProofs } from "../persistence/schema.ts"; |
| 15 | import * as DpopProofs from "./DpopProofs.ts"; | 15 | import * as DpopProofs from "./DpopProofs.ts"; |
| 16 | 16 | ||
| @@ -78,8 +78,8 @@ function layer( · 61 unmodified lines | |||
| 78 | }), | 78 | }), |
| 79 | }; | 79 | }; |
| 80 | }, | 80 | }, |
| 81 | } as unknown as RelayDatabase; | 81 | } as unknown as RelayDb.RelayDb["Service"]; |
| 82 | return DpopProofs.layer.pipe(Layer.provide(Layer.succeed(RelayDb, fakeDb))); | 82 | return DpopProofs.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb))); |
| 83 | } | 83 | } |
| 84 | 84 | ||
| 85 | function consumeEachProofOnce() { | 85 | function consumeEachProofOnce() { |
| @@ -33,9 +33,7 @@ const config = RelayConfiguration.RelayConfiguration.of({ · 32 unmodified lines | |||
| 33 | managedEndpointNamespace: undefined, | 33 | managedEndpointNamespace: undefined, |
| 34 | }); | 34 | }); |
| 35 | 35 | ||
| 36 | const layer = RelayTokens.layer.pipe( | 36 | const layer = RelayTokens.layer.pipe(Layer.provide(RelayConfiguration.layer(config))); |
| 37 | Layer.provide(Layer.succeed(RelayConfiguration.RelayConfiguration, config)), | ||
| 38 | ); | ||
| 39 | 37 | ||
| 40 | describe("RelayTokens", () => { | 38 | describe("RelayTokens", () => { |
| 41 | it.effect("issues a user-bound environment link challenge", () => | 39 | it.effect("issues a user-bound environment link challenge", () => |
| @@ -93,45 +93,44 @@ function resolveDpopAccessTokenScopes(input: { · 92 unmodified lines | |||
| 93 | }); | 93 | }); |
| 94 | } | 94 | } |
| 95 | 95 | ||
| 96 | export interface RelayTokensShape { | 96 | export class RelayTokens extends Context.Service< |
| 97 | readonly resolveDpopAccessTokenScopes: typeof resolveDpopAccessTokenScopes; | 97 | RelayTokens, |
| 98 | readonly issueLinkChallenge: (input: { | 98 | { |
| 99 | readonly userId: string; | 99 | readonly resolveDpopAccessTokenScopes: typeof resolveDpopAccessTokenScopes; |
| 100 | readonly request: RelayEnvironmentLinkChallengeRequest; | 100 | readonly issueLinkChallenge: (input: { |
| 101 | readonly jti: string; | 101 | readonly userId: string; |
| 102 | readonly issuedAtEpochSeconds: number; | 102 | readonly request: RelayEnvironmentLinkChallengeRequest; |
| 103 | readonly expiresAtEpochSeconds: number; | 103 | readonly jti: string; |
| 104 | }) => Effect.Effect<string, RelayJwtError>; | 104 | readonly issuedAtEpochSeconds: number; |
| 105 | readonly verifyLinkChallenge: (input: { | 105 | readonly expiresAtEpochSeconds: number; |
| 106 | readonly token: string; | 106 | }) => Effect.Effect<string, RelayJwtError>; |
| 107 | readonly userId: string; | 107 | readonly verifyLinkChallenge: (input: { |
| 108 | readonly request: RelayEnvironmentLinkChallengeRequest; | 108 | readonly token: string; |
| 109 | readonly nowEpochSeconds: number; | 109 | readonly userId: string; |
| 110 | }) => Effect.Effect<LinkChallengeClaims | null>; | 110 | readonly request: RelayEnvironmentLinkChallengeRequest; |
| 111 | readonly issueDpopAccessToken: (input: { | 111 | readonly nowEpochSeconds: number; |
| 112 | readonly userId: string; | 112 | }) => Effect.Effect<LinkChallengeClaims | null>; |
| 113 | readonly proofKeyThumbprint: string; | 113 | readonly issueDpopAccessToken: (input: { |
| 114 | readonly jti: string; | 114 | readonly userId: string; |
| 115 | readonly issuedAtEpochSeconds: number; | 115 | readonly proofKeyThumbprint: string; |
| 116 | readonly expiresAtEpochSeconds: number; | 116 | readonly jti: string; |
| 117 | readonly clientId: RelayPublicClientId; | 117 | readonly issuedAtEpochSeconds: number; |
| 118 | readonly scopes: ReadonlyArray<RelayDpopAccessTokenScope>; | 118 | readonly expiresAtEpochSeconds: number; |
| 119 | }) => Effect.Effect<string, RelayJwtError>; | 119 | readonly clientId: RelayPublicClientId; |
| 120 | readonly verifyDpopAccessToken: (input: { | 120 | readonly scopes: ReadonlyArray<RelayDpopAccessTokenScope>; |
| 121 | readonly token: string; | 121 | }) => Effect.Effect<string, RelayJwtError>; |
| 122 | readonly nowEpochSeconds: number; | 122 | readonly verifyDpopAccessToken: (input: { |
| 123 | }) => Effect.Effect<RelayDpopAccessTokenClaims | null>; | 123 | readonly token: string; |
| 124 | } | 124 | readonly nowEpochSeconds: number; |
| 125 | 125 | }) => Effect.Effect<RelayDpopAccessTokenClaims | null>; | |
| 126 | export class RelayTokens extends Context.Service<RelayTokens, RelayTokensShape>()( | 126 | } |
| 127 | "t3code-relay/auth/RelayTokens", | 127 | >()("t3code-relay/auth/RelayTokens") {} |
| 128 | ) {} | ||
| 129 | 128 | ||
| 130 | const make = Effect.gen(function* () { | 129 | const make = Effect.gen(function* () { |
| 131 | const config = yield* RelayConfiguration.RelayConfiguration; | 130 | const config = yield* RelayConfiguration.RelayConfiguration; |
| 132 | const issuer = normalizeRelayIssuer(config.relayIssuer); | 131 | const issuer = normalizeRelayIssuer(config.relayIssuer); |
| 133 | 132 | ||
| 134 | const issueLinkChallenge: RelayTokensShape["issueLinkChallenge"] = Effect.fn( | 133 | const issueLinkChallenge: RelayTokens["Service"]["issueLinkChallenge"] = Effect.fn( |
| 135 | "relay.tokens.issue_link_challenge", | 134 | "relay.tokens.issue_link_challenge", |
| 136 | )(function* (input) { | 135 | )(function* (input) { |
| 137 | return yield* signRelayJwt({ | 136 | return yield* signRelayJwt({ |
| @@ -150,7 +149,7 @@ const make = Effect.gen(function* () { · 12 unmodified lines | |||
| 150 | }); | 149 | }); |
| 151 | }); | 150 | }); |
| 152 | 151 | ||
| 153 | const verifyLinkChallenge: RelayTokensShape["verifyLinkChallenge"] = Effect.fn( | 152 | const verifyLinkChallenge: RelayTokens["Service"]["verifyLinkChallenge"] = Effect.fn( |
| 154 | "relay.tokens.verify_link_challenge", | 153 | "relay.tokens.verify_link_challenge", |
| 155 | )((input) => | 154 | )((input) => |
| 156 | verifyRelayJwt({ | 155 | verifyRelayJwt({ |
| @@ -177,7 +176,7 @@ const make = Effect.gen(function* () { · 20 unmodified lines | |||
| 177 | ), | 176 | ), |
| 178 | ); | 177 | ); |
| 179 | 178 | ||
| 180 | const issueDpopAccessToken: RelayTokensShape["issueDpopAccessToken"] = Effect.fn( | 179 | const issueDpopAccessToken: RelayTokens["Service"]["issueDpopAccessToken"] = Effect.fn( |
| 181 | "relay.tokens.issue_dpop_access_token", | 180 | "relay.tokens.issue_dpop_access_token", |
| 182 | )(function* (input) { | 181 | )(function* (input) { |
| 183 | return yield* signRelayJwt({ | 182 | return yield* signRelayJwt({ |
| @@ -197,7 +196,7 @@ const make = Effect.gen(function* () { · 13 unmodified lines | |||
| 197 | }); | 196 | }); |
| 198 | }); | 197 | }); |
| 199 | 198 | ||
| 200 | const verifyDpopAccessToken: RelayTokensShape["verifyDpopAccessToken"] = Effect.fn( | 199 | const verifyDpopAccessToken: RelayTokens["Service"]["verifyDpopAccessToken"] = Effect.fn( |
| 201 | "relay.tokens.verify_dpop_access_token", | 200 | "relay.tokens.verify_dpop_access_token", |
| 202 | )((input) => | 201 | )((input) => |
| 203 | verifyRelayJwt({ | 202 | verifyRelayJwt({ |
| @@ -10,11 +10,12 @@ import * as Effect from "effect/Effect"; · 9 unmodified lines | |||
| 10 | 10 | ||
| 11 | import { relayDatabaseMode } from "./dbConfig.ts"; | 11 | import { relayDatabaseMode } from "./dbConfig.ts"; |
| 12 | 12 | ||
| 13 | export interface RelayDatabase extends EffectPgDatabase { | 13 | export class RelayDb extends Context.Service< |
| 14 | readonly $client: PgClient; | 14 | RelayDb, |
| 15 | } | 15 | EffectPgDatabase & { |
| 16 | 16 | readonly $client: PgClient; | |
| 17 | export class RelayDb extends Context.Service<RelayDb, RelayDatabase>()("t3code-relay/db/RelayDb") {} | 17 | } |
| 18 | >()("t3code-relay/db/RelayDb") {} | ||
| 18 | 19 | ||
| 19 | export const PlanetscaleDatabase = Effect.gen(function* () { | 20 | export const PlanetscaleDatabase = Effect.gen(function* () { |
| 20 | const { stage } = yield* Alchemy.Stack; | 21 | const { stage } = yield* Alchemy.Stack; |
| @@ -161,8 +161,8 @@ function connectorTestLayer( · 160 unmodified lines | |||
| 161 | request: HttpClientRequest.HttpClientRequest, | 161 | request: HttpClientRequest.HttpClientRequest, |
| 162 | ) => Effect.Effect<HttpClientResponse.HttpClientResponse>, | 162 | ) => Effect.Effect<HttpClientResponse.HttpClientResponse>, |
| 163 | options?: { | 163 | options?: { |
| 164 | readonly links?: EnvironmentLinks.EnvironmentLinksShape; | 164 | readonly links?: EnvironmentLinks.EnvironmentLinks["Service"]; |
| 165 | readonly allocations?: ManagedEndpointAllocations.ManagedEndpointAllocationsShape; | 165 | readonly allocations?: ManagedEndpointAllocations.ManagedEndpointAllocations["Service"]; |
| 166 | }, | 166 | }, |
| 167 | ) { | 167 | ) { |
| 168 | return EnvironmentConnector.layer.pipe( | 168 | return EnvironmentConnector.layer.pipe( |
| @@ -174,7 +174,7 @@ function connectorTestLayer( · 5 unmodified lines | |||
| 174 | options?.allocations ?? makeAllocations(), | 174 | options?.allocations ?? makeAllocations(), |
| 175 | ), | 175 | ), |
| 176 | ), | 176 | ), |
| 177 | Layer.provide(Layer.succeed(RelayConfiguration.RelayConfiguration, settings)), | 177 | Layer.provide(RelayConfiguration.layer(settings)), |
| 178 | Layer.provide(Layer.succeed(HttpClient.HttpClient, HttpClient.make(execute))), | 178 | Layer.provide(Layer.succeed(HttpClient.HttpClient, HttpClient.make(execute))), |
| 179 | ); | 179 | ); |
| 180 | } | 180 | } |
| @@ -189,7 +189,7 @@ function makeAllocations( · 8 unmodified lines | |||
| 189 | dnsRecordId: "dns-record-id", | 189 | dnsRecordId: "dns-record-id", |
| 190 | readyAt: "2026-05-25T00:00:00.000Z", | 190 | readyAt: "2026-05-25T00:00:00.000Z", |
| 191 | }, | 191 | }, |
| 192 | ): ManagedEndpointAllocations.ManagedEndpointAllocationsShape { | 192 | ): ManagedEndpointAllocations.ManagedEndpointAllocations["Service"] { |
| 193 | return { | 193 | return { |
| 194 | get: () => Effect.succeed(allocation), | 194 | get: () => Effect.succeed(allocation), |
| 195 | reserve: () => Effect.die("unused"), | 195 | reserve: () => Effect.die("unused"), |
| @@ -202,7 +202,7 @@ function makeAllocations( · 6 unmodified lines | |||
| 202 | 202 | ||
| 203 | function makeLinks( | 203 | function makeLinks( |
| 204 | overrides: Partial<EnvironmentLinks.RelayLinkedEnvironmentRecord> = {}, | 204 | overrides: Partial<EnvironmentLinks.RelayLinkedEnvironmentRecord> = {}, |
| 205 | ): EnvironmentLinks.EnvironmentLinksShape { | 205 | ): EnvironmentLinks.EnvironmentLinks["Service"] { |
| 206 | return { | 206 | return { |
| 207 | upsert: () => Effect.void, | 207 | upsert: () => Effect.void, |
| 208 | listUsersForEnvironment: () => Effect.succeed([]), | 208 | listUsersForEnvironment: () => Effect.succeed([]), |
| @@ -35,7 +35,9 @@ import * as Layer from "effect/Layer"; · 34 unmodified lines | |||
| 35 | import * as Option from "effect/Option"; | 35 | import * as Option from "effect/Option"; |
| 36 | import * as Redacted from "effect/Redacted"; | 36 | import * as Redacted from "effect/Redacted"; |
| 37 | import * as Schema from "effect/Schema"; | 37 | 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"; | ||
| 39 | 41 | ||
| 40 | import * as EnvironmentLinks from "./EnvironmentLinks.ts"; | 42 | import * as EnvironmentLinks from "./EnvironmentLinks.ts"; |
| 41 | import * as ManagedEndpointAllocations from "./ManagedEndpointAllocations.ts"; | 43 | import * as ManagedEndpointAllocations from "./ManagedEndpointAllocations.ts"; |
| @@ -139,22 +141,20 @@ export type EnvironmentConnectorError = · 97 unmodified lines | |||
| 139 | export const ENVIRONMENT_MINT_REQUEST_TIMEOUT_MS = 10_000; | 141 | export const ENVIRONMENT_MINT_REQUEST_TIMEOUT_MS = 10_000; |
| 140 | const ENVIRONMENT_HEALTH_CLOCK_SKEW_MILLIS = 60 * 1_000; | 142 | const ENVIRONMENT_HEALTH_CLOCK_SKEW_MILLIS = 60 * 1_000; |
| 141 | 143 | ||
| 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 | |||
| 155 | export class EnvironmentConnector extends Context.Service< | 144 | export class EnvironmentConnector extends Context.Service< |
| 156 | EnvironmentConnector, | 145 | 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 | } | ||
| 158 | >()("t3code-relay/environments/EnvironmentConnector") {} | 158 | >()("t3code-relay/environments/EnvironmentConnector") {} |
| 159 | 159 | ||
| 160 | const decodeMintResponseProof = Schema.decodeUnknownEffect( | 160 | const decodeMintResponseProof = Schema.decodeUnknownEffect( |
| @@ -4,7 +4,7 @@ import { PgDialect, QueryBuilder } from "drizzle-orm/pg-core"; · 3 unmodified lines | |||
| 4 | import * as Effect from "effect/Effect"; | 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as Layer from "effect/Layer"; | 5 | import * as Layer from "effect/Layer"; |
| 6 | 6 | ||
| 7 | import { RelayDb, type RelayDatabase } from "../db.ts"; | 7 | import * as RelayDb from "../db.ts"; |
| 8 | import { relayEnvironmentCredentials } from "../persistence/schema.ts"; | 8 | import { relayEnvironmentCredentials } from "../persistence/schema.ts"; |
| 9 | import * as EnvironmentCredentials from "./EnvironmentCredentials.ts"; | 9 | import * as EnvironmentCredentials from "./EnvironmentCredentials.ts"; |
| 10 | 10 | ||
| @@ -47,7 +47,7 @@ describe("EnvironmentCredentials", () => { · 36 unmodified lines | |||
| 47 | }), | 47 | }), |
| 48 | }; | 48 | }; |
| 49 | }, | 49 | }, |
| 50 | } as unknown as RelayDatabase; | 50 | } as unknown as RelayDb.RelayDb["Service"]; |
| 51 | 51 | ||
| 52 | return Effect.gen(function* () { | 52 | return Effect.gen(function* () { |
| 53 | const credentials = yield* EnvironmentCredentials.EnvironmentCredentials; | 53 | const credentials = yield* EnvironmentCredentials.EnvironmentCredentials; |
| @@ -87,7 +87,7 @@ describe("EnvironmentCredentials", () => { · 33 unmodified lines | |||
| 87 | Effect.provide( | 87 | Effect.provide( |
| 88 | EnvironmentCredentials.layer.pipe( | 88 | EnvironmentCredentials.layer.pipe( |
| 89 | Layer.provide(NodeCryptoLayer.layer), | 89 | Layer.provide(NodeCryptoLayer.layer), |
| 90 | Layer.provide(Layer.succeed(RelayDb, fakeDb)), | 90 | Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)), |
| 91 | ), | 91 | ), |
| 92 | ), | 92 | ), |
| 93 | ); | 93 | ); |
| @@ -118,7 +118,7 @@ describe("EnvironmentCredentials", () => { · 24 unmodified lines | |||
| 118 | }, | 118 | }, |
| 119 | }; | 119 | }; |
| 120 | }, | 120 | }, |
| 121 | } as unknown as RelayDatabase; | 121 | } as unknown as RelayDb.RelayDb["Service"]; |
| 122 | 122 | ||
| 123 | return Effect.gen(function* () { | 123 | return Effect.gen(function* () { |
| 124 | const credentials = yield* EnvironmentCredentials.EnvironmentCredentials; | 124 | const credentials = yield* EnvironmentCredentials.EnvironmentCredentials; |
| @@ -150,7 +150,7 @@ describe("EnvironmentCredentials", () => { · 25 unmodified lines | |||
| 150 | Effect.provide( | 150 | Effect.provide( |
| 151 | EnvironmentCredentials.layer.pipe( | 151 | EnvironmentCredentials.layer.pipe( |
| 152 | Layer.provide(NodeCryptoLayer.layer), | 152 | Layer.provide(NodeCryptoLayer.layer), |
| 153 | Layer.provide(Layer.succeed(RelayDb, fakeDb)), | 153 | Layer.provide(Layer.succeed(RelayDb.RelayDb, fakeDb)), |
| 154 | ), | 154 | ), |
| 155 | ), | 155 | ), |
| 156 | ); | 156 | ); |
| @@ -8,7 +8,7 @@ import * as Option from "effect/Option"; · 7 unmodified lines | |||
| 8 | import * as Schema from "effect/Schema"; | 8 | import * as Schema from "effect/Schema"; |
| 9 | import { and, eq, isNull, ne, notExists } from "drizzle-orm"; | 9 | import { and, eq, isNull, ne, notExists } from "drizzle-orm"; |
| 10 | 10 | ||
| 11 | import { RelayDb } from "../db.ts"; | 11 | import * as RelayDb from "../db.ts"; |
| 12 | import { relayEnvironmentCredentials, relayEnvironmentLinks } from "../persistence/schema.ts"; | 12 | import { relayEnvironmentCredentials, relayEnvironmentLinks } from "../persistence/schema.ts"; |
| 13 | 13 | ||
| 14 | export class EnvironmentCredentialCreatePersistenceError extends Schema.TaggedErrorClass<EnvironmentCredentialCreatePersistenceError>()( | 14 | export class EnvironmentCredentialCreatePersistenceError extends Schema.TaggedErrorClass<EnvironmentCredentialCreatePersistenceError>()( |
| @@ -44,30 +44,28 @@ export interface EnvironmentCredentialPrincipal { · 29 unmodified lines | |||
| 44 | readonly environmentPublicKey: string; | 44 | readonly environmentPublicKey: string; |
| 45 | } | 45 | } |
| 46 | 46 | ||
| 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 | |||
| 64 | export class EnvironmentCredentials extends Context.Service< | 47 | export class EnvironmentCredentials extends Context.Service< |
| 65 | EnvironmentCredentials, | 48 | 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 | } | ||
| 67 | >()("t3code-relay/environments/EnvironmentCredentials") {} | 65 | >()("t3code-relay/environments/EnvironmentCredentials") {} |
| 68 | 66 | ||
| 69 | const make = Effect.gen(function* () { | 67 | const make = Effect.gen(function* () { |
| 70 | const db = yield* RelayDb; | 68 | const db = yield* RelayDb.RelayDb; |
| 71 | const crypto = yield* Crypto.Crypto; | 69 | const crypto = yield* Crypto.Crypto; |
| 72 | const hashToken = (token: string) => | 70 | const hashToken = (token: string) => |
| 73 | crypto | 71 | crypto |
| @@ -105,14 +105,14 @@ const makeRequest = Effect.gen(function* () { · 104 unmodified lines | |||
| 105 | }); | 105 | }); |
| 106 | 106 | ||
| 107 | function testLayer(input?: { | 107 | function testLayer(input?: { |
| 108 | readonly upsert?: EnvironmentLinks.EnvironmentLinksShape["upsert"]; | 108 | readonly upsert?: EnvironmentLinks.EnvironmentLinks["Service"]["upsert"]; |
| 109 | readonly consume?: DpopProofs.DpopProofReplayShape["consume"]; | 109 | readonly consume?: DpopProofs.DpopProofReplay["Service"]["consume"]; |
| 110 | }) { | 110 | }) { |
| 111 | return EnvironmentLinker.layer.pipe( | 111 | return EnvironmentLinker.layer.pipe( |
| 112 | Layer.provideMerge(RelayTokens.layer), | 112 | Layer.provideMerge(RelayTokens.layer), |
| 113 | Layer.provide( | 113 | Layer.provide( |
| 114 | Layer.mergeAll( | 114 | Layer.mergeAll( |
| 115 | Layer.succeed(RelayConfiguration.RelayConfiguration, config), | 115 | RelayConfiguration.layer(config), |
| 116 | Layer.succeed(DpopProofs.DpopProofReplay, { | 116 | Layer.succeed(DpopProofs.DpopProofReplay, { |
| 117 | verifyAndConsume: () => Effect.die("unexpected DPoP proof verification"), | 117 | verifyAndConsume: () => Effect.die("unexpected DPoP proof verification"), |
| 118 | consume: input?.consume ?? (() => Effect.succeed(true)), | 118 | consume: input?.consume ?? (() => Effect.succeed(true)), |
| @@ -53,26 +53,25 @@ export type EnvironmentLinkError = · 52 unmodified lines | |||
| 53 | | EnvironmentCredentials.EnvironmentCredentialCreatePersistenceError | 53 | | EnvironmentCredentials.EnvironmentCredentialCreatePersistenceError |
| 54 | | ManagedEndpointProvider.ManagedEndpointProviderError; | 54 | | ManagedEndpointProvider.ManagedEndpointProviderError; |
| 55 | 55 | ||
| 56 | export interface EnvironmentLinkerShape { | 56 | export class EnvironmentLinker extends Context.Service< |
| 57 | readonly link: (input: { | 57 | EnvironmentLinker, |
| 58 | readonly userId: string; | 58 | { |
| 59 | readonly request: RelayEnvironmentLinkRequest; | 59 | readonly link: (input: { |
| 60 | }) => Effect.Effect< | 60 | readonly userId: string; |
| 61 | { | 61 | readonly request: RelayEnvironmentLinkRequest; |
| 62 | readonly environmentId: RelayEnvironmentLinkProofPayload["environmentId"]; | 62 | }) => Effect.Effect< |
| 63 | readonly endpoint: RelayEnvironmentLinkProofPayload["endpoint"]; | 63 | { |
| 64 | readonly endpointRuntime: | 64 | readonly environmentId: RelayEnvironmentLinkProofPayload["environmentId"]; |
| 65 | | ManagedEndpointProvider.ManagedEndpointProvisioningResult["runtime"] | 65 | readonly endpoint: RelayEnvironmentLinkProofPayload["endpoint"]; |
| 66 | | null; | 66 | readonly endpointRuntime: |
| 67 | readonly environmentCredential: string; | 67 | | ManagedEndpointProvider.ManagedEndpointProvisioningResult["runtime"] |
| 68 | }, | 68 | | null; |
| 69 | EnvironmentLinkError | 69 | readonly environmentCredential: string; |
| 70 | >; | 70 | }, |
| 71 | } | 71 | EnvironmentLinkError |
| 72 | 72 | >; | |
| 73 | export class EnvironmentLinker extends Context.Service<EnvironmentLinker, EnvironmentLinkerShape>()( | 73 | } |
| 74 | "t3code-relay/environments/EnvironmentLinker", | 74 | >()("t3code-relay/environments/EnvironmentLinker") {} |
| 75 | ) {} | ||
| 76 | 75 | ||
| 77 | const decodeProof = Schema.decodeUnknownEffect(RelayEnvironmentLinkProofPayload); | 76 | const decodeProof = Schema.decodeUnknownEffect(RelayEnvironmentLinkProofPayload); |
| 78 | 77 | ||
| @@ -3,9 +3,9 @@ import * as Effect from "effect/Effect"; · 2 unmodified lines | |||
| 3 | import * as Layer from "effect/Layer"; | 3 | import * as Layer from "effect/Layer"; |
| 4 | import { PgDialect } from "drizzle-orm/pg-core"; | 4 | import { PgDialect } from "drizzle-orm/pg-core"; |
| 5 | 5 | ||
| 6 | import { RelayDb, type RelayDatabase } from "../db.ts"; | 6 | import * as RelayDb from "../db.ts"; |
| 7 | import { relayEnvironmentLinks } from "../persistence/schema.ts"; | 7 | import { relayEnvironmentLinks } from "../persistence/schema.ts"; |
| 8 | import { EnvironmentLinks, layer } from "./EnvironmentLinks.ts"; | 8 | import * as EnvironmentLinks from "./EnvironmentLinks.ts"; |
| 9 | 9 | ||
| 10 | describe("EnvironmentLinks", () => { | 10 | describe("EnvironmentLinks", () => { |
| 11 | it.effect("selects users when either notifications or Live Activities are enabled", () => { | 11 | it.effect("selects users when either notifications or Live Activities are enabled", () => { |
| @@ -25,10 +25,10 @@ describe("EnvironmentLinks", () => { · 13 unmodified lines | |||
| 25 | }, | 25 | }, |
| 26 | }; | 26 | }; |
| 27 | }, | 27 | }, |
| 28 | } as unknown as RelayDatabase; | 28 | } as unknown as RelayDb.RelayDb["Service"]; |
| 29 | 29 | ||
| 30 | return Effect.gen(function* () { | 30 | return Effect.gen(function* () { |
| 31 | const links = yield* EnvironmentLinks; | 31 | const links = yield* EnvironmentLinks.EnvironmentLinks; |
| 32 | expect(yield* links.listUsersForEnvironment({ environmentId: "env-1" })).toEqual([]); | 32 | expect(yield* links.listUsersForEnvironment({ environmentId: "env-1" })).toEqual([]); |
| 33 | expect(whereConditions).toHaveLength(1); | 33 | expect(whereConditions).toHaveLength(1); |
| 34 | 34 | ||
| @@ -39,7 +39,11 @@ describe("EnvironmentLinks", () => { · 4 unmodified lines | |||
| 39 | expect(query.sql).toContain('"relay_environment_links"."live_activities_enabled" = $3'); | 39 | expect(query.sql).toContain('"relay_environment_links"."live_activities_enabled" = $3'); |
| 40 | expect(query.sql).toContain(" or "); | 40 | expect(query.sql).toContain(" or "); |
| 41 | expect(query.params).toEqual(["env-1", true, true]); | 41 | 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 | ); | ||
| 43 | }); | 47 | }); |
| 44 | 48 | ||
| 45 | it.effect("revokes only the active link owned by the requesting user", () => { | 49 | it.effect("revokes only the active link owned by the requesting user", () => { |
| @@ -65,10 +69,10 @@ describe("EnvironmentLinks", () => { · 19 unmodified lines | |||
| 65 | }, | 69 | }, |
| 66 | }; | 70 | }; |
| 67 | }, | 71 | }, |
| 68 | } as unknown as RelayDatabase; | 72 | } as unknown as RelayDb.RelayDb["Service"]; |
| 69 | 73 | ||
| 70 | return Effect.gen(function* () { | 74 | return Effect.gen(function* () { |
| 71 | const links = yield* EnvironmentLinks; | 75 | const links = yield* EnvironmentLinks.EnvironmentLinks; |
| 72 | const revoked = yield* links.revokeForUser({ | 76 | const revoked = yield* links.revokeForUser({ |
| 73 | userId: "user-1", | 77 | userId: "user-1", |
| 74 | environmentId: "env-1", | 78 | environmentId: "env-1", |
| @@ -86,6 +90,10 @@ describe("EnvironmentLinks", () => { · 11 unmodified lines | |||
| 86 | expect(query.sql).toContain('"relay_environment_links"."environment_id" = $2'); | 90 | expect(query.sql).toContain('"relay_environment_links"."environment_id" = $2'); |
| 87 | expect(query.sql).toContain('"relay_environment_links"."revoked_at" is null'); | 91 | expect(query.sql).toContain('"relay_environment_links"."revoked_at" is null'); |
| 88 | expect(query.params).toEqual(["user-1", "env-1"]); | 92 | 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 | ); | ||
| 90 | }); | 98 | }); |
| 91 | }); | 99 | }); |
| @@ -11,7 +11,7 @@ import * as Layer from "effect/Layer"; · 10 unmodified lines | |||
| 11 | import * as Schema from "effect/Schema"; | 11 | import * as Schema from "effect/Schema"; |
| 12 | import { and, eq, isNull, or } from "drizzle-orm"; | 12 | import { and, eq, isNull, or } from "drizzle-orm"; |
| 13 | 13 | ||
| 14 | import { RelayDb } from "../db.ts"; | 14 | import * as RelayDb from "../db.ts"; |
| 15 | import { relayEnvironmentLinks } from "../persistence/schema.ts"; | 15 | import { relayEnvironmentLinks } from "../persistence/schema.ts"; |
| 16 | 16 | ||
| 17 | export interface RelayLinkedEnvironmentRecord extends RelayClientEnvironmentRecord { | 17 | export interface RelayLinkedEnvironmentRecord extends RelayClientEnvironmentRecord { |
| @@ -78,45 +78,44 @@ export class EnvironmentLinkRevokePersistenceError extends Schema.TaggedErrorCla · 60 unmodified lines | |||
| 78 | } | 78 | } |
| 79 | } | 79 | } |
| 80 | 80 | ||
| 81 | export interface EnvironmentLinksShape { | 81 | export class EnvironmentLinks extends Context.Service< |
| 82 | readonly upsert: (input: { | 82 | EnvironmentLinks, |
| 83 | readonly userId: string; | 83 | { |
| 84 | readonly request: RelayEnvironmentLinkRequest; | 84 | readonly upsert: (input: { |
| 85 | readonly proof: RelayEnvironmentLinkProofPayload; | 85 | readonly userId: string; |
| 86 | readonly endpoint: RelayManagedEndpoint; | 86 | readonly request: RelayEnvironmentLinkRequest; |
| 87 | }) => Effect.Effect<void, EnvironmentLinkUpsertPersistenceError>; | 87 | readonly proof: RelayEnvironmentLinkProofPayload; |
| 88 | readonly listUsersForEnvironment: (input: { | 88 | readonly endpoint: RelayManagedEndpoint; |
| 89 | readonly environmentId: string; | 89 | }) => Effect.Effect<void, EnvironmentLinkUpsertPersistenceError>; |
| 90 | }) => Effect.Effect<ReadonlyArray<string>, EnvironmentLinkUserListPersistenceError>; | 90 | readonly listUsersForEnvironment: (input: { |
| 91 | readonly listDeliveryUsersForEnvironment: (input: { | 91 | readonly environmentId: string; |
| 92 | readonly environmentId: string; | 92 | }) => Effect.Effect<ReadonlyArray<string>, EnvironmentLinkUserListPersistenceError>; |
| 93 | readonly environmentPublicKey: string; | 93 | readonly listDeliveryUsersForEnvironment: (input: { |
| 94 | }) => Effect.Effect< | 94 | readonly environmentId: string; |
| 95 | ReadonlyArray<AgentAwarenessDeliveryUserRecord>, | 95 | readonly environmentPublicKey: string; |
| 96 | EnvironmentLinkUserListPersistenceError | 96 | }) => Effect.Effect< |
| 97 | >; | 97 | ReadonlyArray<AgentAwarenessDeliveryUserRecord>, |
| 98 | readonly listPublicKeysForEnvironment: (input: { | 98 | EnvironmentLinkUserListPersistenceError |
| 99 | readonly environmentId: string; | 99 | >; |
| 100 | }) => Effect.Effect<ReadonlyArray<string>, EnvironmentPublicKeyListPersistenceError>; | 100 | readonly listPublicKeysForEnvironment: (input: { |
| 101 | readonly listForUser: (input: { | 101 | readonly environmentId: string; |
| 102 | readonly userId: string; | 102 | }) => Effect.Effect<ReadonlyArray<string>, EnvironmentPublicKeyListPersistenceError>; |
| 103 | }) => Effect.Effect< | 103 | readonly listForUser: (input: { |
| 104 | ReadonlyArray<RelayClientEnvironmentRecord>, | 104 | readonly userId: string; |
| 105 | EnvironmentLinkListPersistenceError | 105 | }) => Effect.Effect< |
| 106 | >; | 106 | ReadonlyArray<RelayClientEnvironmentRecord>, |
| 107 | readonly getForUser: (input: { | 107 | EnvironmentLinkListPersistenceError |
| 108 | readonly userId: string; | 108 | >; |
| 109 | readonly environmentId: string; | 109 | readonly getForUser: (input: { |
| 110 | }) => Effect.Effect<RelayLinkedEnvironmentRecord | null, EnvironmentLinkLookupPersistenceError>; | 110 | readonly userId: string; |
| 111 | readonly revokeForUser: (input: { | 111 | readonly environmentId: string; |
| 112 | readonly userId: string; | 112 | }) => Effect.Effect<RelayLinkedEnvironmentRecord | null, EnvironmentLinkLookupPersistenceError>; |
| 113 | readonly environmentId: string; | 113 | readonly revokeForUser: (input: { |
| 114 | }) => Effect.Effect<boolean, EnvironmentLinkRevokePersistenceError>; | 114 | readonly userId: string; |
| 115 | } | 115 | readonly environmentId: string; |
| 116 | 116 | }) => Effect.Effect<boolean, EnvironmentLinkRevokePersistenceError>; | |
| 117 | export class EnvironmentLinks extends Context.Service<EnvironmentLinks, EnvironmentLinksShape>()( | 117 | } |
| 118 | "t3code-relay/environments/EnvironmentLinks", | 118 | >()("t3code-relay/environments/EnvironmentLinks") {} |
| 119 | ) {} | ||
| 120 | 119 | ||
| 121 | function agentAwarenessDeliveryUserCondition(environmentId: string) { | 120 | function agentAwarenessDeliveryUserCondition(environmentId: string) { |
| 122 | return and( | 121 | return and( |
| @@ -140,7 +139,7 @@ function agentAwarenessDeliveryUserKeyCondition(input: { · 17 unmodified lines | |||
| 140 | } | 139 | } |
| 141 | 140 | ||
| 142 | const make = Effect.gen(function* () { | 141 | const make = Effect.gen(function* () { |
| 143 | const db = yield* RelayDb; | 142 | const db = yield* RelayDb.RelayDb; |
| 144 | 143 | ||
| 145 | return EnvironmentLinks.of({ | 144 | return EnvironmentLinks.of({ |
| 146 | upsert: Effect.fn("relay.environment_links.upsert")( | 145 | upsert: Effect.fn("relay.environment_links.upsert")( |
| @@ -80,11 +80,11 @@ const freshRequest = Effect.gen(function* () { · 79 unmodified lines | |||
| 80 | } satisfies RelayAgentActivityPublishRequest; | 80 | } satisfies RelayAgentActivityPublishRequest; |
| 81 | }); | 81 | }); |
| 82 | 82 | ||
| 83 | function layer(replay?: Partial<DpopProofs.DpopProofReplayShape>) { | 83 | function layer(replay?: Partial<DpopProofs.DpopProofReplay["Service"]>) { |
| 84 | return EnvironmentPublishSignatures.layer.pipe( | 84 | return EnvironmentPublishSignatures.layer.pipe( |
| 85 | Layer.provide( | 85 | Layer.provide( |
| 86 | Layer.merge( | 86 | Layer.merge( |
| 87 | Layer.succeed(RelayConfiguration.RelayConfiguration, config), | 87 | RelayConfiguration.layer(config), |
| 88 | Layer.succeed(DpopProofs.DpopProofReplay, { | 88 | Layer.succeed(DpopProofs.DpopProofReplay, { |
| 89 | verifyAndConsume: | 89 | verifyAndConsume: |
| 90 | replay?.verifyAndConsume ?? (() => Effect.die("unexpected DPoP proof verification")), | 90 | replay?.verifyAndConsume ?? (() => Effect.die("unexpected DPoP proof verification")), |
| @@ -59,18 +59,16 @@ export type EnvironmentPublishSignatureError = · 58 unmodified lines | |||
| 59 | | EnvironmentPublishPublicKeyMissing | 59 | | EnvironmentPublishPublicKeyMissing |
| 60 | | DpopProofs.DpopProofReplayPersistenceError; | 60 | | DpopProofs.DpopProofReplayPersistenceError; |
| 61 | 61 | ||
| 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 | |||
| 71 | export class EnvironmentPublishSignatures extends Context.Service< | 62 | export class EnvironmentPublishSignatures extends Context.Service< |
| 72 | EnvironmentPublishSignatures, | 63 | 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 | } | ||
| 74 | >()("t3code-relay/environments/EnvironmentPublishSignatures") {} | 72 | >()("t3code-relay/environments/EnvironmentPublishSignatures") {} |
| 75 | 73 | ||
| 76 | const decodeProof = Schema.decodeUnknownEffect(RelayAgentActivityPublishProofPayload); | 74 | const decodeProof = Schema.decodeUnknownEffect(RelayAgentActivityPublishProofPayload); |
| @@ -6,7 +6,7 @@ import * as Effect from "effect/Effect"; · 5 unmodified lines | |||
| 6 | import * as Layer from "effect/Layer"; | 6 | import * as Layer from "effect/Layer"; |
| 7 | import * as Schema from "effect/Schema"; | 7 | import * as Schema from "effect/Schema"; |
| 8 | 8 | ||
| 9 | import { RelayDb } from "../db.ts"; | 9 | import * as RelayDb from "../db.ts"; |
| 10 | import { isManagedEndpointHostname, managedEndpointForHostname } from "../deploymentConfig.ts"; | 10 | import { isManagedEndpointHostname, managedEndpointForHostname } from "../deploymentConfig.ts"; |
| 11 | import { relayManagedEndpointAllocations } from "../persistence/schema.ts"; | 11 | import { relayManagedEndpointAllocations } from "../persistence/schema.ts"; |
| 12 | 12 | ||
| @@ -66,26 +66,29 @@ interface RecordManagedEndpointDnsInput extends ManagedEndpointAllocationKey { · 53 unmodified lines | |||
| 66 | readonly dnsRecordId: string; | 66 | readonly dnsRecordId: string; |
| 67 | } | 67 | } |
| 68 | 68 | ||
| 69 | export interface ManagedEndpointAllocationsShape { | 69 | export class ManagedEndpointAllocations extends Context.Service< |
| 70 | readonly get: ( | 70 | ManagedEndpointAllocations, |
| 71 | input: ManagedEndpointAllocationKey, | 71 | { |
| 72 | ) => Effect.Effect<ManagedEndpointAllocation | null, ManagedEndpointAllocationPersistenceError>; | 72 | readonly get: ( |
| 73 | readonly reserve: ( | 73 | input: ManagedEndpointAllocationKey, |
| 74 | input: ReserveManagedEndpointAllocationInput, | 74 | ) => Effect.Effect<ManagedEndpointAllocation | null, ManagedEndpointAllocationPersistenceError>; |
| 75 | ) => Effect.Effect<ManagedEndpointAllocation, ManagedEndpointAllocationPersistenceError>; | 75 | readonly reserve: ( |
| 76 | readonly recordTunnel: ( | 76 | input: ReserveManagedEndpointAllocationInput, |
| 77 | input: RecordManagedEndpointTunnelInput, | 77 | ) => Effect.Effect<ManagedEndpointAllocation, ManagedEndpointAllocationPersistenceError>; |
| 78 | ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>; | 78 | readonly recordTunnel: ( |
| 79 | readonly recordDns: ( | 79 | input: RecordManagedEndpointTunnelInput, |
| 80 | input: RecordManagedEndpointDnsInput, | 80 | ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>; |
| 81 | ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>; | 81 | readonly recordDns: ( |
| 82 | readonly markReady: ( | 82 | input: RecordManagedEndpointDnsInput, |
| 83 | input: ManagedEndpointAllocationKey, | 83 | ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>; |
| 84 | ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>; | 84 | readonly markReady: ( |
| 85 | readonly remove: ( | 85 | input: ManagedEndpointAllocationKey, |
| 86 | input: ManagedEndpointAllocationKey, | 86 | ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>; |
| 87 | ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>; | 87 | readonly remove: ( |
| 88 | } | 88 | input: ManagedEndpointAllocationKey, |
| 89 | ) => Effect.Effect<void, ManagedEndpointAllocationPersistenceError>; | ||
| 90 | } | ||
| 91 | >()("t3code-relay/environments/ManagedEndpointAllocations") {} | ||
| 89 | 92 | ||
| 90 | const allocationSelection = { | 93 | const allocationSelection = { |
| 91 | userId: relayManagedEndpointAllocations.userId, | 94 | userId: relayManagedEndpointAllocations.userId, |
| @@ -109,7 +112,7 @@ const persistenceError = (cause: unknown) => · 17 unmodified lines | |||
| 109 | : new ManagedEndpointAllocationPersistenceError({ cause }); | 112 | : new ManagedEndpointAllocationPersistenceError({ cause }); |
| 110 | 113 | ||
| 111 | const make = Effect.gen(function* () { | 114 | const make = Effect.gen(function* () { |
| 112 | const db = yield* RelayDb; | 115 | const db = yield* RelayDb.RelayDb; |
| 113 | 116 | ||
| 114 | return ManagedEndpointAllocations.of({ | 117 | return ManagedEndpointAllocations.of({ |
| 115 | get: Effect.fn("relay.managed_endpoint_allocations.get")(function* ( | 118 | get: Effect.fn("relay.managed_endpoint_allocations.get")(function* ( |
| @@ -198,9 +201,4 @@ const make = Effect.gen(function* () { · 82 unmodified lines | |||
| 198 | }); | 201 | }); |
| 199 | }); | 202 | }); |
| 200 | 203 | ||
| 201 | export class ManagedEndpointAllocations extends Context.Service< | 204 | export const layer = Layer.effect(ManagedEndpointAllocations, make); |
| 202 | ManagedEndpointAllocations, | ||
| 203 | ManagedEndpointAllocationsShape | ||
| 204 | >()("t3code-relay/environments/ManagedEndpointAllocations") { | ||
| 205 | static readonly layer = Layer.effect(this, make); | ||
| 206 | } | ||
| @@ -204,9 +204,9 @@ function providerLayer( · 203 unmodified lines | |||
| 204 | ) { | 204 | ) { |
| 205 | return ManagedEndpointProvider.layer.pipe( | 205 | return ManagedEndpointProvider.layer.pipe( |
| 206 | Layer.provideMerge(NodeServices.layer), | 206 | Layer.provideMerge(NodeServices.layer), |
| 207 | Layer.provide(Layer.succeed(RelayConfiguration.RelayConfiguration, config)), | 207 | Layer.provide(RelayConfiguration.layer(config)), |
| 208 | Layer.provide(Layer.succeed(ManagedEndpointProvider.ManagedEndpointTunnelClient, tunnelClient)), | 208 | Layer.provide(ManagedEndpointProvider.layerTunnelClient(tunnelClient)), |
| 209 | Layer.provide(Layer.succeed(ManagedEndpointProvider.ManagedEndpointDnsClient, dnsClient)), | 209 | Layer.provide(ManagedEndpointProvider.layerDnsClient(dnsClient)), |
| 210 | Layer.provide( | 210 | Layer.provide( |
| 211 | Layer.succeed(ManagedEndpointAllocations.ManagedEndpointAllocations, allocations), | 211 | Layer.succeed(ManagedEndpointAllocations.ManagedEndpointAllocations, allocations), |
| 212 | ), | 212 | ), |
| @@ -23,7 +23,7 @@ import { · 22 unmodified lines | |||
| 23 | managedEndpointHostname, | 23 | managedEndpointHostname, |
| 24 | managedEndpointTunnelName, | 24 | managedEndpointTunnelName, |
| 25 | } from "../deploymentConfig.ts"; | 25 | } from "../deploymentConfig.ts"; |
| 26 | import { ManagedEndpointAllocations } from "./ManagedEndpointAllocations.ts"; | 26 | import * as ManagedEndpointAllocations from "./ManagedEndpointAllocations.ts"; |
| 27 | 27 | ||
| 28 | export class ManagedEndpointProvisioningNotConfigured extends Schema.TaggedErrorClass<ManagedEndpointProvisioningNotConfigured>()( | 28 | export class ManagedEndpointProvisioningNotConfigured extends Schema.TaggedErrorClass<ManagedEndpointProvisioningNotConfigured>()( |
| 29 | "ManagedEndpointProvisioningNotConfigured", | 29 | "ManagedEndpointProvisioningNotConfigured", |
| @@ -74,21 +74,19 @@ export interface ManagedEndpointProvisioningResult { · 44 unmodified lines | |||
| 74 | readonly runtime: RelayManagedEndpointRuntimeConfig; | 74 | readonly runtime: RelayManagedEndpointRuntimeConfig; |
| 75 | } | 75 | } |
| 76 | 76 | ||
| 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 | |||
| 89 | export class ManagedEndpointProvider extends Context.Service< | 77 | export class ManagedEndpointProvider extends Context.Service< |
| 90 | ManagedEndpointProvider, | 78 | 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 | } | ||
| 92 | >()("t3code-relay/environments/ManagedEndpointProvider") {} | 90 | >()("t3code-relay/environments/ManagedEndpointProvider") {} |
| 93 | 91 | ||
| 94 | interface ManagedEndpointTunnel { | 92 | interface ManagedEndpointTunnel { |
| @@ -105,36 +103,42 @@ export class ManagedEndpointTunnelClientError extends Schema.TaggedErrorClass<Ma · 10 unmodified lines | |||
| 105 | } | 103 | } |
| 106 | } | 104 | } |
| 107 | 105 | ||
| 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 | |||
| 133 | export class ManagedEndpointTunnelClient extends Context.Service< | 106 | export class ManagedEndpointTunnelClient extends Context.Service< |
| 134 | ManagedEndpointTunnelClient, | 107 | 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 | } | ||
| 136 | >()("t3code-relay/environments/ManagedEndpointProvider/ManagedEndpointTunnelClient") {} | 134 | >()("t3code-relay/environments/ManagedEndpointProvider/ManagedEndpointTunnelClient") {} |
| 137 | 135 | ||
| 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 | |||
| 138 | interface ManagedEndpointCnameRecordInput { | 142 | interface ManagedEndpointCnameRecordInput { |
| 139 | readonly type: "CNAME"; | 143 | readonly type: "CNAME"; |
| 140 | readonly name: string; | 144 | readonly name: string; |
| @@ -152,29 +156,33 @@ export class ManagedEndpointDnsClientError extends Schema.TaggedErrorClass<Manag · 11 unmodified lines | |||
| 152 | } | 156 | } |
| 153 | } | 157 | } |
| 154 | 158 | ||
| 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 | |||
| 171 | export class ManagedEndpointDnsClient extends Context.Service< | 159 | export class ManagedEndpointDnsClient extends Context.Service< |
| 172 | ManagedEndpointDnsClient, | 160 | 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 | } | ||
| 174 | >()("t3code-relay/environments/ManagedEndpointProvider/ManagedEndpointDnsClient") {} | 176 | >()("t3code-relay/environments/ManagedEndpointProvider/ManagedEndpointDnsClient") {} |
| 175 | 177 | ||
| 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 | |||
| 176 | const requireCloudflareSettings = Effect.fnUntraced(function* ( | 184 | const requireCloudflareSettings = Effect.fnUntraced(function* ( |
| 177 | settings: RelayConfiguration.RelayConfigurationShape, | 185 | settings: RelayConfiguration.RelayConfiguration["Service"], |
| 178 | ) { | 186 | ) { |
| 179 | if (!settings.managedEndpointBaseDomain || !settings.managedEndpointNamespace) { | 187 | if (!settings.managedEndpointBaseDomain || !settings.managedEndpointNamespace) { |
| 180 | return yield* new ManagedEndpointProvisioningNotConfigured(); | 188 | return yield* new ManagedEndpointProvisioningNotConfigured(); |
| @@ -234,7 +242,7 @@ const make = Effect.gen(function* () { · 53 unmodified lines | |||
| 234 | const crypto = yield* Crypto.Crypto; | 242 | const crypto = yield* Crypto.Crypto; |
| 235 | const tunnels = yield* ManagedEndpointTunnelClient; | 243 | const tunnels = yield* ManagedEndpointTunnelClient; |
| 236 | const dns = yield* ManagedEndpointDnsClient; | 244 | const dns = yield* ManagedEndpointDnsClient; |
| 237 | const allocations = yield* ManagedEndpointAllocations; | 245 | const allocations = yield* ManagedEndpointAllocations.ManagedEndpointAllocations; |
| 238 | 246 | ||
| 239 | const updateExistingDnsRecords = Effect.fnUntraced(function* ( | 247 | const updateExistingDnsRecords = Effect.fnUntraced(function* ( |
| 240 | records: ReadonlyArray<{ readonly id: string }>, | 248 | records: ReadonlyArray<{ readonly id: string }>, |
| @@ -452,8 +460,7 @@ export const layerCloudflareBindings = ( · 211 unmodified lines | |||
| 452 | layer.pipe( | 460 | layer.pipe( |
| 453 | Layer.provide( | 461 | Layer.provide( |
| 454 | Layer.mergeAll( | 462 | Layer.mergeAll( |
| 455 | Layer.succeed( | 463 | layerTunnelClient( |
| 456 | ManagedEndpointTunnelClient, | ||
| 457 | ManagedEndpointTunnelClient.of({ | 464 | ManagedEndpointTunnelClient.of({ |
| 458 | list: (request) => | 465 | list: (request) => |
| 459 | tunnelClient.list(request).pipe( | 466 | tunnelClient.list(request).pipe( |
| @@ -482,8 +489,7 @@ export const layerCloudflareBindings = ( · 22 unmodified lines | |||
| 482 | ), | 489 | ), |
| 483 | }), | 490 | }), |
| 484 | ), | 491 | ), |
| 485 | Layer.succeed( | 492 | layerDnsClient( |
| 486 | ManagedEndpointDnsClient, | ||
| 487 | ManagedEndpointDnsClient.of({ | 493 | ManagedEndpointDnsClient.of({ |
| 488 | listRecords: (hostname) => | 494 | listRecords: (hostname) => |
| 489 | dnsClient.listDnsRecords({ search: hostname }).pipe( | 495 | dnsClient.listDnsRecords({ search: hostname }).pipe( |
| @@ -30,7 +30,7 @@ vi.mock("@clerk/backend", () => ({ · 29 unmodified lines | |||
| 30 | verifyToken: vi.fn(), | 30 | verifyToken: vi.fn(), |
| 31 | })); | 31 | })); |
| 32 | 32 | ||
| 33 | const relaySettings: RelayConfiguration.RelayConfigurationShape = { | 33 | const relaySettings: RelayConfiguration.RelayConfiguration["Service"] = { |
| 34 | relayIssuer: "https://relay.example.test", | 34 | relayIssuer: "https://relay.example.test", |
| 35 | apns: { | 35 | apns: { |
| 36 | teamId: "apns-team", | 36 | teamId: "apns-team", |
| @@ -110,7 +110,7 @@ describe("relay environment authentication", () => { · 73 unmodified lines | |||
| 110 | const failure = new EnvironmentCredentials.EnvironmentCredentialAuthenticatePersistenceError({ | 110 | const failure = new EnvironmentCredentials.EnvironmentCredentialAuthenticatePersistenceError({ |
| 111 | cause: "database unavailable", | 111 | cause: "database unavailable", |
| 112 | }); | 112 | }); |
| 113 | const credentials: EnvironmentCredentials.EnvironmentCredentialsShape = { | 113 | const credentials: EnvironmentCredentials.EnvironmentCredentials["Service"] = { |
| 114 | create: () => Effect.die("unused create"), | 114 | create: () => Effect.die("unused create"), |
| 115 | authenticate: () => Effect.fail(failure), | 115 | authenticate: () => Effect.fail(failure), |
| 116 | revokeForEnvironmentPublicKey: () => Effect.die("unused revoke"), | 116 | revokeForEnvironmentPublicKey: () => Effect.die("unused revoke"), |
| @@ -66,7 +66,7 @@ import * as ManagedEndpointAllocations from "../environments/ManagedEndpointAllo · 65 unmodified lines | |||
| 66 | import * as EnvironmentPublishSignatures from "../environments/EnvironmentPublishSignatures.ts"; | 66 | import * as EnvironmentPublishSignatures from "../environments/EnvironmentPublishSignatures.ts"; |
| 67 | import * as MobileRegistrations from "../agentActivity/MobileRegistrations.ts"; | 67 | import * as MobileRegistrations from "../agentActivity/MobileRegistrations.ts"; |
| 68 | import { withSpanAttributes } from "../observability.ts"; | 68 | import { withSpanAttributes } from "../observability.ts"; |
| 69 | import { RelayDb } from "../db.ts"; | 69 | import * as RelayDb from "../db.ts"; |
| 70 | 70 | ||
| 71 | const relayCorsAllowedMethods = ["GET", "POST", "DELETE", "OPTIONS"] as const; | 71 | const relayCorsAllowedMethods = ["GET", "POST", "DELETE", "OPTIONS"] as const; |
| 72 | const relayCorsAllowedHeaders = [ | 72 | const relayCorsAllowedHeaders = [ |
| @@ -347,7 +347,7 @@ export const healthApi = HttpApiBuilder.group( · 274 unmodified lines | |||
| 347 | RelayApi, | 347 | RelayApi, |
| 348 | "health", | 348 | "health", |
| 349 | Effect.fnUntraced(function* (handlers) { | 349 | Effect.fnUntraced(function* (handlers) { |
| 350 | const db = yield* RelayDb; | 350 | const db = yield* RelayDb.RelayDb; |
| 351 | return handlers.handle( | 351 | return handlers.handle( |
| 352 | "health", | 352 | "health", |
| 353 | Effect.fn("relay.api.health")( | 353 | Effect.fn("relay.api.health")( |
| @@ -985,7 +985,10 @@ function hasExpectedClerkAudience(audience: unknown, expectedAudience: string): · 631 unmodified lines | |||
| 985 | audience.some((entry) => typeof entry === "string" && entry === expectedAudience); | 985 | audience.some((entry) => typeof entry === "string" && entry === expectedAudience); |
| 986 | } | 986 | } |
| 987 | 987 | ||
| 988 | function verifyClerkBearerToken(config: RelayConfiguration.RelayConfigurationShape, token: string) { | 988 | function verifyClerkBearerToken( |
| 989 | config: RelayConfiguration.RelayConfiguration["Service"], | ||
| 990 | token: string, | ||
| 991 | ) { | ||
| 989 | return Effect.tryPromise({ | 992 | return Effect.tryPromise({ |
| 990 | try: () => | 993 | try: () => |
| 991 | verifyToken(token, { | 994 | verifyToken(token, { |
| @@ -1001,7 +1004,7 @@ function verifyClerkBearerToken(config: RelayConfiguration.RelayConfigurationSha · 9 unmodified lines | |||
| 1001 | } | 1004 | } |
| 1002 | 1005 | ||
| 1003 | function verifyClerkOAuthBearerToken( | 1006 | function verifyClerkOAuthBearerToken( |
| 1004 | config: RelayConfiguration.RelayConfigurationShape, | 1007 | config: RelayConfiguration.RelayConfiguration["Service"], |
| 1005 | token: string, | 1008 | token: string, |
| 1006 | ) { | 1009 | ) { |
| 1007 | return Effect.tryPromise({ | 1010 | return Effect.tryPromise({ |
| @@ -1027,7 +1030,7 @@ function verifyClerkOAuthBearerToken( · 19 unmodified lines | |||
| 1027 | } | 1030 | } |
| 1028 | 1031 | ||
| 1029 | export function verifyRelayClientBearerToken( | 1032 | export function verifyRelayClientBearerToken( |
| 1030 | config: RelayConfiguration.RelayConfigurationShape, | 1033 | config: RelayConfiguration.RelayConfiguration["Service"], |
| 1031 | token: string, | 1034 | token: string, |
| 1032 | ) { | 1035 | ) { |
| 1033 | return verifyClerkBearerToken(config, token).pipe( | 1036 | return verifyClerkBearerToken(config, token).pipe( |
| @@ -9,7 +9,7 @@ import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest"; · 8 unmodified lines | |||
| 9 | import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse"; | 9 | import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse"; |
| 10 | import type { OtlpTracer } from "effect/unstable/observability"; | 10 | import type { OtlpTracer } from "effect/unstable/observability"; |
| 11 | 11 | ||
| 12 | import { EnvironmentConnectNotAuthorized } from "./environments/EnvironmentConnector.ts"; | 12 | import * as EnvironmentConnector from "./environments/EnvironmentConnector.ts"; |
| 13 | import { makeRelayTraceLayer } from "./observability.ts"; | 13 | import { makeRelayTraceLayer } from "./observability.ts"; |
| 14 | 14 | ||
| 15 | interface ExportedRequest { | 15 | interface ExportedRequest { |
| @@ -43,7 +43,7 @@ it.effect("exports schema error fields as span attributes", () => · 27 unmodified lines | |||
| 43 | ); | 43 | ); |
| 44 | 44 | ||
| 45 | yield* Effect.fail( | 45 | yield* Effect.fail( |
| 46 | new EnvironmentConnectNotAuthorized({ | 46 | new EnvironmentConnector.EnvironmentConnectNotAuthorized({ |
| 47 | environmentId: "environment-1", | 47 | environmentId: "environment-1", |
| 48 | operation: "connect", | 48 | operation: "connect", |
| 49 | reason: "managed_endpoint_allocation_not_ready", | 49 | reason: "managed_endpoint_allocation_not_ready", |
| @@ -42,7 +42,7 @@ import * as EnvironmentCredentials from "./environments/EnvironmentCredentials.t · 41 unmodified lines | |||
| 42 | import * as EnvironmentLinks from "./environments/EnvironmentLinks.ts"; | 42 | import * as EnvironmentLinks from "./environments/EnvironmentLinks.ts"; |
| 43 | import * as ManagedEndpointAllocations from "./environments/ManagedEndpointAllocations.ts"; | 43 | import * as ManagedEndpointAllocations from "./environments/ManagedEndpointAllocations.ts"; |
| 44 | import * as LiveActivities from "./agentActivity/LiveActivities.ts"; | 44 | import * as LiveActivities from "./agentActivity/LiveActivities.ts"; |
| 45 | import { RelayDb, RelayHyperdrive } from "./db.ts"; | 45 | import * as RelayDb from "./db.ts"; |
| 46 | import { RelayApnsDeliveryDeadLetterQueue, RelayApnsDeliveryQueue } from "./queues.ts"; | 46 | import { RelayApnsDeliveryDeadLetterQueue, RelayApnsDeliveryQueue } from "./queues.ts"; |
| 47 | import * as RelayConfiguration from "./Config.ts"; | 47 | import * as RelayConfiguration from "./Config.ts"; |
| 48 | import * as AgentActivityPublisher from "./agentActivity/AgentActivityPublisher.ts"; | 48 | import * as AgentActivityPublisher from "./agentActivity/AgentActivityPublisher.ts"; |
| @@ -138,7 +138,7 @@ export default class Api extends Cloudflare.Worker<Api>()( · 89 unmodified lines | |||
| 138 | 138 | ||
| 139 | const cloudMintPrivateKey = yield* cloudMintKeyPair.privateKey; | 139 | const cloudMintPrivateKey = yield* cloudMintKeyPair.privateKey; |
| 140 | const cloudMintPublicKey = yield* cloudMintKeyPair.publicKey; | 140 | const cloudMintPublicKey = yield* cloudMintKeyPair.publicKey; |
| 141 | const hyperdrive = yield* Cloudflare.Hyperdrive.bind(yield* RelayHyperdrive); | 141 | const hyperdrive = yield* Cloudflare.Hyperdrive.bind(yield* RelayDb.RelayHyperdrive); |
| 142 | const db = yield* Drizzle.postgres(hyperdrive.connectionString); | 142 | const db = yield* Drizzle.postgres(hyperdrive.connectionString); |
| 143 | 143 | ||
| 144 | const managedEndpointTunnelBinding = yield* Cloudflare.TunnelReadWrite.bind(); | 144 | const managedEndpointTunnelBinding = yield* Cloudflare.TunnelReadWrite.bind(); |
| @@ -203,16 +203,11 @@ export default class Api extends Cloudflare.Worker<Api>()( · 58 unmodified lines | |||
| 203 | Layer.provideMerge(AgentActivityRows.layer), | 203 | Layer.provideMerge(AgentActivityRows.layer), |
| 204 | Layer.provideMerge(Devices.layer), | 204 | Layer.provideMerge(Devices.layer), |
| 205 | Layer.provideMerge(EnvironmentCredentials.layer), | 205 | Layer.provideMerge(EnvironmentCredentials.layer), |
| 206 | Layer.provideMerge( | 206 | Layer.provideMerge(Layer.mergeAll(EnvironmentLinks.layer, ManagedEndpointAllocations.layer)), |
| 207 | Layer.mergeAll( | ||
| 208 | EnvironmentLinks.layer, | ||
| 209 | ManagedEndpointAllocations.ManagedEndpointAllocations.layer, | ||
| 210 | ), | ||
| 211 | ), | ||
| 212 | Layer.provideMerge(LiveActivities.layer), | 207 | Layer.provideMerge(LiveActivities.layer), |
| 213 | Layer.provideMerge(DeliveryAttempts.layer), | 208 | Layer.provideMerge(DeliveryAttempts.layer), |
| 214 | Layer.provideMerge(RelayTokens.layer), | 209 | Layer.provideMerge(RelayTokens.layer), |
| 215 | Layer.provideMerge(Layer.succeed(RelayDb, db)), | 210 | Layer.provideMerge(Layer.succeed(RelayDb.RelayDb, db)), |
| 216 | Layer.provideMerge(Layer.effect(RelayConfiguration.RelayConfiguration, loadSettings)), | 211 | Layer.provideMerge(Layer.effect(RelayConfiguration.RelayConfiguration, loadSettings)), |
| 217 | Layer.provideMerge(webcryptoLayer), | 212 | Layer.provideMerge(webcryptoLayer), |
| 218 | ); | 213 | ); |
| @@ -37,6 +37,10 @@ export function createPreviewEnvironmentAtoms<R, E>( · 36 unmodified lines | |||
| 37 | automationRequests: createEnvironmentRpcSubscriptionAtomFamily(runtime, { | 37 | automationRequests: createEnvironmentRpcSubscriptionAtomFamily(runtime, { |
| 38 | label: "environment-data:preview:automation-requests", | 38 | label: "environment-data:preview:automation-requests", |
| 39 | tag: WS_METHODS.previewAutomationConnect, | 39 | 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, | ||
| 40 | }), | 44 | }), |
| 41 | open: createEnvironmentRpcCommand(runtime, { | 45 | open: createEnvironmentRpcCommand(runtime, { |
| 42 | label: "environment-data:preview:open", | 46 | label: "environment-data:preview:open", |
| @@ -125,6 +125,8 @@ export const VcsListRefsInput = Schema.Struct({ · 124 unmodified lines | |||
| 125 | cwd: TrimmedNonEmptyStringSchema, | 125 | cwd: TrimmedNonEmptyStringSchema, |
| 126 | query: Schema.optional(TrimmedNonEmptyStringSchema.check(Schema.isMaxLength(256))), | 126 | query: Schema.optional(TrimmedNonEmptyStringSchema.check(Schema.isMaxLength(256))), |
| 127 | cursor: Schema.optional(NonNegativeInt), | 127 | cursor: Schema.optional(NonNegativeInt), |
| 128 | includeMatchingRemoteRefs: Schema.optional(Schema.Boolean), | ||
| 129 | refKind: Schema.optional(Schema.Literals(["all", "local", "remote"])), | ||
| 128 | limit: Schema.optional( | 130 | limit: Schema.optional( |
| 129 | PositiveInt.check(Schema.isLessThanOrEqualTo(GIT_LIST_BRANCHES_MAX_LIMIT)), | 131 | PositiveInt.check(Schema.isLessThanOrEqualTo(GIT_LIST_BRANCHES_MAX_LIMIT)), |
| 130 | ), | 132 | ), |
| @@ -75,6 +75,7 @@ import { · 74 unmodified lines | |||
| 75 | PreviewAutomationClickInput, | 75 | PreviewAutomationClickInput, |
| 76 | PreviewAutomationEvaluateInput, | 76 | PreviewAutomationEvaluateInput, |
| 77 | PreviewAutomationOwner, | 77 | PreviewAutomationOwner, |
| 78 | PreviewAutomationOwnerIdentity, | ||
| 78 | PreviewAutomationPressInput, | 79 | PreviewAutomationPressInput, |
| 79 | PreviewAutomationRequest, | 80 | PreviewAutomationRequest, |
| 80 | PreviewAutomationResponse, | 81 | PreviewAutomationResponse, |
| @@ -1232,7 +1233,7 @@ export interface EnvironmentApi { · 1151 unmodified lines | |||
| 1232 | ) => () => void; | 1233 | ) => () => void; |
| 1233 | respond: (response: PreviewAutomationResponse) => Promise<void>; | 1234 | respond: (response: PreviewAutomationResponse) => Promise<void>; |
| 1234 | reportOwner: (owner: PreviewAutomationOwner) => Promise<void>; | 1235 | reportOwner: (owner: PreviewAutomationOwner) => Promise<void>; |
| 1235 | clearOwner: (input: { clientId: string }) => Promise<void>; | 1236 | clearOwner: (input: PreviewAutomationOwnerIdentity) => Promise<void>; |
| 1236 | }; | 1237 | }; |
| 1237 | onEvent: ( | 1238 | onEvent: ( |
| 1238 | callback: (event: PreviewEvent) => void, | 1239 | callback: (event: PreviewEvent) => void, |
| @@ -410,10 +410,15 @@ export const PreviewAutomationRecordingArtifact = Schema.Struct({ · 409 unmodified lines | |||
| 410 | }); | 410 | }); |
| 411 | export type PreviewAutomationRecordingArtifact = typeof PreviewAutomationRecordingArtifact.Type; | 411 | export type PreviewAutomationRecordingArtifact = typeof PreviewAutomationRecordingArtifact.Type; |
| 412 | 412 | ||
| 413 | export const PreviewAutomationOwner = Schema.Struct({ | 413 | export const PreviewAutomationOwnerIdentity = Schema.Struct({ |
| 414 | clientId: TrimmedNonEmptyString, | 414 | clientId: TrimmedNonEmptyString, |
| 415 | environmentId: EnvironmentId, | 415 | environmentId: EnvironmentId, |
| 416 | threadId: ThreadId, | 416 | threadId: ThreadId, |
| 417 | }); | ||
| 418 | export type PreviewAutomationOwnerIdentity = typeof PreviewAutomationOwnerIdentity.Type; | ||
| 419 | |||
| 420 | export const PreviewAutomationOwner = Schema.Struct({ | ||
| 421 | ...PreviewAutomationOwnerIdentity.fields, | ||
| 417 | tabId: Schema.NullOr(PreviewTabId), | 422 | tabId: Schema.NullOr(PreviewTabId), |
| 418 | visible: Schema.Boolean, | 423 | visible: Schema.Boolean, |
| 419 | supportsAutomation: Schema.Boolean, | 424 | supportsAutomation: Schema.Boolean, |
| @@ -1,5 +1,5 @@ | |||
| 1 | import * as Schema from "effect/Schema"; | ||
| 2 | import * as Context from "effect/Context"; | 1 | import * as Context from "effect/Context"; |
| 2 | import * as Schema from "effect/Schema"; | ||
| 3 | import * as HttpApi from "effect/unstable/httpapi/HttpApi"; | 3 | import * as HttpApi from "effect/unstable/httpapi/HttpApi"; |
| 4 | import * as HttpApiEndpoint from "effect/unstable/httpapi/HttpApiEndpoint"; | 4 | import * as HttpApiEndpoint from "effect/unstable/httpapi/HttpApiEndpoint"; |
| 5 | import * as HttpApiGroup from "effect/unstable/httpapi/HttpApiGroup"; | 5 | import * as HttpApiGroup from "effect/unstable/httpapi/HttpApiGroup"; |
| @@ -512,26 +512,22 @@ const RelayAgentActivityPublishErrors = [ · 506 unmodified lines | |||
| 512 | RelayInternalError, | 512 | RelayInternalError, |
| 513 | ] as const; | 513 | ] as const; |
| 514 | 514 | ||
| 515 | export interface RelayClientPrincipalShape { | ||
| 516 | readonly userId: string; | ||
| 517 | readonly token: string; | ||
| 518 | readonly proofKeyThumbprint?: string; | ||
| 519 | readonly dpopScopes?: ReadonlyArray<RelayDpopAccessTokenScope>; | ||
| 520 | } | ||
| 521 | |||
| 522 | export class RelayClientPrincipal extends Context.Service< | 515 | export class RelayClientPrincipal extends Context.Service< |
| 523 | RelayClientPrincipal, | 516 | RelayClientPrincipal, |
| 524 | RelayClientPrincipalShape | 517 | { |
| 518 | readonly userId: string; | ||
| 519 | readonly token: string; | ||
| 520 | readonly proofKeyThumbprint?: string; | ||
| 521 | readonly dpopScopes?: ReadonlyArray<RelayDpopAccessTokenScope>; | ||
| 522 | } | ||
| 525 | >()("@t3tools/contracts/relay/RelayClientPrincipal") {} | 523 | >()("@t3tools/contracts/relay/RelayClientPrincipal") {} |
| 526 | 524 | ||
| 527 | export interface RelayEnvironmentPrincipalShape { | ||
| 528 | readonly environmentId: string; | ||
| 529 | readonly environmentPublicKey: string; | ||
| 530 | } | ||
| 531 | |||
| 532 | export class RelayEnvironmentPrincipal extends Context.Service< | 525 | export class RelayEnvironmentPrincipal extends Context.Service< |
| 533 | RelayEnvironmentPrincipal, | 526 | RelayEnvironmentPrincipal, |
| 534 | RelayEnvironmentPrincipalShape | 527 | { |
| 528 | readonly environmentId: string; | ||
| 529 | readonly environmentPublicKey: string; | ||
| 530 | } | ||
| 535 | >()("@t3tools/contracts/relay/RelayEnvironmentPrincipal") {} | 531 | >()("@t3tools/contracts/relay/RelayEnvironmentPrincipal") {} |
| 536 | 532 | ||
| 537 | const RelayClientBearerAuthorization = HttpApiSecurity.http({ scheme: "bearer" }).pipe( | 533 | const RelayClientBearerAuthorization = HttpApiSecurity.http({ scheme: "bearer" }).pipe( |
| @@ -6,6 +6,7 @@ import { VcsError } from "./vcs.ts"; · 5 unmodified lines | |||
| 6 | export const ReviewDiffPreviewInput = Schema.Struct({ | 6 | export const ReviewDiffPreviewInput = Schema.Struct({ |
| 7 | cwd: TrimmedNonEmptyString, | 7 | cwd: TrimmedNonEmptyString, |
| 8 | baseRef: Schema.optional(TrimmedNonEmptyString), | 8 | baseRef: Schema.optional(TrimmedNonEmptyString), |
| 9 | ignoreWhitespace: Schema.optionalKey(Schema.Boolean), | ||
| 9 | }); | 10 | }); |
| 10 | export type ReviewDiffPreviewInput = typeof ReviewDiffPreviewInput.Type; | 11 | export type ReviewDiffPreviewInput = typeof ReviewDiffPreviewInput.Type; |
| 11 | 12 | ||
| @@ -108,6 +108,7 @@ import { · 107 unmodified lines | |||
| 108 | import { | 108 | import { |
| 109 | PreviewAutomationError, | 109 | PreviewAutomationError, |
| 110 | PreviewAutomationOwner, | 110 | PreviewAutomationOwner, |
| 111 | PreviewAutomationOwnerIdentity, | ||
| 111 | PreviewAutomationRequest, | 112 | PreviewAutomationRequest, |
| 112 | PreviewAutomationResponse, | 113 | PreviewAutomationResponse, |
| 113 | } from "./previewAutomation.ts"; | 114 | } from "./previewAutomation.ts"; |
| @@ -549,7 +550,7 @@ export const WsPreviewReportStatusRpc = Rpc.make(WS_METHODS.previewReportStatus, · 435 unmodified lines | |||
| 549 | }); | 550 | }); |
| 550 | 551 | ||
| 551 | export const WsPreviewAutomationConnectRpc = Rpc.make(WS_METHODS.previewAutomationConnect, { | 552 | export const WsPreviewAutomationConnectRpc = Rpc.make(WS_METHODS.previewAutomationConnect, { |
| 552 | payload: Schema.Struct({ clientId: Schema.String }), | 553 | payload: PreviewAutomationOwner, |
| 553 | success: PreviewAutomationRequest, | 554 | success: PreviewAutomationRequest, |
| 554 | error: Schema.Union([PreviewAutomationError, EnvironmentAuthorizationError]), | 555 | error: Schema.Union([PreviewAutomationError, EnvironmentAuthorizationError]), |
| 555 | stream: true, | 556 | stream: true, |
| @@ -566,7 +567,7 @@ export const WsPreviewAutomationReportOwnerRpc = Rpc.make(WS_METHODS.previewAuto · 10 unmodified lines | |||
| 566 | }); | 567 | }); |
| 567 | 568 | ||
| 568 | export const WsPreviewAutomationClearOwnerRpc = Rpc.make(WS_METHODS.previewAutomationClearOwner, { | 569 | export const WsPreviewAutomationClearOwnerRpc = Rpc.make(WS_METHODS.previewAutomationClearOwner, { |
| 569 | payload: Schema.Struct({ clientId: Schema.String }), | 570 | payload: PreviewAutomationOwnerIdentity, |
| 570 | error: Schema.Union([PreviewAutomationError, EnvironmentAuthorizationError]), | 571 | error: Schema.Union([PreviewAutomationError, EnvironmentAuthorizationError]), |
| 571 | }); | 572 | }); |
| 572 | 573 | ||
| @@ -194,10 +194,10 @@ importers: · 193 unmodified lines | |||
| 194 | dependencies: | 194 | dependencies: |
| 195 | '@callstack/liquid-glass': | 195 | '@callstack/liquid-glass': |
| 196 | specifier: ^0.7.1 | 196 | 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) |
| 198 | '@clerk/expo': | 198 | '@clerk/expo': |
| 199 | specifier: 3.4.8-snapshot.v20260619001138 | 199 | 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) |
| 201 | '@effect/atom-react': | 201 | '@effect/atom-react': |
| 202 | specifier: 4.0.0-beta.78 | 202 | specifier: 4.0.0-beta.78 |
| 203 | version: 4.0.0-beta.78(effect@4.0.0-beta.78(patch_hash=c502bc684210b707dfceb87d8fe6ad6843395af6e19cfc02cd65854898bde2c5))(react@19.2.3)(scheduler@0.27.0) | 203 | 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 | |||
| 206 | version: 0.4.2 | 206 | version: 0.4.2 |
| 207 | '@expo/ui': | 207 | '@expo/ui': |
| 208 | specifier: ~56.0.8 | 208 | specifier: ~56.0.8 |
| 209 | version: 56.0.15(961c4aa6f32829b318e3c87ef20ad401) | 209 | version: 56.0.15(ab3f255d102c60ba0fd2bbe4e47ba584) |
| 210 | '@legendapp/list': | 210 | '@legendapp/list': |
| 211 | specifier: 3.0.0-beta.44 | 211 | 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) |
| 213 | '@noble/curves': | 213 | '@noble/curves': |
| 214 | specifier: 'catalog:' | 214 | specifier: 'catalog:' |
| 215 | version: 1.9.1 | 215 | version: 1.9.1 |
| @@ -221,7 +221,7 @@ importers: · 5 unmodified lines | |||
| 221 | 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) | 221 | 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) |
| 222 | '@react-native-menu/menu': | 222 | '@react-native-menu/menu': |
| 223 | specifier: ^2.0.0 | 223 | 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) |
| 225 | '@shikijs/core': | 225 | '@shikijs/core': |
| 226 | specifier: 4.2.0 | 226 | specifier: 4.2.0 |
| 227 | version: 4.2.0 | 227 | version: 4.2.0 |
| @@ -242,7 +242,7 @@ importers: · 14 unmodified lines | |||
| 242 | version: link:../../packages/contracts | 242 | version: link:../../packages/contracts |
| 243 | '@t3tools/mobile-markdown-text': | 243 | '@t3tools/mobile-markdown-text': |
| 244 | specifier: file:./modules/t3-markdown-text | 244 | 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) |
| 246 | '@t3tools/mobile-review-diff-native': | 246 | '@t3tools/mobile-review-diff-native': |
| 247 | specifier: file:./modules/t3-review-diff | 247 | specifier: file:./modules/t3-review-diff |
| 248 | version: file:apps/mobile/modules/t3-review-diff | 248 | version: file:apps/mobile/modules/t3-review-diff |
| @@ -263,40 +263,40 @@ importers: · 14 unmodified lines | |||
| 263 | version: 4.0.0-beta.78(patch_hash=c502bc684210b707dfceb87d8fe6ad6843395af6e19cfc02cd65854898bde2c5) | 263 | version: 4.0.0-beta.78(patch_hash=c502bc684210b707dfceb87d8fe6ad6843395af6e19cfc02cd65854898bde2c5) |
| 264 | expo: | 264 | expo: |
| 265 | specifier: ^56.0.0 | 265 | 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) |
| 267 | expo-asset: | 267 | expo-asset: |
| 268 | specifier: ~56.0.15 | 268 | 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) |
| 270 | expo-auth-session: | 270 | expo-auth-session: |
| 271 | specifier: ~56.0.12 | 271 | 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) |
| 273 | expo-build-properties: | 273 | expo-build-properties: |
| 274 | specifier: ~56.0.15 | 274 | specifier: ~56.0.15 |
| 275 | version: 56.0.16(expo@56.0.8) | 275 | version: 56.0.16(expo@56.0.8) |
| 276 | expo-camera: | 276 | expo-camera: |
| 277 | specifier: ~56.0.7 | 277 | 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) |
| 279 | expo-clipboard: | 279 | expo-clipboard: |
| 280 | specifier: ~56.0.3 | 280 | 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) |
| 282 | expo-constants: | 282 | expo-constants: |
| 283 | specifier: ~56.0.16 | 283 | 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)) |
| 285 | expo-crypto: | 285 | expo-crypto: |
| 286 | specifier: ~56.0.4 | 286 | specifier: ~56.0.4 |
| 287 | version: 56.0.4(expo@56.0.8) | 287 | version: 56.0.4(expo@56.0.8) |
| 288 | expo-dev-client: | 288 | expo-dev-client: |
| 289 | specifier: ~56.0.16 | 289 | 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)) |
| 291 | expo-file-system: | 291 | expo-file-system: |
| 292 | specifier: ~56.0.7 | 292 | 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)) |
| 294 | expo-font: | 294 | expo-font: |
| 295 | specifier: ~56.0.5 | 295 | 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) |
| 297 | expo-glass-effect: | 297 | expo-glass-effect: |
| 298 | specifier: ~56.0.4 | 298 | 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) |
| 300 | expo-haptics: | 300 | expo-haptics: |
| 301 | specifier: ~56.0.3 | 301 | specifier: ~56.0.3 |
| 302 | version: 56.0.3(expo@56.0.8) | 302 | version: 56.0.3(expo@56.0.8) |
| @@ -305,19 +305,19 @@ importers: · 2 unmodified lines | |||
| 305 | version: 56.0.15(expo@56.0.8) | 305 | version: 56.0.15(expo@56.0.8) |
| 306 | expo-linking: | 306 | expo-linking: |
| 307 | specifier: ~56.0.12 | 307 | 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) |
| 309 | expo-network: | 309 | expo-network: |
| 310 | specifier: ~56.0.5 | 310 | specifier: ~56.0.5 |
| 311 | version: 56.0.5(expo@56.0.8)(react@19.2.3) | 311 | version: 56.0.5(expo@56.0.8)(react@19.2.3) |
| 312 | expo-notifications: | 312 | expo-notifications: |
| 313 | specifier: ~56.0.14 | 313 | 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) |
| 315 | expo-paste-input: | 315 | expo-paste-input: |
| 316 | specifier: ^0.1.15 | 316 | 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) |
| 318 | expo-router: | 318 | expo-router: |
| 319 | specifier: ~56.2.7 | 319 | specifier: ~56.2.7 |
| 320 | version: 56.2.8(5bfdf39b8f760e4dc2d5c3acffc97310) | 320 | version: 56.2.8(c021de11d02907bd585610408f5252e8) |
| 321 | expo-secure-store: | 321 | expo-secure-store: |
| 322 | specifier: ~56.0.4 | 322 | specifier: ~56.0.4 |
| 323 | version: 56.0.4(expo@56.0.8) | 323 | version: 56.0.4(expo@56.0.8) |
| @@ -326,16 +326,16 @@ importers: · 2 unmodified lines | |||
| 326 | version: 56.0.10(expo@56.0.8)(typescript@6.0.3) | 326 | version: 56.0.10(expo@56.0.8)(typescript@6.0.3) |
| 327 | expo-symbols: | 327 | expo-symbols: |
| 328 | specifier: ~56.0.5 | 328 | 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) |
| 330 | expo-updates: | 330 | expo-updates: |
| 331 | specifier: ~56.0.17 | 331 | 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) |
| 333 | expo-web-browser: | 333 | expo-web-browser: |
| 334 | specifier: ~56.0.5 | 334 | 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)) |
| 336 | expo-widgets: | 336 | expo-widgets: |
| 337 | specifier: ~56.0.15 | 337 | specifier: ~56.0.15 |
| 338 | version: 56.0.16(961c4aa6f32829b318e3c87ef20ad401) | 338 | version: 56.0.16(ab3f255d102c60ba0fd2bbe4e47ba584) |
| 339 | punycode: | 339 | punycode: |
| 340 | specifier: ^2.3.1 | 340 | specifier: ^2.3.1 |
| 341 | version: 2.3.1 | 341 | version: 2.3.1 |
| @@ -347,43 +347,43 @@ importers: · 5 unmodified lines | |||
| 347 | version: 19.2.3(react@19.2.3) | 347 | version: 19.2.3(react@19.2.3) |
| 348 | react-native: | 348 | react-native: |
| 349 | specifier: 0.85.3 | 349 | 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) |
| 351 | react-native-gesture-handler: | 351 | react-native-gesture-handler: |
| 352 | specifier: ~2.31.1 | 352 | 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) |
| 354 | react-native-image-viewing: | 354 | react-native-image-viewing: |
| 355 | specifier: ^0.2.2 | 355 | 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) |
| 357 | react-native-keyboard-controller: | 357 | react-native-keyboard-controller: |
| 358 | specifier: 1.21.6 | 358 | 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) |
| 360 | react-native-nitro-markdown: | 360 | react-native-nitro-markdown: |
| 361 | specifier: ^0.5.0 | 361 | 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) |
| 363 | react-native-nitro-modules: | 363 | react-native-nitro-modules: |
| 364 | specifier: ^0.35.4 | 364 | 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) |
| 366 | react-native-reanimated: | 366 | react-native-reanimated: |
| 367 | specifier: 4.3.1 | 367 | 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) |
| 369 | react-native-safe-area-context: | 369 | react-native-safe-area-context: |
| 370 | specifier: ~5.7.0 | 370 | 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) |
| 372 | react-native-screens: | 372 | react-native-screens: |
| 373 | specifier: 4.25.2 | 373 | 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) |
| 375 | react-native-shiki-engine: | 375 | react-native-shiki-engine: |
| 376 | specifier: ^0.3.12 | 376 | 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) |
| 378 | react-native-svg: | 378 | react-native-svg: |
| 379 | specifier: 15.15.4 | 379 | 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) |
| 381 | react-native-webview: | 381 | react-native-webview: |
| 382 | specifier: ^13.16.1 | 382 | 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) |
| 384 | react-native-worklets: | 384 | react-native-worklets: |
| 385 | specifier: 0.8.3 | 385 | 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) |
| 387 | shiki: | 387 | shiki: |
| 388 | specifier: 4.2.0 | 388 | specifier: 4.2.0 |
| 389 | version: 4.2.0 | 389 | version: 4.2.0 |
| @@ -392,7 +392,7 @@ importers: · 2 unmodified lines | |||
| 392 | version: 3.6.0 | 392 | version: 3.6.0 |
| 393 | uniwind: | 393 | uniwind: |
| 394 | specifier: ^1.6.2 | 394 | 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) |
| 396 | devDependencies: | 396 | devDependencies: |
| 397 | '@effect/vitest': | 397 | '@effect/vitest': |
| 398 | specifier: 4.0.0-beta.78 | 398 | specifier: 4.0.0-beta.78 |
| @@ -10839,10 +10839,10 @@ snapshots: · 10440 unmodified lines | |||
| 10839 | optionalDependencies: | 10839 | optionalDependencies: |
| 10840 | '@types/react': 19.2.16 | 10840 | '@types/react': 19.2.16 |
| 10841 | 10841 | ||
| 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)': |
| 10843 | dependencies: | 10843 | dependencies: |
| 10844 | react: 19.2.3 | 10844 | 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) |
| 10846 | 10846 | ||
| 10847 | '@capsizecss/unpack@4.0.0': | 10847 | '@capsizecss/unpack@4.0.0': |
| 10848 | dependencies: | 10848 | dependencies: |
| @@ -10946,23 +10946,23 @@ snapshots: · 97 unmodified lines | |||
| 10946 | electron-store: 8.2.0 | 10946 | electron-store: 8.2.0 |
| 10947 | react-dom: 19.2.6(react@19.2.6) | 10947 | react-dom: 19.2.6(react@19.2.6) |
| 10948 | 10948 | ||
| 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)': |
| 10950 | dependencies: | 10950 | dependencies: |
| 10951 | '@clerk/clerk-js': 6.18.2-snapshot.v20260619001138(react-dom@19.2.3(react@19.2.3))(react@19.2.3) | 10951 | '@clerk/clerk-js': 6.18.2-snapshot.v20260619001138(react-dom@19.2.3(react@19.2.3))(react@19.2.3) |
| 10952 | '@clerk/react': 6.10.4-snapshot.v20260619001138(react-dom@19.2.3(react@19.2.3))(react@19.2.3) | 10952 | '@clerk/react': 6.10.4-snapshot.v20260619001138(react-dom@19.2.3(react@19.2.3))(react@19.2.3) |
| 10953 | '@clerk/shared': 4.19.2-snapshot.v20260619001138(react-dom@19.2.3(react@19.2.3))(react@19.2.3) | 10953 | '@clerk/shared': 4.19.2-snapshot.v20260619001138(react-dom@19.2.3(react@19.2.3))(react@19.2.3) |
| 10954 | base-64: 1.0.0 | 10954 | 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) |
| 10956 | react: 19.2.3 | 10956 | 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) | 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)(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))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6)) |
| 10959 | tslib: 2.8.1 | 10959 | tslib: 2.8.1 |
| 10960 | optionalDependencies: | 10960 | 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) | 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)(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)) | 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)) |
| 10963 | expo-crypto: 56.0.4(expo@56.0.8) | 10963 | expo-crypto: 56.0.4(expo@56.0.8) |
| 10964 | expo-secure-store: 56.0.4(expo@56.0.8) | 10964 | 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)) |
| 10966 | react-dom: 19.2.3(react@19.2.3) | 10966 | react-dom: 19.2.3(react@19.2.3) |
| 10967 | 10967 | ||
| 10968 | '@clerk/react@6.10.4-snapshot.v20260619001138(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': | 10968 | '@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 | |||
| 11529 | 11529 | ||
| 11530 | '@expo-google-fonts/material-symbols@0.4.38': {} | 11530 | '@expo-google-fonts/material-symbols@0.4.38': {} |
| 11531 | 11531 | ||
| 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)': |
| 11533 | dependencies: | 11533 | dependencies: |
| 11534 | '@expo/code-signing-certificates': 0.0.6 | 11534 | '@expo/code-signing-certificates': 0.0.6 |
| 11535 | '@expo/config': 56.0.9(typescript@6.0.3) | 11535 | '@expo/config': 56.0.9(typescript@6.0.3) |
| @@ -11539,7 +11539,7 @@ snapshots: · 3 unmodified lines | |||
| 11539 | '@expo/image-utils': 0.10.1(typescript@6.0.3) | 11539 | '@expo/image-utils': 0.10.1(typescript@6.0.3) |
| 11540 | '@expo/inline-modules': 0.0.10(typescript@6.0.3) | 11540 | '@expo/inline-modules': 0.0.10(typescript@6.0.3) |
| 11541 | '@expo/json-file': 10.2.0 | 11541 | '@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) |
| 11543 | '@expo/metro': 56.0.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) | 11543 | '@expo/metro': 56.0.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) |
| 11544 | '@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) | 11544 | '@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) |
| 11545 | '@expo/metro-file-map': 56.0.3 | 11545 | '@expo/metro-file-map': 56.0.3 |
| @@ -11564,7 +11564,7 @@ snapshots: · 18 unmodified lines | |||
| 11564 | connect: 3.7.0 | 11564 | connect: 3.7.0 |
| 11565 | debug: 4.4.3 | 11565 | debug: 4.4.3 |
| 11566 | dnssd-advertise: 1.1.4 | 11566 | 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) |
| 11568 | expo-server: 56.0.4 | 11568 | expo-server: 56.0.4 |
| 11569 | fetch-nodeshim: 0.4.10 | 11569 | fetch-nodeshim: 0.4.10 |
| 11570 | getenv: 2.0.0 | 11570 | getenv: 2.0.0 |
| @@ -11590,8 +11590,8 @@ snapshots: · 19 unmodified lines | |||
| 11590 | ws: 8.21.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) | 11590 | ws: 8.21.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) |
| 11591 | zod: 3.25.76 | 11591 | zod: 3.25.76 |
| 11592 | optionalDependencies: | 11592 | optionalDependencies: |
| 11593 | expo-router: 56.2.8(5bfdf39b8f760e4dc2d5c3acffc97310) | 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)(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) | 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) |
| 11595 | transitivePeerDependencies: | 11595 | transitivePeerDependencies: |
| 11596 | - '@expo/dom-webview' | 11596 | - '@expo/dom-webview' |
| 11597 | - '@expo/metro-runtime' | 11597 | - '@expo/metro-runtime' |
| @@ -11653,18 +11653,18 @@ snapshots: · 55 unmodified lines | |||
| 11653 | transitivePeerDependencies: | 11653 | transitivePeerDependencies: |
| 11654 | - supports-color | 11654 | - supports-color |
| 11655 | 11655 | ||
| 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)': |
| 11657 | dependencies: | 11657 | dependencies: |
| 11658 | chalk: 4.1.2 | 11658 | chalk: 4.1.2 |
| 11659 | optionalDependencies: | 11659 | optionalDependencies: |
| 11660 | react: 19.2.3 | 11660 | 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) |
| 11662 | 11662 | ||
| 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)': |
| 11664 | dependencies: | 11664 | 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) |
| 11666 | react: 19.2.3 | 11666 | 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) |
| 11668 | 11668 | ||
| 11669 | '@expo/env@2.3.0': | 11669 | '@expo/env@2.3.0': |
| 11670 | dependencies: | 11670 | dependencies: |
| @@ -11725,13 +11725,13 @@ snapshots: · 54 unmodified lines | |||
| 11725 | - supports-color | 11725 | - supports-color |
| 11726 | - typescript | 11726 | - typescript |
| 11727 | 11727 | ||
| 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)': |
| 11729 | dependencies: | 11729 | 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) |
| 11731 | anser: 1.4.10 | 11731 | 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) |
| 11733 | react: 19.2.3 | 11733 | 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) |
| 11735 | stacktrace-parser: 0.1.11 | 11735 | stacktrace-parser: 0.1.11 |
| 11736 | 11736 | ||
| 11737 | '@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)': | 11737 | '@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 | |||
| 11761 | postcss: 8.5.15 | 11761 | postcss: 8.5.15 |
| 11762 | resolve-from: 5.0.0 | 11762 | resolve-from: 5.0.0 |
| 11763 | optionalDependencies: | 11763 | 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) |
| 11765 | transitivePeerDependencies: | 11765 | transitivePeerDependencies: |
| 11766 | - bufferutil | 11766 | - bufferutil |
| 11767 | - supports-color | 11767 | - supports-color |
| @@ -11779,14 +11779,14 @@ snapshots: · 11 unmodified lines | |||
| 11779 | transitivePeerDependencies: | 11779 | transitivePeerDependencies: |
| 11780 | - supports-color | 11780 | - supports-color |
| 11781 | 11781 | ||
| 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)': |
| 11783 | dependencies: | 11783 | 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) |
| 11785 | anser: 1.4.10 | 11785 | 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) |
| 11787 | pretty-format: 29.7.0 | 11787 | pretty-format: 29.7.0 |
| 11788 | react: 19.2.3 | 11788 | 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) |
| 11790 | stacktrace-parser: 0.1.11 | 11790 | stacktrace-parser: 0.1.11 |
| 11791 | whatwg-fetch: 3.6.20 | 11791 | whatwg-fetch: 3.6.20 |
| 11792 | optionalDependencies: | 11792 | optionalDependencies: |
| @@ -11861,14 +11861,14 @@ snapshots: · 68 unmodified lines | |||
| 11861 | '@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)': | 11861 | '@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)': |
| 11862 | dependencies: | 11862 | dependencies: |
| 11863 | debug: 4.4.3 | 11863 | debug: 4.4.3 |
| 11864 | expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d) | 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)(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)) | 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)(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) | 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) |
| 11867 | expo-server: 56.0.4 | 11867 | expo-server: 56.0.4 |
| 11868 | react: 19.2.3 | 11868 | react: 19.2.3 |
| 11869 | optionalDependencies: | 11869 | 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) | 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(5bfdf39b8f760e4dc2d5c3acffc97310) | 11871 | expo-router: 56.2.8(c021de11d02907bd585610408f5252e8) |
| 11872 | react-dom: 19.2.3(react@19.2.3) | 11872 | react-dom: 19.2.3(react@19.2.3) |
| 11873 | transitivePeerDependencies: | 11873 | transitivePeerDependencies: |
| 11874 | - supports-color | 11874 | - supports-color |
| @@ -11883,18 +11883,18 @@ snapshots: · 8 unmodified lines | |||
| 11883 | 11883 | ||
| 11884 | '@expo/sudo-prompt@9.3.2': {} | 11884 | '@expo/sudo-prompt@9.3.2': {} |
| 11885 | 11885 | ||
| 11886 | '@expo/ui@56.0.15(961c4aa6f32829b318e3c87ef20ad401)': | 11886 | '@expo/ui@56.0.15(ab3f255d102c60ba0fd2bbe4e47ba584)': |
| 11887 | dependencies: | 11887 | 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) |
| 11889 | react: 19.2.3 | 11889 | 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) |
| 11891 | sf-symbols-typescript: 2.2.0 | 11891 | sf-symbols-typescript: 2.2.0 |
| 11892 | 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) | 11892 | 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) |
| 11893 | optionalDependencies: | 11893 | optionalDependencies: |
| 11894 | '@babel/core': 7.29.7 | 11894 | '@babel/core': 7.29.7 |
| 11895 | react-dom: 19.2.3(react@19.2.3) | 11895 | 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) | 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)(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) | 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) |
| 11898 | transitivePeerDependencies: | 11898 | transitivePeerDependencies: |
| 11899 | - '@types/react' | 11899 | - '@types/react' |
| 11900 | - '@types/react-dom' | 11900 | - '@types/react-dom' |
| @@ -12155,13 +12155,13 @@ snapshots: · 254 unmodified lines | |||
| 12155 | dependencies: | 12155 | dependencies: |
| 12156 | jsbi: 4.3.2 | 12156 | jsbi: 4.3.2 |
| 12157 | 12157 | ||
| 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)': |
| 12159 | dependencies: | 12159 | dependencies: |
| 12160 | react: 19.2.3 | 12160 | react: 19.2.3 |
| 12161 | use-sync-external-store: 1.6.0(react@19.2.3) | 12161 | use-sync-external-store: 1.6.0(react@19.2.3) |
| 12162 | optionalDependencies: | 12162 | optionalDependencies: |
| 12163 | react-dom: 19.2.3(react@19.2.3) | 12163 | 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) |
| 12165 | 12165 | ||
| 12166 | '@legendapp/list@3.0.0-beta.44(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': | 12166 | '@legendapp/list@3.0.0-beta.44(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': |
| 12167 | dependencies: | 12167 | dependencies: |
| @@ -12984,15 +12984,15 @@ snapshots: · 816 unmodified lines | |||
| 12984 | prompts: 2.4.2 | 12984 | prompts: 2.4.2 |
| 12985 | tinyexec: 1.2.4 | 12985 | tinyexec: 1.2.4 |
| 12986 | 12986 | ||
| 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)': |
| 12988 | dependencies: | 12988 | dependencies: |
| 12989 | react: 19.2.3 | 12989 | 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) |
| 12991 | 12991 | ||
| 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)': |
| 12993 | dependencies: | 12993 | dependencies: |
| 12994 | react: 19.2.3 | 12994 | 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) |
| 12996 | 12996 | ||
| 12997 | '@react-native/assets-registry@0.85.3': {} | 12997 | '@react-native/assets-registry@0.85.3': {} |
| 12998 | 12998 | ||
| @@ -13052,7 +13052,7 @@ snapshots: · 53 unmodified lines | |||
| 13052 | tinyglobby: 0.2.17 | 13052 | tinyglobby: 0.2.17 |
| 13053 | yargs: 17.7.2 | 13053 | yargs: 17.7.2 |
| 13054 | 13054 | ||
| 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)': |
| 13056 | dependencies: | 13056 | dependencies: |
| 13057 | '@react-native/dev-middleware': 0.85.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) | 13057 | '@react-native/dev-middleware': 0.85.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) |
| 13058 | debug: 4.4.3 | 13058 | debug: 4.4.3 |
| @@ -13062,7 +13062,7 @@ snapshots: · 3 unmodified lines | |||
| 13062 | metro-core: 0.84.4 | 13062 | metro-core: 0.84.4 |
| 13063 | semver: 7.8.1 | 13063 | semver: 7.8.1 |
| 13064 | optionalDependencies: | 13064 | 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) |
| 13066 | transitivePeerDependencies: | 13066 | transitivePeerDependencies: |
| 13067 | - bufferutil | 13067 | - bufferutil |
| 13068 | - supports-color | 13068 | - supports-color |
| @@ -13110,7 +13110,7 @@ snapshots: · 41 unmodified lines | |||
| 13110 | transitivePeerDependencies: | 13110 | transitivePeerDependencies: |
| 13111 | - supports-color | 13111 | - supports-color |
| 13112 | 13112 | ||
| 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)': |
| 13114 | dependencies: | 13114 | dependencies: |
| 13115 | '@react-native/js-polyfills': 0.85.3 | 13115 | '@react-native/js-polyfills': 0.85.3 |
| 13116 | '@react-native/metro-babel-transformer': 0.85.3(@babel/core@7.29.7) | 13116 | '@react-native/metro-babel-transformer': 0.85.3(@babel/core@7.29.7) |
| @@ -13118,18 +13118,16 @@ snapshots: · 1 unmodified line | |||
| 13118 | metro-runtime: 0.84.4 | 13118 | metro-runtime: 0.84.4 |
| 13119 | transitivePeerDependencies: | 13119 | transitivePeerDependencies: |
| 13120 | - '@babel/core' | 13120 | - '@babel/core' |
| 13121 | - bufferutil | ||
| 13122 | - supports-color | 13121 | - supports-color |
| 13123 | - utf-8-validate | ||
| 13124 | 13122 | ||
| 13125 | '@react-native/normalize-colors@0.85.3': {} | 13123 | '@react-native/normalize-colors@0.85.3': {} |
| 13126 | 13124 | ||
| 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)': |
| 13128 | dependencies: | 13126 | dependencies: |
| 13129 | invariant: 2.2.4 | 13127 | invariant: 2.2.4 |
| 13130 | nullthrows: 1.1.1 | 13128 | nullthrows: 1.1.1 |
| 13131 | react: 19.2.3 | 13129 | 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) |
| 13133 | optionalDependencies: | 13131 | optionalDependencies: |
| 13134 | '@types/react': 19.2.16 | 13132 | '@types/react': 19.2.16 |
| 13135 | 13133 | ||
| @@ -13542,15 +13540,15 @@ snapshots: · 406 unmodified lines | |||
| 13542 | dependencies: | 13540 | dependencies: |
| 13543 | defer-to-connect: 2.0.1 | 13541 | defer-to-connect: 2.0.1 |
| 13544 | 13542 | ||
| 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)': |
| 13546 | dependencies: | 13544 | 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) | 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) |
| 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) | 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) |
| 13549 | expo-haptics: 56.0.3(expo@56.0.8) | 13547 | 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) |
| 13551 | react: 19.2.3 | 13549 | 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) | 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) |
| 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) | 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) |
| 13554 | 13552 | ||
| 13555 | '@t3tools/mobile-review-diff-native@file:apps/mobile/modules/t3-review-diff': {} | 13553 | '@t3tools/mobile-review-diff-native@file:apps/mobile/modules/t3-review-diff': {} |
| 13556 | 13554 | ||
| @@ -14626,8 +14624,8 @@ snapshots: · 1069 unmodified lines | |||
| 14626 | react-refresh: 0.14.2 | 14624 | react-refresh: 0.14.2 |
| 14627 | optionalDependencies: | 14625 | optionalDependencies: |
| 14628 | '@babel/runtime': 7.29.7 | 14626 | '@babel/runtime': 7.29.7 |
| 14629 | expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d) | 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) |
| 14630 | expo-widgets: 56.0.16(961c4aa6f32829b318e3c87ef20ad401) | 14628 | expo-widgets: 56.0.16(ab3f255d102c60ba0fd2bbe4e47ba584) |
| 14631 | transitivePeerDependencies: | 14629 | transitivePeerDependencies: |
| 14632 | - '@babel/core' | 14630 | - '@babel/core' |
| 14633 | - supports-color | 14631 | - supports-color |
| @@ -15518,29 +15516,29 @@ snapshots: · 884 unmodified lines | |||
| 15518 | 15516 | ||
| 15519 | expo-application@56.0.3(expo@56.0.8): | 15517 | expo-application@56.0.3(expo@56.0.8): |
| 15520 | dependencies: | 15518 | 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) |
| 15522 | 15520 | ||
| 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): |
| 15524 | dependencies: | 15522 | dependencies: |
| 15525 | '@expo/image-utils': 0.10.1(typescript@6.0.3) | 15523 | '@expo/image-utils': 0.10.1(typescript@6.0.3) |
| 15526 | expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d) | 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) |
| 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)) | 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)) |
| 15528 | react: 19.2.3 | 15526 | 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) |
| 15530 | transitivePeerDependencies: | 15528 | transitivePeerDependencies: |
| 15531 | - supports-color | 15529 | - supports-color |
| 15532 | - typescript | 15530 | - typescript |
| 15533 | 15531 | ||
| 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): |
| 15535 | dependencies: | 15533 | dependencies: |
| 15536 | expo-application: 56.0.3(expo@56.0.8) | 15534 | 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)) |
| 15538 | expo-crypto: 56.0.4(expo@56.0.8) | 15536 | 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) | 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) |
| 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)) | 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)) |
| 15541 | invariant: 2.2.4 | 15539 | invariant: 2.2.4 |
| 15542 | react: 19.2.3 | 15540 | 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) |
| 15544 | transitivePeerDependencies: | 15542 | transitivePeerDependencies: |
| 15545 | - expo | 15543 | - expo |
| 15546 | - supports-color | 15544 | - supports-color |
| @@ -15548,119 +15546,119 @@ snapshots: · 1 unmodified line | |||
| 15548 | expo-build-properties@56.0.16(expo@56.0.8): | 15546 | expo-build-properties@56.0.16(expo@56.0.8): |
| 15549 | dependencies: | 15547 | dependencies: |
| 15550 | '@expo/schema-utils': 56.0.1 | 15548 | '@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) |
| 15552 | resolve-from: 5.0.0 | 15550 | resolve-from: 5.0.0 |
| 15553 | semver: 7.8.1 | 15551 | semver: 7.8.1 |
| 15554 | 15552 | ||
| 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): |
| 15556 | dependencies: | 15554 | dependencies: |
| 15557 | barcode-detector: 3.2.0(@types/emscripten@1.41.5) | 15555 | 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) |
| 15559 | react: 19.2.3 | 15557 | 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) |
| 15561 | transitivePeerDependencies: | 15559 | transitivePeerDependencies: |
| 15562 | - '@types/emscripten' | 15560 | - '@types/emscripten' |
| 15563 | 15561 | ||
| 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): |
| 15565 | dependencies: | 15563 | 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) |
| 15567 | react: 19.2.3 | 15565 | 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) |
| 15569 | 15567 | ||
| 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)): |
| 15571 | dependencies: | 15569 | dependencies: |
| 15572 | '@expo/env': 2.3.0 | 15570 | '@expo/env': 2.3.0 |
| 15573 | expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d) | 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) |
| 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) | 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) |
| 15575 | transitivePeerDependencies: | 15573 | transitivePeerDependencies: |
| 15576 | - supports-color | 15574 | - supports-color |
| 15577 | 15575 | ||
| 15578 | expo-crypto@56.0.4(expo@56.0.8): | 15576 | expo-crypto@56.0.4(expo@56.0.8): |
| 15579 | dependencies: | 15577 | 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) |
| 15581 | 15579 | ||
| 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)): |
| 15583 | dependencies: | 15581 | dependencies: |
| 15584 | expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d) | 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) |
| 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)) | 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)) |
| 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)) | 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)) |
| 15587 | expo-dev-menu-interface: 56.0.1(expo@56.0.8) | 15585 | expo-dev-menu-interface: 56.0.1(expo@56.0.8) |
| 15588 | expo-manifests: 56.0.4(expo@56.0.8) | 15586 | expo-manifests: 56.0.4(expo@56.0.8) |
| 15589 | expo-updates-interface: 56.0.2(expo@56.0.8) | 15587 | expo-updates-interface: 56.0.2(expo@56.0.8) |
| 15590 | transitivePeerDependencies: | 15588 | transitivePeerDependencies: |
| 15591 | - react-native | 15589 | - react-native |
| 15592 | 15590 | ||
| 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)): |
| 15594 | dependencies: | 15592 | dependencies: |
| 15595 | '@expo/schema-utils': 56.0.1 | 15593 | '@expo/schema-utils': 56.0.1 |
| 15596 | expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d) | 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) |
| 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)) | 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)) |
| 15598 | expo-manifests: 56.0.4(expo@56.0.8) | 15596 | 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) |
| 15600 | 15598 | ||
| 15601 | expo-dev-menu-interface@56.0.1(expo@56.0.8): | 15599 | expo-dev-menu-interface@56.0.1(expo@56.0.8): |
| 15602 | dependencies: | 15600 | 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) |
| 15604 | 15602 | ||
| 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)): |
| 15606 | dependencies: | 15604 | 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) |
| 15608 | expo-dev-menu-interface: 56.0.1(expo@56.0.8) | 15606 | 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) |
| 15610 | 15608 | ||
| 15611 | expo-eas-client@56.0.1: {} | 15609 | expo-eas-client@56.0.1: {} |
| 15612 | 15610 | ||
| 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)): |
| 15614 | dependencies: | 15612 | dependencies: |
| 15615 | expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d) | 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) |
| 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) | 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) |
| 15617 | 15615 | ||
| 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): |
| 15619 | dependencies: | 15617 | 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) |
| 15621 | fontfaceobserver: 2.3.0 | 15619 | fontfaceobserver: 2.3.0 |
| 15622 | react: 19.2.3 | 15620 | 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) |
| 15624 | 15622 | ||
| 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): |
| 15626 | dependencies: | 15624 | 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) |
| 15628 | react: 19.2.3 | 15626 | 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) |
| 15630 | 15628 | ||
| 15631 | expo-haptics@56.0.3(expo@56.0.8): | 15629 | expo-haptics@56.0.3(expo@56.0.8): |
| 15632 | dependencies: | 15630 | 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) |
| 15634 | 15632 | ||
| 15635 | expo-image-loader@56.0.3(expo@56.0.8): | 15633 | expo-image-loader@56.0.3(expo@56.0.8): |
| 15636 | dependencies: | 15634 | 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) |
| 15638 | 15636 | ||
| 15639 | expo-image-picker@56.0.15(expo@56.0.8): | 15637 | expo-image-picker@56.0.15(expo@56.0.8): |
| 15640 | dependencies: | 15638 | 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) |
| 15642 | expo-image-loader: 56.0.3(expo@56.0.8) | 15640 | expo-image-loader: 56.0.3(expo@56.0.8) |
| 15643 | 15641 | ||
| 15644 | expo-json-utils@56.0.0: {} | 15642 | expo-json-utils@56.0.0: {} |
| 15645 | 15643 | ||
| 15646 | expo-keep-awake@56.0.3(expo@56.0.8)(react@19.2.3): | 15644 | expo-keep-awake@56.0.3(expo@56.0.8)(react@19.2.3): |
| 15647 | dependencies: | 15645 | 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) |
| 15649 | react: 19.2.3 | 15647 | react: 19.2.3 |
| 15650 | 15648 | ||
| 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): |
| 15652 | dependencies: | 15650 | 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)) |
| 15654 | invariant: 2.2.4 | 15652 | invariant: 2.2.4 |
| 15655 | react: 19.2.3 | 15653 | 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) |
| 15657 | transitivePeerDependencies: | 15655 | transitivePeerDependencies: |
| 15658 | - expo | 15656 | - expo |
| 15659 | - supports-color | 15657 | - supports-color |
| 15660 | 15658 | ||
| 15661 | expo-manifests@56.0.4(expo@56.0.8): | 15659 | expo-manifests@56.0.4(expo@56.0.8): |
| 15662 | dependencies: | 15660 | 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) |
| 15664 | expo-json-utils: 56.0.0 | 15662 | expo-json-utils: 56.0.0 |
| 15665 | 15663 | ||
| 15666 | expo-modules-autolinking@56.0.14(typescript@6.0.3): | 15664 | expo-modules-autolinking@56.0.14(typescript@6.0.3): |
| @@ -15673,66 +15671,66 @@ snapshots: · 6 unmodified lines | |||
| 15673 | - supports-color | 15671 | - supports-color |
| 15674 | - typescript | 15672 | - typescript |
| 15675 | 15673 | ||
| 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): |
| 15677 | dependencies: | 15675 | dependencies: |
| 15678 | '@expo/expo-modules-macros-plugin': 0.0.9 | 15676 | '@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)) |
| 15680 | invariant: 2.2.4 | 15678 | invariant: 2.2.4 |
| 15681 | react: 19.2.3 | 15679 | 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) |
| 15683 | optionalDependencies: | 15681 | 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) |
| 15685 | 15683 | ||
| 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)): |
| 15687 | dependencies: | 15685 | 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) |
| 15689 | 15687 | ||
| 15690 | expo-network@56.0.5(expo@56.0.8)(react@19.2.3): | 15688 | expo-network@56.0.5(expo@56.0.8)(react@19.2.3): |
| 15691 | dependencies: | 15689 | 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) |
| 15693 | react: 19.2.3 | 15691 | react: 19.2.3 |
| 15694 | 15692 | ||
| 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): |
| 15696 | dependencies: | 15694 | dependencies: |
| 15697 | '@expo/image-utils': 0.10.1(typescript@6.0.3) | 15695 | '@expo/image-utils': 0.10.1(typescript@6.0.3) |
| 15698 | abort-controller: 3.0.0 | 15696 | abort-controller: 3.0.0 |
| 15699 | badgin: 1.2.3 | 15697 | 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) |
| 15701 | expo-application: 56.0.3(expo@56.0.8) | 15699 | 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)) |
| 15703 | react: 19.2.3 | 15701 | 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) |
| 15705 | transitivePeerDependencies: | 15703 | transitivePeerDependencies: |
| 15706 | - supports-color | 15704 | - supports-color |
| 15707 | - typescript | 15705 | - typescript |
| 15708 | 15706 | ||
| 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): |
| 15710 | dependencies: | 15708 | 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) |
| 15712 | react: 19.2.3 | 15710 | 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) |
| 15714 | 15712 | ||
| 15715 | expo-router@56.2.8(5bfdf39b8f760e4dc2d5c3acffc97310): | 15713 | expo-router@56.2.8(c021de11d02907bd585610408f5252e8): |
| 15716 | dependencies: | 15714 | 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) | 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) |
| 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) | 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) |
| 15719 | '@expo/schema-utils': 56.0.1 | 15717 | '@expo/schema-utils': 56.0.1 |
| 15720 | '@expo/ui': 56.0.15(961c4aa6f32829b318e3c87ef20ad401) | 15718 | '@expo/ui': 56.0.15(ab3f255d102c60ba0fd2bbe4e47ba584) |
| 15721 | '@radix-ui/react-slot': 1.2.4(@types/react@19.2.16)(react@19.2.3) | 15719 | '@radix-ui/react-slot': 1.2.4(@types/react@19.2.16)(react@19.2.3) |
| 15722 | '@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) | 15720 | '@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) |
| 15724 | '@testing-library/jest-dom': 6.9.1 | 15722 | '@testing-library/jest-dom': 6.9.1 |
| 15725 | '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) | 15723 | '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) |
| 15726 | client-only: 0.0.1 | 15724 | client-only: 0.0.1 |
| 15727 | color: 4.2.3 | 15725 | color: 4.2.3 |
| 15728 | debug: 4.4.3 | 15726 | debug: 4.4.3 |
| 15729 | escape-string-regexp: 4.0.0 | 15727 | escape-string-regexp: 4.0.0 |
| 15730 | expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d) | 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) |
| 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)) | 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)) |
| 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) | 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) |
| 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) | 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) |
| 15734 | expo-server: 56.0.4 | 15732 | 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) |
| 15736 | fast-deep-equal: 3.1.3 | 15734 | fast-deep-equal: 3.1.3 |
| 15737 | invariant: 2.2.4 | 15735 | invariant: 2.2.4 |
| 15738 | nanoid: 3.3.12 | 15736 | nanoid: 3.3.12 |
| @@ -15740,18 +15738,18 @@ snapshots: · 1 unmodified line | |||
| 15740 | react: 19.2.3 | 15738 | react: 19.2.3 |
| 15741 | react-fast-compare: 3.2.2 | 15739 | react-fast-compare: 3.2.2 |
| 15742 | react-is: 19.2.7 | 15740 | 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) | 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) |
| 15744 | react-native-drawer-layout: 4.2.4(0e9729601f58a7a7ae26c76fe6017455) | 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) |
| 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) | 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) |
| 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) | 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) |
| 15747 | server-only: 0.0.1 | 15745 | server-only: 0.0.1 |
| 15748 | sf-symbols-typescript: 2.2.0 | 15746 | sf-symbols-typescript: 2.2.0 |
| 15749 | shallowequal: 1.1.0 | 15747 | shallowequal: 1.1.0 |
| 15750 | 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) | 15748 | 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) |
| 15751 | optionalDependencies: | 15749 | optionalDependencies: |
| 15752 | react-dom: 19.2.3(react@19.2.3) | 15750 | 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) | 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) |
| 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) | 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) |
| 15755 | transitivePeerDependencies: | 15753 | transitivePeerDependencies: |
| 15756 | - '@babel/core' | 15754 | - '@babel/core' |
| 15757 | - '@testing-library/dom' | 15755 | - '@testing-library/dom' |
| @@ -15763,7 +15761,7 @@ snapshots: · 5 unmodified lines | |||
| 15763 | 15761 | ||
| 15764 | expo-secure-store@56.0.4(expo@56.0.8): | 15762 | expo-secure-store@56.0.4(expo@56.0.8): |
| 15765 | dependencies: | 15763 | 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) |
| 15767 | 15765 | ||
| 15768 | expo-server@56.0.4: {} | 15766 | expo-server@56.0.4: {} |
| 15769 | 15767 | ||
| @@ -15771,7 +15769,7 @@ snapshots: · 1 unmodified line | |||
| 15771 | dependencies: | 15769 | dependencies: |
| 15772 | '@expo/config-plugins': 56.0.8(typescript@6.0.3) | 15770 | '@expo/config-plugins': 56.0.8(typescript@6.0.3) |
| 15773 | '@expo/image-utils': 0.10.1(typescript@6.0.3) | 15771 | '@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) |
| 15775 | xml2js: 0.6.0 | 15773 | xml2js: 0.6.0 |
| 15776 | transitivePeerDependencies: | 15774 | transitivePeerDependencies: |
| 15777 | - supports-color | 15775 | - supports-color |
| @@ -15779,20 +15777,20 @@ snapshots: · 1 unmodified line | |||
| 15779 | 15777 | ||
| 15780 | expo-structured-headers@56.0.0: {} | 15778 | expo-structured-headers@56.0.0: {} |
| 15781 | 15779 | ||
| 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): |
| 15783 | dependencies: | 15781 | dependencies: |
| 15784 | '@expo-google-fonts/material-symbols': 0.4.38 | 15782 | '@expo-google-fonts/material-symbols': 0.4.38 |
| 15785 | expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d) | 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) |
| 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) | 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) |
| 15787 | react: 19.2.3 | 15785 | 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) |
| 15789 | sf-symbols-typescript: 2.2.0 | 15787 | sf-symbols-typescript: 2.2.0 |
| 15790 | 15788 | ||
| 15791 | expo-updates-interface@56.0.2(expo@56.0.8): | 15789 | expo-updates-interface@56.0.2(expo@56.0.8): |
| 15792 | dependencies: | 15790 | 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) |
| 15794 | 15792 | ||
| 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): |
| 15796 | dependencies: | 15794 | dependencies: |
| 15797 | '@expo/code-signing-certificates': 0.0.6 | 15795 | '@expo/code-signing-certificates': 0.0.6 |
| 15798 | '@expo/plist': 0.7.0 | 15796 | '@expo/plist': 0.7.0 |
| @@ -15800,7 +15798,7 @@ snapshots: · 1 unmodified line | |||
| 15800 | arg: 4.1.3 | 15798 | arg: 4.1.3 |
| 15801 | chalk: 4.1.2 | 15799 | chalk: 4.1.2 |
| 15802 | debug: 4.4.3 | 15800 | 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) |
| 15804 | expo-eas-client: 56.0.1 | 15802 | expo-eas-client: 56.0.1 |
| 15805 | expo-manifests: 56.0.4(expo@56.0.8) | 15803 | expo-manifests: 56.0.4(expo@56.0.8) |
| 15806 | expo-structured-headers: 56.0.0 | 15804 | expo-structured-headers: 56.0.0 |
| @@ -15810,25 +15808,25 @@ snapshots: · 3 unmodified lines | |||
| 15810 | ignore: 5.3.2 | 15808 | ignore: 5.3.2 |
| 15811 | nullthrows: 1.1.1 | 15809 | nullthrows: 1.1.1 |
| 15812 | react: 19.2.3 | 15810 | 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) |
| 15814 | resolve-from: 5.0.0 | 15812 | resolve-from: 5.0.0 |
| 15815 | optionalDependencies: | 15813 | 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)) |
| 15817 | transitivePeerDependencies: | 15815 | transitivePeerDependencies: |
| 15818 | - supports-color | 15816 | - supports-color |
| 15819 | 15817 | ||
| 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)): |
| 15821 | dependencies: | 15819 | dependencies: |
| 15822 | expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d) | 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) |
| 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) | 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) |
| 15824 | 15822 | ||
| 15825 | expo-widgets@56.0.16(961c4aa6f32829b318e3c87ef20ad401): | 15823 | expo-widgets@56.0.16(ab3f255d102c60ba0fd2bbe4e47ba584): |
| 15826 | dependencies: | 15824 | dependencies: |
| 15827 | '@expo/plist': 0.7.0 | 15825 | '@expo/plist': 0.7.0 |
| 15828 | '@expo/ui': 56.0.15(961c4aa6f32829b318e3c87ef20ad401) | 15826 | '@expo/ui': 56.0.15(ab3f255d102c60ba0fd2bbe4e47ba584) |
| 15829 | expo: 56.0.8(63f7aade424ad9e7b1154b679fa2a14d) | 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) |
| 15830 | react: 19.2.3 | 15828 | 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) |
| 15832 | transitivePeerDependencies: | 15830 | transitivePeerDependencies: |
| 15833 | - '@babel/core' | 15831 | - '@babel/core' |
| 15834 | - '@types/react' | 15832 | - '@types/react' |
| @@ -15837,37 +15835,37 @@ snapshots: · 2 unmodified lines | |||
| 15837 | - react-native-reanimated | 15835 | - react-native-reanimated |
| 15838 | - react-native-worklets | 15836 | - react-native-worklets |
| 15839 | 15837 | ||
| 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): |
| 15841 | dependencies: | 15839 | dependencies: |
| 15842 | '@babel/runtime': 7.29.7 | 15840 | '@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) |
| 15844 | '@expo/config': 56.0.9(typescript@6.0.3) | 15842 | '@expo/config': 56.0.9(typescript@6.0.3) |
| 15845 | '@expo/config-plugins': 56.0.8(typescript@6.0.3) | 15843 | '@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) |
| 15847 | '@expo/fingerprint': 0.19.3 | 15845 | '@expo/fingerprint': 0.19.3 |
| 15848 | '@expo/local-build-cache-provider': 56.0.8(typescript@6.0.3) | 15846 | '@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) |
| 15850 | '@expo/metro': 56.0.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) | 15848 | '@expo/metro': 56.0.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) |
| 15851 | '@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) | 15849 | '@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) |
| 15852 | '@ungap/structured-clone': 1.3.1 | 15850 | '@ungap/structured-clone': 1.3.1 |
| 15853 | 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) | 15851 | 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) | 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) |
| 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)) | 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)) |
| 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)) | 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)) |
| 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) | 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) |
| 15858 | expo-keep-awake: 56.0.3(expo@56.0.8)(react@19.2.3) | 15856 | expo-keep-awake: 56.0.3(expo@56.0.8)(react@19.2.3) |
| 15859 | expo-modules-autolinking: 56.0.14(typescript@6.0.3) | 15857 | 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) |
| 15861 | pretty-format: 29.7.0 | 15859 | pretty-format: 29.7.0 |
| 15862 | react: 19.2.3 | 15860 | 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) |
| 15864 | react-refresh: 0.14.2 | 15862 | react-refresh: 0.14.2 |
| 15865 | whatwg-url-minimum: 0.1.2 | 15863 | whatwg-url-minimum: 0.1.2 |
| 15866 | optionalDependencies: | 15864 | 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) | 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) |
| 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) | 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) |
| 15869 | react-dom: 19.2.3(react@19.2.3) | 15867 | 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) |
| 15871 | transitivePeerDependencies: | 15869 | transitivePeerDependencies: |
| 15872 | - '@babel/core' | 15870 | - '@babel/core' |
| 15873 | - bufferutil | 15871 | - bufferutil |
| @@ -18285,102 +18283,102 @@ snapshots: · 2411 unmodified lines | |||
| 18285 | transitivePeerDependencies: | 18283 | transitivePeerDependencies: |
| 18286 | - supports-color | 18284 | - supports-color |
| 18287 | 18285 | ||
| 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): |
| 18289 | dependencies: | 18287 | dependencies: |
| 18290 | color: 4.2.3 | 18288 | color: 4.2.3 |
| 18291 | react: 19.2.3 | 18289 | 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) | 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) |
| 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) | 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) |
| 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) | 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) |
| 18295 | use-latest-callback: 0.2.6(react@19.2.3) | 18293 | use-latest-callback: 0.2.6(react@19.2.3) |
| 18296 | 18294 | ||
| 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): |
| 18298 | dependencies: | 18296 | dependencies: |
| 18299 | '@egjs/hammerjs': 2.0.17 | 18297 | '@egjs/hammerjs': 2.0.17 |
| 18300 | '@types/react-test-renderer': 19.1.0 | 18298 | '@types/react-test-renderer': 19.1.0 |
| 18301 | hoist-non-react-statics: 3.3.2 | 18299 | hoist-non-react-statics: 3.3.2 |
| 18302 | invariant: 2.2.4 | 18300 | invariant: 2.2.4 |
| 18303 | react: 19.2.3 | 18301 | 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) |
| 18305 | 18303 | ||
| 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): |
| 18307 | dependencies: | 18305 | dependencies: |
| 18308 | react: 19.2.3 | 18306 | 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) |
| 18310 | 18308 | ||
| 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): |
| 18312 | dependencies: | 18310 | dependencies: |
| 18313 | react: 19.2.3 | 18311 | 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) |
| 18315 | 18313 | ||
| 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): |
| 18317 | dependencies: | 18315 | dependencies: |
| 18318 | react: 19.2.3 | 18316 | 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) | 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) |
| 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) | 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) |
| 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) | 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) |
| 18322 | 18320 | ||
| 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): |
| 18324 | dependencies: | 18322 | dependencies: |
| 18325 | react: 19.2.3 | 18323 | 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) | 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) |
| 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) | 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) |
| 18328 | optionalDependencies: | 18326 | 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) |
| 18330 | 18328 | ||
| 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): |
| 18332 | dependencies: | 18330 | dependencies: |
| 18333 | react: 19.2.3 | 18331 | 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) |
| 18335 | 18333 | ||
| 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): |
| 18337 | dependencies: | 18335 | dependencies: |
| 18338 | react: 19.2.3 | 18336 | 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) | 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) |
| 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) | 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) |
| 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) | 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) |
| 18342 | semver: 7.8.1 | 18340 | semver: 7.8.1 |
| 18343 | 18341 | ||
| 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): |
| 18345 | dependencies: | 18343 | dependencies: |
| 18346 | react: 19.2.3 | 18344 | 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) |
| 18348 | 18346 | ||
| 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): |
| 18350 | dependencies: | 18348 | dependencies: |
| 18351 | react: 19.2.3 | 18349 | react: 19.2.3 |
| 18352 | react-freeze: 1.0.4(react@19.2.3) | 18350 | 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) |
| 18354 | warn-once: 0.1.1 | 18352 | warn-once: 0.1.1 |
| 18355 | 18353 | ||
| 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): |
| 18357 | dependencies: | 18355 | dependencies: |
| 18358 | '@shikijs/types': 4.2.0 | 18356 | '@shikijs/types': 4.2.0 |
| 18359 | '@shikijs/vscode-textmate': 10.0.2 | 18357 | '@shikijs/vscode-textmate': 10.0.2 |
| 18360 | react: 19.2.3 | 18358 | 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) |
| 18362 | 18360 | ||
| 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): |
| 18364 | dependencies: | 18362 | dependencies: |
| 18365 | css-select: 5.2.2 | 18363 | css-select: 5.2.2 |
| 18366 | css-tree: 1.1.3 | 18364 | css-tree: 1.1.3 |
| 18367 | react: 19.2.3 | 18365 | 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) |
| 18369 | warn-once: 0.1.1 | 18367 | warn-once: 0.1.1 |
| 18370 | 18368 | ||
| 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)): |
| 18372 | dependencies: | 18370 | 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) |
| 18374 | whatwg-url-without-unicode: 8.0.0-3 | 18372 | whatwg-url-without-unicode: 8.0.0-3 |
| 18375 | 18373 | ||
| 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): |
| 18377 | dependencies: | 18375 | dependencies: |
| 18378 | escape-string-regexp: 4.0.0 | 18376 | escape-string-regexp: 4.0.0 |
| 18379 | invariant: 2.2.4 | 18377 | invariant: 2.2.4 |
| 18380 | react: 19.2.3 | 18378 | 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) |
| 18382 | 18380 | ||
| 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): |
| 18384 | dependencies: | 18382 | dependencies: |
| 18385 | '@babel/core': 7.29.7 | 18383 | '@babel/core': 7.29.7 |
| 18386 | '@babel/plugin-transform-arrow-functions': 7.29.7(@babel/core@7.29.7) | 18384 | '@babel/plugin-transform-arrow-functions': 7.29.7(@babel/core@7.29.7) |
| @@ -18392,23 +18390,23 @@ snapshots: · 5 unmodified lines | |||
| 18392 | '@babel/plugin-transform-template-literals': 7.29.7(@babel/core@7.29.7) | 18390 | '@babel/plugin-transform-template-literals': 7.29.7(@babel/core@7.29.7) |
| 18393 | '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7) | 18391 | '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7) |
| 18394 | '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7) | 18392 | '@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) |
| 18396 | convert-source-map: 2.0.0 | 18394 | convert-source-map: 2.0.0 |
| 18397 | react: 19.2.3 | 18395 | 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) |
| 18399 | semver: 7.8.1 | 18397 | semver: 7.8.1 |
| 18400 | transitivePeerDependencies: | 18398 | transitivePeerDependencies: |
| 18401 | - supports-color | 18399 | - supports-color |
| 18402 | 18400 | ||
| 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): |
| 18404 | dependencies: | 18402 | dependencies: |
| 18405 | '@react-native/assets-registry': 0.85.3 | 18403 | '@react-native/assets-registry': 0.85.3 |
| 18406 | '@react-native/codegen': 0.85.3(@babel/core@7.29.7) | 18404 | '@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) |
| 18408 | '@react-native/gradle-plugin': 0.85.3 | 18406 | '@react-native/gradle-plugin': 0.85.3 |
| 18409 | '@react-native/js-polyfills': 0.85.3 | 18407 | '@react-native/js-polyfills': 0.85.3 |
| 18410 | '@react-native/normalize-colors': 0.85.3 | 18408 | '@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) |
| 18412 | abort-controller: 3.0.0 | 18410 | abort-controller: 3.0.0 |
| 18413 | anser: 1.4.10 | 18411 | anser: 1.4.10 |
| 18414 | ansi-regex: 5.0.1 | 18412 | ansi-regex: 5.0.1 |
| @@ -19437,14 +19435,14 @@ snapshots: · 1022 unmodified lines | |||
| 19437 | 19435 | ||
| 19438 | universalify@2.0.1: {} | 19436 | universalify@2.0.1: {} |
| 19439 | 19437 | ||
| 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): |
| 19441 | dependencies: | 19439 | dependencies: |
| 19442 | '@tailwindcss/node': 4.2.1 | 19440 | '@tailwindcss/node': 4.2.1 |
| 19443 | '@tailwindcss/oxide': 4.2.1 | 19441 | '@tailwindcss/oxide': 4.2.1 |
| 19444 | culori: 4.0.2 | 19442 | culori: 4.0.2 |
| 19445 | lightningcss: 1.30.1 | 19443 | lightningcss: 1.30.1 |
| 19446 | react: 19.2.3 | 19444 | 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) |
| 19448 | tailwindcss: 4.3.0 | 19446 | tailwindcss: 4.3.0 |
| 19449 | 19447 | ||
| 19450 | unpipe@1.0.0: {} | 19448 | unpipe@1.0.0: {} |