Skip to content

feat: add a usage recap for any week, month or year - #332

Merged
chattymin merged 3 commits into
chattymin:mainfrom
DyRize:feat/usage-recap
Sep 25, 2026
Merged

chattymin merged 3 commits into
chattymin:mainfrom
DyRize:feat/usage-recap

Conversation

@DyRize

@DyRize DyRize commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds a usage recap: one calendar week, month or year at a time, stepping back through time with Previous / Next, rendered as a small Pokédex-style readout. It reports what already happened: no goal, no reward, nothing to do.

What the card shows: the total, one meter per day (per month in the year view), the change against the previous period, best day, best streak, active days, and the individuals graduated in that period, next to the Pokémon being raised. The title is the period itself (Sep 14 – 20, 2026, September 2026, 2026), so the card says on its own what it covers.

Where the history comes from. Providers only report the current month (monthDaily), so on the 3rd a week would lose four days and a year would be out of reach. Every refresh now copies the daily totals into a small ledger in UserDefaults (UsageLedger):

  • a day never goes down (max-merge), so a rotated or pruned log cannot erase days that really happened;
  • it keeps this year and the last one, the most any view compares against (about 20 KB at most);
  • it is derived data, like the usage cache: not part of the companion save, not transferred, rebuilt by the next refresh.

"No usage" vs "not recorded yet". The ledger also stores the first day it can vouch for. The month series lists every day since the 1st, empty ones included, so the first refresh vouches for the whole month so far. Days before that point, and days still ahead, are drawn as unknown rather than as zeros (dimmer grid, "no data" for VoiceOver), and Previous stops where the history starts.

Comparisons that do not lie:

  • a period still running is compared over the same number of days: on the 18th, September 1–18 against August 1–18, not against the whole of August. A caption under the card says so;
  • a finished period is compared whole;
  • no chip at all unless the ledger covers the previous period from its first day, so a fresh install never claims a jump over days it never saw.

Weeks start on the region's first weekday; periods use a Gregorian calendar whatever the system calendar, so a year runs January to December and day keys match LocalUsageReader.localDayFormatter.

Entry point: a chart button at the end of the "This week / This month" row on Home.

Type of change

  • Bug fix
  • New feature
  • Refactor / cleanup
  • Documentation
  • Other:

UI changes

Before After
Home's week/month row shows the "This week" and "This month" totals and nothing else. A chart button (chart.bar.xaxis) at the end of that row opens the recap inside the popover, like Settings: Back (Esc works too), a Week / Month / Year picker, Previous / Next, the card, a best-day line, and a note on how the chip compares while a period is running. There is no image export.

Screenshots (English, rendered from this branch):

pr332-recap-week pr332-recap-month pr332-recap-year

The card is drawn with explicit colors, so it looks the same in light and dark mode.

Checklist

  • swift build and swift test pass locally
  • PR title and description are written in English
  • UI changes are described above (before/after — images optional)
  • No copyrighted assets, secrets, or private tooling references are committed (see CONTRIBUTING)
  • Tests were added or updated for this change

scripts/test-gate.sh: 1289 tests, 12 skipped, 0 failures, logic-core line coverage 94.20% (UsageRecap.swift added to the logic core, 96.7% of its lines). 19 tests cover the recap: ledger merge, coverage and pruning, junk in hand-edited defaults, the first refresh filling the ledger, retention of last year, week start by region, month and year stepping (31 March back to February), meters per scope, unknown vs zero days, same-day comparison for running periods, whole comparison for finished ones, no comparison over a half-covered period, best streak cut at the period's edges, graduations in the period, where Previous stops, and rendering in all seven languages for every scope. New branches were checked with llvm-cov --show-regions rather than the coverage number; the remaining ^0 regions are API-forced unwraps on Gregorian arithmetic. Every rule above was verified by injecting the defect and watching its test fail.

Two defects found while building this are recorded in docs/reference/defect-log.md: the ledger was first written right after phase 1 of the refresh, before monthDaily exists, which left the first refresh empty; and an early version judged coverage on the newest day of the previous window instead of the oldest, which inflated the delta.

@chattymin chattymin left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove the PNG export functionality before this can be merged. Keep the in-app Usage Recap, but remove Copy Image / Save PNG, RecapExport and its preload/file-writing path, export-only localization strings, and export tests. Exporting a composed image that contains Pokémon sprites conflicts with this repository's copyrighted-asset export policy in CONTRIBUTING.md.

A recap screen opens from the week/month row on Home: one calendar week,
month or year at a time, with Previous and Next to step back through time.
The card shows the total, one meter per day (per month in the year view),
the change against the previous period, best day, best streak, active days
and the individuals graduated in that period. It copies to the clipboard or
saves as a PNG; as with the rest of the app, nothing leaves the Mac.

Providers only report the current month, so every refresh copies the daily
totals into a local ledger in UserDefaults. A day never goes down, which
survives rotated logs, and the ledger keeps this year and the last one so the
year view has something to compare against.

The ledger stores the first day it can vouch for, next to the rows. That is
what tells "no usage" from "not recorded yet": days before it, and days still
ahead, are drawn as unknown instead of as zeros, and a comparison is only shown
when the previous period is covered from its first day. A period still running
is compared over the same number of days, so the 18th of the month does not read
as a drop against a whole month.

The ledger is written after the enrichment phase, where the month series
actually arrives; writing it after phase 1 left the first refresh empty.
The recap was branched before Unown forms landed: the raised Pokémon and the
graduates fell back to the default A sprite, and the strip named them all
"Unown". The export preloads the same form, since ImageRenderer only draws what
is already cached.
Copy image and Save PNG composed Pokémon sprites into an exported image,
which the copyrighted-asset policy in CONTRIBUTING.md rules out. The recap
stays in the app; RecapExport, its sprite preload, the three export strings
and the file-name assertion go.
@DyRize

DyRize commented Sep 24, 2026

Copy link
Copy Markdown
Contributor Author

Done, thanks for the clear pointer to the policy. The in-app recap stays; Copy image / Save…, RecapExport with its sprite preload and file writing, the export-only strings and the file-name assertion are gone, and the README lines no longer mention it. The rendering test still draws the card in every language and scope, since that covers the view itself.

Rebased on main as well (one trivial conflict next to the multi-account settings in UsageStore). scripts/test-gate.sh: 1289 tests, 0 failures, logic-core coverage 94.20%.

@chattymin
chattymin merged commit 78595ed into chattymin:main Sep 25, 2026
2 checks passed
@chattymin

Copy link
Copy Markdown
Owner

Thank you for your contribution!

LuceteYang added a commit to LuceteYang/PikaTokenBar that referenced this pull request Sep 25, 2026
- New usage recap shots (en/ko/ja) rendered from the production RecapCard in
  ScreenshotGenTests, with Gen 1 graduates, plus a tour row in each README (chattymin#332).
- Pace-marker shots pick bar and percent colors with PaceTier like quotaRow does
  (chattymin#349); the pace row explains the six tiers.
- Settings shots gain automatic backup snapshots under Backup & Transfer (chattymin#330)
  and a per-account session key row under Advanced (chattymin#346); footer bumped to 1.9.0.
- "Also in the box" lists Pokedex search/filters (chattymin#329, chattymin#343) and save backups.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@DyRize
DyRize deleted the feat/usage-recap branch September 25, 2026 07:16
donky-ey pushed a commit to donky-ey/PokeDexBar that referenced this pull request Oct 15, 2026
donky-ey pushed a commit to donky-ey/PokeDexBar that referenced this pull request Oct 15, 2026
Also wires the recap's daily ledger into refresh and launch load — the
two chattymin#332 hunks had silently rejected, leaving recordDailyLedger defined
but never called (the recap card would have stayed empty at runtime).
A behavior test now pins refresh → ledger → UserDefaults persistence.
Strongorange added a commit to Strongorange/PokeTokenBar-windows that referenced this pull request Oct 19, 2026
Port macOS chattymin#332 core: UsageLedger keeps day-by-day totals across months
with monotonic-max merges (a rotated log cannot erase a day that really
happened) and covered-since semantics that separate a real zero from an
unrecorded day. RecapPeriod models whole calendar weeks/months/years —
weeks start on Sunday, the same rule as the week totals and the year
heatmap grid, exposed as one named culture so all three switch together.
UsageRecap.Make computes buckets (days, or months in the year view),
totals, the same-days comparison for running periods, best day, streaks,
active/counted days, in-period graduations and the canGoBack bound.
Recap strings ported for the seven app languages (chattymin#367/chattymin#368 included).
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.

2 participants