fix: eliminate 3 migration pitfalls in xterm-zerolag-input

Pitfall 3 (REAL BUG): setFlushed() called _render() unconditionally.
During tab switch, rendering against a stale buffer found the old
session's prompt and locked the column position via the flushed-text
column lock. After the new buffer loaded, rerender() kept the stale
column. Fix: add render parameter (default true), pass false during
tab-switch restore to defer rendering until buffer is loaded.

Pitfall 2: Tab completion "undo" required calling clearFlushed() +
resetBufferDetection() separately — easy to get wrong. Add
undoDetection() convenience method that atomically clears flushed
state and re-enables detection.

Pitfall 1: Backspace Map sync is app-specific (can't be fixed in lib),
but removeChar() return value + getFlushed() make the pattern clean.
Updated tab-switch test to demonstrate the correct pattern.

Tests: 78 total (+4 new)
- setFlushed render=false prevents stale column lock
- setFlushed render=false keeps overlay hidden
- undoDetection clears flushed + re-enables detection
- undoDetection preserves pending text

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
arkon
2026-02-22 11:16:12 +01:00
co-authored by Claude Opus 4.6
parent 36f0772c2f
commit 8dfcfe8d62
3 changed files with 82 additions and 8 deletions
+8 -5
View File
@@ -153,7 +153,7 @@ Step 3 handles tab completion and arrow-key edits: if the user tabs to complete
| Method | Description |
|--------|-------------|
| `setFlushed(count, text)` | Mark characters as sent-but-unacknowledged. Triggers a render. |
| `setFlushed(count, text, render?)` | Mark characters as sent-but-unacknowledged. Pass `render=false` when restoring during a tab switch before the new buffer has loaded (prevents stale prompt column locking). Default: `true`. |
| `getFlushed()` | Returns `{ count: number, text: string }`. |
| `clearFlushed()` | Clear flushed state (call when server echo has arrived). |
@@ -166,6 +166,7 @@ The overlay can scan the terminal buffer for text that already exists after the
| `detectBufferText()` | Scan buffer for text after prompt. Returns the detected text string, or `null`. If found, sets it as flushed text. Guarded: only runs once per `clear()` cycle. |
| `resetBufferDetection()` | Re-enable detection (e.g., after tab completion response arrives). |
| `suppressBufferDetection()` | Prevent detection until next `clear()`. Use when switching to a session whose buffer has UI framework text (e.g., Ink status bars) after the prompt marker that would be falsely detected. |
| `undoDetection()` | Undo the last `detectBufferText()` — clears flushed state and re-enables detection. Use when tab completion detection found text matching the pre-tab baseline (no real completion happened) and needs to retry. |
#### Rendering
@@ -251,12 +252,10 @@ function switchToSession(newSessionId: string) {
loadSessionBuffer(newSessionId);
// 3. Restore overlay state for new session
zerolag.suppressBufferDetection(); // prevent false detection of UI text
const saved = savedFlushed.get(newSessionId);
if (saved) {
zerolag.suppressBufferDetection(); // prevent false detection of UI text
zerolag.setFlushed(saved.count, saved.text);
} else {
zerolag.suppressBufferDetection(); // fresh session, no flushed state
zerolag.setFlushed(saved.count, saved.text, false); // render=false: buffer not loaded yet
}
// 4. Re-render after buffer loads
@@ -281,6 +280,10 @@ zerolag.resetBufferDetection();
const detected = zerolag.detectBufferText();
if (detected && detected !== baseline) {
// Tab completion occurred — overlay now shows the completed text
zerolag.rerender();
} else if (detected) {
// Same text as before Tab — no real completion. Undo and retry next cycle.
zerolag.undoDetection();
}
```