Repository navigation
エージェント階層サイドバー + 色連携 + 独立タブ昇格 #2742
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12994,6 +12994,37 @@ private struct TabItemView: View, Equatable { | |
| .foregroundColor(activeSecondaryColor(0.75)) | ||
| .lineLimit(1) | ||
| } | ||
|
|
||
| // Surface child rows for multi-surface workspaces (agent teams) | ||
| if tab.panels.count > 1 { | ||
| let orderedIds = tab.sidebarOrderedPanelIds() | ||
| let currentFocusedPanelId = tab.focusedPanelId | ||
| VStack(spacing: 2) { | ||
| ForEach(orderedIds, id: \.self) { panelId in | ||
| SurfaceChildRow( | ||
| title: tab.panelTitles[panelId] ?? String(localized: "sidebar.surfaceChild.defaultTitle", defaultValue: "Terminal"), | ||
| colorHex: tab.panelColors[panelId], | ||
| isFocused: panelId == currentFocusedPanelId, | ||
| isWorkspaceActive: isActive, | ||
| colorScheme: colorScheme | ||
| ) | ||
| .contentShape(Rectangle()) | ||
| .onTapGesture { | ||
| updateSelection() | ||
| tab.focusPanel(panelId) | ||
| } | ||
| .contextMenu { | ||
| Button(String(localized: "sidebar.surfaceChild.openInNewWorkspace", defaultValue: "Open in New Workspace")) { | ||
| tabManager.promoteSurfaceToNewWorkspace( | ||
| sourceWorkspace: tab, | ||
| panelId: panelId | ||
| ) | ||
| } | ||
| } | ||
|
Comment on lines
+13017
to
+13023
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
| } | ||
| .padding(.top, 4) | ||
| } | ||
| } | ||
| .animation(.easeInOut(duration: 0.2), value: tab.logEntries.count) | ||
| .animation(.easeInOut(duration: 0.2), value: tab.progress != nil) | ||
|
|
@@ -13440,14 +13471,23 @@ private struct TabItemView: View, Equatable { | |
| } | ||
|
|
||
| private var resolvedCustomTabColor: Color? { | ||
| guard let hex = tab.customColor else { return nil } | ||
| let hex = tab.customColor ?? representativePanelColorHex | ||
| guard let hex else { return nil } | ||
| return WorkspaceTabColorSettings.displayColor( | ||
| hex: hex, | ||
| colorScheme: colorScheme, | ||
| forceBright: activeTabIndicatorStyle == .leftRail | ||
| ) | ||
| } | ||
|
|
||
| /// When a workspace has no customColor but has panel colors (e.g. from agent teams), | ||
| /// use the first panel's color as the representative color for the workspace rail. | ||
| private var representativePanelColorHex: String? { | ||
| guard tab.customColor == nil, !tab.panelColors.isEmpty else { return nil } | ||
| let orderedIds = tab.sidebarOrderedPanelIds() | ||
| return orderedIds.lazy.compactMap { tab.panelColors[$0] }.first | ||
| } | ||
|
|
||
| private func tabColorSwatchColor(for hex: String) -> NSColor { | ||
| WorkspaceTabColorSettings.displayNSColor( | ||
| hex: hex, | ||
|
|
@@ -14172,6 +14212,58 @@ private struct SidebarMetadataRows: View { | |
| } | ||
| } | ||
|
|
||
| private struct SurfaceChildRow: View { | ||
| let title: String | ||
| let colorHex: String? | ||
| let isFocused: Bool | ||
| let isWorkspaceActive: Bool | ||
| let colorScheme: ColorScheme | ||
|
|
||
| var body: some View { | ||
| HStack(spacing: 5) { | ||
| Circle() | ||
| .fill(dotColor) | ||
| .frame(width: 6, height: 6) | ||
| Text(title) | ||
| .font(.system(size: 10)) | ||
| .foregroundColor(textColor) | ||
| .lineLimit(1) | ||
| .truncationMode(.tail) | ||
| Spacer(minLength: 0) | ||
| } | ||
| .padding(.vertical, 2) | ||
| .padding(.horizontal, 4) | ||
| .background( | ||
| RoundedRectangle(cornerRadius: 4) | ||
| .fill(isFocused ? focusedBackgroundColor : Color.clear) | ||
| ) | ||
| } | ||
|
|
||
| private var dotColor: Color { | ||
| if let hex = colorHex, | ||
| let color = WorkspaceTabColorSettings.displayColor(hex: hex, colorScheme: colorScheme) { | ||
| return color | ||
| } | ||
| return Color.secondary.opacity(0.5) | ||
| } | ||
|
|
||
| private var textColor: Color { | ||
| if isWorkspaceActive { | ||
| return isFocused | ||
| ? Color(nsColor: sidebarSelectedWorkspaceForegroundNSColor(opacity: 0.95)) | ||
| : Color(nsColor: sidebarSelectedWorkspaceForegroundNSColor(opacity: 0.7)) | ||
| } | ||
| return isFocused ? .primary : .secondary | ||
| } | ||
|
|
||
| private var focusedBackgroundColor: Color { | ||
| if isWorkspaceActive { | ||
| return Color.white.opacity(0.1) | ||
| } | ||
| return Color.primary.opacity(0.05) | ||
| } | ||
| } | ||
|
|
||
| private struct SidebarMetadataEntryRow: View { | ||
| let entry: SidebarStatusEntry | ||
| let isActive: Bool | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2136,6 +2136,8 @@ class TerminalController { | |||||||||||||||||||||||||||||||
| return v2Result(id: id, self.v2SurfaceClose(params: params)) | ||||||||||||||||||||||||||||||||
| case "surface.move": | ||||||||||||||||||||||||||||||||
| return v2Result(id: id, self.v2SurfaceMove(params: params)) | ||||||||||||||||||||||||||||||||
| case "surface.set_color": | ||||||||||||||||||||||||||||||||
| return v2Result(id: id, self.v2SurfaceSetColor(params: params)) | ||||||||||||||||||||||||||||||||
| case "surface.reorder": | ||||||||||||||||||||||||||||||||
| return v2Result(id: id, self.v2SurfaceReorder(params: params)) | ||||||||||||||||||||||||||||||||
| case "surface.action": | ||||||||||||||||||||||||||||||||
|
|
@@ -4914,6 +4916,38 @@ class TerminalController { | |||||||||||||||||||||||||||||||
| return result | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| private func v2SurfaceSetColor(params: [String: Any]) -> V2CallResult { | ||||||||||||||||||||||||||||||||
| guard let tabManager = v2ResolveTabManager(params: params) else { | ||||||||||||||||||||||||||||||||
| return .err(code: "unavailable", message: "TabManager not available", data: nil) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| var result: V2CallResult = .err(code: "internal_error", message: "Failed to set surface color", data: nil) | ||||||||||||||||||||||||||||||||
| v2MainSync { | ||||||||||||||||||||||||||||||||
| guard let ws = v2ResolveWorkspace(params: params, tabManager: tabManager) else { | ||||||||||||||||||||||||||||||||
| result = .err(code: "not_found", message: "Workspace not found", data: nil) | ||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| 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) | ||||||||||||||||||||||||||||||||
|
Comment on lines
+4929
to
+4935
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||
| if let colorRaw, !colorRaw.isEmpty { | ||||||||||||||||||||||||||||||||
| if let normalized = WorkspaceTabColorSettings.normalizedHex(colorRaw) { | ||||||||||||||||||||||||||||||||
| ws.setPanelColor(surfaceId, hex: normalized) | ||||||||||||||||||||||||||||||||
| result = .ok(["surface_id": surfaceId.uuidString, "color": normalized]) | ||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||
| result = .err(code: "invalid_params", message: "Invalid color hex", data: nil) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||
| ws.setPanelColor(surfaceId, hex: nil) | ||||||||||||||||||||||||||||||||
| result = .ok(["surface_id": surfaceId.uuidString, "color": NSNull()]) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| return result | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| private func v2SurfaceSplit(params: [String: Any]) -> V2CallResult { | ||||||||||||||||||||||||||||||||
| guard let tabManager = v2ResolveTabManager(params: params) else { | ||||||||||||||||||||||||||||||||
| return .err(code: "unavailable", message: "TabManager not available", data: nil) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
selfreference — considerstaticparseTmuxStyleBackgroundHexcloses over nothing fromself; it only callsSelf.xterm256ColorHexandSelf.tmuxNamedColorHex(both alreadystatic). Marking itprivate static funcwould make the intent explicit and mirror the pattern of the two helpers it calls.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!