Skip to content

Comments

activity and performance#136

Merged
clcollins merged 11 commits intomainfrom
activity_and_performance
Jan 13, 2026
Merged

activity and performance#136
clcollins merged 11 commits intomainfrom
activity_and_performance

Conversation

@clcollins
Copy link
Owner

What type of PR is this?

(bug/feature/cleanup/documentation)

What this PR does / Why we need it?

  • Fix double-keypress issue by using highlighted incident directly
  • Remove aggressive pre-fetching to optimize API calls
  • Add input mode, spinner indicator, and help improvements
  • Add bottom status bar with incident ID and git SHA
  • Enable progressive rendering and auto-refresh for incident viewer
  • Fix sluggishness: proper apiInProgress management and remove aggressive polling
  • Fix critical performance issue: truncate debug log on startup
  • Reduce debug logging volume for high-frequency events
  • Implement async logging to eliminate UI blocking
  • Eliminate rendering sluggishness with cached markdown renderer
  • Add activity log

Which Jira/Github issue(s) does this PR fix?

Resolves #

Special notes for your reviewer

Pre-checks (if applicable)

  • Ran unit tests locally against the changes
  • Included documentation changes with PR

clcollins and others added 11 commits January 12, 2026 12:36
Previously, action keys (silence, acknowledge, note, etc.) required
pressing twice from table view because:
1. Duplicate getIncidentMsg calls in action handlers
2. waitForSelectedIncidentThenDoMsg aborting prematurely when no
   m.selectedIncident was set

Changes:
- Added getHighlightedIncident() helper to get incident from
  m.incidentList using the highlighted row's ID
- Simplified action handlers to use highlighted incident directly
  (silence, ack, unack, note, open)
- Updated action message handlers to try highlighted incident first,
  then fall back to selectedIncident
- Fixed waitForSelectedIncidentThenDoMsg abort logic to check for
  highlighted row before aborting
- Login action still fetches alerts separately as needed

Actions now work on first keypress by using incident data already
in memory rather than waiting for API fetch.

Added comprehensive unit tests:
- TestGetHighlightedIncident: Tests incident lookup from list
- TestActionMessagesFallbackToSelectedIncident: Tests message
  handler fallback logic for ack/unack/silence

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Previously, every time the incident list was updated (initial load,
after actions like acknowledge/silence), the code pre-fetched full
details, alerts, and notes for ALL incidents. This resulted in
O(3n) API calls per update.

Why this was inefficient:
- Most incidents are never viewed or acted upon by SREs
- The incident list already contains sufficient data for most actions
  (ID, Title, Service, EscalationPolicy, Status, Assignments)
- getHighlightedIncident() uses data from m.incidentList directly
- Only specific actions need additional data:
  * Viewing incident details (needs alerts and notes)
  * Login action (needs alerts for cluster_id extraction)

Changes:
- Removed pre-fetch loop from updatedIncidentListMsg handler
- Added explanatory comment documenting the optimization
- Data now fetched on-demand when user actions require it:
  * Press Enter to view: fetches alerts and notes
  * Press 'l' to login: fetches alerts for cluster_id

Impact:
- Reduces API calls from O(3n) to O(1) per incident list update
- For 10 incidents: 30 calls → 0 calls (unless viewing)
- For 50 incidents: 150 calls → 0 calls (unless viewing)
- Faster incident list refresh after actions
- Lower PagerDuty API rate limit usage
- Better user experience with less waiting

Added test:
- TestUpdatedIncidentListNoPrefetch: Verifies incident list
  updates no longer trigger pre-fetch commands

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add input mode triggered by 'i' or ':' keys
  - All keypresses become text input (except esc, enter, ctrl+q/ctrl+c)
  - Context-aware help display shows only relevant keys in input mode
  - Two-column help layout for input mode (esc/enter, quit)

- Replace static '>' prompt with animated spinner during API operations
  - MiniDot spinner in pink (#205) appears when interacting with PagerDuty API
  - Spinner shows during: incident refresh, fetch details, acknowledge, re-escalate, silence
  - Status text maintains normal color to prevent spinner color bleed

- Reorganize table view help display
  - Add Top (g) and Bottom (G) navigation keys to help
  - Regroup keys logically across 5 columns
  - Move auto-refresh/auto-ack to top of last column

- Add comprehensive keymap completeness test
  - Validates all key.Matches calls have corresponding help entries
  - Parses Go AST to find key bindings in focus mode handlers
  - Prevents adding keybindings without updating help display

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add bottom status bar at terminal bottom showing incident ID (left) and git SHA (right)
- Create renderBottomStatus() function for status bar rendering
- Add mutedStyle with dark gray color matching help text
- Fix spacing calculation to handle variable help text height
- Account for all UI chrome in incident viewer height calculation
- Inject git SHA at build time via ldflags in Makefile and goreleaser
- Create pkg/tui/version.go to hold build-time version information

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Fix viewport position: Always go to top when content loads to prevent blank window
- Add progressive rendering: Re-render when notes, alerts, or incident details arrive
- Add incident polling: Refresh viewed incident data during auto-refresh cycles
- Progressive updates make the UI feel more responsive as data loads

Now when viewing an incident:
1. Viewer opens immediately at top with cached/placeholder data
2. Three parallel API calls fetch incident, notes, and alerts
3. View updates progressively as each piece of data arrives
4. Incident data auto-refreshes every 15s to show new notes/alerts

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…ve polling

- Stop spinner when all incident data loads (incident, notes, alerts)
- Remove auto-refresh of incident data during polling (was too aggressive)
- Only GotoTop() on first render, not on progressive updates
- Prevents viewport jumping and reduces render churn

The apiInProgress flag was never being set back to false after loading
incident data, causing the spinner to run forever and potentially
contributing to UI sluggishness.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Changed debug.log from O_APPEND to O_TRUNC to prevent unbounded growth.
The log file had grown to 1.1GB, causing massive I/O slowdown on every
keypress since each key logs multiple debug messages.

With a 1.1GB file, every log write was slow, making the UI feel sluggish
especially during rapid navigation. Truncating on startup keeps the log
fresh and performant.

TODO: Implement proper log rotation for long-running sessions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Skip logging for:
- spinner.TickMsg (fires every ~80ms)
- TickMsg (scheduled job ticks)
- Arrow key navigation (up/down)
- Redundant "priority key handling" logs

These high-frequency events were generating 150+ log writes per second,
and since logging is synchronous in Update(), each write blocked the UI
thread. Even with a small log file, this I/O overhead degraded responsiveness.

Now logging only actionable events (enter, esc, commands, API responses)
which is more useful for debugging anyway.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Created asyncWriter that writes logs to a buffered channel (1000 messages)
processed by a background goroutine. This prevents log I/O from blocking
the Update() function.

Key features:
- Non-blocking writes via select statement
- Drops messages if buffer is full (prevents backpressure)
- Graceful shutdown waits for pending logs to flush
- Eliminates I/O blocking that was causing UI sluggishness

Combined with reduced logging volume and log truncation, this ensures
logging never impacts UI responsiveness.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Major performance optimizations to eliminate 1-2 second render delays:

- Cache glamour markdown renderer at initialization instead of creating
  new renderer on every incident view (was the primary performance bottleneck)
- Remove excessive debug logging from focus mode handlers that logged
  every keypress including terminal escape sequences
- Remove debug logging from frequently-called helper functions
  (getDetailFieldFromAlert, getEscalationPolicyKey)
- Expand terminal escape sequence filtering to catch more color response
  fragments that were slipping through

Before: Each incident view took 1-2 seconds to render due to expensive
glamour.NewTermRenderer() calls, causing sluggish keypresses as users
pressed keys before UI finished responding to previous input.

After: Rendering is nearly instant by reusing cached renderer.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@clcollins clcollins merged commit 240b979 into main Jan 13, 2026
1 check failed
@clcollins clcollins deleted the activity_and_performance branch January 13, 2026 20:52
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