Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
06e6274
feat(server): T3 MCP tools take explicit thread and project targets (…
juliusmarminge Oct 5, 2026
c408737
fix(desktop): V2 imports stashed prompts and drafts from the V1 profi…
juliusmarminge Oct 5, 2026
2a778f7
feat(web): shell commands in the timeline are syntax highlighted (#15…
scratchyone Oct 5, 2026
250e052
fix(mobile): upgrade Uniwind and remove local patch (#14597)
Brentlok Oct 5, 2026
cf3e714
fix(server): Stop ends a Codex command after its thread was settled (…
t3dotgg Oct 5, 2026
7812230
fix(server): subagents no longer inherit parent pull-request links (#…
Lucenx9 Oct 5, 2026
7ffa218
fix(prs): queue fast actions and close batches by dragging (#15851)
maria-rcks Oct 5, 2026
1a3f7ad
perf(prs): share concurrent github routing metadata probes (#15853)
maria-rcks Oct 5, 2026
1604ccc
fix(mobile): back from a finished subagent in the feed returns to its…
AKolenda Oct 5, 2026
7b797f2
fix(desktop): bound preview inspector retention and record renderer i…
maria-rcks Oct 5, 2026
e22c880
fix(web): show fast mode beside reasoning as text (#16069)
maria-rcks Oct 5, 2026
68802a4
fix(mobile): make the routes list match the other settings rows (#15958)
juliusmarminge Oct 5, 2026
51f7f77
fix(threads): stop pull request watches when settling (#16095)
Bil0000 Oct 5, 2026
ca3f973
feat(contracts): clients tolerate union members they don't know yet (…
juliusmarminge Oct 5, 2026
5c03ced
refactor(contracts): project icons decode forward-compatibly instead …
juliusmarminge Oct 5, 2026
31f9d83
Removed an unused helper from the Android push payload builder (#16116)
kridaydave Oct 5, 2026
1c33f3b
perf(mobile): reduce shell cache encoding work (#15096)
juliusmarminge Oct 5, 2026
c71ca06
perf(mobile): defer audio recorder creation until dictation (#15248)
juliusmarminge Oct 5, 2026
4dbc012
feat(server): bump Antigravity ACP agent to 1.3.0 (#15746)
Droyder7 Oct 5, 2026
9d029a1
feat(acp): support local provider commands (#16021)
maria-rcks Oct 5, 2026
3e6b450
fix(server): honor submodule settings when creating worktrees (#15594)
BlankParticle Oct 5, 2026
e01bcca
chore: 🔀 merge upstream nightly v0.0.46-nightly.20261005.2689
chamaquita-d3[bot] Oct 5, 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
12 changes: 11 additions & 1 deletion apps/desktop/src/app/DesktopApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import * as DesktopApplicationMenu from "../window/DesktopApplicationMenu.ts";
import * as DesktopWindow from "../window/DesktopWindow.ts";
import * as DesktopBackendPool from "../backend/DesktopBackendPool.ts";
import * as DesktopEnvironment from "./DesktopEnvironment.ts";
import * as DesktopLegacyLocalStorage from "./DesktopLegacyLocalStorage.ts";
import * as DesktopLifecycle from "./DesktopLifecycle.ts";
import * as DesktopLinuxUrlHandler from "./DesktopLinuxUrlHandler.ts";
import * as DesktopObservability from "./DesktopObservability.ts";
Expand All @@ -32,6 +33,7 @@ import * as DesktopRemoteUpdates from "../updates/DesktopRemoteUpdates.ts";
import * as DesktopUpdates from "../updates/DesktopUpdates.ts";
import * as DesktopSnapShot from "../snapShot/DesktopSnapShot.ts";
import * as DesktopWslBackend from "../wsl/DesktopWslBackend.ts";
import * as DesktopRendererHistory from "../telemetry/DesktopRendererHistory.ts";

const DEFAULT_DESKTOP_BACKEND_PORT = 3773;
const MAX_TCP_PORT = 65_535;
Expand Down Expand Up @@ -179,6 +181,10 @@ const bootstrap = Effect.gen(function* () {
});
yield* installDesktopIpcHandlers();
yield* logBootstrapInfo("bootstrap ipc handlers registered");
// Before any window: the preload merges these items before the app reads storage.
yield* (yield* DesktopLegacyLocalStorage.DesktopLegacyLocalStorage).load(
yield* (yield* DesktopAppIdentity.DesktopAppIdentity).resolveUserDataPath,
);

yield* snapShot.initialize;

Expand Down Expand Up @@ -337,14 +343,18 @@ const scopedProgram = Effect.scoped(
yield* Effect.annotateCurrentSpan({ scope: "desktop", runId });

const shutdown = yield* DesktopShutdown.DesktopShutdown;
const rendererHistory = yield* DesktopRendererHistory.DesktopRendererHistory;

yield* Effect.addFinalizer(() =>
// Stop every backend in the pool, not just the primary. The
// electronApp.quit() path can race ahead of the layer-scope
// cascade, so leaving the WSL instance for its parent scope
// finalizer means it gets hard-killed by the OS instead of
// receiving SIGTERM + grace.
stopAllPoolInstances().pipe(Effect.ensuring(shutdown.markComplete)),
stopAllPoolInstances().pipe(
Effect.ensuring(rendererHistory.shutdown),
Effect.ensuring(shutdown.markComplete),
),
);

yield* startup;
Expand Down
117 changes: 117 additions & 0 deletions apps/desktop/src/app/DesktopLegacyLocalStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import * as Context from "effect/Context";
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 Path from "effect/Path";
import * as Ref from "effect/Ref";

import * as ElectronProtocol from "../electron/ElectronProtocol.ts";
import { readChromiumLocalStorage } from "./chromiumLocalStorage.ts";
import * as DesktopEnvironment from "./DesktopEnvironment.ts";

/**
* Carries the renderer's localStorage (prompt stash, unsent drafts, layout,
* theme) over from the V1 desktop profile, which V2 replaced with its own
* `t3code-v2` profile. The V1 profile is only read, never opened by Chromium,
* so this works while V1 is still running.
*
* `load` runs before the window opens; the preload takes the items once,
* merges them into the new profile, and calls `complete`, which writes a
* marker so later launches skip the read. A failed read or merge, or a window
* that never completes, leaves no marker and retries next launch.
*/
export class DesktopLegacyLocalStorage extends Context.Service<
DesktopLegacyLocalStorage,
{
readonly load: (userDataPath: string) => Effect.Effect<void>;
readonly take: Effect.Effect<Option.Option<Readonly<Record<string, string>>>>;
readonly complete: Effect.Effect<void>;
}
>()("@t3tools/desktop/app/DesktopLegacyLocalStorage") {}

const MARKER_FILE_NAME = "v1-local-storage-imported";
// V1 used "T3 Code (Alpha)" when that folder existed and "t3code" otherwise.
const V1_PROFILE_NAMES = ["T3 Code (Alpha)", "t3code"];

const make = Effect.gen(function* () {
const environment = yield* DesktopEnvironment.DesktopEnvironment;
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const items = yield* Ref.make(Option.none<Readonly<Record<string, string>>>());
const markerPath = yield* Ref.make(Option.none<string>());

/** Newest file mtime in a directory; appending to a log leaves the directory's own mtime alone. */
const newestFileMtime = (directory: string) =>
Effect.gen(function* () {
let newest = 0;
for (const name of yield* fs.readDirectory(directory)) {
const info = yield* fs.stat(path.join(directory, name)).pipe(Effect.option);
if (Option.isNone(info)) continue;
const mtime = Option.match(info.value.mtime, {
onNone: () => 0,
onSome: (date) => date.getTime(),
});
newest = Math.max(newest, mtime);
}
return newest;
}).pipe(Effect.option);

/** The V1 profile whose Local Storage was written most recently, if any. */
const findV1LocalStorage = Effect.gen(function* () {
let newest: { readonly directory: string; readonly mtime: number } | null = null;
for (const name of V1_PROFILE_NAMES) {
const directory = path.join(environment.appDataDirectory, name, "Local Storage", "leveldb");
const mtime = yield* newestFileMtime(directory);
if (Option.isNone(mtime)) continue;
if (newest === null || mtime.value > newest.mtime) newest = { directory, mtime: mtime.value };
}
return newest?.directory ?? null;
});

const writeMarker = Effect.gen(function* () {
const marker = yield* Ref.get(markerPath);
if (Option.isNone(marker)) return;
yield* fs
.writeFileString(marker.value, "")
.pipe(
Effect.catch((error) =>
Effect.logWarning("Could not record the V1 Local Storage import", error),
),
);
});

const load = Effect.fn("desktop.legacyLocalStorage.load")(function* (userDataPath: string) {
// Development already shares its profile between versions.
if (environment.isDevelopment) return;
const marker = path.join(userDataPath, MARKER_FILE_NAME);
if (yield* fs.exists(marker).pipe(Effect.orElseSucceed(() => true))) return;
yield* Ref.set(markerPath, Option.some(marker));
const directory = yield* findV1LocalStorage;
if (directory === null) return yield* writeMarker;
const origin = ElectronProtocol.getDesktopUrl(false).replace(/\/$/, "");
const read = yield* readChromiumLocalStorage(directory, origin).pipe(
Effect.provideService(FileSystem.FileSystem, fs),
Effect.provideService(Path.Path, path),
Effect.tapError((error) =>
Effect.logWarning("Could not read V1 Local Storage; will retry next launch", error),
),
Effect.option,
);
if (Option.isNone(read)) return;
yield* Effect.logInfo("V1 Local Storage ready to import", {
directory,
keys: read.value.size,
});
yield* Ref.set(items, Option.some(Object.fromEntries(read.value)));
});

return DesktopLegacyLocalStorage.of({
load,
// Taking clears the items, so a reload before `complete` cannot merge twice.
take: Ref.getAndSet(items, Option.none()),
complete: writeMarker,
});
});

export const layer = Layer.effect(DesktopLegacyLocalStorage, make);
2 changes: 1 addition & 1 deletion apps/desktop/src/app/DesktopObservability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ const refreshFileSize = (
Effect.orElseSucceed(() => 0),
);

const makeRotatingLogFileWriter = Effect.fn("makeRotatingLogFileWriter")(function* (input: {
export const makeRotatingLogFileWriter = Effect.fn("makeRotatingLogFileWriter")(function* (input: {
readonly filePath: string;
readonly maxBytes?: number;
readonly maxFiles?: number;
Expand Down
Loading
Loading