fix(tui): watcher pane rendering — approvals slot, markers, glamour - #429
Merged
Conversation
Tests for: - F1: approvals render in watcher pane slot with body, wrapping, action target - F2: exactly one marker per --- block (both watcher and agent paths) - F3: glamour markdown rendering in watcher viewport These tests intentionally fail — prefixMessage and watcherWasExpanded do not exist yet. Implementation follows in subsequent commits. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Replace all prefixLines() calls in production code with prefixMessage(), which prepends the marker exactly once at the start of each buffer entry instead of every line. Each buffer entry maps to one --- block, so the user sees one 📡 or 🤖 per watcher/agent response. This fixes: - Agent output showing 🤖 on every line including blank ones - Watcher markdown bullets getting 📡 on each line, with terminal- wrapped continuations getting no marker at all The prefixLines function is preserved for backward compatibility with existing tests that verify per-line behavior. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Replace the raw lipgloss.Width() wrapping in updateWatcherViewport and updateChatViewport with glamour markdown rendering. This renders bold, code, bullets, and URLs properly instead of showing raw ** ` - syntax. The stripControl sanitiser still runs at the buffer-append boundary (before glamour), so attacker-influenced AI output cannot inject terminal escape sequences. Glamour's own ANSI styling is trusted. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…g (F1) Move expanded approvals from below the help bar into the watcher pane's slot (views.go ~121), temporarily displacing the watcher pane. The watcher's prior expanded state is saved on open and restored on close. RenderExpanded now: - Wraps all lines using lipgloss.Width() to prevent terminal clipping - Renders the full ask.Body for informed decision-making - Shows an explicit action + target line per ask using the snapshotted ask.IncidentID/IncidentTitle (never reads live m.selectedIncident) - Keeps the collapsed strip as a passive "⚑ N asks — press A" badge Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add golden snapshots for both watcher paths (agent/watcher markers) and expanded approvals pane. Guard glamour rendering with ASCII-mode check to prevent ANSI contamination in deterministic golden tests. Fix lint (unnecessary fmt.Sprintf) and test regression (TrueColor override for glamour path exercise). Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
F1: Gate approvals/watcher/input keys to table view only by
restructuring keyMsgHandler — move per-mode dispatch above
approvals routing so A/W/: keys don't fire in incident/log/docs
views or under the approvals overlay.
F2: Fix setext heading corruption by rendering watcher buffer entries
individually through glamour then joining with Unicode separator
(───) instead of rendering the full buffer joined with \n---\n.
F3: Remove agent result double-rendering — store raw text in the
watcher buffer and let updateWatcherViewport handle the single
glamour render pass.
F4: Sanitize IncidentTitle with stripControl() in buildAskFromVerdict
to prevent terminal injection from attacker-influenced PD data.
F5: Clamp approvals pane content to WatcherHeight lines so long
approval bodies don't overflow the terminal.
F6: Cache the glamour TermRenderer on the model keyed by viewport
width, avoiding per-token renderer recreation during streaming.
SEC-001: Apply url.PathEscape to clusterID and reportID in backplane
client URL construction to prevent path traversal.
SEC-002: Reject non-localhost HTTP endpoints when an API key is set
in the OpenAI-compatible provider to prevent credential
leakage over plaintext.
SEC-003: Add .gitignore patterns for .env, *.pem, *.key, credentials,
and secrets files.
SEC-004b: Sanitize all PagerDuty data (incident titles, service names,
note content, alert fields, team/assignee summaries) with
stripControl() at the summarize boundary and table row
construction to prevent terminal injection.
Also removes dead code: prefixLines function (replaced by
prefixMessage), duplicate input.Focused() case in key handler switch.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Three rendering defects in the watcher/approvals panes:
ask.Body. Users can't review what they're approving.prefixLinesprepends 📡/🤖 to every line in a---block. Multi-line verdicts get cluttered with duplicate emojis.**bold**and`code`instead of rendered text.Approach
ask.IncidentID/IncidentTitle(never livem.selectedIncident), and fullask.Body. Watcher state saved/restored on open/close.prefixMessage(marker, text)prepends marker once at block start, replacingprefixLinesat all call sites (both watcher and agent paths).renderWatcherMarkdownprocesses content through glamour with the model'sGlamourStyle. ASCII-mode guard prevents ANSI contamination in golden tests.stripControlsecurity sanitisation preserved at buffer boundary.Test evidence
13 new tests covering all three fixes:
TestPrefixMessage_OneMarkerPerBlock(6 subtests)TestWatcherBuffer_OneMarkerPerBlock_IntegrationTestWatcherBuffer_StreamingSetLast_OneMarkerTestView_ApprovalsExpandedRendersInWatcherSlotTestView_ApprovalsExpandedWrapsLongTitlesTestView_ApprovalsCollapsedShowsBadgeTestView_ApprovalsRestoredWatcherStateOnCloseTestUpdateWatcherViewport_RendersMarkdownTestRenderApprovalsExpanded_ShowsBodyTestRenderApprovalsExpanded_ActionTargetLineTestGolden_WatcherOneMarkerAgentTestGolden_WatcherOneMarkerWatcherTestGolden_ApprovalsExpandedRevert checks: all three fixes confirmed — reverting each causes test failures; restoring passes.
Testing this live
Ato expand approvals → verify they render in the watcher pane with body text and action targets. PressEscto close → verify watcher state is restored.---block.Config
No new config keys, flags, or keybindings. No README update needed.
UNVERIFIED
Deviations
None.
🤖 Generated with Claude Code