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
+1 -2
View File
@@ -1,6 +1,5 @@
# Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-08-30T08:47:40Z -->
<!-- Generated by mkcapsule --manifest 2026-09-02T21:10:42Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. -->
BIN
View File
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+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);
}