Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
15eda89
perf(web): defer image URL requests for thread history (#9760)
t3dotgg Sep 4, 2026
bc03c36
fix(models): make GPT-6-Astra current (#9762)
juliusmarminge Sep 4, 2026
d115a96
fix(web): stop empty diffs replacing pull requests (#9753)
maria-rcks Sep 4, 2026
45bd3b6
fix(sidebar): mute background working threads (#9759)
maria-rcks Sep 4, 2026
f6db420
fix(web): reset automatic pull to default (#9763)
maria-rcks Sep 4, 2026
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
89ee69e
fix(server): advertise truecolor in the integrated terminal (#7680)
shubhxho Sep 5, 2026
940e823
fix(claude): surface usage-limit pauses in the thread (#7165)
vitalyiegorov Sep 5, 2026
ce4712d
fix: restore UX after performance improvements (#9799)
t3dotgg Sep 5, 2026
57a6b70
fix(codex): show what a file-change approval will change (#8669)
walid-baharwal Sep 5, 2026
4ade365
fix(ci): add fallback Ubuntu package mirrors (#9864)
maria-rcks Sep 5, 2026
03c6cd8
fix(web): copy text over plain HTTP (#8023)
Michel-Liao Sep 5, 2026
d7fe47f
fix(server): dismiss native questions when their turn ends (#9851)
juliusmarminge Sep 5, 2026
d28077e
fix(server): quote copied native provider update commands (#9856)
juliusmarminge Sep 5, 2026
1665d81
fix(release): install the correct Windows Spectre component (#9859)
maria-rcks Sep 5, 2026
1246146
fix(web): show retained runtime diagnostics in the work log (#9870)
juliusmarminge Sep 5, 2026
7f8cf30
fix(web): make sidebar project actions reachable by keyboard and scre…
akj Sep 5, 2026
de1b798
fix(preview): bound automation waits and screenshot captures (#4685)
juliusmarminge Sep 5, 2026
7ee52b0
fix(web): render and filter usage as environments respond (#9860)
juliusmarminge Sep 5, 2026
84b99f3
feat(web): edit usage prices across selected environments (#9861)
juliusmarminge Sep 5, 2026
ac90950
fix(mobile): keep usage tabs below the header when switching (#9876)
juliusmarminge Sep 5, 2026
363cde4
fix(connect): refresh HTTP credentials without reconnecting (#9594)
juliusmarminge Sep 5, 2026
b01771c
fix(web): keep file autosaves active after effect replay (#9878)
juliusmarminge Sep 5, 2026
a0eb239
fix(web): prioritize open panel pull request when copying (#9877)
maria-rcks Sep 5, 2026
9cb4017
fix(cli): resolve projects with missing workspace directories (#9885)
juliusmarminge Sep 5, 2026
931d41f
fix(web): sort equally merge-ready pull requests by diff size (#9887)
maria-rcks Sep 5, 2026
94cc815
fix(chat): show hours for long runs (#9894)
t3dotgg Sep 5, 2026
2fb99a7
fix(server): only run provider updates through the installer that own…
juliusmarminge Sep 5, 2026
df8e0eb
fix(web): discard stale highlights after file edits (#9902)
juliusmarminge Sep 5, 2026
fd77317
feat(web): recall sent prompts with the up arrow (#9173)
t3dotgg Sep 5, 2026
c7dc3cb
fix(server): keep mise-owned npm packages manual-only (#9927)
juliusmarminge Sep 5, 2026
f47a3fe
fix(settings): share restart continuation across environments (#9933)
maria-rcks Sep 5, 2026
7a089b2
fix(server): capture turn checkpoints after all edits finish (#9841)
juliusmarminge Sep 5, 2026
c4c139d
sync: align with current upstream while preserving fork capabilities
aaditagrawal Sep 5, 2026
2413f8a
Merge remote-tracking branch 'origin/sync/alignment-20260905-performa…
cursoragent Sep 5, 2026
dd96e04
Merge branch 'sync/alignment-20260905-performance' into sync/alignmen…
aaditagrawal Sep 5, 2026
4d974c5
fix(web): give Pierre file-editor highlight tests a 60s budget
cursoragent Sep 5, 2026
c649dc0
fix(web): drain Pierre rAF stubs before restoring globals
cursoragent 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
22 changes: 22 additions & 0 deletions .github/actions/setup-apt-mirrors/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Setup APT mirrors
description: Configure Ubuntu package downloads with automatic mirror failover.
runs:
using: composite
steps:
- shell: bash
run: |
# Replace the existing Blacksmith mirror list as well as direct sources.
printf '%s\tpriority:%s\n' \
https://archive.ubuntu.com/ubuntu 1 \
https://mirrors.edge.kernel.org/ubuntu 2 \
https://mirror.math.princeton.edu/pub/ubuntu 3 \
| sudo tee /etc/apt/blacksmith-ubuntu-mirrors.txt > /dev/null

# APT's mirror transport retries each file against the next server.
sudo find /etc/apt -maxdepth 2 -type f \( -name '*.list' -o -name '*.sources' \) \
-exec sed -i -E \
's#https?://(([^/]+\.)?archive|security)\.ubuntu\.com/ubuntu/?#mirror+file:/etc/apt/blacksmith-ubuntu-mirrors.txt#g' {} +

# Move on to a fallback before an unreachable server exhausts the job.
printf '%s\n' 'Acquire::http::Timeout "15";' 'Acquire::https::Timeout "15";' \
| sudo tee /etc/apt/apt.conf.d/80-mirror-timeouts > /dev/null
12 changes: 10 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,12 @@ jobs:
- name: Typecheck
run: vpr typecheck

- uses: ./.github/actions/setup-apt-mirrors

- name: Install browser secret helper build libraries
run: sudo apt-get update && sudo apt-get install -y libsecret-1-dev pkg-config
run: |
sudo sed -i 's|http://|https://|g' /etc/apt/blacksmith-ubuntu-mirrors.txt /etc/apt/sources.list.d/ubuntu.sources
sudo apt-get update && sudo apt-get install -y libsecret-1-dev pkg-config

- name: Build desktop pipeline
run: vp run build:desktop
Expand Down Expand Up @@ -152,8 +156,12 @@ jobs:
continue-on-error: true
run: vp run --filter @t3tools/desktop ensure:electron

- uses: ./.github/actions/setup-apt-mirrors

- name: Install browser secret helper build libraries
run: sudo apt-get update && sudo apt-get install -y libsecret-1-dev pkg-config
run: |
sudo sed -i 's|http://|https://|g' /etc/apt/blacksmith-ubuntu-mirrors.txt /etc/apt/sources.list.d/ubuntu.sources
sudo apt-get update && sudo apt-get install -y libsecret-1-dev pkg-config

- name: Test
run: vp run --parallel --concurrency-limit 4 --filter '!t3' --filter '!@t3tools/desktop' --filter '!@t3tools/monorepo' test
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ jobs:
- name: Typecheck
run: vp run typecheck

- uses: ./.github/actions/setup-apt-mirrors

- name: Install browser secret helper build libraries
run: sudo apt-get update && sudo apt-get install -y libsecret-1-dev pkg-config

Expand Down Expand Up @@ -568,6 +570,9 @@ jobs:
exit $code
}

- uses: ./.github/actions/setup-apt-mirrors
if: matrix.platform == 'linux'

- name: Install Linux desktop build libraries
if: matrix.platform == 'linux'
shell: bash
Expand Down
20 changes: 14 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,9 @@ Long-term maintainability is a core priority. If you add new functionality, firs

### Provider Adapter Pattern

All providers implement a unified adapter interface (`ProviderAdapterShape`) in `apps/server/src/provider/Services/`. Each adapter declares:
All providers implement `ProviderAdapterShape` in `apps/server/src/provider/Services/ProviderAdapter.ts`. Capabilities declare `sessionModelSwitch` (`"in-session"` or `"unsupported"`), optional `promptlessTurnContinuation`, and optional `supportsConversationRollback`. Adapters without native history rewind must explicitly set `supportsConversationRollback: false` so checkpoints reject rewind before restoring files.

- `transport` — how it communicates (`app-server-json-rpc`, `sdk-cli-server`, `acp-stdio`, `http-sse`, `cli-headless-json`, `cli-persistent-json`, `sdk-query`)
- `sessionModelSwitch` — `"in-session"`, `"restart-session"`, or `"unsupported"`
- `modelDiscovery` — `"native"`, `"acp-or-config"`, `"config-or-static"`, `"session-native"`, or `"unsupported"`

Adapters are registered in `provider/Layers/ProviderAdapterRegistry.ts` and looked up by provider kind at runtime. Complex providers have dedicated process managers (e.g. `codexAppServerManager.ts`, `geminiCliServerManager.ts`, `ampServerManager.ts`).
`provider/builtInDrivers.ts` registers built-in drivers; the adapter registry resolves providers and their configured instances. Transport and model discovery are driver implementation details, not adapter capability fields. Complex providers have dedicated process managers, while ACP providers share the runtime in `provider/acp/`.

### Key Server Modules

Expand Down Expand Up @@ -164,3 +160,15 @@ agents.
examples of idiomatic usage, tests, module structure, and API design.
- When writing relay infrastructure code with Alchemy, inspect `.repos/alchemy-effect/` for examples of
idiomatic usage, tests, module structure, and API design.

## 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.
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ First off, thank you for considering contributing to this fork! It's people like

## Developer Setup

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

This fork is maintained in [aaditagrawal/t3code](https://github.com/aaditagrawal/t3code) and focuses on expanding provider support, preserving usage and limit monitoring, and improving the core orchestration and persistence layers.
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
Loading
Loading