Repository navigation
Conversation
…romotion - Display per-surface child rows in sidebar when workspace has multiple panels - Accept Claude Code team agent colors via select-pane -P tmux style parsing - Add surface.set_color socket command for per-panel color assignment - Propagate first panel color as workspace representative rail color - Right-click surface row to promote it to a new independent workspace - Persist panel colors across session restarts
|
@yu-k1804 is attempting to deploy a commit to the Manaflow Team on Vercel. A member of the Team first needs to authorize it. |
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
📝 WalkthroughWalkthroughThis PR introduces per-panel color persistence and management across the application. It adds color parsing from tmux commands, new UI for displaying colored surface child rows in the sidebar, persistence of panel colors across sessions, a panel promotion feature that preserves colors when moving surfaces to new workspaces, and a new remote Changes
Sequence Diagram(s)sequenceDiagram
actor Client as Client/Tmux<br/>(CLI)
participant CMux as cmux.swift<br/>(CLI Handler)
participant Term as TerminalController<br/>(API Router)
participant WS as Workspace<br/>(State)
participant UI as UI Layer<br/>(Observers)
Client->>CMux: select-pane -P with bg=color
CMux->>CMux: Parse tmux bg value<br/>(hex/colorN/named)
CMux->>Term: surface.set_color command<br/>(surface_id, hex color)
Term->>WS: setPanelColor(panelId, hex)
WS->>WS: Normalize & validate hex<br/>Update panelColors dict
WS->>UI: Emit panelColors change
UI->>UI: Re-render surface row<br/>with new color
sequenceDiagram
actor User as User
participant UI as ContentView.swift<br/>(UI)
participant TM as TabManager<br/>(Manager)
participant SRC as Source Workspace<br/>(State)
participant NEW as New Workspace<br/>(State)
User->>UI: Tap "Promote" in context menu<br/>for surface child row
UI->>TM: promoteSurfaceToNewWorkspace<br/>(sourceWorkspace, panelId)
TM->>SRC: Capture panel color & title
TM->>TM: addWorkspace(title, select)
TM->>NEW: Find target pane
TM->>SRC: Detach surface from<br/>source workspace
TM->>NEW: Attach surface to<br/>new workspace pane
TM->>TM: Close dummy panels
TM->>NEW: setCustomColor<br/>(captured color)
NEW->>UI: Emit workspace change
UI->>UI: Display new workspace<br/>with inherited color
Note over TM,NEW: On failure: reattach to source
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
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 |
Greptile SummaryThis PR introduces agent-hierarchy sidebar rendering for Claude Code agent teams: surface child rows with color dots in the sidebar, a
Confidence Score: 4/5Two P1 issues need to be resolved before merging: missing panelColors pruning and missing Localizable.xcstrings entries. The feature logic and TabItemView observation wiring are sound. Two P1 gaps — pruneSurfaceMetadata not covering panelColors (causing stale persistence) and the two new localization keys being absent from Localizable.xcstrings (required by project policy, breaks Japanese UI) — prevent a 5. Sources/Workspace.swift (pruning gap) and Resources/Localizable.xcstrings (missing keys).
|
| Filename | Overview |
|---|---|
| Sources/Workspace.swift | Adds panelColors: [UUID: String] published property and setPanelColor helper, wires both $panelColors and $panelTitles into sidebarObservationSignal, and persists colors in SessionWorkspaceSnapshot. Critical gap: panelColors is not added to pruneSurfaceMetadata, unlike every other panel-keyed dictionary, so stale entries accumulate and are re-serialized across restarts. |
| Sources/ContentView.swift | Adds SurfaceChildRow component and injects child-row rendering into TabItemView body when tab.panels.count > 1; adds representativePanelColorHex for the parent rail. The .equatable() + sidebarObservationSignal contract is preserved correctly, but two new localization keys are missing from Localizable.xcstrings. |
| Sources/TabManager.swift | Adds promoteSurfaceToNewWorkspace: detaches a surface from its source workspace, creates a new workspace, attaches the surface there, closes the auto-created dummy terminal, and applies the panel color as the new workspace custom color. Rollback paths correctly re-attach to source on failure. |
| Sources/TerminalController.swift | Adds v2SurfaceSetColor socket handler dispatched via v2MainSync; routes surface.set_color in the command switch. Uses redundant double-normalization, but otherwise correct. |
| CLI/cmux.swift | Adds claude-teams-debug alias, tmux style color parsing helpers, and wires select-pane -P to send surface.set_color. Color index math and named-color mappings are correct. |
| Sources/SessionPersistence.swift | Adds optional panelColors: [String: String]? to SessionWorkspaceSnapshot; backward-compatible. Straightforward addition with no issues. |
Sequence Diagram
sequenceDiagram
participant CC as Claude Code (tmux)
participant CLI as cmux CLI
participant TC as TerminalController
participant WS as Workspace
participant SB as Sidebar (SwiftUI)
CC->>CLI: select-pane -P bg=colour160 -t %42
CLI->>CLI: parseTmuxStyleBackgroundHex() returns #CD0000
CLI->>TC: surface.set_color {workspace_id, surface_id, color}
TC->>TC: v2MainSync
TC->>WS: setPanelColor(surfaceId, hex)
WS->>WS: panelColors[surfaceId] = hex
WS-->>SB: sidebarObservationSignal triggers re-render
SB->>SB: SurfaceChildRow dot color updated
Note over CC,SB: User right-clicks child row
SB->>TC: promoteSurfaceToNewWorkspace(sourceWS, panelId)
TC->>WS: detachSurface(panelId)
TC->>TC: addWorkspace(title)
TC->>WS: attachDetachedSurface(transfer, inPane)
TC->>WS: closePanel(dummyId, force: true)
TC->>WS: setCustomColor(panelColor)
Comments Outside Diff (1)
-
Sources/Workspace.swift, line 7803-7812 (link)panelColorsmissing frompruneSurfaceMetadatapruneSurfaceMetadatais the single authoritative cleanup point for every panel-keyed dictionary, yetpanelColorswas not added to it. Every other similar dict (panelTitles,panelDirectories,panelCustomTitles,panelGitBranches, etc.) is filtered there, so when panels are closed or pruned,panelColorswill silently accumulate stale entries. These stale entries are then serialized viaSessionWorkspaceSnapshot.panelColorsand re-applied on the next app launch viaapplySnapshot.func pruneSurfaceMetadata(validSurfaceIds: Set<UUID>) { panelDirectories = panelDirectories.filter { validSurfaceIds.contains($0.key) } panelTitles = panelTitles.filter { validSurfaceIds.contains($0.key) } panelCustomTitles = panelCustomTitles.filter { validSurfaceIds.contains($0.key) } pinnedPanelIds = pinnedPanelIds.filter { validSurfaceIds.contains($0) } manualUnreadPanelIds = manualUnreadPanelIds.filter { validSurfaceIds.contains($0) } panelGitBranches = panelGitBranches.filter { validSurfaceIds.contains($0.key) } manualUnreadMarkedAt = manualUnreadMarkedAt.filter { validSurfaceIds.contains($0.key) } surfaceListeningPorts = surfaceListeningPorts.filter { validSurfaceIds.contains($0.key) } surfaceTTYNames = surfaceTTYNames.filter { validSurfaceIds.contains($0.key) } remoteDetectedSurfaceIds = remoteDetectedSurfaceIds.filter { validSurfaceIds.contains($0) } panelShellActivityStates = panelShellActivityStates.filter { validSurfaceIds.contains($0.key) } panelPullRequests = panelPullRequests.filter { validSurfaceIds.contains($0.key) } + panelColors = panelColors.filter { validSurfaceIds.contains($0.key) } syncRemotePortScanTTYs() recomputeListeningPorts() }
Reviews (1): Last reviewed commit: "Add claude-teams-debug as alias for clau..." | Re-trigger Greptile
| Button(String(localized: "sidebar.surfaceChild.openInNewWorkspace", defaultValue: "Open in New Workspace")) { | ||
| tabManager.promoteSurfaceToNewWorkspace( | ||
| sourceWorkspace: tab, | ||
| panelId: panelId | ||
| ) | ||
| } | ||
| } |
There was a problem hiding this comment.
New localization keys missing from
Localizable.xcstrings
"sidebar.surfaceChild.defaultTitle" and "sidebar.surfaceChild.openInNewWorkspace" are correctly using String(localized:defaultValue:), but neither key has been added to Resources/Localizable.xcstrings. Per the project policy in CLAUDE.md, every key must be registered with translations for all supported languages (English and Japanese). Without the entries, Japanese users will see English fallback text for these strings.
| "'" + value.replacingOccurrences(of: "'", with: "'\"'\"'") + "'" | ||
| } | ||
|
|
||
| /// Parse a tmux pane style string (e.g. "bg=colour160,fg=white" or "bg=#C0392B") |
There was a problem hiding this comment.
Instance method with no
self reference — consider static
parseTmuxStyleBackgroundHex closes over nothing from self; it only calls Self.xterm256ColorHex and Self.tmuxNamedColorHex (both already static). Marking it private static func would make the intent explicit and mirror the pattern of the two helpers it calls.
| /// Parse a tmux pane style string (e.g. "bg=colour160,fg=white" or "bg=#C0392B") | |
| private static func parseTmuxStyleBackgroundHex(_ style: String) -> String? { |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| } | ||
| guard let surfaceId = v2UUID(params, "surface_id"), ws.panels[surfaceId] != nil else { | ||
| result = .err(code: "not_found", message: "Surface not found", data: nil) | ||
| return | ||
| } | ||
|
|
||
| let colorRaw = v2String(params, "color")?.trimmingCharacters(in: .whitespacesAndNewlines) |
There was a problem hiding this comment.
Redundant double normalization
v2SurfaceSetColor normalizes colorRaw via WorkspaceTabColorSettings.normalizedHex(colorRaw), then passes the already-normalized string to ws.setPanelColor, which calls normalizedHex a second time. setPanelColor can be called directly with the raw input and the normalization happens exactly once inside it.
| } | |
| guard let surfaceId = v2UUID(params, "surface_id"), ws.panels[surfaceId] != nil else { | |
| result = .err(code: "not_found", message: "Surface not found", data: nil) | |
| return | |
| } | |
| let colorRaw = v2String(params, "color")?.trimmingCharacters(in: .whitespacesAndNewlines) | |
| let colorRaw = v2String(params, "color")?.trimmingCharacters(in: .whitespacesAndNewlines) | |
| if let colorRaw, !colorRaw.isEmpty { | |
| ws.setPanelColor(surfaceId, hex: colorRaw) | |
| if let stored = ws.panelColors[surfaceId] { | |
| result = .ok(["surface_id": surfaceId.uuidString, "color": stored]) | |
| } else { | |
| result = .err(code: "invalid_params", message: "Invalid color hex", data: nil) | |
| } |
概要
Claude Code の agent teams で 1 ワークスペース内に複数エージェントが生成される際、左サイドバーに各 surface を子行として階層表示し、色やフォーカスジャンプ、独立タブ昇格を可能にする。
変更点
select-pane -Pから色を取り込み (surface.set_colorsocket コマンド)panelColorsの SessionPersistence 永続化cmux claude-teams-debugコマンドエイリアス追加テスト
cmux claude-teams-debugで 3 体エージェント起動 → サイドバーに子行表示確認Summary by cubic
Adds an agent surface hierarchy in the sidebar with per-surface colors and lets you promote a surface to its own workspace tab. Improves focus navigation and keeps colors in sync with
tmux.tmuxviaselect-pane -P bg=…. Supportscolour0-255, named colors, and hex, normalized to#RRGGBB. New socket commandsurface.set_color. Colors persist viapanelColorsin session snapshots.cmux claude-teams-debugas an alias forcmux claude-teamswith updated help text.Written for commit 09eb1e7. Summary will update on new commits.
Summary by CodeRabbit
Release Notes