fix(ui): source the marketplace from the session, not from the model list's presence - #256
Merged
Merged
Conversation
…list's presence The marketplace graded its data source on whether /api/models had arrived, not on whether the session is live. With a catalogue request that failed (or had not arrived yet), a logged-in session rendered the guest tables of data.js: the provider filter offered providers this deployment does not have, the "recently used" chips rendered guest-market models (clicking one only showed "load failed"), and the nav badge showed the publish-form price mirror's row count (13) instead of the catalogue's (0). The list itself was already honest (load-failed + retry), so the three leaks were per-consumer drift. All market consumers (list, provider filter, recent chips, chat, nav badge) now go through one source per table -- `marketRows()` / `marketProviders()` -- whose discriminator is `loggedIn()` (null = a live session without a catalogue). The guest path is untouched and still by design. Gate: `state_gate::market_tables_follow_the_session_not_the_data` (three rules with independent teeth, zero exemption list) plus a comment-aware scanner that strips //, /* */ blocks and inline /* */ pairs.
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 marketplace graded its data source on whether
/api/modelshad arrived, not on whether the session is live.ui/js/data.jsstates the contract explicitly (rant 2026-08-19T15:54:06): its tables are for the guest marketplace and for the publish-form fallback — a logged-in session must never read them. Three consumers inui/js/app.jsinstead wroteLive.models ? <live> : D.<guest table>, each with a comment restating the rule right above it.Reachable window (the normal shape of a failure, not a fabricated one): the session is live and the catalogue request fails (500 / timeout / network) —
loadMarketplace()setsLive.models = nulland renders the honest "load failed + retry" row, while:openChat()is guarded byif (loggedIn() && !Live.models) … return— so the chip is a button that can only answer "load failed";Fingerprint of drift rather than design: ten lines below the provider filter, the same function gets it right —
let list = Live.models ? modelsToView(Live.models) : (loggedIn() ? null : D.MARKET);, with the comment "绝不 fallback D.MARKET". One line right, one line wrong, differing only byloggedIn().Changes
ui/js/app.js: one source per market table —marketRows()(returnsnullfor a live session without a catalogue) andmarketProviders(). The discriminator is written once, at the read of the table. List, provider filter, recent chips, chat and nav badge all consume them.openChat's guard line is untouched (it owns the toast); its lookup now goes through the helper. The provider dropdown'sdataset.provSourcedistinguishes three states (live/none/mock) so it still rebuilds when the catalogue arrives.ui/index.html:app.js?v=20260915-10→-11.src/state_gate.rs: new invariantmarket_tables_follow_the_session_not_the_data, three rules with independent teeth and zero exemption list: (1) every code line readingD.MARKET/D.PROVIDERSmust itself branch on the session; (2) no line may mix a market table with the publish-form fallback tables (D.MODELS/D.PLANS) — the badge line did exactly that; (3) each market table has exactly one reader (the source helper), which also rejects the over-correction "just delete the mocks". A companion testthe_market_source_scanners_have_teethpins the scanner on synthetic input.ui/README.md: new section documenting the convention and why the gate and the probe each cover half of it.//-started lines, multi-line/* … */bodies and inline/* … */pairs — the module's own explanation mentions table names, so a naivecontainswould red-flag its own documentation (ledger pitfall #296/#309).Related Issue
No issue exists for this; found during Recon. No linked issue is expected.
Tests
cargo test— 283 passed (281 before; +2 gate tests).cargo fmt --check— clean.cargo clippy --all-targets— only the pre-existingcollapsible_matchatsrc/protocol.rs:662.index.html+ the four real scripts,fetchstubbed and logged, real controls driven) — 14/14 as declared, with per-leg expectations pinned to an explicit baseline:/api/models→ 500git show HEAD:ui/js/app.js){A2,A3,A4}D.MODELS{A4}{C2,C3}{A2}The last two rows are recorded honestly: the gate is stricter than the probe on one axis (it rejects a second, behaviour-identical reader) and the probe is stricter than the gate on another (it rejects a line whose shape lies). Each instrument covers a different half.
Checklist
fix/…)ui/js/*.js