Repository navigation
fix(ui): let the ops key rows be named the way their own card names them - #298
Merged
Merged
Conversation
The ops runtime card renders one line per provider with
`T("ops.keys.count", { total, on })`, and `total` is the backend's unfiltered
`COUNT(*)` over the `keys` table — every status, including `paused` and `off`
(soft-deleted). The stat card directly above it counts `WHERE status = 'on'`,
and that is what the product means by "listed": the sharing page binds its
"Listed" pill to `status === 'on'`, and a count that includes non-`on` rows is
called "history" there. So the row said "6 listed keys · 5 enabled" next to a
card saying "Active keys: 5" — one word, one viewport, two numbers.
The card already names these rows "upstream key" in its own title ("Upstream key
status") and in its empty state ("No upstream keys listed yet"); only the count
line and the subtitle drifted. Provenance is drift, not a trade-off: the three
pack keys were authored in 09e4121, which renamed the prototype's wording, while
the data source from `85982e8` was untouched.
Fix the WORD, not the number. `total` must keep counting every status —
otherwise the row's own pill (`off >= total` -> "All disabled") becomes
structurally always green, which deletes the promise instead of keeping it. No
new i18n keys (the key set is unchanged), no backend change; the subtitle, which
names the same number, moves with the count line, and the static `data-i18n`
fallback and the `i18n.js` cache-bust token move with the pack.
Gate `state_gate::the_ops_key_rows_are_named_the_way_their_own_card_names_them`
derives the noun from `ops.keys.title` (no wording is written into the gate, so
renaming the card does not need a gate edit), and `ui/README.md` states the
contract, the gate's scope and one leg declared blind to it.
Co-authored-by: argszero <argszero@argszerodeMac-mini.local>
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 ops runtime card renders one mini-row per provider with
T("ops.keys.count", { total, on }).totalis the backend's unfilteredCOUNT(*)over thekeystable — every status:on,paused, andoff(soft-deleted). The stat card directlyabove it counts
WHERE status = 'on', and that is what the product means by "listed": the sharingpage binds its 「上架中」/ "Listed" pill to
status === 'on', and a count that includes non-onrows is called 「历史」/ "history" there.
So the row read
6 listed keys · 5 enabled(zh:6 个上架 key · 5 个启用) while the card above itread
Active keys: 5— one word, one viewport, two numbers.The card already names these rows "upstream key" in its own title (「上游 key 状态」/
"Upstream key status") and in its empty state (「尚未上架任何上游 key」/ "No upstream keys
listed yet"); only the count line and the subtitle drifted. The provenance is drift, not a
trade-off: the three pack keys were authored in
09e4121(the settings/admin/ops redesign), whichrenamed the prototype's wording — the prototype writes
k.n + " 个上游 key"— while the datasource (
COUNT(*),85982e8) was untouched.The fix is the word, not the number.
totalmust keep counting every status: the row's ownpill is
off === 0 ? "All enabled" : (off >= total ? "All disabled" : "{n} disabled"), so atotalthat only countedonwould make 「全部停用」 structurally unreachable — deleting thepromise instead of keeping it. The subtitle names the same number, so it moves with the count
line; the static
data-i18nfallback and thei18n.jscache-bust token move with the pack.Zero new i18n keys (the pack key set is byte-identical in count), zero backend change.
Related Issue
No linked issue — the repository has no open issues; this is a self-contained wording/correctness
fix found by scanning the ops view against its own data source (same shape as the previous
UI-contract fixes in this series).
Changes
ui/js/i18n.js—ops.keys.countandops.keys.subreworded in both packs (上架 key→上游 key,listed keys→upstream keys), plus a comment on the key stating why the countmust keep every status
src/state_gate.rs— new gatethe_ops_key_rows_are_named_the_way_their_own_card_names_them(noun derived from
ops.keys.title— no wording written into the gate) with threecompanion tests: single-rule mutation legs, a scanner self-proof, and a comment-stripping
control
ui/index.html— thedata-i18n="ops.keys.sub"static fallback kept word-for-word in stepwith the pack, and the
i18n.jscache-bust token movedui/README.md— new section: the contract, the gate's scope, and one leg declared blindTests
cargo test— 387 passed / 0 failed (baseline31bbff6was 384; +3 tests)cargo fmt --check— cleancargo clippy --all-targets -- -D warnings— clean on both A/B legsflipping exactly the rule it targets
Instrumentation (every instrument declares each leg's expectation up front; declared-vs-actual
and PASS/FAIL are printed as two separate facts, and a mismatch exits non-zero):
ui/index.html+ the four real scripts,fetchstubbed and logged,session role
ops, driving the realrenderOps(). The fixture manufactures the divergence,because it is not on screen in dev today:
key_health={aliyun-bailian: total 6 / on 5 / off 1}and{deepseek: 4 / 0 / 4}, againstactive_keys: 5. For the row withoff = 1itreads the rendered count line, the rendered subtitle, the rendered pill and the stat card's
value — and it derives the card's noun from the rendered title.
7 legs × 2 languages:
P0/P1controls,D1(the row's number is not theon-only number),A1(count line uses the card's own noun),A2(subtitle does),A3(the printed total ison + off),R1(reverse: the stat card's caption does not adopt the row noun).Six trees, 14/14 as declared each:
landed(the shipped bytes) green;basered withexactly
{A1, A2};fix— a conforming but differently worded implementation — green(so the legs test the contract, not one paraphrase);
m_one_line(fix the count line, leavethe subtitle) red with exactly
{A2};m_total_only_on(write the number instead of the word)red with exactly
{D1, A1, A2, A3};m_card_widen(make the stat card's caption say"upstream key" too) red with exactly
{R1}. Combinations outside that declared matrix arerefused (rc 2) rather than judged.
src/state_gate.rson the pre-fix bytes(
git archive 31bbff6) fails the axis test with the declared readingr2=false r3=false r4=true r5=true(the axis rules are false, both preconditions still hold —not a vacuous red); on this branch's tree it is green.
clippy -D warningsrc 0 on both legs.(The legs share a target dir for the dependencies; that bit me once — see the note below.)
bytes and its replacement zero times; applying the sheet to those bytes reproduces this branch's
ui/js/i18n.jsandui/index.htmlbyte for byte (md5); the probe's anchors are read out ofthe probe file and equal the sheet's; the key set is unchanged (no new keys); the count line
keeps both placeholders; the stat card's caption is untouched; the static fallback equals the
pack value; the cache-bust token really moved; the tests the README names all exist.
One instrument bug worth stating: the first A/B run shared one
CARGO_TARGET_DIRbetween thetwo source roots, and cargo served the first leg's test binary to the second one — the crate
embeds the pack through
include_str!. The run therefore reported the pre-fix reading on the fixedtree: an A/B that would have "confirmed" the fix while measuring one tree twice. The legs now touch
the crate root before building, and the script asserts that the two legs' readings differ.
Scope, stated honestly: the gate is lexical — it proves the row count's noun and the card's
own title come from the same source. It does not prove what is on screen at any moment (that is
the jsdom probe's half), and it does not constrain the
totalalgorithm (that is fixed by thepill's structural requirement). One leg is declared blind and written into
ui/README.md:renaming
ops.keys.titleas well (so the card and the count agree on a wording that is wrong forthe data — e.g. calling every-status rows "listed") keeps the gate green; what refuses it is the
design baseline and the probe's
R1leg, not this rule.Checklist
fix/…)