Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
13427ec
fix(web): restore file comment focus in editable preview (#9061)
ShpetimA Sep 4, 2026
2e688a5
docs: keep internal guides focused on architecture (#9755)
juliusmarminge Sep 4, 2026
9e1fb45
docs: focus user guides on features and workflows (#9756)
juliusmarminge Sep 4, 2026
cd71367
fix(web): stop panel motion during navigation (#9766)
juliusmarminge Sep 4, 2026
8e056a0
fix(web): open composer selectors below controls (#9767)
maria-rcks Sep 4, 2026
163d86a
perf(server): skip unused Linux process detail reads (#9768)
t3dotgg Sep 4, 2026
bfef973
fix(server): remove retired Codex models after refresh (#9773)
juliusmarminge Sep 4, 2026
cc60753
test: stop path and platform tests depending on the host OS (#9564)
juliusmarminge Sep 4, 2026
4701041
test(server): skip posix executable fixtures on a Windows host (#9565)
juliusmarminge Sep 4, 2026
30f128f
test: skip symlink fixtures where the host cannot create symlinks (#9…
juliusmarminge Sep 4, 2026
5c6c1d6
test(server): run fake provider CLIs through a Node stub on every hos…
juliusmarminge Sep 4, 2026
c251e41
test(desktop): make path fixtures and timeouts host-portable (#9568)
juliusmarminge Sep 4, 2026
f083f52
fix(server): tolerate the missing directory fsync on Windows (#9569)
juliusmarminge Sep 4, 2026
12b6d02
test(server): pin git config for fixtures and compare native realpath…
juliusmarminge Sep 4, 2026
1108be0
test: skip POSIX mode-bit assertions on a Windows host (#9571)
juliusmarminge Sep 4, 2026
9af5139
test(server): keep provider fixture directories host-portable (#9572)
juliusmarminge Sep 4, 2026
9fa54ee
test: resolve the Windows temp directory to its long name (#9573)
juliusmarminge Sep 4, 2026
912276b
test(server): run the Antigravity install harness for the host platfo…
juliusmarminge Sep 4, 2026
5f4c716
fix(server): canonicalise media paths the same way their callers do (…
juliusmarminge Sep 4, 2026
b123cbb
test(server): make Windows path and async fixtures deterministic (#9576)
juliusmarminge Sep 4, 2026
781f41e
fix(server): refuse symlinked theme files on Windows too (#9580)
juliusmarminge Sep 4, 2026
0a590fa
test(scripts): keep Windows packaging checks host-portable (#9589)
juliusmarminge Sep 4, 2026
b7d6e65
fix(contracts): include tool.denied in runtime event types (#9770)
Lucenx9 Sep 4, 2026
24aef0e
feat(mobile): split the usage page into Usage and Limits tabs (#9775)
juliusmarminge Sep 4, 2026
935917f
fix(web): restore the running tool label shine (#9777)
maria-rcks Sep 4, 2026
7649465
fix(web): align provider controls with machine tabs (#9769)
maria-rcks Sep 4, 2026
5e1c23d
docs(web): document panel motion during navigation (#9778)
maria-rcks Sep 4, 2026
394e847
feat(usage): support custom model prices (#9774)
juliusmarminge Sep 4, 2026
91c66ac
fix(web): center pull request link previews (#9794)
maria-rcks Sep 4, 2026
98a29cb
fix: address usage limits and merge settlement regressions (#9784)
maria-rcks Sep 4, 2026
389bbcc
fix(web): give toggle thumbs consistent inset spacing (#9805)
maria-rcks Sep 4, 2026
5a2f3eb
fix(web): use segmented controls for mode switches (#9781)
maria-rcks Sep 4, 2026
b906ce2
fix(server): recover opted-in threads after machine restarts (#9803)
maria-rcks Sep 4, 2026
dd7bc14
fix(web): simplify changed files into a persistent folder tree (#9821)
maria-rcks Sep 4, 2026
5a43324
feat: add custom model names and option descriptors (#9807)
juliusmarminge Sep 4, 2026
087cfb8
fix(cursor): discover symlinked skills as package boundaries (#9420)
EzraBuild Sep 4, 2026
f33fdc9
fix(ssh): exec managed servers without npm wrappers (#9843)
juliusmarminge Sep 4, 2026
0dd5c64
fix(server): detect nested Git workspaces for checkpoints (#9842)
juliusmarminge Sep 4, 2026
4f1092c
fix(web): keep worktree origin preference visible (#9846)
juliusmarminge Sep 5, 2026
2e61301
fix(web): smooth settings sidebar transitions (#9811)
juliusmarminge Sep 5, 2026
4d3907f
feat(web): highlight visible settings sections (#9812)
juliusmarminge Sep 5, 2026
1963ca0
fix(web): keep the sidebar project filter across navigation (#9416)
Mnigos Sep 5, 2026
a5bbad9
fix(server): surface Claude safety model fallback notices instead of …
darahaas15 Sep 5, 2026
caf4981
fix(web): reload saved colors when reopening the theme editor (#9847)
juliusmarminge Sep 5, 2026
ed2bdbb
feat(desktop): import cookies from Safari (#7262)
juliusmarminge Sep 5, 2026
8faf031
fix(web): respect case in POSIX file links (#9309)
Michel-Liao Sep 5, 2026
fce8508
fix(server): surface a missing workspace folder instead of a spawn er…
kakismash Sep 5, 2026
b6f7268
fix: stop favicon requests for private link hosts on web and mobile (…
fe-franco Sep 5, 2026
2d5464a
fix(server): preserve native provider executable paths during updates…
juliusmarminge Sep 5, 2026
720e126
fix(web): restore composer expansion after tool calls (#9782)
t3dotgg Sep 5, 2026
2dca7a1
fix(client): explain possible network blocking for T3 Connect (#9783)
t3dotgg Sep 5, 2026
b5fb3fb
fix(desktop): isolate preview keyboard shortcuts from the host (#9840)
maria-rcks Sep 5, 2026
110bbe6
fix(prs): reuse GitHub data and defer optional reads (#9835)
maria-rcks Sep 5, 2026
c3cacea
perf(shared): skip duplicate PATH entries and per-probe tracing (#9618)
jadeva Sep 5, 2026
896fe82
fix(web): preserve focus and prioritize picker shortcuts (#9795)
maria-rcks Sep 5, 2026
82f64cd
fix(skills): support names beginning with digits (#9244)
amiralibg Sep 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
# git autocrlf=true converts LF to CRLF on Windows, causing issues with oxfmt
* text=auto eol=lf
# Batch files need CRLF for cmd.exe to parse them reliably.
*.cmd text eol=crlf
*.bat text eol=crlf
15 changes: 13 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ The most common defect in this repo is a change that works on the path you teste
- **Contracts.** Anything crossing the wire is typed in `packages/contracts`. Change the schema and the server, web, mobile, and desktop all follow.
- **Reverse states.** If you added a way in, add the way out and the way to see it. Snooze needs unsnooze. Close needs reopen. A one-way door is a bug.
- **Connection modes.** Local, remote/relay, and tunnel behave differently. Multi-device and multi-environment cases are real.
- **Docs.** `docs/` splits by audience. Behavior changes that a user would notice belong in `docs/user/` (shipped-product voice, no repo tooling or source paths); architecture and contributor changes in `docs/internals/`; runbooks in `docs/operations/`; new vocabulary in `docs/internals/glossary.md`.
- **Docs.** Check whether the change makes existing guidance inaccurate. Apply the [documentation rules](#documentation) before adding anything.

## Dev servers

Expand Down Expand Up @@ -120,11 +120,22 @@ An empty database is a bad test. Seed your worktree's `.t3` with a copy of real
- One concern per PR. If the description says "also", split it.
- When babysitting: poll checks and comments newer than the last push, verify each bot finding against the source, fix real ones, dismiss false positives with a written reason. Stay quiet when nothing is new. Stop when the bots are green on the latest commit.

## Documentation

Most code changes do not need an internal documentation change. Agents can read the code.

- `docs/internals/` is for architectural decisions and their reasons, constraints that span components, and implementation traps that are hard to discover from the source. Before adding a paragraph, ask what a maintainer would get wrong without it. If reading the relevant code answers the question, leave it out.
- Do not document every feature, enumerate fields or methods, narrate control flow, maintain file catalogs, or append PR summaries. Types, tests, and code already record the implementation. The glossary defines shared vocabulary; it is not a feature index.
- Keep a local implementation explanation in a nearby code comment. Use an internal doc when the reasoning crosses boundaries or needs context the code cannot carry well. Link to the relevant source instead of copying it.
- When a documented decision or constraint changes, rewrite or remove the affected text. Do not append another account of the new behavior. A new internal page needs a distinct, durable reason to exist.
- `docs/user/` helps users accomplish tasks. Give each major feature a concise section explaining what it does, how to start, and anything unintuitive. A settings path is useful; descriptions of visible buttons, icons, layouts, animations, or every UI state are not. Before adding text, ask what task or decision it helps the user with.
- Keep user docs in the shipped product's voice, without implementation details or contributor tooling. Update the relevant feature section when how to use it changes. A UI tweak does not need a documentation entry, and a new control does not need its own page.
- `docs/operations/` holds maintainer setup, release, and debugging procedures. Keep instructions for operating an installed T3 Code server in the user guides.

## Plans and work artifacts

- Do not commit implementation plans, research notes, or agent scratch files. Keep temporary working material outside the worktree. `.plans/` is gitignored only as a safety net for legacy tooling.
- Track active maintainer work in the GitHub issue or project item that owns it. External proposals follow `CONTRIBUTING.md` and belong in Ideas discussions.
- Put durable architecture, constraints, and decisions in `docs/internals/`. Update those docs when the product changes so agents find current facts instead of abandoned intentions.
- A merged PR is the implementation record. Close or update its tracking item when the work lands; do not preserve a second checklist in the repository.

## How it works
Expand Down
6 changes: 5 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Developer Setup

See the [maintainer scripts guide](docs/internals/scripts.md#first-checkout) for the initial checkout,
See the [development runbook](docs/operations/development.md#first-checkout) for the initial checkout,
development commands, tests, and platform-specific desktop packaging prerequisites.

## Read This First
Expand Down Expand Up @@ -49,6 +49,10 @@ Explain exactly what changed.

Explain exactly why the change should exist.

Follow the [documentation rules](AGENTS.md#documentation). Keep internal docs for decisions and
hard-to-discover constraints. Update user guides when how to use a feature changes; skip descriptions
of obvious controls and cosmetic changes.

Do not mix unrelated fixes together.

If the PR makes anything resembling a UI change, include clear before/after images.
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,12 @@ Full docs live in [docs/](./docs). There's no docs site yet.
- [Install and first run](./docs/user/install.md)
- [Permission modes](./docs/user/permission-modes.md)
- [Keyboard shortcuts](./docs/user/keybindings.md)
- [Customize a project icon](./docs/user/project-settings.md)
- [Project settings](./docs/user/project-settings.md)
- [Remote access from a phone or another machine](./docs/user/remote-access.md)
- [Keeping app and server in sync](./docs/user/updating.md)
- [Source control integrations](./docs/user/source-control.md)
- Multiple accounts: [Codex](./docs/user/providers-codex.md) · [Claude](./docs/user/providers-claude.md)
- Linux: [run T3 Code as a background service](./docs/user/background-service.md)
- [Run T3 Code as a background service](./docs/user/background-service.md)

Building from source? Start at [docs/internals/overview.md](./docs/internals/overview.md).

Expand Down
12 changes: 8 additions & 4 deletions apps/desktop/scripts/electron-launcher.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,14 @@ function registerMacLauncherBundle(appBundlePath) {
}
}

// Bundle-internal paths are macOS paths whatever host builds them.
export function resolveMacLauncherIconPaths(runtimeDir, development = isDevelopment) {
return {
sourceIconPath: development ? developmentMacIconPngPath : productionMacIconPngPath,
generatedIconPath: NodePath.join(runtimeDir, development ? "icon-dev.icns" : "icon-prod.icns"),
generatedIconPath: NodePath.posix.join(
runtimeDir,
development ? "icon-dev.icns" : "icon-prod.icns",
),
};
}

Expand Down Expand Up @@ -280,12 +284,12 @@ function readJson(path) {
}

export function resolveMacLauncherPaths(appBundlePath, displayName = APP_DISPLAY_NAME) {
const executableDir = NodePath.join(appBundlePath, "Contents", "MacOS");
const executableDir = NodePath.posix.join(appBundlePath, "Contents", "MacOS");
const launcherExecutableName = `${displayName} Launcher`;
return {
launcherExecutableName,
launcherBinaryPath: NodePath.join(executableDir, launcherExecutableName),
runtimeElectronBinaryPath: NodePath.join(executableDir, "Electron"),
launcherBinaryPath: NodePath.posix.join(executableDir, launcherExecutableName),
runtimeElectronBinaryPath: NodePath.posix.join(executableDir, "Electron"),
};
}

Expand Down
5 changes: 3 additions & 2 deletions apps/desktop/scripts/electron-launcher.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,10 @@ describe("electron development launcher", () => {
const development = resolveMacLauncherIconPaths("/runtime", true);
const production = resolveMacLauncherIconPaths("/runtime", false);

assert.match(development.sourceIconPath, /assets\/dev\/blueprint-macos-1024\.png$/);
// The source icons are real repo paths, joined for the host.
assert.match(development.sourceIconPath, /assets[\\/]dev[\\/]blueprint-macos-1024\.png$/);
assert.equal(development.generatedIconPath, "/runtime/icon-dev.icns");
assert.match(production.sourceIconPath, /assets\/prod\/black-macos-1024\.png$/);
assert.match(production.sourceIconPath, /assets[\\/]prod[\\/]black-macos-1024\.png$/);
assert.equal(production.generatedIconPath, "/runtime/icon-prod.icns");
});
});
2 changes: 2 additions & 0 deletions apps/desktop/src/app/DesktopAppIdentity.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as NodePath from "@effect/platform-node/NodePath";
import * as NodeServices from "@effect/platform-node/NodeServices";
import { assert, describe, it } from "@effect/vitest";
import * as Effect from "effect/Effect";
Expand Down Expand Up @@ -88,6 +89,7 @@ const makeEnvironmentLayer = (overrides: TestEnvironmentInput = {}) => {
Layer.provide(
Layer.mergeAll(
NodeServices.layer,
NodePath.layerPosix,
DesktopConfig.layerTest({
...env,
}),
Expand Down
8 changes: 7 additions & 1 deletion apps/desktop/src/app/DesktopAssets.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as NodePath from "@effect/platform-node/NodePath";
import * as NodeServices from "@effect/platform-node/NodeServices";
import { assert, describe, it } from "@effect/vitest";
import * as Effect from "effect/Effect";
Expand All @@ -20,7 +21,11 @@ const environmentLayer = DesktopEnvironment.layer({
isPackaged: true,
resourcesPath: "/Applications/T3 Code.app/Contents/Resources",
runningUnderArm64Translation: false,
}).pipe(Layer.provide(Layer.mergeAll(NodeServices.layer, DesktopConfig.layerTest({}))));
}).pipe(
Layer.provide(
Layer.mergeAll(NodeServices.layer, NodePath.layerPosix, DesktopConfig.layerTest({})),
),
);

describe("DesktopAssets", () => {
it.effect("uses canonical source-tree icons for unpackaged development", () =>
Expand All @@ -39,6 +44,7 @@ describe("DesktopAssets", () => {
Layer.provide(
Layer.mergeAll(
NodeServices.layer,
NodePath.layerPosix,
DesktopConfig.layerTest({ VITE_DEV_SERVER_URL: "http://localhost:5733" }),
),
),
Expand Down
31 changes: 19 additions & 12 deletions apps/desktop/src/app/DesktopConnectionCatalogStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ConnectionCatalogDocument } from "@t3tools/client-runtime/platform";
import { EnvironmentId, type PersistedSavedEnvironmentRecord } from "@t3tools/contracts";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Path from "effect/Path";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import * as PlatformError from "effect/PlatformError";
Expand Down Expand Up @@ -226,10 +227,11 @@ describe("DesktopConnectionCatalogStore", () => {
it.effect("surfaces malformed catalog documents without deleting them", () =>
withStore(
Effect.gen(function* () {
const path = yield* Path.Path;
const environment = yield* DesktopEnvironment.DesktopEnvironment;
const fileSystem = yield* FileSystem.FileSystem;
const store = yield* DesktopConnectionCatalogStore.DesktopConnectionCatalogStore;
const catalogPath = `${environment.stateDir}/connection-catalog.json`;
const catalogPath = path.join(environment.stateDir, "connection-catalog.json");
yield* fileSystem.makeDirectory(environment.stateDir, { recursive: true });
yield* fileSystem.writeFileString(catalogPath, "{not-json");

Expand All @@ -247,6 +249,7 @@ describe("DesktopConnectionCatalogStore", () => {

it.effect("surfaces catalog filesystem failures instead of treating them as missing", () =>
Effect.gen(function* () {
const path = yield* Path.Path;
const baseFileSystem = yield* FileSystem.FileSystem;
const baseDir = yield* baseFileSystem.makeTempDirectoryScoped({
prefix: "t3-desktop-connection-catalog-test-",
Expand All @@ -255,7 +258,7 @@ describe("DesktopConnectionCatalogStore", () => {
_tag: "PermissionDenied",
module: "FileSystem",
method: "readFileString",
pathOrDescriptor: `${baseDir}/userdata/connection-catalog.json`,
pathOrDescriptor: path.join(baseDir, "userdata", "connection-catalog.json"),
});
const fileSystemLayer = Layer.succeed(
FileSystem.FileSystem,
Expand All @@ -272,11 +275,11 @@ describe("DesktopConnectionCatalogStore", () => {
error,
DesktopConnectionCatalogStore.DesktopConnectionCatalogStoreReadError,
);
assert.equal(error.catalogPath, `${baseDir}/userdata/connection-catalog.json`);
assert.equal(error.catalogPath, path.join(baseDir, "userdata", "connection-catalog.json"));
assert.strictEqual(error.cause, permissionError);
assert.equal(
error.message,
`Failed to read the desktop connection catalog at ${baseDir}/userdata/connection-catalog.json.`,
`Failed to read the desktop connection catalog at ${path.join(baseDir, "userdata", "connection-catalog.json")}.`,
);
assert.notEqual(error.message, permissionError.message);
}).pipe(Effect.provide(NodeServices.layer), Effect.scoped),
Expand All @@ -285,14 +288,15 @@ describe("DesktopConnectionCatalogStore", () => {
it.effect("reports the failed catalog write operation and path", () =>
Effect.gen(function* () {
const baseFileSystem = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const baseDir = yield* baseFileSystem.makeTempDirectoryScoped({
prefix: "t3-desktop-connection-catalog-test-",
});
const permissionError = PlatformError.systemError({
_tag: "PermissionDenied",
module: "FileSystem",
method: "makeDirectory",
pathOrDescriptor: `${baseDir}/userdata`,
pathOrDescriptor: path.join(baseDir, "userdata"),
});
const fileSystemLayer = Layer.succeed(
FileSystem.FileSystem,
Expand All @@ -310,11 +314,11 @@ describe("DesktopConnectionCatalogStore", () => {
DesktopConnectionCatalogStore.DesktopConnectionCatalogStoreWriteError,
);
assert.equal(error.operation, "create-directory");
assert.equal(error.path, `${baseDir}/userdata`);
assert.equal(error.path, path.join(baseDir, "userdata"));
assert.strictEqual(error.cause, permissionError);
assert.equal(
error.message,
`Desktop connection catalog write failed during create-directory at ${baseDir}/userdata.`,
`Desktop connection catalog write failed during create-directory at ${path.join(baseDir, "userdata")}.`,
);
assert.notEqual(error.message, permissionError.message);
}).pipe(Effect.provide(NodeServices.layer), Effect.scoped),
Expand All @@ -323,6 +327,7 @@ describe("DesktopConnectionCatalogStore", () => {
it.effect("reports the legacy migration stage", () =>
withStore(
Effect.gen(function* () {
const path = yield* Path.Path;
const environment = yield* DesktopEnvironment.DesktopEnvironment;
const fileSystem = yield* FileSystem.FileSystem;
const store = yield* DesktopConnectionCatalogStore.DesktopConnectionCatalogStore;
Expand All @@ -335,7 +340,7 @@ describe("DesktopConnectionCatalogStore", () => {
DesktopConnectionCatalogStore.DesktopConnectionCatalogStoreMigrationError,
);
assert.equal(error.operation, "read-legacy-registry");
assert.equal(error.catalogPath, `${environment.stateDir}/connection-catalog.json`);
assert.equal(error.catalogPath, path.join(environment.stateDir, "connection-catalog.json"));
assert.instanceOf(
error.cause,
DesktopSavedEnvironments.DesktopSavedEnvironmentsDocumentDecodeError,
Expand All @@ -345,7 +350,7 @@ describe("DesktopConnectionCatalogStore", () => {
assert.exists(registryError.cause);
assert.equal(
error.message,
`Legacy desktop saved-environment migration failed during read-legacy-registry into ${environment.stateDir}/connection-catalog.json.`,
`Legacy desktop saved-environment migration failed during read-legacy-registry into ${path.join(environment.stateDir, "connection-catalog.json")}.`,
);
assert.notEqual(error.message, registryError.message);
}),
Expand All @@ -355,10 +360,11 @@ describe("DesktopConnectionCatalogStore", () => {
it.effect("reports invalid encrypted catalog data without exposing it", () =>
withStore(
Effect.gen(function* () {
const path = yield* Path.Path;
const environment = yield* DesktopEnvironment.DesktopEnvironment;
const fileSystem = yield* FileSystem.FileSystem;
const store = yield* DesktopConnectionCatalogStore.DesktopConnectionCatalogStore;
const catalogPath = `${environment.stateDir}/connection-catalog.json`;
const catalogPath = path.join(environment.stateDir, "connection-catalog.json");
yield* fileSystem.makeDirectory(environment.stateDir, { recursive: true });
yield* fileSystem.writeFileString(catalogPath, '{"version":1,"encryptedCatalog":"%%%"}\n');

Expand All @@ -381,6 +387,7 @@ describe("DesktopConnectionCatalogStore", () => {

it.effect("surfaces a catalog that can no longer be decrypted without deleting it", () =>
Effect.gen(function* () {
const path = yield* Path.Path;
const fileSystem = yield* FileSystem.FileSystem;
const baseDir = yield* fileSystem.makeTempDirectoryScoped({
prefix: "t3-desktop-connection-catalog-test-",
Expand All @@ -399,14 +406,14 @@ describe("DesktopConnectionCatalogStore", () => {
DesktopConnectionCatalogStore.DesktopConnectionCatalogStoreProtectionError,
);
assert.equal(error.operation, "decrypt-catalog");
assert.equal(error.catalogPath, `${baseDir}/userdata/connection-catalog.json`);
assert.equal(error.catalogPath, path.join(baseDir, "userdata", "connection-catalog.json"));
assert.instanceOf(error.cause, ElectronSafeStorage.ElectronSafeStorageDecryptError);
const decryptError = error.cause as ElectronSafeStorage.ElectronSafeStorageDecryptError;
assert.instanceOf(decryptError.cause, Error);
assert.equal(decryptError.cause.message, "invalid encrypted catalog");
assert.equal(
error.message,
`Desktop connection catalog protection failed during decrypt-catalog at ${baseDir}/userdata/connection-catalog.json.`,
`Desktop connection catalog protection failed during decrypt-catalog at ${path.join(baseDir, "userdata", "connection-catalog.json")}.`,
);
assert.notEqual(error.message, decryptError.message);
yield* Ref.set(failDecrypt, false);
Expand Down
7 changes: 6 additions & 1 deletion apps/desktop/src/app/DesktopEnvironment.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as NodePath from "@effect/platform-node/NodePath";
import * as NodeServices from "@effect/platform-node/NodeServices";
import { assert, describe, it } from "@effect/vitest";
import * as Effect from "effect/Effect";
Expand Down Expand Up @@ -26,7 +27,11 @@ const makeEnvironmentLayer = (
DesktopEnvironment.layer({
...defaultInput,
...overrides,
}).pipe(Layer.provide(Layer.mergeAll(NodeServices.layer, DesktopConfig.layerTest(env))));
}).pipe(
Layer.provide(
Layer.mergeAll(NodeServices.layer, NodePath.layerPosix, DesktopConfig.layerTest(env)),
),
);

const makeEnvironment = (
overrides: Partial<DesktopEnvironment.MakeDesktopEnvironmentInput> = {},
Expand Down
14 changes: 14 additions & 0 deletions apps/desktop/src/electron/ElectronShell.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@ describe("ElectronShell", () => {
}).pipe(Effect.provide(ElectronShell.layer)),
);

it.effect("opens the Full Disk Access settings anchor", () =>
Effect.gen(function* () {
openExternalMock.mockResolvedValue(undefined);

const electronShell = yield* ElectronShell.ElectronShell;
const result = yield* electronShell.openSystemSettings("full-disk-access");

assert.equal(result, true);
assert.deepEqual(openExternalMock.mock.calls, [
["x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension?Privacy_AllFiles"],
]);
}).pipe(Effect.provide(ElectronShell.layer)),
);

it.effect("opens remote SSH editor URLs", () =>
Effect.gen(function* () {
openExternalMock.mockResolvedValue(undefined);
Expand Down
Loading
Loading