Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions flatpak/com.t3tools.t3code.metainfo.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,20 @@
<p>An unofficial Flatpak package for T3 Code. The desktop runs inside
Flatpak and starts its matching T3 server on the host, where coding agents
can use your projects and development tools.</p>
<p>Headless browser features, including HTML preview screenshots and some
recording tasks, download and run Chromium on the host. Your system must
provide Chromium's runtime libraries, including NSS, GLib, accessibility,
graphics, and ALSA libraries. Systems that restrict unprivileged user
namespaces through AppArmor may also require a manually configured
AppArmor profile to allow Chromium's sandbox to run.</p>
</description>
<launchable type="desktop-id">com.t3tools.t3code.desktop</launchable>
<url type="homepage">https://t3.codes</url>
<releases>
<release version="0.1.0" date="2026-10-15">
<url type="details">https://github.com/pingdotgg/t3code/releases/tag/v0.1.0</url>
<description></description>
</release>
<release version="0.0.45" date="2026-10-02">
<url type="details">https://github.com/pingdotgg/t3code/releases/tag/v0.0.45</url>
<description>
Expand Down
15 changes: 12 additions & 3 deletions flatpak/modules/t3code.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,19 @@ test-commands:
pnpm --filter @t3tools/desktop exec vp test run
--exclude src/wsl/DesktopWslEnvironment.test.ts
- pnpm --filter @t3tools/desktop typecheck
- pnpm --filter t3 exec vp test run src/process/externalLauncher.test.ts
- >-
pnpm --filter t3 exec vp test run
src/process/externalLauncher.test.ts
src/cloud/selfUpdate.test.ts
src/environment/ServerEnvironment.test.ts
src/preview/PreviewBrowserHost.test.ts
src/preview/ServerBrowserStream.test.ts
- pnpm --filter t3 typecheck
sources:
- type: git
url: https://github.com/pingdotgg/t3code.git
tag: v0.0.45
commit: 6c8fed35dded9ff71c5b46807125457acbb76be6
tag: v0.1.0
commit: 81493efb71e8e8244f95d3e9f71d8369654ab151
x-checker-data:
type: json
url: https://api.github.com/repos/pingdotgg/t3code/releases/latest
Expand All @@ -68,6 +74,9 @@ sources:
- ../../patches/0005-flatpak-build-artifact.patch
- ../../patches/0006-flatpak-document-portal-paths.patch
- ../../patches/0007-flatpak-editor-discovery.patch
- ../../patches/0008-flatpak-cli-launcher.patch
- ../../patches/0009-flatpak-server-updates.patch
- ../../patches/0010-flatpak-browser-requirements.patch
use-git: true
- type: dir
path: ../package
Expand Down
2 changes: 1 addition & 1 deletion flatpak/package/t3code
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ esac
# then pass only PATH back to the host server through flatpak-spawn.
host_path=$(
/usr/bin/flatpak-spawn --host "${SHELL:-/bin/sh}" -ilc 'printf %s "$PATH"' \
3>&- 4>&- 5>&- 2>/dev/null || true
3>&- 4>&- 5>&- 6>&- 7>&- 2>/dev/null || true
)
if [ -n "$host_path" ]; then
export T3CODE_FLATPAK_HOST_PATH="$host_path"
Expand Down
44 changes: 22 additions & 22 deletions patches/0001-flatpak-desktop-integration.patch
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
diff --git a/apps/desktop/src/app/DesktopConfig.ts b/apps/desktop/src/app/DesktopConfig.ts
index 773ea245..8eb05c26 100644
index 773ea2450..8eb05c265 100644
--- a/apps/desktop/src/app/DesktopConfig.ts
+++ b/apps/desktop/src/app/DesktopConfig.ts
@@ -37,6 +37,7 @@ export const DesktopConfig = Config.all({
Expand All @@ -11,7 +11,7 @@ index 773ea245..8eb05c26 100644
devServerUrl: Config.URL("VITE_DEV_SERVER_URL").pipe(Config.option),
appUserModelIdOverride: trimmedString("T3CODE_DESKTOP_APP_USER_MODEL_ID"),
diff --git a/apps/desktop/src/app/DesktopEarlyElectronStartup.ts b/apps/desktop/src/app/DesktopEarlyElectronStartup.ts
index 95922bdf..a77901ce 100644
index c4adf8f3c..06a5cb46d 100644
--- a/apps/desktop/src/app/DesktopEarlyElectronStartup.ts
+++ b/apps/desktop/src/app/DesktopEarlyElectronStartup.ts
@@ -1,3 +1,5 @@
Expand All @@ -20,7 +20,7 @@ index 95922bdf..a77901ce 100644
import { fromLenientJson } from "@t3tools/shared/schemaJson";
import * as Option from "effect/Option";
import * as Schema from "effect/Schema";
@@ -86,10 +88,11 @@ export function resolveEarlyLinuxElectronOptions(
@@ -158,10 +160,11 @@ export function resolveEarlyLinuxElectronOptions(
): EarlyLinuxElectronOptions {
const preference = resolveEarlyLinuxPasswordStorePreference(input);
const isDevelopment = isDevelopmentEnvironment(input.env);
Expand All @@ -35,11 +35,11 @@ index 95922bdf..a77901ce 100644
preference,
env: input.env,
diff --git a/apps/desktop/src/app/DesktopEnvironment.test.ts b/apps/desktop/src/app/DesktopEnvironment.test.ts
index b5aabf25..1ddedc22 100644
index fb6c5a8b1..b8be6acf4 100644
--- a/apps/desktop/src/app/DesktopEnvironment.test.ts
+++ b/apps/desktop/src/app/DesktopEnvironment.test.ts
@@ -40,6 +40,18 @@ const makeEnvironment = (
DesktopEnvironment.DesktopEnvironment.pipe(Effect.provide(makeEnvironmentLayer(overrides, env)));
@@ -39,6 +39,18 @@ const makeEnvironment = (
) => DesktopEnvironment.DesktopEnvironment.pipe(Effect.provide(layerEnvironment(overrides, env)));

describe("DesktopEnvironment", () => {
+ it.effect("uses a distinct Flatpak display name", () =>
Expand All @@ -58,10 +58,10 @@ index b5aabf25..1ddedc22 100644
Effect.gen(function* () {
const environment = yield* makeEnvironment(
diff --git a/apps/desktop/src/app/DesktopEnvironment.ts b/apps/desktop/src/app/DesktopEnvironment.ts
index beb09bd5..17053c3c 100644
index f697e0456..bb0a28ece 100644
--- a/apps/desktop/src/app/DesktopEnvironment.ts
+++ b/apps/desktop/src/app/DesktopEnvironment.ts
@@ -177,10 +177,14 @@ const make = Effect.fn("desktop.environment.make")(function* (
@@ -175,10 +175,14 @@ const make = Effect.fn("desktop.environment.make")(function* (
input.isPackaged && input.platform === "win32"
? path.join(input.resourcesPath, "server.asar")
: appRoot;
Expand All @@ -77,7 +77,7 @@ index beb09bd5..17053c3c 100644
const displayName = branding.displayName;
const stateDir = resolveDesktopStateDir({
baseDir,
@@ -242,8 +246,10 @@ const make = Effect.fn("desktop.environment.make")(function* (
@@ -238,8 +242,10 @@ const make = Effect.fn("desktop.environment.make")(function* (
appUserModelId: Option.getOrElse(config.appUserModelIdOverride, () =>
isDevelopment ? "com.t3tools.t3code.dev" : "com.t3tools.t3code",
),
Expand All @@ -89,9 +89,9 @@ index beb09bd5..17053c3c 100644
+ linuxWmClass: flatpakId ?? (isDevelopment ? "t3code-dev" : "t3code"),
linuxApplicationsDir,
appImagePath: config.appImagePath,
userDataDirName,
defaultDesktopSettings: DesktopAppSettings.resolveDefaultDesktopSettings(input.appVersion),
diff --git a/apps/desktop/src/app/DesktopLinuxUrlHandler.test.ts b/apps/desktop/src/app/DesktopLinuxUrlHandler.test.ts
index 893c1aae..9eb024a2 100644
index cbabe14dd..209853d37 100644
--- a/apps/desktop/src/app/DesktopLinuxUrlHandler.test.ts
+++ b/apps/desktop/src/app/DesktopLinuxUrlHandler.test.ts
@@ -1,4 +1,4 @@
Expand All @@ -117,10 +117,10 @@ index 893c1aae..9eb024a2 100644
const entry = DesktopLinuxUrlHandler.renderUrlHandlerDesktopEntry({
displayName: "T3 Code (Nightly)",
diff --git a/apps/desktop/src/app/DesktopLinuxUrlHandler.ts b/apps/desktop/src/app/DesktopLinuxUrlHandler.ts
index f01e81d8..9dcf500e 100644
index e67cdcefe..f61264212 100644
--- a/apps/desktop/src/app/DesktopLinuxUrlHandler.ts
+++ b/apps/desktop/src/app/DesktopLinuxUrlHandler.ts
@@ -9,6 +9,7 @@ import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawne
@@ -9,6 +9,7 @@ import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";

import * as ElectronProtocol from "../electron/ElectronProtocol.ts";
import * as DesktopAssets from "./DesktopAssets.ts";
Expand All @@ -141,10 +141,10 @@ index f01e81d8..9dcf500e 100644
}
yield* writeDesktopEntry;
diff --git a/apps/desktop/src/app/DesktopPreReadyPlatform.test.ts b/apps/desktop/src/app/DesktopPreReadyPlatform.test.ts
index 7e859aaf..c13bd25f 100644
index dd15f879f..08731decb 100644
--- a/apps/desktop/src/app/DesktopPreReadyPlatform.test.ts
+++ b/apps/desktop/src/app/DesktopPreReadyPlatform.test.ts
@@ -52,6 +52,28 @@ vi.mock("node:fs", () => ({
@@ -58,6 +58,28 @@ vi.mock("node:child_process", () => ({
import * as DesktopPreReadyPlatform from "./DesktopPreReadyPlatform.ts";

describe("DesktopPreReadyPlatform", () => {
Expand All @@ -163,7 +163,7 @@ index 7e859aaf..c13bd25f 100644
+ }).pipe(
+ Effect.provide(
+ DesktopPreReadyPlatform.layer.pipe(
+ Layer.provide(Layer.succeed(HostProcessPlatform, "linux")),
+ Layer.provide(Layer.succeed(HostProcess.Platform, "linux")),
+ ),
+ ),
+ Effect.ensuring(Effect.sync(() => vi.unstubAllEnvs())),
Expand All @@ -174,25 +174,25 @@ index 7e859aaf..c13bd25f 100644
appendSwitchMock.mockReset();
getSwitchValueMock.mockReset();
diff --git a/apps/desktop/src/app/DesktopPreReadyPlatform.ts b/apps/desktop/src/app/DesktopPreReadyPlatform.ts
index 9b73a5d8..321fc028 100644
index b021ae02e..b434b2a77 100644
--- a/apps/desktop/src/app/DesktopPreReadyPlatform.ts
+++ b/apps/desktop/src/app/DesktopPreReadyPlatform.ts
@@ -9,6 +9,7 @@ import * as Layer from "effect/Layer";
import * as Electron from "electron";
import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
import * as HostProcess from "@t3tools/shared/HostProcess";

+import { flatpakAppId } from "./Flatpak.ts";
import * as DesktopEarlyElectronStartup from "./DesktopEarlyElectronStartup.ts";
import { resolveDesktopAppBranding } from "./DesktopEnvironment.ts";
import { renderUrlHandlerDesktopEntry } from "./DesktopLinuxUrlHandler.ts";
@@ -61,50 +62,52 @@ export const make = Effect.gen(function* () {
@@ -64,50 +65,52 @@ export const make = Effect.gen(function* () {
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"),
- NodePath.posix.join(homeDirectory, ".local", "share"),
- "applications",
- );
- NodeFS.mkdirSync(applicationsDir, { recursive: true });
Expand Down Expand Up @@ -220,7 +220,7 @@ index 9b73a5d8..321fc028 100644
+ try {
+ const applicationsDir = NodePath.posix.join(
+ process.env.XDG_DATA_HOME?.trim() ||
+ NodePath.posix.join(NodeOS.homedir(), ".local", "share"),
+ NodePath.posix.join(homeDirectory, ".local", "share"),
+ "applications",
+ );
+ NodeFS.mkdirSync(applicationsDir, { recursive: true });
Expand Down Expand Up @@ -282,7 +282,7 @@ index 9b73a5d8..321fc028 100644
// before any asynchronous work can initialize it with Electron's default.
diff --git a/apps/desktop/src/app/Flatpak.ts b/apps/desktop/src/app/Flatpak.ts
new file mode 100644
index 00000000..006f94e0
index 000000000..006f94e00
--- /dev/null
+++ b/apps/desktop/src/app/Flatpak.ts
@@ -0,0 +1,5 @@
Expand Down
30 changes: 21 additions & 9 deletions patches/0002-flatpak-host-backend.patch
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
diff --git a/apps/desktop/src/backend/DesktopBackendConfiguration.ts b/apps/desktop/src/backend/DesktopBackendConfiguration.ts
index c2012299..4edb253f 100644
index b306975be..7f70a3cfc 100644
--- a/apps/desktop/src/backend/DesktopBackendConfiguration.ts
+++ b/apps/desktop/src/backend/DesktopBackendConfiguration.ts
@@ -1,5 +1,8 @@
Expand All @@ -9,9 +9,9 @@ index c2012299..4edb253f 100644
+import { flatpakBackendConfig } from "./flatpakBackend.ts";
+
import { parsePersistedServerObservabilitySettings } from "@t3tools/shared/serverSettings";
import * as Context from "effect/Context";
import * as Crypto from "effect/Crypto";
@@ -567,7 +570,7 @@ const resolvePrimaryStartConfig = Effect.fn("desktop.backendConfiguration.resolv
import { currentDesktopBootstrapToken } from "@t3tools/shared/desktopBootstrapToken";
import * as Clock from "effect/Clock";
@@ -584,7 +587,7 @@ const resolvePrimaryStartConfig = Effect.fn("desktop.backendConfiguration.resolv
...buildObservabilityFragment(input.observabilitySettings),
};

Expand All @@ -20,7 +20,7 @@ index c2012299..4edb253f 100644
executablePath: process.execPath,
// Packaged builds only, so a dev instance never shares the cache with the
// prod app it is often run from. `--require` rather than NODE_COMPILE_CACHE,
@@ -593,6 +596,15 @@ const resolvePrimaryStartConfig = Effect.fn("desktop.backendConfiguration.resolv
@@ -612,6 +615,15 @@ const resolvePrimaryStartConfig = Effect.fn("desktop.backendConfiguration.resolv
captureOutput: true,
preflightFailure: Option.none(),
} satisfies DesktopBackendManager.DesktopBackendStartConfig;
Expand All @@ -38,10 +38,10 @@ index c2012299..4edb253f 100644

diff --git a/apps/desktop/src/backend/flatpakBackend.test.ts b/apps/desktop/src/backend/flatpakBackend.test.ts
new file mode 100644
index 00000000..4839d211
index 000000000..cfc536d2e
--- /dev/null
+++ b/apps/desktop/src/backend/flatpakBackend.test.ts
@@ -0,0 +1,78 @@
@@ -0,0 +1,86 @@
+import { assert, describe, it } from "@effect/vitest";
+import * as Option from "effect/Option";
+
Expand All @@ -62,10 +62,13 @@ index 00000000..4839d211
+ host: "127.0.0.1",
+ t3Home: "/home/alice/.var/app/com.t3tools.t3code/data/t3",
+ desktopBootstrapToken: "test-token",
+ shellEnvironmentPrepared: true,
+ tailscaleServeEnabled: false,
+ tailscaleServePort: 443,
+ desktopTelemetryFd: 4,
+ desktopTelemetryControlFd: 5,
+ desktopBrowserFd: 6,
+ desktopBrowserControlFd: 7,
+ resourceMonitorPath: "/app/resources/resource-monitor/t3-resource-monitor",
+ },
+ bootstrapDelivery: "fd3",
Expand All @@ -89,6 +92,8 @@ index 00000000..4839d211
+ "--forward-fd=3",
+ "--forward-fd=4",
+ "--forward-fd=5",
+ "--forward-fd=6",
+ "--forward-fd=7",
+ `--directory=${runtime}`,
+ `--env=T3CODE_HOME=${config.bootstrap.t3Home}`,
+ `--env=T3CODE_FLATPAK_SERVER=${runtime}/t3code`,
Expand All @@ -105,6 +110,9 @@ index 00000000..4839d211
+ assert.equal(result.bootstrap.desktopBootstrapToken, "test-token");
+ assert.equal(result.bootstrap.desktopTelemetryFd, 4);
+ assert.equal(result.bootstrap.desktopTelemetryControlFd, 5);
+ assert.isFalse(result.bootstrap.shellEnvironmentPrepared);
+ assert.equal(result.bootstrap.desktopBrowserFd, 6);
+ assert.equal(result.bootstrap.desktopBrowserControlFd, 7);
+ assert.equal(
+ result.bootstrap.resourceMonitorPath,
+ `${runtime}/resources/resource-monitor/t3-resource-monitor`,
Expand All @@ -122,10 +130,10 @@ index 00000000..4839d211
+});
diff --git a/apps/desktop/src/backend/flatpakBackend.ts b/apps/desktop/src/backend/flatpakBackend.ts
new file mode 100644
index 00000000..76d37f9e
index 000000000..e912b2119
--- /dev/null
+++ b/apps/desktop/src/backend/flatpakBackend.ts
@@ -0,0 +1,52 @@
@@ -0,0 +1,56 @@
+import * as Option from "effect/Option";
+
+import type { DesktopBackendStartConfig } from "./DesktopBackendManager.ts";
Expand All @@ -149,6 +157,8 @@ index 00000000..76d37f9e
+ // Keep entryPath in the sandbox for preflight checks; pass host paths to the server.
+ const bootstrap = {
+ ...config.bootstrap,
+ // The sandbox skips shell hydration; the host server must prepare its own environment.
+ shellEnvironmentPrepared: false,
+ resourceMonitorPath: `${runtimeDir}/resources/resource-monitor/t3-resource-monitor`,
+ };
+ return {
Expand All @@ -160,6 +170,8 @@ index 00000000..76d37f9e
+ "--forward-fd=3",
+ "--forward-fd=4",
+ "--forward-fd=5",
+ "--forward-fd=6",
+ "--forward-fd=7",
+ `--directory=${runtimeDir}`,
+ `--env=T3CODE_HOME=${bootstrap.t3Home}`,
+ `--env=T3CODE_FLATPAK_SERVER=${runtimeDir}/t3code`,
Expand Down
24 changes: 12 additions & 12 deletions patches/0003-flatpak-host-environment.patch
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
diff --git a/apps/desktop/src/shell/DesktopShellEnvironment.test.ts b/apps/desktop/src/shell/DesktopShellEnvironment.test.ts
index 6a369a45..6ebdaf06 100644
index 6efe81757..c453e6274 100644
--- a/apps/desktop/src/shell/DesktopShellEnvironment.test.ts
+++ b/apps/desktop/src/shell/DesktopShellEnvironment.test.ts
@@ -100,6 +100,21 @@ function runShellEnvironment(input: {
@@ -120,6 +120,21 @@ function runShellEnvironment(input: {
}

describe("DesktopShellEnvironment", () => {
Expand All @@ -21,22 +21,22 @@ index 6a369a45..6ebdaf06 100644
+ }).pipe(Effect.tap(() => Effect.sync(() => assert.equal(env.PATH, "/app/bin:/usr/bin"))));
+ });
+
it.effect("hydrates PATH and missing SSH_AUTH_SOCK from the login shell on macOS", () =>
it.effect("rejects PATH output from a failed shell and tries the next candidate", () =>
Effect.gen(function* () {
const env: NodeJS.ProcessEnv = {
const env: NodeJS.ProcessEnv = { SHELL: "/bin/failing", PATH: "/usr/bin" };
diff --git a/apps/desktop/src/shell/DesktopShellEnvironment.ts b/apps/desktop/src/shell/DesktopShellEnvironment.ts
index 704b061d..71b33253 100644
index 2b41b6ea7..dfe68ac88 100644
--- a/apps/desktop/src/shell/DesktopShellEnvironment.ts
+++ b/apps/desktop/src/shell/DesktopShellEnvironment.ts
@@ -8,6 +8,7 @@ import * as Schema from "effect/Schema";
import * as ChildProcess from "effect/unstable/process/ChildProcess";
import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner";
@@ -10,6 +10,7 @@ import * as Stream from "effect/Stream";
import * as ChildProcess from "effect/process/ChildProcess";
import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";

+import { flatpakAppId } from "../app/Flatpak.ts";
import * as DesktopEnvironment from "../app/DesktopEnvironment.ts";

type EnvironmentPatch = Record<string, string>;
@@ -500,6 +501,7 @@ const installPosixEnvironment = Effect.fn("desktop.shellEnvironment.installPosix
@@ -515,6 +516,7 @@ const installPosixEnvironment = Effect.fn("desktop.shellEnvironment.installPosix
const installShellEnvironment = (
config: ShellEnvironmentConfig,
): Effect.Effect<void, never, ChildProcessSpawner.ChildProcessSpawner | FileSystem.FileSystem> => {
Expand All @@ -45,7 +45,7 @@ index 704b061d..71b33253 100644
return installWindowsEnvironment(config);
}
diff --git a/apps/desktop/src/updates/DesktopUpdates.test.ts b/apps/desktop/src/updates/DesktopUpdates.test.ts
index 9745662c..39c39503 100644
index 26ad0f1c4..fc5f324df 100644
--- a/apps/desktop/src/updates/DesktopUpdates.test.ts
+++ b/apps/desktop/src/updates/DesktopUpdates.test.ts
@@ -1,4 +1,4 @@
Expand Down Expand Up @@ -84,15 +84,15 @@ index 9745662c..39c39503 100644
const cause = Cause.combine(
Cause.fail(new Error("updater failed")),
diff --git a/apps/desktop/src/updates/DesktopUpdates.ts b/apps/desktop/src/updates/DesktopUpdates.ts
index 245925a6..3aac79ec 100644
index 77069d3ba..03ff73f2a 100644
--- a/apps/desktop/src/updates/DesktopUpdates.ts
+++ b/apps/desktop/src/updates/DesktopUpdates.ts
@@ -1,3 +1,4 @@
+import { flatpakAppId } from "../app/Flatpak.ts";
import {
DESKTOP_UPDATE_RESTART_MARKER_FILE,
DesktopUpdateChannelSchema,
@@ -254,6 +255,9 @@ function getAutoUpdateDisabledReason(args: {
@@ -321,6 +322,9 @@ function getAutoUpdateDisabledReason(args: {
disabledByEnv: boolean;
hasUpdateFeedConfig: boolean;
}): string | null {
Expand Down
Loading