@mrrisega/dsh-remote 0.7.11-beta.3 → 0.7.12-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -266,7 +266,7 @@ test("① 免费用户账号卡:升级 PRO 按钮仍在,但协议文案/链
266
266
  const plugin = loadPlugin();
267
267
  const tree = await plugin.settle(4);
268
268
  // 购买入口(升级按钮)必须还在 —— 这次只删协议文案,不动购买能力
269
- assert.ok(nodeWithText(tree, "🚀 升级 PRO"), "免费用户的「🚀 升级 PRO」按钮必须保留");
269
+ assert.ok(nodeWithText(tree, "升级 PRO"), "免费用户的「升级 PRO」按钮必须保留(图标换成内联 SVG,文案不再用 emoji)");
270
270
  // 协议痕迹一个都不能有
271
271
  assertNoAgreementInPanel(tree);
272
272
  // 客户端也不再为了这句话去问 node 半(面板里已经没有消费者了)
@@ -278,7 +278,7 @@ test("② 会员账号卡(业主点名场景):续费会员按钮下方不
278
278
  account: { plan: "pro", plan_source: "subscription", plan_ends_at: 1893456000000, invite_code: "ABC12345" },
279
279
  });
280
280
  const tree = await plugin.settle(4);
281
- assert.ok(nodeWithText(tree, "🔄 续费会员"), "前提:会员的续费入口在(这正是业主看到那句话的位置)");
281
+ assert.ok(nodeWithText(tree, "续费会员"), "前提:会员的续费入口在(这正是业主看到那句话的位置)");
282
282
  assertNoAgreementInPanel(tree);
283
283
  });
284
284
 
@@ -314,7 +314,7 @@ test("④ 未登录:没有购买入口,也没有任何协议痕迹/请求",
314
314
  },
315
315
  });
316
316
  const tree = await plugin.settle(4);
317
- assert.equal(nodeWithText(tree, "🚀 升级 PRO"), undefined, "未登录没有购买入口");
317
+ assert.equal(nodeWithText(tree, "升级 PRO"), undefined, "未登录没有购买入口");
318
318
  assertNoAgreementInPanel(tree);
319
319
  assert.equal(plugin.agreementRequests().length, 0);
320
320
  });
@@ -334,7 +334,7 @@ test("⑤ 源码级回归锁:client.js 里不得再残留协议渲染痕迹(
334
334
  test("⑥ 只删文案、不动购买能力:点「升级 PRO」仍走既有购买流程(请求 /dsh-remote/access-key)", async () => {
335
335
  const plugin = loadPlugin();
336
336
  let tree = await plugin.settle(4);
337
- const btn = nodeWithText(tree, "🚀 升级 PRO");
337
+ const btn = nodeWithText(tree, "升级 PRO");
338
338
  assert.ok(btn, "升级按钮必须还在");
339
339
  btn.props.onClick();
340
340
  await plugin.settle(2);
@@ -178,7 +178,9 @@ test("📱 远程访问卡:bridge 在线 → 绿点文案;登录态点「生
178
178
 
179
179
  // 状态行:bridge 运行 → “已连接(可远程访问)”
180
180
  assert.ok(textHas(tree, "已连接(可远程访问)"), "bridge 在线状态文案应显示");
181
- assert.ok(find(tree, (n) => n.children?.includes("📱 远程访问")), "应渲染「📱 远程访问」卡标题");
181
+ // A1(2026-11-07 业主截图):正文里不再重复「远程访问」标题 —— 顶部与弹层标题已经表达了。
182
+ assert.equal(find(tree, (n) => n.children?.includes("📱 远程访问")), undefined,
183
+ "正文里不得再有重复的「远程访问」卡片标题(业主截图框出的那行)");
182
184
 
183
185
  // 未生成 key 的空态按钮 → 点击触发 /dsh-remote/access-key
184
186
  const genBtn = find(tree, (n) => (n.children || []).some((c) => typeof c === "string" && c.includes("生成访问链接")));
@@ -422,130 +424,66 @@ test("E2EE 徽标:未登录 / host 未下发 e2ee → 不打扰(不渲染状
422
424
  assert.equal(e2eeLine(oldHost.render()), undefined, "未下发 service.e2ee 时不应渲染状态行");
423
425
  });
424
426
 
425
- test("「更多」只收纳营销入口:档位规格数字(Mbps / GB / 价格)不再出现在账号卡里", () => {
426
- // 收纳态(localStorage 里记着用户关掉过引导)=「更多」里会出现那两条折叠入口的场景
427
+ test("账号弹层平铺(A2):所有功能直接可见,不再有「更多」折叠;且不渲染档位数值", () => {
427
428
  const plugin = loadPlugin({ autoModal: "account", localStorageSeed: { "dsh-remote-marketing-more-v1": "1" } });
428
429
  plugin.states[0] = { config: { phone: "13800000000", deviceId: "dev-x" }, service: { running: true } };
429
- let tree = plugin.render();
430
+ const tree = plugin.render();
430
431
 
431
- // 真实用户路径:点披露按钮展开「更多」
432
- const toggle = find(tree, (n) => n.props?.id === "dru-account-more");
433
- assert.ok(toggle, "账号卡应有「更多」披露按钮");
434
- assert.equal(toggle.props["aria-expanded"], "false", "默认收起");
435
- toggle.props.onClick();
436
- tree = plugin.render();
437
- const body = find(tree, (n) => n.props?.className === "dru-more-body");
438
- assert.ok(body, "「更多」内容体应在 DOM 里");
439
- assert.equal(body.props.hidden, false, "点击后应展开(hidden=false)");
440
-
441
- // 【0.6.11】档位明细整块删除:标题「套餐与额度」、PRO / Pro Max 规格行、价格与结论句都不再渲染
442
- assert.ok(!textHas(body, "套餐与额度"), "「套餐与额度」标题应删除");
443
- assert.ok(!textHas(body, "Mbps"), "不得再渲染带宽数值(用户约定:本地面板不展示 PRO 版本流量带宽)");
444
- assert.ok(!textHas(body, "GB"), "不得再渲染流量数值");
445
- assert.ok(!textHas(body, "¥"), "不得再渲染价格");
446
- assert.ok(!textHas(body, "以套餐页为准"), "随档位明细一起删掉的结论句不得残留");
447
- // 整张账号卡同样一个规格数字都不许有
448
- assert.ok(!textHas(tree, "Mbps"), "账号卡里不得出现 Mbps");
449
- assert.ok(!textHas(tree, "¥"), "账号卡里不得出现价格");
450
-
451
- // 2026-10-17 版式简化(业主):正面只留「升级/续费 + 用户反馈 + 更多」;
452
- // 「带新用户换会员 / 修改密码」收进「更多」。升级入口**永远在正面**(不再随收纳状态搬家)。
453
- const upsell = find(tree, (n) => n.type === "button" && (n.children || []).some((c) => typeof c === "string" && c.includes("升级 PRO")));
454
- const invite = find(body, (n) => n.type === "button" && (n.children || []).some((c) => typeof c === "string" && c.includes("带新用户换会员")));
455
- assert.ok(upsell, "正面应保留「🚀 升级 PRO」入口(主行动,位置稳定)");
456
- assert.ok(invite, "「更多」里应保留「🎁 带新用户换会员」入口");
457
- assert.equal(typeof upsell.props.onClick, "function");
458
- assert.equal(typeof invite.props.onClick, "function");
459
- // 正面不应再出现「带新用户换会员」(已收进「更多」)
460
- const actions = find(tree, (n) => n.props?.className === "dru-actions");
461
- assert.ok(actions, "应有主按钮行");
462
- assert.ok(!textHas(actions, "带新用户换会员"), "带新用户入口不再占正面按钮行");
463
- // 「更多」现在与主行动同排:按钮本体带 .dru-btn(原来的整宽 .dru-disclose 披露条已去掉)
464
- const moreBtn = find(tree, (n) => n.props?.id === "dru-account-more");
465
- assert.ok(moreBtn, "「更多」按钮应存在");
466
- assert.match(String(moreBtn.props.className || ""), /dru-btn/, "「更多」必须与主行动同排(按钮样式,不再是整宽披露条)");
467
- assert.doesNotMatch(String(moreBtn.props.className || ""), /dru-disclose(?!-inline)/, "不得再继承整宽披露条样式");
468
-
469
- // 源码级:函数与其调用都删掉了(不留死代码)。
470
- // ⚠️ 断言「代码形态」而不是裸标识符 —— 注释里**故意**留着「这里原本是什么、为什么删」的说明
471
- // (与 settings-entry.test.mjs 的同款约定:注释里允许出现说明文字)。
472
- assert.doesNotMatch(SOURCE, /function renderPlanDetail\(/, "renderPlanDetail 函数必须删除");
473
- assert.doesNotMatch(SOURCE, /^\s*renderPlanDetail\(\),/m, "renderPlanDetail 的调用点必须删除");
474
- assert.doesNotMatch(SOURCE, /function planSpecText\(/, "planSpecText 定义必须删除");
475
- assert.doesNotMatch(SOURCE, /planSpecText\("/, "planSpecText 的调用必须删除");
476
- assert.doesNotMatch(SOURCE, /function freeMbpsOf\(/, "freeMbpsOf 定义必须删除");
477
- assert.doesNotMatch(SOURCE, /=\s*freeMbpsOf\(\)/, "freeMbpsOf 的调用必须删除");
478
- assert.doesNotMatch(SOURCE, /var FREE_FACTS\s*=/, "FREE_FACTS 定义必须删除(只被 freeMbpsOf 使用)");
479
- assert.doesNotMatch(SOURCE, /" Mbps"|" GB\/月,¥"/, "不得残留渲染规格数字的字符串拼接");
480
- // 不变量仍在:planFactsOf 仍被 upgradePath 用来取档位展示名(不是死代码)
481
- assert.match(SOURCE, /var name = planFactsOf\(key\)\.name;/);
482
- });
432
+ // 不再有「更多」披露按钮 / 折叠体(业主:账号弹层不要折叠,空间足够)
433
+ assert.equal(find(tree, (n) => n.props?.id === "dru-account-more"), undefined,
434
+ "账号弹层不得再有「更多」折叠按钮");
435
+ assert.equal(find(tree, (n) => n.props?.className === "dru-more-body"), undefined,
436
+ "账号弹层不得再有「更多」折叠体");
483
437
 
484
- test("E2EE 徽标(源码级约束):client 含徽标字段/文案与 reason 映射表", () => {
485
- assert.match(SOURCE, /service\.e2ee/);
486
- assert.match(SOURCE, /\.e2ee-state\.json/);
487
- assert.match(SOURCE, /🔒 端到端加密已启用(手机解锁后生效)/);
488
- assert.match(SOURCE, /端到端加密暂不可用(当前为普通安全连接 HTTPS)/);
489
- assert.doesNotMatch(SOURCE, /等待服务端开启 E2EE/); // 已正式开启,不再出现“灰度等待”措辞
490
- assert.match(SOURCE, /当前为普通安全连接(HTTPS)/);
491
- assert.match(SOURCE, /server_disabled/);
492
- assert.match(SOURCE, /params_unreachable/);
493
- assert.match(SOURCE, /disabled_by_config/);
494
- assert.match(SOURCE, /derive_failed/);
495
- assert.match(SOURCE, /describeE2ee/);
496
- assert.match(SOURCE, /dru-e2ee-line/);
497
- });
438
+ // 各功能直接平铺(无需展开就能看到)
439
+ for (const label of ["用户反馈", "带新用户换会员", "修改密码"]) {
440
+ assert.ok(find(tree, (n) => n.type === "button" && (n.children || []).some((c) => typeof c === "string" && c.includes(label))),
441
+ "应直接平铺按钮: " + label);
442
+ }
498
443
 
499
- // ── 2026-10-17 版式与文案精简(业主 UX 反馈)──────────────────────────────────
500
- // 动线:账号(我是谁 / 退出)→ 主行动(升级或续费)→ 次要(反馈)→ 更多(其它入口)。
444
+ // 档位明细/数值始终不渲染(用户约定:本地面板不展示 PRO 版本流量带宽)
445
+ assert.ok(!textHas(tree, "套餐与额度"), "「套餐与额度」标题应删除");
446
+ assert.ok(!textHas(tree, "Mbps"), "不得再渲染带宽数值");
447
+ assert.ok(!textHas(tree, "GB"), "不得再渲染流量数值");
448
+ assert.ok(!textHas(tree, "¥"), "不得再渲染价格");
449
+ });
501
450
 
502
- test("★ 正面按钮收敛为三个:升级/续费 + 用户反馈 + 更多(不再一排按钮疯狂换行)", () => {
451
+ test("账号弹层:一屏一个主操作(只有升级/续费是 primary);退出登录在状态头(A3)", () => {
503
452
  const plugin = loadPlugin({ autoModal: "account" });
504
453
  plugin.states[0] = { config: { phone: "13800000000", deviceId: "dev-x" }, service: { running: true } };
505
454
  const tree = plugin.render();
506
- // 面板里可能有多处 .dru-actions(访问密钥卡等);账号卡那一处用「更多」按钮定位。
507
- const actions = find(tree, (n) => {
508
- if (n.props?.className !== "dru-actions") return false;
509
- let hit = false;
510
- walk(n, (x) => { if (x.props?.id === "dru-account-more") hit = true; });
511
- return hit;
512
- });
513
- assert.ok(actions, "应有账号卡主按钮行");
514
-
515
- const btns = [];
516
- walk(actions, (n) => { if (n.type === "button") btns.push(n); });
517
- // 三个按钮:主行动、用户反馈、更多
518
- assert.equal(btns.length, 3, `正面按钮应为 3 个,实际 ${btns.length} 个(多了就是又回到"按钮墙")`);
519
- const labels = btns.map((b) => JSON.stringify(b.children || []));
520
- assert.ok(labels.some((t) => t.includes("升级 PRO") || t.includes("续费会员")), "第一个是主行动");
521
- assert.ok(labels.some((t) => t.includes("用户反馈")), "用户反馈留在正面");
522
- assert.ok(labels.some((t) => t.includes("更多")), "「更多」与主行动同排");
523
-
524
- // 退出登录**不在**主按钮行里,而在账号行(.dru-user)右侧
525
- const logoutInActions = btns.some((b) => (b.children || []).some((c) => c === "退出登录"));
526
- assert.equal(logoutInActions, false, "退出登录不得再占主按钮行");
527
- const userRow = find(tree, (n) => n.props?.className === "dru-user");
528
- assert.ok(userRow, "应有账号行");
529
- const logout = find(userRow, (n) => n.type === "button" && (n.children || []).includes("退出登录"));
530
- assert.ok(logout, "退出登录应在账号行里(业主:放到账号那一行旁边)");
531
- assert.match(String(logout.props.className || ""), /dru-user-logout/);
455
+
456
+ const primary = findAll(tree, (n) => n.type === "button" && String(n.props?.className || "").includes("dru-btn-primary"));
457
+ assert.ok(primary.length <= 1, `一屏最多一个主操作,实际 ${primary.length} 个 primary 按钮`);
458
+
459
+ // 登录/退出登录同一位置:状态头右侧(`.dru-ia-head-actions`)
460
+ const headActions = find(tree, (n) => String(n.props?.className || "").split(/\s+/).includes("dru-ia-head-actions"));
461
+ assert.ok(headActions, "状态头右侧应有按钮组(登录/退出登录与打开手机端同一处)");
462
+ assert.ok(find(headActions, (n) => n.type === "button" && (n.children || []).includes("退出登录")),
463
+ "已登录时状态头应有「退出登录」");
464
+ // 账号弹层内部不再重复一个退出登录按钮
465
+ const dlg = find(tree, (n) => String(n.props?.className || "").includes("dru-ia-dialog"));
466
+ assert.equal(find(dlg, (n) => n.type === "button" && (n.children || []).includes("退出登录")), undefined,
467
+ "退出登录不得在账号弹层里重复出现(同一动作只在一个位置)");
532
468
  });
533
469
 
534
- test("★ 次要入口全部在「更多」里:带新用户 / 修改密码 / 加入交流群(展开后可见)", () => {
535
- const plugin = loadPlugin({ autoModal: "account" });
536
- plugin.states[0] = { config: { phone: "13800000000", deviceId: "dev-x" }, service: { running: true } };
470
+ test("未登录:状态头显示「登录 / 注册」;点「打开手机端」直接弹登录弹层并说明原因(A3/A4)", () => {
471
+ const plugin = loadPlugin();
472
+ // 该 harness 无 setSignedIn:直接给状态槽一个"未登录"的 st(useEffect 在本 harness 是空实现)
473
+ plugin.states[0] = { ok: true, config: {}, service: { running: false } };
537
474
  let tree = plugin.render();
538
- const toggle = find(tree, (n) => n.props?.id === "dru-account-more");
539
- assert.ok(toggle, "应有「更多」按钮");
540
- toggle.props.onClick(); // 展开
541
- tree = plugin.render();
542
- const body = find(tree, (n) => n.props?.className === "dru-more-body");
543
- assert.ok(body && body.props.hidden === false, "展开后「更多」内容体可见");
544
- const btns = [];
545
- walk(body, (n) => { if (n.type === "button") btns.push(n); });
546
- const labels = btns.map((b) => JSON.stringify(b.children || []));
547
- assert.ok(labels.some((t) => t.includes("带新用户换会员")), "带新用户换会员在「更多」里");
548
- assert.ok(labels.some((t) => t.includes("修改密码")), "修改密码在「更多」里");
475
+ const headActions = find(tree, (n) => String(n.props?.className || "").split(/\s+/).includes("dru-ia-head-actions"));
476
+ assert.ok(headActions, "未登录也要有状态头按钮组");
477
+ const loginBtn = find(headActions, (n) => n.type === "button" && (n.children || []).some((c) => typeof c === "string" && c.includes("登录 / 注册")));
478
+ assert.ok(loginBtn, "未登录时同一位置显示「登录 / 注册」入口");
479
+
480
+ // 点状态头的「打开手机端」→ 不许静默失败:直接弹登录弹层 + 说明
481
+ const openPhone = find(headActions, (n) => n.type === "button" && (n.children || []).some((c) => typeof c === "string" && c.includes("打开手机端")));
482
+ assert.ok(openPhone, "应有「打开手机端」主按钮");
483
+ openPhone.props.onClick();
484
+ tree = plugin.settle ? plugin.render() : plugin.render();
485
+ assert.ok(find(tree, (n) => String(n.props?.className || "").includes("dru-ia-dialog")), "应弹出登录弹层(不是静默失败)");
486
+ assert.ok(textHas(tree, "登录后才能用手机访问"), "弹层里要说明「登录后才能用手机访问」");
549
487
  });
550
488
 
551
489
  test("★ 删除了两段解释性文案:升级带登录态说明 / 重启 DeepSeek Harness 说明", () => {
@@ -231,7 +231,11 @@ test("源码约束:浏览器半提供版本与更新卡片(含彻底卸载
231
231
  assert.match(source, /一键更新/);
232
232
  assert.match(source, /彻底卸载/);
233
233
  assert.match(source, /dsh-remote\/self\/uninstall/);
234
- assert.match(source, /再点一次确认彻底卸载/);
234
+ // A7(2026-11-07 业主):彻底卸载前先弹**挽留反馈**(原因单选 + 建议),提交后继续卸载。
235
+ assert.match(source, /为什么要卸载/, "必须询问卸载原因(挽留反馈)");
236
+ assert.match(source, /我再想想/, "必须给「我再想想」的退出选项");
237
+ assert.match(source, /仍要卸载/, "确认按钮必须是「仍要卸载」");
238
+ assert.match(source, /kind: "uninstall"/, "反馈必须复用现有反馈通道(kind=uninstall)");
235
239
  });
236
240
 
237
241
  test("★ 用户反馈锁:已是最新版时,修复入口必须**看得出是修复入口**(不能只叫「重新检查」)", () => {
@@ -238,15 +238,19 @@ test("登录态账号区:无「切换账号」,有「退出登录」,头
238
238
  let tree = plugin.renderSection();
239
239
 
240
240
  // 栏目头部:标题「远程访问」+ 副文案(人在哪都能用,免公网 IP)
241
- assert.ok(find(tree, (n) => n.children?.includes("远程访问")), "头部标题应为「远程访问」");
242
- assert.ok(find(tree, (n) => n.children?.includes("通过手机或另一台电脑远程使用同一份 dsh web,人在哪都能用(免公网 IP)")), "应渲染新副文案(人在哪都能用,免公网 IP)");
241
+ // 2026-11-07 品牌统一:顶部面板标题改为插件全名 dsh-remote-web(「远程访问」保留在副标题/栏目名里)
242
+ assert.ok(find(tree, (n) => n.children?.includes("dsh-remote-web")), "头部标题应为品牌全名 dsh-remote-web");
243
+ assert.ok(find(tree, (n) => (n.children || []).some((c) => typeof c === "string" && c.includes("远程访问"))),
244
+ "副标题仍说明这是「远程访问」能力");
245
+ assert.ok(find(tree, (n) => (n.children || []).some((c) => typeof c === "string" && c.includes("通过手机或另一台电脑远程使用同一份 dsh web,人在哪都能用(免公网 IP)"))),
246
+ "应渲染新副文案(人在哪都能用,免公网 IP)");
243
247
 
244
248
  // 账号区按钮
245
249
  assert.ok(find(tree, (n) => n.children?.includes("退出登录")), "应保留「退出登录」");
246
250
  assert.ok(!find(tree, (n) => n.children?.includes("切换账号")), "「切换账号」按钮应移除");
247
251
 
248
252
  // 关于 dsh-remote 卡片:在「更多设置」折叠区里 —— 标题不再带 emoji(图标一律内联 SVG)
249
- assert.ok(find(tree, (n) => n.children?.includes("关于 dsh-remote")), "应渲染「关于 dsh-remote」卡片标题");
253
+ assert.ok(find(tree, (n) => n.children?.includes("关于 dsh-remote-web")), "应渲染「关于 dsh-remote-web」卡片标题");
250
254
  // 价值要点仍要在(不要求逐字长文案:IA 重做后关于卡精简成结论句,避免文字堆砌)
251
255
  const aboutBits = ["免公网 IP", "端到端加密", "匿名装机统计"];
252
256
  for (const bit of aboutBits) {
@@ -377,11 +381,15 @@ test("源码约束:侧栏入口走官方槽位/无浮动面板/无切换账号
377
381
  assert.doesNotMatch(SOURCE, /dru-backdrop\{/);
378
382
  assert.doesNotMatch(SOURCE, /切换账号/);
379
383
  assert.doesNotMatch(SOURCE, /dsh-remote-panel/);
380
- assert.doesNotMatch(SOURCE, /远程控制/); // 旧称呼「远程控制」不再出现(含 label/头部/说明)
384
+ // 旧称呼「远程控制」不再作为**面板名字**出现(label/头部/菜单)。
385
+ // 注:2026-11-07 业主新文案「保持在线,才能用手机端远程控制这台电脑」把该词当**动词**用,
386
+ // 因此不再对全文做一刀切禁止;这里锁"仍然叫远程访问"这个真正的约束。
387
+ assert.match(SOURCE, /"远程访问"/);
388
+ assert.doesNotMatch(SOURCE, /"远程控制"|远程控制(/);
381
389
  assert.match(SOURCE, /settings\.section/);
382
390
  assert.match(SOURCE, /dsh-remote-seen-dot/);
383
391
  assert.match(SOURCE, /fbClearThreads/);
384
- assert.match(SOURCE, /关于 dsh-remote/);
392
+ assert.match(SOURCE, /关于 dsh-remote-web/);
385
393
  assert.match(SOURCE, /免公网 IP/); // 关于卡 v0.5+ 价值要点
386
394
  assert.match(SOURCE, /端到端/);
387
395
  assert.match(SOURCE, /已授权设备/);
@@ -810,13 +810,13 @@ test("面板可见性:关于卡片写明「匿名统计 + 关闭方式」,
810
810
  // 现在取片边界是「关于卡片开头 → renderHome 结尾」,并**显式断言切片非空且像样**。
811
811
  // IA 重做(2026-11-07):关于卡不再用 card() 助手(标题图标改成内联 SVG,禁止 emoji)→
812
812
  // 取片锚点改成"关于卡标题 → 弹层段开头",仍然要求切片非空且真的包含卡片本体。
813
- const aboutStart = client.indexOf('h("h3", null, "关于 dsh-remote")');
813
+ const aboutStart = client.indexOf('h("h3", null, "关于 dsh-remote-web")');
814
814
  assert.ok(aboutStart > 0, "必须能定位到「关于」卡片的开头");
815
815
  const aboutEnd = client.indexOf("// ── 弹层内容", aboutStart);
816
816
  assert.ok(aboutEnd > aboutStart, "必须能定位到「关于」卡片的结束边界(弹层段开始之前)");
817
817
  const about = client.slice(aboutStart, aboutEnd);
818
818
  assert.ok(about.length > 400, "「关于」卡片切片不能是空串/碎片(空串会让下面四条断言全部平凡通过)");
819
- assert.ok(about.includes("关于 dsh-remote"), "切片必须真的包含「关于」卡片本体");
819
+ assert.ok(about.includes("关于 dsh-remote-web"), "切片必须真的包含「关于」卡片本体");
820
820
  // ⚠️ 断言的是**渲染出来的文案**:上面那条"不再单列隐私说明行"的解释性注释本身就写着这些词,
821
821
  // 所以先剥掉整行注释再断言(注释不会出现在用户屏幕上,不该被当成"面板里放了链接")。
822
822
  const aboutText = about.split("\n").filter((l) => !/^\s*\/\//.test(l)).join("\n");