@xrn1997/dsh-session-delete 0.6.0 → 0.6.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.
package/lib/client.js CHANGED
@@ -264,6 +264,7 @@ window.__ModuleLoader__.load({
264
264
  "detail.role.assistant": "助手",
265
265
  "menu.delete": "删除",
266
266
  "menu.live": "会话正在运行,先停止再删除",
267
+ "menu.deleteLive": "删除(运行中)",
267
268
  "dialog.title": "删除这个会话?",
268
269
  "dialog.subagents": "含 {n} 个子会话,将一起移入回收站。",
269
270
  "dialog.hint": "会话会移入回收站,之后可以随时恢复。彻底删除是回收站里的另一个动作。",
@@ -447,6 +448,7 @@ window.__ModuleLoader__.load({
447
448
  "detail.role.assistant": "Assistant",
448
449
  "menu.delete": "Delete",
449
450
  "menu.live": "This session is running. Stop it first.",
451
+ "menu.deleteLive": "Delete (running)",
450
452
  "dialog.title": "Delete this session?",
451
453
  "dialog.subagents": "It also moves {n} subagent sessions to the trash.",
452
454
  "dialog.hint": "The session moves to the trash and can be restored at any time. Permanent deletion is a separate action in the trash.",
@@ -477,21 +479,42 @@ window.__ModuleLoader__.load({
477
479
  /** 服务缺席时的缺省:字典里那套 zh。**也是组件 prop 的缺省值**——它表达的是"这次没有 locale 服务",
478
480
  * 与运行期 `createTranslate` 的降级分支是同一件事,不是"忘了传"。 */
479
481
  const zhTranslate = (key, params) => interpolate(ZH[key], params);
482
+ /** 探测宿主 locale 服务:缺席 / `ctx.get` 抛错都回 `undefined`,不往上抛。 */
483
+ function detectLocaleService(ctx) {
484
+ try {
485
+ const found = ctx.get?.("locale");
486
+ return found == null ? void 0 : found;
487
+ } catch {
488
+ return;
489
+ }
490
+ }
480
491
  /**
481
492
  * 注册字典并交回本插件的 `t`。服务不在时返回 `zhTranslate`(不抛、不 pending)。
482
493
  *
494
+ * **空值判据同时认 `undefined` 与 `null`**:宿主 `ctx.get` 可以回 null(同一条窄镜像的读法见
495
+ * `main-view.ts` 的 `isReadyUiWorkspace`)。这里只判 `undefined` 的版本会把 null 当成"服务在",
496
+ * 下一句 `service.register` 抛错——而本函数跑在**任何槽位注册之前**,抛出去等于整个客户端条目
497
+ * 从页面消失(`apply` 只打一条 warn)。注册 / 绑定自身失败同样退回内置 zh:文案降级远好过没有插件。
498
+ * `bind` 在注册之后才跑,它失败时字典已挂上但没人用——留着无害(宿主只是多一份不用的词典),
499
+ * 不为它补撤销路径再引入新的失败面。
500
+ *
483
501
  * 注册必须挂在 `ctx.effect` 上:`register` 返回的 disposer 是**官方契约**(卸载时要把字典摘掉),
484
502
  * 而且同一命名空间的同一语言**只能注册一次**——effect 没挂,HMR 重挂就撞 "already has locale"。
485
503
  */
486
504
  function createTranslate(ctx) {
487
- const service = ctx.get?.("locale");
505
+ const service = detectLocaleService(ctx);
488
506
  if (service === void 0) return zhTranslate;
489
- ctx.effect(() => service.register(COPY_NS, {
490
- zh: ZH,
491
- en: EN
492
- }), "session-delete: copy dictionaries");
493
- const bound = service.bind(COPY_NS);
494
- return (key, params) => bound(key, params);
507
+ try {
508
+ ctx.effect(() => service.register(COPY_NS, {
509
+ zh: ZH,
510
+ en: EN
511
+ }), "session-delete: copy dictionaries");
512
+ const bound = service.bind(COPY_NS);
513
+ return (key, params) => bound(key, params);
514
+ } catch (error) {
515
+ console.warn("[dsh-session-delete] locale 服务不可用,退回内置中文:", error);
516
+ return zhTranslate;
517
+ }
495
518
  }
496
519
  /**
497
520
  * `MarkdownBody`(`markdown.tsx`)memo comparator 的**语言探针**:借一句固定键的译文,当
@@ -984,6 +1007,38 @@ window.__ModuleLoader__.load({
984
1007
  return encoded.replace(/~([0-9A-F]{4})/g, (_whole, hex) => String.fromCharCode(parseInt(hex, 16)));
985
1008
  }
986
1009
  //#endregion
1010
+ //#region src/client/api-base.ts
1011
+ /**
1012
+ * 取数路径的挂载点:官方 `/api` 前缀 + **当前文档所在的目录**。
1013
+ *
1014
+ * 为什么不能写死根绝对路径:宿主自己的客户端模块按「app-directory-relative form」下发 bundle 与
1015
+ * chunk 描述符,浏览器在**自己的挂载点**下解析(`dsh-client-modules` README 原文:*"which a browser
1016
+ * document resolves under its own mount"*);同一个部署可以被反代到 `/dsh/`,此时 `/api/...` 会打到
1017
+ * 反代根、而不是宿主。路由注册侧仍然必须写 `/api/...`(宿主 `connection.fetch.register` 用
1018
+ * `endpointFromPath('/api', path)` 校验),所以本文件只给**浏览器半**投影一层挂载点前缀。
1019
+ *
1020
+ * 根部署(`http://host/`)下 `mountPathOf` 返回空串,拼出来的 URL 与旧版逐字相同;只有挂载部署才
1021
+ * 多出 `/dsh` 这一段。目录取自 `new URL('.', baseURI)`:页面是 `/dsh/`、`/dsh/index.html` 都归到
1022
+ * `/dsh/`;`about:blank` 这类不透明 base 退回根路径(此时相对地址本来也无法成立)。
1023
+ */
1024
+ /** `baseURI` 所在目录的路径,去掉尾斜杠;根路径回空串。 */
1025
+ function mountPathOf(baseURI) {
1026
+ try {
1027
+ const dir = new URL(".", baseURI).pathname;
1028
+ return dir === "/" ? "" : dir.replace(/\/$/, "");
1029
+ } catch {
1030
+ return "";
1031
+ }
1032
+ }
1033
+ /** 一段相对段名 → 宿主 `/api` 前缀 + 挂载点。`path` 以 `/` 开头,与 `ROUTES` 的形状一致。 */
1034
+ function apiPathOf(path, baseURI) {
1035
+ return `${mountPathOf(baseURI)}${OFFICIAL_SESSION_DELETE_API_PREFIX}${path}`;
1036
+ }
1037
+ /** 当前文档的取数路径;Node 侧(无 `document`)退回根路径。 */
1038
+ function apiPath(path) {
1039
+ return apiPathOf(path, typeof document === "undefined" ? "http://localhost/" : document.baseURI);
1040
+ }
1041
+ //#endregion
987
1042
  //#region src/client/receipt.ts
988
1043
  /** 浏览器半自己的未分类诊断码(通道打不通 / 非信封 / 单条协议错)。它不过线,线上失败码仍归 `shared/wire.ts`。 */
989
1044
  const CLIENT_UNKNOWN_CODE = "client/unknown";
@@ -1212,10 +1267,12 @@ window.__ModuleLoader__.load({
1212
1267
  * 没有旧前缀 fallback——支持面是 web UI 与桌面端,二者的标准组合都装 `client-connection`。
1213
1268
  * 官方 remote.<namespace> 仍不可用(第三方拿不到 Typert 描述符的动态发现挂载点)。
1214
1269
  * - 九段的**段名与方法**住 `src/shared/wire.ts` 的结构化段名表(`ROUTES` 每项 `{ path, method }`),
1215
- * 本文件只投影路径(`prefix + ROUTES.x.path`)与方法(GET 段直接取 `ROUTES.x.method`,写段把它
1216
- * 交给 `post()`)——方法字面量不在本文件出现第二份;
1217
- * 写动作统一走 apply;`attachment` 是唯一二进制段,由 `src/client/attachment-view.tsx` 直接拼地址,
1218
- * 不经过本文件的信封通道;`attachment-orphans`(纯读)与 `attachment-clean`(聚合写)是孤儿附件的两段。
1270
+ * 本文件只投影段名与方法(GET 段直接取 `ROUTES.x.method`,写段把它交给 `post()`)——方法字面量
1271
+ * 不在本文件出现第二份;取数地址(官方 `/api` 前缀 + 文档挂载点)归 `src/client/api-base.ts`
1272
+ * (根部署下与旧版逐字相同)。
1273
+ * 写动作统一走 apply;`attachment` 是唯一二进制段,由 `src/client/attachment-view.tsx` 经同一投影
1274
+ * 拼地址,不经过本文件的信封通道;`attachment-orphans`(纯读)与 `attachment-clean`(聚合写)是孤儿
1275
+ * 附件的两段。
1219
1276
  * - 信封与官方 unary 面同形:{ok:true,value} / {ok:false,error}。HTTP 往返本身失败才 reject;
1220
1277
  * 业务失败以信封返回,由 unwrapRemoteResult 在注入面抛出。
1221
1278
  * - error 在线上是 {code,message,details?} 普通对象;判别依据 isDSHRemoteError 住在
@@ -1311,9 +1368,11 @@ window.__ModuleLoader__.load({
1311
1368
  return body;
1312
1369
  }
1313
1370
  /**
1314
- * exact Fetch 路由的 fetch 实现。**相对路径**:不拼 host、不认 origin——页面本身就是从宿主页面
1315
- * 加载的,同源是这条通道的全部前提;鉴权归宿主 `/api` bridge(ADR-0033)。所以这条通道**只对**
1316
- * 宿主页面打开过、带宿主身份的客户端成立(凭据是那次访问换来的 cookie,不是插件自己的秘密)。
1371
+ * exact Fetch 路由的 fetch 实现。**同源、带挂载点前缀**:不拼 host、不认 origin——页面本身就是从
1372
+ * 宿主页面加载的,同源是这条通道的全部前提;路径只由 `api-base.ts` 从 `document.baseURI` 的目录
1373
+ * 与官方 `/api` 前缀拼出(根部署下与旧版逐字相同,反代到 `/dsh/` 时得到 `/dsh/api/...`)。
1374
+ * 鉴权归宿主 `/api` bridge(ADR-0033)。所以这条通道**只对**宿主页面打开过、带宿主身份的客户端
1375
+ * 成立(凭据是那次访问换来的 cookie,不是插件自己的秘密)。
1317
1376
  */
1318
1377
  function createHttpRemote() {
1319
1378
  const post = (route, body) => ({
@@ -1322,20 +1381,20 @@ window.__ModuleLoader__.load({
1322
1381
  body: JSON.stringify(body)
1323
1382
  });
1324
1383
  return {
1325
- list: () => requestEnvelope(OFFICIAL_SESSION_DELETE_API_PREFIX + ROUTES.list.path, { method: ROUTES.list.method }),
1326
- ping: () => requestEnvelope(OFFICIAL_SESSION_DELETE_API_PREFIX + ROUTES.ping.path, { method: ROUTES.ping.method }),
1327
- orphans: () => requestEnvelope(OFFICIAL_SESSION_DELETE_API_PREFIX + ROUTES.orphans.path, { method: ROUTES.orphans.method }),
1328
- attachmentOrphans: () => requestEnvelope(OFFICIAL_SESSION_DELETE_API_PREFIX + ROUTES.attachmentOrphans.path, { method: ROUTES.attachmentOrphans.method }),
1329
- attachmentClean: () => requestEnvelope(OFFICIAL_SESSION_DELETE_API_PREFIX + ROUTES.attachmentClean.path, post(ROUTES.attachmentClean, {})),
1330
- detail: (entryId, before) => requestEnvelope(OFFICIAL_SESSION_DELETE_API_PREFIX + ROUTES.detail.path, post(ROUTES.detail, {
1384
+ list: () => requestEnvelope(apiPath(ROUTES.list.path), { method: ROUTES.list.method }),
1385
+ ping: () => requestEnvelope(apiPath(ROUTES.ping.path), { method: ROUTES.ping.method }),
1386
+ orphans: () => requestEnvelope(apiPath(ROUTES.orphans.path), { method: ROUTES.orphans.method }),
1387
+ attachmentOrphans: () => requestEnvelope(apiPath(ROUTES.attachmentOrphans.path), { method: ROUTES.attachmentOrphans.method }),
1388
+ attachmentClean: () => requestEnvelope(apiPath(ROUTES.attachmentClean.path), post(ROUTES.attachmentClean, {})),
1389
+ detail: (entryId, before) => requestEnvelope(apiPath(ROUTES.detail.path), post(ROUTES.detail, {
1331
1390
  [PARAMS.entryId]: entryId,
1332
1391
  ...before === void 0 ? {} : { [PARAMS.before]: before }
1333
1392
  })),
1334
- apply: (action, ids) => requestEnvelope(OFFICIAL_SESSION_DELETE_API_PREFIX + ROUTES.apply.path, post(ROUTES.apply, {
1393
+ apply: (action, ids) => requestEnvelope(apiPath(ROUTES.apply.path), post(ROUTES.apply, {
1335
1394
  [PARAMS.action]: action,
1336
1395
  [PARAMS.ids]: [...ids]
1337
1396
  })),
1338
- empty: () => requestEnvelope(OFFICIAL_SESSION_DELETE_API_PREFIX + ROUTES.empty.path, post(ROUTES.empty, {}))
1397
+ empty: () => requestEnvelope(apiPath(ROUTES.empty.path), post(ROUTES.empty, {}))
1339
1398
  };
1340
1399
  }
1341
1400
  /**
@@ -1425,6 +1484,9 @@ window.__ModuleLoader__.load({
1425
1484
  function getPicks() {
1426
1485
  return getSelection().picks;
1427
1486
  }
1487
+ function isSelecting() {
1488
+ return getSelection().selecting;
1489
+ }
1428
1490
  /** 供组件订阅:模式与名单是同一次交互的两半,读它们的组件订同一份快照。 */
1429
1491
  function useSelection() {
1430
1492
  return selectionStore.use();
@@ -1650,10 +1712,10 @@ window.__ModuleLoader__.load({
1650
1712
  }
1651
1713
  //#endregion
1652
1714
  //#region src/client/ui/ui.css?dsh-css-text
1653
- var ui_default = "/*\r\n * 本插件自己的控件样式(**不再 require 宿主的 `@deepseek-ai/dsh-client-ui-primitives`**)。\r\n *\r\n * 出处:逐条抄自 `@deepseek-ai/dsh-client-ui-primitives@0.2.0-rc.2` 随包发布的明文 CSS\r\n * (`lib/Button.module.css`、`Tag.module.css`、`Modal.module.css`、`Tooltip.module.css`、\r\n * `Toast.module.css`、`Menu.module.css`;2026-10-15 又加两张:`lib/Checkbox.module.css`、\r\n * `lib/DisclosureRow.module.css`,以及 `dsh-client-ui-conversation` 里的\r\n * `ConversationRoot.module.css` 的 `.widthHandle` 那一族——宿主自己那根可拖的宽度把手;\r\n * 2026-10-17 又加一张,也是首张来自 ui-primitives 之外的包:`dsh-client-ui-workspace` 的\r\n * `Rows.module.css` 的 `.iconButton` 那一族——会话行尾悬停排那颗按钮的基底(行尾兜底勾选开关\r\n * 要跟宿主自己的归档/置顶同形)。\r\n * 这是官方给第三方 UI 插件的处方:\r\n * 「copy markup, CSS, and behavior from the primitive into the plugin … Rename copied classes under\r\n * your plugin's prefix, keep only `--dsw-alias-*` token references」(宿主技能\r\n * `cordis-plugin-development/references/practices.md` 原文)。理由是那个包**随宿主换代而变、且不经类型检查**,\r\n * 一个抛错的组件会把整个槽位条目打空(控制台 `slot entry crashed in '<slot>'`)。\r\n *\r\n * 三条改写规则:\r\n * 1. **类名全部加本插件前缀 `sd-`**(原来是 CSS module 的哈希名,全局无冲突由前缀承担)。\r\n * 2. **配色一律走 token**,一个颜色的字面量都没有——所以宿主换主题、改名 token 时最差是难看,\r\n * 不会崩。几何量(圆角/阴影/层级)同样取 token;**只有字号/行高/尺寸是字面量 px**,与官方 CSS 逐字一致。\r\n * 3. **`@keyframes` 全部改名**(`sd-` 前缀):它们是全局命名空间,撞名会互相覆盖。\r\n *\r\n * 只收本插件真正用到的部分:Button 的 `primary`/`ghost`/`outline` 三档与 `md`/`sm` 两号\r\n * (没有 `toolbar` 变体、没有 `lg`)、Tag 的 `danger` 一档、Modal 的全部骨架、Tooltip 的三个方位、\r\n * Toast 全部、Menu 行的全部(含 danger 态与 separator)、Checkbox 的全部、DisclosureRow 的\r\n * 折叠头那一半(没有 `data-state` 那档状态点)、宽度把手的视觉与光标。**没抄进来的就是本插件不用的**,\r\n * 不预先铺开——宿主那套里还有几十个变体与控件,抄全了等于把它的表面积整个搬进来。\r\n *\r\n * **七处不是抄本**:① `.sd-resizing`(拖动期间挂在面板根上、关掉文字选中)——宿主那份没有对应的类,\r\n * 它的把手由 JS 侧的 pointer capture 承担;② `[data-dsh-session-delete=\"trash-row\"]:focus-visible`\r\n * (清单那一行的键盘焦点描边,复用选中态那枚 `ghost-active-border`)——宿主那套表里行不是可聚焦\r\n * div,我们这一行是 `role=\"row\"` 且要吃 ↑↓,而这一代 `--dsw-focus-ring-color` 是 `transparent`;\r\n * ③ `.sd-rowaction-button` 的 `[aria-pressed='true']` 与 `:disabled` 两档(2026-10-17)——\r\n * 官方那颗 `iconButton` 只有常态与 hover,勾选开关要有\"按下\"和\"这条删不动\"两档,token 与回收站\r\n * 选中行同源(`--dsw-alias-button-ghost-active-*`);④ `.sd-batch-bar`(同日)——宿主没有\"浮条\"\r\n * 这件控件,四件 token 自拼;⑤ 宿主行的批量选中底色与兜底隐藏(同日改版)——宿主的行选中态不归\r\n * 第三方,整行可见性靠 `data-row-key` + `:has`,token 与回收站选中行同源;行首格在场时行尾兜底退场;\r\n * ⑥ `.sd-batch-pick-leading`(同日)——宿主行首那格只给 16×20,勾选框要一层 inline-flex 包装才压得回\r\n * 同样大小,官方没有这层包装;⑦ `.sd-toast-action:disabled`(2026-11-01)——撤销横幅在飞时那颗动作要\r\n * 看着就按不动,官方 Toast 的 action 没有禁用档,而我们这份自带 `color`、浏览器默认置灰不生效,\r\n * 档位与③的禁用档同源(`opacity` + `not-allowed`)。\r\n * 整张表里就这七条是我们自造的,标在这里,\r\n * 下次做「与官方逐条比对」时它属于**已知差异**,不是漂移。\r\n *\r\n * **另有一处已知差异不住在本文件**:回收站组头那颗按钮的整套 UA reset。宿主 `DisclosureRow` 那份是\r\n * `div`,没有这个坑;我们这行要做成可点的 `<button>`,UA 样式表会把背景、边框、内距、字体、居中与\r\n * flex 基线一起渗进来(真机上就是项目名整条居中,2026-10-15 截图)。它写在\r\n * `src/client/trash-panel.tsx` 组头那一行的 inline 样式上,**具体 reset 了哪几项由\r\n * `tests/skeleton.test.ts` 逐条钉住**(不在这里再抄一份名单)——所以「与官方逐条比对」时本表仍然只数\r\n * 上面那些,这条不算进抄本差异。\r\n */\r\n\r\n/* ────────────────────────── Button ────────────────────────── */\r\n\r\n.sd-button {\r\n box-sizing: border-box;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 4px;\r\n border: none;\r\n border-radius: var(--dsw-radius-md);\r\n cursor: pointer;\r\n font-size: 14px;\r\n line-height: 22px;\r\n color: var(--dsw-alias-label-primary);\r\n background: transparent;\r\n padding: 0 14px;\r\n}\r\n\r\n.sd-button:disabled {\r\n cursor: not-allowed;\r\n opacity: 0.4;\r\n}\r\n\r\n.sd-button-md {\r\n height: 36px;\r\n}\r\n\r\n/* 紧凑档:尺寸不随填充/描边换档(官方原文的注释即此意)。 */\r\n.sd-button-sm {\r\n height: 28px;\r\n font-size: 12px;\r\n line-height: 18px;\r\n padding: 0 10px;\r\n border-radius: var(--dsw-radius-sm);\r\n}\r\n\r\n.sd-button-primary {\r\n background: var(--dsw-alias-button-primary-fill);\r\n color: var(--dsw-alias-label-primary-foreground);\r\n}\r\n\r\n.sd-button-primary:hover:not(:disabled) {\r\n background: var(--dsw-alias-button-primary-hover);\r\n}\r\n\r\n.sd-button-ghost:hover:not(:disabled) {\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n}\r\n\r\n.sd-button-ghost:active:not(:disabled) {\r\n background: var(--dsw-alias-interactive-bg-active);\r\n}\r\n\r\n.sd-button-outline {\r\n border: 0.5px solid var(--dsw-alias-border-l3);\r\n background: transparent;\r\n}\r\n\r\n.sd-button-outline:hover:not(:disabled) {\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n}\r\n\r\n.sd-button-icon {\r\n display: inline-flex;\r\n width: 16px;\r\n height: 16px;\r\n align-items: center;\r\n justify-content: center;\r\n}\r\n\r\n/* ────────────────────────── Tag ────────────────────────── */\r\n\r\n/* 胶囊几何固定:一个 tag 在哪里都是同一个尺寸,只有色板随 tone 走。 */\r\n.sd-tag {\r\n display: inline-flex;\r\n align-items: center;\r\n border-radius: 999px;\r\n corner-shape: round;\r\n padding: 1px 8px;\r\n font-size: 11px;\r\n line-height: 17px;\r\n font-weight: 500;\r\n white-space: nowrap;\r\n}\r\n\r\n/* 状态色自带填充:底色由自身颜色 10% 混出,换色板时底与字一起走,不需要第二个 token。 */\r\n.sd-tag-danger {\r\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);\r\n color: var(--dsw-alias-state-error-primary);\r\n}\r\n\r\n/* ────────────────────────── Modal ────────────────────────── */\r\n\r\n/* 整视口层(figma Mask + Dialog):蒙层 + 居中卡片。四边留 24px,上下按外壳公布的浮层内缩加宽,\r\n 高卡片才不会顶到外壳。卡片按这个内边距盒取尺寸:消费方用 `max-height:100%` 收口,不自己算视口。 */\r\n.sd-modal-root {\r\n pointer-events: auto;\r\n position: fixed;\r\n inset: 0;\r\n z-index: 1000;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n padding: max(24px, var(--dsh-frame-overlay-top, 24px)) 24px;\r\n}\r\n\r\n/* 只有绘制避让原生标题栏;根仍然挡住背景点击。 */\r\n.sd-modal-mask {\r\n position: absolute;\r\n inset: var(--dsh-frame-chrome-top, 0px) 0 0;\r\n backdrop-filter: var(--dsw-mask-blur);\r\n}\r\n\r\n.sd-modal-mask::after {\r\n content: '';\r\n position: absolute;\r\n inset: 0;\r\n background: var(--dsw-alias-bg-mask-1);\r\n animation: sd-modal-enter var(--ds-transition-duration) var(--ds-ease-in-out);\r\n}\r\n\r\n.sd-modal-dialog {\r\n box-sizing: border-box;\r\n position: relative;\r\n z-index: 1;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 20px;\r\n width: min(380px, 100%);\r\n padding: 0 0 24px;\r\n overflow: hidden;\r\n border: 0;\r\n border-radius: var(--dsw-radius-panel);\r\n background: var(--dsw-alias-bg-layer-2);\r\n box-shadow: var(--dsw-elevation-prominent);\r\n animation: sd-modal-enter var(--ds-transition-duration) var(--ds-ease-in-out);\r\n}\r\n\r\n@keyframes sd-modal-enter {\r\n from { opacity: 0; }\r\n to { opacity: 1; }\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .sd-modal-mask::after,\r\n .sd-modal-dialog { animation: none; }\r\n}\r\n\r\n.sd-modal-dialog:focus {\r\n outline: none;\r\n}\r\n\r\n.sd-modal-content {\r\n display: flex;\r\n flex-direction: column;\r\n width: 100%;\r\n}\r\n\r\n/* 头行:标题左、关闭钮右。 */\r\n.sd-modal-header {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n gap: 8px;\r\n padding: 22px 14px 12px 24px;\r\n}\r\n\r\n.sd-modal-title {\r\n margin: 0;\r\n font-size: 16px;\r\n line-height: 24px;\r\n font-weight: 500;\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.sd-modal-close {\r\n flex: none;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 28px;\r\n height: 28px;\r\n border: none;\r\n border-radius: var(--dsw-radius-sm);\r\n background: transparent;\r\n cursor: pointer;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n.sd-modal-close:hover {\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n}\r\n\r\n.sd-modal-body {\r\n display: flex;\r\n flex-direction: column;\r\n min-width: 0;\r\n margin-top: 20px;\r\n padding: 0 24px;\r\n}\r\n\r\n.sd-modal-footer {\r\n display: flex;\r\n align-items: center;\r\n justify-content: flex-end;\r\n gap: 8px;\r\n padding: 0 24px;\r\n}\r\n\r\n/* ────────────────────────── Tooltip ────────────────────────── */\r\n\r\n.sd-tooltip-bubble {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 8px;\r\n position: fixed;\r\n z-index: 100;\r\n /* 定宽收缩只按 `left` 到视口右缘量,靠右的锚点会提前折行;\r\n `max-content` 只按文案定宽,再用半个视口收口。 */\r\n width: max-content;\r\n max-width: 50vw;\r\n padding: 3px 7px;\r\n border-radius: var(--dsw-radius-sm);\r\n background: var(--dsw-alias-tooltip-bg);\r\n color: var(--dsw-static-neutral-bluish-00);\r\n font-size: 13px;\r\n line-height: 20px;\r\n white-space: pre-line;\r\n /* 不可断的长 token(URL、路径)不许顶破 max-width。 */\r\n overflow-wrap: break-word;\r\n pointer-events: none;\r\n animation: sd-tooltip-in 150ms var(--ds-ease-in-out);\r\n}\r\n\r\n.sd-tooltip-bubble[data-portal] { z-index: 1100; }\r\n.sd-tooltip-label { min-width: 0; }\r\n\r\n.sd-tooltip-bubble[data-side='right'] {\r\n transform: translateY(-50%);\r\n}\r\n\r\n.sd-tooltip-bubble[data-side='bottom'] {\r\n transform: translateX(-50%);\r\n}\r\n\r\n.sd-tooltip-bubble[data-side='top'] {\r\n transform: translate(-50%, -100%);\r\n}\r\n\r\n@keyframes sd-tooltip-in {\r\n from { opacity: 0; }\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .sd-tooltip-bubble {\r\n animation: none;\r\n }\r\n}\r\n\r\n/* ────────────────────────── Toast ────────────────────────── */\r\n\r\n/* 淡出延迟由组件以 `--dsh-toast-hold` 给出 ⇒ 同一个值同时驱动卸载计时器与这段动画,\r\n 两者不可能再对不上。淡出**时长**仍需与组件里的 FADE_MS 一致(没有 owner 会改它)。 */\r\n.sd-toast {\r\n position: fixed;\r\n top: 40px;\r\n left: 50%;\r\n /* 高过图片灯箱底层的 1000:预览开着时报错也必须读得到。 */\r\n z-index: 1100;\r\n /* 通告不截点击。 */\r\n pointer-events: none;\r\n display: flex;\r\n align-items: center;\r\n gap: 10px;\r\n /* 定宽盒设了 `left` 只按右侧空间收缩(-50% 的位移发生在定尺之后)⇒ 靠窗边的横幅会提前折行。\r\n `max-content` 只按文案定宽,再由下面的 max-width 收口。 */\r\n width: max-content;\r\n max-width: min(640px, calc(100vw - 48px));\r\n padding: 12px 16px;\r\n border-radius: var(--dsw-radius-lg);\r\n /* 设计平台的 toast 表面在**两套主题下都是深的**(浅色 bluish-800 / 深色 bluish-750),\r\n 所以这里的字色与动作色是固定值,不随主题翻转。 */\r\n background: var(--dsw-alias-toast-bg);\r\n color: var(--dsw-alias-toast-label);\r\n font-size: 14px;\r\n line-height: 22px;\r\n box-shadow: var(--dsw-shadow-lv3);\r\n transform: translateX(-50%);\r\n animation:\r\n sd-toast-in 160ms ease-out,\r\n sd-toast-fade 1000ms ease var(--dsh-toast-hold, 3000ms) forwards;\r\n}\r\n\r\n.sd-toast-icon {\r\n display: grid;\r\n place-items: center;\r\n flex: none;\r\n color: var(--dsw-alias-state-warn-label);\r\n}\r\n\r\n.sd-toast-icon-success {\r\n color: var(--dsw-alias-state-success-primary);\r\n}\r\n\r\n.sd-toast-text {\r\n min-width: 0;\r\n}\r\n\r\n.sd-toast-action {\r\n display: inline;\r\n padding: 0;\r\n border: 0;\r\n background: none;\r\n color: var(--dsw-static-deepseek-400);\r\n font: inherit;\r\n cursor: pointer;\r\n /* 动作文字与周围文案之间的呼吸位。 */\r\n margin-inline: 3px;\r\n /* 横幅表面整体是穿透点击的,只有动作文字接指针。 */\r\n pointer-events: auto;\r\n}\r\n\r\n.sd-toast-action:hover {\r\n opacity: 0.8;\r\n}\r\n\r\n/* 本插件自造的第七条(不是抄本,见文件头点名):撤销横幅在飞时那颗动作要**看着就按不动**。\r\n 官方 Toast 的 action 只有常态与 hover,没有禁用档;而 `.sd-toast-action` 自带 `color`,\r\n 浏览器默认的置灰根本不生效(真机上只会看见一个光标照常是手、点下去没反应的「撤销」)。\r\n 禁用档与行尾勾选开关同源(`opacity` + `not-allowed`),不新造颜色。 */\r\n.sd-toast-action:disabled {\r\n opacity: 0.4;\r\n cursor: not-allowed;\r\n}\r\n\r\n@keyframes sd-toast-in {\r\n from {\r\n opacity: 0;\r\n transform: translate(-50%, -6px);\r\n }\r\n\r\n to {\r\n opacity: 1;\r\n transform: translate(-50%, 0);\r\n }\r\n}\r\n\r\n@keyframes sd-toast-fade {\r\n to {\r\n opacity: 0;\r\n visibility: hidden;\r\n }\r\n}\r\n\r\n/* 减动效去掉滑入;延迟淡出(透明度变化、不是位移)仍然在定时卸载前收掉横幅。 */\r\n@media (prefers-reduced-motion: reduce) {\r\n .sd-toast {\r\n animation: sd-toast-fade 1000ms ease var(--dsh-toast-hold, 3000ms) forwards;\r\n }\r\n}\r\n\r\n/* ────────────────────────── 菜单行 ────────────────────────── */\r\n\r\n.sd-menu-item-wrap {\r\n position: relative;\r\n}\r\n\r\n/* 菜单格:紧凑文字、图标与选中间距。 */\r\n.sd-menu-item {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n width: 100%;\r\n min-height: 34px;\r\n padding: 6px 8px;\r\n border: none;\r\n border-radius: var(--dsw-radius-md);\r\n background: transparent;\r\n cursor: pointer;\r\n font-size: 13px;\r\n line-height: 20px;\r\n color: var(--dsw-alias-label-primary);\r\n text-align: left;\r\n}\r\n\r\n.sd-menu-item:hover:not(:disabled) {\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n}\r\n\r\n/* 方向键移动的是真焦点 ⇒ 键盘所在的那一行拿指针同款填充;填充就是这一行的焦点呈现,\r\n 浏览器默认的 ring 再叠一层就重了。 */\r\n.sd-menu-item:focus-visible:not(:disabled) {\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n outline: none;\r\n}\r\n\r\n.sd-menu-item:disabled {\r\n opacity: 0.4;\r\n cursor: not-allowed;\r\n}\r\n\r\n.sd-menu-item-icon {\r\n display: inline-flex;\r\n flex: none;\r\n width: 14px;\r\n height: 14px;\r\n align-items: center;\r\n justify-content: center;\r\n color: var(--dsw-alias-menu-icon);\r\n}\r\n\r\n.sd-menu-item-icon svg {\r\n width: 14px;\r\n height: 14px;\r\n}\r\n\r\n.sd-menu-item-label {\r\n flex: 1;\r\n min-width: 0;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n/* 破坏性行:error 字/图标 + danger 悬停底。 */\r\n.sd-menu-item-danger {\r\n color: var(--dsw-alias-state-error-primary);\r\n}\r\n\r\n.sd-menu-item-danger .sd-menu-item-icon {\r\n color: var(--dsw-alias-state-error-primary);\r\n}\r\n\r\n.sd-menu-item-danger:hover:not(:disabled) {\r\n background: var(--dsw-alias-interactive-bg-hover-danger);\r\n}\r\n\r\n.sd-menu-item-danger:focus-visible:not(:disabled) {\r\n background: var(--dsw-alias-interactive-bg-hover-danger);\r\n outline: none;\r\n}\r\n\r\n/* 分隔格:发丝线上下的间距。取 l2 而不是 l1——l1 在这个表面上几乎看不见。 */\r\n.sd-menu-separator {\r\n height: 0.5px;\r\n margin: 3px 2px;\r\n background: var(--dsw-alias-border-l2);\r\n}\r\n\r\n/* ────────────────────────── Checkbox ────────────────────────── */\r\n/* 抄 `lib/Checkbox.module.css` 全部四条声明,逐字。勾选框本体是**原生 input** + `accent-color`,\r\n 不是自绘的圆框——回收站多选态用它(清单行左侧那一格)。 */\r\n\r\n.sd-checkbox {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 6px;\r\n font-size: 14px;\r\n line-height: 20px;\r\n color: var(--dsw-alias-label-primary);\r\n cursor: pointer;\r\n}\r\n\r\n.sd-checkbox input {\r\n flex: 0 0 auto;\r\n width: 16px;\r\n height: 16px;\r\n margin: 0;\r\n accent-color: var(--dsw-alias-brand-primary);\r\n cursor: inherit;\r\n}\r\n\r\n.sd-checkbox input:focus-visible {\r\n outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));\r\n outline-offset: 2px;\r\n}\r\n\r\n.sd-checkbox:has(input:disabled) {\r\n cursor: default;\r\n opacity: 0.5;\r\n}\r\n\r\n/* ────────────────────────── DisclosureRow(分组折叠头) ────────────────────────── */\r\n/* 抄 `lib/DisclosureRow.module.css` 的 `.root`/`.row`/`.leading`/`.title` 与那两枚 glyph 的\r\n 显隐。没抄进来的是 `StateDot`(`svg[data-state]`)那一档——它说\"这是状态标记不是文字装饰\",\r\n 我们的组头没有状态点。行高与 leading 盒走 `--dsh-content-font-delta` 那根轴,\r\n 标题字号走 secondary 档:宿主的\"设置里调正文字号\"会一起推着它走。 */\r\n\r\n.sd-disclosure {\r\n display: flex;\r\n flex-direction: column;\r\n width: 100%;\r\n min-width: 0;\r\n}\r\n\r\n.sd-disclosure-row {\r\n position: relative;\r\n overflow: hidden;\r\n display: flex;\r\n align-items: center;\r\n height: calc(24px + var(--dsh-content-font-delta, 0px));\r\n min-width: 0;\r\n color: var(--dsw-alias-label-tertiary);\r\n transition: color 100ms ease;\r\n}\r\n\r\n.sd-disclosure-row[data-expandable] {\r\n cursor: pointer;\r\n}\r\n\r\n.sd-disclosure-row:hover {\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n.sd-disclosure-leading {\r\n position: relative;\r\n flex: none;\r\n width: calc(16px + var(--dsh-content-font-delta, 0px));\r\n height: calc(16px + var(--dsh-content-font-delta, 0px));\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n margin-right: 6px;\r\n padding: 0;\r\n border: none;\r\n background: none;\r\n color: inherit;\r\n}\r\n\r\n.sd-disclosure-leading svg:not([data-state]) {\r\n width: calc(14px + var(--dsh-content-font-delta, 0px));\r\n height: calc(14px + var(--dsh-content-font-delta, 0px));\r\n}\r\n\r\nbutton.sd-disclosure-leading {\r\n cursor: pointer;\r\n}\r\n\r\n.sd-disclosure-title {\r\n flex: none;\r\n font-size: var(--dsh-content-font-size-secondary, 13px);\r\n line-height: calc(24px + var(--dsh-content-font-delta, 0px));\r\n color: inherit;\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .sd-disclosure-row {\r\n transition: none;\r\n }\r\n}\r\n\r\n/* ────────────────────────── 宽度把手(可拖的分栏线) ────────────────────────── */\r\n/* 抄 `ConversationRoot.module.css` 的 `.widthHandle` 一族:10px 的命中区、`col-resize` 光标、\r\n 平时不画、悬停或拖动时才浮出一条 2px 的 `--dsw-alias-scrollbar-bg-l1` 短标(那条渐变里\r\n `--dsh-width-handle-pointer-y` 的兜底是 50%,宿主靠 JS 跟着指针走;我们不跟,\r\n 停在中间——少一条 pointermove 上的写字,视觉上是同一族东西)。\r\n 与宿主的差别只有位置:它绝对定位在会话栏两侧,我们这根住在两栏之间的那一格 flex 位里,\r\n 所以 `top/bottom/position` 那三样交给内联布局,这里只留视觉与光标。 */\r\n\r\n.sd-width-handle {\r\n position: relative;\r\n z-index: 0;\r\n cursor: col-resize;\r\n align-self: stretch;\r\n flex: 0 0 10px;\r\n width: 10px;\r\n padding: 0;\r\n border: none;\r\n background: none;\r\n}\r\n\r\n.sd-width-handle::after {\r\n content: \"\";\r\n background: linear-gradient(\r\n to bottom,\r\n transparent calc(var(--dsh-width-handle-pointer-y, 50%) - 36px),\r\n var(--dsw-alias-scrollbar-bg-l1) calc(var(--dsh-width-handle-pointer-y, 50%) - 8px),\r\n var(--dsw-alias-scrollbar-bg-l1) calc(var(--dsh-width-handle-pointer-y, 50%) + 8px),\r\n transparent calc(var(--dsh-width-handle-pointer-y, 50%) + 36px)\r\n );\r\n opacity: 0;\r\n pointer-events: none;\r\n border-radius: 2px;\r\n width: 2px;\r\n position: absolute;\r\n top: 0;\r\n bottom: 0;\r\n left: 4px;\r\n}\r\n\r\n.sd-width-handle:hover::after,\r\n.sd-width-handle[data-dragging]::after {\r\n opacity: 1;\r\n}\r\n\r\n.sd-width-handle:focus-visible {\r\n outline: none;\r\n}\r\n\r\n.sd-width-handle:focus-visible::after {\r\n opacity: 1;\r\n}\r\n\r\n/* 本插件自造的第一条(不是抄本,见文件头点名):拖动期间整块面板别把文字选上了。 */\r\n.sd-resizing {\r\n user-select: none;\r\n cursor: col-resize;\r\n}\r\n\r\n/* 本插件自造的第二条(不是抄本,见文件头点名):清单行是 `role=\"row\"` 的可聚焦 div,而这一代\r\n `--dsw-focus-ring-color` 逐字是 `transparent`(2026-10-15 实测)——不自己描一条,键盘走\r\n ↑↓ 选会话时就没有任何焦点可见性。描边**复用选中态那枚** `ghost-active-border`,不新造颜色;\r\n 0.5px 与 `outline-offset: 1px` 是行的描边宽度与一档外移,不动布局(outline 不进滚动区)。 */\r\n[data-dsh-session-delete=\"trash-row\"]:focus-visible {\r\n outline: 0.5px solid var(--dsw-alias-button-ghost-active-border);\r\n outline-offset: 1px;\r\n}\r\n\r\n/* 会话行尾悬停排那颗**兜底**勾选开关(状态点行 / 归档行;行首主勾选是原生 Checkbox)。\r\n 基底**逐字抄** `dsh-client-ui-workspace` 的 `Rows.module.css` 的 `.iconButton` / `.iconButton:hover`,\r\n 只改类名——兜底开关要跟同排宿主自己的归档/置顶同形,尺寸与色档都不自己发明。 */\r\n.sd-rowaction-button {\r\n border-radius: var(--dsw-radius-xs);\r\n cursor: pointer;\r\n width: 16px;\r\n height: 16px;\r\n color: var(--dsw-alias-label-tertiary);\r\n background: 0 0;\r\n border: none;\r\n flex: none;\r\n justify-content: center;\r\n align-items: center;\r\n padding: 0;\r\n display: inline-flex;\r\n}\r\n.sd-rowaction-button:hover {\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n/* 本插件自造的第三条(不是抄本,见文件头点名):官方那颗只有常态与 hover,勾选开关还要有\r\n 「已按下」与「这条删不动」两档。按下档**复用回收站选中行那两枚** `ghost-active-*`,不新造颜色;\r\n 禁用档走 opacity。 */\r\n.sd-rowaction-button[aria-pressed='true'] {\r\n background: var(--dsw-alias-button-ghost-active-fill);\r\n box-shadow: inset 0 0 0 1px var(--dsw-alias-button-ghost-active-border);\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n.sd-rowaction-button:disabled {\r\n opacity: 0.4;\r\n cursor: not-allowed;\r\n}\r\n\r\n/* 本插件自造的第⑥条(不是抄本,见文件头点名):行首 16px 格里的勾选框(2026-10-17 改版,**只在\r\n 多选模式内渲染**)。格本身由宿主居中(16×20),这里只把包装层压成同样大小;勾选框本体是回收站\r\n 多选同款原生 input(`.sd-checkbox`,见上)。 */\r\n.sd-batch-pick-leading {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n}\r\n\r\n/* 本插件自造的第⑤条(不是抄本,见文件头点名):宿主行自己的选中态只归它自己,批量选择要有整行\r\n 底色;行首格在场时行尾兜底要退场(两条入口不许同屏)。两条都只认宿主行已有的\r\n `data-row-key`;将来这个属性改名,最差是底色不亮 / 兜底多露一颗,勾选本身仍在。 */\r\n[data-row-key^=\"session:\"]:has([data-dsh-session-delete=\"batch-pick\"][data-checked=\"true\"]) {\r\n background: var(--dsw-alias-button-ghost-active-fill);\r\n box-shadow: inset 0 0 0 0.5px var(--dsw-alias-button-ghost-active-border);\r\n}\r\n[data-row-key^=\"session:\"]:has([data-dsh-session-delete=\"batch-pick\"][data-seat=\"leading\"]) .sd-batch-pick-action {\r\n display: none;\r\n}\r\n\r\n/* 本插件自造的第四条(不是抄本,见文件头点名):批量删除的浮条。宿主没有\"浮条\"这件控件,形状照\r\n 本插件自己的浮层口径来——固定底部居中,表面/描边/圆角/高程四件全走 token(与单删那张目标卡同源)。\r\n **层级必须低于确认框(`.sd-modal-root` 1000)**:2026-10-17 真机发现浮条在 1100 时会盖住确认框、\r\n 还能抢它的按钮;撤销横幅与 Tooltip 是瞬时通告、保持 1100。列方向:清单在上、操作行在下;\r\n 清单限高可滚,默认收起(见 `batch-bar.tsx`)。**宽度按内容展开**(`max-content`,最多视口减 48):\r\n 只写 `left:50% + width:auto` 时 shrink-to-fit 只拿左半屏,新增「全选/取消全选」后会把「退出多选」\r\n 挤到第二行;窄到 max-width 还放不下才由 `.sd-batch-bar-main` 的 `flex-wrap` 兜底。 */\r\n.sd-batch-bar {\r\n position: fixed;\r\n bottom: 24px;\r\n left: 50%;\r\n transform: translateX(-50%);\r\n z-index: 900;\r\n width: max-content;\r\n max-width: calc(100vw - 48px);\r\n display: flex;\r\n flex-direction: column;\r\n align-items: stretch;\r\n gap: 8px;\r\n padding: 8px 12px;\r\n background: var(--dsw-alias-bg-layer-2);\r\n border: 0.5px solid var(--dsw-alias-border-l4);\r\n border-radius: var(--dsw-radius-lg);\r\n box-shadow: var(--dsw-elevation-prominent);\r\n font-size: 12px;\r\n line-height: 18px;\r\n}\r\n\r\n.sd-batch-bar-main {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n flex-wrap: wrap;\r\n}\r\n\r\n.sd-batch-bar-hint {\r\n color: var(--dsw-alias-label-tertiary);\r\n}\r\n\r\n.sd-batch-bar-list-head {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n flex-wrap: wrap;\r\n}\r\n\r\n/* 汇总的第二句(含 K 个子会话)比条数轻一档,和列表行里的 chip 同级。 */\r\n.sd-batch-bar-list-head span + span {\r\n color: var(--dsw-alias-label-tertiary);\r\n}\r\n\r\n.sd-batch-bar-list {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 2px;\r\n max-height: 240px;\r\n overflow: auto;\r\n margin: 0;\r\n padding: 0 0 8px;\r\n border-bottom: 0.5px solid var(--dsw-alias-border-l4);\r\n list-style: none;\r\n}\r\n\r\n.sd-batch-bar-item {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n min-width: 0;\r\n}\r\n\r\n.sd-batch-bar-title {\r\n flex: 1;\r\n min-width: 0;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n.sd-batch-bar-meta {\r\n flex: none;\r\n color: var(--dsw-alias-label-tertiary);\r\n}\r\n\r\n.sd-batch-bar-remove {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n flex: none;\r\n width: 16px;\r\n height: 16px;\r\n padding: 0;\r\n border: none;\r\n border-radius: var(--dsw-radius-xs);\r\n background: 0 0;\r\n color: var(--dsw-alias-label-tertiary);\r\n cursor: pointer;\r\n}\r\n\r\n.sd-batch-bar-remove:hover {\r\n color: var(--dsw-alias-label-primary);\r\n background: var(--dsw-alias-interactive-bg-hover);\r\n}\r\n";
1715
+ var ui_default = "/*\r\n * 本插件自己的控件样式(**不再 require 宿主的 `@deepseek-ai/dsh-client-ui-primitives`**)。\r\n *\r\n * 出处:逐条抄自 `@deepseek-ai/dsh-client-ui-primitives@0.2.0-rc.2` 随包发布的明文 CSS\r\n * (`lib/Button.module.css`、`Tag.module.css`、`Modal.module.css`、`Tooltip.module.css`、\r\n * `Toast.module.css`、`Menu.module.css`;2026-10-15 又加两张:`lib/Checkbox.module.css`、\r\n * `lib/DisclosureRow.module.css`,以及 `dsh-client-ui-conversation` 里的\r\n * `ConversationRoot.module.css` 的 `.widthHandle` 那一族——宿主自己那根可拖的宽度把手;\r\n * 2026-10-17 又加一张,也是首张来自 ui-primitives 之外的包:`dsh-client-ui-workspace` 的\r\n * `Rows.module.css` 的 `.iconButton` 那一族——会话行尾悬停排那颗按钮的基底(行尾兜底勾选开关\r\n * 要跟宿主自己的归档/置顶同形)。\r\n * 这是官方给第三方 UI 插件的处方:\r\n * 「copy markup, CSS, and behavior from the primitive into the plugin … Rename copied classes under\r\n * your plugin's prefix, keep only `--dsw-alias-*` token references」(宿主技能\r\n * `cordis-plugin-development/references/practices.md` 原文)。理由是那个包**随宿主换代而变、且不经类型检查**,\r\n * 一个抛错的组件会把整个槽位条目打空(控制台 `slot entry crashed in '<slot>'`)。\r\n *\r\n * 三条改写规则:\r\n * 1. **类名全部加本插件前缀 `sd-`**(原来是 CSS module 的哈希名,全局无冲突由前缀承担)。\r\n * 2. **配色一律走 token**,一个颜色的字面量都没有——所以宿主换主题、改名 token 时最差是难看,\r\n * 不会崩。几何量(圆角/阴影/层级)同样取 token;**只有字号/行高/尺寸是字面量 px**,与官方 CSS 逐字一致。\r\n * 3. **`@keyframes` 全部改名**(`sd-` 前缀):它们是全局命名空间,撞名会互相覆盖。\r\n *\r\n * 只收本插件真正用到的部分:Button 的 `primary`/`ghost`/`outline` 三档与 `md`/`sm` 两号\r\n * (没有 `toolbar` 变体、没有 `lg`)、Tag 的 `danger` 一档、Modal 的全部骨架、Tooltip 的三个方位、\r\n * Toast 全部、Menu 行的全部(含 danger 态与 separator)、Checkbox 的全部、DisclosureRow 的\r\n * 折叠头那一半(没有 `data-state` 那档状态点)、宽度把手的视觉与光标。**没抄进来的就是本插件不用的**,\r\n * 不预先铺开——宿主那套里还有几十个变体与控件,抄全了等于把它的表面积整个搬进来。\r\n *\r\n * **七处不是抄本**:① `.sd-resizing`(拖动期间挂在面板根上、关掉文字选中)——宿主那份没有对应的类,\r\n * 它的把手由 JS 侧的 pointer capture 承担;② `[data-dsh-session-delete=\"trash-row\"]:focus-visible`\r\n * (清单那一行的键盘焦点描边,复用选中态那枚 `ghost-active-border`)——宿主那套表里行不是可聚焦\r\n * div,我们这一行是 `role=\"row\"` 且要吃 ↑↓,而这一代 `--dsw-focus-ring-color` 是 `transparent`;\r\n * ③ `.sd-rowaction-button` 的 `[aria-pressed='true']` 与 `:disabled` 两档(2026-10-17)——\r\n * 官方那颗 `iconButton` 只有常态与 hover,勾选开关要有\"按下\"和\"这条删不动\"两档,token 与回收站\r\n * 选中行同源(`--sd-button-ghost-active-*`,别名到 `--dsw-alias-button-ghost-active-*`);④ `.sd-batch-bar`(同日)——宿主没有\"浮条\"\r\n * 这件控件,四件 token 自拼;⑤ 宿主行的批量选中底色与兜底隐藏(同日改版)——宿主的行选中态不归\r\n * 第三方,整行可见性靠 `data-row-key` + `:has`,token 与回收站选中行同源;行首格在场时行尾兜底退场;\r\n * ⑥ `.sd-batch-pick-leading`(同日)——宿主行首那格只给 16×20,勾选框要一层 inline-flex 包装才压得回\r\n * 同样大小,官方没有这层包装;⑦ `.sd-toast-action:disabled`(2026-11-01)——撤销横幅在飞时那颗动作要\r\n * 看着就按不动,官方 Toast 的 action 没有禁用档,而我们这份自带 `color`、浏览器默认置灰不生效,\r\n * 档位与③的禁用档同源(`opacity` + `not-allowed`)。\r\n * 整张表里就这七条是我们自造的,标在这里,\r\n * 下次做「与官方逐条比对」时它属于**已知差异**,不是漂移。\r\n *\r\n * **还有一处系统性改写不算抄本差异**:抄来的每条规则里,配色与几何的 token 引用都走下面那层\r\n * `--sd-*` 别名(本插件新增的一层间接)。逐属性比对时按「别名 → 它引用/兜底的那枚 `--dsw-*`」还原\r\n * 之后再比;这一层不新造颜色、只加兜底,也不重复计入上面七条。\r\n *\r\n * **另有一处已知差异不住在本文件**:回收站组头那颗按钮的整套 UA reset。宿主 `DisclosureRow` 那份是\r\n * `div`,没有这个坑;我们这行要做成可点的 `<button>`,UA 样式表会把背景、边框、内距、字体、居中与\r\n * flex 基线一起渗进来(真机上就是项目名整条居中,2026-10-15 截图)。它写在\r\n * `src/client/trash-panel.tsx` 组头那一行的 inline 样式上,**具体 reset 了哪几项由\r\n * `tests/skeleton.test.ts` 逐条钉住**(不在这里再抄一份名单)——所以「与官方逐条比对」时本表仍然只数\r\n * 上面那些,这条不算进抄本差异。\r\n */\r\n\r\n/* ──────────────── 主题 token 兜底(本插件的别名层) ────────────────\r\n * 宿主 `Theme.listTokens` 的公开面只有 14 个 `--dsw-*`(13 个 `--dsw-alias-*` +\r\n * `--dsw-specific-sidebar-fill`);这张抄本表还用了一批宿主内部 token\r\n * (`label-tertiary` / `border-l3`·`l4` / `markdown-code-*` / `scrollbar-*` …)。它们在同代宿主上\r\n * 都存在,但换代改名时 `var()` 会整条失效、颜色退回继承值。这里给**有公开等价物的语义 token**\r\n * 建一层 `--sd-*` 别名:私有 token 在场时逐字等价;不在时退到最接近的公开 token —— 最差是难看,\r\n * 不会没有颜色。\r\n *\r\n * **为什么挂 `body` 而不是 `:root`**(2026-11-04 当代宿主产物实测):`--dsw-alias-*` 那套主题\r\n * palette 写在 `body` / `body[data-ds-dark-theme]` 两条规则上,主题服务还把当前快照逐条 inline\r\n * 写到 `document.body.style`;`:root` 那份 base.css 只有字体、圆角与动效。别名层若挂 `:root`,\r\n * 引用就在 `html` 上求值——私有 token 与公开兜底都还没定义,整条 `var()` 链在计算值阶段失效,\r\n * 别名拿到的是**保证无效值**,用到它的声明(字色、边框、底色)会被整条丢弃;连 `--dsw-mask-blur`\r\n * 都会拿到兜底 `blur(2px)`,而宿主这一代给的是 `none`。挂 `body` 时:palette 与别名在同一元素上\r\n * (暗色切换一并跟着),base.css 的几何 token 从 `html` 继承下来,两边都取得到;宿主哪天把 palette\r\n * 挪回 `:root` 也仍然可见(自定义属性本来就继承),只有挪到更深的包裹元素才会再次失效——那时先\r\n * 复核这段的落点。\r\n *\r\n * 不在这一层的是:`--dsw-font-markdown-*`(整条 `font` 简写,没有等价公开 token,失效退回继承字体)、\r\n * `--dsw-static-*`(固定色:tooltip 文字、toast 动作等,允许自带颜色)、`--dsw-focus-ring-color`(原处已带 fallback)。\r\n * 新增私有 token 时先在这张表里找等价公开 token;找不到就别进别名层,并在头注记一笔。\r\n */\r\nbody {\r\n --sd-label-tertiary: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\r\n --sd-label-caption: var(--dsw-alias-label-caption, var(--dsw-alias-label-secondary));\r\n --sd-label-primary-foreground: var(--dsw-alias-label-primary-foreground, var(--dsw-alias-bg-base));\r\n --sd-border-l3: var(--dsw-alias-border-l3, var(--dsw-alias-border-l2));\r\n --sd-border-l4: var(--dsw-alias-border-l4, var(--dsw-alias-border-l2));\r\n --sd-menu-icon: var(--dsw-alias-menu-icon, var(--dsw-alias-label-secondary));\r\n --sd-link: var(--dsw-alias-link, var(--dsw-alias-brand-primary));\r\n --sd-interactive-bg-hover: var(--dsw-alias-interactive-bg-hover, var(--dsw-alias-bg-layer-2));\r\n --sd-interactive-bg-active: var(--dsw-alias-interactive-bg-active, var(--dsw-alias-bg-layer-2));\r\n --sd-interactive-bg-hover-danger: var(\r\n --dsw-alias-interactive-bg-hover-danger,\r\n color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent)\r\n );\r\n --sd-button-primary-fill: var(--dsw-alias-button-primary-fill, var(--dsw-alias-brand-primary));\r\n --sd-button-primary-hover: var(--dsw-alias-button-primary-hover, var(--dsw-alias-brand-primary));\r\n --sd-button-ghost-active-fill: var(--dsw-alias-button-ghost-active-fill, var(--dsw-alias-bg-layer-2));\r\n --sd-button-ghost-active-border: var(--dsw-alias-button-ghost-active-border, var(--dsw-alias-border-l2));\r\n --sd-markdown-code-block: var(--dsw-alias-markdown-code-block, var(--dsw-alias-bg-layer-2));\r\n --sd-markdown-inline-code: var(--dsw-alias-markdown-inline-code, var(--dsw-alias-bg-layer-2));\r\n --sd-state-business-primary: var(--dsw-alias-state-business-primary, var(--dsw-alias-brand-primary));\r\n --sd-state-warn-label: var(--dsw-alias-state-warn-label, var(--dsw-alias-state-warn-primary));\r\n --sd-toast-bg: var(--dsw-alias-toast-bg, var(--dsw-alias-bg-overlay));\r\n --sd-toast-label: var(--dsw-alias-toast-label, var(--dsw-alias-label-primary));\r\n --sd-tooltip-bg: var(--dsw-alias-tooltip-bg, var(--dsw-alias-bg-overlay));\r\n --sd-specific-bubble: var(--dsw-specific-bubble, var(--dsw-alias-bg-layer-2));\r\n --sd-bg-mask-1: var(\r\n --dsw-alias-bg-mask-1,\r\n color-mix(in srgb, var(--dsw-alias-label-primary) 35%, transparent)\r\n );\r\n --sd-scrollbar-bg-l1: var(--dsw-alias-scrollbar-bg-l1, var(--dsw-alias-border-l1));\r\n --sd-scrollbar-bg-l2: var(--dsw-alias-scrollbar-bg-l2, var(--dsw-alias-border-l1));\r\n --sd-scrollbar-hover-l2: var(--dsw-alias-scrollbar-hover-l2, var(--dsw-alias-border-l2));\r\n --sd-elevation-prominent: var(\r\n --dsw-elevation-prominent,\r\n 0 8px 24px color-mix(in srgb, var(--dsw-alias-label-primary) 18%, transparent)\r\n );\r\n --sd-shadow-lv3: var(\r\n --dsw-shadow-lv3,\r\n 0 1px 3px color-mix(in srgb, var(--dsw-alias-label-primary) 12%, transparent)\r\n );\r\n --sd-radius-xs: var(--dsw-radius-xs, 2px);\r\n --sd-radius-sm: var(--dsw-radius-sm, 4px);\r\n --sd-radius-md: var(--dsw-radius-md, 6px);\r\n --sd-radius-lg: var(--dsw-radius-lg, 8px);\r\n --sd-radius-xl: var(--dsw-radius-xl, 12px);\r\n --sd-radius-panel: var(--dsw-radius-panel, 12px);\r\n --sd-mask-blur: var(--dsw-mask-blur, blur(2px));\r\n --sd-focus-ring-width: var(--dsw-focus-ring-width, 2px);\r\n}\r\n\r\n/* ────────────────────────── Button ────────────────────────── */\r\n\r\n.sd-button {\r\n box-sizing: border-box;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 4px;\r\n border: none;\r\n border-radius: var(--sd-radius-md);\r\n cursor: pointer;\r\n font-size: 14px;\r\n line-height: 22px;\r\n color: var(--dsw-alias-label-primary);\r\n background: transparent;\r\n padding: 0 14px;\r\n}\r\n\r\n.sd-button:disabled {\r\n cursor: not-allowed;\r\n opacity: 0.4;\r\n}\r\n\r\n.sd-button-md {\r\n height: 36px;\r\n}\r\n\r\n/* 紧凑档:尺寸不随填充/描边换档(官方原文的注释即此意)。 */\r\n.sd-button-sm {\r\n height: 28px;\r\n font-size: 12px;\r\n line-height: 18px;\r\n padding: 0 10px;\r\n border-radius: var(--sd-radius-sm);\r\n}\r\n\r\n.sd-button-primary {\r\n background: var(--sd-button-primary-fill);\r\n color: var(--sd-label-primary-foreground);\r\n}\r\n\r\n.sd-button-primary:hover:not(:disabled) {\r\n background: var(--sd-button-primary-hover);\r\n}\r\n\r\n.sd-button-ghost:hover:not(:disabled) {\r\n background: var(--sd-interactive-bg-hover);\r\n}\r\n\r\n.sd-button-ghost:active:not(:disabled) {\r\n background: var(--sd-interactive-bg-active);\r\n}\r\n\r\n.sd-button-outline {\r\n border: 0.5px solid var(--sd-border-l3);\r\n background: transparent;\r\n}\r\n\r\n.sd-button-outline:hover:not(:disabled) {\r\n background: var(--sd-interactive-bg-hover);\r\n}\r\n\r\n.sd-button-icon {\r\n display: inline-flex;\r\n width: 16px;\r\n height: 16px;\r\n align-items: center;\r\n justify-content: center;\r\n}\r\n\r\n/* ────────────────────────── Tag ────────────────────────── */\r\n\r\n/* 胶囊几何固定:一个 tag 在哪里都是同一个尺寸,只有色板随 tone 走。 */\r\n.sd-tag {\r\n display: inline-flex;\r\n align-items: center;\r\n border-radius: 999px;\r\n corner-shape: round;\r\n padding: 1px 8px;\r\n font-size: 11px;\r\n line-height: 17px;\r\n font-weight: 500;\r\n white-space: nowrap;\r\n}\r\n\r\n/* 状态色自带填充:底色由自身颜色 10% 混出,换色板时底与字一起走,不需要第二个 token。 */\r\n.sd-tag-danger {\r\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);\r\n color: var(--dsw-alias-state-error-primary);\r\n}\r\n\r\n/* ────────────────────────── Modal ────────────────────────── */\r\n\r\n/* 整视口层(figma Mask + Dialog):蒙层 + 居中卡片。四边留 24px,上下按外壳公布的浮层内缩加宽,\r\n 高卡片才不会顶到外壳。卡片按这个内边距盒取尺寸:消费方用 `max-height:100%` 收口,不自己算视口。 */\r\n.sd-modal-root {\r\n pointer-events: auto;\r\n position: fixed;\r\n inset: 0;\r\n z-index: 1000;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n padding: max(24px, var(--dsh-frame-overlay-top, 24px)) 24px;\r\n}\r\n\r\n/* 只有绘制避让原生标题栏;根仍然挡住背景点击。 */\r\n.sd-modal-mask {\r\n position: absolute;\r\n inset: var(--dsh-frame-chrome-top, 0px) 0 0;\r\n backdrop-filter: var(--sd-mask-blur);\r\n}\r\n\r\n.sd-modal-mask::after {\r\n content: '';\r\n position: absolute;\r\n inset: 0;\r\n background: var(--sd-bg-mask-1);\r\n animation: sd-modal-enter var(--ds-transition-duration) var(--ds-ease-in-out);\r\n}\r\n\r\n.sd-modal-dialog {\r\n box-sizing: border-box;\r\n position: relative;\r\n z-index: 1;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 20px;\r\n width: min(380px, 100%);\r\n padding: 0 0 24px;\r\n overflow: hidden;\r\n border: 0;\r\n border-radius: var(--sd-radius-panel);\r\n background: var(--dsw-alias-bg-layer-2);\r\n box-shadow: var(--sd-elevation-prominent);\r\n animation: sd-modal-enter var(--ds-transition-duration) var(--ds-ease-in-out);\r\n}\r\n\r\n@keyframes sd-modal-enter {\r\n from { opacity: 0; }\r\n to { opacity: 1; }\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .sd-modal-mask::after,\r\n .sd-modal-dialog { animation: none; }\r\n}\r\n\r\n.sd-modal-dialog:focus {\r\n outline: none;\r\n}\r\n\r\n.sd-modal-content {\r\n display: flex;\r\n flex-direction: column;\r\n width: 100%;\r\n}\r\n\r\n/* 头行:标题左、关闭钮右。 */\r\n.sd-modal-header {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n gap: 8px;\r\n padding: 22px 14px 12px 24px;\r\n}\r\n\r\n.sd-modal-title {\r\n margin: 0;\r\n font-size: 16px;\r\n line-height: 24px;\r\n font-weight: 500;\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.sd-modal-close {\r\n flex: none;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 28px;\r\n height: 28px;\r\n border: none;\r\n border-radius: var(--sd-radius-sm);\r\n background: transparent;\r\n cursor: pointer;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n.sd-modal-close:hover {\r\n background: var(--sd-interactive-bg-hover);\r\n}\r\n\r\n.sd-modal-body {\r\n display: flex;\r\n flex-direction: column;\r\n min-width: 0;\r\n margin-top: 20px;\r\n padding: 0 24px;\r\n}\r\n\r\n.sd-modal-footer {\r\n display: flex;\r\n align-items: center;\r\n justify-content: flex-end;\r\n gap: 8px;\r\n padding: 0 24px;\r\n}\r\n\r\n/* ────────────────────────── Tooltip ────────────────────────── */\r\n\r\n.sd-tooltip-bubble {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 8px;\r\n position: fixed;\r\n z-index: 100;\r\n /* 定宽收缩只按 `left` 到视口右缘量,靠右的锚点会提前折行;\r\n `max-content` 只按文案定宽,再用半个视口收口。 */\r\n width: max-content;\r\n max-width: 50vw;\r\n padding: 3px 7px;\r\n border-radius: var(--sd-radius-sm);\r\n background: var(--sd-tooltip-bg);\r\n color: var(--dsw-static-neutral-bluish-00);\r\n font-size: 13px;\r\n line-height: 20px;\r\n white-space: pre-line;\r\n /* 不可断的长 token(URL、路径)不许顶破 max-width。 */\r\n overflow-wrap: break-word;\r\n pointer-events: none;\r\n animation: sd-tooltip-in 150ms var(--ds-ease-in-out);\r\n}\r\n\r\n.sd-tooltip-bubble[data-portal] { z-index: 1100; }\r\n.sd-tooltip-label { min-width: 0; }\r\n\r\n.sd-tooltip-bubble[data-side='right'] {\r\n transform: translateY(-50%);\r\n}\r\n\r\n.sd-tooltip-bubble[data-side='bottom'] {\r\n transform: translateX(-50%);\r\n}\r\n\r\n.sd-tooltip-bubble[data-side='top'] {\r\n transform: translate(-50%, -100%);\r\n}\r\n\r\n@keyframes sd-tooltip-in {\r\n from { opacity: 0; }\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .sd-tooltip-bubble {\r\n animation: none;\r\n }\r\n}\r\n\r\n/* ────────────────────────── Toast ────────────────────────── */\r\n\r\n/* 淡出延迟由组件以 `--dsh-toast-hold` 给出 ⇒ 同一个值同时驱动卸载计时器与这段动画,\r\n 两者不可能再对不上。淡出**时长**仍需与组件里的 FADE_MS 一致(没有 owner 会改它)。 */\r\n.sd-toast {\r\n position: fixed;\r\n top: 40px;\r\n left: 50%;\r\n /* 高过图片灯箱底层的 1000:预览开着时报错也必须读得到。 */\r\n z-index: 1100;\r\n /* 通告不截点击。 */\r\n pointer-events: none;\r\n display: flex;\r\n align-items: center;\r\n gap: 10px;\r\n /* 定宽盒设了 `left` 只按右侧空间收缩(-50% 的位移发生在定尺之后)⇒ 靠窗边的横幅会提前折行。\r\n `max-content` 只按文案定宽,再由下面的 max-width 收口。 */\r\n width: max-content;\r\n max-width: min(640px, calc(100vw - 48px));\r\n padding: 12px 16px;\r\n border-radius: var(--sd-radius-lg);\r\n /* 设计平台的 toast 表面在**两套主题下都是深的**(浅色 bluish-800 / 深色 bluish-750),\r\n 所以这里的字色与动作色是固定值,不随主题翻转。 */\r\n background: var(--sd-toast-bg);\r\n color: var(--sd-toast-label);\r\n font-size: 14px;\r\n line-height: 22px;\r\n box-shadow: var(--sd-shadow-lv3);\r\n transform: translateX(-50%);\r\n animation:\r\n sd-toast-in 160ms ease-out,\r\n sd-toast-fade 1000ms ease var(--dsh-toast-hold, 3000ms) forwards;\r\n}\r\n\r\n.sd-toast-icon {\r\n display: grid;\r\n place-items: center;\r\n flex: none;\r\n color: var(--sd-state-warn-label);\r\n}\r\n\r\n.sd-toast-icon-success {\r\n color: var(--dsw-alias-state-success-primary);\r\n}\r\n\r\n.sd-toast-text {\r\n min-width: 0;\r\n}\r\n\r\n.sd-toast-action {\r\n display: inline;\r\n padding: 0;\r\n border: 0;\r\n background: none;\r\n color: var(--dsw-static-deepseek-400);\r\n font: inherit;\r\n cursor: pointer;\r\n /* 动作文字与周围文案之间的呼吸位。 */\r\n margin-inline: 3px;\r\n /* 横幅表面整体是穿透点击的,只有动作文字接指针。 */\r\n pointer-events: auto;\r\n}\r\n\r\n.sd-toast-action:hover {\r\n opacity: 0.8;\r\n}\r\n\r\n/* 本插件自造的第七条(不是抄本,见文件头点名):撤销横幅在飞时那颗动作要**看着就按不动**。\r\n 官方 Toast 的 action 只有常态与 hover,没有禁用档;而 `.sd-toast-action` 自带 `color`,\r\n 浏览器默认的置灰根本不生效(真机上只会看见一个光标照常是手、点下去没反应的「撤销」)。\r\n 禁用档与行尾勾选开关同源(`opacity` + `not-allowed`),不新造颜色。 */\r\n.sd-toast-action:disabled {\r\n opacity: 0.4;\r\n cursor: not-allowed;\r\n}\r\n\r\n@keyframes sd-toast-in {\r\n from {\r\n opacity: 0;\r\n transform: translate(-50%, -6px);\r\n }\r\n\r\n to {\r\n opacity: 1;\r\n transform: translate(-50%, 0);\r\n }\r\n}\r\n\r\n@keyframes sd-toast-fade {\r\n to {\r\n opacity: 0;\r\n visibility: hidden;\r\n }\r\n}\r\n\r\n/* 减动效去掉滑入;延迟淡出(透明度变化、不是位移)仍然在定时卸载前收掉横幅。 */\r\n@media (prefers-reduced-motion: reduce) {\r\n .sd-toast {\r\n animation: sd-toast-fade 1000ms ease var(--dsh-toast-hold, 3000ms) forwards;\r\n }\r\n}\r\n\r\n/* ────────────────────────── 菜单行 ────────────────────────── */\r\n\r\n.sd-menu-item-wrap {\r\n position: relative;\r\n}\r\n\r\n/* 菜单格:紧凑文字、图标与选中间距。 */\r\n.sd-menu-item {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n width: 100%;\r\n min-height: 34px;\r\n padding: 6px 8px;\r\n border: none;\r\n border-radius: var(--sd-radius-md);\r\n background: transparent;\r\n cursor: pointer;\r\n font-size: 13px;\r\n line-height: 20px;\r\n color: var(--dsw-alias-label-primary);\r\n text-align: left;\r\n}\r\n\r\n.sd-menu-item:hover:not(:disabled) {\r\n background: var(--sd-interactive-bg-hover);\r\n}\r\n\r\n/* 方向键移动的是真焦点 ⇒ 键盘所在的那一行拿指针同款填充;填充就是这一行的焦点呈现,\r\n 浏览器默认的 ring 再叠一层就重了。 */\r\n.sd-menu-item:focus-visible:not(:disabled) {\r\n background: var(--sd-interactive-bg-hover);\r\n outline: none;\r\n}\r\n\r\n.sd-menu-item:disabled {\r\n opacity: 0.4;\r\n cursor: not-allowed;\r\n}\r\n\r\n.sd-menu-item-icon {\r\n display: inline-flex;\r\n flex: none;\r\n width: 14px;\r\n height: 14px;\r\n align-items: center;\r\n justify-content: center;\r\n color: var(--sd-menu-icon);\r\n}\r\n\r\n.sd-menu-item-icon svg {\r\n width: 14px;\r\n height: 14px;\r\n}\r\n\r\n.sd-menu-item-label {\r\n flex: 1;\r\n min-width: 0;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n/* 破坏性行:error 字/图标 + danger 悬停底。 */\r\n.sd-menu-item-danger {\r\n color: var(--dsw-alias-state-error-primary);\r\n}\r\n\r\n.sd-menu-item-danger .sd-menu-item-icon {\r\n color: var(--dsw-alias-state-error-primary);\r\n}\r\n\r\n.sd-menu-item-danger:hover:not(:disabled) {\r\n background: var(--sd-interactive-bg-hover-danger);\r\n}\r\n\r\n.sd-menu-item-danger:focus-visible:not(:disabled) {\r\n background: var(--sd-interactive-bg-hover-danger);\r\n outline: none;\r\n}\r\n\r\n/* 分隔格:发丝线上下的间距。取 l2 而不是 l1——l1 在这个表面上几乎看不见。 */\r\n.sd-menu-separator {\r\n height: 0.5px;\r\n margin: 3px 2px;\r\n background: var(--dsw-alias-border-l2);\r\n}\r\n\r\n/* ────────────────────────── Checkbox ────────────────────────── */\r\n/* 抄 `lib/Checkbox.module.css` 全部四条声明,逐字。勾选框本体是**原生 input** + `accent-color`,\r\n 不是自绘的圆框——回收站多选态用它(清单行左侧那一格)。 */\r\n\r\n.sd-checkbox {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 6px;\r\n font-size: 14px;\r\n line-height: 20px;\r\n color: var(--dsw-alias-label-primary);\r\n cursor: pointer;\r\n}\r\n\r\n.sd-checkbox input {\r\n flex: 0 0 auto;\r\n width: 16px;\r\n height: 16px;\r\n margin: 0;\r\n accent-color: var(--dsw-alias-brand-primary);\r\n cursor: inherit;\r\n}\r\n\r\n.sd-checkbox input:focus-visible {\r\n outline: var(--sd-focus-ring-width) solid var(--dsw-focus-ring-color, var(--sd-state-business-primary));\r\n outline-offset: 2px;\r\n}\r\n\r\n.sd-checkbox:has(input:disabled) {\r\n cursor: default;\r\n opacity: 0.5;\r\n}\r\n\r\n/* ────────────────────────── DisclosureRow(分组折叠头) ────────────────────────── */\r\n/* 抄 `lib/DisclosureRow.module.css` 的 `.root`/`.row`/`.leading`/`.title` 与那两枚 glyph 的\r\n 显隐。没抄进来的是 `StateDot`(`svg[data-state]`)那一档——它说\"这是状态标记不是文字装饰\",\r\n 我们的组头没有状态点。行高与 leading 盒走 `--dsh-content-font-delta` 那根轴,\r\n 标题字号走 secondary 档:宿主的\"设置里调正文字号\"会一起推着它走。 */\r\n\r\n.sd-disclosure {\r\n display: flex;\r\n flex-direction: column;\r\n width: 100%;\r\n min-width: 0;\r\n}\r\n\r\n.sd-disclosure-row {\r\n position: relative;\r\n overflow: hidden;\r\n display: flex;\r\n align-items: center;\r\n height: calc(24px + var(--dsh-content-font-delta, 0px));\r\n min-width: 0;\r\n color: var(--sd-label-tertiary);\r\n transition: color 100ms ease;\r\n}\r\n\r\n.sd-disclosure-row[data-expandable] {\r\n cursor: pointer;\r\n}\r\n\r\n.sd-disclosure-row:hover {\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n.sd-disclosure-leading {\r\n position: relative;\r\n flex: none;\r\n width: calc(16px + var(--dsh-content-font-delta, 0px));\r\n height: calc(16px + var(--dsh-content-font-delta, 0px));\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n margin-right: 6px;\r\n padding: 0;\r\n border: none;\r\n background: none;\r\n color: inherit;\r\n}\r\n\r\n.sd-disclosure-leading svg:not([data-state]) {\r\n width: calc(14px + var(--dsh-content-font-delta, 0px));\r\n height: calc(14px + var(--dsh-content-font-delta, 0px));\r\n}\r\n\r\nbutton.sd-disclosure-leading {\r\n cursor: pointer;\r\n}\r\n\r\n.sd-disclosure-title {\r\n flex: none;\r\n font-size: var(--dsh-content-font-size-secondary, 13px);\r\n line-height: calc(24px + var(--dsh-content-font-delta, 0px));\r\n color: inherit;\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .sd-disclosure-row {\r\n transition: none;\r\n }\r\n}\r\n\r\n/* ────────────────────────── 宽度把手(可拖的分栏线) ────────────────────────── */\r\n/* 抄 `ConversationRoot.module.css` 的 `.widthHandle` 一族:10px 的命中区、`col-resize` 光标、\r\n 平时不画、悬停或拖动时才浮出一条 2px 的 `--dsw-alias-scrollbar-bg-l1` 短标(那条渐变里\r\n `--dsh-width-handle-pointer-y` 的兜底是 50%,宿主靠 JS 跟着指针走;我们不跟,\r\n 停在中间——少一条 pointermove 上的写字,视觉上是同一族东西)。\r\n 与宿主的差别只有位置:它绝对定位在会话栏两侧,我们这根住在两栏之间的那一格 flex 位里,\r\n 所以 `top/bottom/position` 那三样交给内联布局,这里只留视觉与光标。 */\r\n\r\n.sd-width-handle {\r\n position: relative;\r\n z-index: 0;\r\n cursor: col-resize;\r\n align-self: stretch;\r\n flex: 0 0 10px;\r\n width: 10px;\r\n padding: 0;\r\n border: none;\r\n background: none;\r\n}\r\n\r\n.sd-width-handle::after {\r\n content: \"\";\r\n background: linear-gradient(\r\n to bottom,\r\n transparent calc(var(--dsh-width-handle-pointer-y, 50%) - 36px),\r\n var(--sd-scrollbar-bg-l1) calc(var(--dsh-width-handle-pointer-y, 50%) - 8px),\r\n var(--sd-scrollbar-bg-l1) calc(var(--dsh-width-handle-pointer-y, 50%) + 8px),\r\n transparent calc(var(--dsh-width-handle-pointer-y, 50%) + 36px)\r\n );\r\n opacity: 0;\r\n pointer-events: none;\r\n border-radius: 2px;\r\n width: 2px;\r\n position: absolute;\r\n top: 0;\r\n bottom: 0;\r\n left: 4px;\r\n}\r\n\r\n.sd-width-handle:hover::after,\r\n.sd-width-handle[data-dragging]::after {\r\n opacity: 1;\r\n}\r\n\r\n.sd-width-handle:focus-visible {\r\n outline: none;\r\n}\r\n\r\n.sd-width-handle:focus-visible::after {\r\n opacity: 1;\r\n}\r\n\r\n/* 本插件自造的第一条(不是抄本,见文件头点名):拖动期间整块面板别把文字选上了。 */\r\n.sd-resizing {\r\n user-select: none;\r\n cursor: col-resize;\r\n}\r\n\r\n/* 本插件自造的第二条(不是抄本,见文件头点名):清单行是 `role=\"row\"` 的可聚焦 div,而这一代\r\n `--dsw-focus-ring-color` 逐字是 `transparent`(2026-10-15 实测)——不自己描一条,键盘走\r\n ↑↓ 选会话时就没有任何焦点可见性。描边**复用选中态那枚** `ghost-active-border`,不新造颜色;\r\n 0.5px 与 `outline-offset: 1px` 是行的描边宽度与一档外移,不动布局(outline 不进滚动区)。 */\r\n[data-dsh-session-delete=\"trash-row\"]:focus-visible {\r\n outline: 0.5px solid var(--sd-button-ghost-active-border);\r\n outline-offset: 1px;\r\n}\r\n\r\n/* 会话行尾悬停排那颗**兜底**勾选开关(状态点行 / 归档行;行首主勾选是原生 Checkbox)。\r\n 基底**逐字抄** `dsh-client-ui-workspace` 的 `Rows.module.css` 的 `.iconButton` / `.iconButton:hover`,\r\n 只改类名——兜底开关要跟同排宿主自己的归档/置顶同形,尺寸与色档都不自己发明。 */\r\n.sd-rowaction-button {\r\n border-radius: var(--sd-radius-xs);\r\n cursor: pointer;\r\n width: 16px;\r\n height: 16px;\r\n color: var(--sd-label-tertiary);\r\n background: 0 0;\r\n border: none;\r\n flex: none;\r\n justify-content: center;\r\n align-items: center;\r\n padding: 0;\r\n display: inline-flex;\r\n}\r\n.sd-rowaction-button:hover {\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n/* 本插件自造的第三条(不是抄本,见文件头点名):官方那颗只有常态与 hover,勾选开关还要有\r\n 「已按下」与「这条删不动」两档。按下档**复用回收站选中行那两枚** `ghost-active-*`,不新造颜色;\r\n 禁用档走 opacity。 */\r\n.sd-rowaction-button[aria-pressed='true'] {\r\n background: var(--sd-button-ghost-active-fill);\r\n box-shadow: inset 0 0 0 1px var(--sd-button-ghost-active-border);\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n.sd-rowaction-button:disabled {\r\n opacity: 0.4;\r\n cursor: not-allowed;\r\n}\r\n\r\n/* 本插件自造的第⑥条(不是抄本,见文件头点名):行首 16px 格里的勾选框(2026-10-17 改版,**只在\r\n 多选模式内渲染**)。格本身由宿主居中(16×20),这里只把包装层压成同样大小;勾选框本体是回收站\r\n 多选同款原生 input(`.sd-checkbox`,见上)。 */\r\n.sd-batch-pick-leading {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n}\r\n\r\n/* 本插件自造的第⑤条(不是抄本,见文件头点名):宿主行自己的选中态只归它自己,批量选择要有整行\r\n 底色;行首格在场时行尾兜底要退场(两条入口不许同屏)。两条都只认宿主行已有的\r\n `data-row-key`;将来这个属性改名,最差是底色不亮 / 兜底多露一颗,勾选本身仍在。 */\r\n[data-row-key^=\"session:\"]:has([data-dsh-session-delete=\"batch-pick\"][data-checked=\"true\"]) {\r\n background: var(--sd-button-ghost-active-fill);\r\n box-shadow: inset 0 0 0 0.5px var(--sd-button-ghost-active-border);\r\n}\r\n[data-row-key^=\"session:\"]:has([data-dsh-session-delete=\"batch-pick\"][data-seat=\"leading\"]) .sd-batch-pick-action {\r\n display: none;\r\n}\r\n\r\n/* 本插件自造的第四条(不是抄本,见文件头点名):批量删除的浮条。宿主没有\"浮条\"这件控件,形状照\r\n 本插件自己的浮层口径来——固定底部居中,表面/描边/圆角/高程四件全走 token(与单删那张目标卡同源)。\r\n **层级必须低于确认框(`.sd-modal-root` 1000)**:2026-10-17 真机发现浮条在 1100 时会盖住确认框、\r\n 还能抢它的按钮;撤销横幅与 Tooltip 是瞬时通告、保持 1100。列方向:清单在上、操作行在下;\r\n 清单限高可滚,默认收起(见 `batch-bar.tsx`)。**宽度按内容展开**(`max-content`,最多视口减 48):\r\n 只写 `left:50% + width:auto` 时 shrink-to-fit 只拿左半屏,新增「全选/取消全选」后会把「退出多选」\r\n 挤到第二行;窄到 max-width 还放不下才由 `.sd-batch-bar-main` 的 `flex-wrap` 兜底。 */\r\n.sd-batch-bar {\r\n position: fixed;\r\n bottom: 24px;\r\n left: 50%;\r\n transform: translateX(-50%);\r\n z-index: 900;\r\n width: max-content;\r\n max-width: calc(100vw - 48px);\r\n display: flex;\r\n flex-direction: column;\r\n align-items: stretch;\r\n gap: 8px;\r\n padding: 8px 12px;\r\n background: var(--dsw-alias-bg-layer-2);\r\n border: 0.5px solid var(--sd-border-l4);\r\n border-radius: var(--sd-radius-lg);\r\n box-shadow: var(--sd-elevation-prominent);\r\n font-size: 12px;\r\n line-height: 18px;\r\n}\r\n\r\n.sd-batch-bar-main {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n flex-wrap: wrap;\r\n}\r\n\r\n.sd-batch-bar-hint {\r\n color: var(--sd-label-tertiary);\r\n}\r\n\r\n.sd-batch-bar-list-head {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n flex-wrap: wrap;\r\n}\r\n\r\n/* 汇总的第二句(含 K 个子会话)比条数轻一档,和列表行里的 chip 同级。 */\r\n.sd-batch-bar-list-head span + span {\r\n color: var(--sd-label-tertiary);\r\n}\r\n\r\n.sd-batch-bar-list {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 2px;\r\n max-height: 240px;\r\n overflow: auto;\r\n margin: 0;\r\n padding: 0 0 8px;\r\n border-bottom: 0.5px solid var(--sd-border-l4);\r\n list-style: none;\r\n}\r\n\r\n.sd-batch-bar-item {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n min-width: 0;\r\n}\r\n\r\n.sd-batch-bar-title {\r\n flex: 1;\r\n min-width: 0;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n.sd-batch-bar-meta {\r\n flex: none;\r\n color: var(--sd-label-tertiary);\r\n}\r\n\r\n.sd-batch-bar-remove {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n flex: none;\r\n width: 16px;\r\n height: 16px;\r\n padding: 0;\r\n border: none;\r\n border-radius: var(--sd-radius-xs);\r\n background: 0 0;\r\n color: var(--sd-label-tertiary);\r\n cursor: pointer;\r\n}\r\n\r\n.sd-batch-bar-remove:hover {\r\n color: var(--dsw-alias-label-primary);\r\n background: var(--sd-interactive-bg-hover);\r\n}\r\n";
1654
1716
  //#endregion
1655
1717
  //#region src/client/ui/markdown.css?dsh-css-text
1656
- var markdown_default = "/**\r\n * 回收站详情里**会话正文**的排版(2026-10-15 真机反馈后新增)。\r\n *\r\n * 为什么单独一个文件而不并进 `ui.css`:`ui.css` 是\"照官方 primitives 逐字抄来的那一份表\",\r\n * 那条 fidelity 核对(`docs/development.md` 的界面保真复核门)要的是它与官方明文**逐属性零差异**,只许文件头点名的几类例外\r\n * (类名前缀、`@keyframes` 改名,外加它点名的自造与已声明的差别);条数不钉在这里,钉了就会漂成假基线。这一份抄的是\r\n * **另一个来源**(宿主 markdown 那一张表),混进去就把那份核对弄脏了。\r\n *\r\n * **来源(逐字抄,只改选择器与下面点名的三处)**:\r\n * `app.asar` 里 `@deepseek-ai/dsh-client-ui-primitives/lib/markdown/MarkdownText.module.css`\r\n * (`.markdown` 那一支;`.compact` 一支我们不用)+ `lib/markdown/CodeBlock.module.css` 的 `pre`。\r\n * token 名一个没换:`--dsw-font-markdown-*` / `--dsw-alias-markdown-*` / `--dsh-content-font-*`\r\n * 全是宿主会话区自己吃的那组,所以宿主调内容字号时,这里跟着一起动。\r\n *\r\n * 三处**有意**的差异,每条都是因为我们的 DOM 长不出宿主那个节点:\r\n * ① 宿主给表包一层 `.tableScroll`(div)再横向滚动;react-markdown 不产这层 ⇒ 滚动挂在 `table`\r\n * 自己身上(`display:block` + 匿名表盒)。这条得真机量过才算成立。\r\n * ② 宿主的代码卡有 banner 行(语言名 + 复制),四个圆角被拆成\"上圆角在 banner、下圆角在 pre\";\r\n * 我们没有 banner ⇒ 圆角整块给 `pre`。\r\n * ③ 宿主的链接可点(点开外链/文件),我们不碰宿主导航,链接渲成不可点的文本 ⇒ 保留链接色与字重,\r\n * **去掉** hover 的下划线(那是\"点我\"的邀请)。\r\n *\r\n * 抄过来但用不上的选择器已删:`.fileLink` / `.fileMention` / `.linkIcon` / `.image` / `.imageAlt`\r\n * (图片渲成本地化占位,见 `copy.ts` 的 `detail.image` / `detail.image.alt`)/ `.katex-display` /\r\n * `:global(.md-code-block)` / `.shiki`(高亮由宿主的 highlighter 提供,我们没有)。\r\n */\r\n.sd-md {\r\n min-width: 0;\r\n overflow-wrap: anywhere;\r\n font: var(--dsw-font-markdown-base);\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.sd-md strong {\r\n font-weight: 600;\r\n}\r\n\r\n.sd-md h1 {\r\n font: var(--dsw-font-markdown-h1);\r\n margin: 32px 0 16px;\r\n}\r\n\r\n.sd-md h2 {\r\n font: var(--dsw-font-markdown-h2);\r\n margin: 32px 0 16px;\r\n}\r\n\r\n.sd-md h3 {\r\n font: var(--dsw-font-markdown-h3);\r\n margin: 32px 0 16px;\r\n}\r\n\r\n.sd-md h4 {\r\n font: var(--dsw-font-markdown-h4);\r\n margin: 16px 0;\r\n}\r\n\r\n.sd-md :where(h5, h6) {\r\n font: var(--dsw-font-markdown-base-strong);\r\n margin: 16px 0;\r\n}\r\n\r\n.sd-md :where(h1, h2, h3, h4, h5, h6) strong {\r\n font-weight: inherit;\r\n}\r\n\r\n.sd-md p {\r\n margin: 16px 0;\r\n}\r\n\r\n/* Tighten h4–h6 against a following list (design: 8px gap). */\r\n.sd-md :where(h4, h5, h6) + :where(ul, ol) {\r\n margin-top: 8px;\r\n}\r\n\r\n.sd-md :where(h4, h5, h6):has(+ :where(ul, ol)) {\r\n margin-bottom: 8px;\r\n}\r\n\r\n/* 见文件头差异③:颜色与字重照抄,hover 的 dotted 下划线不抄——这里没有可点的东西。 */\r\n.sd-md-link {\r\n color: var(--dsw-alias-link);\r\n font-weight: 500;\r\n}\r\n\r\n.sd-md :where(ul, ol) {\r\n margin: 16px 0;\r\n padding-left: 18px;\r\n}\r\n\r\n.sd-md li:not(:first-child) {\r\n margin-top: 6px;\r\n}\r\n\r\n.sd-md li > :where(ul, ol) {\r\n margin-top: 4px;\r\n}\r\n\r\n.sd-md li::marker {\r\n line-height: 24px;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n/* Nested ol under ul/ol: markers inside (models sometimes emit this shape). */\r\n.sd-md :where(ul, ol) ol {\r\n list-style-position: inside;\r\n padding-left: 0;\r\n}\r\n\r\n.sd-md :where(ul, ol) ol li p {\r\n display: inline;\r\n}\r\n\r\n.sd-md li > p {\r\n margin: 8px 0;\r\n}\r\n\r\n.sd-md li > *:first-child {\r\n margin-top: 0;\r\n}\r\n\r\n.sd-md li > *:last-child {\r\n margin-bottom: 0;\r\n}\r\n\r\n.sd-md hr {\r\n display: block;\r\n border: none;\r\n height: 0.5px;\r\n margin: 32px 0;\r\n background: var(--dsw-alias-border-l2);\r\n}\r\n\r\n.sd-md blockquote {\r\n border-left: 2px solid var(--dsw-alias-label-caption);\r\n margin: 16px 0 0;\r\n padding-left: 14px;\r\n}\r\n\r\n/* 代码卡:宿主的卡片由 `.block`(底色+圆角)与其内 `pre`(字号+内边距)两层拼成。\r\n 我们只有 `pre` 这一层,所以两层的声明合成在这里——见文件头差异②。 */\r\n.sd-md pre {\r\n font: var(--dsw-font-markdown-code-block);\r\n margin: 16px 0;\r\n padding: 16px;\r\n color: var(--dsw-alias-label-primary);\r\n background: var(--dsw-alias-markdown-code-block);\r\n border-radius: var(--dsw-radius-lg);\r\n overflow-x: auto;\r\n white-space: pre-wrap;\r\n word-break: break-all;\r\n}\r\n\r\n.sd-md pre code {\r\n font: inherit;\r\n background: none;\r\n padding: 0;\r\n}\r\n\r\n.sd-md :not(pre) > code {\r\n display: inline-flex;\r\n align-items: center;\r\n box-sizing: border-box;\r\n font: var(--dsw-font-markdown-code);\r\n font-family: var(--ds-font-family-code);\r\n font-size: 0.875em !important;\r\n background-color: var(--dsw-alias-markdown-inline-code);\r\n border: 0.5px solid var(--dsw-alias-border-l1);\r\n border-radius: var(--dsw-radius-sm);\r\n padding: 0 5px;\r\n}\r\n\r\n.sd-md :where(h1, h2, h3, h4, h5, h6) code {\r\n font: inherit;\r\n font-family: var(--ds-font-family-code);\r\n}\r\n\r\n.sd-md input[type='checkbox'] {\r\n margin: 0 8px 0 0;\r\n accent-color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n/* 见文件头差异①:宿主是 `.tableScroll > table`,我们没有那层 div。 */\r\n.sd-md table {\r\n display: block;\r\n box-sizing: border-box;\r\n max-width: 100%;\r\n overflow-x: auto;\r\n overscroll-behavior-x: contain;\r\n border-collapse: collapse;\r\n width: max-content;\r\n}\r\n\r\n.sd-md th {\r\n text-align: start;\r\n padding: 10px 16px;\r\n border-bottom: 0.5px solid var(--dsw-alias-border-l3);\r\n border-top: none;\r\n font: var(--dsw-font-markdown-table-head);\r\n max-width: 320px;\r\n max-width: min(30vw, 320px);\r\n min-width: 100px;\r\n}\r\n\r\n.sd-md td {\r\n padding: 10px 16px;\r\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\r\n font: var(--dsw-font-markdown-table);\r\n max-width: 320px;\r\n max-width: min(30vw, 320px);\r\n min-width: 100px;\r\n}\r\n\r\n.sd-md th:first-child,\r\n.sd-md td:first-child {\r\n padding-left: 0;\r\n}\r\n\r\n.sd-md td:last-child {\r\n padding-right: 0;\r\n}\r\n\r\n.sd-md table code {\r\n font-size: 11px;\r\n}\r\n\r\n.sd-md > *:first-child,\r\n.sd-md p:first-child {\r\n margin-top: 0 !important;\r\n}\r\n\r\n.sd-md > *:last-child,\r\n.sd-md p:last-child {\r\n margin-bottom: 0 !important;\r\n}\r\n\r\n/* 图片在回收站里读不到(路径属于会话当时的 cwd),渲成一句说明,字色用宿主的 tertiary。 */\r\n.sd-md-image {\r\n color: var(--dsw-alias-label-tertiary);\r\n font-style: italic;\r\n}\r\n";
1718
+ var markdown_default = "/**\r\n * 回收站详情里**会话正文**的排版(2026-10-15 真机反馈后新增)。\r\n *\r\n * 为什么单独一个文件而不并进 `ui.css`:`ui.css` 是\"照官方 primitives 逐字抄来的那一份表\",\r\n * 那条 fidelity 核对(`docs/development.md` 的界面保真复核门)要的是它与官方明文**逐属性零差异**,只许文件头点名的几类例外\r\n * (类名前缀、`@keyframes` 改名,外加它点名的自造与已声明的差别);条数不钉在这里,钉了就会漂成假基线。这一份抄的是\r\n * **另一个来源**(宿主 markdown 那一张表),混进去就把那份核对弄脏了。\r\n *\r\n * **来源(逐字抄,只改选择器、下面点名的三处,以及 token 引用走 `--sd-*` 别名层)**:\r\n * `app.asar` 里 `@deepseek-ai/dsh-client-ui-primitives/lib/markdown/MarkdownText.module.css`\r\n * (`.markdown` 那一支;`.compact` 一支我们不用)+ `lib/markdown/CodeBlock.module.css` 的 `pre`。\r\n * 字体简写 `--dsw-font-markdown-*` 与内容字号 `--dsh-content-font-*` 逐字保留:它们是宿主会话区\r\n * 自己吃的那组,宿主调内容字号时这里跟着一起动。配色/几何的 `--dsw-alias-*` / `--dsw-radius-*`\r\n * 走 `ui.css` 头注那层 `--sd-*` 别名(私有 token 带公开兜底);逐属性复核时按别名还原成它引用的\r\n * `--dsw-*` 再比,不把别名层算成抄本差异。\r\n *\r\n * 三处**有意**的差异,每条都是因为我们的 DOM 长不出宿主那个节点:\r\n * ① 宿主给表包一层 `.tableScroll`(div)再横向滚动;react-markdown 不产这层 ⇒ 滚动挂在 `table`\r\n * 自己身上(`display:block` + 匿名表盒)。这条得真机量过才算成立。\r\n * ② 宿主的代码卡有 banner 行(语言名 + 复制),四个圆角被拆成\"上圆角在 banner、下圆角在 pre\";\r\n * 我们没有 banner ⇒ 圆角整块给 `pre`。\r\n * ③ 宿主的链接可点(点开外链/文件),我们不碰宿主导航,链接渲成不可点的文本 ⇒ 保留链接色与字重,\r\n * **去掉** hover 的下划线(那是\"点我\"的邀请)。\r\n *\r\n * 抄过来但用不上的选择器已删:`.fileLink` / `.fileMention` / `.linkIcon` / `.image` / `.imageAlt`\r\n * (图片渲成本地化占位,见 `copy.ts` 的 `detail.image` / `detail.image.alt`)/ `.katex-display` /\r\n * `:global(.md-code-block)` / `.shiki`(高亮由宿主的 highlighter 提供,我们没有)。\r\n */\r\n.sd-md {\r\n min-width: 0;\r\n overflow-wrap: anywhere;\r\n font: var(--dsw-font-markdown-base);\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.sd-md strong {\r\n font-weight: 600;\r\n}\r\n\r\n.sd-md h1 {\r\n font: var(--dsw-font-markdown-h1);\r\n margin: 32px 0 16px;\r\n}\r\n\r\n.sd-md h2 {\r\n font: var(--dsw-font-markdown-h2);\r\n margin: 32px 0 16px;\r\n}\r\n\r\n.sd-md h3 {\r\n font: var(--dsw-font-markdown-h3);\r\n margin: 32px 0 16px;\r\n}\r\n\r\n.sd-md h4 {\r\n font: var(--dsw-font-markdown-h4);\r\n margin: 16px 0;\r\n}\r\n\r\n.sd-md :where(h5, h6) {\r\n font: var(--dsw-font-markdown-base-strong);\r\n margin: 16px 0;\r\n}\r\n\r\n.sd-md :where(h1, h2, h3, h4, h5, h6) strong {\r\n font-weight: inherit;\r\n}\r\n\r\n.sd-md p {\r\n margin: 16px 0;\r\n}\r\n\r\n/* Tighten h4–h6 against a following list (design: 8px gap). */\r\n.sd-md :where(h4, h5, h6) + :where(ul, ol) {\r\n margin-top: 8px;\r\n}\r\n\r\n.sd-md :where(h4, h5, h6):has(+ :where(ul, ol)) {\r\n margin-bottom: 8px;\r\n}\r\n\r\n/* 见文件头差异③:颜色与字重照抄,hover 的 dotted 下划线不抄——这里没有可点的东西。 */\r\n.sd-md-link {\r\n color: var(--sd-link);\r\n font-weight: 500;\r\n}\r\n\r\n.sd-md :where(ul, ol) {\r\n margin: 16px 0;\r\n padding-left: 18px;\r\n}\r\n\r\n.sd-md li:not(:first-child) {\r\n margin-top: 6px;\r\n}\r\n\r\n.sd-md li > :where(ul, ol) {\r\n margin-top: 4px;\r\n}\r\n\r\n.sd-md li::marker {\r\n line-height: 24px;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n/* Nested ol under ul/ol: markers inside (models sometimes emit this shape). */\r\n.sd-md :where(ul, ol) ol {\r\n list-style-position: inside;\r\n padding-left: 0;\r\n}\r\n\r\n.sd-md :where(ul, ol) ol li p {\r\n display: inline;\r\n}\r\n\r\n.sd-md li > p {\r\n margin: 8px 0;\r\n}\r\n\r\n.sd-md li > *:first-child {\r\n margin-top: 0;\r\n}\r\n\r\n.sd-md li > *:last-child {\r\n margin-bottom: 0;\r\n}\r\n\r\n.sd-md hr {\r\n display: block;\r\n border: none;\r\n height: 0.5px;\r\n margin: 32px 0;\r\n background: var(--dsw-alias-border-l2);\r\n}\r\n\r\n.sd-md blockquote {\r\n border-left: 2px solid var(--sd-label-caption);\r\n margin: 16px 0 0;\r\n padding-left: 14px;\r\n}\r\n\r\n/* 代码卡:宿主的卡片由 `.block`(底色+圆角)与其内 `pre`(字号+内边距)两层拼成。\r\n 我们只有 `pre` 这一层,所以两层的声明合成在这里——见文件头差异②。 */\r\n.sd-md pre {\r\n font: var(--dsw-font-markdown-code-block);\r\n margin: 16px 0;\r\n padding: 16px;\r\n color: var(--dsw-alias-label-primary);\r\n background: var(--sd-markdown-code-block);\r\n border-radius: var(--sd-radius-lg);\r\n overflow-x: auto;\r\n white-space: pre-wrap;\r\n word-break: break-all;\r\n}\r\n\r\n.sd-md pre code {\r\n font: inherit;\r\n background: none;\r\n padding: 0;\r\n}\r\n\r\n.sd-md :not(pre) > code {\r\n display: inline-flex;\r\n align-items: center;\r\n box-sizing: border-box;\r\n font: var(--dsw-font-markdown-code);\r\n font-family: var(--ds-font-family-code);\r\n font-size: 0.875em !important;\r\n background-color: var(--sd-markdown-inline-code);\r\n border: 0.5px solid var(--dsw-alias-border-l1);\r\n border-radius: var(--sd-radius-sm);\r\n padding: 0 5px;\r\n}\r\n\r\n.sd-md :where(h1, h2, h3, h4, h5, h6) code {\r\n font: inherit;\r\n font-family: var(--ds-font-family-code);\r\n}\r\n\r\n.sd-md input[type='checkbox'] {\r\n margin: 0 8px 0 0;\r\n accent-color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n/* 见文件头差异①:宿主是 `.tableScroll > table`,我们没有那层 div。 */\r\n.sd-md table {\r\n display: block;\r\n box-sizing: border-box;\r\n max-width: 100%;\r\n overflow-x: auto;\r\n overscroll-behavior-x: contain;\r\n border-collapse: collapse;\r\n width: max-content;\r\n}\r\n\r\n.sd-md th {\r\n text-align: start;\r\n padding: 10px 16px;\r\n border-bottom: 0.5px solid var(--sd-border-l3);\r\n border-top: none;\r\n font: var(--dsw-font-markdown-table-head);\r\n max-width: 320px;\r\n max-width: min(30vw, 320px);\r\n min-width: 100px;\r\n}\r\n\r\n.sd-md td {\r\n padding: 10px 16px;\r\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\r\n font: var(--dsw-font-markdown-table);\r\n max-width: 320px;\r\n max-width: min(30vw, 320px);\r\n min-width: 100px;\r\n}\r\n\r\n.sd-md th:first-child,\r\n.sd-md td:first-child {\r\n padding-left: 0;\r\n}\r\n\r\n.sd-md td:last-child {\r\n padding-right: 0;\r\n}\r\n\r\n.sd-md table code {\r\n font-size: 11px;\r\n}\r\n\r\n.sd-md > *:first-child,\r\n.sd-md p:first-child {\r\n margin-top: 0 !important;\r\n}\r\n\r\n.sd-md > *:last-child,\r\n.sd-md p:last-child {\r\n margin-bottom: 0 !important;\r\n}\r\n\r\n/* 图片在回收站里读不到(路径属于会话当时的 cwd),渲成一句说明,字色用宿主的 tertiary。 */\r\n.sd-md-image {\r\n color: var(--sd-label-tertiary);\r\n font-style: italic;\r\n}\r\n";
1657
1719
  //#endregion
1658
1720
  //#region src/client/ui/styles.ts
1659
1721
  /**
@@ -2657,8 +2719,8 @@ window.__ModuleLoader__.load({
2657
2719
  const TARGET_CARD = {
2658
2720
  padding: "10px 12px",
2659
2721
  background: "var(--dsw-alias-bg-layer-2)",
2660
- border: "0.5px solid var(--dsw-alias-border-l4)",
2661
- borderRadius: "var(--dsw-radius-lg)"
2722
+ border: "0.5px solid var(--sd-border-l4)",
2723
+ borderRadius: "var(--sd-radius-lg)"
2662
2724
  };
2663
2725
  const requestStore$1 = createStore(null);
2664
2726
  /** 菜单行调用:请求删除这个会话(确认框由 overlay 上的宿主渲染)。 */
@@ -2840,7 +2902,7 @@ window.__ModuleLoader__.load({
2840
2902
  const TARGET_META = {
2841
2903
  fontSize: 12,
2842
2904
  lineHeight: "18px",
2843
- color: "var(--dsw-alias-label-tertiary)"
2905
+ color: "var(--sd-label-tertiary)"
2844
2906
  };
2845
2907
  /**
2846
2908
  * 目标卡要能装几十条:选中很多条时卡片不能把弹框撑出视口,标题与底部按钮必须保持可见。
@@ -3155,6 +3217,77 @@ window.__ModuleLoader__.load({
3155
3217
  });
3156
3218
  }
3157
3219
  //#endregion
3220
+ //#region src/client/context-menu-actions.ts
3221
+ /**
3222
+ * 0.2.2+ 会话菜单的数据动作**定义**:只造贡献对象,不碰客户端注册表。
3223
+ * 注册仍统一住 `index.tsx`(客户端条目注册只住那里),这样旧宿主的 legacy 回退与
3224
+ * contextMenus 路径的互斥判断也只有一个地方。
3225
+ *
3226
+ * 为什么需要这一层:0.2.2 把内置会话动作迁到 contextMenus,legacy React 槽位
3227
+ * (`sidebar.workspaces.session.menu.item`) 的条目固定渲染在 `afterContent` 动作之前;Archive
3228
+ * 是 `afterContent: true` 的 footer 动作,于是 legacy 行会跑到 Archive 前面。把这两行也注册成
3229
+ * `afterContent` 数据动作、order 排在 Archive(600) 之后,才能恢复 0.2.1 的顺序:
3230
+ * Archive → 多选 → 删除。
3231
+ *
3232
+ * 数据动作没有 `disabledReason`(底层 MenuRow 有,但 MenuActionView 没透传),所以运行中禁用时
3233
+ * 改 label 为「删除(运行中)」;legacy 路径仍保留原来的 tooltip。
3234
+ */
3235
+ const ICON_BATCH = "xrn1997.session-delete.batch-select";
3236
+ const ICON_DELETE = "xrn1997.session-delete.delete";
3237
+ const CONTRIBUTION_ID = "xrn1997.session-delete.session-menu";
3238
+ /** 运行中把 label 换成短句:数据动作没有 disabledReason,不能再用 tooltip 解释。 */
3239
+ function deleteLabel(t, live) {
3240
+ return live ? t("menu.deleteLive") : t("menu.delete");
3241
+ }
3242
+ /** 会话是否在跑;服务缺席或读数抛错都按「不知道」处理(Node 半仍会拒绝 live 删除)。 */
3243
+ function liveOf(sessionList, sessionId) {
3244
+ try {
3245
+ return sessionList?.getSnapshot().byId?.[sessionId]?.running === true;
3246
+ } catch {
3247
+ return false;
3248
+ }
3249
+ }
3250
+ /**
3251
+ * 造 0.2.2+ 的会话菜单贡献。order 700 落在内置 Archive(600) 之后;两行都是 `afterContent`,
3252
+ * 所以它们与 Archive 一起排掉 legacy 组件行,恢复「归档在多选/删除之前」。
3253
+ */
3254
+ function createSessionMenuContribution(t, sessionList) {
3255
+ const resolve = (target) => {
3256
+ const live = liveOf(sessionList, target.sessionId);
3257
+ return [{
3258
+ id: "batch-select",
3259
+ label: isSelecting() ? t("trash.toolbar.selectExit") : t("trash.toolbar.select"),
3260
+ icon: { key: ICON_BATCH },
3261
+ separatorBefore: true,
3262
+ afterContent: true,
3263
+ run: () => {
3264
+ if (isSelecting()) exitSelection();
3265
+ else enterSelection();
3266
+ }
3267
+ }, {
3268
+ id: "delete",
3269
+ label: deleteLabel(t, live),
3270
+ disabled: live,
3271
+ icon: { key: ICON_DELETE },
3272
+ danger: true,
3273
+ afterContent: true,
3274
+ run: () => {
3275
+ requestDelete({
3276
+ sessionId: target.sessionId,
3277
+ title: target.displayTitle
3278
+ });
3279
+ }
3280
+ }];
3281
+ };
3282
+ return {
3283
+ id: CONTRIBUTION_ID,
3284
+ target: "session",
3285
+ order: 700,
3286
+ ...sessionList === void 0 ? {} : { observe: () => [sessionList] },
3287
+ resolve
3288
+ };
3289
+ }
3290
+ //#endregion
3158
3291
  //#region src/client/delete-menu-item.tsx
3159
3292
  /**
3160
3293
  * 会话条目「…」菜单里的「删除」行(docs/ui-spec.md 状态 ①)。
@@ -3216,22 +3349,25 @@ window.__ModuleLoader__.load({
3216
3349
  minWidth: 0,
3217
3350
  padding: "9px 14px",
3218
3351
  background: "var(--dsw-alias-bg-layer-2)",
3219
- border: "0.5px solid var(--dsw-alias-border-l4)",
3220
- borderRadius: "var(--dsw-radius-lg)"
3352
+ border: "0.5px solid var(--sd-border-l4)",
3353
+ borderRadius: "var(--sd-radius-lg)"
3221
3354
  };
3222
3355
  /** 选中行:底色与描边都换成宿主"选中"那一档(`trash-panel.tsx` 头注里有这两枚 token 的实测取值)。 */
3223
3356
  const CARD_SELECTED = {
3224
3357
  ...CARD,
3225
- background: "var(--dsw-alias-button-ghost-active-fill)",
3226
- border: "0.5px solid var(--dsw-alias-button-ghost-active-border)"
3358
+ background: "var(--sd-button-ghost-active-fill)",
3359
+ border: "0.5px solid var(--sd-button-ghost-active-border)"
3227
3360
  };
3228
3361
  /**
3229
3362
  * **整页唯一那块实心红**的写法:`variant="primary"` + 在元素上覆盖 primary 那对填充 token。
3230
3363
  * 宿主插件管理页的 danger 按钮就是这个写法(asar @21658274),所以 hover 仍走 `.primary:hover` 类。
3364
+ *
3365
+ * 覆盖的是**本插件别名层**(`--sd-*`,见 `ui.css` 头注)而不是宿主私有 token:别名在 `body` 上
3366
+ * 就把 `var(--dsw-…, 兜底)` 算完并继承下来了,元素上再覆盖底层私有 token 已经传不进继承值。
3231
3367
  */
3232
3368
  const BULK_DANGER = {
3233
- "--dsw-alias-button-primary-fill": "var(--dsw-alias-state-error-primary)",
3234
- "--dsw-alias-button-primary-hover": "var(--dsw-alias-state-error-primary)"
3369
+ "--sd-button-primary-fill": "var(--dsw-alias-state-error-primary)",
3370
+ "--sd-button-primary-hover": "var(--dsw-alias-state-error-primary)"
3235
3371
  };
3236
3372
  /**
3237
3373
  * 项目名怎么念:宿主的 `projectKey` 把 cwd 里的 `/`、`\`、`:` 全折成**一个** `-` ⇒ 分隔符那部分有损、
@@ -3297,7 +3433,7 @@ window.__ModuleLoader__.load({
3297
3433
  if (a.height !== void 0) query.set(PARAMS.attachmentHeight, String(a.height));
3298
3434
  }
3299
3435
  if (a.kind === "file" && a.name !== void 0 && a.name !== "") query.set(PARAMS.attachmentName, a.name);
3300
- return `${OFFICIAL_SESSION_DELETE_API_PREFIX}${ROUTES.attachment.path}?${query.toString()}`;
3436
+ return apiPath(`${ROUTES.attachment.path}?${query.toString()}`);
3301
3437
  }
3302
3438
  /** 空串与缺席同义:宿主侧的 `name` 是可选建议名,空串不是名字(兜底文案与"不带 name 查询"都看它)。 */
3303
3439
  function nameOf(a) {
@@ -3316,7 +3452,7 @@ window.__ModuleLoader__.load({
3316
3452
  maxWidth: "100%",
3317
3453
  maxHeight: 260,
3318
3454
  objectFit: "contain",
3319
- borderRadius: "var(--dsw-radius-lg)",
3455
+ borderRadius: "var(--sd-radius-lg)",
3320
3456
  background: "var(--dsw-alias-bg-layer-2)"
3321
3457
  };
3322
3458
  const FILE_CHIP = {
@@ -3332,7 +3468,7 @@ window.__ModuleLoader__.load({
3332
3468
  alignItems: "center",
3333
3469
  gap: 6,
3334
3470
  fontSize: 12,
3335
- color: "var(--dsw-alias-label-tertiary)"
3471
+ color: "var(--sd-label-tertiary)"
3336
3472
  };
3337
3473
  /**
3338
3474
  * 图裂之后的占位。**仍然说清是哪一件**:名字(没有就用「附件图片」/「文件」)+ 体量 + 「附件不可用」。
@@ -3699,7 +3835,7 @@ window.__ModuleLoader__.load({
3699
3835
  ...ELLIPSIS,
3700
3836
  fontSize: 12,
3701
3837
  lineHeight: "16px",
3702
- color: "var(--dsw-alias-label-tertiary)"
3838
+ color: "var(--sd-label-tertiary)"
3703
3839
  },
3704
3840
  children: projectLabel(entry.projectDir, t)
3705
3841
  }),
@@ -3707,7 +3843,7 @@ window.__ModuleLoader__.load({
3707
3843
  style: {
3708
3844
  fontSize: 12,
3709
3845
  lineHeight: "16px",
3710
- color: "var(--dsw-alias-label-tertiary)"
3846
+ color: "var(--sd-label-tertiary)"
3711
3847
  },
3712
3848
  children: t("trash.row.workspaceGone")
3713
3849
  }) : null
@@ -3756,7 +3892,7 @@ window.__ModuleLoader__.load({
3756
3892
  ...ELLIPSIS,
3757
3893
  fontSize: 12,
3758
3894
  lineHeight: "16px",
3759
- color: "var(--dsw-alias-label-tertiary)"
3895
+ color: "var(--sd-label-tertiary)"
3760
3896
  },
3761
3897
  children: t("trash.confirm.one.meta", {
3762
3898
  project: projectLabel(pending.entry.projectDir, t),
@@ -3827,7 +3963,7 @@ window.__ModuleLoader__.load({
3827
3963
  style: {
3828
3964
  fontSize: 12,
3829
3965
  lineHeight: "17px",
3830
- color: "var(--dsw-alias-label-tertiary)"
3966
+ color: "var(--sd-label-tertiary)"
3831
3967
  },
3832
3968
  children: t("trash.orphans.cacheNote")
3833
3969
  })
@@ -3855,7 +3991,7 @@ window.__ModuleLoader__.load({
3855
3991
  style: {
3856
3992
  fontSize: 12,
3857
3993
  lineHeight: "16px",
3858
- color: "var(--dsw-alias-label-tertiary)"
3994
+ color: "var(--sd-label-tertiary)"
3859
3995
  },
3860
3996
  children: t("trash.orphans.meta", {
3861
3997
  size: fileSizeText(report.totalBytes),
@@ -3885,7 +4021,7 @@ window.__ModuleLoader__.load({
3885
4021
  display: "inline-flex",
3886
4022
  flex: "none",
3887
4023
  paddingTop: 2,
3888
- color: "var(--dsw-alias-label-tertiary)"
4024
+ color: "var(--sd-label-tertiary)"
3889
4025
  },
3890
4026
  "aria-hidden": "true",
3891
4027
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconWarningOutlineRegular, { size: 15 })
@@ -4157,7 +4293,7 @@ window.__ModuleLoader__.load({
4157
4293
  style: {
4158
4294
  fontSize: 12,
4159
4295
  lineHeight: "17px",
4160
- color: "var(--dsw-alias-label-tertiary)"
4296
+ color: "var(--sd-label-tertiary)"
4161
4297
  },
4162
4298
  children: hint
4163
4299
  })]
@@ -4169,7 +4305,7 @@ window.__ModuleLoader__.load({
4169
4305
  paddingTop: 1,
4170
4306
  fontSize: 12,
4171
4307
  lineHeight: "17px",
4172
- color: "var(--dsw-alias-label-tertiary)",
4308
+ color: "var(--sd-label-tertiary)",
4173
4309
  whiteSpace: "nowrap"
4174
4310
  },
4175
4311
  children: status
@@ -4285,7 +4421,7 @@ window.__ModuleLoader__.load({
4285
4421
  style: {
4286
4422
  fontSize: 12,
4287
4423
  lineHeight: "17px",
4288
- color: "var(--dsw-alias-label-tertiary)"
4424
+ color: "var(--sd-label-tertiary)"
4289
4425
  },
4290
4426
  children: t("maintenance.attachments.previewTruncated", {
4291
4427
  shown: items.length,
@@ -4296,18 +4432,19 @@ window.__ModuleLoader__.load({
4296
4432
  style: {
4297
4433
  fontSize: 12,
4298
4434
  lineHeight: "17px",
4299
- color: "var(--dsw-alias-label-tertiary)"
4435
+ color: "var(--sd-label-tertiary)"
4300
4436
  },
4301
4437
  children: t("maintenance.attachments.previewHint")
4302
4438
  })
4303
4439
  ]
4304
4440
  });
4305
4441
  }
4306
- function MaintenanceSettings({ deps, t = zhTranslate }) {
4442
+ function MaintenanceSettings({ deps, t = zhTranslate, view }) {
4307
4443
  const maintenance = useMaintenance({
4308
4444
  deps,
4309
4445
  t
4310
4446
  });
4447
+ if (view === "summary") return null;
4311
4448
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4312
4449
  "data-dsh-session-delete": "maintenance-card",
4313
4450
  style: CARD_SHELL,
@@ -26387,7 +26524,7 @@ window.__ModuleLoader__.load({
26387
26524
  lineHeight: "17px"
26388
26525
  };
26389
26526
  const META_LABEL = {
26390
- color: "var(--dsw-alias-label-tertiary)",
26527
+ color: "var(--sd-label-tertiary)",
26391
26528
  whiteSpace: "nowrap"
26392
26529
  };
26393
26530
  const META_VALUE = {
@@ -26429,8 +26566,8 @@ window.__ModuleLoader__.load({
26429
26566
  * 我们一行只有一个气泡,直接把上限挂在气泡上(同一父宽下百分比与 `min()` 的结果一致,少一层)。
26430
26567
  */
26431
26568
  const BUBBLE = {
26432
- background: "var(--dsw-specific-bubble)",
26433
- borderRadius: "var(--dsw-radius-xl)",
26569
+ background: "var(--sd-specific-bubble)",
26570
+ borderRadius: "var(--sd-radius-xl)",
26434
26571
  maxWidth: "min(calc(var(--dsh-chat-content-width, 748px) * .702), 82%)",
26435
26572
  fontSize: "var(--dsh-content-font-size, 14px)",
26436
26573
  lineHeight: "calc(22px + var(--dsh-content-font-delta, 0px))",
@@ -26444,7 +26581,7 @@ window.__ModuleLoader__.load({
26444
26581
  const META_LINE = {
26445
26582
  fontSize: "var(--dsh-content-font-size-secondary, 13px)",
26446
26583
  lineHeight: "calc(18px + var(--dsh-content-font-delta-secondary, 0px))",
26447
- color: "var(--dsw-alias-label-tertiary)"
26584
+ color: "var(--sd-label-tertiary)"
26448
26585
  };
26449
26586
  /** 读失败与写失败共用的那块(`role="status"`;播报口径与其未验证的边界见 docs/ui-spec.md)。 */
26450
26587
  const STATUS_BLOCK = {
@@ -26453,8 +26590,8 @@ window.__ModuleLoader__.load({
26453
26590
  gap: 10,
26454
26591
  padding: "12px 14px",
26455
26592
  background: "var(--dsw-alias-bg-layer-2)",
26456
- border: "0.5px solid var(--dsw-alias-border-l4)",
26457
- borderRadius: "var(--dsw-radius-lg)"
26593
+ border: "0.5px solid var(--sd-border-l4)",
26594
+ borderRadius: "var(--sd-radius-lg)"
26458
26595
  };
26459
26596
  const BLOCK = {
26460
26597
  display: "flex",
@@ -26486,13 +26623,13 @@ window.__ModuleLoader__.load({
26486
26623
  minWidth: 0,
26487
26624
  fontSize: 12,
26488
26625
  lineHeight: "18px",
26489
- color: "var(--dsw-alias-label-tertiary)"
26626
+ color: "var(--sd-label-tertiary)"
26490
26627
  };
26491
26628
  const BLOCK_META = {
26492
26629
  flex: "none",
26493
26630
  fontSize: 11.5,
26494
26631
  lineHeight: "18px",
26495
- color: "var(--dsw-alias-label-tertiary)",
26632
+ color: "var(--sd-label-tertiary)",
26496
26633
  whiteSpace: "nowrap"
26497
26634
  };
26498
26635
  const BLOCK_BODY = {
@@ -26514,8 +26651,8 @@ window.__ModuleLoader__.load({
26514
26651
  margin: 0,
26515
26652
  padding: "8px 10px",
26516
26653
  background: "var(--dsw-alias-bg-layer-2)",
26517
- border: "0.5px solid var(--dsw-alias-border-l4)",
26518
- borderRadius: "var(--dsw-radius-md)",
26654
+ border: "0.5px solid var(--sd-border-l4)",
26655
+ borderRadius: "var(--sd-radius-md)",
26519
26656
  fontFamily: "var(--ds-font-family-code)",
26520
26657
  fontSize: 12,
26521
26658
  lineHeight: "18px",
@@ -26527,7 +26664,7 @@ window.__ModuleLoader__.load({
26527
26664
  const SECTION_LABEL = {
26528
26665
  fontSize: 11.5,
26529
26666
  lineHeight: "16px",
26530
- color: "var(--dsw-alias-label-tertiary)"
26667
+ color: "var(--sd-label-tertiary)"
26531
26668
  };
26532
26669
  /**
26533
26670
  * 子会话行:折叠头同族的可点行,卡片底 + 小圆角;悬停走 sd-disclosure-row 的宿主配方。
@@ -26541,8 +26678,8 @@ window.__ModuleLoader__.load({
26541
26678
  height: "auto",
26542
26679
  cursor: "pointer",
26543
26680
  background: "var(--dsw-alias-bg-layer-2)",
26544
- border: "0.5px solid var(--dsw-alias-border-l4)",
26545
- borderRadius: "var(--dsw-radius-md)"
26681
+ border: "0.5px solid var(--sd-border-l4)",
26682
+ borderRadius: "var(--sd-radius-md)"
26546
26683
  };
26547
26684
  const TOOLBAR$1 = {
26548
26685
  display: "flex",
@@ -26554,7 +26691,7 @@ window.__ModuleLoader__.load({
26554
26691
  const TRUNCATED = {
26555
26692
  fontSize: 11.5,
26556
26693
  lineHeight: "16px",
26557
- color: "var(--dsw-alias-label-tertiary)"
26694
+ color: "var(--sd-label-tertiary)"
26558
26695
  };
26559
26696
  const ERROR_TEXT = { color: "var(--dsw-alias-state-error-primary)" };
26560
26697
  const HOUR = 36e5;
@@ -26828,7 +26965,7 @@ window.__ModuleLoader__.load({
26828
26965
  textAlign: "center",
26829
26966
  fontSize: 12.5,
26830
26967
  lineHeight: "18px",
26831
- color: "var(--dsw-alias-label-tertiary)"
26968
+ color: "var(--sd-label-tertiary)"
26832
26969
  },
26833
26970
  children: t("detail.notSelected")
26834
26971
  });
@@ -26895,7 +27032,7 @@ window.__ModuleLoader__.load({
26895
27032
  style: {
26896
27033
  fontSize: 12,
26897
27034
  lineHeight: "17px",
26898
- color: "var(--dsw-alias-label-tertiary)",
27035
+ color: "var(--sd-label-tertiary)",
26899
27036
  wordBreak: "break-word"
26900
27037
  },
26901
27038
  children: state.message
@@ -26927,7 +27064,7 @@ window.__ModuleLoader__.load({
26927
27064
  style: {
26928
27065
  fontSize: 12.5,
26929
27066
  lineHeight: "18px",
26930
- color: "var(--dsw-alias-label-tertiary)"
27067
+ color: "var(--sd-label-tertiary)"
26931
27068
  },
26932
27069
  children: t("detail.loading")
26933
27070
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -27304,7 +27441,7 @@ window.__ModuleLoader__.load({
27304
27441
  * - 方向键走环:未选中时 ↓ 落第一条、↑ 落最后一条;到端回卷。
27305
27442
  * - aria-activedescendant 只指向在 DOM 里的行;tab 落点永远有一行拿着。
27306
27443
  * - Esc 只在下钻期间生效,回退分两程(先收孩子视图、再退下钻);退出下钻后把焦点交还选中行一次;
27307
- * 最上层是确认框/菜单时让路。
27444
+ * 最上层是确认框/菜单时让路;焦点在面板外时不归清单(`scope`)。
27308
27445
  */
27309
27446
  /** 事件是不是清单自己的:从行(`role="row"`)或容器起来才算;行内按钮 / 勾选框的 Enter / Space
27310
27447
  * 归它们自己。测试里的窄事件没有 `target`,按清单自己的处理。 */
@@ -27313,6 +27450,17 @@ window.__ModuleLoader__.load({
27313
27450
  if (target == null || target === event.currentTarget) return true;
27314
27451
  return typeof target.getAttribute === "function" && target.getAttribute("role") === "row";
27315
27452
  }
27453
+ /** 这条 Esc 该不该归清单处理(回答的是"清单收不收这一下",不是"target 在不在作用域里")。
27454
+ * 焦点在页面空地(`body` / `html`)时没有"面板外"的证据,按清单处理,保住窄档下钻的可发现性;
27455
+ * `document` 是 `fireEvent.keyDown(document, …)` 这类合成事件的目标,同样没有焦点信息。
27456
+ * 其余按作用域根的包含关系判;根还没挂上时按不处理。 */
27457
+ function shouldHandleEsc(event, scope) {
27458
+ const target = event.target;
27459
+ if (!(target instanceof Node)) return true;
27460
+ if (target === document || target === document.body || target === document.documentElement) return true;
27461
+ const root = scope.current;
27462
+ return root !== null && root.contains(target);
27463
+ }
27316
27464
  function useTrashNav(input) {
27317
27465
  const { ordered, selectedId, selectMode, drillEnabled, drilled, select, toggleCheck, back, focusRow } = input;
27318
27466
  /** Esc 退出下钻后要交还焦点的那一行;等 `drilled` 落地、清单回到 DOM 之后再用一次。 */
@@ -27353,10 +27501,12 @@ window.__ModuleLoader__.load({
27353
27501
  selectedId,
27354
27502
  toggleCheck
27355
27503
  ]);
27504
+ const scope = input.scope;
27356
27505
  (0, react.useEffect)(() => {
27357
27506
  if (!drilled || !drillEnabled) return;
27358
27507
  const onKey = (event) => {
27359
27508
  if (event.key !== "Escape" || event.defaultPrevented) return;
27509
+ if (!shouldHandleEsc(event, scope)) return;
27360
27510
  if (hasTopLayer(document)) return;
27361
27511
  refocusRow.current = selectedId;
27362
27512
  back();
@@ -27368,6 +27518,7 @@ window.__ModuleLoader__.load({
27368
27518
  }, [
27369
27519
  drillEnabled,
27370
27520
  drilled,
27521
+ scope,
27371
27522
  back,
27372
27523
  selectedId
27373
27524
  ]);
@@ -28113,8 +28264,8 @@ window.__ModuleLoader__.load({
28113
28264
  overflow: "auto",
28114
28265
  paddingRight: 10,
28115
28266
  paddingBottom: 8,
28116
- "--dsh-scrollbar-thumb": "var(--dsw-alias-scrollbar-bg-l2)",
28117
- "--dsh-scrollbar-thumb-hover": "var(--dsw-alias-scrollbar-hover-l2)"
28267
+ "--dsh-scrollbar-thumb": "var(--sd-scrollbar-bg-l2)",
28268
+ "--dsh-scrollbar-thumb-hover": "var(--sd-scrollbar-hover-l2)"
28118
28269
  };
28119
28270
  /** 详情那一栏的外壳(滚动归它自己,正文限宽在内部)。 */
28120
28271
  const DETAIL_COLUMN = {
@@ -28125,8 +28276,8 @@ window.__ModuleLoader__.load({
28125
28276
  overflow: "auto",
28126
28277
  paddingRight: 10,
28127
28278
  paddingBottom: 8,
28128
- "--dsh-scrollbar-thumb": "var(--dsw-alias-scrollbar-bg-l2)",
28129
- "--dsh-scrollbar-thumb-hover": "var(--dsw-alias-scrollbar-hover-l2)"
28279
+ "--dsh-scrollbar-thumb": "var(--sd-scrollbar-bg-l2)",
28280
+ "--dsh-scrollbar-thumb-hover": "var(--sd-scrollbar-hover-l2)"
28130
28281
  };
28131
28282
  const DETAIL_INNER = {
28132
28283
  minWidth: 0,
@@ -28239,7 +28390,8 @@ window.__ModuleLoader__.load({
28239
28390
  select: controller.select,
28240
28391
  toggleCheck,
28241
28392
  back: controller.back,
28242
- focusRow
28393
+ focusRow,
28394
+ scope: rootRef
28243
28395
  });
28244
28396
  const pending = controller.pending;
28245
28397
  const restoreEntries = controller.pendingEntries;
@@ -28313,7 +28465,7 @@ window.__ModuleLoader__.load({
28313
28465
  style: {
28314
28466
  fontSize: 12,
28315
28467
  lineHeight: "17px",
28316
- color: "var(--dsw-alias-label-tertiary)",
28468
+ color: "var(--sd-label-tertiary)",
28317
28469
  wordBreak: "break-word"
28318
28470
  },
28319
28471
  children: view.failure
@@ -28331,7 +28483,7 @@ window.__ModuleLoader__.load({
28331
28483
  style: {
28332
28484
  padding: "20px 2px",
28333
28485
  fontSize: 12.5,
28334
- color: "var(--dsw-alias-label-tertiary)"
28486
+ color: "var(--sd-label-tertiary)"
28335
28487
  },
28336
28488
  children: t("trash.loading")
28337
28489
  }) : view.empty ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -28356,7 +28508,7 @@ window.__ModuleLoader__.load({
28356
28508
  flex: "none",
28357
28509
  borderRadius: 999,
28358
28510
  background: "var(--dsw-alias-bg-layer-2)",
28359
- color: "var(--dsw-alias-label-tertiary)"
28511
+ color: "var(--sd-label-tertiary)"
28360
28512
  },
28361
28513
  "aria-hidden": "true",
28362
28514
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconTrashOutlineRegular, { size: 20 })
@@ -28373,7 +28525,7 @@ window.__ModuleLoader__.load({
28373
28525
  maxWidth: 340,
28374
28526
  fontSize: 12,
28375
28527
  lineHeight: "18px",
28376
- color: "var(--dsw-alias-label-tertiary)"
28528
+ color: "var(--sd-label-tertiary)"
28377
28529
  },
28378
28530
  children: t("trash.empty.hint")
28379
28531
  })
@@ -28445,7 +28597,7 @@ window.__ModuleLoader__.load({
28445
28597
  style: {
28446
28598
  display: "inline-flex",
28447
28599
  flex: "none",
28448
- color: "var(--dsw-alias-label-tertiary)"
28600
+ color: "var(--sd-label-tertiary)"
28449
28601
  },
28450
28602
  "aria-hidden": "true",
28451
28603
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconFolderOpenOutlineRegular, { size: 13 })
@@ -28466,7 +28618,7 @@ window.__ModuleLoader__.load({
28466
28618
  marginLeft: "auto",
28467
28619
  flex: "none",
28468
28620
  fontSize: 11.5,
28469
- color: "var(--dsw-alias-label-tertiary)"
28621
+ color: "var(--sd-label-tertiary)"
28470
28622
  },
28471
28623
  children: t("trash.group.count", { count: group.entries.length })
28472
28624
  })
@@ -28533,7 +28685,7 @@ window.__ModuleLoader__.load({
28533
28685
  ...ELLIPSIS,
28534
28686
  fontSize: 12,
28535
28687
  lineHeight: "16px",
28536
- color: "var(--dsw-alias-label-tertiary)"
28688
+ color: "var(--sd-label-tertiary)"
28537
28689
  },
28538
28690
  title: absoluteText(entry.deletedAt),
28539
28691
  children: [
@@ -28548,7 +28700,7 @@ window.__ModuleLoader__.load({
28548
28700
  style: {
28549
28701
  fontSize: 12,
28550
28702
  lineHeight: "16px",
28551
- color: "var(--dsw-alias-label-tertiary)"
28703
+ color: "var(--sd-label-tertiary)"
28552
28704
  },
28553
28705
  children: t("trash.row.workspaceGone")
28554
28706
  }) : null,
@@ -28702,14 +28854,14 @@ window.__ModuleLoader__.load({
28702
28854
  style: {
28703
28855
  fontSize: 12,
28704
28856
  lineHeight: "17px",
28705
- color: "var(--dsw-alias-label-tertiary)"
28857
+ color: "var(--sd-label-tertiary)"
28706
28858
  },
28707
28859
  children: t("trash.select.count", { count: checkedIds.length })
28708
28860
  }) : hasEntries ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
28709
28861
  style: {
28710
28862
  fontSize: 12,
28711
28863
  lineHeight: "17px",
28712
- color: "var(--dsw-alias-label-tertiary)"
28864
+ color: "var(--sd-label-tertiary)"
28713
28865
  },
28714
28866
  children: t("trash.summary", {
28715
28867
  count: entries.length,
@@ -28751,7 +28903,7 @@ window.__ModuleLoader__.load({
28751
28903
  minWidth: 0,
28752
28904
  fontSize: 11.5,
28753
28905
  lineHeight: "16px",
28754
- color: "var(--dsw-alias-label-tertiary)"
28906
+ color: "var(--sd-label-tertiary)"
28755
28907
  },
28756
28908
  children: [
28757
28909
  noteParts[0],
@@ -28844,28 +28996,39 @@ window.__ModuleLoader__.load({
28844
28996
  * **砍掉的**:`anchor`(把横幅对齐到某个元素的水平中心——本插件的撤销提示就是窗口居中)、
28845
28997
  * `actions[].prefix`(动作文案前的连接词,本插件只用「撤销」两个字)。**没砍 `text`/`icon`/`tone`/
28846
28998
  * `holdMs`/`actions`/`onDone`**:唯一的调用点这几样都在用。
28999
+ * **本插件加的**:`paused`(撤销在飞时冻结停留与淡出,失败原文要落回同一条横幅;官方没有这个开关)。
28847
29000
  */
28848
29001
  /** 不指定时全不透明的停留时长。 */
28849
29002
  const DEFAULT_HOLD_MS = 3e3;
28850
29003
  /** 淡出时长。必须与样式表里 `sd-toast-fade` 的时长一致。 */
28851
29004
  const FADE_MS = 1e3;
28852
- function Toast({ text, icon, tone, holdMs = DEFAULT_HOLD_MS, actions, onDone }) {
29005
+ function Toast({ text, icon, tone, holdMs = DEFAULT_HOLD_MS, actions, paused = false, onDone }) {
28853
29006
  const latestOnDone = (0, react.useRef)(onDone);
28854
29007
  (0, react.useLayoutEffect)(() => {
28855
29008
  latestOnDone.current = onDone;
28856
29009
  }, [onDone]);
29010
+ /** 暂停时剩下的停留时间:`null` = 还没起表(新挂载)。淡出动画被 `animationPlayState` 冻住,
29011
+ * timer 也从同一冻结点续,两者才不会一个先到终点。 */
29012
+ const remaining = (0, react.useRef)(null);
28857
29013
  (0, react.useEffect)(() => {
29014
+ if (paused) return;
29015
+ const wait = remaining.current ?? holdMs + FADE_MS;
29016
+ const deadline = Date.now() + wait;
28858
29017
  const timer = setTimeout(() => {
28859
29018
  latestOnDone.current();
28860
- }, holdMs + FADE_MS);
29019
+ }, wait);
28861
29020
  return () => {
28862
29021
  clearTimeout(timer);
29022
+ remaining.current = Math.max(0, deadline - Date.now());
28863
29023
  };
28864
- }, [holdMs]);
29024
+ }, [holdMs, paused]);
28865
29025
  return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
28866
29026
  className: "sd-toast",
28867
29027
  role: "alert",
28868
- style: { "--dsh-toast-hold": `${String(holdMs)}ms` },
29028
+ style: {
29029
+ "--dsh-toast-hold": `${String(holdMs)}ms`,
29030
+ ...paused ? { animationPlayState: "paused" } : {}
29031
+ },
28869
29032
  children: [tone === "success" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
28870
29033
  className: "sd-toast-icon sd-toast-icon-success",
28871
29034
  "aria-hidden": "true",
@@ -28925,14 +29088,15 @@ window.__ModuleLoader__.load({
28925
29088
  tone: "success",
28926
29089
  text: current.kind === "one" ? t("undo.moved", { title: current.title === "" ? t("undo.session") : current.title }) : t("undo.movedBatch", { n: current.n }),
28927
29090
  holdMs: UNDO_HOLD_MS,
29091
+ paused: gate.busy,
29092
+ onDone: clearUndoNotice,
28928
29093
  actions: canUndo ? [{
28929
29094
  label: t("undo.undo"),
28930
29095
  disabled: gate.busy,
28931
29096
  onClick: () => {
28932
29097
  gate.around(() => restoreFromUndo(deps, current, t));
28933
29098
  }
28934
- }] : void 0,
28935
- onDone: clearUndoNotice
29099
+ }] : void 0
28936
29100
  }, seq);
28937
29101
  }
28938
29102
  //#endregion
@@ -28942,11 +29106,14 @@ window.__ModuleLoader__.load({
28942
29106
  *
28943
29107
  * 槽位贡献的注册面见 docs/architecture.md §3,语义归属见 §4 的主人清单;
28944
29108
  * 本文件只做注册与槽位接线。
28945
- * 两行菜单项的 order 与分隔线由下方注册调用和行包装拥有,均排在宿主官方四行之后。
29109
+ * 菜单两行有两条路径:0.2.2+ 走 `ctx.contextMenus` 数据动作(`context-menu-actions.ts`,
29110
+ * afterContent、排在 Archive 之后);旧宿主没有这个服务,才注册 legacy 槽位行。
28946
29111
  *
28947
29112
  * 槽位出处(宿主产物,不是猜的;换代先复核):
28948
29113
  * - sidebar.workspaces.session.menu.item:声明 asar @46391872;数据面投影 @46271806;
28949
- * menuOpenState hook 工厂 @46195835;宿主四行 @46392466 起。
29114
+ * menuOpenState hook 工厂 @46195835;宿主官方菜单行 @46392466 起(pin/rename/fork/archive/session-id)。
29115
+ * 0.2.2 起内置动作改住 ctx.contextMenus,此槽位只剩 legacy React 扩展位;本插件把多选/删除
29116
+ * 迁到数据动作,见 `context-menu-actions.ts`。
28950
29117
  * - shell.overlay:声明 asar @21206189。
28951
29118
  * - sidebar.panellist:声明 @44796980;行渲染 @44783481;label 解析 @44795361(thunk 随 locale)。
28952
29119
  * - main:渲染 @21185725;id 与 panellist 同字,selectPanel 只校验已注册 @21199710。
@@ -28967,8 +29134,11 @@ window.__ModuleLoader__.load({
28967
29134
  * **`locale` 也不在这里**:宿主自己的 `ui-sidebar` 是硬注入它的,但那是"宿主知道自己那一代"的前提;
28968
29135
  * 本插件要跨代活着 ⇒ 走 `ctx.get('locale')` 可选读,缺席时降级到内置 zh 文案(见 `copy.ts` 头注)。
28969
29136
  * 写进 inject 就等于"这一代没有 locale 就不让整个客户端条目激活",而那等于宿主进程死。
29137
+ * **`contextMenus` 同理**:0.2.2+ 用 `ctx.get('contextMenus')` 探测,包本身在 package.json 的
29138
+ * `dsh.client.inject` 里排到前面;旧宿主没有这个服务时回退 legacy 槽位。
28970
29139
  */
28971
29140
  const inject = ["slots"];
29141
+ /** legacy 菜单槽:只在 contextMenus 不可用时注册(0.2.2+ 的内置动作已迁到数据动作)。 */
28972
29142
  const MENU_SLOT = "sidebar.workspaces.session.menu.item";
28973
29143
  /** 会话行首 16px 格(无状态点时归第三方装饰);批量删除的主勾选框落这里,多选模式内可见。 */
28974
29144
  const LEADING_SLOT = "sidebar.session.row.leading";
@@ -29084,18 +29254,43 @@ window.__ModuleLoader__.load({
29084
29254
  if (!await probeChannel(raw)) return;
29085
29255
  const deps = buildDeps(raw);
29086
29256
  const t = createTranslate(client);
29087
- client.slots.inject(MENU_SLOT, () => client.slots.register({
29088
- name: MENU_SLOT,
29089
- id: `${PLUGIN_ID}.delete`,
29090
- order: 900,
29091
- inject: () => ({ t })
29092
- }, DeleteRow));
29093
- client.slots.inject(MENU_SLOT, () => client.slots.register({
29094
- name: MENU_SLOT,
29095
- id: `${PLUGIN_ID}.batch-select`,
29096
- order: 850,
29097
- inject: () => ({ t })
29098
- }, SelectRow));
29257
+ let usesContextMenus = false;
29258
+ const contextMenus = client.get?.("contextMenus");
29259
+ if (contextMenus !== null && contextMenus !== void 0 && typeof contextMenus.register === "function") {
29260
+ const sessions = client.get?.("sessions");
29261
+ try {
29262
+ client.effect(() => contextMenus.register(createSessionMenuContribution(t, sessions?.list)), "session-delete: context menu actions");
29263
+ usesContextMenus = true;
29264
+ } catch (error) {
29265
+ console.warn("[dsh-session-delete] context menu 注册失败,回退 legacy 菜单行:", error);
29266
+ }
29267
+ if (usesContextMenus) try {
29268
+ client.slots.inject("contextMenu.icon", () => client.slots.register({
29269
+ name: "contextMenu.icon",
29270
+ key: ICON_BATCH
29271
+ }, IconCheckOutlineRegular));
29272
+ client.slots.inject("contextMenu.icon", () => client.slots.register({
29273
+ name: "contextMenu.icon",
29274
+ key: ICON_DELETE
29275
+ }, IconTrashOutlineRegular));
29276
+ } catch (error) {
29277
+ console.warn("[dsh-session-delete] context menu 图标注册失败:", error);
29278
+ }
29279
+ }
29280
+ if (!usesContextMenus) {
29281
+ client.slots.inject(MENU_SLOT, () => client.slots.register({
29282
+ name: MENU_SLOT,
29283
+ id: `${PLUGIN_ID}.delete`,
29284
+ order: 900,
29285
+ inject: () => ({ t })
29286
+ }, DeleteRow));
29287
+ client.slots.inject(MENU_SLOT, () => client.slots.register({
29288
+ name: MENU_SLOT,
29289
+ id: `${PLUGIN_ID}.batch-select`,
29290
+ order: 850,
29291
+ inject: () => ({ t })
29292
+ }, SelectRow));
29293
+ }
29099
29294
  client.slots.inject(LEADING_SLOT, () => client.slots.register({
29100
29295
  name: LEADING_SLOT,
29101
29296
  id: `${PLUGIN_ID}.batch-pick-leading`,