Repository navigation
cmux-next: draw workspace status, progress and log in sidebar rows - #16431
Conversation
The daemon already stores per-workspace status entries, progress and a log (workspace_status.*) and the new CLI writes them (`cmux workspace [<sel>] status|progress|log`), but the app never read them: its WorkspaceStatusBoard was fed only by the deleted compat layer. The app now opens one `session.events` stream per daemon connection right after `subscribe` and keeps only its workspace_status changes: the opening snapshot resets the store's status map, deltas upsert or delete one workspace. Status-less deltas are dropped on the reader thread, so they never wake the main actor. Status events bypass the tree snapshot barrier (list-workspaces does not carry them), a stream end reopens the stream within a small budget, and an inbox collapse keeps one stale marker that asks for a fresh stream. The transport routes v2 stream lines as events, and the stream open never joins the FIFO that id-less raw errors resolve against. Sidebar rows draw the old app's block under the title: up to three entries (icon and text in the entry color) then "N more", the newest log line with its level glyph, then the progress bar and its label. Row height grows one status line per line plus the bar. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Agents: |
This comment has been minimized.
This comment has been minimized.
|
Unclear: CI failed on
Not re-run automatically: Written by |
Status lines take at least their font's height, hex colors need their #, a closed socket forgets its stream, and the placement and lifetime fakes ignore the handshake's session.events request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
2 issues found and verified against the latest diff
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="Packages/macOS/CmuxNext/Sources/CmuxNextDaemon/Store/DaemonStore+Driver.swift">
<violation number="1" location="Packages/macOS/CmuxNext/Sources/CmuxNextDaemon/Store/DaemonStore+Driver.swift:64">
P2: This stale marker resets the session-events no-snapshot retry budget, allowing a daemon that repeatedly ends the stream to loop indefinitely whenever status traffic is dropped. Refresh the stream without resetting the budget; only a successfully received snapshot should refill it.</violation>
</file>
<file name="Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Views/WorkspaceStatusView.swift">
<violation number="1" location="Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Views/WorkspaceStatusView.swift:18">
P2: The progress-bar sublayers use top-origin coordinates, but this flipped view never marks its backing layer as geometry-flipped. Set the backing layer’s `isGeometryFlipped` to `true`, otherwise Core Animation can render the bar vertically mirrored relative to the labels.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| case .workspaceStatus(_, let stream): | ||
| guard !state.statusStale else { return } | ||
| state.statusStale = true | ||
| state.events.append(DaemonEventEnvelope(sequence: envelope.sequence, event: .workspaceStatus(.stale, stream: stream))) |
There was a problem hiding this comment.
P2: This stale marker resets the session-events no-snapshot retry budget, allowing a daemon that repeatedly ends the stream to loop indefinitely whenever status traffic is dropped. Refresh the stream without resetting the budget; only a successfully received snapshot should refill it.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Sources/CmuxNextDaemon/Store/DaemonStore+Driver.swift, line 64:
<comment>This stale marker resets the session-events no-snapshot retry budget, allowing a daemon that repeatedly ends the stream to loop indefinitely whenever status traffic is dropped. Refresh the stream without resetting the budget; only a successfully received snapshot should refill it.</comment>
<file context>
@@ -49,12 +51,17 @@ final class EventInbox: Sendable {
+ case .workspaceStatus(_, let stream):
+ guard !state.statusStale else { return }
+ state.statusStale = true
+ state.events.append(DaemonEventEnvelope(sequence: envelope.sequence, event: .workspaceStatus(.stale, stream: stream)))
default:
guard let transaction = envelope.event.clientTransactionID, state.echoes.insert(transaction).inserted else { return }
</file context>
|
|
||
| override init(frame: NSRect) { | ||
| super.init(frame: frame) | ||
| wantsLayer = true |
There was a problem hiding this comment.
P2: The progress-bar sublayers use top-origin coordinates, but this flipped view never marks its backing layer as geometry-flipped. Set the backing layer’s isGeometryFlipped to true, otherwise Core Animation can render the bar vertically mirrored relative to the labels.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Views/WorkspaceStatusView.swift, line 18:
<comment>The progress-bar sublayers use top-origin coordinates, but this flipped view never marks its backing layer as geometry-flipped. Set the backing layer’s `isGeometryFlipped` to `true`, otherwise Core Animation can render the bar vertically mirrored relative to the labels.</comment>
<file context>
@@ -0,0 +1,158 @@
+
+ override init(frame: NSRect) {
+ super.init(frame: frame)
+ wantsLayer = true
+ for bar in [track, fill] {
+ bar.cornerCurve = .continuous
</file context>
| wantsLayer = true | |
| wantsLayer = true | |
| layer?.isGeometryFlipped = true |
There was a problem hiding this comment.
1 existing issue remains and 6 new issues found across 36 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Resources/Localizable.xcstrings">
<violation number="1" location="Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Resources/Localizable.xcstrings:3416">
P2: The consumer `Strings.statusMore` reads this key with `String(localized:defaultValue:)`, which does not perform printf-style formatting, so the `%lld` placeholders in the catalog values render literally ("%lld more") instead of the count. Wrap the localized string in `String(format:)` in the consumer, e.g. `String(format: String(localized: "sidebar.status.more", defaultValue: "\(value) more", bundle: .module), value)`, or drop the `%lld` placeholders here if formatting is not intended.</violation>
</file>
<file name="Packages/macOS/CmuxNext/Sources/CmuxNextDaemon/Tree/WorkspaceStatusSnapshot.swift">
<violation number="1" location="Packages/macOS/CmuxNext/Sources/CmuxNextDaemon/Tree/WorkspaceStatusSnapshot.swift:80">
P2: These four snapshot fields are required by resource-api-v2, but `decodeIfPresent` turns a partial stream payload into a valid reset and can erase the current status map. Require the keys while still allowing the required nullable fields to contain `null`, so malformed snapshots are discarded rather than applied.</violation>
</file>
<file name="Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Model/SidebarWorkspaceStatus.swift">
<violation number="1" location="Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Model/SidebarWorkspaceStatus.swift:45">
P2: `Progress.value` can bypass the documented `0...1` invariant after initialization because it is publicly writable. Make the setter private (or clamp every assignment) before the renderer uses it to size the fill layer.</violation>
</file>
<file name="Packages/macOS/CmuxNext/Sources/CmuxNextDaemon/Connection/DaemonConnection+SessionEvents.swift">
<violation number="1" location="Packages/macOS/CmuxNext/Sources/CmuxNextDaemon/Connection/DaemonConnection+SessionEvents.swift:63">
P2: A stream ending during the handshake loses recovery because `reopen()` runs while the connection is still `.connecting`, when `reopenSessionEvents` ignores it. Defer this reopen until the handshake publishes `.ready`, or retry it after the initial open completes.</violation>
</file>
<file name="Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Views/WorkspaceStatusView.swift">
<violation number="1" location="Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Views/WorkspaceStatusView.swift:73">
P2: Each status line advances only `statusLineHeight` (`y += lineHeight`), but its label frame is sized to `labelHeight = max(statusLineHeight, ceil(subtitleFont.boundingRectForFont.height))` and centered on the slot. When the font's actual line height exceeds `statusLineHeight`, adjacent status lines overlap. Guarantee `labelHeight <= lineHeight` (e.g. clamp it) so multi-line status rows do not clip.</violation>
</file>
<file name="plans/cmux-next/cli-compat.md">
<violation number="1" location="plans/cmux-next/cli-compat.md:85">
P3: The updated `set_status` row says progress and log are superseded by `cmux workspace status|progress|log` (daemon `workspace_status.*`), but the adjacent `set_progress / clear_progress / log / clear_log / list_log / sidebar_state` row still classifies them as `impl (same store)`. Since the new command and the Decisions note cover progress and log as daemon state, update that row (separate the still-store-sidebar_state verb) to keep the compat table internally consistent.</violation>
</file>
Requires human review: Auto-approval blocked because this review re-detected 1 unresolved issue already reported by Cubic.
Re-trigger cubic
| "en": { | ||
| "stringUnit": { | ||
| "state": "translated", | ||
| "value": "%lld more" |
There was a problem hiding this comment.
P2: The consumer Strings.statusMore reads this key with String(localized:defaultValue:), which does not perform printf-style formatting, so the %lld placeholders in the catalog values render literally ("%lld more") instead of the count. Wrap the localized string in String(format:) in the consumer, e.g. String(format: String(localized: "sidebar.status.more", defaultValue: "\(value) more", bundle: .module), value), or drop the %lld placeholders here if formatting is not intended.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Resources/Localizable.xcstrings, line 3416:
<comment>The consumer `Strings.statusMore` reads this key with `String(localized:defaultValue:)`, which does not perform printf-style formatting, so the `%lld` placeholders in the catalog values render literally ("%lld more") instead of the count. Wrap the localized string in `String(format:)` in the consumer, e.g. `String(format: String(localized: "sidebar.status.more", defaultValue: "\(value) more", bundle: .module), value)`, or drop the `%lld` placeholders here if formatting is not intended.</comment>
<file context>
@@ -3406,6 +3406,137 @@
+ "en": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "%lld more"
+ }
+ },
</file context>
| public init(from decoder: any Decoder) throws { | ||
| let c = try decoder.container(keyedBy: CodingKeys.self) | ||
| workspaceID = try c.decode(ResourceID.self, forKey: .workspaceID) | ||
| entries = try c.decodeIfPresent([Entry].self, forKey: .entries) ?? [] |
There was a problem hiding this comment.
P2: These four snapshot fields are required by resource-api-v2, but decodeIfPresent turns a partial stream payload into a valid reset and can erase the current status map. Require the keys while still allowing the required nullable fields to contain null, so malformed snapshots are discarded rather than applied.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Sources/CmuxNextDaemon/Tree/WorkspaceStatusSnapshot.swift, line 80:
<comment>These four snapshot fields are required by resource-api-v2, but `decodeIfPresent` turns a partial stream payload into a valid reset and can erase the current status map. Require the keys while still allowing the required nullable fields to contain `null`, so malformed snapshots are discarded rather than applied.</comment>
<file context>
@@ -0,0 +1,85 @@
+ public init(from decoder: any Decoder) throws {
+ let c = try decoder.container(keyedBy: CodingKeys.self)
+ workspaceID = try c.decode(ResourceID.self, forKey: .workspaceID)
+ entries = try c.decodeIfPresent([Entry].self, forKey: .entries) ?? []
+ progress = try c.decodeIfPresent(Progress.self, forKey: .progress)
+ logCount = try c.decodeIfPresent(Int.self, forKey: .logCount) ?? 0
</file context>
|
|
||
| /// `value` in 0...1; nil draws an empty track (indeterminate). | ||
| public nonisolated struct Progress: Hashable, Sendable { | ||
| public var value: Double? |
There was a problem hiding this comment.
P2: Progress.value can bypass the documented 0...1 invariant after initialization because it is publicly writable. Make the setter private (or clamp every assignment) before the renderer uses it to size the fill layer.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Model/SidebarWorkspaceStatus.swift, line 45:
<comment>`Progress.value` can bypass the documented `0...1` invariant after initialization because it is publicly writable. Make the setter private (or clamp every assignment) before the renderer uses it to size the fill layer.</comment>
<file context>
@@ -0,0 +1,133 @@
+
+ /// `value` in 0...1; nil draws an empty track (indeterminate).
+ public nonisolated struct Progress: Hashable, Sendable {
+ public var value: Double?
+ public var label: String?
+
</file context>
| public var value: Double? | |
| public private(set) var value: Double? |
| state.endsWithoutSnapshot += 1 | ||
| return state.endsWithoutSnapshot <= Self.reopenBudget | ||
| } | ||
| if again { reopen() } |
There was a problem hiding this comment.
P2: A stream ending during the handshake loses recovery because reopen() runs while the connection is still .connecting, when reopenSessionEvents ignores it. Defer this reopen until the handshake publishes .ready, or retry it after the initial open completes.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Sources/CmuxNextDaemon/Connection/DaemonConnection+SessionEvents.swift, line 63:
<comment>A stream ending during the handshake loses recovery because `reopen()` runs while the connection is still `.connecting`, when `reopenSessionEvents` ignores it. Defer this reopen until the handshake publishes `.ready`, or retry it after the initial open completes.</comment>
<file context>
@@ -0,0 +1,89 @@
+ state.endsWithoutSnapshot += 1
+ return state.endsWithoutSnapshot <= Self.reopenBudget
+ }
+ if again { reopen() }
+ return false
+ case .unknown(let name, _) where name == "stream_item" || name == "stream_end":
</file context>
| layoutBar(y: y, height: metrics.progressBarHeight) | ||
| y += metrics.progressBarHeight | ||
| } | ||
| labels[index].frame = NSRect(x: 0, y: y - (labelHeight - lineHeight) / 2, width: bounds.width, height: labelHeight) |
There was a problem hiding this comment.
P2: Each status line advances only statusLineHeight (y += lineHeight), but its label frame is sized to labelHeight = max(statusLineHeight, ceil(subtitleFont.boundingRectForFont.height)) and centered on the slot. When the font's actual line height exceeds statusLineHeight, adjacent status lines overlap. Guarantee labelHeight <= lineHeight (e.g. clamp it) so multi-line status rows do not clip.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Sources/CmuxNextSidebar/Views/WorkspaceStatusView.swift, line 73:
<comment>Each status line advances only `statusLineHeight` (`y += lineHeight`), but its label frame is sized to `labelHeight = max(statusLineHeight, ceil(subtitleFont.boundingRectForFont.height))` and centered on the slot. When the font's actual line height exceeds `statusLineHeight`, adjacent status lines overlap. Guarantee `labelHeight <= lineHeight` (e.g. clamp it) so multi-line status rows do not clip.</comment>
<file context>
@@ -0,0 +1,158 @@
+ layoutBar(y: y, height: metrics.progressBarHeight)
+ y += metrics.progressBarHeight
+ }
+ labels[index].frame = NSRect(x: 0, y: y - (labelHeight - lineHeight) / 2, width: bounds.width, height: labelHeight)
+ y += lineHeight
+ }
</file context>
| | agent.resolve_delivery_target | 0 | 0 | yes | hooks | impl by surface_id; pid routing answers "No live delivery target" | | ||
| | agent.hook.*, surface.resume.*, workspace.set_auto_title, surface.sync_codex_native_title, agent.hibernation.* | 0 | 0 | yes | hooks | unsup: agent state moves to cmux-tui report-agent | | ||
| | v1 set_status / clear_status / list_status | 3/1 (CLI) | 2 | yes | set-status, … | impl: in-memory per workspace (bounded), not rendered in the sidebar yet | | ||
| | v1 set_status / clear_status / list_status | 3/1 (CLI) | 2 | yes | set-status, … | superseded: `cmux workspace status\|progress\|log` (daemon state), rendered in the sidebar row | |
There was a problem hiding this comment.
P3: The updated set_status row says progress and log are superseded by cmux workspace status|progress|log (daemon workspace_status.*), but the adjacent set_progress / clear_progress / log / clear_log / list_log / sidebar_state row still classifies them as impl (same store). Since the new command and the Decisions note cover progress and log as daemon state, update that row (separate the still-store-sidebar_state verb) to keep the compat table internally consistent.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At plans/cmux-next/cli-compat.md, line 85:
<comment>The updated `set_status` row says progress and log are superseded by `cmux workspace status|progress|log` (daemon `workspace_status.*`), but the adjacent `set_progress / clear_progress / log / clear_log / list_log / sidebar_state` row still classifies them as `impl (same store)`. Since the new command and the Decisions note cover progress and log as daemon state, update that row (separate the still-store-sidebar_state verb) to keep the compat table internally consistent.</comment>
<file context>
@@ -82,7 +82,7 @@ Usage columns: tests_v2 calls/files, skills mentions (via the CLI verb), and whe
| agent.resolve_delivery_target | 0 | 0 | yes | hooks | impl by surface_id; pid routing answers "No live delivery target" |
| agent.hook.*, surface.resume.*, workspace.set_auto_title, surface.sync_codex_native_title, agent.hibernation.* | 0 | 0 | yes | hooks | unsup: agent state moves to cmux-tui report-agent |
-| v1 set_status / clear_status / list_status | 3/1 (CLI) | 2 | yes | set-status, … | impl: in-memory per workspace (bounded), not rendered in the sidebar yet |
+| v1 set_status / clear_status / list_status | 3/1 (CLI) | 2 | yes | set-status, … | superseded: `cmux workspace status\|progress\|log` (daemon state), rendered in the sidebar row |
| v1 set_progress / clear_progress / log / clear_log / list_log / sidebar_state | CLI | 1–2 | | set-progress, log, sidebar-state | impl (same store; sidebar_state reads cwd/branch from cmux-tui) |
| v1 set_agent_pid / clear_agent_pid | 0 | 0 | yes | hooks | impl (stored) |
</file context>
|
Review: a fresh review agent read the change at 94aa790. The wire matches the cmux-tui server ( Fixed in af90c56:
Left:
CI: the red checks are inherited from the feat-cmux-next-acpmux base; see the "Stacked on #16174" section. Head: af90c56 |
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
|
Superseded on feat-cmux-next: 758fef6 ("cmux-next: mirror the daemon's state resources from session.events") added |
Base now delivers workspace status through SessionStateMirror into WorkspaceModel.status, so this PR's daemon/transport half is superseded. Resolved by taking the base tree; the sidebar UI is re-ported on top in follow-up commits. --no-verify: merge commit of base content only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Red on the merged base: a workspace that reports status, a log line and progress still draws the minimal one-line row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Re-port #16431's sidebar UI on the base's data path: WorkspaceModel.status (state resources via SessionStateMirror) maps into SidebarWorkspace.reported, minus the ports and pr entries other elements show. WorkspaceRowContent carries classic's status block (entries with icon and color, "N more", the newest log line, the progress label) and SidebarLayoutMetrics sizes the row from it; the row view draws it under the name with the progress bar in its slot before the label. Defaults ("on when reported"): new `log` element; agentStatus, log and progress now show by default but draw nothing until a workspace reports something, so quiet rows stay minimal. The second line no longer repeats the entries the block draws. Localized in every catalog locale; the settings page strings are regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From the fleet (export-settings-artifacts.sh on 21eaf58): the new sidebar.workspaceRow.log key and agentStatus/log/progress on by default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- regenerated webviews/src/pages/settings/generated/strings.json Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/38134544040
Gallery601 states changed: pages.settings/general-group-2, pages.settings/appearance-group-8, pages.settings/general-group-9, pages.settings-motion/play-reset-secondlineorder, agent-pane.transcript/shell-grouped-commands, ... · 2 new states · 38 broken states · 41 play checks failed Diff page and renders (the run's 595 more on the diff page. Live preview (follows this branch; start it with
Play checks failed (0 px anchor movement, 0 layout shift, no frame over 33 ms unless the entry gives a reason):
375 nondeterministic states differed from a second render of the same head (a clock or fixture leak) and are not counted as changes. |
Keeps the row status block (#16431) beside expanded rows: an expanded row leaves the block off (its second line shows the status), and the row height adds the block's height to the line count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>






Workspace rows in cmux-next now draw what agents and scripts report with
cmux workspace status|log|progress, the way classic's sidebar did. A workspace that reports something shows a block under its name: its status entries (icon and text in the entry's color, then "N more" past three), the newest log line with its level glyph, then the progress bar and its label. A workspace that reports nothing keeps its one-line row.What changes
workspace_statusstate intoWorkspaceModel.status(state resources viaSessionStateMirror).SidebarMappingmaps it intoSidebarWorkspace.reported, minus theportsandprentries that have their own row elements.WorkspaceRowContent.statuscarries the block;SidebarLayoutMetricssizes the row from it (one status line per line, plus the bar's slot).WorkspaceStatusViewdraws it under the name, and the row's progress bar sits in its slot before the label. Accessibility reads every entry, the log line and the label.sidebar.workspaceRow.logelement.agentStatus,logandprogressare now on by default, but they draw nothing until a workspace reports something, so quiet rows stay minimal. Each can still be turned off, globally or per workspace kind. The second line no longer repeats the entries the block draws.needs_review).Superseded
This PR's first version also added the delivery path (a
session.eventstracker, v2 line routing, aDaemonStorestatus map).feat-cmux-nextnow delivers the same state throughSessionStateMirror, so the merge takes the base for all of that and this PR is UI only. It doesn't need #16819.Not ported, as before: the "Show more" toggle, clickable URL and markdown entries, and classic's
sidebar.showLog/showProgress/showCustomMetadatakeys (thesidebar.workspaceRow.*toggles replace them).Verification
SidebarStatusBlockTests(CmuxNextAppTests): a store with one reporting and one quiet workspace, mapped and laid out under cmux.json settings. Red on a1bf038 (the merged base plus the test): the reporting row was 24 pt, the minimal height. Green on the head below, in the same fleet suite run.BUILD SUCCEEDED(cmux-ci step e4079fa4c8dd), and fleet Swift suites passed 74 of 74, includingSidebarStatusBlockTestsand the settings export tests (step abcc0ba79a3b).Live verification
DEV build
sbstatof 81173c9 on a capture mini. Codex (codex exec, the mini's own sign-in) ran in a cmux terminal of a new workspace and reported, through the cmux CLI, four status entries (buildwith hammer and#ff8800,testswith checkmark.circle and green,lint,docs), a warning log line and 40% "Linking" progress. The sidebar row drew Building, 412 passed, clean, "1 more", ⚠ 2 warnings and the progress bar with its label;workspace-2, which reported nothing, stayed one line. Settingsidebar.workspaceRow.logto false hid only the log line (the mini's config was restored afterwards); clearing the entries and progress shrank the row to its name and last log line.Review subagent: nothing blocking. Noted: a placeholder row would still reserve the block's height (unlikely), VoiceOver reads the log line without its level, and the test does not cover "N more" or the log toggle alone.
Changelog
cmux workspace status|log|progress. Progress is now on by default, so a terminal's own OSC 9;4 progress and program or agent busy/paused marks also show on rows that report nothing else (the row stays one line).🤖 Generated with Claude Code