Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
137 commits
Select commit Hold shift + click to select a range
8de8938
fix(watchface): melt remaining arcs clockwise from 12
neuroborus Jul 27, 2026
08f7f66
docs(watchface): align ring melt, strip accents, and credits docs
neuroborus Jul 27, 2026
2c350c2
feat(cursor): surface Cursor Models and Other Models plan pools
neuroborus Jul 27, 2026
6af14cc
feat(phone): load seeded multi-provider demo for Mock data
neuroborus Jul 27, 2026
80f459b
fix(phone): keep Extra usage off watch rings and cap slots at three
neuroborus Jul 27, 2026
4ac2422
fix(phone): plot request-only Cursor usage history bars
neuroborus Jul 27, 2026
ee8431a
feat(wear): show Glance-matched strip credits and glanceable mock bal…
neuroborus Jul 27, 2026
8886336
fix(phone): icon-only status chrome and one plaque per provider family
neuroborus Jul 27, 2026
b305067
feat(phone): move connection catalog onto the Providers tab
neuroborus Jul 27, 2026
0b2640f
refactor(phone): structure Settings as systemic sections
neuroborus Jul 27, 2026
08572f8
feat(phone): add opt-in alert threshold prefs on Providers and Settings
neuroborus Jul 27, 2026
c10848b
feat(phone): move watch ring slots onto the Watchface tab
neuroborus Jul 27, 2026
cb05a82
feat(phone): add Widget tab with independent home-widget metric prefs
neuroborus Jul 27, 2026
7acf024
feat(core): evaluate dashboard alerts from opt-in user thresholds
neuroborus Jul 27, 2026
14a0a5b
feat(phone): add Android home-screen widget with locked design
neuroborus Jul 27, 2026
ed671c4
feat(phone): expand home-widget plans and keep launcher sync fresh
neuroborus Jul 27, 2026
273a3c7
feat(phone): hug home widget rows and columns with themed preview
neuroborus Jul 30, 2026
f785c09
feat(phone): use remaining-% alert thresholds on Providers
neuroborus Aug 1, 2026
fd0ffa0
style(phone): normalize Dart formatting across phone shell and bindings
neuroborus Aug 1, 2026
7d279eb
ci(phone): gate Dart formatting for the phone shell and bindings
neuroborus Aug 1, 2026
cab6d65
chore(deps): refresh Dart and Rust lockfiles
neuroborus Aug 1, 2026
cd8989e
test(phone): cross-check connection storage keys against the Rust table
neuroborus Aug 1, 2026
d842025
chore(repo): ignore the local artifacts directory
neuroborus Aug 1, 2026
b54f3d5
fix(core): rank provider status once and break severity ties
neuroborus Aug 1, 2026
9858835
chore(docs): upgrade Vocs toolchain and clear npm advisories
neuroborus Aug 1, 2026
49a6025
fix(phone): rank provider status like the core does
neuroborus Aug 1, 2026
d17fda5
fix(core): canonicalize UTC timestamps to whole seconds
neuroborus Aug 1, 2026
f641dfc
fix(core): keep mixed-currency budgets unknown
neuroborus Aug 1, 2026
6fa96a6
refactor(core): return one alert at most from budget rules
neuroborus Aug 1, 2026
8f32412
chore(core): record why linear projection has no caller yet
neuroborus Aug 1, 2026
af0f0a8
fix(core): return result envelopes from every FFI entry point
neuroborus Aug 1, 2026
ac39df6
feat(core): key alert thresholds by connection, not provider family
neuroborus Aug 1, 2026
c8b429a
chore(agents): require an emulator check when a change can only fail …
neuroborus Aug 1, 2026
ff56565
refactor(phone): share one connected-account dialog across providers
neuroborus Aug 1, 2026
8d8e97d
refactor(phone): move alert threshold dialogs out of the screen
neuroborus Aug 1, 2026
7a5d8e7
refactor(phone): move shared provider dialogs into their own files
neuroborus Aug 1, 2026
73112f8
refactor(phone): give the Cursor plan row its own widget
neuroborus Aug 1, 2026
56d5c59
refactor(phone): finish publishing the shared provider dialogs
neuroborus Aug 1, 2026
c6c8598
refactor(phone): give the Codex and Claude rows their own widgets
neuroborus Aug 1, 2026
1cd0c57
feat(core): scope budget alerts to a connection and its local limit
neuroborus Aug 1, 2026
3877754
refactor(phone): let widget sync failures reach their caller
neuroborus Aug 1, 2026
d29480b
docs(core): record why DateTimeUtc does not reject malformed instants
neuroborus Aug 1, 2026
0119b01
fix(wear): rank stale and mock data above a rate limit in Glance detail
neuroborus Aug 2, 2026
91bb59f
fix(core): stop repeating the provider name in pool alert messages
neuroborus Aug 2, 2026
25baabe
fix(phone): qualify catalog metric names by provider family
neuroborus Aug 2, 2026
e7b7f80
fix(wear): stop repeating a family the pool name already carries
neuroborus Aug 2, 2026
29e090c
refactor(core): compare Anthropic bucket bounds as canonical instants
neuroborus Aug 2, 2026
d30a149
feat(core): raise a card to Warning when its own threshold is crossed
neuroborus Aug 2, 2026
a533562
fix(wear): mark mock data in the status complication
neuroborus Aug 2, 2026
5c4ef23
style(wear): use KDoc for the mock-data test comment
neuroborus Aug 3, 2026
68d802c
feat(watchface): widen the ring band and re-space the face
neuroborus Aug 7, 2026
9857cee
feat(core): give demo budgets a stand-in limit so rings can fill
neuroborus Aug 7, 2026
2ad0fcc
fix(core): keep invented demo budget ceilings short of exhaustion
neuroborus Aug 8, 2026
30cc12b
chore(agents): expose repository skills to Claude Code via symlink
neuroborus Aug 8, 2026
b4cbdac
chore(docs): make budget rings per connection
neuroborus Aug 8, 2026
5082608
chore(wear): recolor the Glance budget row by family
neuroborus Aug 8, 2026
29a950f
chore(agents): map the repository for coding agents
neuroborus Aug 8, 2026
aba13b2
feat(phone): give budget limits their own Providers entry
neuroborus Aug 8, 2026
e60c4ea
chore(wear): map Anthropic connection ids to the Claude family
neuroborus Aug 8, 2026
f8da518
feat(phone): key watch budget rings by connection
neuroborus Aug 9, 2026
2e982bf
test(wear): lock the phone-composed budget ring label on Glance
neuroborus Aug 9, 2026
d54d345
fix(core): drop the aggregate budget percentage
neuroborus Aug 9, 2026
4e13194
fix(design): carry the measured strip stack into review art
neuroborus Aug 9, 2026
fbbfd11
feat(sync): put budget money on the face strip
neuroborus Aug 9, 2026
732e00d
chore(design): show the budget money strip in review art
neuroborus Aug 9, 2026
3bd9e9e
fix(design): stop guessing font metrics in glance review art
neuroborus Aug 9, 2026
e5a911f
fix(phone): count used metric slots instead of naming the cap
neuroborus Aug 10, 2026
2cfd283
ci(core): gate rustdoc so broken intra-doc links fail
neuroborus Aug 10, 2026
b8a2836
chore(watchface): generate watchface.xml from a script
neuroborus Aug 10, 2026
814141c
feat(watchface): repeat the budget period around its ring band
neuroborus Aug 11, 2026
82053af
fix(watchface): bake the ring type into four dimmed sweeps
neuroborus Aug 11, 2026
df548a0
docs(wear): record how to enter ambient from adb
neuroborus Aug 12, 2026
0fef914
chore(docs): bring the Phase 13 log up to the face revisions
neuroborus Aug 12, 2026
d1604e8
chore(design): show ring type on an ambient board
neuroborus Aug 12, 2026
6848ca5
chore(repo): ignore the local pre-push script
neuroborus Aug 12, 2026
eddfa87
chore(docs): plan adaptive provider UI, polling, and watch rings
neuroborus Aug 12, 2026
1fe3b00
chore(docs): lock a status language for the phone dashboard
neuroborus Aug 13, 2026
102e1d5
fix(phone): stop marking a healthy allowance
neuroborus Aug 13, 2026
86278b2
feat(phone): summarize a family's status in its header
neuroborus Aug 13, 2026
37fc78e
feat(phone): make the app-bar status jump to the first problem
neuroborus Aug 13, 2026
280c9a8
test(phone): pin what the app-bar count counts
neuroborus Aug 13, 2026
1b2947c
docs(watchface): split a Cursor plan's two pools across one band
neuroborus Aug 13, 2026
7569476
fix(docs): count six ring slots for a split band, not four
neuroborus Aug 13, 2026
1e791f2
chore(design): repaint the Cursor pools and the unresolved fallback
neuroborus Aug 13, 2026
25630a1
docs(watchface): measure the halves of a split band on the watch
neuroborus Aug 13, 2026
2ec4f5f
feat(sync): carry both Cursor pools in one ring slot
neuroborus Aug 14, 2026
bed9dd0
feat(phone): charge a Cursor pair one watch ring slot
neuroborus Aug 14, 2026
d09211d
feat(wear): publish a paired ring into two complication slots
neuroborus Aug 14, 2026
0a10f3f
docs(watchface): record the eight-slot ceiling and the shared outer slot
neuroborus Aug 15, 2026
b880198
fix(wear): keep both pools of a paired band on Glance
neuroborus Aug 15, 2026
b4b6d40
test(phone): pin the wire shape of a paired ring
neuroborus Aug 15, 2026
f62fda2
chore(design): paint the Cursor pools on the review boards
neuroborus Aug 16, 2026
a1ba528
docs(watchface): fix the palette distances and where demo blue lives
neuroborus Aug 16, 2026
f8250e4
test(wear): keep a pair's rows together on Glance
neuroborus Aug 16, 2026
a05d5d7
fix(phone): count a paired band once in the payload preview
neuroborus Aug 16, 2026
6f2be65
refactor(wear): serve the outer half from one data source
neuroborus Aug 16, 2026
3073f02
feat(watchface): split a paired band
neuroborus Aug 16, 2026
bd3bbcf
feat(watchface): mark the far end of a split strip
neuroborus Aug 16, 2026
4a9ec22
chore(design): show a split pair on a review board
neuroborus Aug 16, 2026
a4c0bf7
fix(phone): pick the Cursor pools one by one
neuroborus Aug 16, 2026
df3cdae
fix(watchface): let the melt end where the value does
neuroborus Aug 16, 2026
91ba8e1
fix(wear): let a Glance row end where its value does
neuroborus Aug 16, 2026
700583d
fix(phone): count ring slots by band, not by row
neuroborus Aug 16, 2026
3099935
feat(phone): pull a tab to refresh it
neuroborus Aug 16, 2026
7569636
feat(phone): group the metric pickers by connection
neuroborus Aug 16, 2026
5a38bbe
chore(docs): decide how the phone tabs declare their order
neuroborus Aug 16, 2026
e8f53c9
feat(phone): declare how a surface orders its rows
neuroborus Aug 16, 2026
63e17fc
feat(phone): order the dashboard by what needs action
neuroborus Aug 16, 2026
efdab76
feat(phone): order the providers tab by connection state
neuroborus Aug 16, 2026
39e3e25
feat(phone): order picker connections by what is selectable
neuroborus Aug 16, 2026
5d7751f
chore(docs): mark Phase 16 delivered
neuroborus Aug 16, 2026
3520518
feat(core): detect a plan window coming back
neuroborus Aug 17, 2026
305be08
chore(docs): decide how a plan recovery reaches the user
neuroborus Aug 17, 2026
e4cc832
feat(core): remember which plan windows were exhausted
neuroborus Aug 17, 2026
69e2824
feat(phone): route plan recoveries to a notifier
neuroborus Aug 17, 2026
cd8fe0d
feat(phone): notify when a plan window comes back
neuroborus Aug 17, 2026
de9483b
feat(phone): book a wake for a spent window's reset
neuroborus Aug 17, 2026
574fc16
feat(phone): keep a recovery notification to family and window
neuroborus Aug 17, 2026
d0163fa
feat(phone): switch recovery notifications in Settings
neuroborus Aug 17, 2026
4a32c8f
chore(docs): close Phase 15 with the watch delivery unverified
neuroborus Aug 17, 2026
2e8e3a4
fix(wear): give the Glance rows their art spacing back
neuroborus Aug 17, 2026
44ee6d1
chore(docs): bring Phase 13 status up to what landed
neuroborus Aug 17, 2026
5b16242
fix(design): keep a paired band together on the Glance boards
neuroborus Aug 17, 2026
d9910c7
chore(docs): mark Phase 13 delivered
neuroborus Aug 17, 2026
ddc093f
feat(watchface): close a ring that has spent nothing
neuroborus Aug 17, 2026
3734cf4
chore(docs): decide what the watch shows besides the Glance
neuroborus Aug 17, 2026
18232ec
feat(wear): show when each plan window comes back
neuroborus Aug 17, 2026
9ae7000
feat(wear): open the plan windows screen from the watch menu
neuroborus Aug 17, 2026
fe473d1
feat(wear): drop the screens the Glance replaced
neuroborus Aug 17, 2026
bff50d9
chore(docs): record the phase that replaced the watch menu
neuroborus Aug 18, 2026
8a4076a
refactor(wear): remove the ring canvas the Glance replaced
neuroborus Aug 18, 2026
f2d9b25
refactor(phone): give the status pill its own file
neuroborus Aug 18, 2026
09b73f8
feat(agents): check fixtures against their schemas
neuroborus Aug 18, 2026
e46901a
feat(agents): check the payload version against the contract
neuroborus Aug 18, 2026
08afdbf
feat(design): fail when review art drifts from its generator
neuroborus Aug 18, 2026
558604d
ci(design): run the review-art check on pull requests
neuroborus Aug 18, 2026
1900d43
refactor(phone): remove the display preference nothing could set
neuroborus Aug 18, 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
98 changes: 73 additions & 25 deletions .agents/skills/android-surfaces/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,33 @@ Use this skill for `apps/phone_flutter/`, `apps/wear_android/`, and `apps/watchf
## Phone App

- Own provider setup, credential entry, secure storage integration, platform transport, sync scheduling, diagnostics export, and Wear Data Layer send.
- **Dashboard status marks** follow `docs/product/PHONE_DASHBOARD_DESIGN.md` (**rules locked
2026-08-13**): `Ok` draws nothing; a rollup counts the problems rendered below it and shows a
count where a leaf shows a glyph; family color stays
identity; rank from `ProviderStatus::severity`, never a new list.
- Consume dashboard snapshots from Rust.
- Show today, week, month, provider list, provider details, charts, budgets, credits, sync status, and settings.
- Show today, week, month, provider plaques on Dashboard, charts, budgets, credits, sync
status, Providers connection hub, and Settings.
- Phase 14 primary tabs: Dashboard → Watchface → Widget → Providers → Settings. Watchface owns
Wear/WFF ring-slot prefs; Widget owns phone home-widget prefs. Do not keep those controls in
Settings once the tabs land (Settings “Watch display” is transitional only).
- Do not add a phone Alerts tab. Active alerts render on the Dashboard; alert rules and budget
thresholds belong in Settings on connection rows / a global budget card (editable even when
Not connected). Providers consumes status; it does not own rule creation.
Wear/WFF ring-slot prefs; Widget owns phone home-widget prefs and the Android App Widget
(`PHONE_WIDGET_DESIGN.md` locked). Phone widget **hugs** complete rows/columns inside
the resize cell (olive `wrap_content`; hide trailing rows and right-hand columns that
would clip — credits → label → `%`; unused cell area is wallpaper until the user shrinks
the span), vertical+horizontal resize (default ~3 cells), day/night chrome, overlay face
watermark (no header band), expands Claude windows, keeps exhausted plan pools as
`0% left`, and appends per-provider purchased credits end-aligned (label centered).
Watchface still collapses Claude / omits exhausted. Do not keep
those controls in Settings.
- **Providers** owns the full connection catalog (Connected / Not connected), credentials /
auth, and **all user-configured** alert thresholds (plan/purchased per connection;
Today/Week/Month budgets on OpenAI Platform). One threshold per meter; UI speaks
**% left** (storage/Rust keep used% as `at`). Editable before first sync. Do not keep
alert rules under Settings.
- **Settings** is systemic only: global polling interval, diagnostics, data deletion,
legal, debug toggles — not connections, credentials, display surface toggles, or alerts.
- Do not add a phone Alerts tab. Active alerts render on the Dashboard. Thresholds are
**user-configured only** — never invent alerts from hard-coded 80%/100% utilization cutoffs.
Provider status chrome (Warning / RateLimited) is separate from the alerts list.
- Keep analytics UI dense, clear, and operational rather than marketing-like.

## Wear OS App
Expand All @@ -41,41 +60,70 @@ Use this skill for `apps/phone_flutter/`, `apps/wear_android/`, and `apps/watchf
a local cooldown. Review art: `preview-glance-legend-*.png`. Compose: `GlanceLegendPage`
(+ watch→phone refresh message).
- **Watch face / WFF** (and any face-like complication preview) follow
`docs/product/WATCH_RING_DESIGN.md` (**locked 2026-07-25**): **inner/center = tightest**
`docs/product/WATCH_RING_DESIGN.md` (**locked 2026-07-25**; later revisions are listed in the
document header): **inner/center = tightest**
remaining (plan `%` primary; credit request-runway secondary from internal costs — never show
request counts); arc = remaining; large time hero; sunk family strips (equal width; first strip
nearest center; stroke tuned so 3 strips clear arcs); family colors. Future multi-profile /
hatch / three-ring cap notes there are planning-only until a later phase.
request counts); arc = remaining (clockwise melt from 12); large time hero; sunk family strips
(equal width; first strip nearest center; stroke tuned so 3 strips clear arcs); family colors.
Hard cap: **three** concentric plan/budget rings (`watchRingSlotCount = 3`). Purchased meters
(Extra usage, on-demand, Codex credits) are not ring candidates — phone cards only; alerts
only via user thresholds on Providers.
Future multi-profile / hatch notes there are planning-only until a later phase.
- Claude plan windows (`5h` / weekly / Opus / Sonnet) collapse to one watch ring on the phone
(`allowance.claude.plan`); Glance shows the active window label. Phone dashboard still lists all.
- Prefer concentric percent layers from schema v4+ on the **face** (up to four selected metrics
today); never invent `Unknown` filler. Unselected, unavailable, or exhausted (`>= 100%`)
- Prefer concentric percent layers from schema v4+ on the **face** (up to three selected metrics);
never invent `Unknown` filler. Unselected, unavailable, or exhausted (`>= 100%`)
layers do not render.
- Face center (first) strip / `creditsGlance` may show a compact remaining-credits aggregate when
reported and Settings shows purchased usage (not a ring; never LLM `TOK`). App Glance credits
stay **per provider** with an explicit `credits` label.
- Keep today, week, usage, providers, alerts (active list only — no rule editing), and last sync
as secondary detail screens.
- Face strips show compact remaining credits per provider family from `allowances` (`% · 500`),
same matching rule as Glance; accents use the family ColorRamp. `creditsGlance` is for
credits-only faces when there are no plan rings.
- Beside the Glance the app keeps exactly two surfaces: a **plan windows** page saying when each
window comes back (rules in `apps/wear_android/README.md`), and **Alerts** from the Glance pill
(active list only — no rule editing). The today, week, usage, providers, and last sync screens
were removed in Phase 17; do not bring a currency total back to the watch.
- Store and render the latest successful watch summary.
- Make stale data explicit.
- Do not enter, display, or store provider credentials.
- Keep screens glanceable; avoid long tables and cheap floating caption stacks.

## Watch Face Format

- `watchface.xml` is written by `tools/render-watchface.mjs` (`just render-watchface`), together
with the ring-type drawables it names (`res/drawable-nodpi/`, ImageMagick as `just
export-icons` already uses). Change the generator, never the XML or the PNGs;
`just check-watchface` fails on drift. Geometry and canvas are both the **450**-unit design
language `WATCH_RING_DESIGN.md` locks — the canvas is what WFF renders at before scaling to
the screen, so raising it softens every edge on the face. Do not raise it to sharpen type:
WFF rounds each glyph of a `TextCircular` to a whole canvas unit, and the answer to that is
baking, not resolution.
- Keep WFF declarative and minimal; follow the same concentric language as Wear (not
side-by-side `RING 1` / `RING 2` placeholders).
- **A scene holds at most eight `ComplicationSlot` elements** — the same in format versions 2, 4
and 5, so raising the version buys nothing. This face has used all eight since 2026-07-25
(four rings, four strips; the fourth of each was undrawn until the split-band revision took
the fourth ring). Count before designing anything that needs a new slot — the validator never
says "too many slots": a ninth is reported as `Invalid content was found starting with element
'ComplicationSlot'`, listing `PartText`, `PartImage`, `Condition` and friends as what it
expected instead.
- WFF format version 2 (Wear OS 5+): concentric `RANGED_VALUE` arcs with `WeightedStroke`
colors from Wear `ColorRamp` / `[COMPLICATION.RANGED_VALUE_COLORS]`; arc = remaining.
Keep track/progress `endAngle` under 360° (scale onto 359.9°) — a closed circle collapses
to a ROUND tip at 12 o'clock. Prefer simple Transform arithmetic over `clamp()`.
Keep track spans under 360° (scale onto 359.9°) — a closed circle collapses
to a ROUND tip. Remaining melts **clockwise from 12**: Transform `startAngle`
with `(1 - value/max) * 359.9` and fixed `endAngle` 359.9. Prefer simple
Transform arithmetic over `clamp()`.
`BoundingArc` clips ring-slot content to the arc band — sunk `%` / credits strips must use
separate `BoundingBox` SHORT_TEXT slots of **equal width**, stacked inside the clear aperture.
Center (first) strip TEXT = full label (`100% · 500`); lower strips TEXT = remaining digits
with Template `%%`. Avoid `length(TITLE)` Conditions — they are unreliable on WFF. Draw strips
after `DigitalClock` so the clock does not cover them.
- Prefer live arcs for selected layers, optional `creditsGlance` on the center strip when present,
large time. Never LLM `TOK` on the face.
separate `BoundingBox` `RANGED_VALUE` slots of **equal width**, stacked inside the clear
aperture. Every strip TEXT = full label (`46%` or `100% · 500`); accents use
`[COMPLICATION.RANGED_VALUE_COLORS]`. Avoid `length(TITLE)` Conditions — they are unreliable
on WFF. Draw strips after `DigitalClock` so the clock does not cover them.
- A ring slot's TITLE carries the budget period (`D` / `7D` / `M`, empty for plan rings), and the
face repeats it around that band as background-colour cut-out type, branching on
`[COMPLICATION.TITLE]` equality to pick one baked image per ring and period. The type runs in
four sweeps with a bare break on each diagonal, aligned across rings; within a sweep tokens are
placed by rotation, and the repeat counts in `tools/render-watchface.mjs` are the fitted
constants both that generator and the review-art one read — copy, never hand-tune.
- Prefer live arcs for selected layers, optional `creditsGlance` on the **matching provider**
strip when present, large time. Never LLM `TOK` on the face.
- Support tap-to-open into the Wear OS app where possible.
- Keep ambient mode readable (dim arcs, strips off).

Expand Down
52 changes: 43 additions & 9 deletions .agents/skills/finalization/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ description: Post-change finalization checklist for WardPulse (Rust core, Flutte
>
> Run this after implementation or documentation work to keep the monorepo coherent.
>
> Last Updated: 2026-07-24
> Last Updated: 2026-08-01

## 1. Scope Review

Expand Down Expand Up @@ -44,25 +44,58 @@ description: Post-change finalization checklist for WardPulse (Rust core, Flutte
```bash
cd core && cargo fmt --all -- --check
cd core && cargo clippy --workspace --all-targets -- -D warnings
cd core && RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --document-private-items
cd core && cargo test --workspace
```

- [ ] If Flutter code changed and the project is generated, run:

```bash
cd apps/phone_flutter && flutter analyze && flutter test
just check-phone
```

This runs `dart format --set-exit-if-changed` over `apps/phone_flutter` and `bindings/dart`,
then `flutter analyze` and `flutter test`. Use `just fmt-phone` to apply formatting.

- [ ] If Android/Wear/WFF Gradle projects are generated, run the relevant Gradle test/build task.
- [ ] If schemas or fixtures changed, validate fixture shape manually or with `tools/validate-fixtures/` when available.
- [ ] If schemas or fixtures changed, run `just validate-fixtures`: it parses every file and checks each golden fixture against its schema.
- [ ] If GitHub Actions workflows changed, run `actionlint .github/workflows/*.yml`.
- [ ] If durable docs, Vocs pages, component READMEs, or site navigation changed, run:

```bash
just check-docs
```

## 5. Documentation Review
## 5. Emulator Check

Automated gates do not exercise the app. Unit tests use fakes, so they never load
`libward_pulse_ffi.so`, never resolve an exported symbol, and never render a screen.

Run the app on an emulator when the change could only fail there:

- [ ] FFI surface changed — exported symbol names, argument or return shapes, or the
Dart binding. Symbols resolve at **run time**, so a mismatch survives every build.
- [ ] The serialized snapshot contract changed — a new or renamed field, or a different
value shape. Host-side round trips do not prove the Android build agrees.
- [ ] UI, widget, watch face, or phone-to-watch payload behaviour changed.
- [ ] Background scheduling, secure storage, or WebView sign-in changed.

Skip it, and say so, for changes that cannot reach a device: pure refactors behind an
unchanged contract, comments and docs, tests, CI wiring, lockfiles.

```bash
adb devices -l # phone and Wear AVDs are usually already running
tools/build-android-rust/build.sh # Rust .so for Android ABIs — slow
cd apps/phone_flutter && flutter build apk --debug
adb -s <serial> install -r build/app/outputs/flutter-apk/app-debug.apk
adb -s <serial> shell am start -n app.wardpulse/.MainActivity
adb -s <serial> logcat -d | grep -i wardpulse
```

Report what was exercised on device and what was not. "Tests pass" is not evidence that
the app runs; only the device is.

## 6. Documentation Review

- [ ] `docs/README.md` links any new durable document.
- [ ] The Vocs page and `docs/site/vocs.config.ts` navigation are updated for documentation exposed on the site.
Expand All @@ -71,7 +104,7 @@ just check-docs
- [ ] Product, provider, release, or security docs are updated when behavior or boundaries changed.
- [ ] Placeholder TODOs are acceptable only for intentionally deferred platform generation work.

## 6. Staging And Commit Boundary
## 7. Staging And Commit Boundary

**Finalization never creates a git commit.**

Expand All @@ -85,7 +118,7 @@ this change set (`git add` the relevant paths). Stop there.
- Draft the commit message for the staged set; leave the actual commit to the user or to a
later explicit request.

## 7. Commit Message Draft
## 8. Commit Message Draft

Draft commit messages in Conventional Commits form:

Expand Down Expand Up @@ -148,15 +181,16 @@ chore(docs): plan adaptive provider UI, polling, and watch rings
Record the dual plan/platform model and phases 9–13, set the global refresh floor to 5–60 minutes, and make finalization stage-only with an explicit no-commit rule.
```

## 8. Handoff Summary
## 9. Handoff Summary

When finalization completes, report:

1. Relevant checklist sections completed or skipped with reason.
1. Relevant checklist sections completed or skipped with reason, including whether the
change was exercised on an emulator.
2. Commands run and pass/fail status.
3. Important files changed.
4. Remaining risks or intentionally deferred work.
5. Git staging status after staging: what is staged, what remains unstaged or untracked.
6. Idiomatic draft commit message for the staged set, using section 7.
6. Idiomatic draft commit message for the staged set, using section 8.

Remind the user that the changes are staged only and that no commit was created.
6 changes: 6 additions & 0 deletions .agents/skills/rust-core/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ Use this skill for work under `core/` and Rust-owned contracts.

- Provider-specific raw data enters the core; normalized dashboard snapshots leave the core.
- Rust owns budgets, credits, projections, alerts, model breakdowns, status mapping, and watch summary derivation.
- Alert evaluation uses **user-configured** rules/thresholds from platform preferences
(`calculate_alerts(snapshot, settings)`). Each rule is a single used-% threshold (`at`).
Providers owns all alert rules (plan/purchased + OpenAI Platform budgets). Do not invent
dashboard alerts from allowance or budget `ProviderStatus` alone; status chrome and the
alerts list are separate.
- Platform code owns HTTP clients, TLS, retries, background execution, secure credential retrieval, encrypted storage, and UI state.
- Prefer deterministic functions that are easy to fixture-test.
- Preserve previous successful snapshot semantics when modeling sync failure states.
Expand All @@ -40,6 +45,7 @@ After Rust changes, run:
```bash
cd core && cargo fmt --all -- --check
cd core && cargo clippy --workspace --all-targets -- -D warnings
cd core && RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps --document-private-items
cd core && cargo test --workspace
```

Expand Down
1 change: 1 addition & 0 deletions .claude/skills
5 changes: 4 additions & 1 deletion .github/workflows/core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@ jobs:
with:
toolchain: ${{ matrix.rust }}
components: rustfmt, clippy
- name: Validate JSON syntax
- name: Validate shared contracts
working-directory: .
run: python3 tools/validate-fixtures/validate_json.py
- run: cargo fmt --all -- --check
- run: cargo clippy --workspace --all-targets -- -D warnings
- run: cargo doc --workspace --no-deps --document-private-items
env:
RUSTDOCFLAGS: -D warnings
- run: cargo test --workspace
36 changes: 36 additions & 0 deletions .github/workflows/designs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Review Art

on:
pull_request:
branches: [main]
paths:
- "tools/render-watch-ring-designs.mjs"
- "tools/render-wear-glance-designs.mjs"
- "tools/render-phone-widget-designs.mjs"
- "tools/render-brand-icons.mjs"
- "tools/design-output.mjs"
- "apps/*/design/**"
- "brand/icons/**"
- ".github/workflows/designs.yml"

permissions:
contents: read

jobs:
designs:
name: Check against generators
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
- name: Install Noto and Pillow
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends fonts-noto-core python3-pil
- run: node tools/render-watch-ring-designs.mjs --check
- run: node tools/render-wear-glance-designs.mjs --check
- run: node tools/render-phone-widget-designs.mjs --check
- run: node tools/render-brand-icons.mjs --check
6 changes: 6 additions & 0 deletions .github/workflows/phone-android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,13 @@ jobs:
targets: aarch64-linux-android,x86_64-linux-android
- run: cargo install cargo-ndk --version 4.1.2 --locked
working-directory: .
- name: Validate shared contracts
run: python3 tools/validate-fixtures/validate_json.py
working-directory: .
- run: flutter pub get --enforce-lockfile
- run: dart format --output=none --set-exit-if-changed lib test
- run: dart format --output=none --set-exit-if-changed lib
working-directory: bindings/dart
- run: flutter analyze
- run: flutter test
- run: tools/build-android-rust/build.sh
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/watchface.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ on:
branches: [main]
paths:
- "apps/watchface_wff/**"
- "tools/render-watchface.mjs"
- "tools/design-output.mjs"
- "tools/validate-watchface/**"
- ".github/workflows/watchface.yml"

Expand All @@ -21,6 +23,9 @@ jobs:
working-directory: apps/watchface_wff
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
- uses: actions/setup-java@v5
with:
distribution: temurin
Expand All @@ -32,4 +37,6 @@ jobs:
packages: "platforms;android-37.1 build-tools;36.0.0"
- run: tools/validate-watchface/validate.sh
working-directory: .
- run: node tools/render-watchface.mjs --check
working-directory: .
- run: ./gradlew --no-daemon lintDebug assembleDebug bundleDebug
3 changes: 3 additions & 0 deletions .github/workflows/wear-android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,7 @@ jobs:
uses: android-actions/setup-android@v4
with:
packages: "platforms;android-37.1 build-tools;36.0.0"
- name: Validate shared contracts
run: python3 tools/validate-fixtures/validate_json.py
working-directory: .
- run: ./gradlew --no-daemon lintDebug testDebugUnitTest assembleDebug assembleDebugAndroidTest
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
!.env.example
*.local
*.log
LOCAL_ARTIFACTS/
/prepush.sh

# Rust
target/
Expand All @@ -28,6 +30,8 @@ docs/site/.vocs/
.packages
build/
coverage/
# Library package — consumers resolve deps; app lock lives under apps/phone_flutter/.
bindings/dart/pubspec.lock

# Android / Gradle
.gradle/
Expand Down
Loading
Loading