From 72f2195f5cd1ef25b4f18865affe8839c473884e Mon Sep 17 00:00:00 2001 From: Akshay Prabhu <12824090+akshayprabhu200@users.noreply.github.com> Date: Sat, 22 Aug 2026 08:39:04 -0700 Subject: [PATCH] Fix single-quota icon scaling --- Sources/CodexBar/IconRenderer.swift | 70 ++++++++++++-- Sources/CodexBar/MenuContent.swift | 3 +- .../StatusItemController+Animation.swift | 6 +- .../CodexBar/StatusItemController+Menu.swift | 3 +- .../Claude/ClaudeProviderDescriptor.swift | 1 + .../Providers/ProviderUsagePresentation.swift | 3 + .../IconRendererHideCrittersTests.swift | 71 ++++++++++++++ .../IconRendererScreenshotRenderTests.swift | 87 ++++++++++++++++++ .../ProviderArchitectureGatekeeperTests.swift | 6 +- ...sentationPolicyCharacterizationTests.swift | 9 ++ .../codex-single-quota-icon-after.png | Bin 0 -> 11174 bytes .../codex-single-quota-icon-before.png | Bin 0 -> 11219 bytes .../codex-single-quota-icon-proof.md | 24 +++++ 13 files changed, 267 insertions(+), 16 deletions(-) create mode 100644 Tests/CodexBarTests/IconRendererScreenshotRenderTests.swift create mode 100644 docs/screenshots/codex-single-quota-icon-after.png create mode 100644 docs/screenshots/codex-single-quota-icon-before.png create mode 100644 docs/screenshots/codex-single-quota-icon-proof.md diff --git a/Sources/CodexBar/IconRenderer.swift b/Sources/CodexBar/IconRenderer.swift index 774589b78e..e242290ba1 100644 --- a/Sources/CodexBar/IconRenderer.swift +++ b/Sources/CodexBar/IconRenderer.swift @@ -3,6 +3,23 @@ import CodexBarCore // swiftlint:disable:next type_body_length enum IconRenderer { + struct QuotaLayoutPolicy: Hashable { + let reservesMissingSecondaryLane: Bool + let treatsExhaustedSecondaryAsMissing: Bool + + static func provider(_ provider: UsageProvider) -> Self { + let presentation = ProviderDescriptorRegistry.descriptor(for: provider).presentation + return Self( + reservesMissingSecondaryLane: presentation.reservesMissingSecondaryIconLane, + treatsExhaustedSecondaryAsMissing: presentation.treatsExhaustedSecondaryIconWindowAsMissing) + } + + static func style(_ style: IconStyle) -> Self { + UsageProvider(rawValue: style.rawValue).map(self.provider) + ?? Self(reservesMissingSecondaryLane: false, treatsExhaustedSecondaryAsMissing: false) + } + } + private static let creditsCap: Double = 1000 private static let baseSize = NSSize(width: 18, height: 18) // Render to an 18×18 pt template (36×36 px at 2×) to match the system menu bar size. @@ -28,6 +45,11 @@ enum IconRenderer { private static let grid = PixelGrid(scale: outputScale) + static func fillWidthPixels(remaining: Double, rectWidth: Int) -> Int { + let clamped = max(0, min(remaining / 100, 1)) + return max(0, min(rectWidth, Int((CGFloat(rectWidth) * CGFloat(clamped)).rounded()))) + } + private struct IconCacheKey: Hashable { let primary: Int let weekly: Int @@ -36,6 +58,7 @@ enum IconRenderer { let style: Int let indicator: Int let hideCritters: Bool + let quotaLayoutPolicy: QuotaLayoutPolicy } private final class IconCacheStore: @unchecked Sendable { @@ -120,8 +143,10 @@ enum IconRenderer { wiggle: CGFloat = 0, tilt: CGFloat = 0, statusIndicator: ProviderStatusIndicator = .none, - hideCritters: Bool = false) -> NSImage + hideCritters: Bool = false, + quotaLayoutPolicy: QuotaLayoutPolicy? = nil) -> NSImage { + let quotaLayoutPolicy = quotaLayoutPolicy ?? .style(style) let shouldCache = blink <= 0.0001 && wiggle <= 0.0001 && tilt <= 0.0001 let render = { self.renderImage { @@ -178,8 +203,7 @@ enum IconRenderer { // Fill: clip to the capsule and paint a left-to-right rect so the progress edge is straight. if let remaining { - let clamped = max(0, min(remaining / 100, 1)) - let fillWidthPx = max(0, min(rectPx.w, Int((CGFloat(rectPx.w) * CGFloat(clamped)).rounded()))) + let fillWidthPx = Self.fillWidthPixels(remaining: remaining, rectWidth: rectPx.w) if fillWidthPx > 0 { NSGraphicsContext.current?.cgContext.saveGState() trackPath.addClip() @@ -638,8 +662,7 @@ enum IconRenderer { let providerPresentation = UsageProvider(rawValue: style.rawValue) .map { ProviderDescriptorRegistry.descriptor(for: $0).presentation } - let usesMissingSecondaryLayout = - providerPresentation?.treatsExhaustedSecondaryIconWindowAsMissing == true + let usesMissingSecondaryLayout = quotaLayoutPolicy.treatsExhaustedSecondaryAsMissing let effectiveWeeklyRemaining: Double? = { if usesMissingSecondaryLayout, let weeklyRemaining, weeklyRemaining <= 0 { return nil @@ -670,7 +693,22 @@ enum IconRenderer { let twistFactory = decorations.contains(.factory) let twistWarp = decorations.contains(.warp) - if weeklyAvailable { + if let bottomValue, bottomValue > 0, topValue == nil, + !quotaLayoutPolicy.reservesMissingSecondaryLane, + !usesMissingSecondaryLayout + { + // Some providers surface their only meaningful quota in the secondary slot. + drawBar( + rectPx: creditsRectPx, + remaining: bottomValue, + addNotches: twistNotches, + addFace: twistFace, + addGeminiTwist: twistGemini, + addAntigravityTwist: twistAntigravity, + addFactoryTwist: twistFactory, + addWarpTwist: twistWarp, + blink: blink) + } else if weeklyAvailable { // Normal: top=primary, bottom=secondary (bonus/weekly). drawBar( rectPx: topRectPx, @@ -693,8 +731,6 @@ enum IconRenderer { blink: blink) drawBar(rectPx: bottomRectPx, remaining: nil, alpha: 0.45) } else { - // Weekly missing (e.g. Claude enterprise): keep normal layout but - // dim the bottom track to indicate N/A. if topValue == nil, let ratio = creditsRatio { // Credits-only: show credits prominently (e.g. credits loaded before usage). drawBar( @@ -709,7 +745,22 @@ enum IconRenderer { addWarpTwist: twistWarp, blink: blink) drawBar(rectPx: creditsBottomRectPx, remaining: nil, alpha: 0.45) + } else if !quotaLayoutPolicy.reservesMissingSecondaryLane, let topValue { + // One meaningful quota should read as one meter. Reserving an unavailable second + // lane makes (for example) 46% remaining look like roughly 23% of the icon. + drawBar( + rectPx: creditsRectPx, + remaining: topValue, + addNotches: twistNotches, + addFace: twistFace, + addGeminiTwist: twistGemini, + addAntigravityTwist: twistAntigravity, + addFactoryTwist: twistFactory, + addWarpTwist: twistWarp, + blink: blink) } else { + // Missing secondary (for example Claude Enterprise): preserve the normal two-lane + // layout and dim the unavailable lane. drawBar( rectPx: topRectPx, remaining: topValue, @@ -765,7 +816,8 @@ enum IconRenderer { stale: stale, style: self.styleKey(style), indicator: self.indicatorKey(statusIndicator), - hideCritters: hideCritters) + hideCritters: hideCritters, + quotaLayoutPolicy: quotaLayoutPolicy) if let cached = self.cachedIcon(for: key) { return cached } diff --git a/Sources/CodexBar/MenuContent.swift b/Sources/CodexBar/MenuContent.swift index d7df29cafe..5462ec7368 100644 --- a/Sources/CodexBar/MenuContent.swift +++ b/Sources/CodexBar/MenuContent.swift @@ -262,6 +262,7 @@ struct StatusIconView: View { stale: self.store.isStale(provider: self.provider), style: self.store.style(for: self.provider), statusIndicator: self.store.statusIndicator(for: self.provider), - hideCritters: self.store.settings.menuBarHidesCritters) + hideCritters: self.store.settings.menuBarHidesCritters, + quotaLayoutPolicy: .provider(self.provider)) } } diff --git a/Sources/CodexBar/StatusItemController+Animation.swift b/Sources/CodexBar/StatusItemController+Animation.swift index 2ad7f1b211..5bb026843b 100644 --- a/Sources/CodexBar/StatusItemController+Animation.swift +++ b/Sources/CodexBar/StatusItemController+Animation.swift @@ -412,7 +412,8 @@ extension StatusItemController { wiggle: wiggle, tilt: tilt, statusIndicator: statusIndicator, - hideCritters: self.settings.menuBarHidesCritters) + hideCritters: self.settings.menuBarHidesCritters, + quotaLayoutPolicy: .provider(primaryProvider)) self.setButtonContent( image: warningFlash ? Self.quotaWarningFlashImage(base: image) : image, title: nil, @@ -636,7 +637,8 @@ extension StatusItemController { wiggle: wiggle, tilt: tilt, statusIndicator: statusIndicator, - hideCritters: self.settings.menuBarHidesCritters) + hideCritters: self.settings.menuBarHidesCritters, + quotaLayoutPolicy: .provider(provider)) self.setButtonContent( image: warningFlash ? Self.quotaWarningFlashImage(base: image) : image, title: nil, diff --git a/Sources/CodexBar/StatusItemController+Menu.swift b/Sources/CodexBar/StatusItemController+Menu.swift index fc2df6a124..f467a3c51f 100644 --- a/Sources/CodexBar/StatusItemController+Menu.swift +++ b/Sources/CodexBar/StatusItemController+Menu.swift @@ -1432,7 +1432,8 @@ extension StatusItemController { wiggle: 0, tilt: 0, statusIndicator: indicator, - hideCritters: self.settings.menuBarHidesCritters) + hideCritters: self.settings.menuBarHidesCritters, + quotaLayoutPolicy: .provider(provider)) image.isTemplate = true return image } diff --git a/Sources/CodexBarCore/Providers/Claude/ClaudeProviderDescriptor.swift b/Sources/CodexBarCore/Providers/Claude/ClaudeProviderDescriptor.swift index 3142e7cffe..6a4bb1314b 100644 --- a/Sources/CodexBarCore/Providers/Claude/ClaudeProviderDescriptor.swift +++ b/Sources/CodexBarCore/Providers/Claude/ClaudeProviderDescriptor.swift @@ -180,6 +180,7 @@ public enum ClaudeProviderDescriptor { menuCardStyle: .claude) }, iconDecorations: [.notches], + reservesMissingSecondaryIconLane: true, automaticSelectionPrioritizesExhaustedWindow: false, menuBarWindowResolver: self.menuBarWindow, planUtilizationSeriesResolver: { snapshot in diff --git a/Sources/CodexBarCore/Providers/ProviderUsagePresentation.swift b/Sources/CodexBarCore/Providers/ProviderUsagePresentation.swift index fba4bdc0e1..4121c101d7 100644 --- a/Sources/CodexBarCore/Providers/ProviderUsagePresentation.swift +++ b/Sources/CodexBarCore/Providers/ProviderUsagePresentation.swift @@ -423,6 +423,7 @@ public struct ProviderUsagePresentation: Sendable { private let widgetRowLimitResolver: WidgetRowLimitResolver public let iconDecorations: ProviderIconDecorations public let treatsExhaustedSecondaryIconWindowAsMissing: Bool + public let reservesMissingSecondaryIconLane: Bool public let primarySemanticWindow: ProviderSemanticWindow public let secondarySemanticWindow: ProviderSemanticWindow public let menuBarLayoutSecondaryLabel: String? @@ -448,6 +449,7 @@ public struct ProviderUsagePresentation: Sendable { }, iconDecorations: ProviderIconDecorations = [], treatsExhaustedSecondaryIconWindowAsMissing: Bool = false, + reservesMissingSecondaryIconLane: Bool = false, semanticWindowResolver: @escaping SemanticWindowResolver = Self.standardSemanticWindows, primarySemanticWindow: ProviderSemanticWindow = .session, secondarySemanticWindow: ProviderSemanticWindow = .weekly, @@ -473,6 +475,7 @@ public struct ProviderUsagePresentation: Sendable { self.iconWindowResolver = iconWindowResolver self.iconDecorations = iconDecorations self.treatsExhaustedSecondaryIconWindowAsMissing = treatsExhaustedSecondaryIconWindowAsMissing + self.reservesMissingSecondaryIconLane = reservesMissingSecondaryIconLane self.semanticWindowResolver = semanticWindowResolver self.primarySemanticWindow = primarySemanticWindow self.secondarySemanticWindow = secondarySemanticWindow diff --git a/Tests/CodexBarTests/IconRendererHideCrittersTests.swift b/Tests/CodexBarTests/IconRendererHideCrittersTests.swift index eb879f1b2c..e8b8eab26f 100644 --- a/Tests/CodexBarTests/IconRendererHideCrittersTests.swift +++ b/Tests/CodexBarTests/IconRendererHideCrittersTests.swift @@ -58,6 +58,77 @@ struct IconRendererHideCrittersTests { #expect(try self.pixels(decorated) != self.pixels(plain)) } + @Test + func `fill width tracks and clamps the reported percentage`() { + #expect(IconRenderer.fillWidthPixels(remaining: 46, rectWidth: 30) == 14) + #expect(IconRenderer.fillWidthPixels(remaining: -1, rectWidth: 30) == 0) + #expect(IconRenderer.fillWidthPixels(remaining: 0, rectWidth: 30) == 0) + #expect(IconRenderer.fillWidthPixels(remaining: 100, rectWidth: 30) == 30) + #expect(IconRenderer.fillWidthPixels(remaining: 120, rectWidth: 30) == 30) + } + + @Test + func `single quota layout follows provider policy even in combined style`() throws { + func image( + primary: Double?, + weekly: Double?, + policy: IconRenderer.QuotaLayoutPolicy) -> NSImage + { + IconRenderer.makeIcon( + primaryRemaining: primary, + weeklyRemaining: weekly, + creditsRemaining: nil, + stale: false, + style: .combined, + hideCritters: true, + quotaLayoutPolicy: policy) + } + + let compact = IconRenderer.QuotaLayoutPolicy.provider(.codex) + let reserved = IconRenderer.QuotaLayoutPolicy.provider(.claude) + let compactPrimary = image(primary: 46, weekly: nil, policy: compact) + let compactSecondary = image(primary: nil, weekly: 46, policy: compact) + let reservedPrimary = image(primary: 46, weekly: nil, policy: reserved) + + #expect(try self.pixels(compactPrimary) == self.pixels(compactSecondary)) + #expect(try self.pixels(compactPrimary) != self.pixels(reservedPrimary)) + } + + @Test + func `special and multi-value layouts remain unchanged`() throws { + func image( + primary: Double?, + weekly: Double?, + credits: Double? = nil, + policy: IconRenderer.QuotaLayoutPolicy) -> NSImage + { + IconRenderer.makeIcon( + primaryRemaining: primary, + weeklyRemaining: weekly, + creditsRemaining: credits, + stale: false, + style: .combined, + hideCritters: true, + quotaLayoutPolicy: policy) + } + + let compact = IconRenderer.QuotaLayoutPolicy.provider(.codex) + let reserved = IconRenderer.QuotaLayoutPolicy.provider(.claude) + let warp = IconRenderer.QuotaLayoutPolicy.provider(.warp) + + #expect(try self.pixels(image(primary: 46, weekly: 46, policy: compact)) + == self.pixels(image(primary: 46, weekly: 46, policy: reserved))) + #expect(try self.pixels(image(primary: 46, weekly: 0, policy: compact)) + == self.pixels(image(primary: 46, weekly: 0, policy: reserved))) + #expect(try self.pixels(image(primary: nil, weekly: nil, credits: 460, policy: compact)) + == self.pixels(image(primary: nil, weekly: nil, credits: 460, policy: reserved))) + #expect(try self.pixels(image(primary: 46, weekly: nil, policy: warp)) + == self.pixels(image(primary: 46, weekly: 0, policy: warp))) + + let unknown = image(primary: nil, weekly: nil, policy: compact) + #expect(try self.pixels(unknown).isEmpty == false) + } + @Test func `hiding critters is a no-op for an undecorated style`() throws { // Cursor has no critter twist, so the flag must not alter its bars. diff --git a/Tests/CodexBarTests/IconRendererScreenshotRenderTests.swift b/Tests/CodexBarTests/IconRendererScreenshotRenderTests.swift new file mode 100644 index 0000000000..2b27ff0e41 --- /dev/null +++ b/Tests/CodexBarTests/IconRendererScreenshotRenderTests.swift @@ -0,0 +1,87 @@ +import AppKit +import XCTest +@testable import CodexBar + +/// Developer tool, skipped by default: renders a synthetic single-quota icon for PR proof. +/// +/// Run with: +/// CODEXBAR_ICON_SCREENSHOT_DIR=docs/screenshots \ +/// swift test --filter IconRendererScreenshotRenderTests +@MainActor +final class IconRendererScreenshotRenderTests: XCTestCase { + private static let canvasSize = NSSize(width: 360, height: 240) + + func test_renderSyntheticSingleQuotaIcon() throws { + guard let dir = ProcessInfo.processInfo.environment["CODEXBAR_ICON_SCREENSHOT_DIR"] else { + throw XCTSkip("Set CODEXBAR_ICON_SCREENSHOT_DIR to render the synthetic icon proof.") + } + let directory = URL(fileURLWithPath: dir, isDirectory: true) + try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) + + let icon = IconRenderer.makeIcon( + primaryRemaining: 46, + weeklyRemaining: nil, + creditsRemaining: nil, + stale: false, + style: .combined, + hideCritters: true, + quotaLayoutPolicy: .provider(.codex)) + let data = try XCTUnwrap(Self.proofPNG(icon: icon), "synthetic icon proof render failed") + let url = directory.appendingPathComponent("codex-single-quota-icon.png") + try data.write(to: url, options: .atomic) + print("Wrote \(url.lastPathComponent)") + } + + private static func proofPNG(icon: NSImage) -> Data? { + guard let representation = NSBitmapImageRep( + bitmapDataPlanes: nil, + pixelsWide: Int(canvasSize.width), + pixelsHigh: Int(canvasSize.height), + bitsPerSample: 8, + samplesPerPixel: 4, + hasAlpha: true, + isPlanar: false, + colorSpaceName: .deviceRGB, + bytesPerRow: 0, + bitsPerPixel: 0), + let context = NSGraphicsContext(bitmapImageRep: representation) + else { return nil } + representation.size = Self.canvasSize + + NSGraphicsContext.saveGraphicsState() + NSGraphicsContext.current = context + NSColor(srgbRed: 0.10, green: 0.11, blue: 0.12, alpha: 1).setFill() + NSRect(origin: .zero, size: Self.canvasSize).fill() + + let paragraph = NSMutableParagraphStyle() + paragraph.alignment = .center + let titleAttributes: [NSAttributedString.Key: Any] = [ + .font: NSFont.systemFont(ofSize: 26, weight: .semibold), + .foregroundColor: NSColor.white, + .paragraphStyle: paragraph, + ] + let subtitleAttributes: [NSAttributedString.Key: Any] = [ + .font: NSFont.monospacedDigitSystemFont(ofSize: 22, weight: .regular), + .foregroundColor: NSColor(white: 0.72, alpha: 1), + .paragraphStyle: paragraph, + ] + NSString(string: "Synthetic proof").draw( + in: NSRect(x: 24, y: 192, width: 312, height: 36), + withAttributes: titleAttributes) + NSString(string: "46% remaining").draw( + in: NSRect(x: 24, y: 158, width: 312, height: 32), + withAttributes: subtitleAttributes) + + context.imageInterpolation = .none + icon.isTemplate = false + icon.draw( + in: NSRect(x: 108, y: 20, width: 144, height: 144), + from: NSRect(origin: .zero, size: icon.size), + operation: .sourceOver, + fraction: 1, + respectFlipped: false, + hints: nil) + NSGraphicsContext.restoreGraphicsState() + return representation.representation(using: .png, properties: [.interlaced: false]) + } +} diff --git a/Tests/CodexBarTests/ProviderArchitectureGatekeeperTests.swift b/Tests/CodexBarTests/ProviderArchitectureGatekeeperTests.swift index a189eecbaa..a9414c05b9 100644 --- a/Tests/CodexBarTests/ProviderArchitectureGatekeeperTests.swift +++ b/Tests/CodexBarTests/ProviderArchitectureGatekeeperTests.swift @@ -1719,7 +1719,7 @@ struct ProviderArchitectureGatekeeperTests { reason: "This exact shared construct dispatches a provider-owned capability at the generic integration boundary."), AllowedProviderConstruct( path: "Sources/CodexBar/IconRenderer.swift", - line: 668, + line: 691, anchor: "let twistGemini = decorations.contains(.gemini)", expectedProviderIDs: ["antigravity", "factory", "gemini", "warp"], expectedReferenceCount: 4, @@ -2464,7 +2464,7 @@ struct ProviderArchitectureGatekeeperTests { reason: "This exact app-runtime bridge coordinates provider-owned state through the shared controller."), AllowedProviderConstruct( path: "Sources/CodexBar/StatusItemController+Animation.swift", - line: 572, + line: 573, anchor: "guard isLoading, style == .warp, let phase else {", expectedProviderIDs: ["warp"], expectedReferenceCount: 1, @@ -2472,7 +2472,7 @@ struct ProviderArchitectureGatekeeperTests { reason: "This exact app-runtime bridge coordinates provider-owned state through the shared controller."), AllowedProviderConstruct( path: "Sources/CodexBar/StatusItemController+Animation.swift", - line: 926, + line: 928, anchor: "if provider == .kiro {", expectedProviderIDs: ["cursor", "kiro"], expectedReferenceCount: 2, diff --git a/Tests/CodexBarTests/ProviderPresentationPolicyCharacterizationTests.swift b/Tests/CodexBarTests/ProviderPresentationPolicyCharacterizationTests.swift index 77446d9eb3..93194c3ca6 100644 --- a/Tests/CodexBarTests/ProviderPresentationPolicyCharacterizationTests.swift +++ b/Tests/CodexBarTests/ProviderPresentationPolicyCharacterizationTests.swift @@ -174,6 +174,15 @@ struct ProviderPresentationPolicyCharacterizationTests { } } + @Test + func `missing secondary lane reservation is claude only`() { + for provider in UsageProvider.allCases { + let actual = ProviderDescriptorRegistry.descriptor(for: provider) + .presentation.reservesMissingSecondaryIconLane + #expect(actual == (provider == .claude), "Unexpected missing-lane policy for \(provider.rawValue)") + } + } + @Test func `credit visibility exceptions are pinned`() { let codex = ProviderDescriptorRegistry.descriptor(for: .codex).metadata diff --git a/docs/screenshots/codex-single-quota-icon-after.png b/docs/screenshots/codex-single-quota-icon-after.png new file mode 100644 index 0000000000000000000000000000000000000000..0118c1fa91b2520b2129b4f123ec68d37e3eda19 GIT binary patch literal 11174 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 literal 0 HcmV?d00001 diff --git a/docs/screenshots/codex-single-quota-icon-before.png b/docs/screenshots/codex-single-quota-icon-before.png new file mode 100644 index 0000000000000000000000000000000000000000..f921b09297e561936e8ae1712babc09ba88ac1c8 GIT binary patch literal 11219 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 M1r7PyH|C%J7fG2@kpKVy literal 0 HcmV?d00001 diff --git a/docs/screenshots/codex-single-quota-icon-proof.md b/docs/screenshots/codex-single-quota-icon-proof.md new file mode 100644 index 0000000000..d0ad401439 --- /dev/null +++ b/docs/screenshots/codex-single-quota-icon-proof.md @@ -0,0 +1,24 @@ +# Codex single-quota icon proof + +These screenshots use a fixed synthetic input: `46% remaining`, with no secondary quota and no credits. +They contain only pixels from `IconRenderer` plus the generic labels shown in the cards. No live provider, +account, Keychain, desktop, username, email, or filesystem data is read or displayed. + +| Before | After | +| --- | --- | +| ![Before: 46% occupies the upper lane while the unavailable lower lane remains](codex-single-quota-icon-before.png) | ![After: 46% occupies one prominent meter](codex-single-quota-icon-after.png) | + +The before image was rendered from upstream commit `27c7f334e3c46c96ff8c063afbe0c7944ba5e0b7` with +`style: .codex`. The after image was rendered from this branch with `style: .combined` and +`quotaLayoutPolicy: .provider(.codex)`, matching the merged-menu dispatch path. The old renderer did not +accept an explicit quota-layout policy; with critters hidden, its relevant single-quota geometry was the same +for `.codex` and `.combined`. + +Generate the after proof with: + +```sh +CODEXBAR_ICON_SCREENSHOT_DIR=docs/screenshots \ + swift test --filter IconRendererScreenshotRenderTests +``` + +The opt-in screenshot test is skipped during normal test runs.