Skip to content

Commit 65d82ee

Browse files
Polish share preview and proof
1 parent 3d8f0bd commit 65d82ee

9 files changed

Lines changed: 85 additions & 32 deletions

File tree

Sources/CodexBar/ShareStatsWindowController.swift

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -76,17 +76,28 @@ private struct ShareStatsPreviewView: View {
7676

7777
@State private var style: ShareStatsCardStyle = .defaultStyle
7878
@State private var statusMessage: String?
79+
@State private var statusIsError = false
7980

8081
var body: some View {
81-
VStack(spacing: 16) {
82-
if self.payload.hasModelActivityData {
83-
Picker(L("Share card style"), selection: self.$style) {
84-
Text(L("Summary")).tag(ShareStatsCardStyle.summary)
85-
Text(L("Model activity")).tag(ShareStatsCardStyle.modelActivity)
82+
VStack(alignment: .leading, spacing: 18) {
83+
HStack(alignment: .top, spacing: 24) {
84+
VStack(alignment: .leading, spacing: 4) {
85+
Text(L("Share AI Usage"))
86+
.font(.title2.weight(.semibold))
87+
Text(L("Nothing is uploaded. This image is created on your Mac."))
88+
.font(.subheadline)
89+
.foregroundStyle(.secondary)
90+
}
91+
Spacer(minLength: 16)
92+
if self.payload.hasModelActivityData {
93+
Picker(L("Share card style"), selection: self.$style) {
94+
Text(L("Summary")).tag(ShareStatsCardStyle.summary)
95+
Text(L("Model activity")).tag(ShareStatsCardStyle.modelActivity)
96+
}
97+
.pickerStyle(.segmented)
98+
.frame(width: 280)
99+
.accessibilityLabel(L("Share card style"))
86100
}
87-
.pickerStyle(.segmented)
88-
.frame(width: 280)
89-
.accessibilityLabel(L("Share card style"))
90101
}
91102

92103
ShareStatsScaledPreview(payload: self.payload, style: self.style)
@@ -99,7 +110,9 @@ private struct ShareStatsPreviewView: View {
99110

100111
HStack(spacing: 12) {
101112
Button {
102-
self.statusMessage = self.copyImage(self.style) ? L("Image copied") : L("Could not copy image")
113+
let didCopy = self.copyImage(self.style)
114+
self.statusMessage = didCopy ? L("Image copied") : L("Could not copy image")
115+
self.statusIsError = !didCopy
103116
} label: {
104117
Label(L("Copy Image"), systemImage: "photo.on.rectangle")
105118
}
@@ -108,29 +121,36 @@ private struct ShareStatsPreviewView: View {
108121
Button {
109122
self.copyText(self.style)
110123
self.statusMessage = L("Stats copied")
124+
self.statusIsError = false
111125
} label: {
112126
Label(L("Copy Stats"), systemImage: "doc.on.doc")
113127
}
114128

115129
Button {
116130
if self.saveImage(self.style) {
117131
self.statusMessage = L("Image saved")
132+
self.statusIsError = false
118133
}
119134
} label: {
120135
Label(L("Save..."), systemImage: "square.and.arrow.down")
121136
}
122137

123138
Spacer()
124139

125-
Text(self.statusMessage ?? L("Nothing is uploaded. This image is created on your Mac."))
126-
.font(.footnote)
127-
.foregroundStyle(.secondary)
128-
.accessibilityLabel(self
129-
.statusMessage ?? L("Nothing is uploaded. This image is created on your Mac."))
140+
if let statusMessage = self.statusMessage {
141+
Label(
142+
statusMessage,
143+
systemImage: self.statusIsError
144+
? "exclamationmark.circle.fill"
145+
: "checkmark.circle.fill")
146+
.font(.footnote.weight(.medium))
147+
.foregroundStyle(self.statusIsError ? Color.red : Color.secondary)
148+
}
130149
}
131150
}
132151
.padding(24)
133152
.frame(minWidth: 780, minHeight: 570)
153+
.background(Color(nsColor: .windowBackgroundColor))
134154
}
135155
}
136156

Tests/CodexBarTests/ShareStatsTests.swift

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -721,7 +721,9 @@ struct ShareStatsTests {
721721

722722
@Test @MainActor
723723
func `renderer creates nonblank social card PNGs at share sizes`() throws {
724-
let payload = try #require(ShareStatsBuilder.make(model: Self.proofDashboard))
724+
let payload = try #require(ShareStatsBuilder.make(
725+
model: Self.proofDashboard,
726+
trackedSources: Self.proofTrackedSources))
725727
#expect(ShareStatsCardView.size == CGSize(width: 1200, height: 630))
726728
#expect(payload.providers.count == 4)
727729
#expect(payload.topModels.count == 4)
@@ -766,7 +768,9 @@ struct ShareStatsTests {
766768

767769
@Test @MainActor
768770
func `summary card remains the default export while model activity is opt in`() throws {
769-
let payload = try #require(ShareStatsBuilder.make(model: Self.proofDashboard))
771+
let payload = try #require(ShareStatsBuilder.make(
772+
model: Self.proofDashboard,
773+
trackedSources: Self.proofTrackedSources))
770774

771775
#expect(ShareStatsCardStyle.defaultStyle == .summary)
772776
#expect(ShareStatsFormatting.text(payload).hasPrefix("My AI subscriptions"))
@@ -1010,29 +1014,29 @@ extension ShareStatsTests {
10101014
private static var proofDashboard: SpendDashboardModel {
10111015
var calendar = Calendar(identifier: .gregorian)
10121016
calendar.timeZone = TimeZone(secondsFromGMT: 0) ?? .gmt
1013-
let start = calendar.startOfDay(for: self.date)
1017+
let start = calendar.startOfDay(for: Date(timeIntervalSince1970: 1_781_481_600))
10141018
let exclusiveEnd = calendar.date(byAdding: .day, value: 30, to: start) ?? start
10151019
let sources = [
10161020
ProofSource(id: "codex", provider: .codex, name: "Codex", model: "gpt-5.4", tokens: 8_000_000, cost: 94),
10171021
ProofSource(
1018-
id: "openrouter",
1019-
provider: .openrouter,
1020-
name: "OpenRouter",
1021-
model: "anthropic/claude-sonnet-4",
1022+
id: "claude",
1023+
provider: .claude,
1024+
name: "Claude",
1025+
model: "claude-sonnet-4",
10221026
tokens: 7_000_000,
10231027
cost: 140),
10241028
ProofSource(
1025-
id: "kimi",
1026-
provider: .kimi,
1027-
name: "Kimi",
1028-
model: "moonshotai/kimi-k2.5",
1029+
id: "cursor",
1030+
provider: .cursor,
1031+
name: "Cursor",
1032+
model: "claude-4.5-sonnet",
10291033
tokens: 4_000_000,
10301034
cost: 22),
10311035
ProofSource(
1032-
id: "zai",
1033-
provider: .zai,
1034-
name: "Z.ai",
1035-
model: "z-ai/glm-4.5",
1036+
id: "mistral",
1037+
provider: .mistral,
1038+
name: "Mistral",
1039+
model: "mistral-large-3",
10361040
tokens: 2_700_000,
10371041
cost: 18),
10381042
]
@@ -1082,6 +1086,17 @@ extension ShareStatsTests {
10821086
return SpendDashboardModel(requestedDays: 30, groups: [group])
10831087
}
10841088

1089+
private static let proofTrackedSources = [
1090+
ShareStatsTests.trackedSource(id: "codex", provider: .codex, contributes: true),
1091+
ShareStatsTests.trackedSource(id: "claude", provider: .claude, contributes: true),
1092+
ShareStatsTests.trackedSource(id: "cursor", provider: .cursor, contributes: true),
1093+
ShareStatsTests.trackedSource(id: "mistral", provider: .mistral, contributes: true),
1094+
ShareStatsTests.trackedSource(id: "openrouter", provider: .openrouter, contributes: false),
1095+
ShareStatsTests.trackedSource(id: "kimi", provider: .kimi, contributes: false),
1096+
ShareStatsTests.trackedSource(id: "gemini", provider: .gemini, contributes: false),
1097+
ShareStatsTests.trackedSource(id: "zai", provider: .zai, contributes: false),
1098+
]
1099+
10851100
private static func dashboard(models: [String]) -> SpendDashboardModel {
10861101
SpendDashboardModel(requestedDays: 30, groups: [
10871102
SpendDashboardModel.CurrencyGroup(
3.78 KB
Loading
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Share stats proof
2+
3+
The PNGs in this directory are rendered by the production SwiftUI share-card
4+
renderer. `share-preview-window.png` captures the real preview window with the
5+
opt-in Model activity style selected.
6+
7+
All values, provider names, and model routes come from a synthetic aggregate
8+
fixture. No account identifiers, prompts, keys, or live usage data are included.
9+
The four cost-history contributors use providers supported by the dashboard
10+
contract: Codex, Claude, Cursor, and Mistral. Four additional fixture sources are
11+
tracked but excluded from cost totals.
12+
13+
With a full Xcode toolchain, regenerate the card sizes with:
14+
15+
```sh
16+
CODEXBAR_SHARE_STATS_PROOF_DIR="$PWD/docs/screenshots/share-stats-proof" \
17+
swift test --filter ShareStatsTests
18+
```
211 KB
Loading
3.78 KB
Loading
362 Bytes
Loading
1.37 KB
Loading

docs/screenshots/share-stats-proof/share-stats.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ You kept the models busy · last 30 days
44
Estimated token spend: $274.00 · pricing for 4 of 4 sources
55
Top model routes:
66
GPT-5.4 via Codex
7-
Claude Sonnet 4 via OpenRouter
8-
Kimi K2.5 via Kimi
7+
Claude Sonnet 4 via Claude
8+
Claude 4.5 Sonnet via Cursor
99
+1 more safe route summaries
1010
8 sources tracked
1111
4 with cost history · 4 excluded from cost totals
12-
Aggregated locally by CodexBar · No prompts shared · Data through Jul 15, 2026
12+
Aggregated locally by CodexBar · No prompts shared · Data through Jul 13, 2026

0 commit comments

Comments
 (0)