Skip to content

fix(tui): watcher pane rendering — approvals slot, markers, glamour - #429

Merged
clcollins merged 7 commits into
mainfrom
srepd/watcher-pane-rendering
Aug 13, 2026
Merged

fix(tui): watcher pane rendering — approvals slot, markers, glamour#429
clcollins merged 7 commits into
mainfrom
srepd/watcher-pane-rendering

Conversation

@clcollins

Copy link
Copy Markdown
Owner

Problem

Three rendering defects in the watcher/approvals panes:

  1. F1 — Approvals render below help bar: expanded approvals appear outside the watcher pane, unwrapped, and never show ask.Body. Users can't review what they're approving.
  2. F2 — Markers repeated on every line: prefixLines prepends 📡/🤖 to every line in a --- block. Multi-line verdicts get cluttered with duplicate emojis.
  3. F3 — Verdict markdown rendered raw: watcher pane shows **bold** and `code` instead of rendered text.

Approach

  • F1: Expanded approvals render INTO the watcher pane slot (views.go), temporarily replacing the watcher viewport. Shows wrapped titles, action+target lines using snapshotted ask.IncidentID/IncidentTitle (never live m.selectedIncident), and full ask.Body. Watcher state saved/restored on open/close.
  • F2: New prefixMessage(marker, text) prepends marker once at block start, replacing prefixLines at all call sites (both watcher and agent paths).
  • F3: renderWatcherMarkdown processes content through glamour with the model's GlamourStyle. ASCII-mode guard prevents ANSI contamination in golden tests. stripControl security sanitisation preserved at buffer boundary.

Test evidence

13 new tests covering all three fixes:

Test Covers
TestPrefixMessage_OneMarkerPerBlock (6 subtests) F2
TestWatcherBuffer_OneMarkerPerBlock_Integration F2
TestWatcherBuffer_StreamingSetLast_OneMarker F2
TestView_ApprovalsExpandedRendersInWatcherSlot F1
TestView_ApprovalsExpandedWrapsLongTitles F1
TestView_ApprovalsCollapsedShowsBadge F1
TestView_ApprovalsRestoredWatcherStateOnClose F1
TestUpdateWatcherViewport_RendersMarkdown F3
TestRenderApprovalsExpanded_ShowsBody F1
TestRenderApprovalsExpanded_ActionTargetLine F1
TestGolden_WatcherOneMarkerAgent F2 golden
TestGolden_WatcherOneMarkerWatcher F2/F3 golden
TestGolden_ApprovalsExpanded F1 golden

Revert checks: all three fixes confirmed — reverting each causes test failures; restoring passes.

Testing this live

make build
./dist/srepd_linux_amd64_v1/srepd --dev
  1. F1: Press A to expand approvals → verify they render in the watcher pane with body text and action targets. Press Esc to close → verify watcher state is restored.
  2. F2: Watch for multi-line watcher verdicts → verify only one 📡/🤖 marker at the start of each --- block.
  3. F3: Watch for markdown in verdicts → verify bold/code/lists render as formatted text, not raw syntax.

Config

No new config keys, flags, or keybindings. No README update needed.

UNVERIFIED

  • Live PagerDuty integration (requires real incidents and AI provider)
  • Glamour rendering with actual terminal color profiles (tested via TrueColor override in unit tests)

Deviations

None.

🤖 Generated with Claude Code

agent-bot and others added 7 commits August 12, 2026 00:05
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>
@clcollins
clcollins merged commit 48d04da into main Aug 13, 2026
12 checks passed
@clcollins clcollins mentioned this pull request Aug 13, 2026
4 tasks
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