Skip to content

Commit a3640a3

Browse files
committed
🐛 fix(ci): restore compiler matrix
Xcode 16 timed out on Claude's compound mapping expressions, and Swift 6.3 rejected the observation capture. The UI test host path used the target name; its Debug product used the display name. Use typed assignments across the supported toolchains and give the Debug host the target name. Preserve the Release bundle name and use weak self ownership at both observation layers.
1 parent 70d269c commit a3640a3

7 files changed

Lines changed: 76 additions & 52 deletions

File tree

App/project.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ targets:
117117
CODE_SIGN_ENTITLEMENTS: ""
118118
CODE_SIGN_IDENTITY: "-"
119119
CODE_SIGN_STYLE: Manual
120+
PRODUCT_NAME: TokenMenuBar
120121
PRODUCT_BUNDLE_IDENTIFIER: dev.tox.token-menu-bar.verification
121122
Release:
122123
CODE_SIGN_IDENTITY: "Developer ID Application"

Sources/TokenMenuBarCore/Providers/Claude/ClaudeAPI.swift

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -223,20 +223,37 @@ enum ClaudeMapper {
223223

224224
static func window(_ limit: ClaudeAPI.Limit) -> QuotaWindow {
225225
let scopeName = limit.scope?.model?.displayName ?? limit.scope?.surface
226-
let (id, label, group, duration): (String, String, WindowGroup, TimeInterval?) =
227-
switch limit.kind {
228-
case "session": ("session", "Current session", .session, sessionDuration)
229-
case "weekly_all": ("weekly", "All models", .weekly, weeklyDuration)
230-
case "weekly_scoped":
231-
("weekly:\(Format.slug(scopeName ?? "scoped"))", scopeName ?? "Scoped weekly", .weekly, weeklyDuration)
232-
default:
233-
(
234-
scopeName.map { "\(limit.kind):\(Format.slug($0))" } ?? limit.kind,
235-
scopeName.map { "\(Format.humanize(limit.kind)) \($0)" } ?? Format.humanize(limit.kind),
236-
WindowGroup(rawValue: limit.group ?? "") ?? .other,
237-
nil
238-
)
226+
let id: String
227+
let label: String
228+
let group: WindowGroup
229+
let duration: TimeInterval?
230+
switch limit.kind {
231+
case "session":
232+
id = "session"
233+
label = "Current session"
234+
group = .session
235+
duration = sessionDuration
236+
case "weekly_all":
237+
id = "weekly"
238+
label = "All models"
239+
group = .weekly
240+
duration = weeklyDuration
241+
case "weekly_scoped":
242+
id = "weekly:\(Format.slug(scopeName ?? "scoped"))"
243+
label = scopeName ?? "Scoped weekly"
244+
group = .weekly
245+
duration = weeklyDuration
246+
default:
247+
if let scopeName {
248+
id = "\(limit.kind):\(Format.slug(scopeName))"
249+
label = "\(Format.humanize(limit.kind)) \(scopeName)"
250+
} else {
251+
id = limit.kind
252+
label = Format.humanize(limit.kind)
239253
}
254+
group = WindowGroup(rawValue: limit.group ?? "") ?? .other
255+
duration = nil
256+
}
240257
return QuotaWindow(
241258
id: id,
242259
label: label,
@@ -300,16 +317,22 @@ enum ClaudeMapper {
300317
profile: ClaudeAPI.ProfileResponse?, credentials: ClaudeOAuthCredentials?, local: ClaudeLocalAccount?
301318
) -> ProviderIdentity {
302319
let tier = profile?.organization?.rateLimitTier ?? credentials?.rateLimitTier ?? local?.rateLimitTier
303-
let base: String =
304-
switch profile?.organization?.organizationType ?? credentials?.subscriptionType {
305-
case "claude_max", "max": "Max"
306-
case "claude_pro", "pro": "Pro"
307-
case "claude_team", "team": "Team"
308-
case "claude_enterprise", "enterprise": "Enterprise"
309-
case .some(let other): Format.humanize(other.replacingOccurrences(of: "claude_", with: ""))
310-
case nil:
311-
profile?.account?.hasClaudeMax == true ? "Max" : profile?.account?.hasClaudePro == true ? "Pro" : "Claude"
320+
let base: String
321+
switch profile?.organization?.organizationType ?? credentials?.subscriptionType {
322+
case "claude_max", "max": base = "Max"
323+
case "claude_pro", "pro": base = "Pro"
324+
case "claude_team", "team": base = "Team"
325+
case "claude_enterprise", "enterprise": base = "Enterprise"
326+
case .some(let other): base = Format.humanize(other.replacingOccurrences(of: "claude_", with: ""))
327+
case nil:
328+
if profile?.account?.hasClaudeMax == true {
329+
base = "Max"
330+
} else if profile?.account?.hasClaudePro == true {
331+
base = "Pro"
332+
} else {
333+
base = "Claude"
312334
}
335+
}
313336
let multiplier = tier.flatMap(trailingMultiplier)
314337
return ProviderIdentity(
315338
planName: multiplier.map { "\(base) \($0)x" } ?? base,

Sources/TokenMenuBarCore/Refresh/RefreshCoordinator.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ public final class RefreshCoordinator {
300300
_ = settings.configuredProviders
301301
_ = settings.refreshSeconds
302302
_ = settings.analyticsRefreshMinutes
303-
} onChange: {
303+
} onChange: { [weak self] in
304304
Task { @MainActor [weak self] in
305305
guard let self, loop != nil else { return }
306306
observeScheduleInputs()

Sources/TokenMenuBarUI/Resources/ProviderMarks/CUBE_2D_DARK.svg

Lines changed: 1 addition & 1 deletion
Loading

Sources/TokenMenuBarUI/Resources/ProviderMarks/CUBE_2D_LIGHT.svg

Lines changed: 1 addition & 1 deletion
Loading

Sources/TokenMenuBarUI/Resources/ProviderMarks/provider-marks.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,13 @@
4040
"appearance": "light",
4141
"resource": "CUBE_2D_LIGHT.svg",
4242
"archiveMember": "General Logos/Cube/SVG/CUBE_2D_LIGHT.svg",
43-
"sha256": "c483c02f78eb2619778fdd959e72a9adfac4844854472cd2653d4cbfd60e4d71"
43+
"sha256": "58d7237ab77e9ed8d5f8b297577bf673b21c1fe6a1610d6f1bc8598e886d18c6"
4444
},
4545
{
4646
"appearance": "dark",
4747
"resource": "CUBE_2D_DARK.svg",
4848
"archiveMember": "General Logos/Cube/SVG/CUBE_2D_DARK.svg",
49-
"sha256": "cd0e3e5d8991a4cdd4577f8896cd063105207665165c73e25a1ff918dd367eb7"
49+
"sha256": "0c1940179c8fe0a877c331a1e19a2cb9437a8f9633594e1d81f9ffba106615ff"
5050
}
5151
]
5252
}

mockups/implementation-brief.md

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -87,15 +87,15 @@ The first repair stays on supported popover APIs:
8787

8888
1. Each tab emits a typed `(tab, size)` event from inside that tab's view. No asynchronous callback reads the mutable
8989
selected tab.
90-
1. The controller stores height by tab and only applies a measurement when it belongs to the active tab.
91-
1. Width resolves once per open session: 880 points on a normal display, or the available screen width minus margins. It
90+
2. The controller stores height by tab and only applies a measurement when it belongs to the active tab.
91+
3. Width resolves once per open session: 880 points on a normal display, or the available screen width minus margins. It
9292
never uses `fittingSize` or a transient measured width.
93-
1. The maximum body height is the room below the status item after fixed chrome and screen margins. Oversized content
93+
4. The maximum body height is the room below the status item after fixed chrome and screen margins. Oversized content
9494
uses that complete viewport and scrolls; shorter content keeps its ideal height.
95-
1. The status-item button's screen and current `visibleFrame` are read on every open and after screen-parameter changes.
96-
1. The hosting root fills its assigned frame with `.top` alignment.
97-
1. `NSPopover.animates` drives the size change. A simultaneous independent SwiftUI transition is not used.
98-
1. The visibility guard is set before `NSApplication.activate()`. Fit tasks are cancelled while open and one restart
95+
5. The status-item button's screen and current `visibleFrame` are read on every open and after screen-parameter changes.
96+
6. The hosting root fills its assigned frame with `.top` alignment.
97+
7. `NSPopover.animates` drives the size change. A simultaneous independent SwiftUI transition is not used.
98+
8. The visibility guard is set before `NSApplication.activate()`. Fit tasks are cancelled while open and one restart
9999
runs after close.
100100

101101
The UI must not call `setFrameTopLeftPoint` on the popover's private backing window. Every surveyed implementation with
@@ -170,9 +170,9 @@ The approved preview is a real editor surface backed by the same Core status-ite
170170
provider. The preview exposes the resulting global status order. Drag actions have Move Earlier and Move Later
171171
keyboard and accessibility actions.
172172

173-
The model list mounts its regular stack in stages inside the tab's single scroller. A lazy stack cannot be the source
174-
of an intrinsic popover height: its reported height changes with the viewport and creates a resize-layout feedback
175-
loop. An AppKit drag island is used only if native SwiftUI drop feedback proves unstable on screen.
173+
The model list mounts its regular stack in stages inside the tab's single scroller. A lazy stack cannot be the source of
174+
an intrinsic popover height: its reported height changes with the viewport and creates a resize-layout feedback loop. An
175+
AppKit drag island is used only if native SwiftUI drop feedback proves unstable on screen.
176176

177177
### Providers
178178

@@ -183,12 +183,12 @@ The provider header contains enablement, mark or text fallback, official name, l
183183
status, and refresh interval. Recovery details remain lazy and follow this order:
184184

185185
1. Disabled: preserve account, credential source, and failure detail while polling stops.
186-
1. Access needed: show each resource as Needed, Granted, Stale, or Error with Grant or Grant Again.
187-
1. Credential store unreadable or unsupported: name the store and show the matching setup path.
188-
1. Missing, expired, or revoked: show a copyable official CLI recovery command and Check Again.
189-
1. Policy or license denial: show the account or administrator action.
190-
1. Offline or rate limited: keep stale data and show retry timing.
191-
1. Connected: show account, plan, credential source, and last successful refresh.
186+
2. Access needed: show each resource as Needed, Granted, Stale, or Error with Grant or Grant Again.
187+
3. Credential store unreadable or unsupported: name the store and show the matching setup path.
188+
4. Missing, expired, or revoked: show a copyable official CLI recovery command and Check Again.
189+
5. Policy or license denial: show the account or administrator action.
190+
6. Offline or rate limited: keep stale data and show retry timing.
191+
7. Connected: show account, plan, credential source, and last successful refresh.
192192

193193
Providers discovered from installed CLIs, credential stores, or existing snapshots appear by default. Show All reveals
194194
the remaining supported providers for manual setup. Recovery and empty states keep the provider mark or fallback badge
@@ -350,10 +350,10 @@ Verification uses:
350350

351351
1. Core tests for typed measurements, constant width, caps, geometry, chart rules, style identity, launch policy,
352352
tooltip timing, provider setup, refresh policy, and diagnostics.
353-
1. AppKit integration tests with a real popover and window frame.
354-
1. An Xcode UI-test target for status-item opening, Command-R, Command-F, Escape, Tab and Shift-Tab order, controls,
353+
2. AppKit integration tests with a real popover and window frame.
354+
3. An Xcode UI-test target for status-item opening, Command-R, Command-F, Escape, Tab and Shift-Tab order, controls,
355355
accessibility, and performance.
356-
1. An on-screen release matrix for macOS 14, 15, 26, and 27 covering light and dark mode, contrast, reduced motion and
356+
4. An on-screen release matrix for macOS 14, 15, 26, and 27 covering light and dark mode, contrast, reduced motion and
357357
transparency, both screen edges, notch-adjacent placement, external displays, every Dock position, every tab pair,
358358
arrow shape, bezels, hover, press, and focus.
359359

@@ -363,11 +363,11 @@ of persisted user settings. Development launch recipes use that isolated mode by
363363
## Delivery order
364364

365365
1. Preserve the existing quick-win changes, correct their sizing seam, and add the research brief and approved artifact.
366-
1. Land geometry, measurement, visibility, status re-tier, launch isolation, and CI foundations.
367-
1. Land Core presentation, setup, refresh, diagnostics, tooltip, and chart models.
368-
1. Land the approved Usage and Settings sections with native controls and complete inventory.
369-
1. Land the unified History chart, legend, selection, export, and accessibility.
370-
1. Land the shared tooltip panel, log UI, keyboard routes, and provider marks with approved licensing metadata.
371-
1. Run the full unit, integration, UI, accessibility, coverage, performance, and OS verification matrix.
366+
2. Land geometry, measurement, visibility, status re-tier, launch isolation, and CI foundations.
367+
3. Land Core presentation, setup, refresh, diagnostics, tooltip, and chart models.
368+
4. Land the approved Usage and Settings sections with native controls and complete inventory.
369+
5. Land the unified History chart, legend, selection, export, and accessibility.
370+
6. Land the shared tooltip panel, log UI, keyboard routes, and provider marks with approved licensing metadata.
371+
7. Run the full unit, integration, UI, accessibility, coverage, performance, and OS verification matrix.
372372

373373
Nothing except usage-site links is removed. Condensed information remains reachable and stays in accessibility values.

0 commit comments

Comments
 (0)