feat(ui): replace native confirm/prompt dialogs with inline components (v1.17 A) - #41
Merged
Merged
Conversation
4 tasks done
6 tasks done
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.
8 tasks done
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.
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
Rant
2026-08-17T16:57:17(UI/UE 多角度深化优化), item A — 清除原生弹窗: remove all remaining nativewindow.confirm/window.promptdialogs fromui/and replace them with reusable inline components.Changes
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.validateshows an error toast on invalid input.confirmInlineconfirmInlineconfirmInline#ak-new-inline(Enter/Esc bound)inlineForminlineForminlineForm.btn.confirming(red confirming state) and.inline-editcontainer styles.docs/user-stories.md→ v1.17 (§1.1 原生弹窗清零 + changelog);ui/README.md行内组件约定 section synced.Acceptance
grep ui/shows nowindow.confirm/window.promptresiduals (verified clean).Tests
node --check ui/js/app.js+ui/js/data.js— syntax OK#ak-new-inline/#ak-new-name/#ak-new-ok/#ak-new-cancelall wired)cargo test— 1 passedcargo fmt --check— clean