Skip to content

Commit dcff125

Browse files
chore: refresh fff rescan fix against main
2 parents c09c710 + 54084ae commit dcff125

196 files changed

Lines changed: 7518 additions & 4311 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.agents/skills/test-t3-mobile/SKILL.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ Start `vp run dev:client` from `apps/mobile`, or reuse a healthy Metro belonging
4141
to this checkout. Open its printed development-client URL with AgentDevice
4242
`open com.t3tools.t3code.dev <url>` and all returned target arguments.
4343
The device must be able to reach both Metro and the isolated backend.
44+
Append `&disableAutoLaunch=1&disableFab=1` to the development-client URL query
45+
when developer chrome would obscure screenshots or taps; the SDK 58 dev client
46+
applies these preferences before the app loads.
4447

4548
## Pair and verify
4649

‎.github/workflows/mobile-eas-preview.yml‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ jobs:
6666
if: steps.expo-token.outputs.present == 'true'
6767
uses: expo/expo-github-action@v8
6868
with:
69-
eas-version: latest
69+
# eas-cli 22 renamed `project` to `app` in build JSON, and
70+
# continuous-deploy-fingerprint still reads `build.project.ownerAccount`.
71+
# Unpin once https://github.com/expo/expo-github-action/issues/358 is fixed.
72+
eas-version: 21.8.0
7073
token: ${{ secrets.EXPO_TOKEN }}
7174
# npm, not pnpm: this only installs eas-cli into the action's own
7275
# tool dir, and pnpm 11 hard-fails that install on dtrace-provider's

‎.macroscope/check-run-agents/effect-service-conventions.md‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ showToolCalls: true
2222

2323
# Effect service review
2424

25-
Review changed TypeScript for the conventions below. They apply when a pull request creates, moves, refactors, or consumes an Effect service. Review only the lines the PR changed; older code in the same file that predates these conventions is not a finding. Do not demand repository-wide cleanup.
25+
Review changed TypeScript for the conventions below. They apply when a pull request creates, moves, refactors, or consumes an Effect service, or adds server behavior. Authors read the same rules in `docs/internals/effect-services.md`; keep the two in step. Review only the lines the PR changed; older code in the same file that predates these conventions is not a finding. Do not demand repository-wide cleanup.
2626

2727
## Imports and module namespaces
2828

@@ -31,11 +31,16 @@ Review changed TypeScript for the conventions below. They apply when a pull requ
3131
- Named imports stay correct for whole packages such as `@t3tools/contracts` and for modules used only for a pure helper, error, schema, config value, or type. Do not request `import type * as Contracts`.
3232
- When a barrel exposes a whole service module, prefer `export * as TokenStore from "./tokenStore.ts"` over individually renamed `make` and `layer` exports.
3333

34+
## Placement
35+
36+
- Flag new or broadened feature logic in a transport handler: a WebSocket RPC handler in `apps/server/src/ws.ts`, an HTTP route handler, or an MCP tool handler. A handler decodes input, calls one service method, and maps the service's errors to the transport's error. Filesystem, Git, process, or persistence work, folder naming, multi-step command dispatch, retries, or rollback inside a handler belongs in a service method; ask for it to move to the domain's existing service, or a new one when none owns the domain. Existing inline handlers are legacy; flag only changed lines.
37+
- Flag a new exported free function that does a server capability's effectful work (filesystem, Git, processes, persisted state) and is called from a handler, when it should be a service method. Plain functions for pure work (formatting, naming, parsing) are fine.
38+
3439
## Service definition
3540

3641
- One canonical module per service in this order: imports, error and schema declarations, the `Context.Service` tag with its interface inline, `make`, then `layer`.
3742
- Define the interface inline in `Context.Service`. Do not add a standalone `FooShape` interface; refer to the inferred type as `Foo["Service"]`.
38-
- Export a real `make` when the module owns construction. Do not write `make = Effect.succeed(...)` only to force `Layer.effect`; use `Layer.succeed`, `Layer.scoped`, or whichever constructor matches.
43+
- Define a real `make` when the module owns construction, and export it only when another module imports it; knip fails CI on an unused export, so do not ask for an export nothing uses. Do not write `make = Effect.succeed(...)` only to force `Layer.effect`; use `Layer.succeed`, `Layer.scoped`, or whichever constructor matches.
3944
- Use plain `make` and `layer` in a module named for its implementation (`BunPtyAdapter.ts`). Keep implementation-specific names when one abstract port module holds several implementations (`makeCloudflaredRelayClient`, `layerCloudflared` in `RelayClient.ts`). `infra/relay/src/db.ts` may keep its inline `Layer.succeed(RelayDb, db)`.
4045
- When a service moves, delete the old files and update every consumer, including orchestration, MCP, tests, and integration harnesses. Do not leave compatibility re-export shims.
4146

‎AGENTS.md‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ You can think of T3 Code as an open source "bring-your-own-subscription" alterna
66

77
## What makes T3 Code special?
88

9-
We have over 200,000 users who love T3 Code. It's important we maintain the things they love as we continue to iterate on the product. Here's a brief list of the things we can never compromise on.
9+
We have over 400,000 users who love T3 Code. It's important we maintain the things they love as we continue to iterate on the product. Here's a brief list of the things we can never compromise on.
1010

1111
### 1. Open at the core
1212

@@ -69,6 +69,7 @@ The most common defect in this repo is a change that works on the path you teste
6969
- **Entry points.** A behavior reachable from the chat view is usually also reachable from Settings, the command palette, and a keybinding. Fixing one is not fixing the feature.
7070
- **Clients.** Web, desktop (wraps web, adds Electron shell/IPC), and mobile (React Native, separate navigation). Shared logic lives in `packages/client-runtime`
7171
- **Providers.** Codex, Claude, Cursor, Grok, OpenCode, and Antigravity each have an adapter. Provider-shaped features need a decision per adapter, even if the decision is "not supported here".
72+
- **Agents.** A capability a user can trigger is usually one an agent should reach through MCP tools, and scheduled tasks run the same paths. That only works when it is a service method, not handler code.
7273
- **Contracts.** Anything crossing the wire is typed in `packages/contracts`. Change the schema and the server, web, mobile, and desktop all follow.
7374
- **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.
7475
- **Connection modes.** Local, remote/relay, and tunnel behave differently. Multi-device and multi-environment cases are real.
@@ -148,7 +149,7 @@ Full glossary with file links: `docs/internals/glossary.md`
148149

149150
## Where code lives
150151

151-
- `apps/server` - WebSocket, orchestration, providers, checkpointing. Effect-heavy: read `.repos/effect-smol/LLMS.md` before writing Effect code.
152+
- `apps/server` - WebSocket, orchestration, providers, checkpointing. Effect-heavy: read [Effect services](docs/internals/effect-services.md) before adding server code, and `.repos/effect-smol/LLMS.md` for the Effect library itself.
152153
- `apps/web` - React/Vite UI. `apps/desktop` wraps it, `apps/mobile` is React Native, `apps/marketing` is the site.
153154
- `packages/contracts` - Effect/Schema contracts plus small derived helpers. No heavy runtime logic.
154155
- `packages/shared` - shared runtime utils, subpath exports, no barrel.
@@ -158,6 +159,7 @@ Full glossary with file links: `docs/internals/glossary.md`
158159
## Taste
159160

160161
- Complexity belongs at the adapter boundary. Orchestration stays pure, UI stays dumb.
162+
- Server features are services; transports stay thin. A `ws.ts` RPC handler, HTTP route, or MCP tool decodes input, calls one service method, and maps errors. See [Effect services](docs/internals/effect-services.md).
161163
- `apps/web/src/components/ui` exports own their look. Pick a `variant` or `size`; do not restyle one with `className`. If none fits and the look is a generic concept, add a variant to the component; a look that belongs to one feature stays in that feature's own component, not in `components/ui`. Layout classes (width, flex, margin, position) belong on the parent. `shadcn/no-restyle` fails lint on violations.
162164
- Inferred types over annotations. `any` is the enemy.
163165
- Comments describe how a thing is used, and move when the code moves. To be used mostly to describe functions, not to annotate every line of behavior.

‎apps/desktop/src/preview/Manager.test.ts‎

Lines changed: 134 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ const {
186186
fromId,
187187
getFocusedWebContents,
188188
mkdir,
189+
previewSession,
189190
showItemInFolder,
190191
webviewSend,
191192
writeFile,
@@ -200,6 +201,7 @@ const {
200201
fromId: vi.fn<(_id?: number) => Electron.WebContents | null>((_id?: number) => null),
201202
getFocusedWebContents: vi.fn(() => null),
202203
mkdir: vi.fn((_path: string) => undefined),
204+
previewSession: { on: vi.fn() },
203205
showItemInFolder: vi.fn(),
204206
webviewSend: vi.fn(),
205207
writeFile: vi.fn((_path: string, _data: Uint8Array) => undefined),
@@ -236,7 +238,7 @@ const browserSessionLayer = Layer.succeed(
236238
BrowserSession.BrowserSession.of({
237239
getPartition: () => Effect.succeed("persist:t3code-preview-test"),
238240
isPartition: (partition) => partition.startsWith("persist:t3code-preview-"),
239-
getSession: () => Effect.die("unexpected getSession"),
241+
getSession: () => Effect.succeed(previewSession as unknown as Electron.Session),
240242
clearCookies: () => Effect.void,
241243
clearCache: () => Effect.void,
242244
}),
@@ -547,6 +549,7 @@ describe("PreviewManager", () => {
547549
getFocusedWebContents.mockReset();
548550
getFocusedWebContents.mockReturnValue(null);
549551
mkdir.mockClear();
552+
previewSession.on.mockClear();
550553
writeFile.mockClear();
551554
showItemInFolder.mockClear();
552555
clipboardItemConstructor.mockClear();
@@ -1598,7 +1601,7 @@ describe("PreviewManager", () => {
15981601
),
15991602
);
16001603

1601-
effectIt.effect("emulates prefers-color-scheme and re-applies it across webview swaps", () =>
1604+
effectIt.effect("re-applies opaque base and color scheme across webview swaps", () =>
16021605
withManager((manager) =>
16031606
Effect.gen(function* () {
16041607
const makeWebContents = (id: number) => {
@@ -1647,6 +1650,16 @@ describe("PreviewManager", () => {
16471650
yield* manager.registerWebview("tab_scheme", 42);
16481651
yield* Effect.yieldNow;
16491652

1653+
// Guests start with a transparent base; dark-scheme pages need an opaque
1654+
// one or Chromium skips their dark canvas.
1655+
const opaqueBase = {
1656+
color: { r: 255, g: 255, b: 255, a: 1 },
1657+
};
1658+
expect(first.sendCommand).toHaveBeenCalledWith(
1659+
"Emulation.setDefaultBackgroundColorOverride",
1660+
opaqueBase,
1661+
);
1662+
16501663
yield* manager.setColorScheme("tab_scheme", "dark");
16511664

16521665
expect(first.sendCommand).toHaveBeenCalledWith("Emulation.setEmulatedMedia", {
@@ -1659,6 +1672,10 @@ describe("PreviewManager", () => {
16591672
yield* manager.registerWebview("tab_scheme", 43);
16601673
yield* Effect.yieldNow;
16611674

1675+
expect(replacement.sendCommand).toHaveBeenCalledWith(
1676+
"Emulation.setDefaultBackgroundColorOverride",
1677+
opaqueBase,
1678+
);
16621679
expect(replacement.sendCommand).toHaveBeenCalledWith("Emulation.setEmulatedMedia", {
16631680
features: [{ name: "prefers-color-scheme", value: "dark" }],
16641681
});
@@ -4536,6 +4553,121 @@ describe("PreviewManager", () => {
45364553
}),
45374554
),
45384555
);
4556+
effectIt.effect("saves downloads from agent-driven pages without a Save dialog", () =>
4557+
withManager((manager) =>
4558+
Effect.gen(function* () {
4559+
let humanInput: ((event: unknown, signal?: unknown) => void) | undefined;
4560+
const wc = makeTestPreviewWebContents(vi.fn());
4561+
Object.assign(wc, {
4562+
isDevToolsOpened: () => false,
4563+
loadURL: vi.fn(async () => undefined),
4564+
reload: vi.fn(),
4565+
});
4566+
Object.assign(wc.ipc, {
4567+
on: vi.fn((channel: string, listener: typeof humanInput) => {
4568+
if (channel === "preview:human-input") humanInput = listener;
4569+
}),
4570+
});
4571+
let holdEvaluate = false;
4572+
let releaseEvaluate: (() => void) | undefined;
4573+
Object.assign(wc.debugger, {
4574+
sendCommand: vi.fn(async (method: string, params?: { expression?: string }) => {
4575+
if (method !== "Runtime.evaluate") return undefined;
4576+
if (params?.expression?.includes("matched"))
4577+
return { result: { value: { matched: true } } };
4578+
if (holdEvaluate) {
4579+
holdEvaluate = false;
4580+
await new Promise<void>((resolve) => {
4581+
releaseEvaluate = resolve;
4582+
});
4583+
}
4584+
return { result: { value: 42 } };
4585+
}),
4586+
});
4587+
fromId.mockReturnValue(wc);
4588+
const takeovers = [yield* Deferred.make<void>(), yield* Deferred.make<void>()];
4589+
let takeoverCount = 0;
4590+
let controller = "none";
4591+
yield* manager.subscribeStateChanges((_tabId, state) => {
4592+
const takeover =
4593+
controller !== "human" && state.controller === "human"
4594+
? takeovers[takeoverCount++]
4595+
: undefined;
4596+
controller = state.controller;
4597+
return takeover ? Deferred.succeed(takeover, undefined).pipe(Effect.asVoid) : Effect.void;
4598+
});
4599+
yield* manager.getBrowserSession();
4600+
yield* manager.getBrowserSession();
4601+
const installs = previewSession.on.mock.calls.filter(
4602+
([event]) => event === "will-download",
4603+
);
4604+
expect(installs).toHaveLength(1);
4605+
const willDownload = installs[0]![1] as (
4606+
event: unknown,
4607+
item: {
4608+
getFilename: () => string;
4609+
getStartTime: () => number;
4610+
setSavePath: (path: string) => void;
4611+
},
4612+
source: Electron.WebContents,
4613+
) => void;
4614+
const download = () => {
4615+
const setSavePath = vi.fn();
4616+
willDownload(
4617+
{},
4618+
{ getFilename: () => "chart.png", getStartTime: () => 1_790_844_530.5, setSavePath },
4619+
wc,
4620+
);
4621+
return setSavePath;
4622+
};
4623+
yield* manager.createTab("tab_1");
4624+
yield* manager.registerWebview("tab_1", 42);
4625+
4626+
expect(download()).not.toHaveBeenCalled();
4627+
4628+
yield* manager.automationEvaluate("tab_1", { expression: "42" });
4629+
const startedAt = (1_790_844_530_500).toString(36);
4630+
expect(download()).toHaveBeenCalledWith(
4631+
`/tmp/t3/dev/browser-artifacts/browser-download-${startedAt}-0-chart.png`,
4632+
);
4633+
// Same name, same millisecond: still a separate file.
4634+
expect(download()).toHaveBeenCalledWith(
4635+
`/tmp/t3/dev/browser-artifacts/browser-download-${startedAt}-1-chart.png`,
4636+
);
4637+
4638+
humanInput?.({}, { kind: "pointer", x: 10, y: 10, button: 0 });
4639+
yield* Deferred.await(takeovers[0]!);
4640+
expect(download()).not.toHaveBeenCalled();
4641+
4642+
// An action still waiting for the page when the human takes over must
4643+
// not mark it as agent-driven again.
4644+
holdEvaluate = true;
4645+
const running = yield* manager
4646+
.automationEvaluate("tab_1", { expression: "42" })
4647+
.pipe(Effect.forkChild({ startImmediately: true }));
4648+
yield* settle(() => releaseEvaluate !== undefined);
4649+
const queued = yield* manager
4650+
.automationEvaluate("tab_1", { expression: "42" })
4651+
.pipe(Effect.forkChild({ startImmediately: true }));
4652+
humanInput?.({}, { kind: "pointer", x: 20, y: 20, button: 0 });
4653+
yield* Deferred.await(takeovers[1]!);
4654+
releaseEvaluate?.();
4655+
yield* Fiber.await(running);
4656+
expect(Exit.isFailure(yield* Fiber.await(queued))).toBe(true);
4657+
expect(download()).not.toHaveBeenCalled();
4658+
4659+
// URL-bar navigation hands the page back to the human.
4660+
yield* manager.automationEvaluate("tab_1", { expression: "42" });
4661+
expect(download()).toHaveBeenCalled();
4662+
yield* manager.navigate("tab_1", "https://example.com/report.csv");
4663+
expect(download()).not.toHaveBeenCalled();
4664+
4665+
// Reading the page does not make it agent-driven.
4666+
yield* manager.automationWaitFor("tab_1", { text: "Example" });
4667+
expect(download()).not.toHaveBeenCalled();
4668+
}),
4669+
),
4670+
);
45394671
});
45404672

45414673
describe("PreviewOperationError", () => {

0 commit comments

Comments
 (0)