@@ -106,28 +106,6 @@ constexpr std::size_t kLiveTailLines = 64;
106106 return std::string{s.substr (start)};
107107}
108108
109- // First `keep_lines` lines of `s`, head-anchored. Unlike tail_window,
110- // this is APPEND-ONLY as `s` grows: line 1..keep_lines never change
111- // content when more lines arrive at the end, they just stop being the
112- // last thing rendered. Used for the streaming WRITE preview so the
113- // rows it commits to native scrollback are byte-identical to the
114- // settled show_all render's first rows — a tail-anchored preview
115- // instead SHIFTS its top rows as the file grows, so when those rows
116- // have already overflowed into scrollback and the card later settles to
117- // a head-anchored full body, the committed rows no longer match and the
118- // card is re-emitted below them (the duplicated-write ghost).
119- [[nodiscard]] std::string head_window (std::string_view s,
120- std::size_t keep_lines) {
121- if (s.empty ()) return {};
122- std::size_t nl_seen = 0 ;
123- for (std::size_t i = 0 ; i < s.size (); ++i) {
124- if (s[i] == ' \n ' ) {
125- if (++nl_seen >= keep_lines) return std::string{s.substr (0 , i + 1 )};
126- }
127- }
128- return std::string{s};
129- }
130-
131109// Body for a terminal line-oriented tool sitting in the LIVE tail.
132110// Frozen builds keep the full body (full content, painted once); the
133111// live path elides to a bounded tail window so per-frame split_lines
@@ -322,40 +300,36 @@ maya::ToolBodyPreview::Config tool_body_preview_config(
322300 out.kind = Kind::FileWrite;
323301 out.text_color = text_tertiary;
324302 out.show_footer_stats = true ;
325- // While streaming, the body grows every ~120 ms. The PRIOR
326- // approach pinned the preview to a TAIL window (last N lines):
327- // as the file grew, the top rows of that window kept changing
328- // content. Once they overflowed into native scrollback they
329- // were frozen there — but on settle the card switches to a
330- // head-anchored show_all render from line 1, so the committed
331- // rows no longer matched and the whole card was re-emitted
332- // below them: the write appeared TWICE in scrollback.
333- //
334- // Fix: render the streaming preview HEAD-anchored with
335- // show_all over a bounded head window. The first N lines never
336- // change as more arrive (append-only growth), and they are
337- // byte-identical to the settled show_all render's first N
338- // lines — so anything that commits to scrollback mid-stream
339- // stays valid through the freeze handoff. No row is ever
340- // rewritten; the card only grows downward.
303+ // While streaming, show a SMALL tail preview (the last few
304+ // lines of what's been written so far) — show_all=false lets
305+ // maya elide to its `code_tail` budget, the compact "watch it
306+ // write" look. The duplicated-write ghost came from feeding a
307+ // LARGE tail slice (64 lines): once those rows overflowed into
308+ // native scrollback they were frozen, but on settle the card
309+ // switched to a head-anchored show_all render from line 1, so
310+ // the committed rows no longer matched and the card was
311+ // re-emitted below them (two copies). Keeping the streaming
312+ // preview SMALL (a slice barely above maya's tail budget)
313+ // keeps it inside the live viewport so it never commits to
314+ // scrollback — only the settled show_all render reaches
315+ // scrollback, and it's painted once.
316+ out.show_all = !streaming_now;
341317 out.is_streaming = streaming_now;
342318 if (streaming_now) {
343- // Head window, full render: append-only and seam-stable.
344- // O(window) per frame (the slice bounds split_lines).
345- // Footer derives line/byte totals from out.text, which is
346- // a partial head here, so suppress it mid-stream (the
347- // status bar carries the live byte/tok rate); it returns
348- // with the true total the instant the tool settles.
349- out.text = head_window (content, kStreamTailLines );
350- out.show_all = true ;
319+ // Small tail slice → O(window) per frame and a fixed
320+ // compact card height. show_all=false above makes maya
321+ // render just its `code_tail` lines from this slice.
322+ // Footer totals would be wrong on a partial body, so
323+ // suppress mid-stream (status bar carries the live rate);
324+ // it returns with the true total the instant we settle.
325+ out.text = tail_window (content, kStreamTailLines );
351326 out.show_footer_stats = false ;
352327 } else {
353- // Terminal: full body, head-anchored, in BOTH the live
354- // tail and the frozen snapshot. Identical bytes / anchor /
355- // height across the freeze instant, and a pure superset of
356- // the streaming head window above — so the handoff never
357- // moves a committed row.
358- out.show_all = true ;
328+ // Terminal: full body, show_all, in BOTH the live tail and
329+ // the frozen snapshot — identical bytes/height across the
330+ // freeze instant. This is the only write render that ever
331+ // commits to native scrollback, so there is nothing above
332+ // it to disagree with.
359333 out.text = std::move (content);
360334 }
361335 } else if (tc.is_running ()) {
0 commit comments