Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
b19fc1b
[codex] refactor desktop Electron Effect services (#3178)
juliusmarminge Jun 20, 2026
97e5cd3
[codex] align server auth Effect services (#3180)
juliusmarminge Jun 20, 2026
86db35c
[codex] Structure mobile native static-check failures (#3302)
juliusmarminge Jun 20, 2026
20734d4
[codex] Structure macOS passkey signing failures (#3303)
juliusmarminge Jun 20, 2026
515303e
[codex] Preserve desktop user-data probe failures (#3304)
juliusmarminge Jun 20, 2026
b4fe8fa
[codex] Structure relay activity-row persistence errors (#3305)
juliusmarminge Jun 20, 2026
8c3755a
[codex] Structure bootstrap errors (#3256)
juliusmarminge Jun 20, 2026
e55dd00
[codex] Structure preview session key errors (#3388)
juliusmarminge Jun 20, 2026
d87ec96
[codex] Structure empty mobile pairing payload errors (#3372)
juliusmarminge Jun 20, 2026
0675252
[codex] Structure unavailable Bun PTY operations (#3394)
juliusmarminge Jun 20, 2026
7a8bab5
[codex] Keep PTY spawn errors structural (#3325)
juliusmarminge Jun 20, 2026
f7867ad
[codex] Structure mobile notification setting failures (#3391)
juliusmarminge Jun 20, 2026
bf1a650
[codex] Preserve review path resolution failures (#3357)
juliusmarminge Jun 20, 2026
7160814
[codex] Split preferred editor precondition errors (#3324)
juliusmarminge Jun 20, 2026
cc69aef
[codex] Structure relay domain label errors (#3347)
juliusmarminge Jun 20, 2026
40d1464
[codex] Structure catalog dependency resolution failures (#3298)
juliusmarminge Jun 20, 2026
08650a7
[codex] Structure web diff worker failures (#3356)
juliusmarminge Jun 20, 2026
8331511
[codex] structure Electron theme source errors (#3294)
juliusmarminge Jun 20, 2026
f3b43a1
[codex] Structure missing client cloud config errors (#3346)
juliusmarminge Jun 20, 2026
b9e22de
[codex] Preserve desktop update state read failures (#3370)
juliusmarminge Jun 20, 2026
8112aff
[codex] Structure relay install confirmation conflicts (#3365)
juliusmarminge Jun 20, 2026
350e229
[codex] Structure OAuth scope encoding failures (#3368)
juliusmarminge Jun 20, 2026
779c237
[codex] Structure native view resolution failures (#3353)
juliusmarminge Jun 20, 2026
4fbc4f9
[codex] Structure mobile project thread validation errors (#3387)
juliusmarminge Jun 20, 2026
ac77fe4
[codex] Preserve relay trace error causes (#3377)
juliusmarminge Jun 20, 2026
9d5ca2c
[codex] Structure Electron protocol teardown failures (#3310)
juliusmarminge Jun 20, 2026
30a084c
[codex] Preserve mobile composer draft failures (#3348)
juliusmarminge Jun 20, 2026
fccecd8
[codex] Preserve detached desktop action causes (#3371)
juliusmarminge Jun 20, 2026
ce0c20b
[codex] Structure desktop network interface failures (#3313)
juliusmarminge Jun 20, 2026
04f82ae
[codex] Structure relay environment link errors (#3334)
juliusmarminge Jun 20, 2026
61aade9
[codex] Preserve desktop asset probe failures (#3373)
juliusmarminge Jun 20, 2026
5a2c92e
[codex] Structure Electron app boundary failures (#3301)
juliusmarminge Jun 20, 2026
d84ebe8
[codex] Structure process resource sampling failures (#3415)
juliusmarminge Jun 20, 2026
53a477c
[codex] Structure desktop backend settings read errors (#3379)
juliusmarminge Jun 20, 2026
300d4d5
[codex] Structure missing provider command failures (#3384)
juliusmarminge Jun 20, 2026
716ae73
[codex] Structure relay publish signature errors (#3335)
juliusmarminge Jun 20, 2026
3ecf368
[codex] structure Bitbucket API failures (#3332)
juliusmarminge Jun 20, 2026
4d790f0
[codex] Bound relay registration replay diagnostics (#3420)
juliusmarminge Jun 20, 2026
4d3fcac
[codex] Type malformed Clerk public config failures (#3422)
juliusmarminge Jun 20, 2026
bfe6174
[codex] Simplify desktop client settings errors (#3265)
juliusmarminge Jun 20, 2026
f98448e
[codex] Structure relay JWT failures (#3270)
juliusmarminge Jun 20, 2026
ed6ba74
[codex] sanitize provider runtime failure diagnostics (#3414)
juliusmarminge Jun 20, 2026
1e5f628
[codex] Structure MCP snapshot failures (#3423)
juliusmarminge Jun 20, 2026
1486a4a
[codex] Structure Electron updater errors (#3280)
juliusmarminge Jun 20, 2026
c3e3e26
[codex] Structure primary environment request failures (#3409)
juliusmarminge Jun 20, 2026
5ca7676
[codex] Structure Claude adapter failures (#3406)
juliusmarminge Jun 20, 2026
49c2322
[codex] Structure mobile secure storage failures (#3345)
juliusmarminge Jun 20, 2026
2b8e012
[codex] Structure desktop server exposure errors (#3269)
juliusmarminge Jun 20, 2026
2910d9f
[codex] Structure preview config failures (#3271)
juliusmarminge Jun 20, 2026
48f88d5
[codex] Structure desktop Clerk bridge failures (#3308)
juliusmarminge Jun 20, 2026
9c98cd6
Remove persistence error constructor wrappers (#3398)
juliusmarminge Jun 20, 2026
ee3e2da
[codex] Structure remote pairing input errors (#3393)
juliusmarminge Jun 20, 2026
5b1b35c
[codex] Preserve desktop shell environment probe failures (#3383)
juliusmarminge Jun 20, 2026
833c8ab
[codex] Remove project setup error constructor wrappers (#3329)
juliusmarminge Jun 20, 2026
1dc36ce
Structure relay auth parsing errors (#3290)
juliusmarminge Jun 20, 2026
e01b190
[codex] Structure process diagnostics failures (#3389)
juliusmarminge Jun 20, 2026
7eb7b4f
[codex] Structure release metadata failures (#3296)
juliusmarminge Jun 20, 2026
708bc70
Structure server environment ID failures (#3286)
juliusmarminge Jun 20, 2026
2c16edd
[codex] Structure desktop bridge state errors (#3381)
juliusmarminge Jun 20, 2026
32c7f90
[codex] Structure APNs delivery queue errors (#3326)
juliusmarminge Jun 20, 2026
d512dea
[codex] Structure preview URL failures (#3275)
juliusmarminge Jun 20, 2026
d8bf307
[codex] Enrich process runner errors (#3268)
juliusmarminge Jun 20, 2026
b6e384f
[codex] Preserve trace IDs across error causes (#3426)
juliusmarminge Jun 20, 2026
f207aec
sync: merge upstream structured error diagnostics
Jun 23, 2026
9c6b45b
Fix preview CI checks
Jun 24, 2026
72b4748
Fix provider diagnostics checks
Jun 24, 2026
7abd00c
Merge branch 'sync/upstream-20260619-ui-settings-imports' into sync/u…
Jun 24, 2026
a16b4e4
Merge branch 'sync/upstream-20260619-ui-settings-imports' into sync/u…
Jun 24, 2026
b4917f3
Merge branch 'sync/upstream-20260619-ui-settings-imports' into sync/u…
Jun 24, 2026
017e7f9
Merge branch 'sync/upstream-20260619-ui-settings-imports' into sync/u…
Jun 24, 2026
71f28f3
Merge branch 'sync/upstream-20260619-ui-settings-imports' into sync/u…
Jun 24, 2026
f0ef08d
Merge branch 'sync/upstream-20260619-ui-settings-imports' into sync/u…
Jun 24, 2026
b8c78bb
Fix structured errors runtime test imports
Jun 24, 2026
2b7f4d0
Merge branch 'sync/upstream-20260619-ui-settings-imports' into sync/u…
Jun 24, 2026
2335aaf
Merge branch 'sync/upstream-20260619-ui-settings-imports' into sync/u…
Jun 24, 2026
c4c04f2
Merge branch 'sync/upstream-20260619-ui-settings-imports' into sync/u…
Jun 24, 2026
0984dd6
Merge branch 'sync/upstream-20260619-ui-settings-imports' into sync/u…
Jun 24, 2026
39470e6
Merge branch 'sync/upstream-20260619-ui-settings-imports' into sync/u…
Jun 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion apps/desktop/src/app/DesktopAppIdentity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import * as PlatformError from "effect/PlatformError";

import type * as Electron from "electron";

Expand Down Expand Up @@ -105,6 +106,7 @@ const withIdentity = <A, E, R>(
readonly calls?: ElectronAppCalls;
readonly environment?: TestEnvironmentInput;
readonly legacyPathExists?: boolean;
readonly legacyPathProbeError?: PlatformError.PlatformError;
readonly packageJson?: string;
readonly pngIconPath?: Option.Option<string>;
} = {},
Expand All @@ -121,7 +123,11 @@ const withIdentity = <A, E, R>(
Layer.provideMerge(
FileSystem.layerNoop({
exists: (path) =>
Effect.succeed(input.legacyPathExists === true && path.includes("T3 Code (Alpha)")),
input.legacyPathProbeError
? Effect.fail(input.legacyPathProbeError)
: Effect.succeed(
input.legacyPathExists === true && path.includes("T3 Code (Alpha)"),
),
readFileString: () =>
Effect.succeed(input.packageJson ?? '{"t3codeCommitHash":"abcdef1234567890"}'),
}),
Expand All @@ -147,6 +153,33 @@ describe("DesktopAppIdentity", () => {
),
);

it.effect("preserves failures while inspecting the legacy userData path", () => {
const legacyPath = "/Users/alice/Library/Application Support/T3 Code (Alpha)";
const cause = PlatformError.systemError({
_tag: "PermissionDenied",
module: "FileSystem",
method: "exists",
description: "permission denied",
pathOrDescriptor: legacyPath,
});

return withIdentity(
Effect.gen(function* () {
const identity = yield* DesktopAppIdentity.DesktopAppIdentity;
const error = yield* identity.resolveUserDataPath.pipe(Effect.flip);

assert.instanceOf(error, DesktopAppIdentity.DesktopUserDataPathResolutionError);
assert.equal(error.legacyPath, legacyPath);
assert.strictEqual(error.cause, cause);
assert.equal(
error.message,
`Failed to inspect legacy desktop user-data path at "${legacyPath}".`,
);
}),
{ legacyPathProbeError: cause },
);
});

it.effect("configures app identity from the environment commit override", () => {
const calls: ElectronAppCalls = {
setAboutPanelOptions: [],
Expand Down
28 changes: 23 additions & 5 deletions apps/desktop/src/app/DesktopAppIdentity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,22 @@ const AppPackageMetadata = Schema.Struct({
});
const decodeAppPackageMetadata = Schema.decodeEffect(Schema.fromJsonString(AppPackageMetadata));

export class DesktopUserDataPathResolutionError extends Schema.TaggedErrorClass<DesktopUserDataPathResolutionError>()(
"DesktopUserDataPathResolutionError",
{
legacyPath: Schema.String,
cause: Schema.Defect(),
},
) {
override get message(): string {
return `Failed to inspect legacy desktop user-data path at "${this.legacyPath}".`;
}
}

export class DesktopAppIdentity extends Context.Service<
DesktopAppIdentity,
{
readonly resolveUserDataPath: Effect.Effect<string>;
readonly resolveUserDataPath: Effect.Effect<string, DesktopUserDataPathResolutionError>;
readonly configure: Effect.Effect<void>;
}
>()("@t3tools/desktop/app/DesktopAppIdentity") {}
Expand All @@ -33,7 +45,7 @@ const normalizeCommitHash = (value: string): Option.Option<string> => {
: Option.none();
};

const make = Effect.gen(function* () {
export const make = Effect.gen(function* () {
const assets = yield* DesktopAssets.DesktopAssets;
const electronApp = yield* ElectronApp.ElectronApp;
const environment = yield* DesktopEnvironment.DesktopEnvironment;
Expand Down Expand Up @@ -83,9 +95,15 @@ const make = Effect.gen(function* () {
environment.appDataDirectory,
environment.legacyUserDataDirName,
);
const legacyPathExists = yield* fileSystem
.exists(legacyPath)
.pipe(Effect.orElseSucceed(() => false));
const legacyPathExists = yield* fileSystem.exists(legacyPath).pipe(
Effect.mapError(
(cause) =>
new DesktopUserDataPathResolutionError({
legacyPath,
cause,
}),
),
);
return legacyPathExists
? legacyPath
: environment.path.join(environment.appDataDirectory, environment.userDataDirName);
Expand Down
57 changes: 57 additions & 0 deletions apps/desktop/src/app/DesktopAssets.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import * as NodeServices from "@effect/platform-node/NodeServices";
import { assert, describe, it } from "@effect/vitest";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
import * as PlatformError from "effect/PlatformError";

import * as DesktopAssets from "./DesktopAssets.ts";
import * as DesktopConfig from "./DesktopConfig.ts";
import * as DesktopEnvironment from "./DesktopEnvironment.ts";

const environmentLayer = DesktopEnvironment.layer({
dirname: "/repo/apps/desktop/dist-electron",
homeDirectory: "/Users/alice",
platform: "darwin",
processArch: "arm64",
appVersion: "1.2.3",
appPath: "/Applications/T3 Code.app/Contents/Resources/app.asar",
isPackaged: true,
resourcesPath: "/Applications/T3 Code.app/Contents/Resources",
runningUnderArm64Translation: false,
}).pipe(Layer.provide(Layer.mergeAll(NodeServices.layer, DesktopConfig.layerTest({}))));

describe("DesktopAssets", () => {
it.effect("preserves the failed asset candidate and filesystem cause", () =>
Effect.gen(function* () {
const fileName = "custom.bin";
const candidatePath = "/repo/apps/desktop/resources/custom.bin";
const cause = PlatformError.systemError({
_tag: "PermissionDenied",
module: "FileSystem",
method: "exists",
pathOrDescriptor: candidatePath,
description: "private filesystem diagnostic",
});
const fileSystemLayer = FileSystem.layerNoop({
exists: (path) => (path === candidatePath ? Effect.fail(cause) : Effect.succeed(false)),
});
const assetsLayer = DesktopAssets.layer.pipe(
Layer.provide(Layer.merge(fileSystemLayer, environmentLayer)),
);
const assets = yield* DesktopAssets.DesktopAssets.pipe(Effect.provide(assetsLayer));

const error = yield* assets.resolveResourcePath(fileName).pipe(Effect.flip);

assert.instanceOf(error, DesktopAssets.DesktopAssetProbeError);
assert.equal(error.fileName, fileName);
assert.equal(error.candidatePath, candidatePath);
assert.strictEqual(error.cause, cause);
assert.equal(
error.message,
`Failed to probe desktop asset "${fileName}" at ${candidatePath}.`,
);
assert.notInclude(error.message, "private filesystem diagnostic");
}),
);
});
45 changes: 37 additions & 8 deletions apps/desktop/src/app/DesktopAssets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import * as Schema from "effect/Schema";

import * as DesktopEnvironment from "./DesktopEnvironment.ts";

Expand All @@ -12,26 +13,47 @@ export interface DesktopIconPaths {
readonly png: Option.Option<string>;
}

export class DesktopAssetProbeError extends Schema.TaggedErrorClass<DesktopAssetProbeError>()(
"DesktopAssetProbeError",
{
fileName: Schema.String,
candidatePath: Schema.String,
cause: Schema.Defect(),
},
) {
override get message(): string {
return `Failed to probe desktop asset "${this.fileName}" at ${this.candidatePath}.`;
}
}

export class DesktopAssets extends Context.Service<
DesktopAssets,
{
readonly iconPaths: Effect.Effect<DesktopIconPaths>;
readonly resolveResourcePath: (fileName: string) => Effect.Effect<Option.Option<string>>;
readonly resolveResourcePath: (
fileName: string,
) => Effect.Effect<Option.Option<string>, DesktopAssetProbeError>;
}
>()("@t3tools/desktop/app/DesktopAssets") {}

const resolveResourcePath = Effect.fn("desktop.assets.resolveResourcePath")(function* (
fileName: string,
): Effect.fn.Return<
Option.Option<string>,
never,
DesktopAssetProbeError,
FileSystem.FileSystem | DesktopEnvironment.DesktopEnvironment
> {
const fileSystem = yield* FileSystem.FileSystem;
const environment = yield* DesktopEnvironment.DesktopEnvironment;
const candidates = environment.resolveResourcePathCandidates(fileName);
for (const candidate of candidates) {
const exists = yield* fileSystem.exists(candidate).pipe(Effect.orElseSucceed(() => false));
const exists = yield* fileSystem
.exists(candidate)
.pipe(
Effect.mapError(
(cause) => new DesktopAssetProbeError({ fileName, candidatePath: candidate, cause }),
),
);
if (exists) {
return Option.some(candidate);
}
Expand All @@ -43,16 +65,23 @@ const resolveIconPath = Effect.fn("desktop.assets.resolveIconPath")(function* (
ext: keyof DesktopIconPaths,
): Effect.fn.Return<
Option.Option<string>,
never,
DesktopAssetProbeError,
FileSystem.FileSystem | DesktopEnvironment.DesktopEnvironment
> {
const fileSystem = yield* FileSystem.FileSystem;
const environment = yield* DesktopEnvironment.DesktopEnvironment;
if (environment.isDevelopment && environment.platform === "darwin" && ext === "png") {
const developmentDockIconPath = environment.developmentDockIconPath;
const developmentDockIconExists = yield* fileSystem
.exists(developmentDockIconPath)
.pipe(Effect.orElseSucceed(() => false));
const developmentDockIconExists = yield* fileSystem.exists(developmentDockIconPath).pipe(
Effect.mapError(
(cause) =>
new DesktopAssetProbeError({
fileName: "icon.png",
candidatePath: developmentDockIconPath,
cause,
}),
),
);
if (developmentDockIconExists) {
return Option.some(developmentDockIconPath);
}
Expand All @@ -61,7 +90,7 @@ const resolveIconPath = Effect.fn("desktop.assets.resolveIconPath")(function* (
return yield* resolveResourcePath(`icon.${ext}`);
});

const make = Effect.gen(function* () {
export const make = Effect.gen(function* () {
const context = yield* Effect.context<
FileSystem.FileSystem | DesktopEnvironment.DesktopEnvironment
>();
Expand Down
79 changes: 67 additions & 12 deletions apps/desktop/src/app/DesktopClerk.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { assert, describe, it } from "@effect/vitest";
import * as Cause from "effect/Cause";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import { vi } from "vite-plus/test";
import { beforeEach, vi } from "vite-plus/test";

const { createClerkBridgeMock, storageAdapter, storageMock } = vi.hoisted(() => ({
createClerkBridgeMock: vi.fn(),
Expand All @@ -24,7 +25,23 @@ vi.mock("@clerk/electron/storage", () => ({
import * as DesktopClerk from "./DesktopClerk.ts";
import * as DesktopEnvironment from "./DesktopEnvironment.ts";

const makeDesktopClerkLayer = (isDevelopment = true) => {
const environment = DesktopEnvironment.DesktopEnvironment.of({
stateDir: "/tmp/t3-state",
isDevelopment,
} as unknown as DesktopEnvironment.DesktopEnvironment["Service"]);

return DesktopClerk.layer.pipe(
Layer.provide(Layer.succeed(DesktopEnvironment.DesktopEnvironment, environment)),
);
};

describe("DesktopClerk", () => {
beforeEach(() => {
createClerkBridgeMock.mockReset();
storageMock.mockReset();
});

it("derives the Clerk Frontend API hostname used by the desktop CSP", () => {
const publishableKey = `pk_test_${btoa("clerk.t3.codes$")}`;

Expand All @@ -40,19 +57,9 @@ describe("DesktopClerk", () => {
const cleanup = vi.fn();
storageMock.mockReturnValue(storageAdapter);
createClerkBridgeMock.mockReturnValue({ cleanup });
const environment = DesktopEnvironment.DesktopEnvironment.of({
stateDir: "/tmp/t3-state",
isDevelopment: true,
} as unknown as DesktopEnvironment.DesktopEnvironment["Service"]);

return Effect.gen(function* () {
yield* Effect.scoped(
Layer.build(
DesktopClerk.layer.pipe(
Layer.provide(Layer.succeed(DesktopEnvironment.DesktopEnvironment, environment)),
),
),
);
yield* Effect.scoped(Layer.build(makeDesktopClerkLayer()));

assert.deepEqual(createClerkBridgeMock.mock.calls, [
[
Expand All @@ -69,6 +76,54 @@ describe("DesktopClerk", () => {
});
});

it.effect("preserves bridge initialization failures", () => {
const cause = new Error("bridge initialization failed");
storageMock.mockReturnValue(storageAdapter);
createClerkBridgeMock.mockImplementationOnce(() => {
throw cause;
});

return Effect.gen(function* () {
const error = yield* Effect.scoped(Layer.build(makeDesktopClerkLayer())).pipe(Effect.flip);

assert.instanceOf(error, DesktopClerk.DesktopClerkBridgeInitializationError);
assert.equal(error.stateDir, "/tmp/t3-state");
assert.equal(error.isDevelopment, true);
assert.strictEqual(error.cause, cause);
assert.equal(
error.message,
'Failed to initialize the desktop Clerk bridge for state directory "/tmp/t3-state" (development: true).',
);
});
});

it.effect("preserves bridge cleanup failures", () => {
const cause = new Error("bridge cleanup failed");
storageMock.mockReturnValue(storageAdapter);
createClerkBridgeMock.mockReturnValue({
cleanup: () => {
throw cause;
},
});

return Effect.gen(function* () {
const exit = yield* Effect.exit(Effect.scoped(Layer.build(makeDesktopClerkLayer(false))));

assert.equal(exit._tag, "Failure");
if (exit._tag === "Failure") {
const error = Cause.squash(exit.cause);
assert.instanceOf(error, DesktopClerk.DesktopClerkBridgeCleanupError);
assert.equal(error.stateDir, "/tmp/t3-state");
assert.equal(error.isDevelopment, false);
assert.strictEqual(error.cause, cause);
assert.equal(
error.message,
'Failed to clean up the desktop Clerk bridge for state directory "/tmp/t3-state" (development: false).',
);
}
});
});

it.each([
{ isDevelopment: true, scheme: "t3code-dev" },
{ isDevelopment: false, scheme: "t3code" },
Expand Down
Loading
Loading