fix(ui): submit the inline cards from every field, not from a hand-written roster - #247
Merged
argszero merged 1 commit intoSep 14, 2026
Conversation
…itten roster
The inline form cards (`#topup-card`, `#raise-card`, `#dept-form-card`, `#model-form-card`,
`#ak-new-inline`) are `<div class="form">` / `<span class="inline-edit">`, not real `<form>`s
(the real one is `#share-form`, where the implicit submission of a `type="submit"` button makes
Enter work in every field for free). So Enter has to be implemented by hand — and it was
implemented as a **per-field roster**:
$("#model-form-provider").addEventListener("keydown", ... $("#model-confirm").click());
$("#model-form-model").addEventListener("keydown", ...) // and that is the whole list
The model form has ten text controls. The roster named two of them, so the other eight — input
price, cache-hit price, output price, all three peak prices, context window, max output — did
nothing at all when you pressed Enter, while every one of them submits when you click Confirm.
Nothing errors; the card simply ignores the key. The department form on the same page registered
every field it has (2/2), and `#share-form` needs no registration at all, so this is an omission
that never got caught, not a deliberate trade-off.
A roster is the problem, not its entries: it does not grow when the markup does. The fix deletes
it and delegates Enter to the card:
wireEnterSubmit($("#model-form-card"), "#model-confirm")
`wireEnterSubmit` listens on the container, so every text control the card has — today's and
tomorrow's — is covered, and it bails on non-text targets, so pressing Enter while the *Cancel*
button has focus still dismisses the card instead of submitting it. The delegate only calls
`btn.click()`: the busy state (`withLoading`), the field validation and the request all stay in
the confirm button's own listener, which is the one place that already implements them.
`src/i18n_pack.rs::enter_submit_is_delegated_to_the_card` pins the shape in CI:
* no `keydown` registration that clicks a confirm button may hang off an id that
`ui/index.html` declares on an `<input>` / `<select>` / `<textarea>` (the rule is derived
from the markup, not from a list someone has to remember to update);
* `wireEnterSubmit` must listen on the container, bail on non-`INPUT` and on
`NON_TEXT_INPUT_TYPES`, and delegate with `btn.click()`;
* all five cards must go through the helper, and the helper's call count must equal the
number of cards.
The detector is proven on synthetic pre-fix text and on the pre-fix tree itself, and against two
shapes it must leave alone: `#chat-input`'s Enter sends a message (it does not click a button),
and the delegate's own target is the `card` parameter, not a `$("#id")`. The control flow has no
JS runner in CI, so the runtime half is carried by `ui/README.md` and its smoke-test notes.
Verified with a jsdom instrument over the real `ui/index.html` + four real scripts (only `fetch`
stubbed, answering in the backend's real shapes), driven through the real login form and the real
nav. Every control runs the same submission twice — leg A presses Enter in the control, leg B
clicks the confirm button with identical field values — so each control carries its own positive
control that refutes "this form could not submit with these values anyway":
live (this tree) passed=23/23 reds={}
pre-fix tree (6e022e1) passed=21/23 reds={S1b B1:model-form-card}
-> 8 of 10 controls: Enter fired nothing, the click fired POST /api/admin/models
guard removed (delegate fires on any Enter) reds={D2:model-form-card D2:topup-card}
-> Enter on the Cancel button submits the card
The two legs turn disjoint sets red, and the pre-fix red set is exactly the axis (the eight
controls). The guardless leg is the mutation that pins the direction: deleting the roster is not
enough, the delegate also has to keep "only text inputs submit" true.
- ui/js/app.js : `wireEnterSubmit` + `NON_TEXT_INPUT_TYPES`; five cards delegated; roster gone
- src/i18n_pack.rs: CI tripwire for the shape, with detector controls
- ui/README.md : record the rule and the smoke-test shape
- ui/index.html : cache-bust app.js
Co-authored-by: EMRG Evolution <emrg@argszero.dev>
argszero
deleted the
fix/enter-submits-every-field-of-the-inline-form-cards
branch
September 14, 2026 16:57
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 inline form cards —
#topup-card,#raise-card,#dept-form-card,#model-form-card,#ak-new-inline— are<div class="form">/<span class="inline-edit">, not real<form>s(the real one is
#share-form, where atype="submit"button gives every field Enter for free).So Enter had to be implemented by hand, and it was implemented as a per-field roster:
The model form has ten text controls. The roster named two, so the other eight — input price,
cache-hit price, output price, all three peak prices, context window, max output — did nothing on
Enter, while all ten submit when you click Confirm. Nothing errors; the card just ignores the key.
The department form on the same page registered every field it has (2/2) and
#share-formneedsno registration at all, so this is an omission rather than a trade-off.
A roster is the problem, not its entries: it does not grow when the markup does. The fix deletes
it and delegates Enter to the card, so every text control the card has — today's and tomorrow's —
is covered:
The delegate bails on non-text targets, so pressing Enter while the Cancel button has focus
still dismisses the card instead of submitting it, and it only calls
btn.click()— busy state(
withLoading), validation and the request stay in the confirm button's own listener.Related Issue
Changes
(
ui/js/app.js)src/i18n_pack.rs)ui/README.md)ui/index.htmlTests
cargo test全部通过 (260 passed, 0 failed)cargo fmt --check通过cargo clippy无新增 warning (only the pre-existingcollapsible_matchfalse positive atsrc/protocol.rs:662on the local 1.95 toolchain; CI's stable is authoritative)src/i18n_pack.rs::enter_submit_is_delegated_to_the_cardRuntime verification (jsdom over the real
ui/index.html+ four real scripts, onlyfetchstubbed, driven through the real login form and the real nav). Every control runs the same
submission twice — leg A presses Enter in the control, leg B clicks the confirm button with
identical field values — so each control carries its own positive control against "this form
could not submit with these values anyway":
The two legs turn disjoint sets red, and the pre-fix red set is exactly the axis. The guardless
leg pins the direction: deleting the roster is not enough on its own — the delegate must also keep
"only text inputs submit" true.
Checklist
fix/...)