Skip to content

fix(app): keep the unread session indicator stable during polling#1403

Open
chphch wants to merge 1 commit into
slopus:mainfrom
chphch:fix/unread-title-flicker-on-poll
Open

fix(app): keep the unread session indicator stable during polling#1403
chphch wants to merge 1 commit into
slopus:mainfrom
chphch:fix/unread-title-flicker-on-poll

Conversation

@chphch

@chphch chphch commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

The unread session indicator flickers off and back on during normal polling, with no user interaction.

buildSessionListViewData() bakes each row's hasUnread into the list snapshot, reading it from an optional unreadSessionIds set. applySessions() passes the set, but several other reducers rebuild the snapshot without it — most importantly applyMachines(), which runs on every machine heartbeat (update-machine, machine-activity) and the periodic fetchMachines poll. On each of those rebuilds hasUnread falls back to undefined?.has(id) ?? false, so every row loses its unread state until the next applySessions() restores it. Because machine heartbeats arrive on a timer, the unread indicator (the status dot/text on main) blinks off→on repeatedly while the user just watches the list.

The fix makes unreadSessionIds a required parameter on buildSessionRowData() / buildSessionListViewData() so any reducer that forgets it becomes a compile error, and threads state.unreadSessionIds through the four reducers that dropped it: updateSessionDraft, applyMachines, deleteMachine, deleteSession. (applySessions, markSessionRead, markSessionUnread, and setCurrentViewingSession already passed it.)

Repro: open the sessions list with at least one unread session and an online machine; the unread indicator blinks each time a machine heartbeat arrives (a few times a minute). After the fix it stays stable.

Verification: pnpm typecheck is clean. Making the parameter required is what guarantees completeness here — every call site is now compiler-checked. A running-app capture is awkward for a sub-second timer transient; happy to add one if you'd prefer it over the mechanism walkthrough above.

`buildSessionListViewData` bakes each row's `hasUnread` flag into a snapshot,
reading it from an *optional* `unreadSessionIds` set. `applySessions` passes
the set, but several other reducers rebuild the snapshot without it — most
importantly `applyMachines`, which fires on every machine heartbeat /
`fetchMachines` poll. On each such rebuild `hasUnread` falls back to
`undefined?.has(id) ?? false`, so every row loses its unread state until the
next `applySessions` restores it. The result is a distracting flicker of the
unread indicator (bold title on the bold-title builds, the status dot/text on
main) on the polling cadence, with no user action involved.

Make `unreadSessionIds` a required parameter on `buildSessionRowData` /
`buildSessionListViewData` so any reducer that forgets it is a compile error,
and thread `state.unreadSessionIds` through the call sites that dropped it
(`updateSessionDraft`, `applyMachines`, `deleteMachine`, `deleteSession`).

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
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