Typing underscore emits Shift before Minus. Clearing the Meta prefix on that modifier event dropped M-_ after autocomplete dismiss.
Co-authored-by: Cursor <cursoragent@cursor.com>
Underscore is Shift+Minus, so rejecting all shiftKey follow-ups dropped M-_ after autocomplete dismissed.
Co-authored-by: Cursor <cursoragent@cursor.com>
Autocomplete was swallowing Escape, and macOS Option+. typed ≥ instead of Meta-., so the standard shell shortcut never reached bash/zsh.
Co-authored-by: Cursor <cursoragent@cursor.com>
#3026
An empty Ctrl+C copy binding kept the overlay up and still sent SIGINT.
The same happened for an unbound Ctrl/Cmd+A and for context-menu paste.
Keep the preview only when copy/select-all actually consume the chord,
and hide it before ordinary paste.
#3026
Dragging on the overlay can blur xterm, so native copy never hit the
container listener and the raw wrapped rows went to the clipboard.
Listen on document while the preview is open. Also keep a selected
hard newline at a row boundary instead of dropping it.
#3026
Select All built an element-boundary range, so wrap joining never ran
and copied extra newlines. Context-menu paste selection also left the
overlay up, unlike the keyboard path. Select the text node, accept
element-boundary offsets, and dismiss the preview before pasting.
#3026
The overlay had to keep pointer events so history could be copied, but
then every later click stayed on the preview. A click without a drag
now closes it and restores terminal focus. Copy also joins overlay
rows that xterm marked as soft-wrapped, so a long command does not
pick up extra newlines.
#3026
Urgent interrupt still asked only xterm for a selection. With history
preview text selected, Ctrl+C therefore sent SIGINT into vim before the
copy path ran. Use the same copyable-selection flag for interrupt,
shortcut passthrough, and Kitty broadcast.
#3026
Shift+scroll already overlays the normal-buffer history on the
alternate screen, but the overlay ignored pointer events and any
mousedown or copy chord dismissed it. Selection now stays on the
preview so the usual copy shortcuts, context menu, and copy-on-select
can take that text without leaving vi.
#2923
Copy Tab pins the source transport across preflight. If that shell
closes before the queued channel opens, pendingShellReconnectRisk is
already recorded and TERM-SSHD marks the conn non-reusable for shells.
Decide liveness and allowShellReuse at channel-open time so a pinned
captured source cannot accept an immediately exiting session.
#2923
Idle park is not the only last-shell state. An SFTP or forward lease
keeps the transport live and records pendingShellReconnectRisk; those
reconnects now run the same liveness settle. Known-bad banners also
refuse later shell reuse on that socket so start() dials fresh instead
of borrowing the dead session again.
Keep the liveness timer referenced so node --test cannot exit while
the settle is still pending.
#2923
QiZhi TERM-SSHD accepts a new session channel on a parked connection
and immediately exits 0, so reconnect after close flashes the tab
shut. 1.1.80 skipped PID discovery on that path but still reused the
parked transport.
Known-bad banners now end the transport instead of parking. Idle-park
reconnects on unknown banners wait for the new shell to stay open; an
immediate exit discards the transport, denylists the endpoint, and
falls back to a fresh dial. Copy Tab and live multiplex are unchanged.
When the keyboard shortcut scheme is off, snippet recording still
treated default Mac and PC bindings as occupied. Conflict checks now
use only bindings that can actually fire, and the error names the
conflicting action.
Closes#3025
Older AxonHub 0.9 and some intranet OpenAI-compat proxies emit
`data:{json}` instead of `data: {json}`. The stream parser required
the space, so chat chunks were dropped while the model-list probe
still succeeded.
Parse `data:` per the EventSource spec (optional single space) and
cover both the line and end-of-stream flush paths.
Closes#3020
- Add complete es message dictionaries (core, vault, terminal, ai,
systemManager, scripts) covering all en keys with matching placeholders
- Register 'es' in MESSAGES_BY_LOCALE and SUPPORTED_UI_LOCALES
- Extend locale test coverage to include es
A 300ms initial delay let ChatMessageList start the Streamdown import
while the user was still opening the panel to type. Restore the 4s
expand grace and drop a pending Shiki plugin setState if the message
unmounts before the idle callback lands.
Blocking hydrate on focus left history as raw markdown for as long
as the input was focused. Shiki still waits for a real idle gap.
Prose, lists, and inline code can mount after typing stops even if
the textarea still has focus.
messageResponse pulled @streamdown/code on first markdown import, so
opening a chat with history paid for the full highlighter even when
the reply was just prose.
Render with CJK only first. When a fence exists and the composer is
idle, lazily import the isolated Shiki plugin.
Typing a few characters then pausing still hitch because markdown
hydrate only checked the 800ms activity window. History rows would
mount Streamdown while the textarea still had focus.
Treat focused/composing as busy, wait 2s after the last key, and
keep idle IPC on the same predicate.
The first glyph still re-rendered the whole 1500-line ChatInput because
hasComposerText lived in the same component as the model picker. Token
usage snapshots also broke memo with a new object each time.
Move send-button emptiness onto a tiny external store so only that
control updates, and compare contextUsage by value in ChatInput's memo.
Clearing the box immediately after onSend overwrote the pending draft
while handleSend was still awaiting provider sync. A rejected sync or
stale-scope abort then left the prompt gone even though no user
message was created.
Leave the text in place until the parent clears the draft after the
send is accepted. Unmount commit still uses a ref so setter identity
changes cannot write the prompt back.
Typing still hitched because each IME commit started a 120ms
updateDraft, which re-ran the whole useAIState hook. Parent also
passed a new hosts array and providerSwitcher object on every
render, so ChatInput's memo never held. field-sizing-content
relayouted the textarea on every glyph.
Keep typed text in pending refs until send/hide/unmount. Memoize
mention hosts and the provider switcher. Use fixed field sizing
in the composer.
Send awaits provider sync before dropping the draft. During that
wait, ChatInput's onChange cleanup could fire on a new setter
identity and write the still-visible text back into the pending
draft, so the uncontrolled box kept the last prompt.
Clear the field right after onSend starts, and only commit on true
unmount via a ref so callback identity changes cannot restore it.
Codex P1: switching agent on a pristine composer called ensureDraft
then updateDraft. Missing-to-empty was treated as text-only, so the
store skipped notify and the panel kept the previous agent.
Keep first typed flush silent. Empty draft creation, including agent
switch, is a lifecycle change. Sibling empty drafts still do not
wake other panels.
New Chat wrote an empty composer-only draft, which the text-only
equality treated as identical to the previous typed draft. The panel
skipped re-render, so the uncontrolled textarea kept the old string
and send could still submit it.
Treat emptying draft text as a lifecycle change so listeners wake, and
commit the live box on ChatInput unmount so mid-IME hide still flushes.
Every keystroke was re-rendering ChatInput and the first character
created a store draft that broadcast AI state, so the first 1-2 glyphs
painted and the rest dumped after a stall. IME composition hit the
parent on every pinyin letter, and Chromium spellcheck loaded on the
first word.
Keep the textarea uncontrolled, commit parent state on idle/blur/send,
and treat missing-to-composer-only drafts as text-only so store
listeners do not wake. Draft flush, send, and hide still persist the
current box text.
The throughput test failed on CI because quiet catch-up sliced the
viewport into 32-line chunks. A 40-row terminal therefore called
refresh twice while catching up, which is not an atomic paint.
Recolor offscreen first, then refresh the visible range once after
catch-up finishes. Count post-settle compositor frames separately so
DOM's extra layout paint is not treated as a second catch-up render.
If the terminal or workspace goes away during provider/model preflight,
the awaited send must not create a session on a pruned scope. Bump a
send epoch on unmount or scope change and abort after the awaits.
Codex P2: Agent selector, New Chat, and history stayed clickable while
provider/model IPC ran. Switching agent or starting a new chat then let
the stale send finish and wipe the replacement draft. Disable those
header transitions until preflight ends, and abort if the view still
changed.
Codex P1s: isSending must end after sync so Codex App Server steering
works, and permission mode is frozen via lockTurnConfiguration during
preflight then read from a ref at launch. The first SDK send now uses
the catalog model from this same turn, not a stale curated fallback.
Codex: users could still attach files or change skills while provider
sync was in flight, and the first SDK turn could send a curated fallback
model. Disable the composer for the whole send, and load the runtime
model catalog before starting the turn.
Codex P1s: do not wipe text typed while provider sync is in flight, and
push web-search credentials before the first turn. Also resolve custom
Codex config on show instead of after the idle grace window.
Codex P2s: keep a stable pending draft identity so steer success can
clear the composer; take the send gate before awaiting provider sync;
and hydrate Streamdown after recent typing instead of waiting for blur.
Codex P1: first-launch send could race a 4s idle delay on
aiSyncProviders while main still had an empty provider list. Sync on
panel show and await it in handleSend.
Codex P2: slash-picker /stop and /compact called parent onChange('')
without updating local composerText, so the command stayed in the
textarea. Route those clears through commitComposerText.
Codex P2: focusing or typing cancelled the only scheduled callback, so
provider sync and discovery never ran if the user submitted without
blurring. Re-arm after activity and treat IPC as busy only while the
composer was recently used, not for the entire focused lifetime.
Review found three real gaps: send/new chat left pending composer text in
place, hiding the panel could leave body-portaled menus open, and idle IPC
could still start on the first frame after expand.
Discard pending text when the draft is cleared, close ChatInput and agent
menus when the panel is parked, and give composer-idle IPC the same expand
grace as Streamdown warmup.
Opening the AI panel and typing a few characters hit a main-thread stall.
Streamdown warmup, panel remount, draft-store fan-out, and expand-time
IPC all raced the first keystrokes. import() cannot be cancelled, so the
heavy renderer must not start while the composer is busy.
Keep the composer mounted on hidden retained panels, ignore composer-text
store notifications, debounce draft flushes, and defer discovery/skills/
model IPC until the composer is idle. History stays plaintext until
Streamdown is warm, then hydrates a few rows at a time.