Title flashing stops via `stopTitleFlash()` (lines 1097-1104):
```js
stopTitleFlash(){
if(this.titleFlashInterval){
clearInterval(this.titleFlashInterval);
this.titleFlashInterval=null;
this.titleFlashState=false;
document.title=this.originalTitle;
}
}
```
Called from:
1.**`onTabVisible()`** (line 1242) - When tab becomes visible again
2.**`markAllRead()`** (line 1218) - When user marks all notifications read
3.**`clearAll()`** (line 1226) - When user clears all notifications
4.**`handleInit()` cleanup** (lines 3294-3298) - On SSE reconnect
### Interval Safety
The interval guard (`if (!this.titleFlashInterval)`) at line 1084 prevents stacking - only one interval can exist at a time. New notifications while blinking update the `unreadCount` displayed but don't create additional intervals. This is **correct and safe**.
### Memory Leak Risk: LOW
The interval is properly cleaned up in:
-`onTabVisible()` - on every tab return
-`handleInit()` - on SSE reconnect (lines 3294-3298)
-`markAllRead()` and `clearAll()` - user actions
The `handleInit()` cleanup is particularly important because SSE reconnects reset all state. The explicit cleanup at lines 3294-3298 prevents orphaned intervals:
```js
// Clear notification manager title flash interval to prevent memory leak
This is for the OS notification popup icon, not the browser tab favicon. There is no code that manipulates `link[rel="icon"]` or swaps the favicon for attention.
A one-shot green glow burst when switching tabs. Applied in `selectSession()` (app.js line 3985) and cleaned up via `animationend` event with `{ once: true }`:
Key detail: Title flash always stops when tab becomes visible, but **unread count is NOT reset** unless the notification drawer is open. This means the badge count persists until the user interacts with it.
Codeman does **not** use `window.addEventListener('focus')` or `window.addEventListener('blur')`. It relies solely on the Page Visibility API (`visibilitychange` + `pageshow`).
This is the correct modern approach. The `focus`/`blur` events are unreliable (fire for devtools, iframe changes, etc.) while `visibilitychange` accurately reflects whether the user can see the tab.
### Other focus-related listeners
-`document.addEventListener('focusin')` (line 238) - Mobile keyboard handler for scrolling inputs into view
- Various `element.focus()` calls for modal focus trapping (`FocusTrap` class, line 742)
-`window.focus()` in browser notification click handler (line 1135) to bring window to front
None of these are related to notification/blinking behavior.
---
## 6. Multiple Notification Stacking
### Can intervals stack? NO
The `updateTabTitle()` method has a guard (line 1084):
Only one interval is ever created. Subsequent notifications while the tab is hidden simply update `this.unreadCount` which is read by the existing interval callback. The displayed count stays current without creating new intervals.
### Notification Grouping
Notifications within the same category + session within 5 seconds are **grouped** instead of creating new entries (lines 986-997):
1. First event: creates notification, starts title flash, sends browser notif
2. Events 2-N within 5s of same category+session: grouped (count increments, no new intervals)
3. Events of different categories: new notifications, but title flash interval is singular
4. Browser notifications: only 1 per 3s gets through
**Verdict**: Well-protected against stacking/compounding.
---
## 7. Team Agent Blinking
### Current State: NO team-specific blinking
Searching for `team:` SSE event listeners finds **none**. There are no `addListener('team:...')` handlers in app.js.
Team agent data (teammates, tasks, colors) is tracked via:
-`this.teammateMap` (Map of agent info)
-`this.teammatePanesByName` (Map of pane targets)
-`this.teammateTerminals` (Map of terminal instances)
But these are populated from **subagent data**, not dedicated team events. Teammates appear as standard subagents and are detected by `subagent-watcher.ts` (as noted in MEMORY.md: "Teammates appear as standard subagents").
### What team events could trigger blinking?
Currently, subagent events have their own notification category:
Both are **disabled by default**. Even if enabled, they go through the standard `notify()` path which would trigger title blinking only when the tab is hidden.
There is no `TeammateIdle` or `TaskCompleted` hook handler in the frontend. The hooks are mentioned in MEMORY.md as valid settings schema keys but have no frontend implementation yet.
If/when team hooks are implemented, they should:
- Potentially trigger tab-alert-action (red blink) for teammate stuck/blocked states
- Use the notification system for teammate task completions
- Consider a new notification category (e.g., `teammate_idle`, `team_task_complete`) with configurable per-event preferences
| Memory leak risk | LOW | All timers have explicit cleanup paths |
### Potential Improvements
1.**Dynamic favicon**: Could swap favicon to a red/orange variant when there are unread critical notifications (common pattern in web apps).
2.**Team agent notifications**: When `TeammateIdle` and `TaskCompleted` hooks are implemented, add dedicated notification categories with configurable preferences in the per-event settings grid.
3.**Unread count on tab return**: Currently, returning to the tab stops the title flash but does NOT reset the unread count. The user must open the drawer or click individual notifications. Consider auto-marking as read after a brief delay when the tab becomes visible.
4.**Notification sound variety**: Currently all audio alerts use the same 660Hz sine wave. Different categories could use different tones (e.g., lower pitch for info, higher for critical).