native macOS codings agent orchestrator
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(repo-appearance): add icon and color tint to canvas card title bar

- CanvasCardView gains optional `repositoryIcon`, `repositoryColor`,
`repositoryRootURL` parameters. The icon renders before the repo
name at 12pt, tinted via the shared RepositoryIconImage rules.
- The repo color overlays the existing `.bar` material in the title
bar background — above `.bar` rather than below — so the always-on
identity strip is visible despite the bar's 0.9 opacity. Existing
notification orange and selected-unfocused accent layers are
unchanged, preserving today's look for repos with no appearance.
- CanvasView reads `@Shared(.repositoryAppearances)` and resolves each
card's repo via `PathPolicy.normalizePath` against the worktree's
repositoryRootURL, then forwards the lookup to CanvasCardView. Cards
for repos without an appearance entry render exactly as before.
- Focused cards get a slightly stronger tint (0.18 vs 0.10 alpha) so
the active card's identity reads even more clearly.

onevcat dc2e31b4 4fa01da1

+47
+28
supacode/Features/Canvas/Views/CanvasCardView.swift
··· 4 4 struct CanvasCardView: View { 5 5 let repositoryName: String 6 6 let worktreeName: String 7 + /// User-pinned icon for this card's repository, drawn before the 8 + /// repo name in the title bar. `nil` keeps the historical text-only 9 + /// title bar. 10 + var repositoryIcon: RepositoryIconSource? 11 + /// User-pinned color for this card's repository. When set, it tints 12 + /// both the icon (if tintable) and the title-bar background as an 13 + /// always-on identity strip. 14 + var repositoryColor: Color? 15 + /// Repo root URL, needed by `RepositoryIconImage` to resolve user 16 + /// PNG/SVG filenames against the per-repo icons directory. 17 + var repositoryRootURL: URL? 7 18 let tree: SplitTree<GhosttySurfaceView> 8 19 let isFocused: Bool 9 20 let isSelected: Bool ··· 109 120 110 121 private var titleBar: some View { 111 122 HStack(spacing: 6) { 123 + if let repositoryIcon, let repositoryRootURL { 124 + RepositoryIconImage( 125 + icon: repositoryIcon, 126 + repositoryRootURL: repositoryRootURL, 127 + tintColor: repositoryColor, 128 + size: 12 129 + ) 130 + } 112 131 Text(repositoryName) 113 132 .font(.caption.bold()) 114 133 .lineLimit(1) ··· 178 197 179 198 @ViewBuilder 180 199 private var titleBarBackground: some View { 200 + // Layering: existing notification orange + selected accent stay 201 + // BELOW the `.bar` material so their behavior is unchanged from 202 + // before this feature shipped. The repo color sits ABOVE the bar 203 + // as a thin always-on identity strip — putting it below would 204 + // dilute it to invisibility against the 0.9-opaque material, and 205 + // moving the bar to the bottom would amplify the existing tints. 181 206 ZStack { 182 207 if hasUnseenNotification { 183 208 Color.orange.opacity(0.3) ··· 188 213 Rectangle() 189 214 .fill(.bar) 190 215 .opacity(0.9) 216 + if let repositoryColor { 217 + repositoryColor.opacity(isFocused ? 0.18 : 0.10) 218 + } 191 219 } 192 220 } 193 221
+19
supacode/Features/Canvas/Views/CanvasView.swift
··· 1 1 import AppKit 2 + import Sharing 2 3 import SwiftUI 3 4 4 5 struct CanvasView: View { ··· 8 9 let terminalManager: WorktreeTerminalManager 9 10 var onExitToTab: () -> Void = {} 10 11 @State private var layoutStore = CanvasLayoutStore() 12 + @Shared(.repositoryAppearances) private var repositoryAppearances 11 13 12 14 @State private var canvasOffset: CGSize = .zero 13 15 @State private var lastCanvasOffset: CGSize = .zero ··· 82 84 let screenCenter = screenPosition(for: resized.center) 83 85 let cardTotalHeight = resized.size.height + titleBarHeight 84 86 87 + let repositoryAppearance = appearance(for: state.repositoryRootURL) 85 88 CanvasCardView( 86 89 repositoryName: Repository.name(for: state.repositoryRootURL), 87 90 worktreeName: tab.title, 91 + repositoryIcon: repositoryAppearance.icon, 92 + repositoryColor: repositoryAppearance.color?.color, 93 + repositoryRootURL: state.repositoryRootURL, 88 94 tree: tree, 89 95 isFocused: selectionState.primaryTabID == tab.id, 90 96 isSelected: selectionState.selectedTabIDs.contains(tab.id), ··· 766 772 // WorktreeTerminalTabsView.onAppear's syncFocus() and cause blank 767 773 // surfaces. Cleanup of non-selected worktrees is handled by 768 774 // setSelectedWorktreeID in the async exit flow. 775 + } 776 + 777 + /// Looks up the user-pinned `RepositoryAppearance` for a given repo 778 + /// root URL by deriving the canonical `Repository.ID` (the 779 + /// path-policy-normalized path string) and querying the @Shared 780 + /// dict. Returns `.empty` when no entry exists, which keeps cards 781 + /// visually identical to before the appearance feature shipped. 782 + private func appearance(for repositoryRootURL: URL) -> RepositoryAppearance { 783 + let id = 784 + PathPolicy.normalizePath( 785 + repositoryRootURL.path(percentEncoded: false), resolvingSymlinks: true 786 + ) ?? repositoryRootURL.path(percentEncoded: false) 787 + return repositoryAppearances[id] ?? .empty 769 788 } 770 789 } 771 790