Uh oh!
There was an error while loading. Please reload this page.
[codex] Rewrite client connection architecture - #2978
Conversation
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
🚀 Expo continuous deployment is ready!
|
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
16c9aba to
2a29e34Compare2a29e34 to
b976d5fCompare| const openDatabase = Effect.fn("web.connectionStorage.openDatabase")(function* () { | ||
| return yield* Effect.callback<IDBDatabase, ConnectionTransientError>((resume) => { | ||
| if (typeof indexedDB === "undefined") { | ||
| resume( | ||
| Effect.fail(catalogError("open", "IndexedDB is unavailable in this browser context.")), | ||
| ); | ||
| return; | ||
| } | ||
| const request = indexedDB.open(DATABASE_NAME, DATABASE_VERSION); | ||
| request.addEventListener("upgradeneeded", () => { | ||
| if (!request.result.objectStoreNames.contains(CATALOG_STORE_NAME)) { | ||
| request.result.createObjectStore(CATALOG_STORE_NAME); | ||
| } | ||
| if (!request.result.objectStoreNames.contains(SHELL_STORE_NAME)) { | ||
| request.result.createObjectStore(SHELL_STORE_NAME); | ||
| } | ||
| if (!request.result.objectStoreNames.contains(THREAD_STORE_NAME)) { | ||
| request.result.createObjectStore(THREAD_STORE_NAME); | ||
| } | ||
| }); | ||
| request.addEventListener("error", () => { | ||
| resume(Effect.fail(catalogError("open", request.error ?? "Unknown IndexedDB error"))); | ||
| }); | ||
| request.addEventListener("success", () => { | ||
| resume(Effect.succeed(request.result)); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
🟡 Mediumconnection/webConnectionStorage.ts:87
When indexedDB.open() needs to upgrade the schema but another tab holds an older version, the blocked event fires and the Effect never completes — neither success nor error handlers execute until the blocking tab closes. If that tab never closes, openDatabase hangs indefinitely. Consider handling the blocked event by resuming with a descriptive error or triggering a retry with timeout.
+ request.addEventListener("blocked", () => {+ resume(Effect.fail(catalogError("open", "Database upgrade blocked by another tab")));+ });🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/connection/webConnectionStorage.ts around lines 87-113:
When `indexedDB.open()` needs to upgrade the schema but another tab holds an older version, the `blocked` event fires and the Effect never completes — neither `success` nor `error` handlers execute until the blocking tab closes. If that tab never closes, `openDatabase` hangs indefinitely. Consider handling the `blocked` event by resuming with a descriptive error or triggering a retry with timeout.
Evidence trail:
apps/web/src/connection/webConnectionStorage.ts lines 87-114 at REVIEWED_COMMIT: `openDatabase` registers handlers for `upgradeneeded` (line 96), `error` (line 107), and `success` (line 110), but not for `blocked`. IndexedDB spec: https://w3c.github.io/IndexedDB/#request-api — the `blocked` event fires on IDBOpenDBRequest when the open operation is blocked by existing connections, and `success`/`error` don't fire until the block is resolved.
fb36d5e to
8de82fbCompareUh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
9ba3552 to
a01b7f9Compareced8bdf to
22b4b24Compare409fd6c to
ecd84a7Compareecd84a7 to
f5c0afeCompareUh oh!
There was an error while loading. Please reload this page.
| ); | ||
| if (!isSignedIn || !userId) { | ||
| setManagedRelaySession(appAtomRegistry, null); | ||
| if (previousAccount !== null) { |
There was a problem hiding this comment.
🟡 Mediumcloud/managedAuth.tsx:59
On first render when signed out, previousAccount is undefined, so undefined !== null evaluates to true and queueAccountCleanup() runs unnecessarily. This triggers removeRelayEnvironments() and relay client token cache reset even though no account was ever established. Change the condition to if (previousAccount) so cleanup only happens when transitioning from an actual signed-in state.
- if (previousAccount !== null) {+ if (previousAccount) {🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/cloud/managedAuth.tsx around line 59:
On first render when signed out, `previousAccount` is `undefined`, so `undefined !== null` evaluates to `true` and `queueAccountCleanup()` runs unnecessarily. This triggers `removeRelayEnvironments()` and relay client token cache reset even though no account was ever established. Change the condition to `if (previousAccount)` so cleanup only happens when transitioning from an actual signed-in state.
Evidence trail:
apps/web/src/cloud/managedAuth.tsx lines 26, 35, 37, 57-61, 63 at REVIEWED_COMMIT. Line 26: ref initialized to `undefined`. Line 35: `previousAccount = observedAccountRef.current` (undefined on first render). Line 59: condition `previousAccount !== null` doesn't account for `undefined`. Line 63: correctly checks all three states (`!== undefined && !== null && !== userId`).
| ? (activeSavedEnvironmentRuntime?.connectionState ?? "disconnected") | ||
| : "connected"; | ||
| const primaryEnvironmentId = primaryEnvironment?.environmentId ?? null; | ||
| const activeEnvironment = |
There was a problem hiding this comment.
🟡 Mediumcomponents/ChatView.tsx:1119
The refactored code removed the guard that excluded the primary environment from unavailability checks. Now activeEnvironmentUnavailable is computed for all environments including the primary, so if the primary's connection.phase is temporarily "connecting" or "available" during startup, the UI shows an unavailable banner and blocks message dispatch — behavior that never occurred before.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/components/ChatView.tsx around line 1119:
The refactored code removed the guard that excluded the primary environment from unavailability checks. Now `activeEnvironmentUnavailable` is computed for all environments including the primary, so if the primary's `connection.phase` is temporarily `"connecting"` or `"available"` during startup, the UI shows an unavailable banner and blocks message dispatch — behavior that never occurred before.
Evidence trail:
Old guard at MERGE_BASE in ChatView.tsx: `activeThread.environmentId !== primaryEnvironmentId` check in `activeSavedEnvironmentRecord` assignment; new code at REVIEWED_COMMIT ChatView.tsx lines 1119-1123 with no primary guard; `AVAILABLE_CONNECTION_STATE` defined at packages/client-runtime/src/connection/model.ts:148-154 with `phase: "available"`; `activeEnvironmentUnavailable` used to block sends at ChatView.tsx:2806, ChatView.tsx:3427; used to block revert at ChatView.tsx:2746; used for banner at ChatView.tsx:1353; `isConnecting` always false at ChatView.tsx:904 (`_setIsConnecting` unused).
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Integrate 65 upstream commits, including the pingdotgg#2978 client-connection rewrite (imperative WsRpcClient + Zustand store → Effect atoms). Conflict resolution & porting: - Resolved 12 merge conflicts (imports, routes, settings, provider test). - Re-pointed fork features onto the new atom architecture: rebuilt environmentApi/localApi over a thin imperative RPC bridge (rpc/imperativeEnvironmentRpc), migrated store consumers (Sidebar, WorkspacePicker, PR view, notification sounds, CommitControl) from the deleted store.ts to the new entities/projects/threads atoms, and restored deps/assets the rewrite dropped (vscode-icons). Idiomatic PR-review data layer: - Replaced the React-Query module (lib/gitPRReactQuery.ts) with Effect-atom query families + command atoms (state/gitPr.ts); migrated all 11 PR/commit components to useEnvironmentQuery/useAtomCommand and removed @tanstack/react-query entirely (its QueryClientProvider was deleted by the rewrite). Full repo typecheck passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Re-integrates the WSL parallel-backends renderer onto upstream's rewritten connection/RPC architecture (pingdotgg#2978), which deleted the saved-env / environmentApi / serverState / wsConnectionState subsystem this branch was built on. Core (packages/client-runtime/connection): - Add dynamic platform-managed bearer environments: a PlatformConnectionRegistration union (Primary|Bearer) and registry.reconcilePlatform(set), which adds/removes platform-managed environments as the host topology changes and stashes the bearer credential. This models the dual-mode WSL secondary (separate loopback origin, bearer auth, dynamic port so it is rederived rather than persisted). The primary and the WSL-only primary keep the same-origin cookie path. Web platform source (apps/web/src/connection/platform.ts): - Emit the platform set from the plural getLocalEnvironmentBootstraps() IPC: the primary (cookie) plus each WSL backend (bearer via bootstrap-token exchange), polling and reconciling on toggle. Replaces the deleted 541-line reconciler. Renderer re-home: - Drop the old subsystem (environmentApi, rpc/serverState, rpc/wsConnectionState, environments/runtime/*, environments/local/*) and their tests. - Re-home the Sidebar cold-boot indicator, the ConnectionsSettings WSL picker, and the multi-environment provider-update feature onto the new hooks (useEnvironments, serverEnvironment.updateProvider, the primaryServer* atoms). Desktop-local environments are identified by a BearerConnectionTarget with a "local:" connection id. - Drop the inert swap-suppression surface superseded by parallel backends. Verified: typecheck (web, desktop, client-runtime), connection / provider / localApi / authBootstrap tests, and lint all pass.
Merges upstream/main (up to 97e5cd3), including PR pingdotgg#2978 "Rewrite client connection architecture" (deleted apps/web/src/store.ts in favor of an atom architecture) and the Effect-service / namespace-node-import refactors. Fork features preserved and re-homed onto the new architecture: - Quota meter: selectLatestRateLimitActivitiesForInstance re-homed to apps/web/src/state/rateLimits.ts (useLatestRateLimitActivitiesForInstance atom). - Thread forking: added forkThread command builder + fork atom command in client-runtime; useThreadActions dispatches via useAtomCommand and waits via readThreadShell polling. - Thread export: builds the export URL from readPreparedConnection + environmentEndpointUrl (old resolveEnvironmentHttpUrl removed). - File attachments, thread references, in-chat find: preserved; fork server files converted to namespace node imports per the new oxlint rule. Identity: kept A2 Code branding + fork bundle/app IDs, adopted upstream t3code/t3code-dev protocol schemes; kept the fork CI/release build pipelines. Verified: vp run typecheck (all 15 packages), vp check (0 errors), and tests (shared 234, server 1252, web 1148, contracts 164, client-runtime 228; 0 fail). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The connection-architecture rewrite (pingdotgg#2978) reverted the command-palette change that let the "Open in File Manager" picker target a desktop-local secondary backend (the WSL backend). On current code the gate was back to primary-only and pickFolder no longer forwarded a target env, so a dev browsing the WSL env had no way to pick a Linux folder. Re-apply it against the new env model: - Allow the picker when the browsed env is desktop-local, detected via isDesktopLocalConnectionTarget on the connection target (the new replacement for the old desktopLocal marker). - Forward the desktop pool instance id (e.g. wsl:ubuntu) as targetEnvironmentId so the desktop dispatches the dialog into the WSL distro filesystem. The catalog environmentId is descriptor-derived and does not route; map it to the instance id via the bootstrap list by matching backend URL, the same join Sidebar already uses. The desktop converts the selected WSL UNC path back to a Linux path before returning, and handleAddProject already targets the browsed env, so the picked project lands on the WSL backend with no renderer-side path handling.
…d for rebase) Squash of the ft/hyperion stack (Jira provider + the upstream client-architecture rebase/port) for a clean rebase onto the newer upstream main. Per-commit history preserved on ft/hyperion-prerebase2-2026-06-20. Claude-Session: https://claude.ai/code/session_012bpxnLcbEhVHJ4pjamRpUW
Resolves conflicts between upstream's client connection architecture rewrite (pingdotgg#2978) and downstream features: - Tag catalog / sidebar tag filtering (#1): ported tag state into the new client-runtime atom architecture (shellReducer tag events, tagEntities/tagCommands modules, useTags hook) and rewrote the Sidebar/ChatView tag command dispatch from readEnvironmentApi to atom commands. - Custom editors in the Open In picker (#10): threaded customEditors through the rewritten ExternalLauncher service (HostProcessPlatform, typed launcher errors) and replaced the deleted rpc/serverState selectors with a primaryServerCustomEditorsAtom. - Re-applied prTemplate/username text-generation prompt params and GitVcsDriver test mocks onto upstream's restructured providers. - Accepted upstream deletions of store.ts/serverState.ts and moved fork test fixtures (tags: [], listAllTags/getTagById mocks) to the relocated test files. vp check, vp run typecheck, and the client-runtime/web/server test suites pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cture Upstream PR pingdotgg#2978 rewrote the client connection architecture, deleting the fork's WS/transport/state stack. Strategy: adopt upstream's architecture and re-port fork features on top (Track A/B follow-ups), preserving fork server-side work that lives in surviving files. Phase 0 resolutions: - 36 modify/delete: adopted upstream deletions/moves (fork client features deferred to re-port). - Transport cluster (ws.ts, server.ts, http.ts, server.test.ts): took upstream. Upstream subscribeThread is leak-free (no groupedWithin); shell-coalescing groupedWithin is stack-safe under the unioned effect stepToBuffer patch (justified lint-disable added). - Effect patch: unioned fork's OOM Stream.js fix + upstream's RPC ping/pong + MCP DELETE patches. - Preserved fork server features: OOM hub-bounding drain-pump (OrchestrationEngine.subscribeDomainEvents), checkpoint git-timeout resilience (GitVcsDriver/VcsStatusBroadcaster), provider recovery + turn-queuing (ClaudeAdapter), stall/bg-task watchdogs, window/null-turn bounds, migrations 33/34 (upstream renumbered to 35/36), localModels settings, disableAuthentication. - Deferred wire-format (rpcSerialization) removed; JSON-only transport. - Contracts unioned; manifests unioned (marked/web-push kept, +yaml, SDK 0.3.170). Integration fixes follow in the next commit. Not yet deploy-ready (Track A/B re-ports + OOM load-over-time verification pending). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017aHKjwZuipaALhwguxeRJe
Merges 341 upstream commits (branch point 2026-06-09) into Pallet's 69. 35 files conflicted; resolved keeping Pallet features on upstream's rewritten foundations. Notable adaptations: - Zustand store + environments/runtime + environmentApi were deleted upstream (pingdotgg#2978 "Rewrite client connection architecture"). Pallet's non-React flows (GitHub auth store, createSortlyQuick) now go through apps/web/src/lib/palletRuntime.ts, which adapts them to Effect atoms via runAtomCommand. Concentrated in one file so future merges conflict there rather than across four feature files. - Project.cwd -> workspaceRoot, Project.name -> title/displayName. - Desktop auth moved to the main process (pingdotgg#3092 Clerk bridge); cloud-auth IPC members dropped from preload + contracts to match upstream. - Sidebar: kept the Sortly Quick / Projects split; rebranded upstream's SidebarBrand to "Pallet" and removed T3Wordmark. - ChatHeader rebuilt on upstream's layout with QuickHeaderActions, isQuick gating and the Prototypes/Browser toggles re-mounted. - Restored imports silently dropped by auto-merge (useQueryClient, IpcChannels) — these produced no conflict markers. Typecheck passes across all 15 packages. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Summary
Replace the independent web and mobile connection implementations with a shared Effect-based client runtime.
@t3tools/client-runtimeScope
The native mobile composer, markdown renderer, Clerk sheet routing, turn-fold presentation, and Pierre icons were reviewed separately in #3101 and are now part of
main. This PR contains the client connection/runtime rearchitecture on top of that merged base.Validation
node scripts/release-smoke.tsvp checkvp run typecheckvp run lint:mobileNote
High Risk
Touches encrypted credential persistence, legacy migrations, and breaking desktop IPC channels while reshaping mobile connection lifecycle and cloud account transitions—errors in migration or catalog reads can strand users' environments.
Overview
Desktop drops the saved-environment registry IPC surface in favor of
get/set/clearconnection catalog handlers backed by a newDesktopConnectionCatalogStore: Electron safe-storage encryption, atomic writes, one-time migration from legacy relay/SSH/bearer records, and read errors for corrupt or undecryptable catalogs instead of silent empty state.DesktopSavedEnvironmentsnow propagatesReadErrorfor malformed registry JSON (used only for migration). Minor fixes: cloud auth header typing, SSH imports split to client-runtime subpaths.Mobile adds a full connection stack (
connectionPlatformLayer, secure catalog + legacy migration, shell/thread snapshot caches, pairing onboarding commands,environmentCatalogatoms) and rewires the app touseWorkspaceState,useConnectionController, anduseAtomCommandfor environments, pairing, settings, and favicons viauseAssetUrl. Cloud settings/environments UI gains status dots, switches, trace IDs, and serialized account cleanup on Clerk sign-in/out (activateCloudRelayAccount/deactivateCloudRelayAccount). Agent-awareness relay device registration is queued and coalesced; settings/async flows usesettleAsyncResult. Addsexpo-network, iOS modular CocoaPods for App Check deps, and EAS push prompt tweak.CI removes the dedicated Playwright browser test job from
ci.yml.Reviewed by Cursor Bugbot for commit 1551f69. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Replace client connection architecture with atom-based environment-scoped state
ConnectionRegistry,ConnectionDriver,ConnectionResolver,EnvironmentSupervisor) that manages environment lifecycle with retry/backoff, network awareness, and tracing, replacing the previous WebSocket RPC client approach.ConnectionCatalogDocument(targets, profiles, credentials) backed by IndexedDB on web, secure storage on mobile, and encrypted desktop storage, replacing the legacy saved environment registry and secrets APIs.useThreadOutboxDrain) that retries on transient failures.@t3tools/client-runtimenow requires explicit subpath imports (e.g.@t3tools/client-runtime/state/shell); the root export is removed and a lint rule enforces subpath usage.getConnectionCatalog,setConnectionCatalog, andclearConnectionCatalog.@t3tools/client-runtimeroot or calling removed APIs (syncProjects,syncThreads,connectSavedEnvironment,useRemoteEnvironmentBootstrap, etc.) will fail at compile or runtime.Macroscope summarized 1551f69.