Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
fd7ee2c
chore(lint): forbid declaring tests inside for loops (#14921)
juliusmarminge Oct 2, 2026
7ff2eab
fix(desktop): markdown no longer loads invisible in new dark-mode bro…
shivamhwp Oct 2, 2026
6414268
test(server): the fake Claude CLI exits with its parent (#14926)
yordis Oct 3, 2026
8ed276c
fix(mobile): production OTAs no longer reach the previous major's sto…
juliusmarminge Oct 3, 2026
bb79977
fix(web): thread panel shows the remote machine a thread runs on (#14…
t3dotgg Oct 3, 2026
43bd667
fix(web): Claude skill calls show the skill name (#14980)
t3dotgg Oct 3, 2026
858b98b
test(server): the fake Claude CLI is a fixture file, not a generated …
yordis Oct 3, 2026
4f7760e
fix(server): Claude subagents show the model their agent file picks (…
t3dotgg Oct 3, 2026
151c241
fix(client-runtime): format subagent descriptions as display names (#…
Yash-Singh1 Oct 3, 2026
c91e93c
Update TRIAGE_EXEMPTIONS with new entries
juliusmarminge Oct 3, 2026
22e9d35
fix(web): outdated servers can be updated even when the client can't …
juliusmarminge Oct 3, 2026
3a7058d
fix(web): new worktree threads no longer flicker while starting (#15019)
t3dotgg Oct 3, 2026
d668ffc
fix(server): subagent threads stop publishing tombstones to the relay…
juliusmarminge Oct 3, 2026
9333509
fix(client-runtime): reconnects back off with jitter and keep healthy…
t3dotgg Oct 3, 2026
736130c
fix(server): working timers no longer reset on every background wake …
t3dotgg Oct 3, 2026
ca4f84e
fix(server): threads settle as soon as an agent merges their PR (#15024)
t3dotgg Oct 3, 2026
63f74e2
fix(opencode): fill blank reasoning labels (#14978)
Yash-Singh1 Oct 3, 2026
798e945
fix(preview): agent browser clicks no longer steal your composer focu…
t3dotgg Oct 3, 2026
c5a929e
fix(web): Woke dismiss syncs, preview mute is back, and subscription …
t3dotgg Oct 3, 2026
ddcd310
chore: docs, dev scripts and CI catch up with orchestration V2 (#15041)
t3dotgg Oct 3, 2026
8283b48
fix(server): Claude V2 turns start on Windows with the default binary…
maria-rcks Oct 3, 2026
d1034d6
fix(web): diff panel opens on all branch changes, not just uncommitte…
t3dotgg Oct 3, 2026
5bf19d1
fix(server): threads stay working while Claude starts a wake turn (#1…
t3dotgg Oct 3, 2026
5c0429b
feat(web): mod+alt+enter on an existing thread sends and opens a new …
maria-rcks Oct 3, 2026
db51460
fix(chat): sending on an older thread no longer jumps to the top (#15…
t3dotgg Oct 3, 2026
fed41fa
chore: add bmdavis419 to triage exemptions (#15062)
bmdavis419 Oct 3, 2026
6108ef3
fix(server): runs no longer get stuck (#15048)
t3dotgg Oct 3, 2026
5691412
fix(usage): Codex Fast and Ultrafast now cost what they bill (#15101)
t3dotgg Oct 3, 2026
a5b34b2
fix(clients): a dev server left running no longer says the thread is …
t3dotgg Oct 3, 2026
f68e24f
fix(web): a thread that left a shell running shows its unseen complet…
Mnigos Oct 3, 2026
263097a
fix(web): mod+enter starts a new thread in the background again (#15060)
t3dotgg Oct 3, 2026
e8545b2
feat(usage): show cost by token type, speed, and model detail (#15108)
t3dotgg Oct 3, 2026
18b2132
feat(server): agents can watch a PR and get woken when checks, review…
t3dotgg Oct 3, 2026
31a9da1
fix(server): keep delegated review rounds on the task API (#15115)
t3dotgg Oct 3, 2026
f391794
fix(shared): classify workspace previews by literal filenames (#10311)
yashranaway Oct 3, 2026
1945ce8
fix(mobile): iOS threads no longer jump to the top (#14808)
znoraka Oct 3, 2026
44b4965
fix(web): reduce the gap above the draft composer (#15196)
Bil0000 Oct 3, 2026
a0bf1ee
fix(mobile): a dev server left running no longer shows the waiting bo…
jakeleventhal Oct 3, 2026
ce90eec
fix(server): a Claude command you stop shows as interrupted (#14896)
SunkenInTime Oct 3, 2026
0080e80
fix(server): editors appear once a slow discovery scan finishes (#13917)
bfowler Oct 3, 2026
aad7329
fix(server): Claude threads no longer stay stuck in plan mode Claude …
juliusmarminge Oct 3, 2026
65731f9
fix(mobile): show complete subagent details (#15189)
jakeleventhal Oct 3, 2026
77823bd
fix(mobile): an expired Live Activity no longer leaves a second card …
juliusmarminge Oct 3, 2026
cfdff56
test(web): remove redundant thread sort fallback tests (#15095)
t3-code[bot] Oct 3, 2026
70e5a40
fix(web): thinking row after a failed tool expands the run's tool cal…
maria-rcks Oct 3, 2026
39efcd8
perf(web): DOM changes no longer restyle the whole page (#15265)
flamboh Oct 3, 2026
71dbaf1
perf(usage): cut warm usage scans from seconds to milliseconds on lar…
t3dotgg Oct 3, 2026
fffe6e6
perf(web): virtualize command palette results (#15266)
flamboh Oct 3, 2026
0cfa113
chore(lint): flag :has() variants that restyle the whole page (#15274)
flamboh Oct 3, 2026
429c625
fix(web): workspace card docks beside chat when the window is narrow …
t3dotgg Oct 3, 2026
5e35272
feat(web): render mermaid code blocks as diagrams (#15067)
maria-rcks Oct 3, 2026
f1dcd93
feat(web): Nightly tells you to get the beta mobile app (#15070)
t3dotgg Oct 3, 2026
a21702d
test(server): ACP adapter tests no longer race the prompt settle (#15…
t3dotgg Oct 3, 2026
b05adb7
feat(usage): fold preview model IDs into the model they belong to (#1…
t3dotgg Oct 3, 2026
a78760e
refactor(server): check RPC scopes in group middleware (#15324)
juliusmarminge Oct 3, 2026
8d84666
feat(mobile): beta Working section hides busy threads until they need…
t3dotgg Oct 3, 2026
00eb8f6
fix(settings): symlinked settings files stay linked when saved (#15009)
yordis Oct 3, 2026
d65cd33
fix(server): Stop ends a dev server left running before a provider sw…
t3dotgg Oct 3, 2026
243d1e7
fix(server): merged threads settle even after the agent wakes on its …
t3dotgg Oct 3, 2026
c47f426
fix(web): no-project drafts can switch machines (#15356)
maria-rcks Oct 3, 2026
f786ff3
fix(web): highlight tool inputs and remove nested work log indentatio…
Yash-Singh1 Oct 3, 2026
5108c97
fix(server): restarts keep delegated tasks, queued threads, and stops…
maria-rcks Oct 3, 2026
a976f8c
fix(web): sending past the resume banner compacts first (#15290)
maria-rcks Oct 3, 2026
44bd4c9
fix(codex): resume archived native sessions (#15389)
maria-rcks Oct 3, 2026
f2cc80a
feat(web): morph composer and panel action icons (#14924)
jakeleventhal Oct 4, 2026
d307127
fix(web): subagents sent a follow-up show as running in Lineage (#15334)
scratchyone Oct 4, 2026
2d81e4c
fix(web): clear stale chat action shortcuts (#15394)
maria-rcks Oct 4, 2026
88744f3
fix(orchestration-v2): restore earlier app agent transcript pages (#1…
Bil0000 Oct 4, 2026
1b1e5b9
fix(web): remove the square thread info panel shadow (#15069)
PixPMusic Oct 4, 2026
8e39994
fix(mobile): Android usage widget no longer sticks on "Loading widget…
tris203 Oct 4, 2026
a6aebbe
fix(web): size the model picker to its content (#15152)
saphid Oct 4, 2026
2a45557
test(server): replay checks a Claude subagent's thread takes its repo…
juliusmarminge Oct 4, 2026
dab26f5
fix(web): subagent finish notifications look like subagent cards (#15…
flamboh Oct 4, 2026
4543c73
fix(web): thread status dot has an accessible name (#14587)
ryanilano Oct 4, 2026
c5bc98d
fix(web): legacy sidebar options button has a label (#14602)
ryanilano Oct 4, 2026
2b8bb0c
fix(web): imported themes keep switches and focus rings visible (#14498)
flamboh Oct 4, 2026
7bc161f
fix(web): links to issues no longer strand the pull request viewer (#…
flamboh Oct 4, 2026
237d51e
fix(web): repo/task breadcrumb no longer bounces when the sidebar col…
flamboh Oct 4, 2026
f90b77d
fix(web): Pull request panel entry works for linked PRs (#15061)
flamboh Oct 4, 2026
95edeb7
fix(web): add context menu to draft threads in the sidebar (#10637)
flamboh Oct 4, 2026
9efb016
fix(web): keep sidebar branding and build pills from clipping at vary…
flamboh Oct 4, 2026
0c81120
fix(usage): model shares and order follow the selected metric (#11391)
flamboh Oct 4, 2026
2093851
chore: sync with upstream pingdotgg/t3code main
yordis Oct 4, 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
  •  
  •  
  •  
23 changes: 7 additions & 16 deletions .agents/skills/test-t3-app/references/sqlite-fixtures.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ Load this reference only when inspecting or seeding local T3 state directly.

## Select the correct database

When `--base-dir` or `--home-dir` is explicit, runtime state lives under `<base-dir>/userdata` and the database path is `<base-dir>/userdata/state.sqlite`. The `<base-dir>/dev` state directory is only the fallback for an implicit development home, preventing an ordinary `vp run dev` from touching production state.
When `--base-dir` or `--home-dir` is explicit, runtime state lives under `<base-dir>/userdata` and the database path is `<base-dir>/userdata/statev2.sqlite`. The `<base-dir>/dev` state directory is only the fallback for an implicit development home, preventing an ordinary `vp run dev` from touching production state.

The server copies the V1 `state.sqlite` into `statev2.sqlite` only when `statev2.sqlite` is missing. After the first start, edits to `state.sqlite` change nothing.

Start the target runtime once before seeding so all migrations have run. Use an isolated base directory. Stop the server before writes to avoid racing application state or an active projection.

Expand All @@ -23,7 +25,7 @@ Inspect current columns before writing a fixture:
```bash
node apps/server/scripts/t3-sqlite-state.ts query \
--base-dir <base-dir> \
--sql "PRAGMA table_info(projection_threads)"
--sql "PRAGMA table_info(orchestration_v2_projection_threads)"
```

Apply a SQL fixture from a file:
Expand All @@ -38,21 +40,10 @@ Use one statement per invocation for both `query` and `exec`; the helper wraps w

## Seed projection data carefully

The web UI primarily reads these projection tables:

- `projection_projects`
- `projection_threads`
- `projection_thread_messages`
- `projection_thread_activities`
- `projection_thread_sessions`
- `projection_turns`
- `projection_pending_approvals`
- `projection_thread_proposed_plans`

Inspect `PRAGMA table_info(<table>)` and the current migrations under `apps/server/src/persistence/Migrations/` before constructing inserts. Keep identifiers unique, timestamps as ISO strings, JSON columns valid, and related project/thread/turn IDs consistent.
Clients read projects from `projection_projects` and everything else from the `orchestration_v2_projection_*` tables: threads, runs, messages, turn items, runtime requests, plans, and provider sessions. The older `projection_thread*` tables hold V1 history, which the server imports once per thread at startup; later edits there do not reach the UI.

For a substantial current example, inspect `seedDatabase` in `scripts/mobile-showcase-environment.ts`. Adapt its column set to the target database instead of assuming copied SQL remains current.
Most V2 rows carry a `payload_json` that must decode against the schemas in `packages/contracts/src/orchestrationV2.ts`. The safest start is a row the app wrote itself: create a thread through the UI, copy its rows, and edit them. Keep identifiers unique, timestamps as ISO strings, and related project, thread, and run IDs consistent.

Direct projection writes are appropriate for ephemeral visual states, edge-case counts, long titles, activity lists, and similar UI fixtures. They do not create a coherent orchestration event history. Do not modify `orchestration_events` unless the test specifically exercises projector internals, and do not use direct projection writes to claim backend business behavior works.
Direct projection writes are appropriate for ephemeral visual states, edge-case counts, long titles, long timelines, and similar UI fixtures. They do not create a coherent event history. Leave the event log (`orchestration_events`) unchanged, and do not use direct projection writes to claim backend business behavior works.

Use the app's commands or APIs for behavior tests. Use `node apps/server/src/bin.ts auth ...` for auth state rather than editing `auth_pairing_links` or `auth_sessions`.
3 changes: 3 additions & 0 deletions .github/TRIAGE_EXEMPTIONS.td
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Explicit contribution-triage exemptions; independent of VOUCHED.td.
# Syntax: github:username (one entry per line; no denouncements).
# Keep entries sorted alphabetically. Only the trusted upstream main copy applies.
github:bmdavis419
github:juliusmarminge
github:maria-rcks
github:markflorkowski
github:t3dotgg
github:Yash-Singh1
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@ jobs:
test,
test_web,
test_server,
transfer-report,
rust,
mobile_native_changes,
mobile_native_static_analysis,
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/mobile-eas-production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ name: Mobile EAS Production
# Store stays a manual App Store Connect step.
# 2. OTA: publish a production-channel update for each platform where at
# least one finished production build matches the current native
# fingerprint. Old-version binaries with a matching fingerprint receive
# it too. When native drift means no binary could install the update,
# fingerprint. Older binaries of the same major version with a matching
# fingerprint receive it too; the major version is part of the
# fingerprint (apps/mobile/fingerprint.config.js), so a new major never
# reaches the previous major's store binaries over the air. When native
# drift means no binary could install the update,
# it is skipped and flagged in the job summary instead of published
# into the void.
# workflow_dispatch remains as a manual override for both modes (e.g. to
Expand Down
21 changes: 6 additions & 15 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,17 +88,8 @@ The most common defect in this repo is a change that works on the path you teste

An empty database is a bad test. Seed your worktree's `.t3` with a copy of real data instead of pointing at live state:

- Copy from `~/.t3/userdata` (the developer's real data, the most realistic test set) or `~/.t3/dev`. Worktree state lives at `<worktree>/.t3/userdata`.
- Snapshot the database with `VACUUM INTO`, which is safe even while a server has the source open and yields one consistent file:

```bash
mkdir -p .t3/userdata
rm -f .t3/userdata/state.sqlite* # VACUUM INTO refuses to overwrite
bun -e "new (require('bun:sqlite').Database)(process.env.HOME + '/.t3/userdata/state.sqlite', { readonly: true }).run(\"VACUUM INTO '.t3/userdata/state.sqlite'\")"
```

A plain `cp` is only safe when no server has the source open, and must bring the `-wal` and `-shm` siblings along. A live file copy is a corrupt copy.

- Run `vp run migrate-dev-db` with your dev server stopped. It rebuilds `<worktree>/.t3/userdata/statev2.sqlite` from a read-only snapshot of `~/.t3/userdata/statev2.sqlite`, the developer's real data. It keeps recent projects and their stopped threads, and drops scheduled tasks, pending work, and auth sessions, so your dev server never runs the developer's agents. Raise `--projects` and `--threads-per-project` for more data.
- Refresh `statev2.sqlite`, not `state.sqlite`. The server copies the V1 `state.sqlite` only when `statev2.sqlite` is missing.
- Bring `secrets` and `settings.json` only if the flow under test needs them.
- Copy in, never symlink. Data flows one way: into your sandbox, never back out.

Expand All @@ -108,7 +99,7 @@ An empty database is a bad test. Seed your worktree's `.t3` with a copy of real
- Test meaningful logic or observable behavior. Do not render components to static markup to assert props or attributes, or add tests that merely assert callback wiring or mirror the implementation.
- **Do not run repo-wide checks.** No `vp check`, no `vp run -r test`, no `vp run -r typecheck` unless I ask. CI owns the full suite.
- Backend behavior changes ship with focused tests for that behavior.
- The server is event-sourced and its async flows emit typed receipts. Wait on receipts and worker drains, never on sleeps or polling. A test that needs a timeout to pass is wrong.
- The server is event-sourced, and side effects run after the command commits. In tests, drain the effect worker (`OrchestrationEffectWorkerV2.drain`) or await the specific persisted event or `Deferred` that marks the milestone. Never wait on sleeps or polling. A test that needs a timeout to pass is wrong.
- Upon request, user-visible frontend changes should get one integrated pass in a real client: `test-t3-app` for web, `test-t3-mobile` for mobile. The primary agent does this once after integrating. Subagents do not launch their own dev servers. Ask permission before doing computer use or spinning up browsers.

For authorized mobile verification, a missing or outdated native client is a build step, not a blocker. Run `node scripts/mobile-native-client.ts ensure <ios|android> <device-id>` on the simulator host before starting Metro. It checks the local Expo fingerprint and builds/installs when needed. See `test-t3-mobile` for the full workflow.
Expand All @@ -121,7 +112,7 @@ For authorized mobile verification, a missing or outdated native client is a bui
- Body: the problem in a sentence or two, then how you fixed it.
- UI changes need before/after images. Motion or timing needs a short video.
- Upload PR evidence to GitHub. Never commit PR-only screenshots or assets such as `.github/pr-assets/`.
- One concern per PR. If the description says "also", split it.
- One request is one PR. Split it only when the maintainer asks. Outside contributions follow the stricter [one problem per PR](CONTRIBUTING.md#one-problem) rule.
- 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
Expand All @@ -144,9 +135,9 @@ Most code changes do not need an internal documentation change. Agents can read

## How it works

Clients send typed WebSocket requests. The server turns them into _commands_, a pure _decider_ turns commands into persisted _events_, and a _projector_ derives the read model the UI renders. Provider CLIs run as subprocesses; per-provider _adapters_ translate their native protocols into orchestration events. Side effects run in queue-backed _reactors_ that emit _receipts_ when milestones land. Each turn ends with a _checkpoint_, a hidden git ref, so the app can diff and restore.
Clients send typed WebSocket requests. The server turns them into _commands_. The _orchestrator_ (`apps/server/src/orchestration-v2/Orchestrator.ts`) serializes commands and decides _events_ without doing any I/O. The _event sink_ commits those events, the _projections_ the UI reads, the _command receipt_, and _outbox_ effects in one transaction. The _effect worker_ then runs the effects, such as starting a provider turn or capturing a checkpoint, and feeds results back as commands. Provider CLIs run as subprocesses; per-provider _adapters_ translate their native protocols into orchestration events. Each turn ends with a _checkpoint_, a hidden git ref, so the app can diff and restore.

Full glossary with file links: `docs/internals/glossary.md`
Architecture and its constraints: `docs/internals/overview.md`. Glossary: `docs/internals/glossary.md`

## Where code lives

Expand Down
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ Explain their relationship when it is not obvious. An adjacent cleanup, refactor
its own PR unless it is necessary to solve the same problem. A large diff alone does not establish that
the PR contains unrelated work.

This rule is for outside contributions. Maintainers, the logins in
[.github/TRIAGE_EXEMPTIONS.td](.github/TRIAGE_EXEMPTIONS.td), may batch related fixes in one PR.

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.
Expand Down
14 changes: 6 additions & 8 deletions apps/desktop/scripts/browser-secret-native.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -78,21 +78,19 @@ describe.skipIf(hostPlatform !== "linux")("bundled libsecret helper", () => {
expect(result.stderr.length).toBe(0);
});

for (const [scenario, code] of [
it.each([
["missing", 2],
["empty", 2],
["locked", 3],
["cancelled", 3],
["denied", 3],
["unavailable", 4],
["unloaded", 4],
]) {
it(`reports ${scenario} without emitting a secret`, () => {
const result = run([scenario]);
expect(result.status).toBe(code);
expect(result.stdout.length).toBe(0);
});
}
])("reports %s without emitting a secret", (scenario, code) => {
const result = run([scenario]);
expect(result.status).toBe(code);
expect(result.stdout.length).toBe(0);
});
it("rejects invalid arguments before accessing the keyring", () => {
for (const args of [[], [""], ["chrome", "extra"]]) {
const result = run(args);
Expand Down
8 changes: 4 additions & 4 deletions apps/desktop/src/app/DesktopClerk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,9 @@ it.effect(
},
);

for (const entry of ["startup", "open-url"] as const) {
it.effect(`receives hosted web sign-in through the desktop ${entry} handler`, () =>
it.effect.each(["startup", "open-url"] as const)(
"receives hosted web sign-in through the desktop %s handler",
(entry) =>
Effect.gen(function* () {
storageMock.mockReturnValue(storageAdapter);
createClerkBridgeMock.mockReturnValue({ cleanup: vi.fn(), isPrimaryInstance: true });
Expand Down Expand Up @@ -382,5 +383,4 @@ for (const entry of ["startup", "open-url"] as const) {
),
);
}).pipe(Effect.scoped),
);
}
);
9 changes: 5 additions & 4 deletions apps/desktop/src/app/DesktopLifecycle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,9 @@ function makeDesktopWindowLayer(
}

describe("DesktopLifecycle", () => {
for (const platform of ["darwin", "win32", "linux"] satisfies ReadonlyArray<NodeJS.Platform>) {
it.effect(`lets the updater's quit event proceed on ${platform}`, () => {
it.effect.each(["darwin", "win32", "linux"] satisfies ReadonlyArray<NodeJS.Platform>)(
"lets the updater's quit event proceed on %s",
(platform) => {
const appListeners = new Map<string, (...args: readonly unknown[]) => void>();
let windowsDestroyed = false;
const environmentLayer = Layer.succeed(DesktopEnvironment.DesktopEnvironment, {
Expand Down Expand Up @@ -152,8 +153,8 @@ describe("DesktopLifecycle", () => {
assert.isTrue(yield* Ref.get(state.quitting));
}),
).pipe(Effect.provide(layer));
});
}
},
);

it.effect("destroys windows before waiting for backend shutdown", () =>
Effect.gen(function* () {
Expand Down
92 changes: 45 additions & 47 deletions apps/desktop/src/app/DesktopPreReadyPlatform.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,54 +81,52 @@ describe("DesktopPreReadyPlatform", () => {
);
});

for (const previousEntry of [undefined, 'Exec="/Applications/deleted-previous.AppImage" %U']) {
it.effect(
`prepares a ${previousEntry ? "stale" : "missing"} Linux desktop entry before startup yields`,
() => {
vi.stubEnv("VITE_DEV_SERVER_URL", "");
vi.stubEnv("XDG_DATA_HOME", "/xdg");
vi.stubEnv("APPIMAGE", "/Applications/current.AppImage");
getSwitchValueMock.mockReturnValue("");
let desktopName = "t3code.desktop";
let desktopEntry = previousEntry;
let iconInstalled = false;
copyFileSyncMock.mockImplementation((_source: string, destination: string) => {
iconInstalled = destination === "/xdg/icons/com.t3tools.T3Code.desktop.png";
});
setDesktopNameMock.mockImplementation((name: string) => {
desktopName = name;
});
writeFileSyncMock.mockImplementation((path: string, contents: string) => {
if (path === "/xdg/applications/com.t3tools.T3Code.desktop") desktopEntry = contents;
});
it.effect.each([
{ previousEntry: undefined, label: "missing" },
{ previousEntry: 'Exec="/Applications/deleted-previous.AppImage" %U', label: "stale" },
])("prepares a $label Linux desktop entry before startup yields", ({ previousEntry }) => {
vi.stubEnv("VITE_DEV_SERVER_URL", "");
vi.stubEnv("XDG_DATA_HOME", "/xdg");
vi.stubEnv("APPIMAGE", "/Applications/current.AppImage");
getSwitchValueMock.mockReturnValue("");
let desktopName = "t3code.desktop";
let desktopEntry = previousEntry;
let iconInstalled = false;
copyFileSyncMock.mockImplementation((_source: string, destination: string) => {
iconInstalled = destination === "/xdg/icons/com.t3tools.T3Code.desktop.png";
});
setDesktopNameMock.mockImplementation((name: string) => {
desktopName = name;
});
writeFileSyncMock.mockImplementation((path: string, contents: string) => {
if (path === "/xdg/applications/com.t3tools.T3Code.desktop") desktopEntry = contents;
});

return Effect.scoped(
Effect.gen(function* () {
const portalIdentity = Promise.resolve().then(() => ({
desktopName,
desktopEntry,
iconInstalled,
}));
yield* Layer.build(
DesktopPreReadyPlatform.layer.pipe(
Layer.provide(Layer.succeed(HostProcessPlatform, "linux")),
),
);
const identity = yield* Effect.promise(() => portalIdentity);
assert.equal(identity.desktopName, "com.t3tools.T3Code.desktop");
assert.include(identity.desktopEntry ?? "", 'Exec="/Applications/current.AppImage" %U');
assert.include(identity.desktopEntry ?? "", "Name=T3 Code (Alpha)");
assert.include(identity.desktopEntry ?? "", "MimeType=x-scheme-handler/t3code;");
assert.include(
identity.desktopEntry ?? "",
"Icon=/xdg/icons/com.t3tools.T3Code.desktop.png",
);
assert.isTrue(identity.iconInstalled);
}),
).pipe(Effect.ensuring(Effect.sync(() => vi.unstubAllEnvs())));
},
);
}
return Effect.scoped(
Effect.gen(function* () {
const portalIdentity = Promise.resolve().then(() => ({
desktopName,
desktopEntry,
iconInstalled,
}));
yield* Layer.build(
DesktopPreReadyPlatform.layer.pipe(
Layer.provide(Layer.succeed(HostProcessPlatform, "linux")),
),
);
const identity = yield* Effect.promise(() => portalIdentity);
assert.equal(identity.desktopName, "com.t3tools.T3Code.desktop");
assert.include(identity.desktopEntry ?? "", 'Exec="/Applications/current.AppImage" %U');
assert.include(identity.desktopEntry ?? "", "Name=T3 Code (Alpha)");
assert.include(identity.desktopEntry ?? "", "MimeType=x-scheme-handler/t3code;");
assert.include(
identity.desktopEntry ?? "",
"Icon=/xdg/icons/com.t3tools.T3Code.desktop.png",
);
assert.isTrue(identity.iconInstalled);
}),
).pipe(Effect.ensuring(Effect.sync(() => vi.unstubAllEnvs())));
});

it.effect("keeps startup available when the early desktop entry cannot be written", () => {
getSwitchValueMock.mockReturnValue("");
Expand Down
Loading
Loading