fix(ui): render and load a view only when it is the destination - #254
Merged
Merged
Conversation
The DOMContentLoaded handler called renderView("dashboard") unconditionally,
before the session was restored. renderView is "render + load", and with a
token stored loggedIn() is already true at that moment, so the dashboard's
whole query set was fired while the session did not exist yet: the responses
were then discarded by loadSession()'s resetSessionCaches() and requested
again by enterApp() -> switchView(destination).
A jsdom instrument (real index.html + the four real scripts, only fetch
stubbed and logged) measured the current tree: one token boot issues 14
requests with the dashboard set fetched twice each (/api/dashboard, the
type=all&bucket=day trend, page=1&page_size=1, /api/sharings) and
/api/wallet three times, and log[0] is GET /api/wallet while the session
request /api/me is only second. When the destination is another view
(#/transactions) the dashboard set is still fetched five times. With an
expired token every one of those six pre-session requests answers 401, so
__atpLogout() runs six times and the user sees three identical
"Session expired, please sign in again" toasts.
Boot now only builds the shell (nav, events, balance placeholder); every
view is rendered and loaded by switchView for the current destination,
which enterApp() and enterGuest() already drive.
The static gate src/state_gate.rs::the_boot_handler_touches_no_view pins
the shape: the boot body may not call any view renderer/loader (the view
layer is derived from renderView's own branches), renderView(...) may only
take the current destination, and switchView must still call renderView so
that removing the boot line cannot silently empty the app.
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
ui/js/app.js的DOMContentLoaded处理器无条件调用renderView("dashboard"),而renderView是「渲染 + 装载」(登录时还会异步拉取),且它跑在restoreSession()之前。带 token 时loggedIn()此刻已为 true ⇒ 会话还不存在,仪表盘那一整套查询就已经发了出去。Related Issue
(无关联 issue:本改动由一支 jsdom 仪器实测驱动,证据见下)
Changes
ui/js/app.js:boot 只搭外壳(renderNav()/bindEvents()/ 余额占位),不渲染也不装载任何视图 —— 视图一律由switchView按当前目的地渲染装载(登录后enterApp()、游客enterGuest()已各自触发)。src/state_gate.rs:新增静态门禁the_boot_handler_touches_no_view(+提取器自证the_boot_body_extractor_stops_at_the_right_place)。Live.dashboardTrend不在Live字面量里 ⇒resetSessionCaches()的派生名册清不到它」。实测(写后登出)该槽确实为null——Live.x = v会新建 own enumerable 属性,调用时的Object.keys(Live)就包含它。src/state_gate.rs与本文件相关段落一并更正。ui/README.md新增约定段落;ui/index.htmlcache-bustapp.js?v=20260915-9。实测(jsdom 启真
ui/index.html+ 四个真脚本,只 stub 并记账fetch)改前(
f83df40的客户端)://api/dashboard、趋势type=all&bucket=day、page=1&page_size=1、/api/sharings),/api/wallet3 次log[0] = GET /api/wallet—— 视图数据排在会话请求/api/me(第 2 位)之前#/transactions__atpLogout()被调用 6 次(TOAST_MAX = 3⇒ 用户看到 3 条一模一样的「登录已过期,请重新登录」);对照腿 E2:仍正确落在登录页改后:A 各 1 次、
log[0] = GET /api/me、B 目的地之外的请求 0、E 通知 1 次、C 仍 0;控制腿(目的地照常装载、登出/401 行为)全绿。为什么这条要由静态门禁钉
三种「最小改法」在仪器上都能全绿:① boot 里
renderDashboard()(只渲染不装载);②if (!api.getToken()) renderView("dashboard");③(改前)原样。它们只是症状消失 —— 屏幕上看不出来,请求日志也干净。门禁钉的是形状:renderView自己的分支,不写第二份名册);renderView(...)只许以当前目的地为实参(全仓唯一合法的一处是语言切换监听器里的renderView(activeView));switchView必须仍调用renderView—— 防止矫枉过正:删掉 boot 那句之后顺手清空renderView的调用点,就得到一个什么都不渲染的空壳。Tests
cargo test全部通过(277 → 279 passed)cargo fmt --check通过cargo clippy无新增告警(仅既有src/protocol.rs:662假阳性)md5):改前树红;两条竞争修法腿(renderonly/notoken)由仪器接受、由门禁拒绝;另两条反例(switchView硬编码视图名 / 删掉renderView调用)分别让规则 ②/③ 变红 —— 三条规则各有各的牙。Checklist
fix/…)