Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
ede6472
Add LedgerCore library and restore the native macOS platform
kyleve Jul 17, 2026
c25d11a
Add the Ledger menu-bar app and macOS build/test wiring
kyleve Jul 17, 2026
509cb8f
Document Ledger: per-module docs and root AGENTS.md updates
kyleve Jul 17, 2026
ec89b31
Switch Ledger to the individual dashboard API (session token)
kyleve Jul 17, 2026
8a27379
Always show the amount in the menu-bar item
kyleve Jul 17, 2026
33dacdf
Don't refresh spend on popover open
kyleve Jul 18, 2026
e514a2f
Animate spend with the numeric-text transition
kyleve Jul 18, 2026
d398d81
Size the status item to the hosted label so the amount shows
kyleve Jul 18, 2026
889e9ed
Enrich the popover: included-usage bar, status lines, top models
kyleve Jul 18, 2026
56e11c6
Add Ledger/install: build Release and install to /Applications
kyleve Jul 18, 2026
88d06e1
install: wait for the old app to exit before replacing it
kyleve Jul 18, 2026
afa9062
Set Ledger's LSApplicationCategoryType
kyleve Jul 18, 2026
14925ec
Keep spend visible during a refresh
kyleve Jul 19, 2026
ca795e3
Remove the unreliable year-to-date total
kyleve Jul 19, 2026
7d8f898
Refresh every 5 minutes by default + add a Settings picker
kyleve Jul 19, 2026
5ebc9bf
Roll sub-20% models into one multi-colored bar
kyleve Jul 20, 2026
8a5f34b
Drop the duplicated included-usage status lines
kyleve Jul 20, 2026
906ce4d
Move the plan tier to the header
kyleve Jul 20, 2026
2c3431d
Split included usage into first-party and API bars
kyleve Jul 20, 2026
6b1a5de
Add a model-name parser for friendlier labels
kyleve Jul 20, 2026
29e0d99
Popover polish: lowercase badges, drop 'This cycle', move 'Updated'
kyleve Jul 20, 2026
a5a0bec
Add today / this-week spend from local history
kyleve Jul 20, 2026
3ed48b7
Compact menu-bar amount: no cents, $10 increments at $100+
kyleve Jul 20, 2026
d973e0b
Drop the menu-bar dollar-sign icon
kyleve Jul 20, 2026
2222158
Menu bar: truncate cents instead of $10 rounding
kyleve Jul 20, 2026
32e8bb7
Keep spend visible when a refresh fails; show a stale warning
kyleve Jul 20, 2026
7277261
Make the Refresh button icon-only
kyleve Jul 20, 2026
1d69d75
Fix stale model breakdown: use the live per-event endpoint
kyleve Jul 25, 2026
eb1efe4
Throttle the per-model fetch; don't record history from stale refreshes
kyleve Jul 26, 2026
10628c7
Use a dedicated cookie-less session for the dashboard API
kyleve Jul 26, 2026
e4d6308
Cache the resolved session token instead of re-reading per refresh
kyleve Jul 26, 2026
4206377
Fix stale doc comments and drop unused SpendDeltas.none
kyleve Jul 26, 2026
9a72549
Test CursorDashboardAPI's wire shape with a URLProtocol stub
kyleve Jul 26, 2026
b11387b
Give models their own bar at 5% share instead of 20%
kyleve Jul 27, 2026
935e137
Merge remote-tracking branch 'origin/main' into cursor/ledger-spend-m…
kyleve Jul 27, 2026
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
56 changes: 28 additions & 28 deletions .bumper/Sources/WhereProjectRules.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ let whereProjectRules = RuleSet {
Rules.constructionOwnership(
"WhereServices",
allowed: whereServicesConstructionScope,
id: "where.services_composition_ownership"
id: "where.services_composition_ownership",
)
Rules.constructionOwnership(
"CoreLocationSource",
allowed: .files(["Where/WhereUI/Sources/Launch/WhereLaunch.swift"]),
id: "where.live_location_source_ownership"
id: "where.live_location_source_ownership",
)
Rules.singleNominalSpelling(
suffix: "Log",
owner: whereLoggingScope,
id: "where.logging_type_ownership"
id: "where.logging_type_ownership",
)
productionStoreOpeningRule
checkedConcurrencyBoundaryRule
Expand Down Expand Up @@ -46,7 +46,7 @@ private let productionStoreOpeningPaths: Set<RelativeFilePath> = [
private let productionStoreOpeningRule = Rules.files(
"where.production_store_opening",
severity: .error,
summary: "Production SwiftData stores open only at the app and share-extension composition roots."
summary: "Production SwiftData stores open only at the app and share-extension composition roots.",
) { file in
functionCalls()
.filter { match in
Expand All @@ -59,8 +59,8 @@ private let productionStoreOpeningRule = Rules.files(
message: "SwiftDataStore.make is called outside a process composition root.",
evidence: ViolationEvidence(
observed: "SwiftDataStore.make in \(file.path.rawValue)",
expectation: "open the production store in WhereLaunch or ShareEvidenceModel"
)
expectation: "open the production store in WhereLaunch or ShareEvidenceModel",
),
)
}
}
Expand All @@ -76,7 +76,7 @@ private let documentedUnsafeConcurrencyPaths: Set<RelativeFilePath> = [
private let checkedConcurrencyBoundaryRule = Rules.files(
"where.checked_concurrency_boundaries",
severity: .error,
summary: "Unchecked concurrency escape hatches stay inside documented lifecycle boundaries."
summary: "Unchecked concurrency escape hatches stay inside documented lifecycle boundaries.",
) { file in
let preconcurrencyFailures = SyntaxQuery<AttributeSyntax>()
.filter { match in
Expand All @@ -88,8 +88,8 @@ private let checkedConcurrencyBoundaryRule = Rules.files(
message: "Production code uses an @preconcurrency escape hatch.",
evidence: ViolationEvidence(
observed: match.node.trimmedDescription,
expectation: "use checked Swift concurrency"
)
expectation: "use checked Swift concurrency",
),
)
}

Expand All @@ -105,8 +105,8 @@ private let checkedConcurrencyBoundaryRule = Rules.files(
message: "nonisolated(unsafe) is outside a documented lifecycle boundary.",
evidence: ViolationEvidence(
observed: match.node.trimmedDescription,
expectation: "checked isolation or a documented existing boundary"
)
expectation: "checked isolation or a documented existing boundary",
),
)
}

Expand All @@ -116,7 +116,7 @@ private let checkedConcurrencyBoundaryRule = Rules.files(
private let gregorianCalendarRule = Rules.files(
"where.gregorian_calendar",
severity: .error,
summary: "Where day and year calculations do not use the device's potentially non-Gregorian current calendar."
summary: "Where day and year calculations do not use the device's potentially non-Gregorian current calendar.",
) { file in
SyntaxQuery<MemberAccessExprSyntax>()
.filter { match in
Expand All @@ -129,8 +129,8 @@ private let gregorianCalendarRule = Rules.files(
message: "Where uses Calendar.current instead of an explicit Gregorian calendar.",
evidence: ViolationEvidence(
observed: match.node.trimmedDescription,
expectation: "an injected Gregorian calendar or Calendar.whereIntents"
)
expectation: "an injected Gregorian calendar or Calendar.whereIntents",
),
)
}
}
Expand All @@ -151,7 +151,7 @@ private let whereStoreMutatingMethods: Set<String> = [
private let storeTransactionBoundaryRule = Rules.files(
"where.store_transaction_boundary",
severity: .error,
summary: "WhereStore mutations occur inside the transaction owned by store.perform."
summary: "WhereStore mutations occur inside the transaction owned by store.perform.",
) { file in
functionCalls()
.filter { match in
Expand All @@ -170,8 +170,8 @@ private let storeTransactionBoundaryRule = Rules.files(
message: "WhereStore mutation occurs outside store.perform.",
evidence: ViolationEvidence(
observed: match.node.calledExpression.trimmedDescription,
expectation: "call the mutation from inside store.perform { ... }"
)
expectation: "call the mutation from inside store.perform { ... }",
),
)
}
}
Expand All @@ -195,7 +195,7 @@ private func isInsideStorePerform(_ node: FunctionCallExprSyntax) -> Bool {
private let appShortcutsProviderOwnershipRule = Rules.files(
"where.app_shortcuts_provider_ownership",
severity: .error,
summary: "AppShortcutsProvider conformances live in the Where app target."
summary: "AppShortcutsProvider conformances live in the Where app target.",
) { file in
guard file.component.rawValue != WhereComponent.app.rawValue else { return [] }
return SyntaxQuery<InheritedTypeSyntax>()
Expand All @@ -206,16 +206,16 @@ private let appShortcutsProviderOwnershipRule = Rules.files(
message: "AppShortcutsProvider conformance is outside the Where app target.",
evidence: ViolationEvidence(
observed: file.path.rawValue,
expectation: "a source owned by the Where app component"
)
expectation: "a source owned by the Where app component",
),
)
}
}

private let loggingFacadeRule = Rules.files(
"where.logging_facade",
severity: .error,
summary: "Where production logging goes through its typed Periscope facades."
summary: "Where production logging goes through its typed Periscope facades.",
) { file in
let rawLoggingImports = SyntaxQuery<ImportDeclSyntax>()
.filter { $0.node.path.trimmedDescription == "OSLog" }
Expand All @@ -225,8 +225,8 @@ private let loggingFacadeRule = Rules.files(
message: "Where production code imports OSLog directly.",
evidence: ViolationEvidence(
observed: "import OSLog",
expectation: "WhereLog or RegionLog"
)
expectation: "WhereLog or RegionLog",
),
)
}

Expand All @@ -238,8 +238,8 @@ private let loggingFacadeRule = Rules.files(
message: "Where production code prints directly.",
evidence: ViolationEvidence(
observed: "print",
expectation: "a typed WhereLog or RegionLog event"
)
expectation: "a typed WhereLog or RegionLog event",
),
)
}

Expand All @@ -254,7 +254,7 @@ private let previewCoverageRule = Rules.files(
"where.preview_coverage",
severity: .error,
summary: "Every WhereUI or widget source file declaring a previewable component includes a #Preview.",
scope: previewScope
scope: previewScope,
) { file in
let previewableDeclarations = SyntaxQuery<StructDeclSyntax>()
.filter { match in
Expand Down Expand Up @@ -283,8 +283,8 @@ private let previewCoverageRule = Rules.files(
message: "\(match.node.name.text) has no #Preview in its source file.",
evidence: ViolationEvidence(
observed: file.path.rawValue,
expectation: "at least one #Preview in the same file"
)
expectation: "at least one #Preview in the same file",
),
)
}
}
40 changes: 20 additions & 20 deletions .bumper/Tests/WhereArchitectureTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ func `Where architecture accepts downward dependencies`() throws {
files: [
SourceInput(
path: "Where/WhereCore/Sources/Service.swift",
component: try ComponentID(WhereComponent.whereCore.rawValue),
source: "import RegionKit\nstruct Service {}"
component: ComponentID(WhereComponent.whereCore.rawValue),
source: "import RegionKit\nstruct Service {}",
),
SourceInput(
path: "Where/WhereUI/Sources/Screen.swift",
component: try ComponentID(WhereComponent.whereUI.rawValue),
source: "import WhereCore\nimport SwiftUI\nstruct Screen {}"
component: ComponentID(WhereComponent.whereUI.rawValue),
source: "import WhereCore\nimport SwiftUI\nstruct Screen {}",
),
]
)
],
),
)

#expect(report.violations.isEmpty)
Expand All @@ -32,11 +32,11 @@ func `RegionKit cannot depend upward on WhereCore`() throws {
files: [
SourceInput(
path: "Where/RegionKit/Sources/Region.swift",
component: try ComponentID(WhereComponent.regionKit.rawValue),
source: "import WhereCore\nstruct Region {}"
component: ComponentID(WhereComponent.regionKit.rawValue),
source: "import WhereCore\nstruct Region {}",
),
]
)
],
),
)

let violation = try #require(report.violations.first)
Expand All @@ -53,11 +53,11 @@ func `WhereUI cannot import persistence`() throws {
files: [
SourceInput(
path: "Where/WhereUI/Sources/Screen.swift",
component: try ComponentID(WhereComponent.whereUI.rawValue),
source: "import SwiftData\nstruct Screen {}"
component: ComponentID(WhereComponent.whereUI.rawValue),
source: "import SwiftData\nstruct Screen {}",
),
]
)
],
),
)

let violation = try #require(report.violations.first)
Expand All @@ -74,16 +74,16 @@ func `Where adapters cannot link Broadway directly`() throws {
files: [
SourceInput(
path: "Where/WhereWidgets/Sources/Widget.swift",
component: try ComponentID(WhereComponent.widgets.rawValue),
source: "import BroadwayUI\nstruct Widget {}"
component: ComponentID(WhereComponent.widgets.rawValue),
source: "import BroadwayUI\nstruct Widget {}",
),
SourceInput(
path: "Where/WhereIntents/Sources/Intent.swift",
component: try ComponentID(WhereComponent.whereIntents.rawValue),
source: "import BroadwayCore\nstruct Intent {}"
component: ComponentID(WhereComponent.whereIntents.rawValue),
source: "import BroadwayCore\nstruct Intent {}",
),
]
)
],
),
)

#expect(report.violations.count == 2)
Expand Down
Loading
Loading