Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
88ab83e
fix(web): reconnecting notice no longer shifts the new thread compose…
t3dotgg Oct 29, 2026
20497e4
feat(marketing): homepage looks like the app (#26960)
t3dotgg Oct 29, 2026
37fbefd
chore(release): prepare v0.1.5
t3dotgg Oct 29, 2026
960d576
fix(cli): restore Windows desktop launcher console input (#24870)
t3-code-bot[bot] Oct 29, 2026
7a4f5b6
feat(settings): add and list 1Password secret providers behind T3CODE…
yordis Oct 29, 2026
d152784
fix(mobile): use monospace in the Android terminal preview (#23401)
only21mil Oct 29, 2026
ecea6ca
fix(web): edit from here uses a pencil icon (#21878)
voltcrash Oct 29, 2026
b4da896
fix(desktop): clear Cache Storage when deleting browser profile data …
only21mil Oct 29, 2026
3d5baaf
fix(web): respect reduced motion in thread loading controls (#22100)
saphid Oct 29, 2026
49d0142
fix(provider-acp): redact API keys with underscores in ACP stderr log…
only21mil Oct 29, 2026
67afdd5
fix(provider-core): redact compound credential keys in provider error…
only21mil Oct 29, 2026
456892d
fix(web): show rounded diff counts as 1m instead of 1000k (#23723)
only21mil Oct 29, 2026
5e35f6e
fix(desktop): a timed-out login-shell probe no longer skips the serve…
tiliakoos Oct 29, 2026
55e7b5f
fix(server): keep the previous preview browser version on a shared ho…
only21mil Oct 29, 2026
ee81ea4
fix(settings): servers check secret provider writes and keep unknown …
juliusmarminge Oct 29, 2026
bfee446
fix(web): keep a closed split terminal's tab in view (#22106)
only21mil Oct 29, 2026
69310db
fix(web): the ~ breadcrumb opens the home folder (#22412)
Onkarj012 Oct 29, 2026
505d022
fix(gitlab): accept merge request URLs from self-hosted hosts (#23848)
only21mil Oct 29, 2026
2c37797
fix(server): commits no longer land on a branch switched in mid-actio…
only21mil Oct 29, 2026
3b066da
fix(server): image attachments report their size when the T3 home is …
MarcoDotIO Oct 29, 2026
ebc34da
fix(ssh): skip directories in config includes (#25187)
maria-rcks Oct 29, 2026
80dfe47
fix(server): Authorized clients names Edge, Firefox and Opera on phon…
MarcoDotIO Oct 29, 2026
1d7af65
fix(mobile): fork rows show a readable label instead of a thread ID (…
JeremiahDJordan Oct 29, 2026
1d63134
fix(markdown): formulas ending in a comma render (#22883)
NikitaMGrimm Oct 29, 2026
2049052
fix(scripts): dev runner expands ~ in --home-dir and T3CODE_HOME (#25…
MarcoDotIO Oct 29, 2026
1dd8c67
fix(desktop): keep development protocol requests on the configured se…
only21mil Oct 29, 2026
e0f972e
fix(mobile): apply terminal toolbar modifiers to arrow keys (#22161)
only21mil Oct 29, 2026
40f6f5b
fix(server): clean old logs and artifacts under a symlinked T3 home (…
only21mil Oct 29, 2026
b02b436
fix(server): preserve URL arguments in Android device actions (#25205)
maria-rcks Oct 29, 2026
c839797
fix(server): keep terminal history after cancelled sequences (#25193)
maria-rcks Oct 29, 2026
5aa81a3
fix(auth): accept a typed pairing code regardless of letter case (#22…
only21mil Oct 29, 2026
a03e630
fix(web): find in thread opens from the thread title menu, so phones …
mkantautas Oct 29, 2026
e692729
fix(desktop): resolve executable commands when checking CLI shadowing…
only21mil Oct 29, 2026
a8e4188
fix(server): SQLite temp files no longer block the event loop (#24194)
only21mil Oct 29, 2026
849d2b5
fix(server): a branch named like a tag no longer reads as heads/<name…
MarcoDotIO Oct 29, 2026
0ed5540
fix(server): cap generated worktree folder names (#25212)
maria-rcks Oct 29, 2026
c6f2b7d
perf(server): checkpoint capture scans the index listing by record in…
josephv123 Oct 29, 2026
aaba9de
fix(server): PR discovery sweeps skip hidden subagent threads (#26983)
juliusmarminge Oct 29, 2026
8084e00
fix(shared): stop parsing CLI flags after -- (#25204)
maria-rcks Oct 29, 2026
293669d
fix(server): remove abandoned v1 database copies on startup (#25301)
maria-rcks Oct 29, 2026
6ca95bd
fix(web): Enter no longer skips an unanswered question (#23626)
mkantautas Oct 29, 2026
d152335
fix(web): keep wide characters in terminal links (#24013)
only21mil Oct 29, 2026
a49a3f7
fix(server): session stops no longer trigger storage sweeps unless a …
juliusmarminge Oct 29, 2026
443edd5
fix(provider-core): subagent threads no longer inherit the parent's p…
only21mil Oct 29, 2026
1b3e330
fix(device): report failed Android permission changes (#22358)
only21mil Oct 29, 2026
0d51375
fix(server): thread lists load beyond SQLite variable limit (#25696)
juliusmarminge Oct 29, 2026
1e3e3a8
fix(server): allow large git status metadata (#25207)
maria-rcks Oct 29, 2026
781cc55
perf(mobile): flatten large file trees without copying descendants (#…
luke2x Oct 29, 2026
80ae9b5
fix(web): show theme conflicts even when another import fails (#23223)
only21mil Oct 29, 2026
061dc79
fix(usage): reject cyclic model mappings before saving (#23963)
only21mil Oct 29, 2026
bae3c19
fix(settings): offer Use existing CLI in managed Codex settings (#23160)
only21mil Oct 29, 2026
1e95da7
fix(git): honor project provider settings (#23672)
parmartejass Oct 29, 2026
efb940d
fix(desktop): force-close stalled Linux MIME cache helpers (#25210)
maria-rcks Oct 29, 2026
bdf84df
fix(cli): auth pairing create rejects a --base-url without http(s)://…
MarcoDotIO Oct 29, 2026
4afdaba
fix(server): signed asset and upload URLs stay out of HTTP traces (#2…
only21mil Oct 29, 2026
1cc9d41
fix(server): clone errors no longer show a token from the URL's query…
only21mil Oct 29, 2026
c9d3f7c
fix(claude): pass every --add-dir launch directory to Claude (#23281)
only21mil Oct 29, 2026
4b7a347
fix(composer): retry expired catalogs after failed discovery (#23174)
only21mil Oct 29, 2026
7860ee9
fix(auth): preserve pairing codes after scope rejection (#25222)
maria-rcks Oct 29, 2026
04f3538
fix(mobile): discard unchanged queued edits after starting (#25232)
maria-rcks Oct 29, 2026
b7c7a78
fix(server): t3 project commands wait for a busy server's reply (#24090)
only21mil Oct 29, 2026
9d38d68
fix(web): wait for fresh diff pages in review guides (#23225)
only21mil Oct 29, 2026
99ff58a
fix(server): Antigravity stops retrying session startup while signed …
only21mil Oct 29, 2026
de386ad
fix(web): create tasks on a permitted connected environment (#23244)
only21mil Oct 29, 2026
fa2a2dc
fix(mobile): recognize this thread's main checkout (#23764)
only21mil Oct 29, 2026
06ded0b
fix(web): keep usage hubs that share a host (#23767)
only21mil Oct 29, 2026
f8646e0
fix(github): read text and mixed-case pull request templates (#25025)
maria-rcks Oct 29, 2026
de2b1d3
fix(web): keep HTML prototype selection rings visible (#23745)
stevan-borus Oct 29, 2026
12bd9d6
fix(mobile): list pull request repositories once per environment (#25…
maria-rcks Oct 29, 2026
4e274ae
fix(shared): Open in Zed prefers zeditor over the ZFS zed on Linux (#…
pujitha24 Oct 29, 2026
ef51594
fix(server): native Codex subagents no longer show the parent's reaso…
koushikxd Oct 29, 2026
3bd64cf
fix(server): mark unread works after an automatic compaction (#25286)
flamboh Oct 29, 2026
a1621a8
fix(server): show MCP App forms from failed tool results (#25225)
maria-rcks Oct 29, 2026
ad5ba43
fix(server): restore Linux session display for editor launches (#25228)
maria-rcks Oct 29, 2026
9e60e94
fix(usage): keep host identity across container recreation (#25220)
maria-rcks Oct 29, 2026
d796063
fix(opencode): authenticate with the resolved server password (#25230)
maria-rcks Oct 29, 2026
f06dd4b
feat(web): allow resizing the file explorer (#25737)
maria-rcks Oct 29, 2026
f4147dd
fix(server): update shared Codex installs from their owning home (#25…
maria-rcks Oct 29, 2026
8bcc991
fix(cli): keep published runtimes in place during installation (#24092)
only21mil Oct 29, 2026
e836ab1
fix(web): right panel launcher letters work on non-Latin keyboard lay…
MarMarLabs Oct 29, 2026
5b4f2aa
fix(server): report a deleted or replaced ownership lock (#21886)
ScottN-PV Oct 29, 2026
cb5fac9
fix(server): mark unread no longer brings back a dismissed wake (#24080)
fixfon Oct 29, 2026
fa4f3a2
fix(mobile): stop refreshes in hidden PR Guide panes (#25486)
saphid Oct 29, 2026
b6fbd18
fix(desktop): installing the t3 command no longer reports its own lau…
Gourav224 Oct 29, 2026
1fe8ddd
test(mobile): load the branch occupancy test without native sources (…
maria-rcks Oct 29, 2026
08a463e
fix(settings): project edits keep overrides the client does not know …
only21mil Oct 29, 2026
b069ccf
Merge tag 'v0.1.6-nightly.20261029.3892' into custom-nightly
chamaquita-d3[bot] Oct 29, 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
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@t3tools/desktop",
"version": "0.1.4",
"version": "0.1.5",
"private": true,
"type": "module",
"main": "dist-electron/boot.cjs",
Expand Down
70 changes: 70 additions & 0 deletions apps/desktop/src/app/DesktopCliCommand.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as NodeServices from "@effect/platform-node/NodeServices";
import { afterEach, beforeEach, expect, it } from "@effect/vitest";
import * as HostProcess from "@t3tools/shared/HostProcess";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Option from "effect/Option";
Expand Down Expand Up @@ -135,6 +136,14 @@ it.layer(NodeServices.layer)("DesktopCliCommand", (it) => {
yield* command.uninstall;
expect(yield* fs.readFileString(theirs)).toBe("npm's t3\n");
expect(yield* fs.readLink(path.join(home, "bin", "t3"))).toBe(path.join(home, "gone"));

const launcher = path.join(home, ".t3", "bin", "t3");
yield* fs.writeFileString(launcher, "npm's t3\n");
process.env.PATH = path.dirname(launcher);
expect(yield* command.state).toMatchObject({ onPath: false, shadowedBy: launcher });
expect((yield* Effect.flip(command.install)).message).toContain("Could not set up");
yield* command.uninstall;
expect(yield* fs.readFileString(launcher)).toBe("npm's t3\n");
}).pipe(Effect.scoped),
);

Expand Down Expand Up @@ -183,6 +192,67 @@ it.layer(NodeServices.layer)("DesktopCliCommand", (it) => {
}).pipe(Effect.scoped),
);

it.effect.skipIf(HostProcess.Platform.defaultValue() === "win32")(
"skips a t3 directory or a file this user cannot execute",
() =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const home = yield* fs.makeTempDirectoryScoped();
const folder = path.join(home, "folder");
const plain = path.join(home, "plain");
const shadow = path.join(home, "shadow");
yield* fs.makeDirectory(path.join(folder, "t3"), { recursive: true });
yield* fs.makeDirectory(plain);
yield* fs.writeFileString(path.join(plain, "t3"), "notes\n");
// Root can execute mode 0o601; other users must honor the owner permissions.
yield* fs.chmod(path.join(plain, "t3"), process.getuid?.() === 0 ? 0o600 : 0o601);
yield* fs.makeDirectory(shadow);
yield* fs.writeFileString(path.join(shadow, "t3"), "#!/bin/sh\n", { mode: 0o755 });
process.env.PATH = [folder, plain, shadow, path.join(home, ".local", "bin")].join(":");

const command = yield* commandIn({ home });
expect((yield* command.state).shadowedBy).toBe(path.join(shadow, "t3"));

const error = yield* Effect.flip(command.install);
expect(error.message).toContain(path.join(shadow, "t3"));

yield* fs.remove(shadow, { recursive: true });
expect(yield* command.install).toMatchObject({
onPath: true,
installedPath: path.join(home, ".local", "bin", "t3"),
});
}).pipe(Effect.scoped),
);

it.effect("does not treat its own launcher as another t3 when its folder is first on PATH", () =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const home = yield* fs.makeTempDirectoryScoped();
const launcher = path.join(home, ".t3", "bin", "t3");
process.env.PATH = [path.dirname(launcher), path.join(home, ".local", "bin")].join(":");

const command = yield* commandIn({ home });
const installed = yield* command.install;

expect(yield* fs.exists(launcher)).toBe(true);
expect(installed).toMatchObject({
onPath: true,
installedPath: path.join(home, ".local", "bin", "t3"),
});
expect(installed.shadowedBy).toBeUndefined();
expect(yield* command.state).toEqual(installed);

const removed = yield* command.uninstall;
expect(removed.installedPath).toBeNull();
expect(removed.onPath).toBe(true);
expect(yield* command.state).toEqual(removed);
expect(yield* fs.exists(installed.installedPath!)).toBe(false);
expect(yield* fs.exists(launcher)).toBe(true);
}).pipe(Effect.scoped),
);

it.effect("leaves the Windows PATH alone when it cannot be read", () =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
Expand Down
44 changes: 37 additions & 7 deletions apps/desktop/src/app/DesktopCliCommand.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// @effect-diagnostics-next-line nodeBuiltinImport:off -- FileSystem.access has no execute check.
import * as NodeFSP from "node:fs/promises";

import type { DesktopCliCommandState } from "@t3tools/contracts";
import * as Context from "effect/Context";
import * as Effect from "effect/Effect";
Expand Down Expand Up @@ -89,6 +92,19 @@ export const make = Effect.gen(function* () {
Effect.orElseSucceed(() => false),
);

// PATH can reach the regular launcher directly; removal still requires isOurLink.
const runsOurLauncher = Effect.fn("DesktopCliCommand.runsOurLauncher")(
function* (candidate: string) {
if (yield* isOurLink(candidate)) return true;
const [resolved, ours] = yield* Effect.all([fs.realPath(candidate), fs.realPath(launcher)]);
if (resolved !== ours) return false;
const info = yield* fs.stat(candidate);
if (info.type !== "File" || Number(info.size) > 16_384) return false;
return (yield* fs.readFileString(candidate)).includes(DesktopCliShim.MARKER);
},
Effect.orElseSucceed(() => false),
);

const powershell = (env: Record<string, string>) =>
Effect.scoped(
Effect.gen(function* () {
Expand Down Expand Up @@ -118,18 +134,27 @@ export const make = Effect.gen(function* () {
Effect.mapError(() => fail("Could not update your PATH.")),
);

const isExecutableFile = Effect.fn("DesktopCliCommand.isExecutableFile")(
function* (target: string) {
if ((yield* fs.stat(target)).type !== "File") return false;
yield* Effect.tryPromise(() => NodeFSP.access(target, NodeFSP.constants.X_OK));
return true;
},
Effect.orElseSucceed(() => false),
);

const firstOnPath = Effect.gen(function* () {
for (const directory of pathEntries(process.env.PATH, ":")) {
const candidate = path.join(directory, "t3");
if (yield* exists(candidate)) return Option.some(candidate);
if (yield* isExecutableFile(candidate)) return Option.some(candidate);
}
return Option.none<string>();
});

const foreignFirstOnPath = Effect.gen(function* () {
if (windows) return Option.none<string>();
const first = yield* firstOnPath;
if (Option.isNone(first) || (yield* isOurLink(first.value))) return Option.none<string>();
if (Option.isNone(first) || (yield* runsOurLauncher(first.value))) return Option.none<string>();
return first;
});

Expand All @@ -156,12 +181,17 @@ export const make = Effect.gen(function* () {
const installed = yield* installedAt;
const shadowedBy = yield* foreignFirstOnPath;
const shadow = Option.isSome(shadowedBy) ? { shadowedBy: shadowedBy.value } : {};
if (Option.isNone(installed)) {
return { supported: true, installedPath: null, onPath: false, launcherPath, ...shadow };
}
const first = yield* firstOnPath;
const onPath = windows || (Option.isSome(first) && (yield* isOurLink(first.value)));
return { supported: true, installedPath: installed.value, onPath, launcherPath, ...shadow };
const onPath = windows
? Option.isSome(installed)
: Option.isSome(first) && (yield* runsOurLauncher(first.value));
return {
supported: true,
installedPath: Option.getOrNull(installed),
onPath,
launcherPath,
...shadow,
};
}).pipe(
Effect.orElseSucceed(() => ({
supported: false,
Expand Down
71 changes: 71 additions & 0 deletions apps/desktop/src/app/DesktopLinuxUrlHandler.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import * as NodeServices from "@effect/platform-node/NodeServices";
import { assert, describe, it } from "@effect/vitest";
import * as HostProcess from "@t3tools/shared/HostProcess";
import * as Cause from "effect/Cause";
import * as Deferred from "effect/Deferred";
import * as Effect from "effect/Effect";
import * as Exit from "effect/Exit";
import * as Fiber from "effect/Fiber";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import * as Path from "effect/Path";
import * as PlatformError from "effect/PlatformError";
import * as Sink from "effect/Sink";
import * as Stream from "effect/Stream";
import * as TestClock from "effect/testing/TestClock";
import * as ChildProcess from "effect/process/ChildProcess";
import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";

import * as DesktopEnvironment from "./DesktopEnvironment.ts";
Expand Down Expand Up @@ -56,6 +62,7 @@ const layerHandler = (
recorded: RecordedRegistration,
input: {
readonly environment?: Record<string, unknown>;
readonly processSpawner?: ChildProcessSpawner.ChildProcessSpawner["Service"];
readonly updateDesktopDatabaseExitCode?: number;
readonly updateDesktopDatabaseStalled?: boolean;
readonly updateDesktopDatabaseStarted?: Deferred.Deferred<void>;
Expand Down Expand Up @@ -106,6 +113,7 @@ const layerHandler = (
Layer.succeed(
ChildProcessSpawner.ChildProcessSpawner,
ChildProcessSpawner.make((command) => {
if (input.processSpawner) return input.processSpawner.spawn(command);
const childProcess = command as unknown as {
readonly command: string;
readonly args: ReadonlyArray<string>;
Expand Down Expand Up @@ -358,6 +366,69 @@ describe("DesktopLinuxUrlHandler", () => {
assert.deepEqual(writeFailed.commands, []);
});
});

it.effect.skipIf(HostProcess.Platform.defaultValue() !== "linux")(
"continues registration after a timed-out cache helper ignores SIGTERM",
() =>
Effect.gen(function* () {
const realSpawner = yield* ChildProcessSpawner.ChildProcessSpawner;
const nodeExecutable = yield* HostProcess.ExecutablePath;
const recorded = emptyRecording();
const spawned = yield* Deferred.make<ChildProcessSpawner.ChildProcessHandle>();
const ready = yield* Deferred.make<void>();
const processSpawner = ChildProcessSpawner.make((command) =>
Effect.gen(function* () {
if (!ChildProcess.isStandardCommand(command)) {
return yield* Effect.die("expected a single registration command");
}
recorded.commands.push({ command: command.command, args: command.args });
if (command.command !== "update-desktop-database") return mockProcess(0);
const child = yield* realSpawner.spawn(
ChildProcess.make(
nodeExecutable,
[
"-e",
// The watchdog makes the unfixed case exit with a distinguishable code.
"process.on('SIGTERM', () => setTimeout(() => process.exit(99), 5000));" +
"process.stdout.write('ready\\n'); setInterval(() => {}, 2147483647);",
],
{ ...command.options, stdout: "pipe" },
),
);
yield* Deferred.succeed(spawned, child);
yield* Stream.runHead(child.stdout);
yield* Deferred.succeed(ready, undefined);
return child;
}),
);
const registration = yield* runRegister(recorded, { processSpawner }).pipe(
Effect.forkScoped,
);
const child = yield* Deferred.await(spawned);
yield* Effect.addFinalizer(() =>
child.isRunning.pipe(
Effect.flatMap((running) =>
running ? child.kill({ killSignal: "SIGKILL" }) : Effect.void,
),
Effect.ignore,
),
);
yield* Deferred.await(ready);
yield* TestClock.adjust("5 seconds");
yield* Fiber.join(registration);

const exit = yield* Effect.exit(child.exitCode);
if (Exit.isFailure(exit)) {
assert.include(Cause.pretty(exit.cause), "SIGKILL");
} else {
assert.fail(`cache helper exited with ${exit.value} instead of being force-killed`);
}
assert.deepEqual(
recorded.commands.map(({ command }) => command),
["update-desktop-database", "xdg-mime"],
);
}).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
);
});

interface ThemeIconRun {
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/app/DesktopLinuxUrlHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ export const make = Effect.gen(function* () {
stdin: "ignore",
stdout: "ignore",
stderr: "ignore",
forceKillAfter: "1 second",
},
);
const handle = yield* spawner.spawn(command);
Expand Down
33 changes: 33 additions & 0 deletions apps/desktop/src/electron/ElectronProtocol.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,39 @@ describe("ElectronProtocol", () => {
}).pipe(Effect.provide(layerProtocol)),
);

it.effect("keeps proxied paths on the configured server", () =>
Effect.gen(function* () {
let handler: ((request: Request) => Promise<Response>) | undefined;
handleMock.mockImplementation((_scheme, nextHandler) => {
handler = nextHandler;
});
netFetchMock.mockImplementation(async () => new Response("ok"));

yield* Effect.scoped(
Effect.gen(function* () {
const protocol = yield* ElectronProtocol.ElectronProtocol;
yield* protocol.registerDesktopProtocol({
scheme: "t3code-dev",
targetOrigin: new URL("http://127.0.0.1:5733/"),
clerkFrontendApiHostname: undefined,
});
yield* Effect.promise(() =>
handler!(new Request("t3code-dev://app//example.com/src/main.tsx?t=1")),
);
yield* Effect.promise(() => handler!(new Request("t3code-dev://app/\\example.com/")));
}),
);

assert.deepEqual(
netFetchMock.mock.calls.map((call) => call[0]),
[
"http://127.0.0.1:5733//example.com/src/main.tsx?t=1",
"http://127.0.0.1:5733//example.com/",
],
);
}).pipe(Effect.provide(layerProtocol)),
);

it.effect("rejects custom protocol requests for another host", () =>
Effect.gen(function* () {
let handler: ((request: Request) => Promise<Response>) | undefined;
Expand Down
5 changes: 4 additions & 1 deletion apps/desktop/src/electron/ElectronProtocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,10 @@ const proxyRequest = Effect.fn("desktop.protocol.proxyRequest")(function* (
return new Response(null, { status: 404 });
}

const targetUrl = new URL(`${requestUrl.pathname}${requestUrl.search}`, targetOrigin);
// Assign the path so leading slashes cannot replace the configured host.
const targetUrl = new URL(targetOrigin);
targetUrl.pathname = requestUrl.pathname;
targetUrl.search = requestUrl.search;
const headers = new Headers(request.headers);
const headersToRemove: string[] = [];
for (const name of headers.keys()) {
Expand Down
5 changes: 5 additions & 0 deletions apps/desktop/src/preview/BrowserSession.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,11 +280,16 @@ describe("BrowserSession", () => {
assert.isUndefined(sessions.get(partition));

yield* browserSessions.getSession("scope-untouched");
const other = yield* browserSessions.getSession("scope-other");
yield* browserSessions.clearCookies([partition]);

const created = sessions.get(partition);
assert.isDefined(created);
assert.strictEqual(created.clearStorageData.mock.calls.length, 1);
assert.deepStrictEqual(created.clearStorageData.mock.calls[0]?.[0], {
storages: ["cookies", "localstorage", "indexdb", "serviceworkers", "cachestorage"],
});
assert.strictEqual(vi.mocked(other.clearStorageData).mock.calls.length, 0);
}).pipe(Effect.provide(layer)),
);
});
2 changes: 1 addition & 1 deletion apps/desktop/src/preview/BrowserSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ export const make = Effect.gen(function* BrowserSessionMake() {
Effect.tryPromise({
try: () =>
browserSession.clearStorageData({
storages: ["cookies", "localstorage", "indexdb", "serviceworkers"],
storages: ["cookies", "localstorage", "indexdb", "serviceworkers", "cachestorage"],
}),
catch: (cause) =>
new BrowserSessionStorageClearError({
Expand Down
Loading
Loading