Skip to content
6 changes: 4 additions & 2 deletions apps/desktop/src/ipc/methods/window.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import * as DesktopBackendPool from "../../backend/DesktopBackendPool.ts";
import * as ElectronDialog from "../../electron/ElectronDialog.ts";
import * as ElectronWindow from "../../electron/ElectronWindow.ts";
import * as DesktopAppSettings from "../../settings/DesktopAppSettings.ts";
import type { DesktopSettings } from "../../settings/DesktopAppSettings.ts";
import {
getLocalEnvironmentBootstraps,
getWindowFullscreenState,
Expand Down Expand Up @@ -213,7 +212,10 @@ describe("pasteAsText", () => {
});

describe("pickProjectFavicon", () => {
const pickerLayer = (pickFiles: () => Effect.Effect<Array<string>>, settings?: DesktopSettings) =>
const pickerLayer = (
pickFiles: () => Effect.Effect<Array<string>>,
settings?: DesktopAppSettings.DesktopSettings,
) =>
Layer.mergeAll(
Layer.mock(ElectronDialog.ElectronDialog)({ pickFiles }),
Layer.mock(ElectronWindow.ElectronWindow)({
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/src/preview/BrowserImport/LinuxBrowserSecret.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";

import { DesktopEnvironment } from "../../app/DesktopEnvironment.ts";
import * as DesktopEnvironment from "../../app/DesktopEnvironment.ts";

/** Absolute path to the helper shipped with this desktop instance. */
export const LinuxBrowserSecretPath = Context.Reference<string | undefined>(
Expand All @@ -14,7 +14,7 @@ export const LinuxBrowserSecretPath = Context.Reference<string | undefined>(
export const layer = Layer.effect(
LinuxBrowserSecretPath,
Effect.gen(function* () {
const environment = yield* DesktopEnvironment;
const environment = yield* DesktopEnvironment.DesktopEnvironment;
if (environment.platform !== "linux") return undefined;
const fileSystem = yield* FileSystem.FileSystem;
const relative = environment.path.join("browser-secret", "t3-browser-secret");
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/src/connection/background-activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const mobileBackgroundActivityObserverLayer = Layer.succeed(

export const mobileBackgroundActivityReporterLayer = Layer.effectDiscard(
Effect.gen(function* () {
const registry = yield* EnvironmentRegistry;
const registry = yield* EnvironmentRegistry.EnvironmentRegistry;
const storage = yield* MobileStorage.MobileStorage;
const clientId = yield* storage.loadOrCreateAgentAwarenessDeviceId.pipe(
Effect.map((deviceId) => `mobile-${deviceId}`),
Expand Down
34 changes: 25 additions & 9 deletions apps/mobile/src/connection/environment-cache-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { vi } from "vite-plus/test";
import * as Deferred from "effect/Deferred";
import * as Option from "effect/Option";

import { type ClientCacheKind, MobileDatabase } from "../persistence/mobile-database";
import * as MobileDatabase from "../persistence/mobile-database";
import { make } from "./environment-cache-store";
import { encodeStoredShellSnapshot } from "./shell-cache-encoding";

Expand Down Expand Up @@ -162,15 +162,19 @@ const REFS: VcsListRefsResult = {
totalCount: 1,
};

function cacheId(environmentId: EnvironmentId, kind: ClientCacheKind, cacheKey: string) {
function cacheId(
environmentId: EnvironmentId,
kind: MobileDatabase.ClientCacheKind,
cacheKey: string,
) {
return `${environmentId}:${kind}:${cacheKey}`;
}

function makeDatabase() {
const values = new Map<string, string>();
const schemaVersions = new Map<string, number>();
const removed: Array<string> = [];
const database = MobileDatabase.of({
const database = MobileDatabase.MobileDatabase.of({
loadCache: (environmentId, kind, cacheKey) =>
Effect.succeed(Option.fromUndefinedOr(values.get(cacheId(environmentId, kind, cacheKey)))),
listCache: (kind) =>
Expand Down Expand Up @@ -215,7 +219,9 @@ describe("mobile SQLite environment cache store", () => {
it.effect("round-trips V2 shell and thread DateTime fields with the shared cache schema", () =>
Effect.gen(function* () {
const memory = makeDatabase();
const store = yield* make().pipe(Effect.provideService(MobileDatabase, memory.database));
const store = yield* make().pipe(
Effect.provideService(MobileDatabase.MobileDatabase, memory.database),
);

yield* store.saveShell(ENVIRONMENT_ID, SHELL_SNAPSHOT);
yield* store.saveThread(ENVIRONMENT_ID, THREAD_SNAPSHOT);
Expand Down Expand Up @@ -251,7 +257,9 @@ describe("mobile SQLite environment cache store", () => {
it.effect("round-trips schema-validated VCS refs", () =>
Effect.gen(function* () {
const memory = makeDatabase();
const store = yield* make().pipe(Effect.provideService(MobileDatabase, memory.database));
const store = yield* make().pipe(
Effect.provideService(MobileDatabase.MobileDatabase, memory.database),
);

yield* store.saveVcsRefs(ENVIRONMENT_ID, "/repo", REFS);

Expand All @@ -262,7 +270,9 @@ describe("mobile SQLite environment cache store", () => {
it.effect("deletes a corrupt cache record and treats it as a miss", () =>
Effect.gen(function* () {
const memory = makeDatabase();
const store = yield* make().pipe(Effect.provideService(MobileDatabase, memory.database));
const store = yield* make().pipe(
Effect.provideService(MobileDatabase.MobileDatabase, memory.database),
);
const id = cacheId(ENVIRONMENT_ID, "vcs-refs", "/repo");
memory.values.set(id, "{not-json");

Expand All @@ -274,7 +284,9 @@ describe("mobile SQLite environment cache store", () => {
it.effect("removes one persisted VCS ref snapshot", () =>
Effect.gen(function* () {
const memory = makeDatabase();
const store = yield* make().pipe(Effect.provideService(MobileDatabase, memory.database));
const store = yield* make().pipe(
Effect.provideService(MobileDatabase.MobileDatabase, memory.database),
);
yield* store.saveVcsRefs(ENVIRONMENT_ID, "/repo", REFS);

yield* store.removeVcsRefs(ENVIRONMENT_ID, "/repo");
Expand All @@ -287,7 +299,9 @@ describe("mobile SQLite environment cache store", () => {
it.effect("clears every persisted VCS ref snapshot in one environment", () =>
Effect.gen(function* () {
const memory = makeDatabase();
const store = yield* make().pipe(Effect.provideService(MobileDatabase, memory.database));
const store = yield* make().pipe(
Effect.provideService(MobileDatabase.MobileDatabase, memory.database),
);
const otherEnvironmentId = EnvironmentId.make("environment-2");
yield* store.saveVcsRefs(ENVIRONMENT_ID, "/repo", REFS);
yield* store.saveVcsRefs(ENVIRONMENT_ID, "/repo-worktree", REFS);
Expand All @@ -304,7 +318,9 @@ describe("mobile SQLite environment cache store", () => {
it.effect("clears one environment without touching another", () =>
Effect.gen(function* () {
const memory = makeDatabase();
const store = yield* make().pipe(Effect.provideService(MobileDatabase, memory.database));
const store = yield* make().pipe(
Effect.provideService(MobileDatabase.MobileDatabase, memory.database),
);
const otherEnvironmentId = EnvironmentId.make("environment-2");
yield* store.saveVcsRefs(ENVIRONMENT_ID, "/repo", REFS);
yield* store.saveVcsRefs(otherEnvironmentId, "/repo", REFS);
Expand Down
13 changes: 6 additions & 7 deletions apps/mobile/src/connection/environment-cache-store.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import {
ConnectionPersistenceError,
EnvironmentCacheStore,
ORCHESTRATION_CACHE_SCHEMA_VERSION,
StoredOrchestrationShellSnapshot,
StoredOrchestrationThreadSnapshot,
Persistence,
} from "@t3tools/client-runtime/platform";
import { type EnvironmentId, ServerConfig, VcsListRefsResult } from "@t3tools/contracts";
import * as Effect from "effect/Effect";
Expand Down Expand Up @@ -49,10 +48,10 @@ const encodeStoredServerConfig = Schema.encodeEffect(Schema.fromJsonString(Store
const decodeStoredVcsRefs = Schema.decodeUnknownEffect(Schema.fromJsonString(StoredVcsRefs));
const encodeStoredVcsRefs = Schema.encodeEffect(Schema.fromJsonString(StoredVcsRefs));

type CacheOperation = ConnectionPersistenceError["operation"];
type CacheOperation = Persistence.ConnectionPersistenceError["operation"];

function persistenceError(operation: CacheOperation, cause: unknown) {
return new ConnectionPersistenceError({
return new Persistence.ConnectionPersistenceError({
operation,
message: `Could not ${operation.replaceAll("-", " ")}: ${String(cause)}`,
});
Expand All @@ -70,7 +69,7 @@ function loadDecodedCache<A, B>(input: {
readonly operation: CacheOperation;
readonly decode: (raw: string) => Effect.Effect<A, unknown>;
readonly select: (value: A) => Option.Option<B>;
}): Effect.Effect<Option.Option<B>, ConnectionPersistenceError> {
}): Effect.Effect<Option.Option<B>, Persistence.ConnectionPersistenceError> {
return input.database.loadCache(input.environmentId, input.kind, input.cacheKey).pipe(
Effect.mapError(mapDatabaseError(input.operation)),
Effect.flatMap(
Expand Down Expand Up @@ -103,7 +102,7 @@ function loadDecodedCache<A, B>(input: {
export const make = Effect.fn("MobileEnvironmentCacheStore.make")(function* () {
const database = yield* MobileDatabase.MobileDatabase;
attachProjectFaviconDatabase(database);
return EnvironmentCacheStore.of({
return Persistence.EnvironmentCacheStore.of({
loadShell: Effect.fn("MobileEnvironmentCache.loadShell")((environmentId) =>
loadDecodedCache({
database,
Expand Down Expand Up @@ -233,4 +232,4 @@ export const make = Effect.fn("MobileEnvironmentCacheStore.make")(function* () {
});
});

export const layer = Layer.effect(EnvironmentCacheStore, make());
export const layer = Layer.effect(Persistence.EnvironmentCacheStore, make());
7 changes: 5 additions & 2 deletions apps/mobile/src/connection/onboarding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const connectPairingUrl = createRuntimeCommand(connectionAtomRuntime, {
scheduler: onboardingScheduler,
concurrency: { mode: "singleFlight", key: (pairingUrl: string) => pairingUrl },
execute: (pairingUrl: string) =>
ConnectionOnboarding.pipe(
ConnectionOnboarding.ConnectionOnboarding.pipe(
Effect.flatMap((onboarding) => onboarding.registerPairing({ pairingUrl })),
),
});
Expand All @@ -31,5 +31,8 @@ export const updateBearerConnection = createRuntimeCommand(connectionAtomRuntime
readonly environmentId: EnvironmentId;
readonly label: string;
readonly httpBaseUrl: string;
}) => ConnectionOnboarding.pipe(Effect.flatMap((onboarding) => onboarding.updateBearer(input))),
}) =>
ConnectionOnboarding.ConnectionOnboarding.pipe(
Effect.flatMap((onboarding) => onboarding.updateBearer(input)),
),
});
38 changes: 18 additions & 20 deletions apps/mobile/src/connection/platform.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import {
ClientPresentation,
CloudSession,
EnvironmentOwnedDataCleanup,
ClientCapabilities,
PlatformConnectionSource,
PrimaryEnvironmentAuth,
RelayDeviceIdentity,
SshEnvironmentGateway,
Persistence,
} from "@t3tools/client-runtime/platform";
import {
ConnectionBlockedError,
Expand Down Expand Up @@ -116,8 +112,8 @@ const capabilitiesLayer = Layer.effectContext(
Effect.gen(function* () {
const storage = yield* MobileStorage.MobileStorage;
return Context.make(
CloudSession,
CloudSession.of({
ClientCapabilities.CloudSession,
ClientCapabilities.CloudSession.of({
identity: Effect.sync(() =>
Option.fromNullishOr(appAtomRegistry.get(managedRelaySessionAtom)),
),
Expand Down Expand Up @@ -149,12 +145,14 @@ const capabilitiesLayer = Layer.effectContext(
}),
).pipe(
Context.add(
PrimaryEnvironmentAuth,
PrimaryEnvironmentAuth.of({ bearerToken: Effect.succeed(Option.none()) }),
ClientCapabilities.PrimaryEnvironmentAuth,
ClientCapabilities.PrimaryEnvironmentAuth.of({
bearerToken: Effect.succeed(Option.none()),
}),
),
Context.add(
RelayDeviceIdentity,
RelayDeviceIdentity.of({
ClientCapabilities.RelayDeviceIdentity,
ClientCapabilities.RelayDeviceIdentity.of({
deviceId: storage.loadOrCreateAgentAwarenessDeviceId.pipe(
Effect.mapError(
(cause) =>
Expand All @@ -168,15 +166,15 @@ const capabilitiesLayer = Layer.effectContext(
}),
),
Context.add(
ClientPresentation,
ClientPresentation.of({
ClientCapabilities.ClientPresentation,
ClientCapabilities.ClientPresentation.of({
metadata: authClientMetadata(Constants.expoConfig?.version),
scopes: AuthStandardClientScopes,
}),
),
Context.add(
SshEnvironmentGateway,
SshEnvironmentGateway.of({
ClientCapabilities.SshEnvironmentGateway,
ClientCapabilities.SshEnvironmentGateway.of({
provision: () =>
Effect.fail(
new ConnectionBlockedError({
Expand All @@ -199,8 +197,8 @@ const capabilitiesLayer = Layer.effectContext(
);

const platformConnectionSourceLayer = Layer.succeed(
PlatformConnectionSource,
PlatformConnectionSource.of({
PlatformConnectionSource.PlatformConnectionSource,
PlatformConnectionSource.PlatformConnectionSource.of({
registrations: Stream.empty,
}),
);
Expand All @@ -213,8 +211,8 @@ const providedCapabilitiesLayer = capabilitiesLayer.pipe(
);

const environmentOwnedDataCleanupLayer = Layer.succeed(
EnvironmentOwnedDataCleanup,
EnvironmentOwnedDataCleanup.of({
Persistence.EnvironmentOwnedDataCleanup,
Persistence.EnvironmentOwnedDataCleanup.of({
clear: (environmentId) =>
Effect.all(
[
Expand Down
8 changes: 4 additions & 4 deletions apps/mobile/src/connection/runtime.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Connection } from "@t3tools/client-runtime/connection";
import { shellSnapshotLoaderLayer } from "@t3tools/client-runtime/state/shell";
import { ShellSnapshotLoader } from "@t3tools/client-runtime/state/shell";
import {
boundedThreadSnapshotLoaderLayer,
threadHistoryControllerLayer,
ThreadHistoryController,
} from "@t3tools/client-runtime/state/threads";
import * as Layer from "effect/Layer";
import { Atom } from "effect/unstable/reactivity";
Expand All @@ -25,8 +25,8 @@ const providedConnectionPlatformLayer = connectionPlatformLayer.pipe(

const snapshotLoaderLayer = Layer.mergeAll(
boundedThreadSnapshotLoaderLayer,
shellSnapshotLoaderLayer,
threadHistoryControllerLayer,
ShellSnapshotLoader.layer,
ThreadHistoryController.layer,
);

type ConnectionLayerSource =
Expand Down
10 changes: 5 additions & 5 deletions apps/mobile/src/connection/storage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ vi.mock("expo-secure-store", () => ({
}));

import { CONNECTION_CATALOG_KEY, LEGACY_CONNECTIONS_KEY, make } from "./catalog-store";
import { MobileSecureStorage } from "../persistence/mobile-secure-storage";
import * as MobileSecureStorage from "../persistence/mobile-secure-storage";

function makeStorage(initial: Readonly<Record<string, string>>) {
const values = new Map(Object.entries(initial));
const deleted: Array<string> = [];
const storage = MobileSecureStorage.of({
const storage = MobileSecureStorage.MobileSecureStorage.of({
getItem: (key) => Effect.sync(() => values.get(key) ?? null),
setItem: (key, value) =>
Effect.sync(() => {
Expand All @@ -40,7 +40,7 @@ describe("mobile connection catalog storage", () => {
[CONNECTION_CATALOG_KEY]: "{not-json",
});
const catalog = yield* make().pipe(
Effect.provideService(MobileSecureStorage, memory.storage),
Effect.provideService(MobileSecureStorage.MobileSecureStorage, memory.storage),
);

expect((yield* catalog.read).targets).toEqual([]);
Expand All @@ -54,7 +54,7 @@ describe("mobile connection catalog storage", () => {
[LEGACY_CONNECTIONS_KEY]: JSON.stringify({ connections: [{ invalid: true }] }),
});
const catalog = yield* make().pipe(
Effect.provideService(MobileSecureStorage, memory.storage),
Effect.provideService(MobileSecureStorage.MobileSecureStorage, memory.storage),
);

expect((yield* catalog.read).targets).toEqual([]);
Expand Down Expand Up @@ -83,7 +83,7 @@ describe("mobile connection catalog storage", () => {
}),
});
const catalog = yield* make().pipe(
Effect.provideService(MobileSecureStorage, memory.storage),
Effect.provideService(MobileSecureStorage.MobileSecureStorage, memory.storage),
);

expect((yield* catalog.read).targets).toHaveLength(1);
Expand Down
Loading
Loading