Skip to content

feat(tui): clickable plan-step detail + plan/sidebar UX & rendering polish - #315

Merged
gnanam1990 merged 30 commits into
mainfrom
feat/plan-step-detail
Jun 25, 2026
Merged

feat(tui): clickable plan-step detail + plan/sidebar UX & rendering polish#315
gnanam1990 merged 30 commits into
mainfrom
feat/plan-step-detail

Conversation

@gnanam1990

@gnanam1990 gnanam1990 commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

A focused set of TUI improvements centered on the plan panel and context sidebar, plus several rendering-polish fixes. The headline feature makes plan steps clickable to surface a plain-English explanation of each step; the rest tightens correctness (a finished task now shows its plan as complete) and removes visual noise (a glow animation and a shaded card band).

What's in this PR

Clickable plan-step detail (new feature)

  • Click any step in the PLAN sidebar to drop a detail card into the transcript explaining that step.
  • The card is status-aware: a finished step reads "what we did" (outcome, duration, the captured file changes, commands run, and the agent's own narration); a not-yet-finished step reads "what we'll do" (its intent / planned approach).
  • The natural-language explanation is written on demand by the model when you click (never pre-computed or run in the background). It shows a brief "Writing explanation…" line, then updates in place, and is cached per step so a re-click is instant. With no model provider configured it falls back to an instant local summary.
  • Click-to-toggle: re-clicking the open step hides the card (no stacking duplicates); clicking another step switches.
  • Minimal card styling: dim grey border and a status-tinted title (green done / red failed / neutral otherwise) instead of the loud accent treatment.

Plan panel correctness

  • The panel now reflects live progress: the header names the step actually in progress (not always step 1), a reworded step keeps its elapsed clock, and the system-prompt cadence guidance was tightened.
  • When a task finishes but the model forgot the final update_plan, the panel now reconciles the remaining steps to complete (so it shows "PLAN COMPLETE" instead of freezing mid-progress). Guarded to fire only on a genuine successful turn — never on an error, cancellation, or a mid-plan pause (ask-user / permission / spec-review). A legitimately failed step keeps its failed status.

Context sidebar UX

  • Auto-hide when empty: with no sub-agents and no active plan, the sidebar collapses and the chat reclaims the full width; it returns the moment an agent spawns or a plan starts.
  • Silent Ctrl+B toggle: hiding/showing the sidebar no longer prints a "Context sidebar hidden/shown" line into the transcript on every toggle.
  • Ctrl+B hides the plan entirely: collapsing the sidebar no longer resurrects the plan as a pinned panel above the composer. The pinned panel is now reserved for terminals too narrow to host the sidebar at all.

Rendering polish

  • Wider chat reading column: transcript text now fills most of a wide terminal (with a small symmetric margin) instead of stopping at a fixed ~90-column cap; narrow/two-column widths are unchanged.
  • No streaming-text glow: removed the lime→ink fade on freshly streamed lines; responses render in static ink. The now-moot fade env-detection and its test were removed too (no dead code).
  • No shaded band on tool-result cards: tool-result rows (diffs, command output, search results, generic results) now blend into the terminal background. ~25 foreground-only swaps strip the panel background while preserving exact cell widths and the status-tinted left rail; unrelated panel surfaces (sidebar, sessions list, ask-user card) are untouched.

Testing

  • New/updated unit + handler tests cover: the clickable-step capture, toggle, status-branched content, narration capture, and the on-click model request (loading → response → cache); plan force-completion (mid-progress, failed-step, empty, already-complete) and its guard (success completes; error / pending-ask-user leave it alone); sidebar auto-hide; the reading-column width helper; and the Ctrl+B-hides-plan behavior.
  • go build ./... clean; the full internal/tui suite passes.

Summary by CodeRabbit

  • New Features

    • Added optional turn recaps, configurable in the app and shown in the runtime settings.
    • Introduced clickable plan-step detail cards with live explanations and step progress visibility.
    • Added a sidebar activity section and improved working-status feedback during long operations.
  • Bug Fixes

    • Plan progress now stays accurate when steps are edited, renamed, or completed without a final update.
    • Improved transcript selection and sidebar behavior, including auto-hiding empty sidebars and better layout handling.
    • Tool output previews and secret redaction are now more reliable.

The plan panel could read as stuck mid-task even while work continued. Three
fixes:
- Header showed steps[0] forever, so the title never advanced as the plan
  progressed; show the current step (in_progress, else first incomplete).
- updateFromItems matched steps by content only, so an in-place reword reset a
  step's elapsed timer to zero; add positional carry-over when the step count is
  unchanged.
- Prompt: tell the model to mark each step completed + the next in_progress
  before starting it, so the plan tracks real progress instead of jumping at the
  end.

Regression tests included.
Each plan step now records the file mutations (write_file/edit_file/apply_patch)
made while it was in_progress, captured from tool-result rows. Clicking a step
row in the context sidebar drops a transcript card listing those changes — the
implementation for that step. Step->line mapping mirrors sidebarAgentSelectables'
offset accounting (one line per step); capture is keyed to the active step and
cleared per run. Read-only and additive. Tests cover the offset math + capture.
Extend the clickable plan-step detail: capture bash/exec_command runs in addition
to file mutations, store each tool's full output (the diff for edit/apply_patch,
stdout/stderr for commands), and render the card grouped into Changes and
Commands with a short truncated excerpt of each item's diff/output.
Re-clicking a plan step now hides its card (a stable transcript id + drop-by-id),
and clicking a different step replaces it, so at most one detail card is shown.
Previously each click appended a fresh card and they piled up.
Clicking a PLAN step now shows a fresh, plain-English write-up of that
step, generated on demand by the active model:

- past tense ('what we did') for completed/failed steps, future tense
  ('what we'll do') for pending/in_progress steps
- immediate feedback: the card drops in showing 'Writing explanation…'
  and updates in place (by stable row id) when the text returns
- cached per step (content+status) so re-clicking is instant with no
  second model call; re-clicking still toggles the card closed
- the prompt is built from already-captured local data (step text,
  status, notes, plus a compact digest of file edits, commands, and the
  agent's narration) and asks for short, non-technical prose
- reuses the TUI's existing provider via a one-shot StreamCompletion;
  no new client, no hardcoded provider. Falls back to the local summary
  when no provider is configured or the request fails.
@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Zero automated PR review

Verdict: No blockers found

Blockers

  • None found.

Validation

  • [pass] Diff hygiene: git diff --check
  • [pass] Tests: go test ./...
  • [pass] Build: go run ./cmd/zero-release build
  • [pass] Smoke build: go run ./cmd/zero-release smoke

Scope

Head: 4440c077f481
Changed files (43): internal/agent/system_prompt.md, internal/agent/system_prompt_test.go, internal/cli/app.go, internal/config/resolver.go, internal/config/types.go, internal/config/writer.go, internal/config/writer_test.go, internal/reltime/reltime.go, internal/tools/apply_patch.go, internal/tools/edit_file.go, internal/tools/preview.go, internal/tools/preview_test.go, and 31 more

This deterministic review checks validation status and basic diff hygiene. A human reviewer still owns product judgment and design quality.

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The PR strengthens agent prompt plan-tracking rules, adds a configurable post-turn recap feature (persisted via config.SetRecapsEnabled, surfaced via /config recaps on|off), moves tool diff content into a new Display.Preview field, introduces clickable plan-step detail cards in the TUI sidebar (with cached AI-generated explanations), adds a sidebar activity feed, reconciles incomplete plans on turn completion, and replaces fade-disabled detection with reduced-motion detection.

Changes

Plan workflow, recaps, tool previews, and TUI transcript updates

Layer / File(s) Summary
Prompt instructions and recap config
internal/agent/system_prompt.md, internal/agent/system_prompt_test.go, internal/config/types.go, internal/config/resolver.go, internal/config/writer.go, internal/config/writer_test.go, internal/cli/app.go, internal/tui/options.go, internal/tui/model.go, internal/tui/command_views.go, internal/tui/commands.go, internal/tui/command_polish_test.go
Agent prompt mandates incremental update_plan calls, elaborated summaries, and selective narration. PreferencesConfig gains a tri-state Recaps *bool field with RecapsEnabled(), SetRecapsEnabled persists it, the TUI Options and model wire the flag, and /config recaps on|off is exposed.
Post-turn recap generation
internal/tui/recap.go, internal/tui/recap_test.go, internal/tui/transcript.go, internal/tui/rendering.go
generateRecap sends a provider completion with a fixed one-sentence system prompt; maybeRecapTurn gates dispatch per-run; handleRecapGenerated appends a rowRecap row; renderRecapRow emits a faint "※ recap: …" line.
Tool diff preview payloads
internal/tools/types.go, internal/tools/preview.go, internal/tools/edit_file.go, internal/tools/write_file.go, internal/tools/apply_patch.go, internal/tools/registry.go, internal/tools/preview_test.go, internal/tools/registry_test.go, internal/tools/write_tools_test.go, internal/tui/tool_render_registry.go, internal/tui/model.go
Display.Preview is added for multi-line card-only diff content; edit_file, write_file, and apply_patch populate it via capPreviewDiff/boundedUnifiedDiff; secret scrubbing covers the new field; the TUI prefers Preview over Output for tool result detail; write_file is registered for diff-first rendering.
Plan progress, reconciliation, and sidebar state
internal/tui/plan_panel.go, internal/tui/plan_panel_progress_test.go, internal/tui/plan_panel_test.go, internal/tui/model.go, internal/tui/sidebar.go, internal/tui/sidebar_test.go, internal/tui/working_status_test.go
updateFromItems adds positional timestamp carry-over; completeRemaining force-completes plans when the agent skips the final update_plan; sidebarAvailable gates on non-empty content; sidebar adds an ACTIVITY section; working status adds a quiet-generation hint and live plan line; plumbing tools (update_plan, tool_search) are hidden from the transcript except on error.
Plan-step detail cards
internal/tui/plan_step_detail.go, internal/tui/plan_step_detail_test.go, internal/tui/command_output.go, internal/tui/model.go, internal/reltime/reltime.go, internal/tui/transcript_selection.go
New 641-line file implements click-to-open step cards: captureStepWork/captureStepNarration attribute tool results and narration to plan steps; planStepAtMouse hit-tests sidebar clicks; openPlanStepDetail toggles the card and optionally dispatches a one-shot AI explanation; stale generations are dropped; RelTime utility formats durations.
Transcript and card rendering
internal/tui/rendering.go, internal/tui/transcript_selection.go, internal/tui/view.go, internal/tui/tool_render_registry.go, internal/tui/rendering_lime_test.go, internal/tui/mouse_test.go, internal/tui/model_test.go
Interim assistant rows render with a "● " narration gutter; selection geometry aligns textStart to that gutter; renderPlanCardRow styles plan cards by step status; card bodies drop onPanel wrappers for direct theme rendering; reading-column sizing is scaled rather than fixed-cap; mouse tests use withSidebarContent for stable geometry.
Reduced-motion detection
internal/tui/streaming_fade.go, internal/tui/model.go, internal/tui/stage10_test.go
defaultFadeDisabled/streamingFadeDisabled are replaced by defaultReducedMotion/reducedMotionEnabled (keyed to ZERO_REDUCED_MOTION and NoTTY); newModel forces fadeDisabled = true unconditionally; the obsolete TestStreamingFadeDisabled test is removed.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • Gitlawb/zero#316: Both PRs modify update_plan transcript handling — the retrieved PR suppresses redundant cards, this PR adds plan-card tagging and skips update_plan tool-call rows except on error.
  • Gitlawb/zero#318: Both directly modify write_file/edit_file unified-diff generation; this PR additionally shifts diff content into Result.Display.Preview, keeping Output clean.
  • Gitlawb/zero#300: Both modify the two-column sidebar layout and plan_panel.go/transcript_selection.go coordinate handling; this PR layers plan-step detail card geometry on top.

Suggested reviewers

  • Vasanthdev2004
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 58.87% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main changes: clickable plan-step details plus broader TUI plan/sidebar and rendering polish.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/tui/plan_panel.go (1)

94-103: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Clear stale completion timestamps for non-terminal carried-over steps.

The positional fallback can copy completedAt from a prior completed/failed step into a new pending or in_progress step. Line 100 then keeps that stale terminal timestamp, so the live step can render with an old completed duration instead of a running clock.

Proposed fix
 		switch step.status {
 		case "in_progress":
+			step.completedAt = time.Time{}
 			if step.startedAt.IsZero() {
 				step.startedAt = now
 			}
 		case "completed", "failed":
 			if step.startedAt.IsZero() {
 				step.startedAt = now
 			}
 			if step.completedAt.IsZero() {
 				step.completedAt = now
 			}
+		default:
+			step.completedAt = time.Time{}
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/tui/plan_panel.go` around lines 94 - 103, The carried-over step
matching in plan_panel.go can copy a stale completedAt from a previous terminal
step into a current pending or in_progress step, causing live steps to show an
old finished duration. Update the step reconciliation logic around the
positional fallback and switch on step.status so that non-terminal statuses
clear any inherited completedAt (and any other terminal-only timing state)
before rendering, while still preserving startedAt when appropriate. Use the
existing step matching block and the status handling in the plan panel logic to
locate the fix.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/tui/model.go`:
- Around line 1620-1633: Drop stale plan-step explanation results by associating
each planStepExplanationMsg with the current run/detail generation and ignoring
any message whose generation does not match the active one. Update the message
handling around planStepExplanationMsg in internal/tui/model.go and the sender
path that triggers the async explanation request so the generation is captured
when the request starts, then validate it before recreating stepExplanation or
storing text. Ensure beginRun still resets state, but stale in-flight results
from a previous run cannot repopulate m.stepExplanation or overwrite newer data.
- Around line 1084-1090: The Ctrl+B toggle in the no-blocking modal path is
using sidebarAvailable() in a way that lets an auto-hidden sidebar become
manually hidden, which can keep the later plan view suppressed. Update the
toggle logic in model.go around m.noBlockingModal and m.sidebarHidden so Ctrl+B
only flips the user-controlled hidden state when the sidebar is actually
available to show, and separate that from the auto-hidden case; keep the change
localized to the sidebar visibility handling and any checks that determine
whether the pinned plan should render.

In `@internal/tui/plan_step_detail.go`:
- Around line 459-473: The digest truncation in planStepDigestItems is using
byte slicing on w.summary, which can break UTF-8 when non-ASCII text is present.
Update the truncation logic to use the existing rune-safe helper already used in
this package, such as truncateRunes, and keep its ellipsis behavior consistent
with the current summary formatting.

---

Outside diff comments:
In `@internal/tui/plan_panel.go`:
- Around line 94-103: The carried-over step matching in plan_panel.go can copy a
stale completedAt from a previous terminal step into a current pending or
in_progress step, causing live steps to show an old finished duration. Update
the step reconciliation logic around the positional fallback and switch on
step.status so that non-terminal statuses clear any inherited completedAt (and
any other terminal-only timing state) before rendering, while still preserving
startedAt when appropriate. Use the existing step matching block and the status
handling in the plan panel logic to locate the fix.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 79bae045-1538-4431-837c-13649dc2bf0b

📥 Commits

Reviewing files that changed from the base of the PR and between 644f61f and ad5334e.

📒 Files selected for processing (18)
  • internal/agent/system_prompt.md
  • internal/tui/command_output.go
  • internal/tui/model.go
  • internal/tui/model_test.go
  • internal/tui/mouse_test.go
  • internal/tui/plan_panel.go
  • internal/tui/plan_panel_progress_test.go
  • internal/tui/plan_panel_test.go
  • internal/tui/plan_step_detail.go
  • internal/tui/plan_step_detail_test.go
  • internal/tui/rendering.go
  • internal/tui/sidebar.go
  • internal/tui/sidebar_test.go
  • internal/tui/stage10_test.go
  • internal/tui/streaming_fade.go
  • internal/tui/tool_render_registry.go
  • internal/tui/transcript_selection.go
  • internal/tui/view.go
💤 Files with no reviewable changes (2)
  • internal/tui/stage10_test.go
  • internal/tui/streaming_fade.go

Comment thread internal/tui/model.go
Comment thread internal/tui/model.go
Comment thread internal/tui/plan_step_detail.go
@gnanam1990

Copy link
Copy Markdown
Collaborator Author

Addressed all review comments (with tests):

  1. plan_panel.go — stale completedAt: the positional carry-over now clears completedAt for non-terminal statuses (in_progress/pending), so a reworded/carried-over live step shows a running clock instead of an old finished duration. (test: TestPlanReconcileClearsStaleCompletion)
  2. model.go — stale plan-step explanation: added a planDetailGen generation, bumped in beginRun, captured at request time and validated in the handler — an in-flight write-up from a previous run is dropped and can't repopulate the cleared cache. (test: TestStaleExplanationDropped)
  3. model.go — Ctrl+B persistence: beginRun now resets sidebarHidden, so a stale hide from a prior context no longer suppresses the new run's sidebar/plan. (test: TestBeginRunResetsSidebarHidden)
  4. plan_step_detail.go — UTF-8 truncation: planStepDigestItems now uses the rune-safe truncateRunes instead of byte slicing.

This push also brings the rest of the branch's work (narration bullet, recap line + /config toggle, ACTIVITY panel, write/edit code previews, etc.). All suites green, gofmt clean.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

@gnanam1990 Thanks for the detailed update. I’ll review the latest changes with extra attention to the four fixes you called out and the added regression coverage.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/tui/model.go (1)

1340-1346: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Refresh the quiet-generation timer on reasoning deltas.

Lines 1344-1346 advance the live token count but never touch lastStreamActivity, so quietGenerationHint() can flip to “still generating…” while reasoning is actively streaming. That makes the new liveness hint contradict the actual provider activity.

Suggested fix
 	case agentReasoningMsg:
 		if msg.runID != m.activeRunID {
 			return m, nil
 		}
 		m.streamingReasoning += msg.delta
 		m.turnStreamedRunes += utf8.RuneCountInString(msg.delta)
+		if msg.delta != "" {
+			m.lastStreamActivity = m.now()
+		}
 		return m, nil
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/tui/model.go` around lines 1340 - 1346, The quiet-generation state
is not being refreshed for reasoning-only streaming updates, so
`quietGenerationHint()` can report “still generating…” even while
`agentReasoningMsg` deltas are arriving. In the `model.go` reasoning stream
handling, update `lastStreamActivity` alongside the existing
`streamingReasoning` and `turnStreamedRunes` updates whenever `msg.runID`
matches `m.activeRunID`, so the liveness hint stays aligned with active provider
output.
🧹 Nitpick comments (2)
internal/tui/sidebar.go (1)

719-744: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

This ACTIVITY lookup is still linear in transcript length.

len(work) < limit caps matches, not rows inspected. If matching tool-result rows are sparse, every sidebar render can still walk most of m.transcript. In this hot path, consider keeping a bounded activity deque/index as rows are appended instead of rescanning on render.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/tui/sidebar.go` around lines 719 - 744, The activity summary lookup
in sidebar rendering is still scanning m.transcript linearly, which can make
every render expensive when matching rows are sparse. Update the sidebar state
around the transcript appends and the activity rendering path so recent
plan-work tool results are kept in a bounded deque or index as rows arrive,
instead of recomputing them inside the render loop. Use the existing helpers and
symbols in sidebar.go such as m.transcript, rowToolResult, isPlanWorkTool,
m.activitySummary, and the code that builds work/live lines to locate and
replace the hot-path scan.
internal/tui/command_polish_test.go (1)

78-80: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the recap value, not just the label.

This only proves the /config output mentions recaps:. It will still pass if the runtime default or onOff(m.recapsEnabled) rendering regresses. Set the flag explicitly in the test model and assert recaps: on or recaps: off exactly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/tui/command_polish_test.go` around lines 78 - 80, The /config test
in command_polish_test is only checking that the recaps label appears, so it
won’t catch regressions in the rendered value. Update the test model to set the
recaps flag explicitly, then assert the exact output from commandPolish and
onOff(m.recapsEnabled) by checking for either recaps: on or recaps: off, not
just the presence of recaps:.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/config/writer_test.go`:
- Around line 300-303: The round-trip test in writer_test.go is ignoring the
error from SetRecapsEnabled when turning recaps back on, which can hide a failed
write and still pass on a zero-value PreferencesConfig. Update the test around
SetRecapsEnabled and RecapsEnabled to assert the error is nil for the re-enable
call, then verify the returned config reflects a persisted true value so the
test only passes when the second write actually succeeds.

In `@internal/tools/apply_patch.go`:
- Line 103: The apply_patch preview is still rendering raw patch content, so
secrets can leak through Display.Preview. Update applyPatch’s preview generation
to use the same redaction flow as the other preview renderers instead of passing
patch directly into capPreviewDiff, and make sure the Display field in
apply_patch.go uses the redacted preview path. Add test coverage for
secret-containing hunks to verify the preview is redacted before being stored or
rendered.

In `@internal/tools/preview.go`:
- Around line 33-35: The preview hunk rendering in preview.go is losing real
trailing and empty lines because strings.TrimRight(..., "\n") is applied before
splitting, which also turns empty content into a fake blank diff line. Update
the logic in the preview hunk builder paths that use this pattern, including the
code around the content-to-body loop and the edit/delete hunk rendering, so line
splitting preserves trailing blank lines and empty content exactly. Keep the
hunk construction in the relevant functions (such as the preview body assembly
for write_file and edit_file) aligned with the original content shape rather
than trimming newline endings first.

In `@internal/tui/recap.go`:
- Around line 80-87: The recap flow in generateRecapCmd and the recap completion
handling is appending results to the current end of the transcript without
checking whether they belong to the latest run, so stale recap messages can land
on the wrong conversation. Fix this by anchoring each recap request to a stable
insertion point or by validating the runID before applying the result, and
update the handler that processes recapGeneratedMsg so it inserts at the
captured position or drops outdated results instead of always appending.
- Around line 124-131: The `recaps on` and `recaps off` branches in
`recap`/`handleRecaps` are returning success even when `persistRecapsEnabled()`
fails. Update these cases to check the error from `persistRecapsEnabled()` and
only return `Config\nrecaps: on/off` when persistence succeeds; on failure,
return an error or failure message instead of success. Keep the in-memory toggle
and persisted state aligned by handling the write failure explicitly in the same
control flow.

In `@internal/tui/rendering.go`:
- Around line 143-144: The row filtering in renderRow/isHiddenPlumbingTool is
hiding all plumbing tool results, including failed ones like StatusError for
update_plan and tool_search. Adjust the rowToolResult handling so only
successful plumbing outputs are suppressed, while error/failure results still
render. Use the rowToolResult switch case and isHiddenPlumbingTool helper to
locate the logic, and preserve visibility for any failed deferred-tool or plan
updates.

In `@internal/tui/sidebar.go`:
- Around line 752-754: The ACTIVITY summary is matching tool-call rows only by
id, which can pick up the wrong command when the same id is reused later in the
transcript. Update activityCommandForRow to scope the lookup by both id and
runID, and make the callers that build the ACTIVITY label in sidebar.go use that
run-aware match so the summary stays tied to the same run. Use the existing
rowToolCall data and the row.runID from the current row to keep the association
local.

---

Outside diff comments:
In `@internal/tui/model.go`:
- Around line 1340-1346: The quiet-generation state is not being refreshed for
reasoning-only streaming updates, so `quietGenerationHint()` can report “still
generating…” even while `agentReasoningMsg` deltas are arriving. In the
`model.go` reasoning stream handling, update `lastStreamActivity` alongside the
existing `streamingReasoning` and `turnStreamedRunes` updates whenever
`msg.runID` matches `m.activeRunID`, so the liveness hint stays aligned with
active provider output.

---

Nitpick comments:
In `@internal/tui/command_polish_test.go`:
- Around line 78-80: The /config test in command_polish_test is only checking
that the recaps label appears, so it won’t catch regressions in the rendered
value. Update the test model to set the recaps flag explicitly, then assert the
exact output from commandPolish and onOff(m.recapsEnabled) by checking for
either recaps: on or recaps: off, not just the presence of recaps:.

In `@internal/tui/sidebar.go`:
- Around line 719-744: The activity summary lookup in sidebar rendering is still
scanning m.transcript linearly, which can make every render expensive when
matching rows are sparse. Update the sidebar state around the transcript appends
and the activity rendering path so recent plan-work tool results are kept in a
bounded deque or index as rows arrive, instead of recomputing them inside the
render loop. Use the existing helpers and symbols in sidebar.go such as
m.transcript, rowToolResult, isPlanWorkTool, m.activitySummary, and the code
that builds work/live lines to locate and replace the hot-path scan.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: af6b47d2-3568-4831-a40a-618674c8bf48

📥 Commits

Reviewing files that changed from the base of the PR and between 4d5628d and 3f17702.

📒 Files selected for processing (34)
  • internal/agent/system_prompt.md
  • internal/agent/system_prompt_test.go
  • internal/cli/app.go
  • internal/config/resolver.go
  • internal/config/types.go
  • internal/config/writer.go
  • internal/config/writer_test.go
  • internal/tools/apply_patch.go
  • internal/tools/edit_file.go
  • internal/tools/preview.go
  • internal/tools/preview_test.go
  • internal/tools/registry.go
  • internal/tools/types.go
  • internal/tools/write_file.go
  • internal/tools/write_tools_test.go
  • internal/tui/command_polish_test.go
  • internal/tui/command_views.go
  • internal/tui/commands.go
  • internal/tui/model.go
  • internal/tui/model_test.go
  • internal/tui/options.go
  • internal/tui/plan_panel.go
  • internal/tui/plan_panel_progress_test.go
  • internal/tui/plan_step_detail.go
  • internal/tui/recap.go
  • internal/tui/recap_test.go
  • internal/tui/rendering.go
  • internal/tui/rendering_lime_test.go
  • internal/tui/sidebar.go
  • internal/tui/sidebar_test.go
  • internal/tui/tool_render_registry.go
  • internal/tui/transcript.go
  • internal/tui/transcript_selection.go
  • internal/tui/working_status_test.go
✅ Files skipped from review due to trivial changes (3)
  • internal/agent/system_prompt_test.go
  • internal/tui/commands.go
  • internal/tools/preview_test.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • internal/tui/sidebar_test.go
  • internal/tui/plan_step_detail.go
  • internal/tui/plan_panel.go
  • internal/tui/model_test.go

Comment thread internal/config/writer_test.go Outdated
Comment thread internal/tools/apply_patch.go
Comment thread internal/tools/preview.go Outdated
Comment thread internal/tui/recap.go
Comment thread internal/tui/recap.go Outdated
Comment thread internal/tui/rendering.go Outdated
Comment thread internal/tui/sidebar.go
@gnanam1990

Copy link
Copy Markdown
Collaborator Author

Round 2 — addressed all 7 actionable comments + both nitpicks (each with a test):

  1. model.go reasoning clockagentReasoningMsg now refreshes lastStreamActivity, so the quiet-generation hint can't read "still generating…" mid-think. (TestReasoningRefreshesActivityClock)
  2. rendering.go failed plumbing — only successful update_plan/tool_search results are hidden; a failed one still surfaces its error. (TestHiddenPlumbingToolsSkippedFromTranscript)
  3. preview.go trailing/empty lines — replaced TrimRight-before-split with splitDiffLines, which preserves trailing/interior blanks and yields no body for empty content (no fake + line). (TestPreviewPreservesEmptyAndTrailingLines)
  4. recap.go stale recaphandleRecapGenerated drops a recap whose runID isn't the latest run, so it can't land on a newer conversation. (TestHandleRecapGenerated)
  5. recap.go persist error/config recaps on|off now returns a failure message when persistRecapsEnabled() errors instead of falsely reporting success. (TestHandleConfigCommandReportsPersistError)
  6. sidebar.go run-aware command lookupactivityCommandForRow matches by id AND runID, so a reused tool-call id can't attribute the wrong command.
  7. writer_test.go — the round-trip test now asserts the re-enable returns nil error and persists an explicit true.

Nitpicks:

  • sidebar.go ACTIVITY scan — bounded the per-render backward scan (maxSidebarActivityScan) so a work-sparse transcript stays O(window), not O(transcript).
  • command_polish_test.go — sets the flag explicitly and asserts the exact recaps: on value.

Note on apply_patch (Display.Preview redaction): the preview is already scrubbed at the universal boundary — RunWithOptions runs scrubResultSecrets on every result, which now redacts Display.Preview alongside Output/Summary. Added TestScrubResultSecretsRedactsPreview to lock that in.

All suites (tui/tools/config/agent) green, gofmt clean.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

@gnanam1990 Thanks for the detailed Round 2 summary. I’ll review the latest updates with extra attention to the seven fixes, the two follow-up nitpicks, and the Display.Preview redaction path you called out.


✏️ Learnings added
Learnt from: gnanam1990
URL: https://github.com/Gitlawb/zero/pull/315

Timestamp: 2026-06-25T09:43:11.085Z
Learning: In the Go tool-result pipeline for this repository, `internal/tools/registry.go` applies `scrubResultSecrets` to every tool result, and that boundary redacts `internal/tools/types.go` field `Display.Preview` in addition to `Output` and `Display.Summary`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
internal/cli/dryrun_test.go (1)

132-135: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clean up stale/contradictory test comment text.

The comment mentions --dry-run=foo and “bad value” behavior, but the test is TestDryRunWithExplicitTrue.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/dryrun_test.go` around lines 132 - 135, The test comment in
TestDryRunWithExplicitTrue is stale and contradicts the test name and current
boolean flag behavior. Update the comment above TestDryRunWithExplicitTrue in
dryrun_test.go to describe the actual scenario being verified, remove references
to “bad value” and --dry-run=foo, and make sure the wording matches the boolean
--dry-run/--dry-run=true/false behavior reflected by the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/cli/dryrun_test.go`:
- Around line 227-233: The dry-run tests only check for “Would” in the output
and can miss regressions that still write to disk. Update
TestDryRunSandboxGrantSetPrintsWould and TestDryRunHooksAddPrintsWould in
dryrun_test.go to also assert the expected file is not created or modified,
using the relevant dry-run helpers and output checks already in place. Keep the
existing stdout/stderr assertions, but add a file non-existence/no-write side
effect check for the target path used by each test.
- Line 186: The dry-run assertion is referencing a field that does not exist on
the launched agent options, so the test does not compile. Update the assertion
in the runWithDeps test to check the actual dry-run field carried through
tui.Options/agent.Options instead of launchedOptions.AgentOptions.DryRun, and
use the concrete options symbol that runWithDeps populates so the test matches
the real structure.
- Line 15: The dry-run test file has an unused zeroruntime import that causes go
test to fail typecheck; remove the unused import from the test module and keep
the remaining imports in internal/cli/dryrun_test.go aligned with actual
references in the test code.
- Around line 222-223: The dry-run test setup references sandbox symbols that
are not currently in scope, so the test won’t compile. Add the missing sandbox
package import in the test file that defines newSandboxStore, then keep the
existing uses of sandbox.GrantStore and sandbox.NewGrantStore(...) unchanged so
the closure can resolve those symbols correctly.

---

Nitpick comments:
In `@internal/cli/dryrun_test.go`:
- Around line 132-135: The test comment in TestDryRunWithExplicitTrue is stale
and contradicts the test name and current boolean flag behavior. Update the
comment above TestDryRunWithExplicitTrue in dryrun_test.go to describe the
actual scenario being verified, remove references to “bad value” and
--dry-run=foo, and make sure the wording matches the boolean
--dry-run/--dry-run=true/false behavior reflected by the test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8297c1d2-d5d7-4b09-8a51-421de54cf2bb

📥 Commits

Reviewing files that changed from the base of the PR and between 3f17702 and 3bffc70.

📒 Files selected for processing (14)
  • internal/cli/dryrun_test.go
  • internal/config/writer_test.go
  • internal/reltime/reltime.go
  • internal/tools/preview.go
  • internal/tools/preview_test.go
  • internal/tools/registry_test.go
  • internal/tui/command_polish_test.go
  • internal/tui/model.go
  • internal/tui/model_test.go
  • internal/tui/recap.go
  • internal/tui/recap_test.go
  • internal/tui/rendering.go
  • internal/tui/sidebar.go
  • internal/tui/working_status_test.go
🚧 Files skipped from review as they are similar to previous changes (8)
  • internal/tui/command_polish_test.go
  • internal/tools/preview_test.go
  • internal/config/writer_test.go
  • internal/tui/working_status_test.go
  • internal/tui/recap.go
  • internal/tui/sidebar.go
  • internal/tui/model.go
  • internal/tui/rendering.go

Comment thread internal/cli/dryrun_test.go Outdated
Comment thread internal/cli/dryrun_test.go Outdated
Comment thread internal/cli/dryrun_test.go Outdated
Comment thread internal/cli/dryrun_test.go Outdated
It is an unrelated, pre-existing dry-run test that does not compile (references a
non-existent agent.Options.DryRun field, an unused import, and out-of-scope
sandbox symbols). It was swept into the prior commit by 'git add -A' and is not
part of this PR's scope.
@gnanam1990

Copy link
Copy Markdown
Collaborator Author

All four comments were on internal/cli/dryrun_test.go, which was an unrelated, pre-existing WIP dry-run test that does not compile (it references a non-existent agent.Options.DryRun field, has an unused zeroruntime import, and uses out-of-scope sandbox symbols).

It was never meant to be part of this PR — it got swept in by a git add -A in the prior commit. I've removed it from the branch (6c323ca8), so it's no longer in the diff and the internal/cli package compiles cleanly again. The dry-run test can be written properly in a separate, focused PR.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

@gnanam1990 Thanks for the clarification — that makes sense. If internal/cli/dryrun_test.go was accidental WIP and is now removed from the PR, then those four comments no longer apply to this branch state.

I’ll review the current diff as it stands, focusing on the intended TUI/plan/recap/sidebar changes and ignoring that removed dry-run test work for this PR.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Reconcile the overlapping write/edit code-preview feature with main's #318:
keep main's robust library-based boundedUnifiedDiff (proper red/green for
create/overwrite/edit) but route it through the card-only Display.Preview so the
model's Output stays the one-line summary — the rich preview costs zero model
tokens. Drop the now-redundant hand-rolled diff builders; keep the 'N lines'
summary. apply_patch keeps capPreviewDiff.
Reconcile the narration guidance: keep #317's plain-language preamble lead AND
this branch's per-significant-step running narration (the story flow), with
both sides' 'don't narrate trivial calls / skip for one-step tasks'.
@gnanam1990

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit review

@gnanam1990

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit review again

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

@gnanam1990 Re-running a full review of the current PR state now, with attention on actionable correctness, safety, CLI UX, and test coverage in the latest diff.

✅ Action performed

Full review finished.

@gnanam1990

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit approvable ?

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@gnanam1990 Yes — based on the current PR state, this looks approvable to me.

The previously raised correctness issues appear addressed, the accidental internal/cli/dryrun_test.go WIP is out of the diff, and the added regression coverage hits the riskier areas well: plan reconciliation, stale async results, sidebar state, recap/run isolation, failed plumbing visibility, and diff preview edge cases.

I don’t see a remaining blocking issue in the intended scope of this PR. If you want me to actually approve it, post @coderabbitai approve.

@gnanam1990

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit if approvable then approve it

@gnanam1990

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit approve

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
internal/tools/preview_test.go (1)

8-21: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Tidy test coverage, one small gap. The bound check uses a bare previewBodyLines+5 slack with no comment — fine, but it silently tolerates up to 5 extra lines (header/trailer). A quick comment on why 5 keeps future maintainers from guessing. Also consider asserting the cap retains the diff header lines (--- a/x / +++ b/x) so a regression that drops context headers doesn't slip through.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/tools/preview_test.go` around lines 8 - 21, Update
TestCapPreviewDiff to make the bounded-line assertion self-explanatory by adding
a brief note around the previewBodyLines+5 allowance, and strengthen the test by
explicitly checking that capPreviewDiff preserves the diff header lines in the
long case. Use the existing TestCapPreviewDiff and capPreviewDiff symbols to
keep the intent tied to the current coverage.
internal/tui/plan_panel.go (1)

378-393: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Stale doc comment on renderPlanHeader. The function now titles the header with the current step (in-progress → first incomplete → first), but the doc comment at Lines 378-381 still says it shows "the truncated first step." Tidy it so the next reader isn't misled.

📝 Comment tweak
 // renderPlanHeader builds the single header line. While running it shows the
-// live spinner, the truncated first step, the done/total count, and the
-// elapsed time in the accent color; once complete it shows a green check and
-// "PLAN COMPLETE".
+// live spinner, the truncated current step (in_progress, else first incomplete,
+// else first), the done/total count, and the elapsed time in the accent color;
+// once complete it shows a green check and "PLAN COMPLETE".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/tui/plan_panel.go` around lines 378 - 393, Update the doc comment
for renderPlanHeader so it matches the current behavior: the header now shows
the current step via currentStepContent(state.steps), not always the truncated
first step. Keep the comment aligned with the function’s completed-state
behavior (“PLAN COMPLETE”) and running-state fields (spinner, current step,
done/total, elapsed time) so future readers aren’t misled.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/reltime/reltime.go`:
- Around line 5-65: RelTime is currently unused dead code, so either hook it up
in the relative-time rendering path (such as the plan/sidebar renderer that
should consume relative timestamps) or remove the RelTime function for now. Use
the unique symbol RelTime in internal/reltime/reltime.go to locate it, and
ensure the chosen path is wired into whatever formatter or UI code will actually
call it.

---

Nitpick comments:
In `@internal/tools/preview_test.go`:
- Around line 8-21: Update TestCapPreviewDiff to make the bounded-line assertion
self-explanatory by adding a brief note around the previewBodyLines+5 allowance,
and strengthen the test by explicitly checking that capPreviewDiff preserves the
diff header lines in the long case. Use the existing TestCapPreviewDiff and
capPreviewDiff symbols to keep the intent tied to the current coverage.

In `@internal/tui/plan_panel.go`:
- Around line 378-393: Update the doc comment for renderPlanHeader so it matches
the current behavior: the header now shows the current step via
currentStepContent(state.steps), not always the truncated first step. Keep the
comment aligned with the function’s completed-state behavior (“PLAN COMPLETE”)
and running-state fields (spinner, current step, done/total, elapsed time) so
future readers aren’t misled.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1ef56864-f98f-431e-bc99-037c86ad1a5b

📥 Commits

Reviewing files that changed from the base of the PR and between ca41ac2 and 4440c07.

📒 Files selected for processing (43)
  • internal/agent/system_prompt.md
  • internal/agent/system_prompt_test.go
  • internal/cli/app.go
  • internal/config/resolver.go
  • internal/config/types.go
  • internal/config/writer.go
  • internal/config/writer_test.go
  • internal/reltime/reltime.go
  • internal/tools/apply_patch.go
  • internal/tools/edit_file.go
  • internal/tools/preview.go
  • internal/tools/preview_test.go
  • internal/tools/registry.go
  • internal/tools/registry_test.go
  • internal/tools/types.go
  • internal/tools/write_file.go
  • internal/tools/write_tools_test.go
  • internal/tui/command_output.go
  • internal/tui/command_polish_test.go
  • internal/tui/command_views.go
  • internal/tui/commands.go
  • internal/tui/model.go
  • internal/tui/model_test.go
  • internal/tui/mouse_test.go
  • internal/tui/options.go
  • internal/tui/plan_panel.go
  • internal/tui/plan_panel_progress_test.go
  • internal/tui/plan_panel_test.go
  • internal/tui/plan_step_detail.go
  • internal/tui/plan_step_detail_test.go
  • internal/tui/recap.go
  • internal/tui/recap_test.go
  • internal/tui/rendering.go
  • internal/tui/rendering_lime_test.go
  • internal/tui/sidebar.go
  • internal/tui/sidebar_test.go
  • internal/tui/stage10_test.go
  • internal/tui/streaming_fade.go
  • internal/tui/tool_render_registry.go
  • internal/tui/transcript.go
  • internal/tui/transcript_selection.go
  • internal/tui/view.go
  • internal/tui/working_status_test.go
💤 Files with no reviewable changes (2)
  • internal/tui/stage10_test.go
  • internal/tui/streaming_fade.go

Comment thread internal/reltime/reltime.go
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

@gnanam1990 Approving — the current PR state looks good to merge from my side.

✅ Action performed

Comments resolved and changes approved.

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
✅ Action performed

Comments resolved and changes approved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant