Skip to content

fix(ui): submit the inline cards from every field, not from a hand-written roster - #247

Merged
argszero merged 1 commit into
mainfrom
fix/enter-submits-every-field-of-the-inline-form-cards
Sep 14, 2026
Merged

argszero merged 1 commit into
mainfrom
fix/enter-submits-every-field-of-the-inline-form-cards

Conversation

@argszero

Copy link
Copy Markdown
Owner

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 a type="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:

$("#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, 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-form needs
no 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:

wireEnterSubmit($("#model-form-card"), "#model-confirm");

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 fix: Enter submits from any field of the inline cards; the per-field roster is gone
    (ui/js/app.js)
  • CI test pins the shape, with detector controls (src/i18n_pack.rs)
  • Rule + smoke-test notes recorded (ui/README.md)
  • cache-bust ui/index.html
  • No configuration / data-structure changes

Tests

  • cargo test 全部通过 (260 passed, 0 failed)
  • cargo fmt --check 通过
  • cargo clippy 无新增 warning (only the pre-existing collapsible_match false positive at
    src/protocol.rs:662 on the local 1.95 toolchain; CI's stable is authoritative)
  • 新增 CI 测试 src/i18n_pack.rs::enter_submit_is_delegated_to_the_card

Runtime verification (jsdom over the real ui/index.html + four real scripts, only fetch
stubbed, 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":

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 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/...)
  • Commit message 使用 Conventional Commits 格式
  • 单一职责,改动最小化 (4 files, one behaviour)

…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
argszero merged commit ecc654c into main Sep 14, 2026
1 check passed
@argszero
argszero deleted the fix/enter-submits-every-field-of-the-inline-form-cards branch September 14, 2026 16:57
@argszero argszero mentioned this pull request Sep 15, 2026
10 tasks
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant