diff --git a/Sources/CodeCaps/GlanceViews.swift b/Sources/CodeCaps/GlanceViews.swift index cbf31ac..0b23496 100644 --- a/Sources/CodeCaps/GlanceViews.swift +++ b/Sources/CodeCaps/GlanceViews.swift @@ -18,6 +18,8 @@ struct GlancePopover: View { /// and `⌘,` land in the same place: the Settings page last used. var openSettings: () -> Void + @Environment(\.colorScheme) private var colorScheme + /// Rows the owner has expanded inline. Lives in the popover so it survives /// re-renders while the popover is open, and is cleared when the popover /// dismisses — persisting across launches would imply the popover remembers @@ -298,14 +300,14 @@ struct GlancePopover: View { .accessibilityElement(children: .ignore) .accessibilityLabel(runawayFooterAccessibilityLabel) .help("Runaway Usage Notice: click to inspect") - .padding(.trailing, 4) } else { Spacer() } openCodeCapsButton } - .padding(.horizontal, Metrics.glanceGutter) + .padding(.leading, Metrics.glanceGutter) + .padding(.trailing, 6) .frame(height: Metrics.glanceFooterHeight) } @@ -315,13 +317,12 @@ struct GlancePopover: View { } label: { HStack(spacing: 5) { Text("Open CodeCaps") - Text("⌘1").font(.system(size: 10)).foregroundStyle(.tertiary) + Text("⌘1") + .font(.system(size: 10, weight: .medium)) + .foregroundStyle(colorScheme == .dark ? Color.black.opacity(0.55) : Color.white.opacity(0.75)) } - .font(.system(size: 12, weight: .medium)) - .padding(.horizontal, 10) - .frame(height: 28) } - .buttonStyle(.borderedProminent) + .buttonStyle(GlanceFooterProminentButtonStyle()) .help("Open CodeCaps") .accessibilityLabel("Open CodeCaps") } @@ -1964,3 +1965,28 @@ func glanceResetHelp(_ reset: Date?, now: Date) -> String? { guard reset > now else { return "Reset was due \(when)" } return "Resets in \(glanceResetFullCountdown(reset, now: now)), on \(when)" } + +/// Dedicated prominent button style matching the runaway notice banner in height (28pt) +/// and corner radius (6pt), while maintaining high contrast in both dark and light modes. +struct GlanceFooterProminentButtonStyle: ButtonStyle { + @Environment(\.isEnabled) private var isEnabled + @Environment(\.colorScheme) private var colorScheme + + func makeBody(configuration: Configuration) -> some View { + configuration.label + .font(.system(size: 12, weight: .medium)) + .foregroundStyle(colorScheme == .dark ? Color.black.opacity(0.92) : Color.white) + .padding(.horizontal, 10) + .frame(height: 28) + .background( + RoundedRectangle(cornerRadius: 6) + .fill(Theme.accent.opacity(configuration.isPressed ? 0.82 : 1.0)) + ) + .overlay( + RoundedRectangle(cornerRadius: 6) + .strokeBorder(Color.white.opacity(colorScheme == .dark ? 0.25 : 0.15), lineWidth: 0.5) + ) + .contentShape(RoundedRectangle(cornerRadius: 6)) + .shadow(color: Color.black.opacity(configuration.isPressed ? 0.02 : 0.12), radius: 1, y: 1) + } +} diff --git a/Sources/CodeCaps/PlatformLogo.swift b/Sources/CodeCaps/PlatformLogo.swift index 626fbee..8c13e66 100644 --- a/Sources/CodeCaps/PlatformLogo.swift +++ b/Sources/CodeCaps/PlatformLogo.swift @@ -254,6 +254,8 @@ public enum PlatformLogoImage { "grok-bot": ("grok-bot", "svg"), "minimax": ("minimax", "png"), "muse": ("muse", "png"), + "muse-assist": ("muse-assist", "png"), + "muse-code": ("muse-code", "png"), "cursor": ("cursor", "svg"), ] diff --git a/Sources/CodeCaps/QuotaComponents.swift b/Sources/CodeCaps/QuotaComponents.swift index b5c87f2..59b1f69 100644 --- a/Sources/CodeCaps/QuotaComponents.swift +++ b/Sources/CodeCaps/QuotaComponents.swift @@ -222,7 +222,7 @@ enum Metrics { static let glanceHeaderSegmentPadding: CGFloat = 11 /// The dots between the cluster's phrases. static let glanceHeaderDotSize: CGFloat = 3 - static let glanceFooterHeight: CGFloat = 38 + static let glanceFooterHeight: CGFloat = 40 /// A source's heading band in From Fleet. static let glanceGroupHeaderHeight: CGFloat = 22 /// The list's own padding above the first row and below the last. diff --git a/Sources/CodeCaps/Resources/ProviderMarks/muse-assist.png b/Sources/CodeCaps/Resources/ProviderMarks/muse-assist.png new file mode 100644 index 0000000..a8b4275 Binary files /dev/null and b/Sources/CodeCaps/Resources/ProviderMarks/muse-assist.png differ diff --git a/Sources/CodeCaps/Resources/ProviderMarks/muse-code.png b/Sources/CodeCaps/Resources/ProviderMarks/muse-code.png new file mode 100644 index 0000000..1730a62 Binary files /dev/null and b/Sources/CodeCaps/Resources/ProviderMarks/muse-code.png differ diff --git a/Sources/QuotaCore/QuotaModels.swift b/Sources/QuotaCore/QuotaModels.swift index 5ed95b4..cf1fe90 100644 --- a/Sources/QuotaCore/QuotaModels.swift +++ b/Sources/QuotaCore/QuotaModels.swift @@ -644,12 +644,21 @@ private enum QuotaProviders { Expected(key: "xai", label: "Grok", via: nil), Expected(key: "grok-bot", label: "Grok Bot", via: "cursor"), Expected(key: "minimax", label: "MiniMax", via: nil), - Expected(key: "muse", label: "Muse", via: nil), + Expected(key: "muse-assist", label: "Muse Assist", via: nil), + Expected(key: "muse-code", label: "Muse Code", via: nil), ] static func canonicalKey(provider: String, providerKey: String?, via: String?) -> String { - if via?.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == "antigravity" { - return "google-antigravity" + if let viaLower = via?.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() { + if viaLower == "antigravity" { + return "google-antigravity" + } + if viaLower == "assist" || viaLower == "assistant" { + return "muse-assist" + } + if viaLower == "code" { + return "muse-code" + } } let raw = (providerKey?.isEmpty == false ? providerKey! : provider) .trimmingCharacters(in: .whitespacesAndNewlines).lowercased() @@ -661,7 +670,9 @@ private enum QuotaProviders { "xai": "xai", "grok": "xai", "grok-build": "xai", "grok-bot": "grok-bot", "grok bot": "grok-bot", "grokbot": "grok-bot", "minimax": "minimax", "minimax-code": "minimax", - "muse": "muse", "muse-cli": "muse", "muse-sdk": "muse", + "muse-assist": "muse-assist", "muse assist": "muse-assist", "muse_assist": "muse-assist", "muse-assistant": "muse-assist", "muse assistant": "muse-assist", + "muse-code": "muse-code", "muse code": "muse-code", "muse_code": "muse-code", + "muse": "muse-code", "muse-cli": "muse-code", "muse-sdk": "muse-code", "kimi": "kimi", "moonshot": "kimi", "moonshot-ai": "kimi", "gemini-cli": "gemini-cli", "copilot": "github-copilot", "github-copilot": "github-copilot", "github_copilot": "github-copilot", diff --git a/Tests/CodeCapsTests/GlanceHeightTests.swift b/Tests/CodeCapsTests/GlanceHeightTests.swift index 86dcd17..307ff24 100644 --- a/Tests/CodeCapsTests/GlanceHeightTests.swift +++ b/Tests/CodeCapsTests/GlanceHeightTests.swift @@ -105,9 +105,9 @@ final class GlanceHeightTests: XCTestCase { } func testThePopoverAddsItsChromeAroundTheList() { - // Header 40 + footer 38 + two 1pt dividers + 8pt above and below the list. + // Header 40 + footer 40 + two 1pt dividers + 8pt above and below the list. let total = QuotaGlanceMetrics.popoverHeight(forListHeight: 100) - let expected: CGFloat = 40 + 38 + 2 + 16 + 100 + let expected: CGFloat = 40 + 40 + 2 + 16 + 100 XCTAssertEqual(total, expected) } diff --git a/Tests/CodeCapsTests/PlatformLogoResourceTests.swift b/Tests/CodeCapsTests/PlatformLogoResourceTests.swift index 0def211..838ba04 100644 --- a/Tests/CodeCapsTests/PlatformLogoResourceTests.swift +++ b/Tests/CodeCapsTests/PlatformLogoResourceTests.swift @@ -25,7 +25,8 @@ final class PlatformLogoResourceTests: XCTestCase { "anthropic", "claude", "openai", "codex", "google-antigravity", "antigravity", "google-antigravity:gemini", "google-antigravity:third-party", - "gemini", "xai", "grok", "grok-cli", "grok-bot", "minimax", "muse", "cursor", + "gemini", "xai", "grok", "grok-cli", "grok-bot", "minimax", + "muse", "muse-assist", "muse-code", "cursor", ] func testEveryProviderKeyResolvesABundledMark() { @@ -50,7 +51,7 @@ final class PlatformLogoResourceTests: XCTestCase { let bundle = try XCTUnwrap(ResourceBundle.resolved, "the SwiftPM resource bundle could not be resolved at all") let marks = ["claude", "openai", "gemini", "gemini-color", "gemini-mono", - "cursor", "grok", "grok-bot", "minimax", "muse"] + "cursor", "grok", "grok-bot", "minimax", "muse", "muse-assist", "muse-code"] // Every named mark must be present in some form. Deliberately not an // exact count: the owner replacing the fabricated MiniMax `{M}` with // the real PNG mark added a file, and an equality assertion is a test @@ -67,7 +68,7 @@ final class PlatformLogoResourceTests: XCTestCase { /// a different hat: `NSImage` returns an object, and the row draws nothing. func testEveryBundledMarkRasterises() throws { let bundle = try XCTUnwrap(ResourceBundle.resolved) - for name in ["claude", "openai", "gemini", "gemini-mono", "cursor", "grok", "grok-bot", "minimax", "muse"] { + for name in ["claude", "openai", "gemini", "gemini-mono", "cursor", "grok", "grok-bot", "minimax", "muse", "muse-assist", "muse-code"] { for ext in ["svg", "png"] { guard let url = bundle.url(forResource: name, withExtension: ext) else { continue } let image = try XCTUnwrap(NSImage(contentsOf: url), "\(name).\(ext) did not load") diff --git a/Tests/QuotaCoreTests/QuotaCoreTests.swift b/Tests/QuotaCoreTests/QuotaCoreTests.swift index be29d72..f371cc3 100644 --- a/Tests/QuotaCoreTests/QuotaCoreTests.swift +++ b/Tests/QuotaCoreTests/QuotaCoreTests.swift @@ -202,10 +202,10 @@ final class QuotaCoreTests: XCTestCase { let future = window(provider: "new-provider", occurred: "2023-11-14T22:05:00Z") let response = QuotaResponse(generatedAt: "2023-11-14T22:10:00Z", windows: [future]) let sections = response.platformSections(now: now) - XCTAssertEqual(Array(sections.prefix(8)).map(\.providerKey), [ - "anthropic", "openai", "google-antigravity", "cursor", "xai", "grok-bot", "minimax", "muse", + XCTAssertEqual(Array(sections.prefix(9)).map(\.providerKey), [ + "anthropic", "openai", "google-antigravity", "cursor", "xai", "grok-bot", "minimax", "muse-assist", "muse-code", ]) - XCTAssertTrue(sections.prefix(8).allSatisfy(\.isMissing)) + XCTAssertTrue(sections.prefix(9).allSatisfy(\.isMissing)) XCTAssertEqual(sections.last?.providerKey, "new-provider") } diff --git a/docs/EFFORT-LOG.md b/docs/EFFORT-LOG.md index 4180395..50993a4 100644 --- a/docs/EFFORT-LOG.md +++ b/docs/EFFORT-LOG.md @@ -1,5 +1,16 @@ # CodeCaps — Effort Log +## 2026-10-05 — Glance footer equidistant polish, Muse Assist & Muse Code quota split [AG, in progress] + +Lane: `ag/glance-footer-equidistant-muse-split`. + +- Glance footer geometry & styling polish: updated footer height to 40pt with 28pt custom prominent button style (`GlanceFooterProminentButtonStyle`), exactly matching the runaway marquee notice banner's 28pt height and `RoundedRectangle(cornerRadius: 6)`. Configured button padding so top (6pt), bottom (6pt), and trailing (6pt) margins to the window and divider edges are mathematically equidistant. Ensured high-contrast rendering across dark mode (pastel accent with dark ink) and light mode (deep accent with crisp white text). +- Split Muse into two separate providers: replaced generic `muse` with dedicated `muse-assist` ("Muse Assist") and `muse-code` ("Muse Code") in `QuotaProviders.expected` (total 9 expected providers). Preserved backward compatibility with aliases for `muse-assist`, `muse-assistant`, `muse-code`, `muse`, `muse-cli`, and `muse-sdk`. +- Asset bundling & cross-platform mappings: bundled high-resolution `muse-assist.png` (transparent background blue script "m") and `muse-code.png` (Meta infinity loop artwork from `MuseCode - Dark Mode.png`). Generated 1x/2x/3x asset catalogs (`provider-muse-assist.imageset`, `provider-muse-code.imageset`) for iOS companion and widgets. Updated `PlatformLogo`, `CompanionQuotaModel`, `WidgetPresentation`, and `WidgetViews`. +- Verification: all 721 unit tests passing across `QuotaCoreTests` (390 tests) and `CodeCapsTests` (331 tests); clean build of `CodeCapsCompanion` iOS simulator target via `xcodebuild`. + +--- + ## 2026-10-04 — Glance title breathing room, dynamic pacing highlights, PiP widget, and iOS companion polish [AG, in progress] Lane: `ag/ui-glance-pacing-pip-ios-polish`. Board `bb2a8b26`, `7175aba0`, `1ac04ba9`, `4be8a67a`, `4bcf84f1`. diff --git a/ios/CodeCapsCompanion/App/Models/CompanionQuotaModel.swift b/ios/CodeCapsCompanion/App/Models/CompanionQuotaModel.swift index 366a87b..690973a 100644 --- a/ios/CodeCapsCompanion/App/Models/CompanionQuotaModel.swift +++ b/ios/CodeCapsCompanion/App/Models/CompanionQuotaModel.swift @@ -293,7 +293,9 @@ public struct CompanionQuotaItem: Identifiable, Codable, Equatable { if key.contains("grok-bot") { return "provider-grok-bot" } if key.contains("grok") || key.contains("xai") { return "provider-grok" } if key.contains("minimax") { return "provider-minimax" } - if key.contains("muse") { return "provider-muse" } + if key.contains("muse-assist") || key.contains("muse_assist") { return "provider-muse-assist" } + if key.contains("muse-code") || key.contains("muse_code") { return "provider-muse-code" } + if key.contains("muse") { return "provider-muse-code" } if key.contains("antigravity") || key.contains("gemini") { return "provider-gemini" } return nil } @@ -1269,8 +1271,14 @@ public final class CompanionQuotaModel: ObservableObject { if pKey.contains("minimax") || prov.contains("minimax") { return ("minimax", "MiniMax", "minimax") } + if pKey.contains("muse-assist") || prov.contains("muse-assist") || prov.contains("muse assist") || id.contains("assist") { + return ("muse-assist", "Muse Assist", "muse-assist") + } + if pKey.contains("muse-code") || prov.contains("muse-code") || prov.contains("muse code") || id.contains("code") { + return ("muse-code", "Muse Code", "muse-code") + } if pKey.contains("muse") || prov.contains("muse") { - return ("muse", "Muse", "muse") + return ("muse-code", "Muse Code", "muse-code") } if pKey.contains("grok-bot") || prov.contains("grok-bot") || prov.contains("grok bot") || id.contains("grok-bot") { return ("grok-bot", "Grok Bot", "grok-bot") diff --git a/ios/CodeCapsCompanion/Assets.xcassets/provider-muse-assist.imageset/Contents.json b/ios/CodeCapsCompanion/Assets.xcassets/provider-muse-assist.imageset/Contents.json new file mode 100644 index 0000000..97500af --- /dev/null +++ b/ios/CodeCapsCompanion/Assets.xcassets/provider-muse-assist.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images": [ + { + "idiom": "universal", + "scale": "1x", + "filename": "provider-muse-assist.png" + }, + { + "idiom": "universal", + "scale": "2x", + "filename": "provider-muse-assist@2x.png" + }, + { + "idiom": "universal", + "scale": "3x", + "filename": "provider-muse-assist@3x.png" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} \ No newline at end of file diff --git a/ios/CodeCapsCompanion/Assets.xcassets/provider-muse-assist.imageset/provider-muse-assist.png b/ios/CodeCapsCompanion/Assets.xcassets/provider-muse-assist.imageset/provider-muse-assist.png new file mode 100644 index 0000000..65b4e50 Binary files /dev/null and b/ios/CodeCapsCompanion/Assets.xcassets/provider-muse-assist.imageset/provider-muse-assist.png differ diff --git a/ios/CodeCapsCompanion/Assets.xcassets/provider-muse-assist.imageset/provider-muse-assist@2x.png b/ios/CodeCapsCompanion/Assets.xcassets/provider-muse-assist.imageset/provider-muse-assist@2x.png new file mode 100644 index 0000000..ebf2dc5 Binary files /dev/null and b/ios/CodeCapsCompanion/Assets.xcassets/provider-muse-assist.imageset/provider-muse-assist@2x.png differ diff --git a/ios/CodeCapsCompanion/Assets.xcassets/provider-muse-assist.imageset/provider-muse-assist@3x.png b/ios/CodeCapsCompanion/Assets.xcassets/provider-muse-assist.imageset/provider-muse-assist@3x.png new file mode 100644 index 0000000..a81d78a Binary files /dev/null and b/ios/CodeCapsCompanion/Assets.xcassets/provider-muse-assist.imageset/provider-muse-assist@3x.png differ diff --git a/ios/CodeCapsCompanion/Assets.xcassets/provider-muse-code.imageset/Contents.json b/ios/CodeCapsCompanion/Assets.xcassets/provider-muse-code.imageset/Contents.json new file mode 100644 index 0000000..71b283d --- /dev/null +++ b/ios/CodeCapsCompanion/Assets.xcassets/provider-muse-code.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images": [ + { + "idiom": "universal", + "scale": "1x", + "filename": "provider-muse-code.png" + }, + { + "idiom": "universal", + "scale": "2x", + "filename": "provider-muse-code@2x.png" + }, + { + "idiom": "universal", + "scale": "3x", + "filename": "provider-muse-code@3x.png" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} \ No newline at end of file diff --git a/ios/CodeCapsCompanion/Assets.xcassets/provider-muse-code.imageset/provider-muse-code.png b/ios/CodeCapsCompanion/Assets.xcassets/provider-muse-code.imageset/provider-muse-code.png new file mode 100644 index 0000000..ac2c323 Binary files /dev/null and b/ios/CodeCapsCompanion/Assets.xcassets/provider-muse-code.imageset/provider-muse-code.png differ diff --git a/ios/CodeCapsCompanion/Assets.xcassets/provider-muse-code.imageset/provider-muse-code@2x.png b/ios/CodeCapsCompanion/Assets.xcassets/provider-muse-code.imageset/provider-muse-code@2x.png new file mode 100644 index 0000000..104f067 Binary files /dev/null and b/ios/CodeCapsCompanion/Assets.xcassets/provider-muse-code.imageset/provider-muse-code@2x.png differ diff --git a/ios/CodeCapsCompanion/Assets.xcassets/provider-muse-code.imageset/provider-muse-code@3x.png b/ios/CodeCapsCompanion/Assets.xcassets/provider-muse-code.imageset/provider-muse-code@3x.png new file mode 100644 index 0000000..b426381 Binary files /dev/null and b/ios/CodeCapsCompanion/Assets.xcassets/provider-muse-code.imageset/provider-muse-code@3x.png differ diff --git a/ios/CodeCapsCompanion/Widgets/WidgetPresentation.swift b/ios/CodeCapsCompanion/Widgets/WidgetPresentation.swift index 184ed60..ea060ea 100644 --- a/ios/CodeCapsCompanion/Widgets/WidgetPresentation.swift +++ b/ios/CodeCapsCompanion/Widgets/WidgetPresentation.swift @@ -678,8 +678,14 @@ public enum WidgetPresentation { if pKey.contains("minimax") || prov.contains("minimax") { return ("minimax", "MiniMax", "minimax") } + if pKey.contains("muse-assist") || prov.contains("muse-assist") || prov.contains("muse assist") || id.contains("assist") { + return ("muse-assist", "Muse Assist", "muse-assist") + } + if pKey.contains("muse-code") || prov.contains("muse-code") || prov.contains("muse code") || id.contains("code") { + return ("muse-code", "Muse Code", "muse-code") + } if pKey.contains("muse") || prov.contains("muse") { - return ("muse", "Muse", "muse") + return ("muse-code", "Muse Code", "muse-code") } if pKey.contains("grok-bot") || prov.contains("grok-bot") || prov.contains("grok bot") || id.contains("grok-bot") { return ("grok-bot", "Grok Bot", "grok-bot") diff --git a/ios/CodeCapsCompanion/Widgets/WidgetViews.swift b/ios/CodeCapsCompanion/Widgets/WidgetViews.swift index 2a23f0f..3b257cb 100644 --- a/ios/CodeCapsCompanion/Widgets/WidgetViews.swift +++ b/ios/CodeCapsCompanion/Widgets/WidgetViews.swift @@ -139,7 +139,9 @@ struct ProviderMarkView: View { if low.contains("openai") || low.contains("codex") { return "provider-openai" } if low.contains("cursor") { return "provider-cursor" } if low.contains("minimax") { return "provider-minimax" } - if low.contains("muse") { return "provider-muse" } + if low.contains("muse-assist") || low.contains("muse_assist") { return "provider-muse-assist" } + if low.contains("muse-code") || low.contains("muse_code") { return "provider-muse-code" } + if low.contains("muse") { return "provider-muse-code" } if low.contains("antigravity") { return "provider-antigravity" } if low.contains("gemini") { return "provider-gemini" } if low.contains("grok-bot") { return "provider-grok-bot" }