Skip to content

Prototype: Services as composable state machines - #190

Draft
kyleve wants to merge 8 commits into
cursor/prototype-post-write-reconcile-planfrom
cursor/prototype-services-state-machines
Draft

Prototype: Services as composable state machines#190
kyleve wants to merge 8 commits into
cursor/prototype-post-write-reconcile-planfrom
cursor/prototype-services-state-machines

Conversation

@kyleve

@kyleve kyleve commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Look-only prototype — not wired into production.

What this is

An exploratory sketch of redefining WhereServices coordination as composable state machines:

ServicesSnapshot     ← whole-system state at a glance
ServicesEvent        ← commands + effect completions
ServiceEffect        ← scheduled side effects
ServicesMachine      ← top-level reduce()
Machines/            ← one reducer per TLA spec slice
ServicesMachineReplay ← scenario tests

Sub-machines (TLA-aligned)

Machine Spec
TrackingMachine TrackingReconciliation
PostWriteMachine PostWriteReconcile
IngestorMachine IngestorQuiesce
LaunchMachine LaunchLifecycle
ResetMachine WhereServices.reset() ordering

Plain actors (ReportReader, backup export, recent activity, …) stay out of scope.

How to read it

  1. Start at Where/WhereCore/Sources/Prototype/ServicesStateMachine/ServicesSnapshot.swift
  2. Then ServicesMachine.reduce — one event in, snapshot + effects out
  3. Pick any Machines/*.swift — maps 1:1 to an existing TLA README
  4. Run ServicesStateMachineTests — replays Coalesced.cfg / Current.cfg scenarios

Docs: Where/WhereCore/Prototype/ServicesStateMachine/README.md

Stacked on

If this were real

A production runner would replace ad-hoc Task loops in WhereSession / DayJournal incrementally — one protocol at a time — not a big-bang rewrite.

Open in Web Open in Cursor 

kyleve and others added 8 commits August 4, 2026 21:42
## What

A new native-macOS menu bar app, **Ledger**, that shows your current-cycle Cursor spend at a glance. Building it also **restores the native-macOS build infrastructure** removed with Foreman (#68) — the `.macOS` platform, a `.mac` app target + hostless macOS test bundle, the platform-scoped CI scheme, and the `test-macos` job.

The menu-bar item shows the current-cycle amount (cents dropped, digits roll over on change). Clicking opens a popover with:
- **This cycle** spend + billing-cycle date range, and the plan tier as a badge.
- **Today** and **this week** spend (differenced from locally recorded history — hidden until enough history exists).
- **Included usage** as two bars — first-party/Auto vs third-party/API — since a single blended figure hides that one pool can be maxed while the other is barely used.
- **Top models this cycle** as usage shares (each model ≥5% gets its own bar; smaller ones roll into one multi-colored "Other models" bar with a legend), with model ids parsed into friendly names + badges (e.g. `Claude Opus 5` · `high`).
- A **Refresh** button, **Settings**, and **Quit**.

## Data source

Works for **individual** accounts by calling the same undocumented endpoints `cursor.com/dashboard` uses (the team Admin API only sees team accounts):

- `GET /api/usage-summary` — cycle dates, plan, included-usage pool percentages, and live usage-based spend (`individualUsage.onDemand.used`).
- `POST /api/dashboard/get-filtered-usage-events` — individual events (per-event model + `chargedCents`), paginated over the cycle and summed per model for the breakdown. Must be called **without** `teamId` for an individual account, or it 401s.

**Auth is zero-config**: it auto-detects your Cursor session read-only from the local app's `state.vscdb` (`cursorAuth/accessToken`) and builds the required `WorkosCursorSessionToken=<userId>::<jwt>` cookie, deriving `userId` from the JWT `sub` (a bare JWT 401s). Settings › Account has an optional paste-a-token override (Keychain-stored) for when auto-detect fails or the session expires. The resolved token is cached and re-read only when it can actually change — the pasted value is edited, the API returns 401, or Settings appears.

### Today / this week

`onDemand.used` is a cycle-cumulative running total, so `SpendHistory` records timestamped samples (`SpendHistoryStore`, JSON in Application Support, pruned to ~14 days) and **differences** them — today = since local midnight, this week = since the start of the calendar week. These are real billed dollars, and the diff captures spend even while the app was closed, as long as a sample exists near the window start. Each figure is hidden until there's a baseline.

## Cost & resilience

- The headline refreshes every 5 minutes (configurable in Settings); opening the popover does not fetch. The **per-model breakdown is throttled separately** to at most every 15 minutes, since it walks every usage event in the cycle — an explicit Refresh or a cycle rollover bypasses the throttle.
- A **failed refresh keeps the last data on screen** and turns the "Updated…" caption into an amber stale warning (reason on hover) rather than blanking. The full error screen shows only before anything has loaded. A per-model failure likewise keeps the last good breakdown.
- Only the newest request may mutate state (including recorded history), so a superseded response can't skew day/week baselines.
- The API gets a **dedicated ephemeral session with no cookie storage** and a 30s timeout: auth is the `Cookie` header we set explicitly, and URLSession must never substitute a stored one.

## Structure

- **`Ledger/LedgerCore`** (macOS-only SPM library) — `LedgerServices` (`@MainActor @Observable` root, single `LoadState`), `SessionToken`/`SessionTokenSource`/`CursorLocalTokenSource`, `KeychainStore`, `DashboardProvider`/`CursorDashboardAPI`, wire/view models (`UsageSummary`, `UsageEvent`/`UsageEventsPage`, `SpendSnapshot`, `ModelShare`, `ModelName`), `SpendHistory`/`SpendHistoryStore`, and `LoginItemController` (launch-at-login, restored from Foreman).
- **`Ledger/Ledger`** (`.mac`, `LSUIElement`) — AppKit `NSStatusItem` + `NSPopover` shell hosting a SwiftUI `MenuBarLabel` (bound to the observable session; deliberately not `MenuBarExtra`), a System-Settings-style sidebar, and the thin `LedgerSession` facade.
- **`Ledger/install`** — builds a Release, ad-hoc-signs it, and installs to `/Applications` so it runs standalone (no Xcode).
- Build wiring: `.macOS(.v26)` + `LedgerCore` in `Package.swift`; `Ledger`/`LedgerCoreTests` targets and `Ledger`/`Ledger-macOS-Tests` schemes in `Project.swift`; the `test-macos` CI job; root `AGENTS.md` + per-module `README.md`/`AGENTS.md`.

## Notes / caveats

- The dashboard endpoints are **undocumented** and can change without notice (e.g. `get-aggregated-usage-events` went stale for some accounts — returning days-old data and omitting newly released models — which is why the breakdown uses the per-event endpoint).
- Cursor's `state.vscdb` is read **strictly read-only** (`SQLITE_OPEN_READONLY`, one parameterized `SELECT` against `ItemTable`); we never write to it. If the key or path ever moves, auto-detect degrades to "no session found" and the paste fallback takes over.
- It rides your Cursor **web session** (re-open Cursor when it expires).
- The per-model figures are **usage shares, not spend**: their summed per-event cost is total usage value (included allowance + on-demand), which exceeds the billed on-demand headline.
- There is **no year-to-date total** — the monthly-invoice endpoint is a billing ledger with cross-month credit/adjustment lines, so summing it isn't a meaningful yearly figure.

## Verification

- `./swiftformat --lint` — clean
- `mise exec -- tuist generate --no-open` — clean
- `mise exec -- tuist test Ledger-macOS-Tests -- -destination 'platform=macOS'` — full LedgerCore suite passes: parsing/decoding, JWT→cookie derivation, a real SQLite round-trip for the token reader, history differencing, stale-vs-error state, per-model aggregation and pagination, the throttle, token caching/invalidation, and a `URLProtocol`-stubbed check of the outgoing request shape (no `teamId`, epoch-ms dates, the session cookie) — mutation-checked to confirm it fails when the rule is broken.
- `mise exec -- tuist build Stuff-iOS-Tests` — iOS scheme unaffected
- The auth-path changes (cookie-less session, token caching) were additionally verified against the live API after installing, since unit tests can't exercise them.
The Ledger PR (#103) landed LedgerLog on PeriscopeCore, but AGENTS.md and
README.md still described LogKit and an OSLog subsystem. Align the module docs
with WhereCore's Periscope wording.

Validation: documentation-only; no tests run.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Add an exploratory ServicesStateMachine layer under WhereCore/Prototype
that models five TLA-aligned protocol slices (tracking, post-write,
ingestor quiesce, launch, reset) as pure reducers over a composite
ServicesSnapshot. Includes scenario replay tests and a README — not
wired into production WhereServices.

Co-authored-by: Cursor <cursoragent@cursor.com>
> _Posted by an AI agent on kve's behalf._

## Summary

- persist the primary Location-card counts last presented for each year
- hold saved counts until the card surface is visible and unobscured, then animate to the current report
- emit one light haptic when one or more visible counts increased; keep decreases, first visits, and newly appearing cards silent
- clear presentation history on reset and cover the behavior with focused persistence and presentation-model tests

## Design notes

The reconciliation task is attached to the card surface and keyed by its counts, year, and explicit visibility. Switching tabs, pushing a destination, or presenting the Resolve sheet leaves the saved baseline untouched; returning to unobscured cards reconciles against the latest report. Multiple increased cards intentionally produce one coordinated haptic event.

## Testing

- `./swiftformat --lint`
- `./test --all` (1,629 tests passed before the focused review commits)
- `./test WhereUITests` (350 tests passed after review fixes)
- `./test --snapshots` (33 tests passed after review fixes; references unchanged)
- `git diff --check`
> _Posted by an AI agent on kve’s behalf._

## Summary

- add a compact passport-style GitHub link with a subtle accent glow at the bottom of Settings > About
- share the guilloché rosette renderer with the existing Locations cards while keeping footer styling independently tokenized
- add in-file previews required by the architecture lint
- capture every About snapshot at intrinsic full-content height across the existing iPhone and iPad matrices
- teach SnapshotKitTesting to measure UIKit-backed SwiftUI forms from their root scroll content, with a regression test
- localize the new copy, extend Settings search terms, and refresh About documentation

## Testing

- ./swiftformat --lint
- ./xcstrings --lint
- swift run bumper lint . --timings
- ./test WhereUITests (345 tests passed)
- ./test SnapshotKitTestingTests (41 tests passed)
- focused About snapshot matrix (16 configurations passed after recording and visual review)
- focused Locations snapshot matrix passed
## Summary

- add an inset “Private by design” passport card at the top of Data and About
- state the privacy model directly: location history stays on the user’s devices and in their private iCloud account, and Where sends no data to anyone
- render the privacy card as a passport-navy, gold-accented reflective surface using the location cards’ snapshot- and Reduce Motion-aware tilt treatment
- keep the security-print rosette and Liquid Glass treatment independent from motion availability, so the complete surface remains visible before or without a tilt sample
- share the passport-card structure with the GitHub link while retaining standard Form insets and the native two-shadow treatment from main; the privacy variant uses a quieter dark-navy glow
- cover the complete Data and About pages with intrinsic-size iPhone and iPad snapshots across appearance, contrast, and accessibility variants

## Testing

- ./test WhereUITests (354 tests passed before the visual follow-ups)
- focused PassportCardSurfaceKindTests, PassportCardSurfaceTests, PassportCardTests, PrivacyPassportCardTests, and passportCardStyle tests
- focused AboutSettingsViewSnapshotTests and DataSettingsViewSnapshotTests
- ./swiftformat --lint
- ./xcstrings --lint
- swift run bumper lint . --timings
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant