fix: admin "total balance" card must sum what its caption names - #258
Merged
Merged
Conversation
The employees pane's total-balance card prints the subtitle
`admin.emp.stats.total.sub` ("余额 + 赠送" / "balance + gift") right under
the number, while its value summed `balance` only — so the card
under-reported the spendable total by the whole gift amount and disagreed
with the 可用 column directly below it (`balance + gift_balance`).
Gift points are real, spendable and expiring (`gift.rs`), and the product
defines the available balance as `balance + gift_balance` (`wallet.rs`;
`loadSession` feeds the sidebar/dashboard 「余额」 from `available`), so the
caption's formula is the correct one — the number, not the promise, is what
had to change.
- ui/js/app.js: include `u.gift_balance` in the card's total.
- src/state_gate.rs: new gate
`the_admin_total_balance_card_sums_what_its_caption_names` — three rules
with independent teeth (the card is unique and its value is a plain
identifier; the value's transitive closure must read BOTH `balance` and
`gift_balance` as identifier tokens; both packs' subtitle still names both
components), plus discriminator self-tests for `stat_value_argument` /
`assignment_statement` / `caption_names_both_components` and a control
that rule 2 only bites that one card.
- ui/index.html: cache-bust `app.js?v=20260915-13`.
- ui/README.md: contract note for the card/caption agreement.
A/B (in-place mutations, byte-exact restore, md5 verified) 9/9 legs as
declared: live green; balance-only, captions weakened in both packs, card
removed, helper that drops the gift, axis reversed (gifts only) and the
"delete the promise" competitor all red; equivalent rewrites (separate gift
total, extracted helper, two per-component helpers) green. jsdom probe
`tmp/c2142_probe.js`: 9/9 on the fixed tree, axis legs red on the pre-fix
tree, and it rejects the axis-reversed competitor too.
cargo test 285 -> 286 passed. `cargo fmt --check` clean; clippy unchanged
(only the pre-existing `protocol.rs:662` warning).
Owner
Author
|
Self-review (Committer, self-merge enabled for this task — recorded as a PR comment because GitHub What I verified
Scope Frontend-only. No i18n keys added or removed (the caption already named both components), so the i18n Residual The static gate is necessarily lexical: it proves the value's closure reads both fields, not that the |
argszero
added a commit
that referenced
this pull request
Sep 15, 2026
Ships the 18 PRs merged since v0.7.24 (#242-#259). Schema 14 -> 15 (two covering indexes, applied at startup). No config change, so no deployment-side config.toml edit is needed. Two themes: Perf on the NFS dev database - #259: stop mapping the db (PRAGMA mmap_size 64MB -> 0) and stop a real write per request (dao::touch_api_key gains a 60s guard). Measured on the live dev db: mmap=64MB 1.7-3.1s per COUNT / 250 MiB read vs mmap=0 ~10.5ms / 80 KiB; mmap=0 alone still leaves ~1.2s behind any write, so the pair is required. - #242: codify the two emergency indexes in a v15 migration and gate the conditional joins at the plan level. - #243: read the sharing page's earn total from one batched aggregate. Frontend: display must equal what it filters on, and one fact, one source - #250 one writer for the transaction cache; #251 clear every session slot at the identity boundary and give the wallet view a loader; #253 one shared writer for the wallet/dashboard month-changes; #254 boot loads only the destination view; #255 a model row's identity is the model, not its index; #256 the marketplace source follows the session, not whether data arrived; #257 the sidebar advertises only digits that work; #258 the admin total-balance card sums the gift amount its caption names. i18n - #249 every backend error reaches the wordlist, and the comment stripper stops mangling UTF-8; #252 the backend stops inventing Chinese display labels in response data fields. Forms and robustness - #244 a non-auth boot failure no longer looks like being logged out; #245 a credential 401 is no longer read as a session expiry; #246 wire timestamps reach the renderer unsliced; #247 inline cards submit from every field; #248 a market row's availability label comes from that row. - Cargo.toml / Cargo.lock: 0.7.24 -> 0.7.25. - CHANGELOG.md: v0.7.25 entry. - ui/index.html: cache-bust left as-is; the UI PRs in this release already advanced it past the value deployed with v0.7.24 (app.js 20260915-13, i18n.js 20260915-3). cargo test 288 passed; cargo fmt --check clean; clippy unchanged.
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 employees pane of the admin view renders a total-balance card whose own subtitle is
admin.emp.stats.total.sub—"余额 + 赠送"(zh) /"balance + gift"(en) — printed directlyunder the number. The card's value, however, only summed the permanent balance:
The same pane's table has a
可用column (admin.emp.col.avail, captionadmin.emp.list.sub=「余额 / 赠送 / 可用(永久点数 + 每日赠送)」) computed as
balance + gift_balance. So the cardunder-reported the platform's spendable balance by exactly the total gift amount, and disagreed
with the column right below it.
Which direction is correct is fixed by the product, not by preference — so "weaken the caption so
the two agree" is not a fix:
src/routes/wallet.rsreturnsavailable = balance + gift_balance; gift points are real money —they are spendable and they expire (
src/gift.rsreally moves them out of the account);#side-balance, dashboarddash.balance) is fed fromexactly that field (
loadSession:D.USER.balance = w.available);Repro (admin, empty accounts in one row is enough): open the admin view with a member who has
gift_balance != 0— the card equals Σbalancewhile the 可用 column sumsbalance + gift_balance.Fix
ui/js/app.js: the card's total includesu.gift_balance(one line + a comment pointing at thecaption).
src/state_gate.rs: new static gatestate_gate::tests::the_admin_total_balance_card_sums_what_its_caption_names, three rules withindependent teeth:
renderAdminand its value argument is a plain identifier;comments stripped) must read both
balanceandgift_balanceas identifier tokens —equivalent rewrites are allowed (
D.fmt(total + giftTotal), a helper, two per-componenthelpers), only "the sum dropped a component" is red;
Plus discriminator self-tests (
stat_value_argumentsegment extraction,assignment_statementattribution,
caption_names_both_components) and a control that rule 2 only bites that one card(the token-usage bar row in the same function is not a
stat(...)card).ui/index.html: cache-bustapp.js?v=20260915-13.ui/README.md: contract note (caption = the card's statement of what the number is; a summary cardmust be sourced from the same definition as its caption).
Tests
cargo test285 → 286 passed (one new gate test)cargo fmt --checkclean;cargo clippy --all-targetsunchanged (only the pre-existingsrc/protocol.rs:662warning)A/B on the gate (in-place mutations, byte-exact restore, md5 verified) — 9/9 legs as declared:
balanceonlyM7 and M8 are the competitor legs: the axis pushed the wrong way, and the "delete the promise"
half-fix. Both are rejected.
Behavioural probe (
tmp/c2142_probe.js, jsdom, realui/index.html+ the four real scripts, onlyfetchstubbed, drives the real admin session and real nav): 9/9 on the fixed tree; on thepre-fix tree exactly the three axis legs (
A1,A2,B2) fail — the card equals Σbalancewhilethe 可用 column sums
balance + gift_balance; the probe also rejects the axis-reversed competitor.Notes
wrong), so the i18n positive-control counts in
src/i18n_pack.rsare untouched.(
src/routes/admin.rsselectsbalance, gift_balance).