Skip to content

feat(ui): replace native confirm/prompt dialogs with inline components (v1.17 A) - #41

Merged
argszero merged 1 commit into
mainfrom
feat/ui-ue-deep-polish-2
Aug 17, 2026
Merged

argszero merged 1 commit into
mainfrom
feat/ui-ue-deep-polish-2

Conversation

@argszero

Copy link
Copy Markdown
Owner

Summary

Rant 2026-08-17T16:57:17 (UI/UE 多角度深化优化), item A — 清除原生弹窗: remove all remaining native window.confirm / window.prompt dialogs from ui/ and replace them with reusable inline components.

Changes

  • New reusable components in ui/js/app.js:
    • confirmInline(btn, onConfirm, text) — inline two-step confirm: first click turns the button into a red 确认删除? state (3s auto-revert or Esc reverts), second click executes;
    • inlineForm(cell, opts) — inline edit form (input + 确认/取消), Enter confirms / Esc cancels, opts.validate shows an error toast on invalid input.
  • Converted call sites (all 5+ native dialogs):
    • Share listing delete (彻底下架) → confirmInline
    • API Key delete → confirmInline
    • Department delete → confirmInline
    • API Key create (生成新 Key) → inline form #ak-new-inline (Enter/Esc bound)
    • API Key rename (改名) → inlineForm
    • Operator topup (运营者充值) → inlineForm
    • Admin member topup (成员充值) → inlineForm
  • CSS: .btn.confirming (red confirming state) and .inline-edit container styles.
  • Docs: docs/user-stories.md → v1.17 (§1.1 原生弹窗清零 + changelog); ui/README.md 行内组件约定 section synced.

Acceptance

grep ui/ shows no window.confirm / window.prompt residuals (verified clean).

Tests

  • node --check ui/js/app.js + ui/js/data.js — syntax OK
  • DOM-stub smoke init — no ReferenceError, all bindings resolve
  • HTML id cross-check (#ak-new-inline / #ak-new-name / #ak-new-ok / #ak-new-cancel all wired)
  • cargo test — 1 passed
  • cargo fmt --check — clean

@argszero
argszero merged commit 990cd5a into main Aug 17, 2026
1 check passed
argszero added a commit that referenced this pull request Sep 29, 2026
`confirmInline(btn, onConfirm, text)` in `ui/js/app.js` turns a button into
its own confirmation ("确认删除?") and has exactly three ways out of that
state: the second click, the 3-second timeout, and Escape. Their shared
teardown was wired to one exit out of three:

- the second click cleared `dataset.confirm` and `.confirming` but never
  restored the label. On a FAILED delete no call site re-renders, so the
  button keeps reading the confirmation prompt although it is not armed any
  more; clicking it again then captures that prompt as the "original" label,
  so from that point on even the timeout restores the prompt -- the button
  never shows its own label again for the rest of the session.
- the timeout exit restored the label but did not unregister the
  document-level keydown listener, and neither did the click exit, so each
  confirmed/timed-out confirmation left one dead closure behind, pinning its
  button. A later Escape fired the whole pile, each rewriting the
  `innerHTML` of a button that is long gone (measured on the pre-fix tree:
  1 -> 2 -> 3 listeners).

Provenance is drift, not a trade-off: the unregister call was written --
`document.removeEventListener("keydown", esc)` -- and attached to the Escape
branch alone, while the label restore lived in a `revert()` that the other
two exits could not reach. Both were introduced by 990cd5a (#41) and the
contract in `ui/README.md:90` has said "3 秒无操作或 Esc 还原,再次点击执行"
ever since, so only the code disagreed.

Both exits now route through one `disarm()`: clear the timer, delete the
flag, drop `.confirming`, unregister the keydown listener, and put the
button's own markup back. Because arming and confirming are two separate
calls, the second one has no handle on the first one's closure, so the
pre-arm label and the listener are recorded on the node (next to the
existing `_confirmT`) and the teardown can reach them from either call.

Gate: `src/state_gate.rs` gains six rules, all derived from `confirmInline`'s
own arming writes (never from a snapshot) -- the teardown is one block-bodied
closure and it is the one that unregisters; the click, timeout and Escape
exits each reach it; the listener removed is the one registered; and the
markup restored is the button's own, not the confirmation prompt. Four
companion tests pin the roster (derived, with a positive control), the
extractors, a six-mutant table in which each mutant reddens exactly the rule
it targets, and the closure rule on the live tree. Scope is lexical: it
proves the three exits share one disposal, not what the screen shows (this
repository's CI has no JS runner). `ui/README.md` gains the same contract as
a sub-bullet of the inline-component section, naming the gate and its scope,
which is how the neighbouring contracts are documented.

Verification:
- jsdom probe over the real `ui/` (10 legs x 2 languages x 7 trees = 140
  checks, `misdeclared=0`): pre-fix tree red on D1/D2/D3/D4/C1, fixed tree
  green on all, and four competing fixes ("restore the label only",
  "unregister only", "drop the Escape exit", "re-render at the failing call
  site") each rejected by their own legs.
- `cargo test` 426 passed, 0 failed (421 before: +5 gate tests).
- `cargo fmt --check` clean; `cargo clippy --all-targets -- -D warnings`
  clean.
- `ui/index.html` cache-bust for `app.js` bumped per convention
  (`20260928-2` -> `20260929-1`), since the file's bytes changed.
argszero added a commit that referenced this pull request Sep 29, 2026
…324)

ui/README.md:90 (the inline-components section) said an inlineForm
`opts.validate` error is surfaced as "toast + 重新聚焦". That was true at
birth (990cd5a, #41) but 2d492ee (#47) changed the code to
`setFieldError(input, err)` and added the `## 一致性约定` section, which
states at line 133 that inline-edit validation goes through
`setFieldError` and explicitly does not use a toast. Only line 90 was
left behind, so the same file contradicted itself and the code.

Fix the wording to match line 133 and the implementation; no behaviour
change, no new i18n keys.
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