Skip to content

fix(unread): count the Dock badge only over nodes that still exist, and sweep stale entries - #1311

Closed
Senzaiken wants to merge 1 commit into
eneskirca:mainfrom
Senzaiken:fix/dock-badge-orphan-unread
Closed

Senzaiken wants to merge 1 commit into
eneskirca:mainfrom
Senzaiken:fix/dock-badge-orphan-unread

Conversation

@Senzaiken

Copy link
Copy Markdown
Contributor

What changed

The Dock badge (and the Windows taskbar overlay and tray count, which share it) summed every unread entry in the persisted agent-status table. That table is only pruned by the delete funnels that call remove, so a node dropped any other way (an outside edit or sync, an in-place reload) left its entry behind for good, still unread.

Measured on one machine: the table held 633 entries, 20 unread, and only 1 of those belonged to a node in any project. The Dock said 20 while the only tab badge said 1.

  • Badge count: unread is now counted over the nodes the projects hold, closed projects included (their sessions still run and "go to node" reopens them). It uses the same unread-id signature as the tab bar, moved into lib/unreadBadge.ts, so the two can't drift.
  • No boot-time 0: the badge is sent only once the workspace has loaded. Before, the first report would read 0 with no projects yet, and on Windows the real count a moment later would read as a rise and flash the taskbar (planBadge treats the first report as the baseline).
  • Sweep: at load, status entries whose node is in no project are deleted, unless their last hook event is under 7 days old. That window covers a node that legitimately comes back: an SSH project's offline copy lagging the host, or a git checkout restoring an older canvas. The sweep is skipped entirely when the load can't be trusted to be complete (no projects, or any unavailable placeholder, which carries no nodes). Deleted entries go through a new batch removeMany, one write per localStorage key.

How it was checked

  • New tests in lib/unreadBadge.test.ts cover the count, including the field case of 20 unread with 1 live, plus closed projects and a node held by two projects. They also cover the sweep's grace window and its refusals. agentStatus.persist.test.ts covers removeMany (both keys written, unknown ids ignored, no-op returns the same state).
  • The canvas, TabBar and agent-status suites pass (822 tests).
  • npm run typecheck is clean apart from WhiteboardEditor.tsx. Those errors come from @excalidraw/excalidraw not being installed in this checkout and are unrelated to this change.
  • Not run live: I haven't watched the Dock itself change in a running app.

Risks

  • An orphaned entry newer than 7 days is kept and simply no longer counted. It disappears on a later boot.
  • A node restored after more than 7 days (a long-lived branch checked out again) comes back without its saved status. If its tmux session is still alive it reattaches warm and nothing is lost. Otherwise it cold-starts without the hook-recorded resume id.

Surfaces: Desktop and Server Edition share this renderer code. Mobile is not affected (the phone's badges come from the core mirror, not this table).

The Dock / taskbar / tray unread count summed every `unread` entry in the
persisted agent-status table, which is only pruned by the delete funnels
that call `remove`. Nodes dropped any other way left their entries behind
for good: one machine had 633 entries, 20 unread, 1 of them a live node, so
the Dock read 20 beside a single tab badge of 1.

- Count unread over the nodes the projects hold (closed projects included),
  through the same unread-id signature the tab bar uses.
- Report the badge only once the workspace is loaded, so a boot-time 0
  followed by the real count does not read as a rise on Windows.
- At load, drop status entries whose node is in no project and whose last
  hook event is older than 7 days. Skipped when the load is incomplete (no
  projects, or an unavailable one).
@Senzaiken Senzaiken changed the title Count the Dock badge over live nodes, sweep orphaned status entries fix(unread): count the Dock badge only over nodes that still exist, and sweep stale entries Oct 28, 2026
@eneskirca

Copy link
Copy Markdown
Owner

Thanks @Senzaiken, this landed. Your commit (bfe63cc) is on main unchanged. It was merged together with a small follow-up in #1318, which is why this PR shows as closed rather than merged.

The one change on top: the orphan sweep's grace window went from 7 days to 90 days, and a test now pins it equal to LAST_SEEN_MAX_AGE_MS. A node id can come back after more than a week. Two examples: a folder dropped from a rebuilt index and opened again, or an older canvas restored by a git checkout, both keep their node ids. Sweeping the entry in that window would cost the node its resume id, so after a reboot it would start a fresh claude instead of claude --resume. The badge count no longer reads orphaned entries at all, so the sweep is only housekeeping, and the longer window costs only a little localStorage. The follow-up also rewraps the CLAUDE.md note to the file's ~100-column width.

Thanks for tracking this down, and for the tests. The removeMany coverage made the follow-up easy to verify.

nexusatemporal pushed a commit to nexusatemporal/nodeterm that referenced this pull request Oct 30, 2026
…(90 days)

Follow-up to eneskirca#1311. The boot sweep dropped an entry whose node is in no
project once its last hook event was 7 days old. That also hits a node
that legitimately comes back later: a folder dropped from a rebuilt
index keeps its node ids when it is opened again, and a git checkout can
restore an older canvas. Losing the entry costs that node its resume id,
so a cold start after a reboot launches a bare CLI instead of resuming.

The count no longer reads orphans, so the sweep only bounds storage and
a long window is cheap. The grace now equals LAST_SEEN_MAX_AGE_MS: an
orphan goes on the same boot its clock ages out on load, never earlier.
A test pins the two constants together.

Also rewraps the CLAUDE.md lines the PR left at ~125 columns and notes
the sweep in the lastSeen field comment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

2 participants