fix: add per-message debounce to suppress spammy repeated notifications #111

Merged
noe.trevino merged 1 commit from bugfix-106/notification-spamming into main 2026-02-18 22:55:23 -06:00
Owner

When holding a navigation key at a boundary (e.g., right arrow at the last column), the same notification would fire on every keypress. This adds a 500ms debounce window keyed by level+message to NotificationState.Add(), so duplicate notifications are suppressed while different notification types still display normally.

https://claude.ai/code/session_01VvYN4LK94mdgoGsLezd7i8

Checklist

  • Read CONTRIBUTING.md
  • Ran tests (go test ./... -race) locally
  • Formatted and linted with gofmt -w . and golangci-lint run ./... respectively
  • Added tests if necessary (significant changes/complex logic)
  • Updated documentation if applicable
  • Updated README.md if applicable

Summary

Closes: #106

When holding a navigation key at a boundary (e.g., right arrow at the last column), the same notification would fire on every keypress. This adds a 500ms debounce window keyed by level+message to NotificationState.Add(), so duplicate notifications are suppressed while different notification types still display normally. https://claude.ai/code/session_01VvYN4LK94mdgoGsLezd7i8 ## Checklist - [x] Read `CONTRIBUTING.md` - [x] Ran tests (`go test ./... -race`) locally - [x] Formatted and linted with `gofmt -w .` and `golangci-lint run ./...` respectively - [x] Added tests if necessary (significant changes/complex logic) - [x] Updated documentation if applicable - [x] Updated `README.md` if applicable ## Summary <!-- What does this PR change and/or fix? Examples: Closes: 54 Fixes: 67 --> Closes: #106
claude[bot] (Migrated from github.com) reviewed 2026-02-18 15:08:42 -06:00
@ -77,2 +109,4 @@
return
}
for i, n := range s.notifications {
if n.ID == id {
claude[bot] (Migrated from github.com) commented 2026-02-18 15:08:42 -06:00

The lastSeen map is never pruned. Every unique level:message key ever seen accumulates indefinitely for the lifetime of the process. In a long-running TUI session with many distinct navigation boundary messages, this is an unbounded memory leak.

Since Add already checks now.Sub(lastTime) < s.debounceDuration, expired entries could be deleted at that point:

	if lastTime, ok := s.lastSeen[key]; ok {
		if now.Sub(lastTime) < s.debounceDuration {
			return 0
		}
		// Entry expired — delete it so the map doesn't grow unboundedly.
		delete(s.lastSeen, key)
	}
The `lastSeen` map is never pruned. Every unique `level:message` key ever seen accumulates indefinitely for the lifetime of the process. In a long-running TUI session with many distinct navigation boundary messages, this is an unbounded memory leak. Since `Add` already checks `now.Sub(lastTime) < s.debounceDuration`, expired entries could be deleted at that point: ```suggestion if lastTime, ok := s.lastSeen[key]; ok { if now.Sub(lastTime) < s.debounceDuration { return 0 } // Entry expired — delete it so the map doesn't grow unboundedly. delete(s.lastSeen, key) } ```
@ -77,2 +109,4 @@
return
}
for i, n := range s.notifications {
if n.ID == id {
Author
Owner

that is never going to happen pal

that is never going to happen pal
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
noe.trevino/paso!111
No description provided.