Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
8e16684
add reference
juliusmarminge Mar 11, 2026
98289a2
remove test file for now
juliusmarminge Mar 11, 2026
7b78c42
remove another test file that just trips the model up
juliusmarminge Mar 11, 2026
61a365a
mirror setup
juliusmarminge Mar 11, 2026
ae2651f
setup http router
juliusmarminge Mar 11, 2026
29fee97
wire in http routes
juliusmarminge Mar 11, 2026
63c7cee
start wiring in rpc
juliusmarminge Mar 11, 2026
aa34e66
add test
juliusmarminge Mar 11, 2026
75e8170
plan out remaining ports
juliusmarminge Mar 11, 2026
9c3a40d
wire up keybindings
juliusmarminge Mar 11, 2026
a5ebd0c
searchEntries
juliusmarminge Mar 11, 2026
70f7adb
finihs phase 2
juliusmarminge Mar 11, 2026
89fc5c1
phase 3 - git - complete
juliusmarminge Mar 11, 2026
0ca441f
phase 4 complete
juliusmarminge Mar 11, 2026
9e3677e
phase 5
juliusmarminge Mar 11, 2026
d984557
prune unused stuff from wsServer.ts
juliusmarminge Mar 11, 2026
84c6b51
add schemas for streaming rpcs
juliusmarminge Mar 11, 2026
95650de
streaming server config
juliusmarminge Mar 11, 2026
c0788c0
remaining
juliusmarminge Mar 13, 2026
7d73b72
type
juliusmarminge Mar 13, 2026
8ea82bc
client
juliusmarminge Mar 14, 2026
80b42f5
Derive server paths from base dir and unify platform layers
juliusmarminge Mar 24, 2026
6295ae6
kewl
juliusmarminge Mar 24, 2026
864c936
rm reference
juliusmarminge Mar 24, 2026
35561a0
stale plan
juliusmarminge Mar 24, 2026
eb8ee3f
Merge origin/main into effect-http-router
juliusmarminge Mar 29, 2026
ee67778
fix stm and sqlite
juliusmarminge Mar 29, 2026
b6105c8
Merge origin/main into effect-http-router
juliusmarminge Mar 29, 2026
ec4226e
mv helper
juliusmarminge Mar 30, 2026
d190fdb
Merge origin/main into effect-http-router
juliusmarminge Mar 30, 2026
dbdc2bf
Use callback queue for terminal event streaming
juliusmarminge Mar 30, 2026
6502a6c
Merge origin/main into effect-http-router
juliusmarminge Mar 30, 2026
b65e74d
Fix RPC browser test websocket mocks
juliusmarminge Mar 30, 2026
0f48b4f
Use Effect RPC in browser test mocks
juliusmarminge Mar 30, 2026
1cf79b5
bump
juliusmarminge Mar 30, 2026
d59a554
merge
juliusmarminge Mar 30, 2026
b6771ca
Move git error types to contracts
juliusmarminge Mar 30, 2026
849a39c
rm reexport shim
juliusmarminge Mar 30, 2026
1f1a3dd
Add bootstrap envelope config precedence
juliusmarminge Mar 31, 2026
f9b0f14
kewl
juliusmarminge Mar 31, 2026
7c692d4
Refactor ServerSettings imports and remove unused error handling
juliusmarminge Mar 31, 2026
0ba3f29
tidy up
juliusmarminge Mar 31, 2026
dd44ef0
rm
juliusmarminge Mar 31, 2026
3656164
rev bun a bit
juliusmarminge Mar 31, 2026
fe8ba96
cool
juliusmarminge Mar 31, 2026
a3a4a22
Refactor websocket native API around typed RPC client
juliusmarminge Mar 31, 2026
6cb183f
Move server config state to ws native atoms
juliusmarminge Mar 31, 2026
b636da5
runtime
juliusmarminge Mar 31, 2026
5ea99a6
make dir
juliusmarminge Mar 31, 2026
7d7c587
rm unnecessary test
juliusmarminge Mar 31, 2026
d7ec9d6
Unify project favicon routing and server setup
juliusmarminge Mar 31, 2026
f306805
Restore rich error message context in git error classes
cursoragent Mar 31, 2026
6dcc05c
Merge origin/main into effect-http-router
juliusmarminge Mar 31, 2026
6ee71f3
Merge remote-tracking branch 'origin/effect-http-router' into effect-…
juliusmarminge Mar 31, 2026
dfeb214
cleanup
juliusmarminge Mar 31, 2026
fe22173
rm more unused cruft
juliusmarminge Mar 31, 2026
7b4bbd0
not that either
juliusmarminge Mar 31, 2026
92f3f08
Normalize workspace paths and inline favicon fallback
juliusmarminge Mar 31, 2026
d628d39
Stream git stacked actions over RPC
juliusmarminge Apr 1, 2026
8a403cc
move test helper
juliusmarminge Apr 1, 2026
44adfc7
Format web tsconfig to match JSON style
juliusmarminge Apr 1, 2026
adc1619
Refactor WS contracts into rpc module
juliusmarminge Apr 1, 2026
cce11f0
Introduce AtomRpc client for websocket RPC
juliusmarminge Apr 1, 2026
3142a92
Move server state handling into RPC layer
juliusmarminge Apr 1, 2026
b858ced
Merge origin/main into effect-http-router
juliusmarminge Apr 1, 2026
0a353d1
Switch WebSocket RPC to secure URLs and await teardown
juliusmarminge Apr 1, 2026
176a58b
Fix WsTransport dispose ordering test
juliusmarminge Apr 1, 2026
0bb9550
log
juliusmarminge Apr 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .oxfmtrc.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"ignorePatterns": [
".reference",
".plans",
"dist",
"dist-electron",
Expand Down
89 changes: 89 additions & 0 deletions .plans/effect-atom.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Replace React Query With AtomRpc + Atom State

## Summary
- Use `effect/unstable/reactivity/AtomRpc` over the existing `WsRpcGroup`; stop wrapping RPC in promises via [wsRpcClient.ts](/Users/julius/.t3/worktrees/codething-mvp/effect-http-router/apps/web/src/wsRpcClient.ts) and [wsNativeApi.ts](/Users/julius/.t3/worktrees/codething-mvp/effect-http-router/apps/web/src/wsNativeApi.ts).
- Keep Zustand for orchestration read model and UI state.
- Keep a narrow `desktopBridge` adapter for dialogs, menus, external links, theme, and updater APIs.
- Do not introduce Suspense in this migration. Atom-backed hooks should keep returning `data`, `error`, `isLoading|isPending`, `refresh`, and `mutateAsync`-style surfaces so component churn stays low.

## Target Architecture
- Extract the websocket `RpcClient.Protocol` layer from [wsTransport.ts](/Users/julius/.t3/worktrees/codething-mvp/effect-http-router/apps/web/src/wsTransport.ts) into `rpc/protocol.ts`.
- Define one `AtomRpc.Service` for `WsRpcGroup` in `rpc/client.ts`.
- Add `rpc/invalidation.ts` with explicit scoped invalidation keys: `git:${cwd}`, `project:${cwd}`, `checkpoint:${threadId}`, `server-config`.
- Add `platform/desktopBridge.ts` as the only browser/desktop facade.
- Remove from web by the end: [wsNativeApi.ts](/Users/julius/.t3/worktrees/codething-mvp/effect-http-router/apps/web/src/wsNativeApi.ts), [nativeApi.ts](/Users/julius/.t3/worktrees/codething-mvp/effect-http-router/apps/web/src/nativeApi.ts), [wsNativeApiState.ts](/Users/julius/.t3/worktrees/codething-mvp/effect-http-router/apps/web/src/wsNativeApiState.ts), [wsNativeApiAtoms.tsx](/Users/julius/.t3/worktrees/codething-mvp/effect-http-router/apps/web/src/wsNativeApiAtoms.tsx), [wsRpcClient.ts](/Users/julius/.t3/worktrees/codething-mvp/effect-http-router/apps/web/src/wsRpcClient.ts), and all `*ReactQuery.ts` modules.

## Phase 1: Infrastructure First
1. Extract the shared websocket RPC protocol layer from [wsTransport.ts](/Users/julius/.t3/worktrees/codething-mvp/effect-http-router/apps/web/src/wsTransport.ts) without changing behavior.
2. Build the AtomRpc client on top of that layer.
3. Add one temporary `runRpc` helper for imperative handlers that still want `Promise` ergonomics; it must call the AtomRpc service directly and must not reintroduce a facade object.
4. Replace manual registry wiring with one app-level registry provider based on `@effect/atom-react`.
5. Land this as a no-behavior-change PR.

## Phase 2: Replace `wsNativeApi`-Owned Push State
1. Migrate welcome/config/provider/settings state first, because it is already atom-shaped and is the lowest-risk way to delete `wsNativeApi` responsibilities.
2. Replace [wsNativeApiState.ts](/Users/julius/.t3/worktrees/codething-mvp/effect-http-router/apps/web/src/wsNativeApiState.ts) with `rpc/serverState.ts`, updated directly from `subscribeServerLifecycle` and `subscribeServerConfig`.
3. Keep the current hook names for one PR: `useServerConfig`, `useServerSettings`, `useServerProviders`, `useServerKeybindings`, `useServerWelcomeSubscription`, `useServerConfigUpdatedSubscription`.
4. Move bootstrap side effects out of [wsNativeApiAtoms.tsx](/Users/julius/.t3/worktrees/codething-mvp/effect-http-router/apps/web/src/wsNativeApiAtoms.tsx) into a new root bootstrap component mounted from [__root.tsx](/Users/julius/.t3/worktrees/codething-mvp/effect-http-router/apps/web/src/routes/__root.tsx).
5. Delete the `server.getConfig()` fallback logic from [wsNativeApi.ts](/Users/julius/.t3/worktrees/codething-mvp/effect-http-router/apps/web/src/wsNativeApi.ts); snapshot fetch now lives beside the stream atoms.

## Phase 3: Replace React Query Domain By Domain
1. Replace [gitReactQuery.ts](/Users/julius/.t3/worktrees/codething-mvp/effect-http-router/apps/web/src/lib/gitReactQuery.ts) first.
2. Add `rpc/gitAtoms.ts` and `rpc/useGit.ts` with `useGitStatus`, `useGitBranches`, `useResolvePullRequest`, and `useGitMutation`.
3. Mutation settlement must invalidate scoped keys, not a global cache. `checkout`, `pull`, `init`, `createWorktree`, `removeWorktree`, `preparePullRequestThread`, and stacked actions invalidate `git:${cwd}`. Worktree create/remove also invalidates `project:${cwd}`.
4. Replace [projectReactQuery.ts](/Users/julius/.t3/worktrees/codething-mvp/effect-http-router/apps/web/src/lib/projectReactQuery.ts) second. `useProjectSearchEntries` must preserve current “keep previous results while loading” behavior.
5. Replace [providerReactQuery.ts](/Users/julius/.t3/worktrees/codething-mvp/effect-http-router/apps/web/src/lib/providerReactQuery.ts) third. Preserve current checkpoint error normalization and retry/backoff semantics inside the atom effect. Invalidate by `checkpoint:${threadId}`.
6. Defer the desktop updater until the last phase.

## Phase 4: Move Root Invalidation Off `queryClient`
1. In [__root.tsx](/Users/julius/.t3/worktrees/codething-mvp/effect-http-router/apps/web/src/routes/__root.tsx), remove `QueryClient` usage and replace the throttled `invalidateQueries` block with throttled invalidation helpers.
2. Keep Zustand orchestration/event application unchanged.
3. Map current effects exactly:
- git or checkpoint-affecting orchestration events touch `checkpoint:${threadId}`
- file creation/deletion/restoration touches `project:${cwd}`
- config-affecting server events touch `server-config`

## Phase 5: Remove Imperative `NativeApi` Usage
1. Create narrow modules instead of a replacement mega-facade:
- `rpc/orchestrationActions.ts`
- `rpc/terminalActions.ts`
- `rpc/gitActions.ts`
- `rpc/projectActions.ts`
- `platform/desktopBridge.ts`
2. Migrate direct [nativeApi.ts](/Users/julius/.t3/worktrees/codething-mvp/effect-http-router/apps/web/src/nativeApi.ts) callers by domain, not file-by-file: git-heavy components first, then orchestration/thread actions, then shell/dialog helpers.
3. After the last caller is gone, delete [nativeApi.ts](/Users/julius/.t3/worktrees/codething-mvp/effect-http-router/apps/web/src/nativeApi.ts) and the `window.nativeApi` fallback entirely.
4. In the final cleanup PR, remove `NativeApi` from [ipc.ts](/Users/julius/.t3/worktrees/codething-mvp/effect-http-router/packages/contracts/src/ipc.ts) if nothing outside web still needs it.

## Phase 6: Remove React Query Completely
1. Delete `@tanstack/react-query` from `apps/web/package.json`.
2. Remove `QueryClientProvider` and router context from [router.ts](/Users/julius/.t3/worktrees/codething-mvp/effect-http-router/apps/web/src/router.ts) and [__root.tsx](/Users/julius/.t3/worktrees/codething-mvp/effect-http-router/apps/web/src/routes/__root.tsx).
3. Replace [desktopUpdateReactQuery.ts](/Users/julius/.t3/worktrees/codething-mvp/effect-http-router/apps/web/src/lib/desktopUpdateReactQuery.ts) with a writable atom plus `desktopBridge.onUpdateState`.
4. Delete the old query-option tests.

## Public Interfaces And Types
- Preserve the current server-state hook names during the transition.
- Add permanent domain hooks: `useGitStatus`, `useGitBranches`, `useResolvePullRequest`, `useProjectSearchEntries`, `useCheckpointDiff`, `useDesktopUpdateState`.
- Do not expose raw AtomRpc clients to components.
- Do not add Suspense as part of this migration.
- Final boundary is direct RPC for server features plus `desktopBridge` for local desktop features.

## Test Plan
- Add unit tests for `rpc/serverState.ts`: snapshot bootstrapping, stream replay, provider/settings updates.
- Add unit tests for git/project/checkpoint hooks: loading, error mapping, retry behavior, invalidation, keep-previous-result behavior.
- Update the browser harness in [wsRpcHarness.ts](/Users/julius/.t3/worktrees/codething-mvp/effect-http-router/apps/web/test/wsRpcHarness.ts) to assert direct RPC + atom behavior instead of `__resetNativeApiForTests`.
- Replace [wsNativeApi.test.ts](/Users/julius/.t3/worktrees/codething-mvp/effect-http-router/apps/web/src/wsNativeApi.test.ts), `gitReactQuery.test.ts`, `providerReactQuery.test.ts`, and `desktopUpdateReactQuery.test.ts` with equivalent atom-backed coverage.
- Acceptance scenarios:
- welcome still bootstraps snapshot and navigation
- keybindings toast still responds to config stream updates
- git status/branches refresh after checkout/pull/worktree actions
- PR resolve dialog keeps cached result while typing
- `@` path search refreshes after file mutations and orchestration events
- diff panel refreshes when checkpoints arrive
- desktop updater still reflects push events and button actions

## Assumptions And Defaults
- Zustand stays in scope; only `react-query` is being removed.
- `desktopBridge` remains the only non-RPC boundary.
- The migration lands as 5-6 small PRs, each green independently.
- Invalidations are explicit and scoped; do not recreate a global cache client abstraction.
- Orchestration recovery/order logic stays as-is; only the data-fetching and mutation layer changes.
4 changes: 2 additions & 2 deletions apps/desktop/scripts/dev-electron.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ const devServerUrl = `http://localhost:${port}`;
const requiredFiles = [
"dist-electron/main.js",
"dist-electron/preload.js",
"../server/dist/index.mjs",
"../server/dist/bin.mjs",
];
const watchedDirectories = [
{ directory: "dist-electron", files: new Set(["main.js", "preload.js"]) },
{ directory: "../server/dist", files: new Set(["index.mjs"]) },
{ directory: "../server/dist", files: new Set(["bin.mjs"]) },
];
const forcedShutdownTimeoutMs = 1_500;
const restartDebounceMs = 120;
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ function resolveAboutCommitHash(): string | null {
}

function resolveBackendEntry(): string {
return Path.join(resolveAppRoot(), "apps/server/dist/index.mjs");
return Path.join(resolveAppRoot(), "apps/server/dist/bin.mjs");
}

function resolveBackendCwd(): string {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import {
} from "./TestProviderAdapter.integration.ts";
import { deriveServerPaths, ServerConfig } from "../src/config.ts";
import { WorkspaceEntriesLive } from "../src/workspace/Layers/WorkspaceEntries.ts";
import { WorkspacePathsLive } from "../src/workspace/Layers/WorkspacePaths.ts";

function runGit(cwd: string, args: ReadonlyArray<string>) {
return execFileSync("git", args, {
Expand Down Expand Up @@ -125,7 +126,7 @@ function waitFor<A, E>(
read: Effect.Effect<A, E>,
predicate: (value: A) => boolean,
description: string,
timeoutMs = 10_000,
timeoutMs = 40_000,
): Effect.Effect<A, never> {
const RETRY_SIGNAL = "wait_for_retry";
const retryIntervalMs = 10;
Expand Down Expand Up @@ -320,10 +321,12 @@ export const makeOrchestrationIntegrationHarness = (
Layer.provideMerge(runtimeServicesLayer),
Layer.provideMerge(
WorkspaceEntriesLive.pipe(
Layer.provide(WorkspacePathsLive),
Layer.provideMerge(gitCoreLayer),
Layer.provide(NodeServices.layer),
),
),
Layer.provideMerge(WorkspacePathsLive),
);
const orchestrationReactorLayer = OrchestrationReactorLive.pipe(
Layer.provideMerge(runtimeIngestionLayer),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ function waitForSync<A>(
read: () => A,
predicate: (value: A) => boolean,
description: string,
timeoutMs = 3000,
timeoutMs = 10_000,
): Effect.Effect<A, never> {
return Effect.gen(function* () {
const deadline = Date.now() + timeoutMs;
Expand Down
11 changes: 5 additions & 6 deletions apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,29 @@
"directory": "apps/server"
},
"bin": {
"t3": "./dist/index.mjs"
"t3": "./dist/bin.mjs"
},
"files": [
"dist"
],
"type": "module",
"scripts": {
"dev": "bun run src/index.ts",
"dev": "bun run src/bin.ts",
"build": "node scripts/cli.ts build",
"start": "node dist/index.mjs",
"start": "node dist/bin.mjs",
"prepare": "effect-language-service patch",
"typecheck": "tsc --noEmit",
"test": "vitest run"
},
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.2.77",
"@effect/platform-bun": "catalog:",
"@effect/platform-node": "catalog:",
"@effect/sql-sqlite-bun": "catalog:",
"@pierre/diffs": "^1.1.0-beta.16",
"effect": "catalog:",
"node-pty": "^1.1.0",
"open": "^10.1.0",
"ws": "^8.18.0"
"open": "^10.1.0"
},
"devDependencies": {
"@effect/language-service": "catalog:",
Expand All @@ -40,7 +40,6 @@
"@t3tools/web": "workspace:*",
"@types/bun": "catalog:",
"@types/node": "catalog:",
"@types/ws": "^8.5.13",
"tsdown": "catalog:",
"typescript": "catalog:",
"vitest": "catalog:"
Expand Down
17 changes: 17 additions & 0 deletions apps/server/src/bin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
import * as NodeServices from "@effect/platform-node/NodeServices";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import { Command } from "effect/unstable/cli";

import { NetService } from "@t3tools/shared/Net";
import { cli } from "./cli";
import { version } from "../package.json" with { type: "json" };

const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, NetService.layer);

Command.run(cli, { version }).pipe(
Effect.scoped,
Effect.provide(CliRuntimeLayer),
NodeRuntime.runMain,
);
2 changes: 1 addition & 1 deletion apps/server/src/checkpointing/Errors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Schema } from "effect";
import type { ProjectionRepositoryError } from "../persistence/Errors.ts";
import { GitCommandError } from "../git/Errors.ts";
import { GitCommandError } from "@t3tools/contracts";

/**
* CheckpointUnavailableError - Expected checkpoint does not exist.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { CheckpointStoreLive } from "./CheckpointStore.ts";
import { CheckpointStore } from "../Services/CheckpointStore.ts";
import { GitCoreLive } from "../../git/Layers/GitCore.ts";
import { GitCore } from "../../git/Services/GitCore.ts";
import { GitCommandError } from "../../git/Errors.ts";
import { GitCommandError } from "@t3tools/contracts";
import { ServerConfig } from "../../config.ts";
import { ThreadId } from "@t3tools/contracts";

Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/checkpointing/Layers/CheckpointStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { randomUUID } from "node:crypto";
import { Effect, Layer, FileSystem, Path } from "effect";

import { CheckpointInvariantError } from "../Errors.ts";
import { GitCommandError } from "../../git/Errors.ts";
import { GitCommandError } from "@t3tools/contracts";
import { GitCore } from "../../git/Services/GitCore.ts";
import { CheckpointStore, type CheckpointStoreShape } from "../Services/CheckpointStore.ts";
import { CheckpointRef } from "@t3tools/contracts";
Expand Down
Loading
Loading