Fix framebuffer console: idle heartbeat corrupts in-progress input line

sk_repl_idle() (called every ~1s from sk_console_readline()'s idle loop)
opens with console_ensure_line_start(), which unconditionally forces a
newline whenever the console isn't at a line boundary -- including
mid-edit, after characters have been typed and echoed but before Enter.
This fired on every elapsed SK_IDLE_BEAT_INTERVAL regardless of whether
sk_repl_idle() had anything to print, visually snapping the in-progress
input line to a fresh empty line -- indistinguishable from Enter having
been pressed. Most noticeable on the space key since it's the most common
key hit during a pause.

Gate the idle beat on n == 0 (no in-progress edit), mirroring the n > 0
guard the prompt reanchor logic just below already uses. Deferring the
xhci/block-sync idle service by at most one more interval while a line
is being edited is within its own documented "coarse cadence, cheap
early-exit" tolerance.

Verified: reproduced via QMP send-key against a live amd64 QEMU boot
(multi-character line typed with pauses across several idle intervals
stayed intact after the fix, where it previously broke on each interval).
Re-verified boot to ok> on all three architectures (amd64/aarch64/riscv64)
per repo acceptance policy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-09-02 17:12:21 -04:00
co-authored by Claude Opus 5
parent daf9021ea5
commit c14324f498
13 changed files with 45991 additions and 3 deletions
+13 -1
View File
@@ -548,7 +548,19 @@ int sk_console_readline(char* buf, int size, VM* active_vm, int reanchor_prompt)
heartbeat_service();
uint64_t now = heartbeat_ticks();
if (now - g_last_beat_tick >= SK_IDLE_BEAT_INTERVAL) {
/* n == 0 gate: sk_repl_idle() opens with console_ensure_line_start(),
* which unconditionally forces a newline whenever the console isn't
* already at a line boundary -- including mid-edit, with characters
* already typed and echoed (n > 0) but Enter not yet pressed. Without
* this gate, that forced break fires on every elapsed
* SK_IDLE_BEAT_INTERVAL regardless of whether sk_repl_idle() actually
* has anything to print, visually snapping the in-progress input line
* to a fresh (empty) line -- indistinguishable from Enter having been
* pressed. Deferring the whole idle beat while a line is being edited
* (same n > 0 guard the prompt reanchor below already uses) delays
* xhci/block-sync servicing by at most one more interval, which its
* own "coarse cadence, cheap early-exit" design already tolerates. */
if (n == 0 && now - g_last_beat_tick >= SK_IDLE_BEAT_INTERVAL) {
g_last_beat_tick = now;
sk_repl_idle(active_vm);
}