diff --git a/README.md b/README.md index c798ca0..af17d30 100644 --- a/README.md +++ b/README.md @@ -57,13 +57,19 @@ That quits `CodexToolbar` if it is running, removes its launch-at-login registra ## Screenshots -Captured from v0.1.17 using fixed sample data in light and dark mode. The website uses the same projection screenshots. +Captured from v0.1.18 with fixed sample values. The default examples show a Weekly Codex window; the optional Spark example adds separately labeled 5h and Weekly windows. Available windows depend on what Codex reports for your account. The website uses the same Weekly projection screenshots. | Theme | Normal | Warning | Critical | Projection | | --- | --- | --- | --- | --- | | Light |  |  |  |  | | Dark |  |  |  |  | +With Spark usage enabled: + +| Light | Dark | +| --- | --- | +|  |  | + ## Notes - The app launches its own `codex app-server` subprocess. You do not need to keep an interactive Codex CLI session open. diff --git a/Sources/CodexToolbar/App/ScreenshotScenario.swift b/Sources/CodexToolbar/App/ScreenshotScenario.swift index 46d1bf3..589964a 100644 --- a/Sources/CodexToolbar/App/ScreenshotScenario.swift +++ b/Sources/CodexToolbar/App/ScreenshotScenario.swift @@ -78,12 +78,12 @@ struct ScreenshotScenario: Equatable, Sendable { ))! } + // Screenshot fixtures mirror the observed window structure: Codex has a + // weekly primary window; Spark has separate 5h and weekly windows. private static func snapshot( - primaryUsed: Int, - primaryReset: Date, - secondaryUsed: Int, - secondaryReset: Date, - secondaryDurationMinutes: Int = 10080 + used: Int, + reset: Date, + durationMinutes: Int = 10080 ) -> CodexRateLimitsSnapshot { CodexRateLimitsSnapshot( credits: nil, @@ -91,25 +91,20 @@ struct ScreenshotScenario: Equatable, Sendable { limitName: "Codex", planType: .pro, primary: CodexRateLimitWindow( - resetsAt: Int64(primaryReset.timeIntervalSince1970), - usedPercent: primaryUsed, - windowDurationMins: 300 + resetsAt: Int64(reset.timeIntervalSince1970), + usedPercent: used, + windowDurationMins: durationMinutes ), - secondary: CodexRateLimitWindow( - resetsAt: Int64(secondaryReset.timeIntervalSince1970), - usedPercent: secondaryUsed, - windowDurationMins: secondaryDurationMinutes - ) + secondary: nil ) } static let normal: ScreenshotScenario = { let now = date(year: 2026, month: 3, day: 8, hour: 13, minute: 3) - let primaryReset = date(year: 2026, month: 3, day: 8, hour: 13, minute: 54) let secondaryReset = date(year: 2026, month: 3, day: 11, hour: 0, minute: 0) return ScreenshotScenario( name: "normal", - snapshot: snapshot(primaryUsed: 19, primaryReset: primaryReset, secondaryUsed: 10, secondaryReset: secondaryReset), + snapshot: snapshot(used: 19, reset: secondaryReset), rateLimitsByLimitId: nil, now: now, lastUpdated: now, @@ -121,11 +116,10 @@ struct ScreenshotScenario: Equatable, Sendable { static let warning: ScreenshotScenario = { let now = date(year: 2026, month: 3, day: 8, hour: 14, minute: 11) - let primaryReset = date(year: 2026, month: 3, day: 8, hour: 14, minute: 46) let secondaryReset = date(year: 2026, month: 3, day: 11, hour: 0, minute: 0) return ScreenshotScenario( name: "warning", - snapshot: snapshot(primaryUsed: 74, primaryReset: primaryReset, secondaryUsed: 41, secondaryReset: secondaryReset), + snapshot: snapshot(used: 74, reset: secondaryReset), rateLimitsByLimitId: nil, now: now, lastUpdated: now, @@ -137,11 +131,10 @@ struct ScreenshotScenario: Equatable, Sendable { static let critical: ScreenshotScenario = { let now = date(year: 2026, month: 3, day: 8, hour: 16, minute: 22) - let primaryReset = date(year: 2026, month: 3, day: 8, hour: 16, minute: 31) let secondaryReset = date(year: 2026, month: 3, day: 11, hour: 0, minute: 0) return ScreenshotScenario( name: "critical", - snapshot: snapshot(primaryUsed: 94, primaryReset: primaryReset, secondaryUsed: 84, secondaryReset: secondaryReset), + snapshot: snapshot(used: 94, reset: secondaryReset), rateLimitsByLimitId: nil, now: now, lastUpdated: now, @@ -153,11 +146,10 @@ struct ScreenshotScenario: Equatable, Sendable { static let exhausted: ScreenshotScenario = { let now = date(year: 2026, month: 3, day: 8, hour: 18, minute: 5) - let primaryReset = date(year: 2026, month: 3, day: 8, hour: 18, minute: 5) let secondaryReset = date(year: 2026, month: 3, day: 11, hour: 0, minute: 0) return ScreenshotScenario( name: "exhausted", - snapshot: snapshot(primaryUsed: 100, primaryReset: primaryReset, secondaryUsed: 91, secondaryReset: secondaryReset), + snapshot: snapshot(used: 100, reset: secondaryReset), rateLimitsByLimitId: nil, now: now, lastUpdated: now, @@ -169,16 +161,13 @@ struct ScreenshotScenario: Equatable, Sendable { static let multiweek: ScreenshotScenario = { let now = date(year: 2026, month: 3, day: 10, hour: 22, minute: 46) - let primaryReset = date(year: 2026, month: 3, day: 10, hour: 23, minute: 7) let secondaryReset = date(year: 2026, month: 3, day: 17, hour: 0, minute: 0) return ScreenshotScenario( name: "multiweek", snapshot: snapshot( - primaryUsed: 15, - primaryReset: primaryReset, - secondaryUsed: 99, - secondaryReset: secondaryReset, - secondaryDurationMinutes: 10081 + used: 99, + reset: secondaryReset, + durationMinutes: 10081 ), rateLimitsByLimitId: nil, now: now, @@ -191,11 +180,10 @@ struct ScreenshotScenario: Equatable, Sendable { static let projection: ScreenshotScenario = { let now = date(year: 2026, month: 5, day: 13, hour: 12, minute: 0) - let primaryReset = date(year: 2026, month: 5, day: 13, hour: 16, minute: 0) let secondaryReset = date(year: 2026, month: 5, day: 18, hour: 12, minute: 0) return ScreenshotScenario( name: "projection", - snapshot: snapshot(primaryUsed: 29, primaryReset: primaryReset, secondaryUsed: 25, secondaryReset: secondaryReset), + snapshot: snapshot(used: 45, reset: secondaryReset), rateLimitsByLimitId: nil, now: now, lastUpdated: now, @@ -212,7 +200,7 @@ struct ScreenshotScenario: Equatable, Sendable { return ScreenshotScenario( name: "spark", - snapshot: snapshot(primaryUsed: 40, primaryReset: primaryReset, secondaryUsed: 50, secondaryReset: secondaryReset), + snapshot: snapshot(used: 50, reset: secondaryReset), rateLimitsByLimitId: [ "codex_bengalfox": CodexRateLimitsSnapshot( credits: nil, @@ -227,7 +215,7 @@ struct ScreenshotScenario: Equatable, Sendable { secondary: CodexRateLimitWindow( resetsAt: Int64(secondaryReset.timeIntervalSince1970) + 12_000, usedPercent: 15, - windowDurationMins: 20_160 + windowDurationMins: 10_080 ) ) ], diff --git a/Tests/CodexToolbarTests/ScreenshotScenarioTests.swift b/Tests/CodexToolbarTests/ScreenshotScenarioTests.swift index 1556eaf..6bcff78 100644 --- a/Tests/CodexToolbarTests/ScreenshotScenarioTests.swift +++ b/Tests/CodexToolbarTests/ScreenshotScenarioTests.swift @@ -4,6 +4,14 @@ import XCTest @MainActor final class ScreenshotScenarioTests: XCTestCase { + func testPublishedDefaultScenariosContainOnlyWeeklyCodexWindow() { + for scenario in [ScreenshotScenario.normal, .warning, .critical, .projection] { + XCTAssertEqual(scenario.snapshot.primary?.windowDurationMins, 10080, scenario.name) + XCTAssertNil(scenario.snapshot.secondary, scenario.name) + XCTAssertNil(scenario.rateLimitsByLimitId, scenario.name) + } + } + func testWarningScenarioMapsToExpectedCardStates() throws { let scenario = try XCTUnwrap(ScreenshotScenario.named("warning")) @@ -15,12 +23,12 @@ final class ScreenshotScenarioTests: XCTestCase { timeZone: scenario.timeZone ) - XCTAssertEqual(cards.count, 2) - XCTAssertEqual(cards[0].title, "5h") + XCTAssertEqual(cards.count, 1) + XCTAssertEqual(cards[0].title, "Weekly") XCTAssertEqual(cards[0].progressState, .warning) XCTAssertEqual(cards[0].usageText, "74% used · 26% remaining") - XCTAssertEqual(cards[0].combinedResetText, "Resets in 35m (2:46 PM)") - XCTAssertEqual(cards[1].progressState, .normal) + XCTAssertNil(scenario.snapshot.secondary) + XCTAssertEqual(scenario.snapshot.primary?.windowDurationMins, 10080) } func testScreenshotModeBuildsFixtureStoreWithoutLiveFetching() async { @@ -36,7 +44,7 @@ final class ScreenshotScenarioTests: XCTestCase { XCTAssertEqual(client.loadSnapshotCallCount, 0) XCTAssertEqual(store.state, .ready) XCTAssertEqual(store.cards.first?.usageText, "19% used · 81% remaining") - XCTAssertEqual(store.statusBarText, "5h: Open") + XCTAssertEqual(store.statusBarText, "Week: Open") XCTAssertEqual( store.statusItemPresentation, .bar(.init(remainingPercent: 81, progressState: .normal)) @@ -66,7 +74,7 @@ final class ScreenshotScenarioTests: XCTestCase { XCTAssertEqual(store.lastUpdated, ScreenshotScenario.multiweek.lastUpdated) } - func testProjectionScenarioBuildsFiveHourAndWeeklyResetRiskChartData() throws { + func testProjectionScenarioBuildsWeeklyResetRiskChartData() throws { let scenario = try XCTUnwrap(ScreenshotScenario.named("projection")) let cards = RateLimitStore.makeCards( from: scenario.snapshot, @@ -75,17 +83,10 @@ final class ScreenshotScenarioTests: XCTestCase { locale: scenario.locale, timeZone: scenario.timeZone ) - let fiveHourCard = try XCTUnwrap(cards.first { $0.title == "5h" }) - let fiveHourProjection = try XCTUnwrap(fiveHourCard.projection) - let weeklyCard = try XCTUnwrap(cards.first { $0.title == "Weekly" }) - let weeklyProjection = try XCTUnwrap(weeklyCard.projection) - - XCTAssertEqual(fiveHourProjection.state, .critical) - XCTAssertEqual(fiveHourProjection.summaryText, "Projected empty before reset") - XCTAssertEqual(fiveHourProjection.detailText, "Empty 2:26 PM at pace") - XCTAssertEqual(weeklyProjection.state, .resetFirst) - XCTAssertEqual(weeklyProjection.summaryText, "Reset comes first") - XCTAssertEqual(weeklyProjection.detailText, "On pace to last through reset") + XCTAssertEqual(cards.map(\.title), ["Weekly"]) + let weeklyProjection = try XCTUnwrap(cards.first?.projection) + XCTAssertEqual(weeklyProjection.state, .warning) + XCTAssertEqual(weeklyProjection.summaryText, "Projected empty before reset") } func testSparkScenarioBuildsFamilySectionsWithCodexFirst() throws { @@ -99,13 +100,13 @@ final class ScreenshotScenarioTests: XCTestCase { ) let sections = RateLimitStore.makeCardSections(from: cards) - XCTAssertEqual(cards.count, 4) + XCTAssertEqual(cards.count, 3) XCTAssertEqual(cards.first?.displayTitle, "GPT-5.3-Codex-Spark · 5h") XCTAssertEqual(sections.map(\.familyId), ["codex", "codex_bengalfox"]) XCTAssertEqual(sections.map(\.title), [nil, "GPT-5.3-Codex-Spark limit"]) XCTAssertEqual(sections.map(\.showsTitle), [false, true]) - XCTAssertEqual(sections[0].cards.map(\.title), ["5h", "Weekly"]) - XCTAssertEqual(sections[1].cards.map(\.title), ["5h", "2 Week"]) + XCTAssertEqual(sections[0].cards.map(\.title), ["Weekly"]) + XCTAssertEqual(sections[1].cards.map(\.title), ["5h", "Weekly"]) } func testSparkScenarioBuildsStoreFromLaunchWithoutLiveFetching() async { diff --git a/screenshots/critical-dark-popover.png b/screenshots/critical-dark-popover.png index 1bec941..96349af 100644 Binary files a/screenshots/critical-dark-popover.png and b/screenshots/critical-dark-popover.png differ diff --git a/screenshots/critical-light-popover.png b/screenshots/critical-light-popover.png index 9a74def..3182caa 100644 Binary files a/screenshots/critical-light-popover.png and b/screenshots/critical-light-popover.png differ diff --git a/screenshots/normal-dark-popover.png b/screenshots/normal-dark-popover.png index 9b00086..253ac3a 100644 Binary files a/screenshots/normal-dark-popover.png and b/screenshots/normal-dark-popover.png differ diff --git a/screenshots/normal-light-popover.png b/screenshots/normal-light-popover.png index 31a824c..d6fc57e 100644 Binary files a/screenshots/normal-light-popover.png and b/screenshots/normal-light-popover.png differ diff --git a/screenshots/projection-dark-popover.png b/screenshots/projection-dark-popover.png index 4bc300d..6c0acb0 100644 Binary files a/screenshots/projection-dark-popover.png and b/screenshots/projection-dark-popover.png differ diff --git a/screenshots/projection-light-popover.png b/screenshots/projection-light-popover.png index 13256c7..c41b586 100644 Binary files a/screenshots/projection-light-popover.png and b/screenshots/projection-light-popover.png differ diff --git a/screenshots/readme-critical-dark-popover.png b/screenshots/readme-critical-dark-popover.png index 1bec941..96349af 100644 Binary files a/screenshots/readme-critical-dark-popover.png and b/screenshots/readme-critical-dark-popover.png differ diff --git a/screenshots/readme-critical-light-popover.png b/screenshots/readme-critical-light-popover.png index 9a74def..3182caa 100644 Binary files a/screenshots/readme-critical-light-popover.png and b/screenshots/readme-critical-light-popover.png differ diff --git a/screenshots/readme-normal-dark-popover.png b/screenshots/readme-normal-dark-popover.png index 9b00086..253ac3a 100644 Binary files a/screenshots/readme-normal-dark-popover.png and b/screenshots/readme-normal-dark-popover.png differ diff --git a/screenshots/readme-normal-light-popover.png b/screenshots/readme-normal-light-popover.png index 31a824c..d6fc57e 100644 Binary files a/screenshots/readme-normal-light-popover.png and b/screenshots/readme-normal-light-popover.png differ diff --git a/screenshots/readme-projection-dark-popover.png b/screenshots/readme-projection-dark-popover.png index 4bc300d..6c0acb0 100644 Binary files a/screenshots/readme-projection-dark-popover.png and b/screenshots/readme-projection-dark-popover.png differ diff --git a/screenshots/readme-projection-light-popover.png b/screenshots/readme-projection-light-popover.png index 13256c7..c41b586 100644 Binary files a/screenshots/readme-projection-light-popover.png and b/screenshots/readme-projection-light-popover.png differ diff --git a/screenshots/readme-spark-dark-popover.png b/screenshots/readme-spark-dark-popover.png new file mode 100644 index 0000000..eeddec4 Binary files /dev/null and b/screenshots/readme-spark-dark-popover.png differ diff --git a/screenshots/readme-spark-light-popover.png b/screenshots/readme-spark-light-popover.png new file mode 100644 index 0000000..af5eb7c Binary files /dev/null and b/screenshots/readme-spark-light-popover.png differ diff --git a/screenshots/readme-warning-dark-popover.png b/screenshots/readme-warning-dark-popover.png index b68ba71..09ab30a 100644 Binary files a/screenshots/readme-warning-dark-popover.png and b/screenshots/readme-warning-dark-popover.png differ diff --git a/screenshots/readme-warning-light-popover.png b/screenshots/readme-warning-light-popover.png index 50d1111..bb807a1 100644 Binary files a/screenshots/readme-warning-light-popover.png and b/screenshots/readme-warning-light-popover.png differ diff --git a/screenshots/spark-dark-popover.png b/screenshots/spark-dark-popover.png new file mode 100644 index 0000000..eeddec4 Binary files /dev/null and b/screenshots/spark-dark-popover.png differ diff --git a/screenshots/spark-light-popover.png b/screenshots/spark-light-popover.png new file mode 100644 index 0000000..af5eb7c Binary files /dev/null and b/screenshots/spark-light-popover.png differ diff --git a/screenshots/warning-dark-popover.png b/screenshots/warning-dark-popover.png index b68ba71..09ab30a 100644 Binary files a/screenshots/warning-dark-popover.png and b/screenshots/warning-dark-popover.png differ diff --git a/screenshots/warning-light-popover.png b/screenshots/warning-light-popover.png index 50d1111..bb807a1 100644 Binary files a/screenshots/warning-light-popover.png and b/screenshots/warning-light-popover.png differ diff --git a/scripts/generate_screenshots.sh b/scripts/generate_screenshots.sh index f701fff..87d86d4 100755 --- a/scripts/generate_screenshots.sh +++ b/scripts/generate_screenshots.sh @@ -4,7 +4,7 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" SCREENSHOTS_DIR="$ROOT_DIR/screenshots" APP_EXECUTABLE="$ROOT_DIR/dist/CodexToolbar.app/Contents/MacOS/CodexToolbar" -README_SCENARIOS=(normal warning critical projection) +README_SCENARIOS=(normal warning critical projection spark) "$ROOT_DIR/scripts/build_app.sh" >/dev/null @@ -35,6 +35,7 @@ run_capture() { local appearance="$2" local capture_status_item="${3:-false}" + CODEX_TOOLBAR_SCREENSHOT_VISIBLE_SUPPLEMENTAL_FAMILIES="codex_bengalfox" \ CODEX_TOOLBAR_SCREENSHOT_SCENARIO="$scenario" \ CODEX_TOOLBAR_SCREENSHOT_APPEARANCE="$appearance" \ CODEX_TOOLBAR_SCREENSHOT_OUTPUT_DIR="$SCREENSHOTS_DIR" \ diff --git a/site/assets/codextoolbar-tahoe-dark.png b/site/assets/codextoolbar-tahoe-dark.png index 4bc300d..6c0acb0 100644 Binary files a/site/assets/codextoolbar-tahoe-dark.png and b/site/assets/codextoolbar-tahoe-dark.png differ diff --git a/site/assets/codextoolbar-tahoe-light.png b/site/assets/codextoolbar-tahoe-light.png index 13256c7..c41b586 100644 Binary files a/site/assets/codextoolbar-tahoe-light.png and b/site/assets/codextoolbar-tahoe-light.png differ diff --git a/site/index.html b/site/index.html index 432a6da..84a2a7d 100644 --- a/site/index.html +++ b/site/index.html @@ -102,9 +102,9 @@