Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 6 additions & 23 deletions Sources/CodeCaps/GlanceViews.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1378,40 +1378,23 @@ func glanceCadence(_ snapshot: QuotaWindowSnapshot, now: Date) -> GlanceCadence
/// the caption column never has to truncate, and derived from the window's own
/// cadence token rather than its label alone, because a label is allowed to be
func isMuseAssistExtra(_ snapshot: QuotaWindowSnapshot) -> Bool {
let key = snapshot.window.canonicalProviderKey
guard key == "muse-assist" else { return false }
let label = snapshot.window.label
if QuotaWindowVocabulary.isExtraAllowanceLabel(label) { return true }
let lower = label.lowercased()
if lower.contains("additional") || lower.contains("extra") { return true }
// Only lean on the vocabulary's "Other limit" catch-all when the window
// carries no cadence token: a token-bearing window with a non-standard
// cadence (8h, 12h) also resolves to "Other limit", and reading that as
// an extra allowance would caption a real meter "+".
guard snapshot.window.window == nil else { return false }
let cadence = QuotaWindowVocabulary.standardCadence(
windowToken: nil,
label: label,
QuotaWindowVocabulary.isMuseAssistExtra(
providerKey: snapshot.window.canonicalProviderKey,
windowToken: snapshot.window.window,
label: snapshot.window.label,
resetAt: snapshot.window.resetDate,
periodStart: snapshot.window.periodStartDate
)
return cadence == QuotaWindowVocabulary.otherLimit
}

func isMuseAssistWeekly(_ snapshot: QuotaWindowSnapshot) -> Bool {
let key = snapshot.window.canonicalProviderKey
guard key == "muse-assist" else { return false }
if isMuseAssistExtra(snapshot) { return false }
let cadence = QuotaWindowVocabulary.standardCadence(
QuotaWindowVocabulary.isMuseAssistWeekly(
providerKey: snapshot.window.canonicalProviderKey,
windowToken: snapshot.window.window,
label: snapshot.window.label,
resetAt: snapshot.window.resetDate,
periodStart: snapshot.window.periodStartDate
)
if cadence == QuotaWindowVocabulary.sevenDay { return true }
let label = snapshot.window.label.lowercased()
let token = (snapshot.window.window ?? "").lowercased()
return label.contains("weekly") || token.contains("weekly") || label.contains("free")
}

/// The unit tag beside a meter: "5h", "7d", "1m". Kept to five characters so
Expand Down
20 changes: 17 additions & 3 deletions Sources/CodeCaps/MonitorModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,18 @@ final class MonitorModel: ObservableObject {
didSet { defaults.set(menuBarStyle.rawValue, forKey: "menuBarStyle") }
}
@Published var menuBarMarkStyle: MenuBarMarkStyle {
didSet { defaults.set(menuBarMarkStyle.rawValue, forKey: "menuBarMarkStyle") }
didSet {
defaults.set(menuBarMarkStyle.rawValue, forKey: "menuBarMarkStyle")
invalidateProviderMarkCaches()
}
}
/// The accent colour. Stored through `AccentChoice` so the swatch, the
/// default and the `Theme` lookup all read the same value.
@Published var accent: AccentChoice {
didSet { AccentChoice.current = accent }
didSet {
AccentChoice.current = accent
invalidateProviderMarkCaches()
}
}

/// The accent as a drawable colour, resolved the same way `Theme.accent`
Expand Down Expand Up @@ -1466,13 +1472,21 @@ final class MonitorModel: ObservableObject {
/// theme. Offered as a switch because some people do want the popover to
/// read as one piece.
@Published var glanceLogosFollowAccent: Bool {
didSet { defaults.set(glanceLogosFollowAccent, forKey: "glanceLogosFollowAccent") }
didSet {
defaults.set(glanceLogosFollowAccent, forKey: "glanceLogosFollowAccent")
invalidateProviderMarkCaches()
}
}

private func invalidateProviderMarkCaches() {
PlatformLogoImage.invalidateCaches()
}

func setMarkStyle(_ style: MarkStyle, for providerKey: String) {
let key = providerKey.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
guard markStyles[key] != style else { return }
markStyles[key] = style
invalidateProviderMarkCaches()
}

func customMarkMode(for providerKey: String) -> CustomMarkMode {
Expand Down
Binary file modified Sources/CodeCaps/Resources/ProviderMarks/muse-code.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
49 changes: 49 additions & 0 deletions Sources/QuotaCore/QuotaWindowVocabulary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -243,4 +243,53 @@ public enum QuotaWindowVocabulary {
return cadenceFromProse(lower, resetAt: nil, periodStart: nil, monthlyHint: false) != nil
|| ResetAlarmCadence.durationSeconds(lower) != nil
}

// MARK: - Muse Assist meter pairing

/// Whether a Muse Assist window is the referral / extra allowance meter
/// (caption `+`), not a timed cadence such as an 8-hour window.
public static func isMuseAssistExtra(
providerKey: String,
windowToken: String?,
label: String,
resetAt: Date? = nil,
periodStart: Date? = nil
) -> Bool {
guard providerKey == "muse-assist" else { return false }
if isExtraAllowanceLabel(label) { return true }
let lower = label.lowercased()
if lower.contains("additional") || lower.contains("extra") { return true }
guard windowToken == nil else { return false }
let cadence = standardCadence(
windowToken: nil,
label: label,
resetAt: resetAt,
periodStart: periodStart
)
return cadence == otherLimit
}

/// The Muse Assist weekly allowance meter (caption `7d`), distinct from
/// `isMuseAssistExtra`.
public static func isMuseAssistWeekly(
providerKey: String,
windowToken: String?,
label: String,
resetAt: Date? = nil,
periodStart: Date? = nil
) -> Bool {
guard providerKey == "muse-assist" else { return false }
if isMuseAssistExtra(providerKey: providerKey, windowToken: windowToken, label: label,
resetAt: resetAt, periodStart: periodStart) { return false }
let cadence = standardCadence(
windowToken: windowToken,
label: label,
resetAt: resetAt,
periodStart: periodStart
)
if cadence == sevenDay { return true }
let lower = label.lowercased()
let token = (windowToken ?? "").lowercased()
return lower.contains("weekly") || token.contains("weekly") || lower.contains("free")
}
}
23 changes: 23 additions & 0 deletions Tests/QuotaCoreTests/QuotaWindowVocabularyTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,27 @@ final class QuotaWindowVocabularyTests: XCTestCase {
XCTAssertTrue(QuotaWindowVocabulary.isExtraAllowanceLabel("Additional additional tokens"))
XCTAssertTrue(QuotaWindowVocabulary.isExtraAllowanceLabel("plan + extra"))
}

func testMuseAssistWeeklyAndExtraMetersAreClassifiedSeparately() {
XCTAssertTrue(QuotaWindowVocabulary.isMuseAssistWeekly(
providerKey: "muse-assist",
windowToken: "168h",
label: "Free weekly limit"
))
XCTAssertTrue(QuotaWindowVocabulary.isMuseAssistExtra(
providerKey: "muse-assist",
windowToken: nil,
label: "Additional tokens"
))
XCTAssertFalse(QuotaWindowVocabulary.isMuseAssistExtra(
providerKey: "muse-assist",
windowToken: "8h",
label: "8h window"
))
XCTAssertFalse(QuotaWindowVocabulary.isMuseAssistWeekly(
providerKey: "openai",
windowToken: "168h",
label: "Weekly"
))
}
}
44 changes: 40 additions & 4 deletions ios/CodeCapsCompanion/App/Models/CompanionQuotaModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,10 @@ public struct CompanionWindowItem: Identifiable, Codable, Equatable {
FleetMacReading.isMacCodeCapsReading(id: id, source: source, via: via)
}

/// Hyphenated cadence beside a meter bar (`5-hour`, `7-day`, `1-month`, `Other limit`).
/// Compact cadence beside a meter bar (`5h`, `7d`, `1m`, `+`), matching Mac Glance.
public var meterCaption: String {
QuotaWindowVocabulary.standardCadence(
windowToken: cadence,
QuotaWindowVocabulary.popoverCaption(
windowToken: cadence.isEmpty ? nil : cadence,
label: label,
resetAt: resetAt,
monthlyHint: label.lowercased().contains("plan") || cadence.lowercased().contains("plan")
Expand Down Expand Up @@ -219,7 +219,7 @@ public struct CompanionWindowItem: Identifiable, Codable, Equatable {

/// Countdown or a placeholder when an extra allowance has no reset clock.
public func meterCountdown(now: Date = Date()) -> String {
if QuotaWindowVocabulary.isExtraAllowanceLabel(label) { return "—" }
if QuotaWindowVocabulary.isExtraAllowanceLabel(label) { return "extra" }
let cd = countdown(now: now)
return cd.isEmpty ? "—" : cd
}
Expand Down Expand Up @@ -398,12 +398,36 @@ public struct CompanionQuotaItem: Identifiable, Codable, Equatable {

public func cardShortWindow(scope: CompanionSourceScope, now: Date = Date()) -> CardWindow? {
let resolved = cardWindowRows(scope: scope, now: now)
if providerKey == "muse-assist" {
if let weekly = resolved.first(where: {
QuotaWindowVocabulary.isMuseAssistWeekly(
providerKey: providerKey,
windowToken: nil,
label: $0.window.label,
resetAt: $0.window.resetAt
)
}) {
return weekly
}
}
let window = resolved.map(\.window).first(where: { $0.isShortCadence }) ?? resolved.first?.window
return resolved.first(where: { $0.window.id == window?.id })
}

public func cardLongWindow(scope: CompanionSourceScope, now: Date = Date()) -> CardWindow? {
let resolved = cardWindowRows(scope: scope, now: now)
if providerKey == "muse-assist" {
if let extra = resolved.first(where: {
QuotaWindowVocabulary.isMuseAssistExtra(
providerKey: providerKey,
windowToken: nil,
label: $0.window.label,
resetAt: $0.window.resetAt
)
}) {
return extra
}
}
let short = cardShortWindow(scope: scope, now: now)?.window
let nonShort = resolved.filter { !$0.window.isShortCadence }
if let match = nonShort.first(where: { $0.window.id != short?.id }) {
Expand Down Expand Up @@ -1338,6 +1362,18 @@ public final class CompanionQuotaModel: ObservableObject {

// Standardize cadence order: shorter periods (e.g. 5-hour) precede longer periods (e.g. weekly).
primaryWindows.sort { left, right in
if providerKey == "muse-assist" {
let leftWeekly = QuotaWindowVocabulary.isMuseAssistWeekly(
providerKey: providerKey, windowToken: nil, label: left.label, resetAt: left.resetAt)
let rightWeekly = QuotaWindowVocabulary.isMuseAssistWeekly(
providerKey: providerKey, windowToken: nil, label: right.label, resetAt: right.resetAt)
if leftWeekly != rightWeekly { return leftWeekly }
let leftExtra = QuotaWindowVocabulary.isMuseAssistExtra(
providerKey: providerKey, windowToken: nil, label: left.label, resetAt: left.resetAt)
let rightExtra = QuotaWindowVocabulary.isMuseAssistExtra(
providerKey: providerKey, windowToken: nil, label: right.label, resetAt: right.resetAt)
if leftExtra != rightExtra { return !leftExtra }
}
if left.isShortCadence != right.isShortCadence {
return left.isShortCadence && !right.isShortCadence
}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion ios/CodeCapsCompanion/Widgets/WidgetPresentation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -779,7 +779,7 @@ public enum WidgetPresentation {
return ("muse-code", "Muse Code", "muse-code")
}
if pKey.contains("muse") || prov.contains("muse") {
return ("muse-code", "Muse Code", "muse-code")
return ("muse-assist", "Muse Assist", "muse-assist")
}
if pKey.contains("grok-bot") || prov.contains("grok-bot") || prov.contains("grok bot") || id.contains("grok-bot") {
return ("grok-bot", "Grok Bot", "grok-bot")
Expand Down
2 changes: 1 addition & 1 deletion ios/CodeCapsCompanion/Widgets/WidgetViews.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ struct ProviderMarkView: View {
if low.contains("minimax") { return "provider-minimax" }
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("muse") { return "provider-muse-assist" }
if low.contains("antigravity") { return "provider-antigravity" }
if low.contains("gemini") { return "provider-gemini" }
if low.contains("grok-bot") { return "provider-grok-bot" }
Expand Down
Loading