cmux NEXT: a tab an agent needs you in shows the blue dot, the ring and why (tab-attention-v1)

TE
7d ago
+683-57

Leo-ask: ask-12231fbe5e

Leo: "idk where things are man i dont see anything other than the current workspace which is you and you have a cmux next github brwoser tab open (we need to have cmux expose this harder if it isn't already/hve better instructions)"

Slice B of the "needs you here" surface: the app shows a tab an agent marked. The daemon half is #24286 (tab.update {attention}, cmux attention request|clear, tab-attention-v1), and this PR lands after it: the bundled-daemon capability check needs that daemon. The palette command and Needs you list item come in slice C, the agent instructions in #24299.

What changes

  • The tab record's extra.attention {reason, at_ms} becomes TabModel.attention (TabAttention, CmuxNextDaemon).
  • TabModel.showsUnread reads it, so the existing blue treatment shows it everywhere showsUnread is read. There is no new dot and no new drawing code:
    • the tab's dot (TabItemMapping);
    • the screen bar;
    • the sidebar tab list;
    • the pane's blue attention ring.
  • The workspace row gets its manual dot, and its text line shows the reason ("Confirm GitHub access") ahead of any notification text.
  • Going into the pane (a click or a key) or Dismiss Highlight hides the ring. The dot stays until the agent clears the mark, the page goes elsewhere or the tab closes. A new request (a newer at_ms) rings again.
  • Mark as Read on the tab also drops the mark (tab.update {attention: null}, when the daemon has tab-attention-v1).
  • notifications.attention.showOnSidebar = false hides it like any unread.

Tests

TabAttentionTests (CmuxNextAppTests):

  • parsing, including malformed values;
  • showsUnread follows the record;
  • the row's dot, the reason and the tab dot; showOnSidebar off hides it;
  • the ring rings, stays on focus, hides on a click and comes back for a new request; Dismiss Highlight hides it.

Fleet compile-check at 2ae82681c1691c14ace328d3e746939a47a12547: compile check app: passed.

Final-head UI capture (built at 5cbebfcba7d1e4f106e13850ff83a8b82d7190d5; final 384766854b790a70e7725f42653508a789eb4266 changes only test-fixture scheduling): the fleet build launched, created and focused an Attention workspace, and issued attention request --tab current --reason "Confirm GitHub access". The sidebar shows the blue attention dot on the workspace and tab in both themes.

LightDark
lightdark

Agent session: cosmic-gray

šŸ¤– Generated with Claude Code


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


Summary by cubic

A tab an agent marked "needs you here" shows the existing unread dot and ring, and its workspace row says why. The daemon half lands here too, with tab-attention-v1 and the conditional tab-attention-clear-revision-v1.

  • The tab's extra.attention {reason, at_ms} becomes TabModel.attention; showsUnread reads it, so the tab dot, screen bar, sidebar list, and pane ring all show the mark with no new drawing. at_ms must be a whole nonnegative number; malformed values are no mark.
  • The workspace row gets the manual dot and shows the reason ahead of any notification text.
  • Going into the pane or Dismiss Highlight hides the ring; the dot stays until the agent clears it, the page navigates away, or the tab closes. Mark as Read also drops the mark, but only when the daemon advertises conditional clears and state resources — without them the clear is refused rather than sent unguarded. Ring dismissal compares a client-local generation, so a newer request rings again even if the daemon clock steps backwards or two requests share a millisecond. An agent mark on a remote app only ever shows the cmux round-tripped state.
  • Clicks, keys, and delayed strip clicks resolve the tab on the machine that owns the window's workspace — a remote marked tab hides only its own ring, and typing in a remote terminal clears no local workspace mark — and menu Mark as Read and Dismiss Highlight items are bound to their machine, including a pinned workspace tile's menu; an action is refused when that machine is gone. A strip press captures the exact tab and store under the pointer, so a workspace switch before mouse-up can't redirect the click.
  • Clears apply immediately, roll back only their own request, and carry the request's at_ms to the daemon, which refuses to clear a newer mark or accept a malformed guard. The daemon assigns each mark a monotonic at_ms it persists across clears and tab close, so a clock step backwards or two requests in the same millisecond still ring as new, and a delayed clear never matches a later request.
  • notifications.attention.showOnSidebar = false hides it like any unread.