Skip to content

keybindings ux improvments - #90

Merged
juliusmarminge merged 1 commit into
mainfrom
codething/orchestration-engine
Feb 26, 2026
Merged

keybindings ux improvments#90
juliusmarminge merged 1 commit into
mainfrom
codething/orchestration-engine

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Feb 26, 2026

Copy link
Copy Markdown
Member

Note

Medium Risk
Adds filesystem watching and new WS payload fields for keybindings config validation issues; changes affect server startup/config loading and client push handling, with moderate risk of missed updates or noisy notifications across platforms.

Overview
Keybindings config loading is refactored to return a runtime KeybindingsConfigState that includes non-fatal validation issues (malformed JSON vs invalid entries), while still falling back to defaults and not overwriting a bad file.

The server now watches keybindings.json for changes, revalidates, and pushes server.configUpdated events over WebSocket; server.getConfig/server.upsertKeybinding responses and contracts are extended to include an issues array.

The web app subscribes to server.configUpdated, invalidates cached config queries, and shows success/warning toasts (with an action to open keybindings.json in the user’s editor). Tests are expanded to cover malformed/partial configs, issue reporting, and the new push channel + caching behavior.

Written by Cursor Bugbot for commit 51dbea2. This will update automatically on new commits. Configure here.

Note

Rework keybindings UX by adding Effect-based server config and WS schema, exposing Keybindings.upsertKeybindingRule over WebSocket, and wiring Settings UI to open keybindings.json

Refactor server to Effect services and schema-validated WebSocket protocol; add a server.configUpdated push and request/response schemas; implement a Keybindings service with atomic writes, issues reporting, and change stream; route serverGetConfig and serverUpsertKeybinding in apps/server/src/wsServer.ts; update web to acquire the API lazily, render settings showing the keybindings path, open the config via native editor, and validate project script keybindings; migrate terminal PTY selection and Node/Bun layers; replace Zod with Effect Schema across contracts and tests; introduce orchestration engine, projections, and snapshot syncing to drive UI state.

📍Where to Start

Start with the WebSocket server routing and pushes in apps/server/src/wsServer.ts, then review the Keybindings service in apps/server/src/keybindings.ts and the web settings integration in apps/web/src/routes/_chat.settings.tsx.

📊 Macroscope summarized 51dbea2. 5 files reviewed, 4 issues evaluated, 1 issue filtered, 0 comments posted

🗂️ Filtered Issues

apps/server/src/keybindings.ts — 0 comments posted, 2 evaluated, 1 filtered
  • line 812: The upsertKeybindingRule operation causes data loss by silently permanently deleting any existing invalid or malformed entries in the configuration file. The loadWritableCustomKeybindingsConfig function (used by upsertKeybindingRule) iterates over the raw configuration and filters out any entries that fail KeybindingRule or ResolvedKeybindingFromConfig validation, returning only the valid subset. upsertKeybindingRule then takes this filtered subset, adds the new rule, and overwrites the configuration file on disk via writeConfigAtomically. Consequently, if a user has a syntax error or an invalid property in one rule, adding a new rule via the application will wipe the invalid rule from the file instead of preserving it. [ Out of scope ]

Summary by CodeRabbit

Release Notes

New Features

  • Configuration validation now detects malformed keybindings and invalid entries, reporting issues with detailed messages
  • Real-time notifications alert you when keybindings are updated or contain problems
  • In-app toast messages display configuration issues with a quick-access button to edit the keybindings file

@coderabbitai

coderabbitai Bot commented Feb 26, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 79b8fd7 and 51dbea2.

📒 Files selected for processing (9)
  • apps/server/src/keybindings.test.ts
  • apps/server/src/keybindings.ts
  • apps/server/src/wsServer.test.ts
  • apps/server/src/wsServer.ts
  • apps/web/src/routes/__root.tsx
  • apps/web/src/wsNativeApi.test.ts
  • apps/web/src/wsNativeApi.ts
  • packages/contracts/src/server.ts
  • packages/contracts/src/ws.ts

Walkthrough

The changes introduce a keybindings configuration validation and state management system. The keybindings module now exposes loadConfigState, validates configurations at runtime, tracks issues as non-fatal state, emits changes via PubSub, and watches for file modifications. The WebSocket server extends its API to include an issues field and broadcasts config updates. The web client subscribes to these updates, deduplicates payloads, and displays notifications to users.

Changes

Cohort / File(s) Summary
Keybindings Configuration & Validation
apps/server/src/keybindings.ts, apps/server/src/keybindings.test.ts
Introduced keybindings state model (KeybindingsConfigState) with non-fatal issue tracking. Replaced loadResolvedKeybindingsConfig with loadConfigState. Added file watching, runtime validation with issue accumulation, and PubSub-based change notifications. Updated tests to verify malformed configs, invalid entries, and state transitions.
WebSocket Server API
apps/server/src/wsServer.ts, apps/server/src/wsServer.test.ts
Extended serverGetConfig and serverUpsertKeybinding responses to include issues field. Introduced broadcastPush helper to centralize push encoding and client transmission. Updated subscription wiring for serverConfigUpdated events. Added comprehensive tests for malformed configs, file watching, and config change notifications.
Web Client Config Handler
apps/web/src/routes/__root.tsx
Added onServerConfigUpdated subscription handler with payload deduplication using signature-based tracking. Displays success toast on valid configs and warning toast with keybindings.json editor action on detected issues. Invalidates server config queries on updates.
Web Native API
apps/web/src/wsNativeApi.ts, apps/web/src/wsNativeApi.test.ts
Introduced onServerConfigUpdated public API function with listener registration, cached payload replay for late subscribers, and error suppression. Added tests verifying caching, listener notification, and schema validation of incoming payloads.
Type Contracts & Schemas
packages/contracts/src/server.ts, packages/contracts/src/ws.ts
Added schemas for KeybindingsMalformedConfigIssue, KeybindingsInvalidEntryIssue, ServerConfigIssue, and ServerConfigUpdatedPayload. Extended ServerConfig and ServerUpsertKeybindingResult with issues field. Added serverConfigUpdated channel to WS_CHANNELS.

Sequence Diagram

sequenceDiagram
    participant FileSystem
    participant Server as Keybindings Module
    participant PubSub
    participant WSServer as WebSocket Server
    participant Client as Web Client
    participant UI as Toast Manager

    FileSystem->>Server: File change detected
    activate Server
    Server->>Server: Validate config<br/>(decode entries,<br/>accumulate issues)
    Server->>Server: Update cache<br/>(KeybindingsConfigState)
    Server->>PubSub: Emit change event<br/>(issues)
    deactivate Server

    PubSub->>WSServer: Notify subscribers
    activate WSServer
    WSServer->>WSServer: Create serverConfigUpdated<br/>push (issues payload)
    WSServer->>Client: Broadcast to all clients
    deactivate WSServer

    activate Client
    Client->>Client: Receive update
    Client->>Client: Compute signature<br/>from issues
    Client->>Client: Deduplicate<br/>(compare with last)
    alt Issues detected
        Client->>UI: Show warning toast<br/>(issue message +<br/>editor action)
    else No issues
        Client->>UI: Show success toast<br/>(config reloaded)
    end
    Client->>Client: Invalidate queries
    deactivate Client
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codething/orchestration-engine

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Feb 26, 2026

Copy link
Copy Markdown

Too many files changed for review. (265 files found, 100 file limit)

@juliusmarminge
juliusmarminge force-pushed the codething/orchestration-engine branch from 842321b to 51dbea2 Compare February 26, 2026 22:25
@juliusmarminge juliusmarminge changed the title keybindings keybindings ux improvments Feb 26, 2026
@juliusmarminge
juliusmarminge merged commit dc530f7 into main Feb 26, 2026
2 of 3 checks passed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Low src/wsServer.ts:113

When a multi-byte UTF-8 character is split across array chunks, decoding each chunk individually corrupts the data. Consider concatenating all buffers first with Buffer.concat(), then decoding to UTF-8 once.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/wsServer.ts around line 113:

When a multi-byte UTF-8 character is split across array chunks, decoding each chunk individually corrupts the data. Consider concatenating all buffers first with `Buffer.concat()`, then decoding to UTF-8 once.

Evidence trail:
apps/server/src/wsServer.ts lines 113-131 (commit REVIEWED_COMMIT): The `websocketRawToString` function handles `Array.isArray(raw)` by iterating over chunks and calling `Buffer.from(chunk).toString('utf8')` on each Uint8Array/ArrayBuffer individually (lines 121-127), then joining with `chunks.join('')` (line 130). This decodes each chunk separately before concatenation, which corrupts multi-byte UTF-8 characters split across chunk boundaries.

aorwall added a commit to aorwall/t3code that referenced this pull request Sep 2, 2026
Merges `upstream/main` (`d937e3075`, 62 commits from base `b17cc3d1b`)
into the fork. Conflicts resolved per the `fork-upstream-merge` skill;
governance docs updated and folded into the merge commit.

## What landed

- Landed `562` files (`HEAD^1..HEAD`) vs `560` in the upstream range —
gap of two: fork resolution edits (`features.ts`,
`moatless/listSearch.ts`) plus `docs/fork/gaps.md`, against
`apps/server/src/cli/pair.ts` (already deleted fork-side). Fork delta
`626` files.
- 14 textual conflicts resolved. Sweep clean (`duplicate-adds`,
`tripwires`); `unsupported-methods` ADD count `0`, only the standing
`scripts.run` DROP exception remains.

## Notable convergence

Upstream moved thread settlement server-side (dropped client
`effectiveSettled`, now renders `thread.settledOverride === "settled"`).
Moatless already owns settlement and never settles from PR state, so the
fork's `prThreadSettling` gate is now redundant — **retired** the flag,
its guard, its convergence entry, and the related gaps.md sections. The
only fork settlement delta kept is pin-before-settled ordering in the
sidebar.

## New upstream features — classification

**Usable as-is**
- HTML/PDF/media rendering in the file viewer, and opening
markdown/HTML/PDF files outside the workspace.
- `serverScoped` settings rows.
- Server-side thread settlement via `settledOverride` (Moatless already
computes this).

**Unsupported in Moatless**
- Desktop remote update system (`server.commitDesktopUpdate`) — declared
`UnsupportedMethodError` in `packages/contracts/src/rpc.ts`, referencing
gaps.md "Desktop and host lifecycle".

**Backend behavior to consider reproducing**
- None new — settlement is already implemented Moatless-side.

## Verification

`verify.mjs --fast` fmt/lint/typecheck clean. Tests pass: web `3348`,
desktop `689`, mobile `1082`, relay `209`, server `3125` (`10` skipped).
`unsupported-methods` exits 1 only for the documented `scripts.run`
exception.

Merge and resolution done by Claude Opus 4.8 via Claude Code, following
the project-local `fork-upstream-merge` skill.

---
Moatless task:
https://moatless.soaplabstest.com/tasks/ef3f3a62-fa94-4dc0-9bc3-3612a6a4a8de
aorwall added a commit to aorwall/t3code that referenced this pull request Sep 6, 2026
…ngdotgg#106)

The 2026-09-02 merge (pingdotgg#90) lost work twice to sandbox restarts.
Conflicted files came back with their `<<<<<<<` markers and were
obvious; files edited as collateral of resolving a conflict elsewhere
reverted in silence, and one was caught only because typecheck happened
to fail on an export it had removed.

That merge got lucky. Both files it lost were fork-only, and fork-only
files already have a detector — the inventory's `guard` entries are what
turned `features.ts` losing `FEATURES.prThreadSettling` into a failing
test. **Nothing covered the other half.** Had the restart reverted the
resolution in an upstream-owned file instead — `Sidebar.tsx`, `ws.ts`,
any of the fourteen that conflicted — no guard names it,
`merge-stats.mjs` only counts files and one file is lost in the noise,
and the merge is green.

## What this adds

**`resolution-check.mjs`** reads each candidate path against both merge
parents and reports where the result contradicts its verdict: a
`converged` path byte-identical to upstream has lost its delta; one
identical to the fork's pre-merge copy never took upstream's change. It
runs mid-merge as well as after, because before the commit exists a
finding is still a plain edit.

Calibrated against pingdotgg#90's own merge commit, which it passes clean.
Reverting a delta there produces:

```
XX apps/web/src/components/Sidebar.tsx landed byte-identical to upstream [mobile-touch-upstream-files, converged]
     Take upstream, then apply Mobile Touch Delta. […] all must survive.
     `// Fork:` markers 4 → 0.
```

Three scoping decisions, each of which would otherwise have shipped a
permanently-red check:

- Rule 3 covers `theirs-verbatim` only, never plain `theirs` —
`pnpm-lock.yaml` is the only `theirs` path and is meant to differ from
upstream forever.
- `decide` only warns, and only where the fork side carried a `// Fork:`
marker. `apps/web/src/browser/**` is six files the fork has never
touched; landing on upstream there is not a resolution.
- The marker census is reported, never enforced.
`apps/server/src/bin.ts` carries its whole delta as a *deletion*, so the
census reads `0 → 0` either way. A signal blind to half its failure
class does not get a vote.

It says nothing about fork-only (`ours`) paths and cannot — with no
upstream side there is no third reference. Guards cover those. Stated in
the file header.

**Exceptions as data.** `unsupported-methods.mjs` exited 1 on every
merge for `scripts.run`, whose derivation reads backwards because it is
upstream's own server that refuses it. Exceptions now live in
`unsupportedMethodExceptions` in `inventory.json`, each carrying the
condition that retires it, printed under `KNOWN EXCEPTIONS` and not
counted. The script also names an exception that has stopped firing, so
they get deleted rather than accumulate.

On unmodified `main` the check reports 4 DROPs; it now reports the 3
real ones. **Those three are genuine pre-existing backend drift**
(`server.getUsageSummary`, `subtasks.list`, `threads.getShell`) that the
permanent failure had been sitting next to. Left for a separate PR —
they need `rpc.ts` and `gaps.md` edits, which is a different concern.

**Procedure.** Commit the merge as soon as the markers are gone, then
`--amend` through the fix cycle: in this sandbox only committed history
survives a restart, so a gitignored state file is not crash insurance
and committing early is. Plus a restart-recovery checklist, never `git
add -A` during a merge (the restarts wiped a symlink and a submodule
gitlink, which `-A` stages as deletions the merge appears to have made),
and re-check `pnpm-lock.yaml` before amending.

**Delegation.** A documented section: what splits (convergence
validation, feature classification, gaps reconciliation), that conflicts
split **by concern, not by file** — pingdotgg#90's settings-search work spanned
five files and dropping a re-export in one broke another — and that
`verify.mjs` must not be delegated, since its packages already contend
for one sandbox's CPU. `preflight.mjs --json` emits the forecast grouped
by concern. Sonnet for these agents.

## Verification

`resolution-check.mjs` green on pingdotgg#90's merge commit; fails correctly when
a delta is reverted (rule 1a) and when an upstream change is dropped
(rule 2a); catches a silent revert in a real in-progress merge before
any commit exists, skipping the 14 unresolved paths. `preflight --json`
parses with empty stderr and keeps the staleness gate.
`features.test.ts` 16 passed. lint and typecheck clean.

Two `verify.mjs --fast` steps remain red, both pre-existing on `main`
and neither touched here: the three real DROPs above, and `fmt:check` on
`apps/web/src/fork/mermaidDiagram.ts` and
`docs/fork/upstream-merge-inventory.md` (both byte-identical to `main`).

Reviewed adversarially by a subagent, which found four real defects
since fixed: `--json` skipped the inventory staleness gate and always
exited 0; `report.failed` was used as a per-section gate so one failure
blanked out later sections' tallies; `--package` dropped OOM attribution
and would report a killed suite as a plain test failure; and
`autoMerged` repeated the conflict set when `merge-tree` cannot predict.

Claude Opus 4.8 via Claude Code.

---
Moatless task:
https://moatless.soaplabstest.com/tasks/ef3f3a62-fa94-4dc0-9bc3-3612a6a4a8de
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant