Skip to content

cmux NEXT sidebar: remote Priority rows age out and keep their read marks - #23609

Merged
teamleaderleo merged 7 commits into
feat-cmux-nextfrom
leo/priority-remote-followups
Oct 21, 2026
Merged

teamleaderleo merged 7 commits into
feat-cmux-nextfrom
leo/priority-remote-followups

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #23588 (merged as 19ee4e3), fixing the non-blocking findings from its review. #23588 made the Priority view list agent chats from the trees of connected Cloud, SSH and server daemons.

  • Remote idle rows now age out. A remote tab with no unread notice had since = nil, and the ranking keeps idle rows with a nil since. A month-old VM chat stayed listed forever. A remote row now ages from its last notice's time, read or not. That time comes from the tab's notice or the daemon's notice ledger. If neither has a time, the row ages from when the app first saw it. It then leaves after idleHorizon like this Mac's idle rows, and the view's next wake is set for that moment.

  • Read and snooze marks on remote rows survive a relaunch. The mark stamp was state plus since, and for a notice with no time since came from an in-memory first-seen map. After a relaunch, or once that map was cleared at 4096 entries, the row came back unread or unsnoozed. A lane now has an optional markKey, and the stamp uses it in place of since. Local lanes leave it nil, so their stamps are unchanged. A remote lane sets it as follows:

    • A notice with a known time (from the tab or the notice ledger): n<id>@<createdAtMs>.
    • An untimed notice: n<id>@g<generation>. generation is the daemon boot id from the handshake (DaemonIdentity.generation, which "fences one daemon boot"), read from DaemonStore.identity. The store keeps it through a disconnect and replaces it on every reconnect.
    • A tab with no notice: none.

    The first-seen map now affects only how long an idle row stays listed.

    • Why: a restarted daemon renumbers its notices (ledger position + 1) and continues numbering from the ledger's length + 1, so a new notice can reuse a marked one's id. With the time, or for untimed notices the boot that numbered them, in the key, a reused id from a new boot never matches an old mark. A new question shows instead of staying hidden. Current daemons send no time on a tab's notice, so the boot part is what applies in practice.
    • Why not a persisted first-seen time: it would need a second store to bound and prune, and it would still have to be keyed by the notice id and the boot to be safe. The notice id and the boot id are already in the tree and the handshake.
    • When marks end: a re-render, a tree update, a disconnect that keeps the last tree, and a reconnect to the same daemon boot all keep a mark. A daemon restart (a new generation) ends every mark on that machine's untimed notices, so those rows show again. This is the safe direction: a mark is lost rather than a new ask hidden.
    • Limits:
      • Remote read and snooze marks set under 19ee4e3 (stamped with a time) are lost once on upgrade, and those rows show again.
      • An untimed row's age falls back to the first-seen time, which is not saved. After a relaunch, an idle untimed remote row shows again for up to 24 h.
  • The chats are built once per change. The rows, the next wake and the prune each rebuilt the chats and the machine list (three times per change, per window). AgentPriorityChats.snapshot() now builds them once in panel(), and the loop reuses that snapshot for scheduleNextChange and the prune. A machine's lanes() reads the notice ledger's times into a map at most once, instead of a notifications.last scan per unread tab. Pruning still waits for every store and machine to be live, and observation tracks the same values as before.

  • Opening a remote row goes to the machine the row came from. AgentPriorityChats.remoteTab(showing:) searches the same machine list as the rows, in the same order. Before, MachineRegistry.remoteAgentTab searched every remote daemon, including ones whose feature is turned off. It is removed.

No new remote request or relay method: everything is read from trees and notices the app already mirrors.

Testing

New suite SidebarPriorityRemoteRowsTests. The red commit is c6df018 (tests only, on base 19ee4e3).

Command: cmux-ci run --class light --script scripts/ci/package-test-lane.sh --ref <sha> --arg=suite --arg=Packages/macOS/CmuxNext --arg=<suites> --env CMUX_CI_XCODE_APP=/Applications/Xcode_26.6.app

Round 1: aging, marks across a relaunch, building once.

  • Red at c6df018 (tests only, on base 19ee4e3), step 4a88697f10964a5c705e1701 (SidebarPriorityRemoteRowsTests): exit 1, 6 tests, 9 issues.
    • aRemoteIdleRowAgesOutLikeThisMacsIdleRows: a two-day-old idle row was still listed, an idle row was still listed 25 h later, and nextChange was nil.
    • aReadOrSnoozeMarkOnAnUntimedNoticeOutlivesTheAppsMemory: once the first-seen map was cleared, and again after a reload from the same defaults, the read row was back to .done and the snoozed row was listed again.
    • oneChangeBuildsTheChatsOnce: builds.count was 4 per change, expected 1.
    • aClosedRemoteTabDropsOffAndLosesItsMarks, aMachineThatIsNotConnectedKeepsItsRowsAndMarks and archivingARemoteRowDoesNothing passed. They cover behavior the base already had and were missing tests.
  • Green at 6127214, step 53ac369e7092422b888d60d1: exit 0.

Round 2 (review): a reused notice id.

  • Red at 393be48 (tests only), step ec59b54c09f380c2cb8301b7 (SidebarPriorityRemoteRowsTests): exit 1, 8 tests, 2 issues.
    • aMarkOnARemoteNoticeEndsWhenARestartedDaemonReusesItsIdForANewNotice: after a restart reused ids 12 and 13 at a new time, the new question was still hidden (nil, expected .needsYou) and the new result still read (.idle, expected .done). The same id at the same time kept its marks across a reload.
    • The open-routing test now gives each machine's tabs their own surfaces. It checks that the two tabs differ, and that the row's machine is the one opened with either machine listed first.
  • Green at 25dfb13, step 30f119a3daf2cfebfc1fbb3b (SidebarPriorityRemoteRowsTests, SidebarPriorityRemoteChatsTests, SidebarPriorityRowActionsTests, AgentPriorityRankingTests): exit 0, 8 + 3 + 5 + 7 tests passed.

Round 3 (review): an untimed reused id. The branch first merged origin/feat-cmux-next at ab17fd0, resolving one conflict in AgentActivityLane.swift by keeping both base's updatedAt/createdAt and this PR's markKey.

  • Red at 2869100 (tests only), step bde7239c73446ac1d6e1aff4 (SidebarPriorityRemoteRowsTests): exit 1, 9 tests, 2 issues.
    • aMarkOnAnUntimedNoticeEndsWhenANewDaemonBootReusesItsId: after a new boot (generation boot-1 to boot-2) reused ids 12 and 13 untimed, the new question was still hidden (nil, expected .needsYou) and the new result still read (.idle, expected .done). The same test checks that a re-render, a disconnect and a reconnect to the same boot keep both marks; those parts passed.
  • Green at 124e23e, step 04c1b2c3a471495bd68b1892 (SidebarPriorityRemoteRowsTests, SidebarPriorityRemoteChatsTests, SidebarPriorityRowActionsTests, AgentPriorityRankingTests): exit 0, 9 + 3 + 5 + 7 tests passed.

Other checks:

Observed / Not run

  • Observed: the red and green steps above, the app compile checks and the prepush gate.
  • openingARemoteRowRevealsTheTabOnTheMachineWhoseRowItIs was added with the fix, because remoteTab(showing:) is new; on the base it would fail to compile. It passes on the head.
  • Not run: a live tagged build against a real Cloud or SSH machine. The App-side open change (AgentPriorityChats.app.remoteTab) is covered only by the compile check and by reading the code.

Changelog

Fixed: In the sidebar's Priority view, idle chats on Cloud, SSH and server machines now leave after a day, and read or snoozed remote chats stay that way after cmux restarts

Proof

No capture: no layout or visual change. The rows that show and their marks are covered by the tests above.

Agent session: cosmic-gray

🤖 Generated with Claude Code

teamleaderleo and others added 2 commits October 21, 2026 10:39
… marks and build once

An idle chat on another machine's tree had no time and never left the view; a read
or snooze mark on a notice with no time held only while the app remembered when it
first saw it; and each change built the chats three times (rows, next wake, prune).
Also covers a closed remote tab losing its marks, a machine that is not connected
keeping them, and archive leaving a remote row alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bf1-fe0d-7f92-b3aa-5ffc1910a994&host=cosmic-gray
…arks

An idle chat on another machine's tree now ages from its last notice's time, else
from when the app first saw it, and leaves after a day like this Mac's idle chats.
A read or snooze mark on a remote row holds for the notice it was made on (the lane's
markKey), not for the time the app first saw it, so it outlives a relaunch.

The view builds its chats once per change and the next wake and the prune reuse
them; a machine's lanes read the notice ledger's times once. Opening a remote row
reveals the tab on the machine the row came from, from the same machine list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bf1-fe0d-7f92-b3aa-5ffc1910a994&host=cosmic-gray
@teamleaderleo teamleaderleo added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 21, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Owned by priority-view on cosmic-gray · session

…on reuses its notice id

A daemon restart renumbers its notices, so a new notice can reuse the id of one
the person snoozed or read; its new time must end the mark. Also makes the
open-routing test tell the two machines' tabs apart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bf1-fe0d-7f92-b3aa-5ffc1910a994&host=cosmic-gray
…hen the time is known

A restarted daemon renumbers its notices, so a new notice can reuse a marked one's
id; keying a mark on the id alone hid a new question. The key now carries the
notice's time whenever the tab or the ledger has one, and the id alone only for
an untimed notice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bf1-fe0d-7f92-b3aa-5ffc1910a994&host=cosmic-gray
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Bead cx-62114 created for this PR by the cmux coordinator.

teamleaderleo and others added 2 commits October 21, 2026 11:07
…rity-remote-followups

# Conflicts:
#	Packages/macOS/CmuxNext/Sources/CmuxNextAgentActivity/AgentActivityLane.swift
…aemon boot reuses its id

Current daemons send no time on a tab's notice, and a restarted daemon renumbers
its notices, so a new question could reuse a snoozed one's id and stay hidden. A
re-render, a disconnect and a reconnect to the same boot keep the marks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bf1-fe0d-7f92-b3aa-5ffc1910a994&host=cosmic-gray
@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review 124e23e: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr23609 -f ref=leo/priority-remote-followups -f platform=macos -f backend_mode=local

Not verified: macOS jobs did not run: compile and app tests were skipped on 124e23e2a3; ci-status is green without them.

CI passes on 124e23e2a3 (run 39650078765 attempt 1).
macOS jobs did not run: compile and app tests were skipped: Fast static checks skipped (the macos job needs both to succeed). Re-run CI once they pass, before merging.

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's; yours means the failing file is one this PR changes, also red on main that main's latest full suite fails the same way, seen on other PRs that it failed on another pull request's run lately.

red: cmux-next checks (god files, concurrency, crash safety, l10n); base red: the same failure on the base ab17fd0; does not block

Tour of 124e23e2a3 (run): no media. The tour job failed before the tour ran (build or setup); the run log has the output.

… the daemon boot

A restarted daemon renumbers its notices, and current daemons send no time on a
tab's notice, so an id-only key let a new question that reused a snoozed id stay
hidden. The key of an untimed notice now also names the daemon boot that numbered
it (the handshake's generation, which DaemonStore keeps through a disconnect): a
new boot ends the mark, a re-render or a reconnect to the same boot keeps it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bf1-fe0d-7f92-b3aa-5ffc1910a994&host=cosmic-gray
@blacksmith-sh

blacksmith-sh Bot commented Oct 21, 2026

Copy link
Copy Markdown

Found 9 test failures on Blacksmith runners:

Failures

Test View Logs
/tmp/tmp3_1rjku3/summary-missing.json (live):/summary-missing.json (live): View Logs
/tmp/tmpxeqvjuzd/summary-fail.json (live):/summary-fail.json (live): View Logs
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows/
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows
View Logs
[new_workspaces] the window draws no frames/[new_workspaces] the window draws no frames View Logs
[tabs] debug.tab_switch timed 0 of 40 switches/
[tabs] debug.tab_switch timed 0 of 40 switches
View Logs
[tabs] the window draws no frames/[tabs] the window draws no frames View Logs
convention violations found (ERROR lines above)./
convention violations found (ERROR lines above).
View Logs
test_a_file_a_test_reads_selects_that_test (main.PullRequestTiers.test_a_file_a_tes
t_reads_selects_that_test)/
test_a_file_a_test_reads_selects_that_test (main.PullRequestTiers.test_a_file_a_tes
t_reads_selects_that_test)
View Logs
test_a_shared_module_selects_its_dependents (main.PullRequestTiers.test_a_shared_mo
dule_selects_its_dependents)/
test_a_shared_module_selects_its_dependents (main.PullRequestTiers.test_a_shared_mo
dule_selects_its_dependents)
View Logs

Fix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

cmux-next crash-class rules / cmux-next crash-class rules is red on 8 of 8 fresh feat-cmux-next PR heads in the last 2:00:00, so it is most likely a base red, not this PR's. The base-reds lane has it; a base merge picks up the fix once it lands.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

cmux-next guards / cmux-next localization debt ratchet is red on 10 of 10 fresh feat-cmux-next PR heads in the last 2:00:00, so it is most likely a base red, not this PR's. The base-reds lane has it; a base merge picks up the fix once it lands.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Merge receipt at 124e23e

  • Independent review: SHIP at 124e23e (untimed remote marks keyed n@g; traced generation from the daemon handshake to the key; a stale mark can't hide a new Needs-you).
  • Red then green: the boot-reuse test failed at 2869100 (fleet step bde7239c73446ac1d6e1aff4, 2 issues) and passed at 124e23e (step 04c1b2c3a471495bd68b1892, the four Priority suites). Fleet compile-check --app passed (job 34d3135379880594184125b4).
  • Reds at this head are not this PR's:
  • Optional hardening left for later (reviewer note, not reachable with today's daemons): key on the boot whenever the tab's own notice has no time.

@teamleaderleo
teamleaderleo merged commit b9f6725 into feat-cmux-next Oct 21, 2026
@teamleaderleo
teamleaderleo deleted the leo/priority-remote-followups branch October 21, 2026 19:18
@github-actions

Copy link
Copy Markdown
Contributor

Shipped in:

This branch was previously deployed

1 inactive deployment
ci-routing-agent — 124e23e2 Deployed Oct 21, 2026 by teamleaderleo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:normal Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant