//! Grid-level resize tests that replay `TranscriptRuntime` output through//! `vt_emulator.Grid` without a TTY or signals. const std = @import("std"); const question_prompt = @import("../core/agent/question_prompt.zig");const debug_trace = @import("../core/shared/debug_trace.zig");const display_width = @import("../core/shared/display_width.zig");const diff_mod = @import("../core/output/diff.zig");const io_mod = @import("../core/shared/io.zig");const skill_runtime = @import("../core/skills/skill_runtime.zig");const types = @import("../core/shared/types.zig");const assistant_presentation = @import("../core/agent/assistant_presentation.zig");const builtin_commands = @import("../builtins/commands.zig"); const surface_frame = @import("footer/surface_frame.zig");const surface_invalidation = @import("footer/surface_invalidation.zig");const interaction_state = @import("footer/interaction_state.zig");const approval_prompt = @import("../core/permissions/approval_prompt.zig");const render_input = @import("footer/render_input.zig");const footer_viewport = @import("footer/viewport.zig");const activity_runtime = @import("../core/output/activity_runtime.zig");const core_input_runtime = @import("../core/input/runtime.zig");const ui_render = @import("render.zig");const render_engine = @import("render_engine.zig");const render_request = @import("render_request.zig");const shell_runtime = @import("shell_runtime.zig");const shimmer_runtime = @import("transcript/shimmer_runtime.zig");const transcript_painter = @import("transcript/painter.zig");const transcript_runtime = @import("transcript/runtime.zig");const full_transcript_screen = @import("full_transcript_screen.zig");const vt_emulator = @import("../core/terminal/engine.zig"); const Allocator = std.mem.Allocator;const Layout = types.Layout;const Metrics = types.Metrics;const TranscriptPreparationSource = transcript_runtime.TranscriptPreparationSource;const TranscriptRuntime = transcript_runtime.TranscriptRuntime;const Grid = vt_emulator.Grid;const InputRuntime = core_input_runtime.Runtime; const TestBodyDisposition = enum { paint, retain,}; const TestFrameObservation = struct { shadow_state: render_engine.terminal_diff.ShadowCommitState = .committed, body_disposition: TestBodyDisposition = .paint, body_paints: usize = 0, retained_transcript_changed_cells: usize = 0, document_append_bytes: usize = 0, document_append_clear_rows: ?u16 = null, transcript_history_floor_respected: bool = true, planned_scroll_rows: u16 = 0, committed_scroll_rows: u16 = 0, unplanned_scroll_rows: u16 = 0, changed_cells: usize = 0,}; /// Test harness that captures every byte `TranscriptRuntime` emits and/// replays it through a virtual terminal.pub const Harness = struct { alloc: Allocator, tmp: std.testing.TmpDir, file: std.Io.File, path: []u8, read_offset: u64 = 0, vt: Grid, shell: TranscriptRuntime, metrics: Metrics = .{}, frame_redraw: bool = false, last_frame: TestFrameObservation = .{}, pub fn init(alloc: Allocator, cols: u16, rows: u16, footer_rows: u16) !Harness { var tmp = std.testing.tmpDir(.{}); errdefer tmp.cleanup(); const filename = "ansi.log"; var file = try tmp.dir.createFile(std.testing.io, filename, .{ .read = true }); errdefer file.close(io_mod.getIo()); const path = try alloc.dupe(u8, filename); errdefer alloc.free(path); const layout = layoutFor(cols, rows, footer_rows); var harness: Harness = .{ .alloc = alloc, .tmp = tmp, .file = file, .path = path, .vt = try Grid.init(alloc, cols, rows), .shell = .{ .stdout_file = file, .layout = layout, .maxxing_mode = .legacy, }, }; harness.vt.cursor_row = layout.content_bottom; harness.vt.cursor_col = 1; try harness.shell.initBacking(alloc); try harness.shell.enableShadowVt(alloc); return harness; } pub fn deinit(self: *Harness) void { self.shell.deinit(self.alloc); self.file.close(io_mod.getIo()); self.tmp.cleanup(); self.alloc.free(self.path); self.vt.deinit(); } /// Pull any bytes written since the last flush into the VT grid. pub fn flush(self: *Harness) !void { const total = try self.file.length(io_mod.getIo()); if (total <= self.read_offset) return; const want: usize = @intCast(total - self.read_offset); const buf = try self.alloc.alloc(u8, want); defer self.alloc.free(buf); const n = try self.file.readPositionalAll(io_mod.getIo(), buf, self.read_offset); try self.vt.feed(buf[0..n]); self.read_offset += n; } pub fn driveResize(self: *Harness, cols: u16, rows: u16, footer_rows: u16, settled: bool) !void { const new_layout = layoutFor(cols, rows, footer_rows); try self.vt.resize(cols, rows); try shell_runtime.applyResizeWithLayout( &self.shell, &self.metrics, new_layout, settled, ); try self.renderTranscriptFrameIfDirty(); try self.flush(); } pub fn renderTranscriptFrame(self: *Harness) !void { try self.renderTranscriptFrameWithOptions(0, null); } pub fn renderTranscriptFrameOmittingEntry(self: *Harness, entry_id: u32) !void { try self.renderTranscriptFrameWithOptions(0, entry_id); } pub fn renderTranscriptFrameIfDirty(self: *Harness) !void { if (!self.shell.transcript_band_dirty and !self.shell.render_requests.hasPending()) return; try self.renderTranscriptFrame(); } pub fn initStartupViewport(self: *Harness, requested_start_row: u16, min_body_rows: u16) !void { const reserve_rows = @min(min_body_rows, self.shell.layout.content_bottom); const max_start_row = if (reserve_rows > 0) self.shell.layout.content_bottom - reserve_rows + 1 else self.shell.layout.content_bottom; if (requested_start_row > max_start_row) { var cursor_buf: [32]u8 = undefined; const cursor = try std.fmt.bufPrint(&cursor_buf, "\x1b[{d};1H", .{self.shell.layout.rows}); try self.file.writeStreamingAll(io_mod.getIo(), cursor); var row: u16 = 0; while (row < requested_start_row - max_start_row) : (row += 1) { try self.file.writeStreamingAll(io_mod.getIo(), "\n"); } } try self.shell.initViewportWithReservedRows(&self.metrics, requested_start_row, min_body_rows); } pub fn renderTranscriptFrameWithBottomReservation(self: *Harness, bottom_reserved_rows: u16) !void { try self.renderTranscriptFrameWithOptions(bottom_reserved_rows, null); } fn renderTranscriptFrameWithOptions( self: *Harness, bottom_reserved_rows: u16, omitted_entry_id: ?u32, ) !void { if (!self.shell.render_requests.hasPending()) self.shell.render_requests.request(.transcript); var attempt = (try self.shell.render_requests.beginAttempt()).?; defer attempt.deinit(); const snapshot = attempt.snapshot; var source = if (self.shell.fullTranscriptActive()) try self.prepareFullTranscriptSource() else if (omitted_entry_id) |entry_id| try self.shell.prepareTranscriptSourceOmittingEntry(self.alloc, entry_id) else try self.shell.prepareTranscriptSource(self.alloc, null); defer source.deinit(self.alloc); const reserved_rows = @min(bottom_reserved_rows, self.shell.layout.content_bottom -| 1); const footer_height = self.shell.layout.rows - self.shell.layout.divider_top_row + 1; var prepared: ?transcript_painter.PreparedTranscriptSurfacePaint = null; defer if (prepared) |*paint| paint.deinit(self.alloc); var fixed_point_ctx = ReservedTranscriptSolveContext{ .h = self, .source = &source, .prepared = &prepared, .reserved_rows = reserved_rows, }; const fixed_point = try render_engine.frame_fixed_point.solve( ReservedTranscriptSolveContext, &fixed_point_ctx, .{ .terminal = self.shell.layout, .owned_top = self.shell.owned_top_row, .footer = .{ .natural_rows = footer_height +| reserved_rows, .min_rows = footer_height +| reserved_rows, .max_rows = footer_height +| reserved_rows, }, .transcript = source.preview, .prior = self.shell.committed_frame_layout, }, ReservedTranscriptSolveContext.prepareCandidate, ReservedTranscriptSolveContext.resolveCandidate, ); var invalidations = snapshot.invalidations; self.shell.normalizeFrameInvalidations(&invalidations); const paint = &prepared.?; var plan = transcriptOnlyPlan(&self.shell, paint, fixed_point.layout.owned_top, invalidations); var transition = try resolveAndSealTranscriptTransitionForTest( &self.shell, self.alloc, &source, paint, &plan, fixed_point.scroll_plan, ); defer transition.deinit(self.alloc); var paint_ctx = TestFramePaintContext{ .h = self, .prepared = paint, }; const result = try commitTestFrame( self, plan, paint, null, null, fixed_point.scroll_plan, &transition, &paint_ctx, ); if (result == .committed) { attempt.commit(0, render_request.animation_interval_ms, false); } else { attempt.restore(); } } pub fn renderFooterFrame( self: *Harness, approval: *const approval_prompt.ApprovalPrompt, force_redraw: *bool, ctx: render_input.RenderContext, ) !void { if (!self.shell.render_requests.hasPending()) self.shell.render_requests.request(.footer); var attempt = (try self.shell.render_requests.beginAttempt()).?; defer attempt.deinit(); const snapshot = attempt.snapshot; var invalidations = snapshot.invalidations; self.shell.normalizeFrameInvalidations(&invalidations); var measurement = try surface_frame.measureSurfaceFooter(self.alloc, &self.shell, approval.projection(), ctx); defer measurement.deinit(self.alloc); const footer_reservation_changed = measurement.changesFooterReservation(&self.shell); const replay_displaced_footer_history = measurement.replaysDisplacedTranscriptHistory(&self.shell); var prepared: ?transcript_painter.PreparedTranscriptSurfacePaint = null; defer if (prepared) |*paint| paint.deinit(self.alloc); var resolved_target: ?transcript_runtime.TranscriptRuntime.ResolvedTranscriptTarget = null; const activity = frameActivityState(self, &measurement); const tracked_entry_id = switch (measurement.activity_projection) { .tool_slot => |slot| slot.entry_id, .none, .turn_thinking => null, }; const omitted_entry_id = switch (measurement.activity_projection) { .tool_slot => |slot| if (slot.thinking_label != null) slot.entry_id else null, .none, .turn_thinking => null, }; var source = if (self.shell.fullTranscriptActive()) try self.prepareFullTranscriptSource() else if (omitted_entry_id) |entry_id| try self.shell.prepareTranscriptSourceOmittingEntry(self.alloc, entry_id) else try self.shell.prepareTranscriptSource(self.alloc, tracked_entry_id); defer source.deinit(self.alloc); const fixed_point = try solveTestFrame( self, &measurement, activity, &source, &prepared, &resolved_target, footer_reservation_changed, replay_displaced_footer_history, invalidations, ); const footer_rows = measurement.frameLayoutRows(self.shell.layout.rows, fixed_point.layout.footer_area.top); const activity_placement = render_engine.activity_placement.resolve( measurement.activity_projection, activity, fixed_point.layout, ); const activity_hides_cursor = switch (activity_placement) { .transient_row => true, .none, .overlay_entry => false, }; const viewport = if (resolved_target) |target| target.selection() else if (prepared) |*paint| paint.selection else surface_frame.currentSurfaceFooterTranscriptState(&self.shell).selection; const cursor_target = if (resolved_target) |target| render_engine.paint_plan.FrameCursorTarget{ .row = target.cursorRow(), .col = target.cursorCol(), .visible = !activity_hides_cursor, } else if (prepared) |*paint| render_engine.paint_plan.FrameCursorTarget{ .row = paint.cursor.cursor_row, .col = paint.cursor.cursor_col, .visible = !activity_hides_cursor, } else render_engine.paint_plan.FrameCursorTarget{ .row = footer_rows.input_base, .col = 1, .visible = !activity_hides_cursor, }; const plan = fixed_point.layout.toPaintPlan(.{ .footer_rows = footer_rows, .viewport = viewport, .activity = activity_placement, .cursor_target = cursor_target, .reset_terminal = self.shell.terminal_reset_pending, .preserve_scrollback = !self.shell.pending_scroll_compact, }); var footer_frame = try surface_frame.prepareMeasuredSurfaceFooterFrameForPlan( self.alloc, &self.shell, force_redraw, approval.projection(), ctx, &measurement, plan, invalidations, ); defer footer_frame.deinit(self.alloc); var transition: ?transcript_runtime.TranscriptTransition = null; defer if (transition) |*value| value.deinit(self.alloc); if (prepared) |*paint| { const target = resolved_target orelse return error.MissingResolvedTranscriptTarget; transition = try self.shell.sealTranscriptTransition( self.alloc, &source, paint, &footer_frame.paint, target, ); } var paint_ctx = TestFramePaintContext{ .h = self, .prepared = if (prepared) |*paint| paint else null, .footer_frame = &footer_frame, }; const result = try commitTestFrame( self, footer_frame.paint, if (prepared) |*paint| paint else null, &footer_frame, &measurement, fixed_point.scroll_plan, if (transition) |*value| value else null, &paint_ctx, ); if (result == .committed) { attempt.commit(0, render_request.animation_interval_ms, paint_ctx.activity_result.painted); force_redraw.* = false; } else { attempt.restore(); } } fn prepareFullTranscriptSource(self: *Harness) !TranscriptPreparationSource { var projection = try full_transcript_screen.buildProjection( self.alloc, self.shell.entries.items, self.shell.tool_details.items, self.shell.command_output_blocks.items, self.shell.command_output_render.styles, self.shell.layout.cols, self.shell.fullTranscriptAnchorEntryId(), ); defer projection.deinit(self.alloc); const measurement = try full_transcript_screen.measureProjectionInterruptible( self.alloc, &projection, null, self.shell.layout.cols, null, ); const visible_rows: u16 = @intCast(@max(@min(measurement.total_rows, std.math.maxInt(u16)), 1)); const bytes = try full_transcript_screen.renderProjectionViewportSourceInterruptible( self.alloc, &projection, null, self.shell.layout.cols, visible_rows, 0, null, ); return self.shell.prepareFullTranscriptViewportSource(self.alloc, bytes); }}; const ReservedTranscriptSolveContext = struct { h: *Harness, source: *const TranscriptPreparationSource, prepared: *?transcript_painter.PreparedTranscriptSurfacePaint, reserved_rows: u16, fn prepareCandidate( self: *ReservedTranscriptSolveContext, candidate: render_engine.frame_layout.FrameLayout, ) !render_engine.frame_fixed_point.CandidatePreparation { if (self.prepared.*) |*paint| { paint.deinit(self.h.alloc); self.prepared.* = null; } const target_bottom = self.h.shell.layout.content_bottom - self.reserved_rows; if (target_bottom < candidate.owned_top) return error.InvalidFrameScrollPlan; self.prepared.* = try self.h.shell.prepareTranscriptSurfacePaintFromSourceForArea( self.h.alloc, &self.h.metrics, self.source, .{ .top = candidate.owned_top, .bottom = target_bottom }, ); self.prepared.*.?.bottom_reserved_rows = self.reserved_rows; return .{ .inline_advance_rows = self.h.shell.planTranscriptScroll(&self.prepared.*.?).planned_rows, .occupied_transcript_rows = candidate.transcript_area.height(), }; } fn resolveCandidate( self: *ReservedTranscriptSolveContext, candidate: render_engine.frame_layout.FrameLayout, scroll_plan: render_engine.frame_scroll_plan.FrameScrollPlan, ) !render_engine.frame_fixed_point.CandidateResolution { _ = self; _ = scroll_plan; return .{ .occupied_transcript_rows = candidate.transcript_area.height() }; }}; fn solveTestFrame( h: *Harness, measurement: *const surface_frame.SurfaceFooterMeasurement, activity: render_engine.frame_layout.ActivityState, source: *const TranscriptPreparationSource, prepared: *?transcript_painter.PreparedTranscriptSurfacePaint, resolved_target: *?transcript_runtime.TranscriptRuntime.ResolvedTranscriptTarget, footer_reservation_changed: bool, replay_displaced_footer_history: bool, attempt_invalidations: render_engine.paint_plan.FrameInvalidationSet,) !render_engine.frame_fixed_point.FramePlan { var ctx = TestFrameSolveContext{ .h = h, .source = source, .prepared = prepared, .resolved_target = resolved_target, .measurement = measurement, .activity = activity, .footer_reservation_changed = footer_reservation_changed, .replay_displaced_footer_history = replay_displaced_footer_history, .attempt_invalidations = attempt_invalidations, }; return render_engine.frame_fixed_point.solve( TestFrameSolveContext, &ctx, .{ .terminal = h.shell.layout, .owned_top = h.shell.owned_top_row, .footer = measurement.frameLayoutMeasurement(), .transcript = source.preview, .activity = activity, .prior = h.shell.committed_frame_layout, }, TestFrameSolveContext.prepareCandidate, TestFrameSolveContext.resolveCandidate, );} const TestFrameSolveContext = struct {Lines 1–500 of 7662. Load more
//! Grid-level resize tests that replay `TranscriptRuntime` output through //! `vt_emulator.Grid` without a TTY or signals. const std = @import("std"); const question_prompt = @import("../core/agent/question_prompt.zig"); const debug_trace = @import("../core/shared/debug_trace.zig"); const display_width = @import("../core/shared/display_width.zig"); const diff_mod = @import("../core/output/diff.zig"); const io_mod = @import("../core/shared/io.zig"); const skill_runtime = @import("../core/skills/skill_runtime.zig"); const types = @import("../core/shared/types.zig"); const assistant_presentation = @import("../core/agent/assistant_presentation.zig"); const builtin_commands = @import("../builtins/commands.zig"); const surface_frame = @import("footer/surface_frame.zig"); const surface_invalidation = @import("footer/surface_invalidation.zig"); const interaction_state = @import("footer/interaction_state.zig"); const approval_prompt = @import("../core/permissions/approval_prompt.zig"); const render_input = @import("footer/render_input.zig"); const footer_viewport = @import("footer/viewport.zig"); const activity_runtime = @import("../core/output/activity_runtime.zig"); const core_input_runtime = @import("../core/input/runtime.zig"); const ui_render = @import("render.zig"); const render_engine = @import("render_engine.zig"); const render_request = @import("render_request.zig"); const shell_runtime = @import("shell_runtime.zig"); const shimmer_runtime = @import("transcript/shimmer_runtime.zig"); const transcript_painter = @import("transcript/painter.zig"); const transcript_runtime = @import("transcript/runtime.zig"); const full_transcript_screen = @import("full_transcript_screen.zig"); const vt_emulator = @import("../core/terminal/engine.zig"); const Allocator = std.mem.Allocator; const Layout = types.Layout; const Metrics = types.Metrics; const TranscriptPreparationSource = transcript_runtime.TranscriptPreparationSource; const TranscriptRuntime = transcript_runtime.TranscriptRuntime; const Grid = vt_emulator.Grid; const InputRuntime = core_input_runtime.Runtime; const TestBodyDisposition = enum { paint, retain, }; const TestFrameObservation = struct { shadow_state: render_engine.terminal_diff.ShadowCommitState = .committed, body_disposition: TestBodyDisposition = .paint, body_paints: usize = 0, retained_transcript_changed_cells: usize = 0, document_append_bytes: usize = 0, document_append_clear_rows: ?u16 = null, transcript_history_floor_respected: bool = true, planned_scroll_rows: u16 = 0, committed_scroll_rows: u16 = 0, unplanned_scroll_rows: u16 = 0, changed_cells: usize = 0, }; /// Test harness that captures every byte `TranscriptRuntime` emits and /// replays it through a virtual terminal. pub const Harness = struct { alloc: Allocator, tmp: std.testing.TmpDir, file: std.Io.File, path: []u8, read_offset: u64 = 0, vt: Grid, shell: TranscriptRuntime, metrics: Metrics = .{}, frame_redraw: bool = false, last_frame: TestFrameObservation = .{}, pub fn init(alloc: Allocator, cols: u16, rows: u16, footer_rows: u16) !Harness { var tmp = std.testing.tmpDir(.{}); errdefer tmp.cleanup(); const filename = "ansi.log"; var file = try tmp.dir.createFile(std.testing.io, filename, .{ .read = true }); errdefer file.close(io_mod.getIo()); const path = try alloc.dupe(u8, filename); errdefer alloc.free(path); const layout = layoutFor(cols, rows, footer_rows); var harness: Harness = .{ .alloc = alloc, .tmp = tmp, .file = file, .path = path, .vt = try Grid.init(alloc, cols, rows), .shell = .{ .stdout_file = file, .layout = layout, .maxxing_mode = .legacy, }, }; harness.vt.cursor_row = layout.content_bottom; harness.vt.cursor_col = 1; try harness.shell.initBacking(alloc); try harness.shell.enableShadowVt(alloc); return harness; } pub fn deinit(self: *Harness) void { self.shell.deinit(self.alloc); self.file.close(io_mod.getIo()); self.tmp.cleanup(); self.alloc.free(self.path); self.vt.deinit(); } /// Pull any bytes written since the last flush into the VT grid. pub fn flush(self: *Harness) !void { const total = try self.file.length(io_mod.getIo()); if (total <= self.read_offset) return; const want: usize = @intCast(total - self.read_offset); const buf = try self.alloc.alloc(u8, want); defer self.alloc.free(buf); const n = try self.file.readPositionalAll(io_mod.getIo(), buf, self.read_offset); try self.vt.feed(buf[0..n]); self.read_offset += n; } pub fn driveResize(self: *Harness, cols: u16, rows: u16, footer_rows: u16, settled: bool) !void { const new_layout = layoutFor(cols, rows, footer_rows); try self.vt.resize(cols, rows); try shell_runtime.applyResizeWithLayout( &self.shell, &self.metrics, new_layout, settled, ); try self.renderTranscriptFrameIfDirty(); try self.flush(); } pub fn renderTranscriptFrame(self: *Harness) !void { try self.renderTranscriptFrameWithOptions(0, null); } pub fn renderTranscriptFrameOmittingEntry(self: *Harness, entry_id: u32) !void { try self.renderTranscriptFrameWithOptions(0, entry_id); } pub fn renderTranscriptFrameIfDirty(self: *Harness) !void { if (!self.shell.transcript_band_dirty and !self.shell.render_requests.hasPending()) return; try self.renderTranscriptFrame(); } pub fn initStartupViewport(self: *Harness, requested_start_row: u16, min_body_rows: u16) !void { const reserve_rows = @min(min_body_rows, self.shell.layout.content_bottom); const max_start_row = if (reserve_rows > 0) self.shell.layout.content_bottom - reserve_rows + 1 else self.shell.layout.content_bottom; if (requested_start_row > max_start_row) { var cursor_buf: [32]u8 = undefined; const cursor = try std.fmt.bufPrint(&cursor_buf, "\x1b[{d};1H", .{self.shell.layout.rows}); try self.file.writeStreamingAll(io_mod.getIo(), cursor); var row: u16 = 0; while (row < requested_start_row - max_start_row) : (row += 1) { try self.file.writeStreamingAll(io_mod.getIo(), "\n"); } } try self.shell.initViewportWithReservedRows(&self.metrics, requested_start_row, min_body_rows); } pub fn renderTranscriptFrameWithBottomReservation(self: *Harness, bottom_reserved_rows: u16) !void { try self.renderTranscriptFrameWithOptions(bottom_reserved_rows, null); } fn renderTranscriptFrameWithOptions( self: *Harness, bottom_reserved_rows: u16, omitted_entry_id: ?u32, ) !void { if (!self.shell.render_requests.hasPending()) self.shell.render_requests.request(.transcript); var attempt = (try self.shell.render_requests.beginAttempt()).?; defer attempt.deinit(); const snapshot = attempt.snapshot; var source = if (self.shell.fullTranscriptActive()) try self.prepareFullTranscriptSource() else if (omitted_entry_id) |entry_id| try self.shell.prepareTranscriptSourceOmittingEntry(self.alloc, entry_id) else try self.shell.prepareTranscriptSource(self.alloc, null); defer source.deinit(self.alloc); const reserved_rows = @min(bottom_reserved_rows, self.shell.layout.content_bottom -| 1); const footer_height = self.shell.layout.rows - self.shell.layout.divider_top_row + 1; var prepared: ?transcript_painter.PreparedTranscriptSurfacePaint = null; defer if (prepared) |*paint| paint.deinit(self.alloc); var fixed_point_ctx = ReservedTranscriptSolveContext{ .h = self, .source = &source, .prepared = &prepared, .reserved_rows = reserved_rows, }; const fixed_point = try render_engine.frame_fixed_point.solve( ReservedTranscriptSolveContext, &fixed_point_ctx, .{ .terminal = self.shell.layout, .owned_top = self.shell.owned_top_row, .footer = .{ .natural_rows = footer_height +| reserved_rows, .min_rows = footer_height +| reserved_rows, .max_rows = footer_height +| reserved_rows, }, .transcript = source.preview, .prior = self.shell.committed_frame_layout, }, ReservedTranscriptSolveContext.prepareCandidate, ReservedTranscriptSolveContext.resolveCandidate, ); var invalidations = snapshot.invalidations; self.shell.normalizeFrameInvalidations(&invalidations); const paint = &prepared.?; var plan = transcriptOnlyPlan(&self.shell, paint, fixed_point.layout.owned_top, invalidations); var transition = try resolveAndSealTranscriptTransitionForTest( &self.shell, self.alloc, &source, paint, &plan, fixed_point.scroll_plan, ); defer transition.deinit(self.alloc); var paint_ctx = TestFramePaintContext{ .h = self, .prepared = paint, }; const result = try commitTestFrame( self, plan, paint, null, null, fixed_point.scroll_plan, &transition, &paint_ctx, ); if (result == .committed) { attempt.commit(0, render_request.animation_interval_ms, false); } else { attempt.restore(); } } pub fn renderFooterFrame( self: *Harness, approval: *const approval_prompt.ApprovalPrompt, force_redraw: *bool, ctx: render_input.RenderContext, ) !void { if (!self.shell.render_requests.hasPending()) self.shell.render_requests.request(.footer); var attempt = (try self.shell.render_requests.beginAttempt()).?; defer attempt.deinit(); const snapshot = attempt.snapshot; var invalidations = snapshot.invalidations; self.shell.normalizeFrameInvalidations(&invalidations); var measurement = try surface_frame.measureSurfaceFooter(self.alloc, &self.shell, approval.projection(), ctx); defer measurement.deinit(self.alloc); const footer_reservation_changed = measurement.changesFooterReservation(&self.shell); const replay_displaced_footer_history = measurement.replaysDisplacedTranscriptHistory(&self.shell); var prepared: ?transcript_painter.PreparedTranscriptSurfacePaint = null; defer if (prepared) |*paint| paint.deinit(self.alloc); var resolved_target: ?transcript_runtime.TranscriptRuntime.ResolvedTranscriptTarget = null; const activity = frameActivityState(self, &measurement); const tracked_entry_id = switch (measurement.activity_projection) { .tool_slot => |slot| slot.entry_id, .none, .turn_thinking => null, }; const omitted_entry_id = switch (measurement.activity_projection) { .tool_slot => |slot| if (slot.thinking_label != null) slot.entry_id else null, .none, .turn_thinking => null, }; var source = if (self.shell.fullTranscriptActive()) try self.prepareFullTranscriptSource() else if (omitted_entry_id) |entry_id| try self.shell.prepareTranscriptSourceOmittingEntry(self.alloc, entry_id) else try self.shell.prepareTranscriptSource(self.alloc, tracked_entry_id); defer source.deinit(self.alloc); const fixed_point = try solveTestFrame( self, &measurement, activity, &source, &prepared, &resolved_target, footer_reservation_changed, replay_displaced_footer_history, invalidations, ); const footer_rows = measurement.frameLayoutRows(self.shell.layout.rows, fixed_point.layout.footer_area.top); const activity_placement = render_engine.activity_placement.resolve( measurement.activity_projection, activity, fixed_point.layout, ); const activity_hides_cursor = switch (activity_placement) { .transient_row => true, .none, .overlay_entry => false, }; const viewport = if (resolved_target) |target| target.selection() else if (prepared) |*paint| paint.selection else surface_frame.currentSurfaceFooterTranscriptState(&self.shell).selection; const cursor_target = if (resolved_target) |target| render_engine.paint_plan.FrameCursorTarget{ .row = target.cursorRow(), .col = target.cursorCol(), .visible = !activity_hides_cursor, } else if (prepared) |*paint| render_engine.paint_plan.FrameCursorTarget{ .row = paint.cursor.cursor_row, .col = paint.cursor.cursor_col, .visible = !activity_hides_cursor, } else render_engine.paint_plan.FrameCursorTarget{ .row = footer_rows.input_base, .col = 1, .visible = !activity_hides_cursor, }; const plan = fixed_point.layout.toPaintPlan(.{ .footer_rows = footer_rows, .viewport = viewport, .activity = activity_placement, .cursor_target = cursor_target, .reset_terminal = self.shell.terminal_reset_pending, .preserve_scrollback = !self.shell.pending_scroll_compact, }); var footer_frame = try surface_frame.prepareMeasuredSurfaceFooterFrameForPlan( self.alloc, &self.shell, force_redraw, approval.projection(), ctx, &measurement, plan, invalidations, ); defer footer_frame.deinit(self.alloc); var transition: ?transcript_runtime.TranscriptTransition = null; defer if (transition) |*value| value.deinit(self.alloc); if (prepared) |*paint| { const target = resolved_target orelse return error.MissingResolvedTranscriptTarget; transition = try self.shell.sealTranscriptTransition( self.alloc, &source, paint, &footer_frame.paint, target, ); } var paint_ctx = TestFramePaintContext{ .h = self, .prepared = if (prepared) |*paint| paint else null, .footer_frame = &footer_frame, }; const result = try commitTestFrame( self, footer_frame.paint, if (prepared) |*paint| paint else null, &footer_frame, &measurement, fixed_point.scroll_plan, if (transition) |*value| value else null, &paint_ctx, ); if (result == .committed) { attempt.commit(0, render_request.animation_interval_ms, paint_ctx.activity_result.painted); force_redraw.* = false; } else { attempt.restore(); } } fn prepareFullTranscriptSource(self: *Harness) !TranscriptPreparationSource { var projection = try full_transcript_screen.buildProjection( self.alloc, self.shell.entries.items, self.shell.tool_details.items, self.shell.command_output_blocks.items, self.shell.command_output_render.styles, self.shell.layout.cols, self.shell.fullTranscriptAnchorEntryId(), ); defer projection.deinit(self.alloc); const measurement = try full_transcript_screen.measureProjectionInterruptible( self.alloc, &projection, null, self.shell.layout.cols, null, ); const visible_rows: u16 = @intCast(@max(@min(measurement.total_rows, std.math.maxInt(u16)), 1)); const bytes = try full_transcript_screen.renderProjectionViewportSourceInterruptible( self.alloc, &projection, null, self.shell.layout.cols, visible_rows, 0, null, ); return self.shell.prepareFullTranscriptViewportSource(self.alloc, bytes); } }; const ReservedTranscriptSolveContext = struct { h: *Harness, source: *const TranscriptPreparationSource, prepared: *?transcript_painter.PreparedTranscriptSurfacePaint, reserved_rows: u16, fn prepareCandidate( self: *ReservedTranscriptSolveContext, candidate: render_engine.frame_layout.FrameLayout, ) !render_engine.frame_fixed_point.CandidatePreparation { if (self.prepared.*) |*paint| { paint.deinit(self.h.alloc); self.prepared.* = null; } const target_bottom = self.h.shell.layout.content_bottom - self.reserved_rows; if (target_bottom < candidate.owned_top) return error.InvalidFrameScrollPlan; self.prepared.* = try self.h.shell.prepareTranscriptSurfacePaintFromSourceForArea( self.h.alloc, &self.h.metrics, self.source, .{ .top = candidate.owned_top, .bottom = target_bottom }, ); self.prepared.*.?.bottom_reserved_rows = self.reserved_rows; return .{ .inline_advance_rows = self.h.shell.planTranscriptScroll(&self.prepared.*.?).planned_rows, .occupied_transcript_rows = candidate.transcript_area.height(), }; } fn resolveCandidate( self: *ReservedTranscriptSolveContext, candidate: render_engine.frame_layout.FrameLayout, scroll_plan: render_engine.frame_scroll_plan.FrameScrollPlan, ) !render_engine.frame_fixed_point.CandidateResolution { _ = self; _ = scroll_plan; return .{ .occupied_transcript_rows = candidate.transcript_area.height() }; } }; fn solveTestFrame( h: *Harness, measurement: *const surface_frame.SurfaceFooterMeasurement, activity: render_engine.frame_layout.ActivityState, source: *const TranscriptPreparationSource, prepared: *?transcript_painter.PreparedTranscriptSurfacePaint, resolved_target: *?transcript_runtime.TranscriptRuntime.ResolvedTranscriptTarget, footer_reservation_changed: bool, replay_displaced_footer_history: bool, attempt_invalidations: render_engine.paint_plan.FrameInvalidationSet, ) !render_engine.frame_fixed_point.FramePlan { var ctx = TestFrameSolveContext{ .h = h, .source = source, .prepared = prepared, .resolved_target = resolved_target, .measurement = measurement, .activity = activity, .footer_reservation_changed = footer_reservation_changed, .replay_displaced_footer_history = replay_displaced_footer_history, .attempt_invalidations = attempt_invalidations, }; return render_engine.frame_fixed_point.solve( TestFrameSolveContext, &ctx, .{ .terminal = h.shell.layout, .owned_top = h.shell.owned_top_row, .footer = measurement.frameLayoutMeasurement(), .transcript = source.preview, .activity = activity, .prior = h.shell.committed_frame_layout, }, TestFrameSolveContext.prepareCandidate, TestFrameSolveContext.resolveCandidate, ); } const TestFrameSolveContext = struct {