Conversation
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.
DyRize
force-pushed
the
feat/usage-recap
branch
from
September 21, 2026 15:40
1eb685c to
b0b6021
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 that copies or saves as a PNG. 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 an exported image 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.
One thing to check with you: the exported PNG includes the sprites shown on the card, as the trainer card in #331 does. CONTRIBUTING rules out features that "redistribute or export copyrighted assets". If that covers a user-made image of their own stats, I can make the export sprite-free and keep the sprites on screen only.
This branch is independent of #331. Both add a bullet to the same README list and strings to
Localization.swift, so whichever merges second needs a trivial rebase — I'll take care of it.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, a note on how the chip compares while a period is running, Copy image and Save….Screenshots (English, rendered from this branch):
The card is drawn with explicit colors, so the exported image looks the same in light and dark mode.
Checklist
swift buildandswift testpass locallyscripts/test-gate.sh: 1097 tests, 11 skipped, 0 failures, logic-core line coverage 92.84% (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.