Skip to content

fix(ui): make the admin model form actually submit its request - #228

Merged
argszero merged 1 commit into
mainfrom
fix/model-form-submit-dead
Sep 13, 2026
Merged

argszero merged 1 commit into
mainfrom
fix/model-form-submit-dead

Conversation

@argszero

Copy link
Copy Markdown
Owner

Summary

The admin Models tab could never add or edit a model: clicking 确认 (Confirm) validated the form and then silently did nothing — no request was ever sent, and the button went back to its idle label after ~320 ms.

Root cause: the button is wired through a click listener that already applies the busy state —

$("#model-confirm").addEventListener("click", (e) => withLoading(e.currentTarget, confirmModel));

— and confirmModel wrapped its own request in a second withLoading:

function withLoading(btn, fn, ms) {
  if (!btn || btn.dataset.loading) return;   // <-- guard
  btn.dataset.loading = "1";
  
  setTimeout(() => { try { fn(); } finally {  } }, ms || 320);
}

The outer wrapper sets btn.dataset.loading = "1" when the click is dispatched, so the inner call hits the guard and returns immediately: fn (the api.post / api.patch) is never reached. Both wrappers and the listener come from the same commit — 98a7a9b (#97, "model info CRUD") — so this has never worked since the tab was introduced.

Fix: drop the inner wrapper and keep the request inline, mirroring the sibling confirmDept (same admin pane, same listener pattern). The busy state still comes from the listener, so double-click protection and the spinner are unchanged; pressing Enter in the provider/model fields (which calls .click()) goes through the same path.

The other handlers were checked for the same shape: confirmTopup / confirmRaise / confirmDept call the API directly because their listeners wrap them, and commitNewKey owns its withLoading because its listener does not wrap it. confirmModel was the only handler with both.

Related Issue

Changes

  • ui/js/app.js: confirmModel no longer nests withLoading; the request (POST on create / PATCH on edit) is issued directly, with a comment stating why the nesting is fatal here.
  • ui/index.html: bump the app.js cache-bust (?v=20260914-5).
  • No config/schema change, no new i18n keys.

Tests

  • cargo test235 passed, 0 failed (unchanged: no Rust file touched)

  • cargo fmt --check — clean

  • cargo clippy --all-targets -- -D warnings — clean

  • node --check ui/js/app.js — clean

  • New unit tests — not applicable: the repo has no JS test infrastructure and this behaviour is not reachable from cargo test. Verified with a real-DOM harness instead (jsdom booting ui/index.html + the four real scripts, only fetch stubbed, driving the real buttons/forms and asserting the request that the click produced):

    check before after
    B6 the confirm button is disabled + loading immediately after the click
    B7 edit path emits PATCH /api/admin/models/203 (none)
    B8 create path emits POST /api/admin/models (none)

    A/B (tmp/c2110_ab.py, baseline read from origin/main):

    leg red checks
    pre-change tree {B7, B8}
    fixed tree + the nested wrapper re-installed {B7, B8} (it is the pre-change hunk — a self-consistency check)
    fixed tree but the listener wrapper dropped instead (the plausible wrong fix) {B6}
    fixed tree none — 20/20

    So the instrument rejects both the pre-change tree and the wrong repair, on disjoint checks; the unfiltered control checks (A2/B2/C2) stay green in every leg.

Checklist

  • Branch naming follows the convention (fix/)
  • Commit message uses Conventional Commits
  • Single responsibility, minimal change (2 files, UI only)

`#model-confirm` is wired through a click listener that already applies the busy
state -- `addEventListener("click", (e) => withLoading(e.currentTarget, confirmModel))`
-- and `confirmModel` then wrapped its own request in a *second* `withLoading`.
`withLoading` opens with `if (!btn || btn.dataset.loading) return;`, and the outer
wrapper has already set `btn.dataset.loading = "1"` when the click was dispatched,
so the inner call returns immediately: the request was never sent. Adding and
editing a model were both dead, and the button silently went back to its idle
label after ~320ms, which is why it looked like nothing happened.

Both wrappers and the listener come from 98a7a9b (#97), the commit that introduced
the admin models tab, so this has never worked.

Fix: drop the inner wrapper and keep the request inline, mirroring its sibling
`confirmDept` (same admin pane, same listener pattern) -- the loading state still
comes from the listener, so double-click protection and the spinner are unchanged.
The other handlers were checked: `confirmTopup` / `confirmRaise` / `confirmDept`
call the API directly, while `commitNewKey` owns its `withLoading` because its
listener does not wrap it. `confirmModel` was the only one with both.
@argszero
argszero merged commit 6673389 into main Sep 13, 2026
1 check passed
@argszero
argszero deleted the fix/model-form-submit-dead branch September 13, 2026 23:49
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