Repository navigation
cmux NEXT sidebar: remote Priority rows age out and keep their read marks - #23609
Conversation
… 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
|
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
|
Bead |
…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
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
Not verified: macOS jobs did not run: compile and app tests were skipped on CI passes on Written by red: cmux-next checks (god files, concurrency, crash safety, l10n); base red: the same failure on the base ab17fd0; does not block Tour of |
… 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
|
Found 9 test failures on Blacksmith runners: Failures
|
|
|
|
|
|
Merge receipt at 124e23e
|
|
Shipped in:
|
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 nilsince. 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 afteridleHorizonlike 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 timesincecame 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 optionalmarkKey, and the stamp uses it in place ofsince. Local lanes leave it nil, so their stamps are unchanged. A remote lane sets it as follows:n<id>@<createdAtMs>.n<id>@g<generation>.generationis the daemon boot id from the handshake (DaemonIdentity.generation, which "fences one daemon boot"), read fromDaemonStore.identity. The store keeps it through a disconnect and replaces it on every reconnect.none.The first-seen map now affects only how long an idle row stays listed.
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 inpanel(), and the loop reuses that snapshot forscheduleNextChangeand the prune. A machine'slanes()reads the notice ledger's times into a map at most once, instead of anotifications.lastscan 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.remoteAgentTabsearched 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.appRound 1: aging, marks across a relaunch, building once.
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, andnextChangewas nil.aReadOrSnoozeMarkOnAnUntimedNoticeOutlivesTheAppsMemory: once the first-seen map was cleared, and again after a reload from the same defaults, the read row was back to.doneand the snoozed row was listed again.oneChangeBuildsTheChatsOnce:builds.countwas 4 per change, expected 1.aClosedRemoteTabDropsOffAndLosesItsMarks,aMachineThatIsNotConnectedKeepsItsRowsAndMarksandarchivingARemoteRowDoesNothingpassed. They cover behavior the base already had and were missing tests.53ac369e7092422b888d60d1: exit 0.Round 2 (review): a reused notice id.
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.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.swiftby keeping both base'supdatedAt/createdAtand this PR'smarkKey.bde7239c73446ac1d6e1aff4(SidebarPriorityRemoteRowsTests): exit 1, 9 tests, 2 issues.aMarkOnAnUntimedNoticeEndsWhenANewDaemonBootReusesItsId: after a new boot (generationboot-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.04c1b2c3a471495bd68b1892(SidebarPriorityRemoteRowsTests,SidebarPriorityRemoteChatsTests,SidebarPriorityRowActionsTests,AgentPriorityRankingTests): exit 0, 9 + 3 + 5 + 7 tests passed.Other checks:
cmux-ci compile-check --ref 124e23e2a3f1b04f8dde907427233f28cfd45429 --app: passed (job34d3135379880594184125b4). Earlier heads also passed (jobsf5a74daa575a504e9b42fc71and955dbe9f8d0a65d20c084eab).python3 scripts/cmux-next/prepush-gate.pybefore each push: no new violations.git merge-treeof 124e23e againstleo/priority-followups(cmux NEXT sidebar: Priority view follow-ups (palette titles, closing rows, archive check) #23597): clean.Observed / Not run
openingARemoteRowRevealsTheTabOnTheMachineWhoseRowItIswas added with the fix, becauseremoteTab(showing:)is new; on the base it would fail to compile. It passes on the head.openchange (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