fix(ui): the wallet view renders a slot only the dashboard loader filled - #253
Merged
Merged
Conversation
…iter `#month-changes` (wallet view) and `#dash-month-changes` (dashboard) are painted by the same `renderMonthChanges()`, which reads `Live.dashboard`. Only the dashboard's loader wrote that slot, while the wallet branch of `renderView` called `loadWallet()` (refreshes `Live.wallet` only). So when the session is established *on* the wallet view (hash `#/wallet` then login; or logout on the wallet page and log back in), nobody ever filled the slot: the block rendered net `0` plus "No changes this month" for good, while the very same `/api/dashboard` payload rendered correctly on the dashboard. - `refreshDashboard()` is now the slot's single writer (C2131 discipline); `loadDashboard()` and `loadWallet()` each call it. - New invariant in `src/state_gate.rs`: every `renderView` branch's loader closure (`load…` transitive calls ∪ the session-level `loadSession` closure) must write every slot its render closure (`render…` transitive calls) reads. The pre-change tree fails it on the wallet branch only, and a display-only "fix" keeps it red.
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.
8 tasks
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
#month-changes(wallet view) and#dash-month-changes(dashboard) are painted by the samerenderMonthChanges(), and both readLive.dashboard. But that slot had exactly one writer — the dashboard's loader (loadDashboard) — while the wallet branch ofrenderViewcalledloadWallet(), which only refreshesLive.wallet.So whenever the session is established on the wallet view, nobody ever filled the slot and the block rendered net
0plus "No changes this month" for good, while the very same/api/dashboardpayload rendered correctly on the dashboard:#/wallet(left over from a 401 or a previous session), then log in;(Refreshing with a live token hides it: boot calls
renderView("dashboard")unconditionally fromDOMContentLoaded, which fills the slot as a side effect. That is why it stayed latent.)Related Issue
None (internal defect found by recon; no issue was opened for it).
Changes
ui/js/app.jsrefreshDashboard()is now the single writer ofLive.dashboard(the C2131 discipline: a shared slot may have exactly one writer).loadDashboard()andloadWallet()eachawait refreshDashboard();— a view loads what it renders.src/state_gate.rs— new invariantevery_view_branch_loads_each_slot_its_renderer_reads: for everyLiveslotSand everyrenderViewbranchB, if anyone inB's render closure (transitive calls ofrender…) readsLive.S, then someone inB's loader closure (transitive calls ofload…∪ the session-levelloadSessionclosure) must writeLive.S. CreditingloadSessioncovers the session-level slots (models,publicUrl) with zero exemption list.the_slot_closure_scanners_have_teethproves the discriminators on synthetic input: transitive closure across functions, identifier boundaries (Live.dashboardTrendis notLive.dashboard),//and/* */comments must not create phantom reads, a loader that only writes its own slot must be red, and an indirect write through a helper must be green.ui/README.md— a new convention section ("渲染谁就装载谁"), including why a refresh does not reproduce it.ui/index.html—app.jscache-bust bumped to20260915-8.A/B evidence (in-place mutation, md5-verified restore):
cargo testgate91a2da1)renderWallet()readsLive.dashboard… branchwalletX1/X3/X4; the "no empty-state copy" check passes by construction)Live.wallet'smonth_use/month_earn"The instrument (jsdom, real
index.html+ the four real scripts, onlyfetchstubbed, driving the real login form / real logout button / real navigation) declares each leg's expectation, so "the defect is visible as intended" and "the fix did not work" cannot be confused.Tests
cargo test全部通过 (277 passed;state_gate8/8)cargo fmt --check通过src/state_gate.rs)Checklist
fix/)