fix(contracts): decode growing config unions forward-compatibly - #5055
Conversation
Nightly 0.0.32 added two keybinding commands and 0.0.31 clients rejected the whole serverGetConfig payload during connect, so they never reached the connected state. The root cause is that clients validate server-owned, growing literal unions (keybinding commands, config issue kinds, editor ids) as closed sets, and one unknown member fails the entire config decode and takes down the connection. Introduce ForwardCompatibleArray: a wire codec that drops array elements the current build cannot decode instead of failing the payload, and use it for resolved keybindings, server config issues, and available editors. Encoding is unchanged, authoring/upsert inputs stay strict, and all client dispatch is by string equality so dropped rules are inert. Servers can now grow these sets without per-client-version projections on the wire (the approach previously attempted in #5018). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
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 Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
ApprovabilityVerdict: Approved c0b2abc This is a defensive forward-compatibility fix that makes schema decoding more resilient by dropping unrecognized union variants instead of failing entirely. Changes are self-contained to schema definitions with comprehensive test coverage. You can customize Macroscope's approvability policy. Learn more. |
…dotgg#5055) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## What's Changed * fix(mobile): show the correct build channel in Android's Threads page header by @PixPMusic in pingdotgg/t3code#4861 * fix(contracts): decode growing config unions forward-compatibly by @juliusmarminge in pingdotgg/t3code#5055 **Full Changelog**: pingdotgg/t3code@v0.0.32-nightly.20260730.960...v0.0.32-nightly.20260731.961 Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.32-nightly.20260731.961
The official mobile app's config decoder predates upstream pingdotgg#5055 (forward-compatible config unions): one unknown keybinding command literal in the initial config fails the whole payload and the app drops the connection in a loop (connect -> green -> drop). Our fork adds commands (git.*, filePicker.toggle, projectSearch.toggle, thread/modelPicker jumps) that trigger exactly that. ws.ts now filters keybinding rules to the upstream-v0.0.31-safe command set (plus script.*.run) for sessions whose client device type is mobile, in both the initial config snapshot and live keybindings updates. Fork web/desktop clients keep the full set. Remove the filter once fixed mobile builds (post-pingdotgg#5055) are the norm. server: 1881 passed; typecheck 0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Brings in 30 upstream commits since abc409c, including the libghostty-vt web terminal (pingdotgg#4860), `npx t3 pair` (pingdotgg#4955), project favicon caching (pingdotgg#4767), and forward-compatible config union decoding (pingdotgg#5055). Three files needed manual resolution; the rest auto-merged. - modelOptions.test.ts / AssetAccess.test.ts: both sides added tests and imports at the same spot, so both sides are kept. - ThreadTerminalDrawer: upstream replaced xterm.js with the libghostty-vt surface, so the fork's terminal typography feature was reimplemented on the new API. Font family and size now flow through GhosttyTerminalSurface `font` options and `setFont`, which owns loading, cell remeasurement, refit, and re-render, replacing the manual FitAddon dance. The appearance default font stack ends in `monospace`, which would swallow the Nerd Font glyph fallbacks the renderer appends to any face it is given, so the default now defers to the renderer's own stack and only a genuinely custom face is forwarded. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
What Changed
ForwardCompatibleArrayto contracts: a wire codec for server→client arrays whose element unions grow over time. Decoding drops elements the current build cannot decode instead of failing the whole payload; encoding is the plain array encoding, byte-identical to before.ResolvedKeybindingsConfig), server config issues (ServerConfigIssues), andavailableEditors.KeybindingRule,ServerUpsertKeybindingInput) stay strict — clients only create bindings for commands they know.Why
Nightly
0.0.32-nightly.20260730.957addedfilePicker.toggle/projectSearch.toggle, and0.0.31clients rejected the entireserverGetConfigresponse during connect (the session'sreadygate awaitsinitialConfig, so a config decode failure means the client never reaches the connected state). The root cause is that clients validate server-owned, growing literal unions as closed sets.#5018 worked around this with per-connection command-set version negotiation and server-side filtering. That treats the symptom: every future command addition would require a version bump plus a new filter branch on the WS hot path, and the same break class remained for issue kinds, editor ids, and
when-node types. This PR fixes the class instead: clients tolerate unknown members by dropping them. This is safe because every dispatch site matches commands by string equality (an unknown command is already an inert no-op), and the settings UI renders any command string.Old servers are unaffected (encoded output is unchanged). Already-shipped 0.0.31 clients still need a new store build to pick this up — this intentionally replaces the OTA/negotiation hotpatch.
Verification
when-node types / malformed entries dropped, known rules preserved in order, encode round-trip unchanged; unknown issue kinds and editor ids dropped.readyand exposes only the known subset (this is the exact incident scenario).vp test run packages/contracts packages/client-runtime apps/server/src/server.test.ts apps/server/src/keybindings.test.ts packages/shared— all passing (1000+ tests)vp run --filter @t3tools/contracts --filter @t3tools/client-runtime --filter t3 --filter @t3tools/shared --filter web --filter mobile typecheck— cleanChecklist
🤖 Generated with Claude Code
Note
Medium Risk
Changes wire decoding for core connect-time
ServerConfigfields; behavior is intentionally permissive (drop unknowns) but affects every client session bootstrap path.Overview
Older clients were failing the entire
serverGetConfigdecode when a newer server sent unknown keybinding commands, issue kinds, or editor IDs—blocking the sessionreadygate. This PR introducesForwardCompatibleArray, which filters out array elements the current schema cannot decode instead of rejecting the whole payload; wire encoding stays unchanged.That codec is applied to
ResolvedKeybindingsConfig,ServerConfigIssues, andavailableEditorson server→client config paths. Client authoring inputs (KeybindingRule, upsert payloads) remain strict.Regression tests cover per-element dropping (unknown commands,
whennode types, malformed entries), encode round-trips, and a client-runtime session test where a mock newer server still reachesreadywith only the known subset.Reviewed by Cursor Bugbot for commit c0b2abc. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Decode growing config unions forward-compatibly by dropping unknown entries
ForwardCompatibleArrayinbaseSchemas.ts, which decodes arrays by silently dropping elements that fail to decode rather than failing the entire array.ForwardCompatibleArraytoResolvedKeybindingsConfig,ServerConfigIssues, andServerConfig.availableEditorsso that unknown commands, issue kinds, or editor IDs from a newer server are dropped instead of breaking the config decode.Macroscope summarized c0b2abc.