Repository navigation
fix(wallet): define the week trend bucket once, Monday-start and inclusive - #215
Merged
Merged
Conversation
…usive
The transactions trend chart's `week` bucket was defined twice, and the two
definitions disagreed:
- backend: `strftime('%Y-%m-%d', time, 'weekday 1', '-7 days')`
- front-end axis (`ui/js/app.js::txTrendDays`): floor 「now」 to the UTC day and
step back 7 days at a time
SQLite's `weekday N` only advances *forward* to the next weekday N and is a
no-op when the date already is N, so the trailing `-7 days` pushed a **Monday**
row into the *previous* week (other weekdays happened to land in the right
week). The front-end axis, meanwhile, kept the weekday of 「now」. The two only
coincided when 「now」 was a Monday, so on the two reachable paths that ask for
`bucket=week` (range 「all」, or a custom range wider than 60 days) the weekly
chart zero-filled every bar except on Mondays: measured through the real
router, one row per weekday of 2026-09-07..2026-09-13 produced backend buckets
`2026-08-31` (1.0) and `2026-09-07` (27.0), while the real `txTrendDays` built
the axis `[2026-08-30, 2026-09-06, 2026-09-13]` and matched **0 of 28** points.
Both layers documented the invariant they broke: `wallet.rs` said
`// 配 modifier:周一起始` and `app.js` said `(UTC 对齐到桶,与后端 strftime 口径一致)`.
Fix: one definition — Monday-start, inclusive — used by both layers.
- backend: `'weekday 0', '-6 days'` (verified against sqlite3 for all seven
weekdays, including the Monday and Sunday boundaries);
- front-end: the `week` axis is floored to the same Monday
(`Date.UTC(y, m, d - ((getUTCDay() + 6) % 7))`); `hour`/`day` are untouched.
Tests / evidence:
- new `transactions_trend_week_bucket_starts_on_monday` drives the real handler
over one calendar week (one row per weekday) and asserts a single bucket whose
start is that week's Monday, holding all 7 rows (count 7, expense 28). It is
**red on the pre-change tree** (2 buckets, the Monday row a week earlier) and
the only failure when just the modifier is reverted.
- the real `txTrendDays` (extracted from `ui/js/app.js`, not re-typed) matches
the backend axis on **7/7** weekdays after the fix, vs **1/7** before; the
`day`/`hour` axes are byte-identical before and after.
Scope: display / API aggregation only — no ledger, balance or settlement change.
`ui/index.html` cache-bust bumped.
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
The transactions trend chart's
weekbucket was defined twice, and the two definitions disagreed — so the weekly chart zero-filled its bars on 6 of 7 days while the summary cards above it showed real numbers.GET /api/transactions/trend?bucket=week)strftime('%Y-%m-%d', time, 'weekday 1', '-7 days')ui/js/app.js::txTrendDays)SQLite's
weekday Nonly advances forward to the next weekday N and is a no-op when the date already is N, so the trailing-7 dayspushed a Monday row into the previous week (the other weekdays happened to land in the right week). The front-end axis, meanwhile, kept the weekday of 「now」. The two could only coincide when 「now」 was a Monday.Measured through the real router with one row per weekday of the calendar week 2026-09-07 (Mon) .. 2026-09-13 (Sun), pts 1..7:
bucket=week)2026-08-31= 1.0 and2026-09-07= 27.0 (the Monday row a week early)txTrendDaysaxis keys (clock = Sunday)[2026-08-30, 2026-09-06, 2026-09-13]Both layers documented the invariant their own code broke:
wallet.rssaid// 配 modifier:周一起始andapp.jssaid(UTC 对齐到桶,与后端 strftime 口径一致). Theweekbucket is reachable from the shipped UI on two paths — the 「all」 range and any custom range wider than 60 days — and neither had test coverage.Impact is display / API aggregation, not money: the ledger, balances, settlement and the transaction list are untouched; only this aggregation endpoint and the axis drawn from it.
Changes
src/routes/wallet.rs): theweekmodifier is now'weekday 0', '-6 days'— Monday-start and inclusive — verified againstsqlite3for all seven weekdays plus the00:00:00/23:59:59boundaries. The comment is corrected to周一起始(含周一当日), with a note that the front-end axis uses the same definition.ui/js/app.js):txTrendDaysfloors the week axis to the same Monday (Date.UTC(y, m, d - ((d.getUTCDay() + 6) % 7))). Thehouranddaybranches are untouched, so their axes are byte-identical before and after.ui/index.html:js/app.js?v=20260913-3→20260914-1(cache-bust convention).config/config.example.tomlneeds no update. No schema, no i18n key, no new dependency.Related Issue
None — the problem was found by a runtime probe, not an issue report.
Tests
cargo test— 217 passed (was 216; +1 new test), 0 failedcargo fmt --check— clean;cargo clippy --all-targets -- -D warnings— cleanNew test
routes::wallet::tests::transactions_trend_week_bucket_starts_on_mondaydrives the real handler over one calendar week (one row per weekday, 2026-09-07 .. 2026-09-13) and asserts the result is a single bucket starting on that week's Monday, holding all 7 rows (count = 7,expense = 28).216 passed / 1 failed), so the assertion discriminates exactly this defect. The existingtransactions_trend_buckets(day / hour / illegal-value fallback) stays green.Front-end evidence (no JS test runner is introduced — the probe over the real source is the evidence, same ruling as earlier UI cycles):
txTrendDaysis extracted from the realui/js/app.jsby brace matching (the source text, not a re-typed copy) and run with the clock frozen at each of the 7 weekdays. It matches the backend axis on 7/7 weekdays after the fix, vs 1/7 before.dayandhouraxes are byte-identical pre/post, so the change is scoped toweek.Checklist
fix/)fix(wallet): …)day/hourbuckets, the 「only non-empty buckets are returned」 contract (the front-end zero-fills), the trend endpoint's filter / summary semantics, and the dashboard sparkline (day-only, already aligned)