Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
f0fae69
faster linux boot: splash earlier, defer reconcile, skip redundant sh…
kridaydave Sep 9, 2026
2b2514e
Fix review findings: reconcile before ready, fresh profile entry, kee…
kridaydave Sep 9, 2026
880652f
Update DesktopPreReadyPlatform test for existsSync check
kridaydave Sep 9, 2026
4e5acb8
Fix CodeRabbit: password-store switches before ready, symmetric entry…
kridaydave Sep 9, 2026
ed26c72
Fix Macroscope: generic splash label, only set shell marker when SHEL…
kridaydave Sep 9, 2026
0eaebb7
Merge branch 'main' into faster-linux-boot
kridaydave Sep 12, 2026
0a18a33
fix(mobile): pin expo-audio to 57.0.4 so the iOS patch stays applied
kridaydave Sep 12, 2026
f6307c1
Fix multiple issues: iOS backspace, Forgejo error details, dialog cli…
kridaydave Sep 13, 2026
e421223
fix: format ProviderInstanceIcon and ProviderModelPicker for oxfmt
kridaydave Sep 13, 2026
bf67ec8
fix: bump z-index to z-20 in ProviderInstanceIcon to prevent badge ov…
kridaydave Sep 13, 2026
ed4e614
fix: raise ProviderInstanceIcon root z-index to z-20
kridaydave Sep 13, 2026
2e692b5
Merge branch 'main' into main
kridaydave Sep 13, 2026
ac602b1
fix: raise ProviderInstanceIcon root stacking level above NEW_BADGE_C…
kridaydave Sep 13, 2026
20708cf
Merge branch 'main' into main
kridaydave Sep 13, 2026
05d61ed
Merge remote-tracking branch 'origin/main' into faster-linux-boot
kridaydave Sep 14, 2026
ec427d5
fix(mobile): dedupe iOS backspace DEL between deleteBackward and dele…
kridaydave Sep 14, 2026
4d6b335
fix(web): let ProviderInstanceIcon adornments paint above the glyph
kridaydave Sep 14, 2026
0322cf6
Merge remote-tracking branch 'upstream/main' into faster-linux-boot
kridaydave Sep 14, 2026
8f54ce6
fix(server): serialize concurrent pulls per working tree
kridaydave Sep 14, 2026
bee692b
Merge remote-tracking branch 'upstream/main' into faster-linux-boot
Sep 15, 2026
032ece5
Merge remote-tracking branch 'upstream/main' into t3code/pr-10972/fas…
kridaydave Sep 17, 2026
99fdd19
trigger mergeability recheck
kridaydave Sep 17, 2026
e69bce2
force recheck
kridaydave Sep 17, 2026
ac71239
Merge branch 'main' into faster-linux-boot
kridaydave Sep 17, 2026
4cc57a9
fix(os-jank): add focused tests for fixPath shell env skip path
kridaydave Sep 17, 2026
8cf52b5
Merge commit 'ac71239dc' into t3code/pr-10972/faster-linux-boot
kridaydave Sep 17, 2026
261cf63
fix(desktop): only mark shell env installed when probe produced PATH
kridaydave Sep 17, 2026
56df25f
Update apps/desktop/src/app/DesktopApp.ts
kridaydave Sep 17, 2026
150b8bb
fix(desktop): report whether shell probe produced PATH, gate marker o…
kridaydave Sep 17, 2026
9737720
Merge branch 'main' into faster-linux-boot
kridaydave Sep 17, 2026
55be05e
fix(server): strengthen startup ordering and fixPath fallback tests
kridaydave Sep 18, 2026
2a15138
Merge branch 'main' into faster-linux-boot
kridaydave Sep 18, 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
28 changes: 20 additions & 8 deletions apps/desktop/src/app/DesktopApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,12 +235,14 @@ const bootstrap = Effect.gen(function* () {
}

if (!(yield* Ref.get(state.quitting))) {
// The main window waits for the primary backend. In wsl-only mode that is
// the WSL backend, which can be slow to cold-boot — show a "Connecting to
// WSL" splash immediately so the app feels responsive instead of presenting
// no window until WSL is ready. (Dual mode opens fast off the Windows
// primary, so no splash there.)
if (settings.wslOnly === true && settings.wslBackendEnabled === true) {
// Show a connecting splash on Linux cold boot so the app feels responsive
// instead of presenting no window until the backend is ready. WSL-only mode
// already does this on Windows; keep that path and extend to all Linux
// launches where the backend is not yet running.
if (
environment.platform === "linux" ||
Comment thread
kridaydave marked this conversation as resolved.
(settings.wslOnly === true && settings.wslBackendEnabled === true)
) {
yield* desktopWindow.showConnectingSplash;
}
yield* primaryBackend.start;
Expand Down Expand Up @@ -271,13 +273,24 @@ const startup = Effect.gen(function* () {
const updates = yield* DesktopUpdates.DesktopUpdates;
const environment = yield* DesktopEnvironment.DesktopEnvironment;

yield* shellEnvironment.installIntoProcess;
const shellProbeProducedPath = yield* shellEnvironment.installIntoProcess;
// Tell the server's fixPath to skip its own login-shell probe when the
// desktop already hydrated PATH. Only set this marker when we actually
// knew which shell to probe (SHELL is set) and the probe produced a PATH —
// a failed or timed-out probe recovers as an empty patch, and then the
// server should do its own probe instead of trusting it.
if ((process.env.SHELL ?? "").trim().length > 0 && shellProbeProducedPath) {
process.env.__T3CODE_SHELL_ENV_INSTALLED = "1";
}
const hasCommandLinePasswordStore =
preReadyElectronOptions.linuxPasswordStoreCommandLine !== null;
const linuxElectronOptions =
environment.platform === "linux" && !hasCommandLinePasswordStore
? DesktopPreReadyPlatform.resolveEarlyLinuxElectronOptionsFromProcess()
: preReadyElectronOptions.linux;
// Resolved after the shell probe so XDG_CURRENT_DESKTOP can come from the
// login shell. These switches must be set before whenReady resolves, or
// Chromium ignores them.
if (linuxElectronOptions !== null && !hasCommandLinePasswordStore) {
if (
linuxElectronOptions.passwordStore !== null ||
Expand Down Expand Up @@ -310,7 +323,6 @@ const startup = Effect.gen(function* () {
yield* appIdentity.configure;
yield* lifecycle.register;
yield* clerk.configure;

yield* electronApp.whenReady.pipe(
Effect.withSpan("desktop.electron.whenReady"),
Effect.catchCause((cause) => fatalStartupCause("whenReady", cause)),
Expand Down
45 changes: 44 additions & 1 deletion apps/desktop/src/app/DesktopPreReadyPlatform.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const {
registerSchemesMock,
setDesktopNameMock,
mkdirSyncMock,
readFileSyncMock,
writeFileSyncMock,
} = vi.hoisted(() => ({
appendSwitchMock: vi.fn(),
Expand All @@ -20,6 +21,7 @@ const {
registerSchemesMock: vi.fn(),
setDesktopNameMock: vi.fn(),
mkdirSyncMock: vi.fn(),
readFileSyncMock: vi.fn(),
writeFileSyncMock: vi.fn(),
}));

Expand All @@ -39,12 +41,15 @@ vi.mock("electron", () => ({
}));

vi.mock("node:fs", () => ({
readFileSync: () => "{}",
readFileSync: readFileSyncMock,
mkdirSync: mkdirSyncMock,
writeFileSync: writeFileSyncMock,
}));

import * as DesktopPreReadyPlatform from "./DesktopPreReadyPlatform.ts";
import { renderUrlHandlerDesktopEntry } from "./DesktopLinuxUrlHandler.ts";
import * as ElectronProtocol from "../electron/ElectronProtocol.ts";
import { resolveDesktopAppBranding } from "./DesktopEnvironment.ts";

describe("DesktopPreReadyPlatform", () => {
beforeEach(() => {
Expand All @@ -54,6 +59,7 @@ describe("DesktopPreReadyPlatform", () => {
registerSchemesMock.mockReset();
setDesktopNameMock.mockReset();
mkdirSyncMock.mockReset();
readFileSyncMock.mockReset();
writeFileSyncMock.mockReset();
});

Expand Down Expand Up @@ -83,6 +89,12 @@ describe("DesktopPreReadyPlatform", () => {
vi.stubEnv("XDG_DATA_HOME", "/xdg");
vi.stubEnv("APPIMAGE", "/Applications/current.AppImage");
getSwitchValueMock.mockReturnValue("");
readFileSyncMock.mockImplementation(() => {
if (previousEntry === undefined) {
throw new Error("ENOENT");
}
return previousEntry;
});
let desktopName = "t3code.desktop";
let desktopEntry = previousEntry;
setDesktopNameMock.mockImplementation((name: string) => {
Expand Down Expand Up @@ -111,6 +123,37 @@ describe("DesktopPreReadyPlatform", () => {
);
}

it.effect("skips rewriting an unchanged Linux desktop entry to avoid cold-boot writes", () => {
vi.stubEnv("VITE_DEV_SERVER_URL", "");
vi.stubEnv("XDG_DATA_HOME", "/xdg");
vi.stubEnv("APPIMAGE", "/Applications/current.AppImage");
getSwitchValueMock.mockReturnValue("");
writeFileSyncMock.mockReset();
// The entry on disk already matches what we would render, including the
// trailing newline, so the pre-ready path must not write again.
readFileSyncMock.mockImplementation(() =>
renderUrlHandlerDesktopEntry({
displayName: resolveDesktopAppBranding({
isDevelopment: false,
appVersion: "0.0.37",
}).displayName,
execTarget: "/Applications/current.AppImage",
scheme: ElectronProtocol.getDesktopScheme(false),
}),
);

return DesktopPreReadyPlatform.make.pipe(
Effect.provideService(HostProcessPlatform, "linux"),
Effect.asVoid,
Effect.andThen(
Effect.sync(() => {
assert.equal(writeFileSyncMock.mock.calls.length, 0);
}),
),
Effect.ensuring(Effect.sync(() => vi.unstubAllEnvs())),
);
});

it.effect("keeps startup available when the early desktop entry cannot be written", () => {
getSwitchValueMock.mockReturnValue("");
mkdirSyncMock.mockImplementation(() => {
Expand Down
82 changes: 46 additions & 36 deletions apps/desktop/src/app/DesktopPreReadyPlatform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,49 +51,59 @@ export class DesktopPreReadyElectronOptions extends Context.Service<
/** @public Service construction is part of the canonical Effect module API. */
export const make = Effect.gen(function* () {
const platform = yield* HostProcessPlatform;
return yield* Effect.sync((): DesktopPreReadyElectronOptions["Service"] => {
const linuxPasswordStoreCommandLine =
platform === "linux"
? readCommandLineSwitchValue(Electron.app.commandLine, "password-store")
: null;
const linux = platform === "linux" ? resolveEarlyLinuxElectronOptionsFromProcess() : null;
const linuxPasswordStoreCommandLine =
platform === "linux"
? readCommandLineSwitchValue(Electron.app.commandLine, "password-store")
: null;
const linux = platform === "linux" ? resolveEarlyLinuxElectronOptionsFromProcess() : null;

if (linux !== null) {
// The portal also requires a valid desktop entry. An AppImage update may
// have removed the executable referenced by the previous launch's entry.
if (linux !== null) {
// The portal also requires a valid desktop entry. An AppImage update may
// have removed the executable referenced by the previous launch's entry.
try {
const applicationsDir = NodePath.posix.join(
process.env.XDG_DATA_HOME?.trim() ||
NodePath.posix.join(NodeOS.homedir(), ".local", "share"),
"applications",
);
NodeFS.mkdirSync(applicationsDir, { recursive: true });
const desktopEntryPath = NodePath.posix.join(applicationsDir, linux.linuxDesktopEntryName);
const newEntryContent = renderUrlHandlerDesktopEntry({
displayName: resolveDesktopAppBranding({
isDevelopment: linux.isDevelopment,
appVersion: Electron.app.getVersion(),
}).displayName,
execTarget: process.env.APPIMAGE?.trim() || process.execPath,
scheme: ElectronProtocol.getDesktopScheme(linux.isDevelopment),
});
// Only rewrite the desktop entry when content actually changed to avoid
// unnecessary filesystem writes and desktop database notifications on every
// cold boot. A missing or unreadable entry is treated as absent content so
// the initial launch still writes it. Both sides are compared unchanged —
// trimming only one side would force a rewrite on every boot when the
// written content ends in a newline.
let existingContent: string | null = null;
try {
const applicationsDir = NodePath.posix.join(
process.env.XDG_DATA_HOME?.trim() ||
NodePath.posix.join(NodeOS.homedir(), ".local", "share"),
"applications",
);
NodeFS.mkdirSync(applicationsDir, { recursive: true });
NodeFS.writeFileSync(
NodePath.posix.join(applicationsDir, linux.linuxDesktopEntryName),
renderUrlHandlerDesktopEntry({
displayName: resolveDesktopAppBranding({
isDevelopment: linux.isDevelopment,
appVersion: Electron.app.getVersion(),
}).displayName,
execTarget: process.env.APPIMAGE?.trim() || process.execPath,
scheme: ElectronProtocol.getDesktopScheme(linux.isDevelopment),
}),
"utf8",
);
existingContent = NodeFS.readFileSync(desktopEntryPath, "utf8");
} catch {
// The URL handler retries with the full environment and logs failures.
// Missing or unreadable entry is treated as absent.
}
// Chromium caches its portal registration during startup. Set the identity
// before any asynchronous work can initialize it with Electron's default.
Electron.app.setDesktopName(linux.linuxDesktopEntryName);
Electron.app.commandLine.appendSwitch("class", linux.linuxWmClass);
if (linux.passwordStore !== null && linuxPasswordStoreCommandLine === null) {
Electron.app.commandLine.appendSwitch("password-store", linux.passwordStore);
if (existingContent !== newEntryContent) {
NodeFS.writeFileSync(desktopEntryPath, newEntryContent, "utf8");
}
} catch {
// The URL handler retries with the full environment and logs failures.
}
// Chromium caches its portal registration during startup. Set the identity
// before any asynchronous work can initialize it with Electron's default.
Electron.app.setDesktopName(linux.linuxDesktopEntryName);
Electron.app.commandLine.appendSwitch("class", linux.linuxWmClass);
if (linux.passwordStore !== null && linuxPasswordStoreCommandLine === null) {
Electron.app.commandLine.appendSwitch("password-store", linux.passwordStore);
}
}

return { linux, linuxPasswordStoreCommandLine };
});
return { linux, linuxPasswordStoreCommandLine };
}).pipe(Effect.withSpan("desktop.electron.configureBeforeReady"));

// Keep Electron's strict pre-ready setup isolated so later runtime layers cannot
Expand Down
47 changes: 45 additions & 2 deletions apps/desktop/src/shell/DesktopShellEnvironment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ function runShellEnvironment(input: {

const program = Effect.gen(function* () {
const shellEnvironment = yield* DesktopShellEnvironment.DesktopShellEnvironment;
yield* shellEnvironment.installIntoProcess;
return yield* shellEnvironment.installIntoProcess;
}).pipe(
Effect.provide(
DesktopShellEnvironment.layer.pipe(
Expand All @@ -108,7 +108,7 @@ describe("DesktopShellEnvironment", () => {
};
const commands: ChildProcess.Command[] = [];

yield* runShellEnvironment({
const producedPath = yield* runShellEnvironment({
env,
platform: "darwin",
handler: (command) => {
Expand All @@ -126,6 +126,7 @@ describe("DesktopShellEnvironment", () => {
assert.equal(env.PATH, "/opt/homebrew/bin:/usr/bin:/Users/test/.local/bin");
assert.equal(env.SSH_AUTH_SOCK, "/tmp/secretive.sock");
assert.equal(env.HOMEBREW_PREFIX, "/opt/homebrew");
assert.equal(producedPath, true);
}),
);

Expand Down Expand Up @@ -276,6 +277,48 @@ describe("DesktopShellEnvironment", () => {
}),
);

it.effect("reports success when the probe returns the inherited PATH unchanged", () =>
Effect.gen(function* () {
const env: NodeJS.ProcessEnv = {
SHELL: "/bin/zsh",
PATH: "/usr/bin",
};

const producedPath = yield* runShellEnvironment({
env,
platform: "linux",
handler: () => envOutput({ PATH: "/usr/bin" }),
});

assert.equal(env.PATH, "/usr/bin");
assert.equal(producedPath, true);
}),
);

it.effect("reports failure when the login-shell probe errors", () =>
Effect.gen(function* () {
const env: NodeJS.ProcessEnv = {
SHELL: "/bin/zsh",
PATH: "/usr/bin",
};

const producedPath = yield* runShellEnvironment({
env,
platform: "linux",
handler: () => "",
failure: PlatformError.systemError({
_tag: "PermissionDenied",
module: "ChildProcess",
method: "spawn",
pathOrDescriptor: "/bin/zsh",
}),
});

assert.equal(env.PATH, "/usr/bin");
assert.equal(producedPath, false);
}),
);

it.effect("falls back to launchctl PATH on macOS when shell probing does not return one", () =>
Effect.gen(function* () {
const env: NodeJS.ProcessEnv = {
Expand Down
20 changes: 15 additions & 5 deletions apps/desktop/src/shell/DesktopShellEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ export class DesktopShellEnvironmentCommandTimeoutError extends Schema.TaggedErr
export class DesktopShellEnvironment extends Context.Service<
DesktopShellEnvironment,
{
readonly installIntoProcess: Effect.Effect<void>;
// True when a probe actually produced a PATH (even one identical to the
// inherited value). Failed or timed-out probes recover as an empty patch
// and report false, so callers can tell success apart from failure.
readonly installIntoProcess: Effect.Effect<boolean>;
}
>()("@t3tools/desktop/shell/DesktopShellEnvironment") {}

Expand Down Expand Up @@ -386,7 +389,7 @@ const readWindowsEnvironment = Effect.fn("desktop.shellEnvironment.readWindowsEn
const installWindowsEnvironment = Effect.fn("desktop.shellEnvironment.installWindowsEnvironment")(
function* (
config: ShellEnvironmentConfig,
): Effect.fn.Return<void, never, ChildProcessSpawner.ChildProcessSpawner> {
): Effect.fn.Return<boolean, never, ChildProcessSpawner.ChildProcessSpawner> {
// Concurrent, not sequential: these two probes are independent (only their
// results are combined below) and each spawns its own PowerShell. Run in
// series they sit at offset 0 of desktop.startup, before anything else, and
Expand Down Expand Up @@ -415,14 +418,15 @@ const installWindowsEnvironment = Effect.fn("desktop.shellEnvironment.installWin
if (!config.env.FNM_MULTISHELL_PATH && profile.FNM_MULTISHELL_PATH) {
config.env.FNM_MULTISHELL_PATH = profile.FNM_MULTISHELL_PATH;
}
return Option.isSome(trimNonEmpty(profile.PATH)) || Option.isSome(trimNonEmpty(noProfile.PATH));
},
);

const installPosixEnvironment = Effect.fn("desktop.shellEnvironment.installPosixEnvironment")(
function* (
config: ShellEnvironmentConfig,
): Effect.fn.Return<
void,
boolean,
never,
ChildProcessSpawner.ChildProcessSpawner | FileSystem.FileSystem
> {
Expand Down Expand Up @@ -518,19 +522,25 @@ const installPosixEnvironment = Effect.fn("desktop.shellEnvironment.installPosix
}
}
}

return Option.isSome(trimNonEmpty(shellEnvironment.PATH));
},
);

const installShellEnvironment = (
config: ShellEnvironmentConfig,
): Effect.Effect<void, never, ChildProcessSpawner.ChildProcessSpawner | FileSystem.FileSystem> => {
): Effect.Effect<
boolean,
never,
ChildProcessSpawner.ChildProcessSpawner | FileSystem.FileSystem
> => {
if (config.platform === "win32") {
return installWindowsEnvironment(config);
}
if (config.platform === "darwin" || config.platform === "linux") {
return installPosixEnvironment(config);
}
return Effect.void;
return Effect.succeed(false);
};

/** @public Service construction is part of the canonical Effect module API. */
Expand Down
Loading
Loading