History

fix(desktop): bump Clerk Electron SDK to 0.0.24 and register t3code:// scheme on Linux (#5015)

Julius Marminge committed 3 months ago

Commit fbecb731 parent 4029b8513 files · +210 −95Browse files

Unified · Split

13 files
  1. apps/desktop/src/app/DesktopAppIdentity.test.ts +0 −1
  2. apps/desktop/src/app/DesktopAppIdentity.ts +27 −19
  3. apps/desktop/src/app/DesktopClerk.test.ts +90 −5
  4. apps/desktop/src/app/DesktopClerk.ts +19 −2
  5. apps/desktop/src/app/DesktopLifecycle.test.ts +0 −1
  6. apps/desktop/src/electron/ElectronApp.test.ts +0 −3
  7. apps/desktop/src/electron/ElectronApp.ts +0 −2
  8. apps/desktop/src/telemetry/DesktopTelemetryPublisher.test.ts +0 −1
  9. apps/desktop/src/window/DesktopApplicationMenu.test.ts +0 −1
  10. pnpm-lock.yaml +48 −48
  11. pnpm-workspace.yaml +12 −12
  12. scripts/build-desktop-artifact.test.ts +5 −0
  13. scripts/build-desktop-artifact.ts +9 −0
apps/desktop/src/app/DesktopAppIdentity.test.ts+0−1
@@ -54,7 +54,6 @@ const makeElectronAppLayer = (calls: ElectronAppCalls) => · 53 unmodified lines
5454 calls.setAboutPanelOptions.push(options);
5555 }),
5656 setAppUserModelId: () => Effect.void,
57− requestSingleInstanceLock: Effect.succeed(true),
5857 getAppMetrics: Effect.succeed([]),
5958 isDefaultProtocolClient: () => Effect.succeed(false),
6059 setAsDefaultProtocolClient: () => Effect.succeed(true),
apps/desktop/src/app/DesktopAppIdentity.ts+27−19
@@ -45,6 +45,27 @@ const normalizeCommitHash = (value: string): Option.Option<string> => { · 44 unmodified lines
4545 : Option.none();
4646 };
4747
48+export const resolveUserDataPath = Effect.gen(function* () {
49+ const environment = yield* DesktopEnvironment.DesktopEnvironment;
50+ const fileSystem = yield* FileSystem.FileSystem;
51+ const legacyPath = environment.path.join(
52+ environment.appDataDirectory,
53+ environment.legacyUserDataDirName,
54+ );
55+ const legacyPathExists = yield* fileSystem.exists(legacyPath).pipe(
56+ Effect.mapError(
57+ (cause) =>
58+ new DesktopUserDataPathResolutionError({
59+ legacyPath,
60+ cause,
61+ }),
62+ ),
63+ );
64+ return legacyPathExists
65+ ? legacyPath
66+ : environment.path.join(environment.appDataDirectory, environment.userDataDirName);
67+}).pipe(Effect.withSpan("desktop.appIdentity.resolveUserDataPath"));
68+
4869 export const make = Effect.gen(function* () {
4970 const assets = yield* DesktopAssets.DesktopAssets;
5071 const electronApp = yield* ElectronApp.ElectronApp;
@@ -90,24 +111,11 @@ export const make = Effect.gen(function* () { · 39 unmodified lines
90111 return commitHash;
91112 });
92113
93− const resolveUserDataPath = Effect.gen(function* () {
94− const legacyPath = environment.path.join(
95− environment.appDataDirectory,
96− environment.legacyUserDataDirName,
97− );
98− const legacyPathExists = yield* fileSystem.exists(legacyPath).pipe(
99− Effect.mapError(
100− (cause) =>
101− new DesktopUserDataPathResolutionError({
102− legacyPath,
103− cause,
104− }),
105− ),
106− );
107− return legacyPathExists
108− ? legacyPath
109− : environment.path.join(environment.appDataDirectory, environment.userDataDirName);
110− }).pipe(Effect.withSpan("desktop.appIdentity.resolveUserDataPath"));
114+ const userDataPath = resolveUserDataPath.pipe(
115+ Effect.provide(
116+ yield* Effect.context<DesktopEnvironment.DesktopEnvironment | FileSystem.FileSystem>(),
117+ ),
118+ );
111119
112120 const configure = Effect.gen(function* () {
113121 const commitHash = yield* resolveAboutCommitHash;
@@ -136,7 +144,7 @@ export const make = Effect.gen(function* () { · 22 unmodified lines
136144 }).pipe(Effect.withSpan("desktop.appIdentity.configure"));
137145
138146 return DesktopAppIdentity.of({
139− resolveUserDataPath,
147+ resolveUserDataPath: userDataPath,
140148 configure,
141149 });
142150 });
apps/desktop/src/app/DesktopClerk.test.ts+90−5
@@ -22,17 +22,38 @@ vi.mock("@clerk/electron/storage", () => ({ · 21 unmodified lines
2222 storage: storageMock,
2323 }));
2424
25+import * as Exit from "effect/Exit";
26+import * as FileSystem from "effect/FileSystem";
27+import * as ElectronApp from "../electron/ElectronApp.ts";
28+import * as ElectronWindow from "../electron/ElectronWindow.ts";
2529 import * as DesktopClerk from "./DesktopClerk.ts";
2630 import * as DesktopEnvironment from "./DesktopEnvironment.ts";
2731
28−const makeDesktopClerkLayer = (isDevelopment = true) => {
32+const makeDesktopClerkLayer = (isDevelopment = true, events: string[] = []) => {
2933 const environment = DesktopEnvironment.DesktopEnvironment.of({
3034 stateDir: "/tmp/t3-state",
3135 isDevelopment,
36+ appDataDirectory: "/tmp/app-data",
37+ userDataDirName: isDevelopment ? "t3code-dev" : "t3code",
38+ legacyUserDataDirName: isDevelopment ? "T3 Code (Dev)" : "T3 Code (Alpha)",
39+ path: { join: (...parts: ReadonlyArray<string>) => parts.join("/") },
3240 } as unknown as DesktopEnvironment.DesktopEnvironment["Service"]);
3341
42+ const electronApp = {
43+ setPath: (name: string, value: string) =>
44+ Effect.sync(() => {
45+ events.push(`setPath:${name}:${value}`);
46+ }),
47+ } as unknown as ElectronApp.ElectronApp["Service"];
48+
3449 return DesktopClerk.layer.pipe(
35− Layer.provide(Layer.succeed(DesktopEnvironment.DesktopEnvironment, environment)),
50+ Layer.provide(
51+ Layer.mergeAll(
52+ Layer.succeed(DesktopEnvironment.DesktopEnvironment, environment),
53+ Layer.succeed(ElectronApp.ElectronApp, electronApp),
54+ FileSystem.layerNoop({ exists: () => Effect.succeed(false) }),
55+ ),
56+ ),
3657 );
3758 };
3859
@@ -55,11 +76,15 @@ describe("DesktopClerk", () => { · 16 unmodified lines
5576
5677 it.effect("acquires and releases the SDK bridge with the layer", () => {
5778 const cleanup = vi.fn();
79+ const events: string[] = [];
5880 storageMock.mockReturnValue(storageAdapter);
59− createClerkBridgeMock.mockReturnValue({ cleanup });
81+ createClerkBridgeMock.mockImplementation(() => {
82+ events.push("createClerkBridge");
83+ return { cleanup, isPrimaryInstance: true };
84+ });
6085
6186 return Effect.gen(function* () {
62− yield* Effect.scoped(Layer.build(makeDesktopClerkLayer()));
87+ yield* Effect.scoped(Layer.build(makeDesktopClerkLayer(true, events)));
6388
6489 assert.deepEqual(createClerkBridgeMock.mock.calls, [
6590 [
@@ -71,6 +96,10 @@ describe("DesktopClerk", () => { · 5 unmodified lines
7196 ],
7297 ]);
7398 assert.equal(cleanup.mock.calls.length, 1);
99+ // The bridge acquires Electron's single-instance lock at creation, and
100+ // the lock both lives in and creates the userData directory — so the
101+ // real path must be set before the bridge exists.
102+ assert.deepEqual(events, ["setPath:userData:/tmp/app-data/t3code-dev", "createClerkBridge"]);
74103 storageMock.mockClear();
75104 createClerkBridgeMock.mockClear();
76105 });
@@ -124,11 +153,67 @@ describe("DesktopClerk", () => { · 47 unmodified lines
124153 });
125154 });
126155
156+ it.effect("registers the second-instance handler in the primary instance", () => {
157+ storageMock.mockReturnValue(storageAdapter);
158+ createClerkBridgeMock.mockReturnValue({ cleanup: vi.fn(), isPrimaryInstance: true });
159+ const quit = vi.fn();
160+ const registeredEvents: string[] = [];
161+ const electronApp = {
162+ quit: Effect.sync(quit),
163+ on: (eventName: string) =>
164+ Effect.sync(() => {
165+ registeredEvents.push(eventName);
166+ }),
167+ } as unknown as ElectronApp.ElectronApp["Service"];
168+ const electronWindow = {} as ElectronWindow.ElectronWindow["Service"];
169+
170+ return Effect.gen(function* () {
171+ const clerk = yield* DesktopClerk.DesktopClerk;
172+ const exit = yield* Effect.exit(Effect.scoped(clerk.configure));
173+
174+ assert.isTrue(Exit.isSuccess(exit));
175+ assert.equal(quit.mock.calls.length, 0);
176+ assert.deepEqual(registeredEvents, ["second-instance"]);
177+ }).pipe(
178+ Effect.provide(makeDesktopClerkLayer()),
179+ Effect.provideService(ElectronApp.ElectronApp, electronApp),
180+ Effect.provideService(ElectronWindow.ElectronWindow, electronWindow),
181+ );
182+ });
183+
184+ it.effect("quits and interrupts startup in a secondary instance", () => {
185+ storageMock.mockReturnValue(storageAdapter);
186+ createClerkBridgeMock.mockReturnValue({ cleanup: vi.fn(), isPrimaryInstance: false });
187+ const quit = vi.fn();
188+ const registeredEvents: string[] = [];
189+ const electronApp = {
190+ quit: Effect.sync(quit),
191+ on: (eventName: string) =>
192+ Effect.sync(() => {
193+ registeredEvents.push(eventName);
194+ }),
195+ } as unknown as ElectronApp.ElectronApp["Service"];
196+ const electronWindow = {} as ElectronWindow.ElectronWindow["Service"];
197+
198+ return Effect.gen(function* () {
199+ const clerk = yield* DesktopClerk.DesktopClerk;
200+ const exit = yield* Effect.exit(Effect.scoped(clerk.configure));
201+
202+ assert.isTrue(Exit.hasInterrupts(exit));
203+ assert.equal(quit.mock.calls.length, 1);
204+ assert.deepEqual(registeredEvents, []);
205+ }).pipe(
206+ Effect.provide(makeDesktopClerkLayer()),
207+ Effect.provideService(ElectronApp.ElectronApp, electronApp),
208+ Effect.provideService(ElectronWindow.ElectronWindow, electronWindow),
209+ );
210+ });
211+
127212 it.each([
128213 { isDevelopment: true, scheme: "t3code-dev" },
129214 { isDevelopment: false, scheme: "t3code" },
130215 ])("configures the SDK with the $scheme renderer origin", ({ isDevelopment, scheme }) => {
131− const bridge = { cleanup: vi.fn() };
216+ const bridge = { cleanup: vi.fn(), isPrimaryInstance: true };
132217 storageMock.mockReturnValue(storageAdapter);
133218 createClerkBridgeMock.mockReturnValue(bridge);
134219
apps/desktop/src/app/DesktopClerk.ts+19−2
@@ -11,6 +11,7 @@ import { clerkFrontendApiHostnameFromPublishableKey } from "@t3tools/shared/rela · 10 unmodified lines
1111 import * as ElectronApp from "../electron/ElectronApp.ts";
1212 import * as ElectronProtocol from "../electron/ElectronProtocol.ts";
1313 import * as ElectronWindow from "../electron/ElectronWindow.ts";
14+import * as DesktopAppIdentity from "./DesktopAppIdentity.ts";
1415 import * as DesktopEnvironment from "./DesktopEnvironment.ts";
1516
1617 declare const __T3CODE_BUILD_CLERK_PUBLISHABLE_KEY__: string | undefined;
@@ -84,7 +85,18 @@ export function createDesktopClerkBridge(stateDir: string, isDevelopment: boolea · 67 unmodified lines
8485
8586 export const make = Effect.gen(function* () {
8687 const environment = yield* DesktopEnvironment.DesktopEnvironment;
87− yield* Effect.acquireRelease(
88+ const electronApp = yield* ElectronApp.ElectronApp;
89+
90+ // Electron scopes the single-instance lock to the userData directory and
91+ // creates that directory when the lock is acquired. The SDK bridge takes
92+ // the lock at creation, so userData must already point at the real
93+ // directory here — under the default productName-derived path, acquiring
94+ // the lock would create "T3 Code (Alpha)" and make the legacy-install
95+ // detection in resolveUserDataPath match on fresh installs.
96+ const userDataPath = yield* DesktopAppIdentity.resolveUserDataPath;
97+ yield* electronApp.setPath("userData", userDataPath);
98+
99+ const bridge = yield* Effect.acquireRelease(
88100 Effect.try({
89101 try: () => createDesktopClerkBridge(environment.stateDir, environment.isDevelopment),
90102 catch: (cause) =>
@@ -113,7 +125,12 @@ export const make = Effect.gen(function* () { · 22 unmodified lines
113125 const context = yield* Effect.context<ElectronWindow.ElectronWindow>();
114126 const runPromise = Effect.runPromiseWith(context);
115127
116− if (!(yield* electronApp.requestSingleInstanceLock)) {
128+ // The SDK bridge holds Electron's single-instance lock (acquired at
129+ // bridge creation) so OAuth deep-link callbacks on Windows/Linux are
130+ // forwarded to the running app. In a secondary instance the bridge has
131+ // already begun quitting the app; app.quit() is asynchronous, so stop
132+ // bootstrap here before whenReady can fire.
133+ if (!bridge.isPrimaryInstance) {
117134 yield* electronApp.quit;
118135 return yield* Effect.interrupt;
119136 }
apps/desktop/src/app/DesktopLifecycle.test.ts+0−1
@@ -29,7 +29,6 @@ describe("DesktopLifecycle", () => { · 28 unmodified lines
2929 setName: () => Effect.void,
3030 setAboutPanelOptions: () => Effect.void,
3131 setAppUserModelId: () => Effect.void,
32− requestSingleInstanceLock: Effect.succeed(true),
3332 getAppMetrics: Effect.succeed([]),
3433 isDefaultProtocolClient: () => Effect.succeed(false),
3534 setAsDefaultProtocolClient: () => Effect.succeed(true),
apps/desktop/src/electron/ElectronApp.test.ts+0−3
@@ -14,7 +14,6 @@ const { · 13 unmodified lines
1414 quitMock,
1515 relaunchMock,
1616 removeListenerMock,
17− requestSingleInstanceLockMock,
1817 setAboutPanelOptionsMock,
1918 setAppUserModelIdMock,
2019 setAsDefaultProtocolClientMock,
@@ -35,7 +34,6 @@ const { · 14 unmodified lines
3534 quitMock: vi.fn(),
3635 relaunchMock: vi.fn(),
3736 removeListenerMock: vi.fn(),
38− requestSingleInstanceLockMock: vi.fn(() => true),
3937 setAboutPanelOptionsMock: vi.fn(),
4038 setAppUserModelIdMock: vi.fn(),
4139 setAsDefaultProtocolClientMock: vi.fn(() => true),
@@ -67,7 +65,6 @@ vi.mock("electron", () => ({ · 25 unmodified lines
6765 quit: quitMock,
6866 relaunch: relaunchMock,
6967 removeListener: removeListenerMock,
70− requestSingleInstanceLock: requestSingleInstanceLockMock,
7168 runningUnderARM64Translation: false,
7269 setAboutPanelOptions: setAboutPanelOptionsMock,
7370 setAsDefaultProtocolClient: setAsDefaultProtocolClientMock,
apps/desktop/src/electron/ElectronApp.ts+0−2
@@ -56,7 +56,6 @@ export class ElectronApp extends Context.Service< · 55 unmodified lines
5656 options: Electron.AboutPanelOptionsOptions,
5757 ) => Effect.Effect<void>;
5858 readonly setAppUserModelId: (id: string) => Effect.Effect<void>;
59− readonly requestSingleInstanceLock: Effect.Effect<boolean>;
6059 readonly getAppMetrics: Effect.Effect<ReadonlyArray<Electron.ProcessMetric>>;
6160 readonly isDefaultProtocolClient: (protocol: string) => Effect.Effect<boolean>;
6261 readonly setAsDefaultProtocolClient: (
@@ -153,7 +152,6 @@ export const make = ElectronApp.of({ · 90 unmodified lines
153152 Effect.sync(() => {
154153 Electron.app.setAppUserModelId(id);
155154 }),
156− requestSingleInstanceLock: Effect.sync(() => Electron.app.requestSingleInstanceLock()),
157155 getAppMetrics: Effect.sync(() => Electron.app.getAppMetrics()),
158156 isDefaultProtocolClient: (protocol) =>
159157 Effect.sync(() => Electron.app.isDefaultProtocolClient(protocol)),
apps/desktop/src/telemetry/DesktopTelemetryPublisher.test.ts+0−1
@@ -34,7 +34,6 @@ function makeElectronAppLayer( · 33 unmodified lines
3434 setName: () => Effect.void,
3535 setAboutPanelOptions: () => Effect.void,
3636 setAppUserModelId: () => Effect.void,
37− requestSingleInstanceLock: Effect.succeed(true),
3837 getAppMetrics: Effect.sync(() => {
3938 onMetricsRead();
4039 return metrics;
apps/desktop/src/window/DesktopApplicationMenu.test.ts+0−1
@@ -39,7 +39,6 @@ const electronAppLayer = Layer.succeed(ElectronApp.ElectronApp, { · 38 unmodified lines
3939 setName: () => Effect.void,
4040 setAboutPanelOptions: () => Effect.void,
4141 setAppUserModelId: () => Effect.void,
42− requestSingleInstanceLock: Effect.succeed(true),
4342 getAppMetrics: Effect.succeed([]),
4443 isDefaultProtocolClient: () => Effect.succeed(false),
4544 setAsDefaultProtocolClient: () => Effect.succeed(true),
pnpm-lock.yaml+48−48
@@ -35,19 +35,19 @@ catalogs: · 34 unmodified lines
3535 version: 0.2.2
3636
3737 overrides:
38− '@clerk/backend': 3.13.0
39− '@clerk/clerk-js': 6.25.7
38+ '@clerk/backend': 3.14.0
39+ '@clerk/clerk-js': 6.25.12
4040 '@clerk/clerk-js>@base-org/account': '-'
4141 '@clerk/clerk-js>@coinbase/wallet-sdk': '-'
4242 '@clerk/clerk-js>@solana/wallet-adapter-base': '-'
4343 '@clerk/clerk-js>@solana/wallet-adapter-react': '-'
4444 '@clerk/clerk-js>@solana/wallet-standard': '-'
4545 '@clerk/clerk-js>@wallet-standard/core': '-'
46− '@clerk/electron': 0.0.18
46+ '@clerk/electron': 0.0.24
4747 '@clerk/electron-passkeys': 0.0.3
48− '@clerk/expo': 4.0.2
49− '@clerk/react': 6.12.7
50− '@clerk/shared': 4.25.7
48+ '@clerk/expo': 4.1.2
49+ '@clerk/react': 6.12.9
50+ '@clerk/shared': 4.25.9
5151 '@effect/atom-react': 4.0.0-beta.102
5252 '@effect/platform-bun': 4.0.0-beta.102
5353 '@effect/platform-node': 4.0.0-beta.102
@@ -111,8 +111,8 @@ importers: · 57 unmodified lines
111111 apps/desktop:
112112 dependencies:
113113 '@clerk/electron':
114− specifier: 0.0.18
115− version: 0.0.18(@clerk/electron-passkeys@0.0.3)(electron-store@8.2.0)(electron@41.5.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
114+ specifier: 0.0.24
115+ version: 0.0.24(@clerk/electron-passkeys@0.0.3)(electron-store@8.2.0)(electron@41.5.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
116116 '@clerk/electron-passkeys':
117117 specifier: 0.0.3
118118 version: 0.0.3
@@ -197,8 +197,8 @@ importers: · 78 unmodified lines
197197 specifier: ^0.7.1
198198 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)
199199 '@clerk/expo':
200− specifier: 4.0.2
201− version: 4.0.2(expo-auth-session@56.0.14(expo@56.0.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))(expo-constants@56.0.18)(expo-crypto@56.0.4(expo@56.0.12))(expo-secure-store@56.0.4(expo@56.0.12))(expo-web-browser@56.0.5(expo@56.0.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)))(expo@56.0.12)(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)
200+ specifier: 4.1.2
201+ version: 4.1.2(expo-auth-session@56.0.14(expo@56.0.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))(expo-constants@56.0.18)(expo-crypto@56.0.4(expo@56.0.12))(expo-secure-store@56.0.4(expo@56.0.12))(expo-web-browser@56.0.5(expo@56.0.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)))(expo@56.0.12)(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)
202202 '@effect/atom-react':
203203 specifier: 4.0.0-beta.102
204204 version: 4.0.0-beta.102(effect@4.0.0-beta.102(patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488))(react@19.2.3)(scheduler@0.27.0)
@@ -514,11 +514,11 @@ importers: · 309 unmodified lines
514514 specifier: ^1.4.1
515515 version: 1.5.0(@types/react@19.2.16)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
516516 '@clerk/electron':
517− specifier: 0.0.18
518− version: 0.0.18(@clerk/electron-passkeys@0.0.3)(electron-store@8.2.0)(electron@41.5.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
517+ specifier: 0.0.24
518+ version: 0.0.24(@clerk/electron-passkeys@0.0.3)(electron-store@8.2.0)(electron@41.5.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
519519 '@clerk/react':
520− specifier: 6.12.7
521− version: 6.12.7(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
520+ specifier: 6.12.9
521+ version: 6.12.9(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
522522 '@dnd-kit/core':
523523 specifier: ^6.3.1
524524 version: 6.3.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
@@ -668,8 +668,8 @@ importers: · 143 unmodified lines
668668 infra/relay:
669669 dependencies:
670670 '@clerk/backend':
671− specifier: 3.13.0
672− version: 3.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
671+ specifier: 3.14.0
672+ version: 3.14.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
673673 '@effect/sql-pg':
674674 specifier: 4.0.0-beta.102
675675 version: 4.0.0-beta.102(effect@4.0.0-beta.102(patch_hash=71215759e1ac0a7f65d7b75d816986687ae6c3a6cba02d928d184ca71790d488))
@@ -1695,12 +1695,12 @@ packages: · 1019 unmodified lines
16951695 resolution: {integrity: sha512-EYlRokl8szrP9Z25qT5aepMdBjzBvHF9ZEhzIiUBc9guz/T31EqRgvD0QSgZcpE93xiwrr+OkB4nz0BZyF6fSA==}
16961696 engines: {node: '>= 20.12.0'}
16971697
1698− '@clerk/backend@3.13.0':
1699− resolution: {integrity: sha512-kYOgeR48iXYypudpYITE0L2yxi4TeFXU+ISAOULlVgOcurw8qnm1/JEuK6Mr2IFUbRS9hcHk1HVO0Ceo0XmL8Q==}
1698+ '@clerk/backend@3.14.0':
1699+ resolution: {integrity: sha512-WsphTvDFHDuQilKI7dyVE5qmt7USu8qSrujAq6SqpEHbVFfNBfINDNuzxlF9M2skOTfHFfgiTE8Jjb1egIBGLg==}
17001700 engines: {node: '>=20.9.0'}
17011701
1702− '@clerk/clerk-js@6.25.7':
1703− resolution: {integrity: sha512-GOnbEUzPKAdsdCodZsHhku2YloBBuwLGx5UP0VeezUj73wKLwlSYMlVPuntsJoY7c8+lVrMCY9LQWYVZ7VMLBw==}
1702+ '@clerk/clerk-js@6.25.12':
1703+ resolution: {integrity: sha512-N91mFKenMF5e+LRQ0NsqFb8bw+icbwnF8MzjzS4cux72xG4Gekh772tiLIetbURe44Srgjw5lj8jkWbt+yH/FA==}
17041704 engines: {node: '>=20.9.0'}
17051705
17061706 '@clerk/electron-passkeys-darwin-arm64@0.0.3':
@@ -1727,8 +1727,8 @@ packages: · 20 unmodified lines
17271727 resolution: {integrity: sha512-OHhIe88qDL+FxyBalXdXNHAS5eEramr6Rerp+6iNkfkjqT8rx4hHNmfpmjg5/T1/am8QfknbOBZkqoXZlCrjPg==}
17281728 engines: {node: '>=20.9.0'}
17291729
1730− '@clerk/electron@0.0.18':
1731− resolution: {integrity: sha512-0lWiITlTxCviglPfPSu+uhERnQyXroiWrSMRpZcEO3FgBt3bnrpfgZUrXW8iaLoUYJrFP2byF7bHJPRwEPVXEw==}
1730+ '@clerk/electron@0.0.24':
1731+ resolution: {integrity: sha512-xORO51KLzCqmRQ4IVsa+Amp1M5NjG28QnvsaRBq1v4CUEjH8vAXDKCPiPBKAE+HuQB6HUhzYmzUkyTw2FcDonw==}
17321732 engines: {node: '>=20.9.0'}
17331733 peerDependencies:
17341734 '@clerk/electron-passkeys': 0.0.3
@@ -1744,8 +1744,8 @@ packages: · 9 unmodified lines
17441744 react-dom:
17451745 optional: true
17461746
1747− '@clerk/expo@4.0.2':
1748− resolution: {integrity: sha512-afBdV2bLRM55p5L7FoAGV/wCamLxXusjIBPmNo+TwAYabU0oUokMsm+0++xWDA0nxzMt/O08Oj+xAGEtE+Qvxg==}
1747+ '@clerk/expo@4.1.2':
1748+ resolution: {integrity: sha512-ZyGozbbizcGwEyK0CEXsPHnHnQoaa2CbTsXAqWBygzKEZyFjLjCHwQsQBKj6WgjAgn8mtw2ByIP9qLpItZffgw==}
17491749 engines: {node: '>=20.9.0'}
17501750 peerDependencies:
17511751 '@clerk/expo-google-signin': '>=0.1.0'
@@ -1783,15 +1783,15 @@ packages: · 31 unmodified lines
17831783 react-dom:
17841784 optional: true
17851785
1786− '@clerk/react@6.12.7':
1787− resolution: {integrity: sha512-K8CK0tS7My/3RT5sAIJTjYgnH0xU+K4Jdnhi76xQdAY4agD/M07BbrBMWXEE0xUEDst2PqPfX660V1LRLOsxOQ==}
1786+ '@clerk/react@6.12.9':
1787+ resolution: {integrity: sha512-lQ1UOJhHGDweLCDf4IgT5r3rgkch1Gx21hNqK2/kOohJrJd/XM89fq2F7VK9Kg/Byc14TsfCh64Dm8ej2MyRWw==}
17881788 engines: {node: '>=20.9.0'}
17891789 peerDependencies:
17901790 react: ^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0
17911791 react-dom: ^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0
17921792
1793− '@clerk/shared@4.25.7':
1794− resolution: {integrity: sha512-FfBbDeFkxwDuz/2YcVs4DhyZ0VxnqNtdbO0Tn/lkLAiDvFFspWw+PDYqo7LQiBymEQaOQkb094dX21WWnZbmlA==}
1793+ '@clerk/shared@4.25.9':
1794+ resolution: {integrity: sha512-nw3maGzqPrwmUKjGffOM9Zvw24IyOllq0TkQGzsTfQX312WLu2u5CNvjKntMb6Ok6kXa1Zdc77zmPLn1/EAIFA==}
17951795 engines: {node: '>=20.9.0'}
17961796 peerDependencies:
17971797 react: ^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0
@@ -11512,18 +11512,18 @@ snapshots: · 9714 unmodified lines
1151211512 fast-wrap-ansi: 0.2.2
1151311513 sisteransi: 1.0.5
1151411514
11515− '@clerk/backend@3.13.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
11515+ '@clerk/backend@3.14.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
1151611516 dependencies:
11517− '@clerk/shared': 4.25.7(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
11517+ '@clerk/shared': 4.25.9(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
1151811518 standardwebhooks: 1.0.0
1151911519 tslib: 2.8.1
1152011520 transitivePeerDependencies:
1152111521 - react
1152211522 - react-dom
1152311523
11524− '@clerk/clerk-js@6.25.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
11524+ '@clerk/clerk-js@6.25.12(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
1152511525 dependencies:
11526− '@clerk/shared': 4.25.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
11526+ '@clerk/shared': 4.25.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
1152711527 '@stripe/stripe-js': 5.6.0
1152811528 '@swc/helpers': 0.5.21
1152911529 '@tanstack/query-core': 5.100.14
@@ -11538,9 +11538,9 @@ snapshots: · 8 unmodified lines
1153811538 - react
1153911539 - react-dom
1154011540
11541− '@clerk/clerk-js@6.25.7(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
11541+ '@clerk/clerk-js@6.25.12(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
1154211542 dependencies:
11543− '@clerk/shared': 4.25.7(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
11543+ '@clerk/shared': 4.25.9(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
1154411544 '@stripe/stripe-js': 5.6.0
1154511545 '@swc/helpers': 0.5.21
1154611546 '@tanstack/query-core': 5.100.14
@@ -11574,11 +11574,11 @@ snapshots: · 27 unmodified lines
1157411574 '@clerk/electron-passkeys-win32-arm64-msvc': 0.0.3
1157511575 '@clerk/electron-passkeys-win32-x64-msvc': 0.0.3
1157611576
11577− '@clerk/electron@0.0.18(@clerk/electron-passkeys@0.0.3)(electron-store@8.2.0)(electron@41.5.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
11577+ '@clerk/electron@0.0.24(@clerk/electron-passkeys@0.0.3)(electron-store@8.2.0)(electron@41.5.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
1157811578 dependencies:
11579− '@clerk/clerk-js': 6.25.7(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
11580− '@clerk/react': 6.12.7(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
11581− '@clerk/shared': 4.25.7(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
11579+ '@clerk/clerk-js': 6.25.12(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
11580+ '@clerk/react': 6.12.9(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
11581+ '@clerk/shared': 4.25.9(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
1158211582 electron: 41.5.0
1158311583 react: 19.2.6
1158411584 tslib: 2.8.1
@@ -11587,11 +11587,11 @@ snapshots: · 2 unmodified lines
1158711587 electron-store: 8.2.0
1158811588 react-dom: 19.2.6(react@19.2.6)
1158911589
11590− '@clerk/expo@4.0.2(expo-auth-session@56.0.14(expo@56.0.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))(expo-constants@56.0.18)(expo-crypto@56.0.4(expo@56.0.12))(expo-secure-store@56.0.4(expo@56.0.12))(expo-web-browser@56.0.5(expo@56.0.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)))(expo@56.0.12)(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)':
11590+ '@clerk/expo@4.1.2(expo-auth-session@56.0.14(expo@56.0.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))(expo-constants@56.0.18)(expo-crypto@56.0.4(expo@56.0.12))(expo-secure-store@56.0.4(expo@56.0.12))(expo-web-browser@56.0.5(expo@56.0.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)))(expo@56.0.12)(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)':
1159111591 dependencies:
11592− '@clerk/clerk-js': 6.25.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
11593− '@clerk/react': 6.12.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
11594− '@clerk/shared': 4.25.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
11592+ '@clerk/clerk-js': 6.25.12(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
11593+ '@clerk/react': 6.12.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
11594+ '@clerk/shared': 4.25.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
1159511595 '@expo/config-plugins': 56.0.9(typescript@6.0.3)
1159611596 base-64: 1.0.0
1159711597 expo: 56.0.12(8895228379997a2a064f9644cda56ed0)
@@ -11610,21 +11610,21 @@ snapshots: · 12 unmodified lines
1161011610 - supports-color
1161111611 - typescript
1161211612
11613− '@clerk/react@6.12.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
11613+ '@clerk/react@6.12.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
1161411614 dependencies:
11615− '@clerk/shared': 4.25.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
11615+ '@clerk/shared': 4.25.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
1161611616 react: 19.2.3
1161711617 react-dom: 19.2.3(react@19.2.3)
1161811618 tslib: 2.8.1
1161911619
11620− '@clerk/react@6.12.7(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
11620+ '@clerk/react@6.12.9(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
1162111621 dependencies:
11622− '@clerk/shared': 4.25.7(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
11622+ '@clerk/shared': 4.25.9(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
1162311623 react: 19.2.6
1162411624 react-dom: 19.2.6(react@19.2.6)
1162511625 tslib: 2.8.1
1162611626
11627− '@clerk/shared@4.25.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
11627+ '@clerk/shared@4.25.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
1162811628 dependencies:
1162911629 '@tanstack/query-core': 5.100.14
1163011630 dequal: 2.0.3
@@ -11634,7 +11634,7 @@ snapshots: · 3 unmodified lines
1163411634 react: 19.2.3
1163511635 react-dom: 19.2.3(react@19.2.3)
1163611636
11637− '@clerk/shared@4.25.7(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
11637+ '@clerk/shared@4.25.9(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
1163811638 dependencies:
1163911639 '@tanstack/query-core': 5.100.14
1164011640 dequal: 2.0.3
pnpm-workspace.yaml+12−12
@@ -23,13 +23,13 @@ allowBuilds: · 22 unmodified lines
2323 workerd: false
2424
2525 catalog:
26− "@clerk/backend": 3.13.0
27− "@clerk/clerk-js": 6.25.7
28− "@clerk/electron": 0.0.18
26+ "@clerk/backend": 3.14.0
27+ "@clerk/clerk-js": 6.25.12
28+ "@clerk/electron": 0.0.24
2929 "@clerk/electron-passkeys": 0.0.3
30− "@clerk/expo": 4.0.2
31− "@clerk/react": 6.12.7
32− "@clerk/shared": 4.25.7
30+ "@clerk/expo": 4.1.2
31+ "@clerk/react": 6.12.9
32+ "@clerk/shared": 4.25.9
3333 "@effect/atom-react": 4.0.0-beta.102
3434 "@effect/openapi-generator": 4.0.0-beta.102
3535 "@effect/platform-bun": 4.0.0-beta.102
@@ -52,12 +52,12 @@ catalog: · 16 unmodified lines
5252 yaml: ^2.9.0
5353
5454 minimumReleaseAgeExclude:
55− - "@clerk/backend@3.13.0"
56− - "@clerk/clerk-js@6.25.7"
57− - "@clerk/electron@0.0.18"
58− - "@clerk/expo@4.0.2"
59− - "@clerk/react@6.12.7"
60− - "@clerk/shared@4.25.7"
55+ - "@clerk/backend@3.14.0"
56+ - "@clerk/clerk-js@6.25.12"
57+ - "@clerk/electron@0.0.24"
58+ - "@clerk/expo@4.1.2"
59+ - "@clerk/react@6.12.9"
60+ - "@clerk/shared@4.25.9"
6161 - "@distilled.cloud/aws@0.30.2"
6262 - "@distilled.cloud/axiom@0.30.2"
6363 - "@distilled.cloud/cloudflare@0.30.2"
scripts/build-desktop-artifact.test.ts+5−0
@@ -349,6 +349,11 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { · 348 unmodified lines
349349 assert.notProperty(mac, "asarUnpack");
350350 assert.notProperty(linux, "asarUnpack");
351351 assert.deepStrictEqual(win.asarUnpack, WINDOWS_ASAR_UNPACK);
352+ // Linux must register the renderer schemes so the generated .desktop
353+ // entry advertises MimeType=x-scheme-handler/t3code; for OAuth deep links.
354+ assert.deepStrictEqual((linux.linux as Record<string, unknown>).protocols, [
355+ { name: "T3 Code", schemes: ["t3code", "t3code-dev"] },
356+ ]);
352357 for (const config of [mac, linux, win]) {
353358 assert.deepStrictEqual(config.electronLanguages, DESKTOP_ELECTRON_LANGUAGES);
354359 assert.deepStrictEqual(config.files, DESKTOP_FILE_EXCLUSIONS);
scripts/build-desktop-artifact.ts+9−0
@@ -1589,6 +1589,15 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* ( · 1588 unmodified lines
15891589 executableName: "t3code",
15901590 icon: "icons",
15911591 category: "Development",
1592+ // electron-builder turns these into MimeType=x-scheme-handler/<scheme>;
1593+ // in the .desktop entry (Exec already gets %U), so browsers can hand
1594+ // t3code:// OAuth callbacks to the app.
1595+ protocols: [
1596+ {
1597+ name: "T3 Code",
1598+ schemes: ["t3code", "t3code-dev"],
1599+ },
1600+ ],
15921601 desktop: {
15931602 entry: {
15941603 StartupWMClass: "t3code",