const std = @import("std"); const display_width = @import("../../core/shared/display_width.zig"); const io_mod = @import("../../core/shared/io.zig"); const types = @import("../../core/shared/types.zig"); const command_output_content = @import("../../core/tooling/command_output_content.zig"); const full_transcript_screen = @import("../full_transcript_screen.zig"); const input_visual_layout = @import("../input/visual_layout.zig"); const render_engine = @import("../render_engine.zig"); const command_output_runtime = @import("command_output_runtime.zig"); const transcript_painter = @import("painter.zig"); const transcript_runtime = @import("runtime.zig"); const transcript_store = @import("store.zig"); const transcript_writer = @import("writer.zig"); const ui_render = @import("../render.zig"); const user_message_card = @import("../assistant/user_message_card.zig"); const vt_emulator = @import("../../core/terminal/engine.zig"); const Allocator = std.mem.Allocator; const Layout = types.Layout; const Metrics = types.Metrics; const transcript_blocks = render_engine.transcript_blocks; const viewport_selection = render_engine.viewport_selection; const RawEntryClass = transcript_blocks.RawEntryClass; const Styles = transcript_blocks.Styles; const ToolDetailRecord = transcript_blocks.ToolDetailRecord; const TranscriptPreparationSource = transcript_runtime.TranscriptPreparationSource; const TranscriptRuntime = transcript_runtime.TranscriptRuntime; const HardLineStarts = viewport_selection.HardLineStarts; const TranscriptBuffer = viewport_selection.TranscriptBuffer; const VisibleTranscriptLine = viewport_selection.VisibleTranscriptLine; fn renderEntriesToBytes(alloc: Allocator, entries: []const transcript_blocks.TranscriptEntry, cols: u16) ![]u8 { return transcript_runtime.renderEntriesToBytes(alloc, entries, cols, .{}); } const visualRowsForLine = transcript_blocks.visualRowsForLine; fn resolveAndSealTranscriptTransitionForTest( runtime: *TranscriptRuntime, alloc: Allocator, source: *TranscriptPreparationSource, prepared: *transcript_painter.PreparedTranscriptSurfacePaint, target_layout: render_engine.frame_layout.CommittedLayoutSnapshot, plan: *render_engine.paint_plan.PaintPlan, scroll_plan: render_engine.frame_scroll_plan.FrameScrollPlan, ) !transcript_runtime.TranscriptTransition { const scroll_facts = try runtime.prepareTranscriptScrollFactsForFrame( alloc, source, prepared, false, false, ); const destructive_invalidation = render_engine.frame_retention.transcriptAreaHasDestructiveInvalidation( runtime.committed_frame_layout.transcript_area, plan.invalidation, ); const resolved = try runtime.resolveTranscriptTransitionTargetForFrame( alloc, source, prepared, target_layout, scroll_plan, scroll_facts, destructive_invalidation, plan.activity == .overlay_entry, ); resolved.applyToPaintPlan(plan); return runtime.sealTranscriptTransition( alloc, source, prepared, plan, resolved, ); } fn resolveVisibleLineForTest(line: VisibleTranscriptLine, buf: TranscriptBuffer) []const u8 { return switch (line) { .transcript => |t| t.ref.resolve(buf), .folded_line => |f| f.text, }; } fn commandOutputTestRuntime(sink: std.Io.File) TranscriptRuntime { return .{ .stdout_file = sink, .layout = .{ .rows = 24, .cols = 80, .content_bottom = 21, .divider_top_row = 22, .input_row = 23, .divider_bottom_row = 24, .hint_row = 22, }, }; } fn testSelection(start_line: usize) viewport_selection.ViewportSelection { return .{ .top_row = 1, .bottom_row = 4, .start_line = start_line, .partial_skip_rows = 0, .line_count = 16, }; } fn transcriptTestLayout(cols: u16, rows: u16, content_bottom: u16) Layout { return .{ .rows = rows, .cols = cols, .content_bottom = content_bottom, .divider_top_row = content_bottom + 1, .input_row = content_bottom + 2, .divider_bottom_row = content_bottom + 3, .hint_row = rows, }; } fn makeLongPastePrompt(alloc: Allocator, line_count: usize) ![]u8 { var out: std.Io.Writer.Allocating = .init(alloc); defer out.deinit(); try out.writer.writeAll("LONG_PASTE_FIRST_MARKER\n"); var index: usize = 0; while (index < line_count) : (index += 1) { try out.writer.print("long-paste-line-{d}\n", .{index}); } try out.writer.writeAll("LONG_PASTE_LAST_MARKER\n"); return out.toOwnedSlice(); } fn installStableAnchorWithOriginForTest( runtime: *TranscriptRuntime, alloc: Allocator, flow: []const u8, selection: viewport_selection.ViewportSelection, visual_offset: u32, cursor_row: u16, cursor_col: u16, layout_id: u64, measured_history_origin: ?transcript_painter.MeasuredHistoryOrigin, ) !void { var target_cache: std.ArrayList(u8) = .empty; errdefer target_cache.deinit(alloc); try target_cache.appendSlice(alloc, flow); var transition = transcript_runtime.TranscriptTransition{ .scroll_plan = render_engine.frame_scroll_plan.FrameScrollPlan.none( runtime.layout.rows, runtime.owned_top_row, ), .document_append = .{}, .materialized_without_append = .{}, .target_flow = try alloc.dupe(u8, flow), .target_cache = target_cache, .target_cache_origin_untrimmed = runtime.transcript_cache_origin_untrimmed, .folded_summary_indices = &.{}, .target_layout = .{ .layout_id = layout_id, .owned_top = runtime.owned_top_row, .owned_band = .{ .top = runtime.owned_top_row, .bottom = runtime.layout.rows }, .body_area = .{ .top = runtime.owned_top_row, .bottom = runtime.layout.content_bottom }, .transcript_area = .{ .top = runtime.owned_top_row, .bottom = runtime.layout.content_bottom }, .footer_area = .{ .top = runtime.layout.content_bottom + 1, .bottom = runtime.layout.rows }, .solved_frame_height = runtime.layout.rows - runtime.owned_top_row + 1, .terminal_rows = runtime.layout.rows, .terminal_cols = runtime.layout.cols, }, .selection = selection, .visual_offset = visual_offset, .history_visual_offset = visual_offset, .total_visual_rows = visual_offset + 64, .cursor_row = cursor_row, .cursor_col = cursor_col, .replaceable_last_line = false, .replaceable_start = 0, .replaceable_row = cursor_row, .trace_transcript_frame = false, .trace_visible_rows = selection.bottom_row - selection.top_row + 1, .measured_history_origin = measured_history_origin, }; defer transition.deinit(alloc); runtime.consumeTranscriptTransition(alloc, &transition, .{ .bytes_written = 0, .changed_cells = 0, .full_repaint = false, .terminal_scroll_rows_committed = 0, .document_append_committed = false, .committed_cursor_row = cursor_row, .committed_cursor_col = cursor_col, .shadow_state = .committed, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); } fn installStableAnchorForTest( runtime: *TranscriptRuntime, alloc: Allocator, flow: []const u8, selection: viewport_selection.ViewportSelection, visual_offset: u32, cursor_row: u16, cursor_col: u16, layout_id: u64, ) !void { return installStableAnchorWithOriginForTest( runtime, alloc, flow, selection, visual_offset, cursor_row, cursor_col, layout_id, null, ); } fn stableHistoryVisualOffsetForTest(runtime: *const TranscriptRuntime) !u32 { return switch (runtime.transcript_commit_state) { .stable => |anchor| anchor.history_visual_offset, .invalid, .recovering => error.TestExpectedStableTranscript, }; } fn expectStableNormalBufferRecoveryForTest( runtime: *const TranscriptRuntime, expected_history_visual_offset: u32, ) !void { switch (runtime.transcript_commit_state) { .stable => |anchor| { try std.testing.expect(anchor.normal_buffer_recovery_pending); try std.testing.expectEqual( expected_history_visual_offset, anchor.history_visual_offset, ); }, .invalid, .recovering => return error.TestExpectedStableTranscript, } } fn testPaintPlan( runtime: *const TranscriptRuntime, selection: viewport_selection.ViewportSelection, ) render_engine.paint_plan.PaintPlan { const footer_top = runtime.layout.content_bottom + 1; return .{ .layout = runtime.layout, .viewport = selection, .footer = .{ .top = footer_top, .top_divider = footer_top, .banner = footer_top, .banner_active = false, .input_base = @min(footer_top + 1, runtime.layout.rows), .picker_divider = @min(footer_top + 2, runtime.layout.rows), .picker_start = runtime.layout.rows, .bottom_divider = @min(footer_top + 2, runtime.layout.rows), .hint = runtime.layout.rows, .total_rows = runtime.layout.rows - footer_top + 1, }, .activity = .none, .preserved_band = if (runtime.owned_top_row > 1) .{ .top = 1, .bottom = runtime.owned_top_row - 1, .owner = .preserved_shell } else render_engine.paint_plan.FrameBand.empty(.preserved_shell), .transcript_band = .{ .top = runtime.owned_top_row, .bottom = runtime.layout.content_bottom, .owner = .transcript, }, .activity_band = render_engine.paint_plan.FrameBand.empty(.activity), .footer_band = .{ .top = footer_top, .bottom = runtime.layout.rows, .owner = .footer, }, .invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), .footer_clean_allowed = true, .synchronized_update = true, .cursor_target = .{ .row = selection.bottom_row, .col = 1, .visible = true }, .bottom_reserved_rows = 0, .preserve_scrollback = true, }; } const TestTranscriptFixedPointFrame = struct { layout: render_engine.frame_layout.FrameLayout, scroll_plan: render_engine.frame_scroll_plan.FrameScrollPlan, }; fn solveTestTranscriptFixedPointFrame( runtime: *TranscriptRuntime, alloc: Allocator, metrics: *Metrics, source: *const TranscriptPreparationSource, footer_rows: u16, prepared: *?transcript_painter.PreparedTranscriptSurfacePaint, ) !TestTranscriptFixedPointFrame { var release_floor_rows: u16 = 0; var previous_candidate_owned_top: ?u16 = null; while (true) { const release = render_engine.frame_layout.solveWithPreservedRowRelease(.{ .terminal = runtime.layout, .owned_top = runtime.owned_top_row, .footer = .{ .natural_rows = footer_rows, .min_rows = footer_rows, .max_rows = footer_rows, }, .transcript = source.preview, .prior = runtime.committed_frame_layout, }, release_floor_rows); const candidate = release.layout; if (previous_candidate_owned_top) |previous| { if (candidate.owned_top >= previous) return error.InvalidFrameScrollPlan; } if (prepared.*) |*paint| { paint.deinit(alloc); prepared.* = null; } var inline_advance_rows: u16 = 0; if (!candidate.transcript_area.isEmpty()) { prepared.* = try runtime.prepareTranscriptSurfacePaintFromSourceForArea( alloc, metrics, source, candidate.transcript_area, ); inline_advance_rows = runtime.planTranscriptScroll(&prepared.*.?).planned_rows; } const scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, release.released_rows, inline_advance_rows, ); if (candidate.owned_top == scroll_plan.post_scroll_owned_top) { return .{ .layout = candidate, .scroll_plan = scroll_plan, }; } previous_candidate_owned_top = candidate.owned_top; release_floor_rows = scroll_plan.preserved_release_rows; } } fn testPaintPlanForFrame( layout: render_engine.frame_layout.FrameLayout, selection: viewport_selection.ViewportSelection, cursor_row: u16, cursor_col: u16, ) render_engine.paint_plan.PaintPlan { const footer_rows = render_engine.footer_layout.resolve(.{ .footer_top_for_extra = layout.footer_area.top, .terminal_rows = layout.terminal.rows, .activity_offset = 0, .extra_input_rows = 0, .input_extra = 0, .picker_rows = 0, .banner_active = false, .allocated_rows = layout.footer_area.height(), }); return layout.toPaintPlan(.{ .footer_rows = footer_rows, .viewport = selection, .cursor_target = .{ .row = cursor_row, .col = cursor_col, .visible = true, }, }); } fn testSource( alloc: Allocator, bytes: []const u8, cols: u16, ) !TranscriptPreparationSource { return .{ .bytes = try alloc.dupe(u8, bytes), .folded_summary_indices = &.{}, .preview = .{ .natural_visual_rows = 4 }, .tail_kind = null, .tracked_entry_id = null, .tracked_entry_start_line = null, .replaceable_last_line = false, .replaceable_start = 0, .replaceable_row = 1, .welcome_cut_line = null, .welcome_boundary = null, .cols = cols, }; } fn testFoldedSource( alloc: Allocator, bytes: []const u8, cols: u16, summary_transcript_index: usize, ) !TranscriptPreparationSource { var source = try testSource(alloc, bytes, cols); errdefer source.deinit(alloc); source.folded_summary_indices = try alloc.dupe( usize, &.{summary_transcript_index}, ); return source; } fn testSourceWithReplaceableTail( alloc: Allocator, bytes: []const u8, cols: u16, replaceable_start: usize, ) !TranscriptPreparationSource { var source = try testSource(alloc, bytes, cols); source.replaceable_last_line = true; source.replaceable_start = replaceable_start; return source; } fn appendFoldedLineForTest( runtime: *TranscriptRuntime, alloc: Allocator, block_index: usize, text: []const u8, ) !void { const owned = try alloc.dupe(u8, text); errdefer alloc.free(owned); try runtime.folded_command_blocks.items[block_index].lines.append(alloc, .{ .stream = .stdout, .text = owned, }); } fn prepareTestSourceForCurrentArea( runtime: *TranscriptRuntime, alloc: Allocator, source: *const TranscriptPreparationSource, ) !transcript_painter.PreparedTranscriptSurfacePaint { var metrics: Metrics = .{}; return runtime.prepareTranscriptSurfacePaintFromSourceForArea( alloc, &metrics, source, .{ .top = runtime.owned_top_row, .bottom = runtime.layout.content_bottom, }, ); } fn preparedVisualOffsetForTest( prepared: *const transcript_painter.PreparedTranscriptSurfacePaint, ) u32 { var offset: u32 = 0; const end = @min( prepared.selection.start_line, prepared.sourceVisualRows().len, ); for (prepared.sourceVisualRows()[0..end]) |rows| offset += rows; return offset + prepared.selection.partial_skip_rows; } test "unchanged transcript keeps stable projection during footer reflow" { const alloc = std.testing.allocator; const flow = "line 00\n" ++ "line 01\n" ++ "line 02\n" ++ "line 03\n" ++ "line 04\n" ++ "line 05\n"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(60, 12, 8), .owned_top_row = 1, }; defer runtime.deinit(alloc); var metrics = Metrics{}; var source = try testSource(alloc, flow, runtime.layout.cols); defer source.deinit(alloc); var compact = try runtime.prepareTranscriptSurfacePaintFromSourceForFrame( alloc, &metrics, &source, .{ .top = 1, .bottom = 4 }, false, ); defer compact.deinit(alloc); try std.testing.expectEqual(@as(usize, 2), compact.selection.start_line); try installStableAnchorWithOriginForTest( &runtime, alloc, flow, compact.selection, preparedVisualOffsetForTest(&compact), compact.cursor.cursor_row, compact.cursor.cursor_col, 800, .{ .source_cols = runtime.layout.cols, .source_visual_offset = preparedVisualOffsetForTest(&compact), }, ); var expanded = try runtime.prepareTranscriptSurfacePaintFromSourceForFrame( alloc, &metrics, &source, .{ .top = 1, .bottom = 6 }, true, ); defer expanded.deinit(alloc); try std.testing.expectEqual(compact.selection.start_line, expanded.selection.start_line); try std.testing.expectEqual(compact.selection.last_visible_row, expanded.selection.last_visible_row); } test "structured user turn source keeps full prompt beyond byte cache cap" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(80, 18, 14), .owned_top_row = 1, .max_transcript_bytes = 256, }; defer runtime.deinit(alloc); const prompt = try makeLongPastePrompt(alloc, 512); try std.testing.expect(prompt.len > runtime.max_transcript_bytes); _ = try runtime.appendUserTurnOwned(alloc, .{ .text = prompt, .images = &.{}, }); const assistant_id = try runtime.appendAssistantTurnEntry(alloc); const assistant = runtime.lookupAssistantSegments(assistant_id) orelse unreachable; try assistant.text.appendSlice(alloc, "assistant after long prompt\n"); var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); try std.testing.expect(source.bytes.len > runtime.max_transcript_bytes); try std.testing.expect(std.mem.indexOf(u8, source.bytes, "LONG_PASTE_FIRST_MARKER") != null); try std.testing.expect(std.mem.indexOf(u8, source.bytes, "LONG_PASTE_LAST_MARKER") != null); try std.testing.expect(std.mem.indexOf(u8, source.bytes, "assistant after long prompt") != null); } test "large current user turn survives assistant retention pass" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(80, 18, 14), .owned_top_row = 1, .max_transcript_bytes = 256, .max_retained_transcript_bytes = 128, }; defer runtime.deinit(alloc); const prompt = try makeLongPastePrompt(alloc, 64); const user_id = try runtime.appendUserTurnOwned(alloc, .{ .text = prompt, .images = &.{}, }); const assistant_id = try runtime.appendAssistantTurnEntry(alloc); const assistant = runtime.lookupAssistantSegments(assistant_id) orelse unreachable; try assistant.text.appendSlice(alloc, "assistant begins\n"); try std.testing.expectEqual(@as(usize, 2), runtime.entries.items.len); try std.testing.expectEqual(user_id, runtime.entries.items[0].id()); try std.testing.expectEqual(assistant_id, runtime.entries.items[1].id()); var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); try std.testing.expect(std.mem.indexOf(u8, source.bytes, "LONG_PASTE_FIRST_MARKER") != null); try std.testing.expect(std.mem.indexOf(u8, source.bytes, "assistant begins") != null); } test "finalize transcript transition rejects frame inline row mismatch" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); var source = try testSource(alloc, "line\n", runtime.layout.cols); defer source.deinit(alloc); var prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &source, ); defer prepared.deinit(alloc); try std.testing.expectEqual(@as(u16, 0), runtime.planTranscriptScroll(&prepared).planned_rows); const scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, 1, ); var plan = testPaintPlan(&runtime, prepared.selection); try std.testing.expectError( error.InvalidFrameScrollPlan, resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &source, &prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(plan), &plan, scroll_plan, ), ); } test "child full transcript finalizer consumes one complete transition" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); try std.testing.expect( try runtime.setTranscriptPresentationDepth(alloc, .full), ); var source = try testSource(alloc, "child full transcript\n", runtime.layout.cols); defer source.deinit(alloc); var prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &source, ); defer prepared.deinit(alloc); const facts = runtime.planTranscriptScrollForFrame(&prepared, false, false); const scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, facts.planned_rows, ); var plan = testPaintPlan(&runtime, prepared.selection); var transition = try runtime.finalizeTranscriptTransitionForFrame( alloc, &source, &prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(plan), &plan, scroll_plan, false, false, ); defer transition.deinit(alloc); runtime.consumeFrameScrollCommit(alloc, scroll_plan, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = scroll_plan.terminal_scroll_rows, .document_append_committed = !transition.document_append.isEmpty(), .committed_cursor_row = transition.cursor_row, .committed_cursor_col = transition.cursor_col, .shadow_state = .committed, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }, &transition); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.stable, runtime.transcriptCommitDiagnostic().state, ); try std.testing.expectEqual( transition.target_layout.layout_id, runtime.committed_frame_layout.layout_id, ); } fn commitPreparedForTest( runtime: *TranscriptRuntime, alloc: Allocator, source: *TranscriptPreparationSource, prepared: *transcript_painter.PreparedTranscriptSurfacePaint, ) !void { const facts = runtime.planTranscriptScroll(prepared); const scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, facts.planned_rows, ); var plan = testPaintPlan(runtime, prepared.selection); var transition = try resolveAndSealTranscriptTransitionForTest( runtime, alloc, source, prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(plan), &plan, scroll_plan, ); defer transition.deinit(alloc); runtime.consumeTranscriptTransition(alloc, &transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = scroll_plan.terminal_scroll_rows, .document_append_committed = !transition.document_append.isEmpty(), .committed_cursor_row = transition.cursor_row, .committed_cursor_col = transition.cursor_col, .shadow_state = .committed, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); } fn commitPreparedPartiallyForTest( runtime: *TranscriptRuntime, alloc: Allocator, source: *TranscriptPreparationSource, prepared: *transcript_painter.PreparedTranscriptSurfacePaint, ) !void { const facts = runtime.planTranscriptScroll(prepared); const scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, facts.planned_rows, ); var plan = testPaintPlan(runtime, prepared.selection); var transition = try resolveAndSealTranscriptTransitionForTest( runtime, alloc, source, prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(plan), &plan, scroll_plan, ); defer transition.deinit(alloc); runtime.consumeTranscriptTransition(alloc, &transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = 0, .document_append_committed = false, .committed_cursor_row = transition.materialized.cursor_row, .committed_cursor_col = transition.materialized.cursor_col, .shadow_state = .terminal_partial_write, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); } fn setPreparedVisualOffsetForTest( prepared: *transcript_painter.PreparedTranscriptSurfacePaint, target: u32, ) !void { var remaining = target; var line_index: usize = 0; while (line_index < prepared.sourceVisualRows().len and remaining >= prepared.sourceVisualRows()[line_index]) : (line_index += 1) { remaining -= prepared.sourceVisualRows()[line_index]; } if (line_index >= prepared.sourceVisualRows().len) { return error.TestExpectedRepresentableVisualOffset; } prepared.selection.start_line = line_index; prepared.selection.partial_skip_rows = @intCast(remaining); prepared.selection.line_count = prepared.sourceVisualRows().len; } const MeasuredEndpointMode = enum { seeded_exact, natural_null, seeded_inexact, }; fn expectZeroReverseRestoresSourceOrigin( runtime: *TranscriptRuntime, alloc: Allocator, wide_source: *TranscriptPreparationSource, narrow_source: *TranscriptPreparationSource, positive_delta: i32, endpoint_mode: MeasuredEndpointMode, ) !void { var wide_prepared = try prepareTestSourceForCurrentArea( runtime, alloc, wide_source, ); defer wide_prepared.deinit(alloc); const source_origin = preparedVisualOffsetForTest(&wide_prepared); try std.testing.expect(source_origin > 0); try installStableAnchorForTest( runtime, alloc, wide_source.bytes, wide_prepared.selection, source_origin, wide_prepared.cursor.cursor_row, wide_prepared.cursor.cursor_col, 700, ); runtime.layout = transcriptTestLayout(narrow_source.cols, 8, 4); runtime.resize_history_row_delta = positive_delta; var narrow_prepared = try prepareTestSourceForCurrentArea( runtime, alloc, narrow_source, ); defer narrow_prepared.deinit(alloc); const natural_endpoint = if (narrow_prepared.measured_history_origin) |origin| origin.endpoint else null; if (endpoint_mode == .seeded_inexact) { try std.testing.expect(natural_endpoint == null); } if (endpoint_mode != .natural_null) { narrow_prepared.measured_history_origin = .{ .source_cols = wide_source.cols, .source_visual_offset = source_origin, .endpoint = .{ .span_start_flow_offset = 0, .flow_offset = 0, .hard_row_cols = wide_source.cols, }, }; } try std.testing.expectEqual( endpoint_mode != .natural_null, if (narrow_prepared.measured_history_origin) |origin| origin.endpoint != null else false, ); const narrow_origin = preparedVisualOffsetForTest(&narrow_prepared); try std.testing.expect(narrow_origin > source_origin); try commitPreparedForTest(runtime, alloc, narrow_source, &narrow_prepared); const narrowed = runtime.stableTranscriptProjectionForFlow( wide_source.bytes, ) orelse return error.TestExpectedStableTranscript; const retained_origin = narrowed.measured_history_origin orelse return error.TestExpectedMeasuredHistoryOrigin; try std.testing.expectEqual(source_origin, retained_origin.source_visual_offset); try std.testing.expectEqual(wide_source.cols, retained_origin.source_cols); try std.testing.expectEqual( endpoint_mode != .natural_null, retained_origin.endpoint != null, ); runtime.layout = transcriptTestLayout(wide_source.cols, 12, 8); runtime.resize_history_row_delta = 0; var restored = try prepareTestSourceForCurrentArea( runtime, alloc, wide_source, ); defer restored.deinit(alloc); try std.testing.expectEqual( source_origin, preparedVisualOffsetForTest(&restored), ); } fn totalVisualRowsForTest(line_rows: []const u16) u32 { var total: u32 = 0; for (line_rows) |rows| total += rows; return total; } test "semantic row identity remains exact beyond 65535 rows" { const alloc = std.testing.allocator; const row_count: usize = 65_540; var flow_writer: std.Io.Writer.Allocating = .init(alloc); defer flow_writer.deinit(); for (0..row_count) |index| { if (index > 0) try flow_writer.writer.writeByte('\n'); try flow_writer.writer.print("semantic-row-{d}", .{index}); } const flow = flow_writer.written(); var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(80, 12, 8), .owned_top_row = 1, }; defer runtime.deinit(alloc); var source = try testSource(alloc, flow, runtime.layout.cols); defer source.deinit(alloc); var prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &source, ); defer prepared.deinit(alloc); try std.testing.expectEqual(row_count, prepared.sourceVisualRows().len); try std.testing.expectEqual( @as(u32, @intCast(row_count)), totalVisualRowsForTest(prepared.sourceVisualRows()), ); try std.testing.expectEqual(row_count - 8, prepared.selection.start_line); try std.testing.expectEqual(@as(u16, 0), prepared.selection.partial_skip_rows); const boundary_indices = [_]usize{ 65_534, 65_535, 65_536, row_count - 1 }; for (boundary_indices) |index| { var marker_buf: [32]u8 = undefined; const marker = try std.fmt.bufPrint( &marker_buf, "semantic-row-{d}", .{index}, ); try std.testing.expectEqual( @as(usize, 1), std.mem.count(u8, flow, marker), ); try std.testing.expectEqualStrings( marker, resolveVisibleLineForTest( prepared.visible_lines.items[index], .{ .bytes = prepared.bytes }, ), ); } try commitPreparedForTest(&runtime, alloc, &source, &prepared); const stable = runtime.stableTranscriptProjectionForFlow(flow) orelse return error.TestExpectedStableTranscript; try std.testing.expectEqual(row_count - 8, stable.selection.start_line); try std.testing.expectEqual( @as(u32, @intCast(row_count - 8)), stable.visual_offset, ); try std.testing.expectEqual( @as(u32, @intCast(row_count)), stable.total_visual_rows, ); } fn makeSyntheticPreparedRenderable( prepared: *transcript_painter.PreparedTranscriptSurfacePaint, alloc: Allocator, ) !void { const line_count = prepared.line_visual_rows.items.len; try prepared.visible_lines.ensureTotalCapacity(alloc, line_count); while (prepared.visible_lines.items.len < line_count) { prepared.visible_lines.appendAssumeCapacity(.{ .transcript = .{ .ref = .{ .ref = .{ .start = 0, .len = 0 }, }, }, }); } prepared.selection.line_count = line_count; prepared.total_lines = line_count; } fn enterRendererDerivedSemanticRecovery( runtime: *TranscriptRuntime, alloc: Allocator, flow: []const u8, attempt_content_bottom: u16, accepted_semantic_rows: u16, replaceable_start: ?usize, ) !u32 { var stable_source = if (replaceable_start) |start| try testSourceWithReplaceableTail(alloc, flow, runtime.layout.cols, start) else try testSource(alloc, flow, runtime.layout.cols); defer stable_source.deinit(alloc); var stable_prepared = try prepareTestSourceForCurrentArea( runtime, alloc, &stable_source, ); defer stable_prepared.deinit(alloc); try std.testing.expectEqual(@as(usize, 0), stable_prepared.selection.start_line); try std.testing.expectEqual(@as(u16, 0), stable_prepared.selection.partial_skip_rows); try installStableAnchorForTest( runtime, alloc, flow, stable_prepared.selection, 0, stable_prepared.cursor.cursor_row, stable_prepared.cursor.cursor_col, 91, ); runtime.layout = transcriptTestLayout( runtime.layout.cols, runtime.layout.rows, attempt_content_bottom, ); var attempt_source = if (replaceable_start) |start| try testSourceWithReplaceableTail(alloc, flow, runtime.layout.cols, start) else try testSource(alloc, flow, runtime.layout.cols); defer attempt_source.deinit(alloc); var attempt_prepared = try prepareTestSourceForCurrentArea( runtime, alloc, &attempt_source, ); defer attempt_prepared.deinit(alloc); const attempt_facts = runtime.planTranscriptScroll(&attempt_prepared); try std.testing.expect(attempt_facts.semantic_rows > accepted_semantic_rows); const scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, attempt_facts.planned_rows, ); var plan = testPaintPlan(runtime, attempt_prepared.selection); var transition = try resolveAndSealTranscriptTransitionForTest( runtime, alloc, &attempt_source, &attempt_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(plan), &plan, scroll_plan, ); defer transition.deinit(alloc); runtime.consumeTranscriptTransition(alloc, &transition, .{ .bytes_written = if (accepted_semantic_rows > 0) 1 else 0, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = accepted_semantic_rows, .document_append_committed = false, .committed_cursor_row = attempt_prepared.cursor.cursor_row, .committed_cursor_col = attempt_prepared.cursor.cursor_col, .shadow_state = .terminal_partial_write, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.recovering, runtime.transcriptCommitDiagnostic().state, ); return attempt_facts.target_visual_offset; } fn expectFinalizedRecoveryProjectionPaintsCoherently( runtime: *TranscriptRuntime, alloc: Allocator, plan: render_engine.paint_plan.PaintPlan, prepared: *const transcript_painter.PreparedTranscriptSurfacePaint, transition: *const transcript_runtime.TranscriptTransition, expected_first_row_text: ?[]const u8, ) !void { var shadow = try vt_emulator.Grid.init(alloc, plan.layout.cols, plan.layout.rows); defer shadow.deinit(); var surface = try render_engine.frame_surface.FrameSurface.initFromShadow( alloc, plan, shadow, ); defer surface.deinit(); const result = try runtime.paintPreparedTranscriptIntoSurface( alloc, &surface, prepared, ); try std.testing.expectEqualDeep(transition.selection, surface.plan.viewport); try std.testing.expectEqual( transition.selection.last_visible_row, result.last_row, ); try std.testing.expectEqual( transition.selection.replaceable_start_row, result.replaceable_start_row, ); try std.testing.expectEqual( transition.cursor_row, surface.cursor_target.?.row, ); try std.testing.expectEqual( transition.cursor_col, surface.cursor_target.?.col, ); try std.testing.expectEqual( transition.replaceable_row, prepared.cursor.replaceable_row, ); if (expected_first_row_text) |expected| { var target = try surface.copyToTargetGrid(alloc); defer target.deinit(); var row_text: std.ArrayList(u8) = .empty; defer row_text.deinit(alloc); try target.rowText(plan.viewport.top_row, &row_text); try std.testing.expect(std.mem.startsWith(u8, row_text.items, expected)); } if (transition.selection.last_visible_row_blank) { var col: u16 = 1; while (col <= surface.cols) : (col += 1) { const cell = surface.cellAt( transition.selection.last_visible_row, col, ).?; try std.testing.expectEqual(@as(u21, ' '), cell.codepoint); } } } const OwnedBandInvalidationProbe = struct { saw_owned_band_change: bool = false, fn paint(ctx: *anyopaque, surface: *render_engine.frame_surface.FrameSurface) anyerror!void { const self: *OwnedBandInvalidationProbe = @ptrCast(@alignCast(ctx)); for (surface.plan.invalidation.ranges()) |range| { if (range.reason == .owned_band_change) { self.saw_owned_band_change = true; } } } }; const PreparedTranscriptFramePainter = struct { runtime: *TranscriptRuntime, alloc: Allocator, prepared: *const transcript_painter.PreparedTranscriptSurfacePaint, fn paint(ctx: *anyopaque, surface: *render_engine.frame_surface.FrameSurface) anyerror!void { const self: *PreparedTranscriptFramePainter = @ptrCast(@alignCast(ctx)); _ = try self.runtime.paintPreparedTranscriptIntoSurface( self.alloc, surface, self.prepared, ); } }; fn appendGridRows( alloc: Allocator, grid: *const vt_emulator.Grid, first_row: u16, row_count: u16, history: *std.ArrayList(u8), row_text: *std.ArrayList(u8), ) !void { var row_offset: u16 = 0; while (row_offset < row_count) : (row_offset += 1) { row_text.clearRetainingCapacity(); try grid.rowTextTrimmed(first_row + row_offset, row_text); try std.testing.expect(row_text.items.len > 0); if (history.items.len > 0) try history.append(alloc, '\n'); try history.appendSlice(alloc, row_text.items); } } fn countExactHistoryLine(history: []const u8, expected: []const u8) usize { var count: usize = 0; var lines = std.mem.splitScalar(u8, history, '\n'); while (lines.next()) |line| { if (std.mem.eql(u8, line, expected)) count += 1; } return count; } fn cloneRecoveryTransitionForTest( alloc: Allocator, source: *const transcript_runtime.TranscriptTransition, ) !transcript_runtime.TranscriptTransition { std.debug.assert(!source.consumed); std.debug.assert(source.document_append.isEmpty()); std.debug.assert(source.document_append_bytes.len == 0); var clone = source.*; clone.document_append.bytes = &.{}; clone.document_append_bytes = &.{}; clone.target_flow = &.{}; clone.target_cache = .empty; clone.folded_summary_indices = &.{}; errdefer clone.deinit(alloc); clone.target_flow = try alloc.dupe(u8, source.target_flow); try clone.target_cache.appendSlice(alloc, source.target_cache.items); clone.folded_summary_indices = try alloc.dupe( usize, source.folded_summary_indices, ); return clone; } fn createProductionCappedFoldedRecoveryTransition( alloc: Allocator, cols: u16, viewport_rows: u16, ) !transcript_runtime.TranscriptTransition { const stable_flow = "summary"; const initial_owned_top = viewport_rows + 1; const content_bottom = initial_owned_top + viewport_rows - 1; const raw_line_count: usize = 3; const wrapped_rows_per_line: usize = (@as(usize, std.math.maxInt(u16)) + 1) / raw_line_count + 1; const raw_line_bytes = wrapped_rows_per_line * @as(usize, cols); var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var sink = try tmp.dir.createFile( std.testing.io, "production-folded-capped-attempt.log", .{ .read = true }, ); defer sink.close(io_mod.getIo()); var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = transcriptTestLayout( cols, content_bottom + 4, content_bottom, ), .owned_top_row = initial_owned_top, .max_transcript_bytes = 2 * 1024 * 1024, .max_retained_transcript_bytes = 2 * 1024 * 1024, .full_transcript = .{ .depth = .full }, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); try runtime.shadow_vt.?.feed("shell0\r\nshell1\r\nshell2\r\nshell3"); try runtime.transcript.appendSlice(alloc, stable_flow); try runtime.folded_command_blocks.append(alloc, .{ .summary_transcript_index = 1, }); var fold_index: usize = 0; while (fold_index < viewport_rows) : (fold_index += 1) { var fold_buf: [16]u8 = undefined; const fold = try std.fmt.bufPrint(&fold_buf, "fold{d}", .{fold_index}); try appendFoldedLineForTest(&runtime, alloc, 0, fold); } var metrics: Metrics = .{}; var stable_source = try runtime.prepareTranscriptSource(alloc, null); defer stable_source.deinit(alloc); try std.testing.expectEqualStrings(stable_flow, stable_source.bytes); try std.testing.expectEqual(@as(usize, 1), stable_source.folded_summary_indices.len); try std.testing.expectEqual(@as(usize, 1), stable_source.folded_summary_indices[0]); try std.testing.expectEqual(viewport_rows, stable_source.preview.natural_visual_rows); var stable_prepared: ?transcript_painter.PreparedTranscriptSurfacePaint = null; defer if (stable_prepared) |*paint| paint.deinit(alloc); const stable_frame = try solveTestTranscriptFixedPointFrame( &runtime, alloc, &metrics, &stable_source, 4, &stable_prepared, ); const stable_paint = &stable_prepared.?; try std.testing.expectEqual( stable_frame.layout.owned_top, stable_frame.scroll_plan.post_scroll_owned_top, ); var stable_plan = testPaintPlanForFrame( stable_frame.layout, stable_paint.selection, stable_paint.cursor.cursor_row, stable_paint.cursor.cursor_col, ); var stable_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &stable_source, stable_paint, render_engine.frame_layout.CommittedLayoutSnapshot.fromLayout(stable_frame.layout), &stable_plan, stable_frame.scroll_plan, ); defer stable_transition.deinit(alloc); try std.testing.expect(stable_transition.document_append.isEmpty()); var stable_painter = PreparedTranscriptFramePainter{ .runtime = &runtime, .alloc = alloc, .prepared = stable_paint, }; const stable_result = try render_engine.frame_builder.buildAndFlushFrame( alloc, &runtime, &metrics, .{ .plan = stable_plan, .scroll_plan = stable_frame.scroll_plan, .document_append = stable_transition.document_append, .body_painter = .{ .ctx = &stable_painter, .paint = PreparedTranscriptFramePainter.paint, }, }, ); try std.testing.expectEqual( render_engine.terminal_diff.ShadowCommitState.committed, stable_result.shadow_state, ); runtime.ackPreservedRowReleaseAssumeValid( stable_frame.scroll_plan, stable_result.terminal_scroll_rows_committed, ); runtime.consumeTranscriptTransition(alloc, &stable_transition, stable_result); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.stable, runtime.transcriptCommitDiagnostic().state, ); var raw_line: std.ArrayList(u8) = .empty; defer raw_line.deinit(alloc); var raw_line_index: usize = 0; while (raw_line_index < raw_line_count) : (raw_line_index += 1) { try runtime.transcript.appendSlice(alloc, "\n"); raw_line.clearRetainingCapacity(); try raw_line.appendNTimes( alloc, 'a' + @as(u8, @intCast(raw_line_index)), raw_line_bytes, ); try std.testing.expectEqual( @as(u16, @intCast(wrapped_rows_per_line)), visualRowsForLine(raw_line.items, cols), ); try runtime.transcript.appendSlice(alloc, raw_line.items); } var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); try std.testing.expectEqual( @as(u16, std.math.maxInt(u16)), source.preview.natural_visual_rows, ); try std.testing.expectEqual(@as(usize, 1), source.folded_summary_indices.len); try std.testing.expectEqual(@as(usize, 1), source.folded_summary_indices[0]); var prepared: ?transcript_painter.PreparedTranscriptSurfacePaint = null; defer if (prepared) |*paint| paint.deinit(alloc); const frame = try solveTestTranscriptFixedPointFrame( &runtime, alloc, &metrics, &source, 8, &prepared, ); const paint = &prepared.?; const facts = runtime.planTranscriptScroll(paint); try std.testing.expect(facts.semantic_rows > std.math.maxInt(u16)); try std.testing.expectEqual(viewport_rows, facts.planned_rows); try std.testing.expectEqual( frame.layout.owned_top, frame.scroll_plan.post_scroll_owned_top, ); try std.testing.expectEqual( viewport_rows, frame.scroll_plan.acceptedInlineRows( frame.scroll_plan.terminal_scroll_rows, ), ); var plan = testPaintPlanForFrame( frame.layout, paint.selection, paint.cursor.cursor_row, paint.cursor.cursor_col, ); var transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &source, paint, render_engine.frame_layout.CommittedLayoutSnapshot.fromLayout(frame.layout), &plan, frame.scroll_plan, ); defer transition.deinit(alloc); try std.testing.expect(transition.document_append.isEmpty()); try std.testing.expectEqual(@as(?usize, null), transition.materialized.flow_len); try std.testing.expectEqual(viewport_rows, transition.materialized.visual_rows); try std.testing.expectEqual( @as(u32, viewport_rows), transition.materialized.visual_offset, ); var transition_template = try cloneRecoveryTransitionForTest( alloc, &transition, ); errdefer transition_template.deinit(alloc); var painter = PreparedTranscriptFramePainter{ .runtime = &runtime, .alloc = alloc, .prepared = paint, }; const result = try render_engine.frame_builder.buildAndFlushFrame( alloc, &runtime, &metrics, .{ .plan = plan, .scroll_plan = frame.scroll_plan, .document_append = transition.document_append, .body_painter = .{ .ctx = &painter, .paint = PreparedTranscriptFramePainter.paint, }, }, ); try std.testing.expectEqual( render_engine.terminal_diff.ShadowCommitState.committed, result.shadow_state, ); try std.testing.expectEqual( frame.scroll_plan.terminal_scroll_rows, result.terminal_scroll_rows_committed, ); try std.testing.expect(!result.document_append_committed); runtime.ackPreservedRowReleaseAssumeValid( frame.scroll_plan, result.terminal_scroll_rows_committed, ); runtime.consumeTranscriptTransition(alloc, &transition, result); const diagnostic = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.recovering, diagnostic.state, ); try std.testing.expectEqual(transition.visual_offset, diagnostic.visual_offset); try std.testing.expect(diagnostic.remaining_inline_rows > 0); try std.testing.expectEqual( facts.semantic_rows - facts.planned_rows, diagnostic.remaining_inline_rows, ); return transition_template; } fn testSelectionInArea( start_line: usize, top_row: u16, bottom_row: u16, ) viewport_selection.ViewportSelection { var selection = testSelection(start_line); selection.top_row = top_row; selection.bottom_row = bottom_row; return selection; } fn enterSemanticAppendRecovery( runtime: *TranscriptRuntime, alloc: Allocator, stable_flow: []const u8, attempt_flow: []const u8, attempt_visual_rows: []const u16, ) !void { const stable_selection = testSelectionInArea( 0, runtime.owned_top_row, runtime.layout.content_bottom, ); try installStableAnchorForTest( runtime, alloc, stable_flow, stable_selection, 0, runtime.layout.content_bottom, 1, 45, ); var source = try testSource(alloc, attempt_flow, runtime.layout.cols); defer source.deinit(alloc); var prepared = transcript_painter.PreparedTranscriptSurfacePaint{ .bytes = source.bytes, .owns_bytes = false, .selection = testSelectionInArea( 4, runtime.owned_top_row, runtime.layout.content_bottom, ), .cursor = .{ .cursor_row = runtime.layout.content_bottom, .cursor_col = 1, .replaceable_row = runtime.layout.content_bottom, }, }; defer prepared.deinit(alloc); try prepared.line_visual_rows.appendSlice(alloc, attempt_visual_rows); try makeSyntheticPreparedRenderable(&prepared, alloc); const facts = runtime.planTranscriptScroll(&prepared); try std.testing.expectEqual(@as(u16, 0), facts.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 4), facts.semantic_rows); const scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 3, facts.planned_rows, ); try std.testing.expectEqual(@as(u16, 1), scroll_plan.remaining_inline_advance_rows); var plan = testPaintPlan(runtime, prepared.selection); const target_layout = render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(plan); var transition = try resolveAndSealTranscriptTransitionForTest( runtime, alloc, &source, &prepared, target_layout, &plan, scroll_plan, ); defer transition.deinit(alloc); try std.testing.expect(transition.document_append.isEmpty()); runtime.ackPreservedRowReleaseAssumeValid( scroll_plan, scroll_plan.terminal_scroll_rows, ); runtime.consumeTranscriptTransition(alloc, &transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = false, .terminal_scroll_rows_committed = scroll_plan.terminal_scroll_rows, .document_append_committed = false, .committed_cursor_row = runtime.layout.content_bottom, .committed_cursor_col = 1, .shadow_state = .committed, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); const recovery = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.recovering, recovery.state, ); try std.testing.expectEqual(@as(u32, 3), recovery.visual_offset); try std.testing.expectEqual(@as(u16, 1), recovery.remaining_inline_rows); } fn consumeUnacceptedSemanticRecoveryRetry( runtime: *TranscriptRuntime, alloc: Allocator, flow: []const u8, visual_rows: []const u16, start_line: usize, ) !void { var source = try testSource(alloc, flow, runtime.layout.cols); defer source.deinit(alloc); var prepared = transcript_painter.PreparedTranscriptSurfacePaint{ .bytes = source.bytes, .owns_bytes = false, .selection = testSelectionInArea( start_line, runtime.owned_top_row, runtime.layout.content_bottom, ), .cursor = .{ .cursor_row = runtime.layout.content_bottom, .cursor_col = 1, .replaceable_row = runtime.layout.content_bottom, }, }; defer prepared.deinit(alloc); try prepared.line_visual_rows.appendSlice(alloc, visual_rows); try makeSyntheticPreparedRenderable(&prepared, alloc); const facts = runtime.planTranscriptScroll(&prepared); try std.testing.expectEqual(@as(u16, 0), facts.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 1), facts.semantic_rows); const scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, facts.planned_rows, ); var plan = testPaintPlan(runtime, prepared.selection); var transition = try resolveAndSealTranscriptTransitionForTest( runtime, alloc, &source, &prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(plan), &plan, scroll_plan, ); defer transition.deinit(alloc); runtime.consumeTranscriptTransition(alloc, &transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = 0, .document_append_committed = false, .committed_cursor_row = runtime.layout.content_bottom, .committed_cursor_col = 1, .shadow_state = .terminal_partial_write, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); const recovery = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.recovering, recovery.state, ); try std.testing.expectEqual(@as(u32, 3), recovery.visual_offset); try std.testing.expectEqual(@as(u16, 1), recovery.remaining_inline_rows); } fn enterResizeReflowRecovery( runtime: *TranscriptRuntime, alloc: Allocator, resize_history_row_delta: ?i32, shadow_state: render_engine.terminal_diff.ShadowCommitState, ) !void { return enterResizeReflowRecoveryWithAcceptedRows( runtime, alloc, resize_history_row_delta, shadow_state, 1, ); } fn enterResizeReflowRecoveryWithAcceptedRows( runtime: *TranscriptRuntime, alloc: Allocator, resize_history_row_delta: ?i32, shadow_state: render_engine.terminal_diff.ShadowCommitState, accepted_rows: u16, ) !void { const flow = "stable\nbase\n"; try runtime.enableShadowVt(alloc); try installStableAnchorForTest( runtime, alloc, flow, testSelectionInArea(0, 1, 4), 0, 4, 1, 46, ); const shadow = runtime.shadow_vt.?; shadow.cells[19].codepoint = 'x'; shadow.cursor_row = 8; shadow.cursor_col = 1; runtime.viewport_clear_pending = true; runtime.layout = transcriptTestLayout(10, 8, 2); runtime.resize_history_row_delta = resize_history_row_delta; var source = try testSource(alloc, flow, runtime.layout.cols); defer source.deinit(alloc); var prepared = try prepareTestSourceForCurrentArea(runtime, alloc, &source); defer prepared.deinit(alloc); const facts = runtime.planTranscriptScroll(&prepared); try std.testing.expectEqual(@as(u16, 2), facts.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 0), facts.semantic_rows); try std.testing.expect(accepted_rows < facts.planned_rows); const scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, facts.planned_rows, ); var plan = testPaintPlan(runtime, prepared.selection); const target_layout = render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(plan); var transition = try resolveAndSealTranscriptTransitionForTest( runtime, alloc, &source, &prepared, target_layout, &plan, scroll_plan, ); defer transition.deinit(alloc); try std.testing.expect(transition.document_append.isEmpty()); runtime.consumeTranscriptTransition(alloc, &transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = accepted_rows, .document_append_committed = false, .committed_cursor_row = 2, .committed_cursor_col = 1, .shadow_state = shadow_state, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); const recovery = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.recovering, recovery.state, ); try std.testing.expectEqual(facts.target_visual_offset, recovery.visual_offset); try std.testing.expectEqual( facts.planned_rows - accepted_rows, recovery.remaining_inline_rows, ); } test "committed resize deferral installs target layout before recovery" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); try enterResizeReflowRecoveryWithAcceptedRows( &runtime, alloc, null, .committed, 0, ); const expected_layout = render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan( testPaintPlan(&runtime, testSelectionInArea(0, 1, 2)), ); try std.testing.expectEqualDeep(expected_layout, runtime.committed_frame_layout); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.recovering, runtime.transcriptCommitDiagnostic().state, ); } const RecoveryAttemptOutcome = struct { facts: transcript_runtime.TranscriptScrollFacts, visual_offset: u32, }; fn consumeRendererRecoveryAttempt( runtime: *TranscriptRuntime, alloc: Allocator, flow: []const u8, committed_inline_rows: u16, shadow_state: render_engine.terminal_diff.ShadowCommitState, ) !RecoveryAttemptOutcome { var source = try testSource(alloc, flow, runtime.layout.cols); defer source.deinit(alloc); var prepared = try prepareTestSourceForCurrentArea(runtime, alloc, &source); defer prepared.deinit(alloc); const facts = runtime.planTranscriptScroll(&prepared); const scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, facts.planned_rows, ); try std.testing.expect(committed_inline_rows <= scroll_plan.terminal_scroll_rows); var plan = testPaintPlan(runtime, prepared.selection); var transition = try resolveAndSealTranscriptTransitionForTest( runtime, alloc, &source, &prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(plan), &plan, scroll_plan, ); defer transition.deinit(alloc); const visual_offset = transition.visual_offset; runtime.consumeTranscriptTransition(alloc, &transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = committed_inline_rows, .document_append_committed = !transition.document_append.isEmpty(), .committed_cursor_row = transition.cursor_row, .committed_cursor_col = transition.cursor_col, .shadow_state = shadow_state, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); return .{ .facts = facts, .visual_offset = visual_offset, }; } fn expectDocumentWireEqualsLogical( alloc: Allocator, logical: []const u8, wire: []const u8, ) !void { var decoded: std.ArrayList(u8) = .empty; defer decoded.deinit(alloc); var index: usize = 0; while (index < wire.len) { if (wire[index] == '\n') return error.TestExpectedCarriageReturn; if (wire[index] == '\r' and index + 1 < wire.len and wire[index + 1] == '\n') { try decoded.append(alloc, '\n'); index += 2; } else { try decoded.append(alloc, wire[index]); index += 1; } } try std.testing.expectEqualStrings(logical, decoded.items); } fn expectRendererAppendRestored( runtime: *TranscriptRuntime, alloc: Allocator, stable_flow: []const u8, grown_flow: []const u8, ) !void { var source = try testSource(alloc, grown_flow, runtime.layout.cols); defer source.deinit(alloc); var prepared = try prepareTestSourceForCurrentArea(runtime, alloc, &source); defer prepared.deinit(alloc); const facts = runtime.planTranscriptScroll(&prepared); try std.testing.expect(!facts.recovery_rebase); try std.testing.expect(facts.semantic_rows > 0); const scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, facts.planned_rows, ); var plan = testPaintPlan(runtime, prepared.selection); var transition = try resolveAndSealTranscriptTransitionForTest( runtime, alloc, &source, &prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(plan), &plan, scroll_plan, ); defer transition.deinit(alloc); try expectDocumentWireEqualsLogical( alloc, grown_flow[stable_flow.len..], transition.document_append.bytes, ); } fn checkTranscriptTransitionStagingAllocationFailures(alloc: Allocator) !void { var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); try installStableAnchorForTest(&runtime, alloc, "old\n", testSelection(0), 0, 3, 1, 71); const committed_before = runtime.transcriptCommitDiagnostic(); var source = try testSource(alloc, "old\nnew\n", runtime.layout.cols); defer source.deinit(alloc); var prepared = transcript_painter.PreparedTranscriptSurfacePaint{ .bytes = source.bytes, .owns_bytes = false, .selection = testSelection(1), .cursor = .{ .cursor_row = 4, .cursor_col = 1, .replaceable_row = 4 }, }; defer prepared.deinit(alloc); try prepared.line_visual_rows.appendSlice(alloc, &.{ 1, 1 }); const scroll_plan = render_engine.frame_scroll_plan.merge(runtime.layout.rows, 1, 0, 1); var plan = testPaintPlan(&runtime, prepared.selection); const target_layout = render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(plan); var transition = resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &source, &prepared, target_layout, &plan, scroll_plan, ) catch |err| { try std.testing.expectEqualDeep(committed_before, runtime.transcriptCommitDiagnostic()); try std.testing.expect(runtime.stableTranscriptProjectionForFlow("old\n") != null); return err; }; defer transition.deinit(alloc); try std.testing.expectEqualDeep(committed_before, runtime.transcriptCommitDiagnostic()); try std.testing.expect(runtime.stableTranscriptProjectionForFlow("old\n") != null); } fn commandOutputTestStyles() Styles { return .{ .system_notice_label_style = "", .system_notice_text_style = "", .reset_style = "", .dim_style = "", .red_style = "", }; } fn commandOutputAnsiTestStyles() Styles { return .{ .system_notice_label_style = "", .system_notice_text_style = "", .reset_style = "\x1b[0m", .dim_style = "\x1b[2m", .red_style = "\x1b[31m", }; } fn semanticNoticePaletteA() Styles { return .{ .system_notice_text_style = "\x1b[37m", .reset_style = "\x1b[0m", .notice_information_style = "\x1b[36m", .notice_success_style = "\x1b[32m", .notice_warning_style = "\x1b[33m", .notice_error_style = "\x1b[31m", .notice_cancelled_style = "\x1b[90m", }; } fn semanticNoticePaletteB() Styles { return .{ .system_notice_text_style = "\x1b[35m", .reset_style = "\x1b[0m", .notice_information_style = "\x1b[34m", .notice_success_style = "\x1b[92m", .notice_warning_style = "\x1b[93m", .notice_error_style = "\x1b[91m", .notice_cancelled_style = "\x1b[2m", }; } test "semantic notice append and replacement preserve ownership identity and timestamp" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(32, 16, 12) }; defer runtime.deinit(alloc); runtime.setCommandOutputRenderPolicy(semanticNoticePaletteA()); const entry_id = try runtime.appendReplaceableSemanticNotice(alloc, .{ .topic = "build", .tone = .warning, .body = "first body", }); try std.testing.expectEqual(@as(usize, 1), runtime.entries.items.len); try std.testing.expect(runtime.entries.items[0] == .semantic_notice); try std.testing.expect(runtime.entries.items[0].semantic_notice.pending_replacement); const created_at_ms = runtime.entries.items[0].createdAtMs(); try std.testing.expectEqualStrings("build", runtime.entries.items[0].semantic_notice.topic); try std.testing.expectEqualStrings("first body", runtime.entries.items[0].semantic_notice.body); try std.testing.expect(try runtime.replaceSemanticNotice(alloc, entry_id, .{ .topic = "deploy", .tone = .success, .body = "replacement body with /tmp/output/path", .visibility = .full_only, })); try std.testing.expectEqual(@as(usize, 1), runtime.entries.items.len); try std.testing.expectEqual(entry_id, runtime.entries.items[0].id()); try std.testing.expectEqual(created_at_ms, runtime.entries.items[0].createdAtMs()); try std.testing.expectEqualStrings("deploy", runtime.entries.items[0].semantic_notice.topic); try std.testing.expectEqual(types.NoticeTone.success, runtime.entries.items[0].semantic_notice.tone); try std.testing.expectEqualStrings("replacement body with /tmp/output/path", runtime.entries.items[0].semantic_notice.body); try std.testing.expectEqual(types.NoticeVisibility.full_only, runtime.entries.items[0].semantic_notice.visibility); try std.testing.expectEqualStrings("", runtime.transcript.items); // The finished replacement clears the pending-replacement pin, making // the entry immutable history: a second in-place replacement is refused. try std.testing.expect(!runtime.entries.items[0].semantic_notice.pending_replacement); try std.testing.expectError( error.MissingNoticeReplacementPin, runtime.replaceSemanticNotice(alloc, entry_id, .{ .topic = "again", .tone = .information, .body = "must not apply", }), ); try std.testing.expect(!(try runtime.replaceSemanticNotice(alloc, entry_id + 1, .{ .topic = "missing", .tone = .information, .body = "unchanged", }))); } fn checkSemanticNoticeAppendAllocationFailures(alloc: Allocator) !void { var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(32, 16, 12) }; defer runtime.deinit(alloc); const entry_id = try runtime.appendSemanticNotice(alloc, .{ .topic = "allocator", .tone = .@"error", .body = "owned topic and body", }); try std.testing.expectEqual(@as(u32, 1), entry_id); try std.testing.expectEqual(@as(usize, 1), runtime.entries.items.len); try std.testing.expectEqualStrings("allocator", runtime.entries.items[0].semantic_notice.topic); try std.testing.expectEqualStrings("owned topic and body", runtime.entries.items[0].semantic_notice.body); } test "semantic notice append is allocator safe" { try std.testing.checkAllAllocationFailures( std.testing.allocator, checkSemanticNoticeAppendAllocationFailures, .{}, ); } fn checkSemanticNoticeReplacementAllocationFailures(alloc: Allocator) !void { const base = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(32, 16, 12) }; defer runtime.deinit(base); const entry_id = try runtime.appendReplaceableSemanticNotice(base, .{ .topic = "before", .tone = .information, .body = "stable body", }); const created_at_ms = runtime.entries.items[0].createdAtMs(); const next_entry_id = runtime.next_entry_id; const replaced = runtime.replaceSemanticNotice(alloc, entry_id, .{ .topic = "after", .tone = .cancelled, .body = "replacement body", }) catch |err| { try std.testing.expectEqual(@as(usize, 1), runtime.entries.items.len); try std.testing.expectEqual(entry_id, runtime.entries.items[0].id()); try std.testing.expectEqual(created_at_ms, runtime.entries.items[0].createdAtMs()); try std.testing.expectEqualStrings("before", runtime.entries.items[0].semantic_notice.topic); try std.testing.expectEqualStrings("stable body", runtime.entries.items[0].semantic_notice.body); try std.testing.expectEqual(next_entry_id, runtime.next_entry_id); return err; }; try std.testing.expect(replaced); try std.testing.expectEqual(@as(usize, 1), runtime.entries.items.len); try std.testing.expectEqual(entry_id, runtime.entries.items[0].id()); try std.testing.expectEqual(created_at_ms, runtime.entries.items[0].createdAtMs()); try std.testing.expectEqualStrings("after", runtime.entries.items[0].semantic_notice.topic); try std.testing.expectEqualStrings("replacement body", runtime.entries.items[0].semantic_notice.body); try std.testing.expectEqual(next_entry_id, runtime.next_entry_id); } test "semantic notice replacement is atomic across allocation failures" { try std.testing.checkAllAllocationFailures( std.testing.allocator, checkSemanticNoticeReplacementAllocationFailures, .{}, ); } test "one retained semantic notice reflows through compact and full projection with retained palettes" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(36, 24, 20) }; defer runtime.deinit(alloc); const notice: types.SemanticNotice = .{ .topic = "paths", .tone = .information, .body = "review /workspace/packages/terminal/rendering.zig before continuing", }; const entry_id = try runtime.appendSemanticNotice(alloc, notice); const cases = [_]struct { cols: u16, styles: Styles }{ .{ .cols = 36, .styles = semanticNoticePaletteA() }, .{ .cols = 18, .styles = semanticNoticePaletteB() }, }; for (cases) |case| { runtime.layout = transcriptTestLayout(case.cols, 24, 20); runtime.setCommandOutputRenderPolicy(case.styles); const expected = try transcript_blocks.renderSemanticNotice(alloc, notice, case.styles, case.cols); defer alloc.free(expected); var compact = try runtime.prepareTranscriptSource(alloc, null); defer compact.deinit(alloc); try std.testing.expect(std.mem.find(u8, compact.bytes, expected) != null); var projection = try runtime.buildFullTranscriptProjection(alloc, null); defer projection.deinit(alloc); const full = try full_transcript_screen.renderProjectionViewportSourceInterruptible( alloc, &projection, null, case.cols, 64, 0, null, ); defer alloc.free(full); try std.testing.expect(std.mem.find(u8, full, expected) != null); try std.testing.expectEqual(entry_id, runtime.entries.items[0].id()); } } test "visible overflow after committed frame leaves scroll planning to viewport selection" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 12, .cols = 40, .content_bottom = 8, .divider_top_row = 9, .input_row = 10, .divider_bottom_row = 11, .hint_row = 12, }, .viewport_top_row = 4, .owned_top_row = 4, .cursor_row = 8, .cursor_col = 1, .has_painted_transcript = true, .has_committed_frame = true, }; defer runtime.deinit(alloc); var metrics = Metrics{}; try runtime.writeTranscript(alloc, &metrics, "overflow\n", true); try std.testing.expectEqual(@as(u16, 4), runtime.owned_top_row); try std.testing.expectEqual(@as(u16, 4), runtime.viewport_top_row); } test "neutral transcript flow preview reports natural rows without footer reservation" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 12, .cols = 40, .content_bottom = 8, .divider_top_row = 9, .input_row = 10, .divider_bottom_row = 11, .hint_row = 12, }, .viewport_top_row = 1, .owned_top_row = 1, .cursor_row = 8, .cursor_col = 1, .has_painted_transcript = true, .last_paint_bottom_reserved_rows = 3, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); try runtime.transcript.appendSlice(alloc, "line 1\nline 2\nline 3\n"); const preview = try runtime.previewTranscriptFlow(alloc); try std.testing.expectEqual(@as(u16, 3), preview.natural_visual_rows); try std.testing.expectEqual(@as(u16, 1), preview.cursor_col); try std.testing.expectEqual(@as(u16, 3), runtime.last_paint_bottom_reserved_rows); } test "stable transcript planning uses visual rows and partial skips" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); try installStableAnchorForTest(&runtime, alloc, "stable", testSelection(0), 0, 3, 1, 41); var prepared = transcript_painter.PreparedTranscriptSurfacePaint{ .bytes = try alloc.dupe(u8, "stable"), .selection = testSelection(1), }; defer prepared.deinit(alloc); prepared.selection.partial_skip_rows = 2; try prepared.line_visual_rows.appendSlice(alloc, &.{ 3, 4, 2, 1 }); const facts = runtime.planTranscriptScroll(&prepared); try std.testing.expect(facts.source_compatible); try std.testing.expectEqual(@as(u32, 5), facts.target_visual_offset); try std.testing.expectEqual(@as(u16, 5), facts.semantic_rows); try std.testing.expectEqual(@as(u16, 5), facts.planned_rows); } test "compatible growth beyond one frame retains exact semantic debt" { const alloc = std.testing.allocator; const stable_flow = "base\n"; const extra_row_count: usize = @as(usize, std.math.maxInt(u16)) + 8; var grown_flow: std.ArrayList(u8) = .empty; defer grown_flow.deinit(alloc); try grown_flow.ensureTotalCapacity( alloc, stable_flow.len + extra_row_count * "x\n".len, ); grown_flow.appendSliceAssumeCapacity(stable_flow); for (0..extra_row_count) |_| { grown_flow.appendSliceAssumeCapacity("x\n"); } var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); try installStableAnchorForTest( &runtime, alloc, stable_flow, testSelection(0), 0, 2, 1, 411, ); var probe_source = try testSource(alloc, grown_flow.items, runtime.layout.cols); defer probe_source.deinit(alloc); var probe_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &probe_source, ); defer probe_prepared.deinit(alloc); const probe_facts = runtime.planTranscriptScroll(&probe_prepared); const exact_semantic_rows = probe_facts.target_visual_offset; try std.testing.expect(exact_semantic_rows > std.math.maxInt(u16)); try std.testing.expectEqual( exact_semantic_rows, @as(u32, probe_facts.semantic_rows), ); try std.testing.expectEqual( std.math.maxInt(u16), probe_facts.planned_rows, ); const accepted_rows = std.math.maxInt(u16); const initial = try consumeRendererRecoveryAttempt( &runtime, alloc, grown_flow.items, accepted_rows, .committed, ); try std.testing.expectEqual( exact_semantic_rows, @as(u32, initial.facts.semantic_rows), ); const remaining_after_cap = exact_semantic_rows - accepted_rows; try std.testing.expectEqual( remaining_after_cap, @as(u32, runtime.transcriptCommitDiagnostic().remaining_inline_rows), ); try std.testing.expect(runtime.render_requests.hasReason(.transcript)); try std.testing.expect(runtime.transcript_band_dirty); runtime.render_requests.clearReason(.transcript); runtime.transcript_band_dirty = false; const repeated = try consumeRendererRecoveryAttempt( &runtime, alloc, grown_flow.items, 1, .terminal_partial_write, ); try std.testing.expectEqual( remaining_after_cap, @as(u32, repeated.facts.semantic_rows), ); const remaining_semantic_rows = remaining_after_cap - 1; try std.testing.expectEqual( remaining_semantic_rows, @as(u32, runtime.transcriptCommitDiagnostic().remaining_inline_rows), ); const complete = try consumeRendererRecoveryAttempt( &runtime, alloc, grown_flow.items, @intCast(remaining_semantic_rows), .committed, ); try std.testing.expectEqual( remaining_semantic_rows, @as(u32, complete.facts.semantic_rows), ); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.stable, runtime.transcriptCommitDiagnostic().state, ); const stable = runtime.stableTranscriptProjectionForFlow(grown_flow.items) orelse return error.TestUnexpectedResult; try std.testing.expectEqual(exact_semantic_rows, stable.visual_offset); } test "oversized compatible growth materializes only each accepted projection" { const alloc = std.testing.allocator; const stable_flow = "base\n"; const filler_row_count: usize = @as(usize, std.math.maxInt(u16)) - 1; const staged_tail = "cap 0\n" ++ "cap 1\n" ++ "cap 2\n" ++ "cap 3\n" ++ "gap\n" ++ "tail 0\n" ++ "tail 1\n" ++ "tail 2\n" ++ "tail 3"; var grown_flow: std.ArrayList(u8) = .empty; defer grown_flow.deinit(alloc); try grown_flow.ensureTotalCapacity( alloc, stable_flow.len + filler_row_count * "x\n".len + staged_tail.len, ); grown_flow.appendSliceAssumeCapacity(stable_flow); for (0..filler_row_count) |_| { grown_flow.appendSliceAssumeCapacity("x\n"); } grown_flow.appendSliceAssumeCapacity(staged_tail); var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var sink = try tmp.dir.createFile( std.testing.io, "oversized-staged-projection.log", .{ .read = true }, ); defer sink.close(io_mod.getIo()); var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = transcriptTestLayout(20, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); try runtime.shadow_vt.?.feed(stable_flow); var stable_source = try testSource(alloc, stable_flow, runtime.layout.cols); defer stable_source.deinit(alloc); var stable_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &stable_source, ); defer stable_prepared.deinit(alloc); try installStableAnchorForTest( &runtime, alloc, stable_flow, stable_prepared.selection, 0, stable_prepared.cursor.cursor_row, stable_prepared.cursor.cursor_col, 412, ); var materialized_append: std.ArrayList(u8) = .empty; defer materialized_append.deinit(alloc); var first_source = try testSource(alloc, grown_flow.items, runtime.layout.cols); defer first_source.deinit(alloc); var first_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &first_source, ); defer first_prepared.deinit(alloc); const first_facts = runtime.planTranscriptScroll(&first_prepared); try std.testing.expectEqual( @as(u16, std.math.maxInt(u16)), first_facts.planned_rows, ); try std.testing.expect(first_facts.semantic_rows > first_facts.planned_rows); const first_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, first_facts.planned_rows, ); var first_plan = testPaintPlan(&runtime, first_prepared.selection); var first_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &first_source, &first_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(first_plan), &first_plan, first_scroll_plan, ); defer first_transition.deinit(alloc); try std.testing.expectEqual( @as(u32, std.math.maxInt(u16)), first_transition.visual_offset, ); try std.testing.expectEqual( first_facts.target_visual_offset + 4, first_transition.total_visual_rows, ); try std.testing.expectEqual(@as(u16, 5), first_transition.cursor_row); try std.testing.expectEqual(@as(u16, 1), first_transition.cursor_col); try std.testing.expect(!first_transition.document_append.isEmpty()); try std.testing.expect(first_transition.materialized.flow_len.? < grown_flow.items.len); try materialized_append.appendSlice( alloc, first_transition.document_append.bytes, ); var first_painter = PreparedTranscriptFramePainter{ .runtime = &runtime, .alloc = alloc, .prepared = &first_prepared, }; var metrics: Metrics = .{}; const first_result = try render_engine.frame_builder.buildAndFlushFrame( alloc, &runtime, &metrics, .{ .plan = first_plan, .scroll_plan = first_scroll_plan, .document_append = first_transition.document_append, .body_painter = .{ .ctx = &first_painter, .paint = PreparedTranscriptFramePainter.paint, }, }, ); try std.testing.expectEqual( render_engine.terminal_diff.ShadowCommitState.committed, first_result.shadow_state, ); var row_text: std.ArrayList(u8) = .empty; defer row_text.deinit(alloc); try runtime.shadow_vt.?.rowTextTrimmed(1, &row_text); try std.testing.expectEqualStrings("cap 0", row_text.items); row_text.clearRetainingCapacity(); try runtime.shadow_vt.?.rowTextTrimmed(4, &row_text); try std.testing.expectEqualStrings("cap 3", row_text.items); runtime.consumeTranscriptTransition(alloc, &first_transition, first_result); const after_first = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.recovering, after_first.state, ); try std.testing.expectEqual( first_facts.semantic_rows - first_facts.planned_rows, after_first.remaining_inline_rows, ); try std.testing.expectEqual( @as(u32, std.math.maxInt(u16)), after_first.visual_offset, ); var retry_source = try testSource(alloc, grown_flow.items, runtime.layout.cols); defer retry_source.deinit(alloc); var retry_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &retry_source, ); defer retry_prepared.deinit(alloc); const retry_facts = runtime.planTranscriptScroll(&retry_prepared); try std.testing.expectEqual( after_first.remaining_inline_rows, retry_facts.semantic_rows, ); const retry_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, retry_facts.planned_rows, ); var retry_plan = testPaintPlan(&runtime, retry_prepared.selection); var retry_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &retry_source, &retry_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(retry_plan), &retry_plan, retry_scroll_plan, ); defer retry_transition.deinit(alloc); try std.testing.expectEqual( retry_facts.target_visual_offset, retry_transition.visual_offset, ); try std.testing.expect(!retry_transition.document_append.isEmpty()); try std.testing.expectEqual( first_transition.cursor_row, retry_transition.document_append.start_row, ); try std.testing.expectEqual( first_transition.cursor_col, retry_transition.document_append.start_col, ); try materialized_append.appendSlice( alloc, retry_transition.document_append.bytes, ); try expectDocumentWireEqualsLogical( alloc, grown_flow.items[stable_flow.len..], materialized_append.items, ); var retry_painter = PreparedTranscriptFramePainter{ .runtime = &runtime, .alloc = alloc, .prepared = &retry_prepared, }; const retry_result = try render_engine.frame_builder.buildAndFlushFrame( alloc, &runtime, &metrics, .{ .plan = retry_plan, .scroll_plan = retry_scroll_plan, .document_append = retry_transition.document_append, .body_painter = .{ .ctx = &retry_painter, .paint = PreparedTranscriptFramePainter.paint, }, }, ); try std.testing.expectEqual( render_engine.terminal_diff.ShadowCommitState.committed, retry_result.shadow_state, ); row_text.clearRetainingCapacity(); try runtime.shadow_vt.?.rowTextTrimmed(1, &row_text); try std.testing.expectEqualStrings("tail 0", row_text.items); row_text.clearRetainingCapacity(); try runtime.shadow_vt.?.rowTextTrimmed(4, &row_text); try std.testing.expectEqualStrings("tail 3", row_text.items); runtime.consumeTranscriptTransition(alloc, &retry_transition, retry_result); const stable = runtime.stableTranscriptProjectionForFlow(grown_flow.items) orelse return error.TestUnexpectedResult; try std.testing.expectEqual(retry_facts.target_visual_offset, stable.visual_offset); try std.testing.expectEqual(retry_transition.cursor_row, stable.cursor_row); try std.testing.expectEqual(retry_transition.cursor_col, stable.cursor_col); } test "folded inexact growth advances only materialized viewport rows" { const alloc = std.testing.allocator; const cols: u16 = 16; const viewport_rows: u16 = 4; const growth_rows: usize = @as(usize, viewport_rows) * 2; const growth_rows_u32: u32 = @intCast(growth_rows); const initial_owned_top = viewport_rows + 1; const content_bottom = initial_owned_top + viewport_rows - 1; const stable_flow = "summary"; var seed_transition = try createProductionCappedFoldedRecoveryTransition( alloc, cols, viewport_rows, ); defer seed_transition.deinit(alloc); try std.testing.expectEqual(@as(?usize, null), seed_transition.materialized.flow_len); try std.testing.expect( seed_transition.semantic_rows > seed_transition.scroll_plan.requested_inline_advance_rows, ); var grown_flow: std.ArrayList(u8) = .empty; defer grown_flow.deinit(alloc); try grown_flow.ensureTotalCapacity( alloc, stable_flow.len + growth_rows * "\ng00000".len, ); grown_flow.appendSliceAssumeCapacity(stable_flow); var growth_index: usize = 0; while (growth_index < growth_rows) : (growth_index += 1) { var line_buf: [16]u8 = undefined; const line = try std.fmt.bufPrint( &line_buf, "\ng{d:0>5}", .{growth_index}, ); grown_flow.appendSliceAssumeCapacity(line); } var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var sink = try tmp.dir.createFile( std.testing.io, "folded-staged-projection.log", .{ .read = true }, ); defer sink.close(io_mod.getIo()); var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = transcriptTestLayout( cols, content_bottom + 4, content_bottom, ), .owned_top_row = initial_owned_top, .full_transcript = .{ .depth = .full }, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); try runtime.shadow_vt.?.feed("shell0\r\nshell1\r\nshell2\r\nshell3"); try runtime.folded_command_blocks.append(alloc, .{ .summary_transcript_index = 1, }); var fold_index: usize = 0; while (fold_index < viewport_rows) : (fold_index += 1) { var fold_buf: [16]u8 = undefined; const fold = try std.fmt.bufPrint(&fold_buf, "fold{d}", .{fold_index}); try appendFoldedLineForTest(&runtime, alloc, 0, fold); } var metrics: Metrics = .{}; var stable_source = try testFoldedSource( alloc, stable_flow, runtime.layout.cols, 1, ); defer stable_source.deinit(alloc); var stable_fixed_prepared: ?transcript_painter.PreparedTranscriptSurfacePaint = null; defer if (stable_fixed_prepared) |*prepared| prepared.deinit(alloc); const stable_frame = try solveTestTranscriptFixedPointFrame( &runtime, alloc, &metrics, &stable_source, 4, &stable_fixed_prepared, ); const stable_fixed = &stable_fixed_prepared.?; try std.testing.expectEqual(@as(u32, 0), preparedVisualOffsetForTest(stable_fixed)); try std.testing.expectEqual( @as(u32, viewport_rows), totalVisualRowsForTest(stable_fixed.sourceVisualRows()), ); try std.testing.expectEqual( stable_frame.layout.owned_top, stable_frame.scroll_plan.post_scroll_owned_top, ); var stable_plan = testPaintPlanForFrame( stable_frame.layout, stable_fixed.selection, stable_fixed.cursor.cursor_row, stable_fixed.cursor.cursor_col, ); var stable_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &stable_source, stable_fixed, render_engine.frame_layout.CommittedLayoutSnapshot.fromLayout(stable_frame.layout), &stable_plan, stable_frame.scroll_plan, ); defer stable_transition.deinit(alloc); var stable_painter = PreparedTranscriptFramePainter{ .runtime = &runtime, .alloc = alloc, .prepared = stable_fixed, }; const stable_result = try render_engine.frame_builder.buildAndFlushFrame( alloc, &runtime, &metrics, .{ .plan = stable_plan, .scroll_plan = stable_frame.scroll_plan, .body_painter = .{ .ctx = &stable_painter, .paint = PreparedTranscriptFramePainter.paint, }, }, ); runtime.ackPreservedRowReleaseAssumeValid( stable_frame.scroll_plan, stable_result.terminal_scroll_rows_committed, ); runtime.consumeTranscriptTransition(alloc, &stable_transition, stable_result); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.stable, runtime.transcriptCommitDiagnostic().state, ); var row_text: std.ArrayList(u8) = .empty; defer row_text.deinit(alloc); try runtime.shadow_vt.?.rowTextTrimmed(initial_owned_top, &row_text); try std.testing.expectEqualStrings("│ fold0", row_text.items); row_text.clearRetainingCapacity(); try runtime.shadow_vt.?.rowTextTrimmed(content_bottom, &row_text); try std.testing.expectEqualStrings("│ fold3", row_text.items); var history: std.ArrayList(u8) = .empty; defer history.deinit(alloc); var seed_source = try testFoldedSource( alloc, grown_flow.items, runtime.layout.cols, 1, ); defer seed_source.deinit(alloc); seed_source.preview.natural_visual_rows = @intCast(@as(u32, viewport_rows) + growth_rows_u32); const seed_release = render_engine.frame_layout.solveWithPreservedRowRelease(.{ .terminal = runtime.layout, .owned_top = runtime.owned_top_row, .footer = .{ .natural_rows = 8, .min_rows = 8, .max_rows = 8, }, .transcript = seed_source.preview, .prior = runtime.committed_frame_layout, }, 0); const compact_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, seed_release.released_rows, seed_transition.scroll_plan.requested_inline_advance_rows, ); try std.testing.expectEqualDeep(seed_transition.scroll_plan, compact_scroll_plan); try std.testing.expectEqual( seed_release.layout.owned_top, seed_transition.scroll_plan.post_scroll_owned_top, ); const seed_target_layout = render_engine.frame_layout.CommittedLayoutSnapshot.fromLayout(seed_release.layout); try std.testing.expectEqualDeep(seed_transition.target_layout, seed_target_layout); var seed_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &seed_source, ); defer seed_prepared.deinit(alloc); const seed_target_offset = preparedVisualOffsetForTest(&seed_prepared); try std.testing.expectEqual(growth_rows_u32, seed_target_offset); const seed_total_visual_rows = totalVisualRowsForTest(seed_prepared.sourceVisualRows()); try std.testing.expectEqual( @as(u32, viewport_rows) + growth_rows_u32, seed_total_visual_rows, ); const seed_projection = try transcript_painter.stagePreparedTranscriptForVisualOffset( alloc, runtime.layout, &seed_prepared, seed_release.layout.transcript_area, seed_transition.visual_offset, ); try std.testing.expectEqual( seed_transition.materialized.flow_len, seed_projection.flow_end, ); try std.testing.expectEqual( seed_transition.materialized.visual_rows, seed_prepared.projection_visual_rows.?, ); try std.testing.expectEqual( seed_transition.materialized.visual_offset, seed_transition.visual_offset, ); var contract_selection = seed_prepared.selection; contract_selection.line_count = seed_transition.selection.line_count; contract_selection.tail_kind = seed_transition.selection.tail_kind; try std.testing.expectEqualDeep(seed_transition.selection, contract_selection); try std.testing.expectEqual( seed_transition.materialized.cursor_row, seed_prepared.cursor.cursor_row, ); try std.testing.expectEqual( seed_transition.materialized.cursor_col, seed_prepared.cursor.cursor_col, ); try std.testing.expectEqual( seed_transition.cursor_row, seed_prepared.cursor.cursor_row, ); try std.testing.expectEqual( seed_transition.cursor_col, seed_prepared.cursor.cursor_col, ); try std.testing.expectEqual( seed_transition.replaceable_last_line, seed_prepared.replaceable_last_line, ); try std.testing.expectEqual( seed_transition.replaceable_start, seed_prepared.replaceable_start, ); try std.testing.expectEqual( seed_transition.replaceable_row, seed_prepared.cursor.replaceable_row, ); try std.testing.expectEqualSlices( usize, seed_transition.folded_summary_indices, seed_source.folded_summary_indices, ); try std.testing.expect(seed_transition.document_append.isEmpty()); try std.testing.expectEqual(@as(usize, 0), seed_transition.document_append_bytes.len); const compact_target_flow = try alloc.dupe(u8, grown_flow.items); if (seed_transition.target_flow.len > 0) alloc.free(seed_transition.target_flow); seed_transition.target_flow = compact_target_flow; seed_transition.target_cache.clearRetainingCapacity(); try seed_transition.target_cache.appendSlice(alloc, grown_flow.items); seed_transition.semantic_rows = growth_rows_u32; seed_transition.semantic_progress_rows = growth_rows_u32; seed_transition.selection = seed_prepared.selection; seed_transition.source_endpoint_visual_offset = seed_target_offset; seed_transition.total_visual_rows = seed_total_visual_rows; try appendGridRows( alloc, runtime.shadow_vt.?, 1, seed_transition.scroll_plan.terminal_scroll_rows, &history, &row_text, ); const seed_plan = testPaintPlanForFrame( seed_release.layout, seed_prepared.selection, seed_prepared.cursor.cursor_row, seed_prepared.cursor.cursor_col, ); var seed_painter = PreparedTranscriptFramePainter{ .runtime = &runtime, .alloc = alloc, .prepared = &seed_prepared, }; const seed_result = try render_engine.frame_builder.buildAndFlushFrame( alloc, &runtime, &metrics, .{ .plan = seed_plan, .scroll_plan = seed_transition.scroll_plan, .document_append = seed_transition.document_append, .body_painter = .{ .ctx = &seed_painter, .paint = PreparedTranscriptFramePainter.paint, }, }, ); try std.testing.expectEqual( render_engine.terminal_diff.ShadowCommitState.committed, seed_result.shadow_state, ); try std.testing.expectEqual( seed_transition.scroll_plan.terminal_scroll_rows, seed_result.terminal_scroll_rows_committed, ); try std.testing.expect(!seed_result.document_append_committed); runtime.ackPreservedRowReleaseAssumeValid( seed_transition.scroll_plan, seed_result.terminal_scroll_rows_committed, ); runtime.consumeTranscriptTransition(alloc, &seed_transition, seed_result); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.recovering, runtime.transcriptCommitDiagnostic().state, ); try std.testing.expectEqual( seed_transition.visual_offset, runtime.transcriptCommitDiagnostic().visual_offset, ); try std.testing.expectEqual( growth_rows_u32 - seed_transition.scroll_plan.requested_inline_advance_rows, runtime.transcriptCommitDiagnostic().remaining_inline_rows, ); var attempt_count: usize = 0; while (runtime.transcriptCommitDiagnostic().state == .recovering) : (attempt_count += 1) { var source = try testFoldedSource( alloc, grown_flow.items, runtime.layout.cols, 1, ); defer source.deinit(alloc); source.preview.natural_visual_rows = @intCast(@as(u32, viewport_rows) + growth_rows_u32); var prepared: ?transcript_painter.PreparedTranscriptSurfacePaint = null; defer if (prepared) |*paint| paint.deinit(alloc); const frame = try solveTestTranscriptFixedPointFrame( &runtime, alloc, &metrics, &source, 8, &prepared, ); const paint = &prepared.?; try std.testing.expectEqual( frame.layout.owned_top, frame.scroll_plan.post_scroll_owned_top, ); try std.testing.expectEqual( viewport_rows, frame.scroll_plan.acceptedInlineRows( frame.scroll_plan.terminal_scroll_rows, ), ); try appendGridRows( alloc, runtime.shadow_vt.?, 1, frame.scroll_plan.terminal_scroll_rows, &history, &row_text, ); var plan = testPaintPlanForFrame( frame.layout, paint.selection, paint.cursor.cursor_row, paint.cursor.cursor_col, ); var transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &source, paint, render_engine.frame_layout.CommittedLayoutSnapshot.fromLayout(frame.layout), &plan, frame.scroll_plan, ); defer transition.deinit(alloc); try std.testing.expect(transition.document_append.isEmpty()); try std.testing.expectEqual( @as(?usize, null), transition.materialized.flow_len, ); try std.testing.expectEqual( viewport_rows, transition.materialized.visual_rows, ); var painter = PreparedTranscriptFramePainter{ .runtime = &runtime, .alloc = alloc, .prepared = paint, }; const result = try render_engine.frame_builder.buildAndFlushFrame( alloc, &runtime, &metrics, .{ .plan = plan, .scroll_plan = frame.scroll_plan, .document_append = transition.document_append, .body_painter = .{ .ctx = &painter, .paint = PreparedTranscriptFramePainter.paint, }, }, ); try std.testing.expectEqual( render_engine.terminal_diff.ShadowCommitState.committed, result.shadow_state, ); try std.testing.expectEqual( frame.scroll_plan.terminal_scroll_rows, result.terminal_scroll_rows_committed, ); runtime.ackPreservedRowReleaseAssumeValid( frame.scroll_plan, result.terminal_scroll_rows_committed, ); runtime.consumeTranscriptTransition(alloc, &transition, result); } try std.testing.expectEqual(@as(usize, 1), attempt_count); const stable = runtime.stableTranscriptProjectionForFlow(grown_flow.items) orelse return error.TestUnexpectedResult; try std.testing.expectEqual(growth_rows_u32, stable.visual_offset); try std.testing.expectEqual(@as(u16, 4), stable.cursor_row); try std.testing.expectEqual(@as(u16, 7), stable.cursor_col); try std.testing.expectEqual(runtime.shadow_vt.?.cursor_row, stable.cursor_row); try std.testing.expectEqual(runtime.shadow_vt.?.cursor_col, stable.cursor_col); const followup_rows: usize = 5; const followup_append = "\ng00008\ng00009\ng00010\ng00011\ng00012"; var followup_flow: std.ArrayList(u8) = .empty; defer followup_flow.deinit(alloc); try followup_flow.appendSlice(alloc, grown_flow.items); try followup_flow.appendSlice(alloc, followup_append); var followup_source = try testFoldedSource( alloc, followup_flow.items, runtime.layout.cols, 1, ); defer followup_source.deinit(alloc); followup_source.preview.natural_visual_rows = @intCast(@as(usize, viewport_rows) + growth_rows + followup_rows); var followup_prepared: ?transcript_painter.PreparedTranscriptSurfacePaint = null; defer if (followup_prepared) |*paint| paint.deinit(alloc); const followup_frame = try solveTestTranscriptFixedPointFrame( &runtime, alloc, &metrics, &followup_source, 4, &followup_prepared, ); const followup_paint = &followup_prepared.?; try std.testing.expectEqual( followup_frame.layout.owned_top, followup_frame.scroll_plan.post_scroll_owned_top, ); const followup_facts = runtime.planTranscriptScroll(followup_paint); try std.testing.expectEqual(@as(u32, 1), followup_facts.semantic_rows); try std.testing.expectEqual(@as(u16, 1), followup_facts.planned_rows); try appendGridRows( alloc, runtime.shadow_vt.?, 1, followup_frame.scroll_plan.terminal_scroll_rows, &history, &row_text, ); var followup_plan = testPaintPlanForFrame( followup_frame.layout, followup_paint.selection, followup_paint.cursor.cursor_row, followup_paint.cursor.cursor_col, ); var followup_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &followup_source, followup_paint, render_engine.frame_layout.CommittedLayoutSnapshot.fromLayout(followup_frame.layout), &followup_plan, followup_frame.scroll_plan, ); defer followup_transition.deinit(alloc); try expectDocumentWireEqualsLogical( alloc, followup_append, followup_transition.document_append.bytes, ); try std.testing.expectEqual( followup_flow.items.len, followup_transition.materialized.flow_len.?, ); try std.testing.expectEqual(@as(u16, 4), followup_transition.document_append.start_row); try std.testing.expectEqual(@as(u16, 7), followup_transition.document_append.start_col); var followup_painter = PreparedTranscriptFramePainter{ .runtime = &runtime, .alloc = alloc, .prepared = followup_paint, }; const followup_result = try render_engine.frame_builder.buildAndFlushFrame( alloc, &runtime, &metrics, .{ .plan = followup_plan, .scroll_plan = followup_frame.scroll_plan, .document_append = followup_transition.document_append, .body_painter = .{ .ctx = &followup_painter, .paint = PreparedTranscriptFramePainter.paint, }, }, ); try std.testing.expectEqual( render_engine.terminal_diff.ShadowCommitState.committed, followup_result.shadow_state, ); try std.testing.expect(followup_result.document_append_committed); try std.testing.expectEqual( @as(u16, 1), followup_result.terminal_scroll_rows_committed, ); runtime.ackPreservedRowReleaseAssumeValid( followup_frame.scroll_plan, followup_result.terminal_scroll_rows_committed, ); runtime.consumeTranscriptTransition(alloc, &followup_transition, followup_result); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.stable, runtime.transcriptCommitDiagnostic().state, ); try appendGridRows( alloc, runtime.shadow_vt.?, followup_frame.layout.transcript_area.top, followup_frame.layout.transcript_area.height(), &history, &row_text, ); const expected_history = "shell0\n" ++ "shell1\n" ++ "shell2\n" ++ "shell3\n" ++ "│ fold0\n" ++ "│ fold1\n" ++ "│ fold2\n" ++ "│ fold3\n" ++ "g00000\n" ++ "g00001\n" ++ "g00002\n" ++ "g00003\n" ++ "g00004\n" ++ "g00005\n" ++ "g00006\n" ++ "g00007\n" ++ "g00008\n" ++ "g00009\n" ++ "g00010\n" ++ "g00011\n" ++ "g00012"; try std.testing.expectEqualStrings(expected_history, history.items); try std.testing.expect(std.mem.indexOf(u8, history.items, "\n\n") == null); fold_index = 0; while (fold_index < viewport_rows) : (fold_index += 1) { var expected_buf: [16]u8 = undefined; const expected = try std.fmt.bufPrint( &expected_buf, "│ fold{d}", .{fold_index}, ); try std.testing.expectEqual( @as(usize, 1), countExactHistoryLine(history.items, expected), ); } growth_index = 0; while (growth_index < growth_rows + followup_rows) : (growth_index += 1) { var expected_buf: [16]u8 = undefined; const expected = try std.fmt.bufPrint( &expected_buf, "g{d:0>5}", .{growth_index}, ); try std.testing.expectEqual( @as(usize, 1), countExactHistoryLine(history.items, expected), ); } } test "staged soft-wrapped presentation resumes across committed projections" { const alloc = std.testing.allocator; const cols: u16 = 8; const stable_flow = "base\n"; const filler_row_count: usize = @as(usize, std.math.maxInt(u16)) - 3; const link_url = "https://staged.example"; const link_params = "id=fx-42"; const presentation_open = "\x1b[1;31m\x1b[9m" ++ "\x1b]8;" ++ link_params ++ ";" ++ link_url ++ "\x1b\\"; const presentation_close = "\x1b]8;;\x1b\\\x1b[29m\x1b[0m"; const resume_bytes = "\x1b[0m\x1b[1m\x1b[9m\x1b[31m" ++ "\x1b]8;" ++ link_params ++ ";" ++ link_url ++ "\x1b\\"; const steady_bytes = "\x1b]8;;\x1b\\\x1b[0m\r"; const linked_row_symbols = "0123456789A"; var linked_text = try alloc.alloc(u8, linked_row_symbols.len * cols); defer alloc.free(linked_text); for (linked_row_symbols, 0..) |symbol, row| { @memset(linked_text[row * cols .. (row + 1) * cols], symbol); } var grown_flow: std.ArrayList(u8) = .empty; defer grown_flow.deinit(alloc); try grown_flow.ensureTotalCapacity( alloc, stable_flow.len + filler_row_count * "x\n".len + presentation_open.len + linked_text.len + presentation_close.len, ); grown_flow.appendSliceAssumeCapacity(stable_flow); for (0..filler_row_count) |_| { grown_flow.appendSliceAssumeCapacity("x\n"); } grown_flow.appendSliceAssumeCapacity(presentation_open); grown_flow.appendSliceAssumeCapacity(linked_text); grown_flow.appendSliceAssumeCapacity(presentation_close); var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var sink = try tmp.dir.createFile( std.testing.io, "staged-presentation.log", .{ .read = true }, ); defer sink.close(io_mod.getIo()); var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = transcriptTestLayout(cols, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); try runtime.shadow_vt.?.feed(stable_flow); var stable_source = try testSource(alloc, stable_flow, runtime.layout.cols); defer stable_source.deinit(alloc); var stable_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &stable_source, ); defer stable_prepared.deinit(alloc); try installStableAnchorForTest( &runtime, alloc, stable_flow, stable_prepared.selection, 0, stable_prepared.cursor.cursor_row, stable_prepared.cursor.cursor_col, 414, ); var raw_append_bytes: std.ArrayList(u8) = .empty; defer raw_append_bytes.deinit(alloc); var first_source = try testSource(alloc, grown_flow.items, runtime.layout.cols); defer first_source.deinit(alloc); var first_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &first_source, ); defer first_prepared.deinit(alloc); const first_facts = runtime.planTranscriptScroll(&first_prepared); try std.testing.expectEqual( @as(u16, std.math.maxInt(u16)), first_facts.planned_rows, ); const first_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, first_facts.planned_rows, ); var first_plan = testPaintPlan(&runtime, first_prepared.selection); var first_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &first_source, &first_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(first_plan), &first_plan, first_scroll_plan, ); defer first_transition.deinit(alloc); const first_raw_end = first_transition.materialized.flow_len orelse return error.TestUnexpectedResult; const first_raw_append = grown_flow.items[stable_flow.len..first_raw_end]; try std.testing.expect(first_raw_end < grown_flow.items.len); try std.testing.expect(std.mem.endsWith( u8, first_transition.document_append.bytes, steady_bytes, )); try expectDocumentWireEqualsLogical( alloc, first_raw_append, first_transition.document_append.bytes[0 .. first_transition.document_append.bytes.len - steady_bytes.len], ); try raw_append_bytes.appendSlice(alloc, first_raw_append); var first_painter = PreparedTranscriptFramePainter{ .runtime = &runtime, .alloc = alloc, .prepared = &first_prepared, }; var metrics: Metrics = .{}; const first_result = try render_engine.frame_builder.buildAndFlushFrame( alloc, &runtime, &metrics, .{ .plan = first_plan, .scroll_plan = first_scroll_plan, .document_append = first_transition.document_append, .body_painter = .{ .ctx = &first_painter, .paint = PreparedTranscriptFramePainter.paint, }, }, ); try std.testing.expectEqual( render_engine.terminal_diff.ShadowCommitState.committed, first_result.shadow_state, ); try std.testing.expect(runtime.shadow_vt.?.current_style.eql(.{})); try std.testing.expect(!runtime.shadow_vt.?.pending_wrap); var row_text: std.ArrayList(u8) = .empty; defer row_text.deinit(alloc); for ("2345", 1..) |symbol, row| { row_text.clearRetainingCapacity(); try runtime.shadow_vt.?.rowTextTrimmed(@intCast(row), &row_text); var expected_row: [cols]u8 = undefined; @memset(&expected_row, symbol); try std.testing.expectEqualStrings(&expected_row, row_text.items); const cell = runtime.shadow_vt.?.cellAt(@intCast(row), 1).?; try std.testing.expect(cell.style.flags.bold); try std.testing.expect(cell.style.flags.strike); try std.testing.expect(cell.style.fg.eql(.{ .indexed = 1 })); try std.testing.expect(cell.style.hyperlink_id != 0); try std.testing.expectEqualStrings( link_url, runtime.shadow_vt.?.hyperlinkUrl(cell.style.hyperlink_id).?, ); } runtime.consumeTranscriptTransition(alloc, &first_transition, first_result); const after_first = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.recovering, after_first.state, ); try std.testing.expect(after_first.remaining_inline_rows > 0); var retry_source = try testSource(alloc, grown_flow.items, runtime.layout.cols); defer retry_source.deinit(alloc); var retry_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &retry_source, ); defer retry_prepared.deinit(alloc); const retry_facts = runtime.planTranscriptScroll(&retry_prepared); const retry_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, retry_facts.planned_rows, ); var retry_plan = testPaintPlan(&runtime, retry_prepared.selection); var retry_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &retry_source, &retry_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(retry_plan), &retry_plan, retry_scroll_plan, ); defer retry_transition.deinit(alloc); const retry_raw_append = grown_flow.items[first_raw_end..]; try std.testing.expect(std.mem.startsWith( u8, retry_transition.document_append.bytes, resume_bytes, )); try expectDocumentWireEqualsLogical( alloc, retry_raw_append, retry_transition.document_append.bytes[resume_bytes.len..], ); try raw_append_bytes.appendSlice(alloc, retry_raw_append); try std.testing.expectEqualStrings( grown_flow.items[stable_flow.len..], raw_append_bytes.items, ); var retry_painter = PreparedTranscriptFramePainter{ .runtime = &runtime, .alloc = alloc, .prepared = &retry_prepared, }; const retry_result = try render_engine.frame_builder.buildAndFlushFrame( alloc, &runtime, &metrics, .{ .plan = retry_plan, .scroll_plan = retry_scroll_plan, .document_append = retry_transition.document_append, .body_painter = .{ .ctx = &retry_painter, .paint = PreparedTranscriptFramePainter.paint, }, }, ); try std.testing.expectEqual( render_engine.terminal_diff.ShadowCommitState.committed, retry_result.shadow_state, ); try std.testing.expect(runtime.shadow_vt.?.current_style.eql(.{})); try std.testing.expect(!runtime.shadow_vt.?.pending_wrap); for ("789A", 1..) |symbol, row| { row_text.clearRetainingCapacity(); try runtime.shadow_vt.?.rowTextTrimmed(@intCast(row), &row_text); var expected_row: [cols]u8 = undefined; @memset(&expected_row, symbol); try std.testing.expectEqualStrings(&expected_row, row_text.items); const cell = runtime.shadow_vt.?.cellAt(@intCast(row), 1).?; try std.testing.expect(cell.style.flags.bold); try std.testing.expect(cell.style.flags.strike); try std.testing.expect(cell.style.fg.eql(.{ .indexed = 1 })); try std.testing.expect(cell.style.hyperlink_id != 0); try std.testing.expectEqualStrings( link_url, runtime.shadow_vt.?.hyperlinkUrl(cell.style.hyperlink_id).?, ); } runtime.consumeTranscriptTransition(alloc, &retry_transition, retry_result); const stable = runtime.stableTranscriptProjectionForFlow(grown_flow.items) orelse return error.TestUnexpectedResult; try std.testing.expectEqual(retry_facts.target_visual_offset, stable.visual_offset); try std.testing.expectEqual( @as(?usize, grown_flow.items.len), retry_transition.materialized.flow_len, ); } test "uncommitted stable-origin append retries from the materialized source endpoint" { const alloc = std.testing.allocator; const stable_flow = "base\n"; const grown_flow = stable_flow ++ "0\n1\n2\n3\n4"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); var stable_source = try testSource(alloc, stable_flow, runtime.layout.cols); defer stable_source.deinit(alloc); var stable_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &stable_source, ); defer stable_prepared.deinit(alloc); try installStableAnchorForTest( &runtime, alloc, stable_flow, stable_prepared.selection, 0, stable_prepared.cursor.cursor_row, stable_prepared.cursor.cursor_col, 413, ); var first_source = try testSource(alloc, grown_flow, runtime.layout.cols); defer first_source.deinit(alloc); var first_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &first_source, ); defer first_prepared.deinit(alloc); const first_facts = runtime.planTranscriptScroll(&first_prepared); const first_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, first_facts.planned_rows, ); var first_plan = testPaintPlan(&runtime, first_prepared.selection); var first_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &first_source, &first_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(first_plan), &first_plan, first_scroll_plan, ); defer first_transition.deinit(alloc); try expectDocumentWireEqualsLogical( alloc, grown_flow[stable_flow.len..], first_transition.document_append.bytes, ); runtime.consumeTranscriptTransition(alloc, &first_transition, .{ .bytes_written = 1, .changed_cells = 0, .full_repaint = false, .terminal_scroll_rows_committed = 0, .document_append_committed = false, .committed_cursor_row = stable_prepared.cursor.cursor_row, .committed_cursor_col = stable_prepared.cursor.cursor_col, .shadow_state = .terminal_partial_write, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); var retry_source = try testSource(alloc, grown_flow, runtime.layout.cols); defer retry_source.deinit(alloc); var retry_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &retry_source, ); defer retry_prepared.deinit(alloc); const retry_facts = runtime.planTranscriptScroll(&retry_prepared); const retry_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, retry_facts.planned_rows, ); var retry_plan = testPaintPlan(&runtime, retry_prepared.selection); var retry_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &retry_source, &retry_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(retry_plan), &retry_plan, retry_scroll_plan, ); defer retry_transition.deinit(alloc); try expectDocumentWireEqualsLogical( alloc, grown_flow[stable_flow.len..], retry_transition.document_append.bytes, ); } test "staged wrapped projection clips paint and flow at one renderer boundary" { const alloc = std.testing.allocator; const flow = "base\nabcdefghijklmnopqrst"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(5, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); var source = try testSource(alloc, flow, runtime.layout.cols); defer source.deinit(alloc); var prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &source, ); defer prepared.deinit(alloc); const projection = try transcript_painter.stagePreparedTranscriptForVisualOffset( alloc, runtime.layout, &prepared, .{ .top = 1, .bottom = 2 }, 2, ); try std.testing.expectEqual(@as(?usize, "base\n".len + 15), projection.flow_end); try std.testing.expectEqual(@as(usize, 1), prepared.selection.start_line); try std.testing.expectEqual(@as(u16, 1), prepared.selection.partial_skip_rows); try std.testing.expectEqual(@as(u16, 3), prepared.cursor.cursor_row); try std.testing.expectEqual(@as(u16, 1), prepared.cursor.cursor_col); var shadow = try vt_emulator.Grid.init( alloc, runtime.layout.cols, runtime.layout.rows, ); defer shadow.deinit(); var plan = testPaintPlan(&runtime, prepared.selection); plan.viewport = prepared.selection; var surface = try render_engine.frame_surface.FrameSurface.initFromShadow( alloc, plan, shadow, ); defer surface.deinit(); _ = try runtime.paintPreparedTranscriptIntoSurface( alloc, &surface, &prepared, ); var target = try surface.copyToTargetGrid(alloc); defer target.deinit(); var row_text: std.ArrayList(u8) = .empty; defer row_text.deinit(alloc); try target.rowTextTrimmed(1, &row_text); try std.testing.expectEqualStrings("fghij", row_text.items); row_text.clearRetainingCapacity(); try target.rowTextTrimmed(2, &row_text); try std.testing.expectEqualStrings("klmno", row_text.items); try std.testing.expectEqual(@as(u16, 3), target.cursor_row); try std.testing.expectEqual(@as(u16, 1), target.cursor_col); } test "width change does not compare semantic offsets across projections" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); try installStableAnchorForTest(&runtime, alloc, "stable", testSelection(4), 4, 3, 1, 42); runtime.layout = transcriptTestLayout(10, 8, 4); var prepared = transcript_painter.PreparedTranscriptSurfacePaint{ .bytes = try alloc.dupe(u8, "stable"), .selection = testSelection(9), }; defer prepared.deinit(alloc); try prepared.line_visual_rows.appendSlice(alloc, &([_]u16{1} ** 16)); const facts = runtime.planTranscriptScroll(&prepared); try std.testing.expect(facts.source_compatible); try std.testing.expectEqual(@as(u16, 0), facts.semantic_rows); try std.testing.expectEqual(@as(u16, 0), facts.planned_rows); } test "stable origin projection rebase narrow to wide survives repeated partial and restores append" { const alloc = std.testing.allocator; const flow = "abcdefghijklmnopqrstuvwxyz0123456789\n" ++ "one\n" ++ "two\n" ++ "three\n" ++ "four\n" ++ "five\n"; const grown_flow = flow ++ "six\nseven\neight\n"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(5, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); var stable_source = try testSource(alloc, flow, runtime.layout.cols); defer stable_source.deinit(alloc); var stable_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &stable_source, ); defer stable_prepared.deinit(alloc); const narrow_offset = preparedVisualOffsetForTest(&stable_prepared); try std.testing.expect(narrow_offset > 0); try installStableAnchorForTest( &runtime, alloc, flow, stable_prepared.selection, narrow_offset, stable_prepared.cursor.cursor_row, stable_prepared.cursor.cursor_col, 420, ); runtime.layout = transcriptTestLayout(20, 8, 4); var first_source = try testSource(alloc, flow, runtime.layout.cols); defer first_source.deinit(alloc); var first_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &first_source, ); defer first_prepared.deinit(alloc); const wide_offset = preparedVisualOffsetForTest(&first_prepared); try std.testing.expect(wide_offset < narrow_offset); const first_facts = runtime.planTranscriptScroll(&first_prepared); try std.testing.expect(first_facts.recovery_rebase); try std.testing.expectEqual(@as(u16, 0), first_facts.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 0), first_facts.semantic_rows); try std.testing.expectEqual(@as(u16, 0), first_facts.planned_rows); const first_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, first_facts.planned_rows, ); var first_plan = testPaintPlan(&runtime, first_prepared.selection); var first_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &first_source, &first_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(first_plan), &first_plan, first_scroll_plan, ); defer first_transition.deinit(alloc); try std.testing.expect(first_transition.recovery_rebase); try std.testing.expect(first_transition.document_append.isEmpty()); runtime.consumeTranscriptTransition(alloc, &first_transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = 0, .document_append_committed = false, .committed_cursor_row = first_transition.cursor_row, .committed_cursor_col = first_transition.cursor_col, .shadow_state = .terminal_partial_write, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); var recovery = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.recovering, recovery.state, ); try std.testing.expectEqual(wide_offset, recovery.visual_offset); try std.testing.expectEqual(@as(u16, 0), recovery.remaining_inline_rows); try std.testing.expect(runtime.stableTranscriptProjectionForFlow(flow) == null); var repeated_source = try testSource(alloc, flow, runtime.layout.cols); defer repeated_source.deinit(alloc); var repeated_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &repeated_source, ); defer repeated_prepared.deinit(alloc); const repeated_facts = runtime.planTranscriptScroll(&repeated_prepared); try std.testing.expect(repeated_facts.recovery_rebase); try std.testing.expectEqual(@as(u16, 0), repeated_facts.semantic_rows); try std.testing.expectEqual(@as(u16, 0), repeated_facts.planned_rows); const repeated_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, repeated_facts.planned_rows, ); var repeated_plan = testPaintPlan(&runtime, repeated_prepared.selection); var repeated_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &repeated_source, &repeated_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(repeated_plan), &repeated_plan, repeated_scroll_plan, ); defer repeated_transition.deinit(alloc); try std.testing.expect(repeated_transition.document_append.isEmpty()); runtime.consumeTranscriptTransition(alloc, &repeated_transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = 0, .document_append_committed = false, .committed_cursor_row = repeated_transition.cursor_row, .committed_cursor_col = repeated_transition.cursor_col, .shadow_state = .terminal_partial_write, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); recovery = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.recovering, recovery.state, ); try std.testing.expectEqual(wide_offset, recovery.visual_offset); var complete_source = try testSource(alloc, flow, runtime.layout.cols); defer complete_source.deinit(alloc); var complete_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &complete_source, ); defer complete_prepared.deinit(alloc); const complete_facts = runtime.planTranscriptScroll(&complete_prepared); try std.testing.expect(complete_facts.recovery_rebase); const complete_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, complete_facts.planned_rows, ); var complete_plan = testPaintPlan(&runtime, complete_prepared.selection); var complete_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &complete_source, &complete_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(complete_plan), &complete_plan, complete_scroll_plan, ); defer complete_transition.deinit(alloc); runtime.consumeTranscriptTransition(alloc, &complete_transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = complete_scroll_plan.terminal_scroll_rows, .document_append_committed = false, .committed_cursor_row = complete_transition.cursor_row, .committed_cursor_col = complete_transition.cursor_col, .shadow_state = .committed, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.stable, runtime.transcriptCommitDiagnostic().state, ); const stable = runtime.stableTranscriptProjectionForFlow(flow) orelse return error.TestUnexpectedResult; try std.testing.expectEqual(wide_offset, stable.visual_offset); var append_source = try testSource(alloc, grown_flow, runtime.layout.cols); defer append_source.deinit(alloc); var append_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &append_source, ); defer append_prepared.deinit(alloc); const append_facts = runtime.planTranscriptScroll(&append_prepared); try std.testing.expect(!append_facts.recovery_rebase); try std.testing.expect(append_facts.semantic_rows > 0); const append_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, append_facts.planned_rows, ); var append_plan = testPaintPlan(&runtime, append_prepared.selection); var append_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &append_source, &append_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(append_plan), &append_plan, append_scroll_plan, ); defer append_transition.deinit(alloc); try expectDocumentWireEqualsLogical( alloc, grown_flow[flow.len..], append_transition.document_append.bytes, ); } test "stable origin projection rebase wide to narrow retains debt until exhausted" { const alloc = std.testing.allocator; const flow = "stable\nbase\n"; const grown_flow = flow ++ "next\n"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); try enterResizeReflowRecovery( &runtime, alloc, null, .terminal_partial_write, ); var repeated_source = try testSource(alloc, flow, runtime.layout.cols); defer repeated_source.deinit(alloc); var repeated_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &repeated_source, ); defer repeated_prepared.deinit(alloc); const repeated_facts = runtime.planTranscriptScroll(&repeated_prepared); try std.testing.expect(repeated_facts.recovery_rebase); try std.testing.expectEqual(@as(u16, 1), repeated_facts.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 0), repeated_facts.semantic_rows); try std.testing.expectEqual(@as(u16, 1), repeated_facts.planned_rows); const repeated_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, repeated_facts.planned_rows, ); var repeated_plan = testPaintPlan(&runtime, repeated_prepared.selection); var repeated_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &repeated_source, &repeated_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(repeated_plan), &repeated_plan, repeated_scroll_plan, ); defer repeated_transition.deinit(alloc); try std.testing.expect(repeated_transition.document_append.isEmpty()); runtime.consumeTranscriptTransition(alloc, &repeated_transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = 0, .document_append_committed = false, .committed_cursor_row = repeated_transition.cursor_row, .committed_cursor_col = repeated_transition.cursor_col, .shadow_state = .terminal_partial_write, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); var recovery = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.recovering, recovery.state, ); try std.testing.expectEqual(@as(u16, 1), recovery.remaining_inline_rows); try std.testing.expect(runtime.stableTranscriptProjectionForFlow(flow) == null); var complete_source = try testSource(alloc, flow, runtime.layout.cols); defer complete_source.deinit(alloc); var complete_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &complete_source, ); defer complete_prepared.deinit(alloc); const complete_facts = runtime.planTranscriptScroll(&complete_prepared); try std.testing.expect(complete_facts.recovery_rebase); try std.testing.expectEqual(@as(u16, 1), complete_facts.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 1), complete_facts.planned_rows); const complete_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, complete_facts.planned_rows, ); var complete_plan = testPaintPlan(&runtime, complete_prepared.selection); var complete_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &complete_source, &complete_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(complete_plan), &complete_plan, complete_scroll_plan, ); defer complete_transition.deinit(alloc); try std.testing.expect(complete_transition.document_append.isEmpty()); runtime.consumeTranscriptTransition(alloc, &complete_transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = complete_scroll_plan.terminal_scroll_rows, .document_append_committed = false, .committed_cursor_row = complete_transition.cursor_row, .committed_cursor_col = complete_transition.cursor_col, .shadow_state = .committed, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); recovery = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.stable, recovery.state, ); try std.testing.expectEqual(@as(u16, 0), recovery.remaining_inline_rows); var append_source = try testSource(alloc, grown_flow, runtime.layout.cols); defer append_source.deinit(alloc); var append_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &append_source, ); defer append_prepared.deinit(alloc); const append_facts = runtime.planTranscriptScroll(&append_prepared); try std.testing.expect(!append_facts.recovery_rebase); try std.testing.expect(append_facts.semantic_rows > 0); const append_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, append_facts.planned_rows, ); var append_plan = testPaintPlan(&runtime, append_prepared.selection); var append_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &append_source, &append_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(append_plan), &append_plan, append_scroll_plan, ); defer append_transition.deinit(alloc); try expectDocumentWireEqualsLogical( alloc, grown_flow[flow.len..], append_transition.document_append.bytes, ); } test "stable origin projection rebase measured history is not semantic debt" { const alloc = std.testing.allocator; const flow = "0\n1\n2\n3\n4\n5\n6\n7"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 10, 6), .owned_top_row = 1, }; defer runtime.deinit(alloc); var stable_source = try testSource(alloc, flow, runtime.layout.cols); defer stable_source.deinit(alloc); var stable_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &stable_source, ); defer stable_prepared.deinit(alloc); const stable_offset = preparedVisualOffsetForTest(&stable_prepared); try installStableAnchorForTest( &runtime, alloc, flow, stable_prepared.selection, stable_offset, stable_prepared.cursor.cursor_row, stable_prepared.cursor.cursor_col, 421, ); runtime.layout = transcriptTestLayout(20, 8, 4); runtime.resize_history_row_delta = 2; var first_source = try testSource(alloc, flow, runtime.layout.cols); defer first_source.deinit(alloc); var first_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &first_source, ); defer first_prepared.deinit(alloc); const measured_offset = preparedVisualOffsetForTest(&first_prepared); try std.testing.expect(measured_offset > stable_offset); const first_facts = runtime.planTranscriptScroll(&first_prepared); try std.testing.expect(first_facts.recovery_rebase); try std.testing.expectEqual(@as(u16, 0), first_facts.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 0), first_facts.semantic_rows); try std.testing.expectEqual(@as(u16, 0), first_facts.planned_rows); const first_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, first_facts.planned_rows, ); var first_plan = testPaintPlan(&runtime, first_prepared.selection); var first_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &first_source, &first_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(first_plan), &first_plan, first_scroll_plan, ); defer first_transition.deinit(alloc); runtime.consumeTranscriptTransition(alloc, &first_transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = 0, .document_append_committed = false, .committed_cursor_row = first_transition.cursor_row, .committed_cursor_col = first_transition.cursor_col, .shadow_state = .terminal_partial_write, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.recovering, runtime.transcriptCommitDiagnostic().state, ); try std.testing.expect(runtime.stableTranscriptProjectionForFlow(flow) == null); var repeated_source = try testSource(alloc, flow, runtime.layout.cols); defer repeated_source.deinit(alloc); var repeated_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &repeated_source, ); defer repeated_prepared.deinit(alloc); const repeated_facts = runtime.planTranscriptScroll(&repeated_prepared); try std.testing.expect(repeated_facts.recovery_rebase); try std.testing.expectEqual(@as(u16, 0), repeated_facts.semantic_rows); try std.testing.expectEqual(@as(u16, 0), repeated_facts.planned_rows); const repeated_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, repeated_facts.planned_rows, ); var repeated_plan = testPaintPlan(&runtime, repeated_prepared.selection); var repeated_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &repeated_source, &repeated_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(repeated_plan), &repeated_plan, repeated_scroll_plan, ); defer repeated_transition.deinit(alloc); runtime.consumeTranscriptTransition(alloc, &repeated_transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = repeated_scroll_plan.terminal_scroll_rows, .document_append_committed = false, .committed_cursor_row = repeated_transition.cursor_row, .committed_cursor_col = repeated_transition.cursor_col, .shadow_state = .committed, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); runtime.resize_history_row_delta = null; const projection = runtime.stableTranscriptProjectionForFlow(flow) orelse return error.TestUnexpectedResult; try std.testing.expectEqual(measured_offset, projection.visual_offset); } test "overlay frame retains an exact-end measured history floor after reset" { const alloc = std.testing.allocator; const flow = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(48, 40, 36), .owned_top_row = 1, }; defer runtime.deinit(alloc); var source = try testSource(alloc, flow, runtime.layout.cols); defer source.deinit(alloc); var prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &source, ); defer prepared.deinit(alloc); var history_offset: u32 = 0; for (prepared.sourceVisualRows()) |rows| history_offset += rows; var history_selection = prepared.selection; history_selection.start_line = prepared.sourceVisualRows().len; history_selection.partial_skip_rows = 0; history_selection.last_visible_row = 1; var plan = testPaintPlan(&runtime, prepared.selection); plan.activity = .{ .overlay_entry = 1 }; const committed_layout = render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(plan); runtime.committed_frame_layout = committed_layout; try installStableAnchorForTest( &runtime, alloc, flow, history_selection, history_offset, 1, 1, committed_layout.layout_id, ); const facts = runtime.planTranscriptScroll(&prepared); const scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, facts.planned_rows, ); var transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &source, &prepared, committed_layout, &plan, scroll_plan, ); defer transition.deinit(alloc); try std.testing.expectEqual(history_offset, transition.visual_offset); try std.testing.expectEqual( prepared.sourceVisualRows().len, transition.selection.start_line, ); } test "zero measured reverse restores exact source origin" { const alloc = std.testing.allocator; const flow = "line 00 abcdefghij\n" ++ "line 01 abcdefghij\n" ++ "line 02 abcdefghij\n" ++ "line 03 abcdefghij\n" ++ "line 04 abcdefghij\n" ++ "line 05 abcdefghij\n" ++ "line 06 abcdefghij\n" ++ "line 07 abcdefghij\n" ++ "line 08 abcdefghij\n" ++ "line 09 abcdefghij\n"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(24, 12, 8), .owned_top_row = 1, }; defer runtime.deinit(alloc); var wide_source = try testSource(alloc, flow, runtime.layout.cols); defer wide_source.deinit(alloc); var narrow_source = try testSource(alloc, flow, 12); defer narrow_source.deinit(alloc); try expectZeroReverseRestoresSourceOrigin( &runtime, alloc, &wide_source, &narrow_source, 2, .seeded_exact, ); } test "zero measured reverse restores folded source origin without endpoint" { const alloc = std.testing.allocator; const flow = "head abcdefghij\n" ++ "summary\n" ++ "tail 00 abcdefghij\n" ++ "tail 01 abcdefghij\n" ++ "tail 02 abcdefghij\n" ++ "tail 03 abcdefghij\n" ++ "tail 04 abcdefghij\n" ++ "tail 05 abcdefghij\n"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(24, 12, 8), .owned_top_row = 1, .full_transcript = .{ .depth = .full }, }; defer runtime.deinit(alloc); try runtime.folded_command_blocks.append(alloc, .{ .summary_transcript_index = 2, }); try appendFoldedLineForTest(&runtime, alloc, 0, "folded output alpha"); try appendFoldedLineForTest(&runtime, alloc, 0, "folded output beta"); var wide_source = try testFoldedSource(alloc, flow, runtime.layout.cols, 2); defer wide_source.deinit(alloc); var narrow_source = try testFoldedSource(alloc, flow, 12, 2); defer narrow_source.deinit(alloc); try expectZeroReverseRestoresSourceOrigin( &runtime, alloc, &wide_source, &narrow_source, 2, .natural_null, ); } test "zero measured reverse restores folded source origin with inexact endpoint mapping" { const alloc = std.testing.allocator; const flow = "head abcdefghij\n" ++ "summary\n" ++ "tail 00 abcdefghij\n" ++ "tail 01 abcdefghij\n" ++ "tail 02 abcdefghij\n" ++ "tail 03 abcdefghij\n" ++ "tail 04 abcdefghij\n" ++ "tail 05 abcdefghij\n"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(24, 12, 8), .owned_top_row = 1, .full_transcript = .{ .depth = .full }, }; defer runtime.deinit(alloc); try runtime.folded_command_blocks.append(alloc, .{ .summary_transcript_index = 2, }); try appendFoldedLineForTest(&runtime, alloc, 0, "folded output alpha"); try appendFoldedLineForTest(&runtime, alloc, 0, "folded output beta"); var wide_source = try testFoldedSource(alloc, flow, runtime.layout.cols, 2); defer wide_source.deinit(alloc); var narrow_source = try testFoldedSource(alloc, flow, 12, 2); defer narrow_source.deinit(alloc); try expectZeroReverseRestoresSourceOrigin( &runtime, alloc, &wide_source, &narrow_source, 2, .seeded_inexact, ); } test "measured history origin matrix restores hard soft wide and folded sources" { const alloc = std.testing.allocator; const Case = struct { source_cols: u16, target_cols: u16, positive_delta: i32, flow: []const u8, folded: bool = false, endpoint_mode: MeasuredEndpointMode, }; const cases = [_]Case{ .{ .source_cols = 24, .target_cols = 12, .positive_delta = 2, .flow = "hard 00\nhard 01\nhard 02\nhard 03\nhard 04\nhard 05\n" ++ "hard 06\nhard 07\nhard 08\nhard 09\nhard 10\nhard 11\n", .endpoint_mode = .seeded_exact, }, .{ .source_cols = 20, .target_cols = 8, .positive_delta = 3, .flow = "soft-wrap-alpha-000\nsoft-wrap-beta-001\nsoft-wrap-gamma-002\n" ++ "soft-wrap-delta-003\nsoft-wrap-epsilon-004\nsoft-wrap-zeta-005\n" ++ "soft-wrap-eta-006\nsoft-wrap-theta-007\nsoft-wrap-iota-008\n" ++ "soft-wrap-kappa-009\n", .endpoint_mode = .seeded_exact, }, .{ .source_cols = 16, .target_cols = 8, .positive_delta = 2, .flow = "wide 界界 00\nwide 界界 01\nwide 界界 02\nwide 界界 03\n" ++ "wide 界界 04\nwide 界界 05\nwide 界界 06\nwide 界界 07\n" ++ "wide 界界 08\nwide 界界 09\nwide 界界 10\nwide 界界 11\n", .endpoint_mode = .seeded_exact, }, .{ .source_cols = 24, .target_cols = 12, .positive_delta = 2, .flow = "head abcdefghij\nsummary\ntail 00 abcdefghij\n" ++ "tail 01 abcdefghij\ntail 02 abcdefghij\ntail 03 abcdefghij\n" ++ "tail 04 abcdefghij\ntail 05 abcdefghij\n", .folded = true, .endpoint_mode = .natural_null, }, .{ .source_cols = 30, .target_cols = 10, .positive_delta = 3, .flow = "head abcdefghij\nsummary\ntail 00 abcdefghij\n" ++ "tail 01 abcdefghij\ntail 02 abcdefghij\ntail 03 abcdefghij\n" ++ "tail 04 abcdefghij\ntail 05 abcdefghij\n", .folded = true, .endpoint_mode = .seeded_inexact, }, }; for (cases) |case| { var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(case.source_cols, 12, 8), .owned_top_row = 1, .full_transcript = .{ .depth = if (case.folded) .full else .inline_mode }, }; defer runtime.deinit(alloc); if (case.folded) { try runtime.folded_command_blocks.append(alloc, .{ .summary_transcript_index = 2, }); try appendFoldedLineForTest(&runtime, alloc, 0, "folded output alpha"); try appendFoldedLineForTest(&runtime, alloc, 0, "folded output beta"); } var wide_source = if (case.folded) try testFoldedSource(alloc, case.flow, case.source_cols, 2) else try testSource(alloc, case.flow, case.source_cols); defer wide_source.deinit(alloc); var narrow_source = if (case.folded) try testFoldedSource(alloc, case.flow, case.target_cols, 2) else try testSource(alloc, case.flow, case.target_cols); defer narrow_source.deinit(alloc); try expectZeroReverseRestoresSourceOrigin( &runtime, alloc, &wide_source, &narrow_source, case.positive_delta, case.endpoint_mode, ); } } test "positive measured shrink retains split source origin without exact endpoint" { const alloc = std.testing.allocator; const flow = "line 00 abcdefghij\n" ++ "line 01 abcdefghij\n" ++ "line 02 abcdefghij\n" ++ "line 03 abcdefghij\n" ++ "line 04 abcdefghij\n" ++ "line 05 abcdefghij\n" ++ "line 06 abcdefghij\n" ++ "line 07 abcdefghij\n" ++ "line 08 abcdefghij\n" ++ "line 09 abcdefghij\n"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(24, 12, 8), .owned_top_row = 1, }; defer runtime.deinit(alloc); var wide_source = try testSource(alloc, flow, runtime.layout.cols); defer wide_source.deinit(alloc); var wide_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &wide_source, ); defer wide_prepared.deinit(alloc); var split_selection = wide_prepared.selection; split_selection.split_active = true; split_selection.split_prefix_lines = 1; split_selection.split_suffix_start_line = split_selection.start_line; const source_origin = preparedVisualOffsetForTest(&wide_prepared); try installStableAnchorForTest( &runtime, alloc, flow, split_selection, source_origin, wide_prepared.cursor.cursor_row, wide_prepared.cursor.cursor_col, 705, ); runtime.layout = transcriptTestLayout(12, 8, 4); runtime.resize_history_row_delta = 2; var narrow_source = try testSource(alloc, flow, runtime.layout.cols); defer narrow_source.deinit(alloc); var narrow_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &narrow_source, ); defer narrow_prepared.deinit(alloc); const origin = narrow_prepared.measured_history_origin orelse return error.TestExpectedMeasuredHistoryOrigin; try std.testing.expectEqual(@as(u16, 24), origin.source_cols); try std.testing.expectEqual(source_origin, origin.source_visual_offset); try std.testing.expect(origin.endpoint == null); } test "zero measured history controls do not consume a nonqualifying origin" { const alloc = std.testing.allocator; const flow = "line 00 abcdefghij\n" ++ "line 01 abcdefghij\n" ++ "line 02 abcdefghij\n" ++ "line 03 abcdefghij\n" ++ "line 04 abcdefghij\n" ++ "line 05 abcdefghij\n"; { var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(12, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); var source = try testSource(alloc, flow, runtime.layout.cols); defer source.deinit(alloc); var prepared = try prepareTestSourceForCurrentArea(&runtime, alloc, &source); defer prepared.deinit(alloc); const offset = preparedVisualOffsetForTest(&prepared); try installStableAnchorForTest( &runtime, alloc, flow, prepared.selection, offset, prepared.cursor.cursor_row, prepared.cursor.cursor_col, 706, ); runtime.resize_history_row_delta = 0; var generic_zero = try prepareTestSourceForCurrentArea(&runtime, alloc, &source); defer generic_zero.deinit(alloc); try std.testing.expectEqual(offset, preparedVisualOffsetForTest(&generic_zero)); try std.testing.expect(!generic_zero.measured_history_origin_consumed); } { var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(12, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); var narrow_source = try testSource(alloc, flow, runtime.layout.cols); defer narrow_source.deinit(alloc); var narrow_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &narrow_source, ); defer narrow_prepared.deinit(alloc); const narrow_offset = preparedVisualOffsetForTest(&narrow_prepared); try installStableAnchorWithOriginForTest( &runtime, alloc, flow, narrow_prepared.selection, narrow_offset, narrow_prepared.cursor.cursor_row, narrow_prepared.cursor.cursor_col, 707, .{ .source_cols = 24, .source_visual_offset = 1, }, ); runtime.resize_history_row_delta = 0; var same_width = try prepareTestSourceForCurrentArea( &runtime, alloc, &narrow_source, ); defer same_width.deinit(alloc); try std.testing.expectEqual( narrow_offset, preparedVisualOffsetForTest(&same_width), ); try std.testing.expect(!same_width.measured_history_origin_consumed); runtime.layout = transcriptTestLayout(18, 10, 6); var middle_source = try testSource(alloc, flow, runtime.layout.cols); defer middle_source.deinit(alloc); var non_source_width = try prepareTestSourceForCurrentArea( &runtime, alloc, &middle_source, ); defer non_source_width.deinit(alloc); try std.testing.expect(!non_source_width.measured_history_origin_consumed); } } test "negative measured rewind consumes the retained exact origin" { const alloc = std.testing.allocator; const flow = "abcdefghijklmnopqrst\n" ++ "abcdefghijklmnopqrst\n" ++ "abcdefghijklmnopqrst\n" ++ "abcdefghijklmnopqrst\n" ++ "abcdefghijklmnopqrst\n" ++ "abcdefghijklmnopqrst\n" ++ "abcdefghijklmnopqrst\n" ++ "abcdefghijklmnopqrst\n" ++ "abcdefghijklmnopqrst\n" ++ "abcdefghijklmnopqrst\n" ++ "abcdefghijklmnopqrst\n" ++ "abcdefghijklmnopqrst\n"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(24, 12, 8), .owned_top_row = 1, }; defer runtime.deinit(alloc); var wide_source = try testSource(alloc, flow, runtime.layout.cols); defer wide_source.deinit(alloc); var wide_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &wide_source, ); defer wide_prepared.deinit(alloc); try setPreparedVisualOffsetForTest(&wide_prepared, 10); try installStableAnchorForTest( &runtime, alloc, flow, wide_prepared.selection, 10, wide_prepared.cursor.cursor_row, wide_prepared.cursor.cursor_col, 708, ); runtime.layout = transcriptTestLayout(12, 8, 4); runtime.resize_history_row_delta = 2; var narrow_source = try testSource(alloc, flow, runtime.layout.cols); defer narrow_source.deinit(alloc); var narrow_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &narrow_source, ); defer narrow_prepared.deinit(alloc); const origin = narrow_prepared.measured_history_origin orelse return error.TestExpectedMeasuredHistoryOrigin; narrow_prepared.measured_history_origin = .{ .source_cols = origin.source_cols, .source_visual_offset = origin.source_visual_offset, .endpoint = .{ .span_start_flow_offset = 210, .flow_offset = 231, .hard_row_cols = 24, }, }; try std.testing.expectEqual(@as(u32, 22), preparedVisualOffsetForTest(&narrow_prepared)); try commitPreparedForTest(&runtime, alloc, &narrow_source, &narrow_prepared); runtime.layout = transcriptTestLayout(18, 10, 6); runtime.resize_history_row_delta = -1; var rewind_source = try testSource(alloc, flow, runtime.layout.cols); defer rewind_source.deinit(alloc); var rewound = try prepareTestSourceForCurrentArea( &runtime, alloc, &rewind_source, ); defer rewound.deinit(alloc); try std.testing.expectEqual(@as(u32, 21), preparedVisualOffsetForTest(&rewound)); try std.testing.expect(rewound.measured_history_origin_consumed); try commitPreparedForTest(&runtime, alloc, &rewind_source, &rewound); const stable = runtime.stableTranscriptProjectionForFlow(flow) orelse return error.TestExpectedStableTranscript; try std.testing.expect(stable.measured_history_origin == null); } test "incompatible transcript replacement clears the retained origin" { const alloc = std.testing.allocator; const flow = "stable 0\nstable 1\nstable 2\nstable 3\nstable 4\nstable 5\n"; const replacement = "replacement 0\nreplacement 1\nreplacement 2\nreplacement 3\n"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(12, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); var stable_source = try testSource(alloc, flow, runtime.layout.cols); defer stable_source.deinit(alloc); var stable_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &stable_source, ); defer stable_prepared.deinit(alloc); try installStableAnchorWithOriginForTest( &runtime, alloc, flow, stable_prepared.selection, preparedVisualOffsetForTest(&stable_prepared), stable_prepared.cursor.cursor_row, stable_prepared.cursor.cursor_col, 709, .{ .source_cols = 24, .source_visual_offset = 2, }, ); var replacement_source = try testSource( alloc, replacement, runtime.layout.cols, ); defer replacement_source.deinit(alloc); var replacement_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &replacement_source, ); defer replacement_prepared.deinit(alloc); try commitPreparedForTest( &runtime, alloc, &replacement_source, &replacement_prepared, ); const stable = runtime.stableTranscriptProjectionForFlow(replacement) orelse return error.TestExpectedStableTranscript; try std.testing.expect(stable.measured_history_origin == null); } test "zero measured reverse restores source origin from recovery receipt" { const alloc = std.testing.allocator; const flow = "line 00 abcdefghij\n" ++ "line 01 abcdefghij\n" ++ "line 02 abcdefghij\n" ++ "line 03 abcdefghij\n" ++ "line 04 abcdefghij\n" ++ "line 05 abcdefghij\n" ++ "line 06 abcdefghij\n" ++ "line 07 abcdefghij\n" ++ "line 08 abcdefghij\n" ++ "line 09 abcdefghij\n" ++ "line 10 abcdefghij\n" ++ "line 11 abcdefghij\n"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(24, 12, 8), .owned_top_row = 1, }; defer runtime.deinit(alloc); var wide_source = try testSource(alloc, flow, runtime.layout.cols); defer wide_source.deinit(alloc); var wide_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &wide_source, ); defer wide_prepared.deinit(alloc); const natural_origin = preparedVisualOffsetForTest(&wide_prepared); const source_origin = natural_origin + 2; try setPreparedVisualOffsetForTest(&wide_prepared, source_origin); try installStableAnchorForTest( &runtime, alloc, flow, wide_prepared.selection, source_origin, wide_prepared.cursor.cursor_row, wide_prepared.cursor.cursor_col, 710, ); runtime.layout = transcriptTestLayout(12, 8, 4); runtime.resize_history_row_delta = 2; var narrow_source = try testSource(alloc, flow, runtime.layout.cols); defer narrow_source.deinit(alloc); var narrow_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &narrow_source, ); defer narrow_prepared.deinit(alloc); try commitPreparedPartiallyForTest( &runtime, alloc, &narrow_source, &narrow_prepared, ); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.recovering, runtime.transcriptCommitDiagnostic().state, ); try std.testing.expect(runtime.stableTranscriptProjectionForFlow(flow) == null); const recovering_origin = runtime.measuredHistoryOriginForFlow(flow) orelse return error.TestExpectedMeasuredHistoryOrigin; try std.testing.expectEqual( source_origin, recovering_origin.origin.source_visual_offset, ); runtime.layout = transcriptTestLayout(24, 12, 8); runtime.resize_history_row_delta = 0; var restored = try prepareTestSourceForCurrentArea( &runtime, alloc, &wide_source, ); defer restored.deinit(alloc); try std.testing.expectEqual( source_origin, preparedVisualOffsetForTest(&restored), ); try commitPreparedForTest(&runtime, alloc, &wide_source, &restored); const recovered = runtime.stableTranscriptProjectionForFlow(flow) orelse return error.TestExpectedStableTranscript; try std.testing.expectEqual(source_origin, recovered.visual_offset); try std.testing.expect(recovered.measured_history_origin == null); runtime.resize_history_row_delta = null; try expectRendererAppendRestored( &runtime, alloc, flow, flow ++ "recovered append 0\nrecovered append 1\nrecovered append 2\n" ++ "recovered append 3\nrecovered append 4\nrecovered append 5\n", ); } test "zero measured reverse restores source origin after compatible paint" { const alloc = std.testing.allocator; const flow = "line 00 abcdefghij\n" ++ "line 01 abcdefghij\n" ++ "line 02 abcdefghij\n" ++ "line 03 abcdefghij\n" ++ "line 04 abcdefghij\n" ++ "line 05 abcdefghij\n" ++ "line 06 abcdefghij\n" ++ "line 07 abcdefghij\n" ++ "line 08 abcdefghij\n" ++ "line 09 abcdefghij\n" ++ "line 10 abcdefghij\n" ++ "line 11 abcdefghij\n"; const grown_flow = flow ++ "compatible append\n"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(24, 12, 8), .owned_top_row = 1, }; defer runtime.deinit(alloc); var wide_source = try testSource(alloc, flow, runtime.layout.cols); defer wide_source.deinit(alloc); var wide_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &wide_source, ); defer wide_prepared.deinit(alloc); const source_origin = preparedVisualOffsetForTest(&wide_prepared) + 2; try setPreparedVisualOffsetForTest(&wide_prepared, source_origin); try installStableAnchorForTest( &runtime, alloc, flow, wide_prepared.selection, source_origin, wide_prepared.cursor.cursor_row, wide_prepared.cursor.cursor_col, 720, ); runtime.layout = transcriptTestLayout(12, 8, 4); runtime.resize_history_row_delta = 2; var narrow_source = try testSource(alloc, flow, runtime.layout.cols); defer narrow_source.deinit(alloc); var narrow_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &narrow_source, ); defer narrow_prepared.deinit(alloc); try commitPreparedForTest( &runtime, alloc, &narrow_source, &narrow_prepared, ); runtime.resize_history_row_delta = null; var grown_narrow_source = try testSource( alloc, grown_flow, runtime.layout.cols, ); defer grown_narrow_source.deinit(alloc); var grown_narrow_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &grown_narrow_source, ); defer grown_narrow_prepared.deinit(alloc); try commitPreparedForTest( &runtime, alloc, &grown_narrow_source, &grown_narrow_prepared, ); const compatible = runtime.stableTranscriptProjectionForFlow(grown_flow) orelse return error.TestExpectedStableTranscript; const compatible_origin = compatible.measured_history_origin orelse return error.TestExpectedMeasuredHistoryOrigin; try std.testing.expectEqual( source_origin, compatible_origin.source_visual_offset, ); runtime.layout = transcriptTestLayout(24, 12, 8); runtime.resize_history_row_delta = 0; var grown_wide_source = try testSource( alloc, grown_flow, runtime.layout.cols, ); defer grown_wide_source.deinit(alloc); var restored = try prepareTestSourceForCurrentArea( &runtime, alloc, &grown_wide_source, ); defer restored.deinit(alloc); try std.testing.expectEqual( source_origin, preparedVisualOffsetForTest(&restored), ); try commitPreparedForTest( &runtime, alloc, &grown_wide_source, &restored, ); const restored_projection = runtime.stableTranscriptProjectionForFlow( grown_flow, ) orelse return error.TestExpectedStableTranscript; try std.testing.expectEqual(source_origin, restored_projection.visual_offset); try std.testing.expect(restored_projection.measured_history_origin == null); runtime.resize_history_row_delta = null; try expectRendererAppendRestored( &runtime, alloc, grown_flow, grown_flow ++ "post-restore append 0\npost-restore append 1\n" ++ "post-restore append 2\npost-restore append 3\n" ++ "post-restore append 4\npost-restore append 5\n", ); } test "unrepresentable zero reverse rebases to natural source projection" { const alloc = std.testing.allocator; const flow = "line 00\n" ++ "line 01\n" ++ "line 02\n" ++ "line 03\n" ++ "line 04\n" ++ "line 05\n"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(12, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); var narrow_source = try testSource(alloc, flow, runtime.layout.cols); defer narrow_source.deinit(alloc); var narrow_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &narrow_source, ); defer narrow_prepared.deinit(alloc); try installStableAnchorWithOriginForTest( &runtime, alloc, flow, narrow_prepared.selection, preparedVisualOffsetForTest(&narrow_prepared), narrow_prepared.cursor.cursor_row, narrow_prepared.cursor.cursor_col, 730, .{ .source_cols = 24, .source_visual_offset = 99, }, ); runtime.layout = transcriptTestLayout(24, 12, 8); runtime.resize_history_row_delta = null; var wide_source = try testSource(alloc, flow, runtime.layout.cols); defer wide_source.deinit(alloc); var natural = try prepareTestSourceForCurrentArea( &runtime, alloc, &wide_source, ); defer natural.deinit(alloc); const natural_offset = preparedVisualOffsetForTest(&natural); runtime.resize_history_row_delta = 0; var rebased = try prepareTestSourceForCurrentArea( &runtime, alloc, &wide_source, ); defer rebased.deinit(alloc); try std.testing.expectEqual( natural_offset, preparedVisualOffsetForTest(&rebased), ); try std.testing.expect(rebased.measured_history_origin_consumed); } test "measured resize history excludes synthetic reflow fallback" { const alloc = std.testing.allocator; const flow = "stable 00\nstable 01\nstable 02\nstable 03\nstable 04\n" ++ "stable 05\nstable 06\nstable 07\nstable 08\nstable 09\n" ++ "stable 10\nstable 11\nstable 12\nstable 13\nstable 14\n" ++ "stable 15\nstable 16\nstable 17\nstable 18\nstable 19\n"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); try installStableAnchorForTest( &runtime, alloc, flow, testSelectionInArea(0, 1, 4), 0, 4, 1, 46, ); const shadow = runtime.shadow_vt.?; shadow.cells[19].codepoint = 'x'; shadow.cursor_row = 8; shadow.cursor_col = 1; runtime.viewport_clear_pending = true; runtime.layout = transcriptTestLayout(10, 8, 2); runtime.resize_history_row_delta = 2; var measured_source = try testSource(alloc, flow, runtime.layout.cols); defer measured_source.deinit(alloc); var measured_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &measured_source, ); defer measured_prepared.deinit(alloc); const measured = runtime.planTranscriptScroll(&measured_prepared); try std.testing.expect(measured.recovery_rebase); try std.testing.expectEqual(@as(u16, 0), measured.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 0), measured.semantic_rows); try std.testing.expectEqual(@as(u16, 0), measured.planned_rows); if (measured_prepared.measured_history_origin == null) { return error.TestExpectedMeasuredHistoryOrigin; } const prepared_endpoint = measured_prepared.measured_history_origin.?.endpoint; runtime.resize_history_row_delta = 0; var zero_source = try testSource(alloc, flow, runtime.layout.cols); defer zero_source.deinit(alloc); var zero_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &zero_source, ); defer zero_prepared.deinit(alloc); const zero = runtime.planTranscriptScroll(&zero_prepared); try std.testing.expect(zero.recovery_rebase); try std.testing.expectEqual(@as(u16, 0), zero.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 0), zero.semantic_rows); try std.testing.expectEqual(@as(u16, 0), zero.planned_rows); runtime.resize_history_row_delta = null; var fallback_source = try testSource(alloc, flow, runtime.layout.cols); defer fallback_source.deinit(alloc); var fallback_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &fallback_source, ); defer fallback_prepared.deinit(alloc); const fallback = runtime.planTranscriptScroll(&fallback_prepared); try std.testing.expect(fallback.recovery_rebase); try std.testing.expectEqual(@as(u16, 2), fallback.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 0), fallback.semantic_rows); try std.testing.expectEqual(@as(u16, 2), fallback.planned_rows); runtime.resize_history_row_delta = 2; try commitPreparedForTest( &runtime, alloc, &measured_source, &measured_prepared, ); const committed = runtime.stableTranscriptProjectionForFlow(flow) orelse return error.TestExpectedStableTranscript; try std.testing.expectEqualDeep( prepared_endpoint, committed.measured_history_origin.?.endpoint, ); } test "measured resize releases one physical boundary row" { const alloc = std.testing.allocator; const flow = "stable 00\nstable 01\nstable 02\nstable 03\nstable 04\n" ++ "stable 05\nstable 06\nstable 07\nstable 08\nstable 09\n" ++ "stable 10\nstable 11\nstable 12\nstable 13\nstable 14\n" ++ "stable 15\nstable 16\nstable 17\nstable 18\nstable 19\n"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 8, 4), .owned_top_row = 1, .full_transcript = .{ .depth = .full }, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); try installStableAnchorForTest( &runtime, alloc, flow, testSelectionInArea(0, 1, 4), 0, 4, 1, 46, ); const shadow = runtime.shadow_vt.?; shadow.cells[19].codepoint = 'x'; shadow.cursor_row = 8; shadow.cursor_col = 1; runtime.viewport_clear_pending = true; runtime.layout = transcriptTestLayout(10, 8, 3); runtime.resize_history_row_delta = 2; var source = try testSource(alloc, flow, runtime.layout.cols); defer source.deinit(alloc); var prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &source, ); defer prepared.deinit(alloc); const facts = runtime.planTranscriptScroll(&prepared); try std.testing.expect(facts.recovery_rebase); try std.testing.expectEqual(@as(u16, 1), facts.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 0), facts.semantic_rows); try std.testing.expectEqual(@as(u16, 1), facts.planned_rows); } test "same-source semantic debt survives narrow to wide projection rebase" { const alloc = std.testing.allocator; const flow = "abcdefghijk\n" ++ "one\n" ++ "two\n" ++ "three\n" ++ "four\n" ++ "five\n"; const grown_flow = flow ++ "six\nseven\n"; const appended_flow = grown_flow ++ "eight\nnine\n"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(5, 10, 8), .owned_top_row = 1, }; defer runtime.deinit(alloc); _ = try enterRendererDerivedSemanticRecovery( &runtime, alloc, flow, 4, 1, null, ); const retained_semantic_rows = runtime.transcriptCommitDiagnostic().remaining_inline_rows; try std.testing.expect(retained_semantic_rows > 0); runtime.layout = transcriptTestLayout(20, 10, 4); const first = try consumeRendererRecoveryAttempt( &runtime, alloc, flow, 0, .terminal_partial_write, ); try std.testing.expect(first.facts.recovery_rebase); try std.testing.expectEqual(@as(u16, 0), first.facts.resize_reflow_rows); try std.testing.expectEqual(retained_semantic_rows, first.facts.semantic_rows); try std.testing.expectEqual(retained_semantic_rows, first.facts.planned_rows); try std.testing.expectEqual( retained_semantic_rows, runtime.transcriptCommitDiagnostic().remaining_inline_rows, ); try std.testing.expectEqual( first.facts.target_visual_offset, first.visual_offset, ); const growth = try consumeRendererRecoveryAttempt( &runtime, alloc, grown_flow, 0, .terminal_partial_write, ); try std.testing.expect(growth.facts.recovery_rebase); try std.testing.expect(growth.facts.semantic_rows > retained_semantic_rows); const growth_rows = growth.facts.semantic_rows - retained_semantic_rows; try std.testing.expectEqual( first.visual_offset + growth_rows, growth.visual_offset, ); try std.testing.expect( runtime.stableTranscriptProjectionForFlow(grown_flow) == null, ); const repeated = try consumeRendererRecoveryAttempt( &runtime, alloc, grown_flow, 0, .terminal_partial_write, ); try std.testing.expectEqual( growth.facts.semantic_rows, repeated.facts.semantic_rows, ); try std.testing.expectEqual(growth.visual_offset, repeated.visual_offset); const complete = try consumeRendererRecoveryAttempt( &runtime, alloc, grown_flow, @intCast(repeated.facts.semantic_rows), .committed, ); try std.testing.expectEqual( repeated.facts.semantic_rows, complete.facts.semantic_rows, ); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.stable, runtime.transcriptCommitDiagnostic().state, ); const stable = runtime.stableTranscriptProjectionForFlow(grown_flow) orelse return error.TestUnexpectedResult; try std.testing.expectEqual(growth.visual_offset, stable.visual_offset); try expectRendererAppendRestored( &runtime, alloc, grown_flow, appended_flow, ); } test "same-source semantic and resize debt survive wide to narrow rebase in order" { const alloc = std.testing.allocator; const flow = "stable\nbase\n"; const grown_flow = flow ++ "growth one\ngrowth two\n"; const appended_flow = grown_flow ++ "after one\nafter two\n"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); try enterResizeReflowRecoveryWithAcceptedRows( &runtime, alloc, null, .terminal_partial_write, 0, ); const growth = try consumeRendererRecoveryAttempt( &runtime, alloc, grown_flow, 0, .terminal_partial_write, ); try std.testing.expectEqual(@as(u16, 2), growth.facts.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 2), growth.facts.semantic_rows); runtime.layout = transcriptTestLayout(5, 8, 2); const first = try consumeRendererRecoveryAttempt( &runtime, alloc, grown_flow, 1, .terminal_partial_write, ); try std.testing.expect(first.facts.recovery_rebase); try std.testing.expectEqual(@as(u16, 2), first.facts.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 2), first.facts.semantic_rows); try std.testing.expectEqual(@as(u16, 4), first.facts.planned_rows); try std.testing.expectEqual( @as(u16, 3), runtime.transcriptCommitDiagnostic().remaining_inline_rows, ); const second = try consumeRendererRecoveryAttempt( &runtime, alloc, grown_flow, 1, .terminal_partial_write, ); try std.testing.expectEqual(@as(u16, 1), second.facts.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 2), second.facts.semantic_rows); try std.testing.expectEqual( @as(u16, 2), runtime.transcriptCommitDiagnostic().remaining_inline_rows, ); try std.testing.expectEqual(first.visual_offset, second.visual_offset); const third = try consumeRendererRecoveryAttempt( &runtime, alloc, grown_flow, 1, .terminal_partial_write, ); try std.testing.expectEqual(@as(u16, 0), third.facts.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 2), third.facts.semantic_rows); try std.testing.expectEqual( @as(u16, 1), runtime.transcriptCommitDiagnostic().remaining_inline_rows, ); try std.testing.expectEqual(first.visual_offset, third.visual_offset); const complete = try consumeRendererRecoveryAttempt( &runtime, alloc, grown_flow, 1, .committed, ); try std.testing.expectEqual(@as(u16, 0), complete.facts.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 1), complete.facts.semantic_rows); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.stable, runtime.transcriptCommitDiagnostic().state, ); const stable = runtime.stableTranscriptProjectionForFlow(grown_flow) orelse return error.TestUnexpectedResult; try std.testing.expectEqual(first.visual_offset, stable.visual_offset); try expectRendererAppendRestored( &runtime, alloc, grown_flow, appended_flow, ); } test "same-source semantic debt survives measured history rebase" { const alloc = std.testing.allocator; const flow = "0\n1\n2\n3\n4\n5\n6\n7\n"; const grown_flow = flow ++ "8\n9\n"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 10, 8), .owned_top_row = 1, }; defer runtime.deinit(alloc); _ = try enterRendererDerivedSemanticRecovery( &runtime, alloc, flow, 4, 1, null, ); const retained_semantic_rows = runtime.transcriptCommitDiagnostic().remaining_inline_rows; try std.testing.expect(retained_semantic_rows > 1); runtime.resize_history_row_delta = 2; const measured = try consumeRendererRecoveryAttempt( &runtime, alloc, flow, 0, .committed, ); try std.testing.expect(measured.facts.recovery_rebase); try std.testing.expectEqual(@as(u16, 0), measured.facts.resize_reflow_rows); try std.testing.expectEqual( retained_semantic_rows, measured.facts.semantic_rows, ); try std.testing.expectEqual( retained_semantic_rows, runtime.transcriptCommitDiagnostic().remaining_inline_rows, ); runtime.resize_history_row_delta = null; const repeated = try consumeRendererRecoveryAttempt( &runtime, alloc, flow, 1, .terminal_partial_write, ); try std.testing.expectEqual( retained_semantic_rows, repeated.facts.semantic_rows, ); try std.testing.expectEqual( retained_semantic_rows - 1, runtime.transcriptCommitDiagnostic().remaining_inline_rows, ); try std.testing.expectEqual(measured.visual_offset, repeated.visual_offset); try std.testing.expect( runtime.stableTranscriptProjectionForFlow(flow) == null, ); const remaining = retained_semantic_rows - 1; const complete = try consumeRendererRecoveryAttempt( &runtime, alloc, flow, @intCast(remaining), .committed, ); try std.testing.expectEqual(remaining, complete.facts.semantic_rows); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.stable, runtime.transcriptCommitDiagnostic().state, ); const stable = runtime.stableTranscriptProjectionForFlow(flow) orelse return error.TestUnexpectedResult; try std.testing.expectEqual(measured.visual_offset, stable.visual_offset); try expectRendererAppendRestored(&runtime, alloc, flow, grown_flow); } test "finalized transcript transition owns append suffix and commits one anchor" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); try installStableAnchorForTest(&runtime, alloc, "old\n", testSelection(0), 0, 3, 1, 43); var source = try testSource(alloc, "old\nnew\n", runtime.layout.cols); defer source.deinit(alloc); var prepared = transcript_painter.PreparedTranscriptSurfacePaint{ .bytes = source.bytes, .owns_bytes = false, .selection = testSelection(1), .cursor = .{ .cursor_row = 4, .cursor_col = 1, .replaceable_row = 4 }, }; defer prepared.deinit(alloc); try prepared.line_visual_rows.appendSlice(alloc, &.{ 1, 1 }); const scroll_plan = render_engine.frame_scroll_plan.merge(runtime.layout.rows, 1, 0, 1); var plan = testPaintPlan(&runtime, prepared.selection); const target_layout = render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(plan); var transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &source, &prepared, target_layout, &plan, scroll_plan, ); defer transition.deinit(alloc); try std.testing.expectEqualStrings("new\r\n", transition.document_append.bytes); try std.testing.expectEqual(@as(u16, 3), transition.document_append.start_row); try std.testing.expectEqual(@as(u16, 1), transition.document_append.start_col); runtime.consumeTranscriptTransition(alloc, &transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = false, .terminal_scroll_rows_committed = 1, .document_append_committed = true, .committed_cursor_row = 4, .committed_cursor_col = 1, .shadow_state = .committed, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); const diagnostic = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual(transcript_runtime.TranscriptCommitDiagnosticState.stable, diagnostic.state); try std.testing.expectEqual(@as(?usize, 1), diagnostic.stable_start_line); try std.testing.expectEqual(@as(u32, 1), diagnostic.visual_offset); const stable = runtime.stableTranscriptProjectionForFlow("old\nnew\n").?; try std.testing.expectEqual(@as(u16, 4), stable.cursor_row); try std.testing.expectEqual(@as(u16, 1), stable.cursor_col); } test "transcript transition staging preserves prior commit across allocation failures" { try std.testing.checkAllAllocationFailures( std.testing.allocator, checkTranscriptTransitionStagingAllocationFailures, .{}, ); } test "committed transcript transition installation performs no allocation" { const backing = std.testing.allocator; var failing = std.testing.FailingAllocator.init(backing, .{}); const alloc = failing.allocator(); var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); try installStableAnchorForTest(&runtime, alloc, "old\n", testSelection(0), 0, 3, 1, 72); var source = try testSource(alloc, "old\nnew\n", runtime.layout.cols); defer source.deinit(alloc); var prepared = transcript_painter.PreparedTranscriptSurfacePaint{ .bytes = source.bytes, .owns_bytes = false, .selection = testSelection(1), .cursor = .{ .cursor_row = 4, .cursor_col = 1, .replaceable_row = 4 }, }; defer prepared.deinit(alloc); try prepared.line_visual_rows.appendSlice(alloc, &.{ 1, 1 }); const scroll_plan = render_engine.frame_scroll_plan.merge(runtime.layout.rows, 1, 0, 1); var plan = testPaintPlan(&runtime, prepared.selection); const target_layout = render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(plan); var transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &source, &prepared, target_layout, &plan, scroll_plan, ); defer transition.deinit(alloc); failing.fail_index = failing.alloc_index; runtime.consumeTranscriptTransition(alloc, &transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = false, .terminal_scroll_rows_committed = 1, .document_append_committed = true, .committed_cursor_row = 4, .committed_cursor_col = 1, .shadow_state = .committed, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); try std.testing.expect(!failing.has_induced_failure); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.stable, runtime.transcriptCommitDiagnostic().state, ); } test "partial transcript transition records exact recovery debt" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); try installStableAnchorForTest(&runtime, alloc, "stable", testSelection(0), 0, 3, 1, 44); var source = try testSource(alloc, "stable", runtime.layout.cols); defer source.deinit(alloc); var prepared = transcript_painter.PreparedTranscriptSurfacePaint{ .bytes = source.bytes, .owns_bytes = false, .selection = testSelection(5), .cursor = .{ .cursor_row = 4, .cursor_col = 1, .replaceable_row = 4 }, }; defer prepared.deinit(alloc); try prepared.line_visual_rows.appendSlice(alloc, &([_]u16{1} ** 16)); const scroll_plan = render_engine.frame_scroll_plan.merge(runtime.layout.rows, 1, 0, 5); var plan = testPaintPlan(&runtime, prepared.selection); const target_layout = render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(plan); var transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &source, &prepared, target_layout, &plan, scroll_plan, ); defer transition.deinit(alloc); runtime.consumeTranscriptTransition(alloc, &transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = false, .terminal_scroll_rows_committed = 2, .document_append_committed = false, .committed_cursor_row = 3, .committed_cursor_col = 1, .shadow_state = .terminal_partial_write, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); const diagnostic = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual(transcript_runtime.TranscriptCommitDiagnosticState.recovering, diagnostic.state); try std.testing.expectEqual(@as(u32, 2), diagnostic.visual_offset); try std.testing.expectEqual(@as(u16, 3), diagnostic.remaining_inline_rows); try std.testing.expect(runtime.stableTranscriptProjectionForFlow("stable") == null); } test "transcript recovery debt does not double count unmaterialized inline rows" { const alloc = std.testing.allocator; const flow = "00\n01\n02\n03\n04\n05\n06\n07\n" ++ "08\n09\n10\n11\n12\n13\n14\n15"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 12, 8), .owned_top_row = 1, }; defer runtime.deinit(alloc); var stable_source = try testSource(alloc, flow, runtime.layout.cols); defer stable_source.deinit(alloc); var stable_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &stable_source, ); defer stable_prepared.deinit(alloc); try std.testing.expectEqual(@as(usize, 8), stable_prepared.selection.start_line); try installStableAnchorForTest( &runtime, alloc, flow, stable_prepared.selection, 8, stable_prepared.cursor.cursor_row, stable_prepared.cursor.cursor_col, 44, ); runtime.owned_top_row = 5; var source = try testSource(alloc, flow, runtime.layout.cols); defer source.deinit(alloc); var prepared = try prepareTestSourceForCurrentArea(&runtime, alloc, &source); defer prepared.deinit(alloc); try std.testing.expectEqual(@as(usize, 12), prepared.selection.start_line); const facts = runtime.planTranscriptScroll(&prepared); try std.testing.expectEqual(@as(u16, 4), facts.semantic_rows); const scroll_plan = render_engine.frame_scroll_plan.merge(runtime.layout.rows, 5, 3, 4); try std.testing.expectEqual(@as(u16, 1), scroll_plan.remaining_inline_advance_rows); var plan = testPaintPlan(&runtime, prepared.selection); const target_layout = render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(plan); var transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &source, &prepared, target_layout, &plan, scroll_plan, ); defer transition.deinit(alloc); runtime.ackPreservedRowReleaseAssumeValid( scroll_plan, scroll_plan.terminal_scroll_rows, ); runtime.consumeTranscriptTransition(alloc, &transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = false, .terminal_scroll_rows_committed = scroll_plan.terminal_scroll_rows, .document_append_committed = false, .committed_cursor_row = prepared.cursor.cursor_row, .committed_cursor_col = prepared.cursor.cursor_col, .shadow_state = .committed, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); const diagnostic = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual(transcript_runtime.TranscriptCommitDiagnosticState.recovering, diagnostic.state); try std.testing.expectEqual(@as(u16, 1), diagnostic.remaining_inline_rows); try std.testing.expectEqual(@as(u32, 11), diagnostic.visual_offset); var recovery_source = try testSource(alloc, flow, runtime.layout.cols); defer recovery_source.deinit(alloc); var recovery_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &recovery_source, ); defer recovery_prepared.deinit(alloc); const recovery_facts = runtime.planTranscriptScroll(&recovery_prepared); try std.testing.expectEqual(@as(u16, 1), recovery_facts.planned_rows); const recovery_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, recovery_facts.planned_rows, ); var recovery_plan = testPaintPlan(&runtime, recovery_prepared.selection); const recovery_layout = render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(recovery_plan); var recovery_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &recovery_source, &recovery_prepared, recovery_layout, &recovery_plan, recovery_scroll_plan, ); defer recovery_transition.deinit(alloc); try std.testing.expect(recovery_transition.document_append.isEmpty()); runtime.consumeTranscriptTransition(alloc, &recovery_transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = 1, .document_append_committed = false, .committed_cursor_row = recovery_transition.cursor_row, .committed_cursor_col = recovery_transition.cursor_col, .shadow_state = .committed, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); const settled = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual(transcript_runtime.TranscriptCommitDiagnosticState.stable, settled.state); try std.testing.expectEqual(@as(u16, 0), settled.remaining_inline_rows); try std.testing.expectEqual(@as(u32, 12), settled.visual_offset); } test "recovery area-only endpoint movement preserves semantic debt" { const alloc = std.testing.allocator; const stable_flow = "stable\n"; const attempt_flow = stable_flow ++ "attempt\n"; const attempt_rows = [_]u16{1} ** 10; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 10, 6), .owned_top_row = 5, }; defer runtime.deinit(alloc); try enterSemanticAppendRecovery( &runtime, alloc, stable_flow, attempt_flow, &attempt_rows, ); var source = try testSource(alloc, attempt_flow, runtime.layout.cols); defer source.deinit(alloc); var prepared = transcript_painter.PreparedTranscriptSurfacePaint{ .bytes = source.bytes, .owns_bytes = false, .selection = testSelectionInArea(7, 1, runtime.layout.content_bottom), .cursor = .{ .cursor_row = 6, .cursor_col = 1, .replaceable_row = 6 }, }; defer prepared.deinit(alloc); try prepared.line_visual_rows.appendSlice(alloc, &attempt_rows); const facts = runtime.planTranscriptScroll(&prepared); try std.testing.expect(facts.source_compatible); try std.testing.expectEqual(@as(u16, 0), facts.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 1), facts.semantic_rows); try std.testing.expectEqual(@as(u16, 1), facts.planned_rows); } test "recovery source growth plus area movement counts only source visual growth" { const alloc = std.testing.allocator; const stable_flow = "stable\n"; const attempt_flow = stable_flow ++ "attempt\n"; const grown_flow = attempt_flow ++ "growth one\ngrowth two\n"; const attempt_rows = [_]u16{1} ** 10; const grown_rows = [_]u16{1} ** 12; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 10, 6), .owned_top_row = 5, }; defer runtime.deinit(alloc); try enterSemanticAppendRecovery( &runtime, alloc, stable_flow, attempt_flow, &attempt_rows, ); var source = try testSource(alloc, grown_flow, runtime.layout.cols); defer source.deinit(alloc); var prepared = transcript_painter.PreparedTranscriptSurfacePaint{ .bytes = source.bytes, .owns_bytes = false, .selection = testSelectionInArea(9, 1, runtime.layout.content_bottom), .cursor = .{ .cursor_row = 6, .cursor_col = 1, .replaceable_row = 6 }, }; defer prepared.deinit(alloc); try prepared.line_visual_rows.appendSlice(alloc, &grown_rows); const facts = runtime.planTranscriptScroll(&prepared); try std.testing.expect(facts.source_compatible); try std.testing.expectEqual(@as(u32, 3), facts.source_visual_offset); try std.testing.expectEqual(@as(u32, 9), facts.target_visual_offset); try std.testing.expectEqual(@as(u16, 0), facts.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 3), facts.semantic_rows); try std.testing.expectEqual(@as(u16, 3), facts.planned_rows); } test "recovery consumes resize reflow before semantic debt across partial results" { const alloc = std.testing.allocator; const grown_flow = "stable\nbase\n" ++ "growth one\ngrowth two\n"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); try enterResizeReflowRecovery( &runtime, alloc, null, .terminal_partial_write, ); var source = try testSource(alloc, grown_flow, runtime.layout.cols); defer source.deinit(alloc); var prepared = try prepareTestSourceForCurrentArea(&runtime, alloc, &source); defer prepared.deinit(alloc); const facts = runtime.planTranscriptScroll(&prepared); try std.testing.expect(facts.source_compatible); try std.testing.expectEqual(@as(u16, 1), facts.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 2), facts.semantic_rows); try std.testing.expectEqual(@as(u16, 3), facts.planned_rows); const scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, facts.planned_rows, ); var plan = testPaintPlan(&runtime, prepared.selection); const target_layout = render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(plan); var transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &source, &prepared, target_layout, &plan, scroll_plan, ); defer transition.deinit(alloc); try std.testing.expect(transition.document_append.isEmpty()); runtime.consumeTranscriptTransition(alloc, &transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = 2, .document_append_committed = false, .committed_cursor_row = 2, .committed_cursor_col = 1, .shadow_state = .terminal_partial_write, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); const recovery = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.recovering, recovery.state, ); try std.testing.expectEqual(@as(u32, 1), recovery.visual_offset); try std.testing.expectEqual(@as(u16, 1), recovery.remaining_inline_rows); var followup_source = try testSource(alloc, grown_flow, runtime.layout.cols); defer followup_source.deinit(alloc); var followup_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &followup_source, ); defer followup_prepared.deinit(alloc); const followup_facts = runtime.planTranscriptScroll(&followup_prepared); try std.testing.expectEqual(@as(u16, 0), followup_facts.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 1), followup_facts.semantic_rows); try std.testing.expectEqual(@as(u16, 1), followup_facts.planned_rows); } test "committed resize cleanup retains fallback recovery reflow debt" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); try enterResizeReflowRecovery( &runtime, alloc, null, .committed, ); const recovery_offset = runtime.transcriptCommitDiagnostic().visual_offset; runtime.resize_history_row_delta = null; var source = try testSource(alloc, "stable\nbase\n", runtime.layout.cols); defer source.deinit(alloc); var prepared = try prepareTestSourceForCurrentArea(&runtime, alloc, &source); defer prepared.deinit(alloc); const facts = runtime.planTranscriptScroll(&prepared); try std.testing.expectEqual(@as(u16, 1), facts.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 0), facts.semantic_rows); try std.testing.expectEqual(@as(u16, 1), facts.planned_rows); const scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, facts.planned_rows, ); var plan = testPaintPlan(&runtime, prepared.selection); const target_layout = render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(plan); var transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &source, &prepared, target_layout, &plan, scroll_plan, ); defer transition.deinit(alloc); try std.testing.expect(transition.document_append.isEmpty()); runtime.consumeTranscriptTransition(alloc, &transition, .{ .bytes_written = 0, .changed_cells = 0, .full_repaint = true, .terminal_scroll_rows_committed = 0, .document_append_committed = false, .committed_cursor_row = 2, .committed_cursor_col = 1, .shadow_state = .committed, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); const recovery = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.recovering, recovery.state, ); try std.testing.expectEqual(recovery_offset, recovery.visual_offset); try std.testing.expectEqual(@as(u16, 1), recovery.remaining_inline_rows); var complete_source = try testSource(alloc, "stable\nbase\n", runtime.layout.cols); defer complete_source.deinit(alloc); var complete_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &complete_source, ); defer complete_prepared.deinit(alloc); const complete_facts = runtime.planTranscriptScroll(&complete_prepared); try std.testing.expect(complete_facts.recovery_rebase); try std.testing.expectEqual(@as(u16, 1), complete_facts.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 0), complete_facts.semantic_rows); const complete_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, complete_facts.planned_rows, ); var complete_plan = testPaintPlan(&runtime, complete_prepared.selection); var complete_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &complete_source, &complete_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(complete_plan), &complete_plan, complete_scroll_plan, ); defer complete_transition.deinit(alloc); try std.testing.expectEqual(recovery_offset, complete_transition.visual_offset); runtime.consumeTranscriptTransition(alloc, &complete_transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = complete_scroll_plan.terminal_scroll_rows, .document_append_committed = false, .committed_cursor_row = complete_transition.cursor_row, .committed_cursor_col = complete_transition.cursor_col, .shadow_state = .committed, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); const stable = runtime.stableTranscriptProjectionForFlow("stable\nbase\n") orelse return error.TestUnexpectedResult; try std.testing.expectEqual(recovery_offset, stable.visual_offset); } test "incompatible recovery source stays on rebase path after partial repaint" { const alloc = std.testing.allocator; const stable_flow = "stable\n"; const attempt_flow = stable_flow ++ "attempt\n"; const replacement_flow = "replacement\n"; const grown_replacement_flow = replacement_flow ++ "growth\n"; const attempt_rows = [_]u16{1} ** 10; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 10, 6), .owned_top_row = 5, }; defer runtime.deinit(alloc); try enterSemanticAppendRecovery( &runtime, alloc, stable_flow, attempt_flow, &attempt_rows, ); var replacement_source = try testSource( alloc, replacement_flow, runtime.layout.cols, ); defer replacement_source.deinit(alloc); var replacement_prepared = transcript_painter.PreparedTranscriptSurfacePaint{ .bytes = replacement_source.bytes, .owns_bytes = false, .selection = testSelectionInArea(0, 1, runtime.layout.content_bottom), .cursor = .{ .cursor_row = 6, .cursor_col = 1, .replaceable_row = 6 }, }; defer replacement_prepared.deinit(alloc); try replacement_prepared.line_visual_rows.appendSlice( alloc, &.{ 1, 1, 1, 1, 1 }, ); const replacement_facts = runtime.planTranscriptScroll(&replacement_prepared); try std.testing.expect(!replacement_facts.source_compatible); try std.testing.expectEqual(@as(u16, 0), replacement_facts.planned_rows); const replacement_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, replacement_facts.planned_rows, ); var replacement_plan = testPaintPlan(&runtime, replacement_prepared.selection); var replacement_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &replacement_source, &replacement_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan( replacement_plan, ), &replacement_plan, replacement_scroll_plan, ); defer replacement_transition.deinit(alloc); try std.testing.expect(replacement_transition.document_append.isEmpty()); runtime.consumeTranscriptTransition(alloc, &replacement_transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = 0, .document_append_committed = false, .committed_cursor_row = 6, .committed_cursor_col = 1, .shadow_state = .terminal_partial_write, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.recovering, runtime.transcriptCommitDiagnostic().state, ); var grown_source = try testSource( alloc, grown_replacement_flow, runtime.layout.cols, ); defer grown_source.deinit(alloc); var grown_prepared = transcript_painter.PreparedTranscriptSurfacePaint{ .bytes = grown_source.bytes, .owns_bytes = false, .selection = testSelectionInArea(1, 1, runtime.layout.content_bottom), .cursor = .{ .cursor_row = 6, .cursor_col = 1, .replaceable_row = 6 }, }; defer grown_prepared.deinit(alloc); try grown_prepared.line_visual_rows.appendSlice( alloc, &.{ 1, 1, 1, 1, 1, 1 }, ); const grown_facts = runtime.planTranscriptScroll(&grown_prepared); try std.testing.expect(grown_facts.source_compatible); try std.testing.expectEqual(@as(u16, 0), grown_facts.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 0), grown_facts.semantic_rows); try std.testing.expectEqual(@as(u16, 0), grown_facts.planned_rows); } test "structured replacement preserves reflow debt and drops old semantic debt" { const alloc = std.testing.allocator; const stable_flow = "stable\nbase\n"; const old_growth = "old growth\n"; const replacement = "replacement\n"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); _ = try runtime.appendRawTranscriptEntry(alloc, stable_flow); try enterResizeReflowRecoveryWithAcceptedRows( &runtime, alloc, null, .terminal_partial_write, 0, ); const growth_entry_id = try runtime.appendRawTranscriptEntry( alloc, old_growth, ); var old_source = try runtime.prepareTranscriptSource(alloc, null); defer old_source.deinit(alloc); var old_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &old_source, ); defer old_prepared.deinit(alloc); const old_facts = runtime.planTranscriptScroll(&old_prepared); try std.testing.expectEqual(@as(u16, 2), old_facts.resize_reflow_rows); try std.testing.expect(old_facts.semantic_rows > 0); try std.testing.expectEqual( old_facts.resize_reflow_rows + old_facts.semantic_rows, old_facts.planned_rows, ); const old_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, old_facts.planned_rows, ); var old_plan = testPaintPlan(&runtime, old_prepared.selection); var old_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &old_source, &old_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(old_plan), &old_plan, old_scroll_plan, ); defer old_transition.deinit(alloc); try std.testing.expect(old_transition.document_append.isEmpty()); runtime.consumeTranscriptTransition(alloc, &old_transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = 0, .document_append_committed = false, .committed_cursor_row = old_transition.cursor_row, .committed_cursor_col = old_transition.cursor_col, .shadow_state = .terminal_partial_write, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); try std.testing.expectEqual( old_facts.planned_rows, runtime.transcriptCommitDiagnostic().remaining_inline_rows, ); try std.testing.expect(try runtime.updateRawBytesEntry( alloc, growth_entry_id, replacement, )); var recovery = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.recovering, recovery.state, ); try std.testing.expectEqual(@as(u16, 2), recovery.remaining_inline_rows); try std.testing.expect( std.mem.find(u8, runtime.transcript.items, replacement) != null, ); var replacement_source = try runtime.prepareTranscriptSource(alloc, null); defer replacement_source.deinit(alloc); var replacement_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &replacement_source, ); defer replacement_prepared.deinit(alloc); const replacement_facts = runtime.planTranscriptScroll(&replacement_prepared); try std.testing.expect(!replacement_facts.source_compatible); try std.testing.expect(replacement_facts.recovery_rebase); try std.testing.expectEqual(@as(u16, 2), replacement_facts.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 0), replacement_facts.semantic_rows); try std.testing.expectEqual(@as(u16, 2), replacement_facts.planned_rows); const replacement_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, replacement_facts.planned_rows, ); var replacement_plan = testPaintPlan( &runtime, replacement_prepared.selection, ); var replacement_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &replacement_source, &replacement_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan( replacement_plan, ), &replacement_plan, replacement_scroll_plan, ); defer replacement_transition.deinit(alloc); try std.testing.expect(replacement_transition.document_append.isEmpty()); runtime.consumeTranscriptTransition(alloc, &replacement_transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = 1, .document_append_committed = false, .committed_cursor_row = replacement_transition.cursor_row, .committed_cursor_col = replacement_transition.cursor_col, .shadow_state = .terminal_partial_write, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); recovery = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.recovering, recovery.state, ); try std.testing.expectEqual(@as(u16, 1), recovery.remaining_inline_rows); var repeated_source = try runtime.prepareTranscriptSource(alloc, null); defer repeated_source.deinit(alloc); var repeated_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &repeated_source, ); defer repeated_prepared.deinit(alloc); const repeated_facts = runtime.planTranscriptScroll(&repeated_prepared); try std.testing.expect(repeated_facts.source_compatible); try std.testing.expect(repeated_facts.recovery_rebase); try std.testing.expectEqual(@as(u16, 1), repeated_facts.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 0), repeated_facts.semantic_rows); const repeated_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, repeated_facts.planned_rows, ); var repeated_plan = testPaintPlan(&runtime, repeated_prepared.selection); var repeated_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &repeated_source, &repeated_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan( repeated_plan, ), &repeated_plan, repeated_scroll_plan, ); defer repeated_transition.deinit(alloc); try std.testing.expect(repeated_transition.document_append.isEmpty()); runtime.consumeTranscriptTransition(alloc, &repeated_transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = 0, .document_append_committed = false, .committed_cursor_row = repeated_transition.cursor_row, .committed_cursor_col = repeated_transition.cursor_col, .shadow_state = .terminal_partial_write, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); try std.testing.expectEqual( @as(u16, 1), runtime.transcriptCommitDiagnostic().remaining_inline_rows, ); var complete_source = try runtime.prepareTranscriptSource(alloc, null); defer complete_source.deinit(alloc); var complete_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &complete_source, ); defer complete_prepared.deinit(alloc); const complete_facts = runtime.planTranscriptScroll(&complete_prepared); try std.testing.expectEqual(@as(u16, 1), complete_facts.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 0), complete_facts.semantic_rows); const complete_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, complete_facts.planned_rows, ); var complete_plan = testPaintPlan(&runtime, complete_prepared.selection); var complete_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &complete_source, &complete_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan( complete_plan, ), &complete_plan, complete_scroll_plan, ); defer complete_transition.deinit(alloc); try std.testing.expect(complete_transition.document_append.isEmpty()); runtime.consumeTranscriptTransition(alloc, &complete_transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = complete_scroll_plan.terminal_scroll_rows, .document_append_committed = false, .committed_cursor_row = complete_transition.cursor_row, .committed_cursor_col = complete_transition.cursor_col, .shadow_state = .committed, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.stable, runtime.transcriptCommitDiagnostic().state, ); var stable_replacement_source = try runtime.prepareTranscriptSource( alloc, null, ); defer stable_replacement_source.deinit(alloc); try std.testing.expect( runtime.stableTranscriptProjectionForFlow( stable_replacement_source.bytes, ) != null, ); const replacement_flow = try alloc.dupe( u8, stable_replacement_source.bytes, ); defer alloc.free(replacement_flow); const append = "after\n"; _ = try runtime.appendRawTranscriptEntry(alloc, append); var append_source = try runtime.prepareTranscriptSource(alloc, null); defer append_source.deinit(alloc); var append_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &append_source, ); defer append_prepared.deinit(alloc); const append_facts = runtime.planTranscriptScroll(&append_prepared); try std.testing.expect(!append_facts.recovery_rebase); try std.testing.expect(append_facts.semantic_rows > 0); const append_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, append_facts.planned_rows, ); var append_plan = testPaintPlan(&runtime, append_prepared.selection); var append_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &append_source, &append_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan( append_plan, ), &append_plan, append_scroll_plan, ); defer append_transition.deinit(alloc); try std.testing.expect(append_source.bytes.len == 0); try std.testing.expect(append_transition.target_flow.len > replacement_flow.len); try expectDocumentWireEqualsLogical( alloc, append_transition.target_flow[replacement_flow.len..], append_transition.document_append.bytes, ); } test "structured retention preserves recovery reflow debt" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); _ = try runtime.appendRawTranscriptEntry(alloc, "stable\n"); _ = try runtime.appendRawTranscriptEntry(alloc, "base\n"); try enterResizeReflowRecoveryWithAcceptedRows( &runtime, alloc, null, .terminal_partial_write, 0, ); runtime.max_retained_transcript_bytes = "base\n".len; try runtime.enforceStructuredRetention(alloc, null); try std.testing.expectEqualStrings("base\n", runtime.transcript.items); const recovery = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.recovering, recovery.state, ); try std.testing.expectEqual(@as(u16, 2), recovery.remaining_inline_rows); var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); var prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &source, ); defer prepared.deinit(alloc); const facts = runtime.planTranscriptScroll(&prepared); try std.testing.expect(!facts.source_compatible); try std.testing.expect(facts.recovery_rebase); try std.testing.expectEqual(@as(u16, 2), facts.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 0), facts.semantic_rows); try std.testing.expectEqual(@as(u16, 2), facts.planned_rows); } test "invalid origin partial recovery rebases until complete repaint then appends" { const alloc = std.testing.allocator; const flow = "0\n1\n2\n3\n4\n5\n6\n7"; const grown_flow = flow ++ "\n8\n9"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); var first_source = try testSource(alloc, flow, runtime.layout.cols); defer first_source.deinit(alloc); var first_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &first_source, ); defer first_prepared.deinit(alloc); try std.testing.expectEqual(@as(usize, 4), first_prepared.selection.start_line); const first_facts = runtime.planTranscriptScroll(&first_prepared); try std.testing.expect(first_facts.recovery_rebase); try std.testing.expect(!first_facts.recovery_projection_deferred); try std.testing.expectEqual(@as(u16, 0), first_facts.planned_rows); const first_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, first_facts.planned_rows, ); var first_plan = testPaintPlan(&runtime, first_prepared.selection); var first_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &first_source, &first_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(first_plan), &first_plan, first_scroll_plan, ); defer first_transition.deinit(alloc); try std.testing.expect(first_transition.recovery_rebase); try std.testing.expect(first_transition.document_append.isEmpty()); try expectFinalizedRecoveryProjectionPaintsCoherently( &runtime, alloc, first_plan, &first_prepared, &first_transition, "4", ); runtime.consumeTranscriptTransition(alloc, &first_transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = 0, .document_append_committed = false, .committed_cursor_row = first_transition.cursor_row, .committed_cursor_col = first_transition.cursor_col, .shadow_state = .terminal_partial_write, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); var recovery = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.recovering, recovery.state, ); try std.testing.expectEqual(@as(u16, 0), recovery.remaining_inline_rows); try std.testing.expect(runtime.stableTranscriptProjectionForFlow(flow) == null); var repeated_source = try testSource(alloc, flow, runtime.layout.cols); defer repeated_source.deinit(alloc); var repeated_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &repeated_source, ); defer repeated_prepared.deinit(alloc); const repeated_facts = runtime.planTranscriptScroll(&repeated_prepared); try std.testing.expect(repeated_facts.source_compatible); try std.testing.expect(repeated_facts.recovery_rebase); try std.testing.expect(!repeated_facts.recovery_projection_deferred); try std.testing.expectEqual(@as(u16, 0), repeated_facts.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 0), repeated_facts.semantic_rows); try std.testing.expectEqual(@as(u16, 0), repeated_facts.planned_rows); const repeated_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, repeated_facts.planned_rows, ); var repeated_plan = testPaintPlan(&runtime, repeated_prepared.selection); var repeated_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &repeated_source, &repeated_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(repeated_plan), &repeated_plan, repeated_scroll_plan, ); defer repeated_transition.deinit(alloc); try std.testing.expect(repeated_transition.recovery_rebase); try std.testing.expect(repeated_transition.document_append.isEmpty()); runtime.consumeTranscriptTransition(alloc, &repeated_transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = 0, .document_append_committed = false, .committed_cursor_row = repeated_transition.cursor_row, .committed_cursor_col = repeated_transition.cursor_col, .shadow_state = .terminal_partial_write, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); recovery = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.recovering, recovery.state, ); try std.testing.expectEqual(@as(u16, 0), recovery.remaining_inline_rows); try std.testing.expect(runtime.stableTranscriptProjectionForFlow(flow) == null); var complete_source = try testSource(alloc, flow, runtime.layout.cols); defer complete_source.deinit(alloc); var complete_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &complete_source, ); defer complete_prepared.deinit(alloc); const complete_facts = runtime.planTranscriptScroll(&complete_prepared); try std.testing.expect(complete_facts.recovery_rebase); try std.testing.expect(!complete_facts.recovery_projection_deferred); const complete_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, complete_facts.planned_rows, ); var complete_plan = testPaintPlan(&runtime, complete_prepared.selection); var complete_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &complete_source, &complete_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(complete_plan), &complete_plan, complete_scroll_plan, ); defer complete_transition.deinit(alloc); try std.testing.expect(complete_transition.document_append.isEmpty()); runtime.consumeTranscriptTransition(alloc, &complete_transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = 0, .document_append_committed = false, .committed_cursor_row = complete_transition.cursor_row, .committed_cursor_col = complete_transition.cursor_col, .shadow_state = .committed, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); const settled = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.stable, settled.state, ); try std.testing.expectEqual(@as(u16, 0), settled.remaining_inline_rows); const stable = runtime.stableTranscriptProjectionForFlow(flow) orelse return error.TestUnexpectedResult; try std.testing.expectEqual(@as(usize, 4), stable.selection.start_line); try std.testing.expectEqual(@as(u32, 4), stable.visual_offset); var append_source = try testSource(alloc, grown_flow, runtime.layout.cols); defer append_source.deinit(alloc); var append_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &append_source, ); defer append_prepared.deinit(alloc); const append_facts = runtime.planTranscriptScroll(&append_prepared); try std.testing.expect(append_facts.source_compatible); try std.testing.expectEqual(@as(u16, 2), append_facts.semantic_rows); try std.testing.expectEqual(@as(u16, 2), append_facts.planned_rows); const append_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, append_facts.planned_rows, ); var append_plan = testPaintPlan(&runtime, append_prepared.selection); var append_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &append_source, &append_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(append_plan), &append_plan, append_scroll_plan, ); defer append_transition.deinit(alloc); try expectDocumentWireEqualsLogical( alloc, grown_flow[flow.len..], append_transition.document_append.bytes, ); try std.testing.expectEqual( stable.cursor_row, append_transition.document_append.start_row, ); try std.testing.expectEqual( stable.cursor_col, append_transition.document_append.start_col, ); } test "recovery projection paints wrapped partial blank tail from acknowledged endpoint" { const alloc = std.testing.allocator; const flow = "abcdefghijklmno\nalpha\n\n"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(5, 10, 6), .owned_top_row = 1, }; defer runtime.deinit(alloc); const attempt_offset = try enterRendererDerivedSemanticRecovery( &runtime, alloc, flow, 3, 1, null, ); try std.testing.expectEqual(@as(u32, 2), attempt_offset); runtime.layout = transcriptTestLayout(5, 10, 6); var source = try testSource(alloc, flow, runtime.layout.cols); defer source.deinit(alloc); var prepared = try prepareTestSourceForCurrentArea(&runtime, alloc, &source); defer prepared.deinit(alloc); try std.testing.expectEqual(@as(usize, 0), prepared.selection.start_line); try std.testing.expectEqual(@as(u16, 0), prepared.selection.partial_skip_rows); const facts = runtime.planTranscriptScroll(&prepared); try std.testing.expectEqual(@as(u16, 1), facts.semantic_rows); const scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, facts.planned_rows, ); var plan = testPaintPlan(&runtime, prepared.selection); var transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &source, &prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(plan), &plan, scroll_plan, ); defer transition.deinit(alloc); try std.testing.expectEqual(@as(u32, 2), transition.visual_offset); try std.testing.expectEqual(@as(usize, 0), transition.selection.start_line); try std.testing.expectEqual(@as(u16, 2), transition.selection.partial_skip_rows); try std.testing.expectEqual(@as(u16, 3), transition.selection.last_visible_row); try std.testing.expect(transition.selection.last_visible_row_blank); try std.testing.expectEqual(@as(u16, 4), transition.cursor_row); try std.testing.expectEqual(@as(u16, 1), transition.cursor_col); try expectFinalizedRecoveryProjectionPaintsCoherently( &runtime, alloc, plan, &prepared, &transition, "klmno", ); } test "recovery projection moves cursor for zero-row trailing newline growth" { const alloc = std.testing.allocator; const attempt_flow = "a\nb\nc"; const grown_flow = attempt_flow ++ "\n"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); const attempt_offset = try enterRendererDerivedSemanticRecovery( &runtime, alloc, attempt_flow, 2, 0, null, ); try std.testing.expectEqual(@as(u32, 1), attempt_offset); runtime.layout = transcriptTestLayout(20, 8, 3); var source = try testSource(alloc, grown_flow, runtime.layout.cols); defer source.deinit(alloc); var prepared = try prepareTestSourceForCurrentArea(&runtime, alloc, &source); defer prepared.deinit(alloc); const facts = runtime.planTranscriptScroll(&prepared); try std.testing.expectEqual(@as(u16, 1), facts.semantic_rows); try std.testing.expectEqual(@as(u32, 0), facts.target_visual_offset); const scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, facts.planned_rows, ); var plan = testPaintPlan(&runtime, prepared.selection); var transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &source, &prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(plan), &plan, scroll_plan, ); defer transition.deinit(alloc); try std.testing.expectEqual(@as(u32, 1), transition.visual_offset); try std.testing.expectEqual(@as(usize, 1), transition.selection.start_line); try std.testing.expectEqual(@as(u16, 2), transition.selection.last_visible_row); try std.testing.expectEqual(@as(u16, 3), transition.cursor_row); try std.testing.expectEqual(@as(u16, 1), transition.cursor_col); try expectFinalizedRecoveryProjectionPaintsCoherently( &runtime, alloc, plan, &prepared, &transition, "b", ); } test "recovery projection relocates replaceable row through production painter" { const alloc = std.testing.allocator; const flow = "a\nb\nc\nreplace"; const replaceable_start = std.mem.lastIndexOfScalar(u8, flow, '\n').? + 1; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 8, 5), .owned_top_row = 1, }; defer runtime.deinit(alloc); const attempt_offset = try enterRendererDerivedSemanticRecovery( &runtime, alloc, flow, 2, 1, replaceable_start, ); try std.testing.expectEqual(@as(u32, 2), attempt_offset); runtime.layout = transcriptTestLayout(20, 8, 6); var source = try testSourceWithReplaceableTail( alloc, flow, runtime.layout.cols, replaceable_start, ); defer source.deinit(alloc); var prepared = try prepareTestSourceForCurrentArea(&runtime, alloc, &source); defer prepared.deinit(alloc); try std.testing.expectEqual(@as(u16, 4), prepared.cursor.replaceable_row); const facts = runtime.planTranscriptScroll(&prepared); try std.testing.expectEqual(@as(u16, 1), facts.semantic_rows); const scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, facts.planned_rows, ); var plan = testPaintPlan(&runtime, prepared.selection); var transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &source, &prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(plan), &plan, scroll_plan, ); defer transition.deinit(alloc); try std.testing.expectEqual(@as(u32, 2), transition.visual_offset); try std.testing.expectEqual(@as(usize, 2), transition.selection.start_line); try std.testing.expectEqual(@as(u16, 2), transition.selection.replaceable_start_row); try std.testing.expectEqual(@as(u16, 2), transition.replaceable_row); try expectFinalizedRecoveryProjectionPaintsCoherently( &runtime, alloc, plan, &prepared, &transition, "c", ); } test "row-only shrink defers wrapped recovery projection and later converges" { const alloc = std.testing.allocator; const flow = "abcdefghijklmno\nalpha\n\n"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(5, 10, 6), .owned_top_row = 1, }; defer runtime.deinit(alloc); const attempt_offset = try enterRendererDerivedSemanticRecovery( &runtime, alloc, flow, 3, 1, null, ); try std.testing.expectEqual(@as(u32, 2), attempt_offset); runtime.layout = transcriptTestLayout(5, 10, 2); var source = try testSource(alloc, flow, runtime.layout.cols); defer source.deinit(alloc); var prepared = try prepareTestSourceForCurrentArea(&runtime, alloc, &source); defer prepared.deinit(alloc); try std.testing.expectEqual(@as(usize, 1), prepared.selection.start_line); try std.testing.expectEqual(@as(u16, 0), prepared.selection.partial_skip_rows); const facts = runtime.planTranscriptScroll(&prepared); try std.testing.expectEqual(@as(u16, 1), facts.semantic_rows); try std.testing.expectEqual(@as(u16, 0), facts.planned_rows); try std.testing.expect(facts.recovery_projection_deferred); const scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, facts.planned_rows, ); var plan = testPaintPlan(&runtime, prepared.selection); var transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &source, &prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(plan), &plan, scroll_plan, ); defer transition.deinit(alloc); try std.testing.expect(transition.document_append.isEmpty()); try std.testing.expect(transition.recovery_projection_deferred); try std.testing.expectEqual(@as(u32, 3), transition.visual_offset); try expectFinalizedRecoveryProjectionPaintsCoherently( &runtime, alloc, plan, &prepared, &transition, "alpha", ); runtime.consumeTranscriptTransition(alloc, &transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = 0, .document_append_committed = false, .committed_cursor_row = transition.cursor_row, .committed_cursor_col = transition.cursor_col, .shadow_state = .committed, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); var diagnostic = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.recovering, diagnostic.state, ); try std.testing.expectEqual(@as(u16, 1), diagnostic.remaining_inline_rows); try std.testing.expectEqual(@as(u32, 1), diagnostic.visual_offset); runtime.layout = transcriptTestLayout(5, 10, 3); var recovery_source = try testSource(alloc, flow, runtime.layout.cols); defer recovery_source.deinit(alloc); var recovery_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &recovery_source, ); defer recovery_prepared.deinit(alloc); const recovery_facts = runtime.planTranscriptScroll(&recovery_prepared); try std.testing.expectEqual(@as(u16, 1), recovery_facts.semantic_rows); try std.testing.expectEqual(@as(u16, 1), recovery_facts.planned_rows); try std.testing.expect(!recovery_facts.recovery_projection_deferred); const recovery_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, recovery_facts.planned_rows, ); var recovery_plan = testPaintPlan(&runtime, recovery_prepared.selection); var recovery_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &recovery_source, &recovery_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(recovery_plan), &recovery_plan, recovery_scroll_plan, ); defer recovery_transition.deinit(alloc); try std.testing.expect(recovery_transition.document_append.isEmpty()); try std.testing.expect(!recovery_transition.recovery_projection_deferred); try std.testing.expectEqual(@as(u32, 2), recovery_transition.visual_offset); try std.testing.expectEqual(@as(usize, 0), recovery_transition.selection.start_line); try std.testing.expectEqual(@as(u16, 2), recovery_transition.selection.partial_skip_rows); try expectFinalizedRecoveryProjectionPaintsCoherently( &runtime, alloc, recovery_plan, &recovery_prepared, &recovery_transition, "klmno", ); runtime.consumeTranscriptTransition(alloc, &recovery_transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = recovery_scroll_plan.terminal_scroll_rows, .document_append_committed = false, .committed_cursor_row = recovery_transition.cursor_row, .committed_cursor_col = recovery_transition.cursor_col, .shadow_state = .committed, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); diagnostic = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.stable, diagnostic.state, ); try std.testing.expectEqual(@as(u16, 0), diagnostic.remaining_inline_rows); try std.testing.expectEqual(@as(u32, 2), diagnostic.visual_offset); const projection = runtime.stableTranscriptProjectionForFlow(flow) orelse return error.TestUnexpectedResult; try std.testing.expectEqual(@as(usize, 0), projection.selection.start_line); try std.testing.expectEqual(@as(u16, 2), projection.selection.partial_skip_rows); try std.testing.expectEqual(@as(u16, 3), projection.cursor_row); try std.testing.expectEqual(@as(u16, 1), projection.cursor_col); } test "committed deferred shrink installs target geometry before recovery stabilizes" { const alloc = std.testing.allocator; const stable_flow = "0\n1\n2\n3"; const attempt_flow = stable_flow ++ "\n4\n5\n6\n7\n8\n9"; const visual_rows = [_]u16{1} ** 10; var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var sink = try tmp.dir.createFile(std.testing.io, "deferred-layout.log", .{ .read = true }); defer sink.close(io_mod.getIo()); var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = transcriptTestLayout(5, 10, 8), .owned_top_row = 5, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); try installStableAnchorForTest( &runtime, alloc, stable_flow, testSelectionInArea(0, 5, 8), 0, 8, 1, 401, ); const stable_layout = runtime.committed_frame_layout; var attempt_source = try testSource(alloc, attempt_flow, runtime.layout.cols); defer attempt_source.deinit(alloc); var attempt_prepared = transcript_painter.PreparedTranscriptSurfacePaint{ .bytes = attempt_source.bytes, .owns_bytes = false, .selection = testSelectionInArea(6, 5, 8), .cursor = .{ .cursor_row = 8, .cursor_col = 1, .replaceable_row = 8 }, }; defer attempt_prepared.deinit(alloc); try attempt_prepared.line_visual_rows.appendSlice(alloc, &visual_rows); try makeSyntheticPreparedRenderable(&attempt_prepared, alloc); const attempt_facts = runtime.planTranscriptScroll(&attempt_prepared); try std.testing.expectEqual(@as(u16, 6), attempt_facts.semantic_rows); const attempt_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, attempt_facts.planned_rows, ); var attempt_plan = testPaintPlan(&runtime, attempt_prepared.selection); var attempt_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &attempt_source, &attempt_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(attempt_plan), &attempt_plan, attempt_scroll_plan, ); defer attempt_transition.deinit(alloc); runtime.ackPreservedRowReleaseAssumeValid(attempt_scroll_plan, 5); runtime.consumeTranscriptTransition(alloc, &attempt_transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = 5, .document_append_committed = true, .committed_cursor_row = 8, .committed_cursor_col = 1, .shadow_state = .terminal_partial_write, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); try std.testing.expectEqualDeep(stable_layout, runtime.committed_frame_layout); try std.testing.expectEqual(@as(u16, 1), runtime.owned_top_row); const partial_recovery = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.recovering, partial_recovery.state, ); try std.testing.expectEqual(@as(u16, 5), partial_recovery.remaining_inline_rows); try std.testing.expectEqual(@as(u32, 1), partial_recovery.visual_offset); runtime.layout = transcriptTestLayout(5, 10, 2); var deferred_source = try testSource(alloc, attempt_flow, runtime.layout.cols); defer deferred_source.deinit(alloc); var deferred_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &deferred_source, ); defer deferred_prepared.deinit(alloc); const deferred_facts = runtime.planTranscriptScroll(&deferred_prepared); try std.testing.expectEqual(@as(u16, 5), deferred_facts.semantic_rows); try std.testing.expectEqual(@as(u16, 0), deferred_facts.planned_rows); try std.testing.expect(deferred_facts.recovery_projection_deferred); const deferred_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, deferred_facts.planned_rows, ); var deferred_plan = testPaintPlan(&runtime, deferred_prepared.selection); const target_layout = render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(deferred_plan); var deferred_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &deferred_source, &deferred_prepared, target_layout, &deferred_plan, deferred_scroll_plan, ); defer deferred_transition.deinit(alloc); try std.testing.expect(deferred_transition.document_append.isEmpty()); runtime.consumeTranscriptTransition(alloc, &deferred_transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = 0, .document_append_committed = false, .committed_cursor_row = deferred_transition.cursor_row, .committed_cursor_col = deferred_transition.cursor_col, .shadow_state = .committed, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); const recovering = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.recovering, recovering.state, ); try std.testing.expectEqual(@as(u16, 5), recovering.remaining_inline_rows); try std.testing.expectEqual(@as(u32, 1), recovering.visual_offset); try std.testing.expectEqualStrings(stable_flow, runtime.transcript.items); try std.testing.expect(runtime.stableTranscriptProjectionForFlow(attempt_flow) == null); var invalidation_probe = OwnedBandInvalidationProbe{}; var metrics: Metrics = .{}; const next_result = try render_engine.frame_builder.buildAndFlushFrame( alloc, &runtime, &metrics, .{ .plan = deferred_plan, .scroll_plan = render_engine.frame_scroll_plan.FrameScrollPlan.none( runtime.layout.rows, runtime.owned_top_row, ), .body_painter = .{ .ctx = &invalidation_probe, .paint = OwnedBandInvalidationProbe.paint, }, }, ); try std.testing.expectEqual( render_engine.terminal_diff.ShadowCommitState.committed, next_result.shadow_state, ); try std.testing.expectEqualDeep(target_layout, runtime.committed_frame_layout); try std.testing.expect(!invalidation_probe.saw_owned_band_change); } test "recovery forward area movement stabilizes at acknowledged semantic endpoint" { const alloc = std.testing.allocator; const stable_flow = "stable\n"; const attempt_flow = stable_flow ++ "attempt\n"; const grown_flow = attempt_flow ++ "growth one\ngrowth two\n"; const attempt_rows = [_]u16{1} ** 10; const grown_rows = [_]u16{1} ** 12; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 10, 6), .owned_top_row = 5, }; defer runtime.deinit(alloc); try enterSemanticAppendRecovery( &runtime, alloc, stable_flow, attempt_flow, &attempt_rows, ); var first_source = try testSource(alloc, grown_flow, runtime.layout.cols); defer first_source.deinit(alloc); var first_prepared = transcript_painter.PreparedTranscriptSurfacePaint{ .bytes = first_source.bytes, .owns_bytes = false, .selection = testSelectionInArea(6, 1, runtime.layout.content_bottom), .cursor = .{ .cursor_row = 6, .cursor_col = 1, .replaceable_row = 6 }, }; defer first_prepared.deinit(alloc); try first_prepared.line_visual_rows.appendSlice(alloc, &grown_rows); try makeSyntheticPreparedRenderable(&first_prepared, alloc); const first_facts = runtime.planTranscriptScroll(&first_prepared); try std.testing.expectEqual(@as(u16, 3), first_facts.semantic_rows); const first_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, first_facts.planned_rows, ); var first_plan = testPaintPlan(&runtime, first_prepared.selection); var first_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &first_source, &first_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(first_plan), &first_plan, first_scroll_plan, ); defer first_transition.deinit(alloc); runtime.consumeTranscriptTransition(alloc, &first_transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = 1, .document_append_committed = false, .committed_cursor_row = 6, .committed_cursor_col = 1, .shadow_state = .terminal_partial_write, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); var recovery = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.recovering, recovery.state, ); try std.testing.expectEqual(@as(u32, 4), recovery.visual_offset); try std.testing.expectEqual(@as(u16, 2), recovery.remaining_inline_rows); var second_source = try testSource(alloc, grown_flow, runtime.layout.cols); defer second_source.deinit(alloc); var second_prepared = transcript_painter.PreparedTranscriptSurfacePaint{ .bytes = second_source.bytes, .owns_bytes = false, .selection = testSelectionInArea(8, 1, runtime.layout.content_bottom), .cursor = .{ .cursor_row = 6, .cursor_col = 1, .replaceable_row = 6 }, }; defer second_prepared.deinit(alloc); try second_prepared.line_visual_rows.appendSlice(alloc, &grown_rows); try makeSyntheticPreparedRenderable(&second_prepared, alloc); const second_facts = runtime.planTranscriptScroll(&second_prepared); try std.testing.expectEqual(@as(u16, 2), second_facts.semantic_rows); const second_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, second_facts.planned_rows, ); var second_plan = testPaintPlan(&runtime, second_prepared.selection); var second_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &second_source, &second_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(second_plan), &second_plan, second_scroll_plan, ); defer second_transition.deinit(alloc); runtime.consumeTranscriptTransition(alloc, &second_transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = 1, .document_append_committed = false, .committed_cursor_row = 6, .committed_cursor_col = 1, .shadow_state = .terminal_partial_write, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); recovery = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.recovering, recovery.state, ); try std.testing.expectEqual(@as(u32, 5), recovery.visual_offset); try std.testing.expectEqual(@as(u16, 1), recovery.remaining_inline_rows); var final_source = try testSource(alloc, grown_flow, runtime.layout.cols); defer final_source.deinit(alloc); var final_prepared = transcript_painter.PreparedTranscriptSurfacePaint{ .bytes = final_source.bytes, .owns_bytes = false, .selection = testSelectionInArea(7, 1, runtime.layout.content_bottom), .cursor = .{ .cursor_row = 5, .cursor_col = 1, .replaceable_row = 5 }, }; defer final_prepared.deinit(alloc); try final_prepared.line_visual_rows.appendSlice(alloc, &grown_rows); try makeSyntheticPreparedRenderable(&final_prepared, alloc); const final_facts = runtime.planTranscriptScroll(&final_prepared); try std.testing.expectEqual(@as(u16, 1), final_facts.semantic_rows); const final_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, final_facts.planned_rows, ); var final_plan = testPaintPlan(&runtime, final_prepared.selection); var final_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &final_source, &final_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(final_plan), &final_plan, final_scroll_plan, ); defer final_transition.deinit(alloc); try std.testing.expectEqual(@as(u32, 6), final_transition.visual_offset); try std.testing.expectEqual(@as(usize, 6), final_transition.selection.start_line); try std.testing.expectEqual(@as(u16, 6), final_transition.selection.last_visible_row); try std.testing.expectEqual(@as(u16, 6), final_transition.cursor_row); try std.testing.expectEqual(@as(u16, 1), final_transition.cursor_col); try std.testing.expectEqual(@as(usize, 6), final_plan.viewport.start_line); try std.testing.expectEqual(@as(u16, 6), final_plan.viewport.last_visible_row); try std.testing.expectEqual(@as(u16, 6), final_plan.cursor_target.?.row); try std.testing.expectEqual(@as(u16, 1), final_plan.cursor_target.?.col); final_prepared.selection = final_transition.selection; runtime.consumeTranscriptTransition(alloc, &final_transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = final_scroll_plan.terminal_scroll_rows, .document_append_committed = !final_transition.document_append.isEmpty(), .committed_cursor_row = 6, .committed_cursor_col = 1, .shadow_state = .committed, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); const settled = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.stable, settled.state, ); try std.testing.expectEqual(@as(u32, 6), settled.visual_offset); const projection = runtime.stableTranscriptProjectionForFlow(grown_flow) orelse return error.TestUnexpectedResult; try std.testing.expectEqual(@as(usize, 6), projection.selection.start_line); try std.testing.expectEqual(@as(u16, 6), projection.occupied_last_row); try std.testing.expectEqual(@as(u16, 6), projection.cursor_row); try std.testing.expectEqual(@as(u16, 1), projection.cursor_col); try std.testing.expectEqual( @as(u16, 0), runtime.planTranscriptScroll(&final_prepared).planned_rows, ); } test "recovery backward area movement stabilizes at acknowledged semantic endpoint" { const alloc = std.testing.allocator; const stable_flow = "stable\n"; const attempt_flow = stable_flow ++ "attempt\n"; const attempt_rows = [_]u16{1} ** 10; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 10, 6), .owned_top_row = 5, }; defer runtime.deinit(alloc); try enterSemanticAppendRecovery( &runtime, alloc, stable_flow, attempt_flow, &attempt_rows, ); try consumeUnacceptedSemanticRecoveryRetry( &runtime, alloc, attempt_flow, &attempt_rows, 8, ); try consumeUnacceptedSemanticRecoveryRetry( &runtime, alloc, attempt_flow, &attempt_rows, 2, ); var final_source = try testSource(alloc, attempt_flow, runtime.layout.cols); defer final_source.deinit(alloc); var final_prepared = transcript_painter.PreparedTranscriptSurfacePaint{ .bytes = final_source.bytes, .owns_bytes = false, .selection = testSelectionInArea( 2, runtime.owned_top_row, runtime.layout.content_bottom, ), .cursor = .{ .cursor_row = 2, .cursor_col = 9, .replaceable_row = 2, }, }; defer final_prepared.deinit(alloc); try final_prepared.line_visual_rows.appendSlice(alloc, &attempt_rows); try makeSyntheticPreparedRenderable(&final_prepared, alloc); const final_facts = runtime.planTranscriptScroll(&final_prepared); try std.testing.expectEqual(@as(u16, 0), final_facts.resize_reflow_rows); try std.testing.expectEqual(@as(u16, 1), final_facts.semantic_rows); const final_scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, final_facts.planned_rows, ); var final_plan = testPaintPlan(&runtime, final_prepared.selection); var final_transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &final_source, &final_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(final_plan), &final_plan, final_scroll_plan, ); defer final_transition.deinit(alloc); try std.testing.expectEqual(@as(u32, 4), final_transition.visual_offset); try std.testing.expectEqual(@as(usize, 4), final_transition.selection.start_line); try std.testing.expectEqual(@as(u16, 6), final_transition.selection.last_visible_row); try std.testing.expectEqual(@as(u16, 6), final_transition.cursor_row); try std.testing.expectEqual(@as(u16, 1), final_transition.cursor_col); try std.testing.expectEqual(@as(usize, 4), final_plan.viewport.start_line); try std.testing.expectEqual(@as(u16, 6), final_plan.viewport.last_visible_row); try std.testing.expectEqual(@as(u16, 6), final_plan.cursor_target.?.row); try std.testing.expectEqual(@as(u16, 1), final_plan.cursor_target.?.col); runtime.consumeTranscriptTransition(alloc, &final_transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = final_scroll_plan.terminal_scroll_rows, .document_append_committed = !final_transition.document_append.isEmpty(), .committed_cursor_row = runtime.layout.content_bottom, .committed_cursor_col = 1, .shadow_state = .committed, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); const settled = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.stable, settled.state, ); try std.testing.expectEqual(@as(u32, 4), settled.visual_offset); const projection = runtime.stableTranscriptProjectionForFlow(attempt_flow) orelse return error.TestUnexpectedResult; try std.testing.expectEqual(@as(usize, 4), projection.selection.start_line); try std.testing.expectEqual(@as(u16, 6), projection.occupied_last_row); try std.testing.expectEqual(@as(u16, 6), projection.cursor_row); try std.testing.expectEqual(@as(u16, 1), projection.cursor_col); } test "same-width backward footer candidate retains one coherent stable anchor" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(120, 34, 30), .owned_top_row = 1, }; defer runtime.deinit(alloc); try installStableAnchorForTest( &runtime, alloc, "stable-flow", testSelection(88), 368, 19, 120, 1, ); var prepared = transcript_painter.PreparedTranscriptSurfacePaint{ .bytes = try alloc.dupe(u8, "stable-flow"), .selection = testSelection(85), .cursor = .{ .cursor_row = 30, .cursor_col = 120, .replaceable_row = 30, }, }; defer prepared.deinit(alloc); var line_rows = [_]u16{4} ** 100; for (line_rows[0..17]) |*rows| rows.* = 5; line_rows[87] = 3; try prepared.line_visual_rows.appendSlice(alloc, &line_rows); try std.testing.expectEqual(@as(u16, 0), runtime.planTranscriptScroll(&prepared).planned_rows); var source = TranscriptPreparationSource{ .bytes = try alloc.dupe(u8, "stable-flow"), .folded_summary_indices = &.{}, .preview = .{ .natural_visual_rows = 30 }, .tail_kind = null, .tracked_entry_id = null, .tracked_entry_start_line = null, .replaceable_last_line = false, .replaceable_start = 0, .replaceable_row = 30, .welcome_cut_line = null, .welcome_boundary = null, .cols = 120, }; defer source.deinit(alloc); const layout = render_engine.frame_layout.solve(.{ .terminal = runtime.layout, .owned_top = 1, .footer = .{ .natural_rows = 4, .min_rows = 4, .max_rows = 4 }, .transcript = source.preview, .prior = runtime.committed_frame_layout, }); var plan = layout.toPaintPlan(.{ .footer_rows = .{ .top = 31, .top_divider = 31, .banner = 31, .banner_active = false, .input_base = 32, .picker_divider = 33, .picker_start = 34, .bottom_divider = 33, .hint = 34, .total_rows = 4, }, .viewport = prepared.selection, .cursor_target = .{ .row = 32, .col = 1, .visible = true }, }); const scroll_plan = render_engine.frame_scroll_plan.FrameScrollPlan.none( runtime.layout.rows, 1, ); const scroll_facts = try runtime.prepareTranscriptScrollFactsForFrame( alloc, &source, &prepared, false, false, ); const resolved = try runtime.resolveTranscriptTransitionTargetForFrame( alloc, &source, &prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromLayout(layout), scroll_plan, scroll_facts, false, false, ); try std.testing.expectEqual(@as(usize, 85), prepared.selection.start_line); try std.testing.expectEqual(@as(u16, 30), prepared.cursor.cursor_row); try std.testing.expectEqual(@as(usize, 88), resolved.selection().start_line); try std.testing.expectEqual(@as(u16, 19), resolved.cursorRow()); try std.testing.expect(resolved.bodyDisposition() == .retain_committed); resolved.applyToPaintPlan(&plan); var transition = try runtime.sealTranscriptTransition( alloc, &source, &prepared, &plan, resolved, ); defer transition.deinit(alloc); try std.testing.expect(transition.body_disposition == .retain_committed); try std.testing.expectEqual(@as(usize, 88), transition.selection.start_line); try std.testing.expectEqual(@as(u32, 368), transition.visual_offset); try std.testing.expectEqual(@as(u16, 19), transition.cursor_row); try std.testing.expectEqual(@as(u16, 120), transition.cursor_col); try std.testing.expectEqual(@as(u16, 30), transition.materialized.cursor_row); try std.testing.expectEqual(@as(u16, 120), transition.materialized.cursor_col); try std.testing.expectEqual(@as(usize, 88), plan.viewport.start_line); try std.testing.expectEqual(@as(u16, 19), plan.cursor_target.?.row); try std.testing.expectEqual(@as(u16, 120), plan.cursor_target.?.col); runtime.consumeTranscriptTransition(alloc, &transition, .{ .bytes_written = 0, .changed_cells = 0, .full_repaint = false, .terminal_scroll_rows_committed = 0, .document_append_committed = false, .committed_cursor_row = 19, .committed_cursor_col = 120, .shadow_state = .committed, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); const diagnostic = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual(transcript_runtime.TranscriptCommitDiagnosticState.stable, diagnostic.state); try std.testing.expectEqual(@as(?usize, 88), diagnostic.stable_start_line); try std.testing.expectEqual(@as(u32, 368), diagnostic.visual_offset); const stable = runtime.stableTranscriptProjectionForFlow("stable-flow").?; try std.testing.expectEqual(@as(u16, 19), stable.cursor_row); try std.testing.expectEqual(@as(u16, 120), stable.cursor_col); } test "attempt source projections leave transcript runtime and commit state unchanged" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(40, 12, 8), .owned_top_row = 1, .viewport_top_row = 1, .last_rendered_cols = 17, .last_paint_bottom_reserved_rows = 3, .pending_scroll_compact = true, .replaceable_last_line = true, .replaceable_row = 7, .replaceable_start = 2, }; defer runtime.deinit(alloc); var metrics: Metrics = .{}; try runtime.writeTranscript( alloc, &metrics, "alpha\nbeta\ngamma\ndelta\nepsilon\nzeta\neta\ntheta\n", true, ); const entry_id = runtime.entries.items[0].id(); try runtime.folded_command_blocks.append(alloc, .{ .summary_entry_id = entry_id, .summary_transcript_index = 777, }); runtime.paint_trace.transcript_initialized = true; runtime.paint_trace.transcript_top = 9; runtime.paint_trace.transcript_bottom = 10; runtime.paint_trace.transcript_start_line = 42; runtime.paint_trace.transcript_total_lines = 43; runtime.paint_trace.transcript_visible_rows = 2; runtime.paint_trace.transcript_partial_skip_rows = 1; runtime.paint_trace.transcript_entries = 99; runtime.paint_trace.transcript_log_frame = true; try installStableAnchorForTest( &runtime, alloc, runtime.transcript.items, testSelection(2), 2, 6, 1, 9, ); const transcript_ptr = runtime.transcript.items.ptr; const transcript_capacity = runtime.transcript.capacity; const transcript_snapshot = try alloc.dupe(u8, runtime.transcript.items); defer alloc.free(transcript_snapshot); const paint_trace_snapshot = runtime.paint_trace; const commit_snapshot = runtime.transcriptCommitDiagnostic(); const cursor_row = runtime.cursor_row; const cursor_col = runtime.cursor_col; const replaceable_last_line = runtime.replaceable_last_line; const replaceable_row = runtime.replaceable_row; const replaceable_start = runtime.replaceable_start; const last_rendered_cols = runtime.last_rendered_cols; const last_paint_bottom_reserved_rows = runtime.last_paint_bottom_reserved_rows; var source = try runtime.prepareTranscriptSource(alloc, entry_id); defer source.deinit(alloc); var collapsed = try runtime.prepareTranscriptSurfacePaintFromSourceForArea( alloc, &metrics, &source, .{ .top = 4, .bottom = 3 }, ); collapsed.deinit(alloc); var compact = try runtime.prepareTranscriptSurfacePaintFromSourceForArea( alloc, &metrics, &source, .{ .top = 1, .bottom = 2 }, ); defer compact.deinit(alloc); var expanded = try runtime.prepareTranscriptSurfacePaintFromSourceForArea( alloc, &metrics, &source, .{ .top = 1, .bottom = 8 }, ); defer expanded.deinit(alloc); _ = runtime.planTranscriptScroll(&compact); _ = runtime.planTranscriptScroll(&expanded); try std.testing.expectEqual(transcript_ptr, runtime.transcript.items.ptr); try std.testing.expectEqual(transcript_capacity, runtime.transcript.capacity); try std.testing.expectEqualStrings(transcript_snapshot, runtime.transcript.items); try std.testing.expectEqual(@as(usize, 777), runtime.folded_command_blocks.items[0].summary_transcript_index); try std.testing.expectEqual(last_rendered_cols, runtime.last_rendered_cols); try std.testing.expectEqual(last_paint_bottom_reserved_rows, runtime.last_paint_bottom_reserved_rows); try std.testing.expect(runtime.pending_scroll_compact); try std.testing.expectEqualDeep(paint_trace_snapshot, runtime.paint_trace); try std.testing.expectEqualDeep(commit_snapshot, runtime.transcriptCommitDiagnostic()); try std.testing.expectEqual(cursor_row, runtime.cursor_row); try std.testing.expectEqual(cursor_col, runtime.cursor_col); try std.testing.expectEqual(replaceable_last_line, runtime.replaceable_last_line); try std.testing.expectEqual(replaceable_row, runtime.replaceable_row); try std.testing.expectEqual(replaceable_start, runtime.replaceable_start); } fn checkPrepareTranscriptSourceAllocationFailures(alloc: Allocator) !void { const welcome = "fx welcome banner line one\n" ++ "fx welcome banner line two\n"; const summary = "● 2 command lines folded\n"; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(24, 10, 6), .owned_top_row = 1, .max_transcript_bytes = 1024, }; defer runtime.deinit(alloc); _ = try runtime.appendRawTranscriptEntryClassified(alloc, welcome, .welcome); const summary_entry_id = try runtime.appendRawTranscriptEntryClassified( alloc, summary, .subagent_status, ); try runtime.folded_command_blocks.append(alloc, .{ .summary_entry_id = summary_entry_id, }); try appendFoldedLineForTest(&runtime, alloc, 0, "command output line"); var uncapped = try runtime.prepareTranscriptSource(alloc, summary_entry_id); defer uncapped.deinit(alloc); try std.testing.expectEqual(@as(usize, 4), uncapped.folded_summary_indices[0]); try std.testing.expectEqual(@as(?usize, 3), uncapped.tracked_entry_start_line); try std.testing.expectEqual(@as(u16, 6), uncapped.preview.natural_visual_rows); try std.testing.expectEqual( transcript_blocks.LeadingWelcomeBoundary{ .full_cut_line = 3, .tail_replay_line = 1, }, uncapped.welcome_boundary.?, ); try std.testing.expectEqual(@as(?usize, 3), uncapped.welcome_cut_line); runtime.max_transcript_bytes = summary.len; var source = try runtime.prepareTranscriptSource(alloc, summary_entry_id); defer source.deinit(alloc); try std.testing.expect(welcome.len + summary.len > runtime.max_transcript_bytes); try std.testing.expectEqualStrings(uncapped.bytes, source.bytes); try std.testing.expect(source.bytes.len > runtime.max_transcript_bytes); try std.testing.expectEqual(@as(usize, 1), source.folded_summary_indices.len); try std.testing.expectEqual(@as(usize, 4), source.folded_summary_indices[0]); try std.testing.expectEqual(@as(?usize, 3), source.tracked_entry_start_line); try std.testing.expectEqual(@as(u16, 6), source.preview.natural_visual_rows); try std.testing.expectEqual( transcript_blocks.LeadingWelcomeBoundary{ .full_cut_line = 3, .tail_replay_line = 1, }, source.welcome_boundary.?, ); try std.testing.expectEqual(@as(?usize, 3), source.welcome_cut_line); } test "structured transcript source frees every partial allocation" { try std.testing.checkAllAllocationFailures( std.testing.allocator, checkPrepareTranscriptSourceAllocationFailures, .{}, ); } fn checkPreviewPreparationFailureLeavesRuntimeUnchanged(alloc: Allocator) !void { var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(24, 10, 6), .owned_top_row = 1, .max_transcript_bytes = 128, .last_rendered_cols = 17, }; defer runtime.deinit(alloc); try runtime.initBacking(alloc); const entry_id = try runtime.appendRawTranscriptEntryClassified( alloc, "recorded source\n", .subagent_status, ); runtime.transcript.clearRetainingCapacity(); try runtime.transcript.appendSlice(alloc, "poisoned cache\n"); runtime.transcript_cache_origin_untrimmed = false; try runtime.folded_command_blocks.append(alloc, .{ .summary_entry_id = entry_id, .summary_transcript_index = 777, }); runtime.transcript_band_dirty = false; runtime.paint_trace.transcript_initialized = true; runtime.paint_trace.transcript_start_line = 41; const entry_count = runtime.entries.items.len; const next_entry_id = runtime.next_entry_id; const commit_state = runtime.transcriptCommitDiagnostic(); const paint_trace = runtime.paint_trace; const pending_repaints = runtime.render_requests.pendingReasonCount(); _ = runtime.previewTranscriptFlow(alloc) catch |err| { try std.testing.expectEqualStrings("poisoned cache\n", runtime.transcript.items); try std.testing.expectEqual(entry_count, runtime.entries.items.len); try std.testing.expectEqual(entry_id, runtime.entries.items[0].id()); try std.testing.expectEqualStrings( "recorded source\n", runtime.entries.items[0].raw_bytes.bytes, ); try std.testing.expectEqual(next_entry_id, runtime.next_entry_id); try std.testing.expectEqual( @as(usize, 777), runtime.folded_command_blocks.items[0].summary_transcript_index, ); try std.testing.expectEqualDeep(commit_state, runtime.transcriptCommitDiagnostic()); try std.testing.expectEqualDeep(paint_trace, runtime.paint_trace); try std.testing.expectEqual( pending_repaints, runtime.render_requests.pendingReasonCount(), ); try std.testing.expect(!runtime.transcript_band_dirty); return err; }; } test "preview preparation failure leaves cache entries anchors and repaint state unchanged" { try std.testing.checkAllAllocationFailures( std.testing.allocator, checkPreviewPreparationFailureLeavesRuntimeUnchanged, .{}, ); } test "tail-capped raw fallback rebases retained folded indices and invalidates removed summaries" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(40, 10, 6), .owned_top_row = 1, .full_transcript = .{ .depth = .full }, }; defer runtime.deinit(alloc); try runtime.transcript.appendSlice(alloc, "removed-summary\nretained-summary"); try runtime.folded_command_blocks.append(alloc, .{ .summary_transcript_index = 1, }); try runtime.folded_command_blocks.append(alloc, .{ .summary_transcript_index = 2, }); try appendFoldedLineForTest(&runtime, alloc, 0, "removed-output"); try appendFoldedLineForTest(&runtime, alloc, 1, "retained-output"); runtime.max_transcript_bytes = "retained-summary".len + 1; var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); try std.testing.expectEqualStrings("retained-summary", source.bytes); try std.testing.expectEqualSlices( usize, &.{ 0, 1 }, source.folded_summary_indices, ); var prepared = try prepareTestSourceForCurrentArea(&runtime, alloc, &source); defer prepared.deinit(alloc); try std.testing.expectEqual(@as(usize, 1), prepared.total_lines); try std.testing.expectEqual(@as(usize, 1), prepared.visible_lines.items.len); try std.testing.expect( std.meta.activeTag(prepared.visible_lines.items[0]) == .folded_line, ); try std.testing.expectEqualStrings( "retained-output", resolveVisibleLineForTest( prepared.visible_lines.items[0], .{ .bytes = prepared.bytes }, ), ); runtime.max_transcript_bytes = 4; var partial = try runtime.prepareTranscriptSource(alloc, null); defer partial.deinit(alloc); try std.testing.expectEqualStrings("mary", partial.bytes); try std.testing.expectEqualSlices( usize, &.{ 0, 0 }, partial.folded_summary_indices, ); var partial_paint = try prepareTestSourceForCurrentArea( &runtime, alloc, &partial, ); defer partial_paint.deinit(alloc); try std.testing.expectEqual(@as(usize, 1), partial_paint.total_lines); try std.testing.expect( std.meta.activeTag(partial_paint.visible_lines.items[0]) == .transcript, ); try std.testing.expectEqualStrings( "mary", resolveVisibleLineForTest( partial_paint.visible_lines.items[0], .{ .bytes = partial_paint.bytes }, ), ); } test "structured source keeps tracked entries before the cache tail" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(40, 10, 6), .owned_top_row = 1, }; defer runtime.deinit(alloc); const removed_id = try runtime.appendRawTranscriptEntryClassified( alloc, "removed\n", .subagent_status, ); const retained_id = try runtime.appendRawTranscriptEntryClassified( alloc, "tracked\n", .subagent_status, ); runtime.max_transcript_bytes = "tracked".len + 1; var retained = try runtime.prepareTranscriptSource(alloc, retained_id); defer retained.deinit(alloc); try std.testing.expectEqualStrings("removed\ntracked", retained.bytes); try std.testing.expectEqual(@as(?usize, 1), retained.tracked_entry_start_line); var retained_paint = try prepareTestSourceForCurrentArea( &runtime, alloc, &retained, ); defer retained_paint.deinit(alloc); try std.testing.expectEqual(@as(?u16, 2), retained_paint.tracked_entry_row); var removed = try runtime.prepareTranscriptSource(alloc, removed_id); defer removed.deinit(alloc); try std.testing.expectEqual(@as(?usize, 0), removed.tracked_entry_start_line); var removed_paint = try prepareTestSourceForCurrentArea( &runtime, alloc, &removed, ); defer removed_paint.deinit(alloc); try std.testing.expectEqual(@as(?u16, 1), removed_paint.tracked_entry_row); runtime.max_transcript_bytes = 4; var partial = try runtime.prepareTranscriptSource(alloc, retained_id); defer partial.deinit(alloc); try std.testing.expectEqualStrings("removed\ntracked", partial.bytes); try std.testing.expectEqual(@as(?usize, 1), partial.tracked_entry_start_line); var partial_paint = try prepareTestSourceForCurrentArea( &runtime, alloc, &partial, ); defer partial_paint.deinit(alloc); try std.testing.expectEqual(@as(?u16, 2), partial_paint.tracked_entry_row); } test "structured source keeps welcome boundary before the cache tail" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(40, 10, 6), .owned_top_row = 1, }; defer runtime.deinit(alloc); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "welcome one\nwelcome two\n", .welcome, ); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "tail\n", .subagent_status, ); var uncapped = try runtime.prepareTranscriptSource(alloc, null); defer uncapped.deinit(alloc); try std.testing.expectEqual( transcript_blocks.LeadingWelcomeBoundary{ .full_cut_line = 3, .tail_replay_line = 1, }, uncapped.welcome_boundary.?, ); try std.testing.expectEqual(@as(?usize, 3), uncapped.welcome_cut_line); runtime.max_transcript_bytes = "tail".len + 1; var capped = try runtime.prepareTranscriptSource(alloc, null); defer capped.deinit(alloc); try std.testing.expectEqualStrings("welcome one\nwelcome two\n\ntail", capped.bytes); try std.testing.expectEqual( transcript_blocks.LeadingWelcomeBoundary{ .full_cut_line = 3, .tail_replay_line = 1, }, capped.welcome_boundary.?, ); try std.testing.expectEqual(@as(?usize, 3), capped.welcome_cut_line); } test "structured source derives replaceable span from full bytes" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 8, 5), .owned_top_row = 1, }; defer runtime.deinit(alloc); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "prefix\n", .subagent_status, ); _ = try runtime.appendReplaceableTranscriptLineSilentClassified( alloc, "0123456789", .subagent_status, ); runtime.max_transcript_bytes = "0123456789".len + 1; var whole_tail = try runtime.prepareTranscriptSource(alloc, null); defer whole_tail.deinit(alloc); try std.testing.expectEqualStrings("prefix\n0123456789", whole_tail.bytes); try std.testing.expect(whole_tail.replaceable_last_line); try std.testing.expectEqual(@as(usize, "prefix\n".len), whole_tail.replaceable_start); runtime.max_transcript_bytes = 4; var partial_tail = try runtime.prepareTranscriptSource(alloc, null); defer partial_tail.deinit(alloc); try std.testing.expectEqualStrings("prefix\n0123456789", partial_tail.bytes); try std.testing.expect(partial_tail.replaceable_last_line); try std.testing.expectEqual(@as(usize, "prefix\n".len), partial_tail.replaceable_start); try std.testing.expect(partial_tail.preview.replaceable_active); var partial_paint = try prepareTestSourceForCurrentArea( &runtime, alloc, &partial_tail, ); defer partial_paint.deinit(alloc); try std.testing.expect(partial_paint.replaceable_last_line); try std.testing.expectEqual(@as(usize, "prefix\n".len), partial_paint.replaceable_start); try std.testing.expectEqual(@as(u16, 2), partial_paint.selection.replaceable_start_row); runtime.max_transcript_bytes = 0; var empty = try runtime.prepareTranscriptSource(alloc, null); defer empty.deinit(alloc); try std.testing.expectEqualStrings("prefix\n0123456789", empty.bytes); try std.testing.expect(empty.replaceable_last_line); try std.testing.expectEqual(@as(usize, "prefix\n".len), empty.replaceable_start); try std.testing.expectEqual(@as(u16, 2), empty.preview.natural_visual_rows); try std.testing.expectEqual(@as(u16, 0), empty.preview.trailing_boundary_blank_rows); try std.testing.expectEqual(@as(u16, 1), empty.preview.footer_boundary_gap_rows); try std.testing.expectEqual( @as(?transcript_blocks.TranscriptBlockKind, .subagent_status), empty.preview.tail_kind, ); try std.testing.expect(empty.preview.replaceable_active); } test "structured source preview matches full paint geometry" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(4, 8, 5), .owned_top_row = 1, }; defer runtime.deinit(alloc); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "removed-long\n", .subagent_status, ); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "tail\n\n", .subagent_status, ); runtime.max_transcript_bytes = "tail".len + 1; var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); try std.testing.expectEqualStrings("removed-long\ntail", source.bytes); try std.testing.expectEqual(@as(u16, 4), source.preview.natural_visual_rows); try std.testing.expect(source.preview.trailing_boundary_blank_rows >= 1); try std.testing.expect(source.preview.footer_boundary_gap_rows >= 1); try std.testing.expectEqual( @as(?transcript_blocks.TranscriptBlockKind, .subagent_status), source.preview.tail_kind, ); try std.testing.expect(!source.preview.replaceable_active); var prepared = try prepareTestSourceForCurrentArea(&runtime, alloc, &source); defer prepared.deinit(alloc); try std.testing.expect(prepared.total_lines >= 1); try std.testing.expect(prepared.sourceVisualRows().len > 0); const solved = render_engine.frame_layout.solve(.{ .terminal = runtime.layout, .owned_top = runtime.owned_top_row, .footer = .{ .natural_rows = 2, .min_rows = 2, .max_rows = 2 }, .transcript = source.preview, }); try std.testing.expect(!solved.transcript_area.isEmpty()); } test "hidden context notices do not change compact footer or activity tail policy" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(80, 12, 8), .owned_top_row = 1, }; defer runtime.deinit(alloc); var metrics = Metrics{}; _ = try runtime.streamAssistantChunk(alloc, &metrics, "visible response"); _ = try runtime.appendSemanticNotice(alloc, .{ .topic = "context", .tone = .warning, .body = "hidden trailing context", .visibility = .full_only, }); runtime.last_paint_bottom_reserved_rows = 2; var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); try std.testing.expect(std.mem.find(u8, source.bytes, "hidden trailing context") == null); try std.testing.expectEqual( @as(?transcript_blocks.TranscriptBlockKind, .assistant_turn), source.preview.tail_kind, ); try std.testing.expectEqual(@as(u16, 1), source.preview.footer_boundary_gap_rows); try std.testing.expectEqual(@as(u16, 1), runtime.transientAssistantGapRows()); try std.testing.expectEqual(@as(u16, 2), runtime.preservedBottomReservationRows()); var only_context = TranscriptRuntime{ .layout = transcriptTestLayout(80, 12, 8), .owned_top_row = 1, }; defer only_context.deinit(alloc); _ = try only_context.appendSemanticNotice(alloc, .{ .topic = "context", .tone = .warning, .body = "only hidden context", .visibility = .full_only, }); only_context.last_paint_bottom_reserved_rows = 2; var empty_source = try only_context.prepareTranscriptSource(alloc, null); defer empty_source.deinit(alloc); try std.testing.expectEqualStrings("", empty_source.bytes); try std.testing.expectEqual(@as(?transcript_blocks.TranscriptBlockKind, null), empty_source.preview.tail_kind); try std.testing.expectEqual(@as(u16, 0), empty_source.preview.footer_boundary_gap_rows); try std.testing.expectEqual(@as(u16, 0), only_context.transientAssistantGapRows()); try std.testing.expectEqual(@as(u16, 0), only_context.preservedBottomReservationRows()); } test "recorded projection ignores a poisoned cache without rebuilding it" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(40, 12, 8), .owned_top_row = 1, }; defer runtime.deinit(alloc); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "recorded first\n", .subagent_status, ); const tracked_id = try runtime.appendRawTranscriptEntryClassified( alloc, "recorded tracked\n", .subagent_status, ); runtime.transcript.clearRetainingCapacity(); try runtime.transcript.appendSlice(alloc, "poisoned cache\n"); runtime.transcript_cache_origin_untrimmed = false; const preview = try runtime.previewTranscriptFlow(alloc); try std.testing.expectEqual(@as(u16, 2), preview.natural_visual_rows); var metrics: Metrics = .{}; var prepared = try runtime.prepareTranscriptSurfacePaintForAreaTrackingEntry( alloc, &metrics, .{ .top = 1, .bottom = 4 }, tracked_id, ); defer prepared.deinit(alloc); try std.testing.expect(std.mem.find(u8, prepared.bytes, "recorded first") != null); try std.testing.expect(std.mem.find(u8, prepared.bytes, "recorded tracked") != null); try std.testing.expect(std.mem.find(u8, prepared.bytes, "poisoned cache") == null); try std.testing.expectEqual(@as(?u16, 2), prepared.tracked_entry_row); var shadow = try vt_emulator.Grid.init( alloc, runtime.layout.cols, runtime.layout.rows, ); defer shadow.deinit(); const plan = testPaintPlan(&runtime, prepared.selection); var surface = try render_engine.frame_surface.FrameSurface.initFromShadow( alloc, plan, shadow, ); defer surface.deinit(); _ = try runtime.paintPreparedTranscriptIntoSurface( alloc, &surface, &prepared, ); var target = try surface.copyToTargetGrid(alloc); defer target.deinit(); var row_text: std.ArrayList(u8) = .empty; defer row_text.deinit(alloc); try target.rowTextTrimmed(1, &row_text); try std.testing.expectEqualStrings("recorded first", row_text.items); row_text.clearRetainingCapacity(); try target.rowTextTrimmed(2, &row_text); try std.testing.expectEqualStrings("recorded tracked", row_text.items); try std.testing.expectEqualStrings("poisoned cache\n", runtime.transcript.items); } test "cache-only paint excludes record false transient bytes" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(40, 10, 6), .owned_top_row = 1, }; defer runtime.deinit(alloc); try runtime.transcript.appendSlice(alloc, "cache only\n"); var metrics: Metrics = .{}; try runtime.writeTranscript(alloc, &metrics, "transient only\n", false); try std.testing.expectEqual(@as(usize, 0), runtime.entries.items.len); try std.testing.expectEqualStrings("cache only\n", runtime.transcript.items); const preview = try runtime.previewTranscriptFlow(alloc); try std.testing.expectEqual(@as(u16, 1), preview.natural_visual_rows); var prepared = try runtime.prepareTranscriptSurfacePaintForArea( alloc, &metrics, .{ .top = 1, .bottom = 3 }, ); defer prepared.deinit(alloc); try std.testing.expectEqualStrings("cache only\n", prepared.bytes); } test "zero-column preparation uses cache fallback even when entries exist" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(40, 10, 6), .owned_top_row = 1, }; defer runtime.deinit(alloc); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "recorded source\n", .subagent_status, ); runtime.transcript.clearRetainingCapacity(); try runtime.transcript.appendSlice(alloc, "bootstrap cache\n"); runtime.layout.cols = 0; var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); try std.testing.expectEqualStrings("bootstrap cache\n", source.bytes); } test "reconstructive paint repairs a poisoned cache from entries" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(40, 10, 6), .owned_top_row = 1, }; defer runtime.deinit(alloc); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "recorded reconstruction\n", .subagent_status, ); runtime.transcript.clearRetainingCapacity(); try runtime.transcript.appendSlice(alloc, "poisoned cache\n"); var metrics: Metrics = .{}; try runtime.reconstructivePaint(alloc, &metrics); try std.testing.expect( std.mem.find(u8, runtime.transcript.items, "recorded reconstruction") != null, ); try std.testing.expect(std.mem.find(u8, runtime.transcript.items, "poisoned cache") == null); try std.testing.expect(runtime.viewport_clear_pending); try std.testing.expect(runtime.render_requests.hasReason(.transcript)); } test "append-only source remains compatible while tail replacement invalidates anchor" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(40, 12, 8), .owned_top_row = 1, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); var metrics: Metrics = .{}; try runtime.writeTranscript(alloc, &metrics, "stable", true); try installStableAnchorForTest( &runtime, alloc, runtime.transcript.items, testSelection(0), 0, 1, 7, 19, ); try runtime.writeTranscript(alloc, &metrics, " append", true); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.stable, runtime.transcriptCommitDiagnostic().state, ); try std.testing.expect(runtime.stableTranscriptProjectionForFlow(runtime.transcript.items) != null); const tail_id = runtime.entries.items[runtime.entries.items.len - 1].id(); try std.testing.expect(try runtime.updateRawBytesEntry( alloc, tail_id, " append extended", )); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.invalid, runtime.transcriptCommitDiagnostic().state, ); } test "prepared wrapped line delta creates visual-row inline scroll rows" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 8, .cols = 10, .content_bottom = 4, .divider_top_row = 5, .input_row = 6, .divider_bottom_row = 7, .hint_row = 8, }, .viewport_top_row = 1, .owned_top_row = 1, .cursor_row = 4, .cursor_col = 1, .has_painted_transcript = true, .has_committed_frame = true, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); try installStableAnchorForTest( &runtime, alloc, "abcdefghijklmnopqrstuvwxyz\nshort\nend", testSelection(0), 0, 4, 1, 11, ); var metrics: Metrics = .{}; var prepared = try runtime.prepareTranscriptSurfacePaintForArea( alloc, &metrics, .{ .top = 1, .bottom = 2 }, ); defer prepared.deinit(alloc); try std.testing.expectEqual(@as(usize, 1), prepared.selection.start_line); try std.testing.expectEqual(@as(u16, 3), prepared.sourceVisualRows()[0]); const planned = runtime.planTranscriptScroll(&prepared).planned_rows; try std.testing.expectEqual(@as(u16, 3), planned); } test "prepareTranscriptSurfacePaintForArea keeps rows inside solved transcript rect" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 12, .cols = 40, .content_bottom = 8, .divider_top_row = 9, .input_row = 10, .divider_bottom_row = 11, .hint_row = 12, }, .viewport_top_row = 1, .owned_top_row = 1, .cursor_row = 8, .cursor_col = 1, .has_painted_transcript = true, .last_paint_bottom_reserved_rows = 3, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); try runtime.transcript.appendSlice(alloc, "line 1\nline 2\nline 3\nline 4\nline 5\nline 6\n"); var metrics: Metrics = .{}; var prepared = try runtime.prepareTranscriptSurfacePaintForArea( alloc, &metrics, .{ .top = 3, .bottom = 5 }, ); defer prepared.deinit(alloc); try std.testing.expect(prepared.selection.top_row >= 3); try std.testing.expect(prepared.selection.bottom_row <= 5); try std.testing.expect(prepared.selection.last_visible_row <= 5); try std.testing.expectEqual(@as(u16, 0), prepared.bottom_reserved_rows); try std.testing.expectEqual(@as(u16, 3), runtime.last_paint_bottom_reserved_rows); } test "prepared transcript tracks an entry row without rerendering the selection" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 12, .cols = 40, .content_bottom = 8, .divider_top_row = 9, .input_row = 10, .divider_bottom_row = 11, .hint_row = 12, }, .viewport_top_row = 1, .owned_top_row = 1, .cursor_row = 8, .cursor_col = 1, .has_painted_transcript = true, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); _ = try runtime.appendRawTranscriptEntryClassified(alloc, "first\n\n", .unknown_raw); const tracked_id = try runtime.appendRawTranscriptEntryClassified( alloc, "● Running command\x1b[0m\n", .tool_status, ); var metrics: Metrics = .{}; var visible = try runtime.prepareTranscriptSurfacePaintForAreaTrackingEntry( alloc, &metrics, .{ .top = 1, .bottom = 4 }, tracked_id, ); defer visible.deinit(alloc); try std.testing.expectEqual(@as(?u32, tracked_id), visible.tracked_entry_id); try std.testing.expectEqual(@as(?u16, 3), visible.tracked_entry_row); var i: usize = 0; while (i < 6) : (i += 1) { _ = try runtime.appendRawTranscriptEntryClassified(alloc, "later\n", .unknown_raw); } var clipped = try runtime.prepareTranscriptSurfacePaintForAreaTrackingEntry( alloc, &metrics, .{ .top = 1, .bottom = 2 }, tracked_id, ); defer clipped.deinit(alloc); try std.testing.expectEqual(@as(?u32, tracked_id), clipped.tracked_entry_id); try std.testing.expectEqual(@as(?u16, null), clipped.tracked_entry_row); } test "prepared transcript source can omit one active entry without changing durable entries" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 12, .cols = 40, .content_bottom = 8, .divider_top_row = 9, .input_row = 10, .divider_bottom_row = 11, .hint_row = 12, }, }; defer runtime.deinit(alloc); _ = try runtime.appendRawTranscriptEntryClassified(alloc, "before\n", .subagent_status); const active_id = try runtime.appendRawTranscriptEntryClassified(alloc, "running\n", .subagent_status); _ = try runtime.appendRawTranscriptEntryClassified(alloc, "after\n", .subagent_status); var active = try runtime.prepareTranscriptSourceOmittingEntry(alloc, active_id); defer active.deinit(alloc); try std.testing.expect(std.mem.find(u8, active.bytes, "running") == null); try std.testing.expect(std.mem.find(u8, active.bytes, "before") != null); try std.testing.expect(std.mem.find(u8, active.bytes, "after") != null); try std.testing.expect(!active.cache_origin_untrimmed); var terminal = try runtime.prepareTranscriptSource(alloc, null); defer terminal.deinit(alloc); try std.testing.expect(std.mem.find(u8, terminal.bytes, "running") != null); try std.testing.expect(terminal.cache_origin_untrimmed); } test "prepareTranscriptSurfacePaintForArea does not update committed inline scroll baseline" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 12, .cols = 40, .content_bottom = 8, .divider_top_row = 9, .input_row = 10, .divider_bottom_row = 11, .hint_row = 12, }, .viewport_top_row = 4, .owned_top_row = 4, .cursor_row = 8, .cursor_col = 1, .has_painted_transcript = true, .has_committed_frame = true, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); try installStableAnchorForTest( &runtime, alloc, "line 1\nline 2\nline 3\nline 4\nline 5\nline 6\n", testSelection(1), 1, 8, 1, 12, ); const commit_before = runtime.transcriptCommitDiagnostic(); var metrics: Metrics = .{}; var prepared = try runtime.prepareTranscriptSurfacePaintForArea( alloc, &metrics, .{ .top = 1, .bottom = 3 }, ); defer prepared.deinit(alloc); try std.testing.expectEqualDeep(commit_before, runtime.transcriptCommitDiagnostic()); try std.testing.expectEqual(@as(u16, 4), runtime.owned_top_row); try std.testing.expectEqual(@as(u16, 4), runtime.viewport_top_row); } test "full transcript paint replaces the fold summary with captured lines" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(80, 12, 8), }; defer runtime.deinit(alloc); try runtime.transcript.appendSlice(alloc, "a\nsummary\nb\n"); try runtime.folded_command_blocks.append(alloc, .{ .summary_transcript_index = 2 }); try runtime.folded_command_blocks.items[0].lines.append(alloc, .{ .stream = .stdout, .text = try alloc.dupe(u8, "hidden-1") }); try runtime.folded_command_blocks.items[0].lines.append(alloc, .{ .stream = .stderr, .text = try alloc.dupe(u8, "hidden-2") }); runtime.full_transcript.depth = .full; var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); var metrics: Metrics = .{}; var prepared = try runtime.prepareTranscriptSurfacePaintFromSourceForArea( alloc, &metrics, &source, .{ .top = 1, .bottom = 8 }, ); defer prepared.deinit(alloc); const source_buf = TranscriptBuffer{ .bytes = source.bytes }; try std.testing.expectEqual(@as(usize, 4), prepared.visible_lines.items.len); try std.testing.expectEqualStrings("a", resolveVisibleLineForTest(prepared.visible_lines.items[0], source_buf)); try std.testing.expect(std.meta.activeTag(prepared.visible_lines.items[1]) == .folded_line); try std.testing.expectEqualStrings("hidden-1", resolveVisibleLineForTest(prepared.visible_lines.items[1], source_buf)); try std.testing.expect(std.meta.activeTag(prepared.visible_lines.items[2]) == .folded_line); try std.testing.expectEqualStrings("hidden-2", resolveVisibleLineForTest(prepared.visible_lines.items[2], source_buf)); try std.testing.expectEqualStrings("b", resolveVisibleLineForTest(prepared.visible_lines.items[3], source_buf)); } test "collapsed state still shows the fold summary" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(80, 12, 8), }; defer runtime.deinit(alloc); try runtime.transcript.appendSlice(alloc, "a\nsummary\nb\n"); try runtime.folded_command_blocks.append(alloc, .{ .summary_transcript_index = 2 }); try runtime.folded_command_blocks.items[0].lines.append(alloc, .{ .stream = .stdout, .text = try alloc.dupe(u8, "hidden-1") }); var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); var metrics: Metrics = .{}; var prepared = try runtime.prepareTranscriptSurfacePaintFromSourceForArea( alloc, &metrics, &source, .{ .top = 1, .bottom = 8 }, ); defer prepared.deinit(alloc); const source_buf = TranscriptBuffer{ .bytes = source.bytes }; try std.testing.expectEqual(@as(usize, 3), prepared.visible_lines.items.len); try std.testing.expect(std.meta.activeTag(prepared.visible_lines.items[1]) == .transcript); try std.testing.expectEqualStrings("summary", resolveVisibleLineForTest(prepared.visible_lines.items[1], source_buf)); } test "source-less surface paint keeps transcript lines one to one under full transcript" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = .{ .rows = 24, .cols = 80, .content_bottom = 21, .divider_top_row = 22, .input_row = 23, .divider_bottom_row = 24, .hint_row = 22 }, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); try runtime.transcript.appendSlice(alloc, "a\nsummary\nb\n"); try runtime.folded_command_blocks.append(alloc, .{ .summary_transcript_index = 2 }); runtime.full_transcript.depth = .full; var metrics: Metrics = .{}; var prepared = try runtime.prepareTranscriptSurfacePaintForArea( alloc, &metrics, .{ .top = 1, .bottom = 3 }, ); defer prepared.deinit(alloc); try std.testing.expectEqual(@as(usize, 3), prepared.visible_lines.items.len); for (prepared.visible_lines.items) |line| { try std.testing.expect(std.meta.activeTag(line) == .transcript); } } test "record false command output remains transient" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(80, 12, 8) }; defer runtime.deinit(alloc); var metrics = Metrics{}; try runtime.writeCommandOutputChunk( alloc, &metrics, commandOutputTestStyles(), .stdout, "transient\n", false, ); try std.testing.expectEqual(@as(usize, 1), runtime.command_output_blocks.items.len); try std.testing.expectEqualStrings("transient", runtime.command_output_blocks.items[0].lines.items[0].text); try std.testing.expectEqual(@as(usize, 0), runtime.entries.items.len); try std.testing.expectEqual(@as(usize, 0), runtime.transcript.items.len); try runtime.flushCommandOutputSummary( alloc, &metrics, commandOutputTestStyles(), false, ); try std.testing.expectEqual(@as(usize, 0), runtime.command_output_blocks.items.len); try std.testing.expect(runtime.command_output_display.open_command_block == null); } test "command output consolidation preserves committed prompt scrollback anchor" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(80, 12, 8), .owned_top_row = 1, }; defer runtime.deinit(alloc); const prompt = try alloc.dupe(u8, "give me a table of my system information"); _ = try runtime.appendUserTurnOwned(alloc, .{ .text = prompt, .images = &.{}, }); var live_entry_ids: [18]u32 = undefined; for (0..18) |index| { var line: [48]u8 = undefined; const text = try std.fmt.bufPrint(&line, "SYSTEM OUTPUT {d:0>2}\\n", .{index + 1}); const bytes = try alloc.dupe(u8, text); live_entry_ids[index] = try runtime.appendRawBytesEntryClassified( alloc, bytes, .unknown_raw, ); } const first_assistant_id = try runtime.appendAssistantTurnEntry(alloc); const first_assistant = runtime.lookupAssistantSegments(first_assistant_id) orelse return error.TestExpectedAssistantSegments; for (0..24) |index| { var line: [48]u8 = undefined; const text = try std.fmt.bufPrint(&line, "assistant table row {d}\n", .{index + 1}); try first_assistant.text.appendSlice(alloc, text); } // The assistant content above is complete; report producer closure so // the finality floor does not hold the tail. runtime.transcript_release = runtime.transcript_release.with_assistant_tail_writable(false); var live_source = try runtime.prepareTranscriptSource(alloc, null); defer live_source.deinit(alloc); try std.testing.expect(std.mem.find(u8, live_source.bytes, "give me a table") != null); try std.testing.expect(std.mem.find(u8, live_source.bytes, "SYSTEM OUTPUT 01") != null); try std.testing.expect(std.mem.find(u8, live_source.bytes, "assistant table row 24") != null); var live_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &live_source, ); defer live_prepared.deinit(alloc); try commitPreparedForTest(&runtime, alloc, &live_source, &live_prepared); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.stable, runtime.transcriptCommitDiagnostic().state, ); try removeRawEntriesForTest(&runtime, alloc, &live_entry_ids); const compact_bytes = try alloc.dupe( u8, "SYSTEM OUTPUT 01\n... 16 command lines folded\nSYSTEM OUTPUT 18\n", ); _ = try runtime.appendRawBytesEntryClassified( alloc, compact_bytes, .unknown_raw, ); try runtime.rebuildTranscriptCacheAfterStructuredRewrite( alloc, "command output consolidation", ); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.stable, runtime.transcriptCommitDiagnostic().state, ); var metrics = Metrics{}; try runtime.writeTranscriptClassified( alloc, &metrics, "permission accepted\n", true, .subagent_status, ); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.stable, runtime.transcriptCommitDiagnostic().state, ); const second_assistant_id = try runtime.appendAssistantTurnEntry(alloc); const second_assistant = runtime.lookupAssistantSegments(second_assistant_id) orelse return error.TestExpectedAssistantSegments; for (0..60) |index| { var line: [40]u8 = undefined; const text = try std.fmt.bufPrint(&line, "follow-up table row {d}\n", .{index + 1}); try second_assistant.text.appendSlice(alloc, text); } var compact_source = try runtime.prepareTranscriptSource(alloc, null); defer compact_source.deinit(alloc); try std.testing.expect(std.mem.find(u8, compact_source.bytes, "give me a table") != null); try std.testing.expect(std.mem.find(u8, compact_source.bytes, "follow-up table row 60") != null); var compact_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &compact_source, ); defer compact_prepared.deinit(alloc); const facts = runtime.planTranscriptScroll(&compact_prepared); try std.testing.expect(facts.target_visual_offset > facts.source_visual_offset); // The intervening recorded write rebases the committed prompt/assistant // prefix before the new table is admitted, so release can start here. try std.testing.expect(facts.source_compatible); try std.testing.expect(!facts.recovery_rebase); try std.testing.expect(facts.semantic_rows > 0); try std.testing.expect(facts.planned_rows > 0); try std.testing.expect(std.mem.startsWith(u8, compact_source.bytes, runtime.transcript_commit_state.stable.flow)); try std.testing.expect(std.mem.find(u8, runtime.transcript_commit_state.stable.flow, "give me a table") != null); try std.testing.expect(std.mem.find(u8, runtime.transcript_commit_state.stable.flow, "follow-up table row") == null); const history_before_release = runtime.transcriptCommitDiagnostic().history_visual_offset; try commitPreparedForTest(&runtime, alloc, &compact_source, &compact_prepared); try std.testing.expect(runtime.transcriptCommitDiagnostic().history_visual_offset > history_before_release); try std.testing.expect(runtime.transcriptCommitDiagnostic().history_visual_offset - history_before_release <= facts.semantic_rows); // This fixture settles in that release; a repeated frame must be quiet. var settle_source = try runtime.prepareTranscriptSource(alloc, null); defer settle_source.deinit(alloc); var settle_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &settle_source, ); defer settle_prepared.deinit(alloc); const settle_facts = runtime.planTranscriptScroll(&settle_prepared); try std.testing.expect(settle_facts.source_compatible); try std.testing.expectEqual(@as(u32, 0), settle_facts.semantic_rows); try std.testing.expectEqual(@as(u16, 0), settle_facts.planned_rows); const settled_history = runtime.transcriptCommitDiagnostic().history_visual_offset; try commitPreparedForTest(&runtime, alloc, &settle_source, &settle_prepared); try std.testing.expectEqual(settled_history, runtime.transcriptCommitDiagnostic().history_visual_offset); try std.testing.expectEqual(runtime.transcript_commit_state.stable.visual_offset, settled_history); } fn removeRawEntriesForTest( runtime: *TranscriptRuntime, alloc: Allocator, ids: []const u32, ) !void { for (ids) |id| { var index: usize = 0; while (index < runtime.entries.items.len) : (index += 1) { const entry = runtime.entries.items[index]; if (entry == .raw_bytes and entry.raw_bytes.id == id) { var removed = runtime.entries.orderedRemove(index); removed.deinit(alloc); break; } } else return error.TestExpectedRawEntry; } } test "command output stores terminal-safe text for live consolidated and full transcript views" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = commandOutputTestRuntime(sink); defer runtime.deinit(alloc); var metrics = Metrics{}; const styles = commandOutputTestStyles(); const raw = "\x1b[2JCSI_CLEAR_TOKEN\x1b[31mCSI_SGR_TOKEN\x1b[0m\r\x08\x07\x7f\n"; const safe = "\\x08\\x07\\x7f"; try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, raw, true); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stderr, "unterminated-final", true); try std.testing.expectEqualStrings(safe, runtime.command_output_blocks.items[0].lines.items[0].text); try std.testing.expectEqualStrings("unterminated-final", runtime.command_output_blocks.items[0].lines.items[1].text); const live = try renderEntriesToBytes(alloc, runtime.entries.items, 80); defer alloc.free(live); try std.testing.expect(std.mem.find(u8, live, safe) != null); try std.testing.expect(std.mem.find(u8, live, "\x1b[2JCSI_CLEAR_TOKEN") == null); try runtime.flushCommandOutputSummary(alloc, &metrics, styles, true); const consolidated = try renderEntriesToBytes(alloc, runtime.entries.items, 80); defer alloc.free(consolidated); try std.testing.expect(std.mem.find(u8, consolidated, safe) != null); try std.testing.expect(std.mem.find(u8, consolidated, "\x1b[2JCSI_CLEAR_TOKEN") == null); runtime.has_committed_frame = true; runtime.owned_top_row = 5; try std.testing.expect(try runtime.setTranscriptPresentationDepth(alloc, .full)); var projection = try full_transcript_screen.buildProjection( alloc, runtime.entries.items, runtime.tool_details.items, runtime.command_output_blocks.items, runtime.command_output_render.styles, runtime.layout.cols, runtime.fullTranscriptAnchorEntryId(), ); defer projection.deinit(alloc); const source_bytes = try full_transcript_screen.renderProjectionViewportSourceInterruptible( alloc, &projection, null, runtime.layout.cols, 16, 0, null, ); var source = try runtime.prepareFullTranscriptViewportSource(alloc, source_bytes); defer source.deinit(alloc); try std.testing.expect(std.mem.find(u8, source.bytes, safe) != null); try std.testing.expect(std.mem.find(u8, source.bytes, "\x1b[2JCSI_CLEAR_TOKEN") == null); } test "command output keeps capped and folded transcript behavior" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = commandOutputTestRuntime(sink); defer runtime.deinit(alloc); var metrics = Metrics{}; const styles = commandOutputTestStyles(); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "line-one\n", true); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stderr, "line-two\n", true); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "line-three\n", true); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "line-four\n", true); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "line-five\n", true); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "line-six\n", true); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "line-seven\n", true); try runtime.flushCommandOutputSummary(alloc, &metrics, styles, true); try std.testing.expectEqual(@as(usize, 1), runtime.command_output_blocks.items.len); try std.testing.expectEqual(@as(usize, 7), runtime.command_output_blocks.items[0].lines.items.len); try std.testing.expectEqualStrings("line-one", runtime.command_output_blocks.items[0].lines.items[0].text); try std.testing.expectEqualStrings("line-seven", runtime.command_output_blocks.items[0].lines.items[6].text); } test "command output state keeps one authoritative folded hint" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = commandOutputTestRuntime(sink); defer runtime.deinit(alloc); var metrics = Metrics{}; const styles = commandOutputTestStyles(); try runtime.writeCommandOutputChunk( alloc, &metrics, styles, .stdout, "fast-1\nfast-2\nfast-3\nfast-4\nfast-5\nfast-6\n", true, ); const old_hint_entry_id = runtime.command_output_blocks.items[0].lines.items[5].entry_id.?; try expectRawEntryBytes( &runtime, old_hint_entry_id, "│ … 1 line more (ctrl+o to view)", ); try runtime.writeCommandOutputChunk( alloc, &metrics, styles, .stdout, "fast-7\nfast-8\n", true, ); const block = runtime.command_output_blocks.items[0]; try std.testing.expectEqual(@as(usize, 8), block.total_lines); try std.testing.expectEqual(@as(usize, 8), block.lines.items.len); const newest_entry_id = block.lines.items[7].entry_id.?; try expectRawEntryBytes( &runtime, old_hint_entry_id, "│ … 1 line more (ctrl+o to view)", ); try expectRawEntryBytes(&runtime, newest_entry_id, ""); var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); try std.testing.expectEqual(@as(usize, 0), source.bytes.len); } test "command output consolidation replaces live row before following transcript rows" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = commandOutputTestRuntime(sink); defer runtime.deinit(alloc); var metrics = Metrics{}; const styles = commandOutputTestStyles(); _ = try runtime.appendRawTranscriptEntryClassified(alloc, "before\n", .subagent_status); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "middle\n", true); _ = try runtime.appendRawTranscriptEntryClassified(alloc, "after\n", .subagent_status); try runtime.flushCommandOutputSummary(alloc, &metrics, styles, true); var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); const before_pos = std.mem.indexOf(u8, source.bytes, "before") orelse return error.MissingBeforeRow; const after_pos = std.mem.indexOf(u8, source.bytes, "after") orelse return error.MissingAfterRow; try std.testing.expect(before_pos < after_pos); try std.testing.expect(std.mem.indexOf(u8, source.bytes, "middle") == null); try std.testing.expectEqualStrings("middle", runtime.command_output_blocks.items[0].lines.items[0].text); } test "command output consolidation preserves rows between noncontiguous live rows" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = commandOutputTestRuntime(sink); defer runtime.deinit(alloc); var metrics = Metrics{}; const styles = commandOutputTestStyles(); _ = try runtime.appendRawTranscriptEntryClassified(alloc, "before\n", .subagent_status); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "command-one\n", true); _ = try runtime.appendRawTranscriptEntryClassified(alloc, "intervening\n", .subagent_status); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "command-two\n", true); _ = try runtime.appendRawTranscriptEntryClassified(alloc, "after\n", .subagent_status); try runtime.flushCommandOutputSummary(alloc, &metrics, styles, true); var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); const before_pos = std.mem.indexOf(u8, source.bytes, "before") orelse return error.MissingBeforeRow; const intervening_pos = std.mem.indexOf(u8, source.bytes, "intervening") orelse return error.MissingInterveningRow; const after_pos = std.mem.indexOf(u8, source.bytes, "after") orelse return error.MissingAfterRow; try std.testing.expect(before_pos < intervening_pos); try std.testing.expect(intervening_pos < after_pos); try std.testing.expect(std.mem.indexOf(u8, source.bytes, "command-one") == null); try std.testing.expect(std.mem.indexOf(u8, source.bytes, "command-two") == null); try std.testing.expectEqualStrings("command-one", runtime.command_output_blocks.items[0].lines.items[0].text); try std.testing.expectEqualStrings("command-two", runtime.command_output_blocks.items[0].lines.items[1].text); } test "command output folding preserves rows between noncontiguous live rows" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = commandOutputTestRuntime(sink); defer runtime.deinit(alloc); var metrics = Metrics{}; const styles = commandOutputTestStyles(); _ = try runtime.appendRawTranscriptEntryClassified(alloc, "before\n", .subagent_status); for (0..5) |index| { var line: [32]u8 = undefined; const text = try std.fmt.bufPrint(&line, "command-visible-{d}\n", .{index + 1}); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, text, true); } _ = try runtime.appendRawTranscriptEntryClassified(alloc, "intervening\n", .subagent_status); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "command-two\n", true); _ = try runtime.appendRawTranscriptEntryClassified(alloc, "after\n", .subagent_status); try runtime.flushCommandOutputSummary(alloc, &metrics, styles, true); var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); const before_pos = std.mem.indexOf(u8, source.bytes, "before") orelse return error.MissingBeforeRow; const intervening_pos = std.mem.indexOf(u8, source.bytes, "intervening") orelse return error.MissingInterveningRow; const after_pos = std.mem.indexOf(u8, source.bytes, "after") orelse return error.MissingAfterRow; try std.testing.expect(before_pos < intervening_pos); try std.testing.expect(intervening_pos < after_pos); try std.testing.expect(std.mem.indexOf(u8, source.bytes, "command-visible-1") == null); try std.testing.expect(std.mem.indexOf(u8, source.bytes, "ctrl+o to view") == null); try std.testing.expectEqual(@as(usize, 6), runtime.command_output_blocks.items[0].lines.items.len); } test "partial command records remain durable across an intervening notice" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = commandOutputTestRuntime(sink); defer runtime.deinit(alloc); runtime.layout.cols = 16; var metrics = Metrics{}; const styles = commandOutputTestStyles(); try runtime.writeCommandOutputChunk( alloc, &metrics, styles, .stdout, "first record wraps across many physical rows and remains intentionally long", true, ); _ = try runtime.appendRawTranscriptEntryClassified(alloc, "intervening-notice\n", .subagent_status); try runtime.writeCommandOutputChunk( alloc, &metrics, styles, .stdout, "\nlater-hidden-record\n", true, ); var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); const notice_pos = std.mem.find(u8, source.bytes, "intervening-notice") orelse return error.MissingInterveningRow; try std.testing.expect(notice_pos < source.bytes.len); try std.testing.expect(std.mem.find(u8, source.bytes, "first record") == null); try std.testing.expect(std.mem.find(u8, source.bytes, "later-hidden-record") == null); try std.testing.expectEqual(@as(usize, 2), runtime.command_output_blocks.items[0].lines.items.len); try std.testing.expect(std.mem.find(u8, runtime.command_output_blocks.items[0].lines.items[0].text, "first record") != null); try std.testing.expectEqualStrings("later-hidden-record", runtime.command_output_blocks.items[0].lines.items[1].text); } test "production command pruning preserves deferred replay around a notice" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = commandOutputTestRuntime(sink); defer runtime.deinit(alloc); var metrics = Metrics{}; const styles = commandOutputTestStyles(); const status_entry_id = try transcript_store.appendPinnedToolStatusAtomic( &runtime, alloc, "● Ran printf ordered-output\n", ); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "output-0\n", true); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "intervening-notice\n", .subagent_status, ); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "output-1\n", true); try runtime.flushCommandOutputSummary(alloc, &metrics, styles, true); try runtime.attachHistoricalToolDetailAfterCommandOutput( alloc, status_entry_id, .{ .id = "ordered-output", .name = "run_command", .arguments_json = "{\"command\":\"printf ordered-output\"}", }, .command, .{ .tool_call_id = @constCast("ordered-output"), .tool_name = @constCast("run_command"), .status = .success, .output = @constCast("exit_code=0\n\noutput-0\noutput-1\n\n"), .output_bytes = "exit_code=0\n\noutput-0\noutput-1\n\n".len, .stored_output_bytes = "exit_code=0\n\noutput-0\noutput-1\n\n".len, }, ); const pruned_source_entry_id = runtime.command_output_blocks.items[0].lines.items[1].entry_id.?; try std.testing.expect(try command_output_runtime.trimRetainedCommandOutputLine( &runtime, alloc, 0, 1, )); _ = try command_output_runtime.syncCommandOutputBlockEntries(&runtime, alloc); try runtime.rebuildTranscriptCacheAfterStructuredRewrite(alloc, "test command prune"); try std.testing.expectEqual(@as(usize, 1), runtime.command_output_blocks.items[0].pruned_ranges.items.len); try std.testing.expectEqual( pruned_source_entry_id, runtime.command_output_blocks.items[0].pruned_ranges.items[0].anchor_entry_id, ); var compact = try runtime.prepareTranscriptSource(alloc, null); defer compact.deinit(alloc); const compact_notice_pos = std.mem.find(u8, compact.bytes, "intervening-notice") orelse return error.MissingInterveningRow; try std.testing.expect(compact_notice_pos < compact.bytes.len); try std.testing.expect(std.mem.find(u8, compact.bytes, "output-0") == null); try std.testing.expect(std.mem.find(u8, compact.bytes, "output-1") == null); try std.testing.expect(std.mem.find(u8, compact.bytes, "ctrl+o to view") == null); runtime.full_transcript.depth = .full; var projection = try runtime.buildFullTranscriptProjection(alloc, null); defer projection.deinit(alloc); const source = try full_transcript_screen.renderProjectionViewportSourceInterruptible( alloc, &projection, null, runtime.layout.cols, 64, 0, null, ); defer alloc.free(source); const output_0_pos = std.mem.find(u8, source, "output-0") orelse return error.MissingFirstCommandOutputRow; const notice_pos = std.mem.find(u8, source, "intervening-notice") orelse return error.MissingInterveningRow; const output_1_pos = std.mem.find(u8, source, "output-1") orelse return error.MissingSecondCommandOutputRow; try std.testing.expect(output_0_pos < notice_pos); try std.testing.expect(notice_pos < output_1_pos); } test "production all-pruned command keeps process and hint at final anchor" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = commandOutputTestRuntime(sink); defer runtime.deinit(alloc); var metrics = Metrics{}; const styles = commandOutputTestStyles(); const status_entry_id = try transcript_store.appendPinnedToolStatusAtomic( &runtime, alloc, "● Ran printf all-pruned\n", ); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "pruned-0\n", true); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "pruned-separator\n", .subagent_status, ); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "pruned-1\n", true); try runtime.flushCommandOutputSummary(alloc, &metrics, styles, true); try runtime.attachHistoricalToolDetailAfterCommandOutput( alloc, status_entry_id, .{ .id = "all-pruned", .name = "run_command", .arguments_json = "{\"command\":\"printf all-pruned\"}", }, .command, .{ .tool_call_id = @constCast("all-pruned"), .tool_name = @constCast("run_command"), .status = .success, .output = @constCast("exit_code=7\n\npruned-0\npruned-1\n\n"), .output_bytes = "exit_code=7\n\npruned-0\npruned-1\n\n".len, .stored_output_bytes = "exit_code=7\n\npruned-0\npruned-1\n\n".len, .command_process_presentation = .{ .exit_code = 7 }, }, ); const final_anchor = runtime.command_output_blocks.items[0].lines.items[1].entry_id.?; try std.testing.expect(try command_output_runtime.trimRetainedCommandOutputLine( &runtime, alloc, 0, 0, )); try std.testing.expect(try command_output_runtime.trimRetainedCommandOutputLine( &runtime, alloc, 0, 0, )); try std.testing.expectEqual(@as(usize, 0), runtime.command_output_blocks.items[0].lines.items.len); try std.testing.expectEqual(@as(usize, 2), runtime.command_output_blocks.items[0].pruned_ranges.items.len); _ = try command_output_runtime.syncCommandOutputBlockEntries(&runtime, alloc); try runtime.rebuildTranscriptCacheAfterStructuredRewrite(alloc, "test all-pruned command"); var final_anchor_bytes: ?[]const u8 = null; for (runtime.entries.items) |entry| { if (entry.id() != final_anchor or entry != .raw_bytes) continue; final_anchor_bytes = entry.raw_bytes.bytes; break; } const final = final_anchor_bytes orelse return error.MissingFinalCommandAnchor; try std.testing.expect(std.mem.find(u8, final, "│ exit code 7") != null); try std.testing.expect(std.mem.find(u8, final, "│ … 2 lines more") != null); var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); const notice_pos = std.mem.find(u8, source.bytes, "pruned-separator") orelse return error.MissingInterveningRow; try std.testing.expect(notice_pos < source.bytes.len); try std.testing.expect(std.mem.find(u8, source.bytes, "│ exit code 7") == null); try std.testing.expect(std.mem.find(u8, source.bytes, "│ … 2 lines more") == null); try std.testing.expect(std.mem.find(u8, source.bytes, "pruned-0") == null); try std.testing.expect(std.mem.find(u8, source.bytes, "pruned-1") == null); } test "pruned command ranges bridge after their separator is retained then removed" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = commandOutputTestRuntime(sink); defer runtime.deinit(alloc); var metrics = Metrics{}; const styles = commandOutputTestStyles(); const status_entry_id = try transcript_store.appendPinnedToolStatusAtomic( &runtime, alloc, "● Ran printf ranges\n", ); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "range-0\nrange-1\n", true); _ = try runtime.appendRawTranscriptEntryClassified(alloc, "range-separator\n", .subagent_status); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "range-2\nrange-3\n", true); try runtime.flushCommandOutputSummary(alloc, &metrics, styles, true); try runtime.attachHistoricalToolDetailAfterCommandOutput( alloc, status_entry_id, .{ .id = "ranges", .name = "run_command", .arguments_json = "{\"command\":\"printf ranges\"}" }, .command, .{ .tool_call_id = @constCast("ranges"), .tool_name = @constCast("run_command"), .status = .success, .output = @constCast("exit_code=0\n\nrange-0\nrange-1\nrange-2\nrange-3\n\n"), .output_bytes = "exit_code=0\n\nrange-0\nrange-1\nrange-2\nrange-3\n\n".len, .stored_output_bytes = "exit_code=0\n\nrange-0\nrange-1\nrange-2\nrange-3\n\n".len, }, ); while (runtime.command_output_blocks.items[0].lines.items.len > 0) { try std.testing.expect(try command_output_runtime.trimRetainedCommandOutputLine( &runtime, alloc, 0, 0, )); } try std.testing.expectEqual(@as(usize, 2), runtime.command_output_blocks.items[0].pruned_ranges.items.len); const retained_before = transcript_store.retainedStructuredBytes(&runtime); runtime.max_retained_transcript_bytes = retained_before - 1; try runtime.enforceStructuredRetention(alloc, null); try std.testing.expectEqual(@as(usize, 1), runtime.command_output_blocks.items[0].pruned_ranges.items.len); const range = runtime.command_output_blocks.items[0].pruned_ranges.items[0]; try std.testing.expectEqual(@as(usize, 0), range.start_record); try std.testing.expectEqual(@as(usize, 4), range.end_record); try std.testing.expect( transcript_store.retainedStructuredBytes(&runtime) <= runtime.max_retained_transcript_bytes, ); } test "command output detail retargets when first split source row is pruned" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = commandOutputTestRuntime(sink); defer runtime.deinit(alloc); var metrics = Metrics{}; const styles = commandOutputTestStyles(); const lifecycle_id = types.ToolLifecycleId{ .turn_id = 3, .call_id = "split-prune" }; _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = lifecycle_id, .reconciles_provisional_call_id = null, .tool_name = "run_command", .activity_kind = .command, .arguments_json = "{\"command\":\"printf split\"}", } }); try runtime.writeCommandOutputChunkForLifecycle(alloc, &metrics, styles, lifecycle_id, .stdout, "command-one\n", true); _ = try runtime.appendRawTranscriptEntryClassified(alloc, "intervening\n", .subagent_status); try runtime.writeCommandOutputChunkForLifecycle(alloc, &metrics, styles, lifecycle_id, .stdout, "command-two\n", true); try runtime.flushCommandOutputSummaryForLifecycle(alloc, &metrics, styles, lifecycle_id, true); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = lifecycle_id, .outcome = .{ .kind = .completed, .summary = "Ran printf split" }, } }); const status_entry_id = runtime.toolActivityRecord(lifecycle_id).?.entry_id; const first_source_id = runtime.command_output_blocks.items[0].source_entry_ids.items[0]; const second_source_id = runtime.command_output_blocks.items[0].source_entry_ids.items[1]; try std.testing.expectEqual(first_source_id, runtime.toolDetailForEntry(status_entry_id).?.command_output_entry_id.?); for (runtime.entries.items, 0..) |entry, entry_index| { if (entry.id() != first_source_id) continue; var removed = runtime.entries.orderedRemove(entry_index); removed.deinit(alloc); break; } try runtime.enforceStructuredRetention(alloc, second_source_id); try std.testing.expectEqual(second_source_id, runtime.command_output_blocks.items[0].entry_id.?); try std.testing.expectEqual(second_source_id, runtime.toolDetailForEntry(status_entry_id).?.command_output_entry_id.?); try std.testing.expectEqual(status_entry_id, runtime.detailEntryIdForCommandOutputSource(second_source_id).?); } test "command output retains records within the compact storage limit" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = commandOutputTestRuntime(sink); defer runtime.deinit(alloc); var metrics = Metrics{}; const styles = commandOutputTestStyles(); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "retained-one\n", true); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stderr, "retained-two\n", true); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "retained-three\n", true); try runtime.flushCommandOutputSummary(alloc, &metrics, styles, true); try std.testing.expectEqual(@as(usize, 0), runtime.folded_command_blocks.items.len); try std.testing.expectEqual(@as(usize, 3), runtime.entries.items.len); try std.testing.expectEqual(@as(usize, 3), runtime.command_output_blocks.items[0].lines.items.len); try std.testing.expectEqualStrings("retained-one", runtime.command_output_blocks.items[0].lines.items[0].text); try std.testing.expectEqualStrings("retained-two", runtime.command_output_blocks.items[0].lines.items[1].text); try std.testing.expectEqualStrings("retained-three", runtime.command_output_blocks.items[0].lines.items[2].text); } test "command output entries are classified" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = commandOutputTestRuntime(sink); defer runtime.deinit(alloc); var metrics = Metrics{}; const styles = commandOutputTestStyles(); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "line one\n", true); try std.testing.expect(runtime.entries.items.len > 0); try std.testing.expectEqual(RawEntryClass.command_output, runtime.entries.items[runtime.entries.items.len - 1].raw_bytes.class); try runtime.flushCommandOutputSummary(alloc, &metrics, styles, true); try std.testing.expect(runtime.entries.items.len > 0); try std.testing.expectEqual(RawEntryClass.command_output, runtime.entries.items[runtime.entries.items.len - 1].raw_bytes.class); } test "pre-flush live command output chunks match consolidated row geometry" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = commandOutputTestRuntime(sink); defer runtime.deinit(alloc); var metrics = Metrics{}; const styles = commandOutputAnsiTestStyles(); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "one\n", true); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stderr, "two\n", true); const live = try renderEntriesToBytes(alloc, runtime.entries.items, 80); defer alloc.free(live); try std.testing.expect(std.mem.find(u8, live, "\x1b[2m│ one\x1b[0m") != null); try std.testing.expect(std.mem.find(u8, live, "\x1b[2m│ two\x1b[0m") != null); try std.testing.expect(std.mem.find(u8, live, "\x1b[31m") == null); try runtime.flushCommandOutputSummary(alloc, &metrics, styles, true); const consolidated = try renderEntriesToBytes(alloc, runtime.entries.items, 80); defer alloc.free(consolidated); try std.testing.expectEqualStrings(live, consolidated); } test "concurrent command output lifecycles remain isolated" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = commandOutputTestRuntime(sink); defer runtime.deinit(alloc); var metrics = Metrics{}; const styles = commandOutputTestStyles(); const active_id = types.ToolLifecycleId{ .turn_id = 7, .call_id = "active-command" }; const foreign_id = types.ToolLifecycleId{ .turn_id = 7, .call_id = "foreign-command" }; try runtime.writeCommandOutputChunkForLifecycle(alloc, &metrics, styles, active_id, .stdout, "active-one\n", true); try runtime.writeCommandOutputChunkForLifecycle( alloc, &metrics, styles, foreign_id, .stderr, "foreign\n", true, ); try runtime.flushCommandOutputSummaryForLifecycle(alloc, &metrics, styles, foreign_id, true); try std.testing.expect(runtime.command_output_display.open_command_block == null); try std.testing.expectEqual(@as(usize, 2), runtime.command_output_blocks.items.len); try std.testing.expectEqual(@as(usize, 1), runtime.command_output_blocks.items[0].lines.items.len); try std.testing.expectEqualStrings("active-one", runtime.command_output_blocks.items[0].lines.items[0].text); try std.testing.expectEqual(@as(usize, 1), runtime.command_output_blocks.items[1].lines.items.len); try std.testing.expectEqualStrings("foreign", runtime.command_output_blocks.items[1].lines.items[0].text); try runtime.writeCommandOutputChunkForLifecycle(alloc, &metrics, styles, active_id, .stderr, "active-two\n", true); try runtime.flushCommandOutputSummaryForLifecycle(alloc, &metrics, styles, active_id, true); try std.testing.expect(runtime.command_output_display.open_command_block == null); try std.testing.expectEqual(@as(usize, 2), runtime.command_output_blocks.items.len); try std.testing.expectEqual(@as(usize, 2), runtime.command_output_blocks.items[0].lines.items.len); try std.testing.expectEqual(command_output_content.Stream.stdout, runtime.command_output_blocks.items[0].lines.items[0].stream); try std.testing.expectEqual(command_output_content.Stream.stderr, runtime.command_output_blocks.items[0].lines.items[1].stream); try std.testing.expectEqualStrings("active-one", runtime.command_output_blocks.items[0].lines.items[0].text); try std.testing.expectEqualStrings("active-two", runtime.command_output_blocks.items[0].lines.items[1].text); try std.testing.expectEqualStrings("foreign", runtime.command_output_blocks.items[1].lines.items[0].text); } test "command output display caps at five physical rows" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = .{ .rows = 24, .cols = 80, .content_bottom = 21, .divider_top_row = 22, .input_row = 23, .divider_bottom_row = 24, .hint_row = 22, }, }; defer runtime.deinit(std.testing.allocator); var metrics = Metrics{}; const styles: Styles = .{ .system_notice_label_style = "", .system_notice_text_style = "", .reset_style = "", .dim_style = "", .red_style = "", }; var line_index: usize = 0; while (line_index < 26) : (line_index += 1) { const line = try std.fmt.allocPrint(std.testing.allocator, "line-{d}\n", .{line_index}); defer std.testing.allocator.free(line); try runtime.writeCommandOutputChunk(std.testing.allocator, &metrics, styles, .stdout, line, true); } try runtime.flushCommandOutputSummary(std.testing.allocator, &metrics, styles, true); try std.testing.expectEqual(@as(usize, 1), runtime.command_output_blocks.items.len); try std.testing.expectEqual(@as(usize, 26), runtime.command_output_blocks.items[0].lines.items.len); var projection = try command_output_runtime.renderCompactCommandOutput( std.testing.allocator, runtime.command_output_blocks.items[0], .{ .styles = styles }, 80, ); defer projection.deinit(std.testing.allocator); try std.testing.expectEqual(@as(usize, 5), std.mem.count(u8, projection.bytes.items, "│ line-")); try std.testing.expect(std.mem.find(u8, projection.bytes.items, "│ line-4") != null); try std.testing.expect(std.mem.find(u8, projection.bytes.items, "│ line-5") == null); try std.testing.expect(std.mem.find(u8, projection.bytes.items, "│ … 21 lines more (ctrl+o to view)") != null); } test "structured retention prunes old raw transcript entries past cap" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = .{ .rows = 24, .cols = 80, .content_bottom = 21, .divider_top_row = 22, .input_row = 23, .divider_bottom_row = 24, .hint_row = 22 }, .max_transcript_bytes = 128, .max_retained_transcript_bytes = 32, }; defer runtime.deinit(alloc); var metrics = Metrics{}; try runtime.initViewport(&metrics, 1); try runtime.writeTranscript(alloc, &metrics, "raw-one-12345\n", true); try runtime.writeTranscript(alloc, &metrics, "raw-two-12345\n", true); try runtime.writeTranscript(alloc, &metrics, "raw-three-123\n", true); try std.testing.expect(transcript_store.retainedStructuredBytes(&runtime) <= runtime.max_retained_transcript_bytes); try std.testing.expect(runtime.entries.items.len < 3); try std.testing.expect(std.mem.indexOf(u8, runtime.transcript.items, "raw-one") == null); try std.testing.expect(std.mem.indexOf(u8, runtime.transcript.items, "raw-three") != null); } test "structured retention compacts a large pruning pass in entry order" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = .{ .rows = 24, .cols = 80, .content_bottom = 21, .divider_top_row = 22, .input_row = 23, .divider_bottom_row = 24, .hint_row = 22 }, .max_transcript_bytes = 4096, .max_retained_transcript_bytes = std.math.maxInt(usize), }; defer runtime.deinit(alloc); for (0..2048) |index| { const line = try std.fmt.allocPrint(alloc, "retained-entry-{d:0>4}\n", .{index}); _ = runtime.appendRawBytesEntryClassified(alloc, line, .unknown_raw) catch |err| { alloc.free(line); return err; }; } runtime.max_retained_transcript_bytes = 1024; try runtime.enforceStructuredRetention(alloc, null); try std.testing.expect(transcript_store.retainedStructuredBytes(&runtime) <= 1024); try std.testing.expect(runtime.entries.items.len < 2048); try std.testing.expect(runtime.entries.items.len > 0); for (runtime.entries.items[1..], runtime.entries.items[0 .. runtime.entries.items.len - 1]) |entry, previous| { try std.testing.expect(previous.id() < entry.id()); } try std.testing.expect(std.mem.indexOf(u8, runtime.transcript.items, "retained-entry-0000") == null); try std.testing.expect(std.mem.indexOf(u8, runtime.transcript.items, "retained-entry-2047") != null); } test "assistant streaming retention trims active segment text" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = .{ .rows = 24, .cols = 80, .content_bottom = 21, .divider_top_row = 22, .input_row = 23, .divider_bottom_row = 24, .hint_row = 22 }, .max_transcript_bytes = 128, .max_retained_transcript_bytes = 32, }; defer runtime.deinit(alloc); var metrics = Metrics{}; try runtime.initViewport(&metrics, 1); _ = try runtime.streamAssistantChunk(alloc, &metrics, "aaaaaaaaaaaaaaaa\n"); _ = try runtime.streamAssistantChunk(alloc, &metrics, "bbbbbbbbbbbbbbbb\n"); _ = try runtime.streamAssistantChunk(alloc, &metrics, "cccccccccccccccc\n"); try std.testing.expectEqual(@as(usize, 1), runtime.entries.items.len); try std.testing.expect(runtime.entries.items[0] == .assistant_turn); const text = runtime.entries.items[0].assistant_turn.segments.text.items; try std.testing.expect(text.len <= runtime.max_retained_transcript_bytes); try std.testing.expect(std.mem.indexOf(u8, text, "cccccccc") != null); try std.testing.expect(std.mem.indexOf(u8, text, "aaaaaaaa") == null); try std.testing.expect(transcript_store.retainedStructuredBytes(&runtime) <= runtime.max_retained_transcript_bytes); } test "hidden command output becomes count-only at the hard cap" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = .{ .rows = 24, .cols = 80, .content_bottom = 21, .divider_top_row = 22, .input_row = 23, .divider_bottom_row = 24, .hint_row = 22 }, .max_transcript_bytes = 512, }; defer runtime.deinit(alloc); var metrics = Metrics{}; const styles = commandOutputTestStyles(); const retained_lines = [_][]const u8{ "line-00\n", "line-01\n", "line-02\n", "line-03\n", "line-04\n", "line-05\n", }; for (retained_lines, 0..) |line, index| { const stream: command_output_content.Stream = if (index % 2 == 0) .stdout else .stderr; try runtime.writeCommandOutputChunk(alloc, &metrics, styles, stream, line, true); } runtime.max_retained_transcript_bytes = transcript_store.retainedStructuredBytes(&runtime); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "line-06\n", true); try std.testing.expectEqual(@as(usize, 6), runtime.command_output_blocks.items[0].lines.items.len); try std.testing.expectEqualStrings("line-00", runtime.command_output_blocks.items[0].lines.items[0].text); try std.testing.expect(std.mem.find(u8, runtime.command_output_blocks.items[0].lines.items[5].text, "line-06") == null); try std.testing.expect(transcript_store.retainedStructuredBytes(&runtime) <= runtime.max_retained_transcript_bytes); var failing = std.testing.FailingAllocator.init(alloc, .{ .fail_index = 0 }); try runtime.writeCommandOutputChunk( failing.allocator(), &metrics, styles, .stderr, "line-07\n", true, ); try std.testing.expectEqual(@as(usize, 0), failing.allocations); try runtime.flushCommandOutputSummary(alloc, &metrics, styles, true); const block = runtime.command_output_blocks.items[0]; try std.testing.expectEqual(@as(usize, 8), block.total_lines); const expected_summary = try std.fmt.allocPrint( alloc, "│ … {d} lines more (ctrl+o to view)", .{block.total_lines - @min(@as(usize, 5), block.lines.items.len)}, ); defer alloc.free(expected_summary); var projection = try command_output_runtime.renderCompactCommandOutput( alloc, block, .{ .styles = styles }, 80, ); defer projection.deinit(alloc); try std.testing.expect(std.mem.find(u8, projection.bytes.items, expected_summary) != null); var retained_text_bytes: usize = 0; for (block.lines.items) |line| retained_text_bytes += line.text.len; try std.testing.expectEqual(retained_text_bytes, block.retained_text_bytes); _ = try runtime.streamAssistantChunk(alloc, &metrics, "assistant after cap"); try std.testing.expect(std.mem.find(u8, runtime.transcript.items, "assistant after cap") != null); } test "command output becomes count-only at the hard cap" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = .{ .rows = 24, .cols = 80, .content_bottom = 21, .divider_top_row = 22, .input_row = 23, .divider_bottom_row = 24, .hint_row = 22 }, .max_transcript_bytes = 512, }; defer runtime.deinit(alloc); var metrics = Metrics{}; const styles = commandOutputTestStyles(); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "line-00\n", true); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stderr, "line-01\n", true); runtime.max_retained_transcript_bytes = transcript_store.retainedStructuredBytes(&runtime); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "line-02\n", true); var failing = std.testing.FailingAllocator.init(alloc, .{ .fail_index = 0 }); try runtime.writeCommandOutputChunk( failing.allocator(), &metrics, styles, .stderr, "line-03\n", true, ); try std.testing.expectEqual(@as(usize, 0), failing.allocations); const block = runtime.command_output_blocks.items[0]; try std.testing.expectEqual(@as(usize, 4), block.total_lines); try std.testing.expect(block.lines.items.len < block.total_lines); try std.testing.expect(transcript_store.retainedStructuredBytes(&runtime) <= runtime.max_retained_transcript_bytes); } test "blank hidden command output becomes count-only at the hard cap" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = .{ .rows = 24, .cols = 80, .content_bottom = 21, .divider_top_row = 22, .input_row = 23, .divider_bottom_row = 24, .hint_row = 22 }, .max_transcript_bytes = 256, .max_retained_transcript_bytes = 64, }; defer runtime.deinit(alloc); var metrics = Metrics{}; const styles = commandOutputTestStyles(); for (0..8) |_| { try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "\n", true); } const block = &runtime.command_output_blocks.items[0]; try std.testing.expect(block.lines.items.len < block.total_lines); try std.testing.expectEqual(@as(usize, 8), block.total_lines); try std.testing.expectEqual(@as(usize, 0), block.retained_text_bytes); try std.testing.expect(transcript_store.retainedStructuredBytes(&runtime) <= runtime.max_retained_transcript_bytes); var failing = std.testing.FailingAllocator.init(alloc, .{ .fail_index = 0 }); try runtime.writeCommandOutputChunk( failing.allocator(), &metrics, styles, .stderr, "\n", true, ); try std.testing.expectEqual(@as(usize, 0), failing.allocations); try std.testing.expectEqual(@as(usize, 9), block.total_lines); } test "command output lines count toward structured retention" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = .{ .rows = 24, .cols = 80, .content_bottom = 21, .divider_top_row = 22, .input_row = 23, .divider_bottom_row = 24, .hint_row = 22 }, .max_transcript_bytes = 256, .max_retained_transcript_bytes = 512, }; defer runtime.deinit(alloc); var metrics = Metrics{}; const styles: Styles = .{ .system_notice_label_style = "", .system_notice_text_style = "", .reset_style = "", .dim_style = "", .red_style = "", }; var i: usize = 0; while (i < 6) : (i += 1) { try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "hidden-command-line\n", true); } try runtime.flushCommandOutputSummary(alloc, &metrics, styles, true); try std.testing.expectEqual(@as(usize, 1), runtime.command_output_blocks.items.len); try std.testing.expect(runtime.command_output_blocks.items[0].entry_id != null); try std.testing.expect(runtime.command_output_blocks.items[0].lines.items.len < 6); try std.testing.expectEqual(@as(usize, 6), runtime.command_output_blocks.items[0].total_lines); var retained_text_bytes: usize = 0; for (runtime.command_output_blocks.items[0].lines.items) |line| retained_text_bytes += line.text.len; try std.testing.expectEqual(retained_text_bytes, runtime.command_output_blocks.items[0].retained_text_bytes); try std.testing.expect(transcript_store.retainedStructuredBytes(&runtime) <= runtime.max_retained_transcript_bytes); } test "structured retention prunes old command output block state" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = .{ .rows = 24, .cols = 80, .content_bottom = 21, .divider_top_row = 22, .input_row = 23, .divider_bottom_row = 24, .hint_row = 22 }, .max_transcript_bytes = 256, .max_retained_transcript_bytes = 260, }; defer runtime.deinit(alloc); var metrics = Metrics{}; const styles = commandOutputTestStyles(); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "old-command-output-line-that-should-prune\n", true); try runtime.flushCommandOutputSummary(alloc, &metrics, styles, true); try std.testing.expectEqual(@as(usize, 1), runtime.command_output_blocks.items.len); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "new-command-output-line-that-should-remain\n", true); try runtime.flushCommandOutputSummary(alloc, &metrics, styles, true); try std.testing.expectEqual(@as(usize, 1), runtime.command_output_blocks.items.len); try std.testing.expectEqual(@as(usize, 1), runtime.command_output_blocks.items[0].lines.items.len); try std.testing.expect(std.mem.find(u8, runtime.command_output_blocks.items[0].lines.items[0].text, "new-command-output") != null); try std.testing.expect(std.mem.find(u8, runtime.transcript.items, "old-command-output") == null); try std.testing.expect(transcript_store.retainedStructuredBytes(&runtime) <= runtime.max_retained_transcript_bytes); } test "resize preparation uses retained entries after pruning without rewriting cache" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = .{ .rows = 24, .cols = 80, .content_bottom = 21, .divider_top_row = 22, .input_row = 23, .divider_bottom_row = 24, .hint_row = 22 }, .max_transcript_bytes = 128, .max_retained_transcript_bytes = 40, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); var metrics = Metrics{}; try runtime.initViewport(&metrics, 1); try runtime.writeTranscript(alloc, &metrics, "old-entry-that-will-prune\n", true); try runtime.writeTranscript(alloc, &metrics, "latest-entry-stays\n", true); const cache_before = try alloc.dupe(u8, runtime.transcript.items); defer alloc.free(cache_before); const rendered_cols_before = runtime.last_rendered_cols; runtime.layout.cols = 20; var prepared = try runtime.prepareTranscriptSurfacePaint(alloc, &metrics, 0); defer prepared.deinit(alloc); try std.testing.expectEqual(rendered_cols_before, runtime.last_rendered_cols); try std.testing.expectEqualStrings(cache_before, runtime.transcript.items); try std.testing.expect(std.mem.indexOf(u8, prepared.bytes, "latest-entry-stays") != null); try std.testing.expect(std.mem.indexOf(u8, prepared.bytes, "old-entry") == null); } test "clearTranscript after retention pruning frees retained entries and command output blocks" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = .{ .rows = 24, .cols = 80, .content_bottom = 21, .divider_top_row = 22, .input_row = 23, .divider_bottom_row = 24, .hint_row = 22 }, .max_transcript_bytes = 128, .max_retained_transcript_bytes = 64, }; defer runtime.deinit(alloc); var metrics = Metrics{}; const styles: Styles = .{ .system_notice_label_style = "", .system_notice_text_style = "", .reset_style = "", .dim_style = "", .red_style = "", }; try runtime.writeTranscript(alloc, &metrics, "first-entry-to-prune\n", true); try runtime.writeTranscript(alloc, &metrics, "second-entry-to-keep\n", true); try runtime.writeCommandOutputChunk(alloc, &metrics, styles, .stdout, "hidden-command-line\n", true); try runtime.flushCommandOutputSummary(alloc, &metrics, styles, true); try std.testing.expect(transcript_store.retainedStructuredBytes(&runtime) <= runtime.max_retained_transcript_bytes); runtime.clearTranscript(alloc); try std.testing.expectEqual(@as(usize, 0), runtime.entries.items.len); try std.testing.expectEqual(@as(usize, 0), runtime.folded_command_blocks.items.len); try std.testing.expectEqual(@as(usize, 0), runtime.command_output_blocks.items.len); try std.testing.expectEqual(@as(usize, 0), transcript_store.retainedStructuredBytes(&runtime)); const after = try alloc.dupe(u8, "after-clear\n"); const id = try runtime.appendRawBytesEntry(alloc, after); try std.testing.expect(id > 0); } test "recomputeCursorFromTranscript respects resized width" { var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 6, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, }, }; defer runtime.deinit(std.testing.allocator); try runtime.transcript.appendSlice(std.testing.allocator, "hello world\nabc"); runtime.cursor_row = 99; runtime.cursor_col = 99; runtime.recomputeCursorFromTranscript(); try std.testing.expectEqual(@as(u16, 3), runtime.cursor_row); try std.testing.expectEqual(@as(u16, 4), runtime.cursor_col); } test "recomputeCursorFromTranscript skips ANSI in styled rows" { var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, }, }; defer runtime.deinit(std.testing.allocator); const bar_row = "\x1b[48;5;235m\x1b[39m❯ hello\x1b[0m"; try runtime.transcript.appendSlice(std.testing.allocator, bar_row); runtime.cursor_row = 99; runtime.cursor_col = 99; runtime.recomputeCursorFromTranscript(); try std.testing.expectEqual(@as(u16, 8), runtime.cursor_col); } test "replaceTrailingTranscriptLine updates latest replaceable line" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = .{ .rows = 24, .cols = 80, .content_bottom = 21, .divider_top_row = 22, .input_row = 23, .divider_bottom_row = 24, .hint_row = 22, }, }; defer runtime.deinit(std.testing.allocator); var metrics = Metrics{}; try runtime.writeTranscript(std.testing.allocator, &metrics, "started\n", true); _ = try runtime.appendReplaceableTranscriptLine(std.testing.allocator, &metrics, "progress one\n"); try std.testing.expect(try runtime.replaceTrailingTranscriptLine(std.testing.allocator, &metrics, "progress two\n")); try std.testing.expectEqualStrings("started\nprogress two\n", runtime.transcript.items); } test "replaceable line with ansi wrapper does not accumulate historical entries" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = .{ .rows = 24, .cols = 80, .content_bottom = 21, .divider_top_row = 22, .input_row = 23, .divider_bottom_row = 24, .hint_row = 22, }, }; defer runtime.deinit(std.testing.allocator); var metrics = Metrics{}; try runtime.writeTranscript(std.testing.allocator, &metrics, "start\n", true); _ = try runtime.appendReplaceableTranscriptLine(std.testing.allocator, &metrics, "\x1b[38;5;245mline one\n\x1b[0m"); try std.testing.expect(try runtime.replaceTrailingTranscriptLine(std.testing.allocator, &metrics, "\x1b[38;5;245mline two\n\x1b[0m")); try std.testing.expectEqualStrings("start\n\x1b[38;5;245mline two\n\x1b[0m", runtime.transcript.items); } test "updateExtraInputRows shrink preserves pre-fx scrollback" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = .{ .rows = 40, .cols = 120, .content_bottom = 36, .divider_top_row = 37, .input_row = 38, .divider_bottom_row = 39, .hint_row = 40, }, }; defer runtime.deinit(std.testing.allocator); try runtime.enableShadowVt(std.testing.allocator); var metrics = Metrics{}; try runtime.initViewport(&metrics, 12); try runtime.updateExtraInputRows(std.testing.allocator, &metrics, 7); try runtime.updateExtraInputRows(std.testing.allocator, &metrics, 0); try std.testing.expectEqual(@as(u16, 12), runtime.viewport_top_row); } test "updateExtraInputRows after committed frame only records invalidation" { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); var sink = try tmp.dir.createFile(std.testing.io, "post-frame-extra-rows.log", .{ .read = true }); defer sink.close(io_mod.getIo()); var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = .{ .rows = 24, .cols = 80, .content_bottom = 20, .divider_top_row = 21, .input_row = 22, .divider_bottom_row = 23, .hint_row = 24, }, .owned_top_row = 5, .viewport_top_row = 5, .has_committed_frame = true, }; defer runtime.deinit(std.testing.allocator); runtime.footer_viewport.has_frame = true; runtime.footer_viewport.geometry = .{ .top = 21, .top_divider = 21, .input_base = 22, .bottom_divider = 23, .hint = 24, }; var metrics = Metrics{}; try runtime.updateExtraInputRows(std.testing.allocator, &metrics, 3); try std.testing.expectEqual(@as(u16, 3), runtime.extra_input_rows); try std.testing.expect(runtime.transcript_band_dirty); try std.testing.expect(runtime.footer_viewport.externally_invalidated); try std.testing.expectEqual(@as(u64, 0), try sink.length(io_mod.getIo())); const pending = runtime.render_requests.pendingInvalidations(); try std.testing.expectEqual(@as(u8, 1), pending.len); try std.testing.expectEqual(render_engine.paint_plan.FrameInvalidationReason.external_clear, pending.ranges()[0].reason); try std.testing.expectEqual(@as(u16, 5), pending.ranges()[0].top); try std.testing.expectEqual(@as(u16, 24), pending.ranges()[0].bottom); } test "resize reservation never moves viewport above launch-owned row" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = .{ .rows = 46, .cols = 131, .content_bottom = 42, .divider_top_row = 43, .input_row = 44, .divider_bottom_row = 45, .hint_row = 46, }, }; defer runtime.deinit(std.testing.allocator); var metrics = Metrics{}; try runtime.initViewportWithReservedRows(&metrics, 45, 11); try std.testing.expectEqual(@as(u16, 32), runtime.viewport_top_row); runtime.layout = .{ .rows = 44, .cols = 132, .content_bottom = 40, .divider_top_row = 41, .input_row = 42, .divider_bottom_row = 43, .hint_row = 44, }; runtime.reserveVisibleViewportRows(11); try std.testing.expectEqual(@as(u16, 32), runtime.viewport_top_row); } test "clearTranscript preserves launch-owned row" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = .{ .rows = 24, .cols = 80, .content_bottom = 21, .divider_top_row = 22, .input_row = 23, .divider_bottom_row = 24, .hint_row = 22, }, }; defer runtime.deinit(std.testing.allocator); var metrics = Metrics{}; try runtime.initViewport(&metrics, 7); try runtime.writeTranscript(std.testing.allocator, &metrics, "hello\n", true); runtime.clearTranscript(std.testing.allocator); try std.testing.expectEqual(@as(u16, 7), runtime.owned_top_row); try std.testing.expectEqual(@as(u16, 7), runtime.viewport_top_row); try std.testing.expectEqual(@as(u16, 7), runtime.cursor_row); } test "writeTranscript overflow defers terminal scroll to frame selection delta" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = .{ .rows = 10, .cols = 80, .content_bottom = 7, .divider_top_row = 8, .input_row = 9, .divider_bottom_row = 10, .hint_row = 10, }, }; defer runtime.deinit(std.testing.allocator); var metrics = Metrics{}; try runtime.initViewport(&metrics, 5); runtime.has_painted_transcript = true; try std.testing.expectEqual(@as(u16, 5), runtime.viewport_top_row); try runtime.writeTranscript(std.testing.allocator, &metrics, "a\nb\nc\nd\ne\nf\ng\nh\n", true); try std.testing.expectEqual(@as(u16, 5), runtime.viewport_top_row); try std.testing.expectEqual(@as(u16, 5), runtime.owned_top_row); try std.testing.expectEqual(@as(u16, 7), runtime.cursor_row); } test "appendRawBytesEntry stamps monotonic ids from 1" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc); const a = try alloc.dupe(u8, "first"); const id_a = try runtime.appendRawBytesEntry(alloc, a); const b = try alloc.dupe(u8, "second"); const id_b = try runtime.appendRawBytesEntry(alloc, b); try std.testing.expectEqual(@as(u32, 1), id_a); try std.testing.expectEqual(@as(u32, 2), id_b); try std.testing.expectEqual(@as(usize, 2), runtime.entries.items.len); try std.testing.expectEqual(@as(u32, 1), runtime.entries.items[0].id()); try std.testing.expectEqualStrings("first", runtime.entries.items[0].raw_bytes.bytes); } test "append helpers cover all three variants with unique ids" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc); const raw = try alloc.dupe(u8, "banner"); const raw_id = try runtime.appendRawBytesEntry(alloc, raw); const prompt = try alloc.dupe(u8, "hello"); const user_id = try runtime.appendUserTurnOwned(alloc, .{ .text = prompt, .images = &.{} }); const assistant_id = try runtime.appendAssistantTurnEntry(alloc); const segs = runtime.lookupAssistantSegments(assistant_id) orelse unreachable; try segs.text.appendSlice(alloc, "streaming"); try std.testing.expectEqual(@as(u32, 1), raw_id); try std.testing.expectEqual(@as(u32, 2), user_id); try std.testing.expectEqual(@as(u32, 3), assistant_id); try std.testing.expectEqualStrings("streaming", runtime.entries.items[2].assistant_turn.segments.text.items); } test "appendRawTranscriptEntryClassified stores raw entry class" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc); const entry_id = try runtime.appendRawTranscriptEntryClassified(alloc, "running tests\n", .tool_status); try std.testing.expectEqual(@as(usize, 1), runtime.entries.items.len); try std.testing.expect(runtime.entries.items[0] == .raw_bytes); try std.testing.expectEqual(entry_id, runtime.entries.items[0].raw_bytes.id); try std.testing.expectEqual(RawEntryClass.tool_status, runtime.entries.items[0].raw_bytes.class); try std.testing.expectEqual(RawEntryClass.tool_status, runtime.rawEntryClass(entry_id).?); } test "appendRawTranscriptEntry defaults to unknown_raw class" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc); const entry_id = try runtime.appendRawTranscriptEntry(alloc, "raw entry\n"); try std.testing.expectEqual(RawEntryClass.unknown_raw, runtime.rawEntryClass(entry_id).?); } test "updateRawBytesEntry preserves raw entry class" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc); const entry_id = try runtime.appendRawTranscriptEntryClassified(alloc, "running\n", .tool_status); try std.testing.expect(try runtime.updateRawBytesEntry(alloc, entry_id, "done\n")); try std.testing.expectEqual(RawEntryClass.tool_status, runtime.rawEntryClass(entry_id).?); try std.testing.expectEqualStrings("done\n", runtime.entries.items[0].raw_bytes.bytes); } test "setRawEntryClass updates only matching raw entry" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc); const raw_id = try runtime.appendRawTranscriptEntry(alloc, "raw entry\n"); _ = try runtime.appendAssistantTurnEntry(alloc); try std.testing.expect(runtime.setRawEntryClass(raw_id, .subagent_status)); try std.testing.expectEqual(RawEntryClass.subagent_status, runtime.rawEntryClass(raw_id).?); try std.testing.expect(!runtime.setRawEntryClass(999_999, .tool_status)); } test "streamAssistantChunk opens a new assistant_turn when there is no trailing one" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = .{ .rows = 10, .cols = 80, .content_bottom = 7, .divider_top_row = 8, .input_row = 9, .divider_bottom_row = 10, .hint_row = 10 } }; defer runtime.deinit(alloc); var metrics = Metrics{}; try runtime.initViewport(&metrics, 5); const id = try runtime.streamAssistantChunk(alloc, &metrics, "first chunk"); try std.testing.expectEqual(@as(usize, 1), runtime.entries.items.len); try std.testing.expect(runtime.entries.items[0] == .assistant_turn); try std.testing.expectEqual(id, runtime.entries.items[0].id()); try std.testing.expectEqualStrings("first chunk", runtime.entries.items[0].assistant_turn.segments.text.items); try std.testing.expect(std.mem.indexOf(u8, runtime.transcript.items, "first chunk") != null); } test "streamAssistantChunk extends the trailing assistant_turn on subsequent chunks" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = .{ .rows = 10, .cols = 80, .content_bottom = 7, .divider_top_row = 8, .input_row = 9, .divider_bottom_row = 10, .hint_row = 10 } }; defer runtime.deinit(alloc); var metrics = Metrics{}; try runtime.initViewport(&metrics, 5); const id_a = try runtime.streamAssistantChunk(alloc, &metrics, "alpha "); const id_b = try runtime.streamAssistantChunk(alloc, &metrics, "beta"); try std.testing.expectEqual(id_a, id_b); try std.testing.expectEqual(@as(usize, 1), runtime.entries.items.len); try std.testing.expect(runtime.entries.items[0] == .assistant_turn); try std.testing.expectEqualStrings("alpha beta", runtime.entries.items[0].assistant_turn.segments.text.items); } test "recorded assistant stream slow path preserves a canonical anchor when retention rewrites its source" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(48, 12, 8), .owned_top_row = 1, .max_transcript_bytes = 64, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); const old_prefix = "old retained prefix\n"; _ = try runtime.appendRawTranscriptEntryClassified( alloc, old_prefix, .subagent_status, ); const long_prompt = try makeLongPastePrompt(alloc, 40); defer alloc.free(long_prompt); var metrics: Metrics = .{}; const user_id = try runtime.writeUserPromptCard( alloc, &metrics, .{ .text = long_prompt, .images = &.{} }, false, &.{}, ); var committed_source = try runtime.prepareTranscriptSource(alloc, null); defer committed_source.deinit(alloc); try std.testing.expect(committed_source.recorded_entries_authoritative); try std.testing.expect(committed_source.bytes.len > runtime.transcript.items.len); var committed_prepared = try runtime.prepareTranscriptSurfacePaintForArea( alloc, &metrics, .{ .top = runtime.owned_top_row, .bottom = runtime.layout.content_bottom }, ); defer committed_prepared.deinit(alloc); try installStableAnchorForTest( &runtime, alloc, committed_source.bytes, committed_prepared.selection, preparedVisualOffsetForTest(&committed_prepared), committed_prepared.cursor.cursor_row, committed_prepared.cursor.cursor_col, 1, ); // The synthetic anchor helper does not seal a source. Supply the same // entry identity as a real sealed frame before exercising retention. runtime.transcript_commit_state.stable.retention_identity = try @import("source_preparation.zig").RetentionIdentity.capture(&runtime, alloc, &committed_source); try committed_source.ensureLineIndex(alloc); const committed_history_visual_offset = try stableHistoryVisualOffsetForTest(&runtime); const user_start = for (committed_source.line_provenance, 0..) |identity, index| { if (identity == .entry and identity.entry.entry_id == user_id) break index; } else return error.TestExpectedUserEntry; const removed_rows = committed_source.transcript_visual_row_offsets[user_start]; try std.testing.expect(removed_rows > 0); const old_boundary = committed_source.byteAtVisualOffset(committed_history_visual_offset); const old_suffix = committed_source.bytes[old_boundary..]; const boundary_row = old_suffix[0..std.mem.findScalar(u8, old_suffix, '\n').?]; try std.testing.expect(std.mem.find(u8, boundary_row, "long-paste-line-34") != null); const chunk = "trimmed assistant tail"; runtime.max_retained_transcript_bytes = transcript_store.retainedStructuredBytes(&runtime) + chunk.len - old_prefix.len; const assistant_id = try runtime.streamAssistantChunk(alloc, &metrics, chunk); var rebased_source = (try runtime.prepareCommittedRetentionSource(alloc)) orelse return error.TestExpectedStableTranscript; defer rebased_source.deinit(alloc); const rebased = runtime.transcript_commit_state.stable; try std.testing.expectEqual(committed_history_visual_offset - removed_rows, rebased.history_visual_offset); try std.testing.expectEqual(rebased.history_visual_offset, rebased.visual_offset); try std.testing.expectEqualStrings( committed_source.bytes[committed_source.hard_line_starts[user_start]..], rebased_source.bytes, ); try std.testing.expectEqualStrings( committed_source.bytes[old_boundary..], rebased_source.bytes[rebased_source.byteAtVisualOffset(rebased.history_visual_offset)..], ); const boundary_identity = rebased_source.line_provenance[rebased.selection.start_line]; try std.testing.expect(boundary_identity == .entry); try std.testing.expectEqual(user_id, boundary_identity.entry.entry_id); try std.testing.expectEqual(transcript_blocks.TranscriptEntryClass.user_turn, boundary_identity.entry.entry_class); try std.testing.expect(rebased.flow_materialized); try std.testing.expect(!rebased.normal_buffer_recovery_pending); try std.testing.expect(std.mem.find(u8, rebased_source.bytes, chunk) == null); try std.testing.expect( transcript_store.retainedStructuredBytes(&runtime) <= runtime.max_retained_transcript_bytes, ); try std.testing.expect(runtime.lookupAssistantSegments(assistant_id) != null); var appended_source = try runtime.prepareTranscriptSource(alloc, null); defer appended_source.deinit(alloc); try std.testing.expect(!std.mem.startsWith( u8, appended_source.bytes, committed_source.bytes, )); try std.testing.expectEqual( @as(usize, 0), std.mem.count(u8, appended_source.bytes, old_prefix), ); try std.testing.expectEqual( @as(usize, 1), std.mem.count(u8, appended_source.bytes, "LONG_PASTE_LAST_MARKER"), ); try std.testing.expect(runtime.entries.items[0].id() == user_id); } const AssistantStreamFastPathCase = enum { opening, continuation, }; const AssistantStreamFastPathFailureWitness = struct { target_failures: usize = 0, }; fn checkOrdinaryAssistantStreamAllocationFailures( alloc: Allocator, test_case: AssistantStreamFastPathCase, witness: *AssistantStreamFastPathFailureWitness, ) !void { const continuation = [_]u8{'b'} ** 4096; const chunk: []const u8 = switch (test_case) { .opening => "first chunk", .continuation => &continuation, }; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(24, 12, 8) }; defer runtime.deinit(alloc); var metrics = Metrics{}; const existing_entry_id: ?u32 = switch (test_case) { .opening => null, .continuation => try runtime.streamAssistantChunk(alloc, &metrics, "a"), }; runtime.render_requests.clearReason(.transcript); runtime.transcript_band_dirty = false; const transcript_before = try std.testing.allocator.dupe(u8, runtime.transcript.items); defer std.testing.allocator.free(transcript_before); const assistant_before = if (existing_entry_id) |entry_id| try std.testing.allocator.dupe( u8, runtime.lookupAssistantSegments(entry_id).?.text.items, ) else null; defer if (assistant_before) |text| std.testing.allocator.free(text); const entry_count_before = runtime.entries.items.len; const next_entry_id_before = runtime.next_entry_id; const cursor_row_before = runtime.cursor_row; const cursor_col_before = runtime.cursor_col; const retained_before = transcript_store.retainedStructuredBytes(&runtime); const cache_origin_before = runtime.transcript_cache_origin_untrimmed; const repaint_count_before = runtime.render_requests.pendingReasonCount(); try std.testing.expect(retained_before <= runtime.max_retained_transcript_bytes); try std.testing.expect( chunk.len <= runtime.max_retained_transcript_bytes - retained_before, ); try std.testing.expect( chunk.len > runtime.transcript.capacity - runtime.transcript.items.len, ); if (existing_entry_id) |entry_id| { const segments = runtime.lookupAssistantSegments(entry_id).?; try std.testing.expect( chunk.len > segments.text.capacity - segments.text.items.len, ); } else { try std.testing.expectEqual(@as(usize, 0), runtime.entries.capacity); } const entry_id = runtime.streamAssistantChunk(alloc, &metrics, chunk) catch |err| { witness.target_failures += 1; try std.testing.expectEqualStrings(transcript_before, runtime.transcript.items); if (existing_entry_id) |prior_entry_id| { try std.testing.expectEqualStrings( assistant_before.?, runtime.lookupAssistantSegments(prior_entry_id).?.text.items, ); } try std.testing.expectEqual(entry_count_before, runtime.entries.items.len); try std.testing.expectEqual(next_entry_id_before, runtime.next_entry_id); try std.testing.expectEqual(cursor_row_before, runtime.cursor_row); try std.testing.expectEqual(cursor_col_before, runtime.cursor_col); try std.testing.expectEqual(retained_before, transcript_store.retainedStructuredBytes(&runtime)); try std.testing.expectEqual(cache_origin_before, runtime.transcript_cache_origin_untrimmed); try std.testing.expectEqual(repaint_count_before, runtime.render_requests.pendingReasonCount()); try std.testing.expect(!runtime.render_requests.hasReason(.transcript)); try std.testing.expect(!runtime.transcript_band_dirty); return switch (err) { error.WriteFailed => error.OutOfMemory, else => err, }; }; const opened_new_turn = test_case == .opening; const expected_entry_id = existing_entry_id orelse next_entry_id_before; try std.testing.expectEqual(expected_entry_id, entry_id); try std.testing.expectEqual( next_entry_id_before +% @intFromBool(opened_new_turn), runtime.next_entry_id, ); try std.testing.expectEqual( entry_count_before + @intFromBool(opened_new_turn), runtime.entries.items.len, ); try std.testing.expectEqual(@as(usize, 1), runtime.entries.items.len); try std.testing.expect(runtime.entries.items[0] == .assistant_turn); try std.testing.expectEqual(entry_id, runtime.entries.items[0].id()); const assistant_text = runtime.entries.items[0].assistant_turn.segments.text.items; switch (test_case) { .opening => try std.testing.expectEqualStrings(chunk, assistant_text), .continuation => { try std.testing.expectEqual(@as(usize, 1) + chunk.len, assistant_text.len); try std.testing.expectEqualStrings("a", assistant_text[0..1]); try std.testing.expectEqualStrings(chunk, assistant_text[1..]); }, } try std.testing.expectEqualStrings(assistant_text, runtime.transcript.items); try std.testing.expectEqual( assistant_text.len, transcript_store.retainedStructuredBytes(&runtime), ); try std.testing.expectEqual(repaint_count_before + 1, runtime.render_requests.pendingReasonCount()); try std.testing.expect(runtime.render_requests.hasReason(.transcript)); try std.testing.expect(runtime.transcript_band_dirty); } test "ordinary assistant stream admission is atomic across allocation failures" { var opening_witness = AssistantStreamFastPathFailureWitness{}; try std.testing.checkAllAllocationFailures( std.testing.allocator, checkOrdinaryAssistantStreamAllocationFailures, .{ .opening, &opening_witness }, ); try std.testing.expectEqual(@as(usize, 3), opening_witness.target_failures); var continuation_witness = AssistantStreamFastPathFailureWitness{}; try std.testing.checkAllAllocationFailures( std.testing.allocator, checkOrdinaryAssistantStreamAllocationFailures, .{ .continuation, &continuation_witness }, ); try std.testing.expectEqual(@as(usize, 2), continuation_witness.target_failures); } fn checkOpeningAssistantStreamAllocationFailures(alloc: Allocator) !void { var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(24, 12, 8) }; defer runtime.deinit(alloc); var metrics = Metrics{}; try runtime.writeTranscript(alloc, &metrics, "older transcript row\n", true); const prompt = try alloc.dupe(u8, "prompt"); _ = try runtime.appendUserTurnOwned(alloc, .{ .text = prompt, .images = &.{} }); runtime.max_retained_transcript_bytes = "prompt".len + "first chunk".len; runtime.render_requests.clearReason(.transcript); runtime.transcript_band_dirty = false; const transcript_before = try std.testing.allocator.dupe(u8, runtime.transcript.items); defer std.testing.allocator.free(transcript_before); const entry_count_before = runtime.entries.items.len; const next_entry_id_before = runtime.next_entry_id; const cursor_row_before = runtime.cursor_row; const cursor_col_before = runtime.cursor_col; const retained_before = transcript_store.retainedStructuredBytes(&runtime); const cache_origin_before = runtime.transcript_cache_origin_untrimmed; const repaint_count_before = runtime.render_requests.pendingReasonCount(); const entry_id = runtime.streamAssistantChunk( alloc, &metrics, "first chunk", ) catch |err| { try std.testing.expectEqualStrings(transcript_before, runtime.transcript.items); try std.testing.expectEqual(entry_count_before, runtime.entries.items.len); try std.testing.expectEqual(next_entry_id_before, runtime.next_entry_id); try std.testing.expectEqual(cursor_row_before, runtime.cursor_row); try std.testing.expectEqual(cursor_col_before, runtime.cursor_col); try std.testing.expectEqual(retained_before, transcript_store.retainedStructuredBytes(&runtime)); try std.testing.expectEqual(cache_origin_before, runtime.transcript_cache_origin_untrimmed); try std.testing.expectEqual(repaint_count_before, runtime.render_requests.pendingReasonCount()); try std.testing.expect(!runtime.render_requests.hasReason(.transcript)); try std.testing.expect(!runtime.transcript_band_dirty); return switch (err) { error.WriteFailed => error.OutOfMemory, else => err, }; }; try std.testing.expectEqual(next_entry_id_before, entry_id); try std.testing.expectEqual(next_entry_id_before +% 1, runtime.next_entry_id); try std.testing.expectEqual(entry_count_before, runtime.entries.items.len); try std.testing.expect(runtime.entries.items[0] == .user_turn); try std.testing.expect(runtime.entries.items[1] == .assistant_turn); try std.testing.expectEqual(entry_id, runtime.entries.items[1].id()); try std.testing.expectEqualStrings( "first chunk", runtime.entries.items[1].assistant_turn.segments.text.items, ); try std.testing.expectEqual( runtime.max_retained_transcript_bytes, transcript_store.retainedStructuredBytes(&runtime), ); const rendered = try renderEntriesToBytes( std.testing.allocator, runtime.entries.items, runtime.layout.cols, ); defer std.testing.allocator.free(rendered); try std.testing.expectEqualStrings(rendered, runtime.transcript.items); try std.testing.expectEqual(repaint_count_before + 1, runtime.render_requests.pendingReasonCount()); try std.testing.expect(runtime.render_requests.hasReason(.transcript)); try std.testing.expect(runtime.transcript_band_dirty); } test "opening a recorded assistant stream is atomic across allocation failures" { try std.testing.checkAllAllocationFailures( std.testing.allocator, checkOpeningAssistantStreamAllocationFailures, .{}, ); } fn checkExtendingAssistantStreamAllocationFailures(alloc: Allocator) !void { var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(24, 12, 8) }; defer runtime.deinit(alloc); var metrics = Metrics{}; try runtime.writeTranscript(alloc, &metrics, "older transcript row\n", true); const entry_id = try runtime.streamAssistantChunk(alloc, &metrics, "alpha "); runtime.max_retained_transcript_bytes = "alpha beta".len; runtime.render_requests.clearReason(.transcript); runtime.transcript_band_dirty = false; const transcript_before = try std.testing.allocator.dupe(u8, runtime.transcript.items); defer std.testing.allocator.free(transcript_before); const assistant_before = try std.testing.allocator.dupe( u8, runtime.lookupAssistantSegments(entry_id).?.text.items, ); defer std.testing.allocator.free(assistant_before); const entry_count_before = runtime.entries.items.len; const next_entry_id_before = runtime.next_entry_id; const cursor_row_before = runtime.cursor_row; const cursor_col_before = runtime.cursor_col; const retained_before = transcript_store.retainedStructuredBytes(&runtime); const cache_origin_before = runtime.transcript_cache_origin_untrimmed; const repaint_count_before = runtime.render_requests.pendingReasonCount(); const continued_id = runtime.streamAssistantChunk( alloc, &metrics, "beta", ) catch |err| { try std.testing.expectEqualStrings(transcript_before, runtime.transcript.items); try std.testing.expectEqualStrings( assistant_before, runtime.lookupAssistantSegments(entry_id).?.text.items, ); try std.testing.expectEqual(entry_count_before, runtime.entries.items.len); try std.testing.expectEqual(next_entry_id_before, runtime.next_entry_id); try std.testing.expectEqual(cursor_row_before, runtime.cursor_row); try std.testing.expectEqual(cursor_col_before, runtime.cursor_col); try std.testing.expectEqual(retained_before, transcript_store.retainedStructuredBytes(&runtime)); try std.testing.expectEqual(cache_origin_before, runtime.transcript_cache_origin_untrimmed); try std.testing.expectEqual(repaint_count_before, runtime.render_requests.pendingReasonCount()); try std.testing.expect(!runtime.render_requests.hasReason(.transcript)); try std.testing.expect(!runtime.transcript_band_dirty); return switch (err) { error.WriteFailed => error.OutOfMemory, else => err, }; }; try std.testing.expectEqual(entry_id, continued_id); try std.testing.expectEqual(entry_count_before - 1, runtime.entries.items.len); try std.testing.expectEqual(next_entry_id_before, runtime.next_entry_id); try std.testing.expect(runtime.entries.items[0] == .assistant_turn); try std.testing.expectEqual(entry_id, runtime.entries.items[0].id()); try std.testing.expectEqualStrings( "alpha beta", runtime.entries.items[0].assistant_turn.segments.text.items, ); try std.testing.expectEqual( runtime.max_retained_transcript_bytes, transcript_store.retainedStructuredBytes(&runtime), ); const rendered = try renderEntriesToBytes( std.testing.allocator, runtime.entries.items, runtime.layout.cols, ); defer std.testing.allocator.free(rendered); try std.testing.expectEqualStrings(rendered, runtime.transcript.items); try std.testing.expectEqual(repaint_count_before + 1, runtime.render_requests.pendingReasonCount()); try std.testing.expect(runtime.render_requests.hasReason(.transcript)); try std.testing.expect(runtime.transcript_band_dirty); } test "extending a recorded assistant stream is atomic across allocation failures" { try std.testing.checkAllAllocationFailures( std.testing.allocator, checkExtendingAssistantStreamAllocationFailures, .{}, ); } test "paced assistant continuations do not clone retained history per chunk" { const backing = std.testing.allocator; var counted = std.testing.FailingAllocator.init(backing, .{}); const alloc = counted.allocator(); var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(80, 24, 20), .max_transcript_bytes = 4096, }; defer runtime.deinit(alloc); try runtime.initBacking(alloc); for (0..48) |_| { _ = try runtime.appendRawTranscriptEntryClassified( alloc, "retained transcript history row\n", .subagent_status, ); } var metrics = Metrics{}; const entry_id = try runtime.streamAssistantChunk(alloc, &metrics, "a"); const allocations_before = counted.allocations; for (0..256) |_| { try std.testing.expectEqual( entry_id, try runtime.streamAssistantChunk(alloc, &metrics, "b"), ); } try std.testing.expect(counted.allocations - allocations_before <= 8); try std.testing.expectEqual(@as(usize, 49), runtime.entries.items.len); try std.testing.expectEqual(@as(usize, 257), runtime.lookupAssistantSegments(entry_id).?.text.items.len); } test "streamAssistantChunk opens a new assistant_turn after a user_turn" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = .{ .rows = 10, .cols = 80, .content_bottom = 7, .divider_top_row = 8, .input_row = 9, .divider_bottom_row = 10, .hint_row = 10 } }; defer runtime.deinit(alloc); var metrics = Metrics{}; try runtime.initViewport(&metrics, 5); _ = try runtime.streamAssistantChunk(alloc, &metrics, "assistant A"); const user_text = try alloc.dupe(u8, "next user prompt"); _ = try runtime.appendUserTurnOwned(alloc, .{ .text = user_text, .images = &.{} }); const id_after = try runtime.streamAssistantChunk(alloc, &metrics, "assistant B"); try std.testing.expectEqual(@as(usize, 3), runtime.entries.items.len); try std.testing.expect(runtime.entries.items[0] == .assistant_turn); try std.testing.expect(runtime.entries.items[1] == .user_turn); try std.testing.expect(runtime.entries.items[2] == .assistant_turn); try std.testing.expectEqual(id_after, runtime.entries.items[2].id()); try std.testing.expectEqualStrings("assistant B", runtime.entries.items[2].assistant_turn.segments.text.items); } test "appendUserTurnOwned frees text, images, and image slices when entries.append OOMs" { const base = std.testing.allocator; // Fail after all transferred slices exist, when the entries list first grows. var failing = std.testing.FailingAllocator.init(base, .{ .fail_index = 4 }); const alloc = failing.allocator(); var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc); const text = try alloc.dupe(u8, "prompt"); const images = try alloc.alloc(types.ImageAttachment, 1); images[0] = .{ .path = try alloc.dupe(u8, "/tmp/image.png"), .media_type = try alloc.dupe(u8, "image/png"), }; try std.testing.expectError( error.OutOfMemory, runtime.appendUserTurnOwned(alloc, .{ .text = text, .images = images }), ); try std.testing.expectEqual(@as(usize, 0), runtime.entries.items.len); try std.testing.expectEqual(failing.allocated_bytes, failing.freed_bytes); } test "appendUserTurnOwnedWithSkillTokens stores selected skill metadata for reflow" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc); const prompt_text = "raw $review then $review"; const prompt = try alloc.dupe(u8, prompt_text); var handed_off = false; errdefer if (!handed_off) alloc.free(prompt); const tokens = [_]input_visual_layout.SkillTokenSpan{.{ .raw_start = "raw $review then ".len, .raw_end = prompt_text.len, .name = "review", .path = "/tmp/review", }}; _ = try runtime.appendUserTurnOwnedWithSkillTokens(alloc, .{ .text = prompt, .images = &.{} }, &tokens); handed_off = true; try std.testing.expectEqual(@as(usize, 1), runtime.entries.items.len); try std.testing.expectEqual(@as(usize, 1), runtime.entries.items[0].user_turn.skill_tokens.len); try std.testing.expectEqualStrings("review", runtime.entries.items[0].user_turn.skill_tokens[0].name); try std.testing.expectEqual( @as(usize, prompt_text.len + "review".len + "/tmp/review".len), transcript_store.retainedStructuredBytes(&runtime), ); const rendered = try renderEntriesToBytes(alloc, runtime.entries.items, 80); defer alloc.free(rendered); try std.testing.expect(std.mem.find(u8, rendered, "$review then ") != null); try std.testing.expect(std.mem.find(u8, rendered, "\x1b[38;5;252mreview") != null); try std.testing.expect(std.mem.find(u8, rendered, "$review then $review") == null); } const UserPromptCardAdmissionCase = enum { text_with_separator, image_with_skill_token, }; const prompt_card_skill_tokens = [_]input_visual_layout.SkillTokenSpan{.{ .raw_start = "inspect ".len, .raw_end = "inspect $review".len, .name = "review", .path = "/tmp/review", }}; fn setup_user_prompt_card_admission_runtime( runtime: *TranscriptRuntime, alloc: Allocator, test_case: UserPromptCardAdmissionCase, ) !u32 { const seed_id = try runtime.appendRawTranscriptEntryClassified( alloc, "seed", .subagent_status, ); runtime.cursor_row = 2; runtime.cursor_col = switch (test_case) { .text_with_separator => 5, .image_with_skill_token => 1, }; runtime.replaceable_last_line = true; runtime.replaceable_start = 1; runtime.replaceable_row = 2; runtime.transcript_cache_origin_untrimmed = true; runtime.full_transcript.depth = .full; runtime.full_transcript.scroll_rows = 3; runtime.full_transcript.follow_tail = false; runtime.full_transcript.anchor_entry_id = seed_id; runtime.full_transcript.anchor_pending = true; runtime.render_requests.clearReason(.transcript); runtime.transcript_band_dirty = false; return seed_id; } fn user_prompt_card_admission_input( test_case: UserPromptCardAdmissionCase, images: []types.ImageAttachment, ) struct { user: types.UserTurn, skill_tokens: []const input_visual_layout.SkillTokenSpan, has_prior_turns: bool, } { return switch (test_case) { .text_with_separator => .{ .user = .{ .text = @constCast("plain prompt"), .images = &.{} }, .skill_tokens = &.{}, .has_prior_turns = false, }, .image_with_skill_token => .{ .user = .{ .text = @constCast("inspect $review"), .images = images }, .skill_tokens = &prompt_card_skill_tokens, .has_prior_turns = true, }, }; } fn check_user_prompt_card_admission_success( test_case: UserPromptCardAdmissionCase, ) !void { const alloc = std.testing.allocator; const layout = transcriptTestLayout(48, 12, 8); var actual = TranscriptRuntime{ .layout = layout }; defer actual.deinit(alloc); const seed_id = try setup_user_prompt_card_admission_runtime(&actual, alloc, test_case); var images = [_]types.ImageAttachment{.{ .id = 7, .path = @constCast("/tmp/prompt-card.png"), .media_type = @constCast("image/png"), }}; const input = user_prompt_card_admission_input(test_case, &images); const input_images_ptr = input.user.images.ptr; const card = try user_message_card.buildUserPromptCardWithSkillTokens( alloc, input.user.text, input.user.images, actual.layout.cols, input.skill_tokens, ); defer alloc.free(card); const separator = if (input.has_prior_turns) "" else "\n"; const expected_transcript = try std.fmt.allocPrint( alloc, "seed{s}{s}", .{ separator, card }, ); defer alloc.free(expected_transcript); const expected_reconstructed = try std.fmt.allocPrint( alloc, "seed\n\n{s}", .{card}, ); defer alloc.free(expected_reconstructed); var actual_metrics = Metrics{}; const actual_id = try actual.writeUserPromptCard( alloc, &actual_metrics, input.user, input.has_prior_turns, input.skill_tokens, ); const expected_id = seed_id + @as(u32, if (input.has_prior_turns) 1 else 2); try std.testing.expectEqual(expected_id, actual_id); try std.testing.expectEqualStrings(expected_transcript, actual.transcript.items); try std.testing.expectEqual( @as(usize, if (input.has_prior_turns) 2 else 3), actual.entries.items.len, ); try std.testing.expectEqual(expected_id +% 1, actual.next_entry_id); var expected_cursor = TranscriptRuntime{ .layout = layout, .cursor_row = 2, .cursor_col = switch (test_case) { .text_with_separator => 5, .image_with_skill_token => 1, }, }; _ = expected_cursor.advanceCursor(expected_transcript["seed".len..]); try std.testing.expectEqual(expected_cursor.cursor_row, actual.cursor_row); try std.testing.expectEqual(expected_cursor.cursor_col, actual.cursor_col); try std.testing.expect(!actual.replaceable_last_line); try std.testing.expectEqual(@as(usize, 0), actual.replaceable_start); try std.testing.expectEqual(@as(u16, 2), actual.replaceable_row); try std.testing.expect(!actual.transcript_cache_origin_untrimmed); try std.testing.expect(actual.render_requests.hasReason(.transcript)); try std.testing.expectEqual(@as(usize, 1), actual.render_requests.pendingReasonCount()); try std.testing.expectEqual(@as(u32, 3), actual.full_transcript.scroll_rows); try std.testing.expect(!actual.full_transcript.follow_tail); try std.testing.expectEqual(@as(?u32, seed_id), actual.full_transcript.anchor_entry_id); try std.testing.expect(actual.full_transcript.anchor_pending); const actual_reconstructed = try renderEntriesToBytes( alloc, actual.entries.items, actual.layout.cols, ); defer alloc.free(actual_reconstructed); try std.testing.expectEqualStrings(expected_reconstructed, actual_reconstructed); var expected_retained_bytes = "seed".len + separator.len + input.user.text.len; for (input.user.images) |image| { expected_retained_bytes += image.path.len + image.media_type.len; } for (input.skill_tokens) |token| { expected_retained_bytes += token.name.len + token.path.len; } try std.testing.expectEqual( expected_retained_bytes, transcript_store.retainedStructuredBytes(&actual), ); try std.testing.expectEqualStrings(input.user.text, actual.entries.items[actual.entries.items.len - 1].user_turn.turn.text); try std.testing.expectEqual(input_images_ptr, input.user.images.ptr); } test "recorded user prompt card admission preserves immediate and reconstructive rendering" { try check_user_prompt_card_admission_success(.text_with_separator); try check_user_prompt_card_admission_success(.image_with_skill_token); } test "recorded user prompt card admission preserves an authoritative committed anchor" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(48, 12, 8), .owned_top_row = 1, .max_transcript_bytes = 256, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); const long_tail = try makeLongPastePrompt(alloc, 80); defer alloc.free(long_tail); _ = try runtime.appendRawTranscriptEntryClassified( alloc, long_tail, .subagent_status, ); try std.testing.expect(!runtime.transcript_cache_origin_untrimmed); var metrics = Metrics{}; var committed_source = try runtime.prepareTranscriptSource(alloc, null); defer committed_source.deinit(alloc); try std.testing.expect(committed_source.recorded_entries_authoritative); try std.testing.expect(committed_source.bytes.len > runtime.transcript.items.len); var committed_prepared = try runtime.prepareTranscriptSurfacePaintForArea( alloc, &metrics, .{ .top = runtime.owned_top_row, .bottom = runtime.layout.content_bottom }, ); defer committed_prepared.deinit(alloc); const committed_offset = preparedVisualOffsetForTest(&committed_prepared); try installStableAnchorForTest( &runtime, alloc, committed_source.bytes, committed_prepared.selection, committed_offset, committed_prepared.cursor.cursor_row, committed_prepared.cursor.cursor_col, 1, ); const committed_diagnostic = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.stable, committed_diagnostic.state, ); _ = try runtime.writeUserPromptCard( alloc, &metrics, .{ .text = @constCast("run the prepared two commands"), .images = &.{} }, true, &.{}, ); try std.testing.expectEqualDeep( committed_diagnostic, runtime.transcriptCommitDiagnostic(), ); var appended_source = try runtime.prepareTranscriptSource(alloc, null); defer appended_source.deinit(alloc); try std.testing.expect(std.mem.startsWith( u8, appended_source.bytes, committed_source.bytes, )); try std.testing.expectEqual( @as(usize, 1), std.mem.count(u8, appended_source.bytes, "LONG_PASTE_LAST_MARKER"), ); try std.testing.expectEqual( @as(usize, 1), std.mem.count(u8, appended_source.bytes, "run the prepared two commands"), ); const appended_flow = try alloc.dupe(u8, appended_source.bytes); defer alloc.free(appended_flow); var appended_prepared = try runtime.prepareTranscriptSurfacePaintForArea( alloc, &metrics, .{ .top = runtime.owned_top_row, .bottom = runtime.layout.content_bottom }, ); defer appended_prepared.deinit(alloc); const facts = runtime.planTranscriptScroll(&appended_prepared); try std.testing.expect(facts.source_compatible); try std.testing.expect(facts.semantic_rows > 0); try std.testing.expectEqual(committed_offset, facts.source_visual_offset); const scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, facts.planned_rows, ); var plan = testPaintPlan(&runtime, appended_prepared.selection); var transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &appended_source, &appended_prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(plan), &plan, scroll_plan, ); defer transition.deinit(alloc); const settled_offset = transition.visual_offset; runtime.consumeTranscriptTransition(alloc, &transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = scroll_plan.terminal_scroll_rows, .document_append_committed = !transition.document_append.isEmpty(), .committed_cursor_row = transition.cursor_row, .committed_cursor_col = transition.cursor_col, .shadow_state = .committed, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); const settled = runtime.transcriptCommitDiagnostic(); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.stable, settled.state, ); try std.testing.expectEqual(settled_offset, settled.visual_offset); const projection = runtime.stableTranscriptProjectionForFlow( appended_flow, ) orelse return error.TestUnexpectedResult; try std.testing.expectEqual(settled_offset, projection.visual_offset); } test "recorded user prompt card preserves a committed anchor after a non-prefix rewrite" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(24, 10, 6), .owned_top_row = 1, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); var metrics = Metrics{}; _ = try runtime.streamAssistantChunk( alloc, &metrics, "alpha beta gamma delta epsilon zeta eta theta", ); const committed_flow = try alloc.dupe(u8, runtime.transcript.items); defer alloc.free(committed_flow); try installStableAnchorForTest( &runtime, alloc, committed_flow, testSelection(0), 0, runtime.cursor_row, runtime.cursor_col, 1, ); _ = try runtime.appendSemanticNotice(alloc, .{ .topic = "response", .tone = .cancelled, .body = "Interrupted by user", }); var notice_source = try runtime.prepareTranscriptSource(alloc, null); defer notice_source.deinit(alloc); try std.testing.expect(!std.mem.startsWith( u8, notice_source.bytes, committed_flow, )); switch (runtime.transcript_commit_state) { .stable => |anchor| try std.testing.expect(anchor.normal_buffer_recovery_pending), .invalid, .recovering => return error.TestExpectedStableTranscript, } _ = try runtime.writeUserPromptCard( alloc, &metrics, .{ .text = @constCast("queued prompt"), .images = &.{} }, true, &.{}, ); switch (runtime.transcript_commit_state) { .stable => |anchor| try std.testing.expect(anchor.normal_buffer_recovery_pending), .invalid, .recovering => return error.TestExpectedStableTranscript, } var appended_source = try runtime.prepareTranscriptSource(alloc, null); defer appended_source.deinit(alloc); try std.testing.expectEqual( @as(usize, 1), std.mem.count(u8, appended_source.bytes, "queued prompt"), ); } test "recorded user prompt card preserves an anchor when retention prunes its source" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(40, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "anchored transcript\n", .subagent_status, ); var committed_source = try runtime.prepareTranscriptSource(alloc, null); defer committed_source.deinit(alloc); try installStableAnchorForTest( &runtime, alloc, committed_source.bytes, testSelection(0), 0, 4, 1, 1, ); const committed_history_visual_offset = try stableHistoryVisualOffsetForTest(&runtime); const prompt = "retained prompt"; runtime.max_retained_transcript_bytes = prompt.len; var metrics = Metrics{}; _ = try runtime.writeUserPromptCard( alloc, &metrics, .{ .text = @constCast(prompt), .images = &.{} }, true, &.{}, ); try expectStableNormalBufferRecoveryForTest( &runtime, committed_history_visual_offset, ); var retained_source = try runtime.prepareTranscriptSource(alloc, null); defer retained_source.deinit(alloc); try std.testing.expectEqual( @as(usize, 0), std.mem.count(u8, retained_source.bytes, "anchored transcript"), ); try std.testing.expectEqual( @as(usize, 1), std.mem.count(u8, retained_source.bytes, prompt), ); } test "recorded transcript append preserves an authoritative committed anchor" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(48, 12, 8), .owned_top_row = 1, .max_transcript_bytes = 64, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); const long_tail = try makeLongPastePrompt(alloc, 40); defer alloc.free(long_tail); _ = try runtime.appendRawTranscriptEntryClassified( alloc, long_tail, .subagent_status, ); var metrics = Metrics{}; var committed_source = try runtime.prepareTranscriptSource(alloc, null); defer committed_source.deinit(alloc); try std.testing.expect(committed_source.recorded_entries_authoritative); try std.testing.expect(committed_source.bytes.len > runtime.transcript.items.len); var committed_prepared = try runtime.prepareTranscriptSurfacePaintForArea( alloc, &metrics, .{ .top = runtime.owned_top_row, .bottom = runtime.layout.content_bottom }, ); defer committed_prepared.deinit(alloc); const committed_offset = preparedVisualOffsetForTest(&committed_prepared); try installStableAnchorForTest( &runtime, alloc, committed_source.bytes, committed_prepared.selection, committed_offset, committed_prepared.cursor.cursor_row, committed_prepared.cursor.cursor_col, 1, ); const committed_diagnostic = runtime.transcriptCommitDiagnostic(); const notice = "permission accepted\ncommand starting\n"; try runtime.writeTranscriptClassified( alloc, &metrics, notice, true, .subagent_status, ); try std.testing.expectEqualDeep( committed_diagnostic, runtime.transcriptCommitDiagnostic(), ); var appended_source = try runtime.prepareTranscriptSource(alloc, null); defer appended_source.deinit(alloc); try std.testing.expect(std.mem.startsWith( u8, appended_source.bytes, committed_source.bytes, )); try std.testing.expectEqual( @as(usize, 1), std.mem.count(u8, appended_source.bytes, "LONG_PASTE_FIRST_MARKER"), ); try std.testing.expectEqual( @as(usize, 1), std.mem.count(u8, appended_source.bytes, "LONG_PASTE_LAST_MARKER"), ); try std.testing.expectEqual( @as(usize, 1), std.mem.count(u8, appended_source.bytes, "permission accepted"), ); try std.testing.expectEqual( @as(usize, 1), std.mem.count(u8, appended_source.bytes, "command starting"), ); var appended_prepared = try runtime.prepareTranscriptSurfacePaintForArea( alloc, &metrics, .{ .top = runtime.owned_top_row, .bottom = runtime.layout.content_bottom }, ); defer appended_prepared.deinit(alloc); const facts = runtime.planTranscriptScroll(&appended_prepared); try std.testing.expect(facts.source_compatible); try std.testing.expect(facts.semantic_rows > 0); try std.testing.expectEqual(committed_offset, facts.source_visual_offset); } test "theme retint preserves a capped canonical anchor and visual geometry" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(48, 12, 8), .owned_top_row = 1, .max_transcript_bytes = 64, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); const long_tail = try makeLongPastePrompt(alloc, 40); defer alloc.free(long_tail); _ = try runtime.appendRawTranscriptEntryClassified( alloc, long_tail, .subagent_status, ); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "\x1b[38;5;252mRETINT_THEME_MARKER\x1b[39m\n", .subagent_status, ); var metrics: Metrics = .{}; var committed_source = try runtime.prepareTranscriptSource(alloc, null); defer committed_source.deinit(alloc); try std.testing.expect(committed_source.recorded_entries_authoritative); try std.testing.expect(committed_source.bytes.len > runtime.transcript.items.len); var committed_prepared = try runtime.prepareTranscriptSurfacePaintForArea( alloc, &metrics, .{ .top = runtime.owned_top_row, .bottom = runtime.layout.content_bottom }, ); defer committed_prepared.deinit(alloc); try installStableAnchorForTest( &runtime, alloc, committed_source.bytes, committed_prepared.selection, preparedVisualOffsetForTest(&committed_prepared), committed_prepared.cursor.cursor_row, committed_prepared.cursor.cursor_col, 1, ); const committed_diagnostic = runtime.transcriptCommitDiagnostic(); try runtime.retintEntriesForTheme(alloc, false, true); try std.testing.expectEqualDeep( committed_diagnostic, runtime.transcriptCommitDiagnostic(), ); var retinted_source = try runtime.prepareTranscriptSource(alloc, null); defer retinted_source.deinit(alloc); const first_marker = std.mem.find( u8, retinted_source.bytes, "LONG_PASTE_FIRST_MARKER", ) orelse return error.TestUnexpectedResult; const last_marker = std.mem.find( u8, retinted_source.bytes, "LONG_PASTE_LAST_MARKER", ) orelse return error.TestUnexpectedResult; const theme_marker = std.mem.find( u8, retinted_source.bytes, "RETINT_THEME_MARKER", ) orelse return error.TestUnexpectedResult; try std.testing.expect(first_marker < last_marker); try std.testing.expect(last_marker < theme_marker); try std.testing.expect(std.mem.find( u8, retinted_source.bytes, "\x1b[38;5;238mRETINT_THEME_MARKER", ) != null); var retinted_prepared = try runtime.prepareTranscriptSurfacePaintForArea( alloc, &metrics, .{ .top = runtime.owned_top_row, .bottom = runtime.layout.content_bottom }, ); defer retinted_prepared.deinit(alloc); try std.testing.expectEqualDeep( committed_prepared.selection, retinted_prepared.selection, ); try std.testing.expectEqualSlices( u16, committed_prepared.sourceVisualRows(), retinted_prepared.sourceVisualRows(), ); try std.testing.expectEqual( committed_prepared.cursor.cursor_row, retinted_prepared.cursor.cursor_row, ); try std.testing.expectEqual( committed_prepared.cursor.cursor_col, retinted_prepared.cursor.cursor_col, ); const retinted_diagnostic = runtime.transcriptCommitDiagnostic(); const retinted_bytes = try alloc.dupe(u8, retinted_source.bytes); defer alloc.free(retinted_bytes); try runtime.retintEntriesForTheme(alloc, true, true); var unchanged_source = try runtime.prepareTranscriptSource(alloc, null); defer unchanged_source.deinit(alloc); try std.testing.expectEqualStrings(retinted_bytes, unchanged_source.bytes); try std.testing.expectEqualDeep( retinted_diagnostic, runtime.transcriptCommitDiagnostic(), ); } test "light to dark theme retint preserves retention with equal-width tokens" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(48, 12, 8), .owned_top_row = 1, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "OLDER_RETINT_ENTRY\n", .subagent_status, ); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "\x1b[38;5;238mRETINT_GROWTH_MARKER\x1b[39m\n", .subagent_status, ); runtime.max_retained_transcript_bytes = transcript_store.retainedStructuredBytes(&runtime); var committed_source = try runtime.prepareTranscriptSource(alloc, null); defer committed_source.deinit(alloc); try installStableAnchorForTest( &runtime, alloc, committed_source.bytes, testSelection(0), 0, 4, 1, 1, ); try runtime.retintEntriesForTheme(alloc, true, false); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.stable, runtime.transcriptCommitDiagnostic().state, ); try std.testing.expect( transcript_store.retainedStructuredBytes(&runtime) <= runtime.max_retained_transcript_bytes, ); var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); try std.testing.expect(std.mem.find( u8, source.bytes, "\x1b[38;5;252mRETINT_GROWTH_MARKER", ) != null); try std.testing.expectEqual( @as(usize, 1), std.mem.count(u8, source.bytes, "OLDER_RETINT_ENTRY"), ); } fn checkThemeRetintAllocationFailures(alloc: Allocator) !void { var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(48, 12, 8), .owned_top_row = 1, .max_transcript_bytes = 64, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "ALLOCATION TRANSCRIPT 01\n" ++ "ALLOCATION TRANSCRIPT 02\n" ++ "ALLOCATION TRANSCRIPT 03\n" ++ "\x1b[38;5;252mRETINT ALLOCATION MARKER\x1b[39m\n", .subagent_status, ); var committed_source = try runtime.prepareTranscriptSource(alloc, null); defer committed_source.deinit(alloc); try installStableAnchorForTest( &runtime, alloc, committed_source.bytes, testSelection(0), 0, 4, 1, 1, ); var source_before = try runtime.prepareTranscriptSource(std.testing.allocator, null); defer source_before.deinit(std.testing.allocator); const cache_before = try std.testing.allocator.dupe(u8, runtime.transcript.items); defer std.testing.allocator.free(cache_before); const diagnostic_before = runtime.transcriptCommitDiagnostic(); const cursor_row_before = runtime.cursor_row; const cursor_col_before = runtime.cursor_col; const pending_repaints_before = runtime.render_requests.pendingReasonCount(); const cache_origin_before = runtime.transcript_cache_origin_untrimmed; runtime.retintEntriesForTheme(alloc, false, true) catch |err| { var source_after = try runtime.prepareTranscriptSource(std.testing.allocator, null); defer source_after.deinit(std.testing.allocator); try std.testing.expectEqualStrings(source_before.bytes, source_after.bytes); try std.testing.expectEqualStrings(cache_before, runtime.transcript.items); try std.testing.expectEqualDeep( diagnostic_before, runtime.transcriptCommitDiagnostic(), ); try std.testing.expectEqual(cursor_row_before, runtime.cursor_row); try std.testing.expectEqual(cursor_col_before, runtime.cursor_col); try std.testing.expectEqual( pending_repaints_before, runtime.render_requests.pendingReasonCount(), ); try std.testing.expectEqual( cache_origin_before, runtime.transcript_cache_origin_untrimmed, ); return switch (err) { error.WriteFailed => error.OutOfMemory, else => err, }; }; try std.testing.expectEqualDeep( diagnostic_before, runtime.transcriptCommitDiagnostic(), ); var retinted_source = try runtime.prepareTranscriptSource(std.testing.allocator, null); defer retinted_source.deinit(std.testing.allocator); try std.testing.expect(std.mem.find( u8, retinted_source.bytes, "\x1b[38;5;238mRETINT ALLOCATION MARKER", ) != null); } test "theme retint is atomic across allocation failures" { try std.testing.checkAllAllocationFailures( std.testing.allocator, checkThemeRetintAllocationFailures, .{}, ); } test "recorded transcript append preserves an anchor when retention prunes its source" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(40, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "anchored transcript\n", .subagent_status, ); var committed_source = try runtime.prepareTranscriptSource(alloc, null); defer committed_source.deinit(alloc); try installStableAnchorForTest( &runtime, alloc, committed_source.bytes, testSelection(0), 0, 4, 1, 1, ); const committed_history_visual_offset = try stableHistoryVisualOffsetForTest(&runtime); const notice = "permission accepted\n"; runtime.max_retained_transcript_bytes = notice.len; var metrics = Metrics{}; try runtime.writeTranscriptClassified( alloc, &metrics, notice, true, .subagent_status, ); try expectStableNormalBufferRecoveryForTest( &runtime, committed_history_visual_offset, ); var retained_source = try runtime.prepareTranscriptSource(alloc, null); defer retained_source.deinit(alloc); try std.testing.expectEqual( @as(usize, 0), std.mem.count(u8, retained_source.bytes, "anchored transcript"), ); try std.testing.expectEqual( @as(usize, 1), std.mem.count(u8, retained_source.bytes, "permission accepted"), ); } fn check_user_prompt_card_admission_allocation_failures( alloc: Allocator, test_case: UserPromptCardAdmissionCase, ) !void { var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(48, 12, 8) }; defer runtime.deinit(alloc); const seed_id = try setup_user_prompt_card_admission_runtime(&runtime, alloc, test_case); var images = [_]types.ImageAttachment{.{ .id = 7, .path = @constCast("/tmp/prompt-card.png"), .media_type = @constCast("image/png"), }}; const input = user_prompt_card_admission_input(test_case, &images); const input_images_ptr = input.user.images.ptr; const transcript_before = try std.testing.allocator.dupe(u8, runtime.transcript.items); defer std.testing.allocator.free(transcript_before); const reconstructed_before = try renderEntriesToBytes( std.testing.allocator, runtime.entries.items, runtime.layout.cols, ); defer std.testing.allocator.free(reconstructed_before); const entry_count_before = runtime.entries.items.len; const next_entry_id_before = runtime.next_entry_id; const cursor_row_before = runtime.cursor_row; const cursor_col_before = runtime.cursor_col; const retained_before = transcript_store.retainedStructuredBytes(&runtime); const replaceable_before = runtime.replaceable_last_line; const replaceable_start_before = runtime.replaceable_start; const replaceable_row_before = runtime.replaceable_row; const cache_origin_before = runtime.transcript_cache_origin_untrimmed; const repaint_count_before = runtime.render_requests.pendingReasonCount(); var committed_source = try runtime.prepareTranscriptSource(alloc, null); defer committed_source.deinit(alloc); try installStableAnchorForTest( &runtime, alloc, committed_source.bytes, testSelection(0), 0, runtime.cursor_row, runtime.cursor_col, 1, ); const commit_before = runtime.transcriptCommitDiagnostic(); const projection_before = runtime.stableTranscriptProjectionForFlow( committed_source.bytes, ) orelse return error.TestUnexpectedResult; const full_transcript_before = runtime.full_transcript; var metrics = Metrics{}; _ = runtime.writeUserPromptCard( alloc, &metrics, input.user, input.has_prior_turns, input.skill_tokens, ) catch |err| { try std.testing.expectEqual(entry_count_before, runtime.entries.items.len); try std.testing.expectEqual(seed_id, runtime.entries.items[0].id()); try std.testing.expectEqualStrings(transcript_before, runtime.transcript.items); const reconstructed_after = try renderEntriesToBytes( std.testing.allocator, runtime.entries.items, runtime.layout.cols, ); defer std.testing.allocator.free(reconstructed_after); try std.testing.expectEqualStrings(reconstructed_before, reconstructed_after); try std.testing.expectEqual(next_entry_id_before, runtime.next_entry_id); try std.testing.expectEqual(cursor_row_before, runtime.cursor_row); try std.testing.expectEqual(cursor_col_before, runtime.cursor_col); try std.testing.expectEqual(retained_before, transcript_store.retainedStructuredBytes(&runtime)); try std.testing.expectEqual(replaceable_before, runtime.replaceable_last_line); try std.testing.expectEqual(replaceable_start_before, runtime.replaceable_start); try std.testing.expectEqual(replaceable_row_before, runtime.replaceable_row); try std.testing.expectEqual(cache_origin_before, runtime.transcript_cache_origin_untrimmed); try std.testing.expectEqual(repaint_count_before, runtime.render_requests.pendingReasonCount()); try std.testing.expect(!runtime.render_requests.hasReason(.transcript)); try std.testing.expect(!runtime.transcript_band_dirty); try std.testing.expectEqualDeep(commit_before, runtime.transcriptCommitDiagnostic()); const projection_after = runtime.stableTranscriptProjectionForFlow( committed_source.bytes, ) orelse return error.TestUnexpectedResult; try std.testing.expectEqualDeep(projection_before, projection_after); try std.testing.expectEqual(full_transcript_before.scroll_rows, runtime.full_transcript.scroll_rows); try std.testing.expectEqual(full_transcript_before.follow_tail, runtime.full_transcript.follow_tail); try std.testing.expectEqual(full_transcript_before.anchor_entry_id, runtime.full_transcript.anchor_entry_id); try std.testing.expectEqual(full_transcript_before.anchor_pending, runtime.full_transcript.anchor_pending); try std.testing.expectEqualStrings(input.user.text, switch (test_case) { .text_with_separator => "plain prompt", .image_with_skill_token => "inspect $review", }); try std.testing.expectEqual(input_images_ptr, input.user.images.ptr); return switch (err) { error.WriteFailed => error.OutOfMemory, else => err, }; }; try std.testing.expect(runtime.render_requests.hasReason(.transcript)); try std.testing.expectEqual(repaint_count_before + 1, runtime.render_requests.pendingReasonCount()); try std.testing.expectEqualStrings(input.user.text, runtime.entries.items[runtime.entries.items.len - 1].user_turn.turn.text); try std.testing.expectEqual(input_images_ptr, input.user.images.ptr); } test "recorded user prompt card admission is atomic across allocation failures" { try std.testing.checkAllAllocationFailures( std.testing.allocator, check_user_prompt_card_admission_allocation_failures, .{UserPromptCardAdmissionCase.text_with_separator}, ); try std.testing.checkAllAllocationFailures( std.testing.allocator, check_user_prompt_card_admission_allocation_failures, .{UserPromptCardAdmissionCase.image_with_skill_token}, ); } test "tailAssistantSegments returns trailing assistant or null" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc); try std.testing.expect(runtime.tailAssistantSegments() == null); const bytes = try alloc.dupe(u8, "banner"); _ = try runtime.appendRawBytesEntry(alloc, bytes); try std.testing.expect(runtime.tailAssistantSegments() == null); const assistant_id = try runtime.appendAssistantTurnEntry(alloc); const segs = runtime.lookupAssistantSegments(assistant_id) orelse unreachable; try segs.text.appendSlice(alloc, "tok1"); const tail = runtime.tailAssistantSegments(); try std.testing.expect(tail != null); try std.testing.expectEqualStrings("tok1", tail.?.text.items); const trailing = try alloc.dupe(u8, "later"); _ = try runtime.appendRawBytesEntry(alloc, trailing); try std.testing.expect(runtime.tailAssistantSegments() == null); } test "lookupAssistantSegments resolves assistant ids and rejects others" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc); const prompt = try alloc.dupe(u8, "hello"); const user_id = try runtime.appendUserTurnOwned(alloc, .{ .text = prompt, .images = &.{} }); const assistant_id = try runtime.appendAssistantTurnEntry(alloc); const segs = runtime.lookupAssistantSegments(assistant_id) orelse unreachable; try segs.text.appendSlice(alloc, "tok"); const looked_up = runtime.lookupAssistantSegments(assistant_id); try std.testing.expect(looked_up != null); try std.testing.expectEqualStrings("tok", looked_up.?.text.items); const raw = try alloc.dupe(u8, "banner"); const raw_id = try runtime.appendRawBytesEntry(alloc, raw); try std.testing.expect(runtime.lookupAssistantSegments(raw_id) == null); try std.testing.expect(runtime.lookupAssistantSegments(user_id) == null); try std.testing.expect(runtime.lookupAssistantSegments(std.math.maxInt(u32)) == null); } test "clearTranscript drops entries but preserves next_entry_id" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc); const first = try alloc.dupe(u8, "first"); _ = try runtime.appendRawBytesEntry(alloc, first); const second = try alloc.dupe(u8, "second"); _ = try runtime.appendRawBytesEntry(alloc, second); runtime.clearTranscript(alloc); try std.testing.expectEqual(@as(usize, 0), runtime.entries.items.len); const after = try alloc.dupe(u8, "after-clear"); const id_after = try runtime.appendRawBytesEntry(alloc, after); try std.testing.expectEqual(@as(u32, 3), id_after); } test "writeTranscript auto-mirrors a raw_bytes entry" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = .{ .rows = 10, .cols = 80, .content_bottom = 7, .divider_top_row = 8, .input_row = 9, .divider_bottom_row = 10, .hint_row = 10 } }; defer runtime.deinit(alloc); var metrics = Metrics{}; try runtime.initViewport(&metrics, 5); try runtime.writeTranscript(alloc, &metrics, "banner line\n", true); try std.testing.expectEqual(@as(usize, 1), runtime.entries.items.len); try std.testing.expect(runtime.entries.items[0] == .raw_bytes); try std.testing.expectEqualStrings("banner line\n", runtime.entries.items[0].raw_bytes.bytes); } test "writeTranscriptClassified records raw entry class" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc); var metrics = Metrics{}; try runtime.writeTranscriptClassified(alloc, &metrics, "cancelled\n", true, .question_resolution); try std.testing.expectEqual(@as(usize, 1), runtime.entries.items.len); try std.testing.expectEqual(RawEntryClass.question_resolution, runtime.entries.items[0].raw_bytes.class); } const RecordedWriteToolDetailFixture = struct { entry_id: u32, tool_name: []const u8, arguments_json: []const u8, result: []const u8, result_handle: []const u8, turn_id: u64, call_id: []const u8, command_output_entry_id: u32, }; fn appendRecordedWriteToolDetailFixture( runtime: *TranscriptRuntime, alloc: Allocator, fixture: RecordedWriteToolDetailFixture, ) !void { var detail: ToolDetailRecord = .{ .entry_id = fixture.entry_id, .tool_name = try alloc.dupe(u8, fixture.tool_name), }; errdefer detail.deinit(alloc); detail.arguments_json = try alloc.dupe(u8, fixture.arguments_json); detail.result = try alloc.dupe(u8, fixture.result); detail.result_handle = try alloc.dupe(u8, fixture.result_handle); detail.outcome = .completed; const lifecycle_call_id = try alloc.dupe(u8, fixture.call_id); detail.lifecycle_id = .{ .turn_id = fixture.turn_id, .call_id = lifecycle_call_id, }; detail.command_output_entry_id = fixture.command_output_entry_id; try runtime.tool_details.append(alloc, detail); } fn expectRecordedWriteToolDetailFixture( runtime: *const TranscriptRuntime, fixture: RecordedWriteToolDetailFixture, ) !void { const detail = runtime.toolDetailForEntry(fixture.entry_id); try std.testing.expect(detail != null); try std.testing.expectEqualStrings(fixture.tool_name, detail.?.tool_name); try std.testing.expectEqualStrings(fixture.arguments_json, detail.?.arguments_json.?); try std.testing.expectEqualStrings(fixture.result, detail.?.result.?); try std.testing.expectEqualStrings(fixture.result_handle, detail.?.result_handle.?); try std.testing.expectEqual(types.ToolOutcomeKind.completed, detail.?.outcome.?); try std.testing.expectEqual(fixture.turn_id, detail.?.lifecycle_id.?.turn_id); try std.testing.expectEqualStrings(fixture.call_id, detail.?.lifecycle_id.?.call_id); try std.testing.expectEqual(fixture.command_output_entry_id, detail.?.command_output_entry_id.?); } fn appendOwnedToolDetail( runtime: *TranscriptRuntime, alloc: Allocator, entry_id: u32, tool_name: []const u8, ) !void { var detail: ToolDetailRecord = .{ .entry_id = entry_id, .tool_name = try alloc.dupe(u8, tool_name), }; errdefer detail.deinit(alloc); try runtime.tool_details.append(alloc, detail); } test "historical tool detail insertion preserves entry id order" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc); for ([_]u32{ 30, 10, 20 }) |entry_id| { try runtime.attachHistoricalToolDetail( alloc, entry_id, .{ .id = "call", .name = "read_file", .arguments_json = "{}", }, .read, .{ .tool_call_id = @constCast("call"), .tool_name = @constCast("read_file"), .status = .success, .output = @constCast("result"), .output_bytes = 6, .stored_output_bytes = 6, }, ); } try std.testing.expectEqual(@as(usize, 3), runtime.tool_details.items.len); try std.testing.expectEqual(@as(u32, 10), runtime.tool_details.items[0].entry_id); try std.testing.expectEqual(@as(u32, 20), runtime.tool_details.items[1].entry_id); try std.testing.expectEqual(@as(u32, 30), runtime.tool_details.items[2].entry_id); try std.testing.expect(runtime.toolDetailForEntry(9) == null); try std.testing.expectEqual(@as(u32, 10), runtime.toolDetailForEntry(10).?.entry_id); try std.testing.expectEqual(@as(u32, 20), runtime.toolDetailForEntry(20).?.entry_id); try std.testing.expectEqual(@as(u32, 30), runtime.toolDetailForEntry(30).?.entry_id); try std.testing.expect(runtime.toolDetailForEntry(31) == null); try runtime.attachHistoricalToolDetail( alloc, 20, .{ .id = "updated-call", .name = "glob_files", .arguments_json = "{\"path\":\"src\"}", }, .list, .{ .tool_call_id = @constCast("updated-call"), .tool_name = @constCast("glob_files"), .status = .success, .output = @constCast("updated result"), .output_bytes = 14, .stored_output_bytes = 14, }, ); try std.testing.expectEqual(@as(usize, 3), runtime.tool_details.items.len); const updated = runtime.toolDetailForEntry(20).?; try std.testing.expectEqualStrings("glob_files", updated.tool_name); try std.testing.expectEqualStrings("{\"path\":\"src\"}", updated.arguments_json.?); try std.testing.expectEqualStrings("updated result", updated.result.?); } test "tool detail pruning preserves retained payload order and capacity" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc); try appendOwnedToolDetail(&runtime, alloc, 1, "first"); try appendOwnedToolDetail(&runtime, alloc, 2, "second"); try appendOwnedToolDetail(&runtime, alloc, 3, "third"); try appendOwnedToolDetail(&runtime, alloc, 4, "fourth"); try appendOwnedToolDetail(&runtime, alloc, 5, "fifth"); const initial_capacity = runtime.tool_details.capacity; var retained_entry_ids: transcript_store.EntryIdSet = .empty; defer retained_entry_ids.deinit(alloc); try retained_entry_ids.put(alloc, 2, {}); try retained_entry_ids.put(alloc, 4, {}); runtime.pruneToolDetailsForRetainedEntries(alloc, &retained_entry_ids); try std.testing.expectEqual(@as(usize, 2), runtime.tool_details.items.len); try std.testing.expectEqual(initial_capacity, runtime.tool_details.capacity); try std.testing.expectEqual(@as(u32, 2), runtime.tool_details.items[0].entry_id); try std.testing.expectEqualStrings("second", runtime.tool_details.items[0].tool_name); try std.testing.expectEqual(@as(u32, 4), runtime.tool_details.items[1].entry_id); try std.testing.expectEqualStrings("fourth", runtime.tool_details.items[1].tool_name); } test "tool detail pruning stays bounded at fifty thousand records" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc); const detail_count = 50_000; try runtime.tool_details.ensureTotalCapacity(alloc, detail_count); var retained_entry_ids: transcript_store.EntryIdSet = .empty; defer retained_entry_ids.deinit(alloc); try retained_entry_ids.ensureTotalCapacity(alloc, detail_count / 2); for (0..detail_count) |index| { const entry_id: u32 = @intCast(index + 1); try appendOwnedToolDetail(&runtime, alloc, entry_id, "read_file"); if (index % 2 == 0) { retained_entry_ids.putAssumeCapacity(entry_id, {}); } } const lookup_started = std.Io.Clock.Timestamp.now(io_mod.getIo(), .awake); for (0..detail_count) |index| { const entry_id: u32 = @intCast(index + 1); try std.testing.expect(runtime.toolDetailForEntry(entry_id) != null); } try std.testing.expect(runtime.toolDetailForEntry( @as(u32, @intCast(detail_count + 1)), ) == null); const lookup_elapsed_ms = lookup_started.durationTo( std.Io.Clock.Timestamp.now(io_mod.getIo(), .awake), ).raw.toMilliseconds(); try std.testing.expect(lookup_elapsed_ms < 1_000); const started = std.Io.Clock.Timestamp.now(io_mod.getIo(), .awake); runtime.pruneToolDetailsForRetainedEntries(alloc, &retained_entry_ids); const elapsed_ms = started.durationTo( std.Io.Clock.Timestamp.now(io_mod.getIo(), .awake), ).raw.toMilliseconds(); try std.testing.expect(elapsed_ms < 1_000); try std.testing.expectEqual(detail_count / 2, runtime.tool_details.items.len); for (runtime.tool_details.items, 0..) |detail, index| { try std.testing.expectEqual(@as(u32, @intCast(index * 2 + 1)), detail.entry_id); try std.testing.expectEqualStrings("read_file", detail.tool_name); } } const RecordedWriteCommandOutputFixture = struct { entry_id: u32, turn_id: u64, call_id: []const u8, line: []const u8, }; fn appendRecordedWriteCommandOutputFixture( runtime: *TranscriptRuntime, alloc: Allocator, fixture: RecordedWriteCommandOutputFixture, ) !void { const Block = @TypeOf(runtime.command_output_blocks.items[0]); var block: Block = .{ .entry_id = fixture.entry_id, .lifecycle_id = .{ .turn_id = fixture.turn_id, .call_id = try alloc.dupe(u8, fixture.call_id), }, }; errdefer block.deinit(alloc); const line = try alloc.dupe(u8, fixture.line); block.lines.append(alloc, .{ .stream = .stdout, .text = line, }) catch |err| { alloc.free(line); return err; }; block.total_lines = 1; block.retained_text_bytes = line.len; try runtime.command_output_blocks.append(alloc, block); } fn expectRecordedWriteCommandOutputFixture( runtime: *const TranscriptRuntime, fixture: RecordedWriteCommandOutputFixture, ) !void { try std.testing.expectEqual(@as(usize, 1), runtime.command_output_blocks.items.len); const block = runtime.command_output_blocks.items[0]; try std.testing.expectEqual(fixture.entry_id, block.entry_id.?); try std.testing.expect(block.lifecycle_id != null); try std.testing.expectEqual(fixture.turn_id, block.lifecycle_id.?.turn_id); try std.testing.expectEqualStrings(fixture.call_id, block.lifecycle_id.?.call_id); try std.testing.expectEqual(@as(usize, 1), block.lines.items.len); try std.testing.expectEqual(command_output_content.Stream.stdout, block.lines.items[0].stream); try std.testing.expectEqualStrings(fixture.line, block.lines.items[0].text); } test "command output retention preserves its artifact detail owner" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .max_retained_transcript_bytes = 128 }; defer runtime.deinit(alloc); const detail_entry_id = try runtime.appendRawTranscriptEntryClassified( alloc, "● Ran command\n", .tool_status, ); const command_entry_id = try runtime.appendRawTranscriptEntryClassified( alloc, "│ ... command lines folded\n", .command_output, ); const detail: RecordedWriteToolDetailFixture = .{ .entry_id = detail_entry_id, .tool_name = "run_command", .arguments_json = "{\"command\":\"generate output\"}", .result = "output_file=/tmp/fx-command-retained.log\n", .result_handle = "result-run-command.txt", .turn_id = 1, .call_id = "retained-command", .command_output_entry_id = command_entry_id, }; try appendRecordedWriteToolDetailFixture(&runtime, alloc, detail); try appendRecordedWriteCommandOutputFixture(&runtime, alloc, .{ .entry_id = command_entry_id, .turn_id = 1, .call_id = "retained-command", .line = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", }); runtime.command_output_blocks.items[0].total_lines = 2; try transcript_store.enforceStructuredRetention(&runtime, alloc, command_entry_id); try std.testing.expect(runtime.toolDetailForEntry(detail_entry_id) != null); try std.testing.expect(runtime.rawEntryClass(detail_entry_id) == .tool_status); try std.testing.expect(runtime.rawEntryClass(command_entry_id) == .command_output); try std.testing.expectEqual(@as(usize, 2), runtime.command_output_blocks.items[0].total_lines); try std.testing.expectEqual(@as(usize, 0), runtime.command_output_blocks.items[0].lines.items.len); try std.testing.expectEqual(@as(usize, 0), runtime.command_output_blocks.items[0].retained_text_bytes); try std.testing.expect(transcript_store.retainedStructuredBytes(&runtime) <= runtime.max_retained_transcript_bytes); } fn checkRecordedTranscriptWriteAllocationFailures(alloc: Allocator) !void { var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 8, 4), .owned_top_row = 1, .viewport_top_row = 1, .cursor_row = 2, .cursor_col = 4, .max_transcript_bytes = 8, .max_retained_transcript_bytes = 19 + @sizeOf(command_output_runtime.CommandOutputLine), .command_output_render = .{}, }; defer runtime.deinit(alloc); const existing_id = try runtime.appendRawTranscriptEntryClassified( alloc, "old\n", .subagent_status, ); const replaceable_id = try runtime.appendRawTranscriptEntryClassified( alloc, "tail\n", .subagent_status, ); const pruned_detail: RecordedWriteToolDetailFixture = .{ .entry_id = existing_id, .tool_name = "read_file", .arguments_json = "{\"path\":\"old\"}", .result = "old result", .result_handle = "old-result.txt", .turn_id = 1, .call_id = "call-old", .command_output_entry_id = existing_id, }; const retained_detail: RecordedWriteToolDetailFixture = .{ .entry_id = replaceable_id, .tool_name = "write_file", .arguments_json = "{\"path\":\"tail\"}", .result = "tail result", .result_handle = "tail-result.txt", .turn_id = 2, .call_id = "call-tail", .command_output_entry_id = replaceable_id, }; const retained_command_output: RecordedWriteCommandOutputFixture = .{ .entry_id = replaceable_id, .turn_id = 3, .call_id = "call-command-tail", .line = "x", }; try appendRecordedWriteToolDetailFixture(&runtime, alloc, pruned_detail); try appendRecordedWriteToolDetailFixture(&runtime, alloc, retained_detail); try appendRecordedWriteCommandOutputFixture(&runtime, alloc, retained_command_output); runtime.cursor_row = 2; runtime.cursor_col = 4; runtime.replaceable_last_line = true; runtime.replaceable_start = 0; runtime.render_requests.clearReason(.transcript); runtime.transcript_band_dirty = false; const entry_count_before = runtime.entries.items.len; const next_entry_id_before = runtime.next_entry_id; const cursor_row_before = runtime.cursor_row; const cursor_col_before = runtime.cursor_col; const replaceable_before = runtime.replaceable_last_line; const replaceable_start_before = runtime.replaceable_start; const repaint_count_before = runtime.render_requests.pendingReasonCount(); const transcript_before = "tail\n"; try std.testing.expectEqualStrings(transcript_before, runtime.transcript.items); var metrics = Metrics{}; const entry_id = transcript_writer.writeTranscriptReturningEntryIdClassified( &runtime, alloc, &metrics, "first\nsecond\n", true, .question_resolution, ) catch |err| { try std.testing.expectEqual(entry_count_before, runtime.entries.items.len); try std.testing.expectEqual(existing_id, runtime.entries.items[0].id()); try std.testing.expectEqualStrings("old\n", runtime.entries.items[0].raw_bytes.bytes); try std.testing.expectEqual(replaceable_id, runtime.entries.items[1].id()); try std.testing.expectEqualStrings("tail\n", runtime.entries.items[1].raw_bytes.bytes); try std.testing.expectEqualStrings(transcript_before, runtime.transcript.items); try std.testing.expectEqual(next_entry_id_before, runtime.next_entry_id); try std.testing.expectEqual(cursor_row_before, runtime.cursor_row); try std.testing.expectEqual(cursor_col_before, runtime.cursor_col); try std.testing.expectEqual(replaceable_before, runtime.replaceable_last_line); try std.testing.expectEqual(replaceable_start_before, runtime.replaceable_start); try std.testing.expectEqual(@as(usize, 2), runtime.tool_details.items.len); try expectRecordedWriteToolDetailFixture(&runtime, pruned_detail); try expectRecordedWriteToolDetailFixture(&runtime, retained_detail); try expectRecordedWriteCommandOutputFixture(&runtime, retained_command_output); try std.testing.expectEqual(repaint_count_before, runtime.render_requests.pendingReasonCount()); try std.testing.expect(!runtime.render_requests.hasReason(.transcript)); try std.testing.expect(!runtime.transcript_band_dirty); return err; }; try std.testing.expectEqual(@as(u32, 3), entry_id); try std.testing.expectEqual(@as(u32, 4), runtime.next_entry_id); try std.testing.expectEqual(@as(usize, 2), runtime.entries.items.len); try std.testing.expectEqual(replaceable_id, runtime.entries.items[0].id()); try std.testing.expectEqual(entry_id, runtime.entries.items[1].id()); try std.testing.expectEqual(RawEntryClass.question_resolution, runtime.entries.items[1].raw_bytes.class); try std.testing.expectEqualStrings("first\nsecond\n", runtime.entries.items[1].raw_bytes.bytes); try std.testing.expectEqualStrings("second\n", runtime.transcript.items); try std.testing.expect(!runtime.replaceable_last_line); try std.testing.expectEqual(@as(usize, 0), runtime.replaceable_start); try std.testing.expectEqual(@as(usize, 1), runtime.tool_details.items.len); try std.testing.expect(runtime.toolDetailForEntry(existing_id) == null); try expectRecordedWriteToolDetailFixture(&runtime, retained_detail); try expectRecordedWriteCommandOutputFixture(&runtime, retained_command_output); try std.testing.expectEqual(@as(usize, 1), runtime.render_requests.pendingReasonCount()); try std.testing.expect(runtime.render_requests.hasReason(.transcript)); try std.testing.expect(runtime.transcript_band_dirty); } test "recorded transcript write is atomic across entry cache and retention allocation failures" { try std.testing.checkAllAllocationFailures( std.testing.allocator, checkRecordedTranscriptWriteAllocationFailures, .{}, ); } test "context notices stay canonical and ordered across compact and full projections" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(80, 24, 20) }; defer runtime.deinit(alloc); _ = try runtime.appendSemanticNotice(alloc, .{ .topic = "context", .tone = .warning, .body = "context-first", .visibility = .full_only, }); try std.testing.expectEqualStrings("", runtime.transcript.items); _ = try runtime.appendSemanticNotice(alloc, .{ .topic = "system", .tone = .information, .body = "ordinary-system", }); const compact_before_open = try alloc.dupe(u8, runtime.transcript.items); defer alloc.free(compact_before_open); try std.testing.expect(std.mem.find(u8, compact_before_open, "context-first") == null); try std.testing.expect(std.mem.find(u8, compact_before_open, "ordinary-system") != null); try std.testing.expect(try runtime.setTranscriptPresentationDepth(alloc, .full)); _ = try runtime.appendSemanticNotice(alloc, .{ .topic = "context", .tone = .warning, .body = "context-second", .visibility = .full_only, }); _ = try runtime.appendSemanticNotice(alloc, .{ .topic = "system", .tone = .@"error", .body = "ordinary-error", }); try std.testing.expect(std.mem.find(u8, runtime.transcript.items, "context-first") == null); try std.testing.expect(std.mem.find(u8, runtime.transcript.items, "context-second") == null); try std.testing.expect(std.mem.find(u8, runtime.transcript.items, "ordinary-system") != null); try std.testing.expect(std.mem.find(u8, runtime.transcript.items, "ordinary-error") != null); const compact_while_open = try alloc.dupe(u8, runtime.transcript.items); defer alloc.free(compact_while_open); try std.testing.expectEqual(@as(usize, 4), runtime.entries.items.len); try std.testing.expect(runtime.entries.items[0] == .semantic_notice); try std.testing.expectEqual(types.NoticeVisibility.full_only, runtime.entries.items[0].semantic_notice.visibility); try std.testing.expectEqualStrings("context-first", runtime.entries.items[0].semantic_notice.body); try std.testing.expect(runtime.entries.items[1] == .semantic_notice); try std.testing.expectEqual(types.NoticeVisibility.compact_and_full, runtime.entries.items[1].semantic_notice.visibility); try std.testing.expectEqualStrings("ordinary-system", runtime.entries.items[1].semantic_notice.body); try std.testing.expect(runtime.entries.items[2] == .semantic_notice); try std.testing.expectEqual(types.NoticeVisibility.full_only, runtime.entries.items[2].semantic_notice.visibility); try std.testing.expectEqualStrings("context-second", runtime.entries.items[2].semantic_notice.body); try std.testing.expect(runtime.entries.items[3] == .semantic_notice); try std.testing.expectEqual(types.NoticeTone.@"error", runtime.entries.items[3].semantic_notice.tone); var projection = try runtime.buildFullTranscriptProjection(alloc, null); defer projection.deinit(alloc); const full = try full_transcript_screen.renderProjectionViewportSourceInterruptible( alloc, &projection, null, runtime.layout.cols, 64, 0, null, ); defer alloc.free(full); const first_pos = std.mem.find(u8, full, "context-first") orelse return error.MissingFirstContextNotice; const system_pos = std.mem.find(u8, full, "ordinary-system") orelse return error.MissingOrdinarySystemNotice; const second_pos = std.mem.find(u8, full, "context-second") orelse return error.MissingSecondContextNotice; const error_pos = std.mem.find(u8, full, "ordinary-error") orelse return error.MissingOrdinaryErrorNotice; try std.testing.expect(first_pos < system_pos); try std.testing.expect(system_pos < second_pos); try std.testing.expect(second_pos < error_pos); try std.testing.expect(try runtime.setTranscriptPresentationDepth(alloc, .inline_mode)); try std.testing.expect(!runtime.fullTranscriptActive()); try std.testing.expectEqualStrings(compact_while_open, runtime.transcript.items); try std.testing.expect(std.mem.find(u8, runtime.transcript.items, "context-first") == null); try std.testing.expect(std.mem.find(u8, runtime.transcript.items, "context-second") == null); } fn checkContextNoticeAllocationFailures(alloc: Allocator) !void { var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(40, 12, 8) }; defer runtime.deinit(alloc); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "visible-before\n", .subagent_status, ); runtime.render_requests.clearReason(.transcript); runtime.transcript_band_dirty = false; const next_entry_id_before = runtime.next_entry_id; const cursor_row_before = runtime.cursor_row; const cursor_col_before = runtime.cursor_col; _ = transcript_store.appendSemanticNoticeAtomic( &runtime, alloc, .{ .topic = "context", .tone = .warning, .body = "hidden-context", .visibility = .full_only, }, ) catch |err| { try std.testing.expectEqual(@as(usize, 1), runtime.entries.items.len); try std.testing.expectEqual(RawEntryClass.subagent_status, runtime.entries.items[0].raw_bytes.class); try std.testing.expectEqualStrings("visible-before\n", runtime.entries.items[0].raw_bytes.bytes); try std.testing.expectEqualStrings("visible-before\n", runtime.transcript.items); try std.testing.expectEqual(next_entry_id_before, runtime.next_entry_id); try std.testing.expectEqual(cursor_row_before, runtime.cursor_row); try std.testing.expectEqual(cursor_col_before, runtime.cursor_col); try std.testing.expect(!runtime.render_requests.hasReason(.transcript)); try std.testing.expect(!runtime.transcript_band_dirty); return err; }; try std.testing.expectEqual(@as(usize, 2), runtime.entries.items.len); try std.testing.expect(runtime.entries.items[1] == .semantic_notice); try std.testing.expectEqualStrings("context", runtime.entries.items[1].semantic_notice.topic); try std.testing.expectEqualStrings("hidden-context", runtime.entries.items[1].semantic_notice.body); try std.testing.expectEqual(types.NoticeVisibility.full_only, runtime.entries.items[1].semantic_notice.visibility); try std.testing.expectEqualStrings("visible-before\n", runtime.transcript.items); try std.testing.expect(runtime.render_requests.hasReason(.transcript)); try std.testing.expect(runtime.transcript_band_dirty); } test "context notice admission is atomic across entry cache and retention allocation failures" { try std.testing.checkAllAllocationFailures( std.testing.allocator, checkContextNoticeAllocationFailures, .{}, ); } test "repeated recorded mutations keep tool detail capacity bounded" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(80, 12, 8) }; defer runtime.deinit(alloc); const fixture: RecordedWriteToolDetailFixture = .{ .entry_id = 1, .tool_name = "run_command", .arguments_json = "{\"command\":\"pwd\"}", .result = "workspace", .result_handle = "command-output.txt", .turn_id = 1, .call_id = "bounded-tool-detail", .command_output_entry_id = 1, }; try appendRecordedWriteToolDetailFixture(&runtime, alloc, fixture); const initial_capacity = runtime.tool_details.capacity; var metrics = Metrics{}; for (0..4) |_| { try runtime.writeTranscriptClassified( alloc, &metrics, "recorded\n", true, .subagent_status, ); } try std.testing.expect(runtime.tool_details.capacity <= initial_capacity); try expectRecordedWriteToolDetailFixture(&runtime, fixture); } const RecordedCommandOutputAtomicFixture = struct { lifecycle_id: types.ToolLifecycleId, status_entry_id: u32, }; fn setupRecordedCommandOutputAtomicFixture( runtime: *TranscriptRuntime, alloc: Allocator, ) !RecordedCommandOutputAtomicFixture { const lifecycle_id = types.ToolLifecycleId{ .turn_id = 71, .call_id = "atomic-command-output", }; _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = lifecycle_id, .reconciles_provisional_call_id = null, .tool_name = "run_command", .activity_kind = .command, .arguments_json = "{\"command\":\"printf atomic\"}", } }); const status_entry_id = runtime.toolActivityRecord(lifecycle_id).?.entry_id; runtime.full_transcript.depth = .full; runtime.full_transcript.scroll_rows = 3; runtime.full_transcript.follow_tail = false; runtime.full_transcript.anchor_entry_id = status_entry_id; runtime.full_transcript.anchor_pending = true; runtime.render_requests.clearReason(.transcript); runtime.transcript_band_dirty = false; return .{ .lifecycle_id = lifecycle_id, .status_entry_id = status_entry_id, }; } fn expectRecordedCommandOutputAnchor( runtime: *const TranscriptRuntime, entry_id: u32, ) !void { try std.testing.expect(runtime.full_transcript.depth.active()); try std.testing.expectEqual(@as(u32, 3), runtime.full_transcript.scroll_rows); try std.testing.expect(!runtime.full_transcript.follow_tail); try std.testing.expectEqual(entry_id, runtime.full_transcript.anchor_entry_id.?); try std.testing.expect(runtime.full_transcript.anchor_pending); } test "visible recorded command output preserves a capped canonical anchor without pruning" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(48, 12, 8), .owned_top_row = 1, .max_transcript_bytes = 64, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); const status_entry_id = try transcript_store.appendPinnedToolStatusAtomic( &runtime, alloc, "canonical command status prefix extends beyond the capped cache\nretained status tail\n", ); var metrics: Metrics = .{}; var committed_source = try runtime.prepareTranscriptSource(alloc, null); defer committed_source.deinit(alloc); try std.testing.expect(committed_source.recorded_entries_authoritative); try std.testing.expect(committed_source.bytes.len > runtime.transcript.items.len); var committed_prepared = try runtime.prepareTranscriptSurfacePaintForArea( alloc, &metrics, .{ .top = runtime.owned_top_row, .bottom = runtime.layout.content_bottom }, ); defer committed_prepared.deinit(alloc); try installStableAnchorForTest( &runtime, alloc, committed_source.bytes, committed_prepared.selection, preparedVisualOffsetForTest(&committed_prepared), committed_prepared.cursor.cursor_row, committed_prepared.cursor.cursor_col, 1, ); const committed_diagnostic = runtime.transcriptCommitDiagnostic(); const styles = commandOutputAnsiTestStyles(); const retained_before = transcript_store.retainedStructuredBytes(&runtime); runtime.max_retained_transcript_bytes = retained_before + 512; try runtime.writeCommandOutputChunk( alloc, &metrics, styles, .stdout, "visible\n", true, ); try std.testing.expectEqualDeep( committed_diagnostic, runtime.transcriptCommitDiagnostic(), ); try std.testing.expectEqual(@as(usize, 2), runtime.entries.items.len); try std.testing.expectEqual(status_entry_id, runtime.entries.items[0].id()); try std.testing.expect(runtime.entries.items[0].raw_bytes.lifecycle_pinned); try std.testing.expectEqual(RawEntryClass.command_output, runtime.entries.items[1].raw_bytes.class); try std.testing.expectEqualStrings( "\x1b[2m│ visible\x1b[0m", runtime.entries.items[1].raw_bytes.bytes, ); try std.testing.expectEqual(@as(usize, 1), runtime.command_output_blocks.items.len); const block = runtime.command_output_blocks.items[0]; try std.testing.expectEqual(@as(usize, 1), block.total_lines); try std.testing.expectEqual(@as(usize, 1), block.lines.items.len); try std.testing.expectEqual(@as(usize, 1), block.live_entry_ids.items.len); try std.testing.expectEqual(runtime.entries.items[1].id(), block.live_entry_ids.items[0]); try std.testing.expect( transcript_store.retainedStructuredBytes(&runtime) <= runtime.max_retained_transcript_bytes, ); var appended_source = try runtime.prepareTranscriptSource(alloc, null); defer appended_source.deinit(alloc); try std.testing.expect(std.mem.startsWith( u8, appended_source.bytes, committed_source.bytes, )); } test "visible recorded command output survives consolidation" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(48, 12, 8), .owned_top_row = 1, .max_transcript_bytes = 64, }; defer runtime.deinit(alloc); const fixture = try setupRecordedCommandOutputAtomicFixture(&runtime, alloc); var metrics: Metrics = .{}; const styles = commandOutputAnsiTestStyles(); const retained_before = transcript_store.retainedStructuredBytes(&runtime); runtime.max_retained_transcript_bytes = retained_before + 512; try runtime.writeCommandOutputChunkForLifecycle( alloc, &metrics, styles, fixture.lifecycle_id, .stdout, "visible\n", true, ); try std.testing.expectEqual(@as(usize, 1), runtime.command_output_blocks.items[0].lines.items.len); try std.testing.expectEqual(@as(usize, 1), runtime.command_output_blocks.items[0].live_entry_ids.items.len); const live_entry_id = runtime.command_output_blocks.items[0].live_entry_ids.items[0]; runtime.full_transcript.anchor_entry_id = live_entry_id; try runtime.flushCommandOutputSummaryForLifecycle( alloc, &metrics, styles, fixture.lifecycle_id, true, ); const block = runtime.command_output_blocks.items[0]; try std.testing.expectEqual(@as(usize, 0), block.live_entry_ids.items.len); try std.testing.expectEqualSlices(u32, &.{live_entry_id}, block.source_entry_ids.items); try std.testing.expectEqual(live_entry_id, block.entry_id.?); try std.testing.expectEqual( live_entry_id, runtime.toolDetailForEntry(fixture.status_entry_id).?.command_output_entry_id.?, ); try std.testing.expectEqual(live_entry_id, runtime.fullTranscriptAnchorEntryId().?); var consolidated_source = try runtime.prepareTranscriptSource(alloc, null); defer consolidated_source.deinit(alloc); try std.testing.expectEqual(@as(usize, 0), std.mem.count(u8, consolidated_source.bytes, "visible")); runtime.setCommandOutputRenderPolicy(styles); _ = try command_output_runtime.syncCommandOutputBlockEntries(&runtime, alloc); var synchronized_source = try runtime.prepareTranscriptSource(alloc, null); defer synchronized_source.deinit(alloc); try std.testing.expectEqual(@as(usize, 0), std.mem.count(u8, synchronized_source.bytes, "visible")); var projection = try runtime.buildFullTranscriptProjection(alloc, null); defer projection.deinit(alloc); const projected = try full_transcript_screen.renderProjectionViewportSourceInterruptible( alloc, &projection, null, runtime.layout.cols, 32, 0, null, ); defer alloc.free(projected); try std.testing.expectEqual(@as(usize, 1), std.mem.count(u8, projected, "visible")); try std.testing.expect((try full_transcript_screen.measureProjectionInterruptible( alloc, &projection, null, runtime.layout.cols, null, )).anchor_row != null); } test "retention capped command output retargets a missing source anchor" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(48, 12, 8), .owned_top_row = 1, }; defer runtime.deinit(alloc); const missing_entry_id = try runtime.appendRawTranscriptEntryClassified( alloc, "│ pruned\n", .command_output, ); const retained_entry_id = try runtime.appendRawTranscriptEntryClassified( alloc, "│ retained\n", .command_output, ); try runtime.command_output_blocks.append(alloc, .{ .total_lines = 2 }); try runtime.command_output_blocks.items[0].live_entry_ids.append(alloc, missing_entry_id); try runtime.command_output_blocks.items[0].live_entry_ids.append(alloc, retained_entry_id); runtime.command_output_display.open_command_block = 0; runtime.full_transcript.depth = .full; runtime.full_transcript.anchor_entry_id = missing_entry_id; try removeRawEntriesForTest(&runtime, alloc, &.{missing_entry_id}); var metrics: Metrics = .{}; const styles = commandOutputAnsiTestStyles(); try runtime.flushCommandOutputSummary(alloc, &metrics, styles, true); try std.testing.expectEqual(retained_entry_id, runtime.command_output_blocks.items[0].entry_id.?); try std.testing.expectEqual(retained_entry_id, runtime.fullTranscriptAnchorEntryId().?); var projection = try runtime.buildFullTranscriptProjection(alloc, null); defer projection.deinit(alloc); const measurement = try full_transcript_screen.measureProjectionInterruptible( alloc, &projection, null, runtime.layout.cols, null, ); try std.testing.expect(measurement.anchor_row != null); } fn checkVisibleRecordedCommandOutputAllocationFailuresImpl(alloc: Allocator) !void { var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(80, 12, 8), }; defer runtime.deinit(alloc); const fixture = try setupRecordedCommandOutputAtomicFixture(&runtime, alloc); const transcript_before = try std.testing.allocator.dupe(u8, runtime.transcript.items); defer std.testing.allocator.free(transcript_before); const entry_count_before = runtime.entries.items.len; const next_entry_id_before = runtime.next_entry_id; const cursor_row_before = runtime.cursor_row; const cursor_col_before = runtime.cursor_col; const retained_before = transcript_store.retainedStructuredBytes(&runtime); const repaint_count_before = runtime.render_requests.pendingReasonCount(); var metrics = Metrics{}; const styles = commandOutputAnsiTestStyles(); runtime.writeCommandOutputChunkForLifecycle( alloc, &metrics, styles, fixture.lifecycle_id, .stdout, "visible\n", true, ) catch |err| { try std.testing.expectEqual(entry_count_before, runtime.entries.items.len); try std.testing.expectEqual(next_entry_id_before, runtime.next_entry_id); try std.testing.expectEqual(@as(usize, 0), runtime.command_output_blocks.items.len); try std.testing.expect(runtime.command_output_display.open_command_block == null); try std.testing.expectEqualDeep(command_output_runtime.CommandOutputRenderPolicy{}, runtime.command_output_render); try std.testing.expectEqualStrings(transcript_before, runtime.transcript.items); try std.testing.expectEqual(cursor_row_before, runtime.cursor_row); try std.testing.expectEqual(cursor_col_before, runtime.cursor_col); try std.testing.expectEqual(retained_before, transcript_store.retainedStructuredBytes(&runtime)); try std.testing.expect(runtime.toolDetailForEntry(fixture.status_entry_id).?.command_output_entry_id == null); try expectRecordedCommandOutputAnchor(&runtime, fixture.status_entry_id); try std.testing.expectEqual(repaint_count_before, runtime.render_requests.pendingReasonCount()); try std.testing.expect(!runtime.render_requests.hasReason(.transcript)); try std.testing.expect(!runtime.transcript_band_dirty); return switch (err) { error.WriteFailed => error.OutOfMemory, else => err, }; }; try std.testing.expectEqual(entry_count_before + 1, runtime.entries.items.len); try std.testing.expectEqual(next_entry_id_before +% 1, runtime.next_entry_id); try std.testing.expectEqual(@as(usize, 1), runtime.command_output_blocks.items.len); const block = runtime.command_output_blocks.items[0]; try std.testing.expect(command_output_runtime.sameLifecycleId(block.lifecycle_id, fixture.lifecycle_id)); try std.testing.expectEqual(@as(usize, 1), block.lines.items.len); try std.testing.expectEqual(command_output_content.Stream.stdout, block.lines.items[0].stream); try std.testing.expectEqualStrings("visible", block.lines.items[0].text); try std.testing.expectEqual(@as(usize, 1), block.live_entry_ids.items.len); try std.testing.expectEqual(runtime.entries.items[entry_count_before].id(), block.live_entry_ids.items[0]); try std.testing.expectEqual(RawEntryClass.command_output, runtime.entries.items[entry_count_before].raw_bytes.class); try std.testing.expectEqualStrings("\x1b[2m│ visible\x1b[0m", runtime.entries.items[entry_count_before].raw_bytes.bytes); try std.testing.expectEqualDeep(styles, runtime.command_output_render.styles); try std.testing.expectEqual(@as(?usize, 0), runtime.command_output_display.open_command_block); try std.testing.expect(runtime.toolDetailForEntry(fixture.status_entry_id).?.command_output_entry_id == null); try expectRecordedCommandOutputAnchor(&runtime, fixture.status_entry_id); try std.testing.expectEqual(@as(usize, 1), runtime.render_requests.pendingReasonCount()); try std.testing.expect(runtime.render_requests.hasReason(.transcript)); try std.testing.expect(runtime.transcript_band_dirty); } fn checkVisibleRecordedCommandOutputAllocationFailures(alloc: Allocator) !void { return checkVisibleRecordedCommandOutputAllocationFailuresImpl(alloc) catch |err| switch (err) { error.WriteFailed => error.OutOfMemory, else => err, }; } test "visible recorded command output admission is atomic across allocation failures" { try std.testing.checkAllAllocationFailures( std.testing.allocator, checkVisibleRecordedCommandOutputAllocationFailures, .{}, ); } fn checkRecordedCommandOutputConsolidationAllocationFailuresImpl(alloc: Allocator) !void { var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(80, 12, 8), }; defer runtime.deinit(alloc); const fixture = try setupRecordedCommandOutputAtomicFixture(&runtime, alloc); var metrics = Metrics{}; const styles = commandOutputAnsiTestStyles(); runtime.writeCommandOutputChunkForLifecycle( alloc, &metrics, styles, fixture.lifecycle_id, .stdout, "one\n", true, ) catch |err| return switch (err) { error.WriteFailed => error.OutOfMemory, else => err, }; runtime.writeCommandOutputChunkForLifecycle( alloc, &metrics, styles, fixture.lifecycle_id, .stderr, "two\n", true, ) catch |err| return switch (err) { error.WriteFailed => error.OutOfMemory, else => err, }; const first_live_entry_id = runtime.command_output_blocks.items[0].live_entry_ids.items[0]; runtime.full_transcript.anchor_entry_id = first_live_entry_id; runtime.render_requests.clearReason(.transcript); runtime.transcript_band_dirty = false; const rendered_before = try renderEntriesToBytes(std.testing.allocator, runtime.entries.items, 80); defer std.testing.allocator.free(rendered_before); const transcript_before = try std.testing.allocator.dupe(u8, runtime.transcript.items); defer std.testing.allocator.free(transcript_before); const entry_count_before = runtime.entries.items.len; const next_entry_id_before = runtime.next_entry_id; const cursor_row_before = runtime.cursor_row; const cursor_col_before = runtime.cursor_col; const retained_before = transcript_store.retainedStructuredBytes(&runtime); const repaint_count_before = runtime.render_requests.pendingReasonCount(); runtime.flushCommandOutputSummaryForLifecycle( alloc, &metrics, styles, fixture.lifecycle_id, true, ) catch |err| { const rendered_after_failure = try renderEntriesToBytes( std.testing.allocator, runtime.entries.items, 80, ); defer std.testing.allocator.free(rendered_after_failure); try std.testing.expectEqualStrings(rendered_before, rendered_after_failure); try std.testing.expectEqualStrings(transcript_before, runtime.transcript.items); try std.testing.expectEqual(entry_count_before, runtime.entries.items.len); try std.testing.expectEqual(next_entry_id_before, runtime.next_entry_id); try std.testing.expectEqual(cursor_row_before, runtime.cursor_row); try std.testing.expectEqual(cursor_col_before, runtime.cursor_col); try std.testing.expectEqual(retained_before, transcript_store.retainedStructuredBytes(&runtime)); try std.testing.expectEqual(@as(usize, 1), runtime.command_output_blocks.items.len); const block = runtime.command_output_blocks.items[0]; try std.testing.expect(block.entry_id == null); try std.testing.expectEqual(@as(usize, 2), block.live_entry_ids.items.len); try std.testing.expectEqual(@as(usize, 0), block.source_entry_ids.items.len); try std.testing.expectEqual(@as(?usize, 0), runtime.command_output_display.open_command_block); try std.testing.expect(runtime.toolDetailForEntry(fixture.status_entry_id).?.command_output_entry_id == null); try expectRecordedCommandOutputAnchor(&runtime, first_live_entry_id); try std.testing.expectEqual(repaint_count_before, runtime.render_requests.pendingReasonCount()); try std.testing.expect(!runtime.render_requests.hasReason(.transcript)); try std.testing.expect(!runtime.transcript_band_dirty); return switch (err) { error.WriteFailed => error.OutOfMemory, else => err, }; }; const rendered_after = try renderEntriesToBytes(std.testing.allocator, runtime.entries.items, 80); defer std.testing.allocator.free(rendered_after); try std.testing.expectEqualStrings(rendered_before, rendered_after); try std.testing.expectEqual(entry_count_before, runtime.entries.items.len); try std.testing.expectEqual(next_entry_id_before, runtime.next_entry_id); try std.testing.expectEqual(retained_before, transcript_store.retainedStructuredBytes(&runtime)); try std.testing.expectEqual(@as(usize, 1), runtime.command_output_blocks.items.len); const block = runtime.command_output_blocks.items[0]; try std.testing.expect(block.entry_id != null); try std.testing.expectEqual(@as(usize, 0), block.live_entry_ids.items.len); try std.testing.expectEqual(@as(usize, 2), block.source_entry_ids.items.len); try std.testing.expectEqualStrings("one", block.lines.items[0].text); try std.testing.expectEqualStrings("two", block.lines.items[1].text); try std.testing.expect(runtime.command_output_display.open_command_block == null); try std.testing.expectEqual(block.entry_id, runtime.toolDetailForEntry(fixture.status_entry_id).?.command_output_entry_id); try expectRecordedCommandOutputAnchor(&runtime, first_live_entry_id); try std.testing.expectEqual(@as(usize, 1), runtime.render_requests.pendingReasonCount()); try std.testing.expect(runtime.render_requests.hasReason(.transcript)); try std.testing.expect(runtime.transcript_band_dirty); } fn checkRecordedCommandOutputConsolidationAllocationFailures(alloc: Allocator) !void { return checkRecordedCommandOutputConsolidationAllocationFailuresImpl(alloc) catch |err| switch (err) { error.WriteFailed => error.OutOfMemory, else => err, }; } test "recorded command output consolidation is atomic across allocation failures" { try std.testing.checkAllAllocationFailures( std.testing.allocator, checkRecordedCommandOutputConsolidationAllocationFailures, .{}, ); } test "recorded command output consolidation preserves a capped canonical anchor without recovery fallback" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(48, 12, 8), .owned_top_row = 1, .max_transcript_bytes = 64, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); var metrics: Metrics = .{}; try runtime.writeTranscript( alloc, &metrics, "canonical command output prefix extends beyond the capped cache\nretained command output prefix tail\n", true, ); const fixture = try setupRecordedCommandOutputAtomicFixture(&runtime, alloc); const styles = commandOutputAnsiTestStyles(); try runtime.writeCommandOutputChunkForLifecycle( alloc, &metrics, styles, fixture.lifecycle_id, .stdout, "one\n", true, ); try runtime.writeCommandOutputChunkForLifecycle( alloc, &metrics, styles, fixture.lifecycle_id, .stderr, "two\n", true, ); var committed_source = try runtime.prepareTranscriptSource(alloc, null); defer committed_source.deinit(alloc); try std.testing.expect(committed_source.recorded_entries_authoritative); try std.testing.expect(committed_source.bytes.len > runtime.transcript.items.len); var committed_prepared = try runtime.prepareTranscriptSurfacePaintForArea( alloc, &metrics, .{ .top = runtime.owned_top_row, .bottom = runtime.layout.content_bottom }, ); defer committed_prepared.deinit(alloc); try installStableAnchorForTest( &runtime, alloc, committed_source.bytes, committed_prepared.selection, preparedVisualOffsetForTest(&committed_prepared), committed_prepared.cursor.cursor_row, committed_prepared.cursor.cursor_col, 1, ); const committed_diagnostic = runtime.transcriptCommitDiagnostic(); const retained_before = transcript_store.retainedStructuredBytes(&runtime); try runtime.flushCommandOutputSummaryForLifecycle( alloc, &metrics, styles, fixture.lifecycle_id, true, ); try std.testing.expectEqualDeep( committed_diagnostic, runtime.transcriptCommitDiagnostic(), ); try std.testing.expectEqual( retained_before, transcript_store.retainedStructuredBytes(&runtime), ); try std.testing.expectEqual(@as(usize, 2), runtime.command_output_blocks.items[0].lines.items.len); var consolidated_source = try runtime.prepareTranscriptSource(alloc, null); defer consolidated_source.deinit(alloc); try std.testing.expectEqualStrings(committed_source.bytes, consolidated_source.bytes); switch (runtime.transcript_commit_state) { .stable => |anchor| try std.testing.expect(!anchor.normal_buffer_recovery_pending), .invalid, .recovering => return error.TestExpectedStableTranscript, } } test "recorded command output completion does not allocate without a matching block" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(80, 12, 8) }; defer runtime.deinit(alloc); var metrics = Metrics{}; const styles = commandOutputAnsiTestStyles(); try runtime.writeTranscript(alloc, &metrics, "existing\n", true); var no_open_failing = std.testing.FailingAllocator.init( alloc, .{ .fail_index = 0 }, ); try runtime.flushCommandOutputSummaryForLifecycle( no_open_failing.allocator(), &metrics, styles, null, true, ); try std.testing.expectEqualDeep(styles, runtime.command_output_render.styles); try std.testing.expect(runtime.command_output_display.open_command_block == null); const open_id = types.ToolLifecycleId{ .turn_id = 81, .call_id = "open-command" }; try runtime.writeCommandOutputChunkForLifecycle( alloc, &metrics, styles, open_id, .stdout, "open\n", true, ); const entry_count_before = runtime.entries.items.len; const next_entry_id_before = runtime.next_entry_id; const display_before = runtime.command_output_display; const mismatch_id = types.ToolLifecycleId{ .turn_id = 82, .call_id = "other-command" }; var mismatch_failing = std.testing.FailingAllocator.init( alloc, .{ .fail_index = 0 }, ); try runtime.flushCommandOutputSummaryForLifecycle( mismatch_failing.allocator(), &metrics, styles, mismatch_id, true, ); try std.testing.expectEqualDeep(styles, runtime.command_output_render.styles); try std.testing.expectEqual(entry_count_before, runtime.entries.items.len); try std.testing.expectEqual(next_entry_id_before, runtime.next_entry_id); try std.testing.expectEqual(display_before.open_command_block, runtime.command_output_display.open_command_block); } test "recorded command output consolidation preserves its anchor when retention changes" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(80, 12, 8) }; defer runtime.deinit(alloc); const fixture = try setupRecordedCommandOutputAtomicFixture(&runtime, alloc); var metrics = Metrics{}; const styles = commandOutputAnsiTestStyles(); try runtime.writeCommandOutputChunkForLifecycle( alloc, &metrics, styles, fixture.lifecycle_id, .stdout, "one\n", true, ); try runtime.writeCommandOutputChunkForLifecycle( alloc, &metrics, styles, fixture.lifecycle_id, .stdout, "two\n", true, ); const retained_before = transcript_store.retainedStructuredBytes(&runtime); runtime.max_retained_transcript_bytes = retained_before - 1; try runtime.enableShadowVt(alloc); try installStableAnchorForTest( &runtime, alloc, "pre-retention source\n", testSelection(0), 0, 3, 1, 91, ); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.stable, runtime.transcriptCommitDiagnostic().state, ); const committed_history_visual_offset = try stableHistoryVisualOffsetForTest(&runtime); try runtime.flushCommandOutputSummaryForLifecycle( alloc, &metrics, styles, fixture.lifecycle_id, true, ); try std.testing.expect(transcript_store.retainedStructuredBytes(&runtime) <= runtime.max_retained_transcript_bytes); try std.testing.expect(runtime.command_output_blocks.items[0].lines.items.len < 2); try expectStableNormalBufferRecoveryForTest( &runtime, committed_history_visual_offset, ); } test "writeTranscriptBytes does NOT append an entry" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = .{ .rows = 10, .cols = 80, .content_bottom = 7, .divider_top_row = 8, .input_row = 9, .divider_bottom_row = 10, .hint_row = 10 } }; defer runtime.deinit(alloc); var metrics = Metrics{}; try runtime.initViewport(&metrics, 5); try runtime.writeTranscriptBytes(alloc, &metrics, "structured writer bytes\n", true); try std.testing.expectEqual(@as(usize, 0), runtime.entries.items.len); try std.testing.expect(std.mem.indexOf(u8, runtime.transcript.items, "structured writer bytes") != null); } test "appendReplaceableTranscriptLineSilent mirrors a raw_bytes entry" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc); _ = try runtime.appendReplaceableTranscriptLineSilent(alloc, "Thinking...\n"); try std.testing.expectEqual(@as(usize, 1), runtime.entries.items.len); try std.testing.expect(runtime.entries.items[0] == .raw_bytes); try std.testing.expectEqualStrings("Thinking...\n", runtime.entries.items[0].raw_bytes.bytes); try std.testing.expect(runtime.replaceable_last_line); } test "classified replaceable append stores raw entry class" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc); const entry_id = try runtime.appendReplaceableTranscriptLineSilentClassified(alloc, "Thinking...\n", .subagent_status); try std.testing.expectEqual(RawEntryClass.subagent_status, runtime.rawEntryClass(entry_id).?); } test "replaceTrailingTranscriptLineSilent swaps the trailing entry's bytes in place" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc); _ = try runtime.appendReplaceableTranscriptLineSilent(alloc, "Thinking...\n"); const initial_id = runtime.entries.items[0].id(); const replaced = try runtime.replaceTrailingTranscriptLineSilent(alloc, "Thinking... done\n"); try std.testing.expect(replaced); try std.testing.expectEqual(@as(usize, 1), runtime.entries.items.len); try std.testing.expectEqual(initial_id, runtime.entries.items[0].id()); try std.testing.expectEqualStrings("Thinking... done\n", runtime.entries.items[0].raw_bytes.bytes); } test "updateRawBytesEntry swaps bytes when entry is trailing" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc); const entry_id = try runtime.appendReplaceableTranscriptLineSilent(alloc, "modal v1\n"); try std.testing.expect(entry_id != 0); try std.testing.expectEqualStrings("modal v1\n", runtime.entries.items[0].raw_bytes.bytes); const ok = try runtime.updateRawBytesEntry(alloc, entry_id, "modal v2\n"); try std.testing.expect(ok); try std.testing.expectEqualStrings("modal v2\n", runtime.entries.items[0].raw_bytes.bytes); try std.testing.expectEqualStrings("modal v2\n", runtime.transcript.items); try std.testing.expect(runtime.replaceable_last_line); try std.testing.expectEqual(runtime.transcript.items.len - "modal v2\n".len, runtime.replaceable_start); } test "updateRawBytesEntry swaps bytes when entry is in middle" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc); const id_a = try runtime.appendReplaceableTranscriptLineSilent(alloc, "block A\n"); const dup_b = try alloc.dupe(u8, "block B\n"); _ = try runtime.appendRawBytesEntry(alloc, dup_b); try runtime.transcript.appendSlice(alloc, "block B\n"); const ok = try runtime.updateRawBytesEntry(alloc, id_a, "block A updated\n"); try std.testing.expect(ok); try std.testing.expectEqualStrings("block A updated\n", runtime.entries.items[0].raw_bytes.bytes); try std.testing.expect(runtime.entries.items[1] == .raw_bytes); try std.testing.expectEqualStrings("block B\n", runtime.entries.items[1].raw_bytes.bytes); try std.testing.expect(std.mem.indexOf(u8, runtime.transcript.items, "block A updated\n") != null); try std.testing.expect(std.mem.indexOf(u8, runtime.transcript.items, "block B\n") != null); try std.testing.expect(!runtime.replaceable_last_line); } test "updateRawBytesEntry returns false for unknown id" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc); _ = try runtime.appendReplaceableTranscriptLineSilent(alloc, "existing\n"); const ok = try runtime.updateRawBytesEntry(alloc, 9999, "nope\n"); try std.testing.expect(!ok); try std.testing.expectEqualStrings("existing\n", runtime.entries.items[0].raw_bytes.bytes); } test "updateRawBytesEntry returns false for non-raw_bytes entry" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc); const text = try alloc.dupe(u8, "hello user"); const user_id = try runtime.appendUserTurnOwned(alloc, .{ .text = text, .images = &.{} }); const ok = try runtime.updateRawBytesEntry(alloc, user_id, "hijack\n"); try std.testing.expect(!ok); } test "updateRawBytesEntry updates modal entry in place after intervening append" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{}; defer runtime.deinit(alloc); const modal_id = try runtime.appendReplaceableTranscriptLineSilent(alloc, "modal v1\n"); const stream_dup = try alloc.dupe(u8, "stream chunk\n"); _ = try runtime.appendRawBytesEntry(alloc, stream_dup); try runtime.transcript.appendSlice(alloc, "stream chunk\n"); const ok = try runtime.updateRawBytesEntry(alloc, modal_id, "modal v2\n"); try std.testing.expect(ok); try std.testing.expectEqual(@as(usize, 2), runtime.entries.items.len); try std.testing.expectEqualStrings("modal v2\n", runtime.entries.items[0].raw_bytes.bytes); try std.testing.expect(std.mem.indexOf(u8, runtime.transcript.items, "modal v2\n") != null); try std.testing.expect(std.mem.indexOf(u8, runtime.transcript.items, "stream chunk\n") != null); try std.testing.expect(std.mem.indexOf(u8, runtime.transcript.items, "modal v1") == null); } test "tool status raw entry updates after command output appends" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = .{ .rows = 24, .cols = 80, .content_bottom = 21, .divider_top_row = 22, .input_row = 23, .divider_bottom_row = 24, .hint_row = 22, } }; defer runtime.deinit(alloc); var metrics = Metrics{}; const status_id = try runtime.appendRawTranscriptEntry(alloc, "Running npm test\n"); try runtime.writeCommandOutputChunk(alloc, &metrics, .{ .system_notice_label_style = "", .system_notice_text_style = "", .reset_style = "", .dim_style = "", .red_style = "", }, .stdout, "ok\n", true); const updated = try runtime.updateRawBytesEntry(alloc, status_id, "Ran npm test\n"); try std.testing.expect(updated); var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); try std.testing.expect(std.mem.indexOf(u8, source.bytes, "Ran npm test") != null); try std.testing.expect(std.mem.indexOf(u8, source.bytes, "│ ok") == null); try std.testing.expect(std.mem.indexOf(u8, source.bytes, "Running npm test") == null); runtime.full_transcript.depth = .full; var projection = try runtime.buildFullTranscriptProjection(alloc, null); defer projection.deinit(alloc); const full = try full_transcript_screen.renderProjectionViewportSourceInterruptible( alloc, &projection, null, runtime.layout.cols, 64, 0, null, ); defer alloc.free(full); try std.testing.expect(std.mem.indexOf(u8, full, "│ ok") != null); try std.testing.expect(std.mem.indexOf(u8, full, "Running npm test") == null); } test "advanceCursor row advance matches visualRowsForLine - 1 for wrap-exact content" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = .{ .rows = 40, .cols = 3, .content_bottom = 37, .divider_top_row = 38, .input_row = 39, .divider_bottom_row = 40, .hint_row = 40, }, }; defer runtime.deinit(std.testing.allocator); const text = "abcdef"; try std.testing.expectEqual(@as(u16, 2), visualRowsForLine(text, 3)); runtime.cursor_row = 1; runtime.cursor_col = 1; _ = runtime.advanceCursor(text); try std.testing.expectEqual(@as(u16, 2), runtime.cursor_row); } test "advanceCursor row advance matches visualRowsForLine - 1 across hard newlines" { var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = .{ .rows = 40, .cols = 3, .content_bottom = 37, .divider_top_row = 38, .input_row = 39, .divider_bottom_row = 40, .hint_row = 40, }, }; defer runtime.deinit(std.testing.allocator); const text = "abcdef\nghijkl"; try std.testing.expectEqual(@as(u16, 4), visualRowsForLine(text, 3)); runtime.cursor_row = 1; runtime.cursor_col = 1; _ = runtime.advanceCursor(text); try std.testing.expectEqual(@as(u16, 4), runtime.cursor_row); } test "writeTranscriptBytes mid-row continuation defers scroll after first paint" { const alloc = std.testing.allocator; var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = .{ .rows = 40, .cols = 80, .content_bottom = 35, .divider_top_row = 36, .input_row = 37, .divider_bottom_row = 38, .hint_row = 39, }, }; defer runtime.deinit(alloc); runtime.cursor_row = 35; runtime.cursor_col = 40; runtime.viewport_top_row = 35; runtime.has_painted_transcript = true; var bytes: std.ArrayList(u8) = .empty; defer bytes.deinit(alloc); var i: usize = 0; while (i < 1000) : (i += 1) try bytes.append(alloc, 'X'); var metrics: Metrics = .{}; try runtime.writeTranscriptBytes(alloc, &metrics, bytes.items, true); try std.testing.expectEqual(@as(u16, 35), runtime.viewport_top_row); try std.testing.expectEqual(@as(u16, 35), runtime.cursor_row); } test "appendReplaceableTranscriptLine does not emit scroll (buffer-only)" { const alloc = std.testing.allocator; var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); var runtime = TranscriptRuntime{ .stdout_file = sink, .layout = .{ .rows = 40, .cols = 10, .content_bottom = 35, .divider_top_row = 36, .input_row = 37, .divider_bottom_row = 38, .hint_row = 39, }, }; defer runtime.deinit(alloc); runtime.cursor_row = 35; runtime.cursor_col = 1; runtime.viewport_top_row = 35; var bytes: std.ArrayList(u8) = .empty; defer bytes.deinit(alloc); try bytes.appendSlice(alloc, "XXXXXXXXXX"); var ln: u16 = 0; while (ln < 9) : (ln += 1) { try bytes.append(alloc, '\n'); try bytes.appendSlice(alloc, "XXXXXXXXXX"); } var metrics: Metrics = .{}; _ = try runtime.appendReplaceableTranscriptLine(alloc, &metrics, bytes.items); try std.testing.expectEqual(@as(u16, 35), runtime.viewport_top_row); try std.testing.expectEqual(@as(u16, 40), runtime.cursor_row); } test "streamed chunk-split produces same viewport as single emission" { const alloc = std.testing.allocator; var sink1 = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink1.close(io_mod.getIo()); var sink2 = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink2.close(io_mod.getIo()); const layout: Layout = .{ .rows = 40, .cols = 80, .content_bottom = 35, .divider_top_row = 36, .input_row = 37, .divider_bottom_row = 38, .hint_row = 39, }; var single = TranscriptRuntime{ .stdout_file = sink1, .layout = layout }; defer single.deinit(alloc); single.cursor_row = 35; single.cursor_col = 1; single.viewport_top_row = 35; var split = TranscriptRuntime{ .stdout_file = sink2, .layout = layout }; defer split.deinit(alloc); split.cursor_row = 35; split.cursor_col = 1; split.viewport_top_row = 35; var full: std.ArrayList(u8) = .empty; defer full.deinit(alloc); var i: usize = 0; while (i < 2000) : (i += 1) try full.append(alloc, 'X'); var half: std.ArrayList(u8) = .empty; defer half.deinit(alloc); i = 0; while (i < 1000) : (i += 1) try half.append(alloc, 'X'); var m1: Metrics = .{}; var m2: Metrics = .{}; try single.writeTranscriptBytes(alloc, &m1, full.items, true); try split.writeTranscriptBytes(alloc, &m2, half.items, true); try split.writeTranscriptBytes(alloc, &m2, half.items, true); try std.testing.expectEqual(single.viewport_top_row, split.viewport_top_row); try std.testing.expectEqual(single.cursor_row, split.cursor_row); try std.testing.expectEqual(single.cursor_col, split.cursor_col); } fn lifecycleTestRuntime(max_retained_bytes: ?usize) TranscriptRuntime { var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(80, 24, 20), }; if (max_retained_bytes) |value| runtime.max_retained_transcript_bytes = value; return runtime; } fn lifecycleId(turn_id: u64, call_id: []const u8) types.ToolLifecycleId { return .{ .turn_id = turn_id, .call_id = call_id }; } fn startLifecycle( runtime: *TranscriptRuntime, alloc: Allocator, turn_id: u64, call_id: []const u8, ) !u32 { _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = lifecycleId(turn_id, call_id), .reconciles_provisional_call_id = null, .tool_name = "read_file", .activity_kind = .read, } }); return runtime.toolActivityRecord(lifecycleId(turn_id, call_id)).?.entry_id; } test "current compact projection groups tool rows without mutating entries" { const alloc = std.testing.allocator; defer user_message_card.setStyle(false, null); user_message_card.setStyle(false, null); var runtime = lifecycleTestRuntime(null); defer runtime.deinit(alloc); const read_id = lifecycleId(1, "read"); _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = read_id, .reconciles_provisional_call_id = null, .tool_name = "read_file", .activity_kind = .read, } }); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = read_id, .outcome = .{ .kind = .completed, .summary = "Read file" }, } }); const edit_id = lifecycleId(1, "edit"); _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = edit_id, .reconciles_provisional_call_id = null, .tool_name = "edit_file", .activity_kind = .edit, } }); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = edit_id, .outcome = .{ .kind = .failed, .summary = "Edit failed" }, } }); const entry_count = runtime.entries.items.len; var compact = try runtime.prepareTranscriptSource(alloc, null); defer compact.deinit(alloc); try std.testing.expect(std.mem.find( u8, compact.bytes, "\x1b[38;5;255m●\x1b[0m \x1b[38;5;245m2 tool calls · 1 read · 1 edit · 1 failed\x1b[0m\n" ++ "\x1b[38;5;245m├ Read file\x1b[0m\n" ++ "\x1b[38;5;245m└ Edit failed\x1b[0m", ) != null); try std.testing.expectEqual(entry_count, runtime.entries.items.len); } test "current compact projection finalizes tool groups from turn finished fallbacks" { const alloc = std.testing.allocator; const cases = [_]struct { call_id: []const u8, provisional: bool, turn_outcome: types.TurnPresentationOutcome, detail_outcome: types.ToolOutcomeKind, fallback_disposition: ?transcript_blocks.ToolFallbackDisposition, summary_text: []const u8, }{ .{ .call_id = "unreported", .provisional = false, .turn_outcome = .completed, .detail_outcome = .completed, .fallback_disposition = .completion_unreported, .summary_text = "1 tool call · 1 read · 1 unreported" }, .{ .call_id = "not-executed", .provisional = true, .turn_outcome = .completed, .detail_outcome = .completed, .fallback_disposition = .not_executed, .summary_text = "1 tool call · 1 read · 1 not executed" }, .{ .call_id = "interrupted", .provisional = false, .turn_outcome = .interrupted, .detail_outcome = .cancelled, .fallback_disposition = null, .summary_text = "1 cancelled" }, .{ .call_id = "failed", .provisional = false, .turn_outcome = .failed, .detail_outcome = .failed, .fallback_disposition = null, .summary_text = "Tool failed" }, }; for (cases) |case| { var runtime = lifecycleTestRuntime(null); defer runtime.deinit(alloc); const id = lifecycleId(1, case.call_id); if (case.provisional) { _ = try runtime.applyToolLifecycle(alloc, .{ .provisional = .{ .id = id, .tool_name = "read_file", .activity_kind = .read, } }); } else { _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = id, .reconciles_provisional_call_id = null, .tool_name = "read_file", .activity_kind = .read, } }); } const entry_id = runtime.toolActivityRecord(id).?.entry_id; _ = try runtime.applyToolLifecycle(alloc, .{ .turn_finished = .{ .turn_id = 1, .outcome = case.turn_outcome, } }); var rendered = try runtime.prepareTranscriptSource(alloc, null); defer rendered.deinit(alloc); try std.testing.expect(std.mem.find(u8, rendered.bytes, case.summary_text) != null); try std.testing.expect(std.mem.find(u8, rendered.bytes, "running") == null); try std.testing.expectEqual( case.detail_outcome, runtime.toolDetailForEntry(entry_id).?.outcome.?, ); try std.testing.expectEqual( case.fallback_disposition, runtime.toolDetailForEntry(entry_id).?.fallback_disposition, ); } } fn checkCompactParallelFallbackAllocationFailuresImpl(alloc: Allocator) !void { var runtime = lifecycleTestRuntime(null); defer runtime.deinit(alloc); const ids = [_]types.ToolLifecycleId{ lifecycleId(1, "provisional-a"), lifecycleId(1, "provisional-b"), }; var entry_ids: [ids.len]u32 = undefined; for (ids, 0..) |id, index| { _ = try runtime.applyToolLifecycle(alloc, .{ .provisional = .{ .id = id, .tool_name = "read_file", .activity_kind = .read, } }); entry_ids[index] = runtime.toolActivityRecord(id).?.entry_id; } var source_before = try runtime.prepareTranscriptSource(std.testing.allocator, null); defer source_before.deinit(std.testing.allocator); _ = runtime.applyToolLifecycle(alloc, .{ .turn_finished = .{ .turn_id = 1, .outcome = .completed, } }) catch |err| { var source_after = try runtime.prepareTranscriptSource(std.testing.allocator, null); defer source_after.deinit(std.testing.allocator); try std.testing.expectEqualStrings(source_before.bytes, source_after.bytes); for (ids) |id| { try std.testing.expect(runtime.toolActivityRecord(id).?.phase == .provisional); } return err; }; var rendered = try runtime.prepareTranscriptSource(alloc, null); defer rendered.deinit(alloc); try std.testing.expect(std.mem.find( u8, rendered.bytes, "2 tool calls · 2 read · 2 not executed", ) != null); try std.testing.expect(std.mem.find(u8, rendered.bytes, "running") == null); for (entry_ids) |entry_id| { try std.testing.expectEqual( types.ToolOutcomeKind.completed, runtime.toolDetailForEntry(entry_id).?.outcome.?, ); try std.testing.expectEqual( transcript_blocks.ToolFallbackDisposition.not_executed, runtime.toolDetailForEntry(entry_id).?.fallback_disposition.?, ); } } fn checkCompactParallelFallbackAllocationFailures(alloc: Allocator) !void { return checkCompactParallelFallbackAllocationFailuresImpl(alloc) catch |err| switch (err) { error.WriteFailed => error.OutOfMemory, else => err, }; } test "compact parallel fallbacks are atomic across allocation failures" { try std.testing.checkAllAllocationFailures( std.testing.allocator, checkCompactParallelFallbackAllocationFailures, .{}, ); } fn prepareTurnFinishedAnchor(runtime: *TranscriptRuntime, alloc: Allocator) !void { try runtime.enableShadowVt(alloc); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "pre 0\npre 1\npre 2\npre 3\npre 4\npre 5\n", .subagent_status, ); _ = try startLifecycle(runtime, alloc, 1, "unfinished"); try installStableAnchorForTest( runtime, alloc, runtime.transcript.items, testSelection(0), 0, 4, 1, 1, ); } test "preserving lifecycle policy keeps anchor for turn finished fallback rewrite" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(40, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); try prepareTurnFinishedAnchor(&runtime, alloc); _ = try runtime.applyToolLifecyclePreservingNormalBufferAnchor(alloc, .{ .turn_finished = .{ .turn_id = 1, .outcome = .interrupted, }, }); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.stable, runtime.transcriptCommitDiagnostic().state, ); } test "preserving batch lifecycle rewrite keeps a capped canonical anchor" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(48, 12, 8), .owned_top_row = 1, .max_transcript_bytes = 64, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); const long_tail = try makeLongPastePrompt(alloc, 40); defer alloc.free(long_tail); _ = try runtime.appendRawTranscriptEntryClassified( alloc, long_tail, .subagent_status, ); const first_entry_id = try startLifecycle(&runtime, alloc, 1, "first"); const second_entry_id = try startLifecycle(&runtime, alloc, 1, "second"); var metrics: Metrics = .{}; var committed_source = try runtime.prepareTranscriptSource(alloc, null); defer committed_source.deinit(alloc); try std.testing.expect(committed_source.recorded_entries_authoritative); try std.testing.expect(committed_source.bytes.len > runtime.transcript.items.len); var committed_prepared = try runtime.prepareTranscriptSurfacePaintForArea( alloc, &metrics, .{ .top = runtime.owned_top_row, .bottom = runtime.layout.content_bottom }, ); defer committed_prepared.deinit(alloc); try installStableAnchorForTest( &runtime, alloc, committed_source.bytes, committed_prepared.selection, preparedVisualOffsetForTest(&committed_prepared), committed_prepared.cursor.cursor_row, committed_prepared.cursor.cursor_col, 1, ); const committed_diagnostic = runtime.transcriptCommitDiagnostic(); _ = try runtime.applyToolLifecyclePreservingNormalBufferAnchor(alloc, .{ .turn_finished = .{ .turn_id = 1, .outcome = .interrupted, }, }); try std.testing.expectEqualDeep( committed_diagnostic, runtime.transcriptCommitDiagnostic(), ); try std.testing.expect(std.mem.find( u8, runtime.toolStatusEntryLabel(first_entry_id).?, "Tool cancelled", ) != null); try std.testing.expect(std.mem.find( u8, runtime.toolStatusEntryLabel(second_entry_id).?, "Tool cancelled", ) != null); } test "turn finished anchor preservation keeps a same-epoch retention rewrite" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(40, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); try prepareTurnFinishedAnchor(&runtime, alloc); const committed_history_visual_offset = try stableHistoryVisualOffsetForTest(&runtime); runtime.max_retained_transcript_bytes = 0; _ = try runtime.applyToolLifecyclePreservingNormalBufferAnchor(alloc, .{ .turn_finished = .{ .turn_id = 1, .outcome = .interrupted }, }); try expectStableNormalBufferRecoveryForTest( &runtime, committed_history_visual_offset, ); } test "turn finished anchor preservation falls back for strict mode or geometry blocker" { const alloc = std.testing.allocator; { var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(40, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); try prepareTurnFinishedAnchor(&runtime, alloc); runtime.max_transcript_bytes = 1; _ = try runtime.applyToolLifecycle(alloc, .{ .turn_finished = .{ .turn_id = 1, .outcome = .interrupted }, }); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.invalid, runtime.transcriptCommitDiagnostic().state, ); } { var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(40, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); try prepareTurnFinishedAnchor(&runtime, alloc); runtime.layout.cols = 39; _ = try runtime.applyToolLifecyclePreservingNormalBufferAnchor(alloc, .{ .turn_finished = .{ .turn_id = 1, .outcome = .interrupted }, }); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.invalid, runtime.transcriptCommitDiagnostic().state, ); } } test "untrimmed pinned status replacement preserves anchor through scroll transition" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(40, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "pre 0\npre 1\npre 2\npre 3\npre 4\npre 5\n", .subagent_status, ); const entry_id = try transcript_store.appendPinnedToolStatusAtomic( &runtime, alloc, "pending\n", ); try installStableAnchorForTest( &runtime, alloc, runtime.transcript.items, testSelection(0), 0, 4, 1, 1, ); try std.testing.expect(try transcript_store.replacePinnedToolStatusAtomic( &runtime, alloc, entry_id, "running\n", )); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.stable, runtime.transcriptCommitDiagnostic().state, ); var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); var prepared = try prepareTestSourceForCurrentArea(&runtime, alloc, &source); defer prepared.deinit(alloc); try setPreparedVisualOffsetForTest(&prepared, 2); const facts = runtime.planTranscriptScroll(&prepared); try std.testing.expect(!facts.source_compatible); try std.testing.expect(facts.recovery_rebase); try std.testing.expectEqual(@as(u16, 2), facts.semantic_rows); try std.testing.expectEqual(@as(u16, 0), facts.semantic_progress_rows); try std.testing.expectEqual(@as(u16, 2), facts.planned_rows); const scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, facts.planned_rows, ); try std.testing.expectEqual(@as(u16, 2), scroll_plan.terminal_scroll_rows); var plan = testPaintPlan(&runtime, prepared.selection); var transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &source, &prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(plan), &plan, scroll_plan, ); defer transition.deinit(alloc); try std.testing.expect(transition.document_append.isEmpty()); runtime.consumeTranscriptTransition(alloc, &transition, .{ .bytes_written = 1, .changed_cells = 1, .full_repaint = true, .terminal_scroll_rows_committed = scroll_plan.terminal_scroll_rows, .document_append_committed = false, .committed_cursor_row = transition.cursor_row, .committed_cursor_col = transition.cursor_col, .shadow_state = .committed, .next_invalidation = render_engine.paint_plan.FrameInvalidationSet.empty(), }); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.stable, runtime.transcriptCommitDiagnostic().state, ); try std.testing.expect(runtime.transcript_cache_origin_untrimmed); var next_source = try runtime.prepareTranscriptSource(alloc, null); defer next_source.deinit(alloc); try std.testing.expect( runtime.stableTranscriptProjectionForFlow(next_source.bytes) != null, ); } test "command terminal replacement preserves committed scrollback anchor" { const alloc = std.testing.allocator; var runtime = lifecycleTestRuntime(null); defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "pre 0\npre 1\npre 2\npre 3\npre 4\npre 5\n", .subagent_status, ); const id = lifecycleId(1, "cancelled-command"); _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = id, .reconciles_provisional_call_id = null, .tool_name = "run_command", .activity_kind = .command, .arguments_json = "{\"command\":\"sleep 30\"}", } }); try installStableAnchorForTest( &runtime, alloc, runtime.transcript.items, testSelection(0), 0, 4, 1, 1, ); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = id, .outcome = .{ .kind = .cancelled, .summary = "Command cancelled" }, .result_memory = .{ .command_process_presentation = .{ .exit_code = 130 } }, } }); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.stable, runtime.transcriptCommitDiagnostic().state, ); const entry_id = runtime.toolActivityRecord(id).?.entry_id; try std.testing.expect(std.mem.find( u8, runtime.toolStatusEntryLabel(entry_id).?, "Command cancelled", ) != null); try std.testing.expect(runtime.toolDetailForEntry(entry_id).?.command_output_entry_id != null); } test "pinned status admission preserves an authoritative committed anchor" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(40, 8, 4), .owned_top_row = 1, .max_transcript_bytes = 64, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); const long_tail = try makeLongPastePrompt(alloc, 40); defer alloc.free(long_tail); _ = try runtime.appendRawTranscriptEntryClassified( alloc, long_tail, .subagent_status, ); const first_status_id = try transcript_store.appendPinnedToolStatusAtomic( &runtime, alloc, "pending\n", ); var committed_source = try runtime.prepareTranscriptSource(alloc, null); defer committed_source.deinit(alloc); try std.testing.expect(committed_source.recorded_entries_authoritative); try std.testing.expect(committed_source.bytes.len > runtime.transcript.items.len); try installStableAnchorForTest( &runtime, alloc, committed_source.bytes, testSelection(0), 0, 4, 1, 1, ); const committed_before = runtime.transcriptCommitDiagnostic(); try std.testing.expect(try transcript_store.replacePinnedToolStatusAtomic( &runtime, alloc, first_status_id, "running\n", )); try std.testing.expectEqualDeep( committed_before, runtime.transcriptCommitDiagnostic(), ); _ = try transcript_store.appendPinnedToolStatusAtomic( &runtime, alloc, "pending\n", ); try std.testing.expectEqualDeep( committed_before, runtime.transcriptCommitDiagnostic(), ); var appended_source = try runtime.prepareTranscriptSource(alloc, null); defer appended_source.deinit(alloc); try std.testing.expectEqual( @as(usize, 1), std.mem.count(u8, appended_source.bytes, "LONG_PASTE_LAST_MARKER"), ); try std.testing.expect(std.mem.find(u8, runtime.toolStatusEntryLabel(first_status_id).?, "running") != null); try std.testing.expectEqual(@as(usize, 2), runtime.lifecyclePinCount()); } test "pinned status admission preserves an anchor when retention prunes its source" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(40, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "anchored transcript\n", .subagent_status, ); var committed_source = try runtime.prepareTranscriptSource(alloc, null); defer committed_source.deinit(alloc); try installStableAnchorForTest( &runtime, alloc, committed_source.bytes, testSelection(0), 0, 4, 1, 1, ); const committed_history_visual_offset = try stableHistoryVisualOffsetForTest(&runtime); runtime.max_retained_transcript_bytes = "pending\n".len; _ = try transcript_store.appendPinnedToolStatusAtomic( &runtime, alloc, "pending\n", ); try expectStableNormalBufferRecoveryForTest( &runtime, committed_history_visual_offset, ); var retained_source = try runtime.prepareTranscriptSource(alloc, null); defer retained_source.deinit(alloc); try std.testing.expectEqual( @as(usize, 0), std.mem.count(u8, retained_source.bytes, "anchored transcript"), ); try std.testing.expectEqual(@as(usize, 1), runtime.lifecyclePinCount()); } test "pinned status replacement preserves authoritative cache changes and rebases retention or geometry" { const alloc = std.testing.allocator; { var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(40, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "ordinary retained entry\n", .subagent_status, ); const entry_id = try transcript_store.appendPinnedToolStatusAtomic( &runtime, alloc, "pending\n", ); runtime.max_retained_transcript_bytes = transcript_store.retainedStructuredBytes(&runtime); try installStableAnchorForTest( &runtime, alloc, runtime.transcript.items, testSelection(0), 0, 4, 1, 1, ); try std.testing.expect(try transcript_store.replacePinnedToolStatusAtomic( &runtime, alloc, entry_id, "replacement that requires structured retention\n", )); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.invalid, runtime.transcriptCommitDiagnostic().state, ); } { var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(40, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); const entry_id = try transcript_store.appendPinnedToolStatusAtomic( &runtime, alloc, "old\n", ); runtime.max_transcript_bytes = runtime.transcript.items.len; try installStableAnchorForTest( &runtime, alloc, runtime.transcript.items, testSelection(0), 0, 4, 1, 1, ); const committed_before = runtime.transcriptCommitDiagnostic(); try std.testing.expect(try transcript_store.replacePinnedToolStatusAtomic( &runtime, alloc, entry_id, "replacement that crosses the visible cache cap\n", )); try std.testing.expectEqualDeep( committed_before, runtime.transcriptCommitDiagnostic(), ); } { var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(40, 8, 4), .owned_top_row = 1, .max_transcript_bytes = 5, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); const entry_id = try transcript_store.appendPinnedToolStatusAtomic( &runtime, alloc, "old\npinned\nstatus", ); try std.testing.expect(runtime.transcript.items.len <= runtime.max_transcript_bytes); var committed_source = try runtime.prepareTranscriptSource(alloc, null); defer committed_source.deinit(alloc); try std.testing.expect(committed_source.recorded_entries_authoritative); try std.testing.expect(committed_source.bytes.len > runtime.transcript.items.len); try installStableAnchorForTest( &runtime, alloc, committed_source.bytes, testSelection(0), 0, 4, 1, 1, ); const committed_before = runtime.transcriptCommitDiagnostic(); try std.testing.expect(try transcript_store.replacePinnedToolStatusAtomic( &runtime, alloc, entry_id, "ok", )); try std.testing.expectEqualDeep( committed_before, runtime.transcriptCommitDiagnostic(), ); } { var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(40, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); const entry_id = try transcript_store.appendPinnedToolStatusAtomic( &runtime, alloc, "pending\n", ); try installStableAnchorForTest( &runtime, alloc, runtime.transcript.items, testSelection(0), 0, 4, 1, 1, ); runtime.layout.cols = 20; try std.testing.expect(try transcript_store.replacePinnedToolStatusAtomic( &runtime, alloc, entry_id, "running\n", )); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.invalid, runtime.transcriptCommitDiagnostic().state, ); } } test "visual epoch retains full history across repeated clears and compact rebuilds" { const alloc = std.testing.allocator; var runtime = lifecycleTestRuntime(null); defer runtime.deinit(alloc); _ = try runtime.appendRawTranscriptEntryClassified(alloc, "old welcome\n", .welcome); const old_id = try runtime.appendRawTranscriptEntryClassified(alloc, "retained answer\n", .subagent_status); _ = try runtime.appendSemanticNotice(alloc, .{ .topic = "history", .tone = .information, .body = "retained notice", }); const retained_bytes = runtime.entries.items[1].raw_bytes.bytes.ptr; for (0..3) |_| { try runtime.resetVisualEpoch(alloc, "current welcome\n"); try std.testing.expectEqual(@as(usize, 3), runtime.entries.items.len); try std.testing.expectEqual(old_id, runtime.entries.items[1].id()); try std.testing.expectEqual(retained_bytes, runtime.entries.items[1].raw_bytes.bytes.ptr); try std.testing.expect(runtime.entries.items[1].raw_bytes.inline_hidden); try std.testing.expect(runtime.entries.items[2].semantic_notice.inline_hidden); try std.testing.expectEqualStrings("current welcome\n", runtime.transcript.items); var compact = try runtime.prepareTranscriptSource(alloc, null); defer compact.deinit(alloc); try std.testing.expect(std.mem.find(u8, compact.bytes, "retained") == null); var projection = try runtime.buildFullTranscriptProjection(alloc, null); defer projection.deinit(alloc); const full = try full_transcript_screen.renderProjectionViewportSourceInterruptible( alloc, &projection, null, runtime.layout.cols, 64, 0, null, ); defer alloc.free(full); try std.testing.expectEqual(@as(usize, 1), std.mem.count(u8, full, "retained answer")); try std.testing.expectEqual(@as(usize, 1), std.mem.count(u8, full, "retained notice")); try std.testing.expectEqual(@as(usize, 1), std.mem.count(u8, full, "current welcome")); try std.testing.expect(std.mem.find(u8, full, "old welcome") == null); } _ = try runtime.appendRawTranscriptEntryClassified(alloc, "new answer\n", .subagent_status); var compact = try runtime.prepareTranscriptSource(alloc, null); defer compact.deinit(alloc); try std.testing.expect(std.mem.find(u8, compact.bytes, "new answer") != null); try std.testing.expect(std.mem.find(u8, compact.bytes, "retained") == null); runtime.clearTranscript(alloc); try std.testing.expectEqual(@as(usize, 0), runtime.entries.items.len); try std.testing.expectEqual(@as(usize, 0), runtime.transcript.items.len); } test "visual epoch preserves monotonic entry IDs across repeated clears" { const alloc = std.testing.allocator; for ([_]bool{ true, false }) |has_original_welcome| { var runtime = lifecycleTestRuntime(null); defer runtime.deinit(alloc); if (has_original_welcome) { _ = try runtime.appendRawTranscriptEntryClassified(alloc, "original welcome\n", .welcome); runtime.entries.items[0].raw_bytes.created_at_ms = 1234; } var ordinary_ids: [300]u32 = undefined; for (&ordinary_ids) |*id| { id.* = try runtime.appendRawTranscriptEntryClassified(alloc, "retained answer\n", .subagent_status); } const welcome_index: usize = if (has_original_welcome) 0 else ordinary_ids.len; const welcome_id = if (has_original_welcome) runtime.entries.items[0].id() else runtime.next_entry_id; var welcome_created_at: ?i64 = if (has_original_welcome) 1234 else null; const expected_next_id = runtime.next_entry_id + @as(u32, if (has_original_welcome) 0 else 1); for (0..3) |_| { try runtime.resetVisualEpoch(alloc, "current welcome\n"); try std.testing.expectEqual(@as(usize, ordinary_ids.len + 1), runtime.entries.items.len); try std.testing.expectEqual(expected_next_id, runtime.next_entry_id); const welcome_entry = runtime.entries.items[welcome_index].raw_bytes; try std.testing.expectEqual(welcome_id, welcome_entry.id); try std.testing.expectEqualStrings("current welcome\n", welcome_entry.bytes); if (welcome_created_at) |created_at| { try std.testing.expectEqual(created_at, welcome_entry.created_at_ms); } else { welcome_created_at = welcome_entry.created_at_ms; } var welcome_count: usize = 0; var ordinary_index: usize = 0; for (runtime.entries.items, 0..) |entry, index| { if (index > 0) try std.testing.expect(runtime.entries.items[index - 1].id() < entry.id()); if (entry == .raw_bytes and entry.raw_bytes.class == .welcome) { welcome_count += 1; } else { try std.testing.expectEqual(ordinary_ids[ordinary_index], entry.id()); try std.testing.expect(entry.raw_bytes.inline_hidden); ordinary_index += 1; } } try std.testing.expectEqual(@as(usize, 1), welcome_count); try std.testing.expectEqual(ordinary_ids.len, ordinary_index); try std.testing.expectEqualStrings("current welcome\n", runtime.transcript.items); } } } fn checkVisualEpochAllocationFailuresImpl(alloc: Allocator) !void { var runtime = lifecycleTestRuntime(null); defer runtime.deinit(alloc); _ = try runtime.appendRawTranscriptEntryClassified(alloc, "original welcome\n", .welcome); const old_id = try runtime.appendRawTranscriptEntryClassified(alloc, "borrowed answer\n", .subagent_status); const pinned_id = try transcript_store.appendPinnedToolStatusAtomic(&runtime, alloc, "running\n"); const old_entries = runtime.entries.items.ptr; const old_payload = runtime.entries.items[1].raw_bytes.bytes.ptr; const old_next_id = runtime.next_entry_id; const before = try std.testing.allocator.dupe(u8, runtime.transcript.items); defer std.testing.allocator.free(before); runtime.resetVisualEpoch(alloc, "replacement welcome\n") catch |err| { try std.testing.expectEqual(old_entries, runtime.entries.items.ptr); try std.testing.expectEqual(@as(usize, 3), runtime.entries.items.len); try std.testing.expectEqual(old_next_id, runtime.next_entry_id); try std.testing.expectEqualStrings(before, runtime.transcript.items); try std.testing.expectEqual(old_payload, runtime.entries.items[1].raw_bytes.bytes.ptr); try std.testing.expect(!runtime.entries.items[1].raw_bytes.inline_hidden); try std.testing.expectEqualStrings("borrowed answer\n", runtime.entries.items[1].raw_bytes.bytes); try std.testing.expect(runtime.isLifecyclePinned(pinned_id)); return err; }; try std.testing.expectEqual(old_id, runtime.entries.items[1].id()); try std.testing.expectEqual(old_payload, runtime.entries.items[1].raw_bytes.bytes.ptr); try std.testing.expect(runtime.entries.items[1].raw_bytes.inline_hidden); try std.testing.expect(runtime.isLifecyclePinned(pinned_id)); } fn checkVisualEpochAllocationFailures(alloc: Allocator) !void { return checkVisualEpochAllocationFailuresImpl(alloc) catch |err| switch (err) { error.WriteFailed => error.OutOfMemory, else => err, }; } test "visual epoch starts a visible assistant tail without resurrecting cleared text" { const alloc = std.testing.allocator; var runtime = lifecycleTestRuntime(null); defer runtime.deinit(alloc); var metrics = Metrics{}; _ = try runtime.appendRawTranscriptEntryClassified(alloc, "welcome\n", .welcome); const before_id = try runtime.streamAssistantChunk(alloc, &metrics, "before clear"); try runtime.resetVisualEpoch(alloc, "welcome\n"); const after_id = try runtime.streamAssistantChunk(alloc, &metrics, "after clear"); try std.testing.expect(before_id != after_id); try std.testing.expectEqualStrings("before clear", runtime.lookupAssistantSegments(before_id).?.text.items); var compact = try runtime.prepareTranscriptSource(alloc, null); defer compact.deinit(alloc); try std.testing.expect(std.mem.find(u8, compact.bytes, "before clear") == null); try std.testing.expect(std.mem.find(u8, compact.bytes, "after clear") != null); } test "visual epoch reset is atomic across allocation failures" { try std.testing.checkAllAllocationFailures( std.testing.allocator, checkVisualEpochAllocationFailures, .{}, ); } test "visual epoch hides ordinary entries and preserves live tool identities through settlement" { const alloc = std.testing.allocator; var runtime = lifecycleTestRuntime(null); defer runtime.deinit(alloc); _ = try runtime.appendRawTranscriptEntryClassified(alloc, "original welcome\n", .welcome); _ = try runtime.appendRawTranscriptEntryClassified(alloc, "old transcript\n", .subagent_status); const entry_id = try startLifecycle(&runtime, alloc, 7, "call-7"); try runtime.resetVisualEpoch(alloc, "welcome\n"); try std.testing.expect(std.mem.startsWith(u8, runtime.transcript.items, "welcome\n")); try std.testing.expect(std.mem.find(u8, runtime.transcript.items, "old transcript") == null); try std.testing.expectEqual(entry_id, runtime.toolActivityRecord(lifecycleId(7, "call-7")).?.entry_id); try std.testing.expect(runtime.isLifecyclePinned(entry_id)); _ = try runtime.applyToolLifecycle(alloc, .{ .progress = .{ .id = lifecycleId(7, "call-7"), .text = "still running", } }); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = lifecycleId(7, "call-7"), .outcome = .{ .kind = .completed, .summary = "finished" }, } }); try std.testing.expect(std.mem.find(u8, runtime.transcript.items, "finished") != null); try std.testing.expectEqual(@as(usize, 3), runtime.entries.items.len); try std.testing.expectEqual(entry_id, runtime.entries.items[2].id()); try std.testing.expect(!runtime.entries.items[2].raw_bytes.inline_hidden); _ = try runtime.applyToolLifecycle(alloc, .{ .turn_finished = .{ .turn_id = 7, .outcome = .completed, } }); try runtime.finishLifecycleBatch(alloc); try std.testing.expect(!runtime.isLifecyclePinned(entry_id)); try std.testing.expect(std.mem.find(u8, runtime.transcript.items, "finished") != null); try runtime.resetVisualEpoch(alloc, "welcome again\n"); try std.testing.expectEqual(@as(usize, 3), runtime.entries.items.len); try std.testing.expectEqual(entry_id, runtime.entries.items[2].id()); try std.testing.expect(runtime.entries.items[2].raw_bytes.inline_hidden); try std.testing.expectEqualStrings("welcome again\n", runtime.transcript.items); var projection = try runtime.buildFullTranscriptProjection(alloc, null); defer projection.deinit(alloc); const full = try full_transcript_screen.renderProjectionViewportSourceInterruptible( alloc, &projection, null, runtime.layout.cols, 64, 0, null, ); defer alloc.free(full); try std.testing.expect(std.mem.find(u8, full, "old transcript") != null); try std.testing.expect(std.mem.find(u8, full, "finished") != null); } test "visual epoch retains command output blocks and detail associations" { const alloc = std.testing.allocator; var sink = try std.Io.Dir.openFileAbsolute(io_mod.getIo(), "/dev/null", .{ .mode = .write_only }); defer sink.close(io_mod.getIo()); var runtime = commandOutputTestRuntime(sink); defer runtime.deinit(alloc); var metrics = Metrics{}; const styles = commandOutputTestStyles(); const id = lifecycleId(1, "retained-command"); _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = id, .reconciles_provisional_call_id = null, .tool_name = "shell", .activity_kind = .command, .arguments_json = "{\"request\":{\"action\":\"run\",\"command\":\"true\"}}", } }); const entry_id = runtime.toolActivityRecord(id).?.entry_id; try runtime.writeCommandOutputChunkForLifecycle(alloc, &metrics, styles, id, .stdout, "retained command result\n", true); try runtime.flushCommandOutputSummaryForLifecycle(alloc, &metrics, styles, id, true); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = id, .outcome = .{ .kind = .completed, .summary = "Ran true" }, .result = "retained command result\n", .result_memory = .{ .command_process_presentation = .{ .exit_code = 0 } }, } }); _ = try runtime.applyToolLifecycle(alloc, .{ .turn_finished = .{ .turn_id = 1, .outcome = .completed, } }); try runtime.finishLifecycleBatch(alloc); const output_id = runtime.toolDetailForEntry(entry_id).?.command_output_entry_id.?; const block_count = runtime.command_output_blocks.items.len; try std.testing.expect(block_count > 0); const blocks_ptr = runtime.command_output_blocks.items.ptr; for (0..2) |_| { try runtime.resetVisualEpoch(alloc, "welcome\n"); try std.testing.expectEqual(block_count, runtime.command_output_blocks.items.len); try std.testing.expectEqual(blocks_ptr, runtime.command_output_blocks.items.ptr); try std.testing.expectEqual(output_id, runtime.toolDetailForEntry(entry_id).?.command_output_entry_id.?); try std.testing.expect(transcriptContainsEntry(&runtime, output_id)); try std.testing.expectEqualStrings("welcome\n", runtime.transcript.items); var projection = try runtime.buildFullTranscriptProjection(alloc, null); defer projection.deinit(alloc); const full = try full_transcript_screen.renderProjectionViewportSourceInterruptible( alloc, &projection, null, runtime.layout.cols, 64, 0, null, ); defer alloc.free(full); // The row renders styled; assert on the command text inside it. try std.testing.expect(std.mem.find(u8, full, "true") != null); try std.testing.expect(std.mem.find(u8, full, "retained command result") != null); } runtime.clearTranscript(alloc); try std.testing.expectEqual(@as(usize, 0), runtime.command_output_blocks.items.len); try std.testing.expectEqual(@as(usize, 0), runtime.tool_details.items.len); } test "transcript lifecycle identity updates are idempotent and atomic" { const alloc = std.testing.allocator; var runtime = lifecycleTestRuntime(null); defer runtime.deinit(alloc); for ([_]?[]const u8{ null, "read_file", "read_file" }) |tool_name| { _ = try runtime.applyToolLifecycle(alloc, .{ .provisional = .{ .id = lifecycleId(1, "call"), .presentation_group_id = .{ .turn_id = 1, .anchor_step_id = 11 }, .tool_name = tool_name, .activity_kind = .read, } }); } const provisional_id = lifecycleId(1, "call"); const entry_id = runtime.toolActivityRecord(provisional_id).?.entry_id; try std.testing.expectEqualStrings( "read_file", runtime.toolActivityRecord(provisional_id).?.tool_name.?, ); // Force record-map growth so stale pointers fail deterministically. inline for ([_][]const u8{ "alias", "target", "fill-1", "fill-2", "fill-3" }) |call_id| { _ = try runtime.applyToolLifecycle(alloc, .{ .provisional = .{ .id = lifecycleId(1, call_id), .tool_name = "read_file", .activity_kind = .read, } }); } const authoritative_id = lifecycleId(1, "authoritative-" ++ ("x" ** 800)); const record_capacity_before = runtime.lifecycle_state.records.capacity(); _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = authoritative_id, .presentation_group_id = .{ .turn_id = 1, .anchor_step_id = 12 }, .reconciles_provisional_call_id = "call", .tool_name = "read_file", .activity_kind = .read, } }); try std.testing.expect(runtime.lifecycle_state.records.capacity() > record_capacity_before); try std.testing.expect(runtime.toolActivityRecord(provisional_id) == null); try std.testing.expectEqual( entry_id, runtime.toolActivityRecord(authoritative_id).?.entry_id, ); try std.testing.expectEqual( types.ToolPresentationGroupId{ .turn_id = 1, .anchor_step_id = 11 }, runtime.toolDetailForEntry(entry_id).?.presentation_group_id.?, ); const alias_before = runtime.toolActivityRecord(lifecycleId(1, "alias")).?.entry_id; const target_before = runtime.toolActivityRecord(lifecycleId(1, "target")).?.entry_id; try std.testing.expectError( error.LifecycleReconciliationCollision, runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = lifecycleId(1, "target"), .reconciles_provisional_call_id = "alias", .tool_name = "glob_files", .activity_kind = .list, } }), ); try std.testing.expectEqual(alias_before, runtime.toolActivityRecord(lifecycleId(1, "alias")).?.entry_id); try std.testing.expectEqual(target_before, runtime.toolActivityRecord(lifecycleId(1, "target")).?.entry_id); const records_before = runtime.toolActivityRecordCount(); try std.testing.expectError( error.UnknownToolLifecycleIdentity, runtime.applyToolLifecycle(alloc, .{ .progress = .{ .id = lifecycleId(1, "missing"), .text = "progress", } }), ); try std.testing.expectError( error.UnknownToolLifecycleIdentity, runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = lifecycleId(1, "missing"), .outcome = .{ .kind = .failed, .summary = "failed" }, } }), ); try std.testing.expectEqual(records_before, runtime.toolActivityRecordCount()); runtime.clearTranscript(alloc); try std.testing.expectEqual(@as(usize, 0), runtime.toolActivityRecordCount()); try std.testing.expectEqual(@as(usize, 0), runtime.lifecyclePinCount()); try std.testing.expectEqual(@as(usize, 0), runtime.entries.items.len); } test "authoritative lifecycle can place a provisional row after the latest transcript entry" { const alloc = std.testing.allocator; var runtime = lifecycleTestRuntime(null); defer runtime.deinit(alloc); const id = lifecycleId(1, "command"); _ = try runtime.applyToolLifecycle(alloc, .{ .provisional = .{ .id = id, .tool_name = "run_command", .activity_kind = .command, } }); _ = try runtime.applyToolLifecycle(alloc, .{ .progress = .{ .id = id, .text = "Preparing command", } }); _ = try runtime.appendSemanticNotice(alloc, .{ .topic = "permissions", .tone = .success, .body = "Auto agent approved this request: Running command.", .visibility = .full_only, }); _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = id, .reconciles_provisional_call_id = null, .tool_name = "run_command", .activity_kind = .command, .arguments_json = "{\"command\":\"printf approved\"}", .place_after_current_transcript = true, } }); _ = try runtime.applyToolLifecycle(alloc, .{ .progress = .{ .id = id, .text = "Running printf approved", } }); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = id, .outcome = .{ .kind = .completed, .summary = "Ran printf approved" }, } }); try std.testing.expect(std.mem.find(u8, runtime.transcript.items, "Auto agent approved") == null); try std.testing.expect(std.mem.find(u8, runtime.transcript.items, "Ran printf approved") != null); var projection = try runtime.buildFullTranscriptProjection(alloc, null); defer projection.deinit(alloc); const full = try full_transcript_screen.renderProjectionViewportSourceInterruptible( alloc, &projection, null, runtime.layout.cols, 64, 0, null, ); defer alloc.free(full); // Tool rows style the command verb; compare against plain text. const plain = try plainTextForTest(alloc, full); defer alloc.free(plain); const notice_index = std.mem.find(u8, plain, "Auto agent approved").?; const completed_index = std.mem.find(u8, plain, "Ran printf approved").?; try std.testing.expect(notice_index < completed_index); } fn plainTextForTest(alloc: std.mem.Allocator, styled: []const u8) ![]u8 { var out: std.ArrayList(u8) = .empty; errdefer out.deinit(alloc); var i: usize = 0; while (i < styled.len) { const next = display_width.ansiSequenceEnd(styled, i); if (next != i) { i = next; continue; } try out.append(alloc, styled[i]); i += 1; } return out.toOwnedSlice(alloc); } test "coalesced approval lifecycle reposition preserves an authoritative committed anchor" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(48, 12, 8), .owned_top_row = 1, .max_transcript_bytes = 64, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); const long_tail = try makeLongPastePrompt(alloc, 40); defer alloc.free(long_tail); _ = try runtime.appendRawTranscriptEntryClassified( alloc, long_tail, .subagent_status, ); var metrics = Metrics{}; var committed_source = try runtime.prepareTranscriptSource(alloc, null); defer committed_source.deinit(alloc); try std.testing.expect(committed_source.recorded_entries_authoritative); try std.testing.expect(committed_source.bytes.len > runtime.transcript.items.len); var committed_prepared = try runtime.prepareTranscriptSurfacePaintForArea( alloc, &metrics, .{ .top = runtime.owned_top_row, .bottom = runtime.layout.content_bottom }, ); defer committed_prepared.deinit(alloc); const committed_offset = preparedVisualOffsetForTest(&committed_prepared); try installStableAnchorForTest( &runtime, alloc, committed_source.bytes, committed_prepared.selection, committed_offset, committed_prepared.cursor.cursor_row, committed_prepared.cursor.cursor_col, 1, ); const committed_diagnostic = runtime.transcriptCommitDiagnostic(); const id = lifecycleId(91, "coalesced-command"); _ = try runtime.applyToolLifecycle(alloc, .{ .provisional = .{ .id = id, .tool_name = "run_command", .activity_kind = .command, } }); _ = try runtime.appendSemanticNotice(alloc, .{ .topic = "permissions", .tone = .success, .body = "Auto agent approved this request: Running command.", .visibility = .full_only, }); _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = id, .reconciles_provisional_call_id = null, .tool_name = "run_command", .activity_kind = .command, .arguments_json = "{\"command\":\"seq 1 1\"}", .place_after_current_transcript = true, } }); try std.testing.expectEqualDeep( committed_diagnostic, runtime.transcriptCommitDiagnostic(), ); var repositioned_source = try runtime.prepareTranscriptSource(alloc, null); defer repositioned_source.deinit(alloc); try std.testing.expect(std.mem.find(u8, repositioned_source.bytes, "Auto agent approved") == null); try std.testing.expect(std.mem.find( u8, repositioned_source.bytes, "run_command", ) != null); var projection = try runtime.buildFullTranscriptProjection(alloc, null); defer projection.deinit(alloc); const full = try full_transcript_screen.renderProjectionViewportSourceInterruptible( alloc, &projection, null, runtime.layout.cols, 64, 0, null, ); defer alloc.free(full); const notice_index = std.mem.find(u8, full, "Auto agent approved") orelse return error.TestExpectedEqual; const full_status_index = std.mem.find(u8, full, "run_command") orelse return error.TestExpectedEqual; try std.testing.expect(notice_index < full_status_index); var repositioned_prepared = try runtime.prepareTranscriptSurfacePaintForArea( alloc, &metrics, .{ .top = runtime.owned_top_row, .bottom = runtime.layout.content_bottom }, ); defer repositioned_prepared.deinit(alloc); const facts = runtime.planTranscriptScroll(&repositioned_prepared); try std.testing.expect(facts.source_compatible); try std.testing.expect(facts.semantic_rows > 0); try std.testing.expectEqual(committed_offset, facts.source_visual_offset); } test "coalesced approval lifecycle reposition preserves its anchor when retention prunes its source" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(48, 12, 8), .owned_top_row = 1, .max_transcript_bytes = 64, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); const anchored = "anchored transcript\n"; _ = try runtime.appendRawTranscriptEntryClassified( alloc, anchored, .subagent_status, ); var committed_source = try runtime.prepareTranscriptSource(alloc, null); defer committed_source.deinit(alloc); try installStableAnchorForTest( &runtime, alloc, committed_source.bytes, testSelection(0), 0, 4, 1, 1, ); const committed_history_visual_offset = try stableHistoryVisualOffsetForTest(&runtime); const id = lifecycleId(92, "retained-command"); _ = try runtime.applyToolLifecycle(alloc, .{ .provisional = .{ .id = id, .tool_name = "run_command", .activity_kind = .command, } }); _ = try runtime.appendSemanticNotice(alloc, .{ .topic = "permissions", .tone = .success, .body = "Auto agent approved this request: Running command.", .visibility = .full_only, }); runtime.max_retained_transcript_bytes = transcript_store.retainedStructuredBytes(&runtime) - anchored.len; _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = id, .reconciles_provisional_call_id = null, .tool_name = "run_command", .activity_kind = .command, .arguments_json = "{\"command\":\"seq 1 1\"}", .place_after_current_transcript = true, } }); try expectStableNormalBufferRecoveryForTest( &runtime, committed_history_visual_offset, ); var retained_source = try runtime.prepareTranscriptSource(alloc, null); defer retained_source.deinit(alloc); try std.testing.expectEqual( @as(usize, 0), std.mem.count(u8, retained_source.bytes, "anchored transcript"), ); try std.testing.expect(std.mem.find(u8, retained_source.bytes, "Auto agent approved") == null); try std.testing.expect(std.mem.find( u8, retained_source.bytes, "run_command", ) != null); var projection = try runtime.buildFullTranscriptProjection(alloc, null); defer projection.deinit(alloc); const full = try full_transcript_screen.renderProjectionViewportSourceInterruptible( alloc, &projection, null, runtime.layout.cols, 64, 0, null, ); defer alloc.free(full); const notice_index = std.mem.find(u8, full, "Auto agent approved") orelse return error.TestExpectedEqual; const status_index = std.mem.find(u8, full, "run_command") orelse return error.TestExpectedEqual; try std.testing.expect(notice_index < status_index); } test "transcript lifecycle preserves caller resolved activity metadata" { const alloc = std.testing.allocator; var runtime = lifecycleTestRuntime(null); defer runtime.deinit(alloc); const id = lifecycleId(1, "custom-browser"); const started_kind = (try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = id, .reconciles_provisional_call_id = null, .tool_name = "custom_probe_tool", .activity_kind = .open, } })) orelse return error.TestExpectedEqual; try std.testing.expectEqual(types.ToolActivityKind.open, started_kind); try std.testing.expectEqual(types.ToolActivityKind.open, runtime.toolActivityRecord(id).?.activity_kind); try std.testing.expectEqual(types.ToolActivityKind.open, runtime.focusedToolActivityKind().?); switch (runtime.activityProjection()) { .tool_slot => |slot| try std.testing.expectEqual(types.ToolActivityKind.open, slot.kind), else => return error.TestExpectedEqual, } } test "transcript lifecycle terminal and finalization transitions stay batch safe" { const alloc = std.testing.allocator; var runtime = lifecycleTestRuntime(null); defer runtime.deinit(alloc); const cases = [_]struct { call_id: []const u8, kind: types.ToolOutcomeKind, summary: []const u8, marker_style: []const u8, marker: []const u8, }{ .{ .call_id = "completed", .kind = .completed, .summary = "completed", .marker_style = ui_render.system_notice_text_style, .marker = "●" }, .{ .call_id = "denied", .kind = .denied, .summary = "denied", .marker_style = ui_render.red_style, .marker = "⊘" }, .{ .call_id = "cancelled", .kind = .cancelled, .summary = "cancelled", .marker_style = ui_render.warning_style, .marker = "■" }, .{ .call_id = "failed", .kind = .failed, .summary = "failed", .marker_style = ui_render.red_style, .marker = "●" }, }; for (cases) |case| { const id = lifecycleId(1, case.call_id); const entry_id = try startLifecycle(&runtime, alloc, 1, case.call_id); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = id, .outcome = .{ .kind = case.kind, .summary = case.summary }, } }); var expected: [128]u8 = undefined; const expected_line = if (case.kind == .cancelled) try std.fmt.bufPrint( &expected, "{s}{s}{s} {s}{s}{s} · What can fx do differently?\n", .{ case.marker_style, case.marker, ui_render.reset_style, ui_render.hint_style, case.summary, ui_render.reset_style, }, ) else try std.fmt.bufPrint( &expected, "{s}{s}{s} {s}\n", .{ case.marker_style, case.marker, ui_render.reset_style, case.summary }, ); try expectRawEntryBytes(&runtime, entry_id, expected_line); } try std.testing.expect(runtime.activityProjection() == .none); _ = try runtime.applyToolLifecycle(alloc, .{ .provisional = .{ .id = lifecycleId(2, "old"), .tool_name = "read_file", .activity_kind = .read, } }); const old_entry_id = runtime.toolActivityRecord(lifecycleId(2, "old")).?.entry_id; _ = try startLifecycle(&runtime, alloc, 3, "new"); for ([_]types.ToolLifecycleEvent{ .{ .turn_finished = .{ .turn_id = 2, .outcome = .completed } }, .{ .turn_finished = .{ .turn_id = 2, .outcome = .interrupted } }, .{ .turn_finished = .{ .turn_id = 1, .outcome = .failed } }, }) |event| _ = try runtime.applyToolLifecycle(alloc, event); try expectRawEntryBytes(&runtime, old_entry_id, "● Tool was not executed\n"); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = lifecycleId(2, "old"), .outcome = .{ .kind = .completed, .summary = "● Late completion" }, } }); var late_completion_expected: [128]u8 = undefined; try expectRawEntryBytes(&runtime, old_entry_id, try std.fmt.bufPrint( &late_completion_expected, "{s}●{s} Late completion\n", .{ ui_render.system_notice_text_style, ui_render.reset_style }, )); try std.testing.expectEqual( null, runtime.toolDetailForEntry(old_entry_id).?.fallback_disposition, ); try std.testing.expect(runtime.isLifecyclePinned(old_entry_id)); try runtime.finishLifecycleBatch(alloc); try std.testing.expectEqual(@as(u64, 2), runtime.finalizedToolTurnWatermark()); try std.testing.expect(runtime.toolActivityRecord(lifecycleId(2, "old")) == null); try std.testing.expect(runtime.toolActivityRecord(lifecycleId(3, "new")) != null); try std.testing.expect(!runtime.isLifecyclePinned(old_entry_id)); _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = lifecycleId(2, "late-start"), .reconciles_provisional_call_id = null, .tool_name = "read_file", .activity_kind = .read, } }); try std.testing.expect(runtime.toolActivityRecord(lifecycleId(2, "late-start")) == null); } test "completed tool status projection keeps lifecycle styling without activity state" { const alloc = std.testing.allocator; var runtime = lifecycleTestRuntime(null); defer runtime.deinit(alloc); var metrics: Metrics = .{}; try runtime.writeCompletedToolStatus( alloc, &metrics, .completed, "● Ran pwd\x1b[0m \x1b[38;5;245mpwd\x1b[0m", true, ); try std.testing.expectEqual(@as(usize, 1), runtime.entries.items.len); try std.testing.expectEqual(RawEntryClass.tool_status, runtime.entries.items[0].raw_bytes.class); try expectRawEntryBytes( &runtime, runtime.entries.items[0].raw_bytes.id, "\x1b[38;5;250m●\x1b[0m Ran pwd\x1b[0m \x1b[38;5;245mpwd\x1b[0m\n", ); try std.testing.expect(runtime.activityProjection() == .none); try std.testing.expectEqual(@as(usize, 0), runtime.toolActivityRecordCount()); } test "transcript lifecycle terminal markers preserve ANSI summaries and normalize markerless failures" { const alloc = std.testing.allocator; var runtime = lifecycleTestRuntime(null); defer runtime.deinit(alloc); const styled_entry_id = try startLifecycle(&runtime, alloc, 1, "styled"); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = lifecycleId(1, "styled"), .outcome = .{ .kind = .completed, .summary = "● Ran command\x1b[0m", }, } }); var styled_expected: [128]u8 = undefined; try expectRawEntryBytes(&runtime, styled_entry_id, try std.fmt.bufPrint( &styled_expected, "{s}●{s} Ran command\x1b[0m\n", .{ ui_render.system_notice_text_style, ui_render.reset_style }, )); const styled_cancelled_entry_id = try startLifecycle(&runtime, alloc, 1, "styled-cancelled"); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = lifecycleId(1, "styled-cancelled"), .outcome = .{ .kind = .cancelled, .summary = "● Cancelled\x1b[0m \x1b[38;5;245msleep 30\x1b[0m", }, } }); var styled_cancelled_expected: [256]u8 = undefined; try expectRawEntryBytes(&runtime, styled_cancelled_entry_id, try std.fmt.bufPrint( &styled_cancelled_expected, "{s}■{s}{s} Cancelled\x1b[0m \x1b[38;5;245msleep 30\x1b[0m{s}" ++ " · What can fx do differently?\n", .{ ui_render.warning_style, ui_render.reset_style, ui_render.hint_style, ui_render.reset_style, }, )); const malformed_entry_id = try startLifecycle(&runtime, alloc, 1, "malformed"); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = lifecycleId(1, "malformed"), .outcome = .{ .kind = .failed, .summary = "write_file failed: invalid JSON arguments", }, } }); var malformed_expected: [128]u8 = undefined; try expectRawEntryBytes(&runtime, malformed_entry_id, try std.fmt.bufPrint( &malformed_expected, "{s}●{s} write_file failed: invalid JSON arguments\n", .{ ui_render.red_style, ui_render.reset_style }, )); const cancelled_entry_id = try startLifecycle(&runtime, alloc, 2, "cancelled"); _ = try runtime.applyToolLifecycle(alloc, .{ .turn_finished = .{ .turn_id = 2, .outcome = .interrupted, } }); var cancelled_expected: [128]u8 = undefined; try expectRawEntryBytes(&runtime, cancelled_entry_id, try std.fmt.bufPrint( &cancelled_expected, "{s}■{s} {s}Tool cancelled{s} · What can fx do differently?\n", .{ ui_render.warning_style, ui_render.reset_style, ui_render.hint_style, ui_render.reset_style, }, )); const failed_entry_id = try startLifecycle(&runtime, alloc, 3, "failed"); _ = try runtime.applyToolLifecycle(alloc, .{ .turn_finished = .{ .turn_id = 3, .outcome = .failed, } }); var failed_expected: [128]u8 = undefined; try expectRawEntryBytes(&runtime, failed_entry_id, try std.fmt.bufPrint( &failed_expected, "{s}●{s} Tool failed\n", .{ ui_render.red_style, ui_render.reset_style }, )); } test "active tool cancellation is presented immediately without closing lifecycle state" { const alloc = std.testing.allocator; var runtime = lifecycleTestRuntime(null); defer runtime.deinit(alloc); const ids = [_]types.ToolLifecycleId{ lifecycleId(1, "read"), lifecycleId(1, "command"), }; for (ids) |id| { _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = id, .reconciles_provisional_call_id = null, .tool_name = if (std.mem.eql(u8, id.call_id, "read")) "read_file" else "run_command", .activity_kind = if (std.mem.eql(u8, id.call_id, "read")) .read else .command, } }); } try std.testing.expect(try runtime.presentActiveToolCancellation(alloc)); try std.testing.expectEqual(@as(usize, 2), runtime.activeToolActivityCount()); var rendered = try runtime.prepareTranscriptSource(alloc, null); defer rendered.deinit(alloc); try std.testing.expectEqual( @as(usize, 2), std.mem.count(u8, rendered.bytes, "What can fx do differently?"), ); try std.testing.expect(std.mem.find(u8, rendered.bytes, "System:") == null); try std.testing.expect(std.mem.find(u8, rendered.bytes, "Cancelling") == null); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = ids[1], .outcome = .{ .kind = .cancelled, .summary = "Cancelled sleep 30" }, } }); try std.testing.expectEqual(@as(usize, 1), runtime.activeToolActivityCount()); } test "late successful settlement preserves its result and one turn cancellation" { const alloc = std.testing.allocator; for ([_]bool{ false, true }) |finish_before_terminal| { var runtime = lifecycleTestRuntime(null); defer runtime.deinit(alloc); const id = lifecycleId(1, "late-success"); _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = id, .reconciles_provisional_call_id = null, .tool_name = "read_file", .activity_kind = .read, } }); try std.testing.expect(try runtime.presentActiveToolCancellation(alloc)); if (finish_before_terminal) { _ = try runtime.applyToolLifecycle(alloc, .{ .turn_finished = .{ .turn_id = id.turn_id, .outcome = .interrupted, } }); } _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = id, .outcome = .{ .kind = .completed, .summary = "Read the file" }, .result = "late result", } }); if (!finish_before_terminal) { _ = try runtime.applyToolLifecycle(alloc, .{ .turn_finished = .{ .turn_id = id.turn_id, .outcome = .interrupted, } }); } var rendered = try runtime.prepareTranscriptSource(alloc, null); defer rendered.deinit(alloc); try std.testing.expectEqual( @as(usize, 1), std.mem.count(u8, rendered.bytes, "What can fx do differently?"), ); try std.testing.expectEqual( @as(usize, 1), std.mem.count(u8, rendered.bytes, "Read the file"), ); try std.testing.expectEqual( RawEntryClass.turn_cancellation, runtime.entries.getLast().raw_bytes.class, ); const detail = runtime.toolDetailForEntry(runtime.toolActivityRecord(id).?.entry_id).?; try std.testing.expectEqualStrings("late result", detail.result.?); try std.testing.expectEqual(types.ToolOutcomeKind.completed, detail.outcome.?); } } test "post-cancel sibling settlement preserves one turn cancellation" { const alloc = std.testing.allocator; for ([_]bool{ false, true }) |sibling_reports_terminal| { var runtime = lifecycleTestRuntime(null); defer runtime.deinit(alloc); const first = lifecycleId(1, "first"); _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = first, .reconciles_provisional_call_id = null, .tool_name = "read_file", .activity_kind = .read, } }); try std.testing.expect(try runtime.presentActiveToolCancellation(alloc)); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = first, .outcome = .{ .kind = .completed, .summary = "Read first" }, } }); const sibling = lifecycleId(1, "sibling"); _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = sibling, .reconciles_provisional_call_id = null, .tool_name = "grep_files", .activity_kind = .read, } }); if (sibling_reports_terminal) { _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = sibling, .outcome = .{ .kind = .cancelled, .summary = "Search cancelled" }, } }); } _ = try runtime.applyToolLifecycle(alloc, .{ .turn_finished = .{ .turn_id = first.turn_id, .outcome = .interrupted, } }); var rendered = try runtime.prepareTranscriptSource(alloc, null); defer rendered.deinit(alloc); try std.testing.expectEqual( @as(usize, 1), std.mem.count(u8, rendered.bytes, "What can fx do differently?"), ); try std.testing.expectEqual( @as(usize, 1), std.mem.count(u8, rendered.bytes, "Read first"), ); } } test "late zero-output command settlement reserves distinct presentation entries" { const alloc = std.testing.allocator; for ([_]bool{ false, true }) |finish_before_terminal| { var runtime = lifecycleTestRuntime(null); defer runtime.deinit(alloc); const id = lifecycleId(1, "zero-output-command"); _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = id, .reconciles_provisional_call_id = null, .tool_name = "shell", .activity_kind = .command, .arguments_json = "{\"request\":{\"action\":\"run\",\"command\":\"true\"}}", } }); try std.testing.expect(try runtime.presentActiveToolCancellation(alloc)); if (finish_before_terminal) { _ = try runtime.applyToolLifecycle(alloc, .{ .turn_finished = .{ .turn_id = id.turn_id, .outcome = .interrupted, } }); } _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = id, .outcome = .{ .kind = .completed, .summary = "Ran true" }, .result = "exit_code=0\n", .result_memory = .{ .command_process_presentation = .{ .exit_code = 0 }, }, } }); if (!finish_before_terminal) { _ = try runtime.applyToolLifecycle(alloc, .{ .turn_finished = .{ .turn_id = id.turn_id, .outcome = .interrupted, } }); } var rendered = try runtime.prepareTranscriptSource(alloc, null); defer rendered.deinit(alloc); try std.testing.expectEqual( @as(usize, 1), std.mem.count(u8, rendered.bytes, "What can fx do differently?"), ); try std.testing.expectEqual( @as(usize, 1), std.mem.count(u8, rendered.bytes, "Ran \x1b[38;5;245m\x1b[38;5;252mtrue\x1b[39m"), ); const detail = runtime.toolDetailForEntry(runtime.toolActivityRecord(id).?.entry_id).?; try std.testing.expectEqual(types.ToolOutcomeKind.completed, detail.outcome.?); try std.testing.expectEqual( types.CommandProcessPresentation{ .exit_code = 0 }, detail.command_process_presentation.?, ); try std.testing.expectEqual( RawEntryClass.command_output, runtime.rawEntryClass(detail.command_output_entry_id.?).?, ); for (runtime.entries.items, 0..) |entry, entry_index| { for (runtime.entries.items[entry_index + 1 ..]) |other| { try std.testing.expect(entry.id() != other.id()); } } } } fn checkLateZeroOutputCommandCancellationAllocationFailuresImpl(alloc: Allocator) !void { var runtime = lifecycleTestRuntime(null); defer runtime.deinit(alloc); const id = lifecycleId(1, "allocation-command"); _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = id, .reconciles_provisional_call_id = null, .tool_name = "shell", .activity_kind = .command, .arguments_json = "{\"request\":{\"action\":\"run\",\"command\":\"true\"}}", } }); try std.testing.expect(try runtime.presentActiveToolCancellation(alloc)); _ = try runtime.applyToolLifecycle(alloc, .{ .turn_finished = .{ .turn_id = id.turn_id, .outcome = .interrupted, } }); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = id, .outcome = .{ .kind = .completed, .summary = "Ran true" }, .result = "exit_code=0\n", .result_memory = .{ .command_process_presentation = .{ .exit_code = 0 }, }, } }); const detail = runtime.toolDetailForEntry(runtime.toolActivityRecord(id).?.entry_id).?; try std.testing.expectEqual(types.ToolOutcomeKind.completed, detail.outcome.?); try std.testing.expectEqual( RawEntryClass.command_output, runtime.rawEntryClass(detail.command_output_entry_id.?).?, ); var rendered = try runtime.prepareTranscriptSource(alloc, null); defer rendered.deinit(alloc); try std.testing.expectEqual( @as(usize, 1), std.mem.count(u8, rendered.bytes, "What can fx do differently?"), ); } fn checkLateZeroOutputCommandCancellationAllocationFailures(alloc: Allocator) !void { return checkLateZeroOutputCommandCancellationAllocationFailuresImpl(alloc) catch |err| switch (err) { error.WriteFailed => error.OutOfMemory, else => err, }; } test "late zero-output command cancellation remains atomic across allocation failures" { try std.testing.checkAllAllocationFailures( std.testing.allocator, checkLateZeroOutputCommandCancellationAllocationFailures, .{}, ); } fn expectRawEntryBytes( runtime: *const TranscriptRuntime, entry_id: u32, expected: []const u8, ) !void { for (runtime.entries.items) |entry| { if (entry == .raw_bytes and entry.raw_bytes.id == entry_id) { try std.testing.expectEqualStrings(expected, entry.raw_bytes.bytes); return; } } return error.TestExpectedEqual; } fn transcriptContainsEntry(runtime: *const TranscriptRuntime, entry_id: u32) bool { for (runtime.entries.items) |entry| { if (entry.id() == entry_id) return true; } return false; } fn checkLifecycleRepositionAllocationFailuresImpl(alloc: Allocator) !void { var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(48, 12, 8), .owned_top_row = 1, .max_transcript_bytes = 64, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "ALLOCATION TRANSCRIPT 01\n" ++ "ALLOCATION TRANSCRIPT 02\n" ++ "ALLOCATION TRANSCRIPT 03\n" ++ "ALLOCATION TRANSCRIPT 04\n" ++ "ALLOCATION TRANSCRIPT 05\n" ++ "ALLOCATION TRANSCRIPT 06\n", .subagent_status, ); var committed_source = try runtime.prepareTranscriptSource(alloc, null); defer committed_source.deinit(alloc); try installStableAnchorForTest( &runtime, alloc, committed_source.bytes, testSelection(0), 0, 4, 1, 1, ); const id = lifecycleId(93, "allocation-command"); _ = try runtime.applyToolLifecycle(alloc, .{ .provisional = .{ .id = id, .tool_name = "run_command", .activity_kind = .command, } }); _ = try runtime.appendSemanticNotice(alloc, .{ .topic = "permissions", .tone = .success, .body = "Auto agent approved this request: Running command.", .visibility = .full_only, }); var source_before = try runtime.prepareTranscriptSource(std.testing.allocator, null); defer source_before.deinit(std.testing.allocator); const diagnostic_before = runtime.transcriptCommitDiagnostic(); _ = runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = id, .reconciles_provisional_call_id = null, .tool_name = "run_command", .activity_kind = .command, .arguments_json = "{\"command\":\"seq 1 1\"}", .place_after_current_transcript = true, } }) catch |err| { var source_after = try runtime.prepareTranscriptSource(std.testing.allocator, null); defer source_after.deinit(std.testing.allocator); try std.testing.expectEqualStrings(source_before.bytes, source_after.bytes); try std.testing.expectEqualDeep( diagnostic_before, runtime.transcriptCommitDiagnostic(), ); try std.testing.expect(runtime.toolActivityRecord(id).?.phase == .provisional); return err; }; try std.testing.expect(runtime.toolActivityRecord(id).?.phase == .authoritative); try std.testing.expectEqual( transcript_runtime.TranscriptCommitDiagnosticState.stable, runtime.transcriptCommitDiagnostic().state, ); } fn checkLifecycleRepositionAllocationFailures(alloc: Allocator) !void { return checkLifecycleRepositionAllocationFailuresImpl(alloc) catch |err| switch (err) { error.WriteFailed => error.OutOfMemory, else => err, }; } test "coalesced approval lifecycle reposition is atomic across allocation failures" { try std.testing.checkAllAllocationFailures( std.testing.allocator, checkLifecycleRepositionAllocationFailures, .{}, ); } test "lifecycle pin cleanup preserves a capped canonical anchor without pruning" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(48, 12, 8), .owned_top_row = 1, .max_transcript_bytes = 64, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); const long_tail = try makeLongPastePrompt(alloc, 40); defer alloc.free(long_tail); _ = try runtime.appendRawTranscriptEntryClassified( alloc, long_tail, .subagent_status, ); const id = lifecycleId(94, "cleanup-command"); _ = try startLifecycle(&runtime, alloc, id.turn_id, id.call_id); _ = try runtime.applyToolLifecycle(alloc, .{ .turn_finished = .{ .turn_id = id.turn_id, .outcome = .completed, } }); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = id, .outcome = .{ .kind = .completed, .summary = "Cleanup complete" }, } }); var metrics = Metrics{}; var committed_source = try runtime.prepareTranscriptSource(alloc, null); defer committed_source.deinit(alloc); try std.testing.expect(committed_source.recorded_entries_authoritative); try std.testing.expect(committed_source.bytes.len > runtime.transcript.items.len); var committed_prepared = try runtime.prepareTranscriptSurfacePaintForArea( alloc, &metrics, .{ .top = runtime.owned_top_row, .bottom = runtime.layout.content_bottom }, ); defer committed_prepared.deinit(alloc); try installStableAnchorForTest( &runtime, alloc, committed_source.bytes, committed_prepared.selection, preparedVisualOffsetForTest(&committed_prepared), committed_prepared.cursor.cursor_row, committed_prepared.cursor.cursor_col, 1, ); const committed_diagnostic = runtime.transcriptCommitDiagnostic(); try runtime.finishLifecycleBatch(alloc); try std.testing.expectEqualDeep( committed_diagnostic, runtime.transcriptCommitDiagnostic(), ); try std.testing.expectEqual(@as(usize, 0), runtime.lifecyclePinCount()); try std.testing.expectEqual(@as(usize, 0), runtime.toolActivityRecordCount()); var cleaned_source = try runtime.prepareTranscriptSource(alloc, null); defer cleaned_source.deinit(alloc); try std.testing.expectEqualStrings(committed_source.bytes, cleaned_source.bytes); } test "lifecycle pin cleanup preserves a canonical anchor when retention prunes" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(48, 12, 8), .owned_top_row = 1, }; defer runtime.deinit(alloc); try runtime.enableShadowVt(alloc); const anchored = "anchored lifecycle cleanup transcript\n"; _ = try runtime.appendRawTranscriptEntryClassified( alloc, anchored, .subagent_status, ); const id = lifecycleId(95, "pruning-cleanup-command"); _ = try startLifecycle(&runtime, alloc, id.turn_id, id.call_id); _ = try runtime.applyToolLifecycle(alloc, .{ .turn_finished = .{ .turn_id = id.turn_id, .outcome = .completed, } }); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = id, .outcome = .{ .kind = .completed, .summary = "Cleanup complete" }, } }); var committed_source = try runtime.prepareTranscriptSource(alloc, null); defer committed_source.deinit(alloc); try installStableAnchorForTest( &runtime, alloc, committed_source.bytes, testSelection(0), 0, 4, 1, 1, ); const committed_history_visual_offset = try stableHistoryVisualOffsetForTest(&runtime); runtime.max_retained_transcript_bytes = transcript_store.retainedStructuredBytes(&runtime) - anchored.len; try runtime.finishLifecycleBatch(alloc); try expectStableNormalBufferRecoveryForTest( &runtime, committed_history_visual_offset, ); try std.testing.expectEqual(@as(usize, 0), runtime.lifecyclePinCount()); try std.testing.expectEqual(@as(usize, 0), runtime.toolActivityRecordCount()); var retained_source = try runtime.prepareTranscriptSource(alloc, null); defer retained_source.deinit(alloc); try std.testing.expectEqual( @as(usize, 0), std.mem.count(u8, retained_source.bytes, anchored), ); } fn checkLifecycleAllocationFailuresImpl(alloc: Allocator) !void { var runtime = lifecycleTestRuntime(64); defer runtime.deinit(alloc); const existing = try runtime.appendRawTranscriptEntryClassified( alloc, "existing transcript state\n", .subagent_status, ); const entry_id = startLifecycle(&runtime, alloc, 1, "transaction") catch |err| { try std.testing.expectEqual(@as(usize, 0), runtime.toolActivityRecordCount()); try std.testing.expectEqual(@as(usize, 0), runtime.lifecyclePinCount()); try expectRawEntryBytes(&runtime, existing, "existing transcript state\n"); return err; }; _ = runtime.applyToolLifecycle(alloc, .{ .progress = .{ .id = lifecycleId(1, "transaction"), .text = "● Reading updated", } }) catch |err| { try std.testing.expect(runtime.isLifecyclePinned(entry_id)); try std.testing.expect(std.mem.find( u8, runtime.toolStatusEntryLabel(entry_id).?, "updated", ) == null); return err; }; _ = runtime.applyToolLifecycle(alloc, .{ .turn_finished = .{ .turn_id = 1, .outcome = .completed }, }) catch |err| { try expectRawEntryBytes(&runtime, entry_id, "● Reading updated\n"); try std.testing.expect(runtime.isLifecyclePinned(entry_id)); return err; }; runtime.finishLifecycleBatch(alloc) catch |err| { try std.testing.expect(runtime.toolActivityRecord(lifecycleId(1, "transaction")) != null); try std.testing.expect(runtime.isLifecyclePinned(entry_id)); return err; }; try std.testing.expect(runtime.toolActivityRecord(lifecycleId(1, "transaction")) == null); try std.testing.expect(!runtime.isLifecyclePinned(entry_id)); } fn checkLifecycleAllocationFailures(alloc: Allocator) !void { return checkLifecycleAllocationFailuresImpl(alloc) catch |err| switch (err) { error.WriteFailed => error.OutOfMemory, else => err, }; } test "transcript lifecycle transactions are atomic across allocation failures" { try std.testing.checkAllAllocationFailures( std.testing.allocator, checkLifecycleAllocationFailures, .{}, ); } fn checkLateTerminalFallbackAllocationFailuresImpl(alloc: Allocator) !void { var runtime = lifecycleTestRuntime(null); defer runtime.deinit(alloc); const id = lifecycleId(1, "late-terminal"); _ = try runtime.applyToolLifecycle(alloc, .{ .provisional = .{ .id = id, .tool_name = "read_file", .activity_kind = .read, } }); const entry_id = runtime.toolActivityRecord(id).?.entry_id; _ = try runtime.applyToolLifecycle(alloc, .{ .turn_finished = .{ .turn_id = 1, .outcome = .completed, } }); _ = runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = id, .outcome = .{ .kind = .completed, .summary = "Late completion" }, .result = "late result", } }) catch |err| { try expectRawEntryBytes(&runtime, entry_id, "● Tool was not executed\n"); const detail = runtime.toolDetailForEntry(entry_id).?; try std.testing.expect(detail.result == null); try std.testing.expectEqual( transcript_blocks.ToolFallbackDisposition.not_executed, detail.fallback_disposition.?, ); var compact = try runtime.prepareTranscriptSource(std.testing.allocator, null); defer compact.deinit(std.testing.allocator); try std.testing.expect(std.mem.find(u8, compact.bytes, "1 not executed") != null); return err; }; var expected: [128]u8 = undefined; try expectRawEntryBytes(&runtime, entry_id, try std.fmt.bufPrint( &expected, "{s}●{s} Late completion\n", .{ ui_render.system_notice_text_style, ui_render.reset_style }, )); const detail = runtime.toolDetailForEntry(entry_id).?; try std.testing.expectEqualStrings("late result", detail.result.?); try std.testing.expect(detail.fallback_disposition == null); var compact = try runtime.prepareTranscriptSource(std.testing.allocator, null); defer compact.deinit(std.testing.allocator); try std.testing.expect(std.mem.find(u8, compact.bytes, "1 not executed") == null); try std.testing.expect(std.mem.find(u8, compact.bytes, "Late completion") != null); } fn checkLateTerminalFallbackAllocationFailures(alloc: Allocator) !void { return checkLateTerminalFallbackAllocationFailuresImpl(alloc) catch |err| switch (err) { error.WriteFailed => error.OutOfMemory, else => err, }; } test "late terminal fallback replacement is atomic across allocation failures" { try std.testing.checkAllAllocationFailures( std.testing.allocator, checkLateTerminalFallbackAllocationFailures, .{}, ); } fn checkProvisionalTerminalAllocationFailuresImpl(alloc: Allocator) !void { var runtime = lifecycleTestRuntime(null); defer runtime.deinit(alloc); const id = lifecycleId(1, "provisional-terminal"); _ = try runtime.applyToolLifecycle(alloc, .{ .provisional = .{ .id = id, .tool_name = "read_file", .activity_kind = .read, } }); const entry_id = runtime.toolActivityRecord(id).?.entry_id; const entries_before = runtime.entries.items.len; const next_entry_id_before = runtime.next_entry_id; _ = runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = id, .outcome = .{ .kind = .completed, .summary = "Read provisional file" }, .result = "provisional result", } }) catch |err| { try std.testing.expect(runtime.toolActivityRecord(id).?.phase == .provisional); try std.testing.expectEqual(entries_before, runtime.entries.items.len); try std.testing.expectEqual(next_entry_id_before, runtime.next_entry_id); const detail = runtime.toolDetailForEntry(entry_id).?; try std.testing.expectEqualStrings("read_file", detail.tool_name); try std.testing.expectEqual(types.ToolActivityKind.read, detail.activity_kind.?); try std.testing.expect(detail.result == null); try std.testing.expect(detail.outcome == null); const status = runtime.toolStatusEntryLabel(entry_id).?; try std.testing.expect(std.mem.find(u8, status, "read_file") != null); try std.testing.expect(std.mem.find(u8, status, "Read provisional file") == null); return err; }; try std.testing.expect(runtime.toolActivityRecord(id).?.phase == .terminal); try std.testing.expectEqual(entries_before, runtime.entries.items.len); try std.testing.expectEqual(next_entry_id_before, runtime.next_entry_id); const detail = runtime.toolDetailForEntry(entry_id).?; try std.testing.expectEqualStrings("read_file", detail.tool_name); try std.testing.expectEqualStrings("provisional result", detail.result.?); try std.testing.expectEqual(types.ToolOutcomeKind.completed, detail.outcome.?); } fn checkProvisionalTerminalAllocationFailures(alloc: Allocator) !void { return checkProvisionalTerminalAllocationFailuresImpl(alloc) catch |err| switch (err) { error.WriteFailed => error.OutOfMemory, else => err, }; } test "provisional terminal admission is atomic across allocation failures" { try std.testing.checkAllAllocationFailures( std.testing.allocator, checkProvisionalTerminalAllocationFailures, .{}, ); } fn checkCommandProcessTerminalAllocationFailuresImpl(alloc: Allocator) !void { var runtime = lifecycleTestRuntime(null); defer runtime.deinit(alloc); const id = lifecycleId(1, "command-process-terminal"); _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = id, .reconciles_provisional_call_id = null, .tool_name = "run_command", .activity_kind = .command, .arguments_json = "{\"command\":\"true\"}", } }); const entry_id = runtime.toolActivityRecord(id).?.entry_id; const entries_before = runtime.entries.items.len; const blocks_before = runtime.command_output_blocks.items.len; const next_entry_id_before = runtime.next_entry_id; _ = runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = id, .outcome = .{ .kind = .completed, .summary = "Ran true" }, .result_memory = .{ .command_process_presentation = .{ .exit_code = 0 } }, } }) catch |err| { try std.testing.expect(runtime.toolActivityRecord(id).?.phase == .authoritative); try std.testing.expectEqual(entries_before, runtime.entries.items.len); try std.testing.expectEqual(blocks_before, runtime.command_output_blocks.items.len); try std.testing.expectEqual(next_entry_id_before, runtime.next_entry_id); const detail = runtime.toolDetailForEntry(entry_id).?; try std.testing.expect(detail.command_process_presentation == null); try std.testing.expect(detail.command_output_entry_id == null); const status = runtime.toolStatusEntryLabel(entry_id).?; try std.testing.expect(std.mem.find(u8, status, "run_command") != null); try std.testing.expect(std.mem.find(u8, status, "\x1b[38;5;250mtrue\x1b[39m") == null); return err; }; try std.testing.expect(runtime.toolActivityRecord(id).?.phase == .terminal); try std.testing.expectEqual(entries_before + 1, runtime.entries.items.len); try std.testing.expectEqual(blocks_before + 1, runtime.command_output_blocks.items.len); try std.testing.expectEqual(next_entry_id_before + 1, runtime.next_entry_id); const detail = runtime.toolDetailForEntry(entry_id).?; try std.testing.expectEqual( types.CommandProcessPresentation{ .exit_code = 0 }, detail.command_process_presentation.?, ); try std.testing.expectEqual(next_entry_id_before, detail.command_output_entry_id.?); try std.testing.expectEqual(next_entry_id_before, runtime.command_output_blocks.items[0].entry_id.?); } fn checkCommandProcessTerminalAllocationFailures(alloc: Allocator) !void { return checkCommandProcessTerminalAllocationFailuresImpl(alloc) catch |err| switch (err) { error.WriteFailed => error.OutOfMemory, else => err, }; } test "command process terminal admission is atomic across allocation failures" { try std.testing.checkAllAllocationFailures( std.testing.allocator, checkCommandProcessTerminalAllocationFailures, .{}, ); } test "capped parallel lifecycle progress keeps bounded allocations and pins" { const backing = std.testing.allocator; var counted = std.testing.FailingAllocator.init(backing, .{}); const alloc = counted.allocator(); var runtime = lifecycleTestRuntime(256); defer runtime.deinit(alloc); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "unrelated output retained near the configured transcript cap\n", .subagent_status, ); const first_entry_id = try startLifecycle(&runtime, alloc, 1, call_ids[0]); const second_entry_id = try startLifecycle(&runtime, alloc, 1, call_ids[1]); try std.testing.expectEqual(@as(usize, 2), runtime.tool_details.items.len); try std.testing.expect(runtime.toolDetailForEntry(first_entry_id) != null); try std.testing.expect(runtime.toolDetailForEntry(second_entry_id) != null); const capacity_after_start = runtime.tool_details.capacity; const progress_text = "● Reading progress " ++ ("x" ** 256); _ = try runtime.applyToolLifecycle(alloc, .{ .progress = .{ .id = lifecycleId(1, call_ids[0]), .text = progress_text, } }); try std.testing.expectEqual(@as(usize, 2), runtime.tool_details.items.len); try std.testing.expectEqual(capacity_after_start, runtime.tool_details.capacity); var allocations_before_progress = counted.allocations; for (0..120) |index| { if (index == 20) allocations_before_progress = counted.allocations; _ = try runtime.applyToolLifecycle(alloc, .{ .progress = .{ .id = lifecycleId(1, call_ids[index % call_ids.len]), .text = "● Reading progress", } }); try std.testing.expectEqual(capacity_after_start, runtime.tool_details.capacity); } try std.testing.expectEqual(@as(usize, 2), runtime.tool_details.items.len); try std.testing.expect(runtime.toolDetailForEntry(first_entry_id) != null); try std.testing.expect(runtime.toolDetailForEntry(second_entry_id) != null); try std.testing.expect(counted.allocations - allocations_before_progress <= 600); try std.testing.expectEqual(@as(usize, 2), runtime.lifecyclePinCount()); } const call_ids = [_][]const u8{ "first", "second" }; test "lifecycle pins survive low cap until batch cleanup restores prune eligibility" { const alloc = std.testing.allocator; var runtime = lifecycleTestRuntime(48); defer runtime.deinit(alloc); const first = try startLifecycle(&runtime, alloc, 1, call_ids[0]); const second = try startLifecycle(&runtime, alloc, 1, call_ids[1]); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "ordinary output that creates retention pressure\n", .subagent_status, ); try std.testing.expectEqual(@as(usize, 2), runtime.lifecyclePinCount()); try std.testing.expect(transcriptContainsEntry(&runtime, first)); try std.testing.expect(transcriptContainsEntry(&runtime, second)); _ = try runtime.applyToolLifecycle(alloc, .{ .turn_finished = .{ .turn_id = 1, .outcome = .completed, } }); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = lifecycleId(1, "first"), .outcome = .{ .kind = .completed, .summary = "● First complete" }, } }); try std.testing.expectEqual(@as(usize, 2), runtime.lifecyclePinCount()); var first_complete_expected: [128]u8 = undefined; try std.testing.expectEqualStrings( try std.fmt.bufPrint( &first_complete_expected, "{s}●{s} First complete\n", .{ ui_render.system_notice_text_style, ui_render.reset_style }, ), runtime.entries.items[0].raw_bytes.bytes, ); try runtime.finishLifecycleBatch(alloc); _ = try runtime.appendRawTranscriptEntryClassified( alloc, "next ordinary retention pass\n", .subagent_status, ); try std.testing.expectEqual(@as(usize, 0), runtime.lifecyclePinCount()); try std.testing.expectEqual(@as(usize, 0), runtime.toolActivityRecordCount()); try std.testing.expect( !transcriptContainsEntry(&runtime, first) or !transcriptContainsEntry(&runtime, second), ); } test "streaming assistant finality releases complete rows and holds only the partial tail" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(40, 10, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); var metrics: Metrics = .{}; _ = try runtime.streamAssistantChunk( alloc, &metrics, "first finalized row\nsecond finalized row\npartial tail", ); var partial_source = try runtime.prepareTranscriptSource(alloc, null); defer partial_source.deinit(alloc); const partial_start = std.mem.find(u8, partial_source.bytes, " partial tail") orelse return error.TestExpectedPartialAssistantTail; try std.testing.expectEqual( @as(?usize, partial_start), runtime.transcript_release.finality_floor( partial_source.finality, runtime.finalizedToolTurnWatermark(), null, ), ); _ = try runtime.streamAssistantChunk(alloc, &metrics, "\n"); var completed_source = try runtime.prepareTranscriptSource(alloc, null); defer completed_source.deinit(alloc); try std.testing.expectEqual( @as(?usize, completed_source.bytes.len), runtime.transcript_release.finality_floor( completed_source.finality, runtime.finalizedToolTurnWatermark(), null, ), ); } test "blank streaming assistant tail keeps finality inside the prepared source" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(40, 10, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); _ = try runtime.appendRawTranscriptEntry(alloc, "stable history\n"); var metrics: Metrics = .{}; _ = try runtime.streamAssistantChunk(alloc, &metrics, " "); var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); try std.testing.expectEqual( @as(?usize, source.bytes.len), runtime.transcript_release.finality_floor( source.finality, runtime.finalizedToolTurnWatermark(), null, ), ); } test "empty assistant tail leaves the complete prepared source final" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(40, 10, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); _ = try runtime.appendRawTranscriptEntry(alloc, "stable history\n"); _ = try runtime.appendAssistantTurnEntry(alloc); var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); try std.testing.expectEqual( @as(?usize, source.bytes.len), runtime.transcript_release.finality_floor( source.finality, runtime.finalizedToolTurnWatermark(), null, ), ); } test "native history defers partial assistant paints but publishes hard lines" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(40, 10, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); try installStableAnchorForTest( &runtime, alloc, "history\n", testSelection(1), 1, 4, 1, 1, ); var metrics: Metrics = .{}; _ = try runtime.streamAssistantChunk(alloc, &metrics, "partial"); try std.testing.expect(!runtime.render_requests.hasReason(.transcript)); _ = try runtime.streamAssistantChunk(alloc, &metrics, " line\n"); try std.testing.expect(runtime.render_requests.hasReason(.transcript)); } test "streaming assistant finality keeps soft wraps of a partial hard line mutable" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(12, 10, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); var metrics: Metrics = .{}; _ = try runtime.streamAssistantChunk( alloc, &metrics, "finalized row\npartial tail wraps across several visual rows", ); var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); const partial_start = std.mem.find(u8, source.bytes, " partial") orelse return error.TestExpectedWrappedPartialAssistantTail; try std.testing.expectEqual( @as(?usize, partial_start), runtime.transcript_release.finality_floor( source.finality, runtime.finalizedToolTurnWatermark(), null, ), ); } test "finality floor holds unfenced tool turn across quiet ticks and settles after the fence" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(60, 10, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); for (0..6) |index| { var line: [32]u8 = undefined; const text = try std.fmt.bufPrint(&line, "final context {d:0>2}\n", .{index}); _ = try runtime.appendRawTranscriptEntry(alloc, text); } var base_source = try runtime.prepareTranscriptSource(alloc, null); defer base_source.deinit(alloc); var base_prepared = try prepareTestSourceForCurrentArea(&runtime, alloc, &base_source); defer base_prepared.deinit(alloc); try commitPreparedForTest(&runtime, alloc, &base_source, &base_prepared); var turn_call_ids: [8][12]u8 = undefined; for (0..8) |index| { const call_id = try std.fmt.bufPrint(&turn_call_ids[index], "t2-call-{d:0>2}", .{index}); const id = types.ToolLifecycleId{ .turn_id = 2, .call_id = call_id }; _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = id, .presentation_group_id = .{ .turn_id = 2, .anchor_step_id = 1 }, .reconciles_provisional_call_id = null, .tool_name = "read_file", .activity_kind = .read, } }); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = id, .outcome = .{ .kind = .completed, .summary = "Read one file" }, } }); } try runtime.finishLifecycleBatch(alloc); try std.testing.expectEqual(@as(u64, 0), runtime.finalizedToolTurnWatermark()); var live_source = try runtime.prepareTranscriptSource(alloc, null); defer live_source.deinit(alloc); var live_prepared = try prepareTestSourceForCurrentArea(&runtime, alloc, &live_source); defer live_prepared.deinit(alloc); try commitPreparedForTest(&runtime, alloc, &live_source, &live_prepared); const held = runtime.transcript_commit_state.stable; try std.testing.expect(held.visual_offset > held.history_visual_offset); // Quiet ticks over the unchanged flow release nothing: byte stability // is not finality while the turn is unfenced. var quiet_source = try runtime.prepareTranscriptSource(alloc, null); defer quiet_source.deinit(alloc); var quiet_prepared = try prepareTestSourceForCurrentArea(&runtime, alloc, &quiet_source); defer quiet_prepared.deinit(alloc); const quiet_facts = runtime.planTranscriptScroll(&quiet_prepared); try std.testing.expect(quiet_facts.source_compatible); try std.testing.expectEqual(@as(u32, 0), quiet_facts.semantic_rows); try std.testing.expect(quiet_facts.finality_hold); // A later mutation of the same group still releases nothing. const late_call = types.ToolLifecycleId{ .turn_id = 2, .call_id = "t2-late" }; _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = late_call, .presentation_group_id = .{ .turn_id = 2, .anchor_step_id = 1 }, .reconciles_provisional_call_id = null, .tool_name = "read_file", .activity_kind = .read, } }); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = late_call, .outcome = .{ .kind = .completed, .summary = "Read late file" }, } }); try runtime.finishLifecycleBatch(alloc); var mutated_source = try runtime.prepareTranscriptSource(alloc, null); defer mutated_source.deinit(alloc); var mutated_prepared = try prepareTestSourceForCurrentArea(&runtime, alloc, &mutated_source); defer mutated_prepared.deinit(alloc); const mutated_facts = runtime.planTranscriptScroll(&mutated_prepared); try std.testing.expectEqual(@as(u32, 0), mutated_facts.semantic_rows); try std.testing.expectEqual(@as(u16, 0), mutated_facts.planned_rows); try commitPreparedForTest(&runtime, alloc, &mutated_source, &mutated_prepared); // The fence and the final mutation arrive in one batch: the first // post-fence frame is byte-incompatible and must hold (zero release), // re-anchoring on the final flow. _ = try runtime.applyToolLifecycle(alloc, .{ .turn_finished = .{ .turn_id = 2, .outcome = .completed, } }); try runtime.finishLifecycleBatch(alloc); try std.testing.expectEqual(@as(u64, 2), runtime.finalizedToolTurnWatermark()); // The debt settles on the first eligible frame after the fence: the // same frame when the fence publication changed no bytes, or the frame // after a byte-incompatible publication re-anchors (Phase A then B). var released: u32 = 0; for (0..2) |frame_index| { var frame_source = try runtime.prepareTranscriptSource(alloc, null); defer frame_source.deinit(alloc); var frame_prepared = try prepareTestSourceForCurrentArea(&runtime, alloc, &frame_source); defer frame_prepared.deinit(alloc); const frame_facts = runtime.planTranscriptScroll(&frame_prepared); if (!frame_facts.source_compatible) { // Phase A: zero release while the anchor is stale. try std.testing.expectEqual(@as(u32, 0), frame_facts.semantic_rows); try std.testing.expectEqual(@as(u16, 0), frame_facts.planned_rows); try std.testing.expect(frame_facts.finality_hold); try std.testing.expectEqual(@as(usize, 0), frame_index); } released += frame_facts.semantic_rows; try commitPreparedForTest(&runtime, alloc, &frame_source, &frame_prepared); } try std.testing.expect(released > 0); const settled = runtime.transcript_commit_state.stable; try std.testing.expectEqual(settled.visual_offset, settled.history_visual_offset); } test "catch-up release materializes history before staging a larger finality hold" { const alloc = std.testing.allocator; const committed_flow = "row-00\nrow-01\nrow-02\nrow-03\n" ++ "row-04\nrow-05\nrow-06\nrow-07\n"; const target_flow = committed_flow ++ "row-08\nrow-09\nrow-10\nrow-11\nrow-12\nrow-13\n"; const finality_floor = "row-00\nrow-01\nrow-02\n".len; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); var committed_source = try testSource(alloc, committed_flow, runtime.layout.cols); defer committed_source.deinit(alloc); var committed_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &committed_source, ); defer committed_prepared.deinit(alloc); try installStableAnchorForTest( &runtime, alloc, committed_flow, committed_prepared.selection, preparedVisualOffsetForTest(&committed_prepared), committed_prepared.cursor.cursor_row, committed_prepared.cursor.cursor_col, 1, ); switch (runtime.transcript_commit_state) { .stable => |*anchor| { anchor.history_visual_offset = 0; anchor.history_catchup_pending = true; }, .invalid, .recovering => return error.TestExpectedStableTranscript, } var source = try testSource(alloc, target_flow, runtime.layout.cols); defer source.deinit(alloc); source.finality.mutation_pin_start = finality_floor; var prepared = try prepareTestSourceForCurrentArea(&runtime, alloc, &source); defer prepared.deinit(alloc); const facts = runtime.planTranscriptScroll(&prepared); try std.testing.expect(facts.source_compatible); try std.testing.expectEqual(@as(u32, 3), facts.semantic_rows); try std.testing.expect(facts.semantic_rows > 0); try std.testing.expect( facts.target_visual_offset - facts.source_visual_offset > facts.semantic_rows, ); try std.testing.expect(facts.finality_hold); const scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, facts.planned_rows, ); var plan = testPaintPlan(&runtime, prepared.selection); var transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &source, &prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(plan), &plan, scroll_plan, ); defer transition.deinit(alloc); try std.testing.expectEqualStrings( "row-00\r\nrow-01\r\nrow-02\r\n", transition.document_append.bytes, ); switch (transition.document_append.clear) { .rows => |rows| try std.testing.expectEqual(@as(u16, 3), rows), .remainder => return error.TestExpectedHistoryReplay, } } test "partial finality release bounds append before staging withheld viewport" { const alloc = std.testing.allocator; const committed_flow = "row-00\nrow-01\nrow-02\nrow-03\n" ++ "row-04\nrow-05\nrow-06\nrow-07\n"; const target_flow = committed_flow ++ "row-08\nrow-09\nrow-10\nrow-11\nrow-12\nrow-13\n"; const finality_floor = "row-00\nrow-01\nrow-02\nrow-03\nrow-04\n".len; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 8, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); var committed_source = try testSource(alloc, committed_flow, runtime.layout.cols); defer committed_source.deinit(alloc); var committed_prepared = try prepareTestSourceForCurrentArea( &runtime, alloc, &committed_source, ); defer committed_prepared.deinit(alloc); try installStableAnchorForTest( &runtime, alloc, committed_flow, committed_prepared.selection, preparedVisualOffsetForTest(&committed_prepared), committed_prepared.cursor.cursor_row, committed_prepared.cursor.cursor_col, 1, ); switch (runtime.transcript_commit_state) { .stable => |*anchor| anchor.total_visual_rows = 8, .invalid, .recovering => return error.TestExpectedStableTranscript, } var source = try testSource(alloc, target_flow, runtime.layout.cols); defer source.deinit(alloc); source.finality.mutation_pin_start = finality_floor; var prepared = try prepareTestSourceForCurrentArea(&runtime, alloc, &source); defer prepared.deinit(alloc); const facts = runtime.planTranscriptScroll(&prepared); try std.testing.expect(facts.source_compatible); try std.testing.expectEqual(@as(u32, 4), facts.source_visual_offset); try std.testing.expectEqual(@as(u32, 10), facts.target_visual_offset); try std.testing.expectEqual(@as(u32, 1), facts.semantic_rows); try std.testing.expectEqual(@as(u16, 1), facts.planned_rows); try std.testing.expect(facts.finality_hold); const scroll_plan = render_engine.frame_scroll_plan.merge( runtime.layout.rows, runtime.owned_top_row, 0, facts.planned_rows, ); var plan = testPaintPlan(&runtime, prepared.selection); var transition = try resolveAndSealTranscriptTransitionForTest( &runtime, alloc, &source, &prepared, render_engine.frame_layout.CommittedLayoutSnapshot.fromPaintPlan(plan), &plan, scroll_plan, ); defer transition.deinit(alloc); try std.testing.expectEqualStrings( "row-08\r\n", transition.document_append.bytes, ); try std.testing.expectEqual(@as(u32, 10), transition.visual_offset); try std.testing.expectEqual(@as(u32, 5), transition.history_visual_offset); } test "recovery growth does not release rows past the finality floor" { const alloc = std.testing.allocator; const attempt_flow = "row-00\nrow-01\nrow-02\nrow-03\n" ++ "row-04\nrow-05\nrow-06\nrow-07\n"; const grown_flow = attempt_flow ++ "row-08\nrow-09\nrow-10\nrow-11\n"; const finality_floor = "row-00\nrow-01\nrow-02\nrow-03\n".len; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(20, 8, 4), .owned_top_row = 1, .transcript_commit_state = .{ .recovering = .{ .accepted_visual_offset = 4, .accepted_history_visual_offset = 4, .attempt_visual_offset = 4, .attempt_total_visual_rows = 8, .materialized_visual_offset = 4, .materialized_visual_rows = 4, .materialized_flow_len = attempt_flow.len, .attempt_cols = 20, .tracks_semantic_progress = true, .flow = try alloc.dupe(u8, attempt_flow), .cursor_row = 4, .cursor_col = 1, } }, }; defer runtime.deinit(alloc); var source = try testSource(alloc, grown_flow, runtime.layout.cols); defer source.deinit(alloc); source.finality.mutation_pin_start = finality_floor; var prepared = try prepareTestSourceForCurrentArea(&runtime, alloc, &source); defer prepared.deinit(alloc); const facts = runtime.planTranscriptScroll(&prepared); try std.testing.expect(facts.source_compatible); try std.testing.expect(facts.recovery_progress_compatible); try std.testing.expectEqual(@as(u32, 8), facts.target_visual_offset); try std.testing.expectEqual(@as(u32, 0), facts.semantic_rows); try std.testing.expectEqual(@as(u32, 0), facts.semantic_progress_rows); try std.testing.expectEqual(@as(u16, 0), facts.planned_rows); } test "pending replacement notice holds release until the finished replacement settles" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(24, 10, 4), .owned_top_row = 1, }; defer runtime.deinit(alloc); for (0..4) |index| { var line: [24]u8 = undefined; const text = try std.fmt.bufPrint(&line, "history {d:0>2}\n", .{index}); _ = try runtime.appendRawTranscriptEntry(alloc, text); } var base_source = try runtime.prepareTranscriptSource(alloc, null); defer base_source.deinit(alloc); var base_prepared = try prepareTestSourceForCurrentArea(&runtime, alloc, &base_source); defer base_prepared.deinit(alloc); try commitPreparedForTest(&runtime, alloc, &base_source, &base_prepared); const notice_id = try runtime.appendReplaceableSemanticNotice(alloc, .{ .topic = "feedback", .tone = .neutral, .body = "Preparing feedback report while external work is blocking", }); for (0..6) |index| { var line: [24]u8 = undefined; const text = try std.fmt.bufPrint(&line, "later {d:0>2}\n", .{index}); _ = try runtime.appendRawTranscriptEntry(alloc, text); } var live_source = try runtime.prepareTranscriptSource(alloc, null); defer live_source.deinit(alloc); var live_prepared = try prepareTestSourceForCurrentArea(&runtime, alloc, &live_source); defer live_prepared.deinit(alloc); try commitPreparedForTest(&runtime, alloc, &live_source, &live_prepared); const held = runtime.transcript_commit_state.stable; try std.testing.expect(held.visual_offset > held.history_visual_offset); // Quiet frames while the producer blocks release nothing. var quiet_source = try runtime.prepareTranscriptSource(alloc, null); defer quiet_source.deinit(alloc); var quiet_prepared = try prepareTestSourceForCurrentArea(&runtime, alloc, &quiet_source); defer quiet_prepared.deinit(alloc); const quiet_facts = runtime.planTranscriptScroll(&quiet_prepared); try std.testing.expect(quiet_facts.source_compatible); try std.testing.expectEqual(@as(u32, 0), quiet_facts.semantic_rows); try std.testing.expect(quiet_facts.finality_hold); // Finishing clears the pin and rebases its replacement before planning. // Compatibility permits release; the frame receipt must still accept it. try std.testing.expect(try runtime.replaceSemanticNotice(alloc, notice_id, .{ .topic = "feedback", .tone = .success, .body = "Feedback report ready", })); var replaced_source = try runtime.prepareTranscriptSource(alloc, null); defer replaced_source.deinit(alloc); var replaced_prepared = try prepareTestSourceForCurrentArea(&runtime, alloc, &replaced_source); defer replaced_prepared.deinit(alloc); const replaced_facts = runtime.planTranscriptScroll(&replaced_prepared); try std.testing.expect(replaced_source.finality.mutation_pin_start == null); try std.testing.expect(replaced_facts.source_compatible); try std.testing.expect(replaced_facts.semantic_rows > 0); try std.testing.expect(replaced_facts.planned_rows > 0); const before_release = runtime.transcriptCommitDiagnostic().history_visual_offset; try commitPreparedForTest(&runtime, alloc, &replaced_source, &replaced_prepared); try std.testing.expectEqual(@min(replaced_facts.semantic_rows, @as(u32, replaced_facts.planned_rows)), runtime.transcriptCommitDiagnostic().history_visual_offset - before_release); try std.testing.expectEqual(replaced_facts.source_visual_offset + @min(replaced_facts.semantic_progress_rows, @as(u32, replaced_facts.planned_rows)), runtime.transcriptCommitDiagnostic().visual_offset); var settle_source = try runtime.prepareTranscriptSource(alloc, null); defer settle_source.deinit(alloc); try std.testing.expect( std.mem.find(u8, settle_source.bytes, "report ready") != null, ); var settle_prepared = try prepareTestSourceForCurrentArea(&runtime, alloc, &settle_source); defer settle_prepared.deinit(alloc); const settle_facts = runtime.planTranscriptScroll(&settle_prepared); try std.testing.expect(settle_facts.source_compatible); try std.testing.expect(settle_facts.semantic_rows > 0); try commitPreparedForTest(&runtime, alloc, &settle_source, &settle_prepared); const settled = runtime.transcript_commit_state.stable; try std.testing.expectEqual(settled.visual_offset, settled.history_visual_offset); } fn appendRecordedWriteForFinalityTest( runtime: *TranscriptRuntime, alloc: Allocator, lifecycle_id: ?types.ToolLifecycleId, ) !u32 { const entry_id = try runtime.appendRawTranscriptEntryClassified(alloc, "● Wrote receipt.txt\n", .tool_status); try runtime.attachHistoricalToolDetailWithLifecycle( alloc, entry_id, .{ .id = "saved-write", .name = "write_file", .arguments_json = "{\"path\":\"receipt.txt\"}" }, .write, .{ .tool_call_id = @constCast("saved-write"), .tool_name = @constCast("write_file"), .status = .success, .output = @constCast("saved result"), .output_bytes = 12, .stored_output_bytes = 12, }, lifecycle_id, ); return entry_id; } test "recorded tool finality does not depend on the current turn watermark" { const alloc = std.testing.allocator; for ([_]?u64{ null, 1, 2, 50_000 }) |turn_id| { var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(88, 24, 20) }; defer runtime.deinit(alloc); const id = try appendRecordedWriteForFinalityTest(&runtime, alloc, if (turn_id) |turn| .{ .turn_id = turn, .call_id = "saved-write" } else null); var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); try std.testing.expectEqual(@as(?usize, null), runtime.transcript_release.finality_floor(source.finality, 0, null)); if (turn_id) |turn| { const archived_id = runtime.toolDetailForEntry(id).?.lifecycle_id.?; try std.testing.expectEqual(turn, archived_id.turn_id); try std.testing.expectEqualStrings("saved-write", archived_id.call_id); } } } test "recorded tool finality stays independent of a live turn with the same number" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(88, 24, 20) }; defer runtime.deinit(alloc); _ = try appendRecordedWriteForFinalityTest(&runtime, alloc, .{ .turn_id = 1, .call_id = "saved-write" }); _ = try runtime.appendRawTranscriptEntry(alloc, "Saved response\n"); const live_id = types.ToolLifecycleId{ .turn_id = 1, .call_id = "live-write" }; _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = live_id, .reconciles_provisional_call_id = null, .tool_name = "write_file", .activity_kind = .write, } }); for ([_]bool{ false, true }) |terminal| { if (terminal) _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = live_id, .outcome = .{ .kind = .completed, .summary = "Wrote live file" }, } }); var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); const floor = runtime.transcript_release.finality_floor(source.finality, 0, null) orelse return error.TestExpectedLiveFinalityFloor; try std.testing.expect(floor > 0); try std.testing.expect(std.mem.find(u8, source.bytes[0..floor], "Saved response") != null); } _ = try runtime.applyToolLifecycle(alloc, .{ .turn_finished = .{ .turn_id = 1, .outcome = .completed } }); try runtime.finishLifecycleBatch(alloc); var finished = try runtime.prepareTranscriptSource(alloc, null); defer finished.deinit(alloc); try std.testing.expectEqual(@as(?usize, null), runtime.transcript_release.finality_floor(finished.finality, runtime.finalizedToolTurnWatermark(), null)); } test "recorded tool finality survives an owned snapshot after source retirement" { const alloc = std.testing.allocator; var restored = TranscriptRuntime{ .layout = transcriptTestLayout(88, 24, 20) }; defer restored.deinit(alloc); const entry_id = try restored.appendRawTranscriptEntryClassified(alloc, "● Wrote receipt.txt\n", .tool_status); { var original = TranscriptRuntime{ .layout = transcriptTestLayout(88, 24, 20) }; defer original.deinit(alloc); const id = try appendRecordedWriteForFinalityTest(&original, alloc, .{ .turn_id = 2, .call_id = "saved-write" }); var detail = try transcript_store.cloneToolDetailForSnapshot(alloc, original.toolDetailForEntry(id).?.*); errdefer detail.deinit(alloc); detail.entry_id = entry_id; try restored.tool_details.append(alloc, detail); } var source = try restored.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); try std.testing.expectEqual(@as(?usize, null), restored.transcript_release.finality_floor(source.finality, 0, null)); const detail = restored.toolDetailForEntry(entry_id).?; try std.testing.expectEqual(@as(u64, 2), detail.lifecycle_id.?.turn_id); try std.testing.expectEqualStrings("saved-write", detail.lifecycle_id.?.call_id); try std.testing.expectEqualStrings("saved result", detail.result.?); } test "finality candidates keep tool turn and replaceable tail offsets independent" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(60, 12, 8), .owned_top_row = 1, }; defer runtime.deinit(alloc); const id = types.ToolLifecycleId{ .turn_id = 5, .call_id = "boundary-call" }; _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = id, .presentation_group_id = .{ .turn_id = 5, .anchor_step_id = 1 }, .reconciles_provisional_call_id = null, .tool_name = "read_file", .activity_kind = .read, } }); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = id, .outcome = .{ .kind = .completed, .summary = "Read boundary file" }, } }); try runtime.finishLifecycleBatch(alloc); _ = try runtime.appendRawTranscriptEntry(alloc, "settled context line\n"); _ = try transcript_store.appendReplaceableTranscriptLineSilent( &runtime, alloc, "working status line", ); var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); try std.testing.expectEqual(@as(usize, 1), source.finality.tool_turn_floors.len); const group_floor = source.finality.tool_turn_floors[0]; try std.testing.expectEqual(@as(u64, 5), group_floor.turn_id); try std.testing.expect(group_floor.start_byte < source.bytes.len); // The replaceable-tail contract keeps its own offset; the finality // boundary does not repurpose it. try std.testing.expect(source.replaceable_last_line); try std.testing.expect(source.replaceable_start > group_floor.start_byte); try std.testing.expect(source.replaceable_start < source.bytes.len); } fn applyCompletedReadForFinalityTest( runtime: *TranscriptRuntime, alloc: Allocator, turn_id: u64, call_id: []const u8, presentation_group_id: ?types.ToolPresentationGroupId, ) !void { const id = types.ToolLifecycleId{ .turn_id = turn_id, .call_id = call_id }; _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = id, .presentation_group_id = presentation_group_id, .reconciles_provisional_call_id = null, .tool_name = "read_file", .activity_kind = .read, } }); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = id, .outcome = .{ .kind = .completed, .summary = "Read finality fixture" }, } }); } test "finality candidates anchor a fully grouped turn at its newest rendered group" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(80, 14, 10), .owned_top_row = 1, }; defer runtime.deinit(alloc); const group_a = types.ToolPresentationGroupId{ .turn_id = 9, .anchor_step_id = 1 }; for ([_][]const u8{ "group-a-1", "group-a-2", "group-a-3" }) |call_id| { try applyCompletedReadForFinalityTest(&runtime, alloc, 9, call_id, group_a); } _ = try runtime.appendRawTranscriptEntry(alloc, "SECOND_GROUP_INTRO\n"); const group_b = types.ToolPresentationGroupId{ .turn_id = 9, .anchor_step_id = 2 }; try applyCompletedReadForFinalityTest(&runtime, alloc, 9, "group-b-1", group_b); var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); try std.testing.expectEqual(@as(usize, 1), source.finality.tool_turn_floors.len); try std.testing.expect(source.finality.mutation_pin_start == null); const group_a_header = try std.fmt.allocPrint( alloc, "{s}●\x1b[0m {s}3 tool calls · 3 read\x1b[0m", .{ user_message_card.promptMarkerStyle(), ui_render.statusline_style }, ); defer alloc.free(group_a_header); const group_b_header = try std.fmt.allocPrint( alloc, "{s}●\x1b[0m {s}1 tool call · 1 read\x1b[0m", .{ user_message_card.promptMarkerStyle(), ui_render.statusline_style }, ); defer alloc.free(group_b_header); const group_a_start = std.mem.find(u8, source.bytes, group_a_header) orelse return error.TestExpectedGroupAHeader; const group_b_start = std.mem.find(u8, source.bytes, group_b_header) orelse return error.TestExpectedGroupBHeader; try std.testing.expect(group_a_start < group_b_start); const floor = source.finality.tool_turn_floors[0]; try std.testing.expectEqual(@as(u64, 9), floor.turn_id); try std.testing.expectEqual(group_b_start, floor.start_byte); } test "finality candidates keep a mixed legacy turn at its earliest rendered tool row" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(80, 14, 10), .owned_top_row = 1, }; defer runtime.deinit(alloc); const group_a = types.ToolPresentationGroupId{ .turn_id = 10, .anchor_step_id = 1 }; try applyCompletedReadForFinalityTest(&runtime, alloc, 10, "mixed-group-a", group_a); try applyCompletedReadForFinalityTest(&runtime, alloc, 10, "mixed-legacy", null); _ = try runtime.appendRawTranscriptEntry(alloc, "LATEST_GROUP_INTRO\n"); const group_b = types.ToolPresentationGroupId{ .turn_id = 10, .anchor_step_id = 2 }; try applyCompletedReadForFinalityTest(&runtime, alloc, 10, "mixed-group-b", group_b); var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); try std.testing.expectEqual(@as(usize, 1), source.finality.tool_turn_floors.len); try std.testing.expect(source.finality.mutation_pin_start == null); const group_a_header = try std.fmt.allocPrint( alloc, "{s}●\x1b[0m {s}1 tool call · 1 read\x1b[0m", .{ user_message_card.promptMarkerStyle(), ui_render.statusline_style }, ); defer alloc.free(group_a_header); const earliest_tool_start = std.mem.find(u8, source.bytes, group_a_header) orelse return error.TestExpectedGroupAHeader; const floor = source.finality.tool_turn_floors[0]; try std.testing.expectEqual(@as(u64, 10), floor.turn_id); try std.testing.expectEqual(earliest_tool_start, floor.start_byte); } test "finality candidates retain the global pin for an unidentified tool row" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(60, 12, 8), .owned_top_row = 1, }; defer runtime.deinit(alloc); _ = try transcript_store.appendPinnedToolStatusAtomic( &runtime, alloc, "● Running unidentified tool\n", ); var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); try std.testing.expectEqual(@as(usize, 0), source.finality.tool_turn_floors.len); try std.testing.expect(source.finality.mutation_pin_start != null); } test "grouped completed command rows shell-highlight quoted strings over the row base" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(100, 14, 10), .owned_top_row = 1, }; defer runtime.deinit(alloc); const id = types.ToolLifecycleId{ .turn_id = 1, .call_id = "cmd-1" }; _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = id, .presentation_group_id = null, .reconciles_provisional_call_id = null, .tool_name = "shell", .activity_kind = .command, } }); try runtime.setToolCommandMetadata(alloc, id, "printf 'hello world'", "Ran"); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = id, .outcome = .{ .kind = .completed, .summary = "Ran printf 'hello world'" }, } }); var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); // The action label keeps the row's ambient style; the command verb and // quoted string pick up syntax palette colors and return to the row base // after their closes. try std.testing.expect(std.mem.find(u8, source.bytes, "Ran \x1b[38;5;245m\x1b[38;5;252mprintf\x1b[39m\x1b[38;5;245m") != null); try std.testing.expect(std.mem.find(u8, source.bytes, "\x1b[38;5;250m'hello world'\x1b[39m\x1b[38;5;245m") != null); } test "multi-word command labels stay intact when a syntax token follows" { const alloc = std.testing.allocator; var runtime = TranscriptRuntime{ .layout = transcriptTestLayout(100, 14, 10), .owned_top_row = 1, }; defer runtime.deinit(alloc); const id = types.ToolLifecycleId{ .turn_id = 1, .call_id = "cmd-timeout" }; _ = try runtime.applyToolLifecycle(alloc, .{ .authoritative_started = .{ .id = id, .presentation_group_id = null, .reconciles_provisional_call_id = null, .tool_name = "shell", .activity_kind = .command, } }); try runtime.setToolCommandMetadata(alloc, id, "sleep 5", "Timed out"); _ = try runtime.applyToolLifecycle(alloc, .{ .terminal = .{ .id = id, .outcome = .{ .kind = .completed, .summary = "Timed out sleep 5" }, } }); var source = try runtime.prepareTranscriptSource(alloc, null); defer source.deinit(alloc); // A first-space split would open the base style between "Timed" and // "out"; the recorded label keeps both words ahead of the base open. try std.testing.expect(std.mem.find(u8, source.bytes, "Timed out \x1b[38;5;245m") != null); // The command word colors; the bare number argument stays plain. try std.testing.expect(std.mem.find(u8, source.bytes, "\x1b[38;5;252msleep\x1b[39m") != null); try std.testing.expect(std.mem.find(u8, source.bytes, "\x1b[38;5;250m5\x1b[39m") == null); }