`,\
+ 没有隐式提交,但「哪些控件能提交」不该靠人写名册(模型表单 10 个可输入控件曾只登记 2 个)。\
+ 改成容器级委托 `wireEnterSubmit($(\"#卡片\"), \"#确认按钮\")`:挂冒泡,卡片里当前和以后的\
+ 文本控件都自动生效",
+ bad.len()
+ );
+
+ // ② 阴性对照:提取器必须认得出修前的两种形态(否则 ① 只是恒真的形状巧合)。
+ let mut synthetic = std::collections::BTreeSet::new();
+ for id in [
+ "model-form-in",
+ "model-form-provider",
+ "dept-form-quota",
+ "chat-input",
+ ] {
+ synthetic.insert(id.to_string());
+ }
+ for (before, what) in [
+ (
+ "$(\"#model-form-in\").addEventListener(\"keydown\", (e) => { if (e.key === \"Enter\") { e.preventDefault(); $(\"#model-confirm\").click(); } });",
+ "模型表单「输入价」字段",
+ ),
+ (
+ "$(\"#dept-form-quota\").addEventListener(\"keydown\", (e) => { if (e.key === \"Enter\") { e.preventDefault(); $(\"#dept-confirm\").click(); } });",
+ "部门表单「配额」字段",
+ ),
+ ] {
+ assert_eq!(
+ enter_click_registrations_on_controls(before, &synthetic).len(),
+ 1,
+ "阴性对照失败:提取器认不出逐字段登记({what})—— ① 等于没写"
+ );
+ }
+ // 阳性对照:同样挂在控件上、但**不点按钮**的 Enter(聊天输入框 = 发送消息)不得误判;
+ // 容器级委托的目标是函数参数 `card`,也不得误判。
+ for (after, what) in [
+ (
+ "$(\"#chat-input\").addEventListener(\"keydown\", (e) => { if (e.key === \"Enter\") sendChat(); });",
+ "聊天输入框(发送消息,不是表单提交)",
+ ),
+ (
+ "card.addEventListener(\"keydown\", (e) => { if (e.key !== \"Enter\") return; btn.click(); });",
+ "容器级委托本身",
+ ),
+ ] {
+ assert!(
+ enter_click_registrations_on_controls(after, &synthetic).is_empty(),
+ "阳性对照失败:提取器把合法形态判成违规({what}:{after})"
+ );
+ }
+
+ // ③ 委托的加载器本身:登记在**容器参数**上,并早退非文本输入(否则焦点在「取消」上
+ // 按 Enter 会同时触发取消与提交)。
+ let body = js_function_body(&app, "function wireEnterSubmit(")
+ .expect("ui/js/app.js 里找不到 wireEnterSubmit 的函数体");
+ assert!(
+ body.contains("card.addEventListener(\"keydown\""),
+ "wireEnterSubmit 不再把 keydown 挂在**容器**上:{body}"
+ );
+ assert!(
+ body.contains("tagName !== \"INPUT\"") && body.contains("NON_TEXT_INPUT_TYPES"),
+ "wireEnterSubmit 不再早退非文本输入控件:勾选框 / 下拉 / 按钮上的 Enter 不该提交,\
+ 而且早退 `button` 才能避免「在『取消』上按 Enter 同时触发取消与提交」"
+ );
+ assert!(
+ body.contains("btn.click()"),
+ "wireEnterSubmit 不再点确认按钮:忙碌态(withLoading)、字段校验与请求都留在确认按钮\
+ 自己的监听器里,委托不得复制一份"
+ );
+
+ // ④ 五张行内卡片都必须走这个 helper。这一条是**卡片的**名册(5 个、每个都是有意的动作),
+ // 不是**字段的**名册(模型表单里 10 个字段就是这么长出来的);数量断言让「新增卡片却
+ // 另写一套」必须先改这里,而字段级遗漏已由 ① 兜住。
+ let mut calls = 0;
+ for card in [
+ "topup-card",
+ "raise-card",
+ "ak-new-inline",
+ "dept-form-card",
+ "model-form-card",
+ ] {
+ let call = format!("wireEnterSubmit($(\"#{card}\")");
+ assert!(
+ app.contains(&call),
+ "行内卡片 #{card} 没有走 wireEnterSubmit —— 它的 Enter 提交要么缺失、要么又回到了逐字段登记"
+ );
+ calls += app.match_indices(&call).count();
+ }
+ assert_eq!(
+ calls,
+ app.matches("wireEnterSubmit($(\"#").count(),
+ "wireEnterSubmit 的调用数与卡片数不一致:新增卡片请同步 ④ 的清单(并确认它是真