Skip to content

feat(web): edit text and move around the app with vim keys - #13

Draft
wSedlacek wants to merge 1 commit into
mainfrom
t3/check-vi-keybindings
Draft

wSedlacek wants to merge 1 commit into
mainfrom
t3/check-vi-keybindings

Conversation

@wSedlacek

Copy link
Copy Markdown

Reopened from closed upstream PR pingdotgg#31242 as a draft for t3.1code.
Idea rank: 12 of 108. Branch is as it was upstream; it likely needs a rebase (modernize) before review.

Problem

People who think in vim have to drop that habit the moment they write a prompt in T3 Code. There is no vim mode in the composer or any other text box, and moving around the app takes the mouse or a set of unrelated shortcuts.

Change

Adds an opt-in Settings → Chat → Vim mode for web and desktop. It is off by default, and the engine is lazy loaded, so nobody pays for it unless they turn it on.

  • Editing: the composer and every multi-line text box get motions, operators, text objects, visual mode, ., registers, marks, macros, / search, and :s. Instead of writing our own vim, this drives the @​replit/codemirror-vim-core engine (exact pin, small local patch) through two adapters: one for ProseMirror and one for textareas. Chips and rich formatting survive yanks and pastes, and each insert session is one undo step.
  • App navigation: outside a text box, j/k scroll, J/K change threads, : opens the command palette, g chords toggle panels, and i focuses the composer. Commands run through the user's configured shortcuts, so rebinding keeps working.
  • Left alone: Enter still sends, app shortcuts keep working, and single-line fields and the terminal are unchanged. Mobile is not included: it has no hardware-keyboard-first editor to attach this to.

The full key list is in docs/user/keybindings.md.

Vim mode demo with the pressed keys shown on screen

There are no before images because nothing changes with the setting off.

Scope and approval

There is no linked issue or discussion, and no maintainer has approved this direction or scope. This is a new feature, so it does not qualify for an exemption. It is opened as a proposal, and I understand it may be closed under the contribution policy until that approval exists.

Verification

  • vp run check:changed passes on the final commit, rebased on current main: format, lint, knip, typecheck, react compiler, and the tests beside the changes (2,442 passed).
  • 323 key sequences with expected results generated by real Vim 9.1 run against the composer.
  • The engine's own test suite runs against both the composer and textarea adapters. Cases that do not apply, such as % handling of code strings and comments, are listed by name in composer-vim.conformance.browser.test.ts.
  • Composer browser tests use the real extensions and cover lists, task items, chips, undo order, and sending.
  • In the real web app, I checked navigation, a textarea in a dialog, and composer editing. That pass ran before the last rebase.

After rebasing onto pingdotgg#30421, ** markers shown at the caret are editable text, so 0x on a bold word now removes a *. I updated the two affected tests to expect that. The rebase also needed one adapter change for the new splitOrLiftListItem argument. Both are covered by tests only, not by a new real-app pass.

Known differences from real Vim, found while checking review feedback: / and ? use JavaScript regex syntax (the engine's pcre default), so \+ is a literal plus. dit across lines does nothing. di( keeps no indented closing line, leaving () where Vim leaves (\n ).

Not tested: Electron, IME input, RTL text, Windows hardware, and in the real app the d, K, L, h, l, gb, gd, gf, gr keys and j/k in menus. Block visual mode is not supported.

Council review

Rounds: 7

Delegate R1 R2 R3 R4 R5 R6 R7
Claude Opus 5/10 6.5/10 6/10 6/10 7/10 6.5/10 9/10
GPT Sol 6/10 7/10 7.5/10 8/10 8/10 8/10 9.5/10
Gemini 6.5/10 7.5/10 9.5/10 6.5/10 no score 9.5/10 8.5/10

The council reviewed the composer only. It did not review the textarea or navigation work, or the last two composer fixes made after round 7. Gemini only reviewed and wrote no code. The fixes for the bot review findings came after the council rounds too.

Created with Claude Opus 5.5 in Claude Code.

🤖 Generated with Claude Code

🤖 Generated with Claude Code

@github-actions github-actions Bot added vouch:trusted Author is trusted by repo permissions or the VOUCHED list. size:XXL labels Nov 14, 2026
@github-actions

Copy link
Copy Markdown

This PR changes the test suite by +87 test cases (browser +87, unit 0, server 0).

New browser test files: apps/web/src/composer-vim.conformance.browser.test.ts, apps/web/src/composer-vim.reference.browser.test.ts, apps/web/src/vim-mode.browser.test.tsx.
Each browser test file costs CI seconds of module loading before its first test runs; a unit test costs milliseconds.

Keep tests that would fail for a real reason, and test logic below the UI where it can be. See Testing.

Reopened from pingdotgg#31242 and squashed onto current main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@wSedlacek
wSedlacek force-pushed the t3/check-vi-keybindings branch from 34b3656 to 2560c25 Compare November 14, 2026 22:35
@github-actions

Copy link
Copy Markdown

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 5.0 KiB 5.0 KiB 0 B (0.0%) 6.8 KiB ✅
Codex Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Codex Live turn WebSocket wire 1.2 KiB 1.2 KiB 0 B (0.0%) 2.0 KiB ✅
Codex Live turn WebSocket decoded 21.5 KiB 21.5 KiB 0 B (0.0%) 24.4 KiB ✅
Codex Live turn messages 1 1 0 (0.0%) 8 ✅
Claude Total thread wire 5.0 KiB 5.0 KiB 0 B (0.0%) 6.8 KiB ✅
Claude Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Claude Live turn WebSocket wire 1.2 KiB 1.2 KiB 0 B (0.0%) 2.0 KiB ✅
Claude Live turn WebSocket decoded 21.9 KiB 21.9 KiB 0 B (0.0%) 24.4 KiB ✅
Claude Live turn messages 1 1 0 (0.0%) 8 ✅

Other scenarios

All 64 scenario measurements match main.

All 64 scenario measurements
Provider Scenario Metric Main baseline This PR Impact PR ceiling
Codex short turn resume WebSocket decoded 10.5 KiB 10.5 KiB 0 B (0.0%) 12.7 KiB ✅
Codex short turn resume WebSocket messages 2 2 0 (0.0%) 2 ✅
Codex long turn running, cold open HTTP decoded 281.7 KiB 281.7 KiB 0 B (0.0%) 302.7 KiB ✅
Codex long turn running, cold open rows 75 75 0 (0.0%) 75 ✅
Codex long turn running, history page HTTP decoded 92.4 KiB 92.4 KiB 0 B (0.0%) 107.4 KiB ✅
Codex long turn running, history page rows 75 75 0 (0.0%) 75 ✅
Codex long turn finished, cold open HTTP decoded 381.5 KiB 381.5 KiB 0 B (0.0%) 410.2 KiB ✅
Codex long turn finished, cold open rows 151 151 0 (0.0%) 151 ✅
Codex long turn finished, socket cold open WebSocket decoded 381.6 KiB 381.6 KiB 0 B (0.0%) 410.2 KiB ✅
Codex long turn finished, socket cold open WebSocket messages 2 2 0 (0.0%) 2 ✅
Codex long turn finished, socket cold open rows 151 151 0 (0.0%) 151 ✅
Codex long turn finished, work page HTTP decoded 186.2 KiB 186.2 KiB 0 B (0.0%) 200.2 KiB ✅
Codex long turn finished, work page rows 150 150 0 (0.0%) 150 ✅
Codex idle thread resume WebSocket decoded 65 B 65 B 0 B (0.0%) 200 B ✅
Codex idle thread resume WebSocket messages 1 1 0 (0.0%) 1 ✅
Codex fleet shell snapshot HTTP decoded 42.4 KiB 42.4 KiB 0 B (0.0%) 53.7 KiB ✅
Codex fleet shell snapshot rows 27 27 0 (0.0%) 27 ✅
Codex fleet shell subscribe WebSocket decoded 65 B 65 B 0 B (0.0%) 200 B ✅
Codex fleet shell subscribe WebSocket messages 1 1 0 (0.0%) 1 ✅
Codex fleet shell resume check HTTP decoded 18 B 18 B 0 B (0.0%) 100 B ✅
Codex fleet shell resume check rows 0 0 0 0 ✅
Codex stale shell reload HTTP decoded 42.4 KiB 42.4 KiB 0 B (0.0%) 53.7 KiB ✅
Codex stale shell reload rows 27 27 0 (0.0%) 27 ✅
Codex stale shell resume WebSocket decoded 140 B 140 B 0 B (0.0%) 400 B ✅
Codex stale shell resume WebSocket messages 2 2 0 (0.0%) 2 ✅
Codex stale todo list cold open HTTP decoded 72.4 KiB 72.4 KiB 0 B (0.0%) 83.0 KiB ✅
Codex stale todo list cold open rows 75 75 0 (0.0%) 75 ✅
Codex stale todo list cold open nodes 2 2 0 (0.0%) 4 ✅
Codex stale todo list cold open subagents 2 2 0 (0.0%) 4 ✅
Codex stale todo list cold open plans 1 1 0 (0.0%) 2 ✅
Codex stale todo list cold open providerTurns 1 1 0 (0.0%) 2 ✅
Codex stale todo list cold open checkpoints 1 1 0 (0.0%) 2 ✅
Claude short turn resume WebSocket decoded 10.6 KiB 10.6 KiB 0 B (0.0%) 12.7 KiB ✅
Claude short turn resume WebSocket messages 2 2 0 (0.0%) 2 ✅
Claude long turn running, cold open HTTP decoded 283.3 KiB 283.3 KiB 0 B (0.0%) 302.7 KiB ✅
Claude long turn running, cold open rows 75 75 0 (0.0%) 75 ✅
Claude long turn running, history page HTTP decoded 93.4 KiB 93.4 KiB 0 B (0.0%) 107.4 KiB ✅
Claude long turn running, history page rows 75 75 0 (0.0%) 75 ✅
Claude long turn finished, cold open HTTP decoded 384.2 KiB 384.2 KiB 0 B (0.0%) 410.2 KiB ✅
Claude long turn finished, cold open rows 151 151 0 (0.0%) 151 ✅
Claude long turn finished, socket cold open WebSocket decoded 384.3 KiB 384.3 KiB 0 B (0.0%) 410.2 KiB ✅
Claude long turn finished, socket cold open WebSocket messages 2 2 0 (0.0%) 2 ✅
Claude long turn finished, socket cold open rows 151 151 0 (0.0%) 151 ✅
Claude long turn finished, work page HTTP decoded 188.1 KiB 188.1 KiB 0 B (0.0%) 200.2 KiB ✅
Claude long turn finished, work page rows 150 150 0 (0.0%) 150 ✅
Claude idle thread resume WebSocket decoded 66 B 66 B 0 B (0.0%) 200 B ✅
Claude idle thread resume WebSocket messages 1 1 0 (0.0%) 1 ✅
Claude fleet shell snapshot HTTP decoded 43.0 KiB 43.0 KiB 0 B (0.0%) 53.7 KiB ✅
Claude fleet shell snapshot rows 27 27 0 (0.0%) 27 ✅
Claude fleet shell subscribe WebSocket decoded 66 B 66 B 0 B (0.0%) 200 B ✅
Claude fleet shell subscribe WebSocket messages 1 1 0 (0.0%) 1 ✅
Claude fleet shell resume check HTTP decoded 18 B 18 B 0 B (0.0%) 100 B ✅
Claude fleet shell resume check rows 0 0 0 0 ✅
Claude stale shell reload HTTP decoded 43.0 KiB 43.0 KiB 0 B (0.0%) 53.7 KiB ✅
Claude stale shell reload rows 27 27 0 (0.0%) 27 ✅
Claude stale shell resume WebSocket decoded 140 B 140 B 0 B (0.0%) 400 B ✅
Claude stale shell resume WebSocket messages 2 2 0 (0.0%) 2 ✅
Claude stale todo list cold open HTTP decoded 73.4 KiB 73.4 KiB 0 B (0.0%) 83.0 KiB ✅
Claude stale todo list cold open rows 75 75 0 (0.0%) 75 ✅
Claude stale todo list cold open nodes 2 2 0 (0.0%) 4 ✅
Claude stale todo list cold open subagents 2 2 0 (0.0%) 4 ✅
Claude stale todo list cold open plans 1 1 0 (0.0%) 2 ✅
Claude stale todo list cold open providerTurns 1 1 0 (0.0%) 2 ✅
Claude stale todo list cold open checkpoints 1 1 0 (0.0%) 2 ✅

Baseline: 7fd5a75 · PR result: 2560c25 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL vouch:trusted Author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant