Repository navigation
feat: add a usage recap for any week, month or year - #332
Conversation
1eb685c to
b0b6021
Compare
chattymin
left a comment
There was a problem hiding this comment.
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.
b0b6021 to
3d913bd
Compare
|
Done, thanks for the clear pointer to the policy. The in-app recap stays; Copy image / Save…, Rebased on main as well (one trivial conflict next to the multi-account settings in |
|
Thank you for your contribution! |
- 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>
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.
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).
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 inUserDefaults(UsageLedger):"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:
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
UI changes
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):
The card is drawn with explicit colors, so it looks the same in light and dark mode.
Checklist
swift buildandswift testpass locallyscripts/test-gate.sh: 1289 tests, 12 skipped, 0 failures, logic-core line coverage 94.20% (UsageRecap.swiftadded 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 withllvm-cov --show-regionsrather than the coverage number; the remaining^0regions 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, beforemonthDailyexists, 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.