@xrn1997/dsh-session-delete 0.0.0-stage → 0.0.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","names":["listeners","useSyncExternalStore","css","forwardRef","useRef","createPortal","useRef","createPortal","nextSeq","listeners","emit","subscribe","useSyncExternalStore","useSyncExternalStore","useState","useRef","useState","useCallback","cloneElement","Fragment","createPortal","useState","useCallback","useMemo"],"sources":["../src/client/copy.ts","../src/shared/wire.ts","../src/client/remote.ts","../src/client/trash-store.ts","../src/client/ui/ui.css?dsh-css-text","../src/client/ui/styles.ts","../src/client/ui/Button.tsx","../src/client/ui/icons.tsx","../src/client/ui/Modal.tsx","../src/client/ui/Tag.tsx","../src/client/ui/Toast.tsx","../src/client/undo-toast.tsx","../src/client/delete-confirm.tsx","../src/client/ui/MenuItemButton.tsx","../src/client/ui/Tooltip.tsx","../src/client/delete-menu-item.tsx","../src/client/trash-entry.tsx","../src/client/ui/format.ts","../src/client/trash-panel.tsx","../src/client/index.tsx"],"sourcesContent":["/**\n * 可见文案(**全部**住在这里)+接上宿主 Client locale 服务的那一步。\n *\n * ## 为什么不是散在各组件里的中文字面量\n *\n * 官方给第三方 UI 插件的规则是「Route visible UI text through the Client locale service」\n * (宿主技能 `cordis-plugin-development/references/ui-plugin.md`)。宿主自己那套就是这么做的:\n * `ui-sidebar` 声明 `inject = ['slots','layout','uiWorkspace','locale','shortcuts']`,并在\n * `ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-sidebar: dictionaries')` 里注册字典\n * (真产物原文)。所以本插件也把文案收成一份带**命名空间**的字典,`zh` 是完整真源、`en` 逐键镜像\n * ——宿主 `register` 的**对象形式要求两套内置语言都齐**(缺一个键就是运行期报错)。\n *\n * ## 服务是**可选读**的,不进 `inject`\n *\n * `ctx.get('locale')` 而不是把 `locale` 写进 `inject`。理由是本仓那条硬约束的另一面:**客户端半的\n * 条目激活失败 = 宿主进程死**(前端启动自检 `web boot: N entry did not activate` ⇒ 桌面壳\n * `reportFatal('web-boot')`)。宿主自己的 `ui-sidebar` 敢硬注入 `locale`(它是宿主的一部分,知道\n * 那一行一定在),而本插件要跨宿主代数活着 ⇒ 服务缺席时**降级到内置 zh 文案继续工作**,\n * 好过整个进程死。这与宿主对可选服务的官方写法同形(`ctx.get('…')?.…`)。\n *\n * 取数面:[`dsh-client-locale`] 的 `LocaleRuntime.register(ns, { zh, en })` 返回一个 disposer\n * (必须挂进 `ctx.effect`,否则卸载后字典留在注册表里,下次挂载会撞 \"already has locale\"),\n * `bind(ns)` 交回一个**按命名空间记忆化**的 `t(key, params)`(官方明说它\"retain stable identity …\n * so they can ride inject surfaces without breaking memoization\"——所以我们把它塞进槽位的\n * `inject` 载荷是正当用法)。\n *\n * ## 面板行那一行字为什么要用 thunk\n *\n * `sidebar.panellist` 的行名字由宿主 `syncPanels` 逐字 `label: resolveSlotLabel(options.label) ?? id`\n * 解析,而 `resolveSlotLabel` 的实现是 `typeof label === 'function' ? label() : label`,\n * 其 JSDoc 原文即 \"thunks follow the active locale\";宿主自己还挂了\n * `ctx.effect(() => ctx.locale.subscribe(syncPanels), 'ui-sidebar: panel labels')` 在语言切换时重读。\n * ⇒ 面板名传 `() => t('trash.title')` 就随语言走;**换成活计数就会 stale**(只在条目或语言变化时重读)。\n */\nimport type { ClientContextLike } from './context.js'\n\n/** 本插件的文案命名空间(第三方必须自带前缀;官方占用的是 `common` / `settings` / `sidebar` 这些)。 */\nconst COPY_NS = 'xrn1997-session-delete'\n\n/** zh 是真源;`en` 必须逐键齐全(宿主的对象形式注册会整体校验)。 */\nconst ZH = {\n 'common.untitled': '(无标题会话)',\n 'common.cancel': '取消',\n 'common.close': '关闭',\n 'common.refresh': '刷新',\n 'common.retry': '重试',\n\n 'trash.title': '回收站',\n 'trash.loading': '正在读回收站…',\n 'trash.readFailed': '回收站读不到',\n 'trash.empty.title': '回收站是空的',\n 'trash.empty.hint': '在会话条目的「…」菜单里选「删除」,会话会先进到这里,随时可以恢复。',\n 'trash.summary': '{count} 个会话 · 共 {size}',\n 'trash.group.count': '{count} 个会话',\n 'trash.project.unknown': '未记录项目',\n 'trash.row.deletedNow': '刚刚删除',\n 'trash.row.deletedAgo': '删除于 {n}{unit}前',\n 'trash.unit.minutes': '分钟',\n 'trash.unit.hours': '小时',\n 'trash.unit.days': '天',\n 'trash.unit.months': '个月',\n 'trash.unit.years': '年',\n 'trash.row.restore': '恢复',\n 'trash.row.purge': '彻底删除…',\n 'trash.footer.note': '回收站本体在宿主数据根下的 {dir} 里,不自动过期;清空后不可恢复。',\n 'trash.emptyAll': '清空回收站…',\n 'trash.confirm.all': '清空回收站?',\n 'trash.confirm.one': '彻底删除 1 个会话?',\n 'trash.confirm.one.meta': '{project} · 删除于 {at} · {size}',\n 'trash.confirm.body': '这会从回收站移除文件,{strong}。',\n 'trash.confirm.body.strong': '之后没有任何副本可以恢复',\n 'trash.confirm.purge': '彻底删除',\n\n 'menu.delete': '删除',\n 'menu.live': '会话正在运行,先停止再删除',\n\n 'dialog.title': '删除这个会话?',\n 'dialog.hint': '会话会移入回收站,之后可以随时恢复。彻底删除是回收站里的另一个动作。',\n 'dialog.confirm': '移入回收站',\n\n 'undo.moved': '{title} 已移入回收站',\n 'undo.session': '会话',\n 'undo.undo': '撤销',\n} as const\n\nexport type CopyKey = keyof typeof ZH\n\n/** en:与 `ZH` 逐键镜像(键集合由 `Record<CopyKey, string>` 盯住,漏一个就编译不过)。\n * 单位词与宿主 en 侧 `relativeTime` 的写法对齐(宿主 en 的桶就是 `5min` / `3h` / `2d` / `4mo` / `1y`)。 */\nconst EN: Record<CopyKey, string> = {\n 'common.untitled': '(Untitled session)',\n 'common.cancel': 'Cancel',\n 'common.close': 'Close',\n 'common.refresh': 'Refresh',\n 'common.retry': 'Retry',\n\n 'trash.title': 'Trash',\n 'trash.loading': 'Reading the trash…',\n 'trash.readFailed': 'Could not read the trash',\n 'trash.empty.title': 'The trash is empty',\n 'trash.empty.hint':\n 'Pick Delete in a session’s “…” menu; the session lands here first and can be restored at any time.',\n 'trash.summary': '{count} sessions · {size} total',\n 'trash.group.count': '{count} sessions',\n 'trash.project.unknown': 'Unrecorded project',\n 'trash.row.deletedNow': 'Deleted just now',\n 'trash.row.deletedAgo': 'Deleted {n}{unit} ago',\n 'trash.unit.minutes': 'min',\n 'trash.unit.hours': 'h',\n 'trash.unit.days': 'd',\n 'trash.unit.months': 'mo',\n 'trash.unit.years': 'y',\n 'trash.row.restore': 'Restore',\n 'trash.row.purge': 'Delete permanently…',\n 'trash.footer.note':\n 'The trash itself lives under the host data root in {dir}; it never expires on its own, and emptying it cannot be undone.',\n 'trash.emptyAll': 'Empty the trash…',\n 'trash.confirm.all': 'Empty the trash?',\n 'trash.confirm.one': 'Delete this session permanently?',\n 'trash.confirm.one.meta': '{project} · deleted {at} · {size}',\n 'trash.confirm.body': 'This removes the files from the trash; {strong}.',\n 'trash.confirm.body.strong': 'no copy remains to restore it',\n 'trash.confirm.purge': 'Delete permanently',\n\n 'menu.delete': 'Delete',\n 'menu.live': 'This session is running. Stop it first.',\n\n 'dialog.title': 'Delete this session?',\n 'dialog.hint':\n 'The session moves to the trash and can be restored at any time. Permanent deletion is a separate action in the trash.',\n 'dialog.confirm': 'Move to trash',\n\n 'undo.moved': '{title} was moved to the trash',\n 'undo.session': 'the session',\n 'undo.undo': 'Undo',\n}\n\nexport type CopyParams = Readonly<Record<string, string | number>>\nexport type Translate = (key: CopyKey, params?: CopyParams) => string\n\n/** 与宿主 `translate` 同一条替换规则(`{name}`,参数缺席时原样留下占位符)。 */\nfunction interpolate(template: string, params?: CopyParams): string {\n if (params === undefined) return template\n return template.replace(/\\{(\\w+)\\}/g, (match, name: string) =>\n name in params ? String(params[name]) : match,\n )\n}\n\n/** 服务缺席时的缺省:字典里那套 zh。**也是组件 prop 的缺省值**——它表达的是\"这次没有 locale 服务\",\n * 与运行期 `createTranslate` 的降级分支是同一件事,不是\"忘了传\"。 */\nexport const zhTranslate: Translate = (key, params) => interpolate(ZH[key], params)\n\n/** 宿主 locale 服务的窄镜像(只声明本插件用到的两个方法 + 一次强转;不装宿主类型包)。 */\ninterface LocaleServiceLike {\n register(ns: string, dicts: { zh: Record<string, string>; en: Record<string, string> }): () => void\n bind(ns: string): (key: string, params?: CopyParams) => string\n}\n\n/**\n * 注册字典并交回本插件的 `t`。服务不在时返回 `zhTranslate`(不抛、不 pending)。\n *\n * 注册必须挂在 `ctx.effect` 上:`register` 返回的 disposer 是**官方契约**(卸载时要把字典摘掉),\n * 而且同一命名空间的同一语言**只能注册一次**——effect 没挂,HMR 重挂就撞 \"already has locale\"。\n */\nexport function createTranslate(ctx: ClientContextLike): Translate {\n const service = ctx.get?.('locale') as LocaleServiceLike | undefined\n if (service === undefined) return zhTranslate\n ctx.effect(() => service.register(COPY_NS, { zh: ZH, en: EN }), 'session-delete: copy dictionaries')\n const bound = service.bind(COPY_NS)\n return (key, params) => bound(key, params)\n}\n\n/**\n * 把一句话里内嵌的那一段拆出来:`'…文件,之后没有任何副本可以恢复。'` 交给调用面用 `<b>` 包中间那段。\n *\n * 为什么不留成\"前段 / 内嵌段 / 后段\"三个键:那等于把英文和中文的语序各钉一次,两段翻译各自\n * 成立、拼起来未必成立。整句一个键 + 一个占位符,语言自己决定那一段落在哪里。\n */\nexport function splitOn(sentence: string, inner: string): [string, string, string] {\n const at = sentence.indexOf(inner)\n if (at < 0) return [sentence, '', '']\n return [sentence.slice(0, at), inner, sentence.slice(at + inner.length)]\n}\n","/**\r\n * 跨半 wire 契约:`/dsh-session-delete-api` 的**前缀、四个端点段、请求字段名**——\r\n * Node 半(`src/api/dispatch.ts` 注册并分发)与浏览器半(`src/client/remote.ts` 同源 fetch)\r\n * 之间唯一的那份真相。两侧都 import 它,不各写一份字面量。\r\n *\r\n * 两条构建期硬约束(不是偏好):\r\n * ① tsdown 的浏览器半纯度门拦 Node 内建与平台模块表外的 `@deepseek-ai/*` 值 import——\r\n * 本文件两者都不涉及,且**零运行时依赖**(client 半把它整个 inline 进 bundle);\r\n * ② 跨插件一律 `import type`——本文件干脆没有 import。\r\n *\r\n * 信封(`RemoteResult`)与失败形状(`RemoteFailure`)也在这里——**因为它们本来就是两半的共同契约**:\r\n * Node 半按它产出(`src/api/dispatch.ts` 的失败支就是 `RemoteFailure` 那两件),浏览器半按它判别\r\n * (`src/client/remote.ts` 的 `unwrapRemoteResult` / `failureOf`)。形状取自宿主网关客户端面的原文\r\n * 「每次 unary 调用 resolve 成 RemoteResult、不为载体问题 reject」,但**它是本仓自己的 wire 契约**,\r\n * 不是对宿主模块的 import。\r\n */\r\n\r\n/**\r\n * 插件专属前缀。\r\n *\r\n * 为什么必须带插件名:宿主 web server 的 `register({kind:'prefix', path})` 对**重复的 (kind, path)\r\n * 直接抛错**(`dsh-host-webserver` 原文 `webserver: duplicate ${route.kind} route \"${route.path}\"`),\r\n * 撞了就是整棵插件树 boot 失败——所以这条路径是与生态的硬边界,不是显示好不好看的问题。\r\n */\r\nexport const SESSION_DELETE_API_PREFIX = '/dsh-session-delete-api'\r\n\r\n/** 四个端点的段名(相对前缀;`GET list` 读、其余三个 `POST` 写)。 */\r\nexport const ROUTES = {\r\n list: '/list',\r\n delete: '/delete',\r\n restore: '/restore',\r\n purge: '/purge',\r\n} as const\r\n\r\n/** 请求体字段名:浏览器半发、Node 半读,同名同义(`purge` 的 `entryId` 缺席 = 清空)。 */\r\nexport const PARAMS = {\r\n sessionId: 'sessionId',\r\n entryId: 'entryId',\r\n} as const\r\n\r\n/**\r\n * 失败信封的**线上形状**(设计稿 §6):两半之间只走这两件——`code` 是判别依据\r\n * (`sessiondelete/<五个码>`),`message` 是上屏原文。**没有 `details`**:本插件的失败没有需要\r\n * 过线的结构化附带物(原始抛出物只住在 Node 进程里,出不了进程边界)。\r\n */\r\nexport interface RemoteFailure {\r\n code: string\r\n message: string\r\n}\r\n\r\n/** 两半之间的一次落地(**信封**):要么值,要么失败。 */\r\nexport type RemoteResult<T> = { ok: true; value: T } | { ok: false; error: RemoteFailure }\r\n\r\n/**\r\n * 进程内判别「这已经是一次失败」——**按标记位认,不按 `instanceof`**(跨 realm / 跨 bundle 都不成立)。\r\n * 位名与判法取自宿主网关客户端面的原文:\r\n * `if (typeof value === \"object\" && value !== null && value.isDSHRemoteError === true && typeof value.code === \"string\") return value`。\r\n *\r\n * 线上递过来的是**一段 JSON**(标记位不跟着过线)⇒ 落到哪一端,由那端的构造处就地补上标记:\r\n * 浏览器半是 `src/client/remote.ts` 的 `throwRemoteFailure`。\r\n */\r\nexport function isDSHRemoteError(value: unknown): value is RemoteFailure {\r\n if (typeof value !== 'object' || value === null) return false\r\n const candidate = value as { isDSHRemoteError?: unknown; code?: unknown }\r\n return candidate.isDSHRemoteError === true && typeof candidate.code === 'string'\r\n}\r\n\r\n/**\r\n * 宿主 `projectKey` 的 **`~XXXX` 转义还原**。\r\n *\r\n * 编码那一半在 Node 侧(`src/ports/host-port.ts` 的 `projectKey`,逐字镜像宿主实现);这里放与它配对的\r\n * 解码,客户端渲染回收站组头用它——**字母表只有这一份**,两边不会各自漂移(配对关系由\r\n * `tests/assembly.test.ts` 的往返用例钉住)。\r\n *\r\n * **只还原可还原的那部分**:不在 `[A-Za-z0-9._-]` 里、又不是 `~` 的字符编成 `~` + 4 位大写十六进制码元,\r\n * 字面 `~` 自己编成 `~007E` ⇒ `~XXXX` 不含歧义。而**分隔符(`/` `\\` `:`)被折成一个 `-`,那一段是有损的**,\r\n * 这里还原不回来——调用方按\"尽力显示\"用它,别拿它反推真实路径。\r\n */\r\nexport function unescapeProjectKey(encoded: string): string {\r\n return encoded.replace(\r\n /~([0-9A-F]{4})/g,\r\n (_whole, hex: string) => String.fromCharCode(parseInt(hex, 16)),\r\n )\r\n}\r\n","/**\r\n * 浏览器半的**取数通道**:同源 fetch 宿主 web server 上的 `/dsh-session-delete-api`,\r\n * 加上信封判别与注入面那一处「展开成裸值」的适配。\r\n *\r\n * 通道形状(裁决):真相/账本/判据全在宿主那边,Node 半用宿主**自己的 web server** 开一条\r\n * 插件专属前缀路由把四个动词搬出来(`src/api/dispatch.ts`),浏览器半同源调用。**不是重造后端**:\r\n * 路由层只做搬运与信封。为什么不用官方的 `remote.<namespace>`:那条路要**生成的 Typert 远程描述符**\r\n * 加一次 `$mount`,而盒内装配清单写死在 `dsh-api-remotes` 里、全宿主没有动态发现,第三方只能自挂自,\r\n * 对一个薄插件不划算。\r\n *\r\n * 三条约束决定了这个文件长这样:\r\n *\r\n * 1. **信封刻意与官方 unary 面同形**:`{ ok: true, value }` / `{ ok: false, error }`。这不是\"还留着\r\n * 官方通道\"——它现在是**我们两半之间**的 wire 契约(Node 半按它产出,见 `src/api/dispatch.ts`),\r\n * 选它是为了让组件面、`unwrapRemoteResult` 与既有用例一行都不用改。失败是**返回**而不是\r\n * reject(同宿主网关原文:\"Every unary call resolves to `RemoteResult<T>` … and never rejects for a\r\n * carrier problem\");只有往返本身失败(打不通、回的不是信封)才 reject,那是**通道缺席**。\r\n * 2. **不许值 import 跨插件包**。`error` 在线上就是一段 JSON(`{code, message}`),不是 `RemoteError`\r\n * 实例——所以判别**所依据的那一个标记位**只镜一份,家在两半共用的 `src/shared/wire.ts`\r\n * (`isDSHRemoteError`)。浏览器半落到本地时用 `throwRemoteFailure` 就地补上 Error 本体与标记位;\r\n * Node 半(`src/remote/map-error.ts`)只用它做「这已经是一次远程失败」的判别,产出的是线上那两件、\r\n * 不造任何 Error,更不 import 宿主那个类。\r\n * 3. **失败原文必须能上屏**。调用方(`callRemote`)只把**抛出来的失败**收成判别式状态——\r\n * `{ ok: false, error }` 不是第三种落地:它在注入面那一处已经被 `unwrapRemoteResult` 抛成了异常,\r\n * 所以到了组件面前只剩「裸值 / throw」两支,调用方只写 `if (!outcome.ok)`,正常流里没有 try/catch;\r\n * 非本信封形状的异常(网络层、TypeError…)也不丢原文(见 `failureOf`)。\r\n *\r\n * 面(`list`/`delete`/`restore`/`purge`)与 Node 半路由的四个段名一一对应(段名本身的家是\r\n * `src/shared/wire.ts`);`list/delete/restore` 回 `TrashEntry`,`purge` 回 `PurgeResult`。\r\n */\r\nimport {\r\n PARAMS,\r\n ROUTES,\r\n SESSION_DELETE_API_PREFIX,\r\n isDSHRemoteError,\r\n type RemoteFailure,\r\n type RemoteResult,\r\n} from '../shared/wire.js'\r\n\r\n/** 回收站条目(镜像 `src/trash/entry.ts` 的 `TrashEntry`;只读面)。 */\r\nexport interface TrashEntryLike {\r\n id: string\r\n sessionId: string\r\n projectDir: string\r\n workspaceId: string\r\n wasArchived: boolean\r\n deletedAt: number\r\n title: string\r\n sizeBytes: number\r\n}\r\n\r\n/** 彻底删除的回执(镜像 `PurgeResult`)。 */\r\nexport interface PurgeResultLike {\r\n removed: number\r\n freedBytes: number\r\n}\r\n\r\n/* 信封(`RemoteResult`)与失败形状(`RemoteFailure`)、以及标记位判别(`isDSHRemoteError`)都住在\r\n * `../shared/wire.js`——两半的共同契约只有那一份家。这里只管通道实现。 */\r\n\r\n/** 任意异常 → 失败面。非带标记的失败(网络层、TypeError…)也保留原文,绝不吞。 */\r\nexport function failureOf(reason: unknown): RemoteFailure {\r\n if (isDSHRemoteError(reason)) return { code: reason.code, message: String(reason.message) }\r\n if (reason instanceof Error) return { code: 'client/unknown', message: reason.message }\r\n return { code: 'client/unknown', message: String(reason) }\r\n}\r\n\r\n/**\r\n * 信封的失败支 → 一个**抛出来的**失败。\r\n *\r\n * 后半句与网关客户端面同形:那边 `error` 是活的 `RemoteError`、`throw result.error` 保持 throw 语义,\r\n * 网关自己的重建函数 `rebuiltFailure(error) { return new RemoteError(error.code, error.message,\r\n * error.details) }` 就干这件事。这里多一层:线上递过来的失败必然是**普通对象**(JSON)而不是本 bundle\r\n * 的 Error 实例,所以就地造一个并打上同一个标记位——**code 与 message 都不丢**\r\n * (`code` 是判别依据,`message` 是上屏的原文;本插件的线上形状没有 `details`,见 `shared/wire.ts`)。\r\n */\r\nfunction throwRemoteFailure(error: RemoteFailure): never {\r\n // 过一遍 `unknown` 再判别:判别的收窄若直接落在已具型的 `error` 上,假支会被 TS 窄成 `never`。\r\n const candidate: unknown = error\r\n if (isDSHRemoteError(candidate)) throw candidate\r\n throw Object.assign(new Error(String(error.message)), {\r\n code: error.code,\r\n isDSHRemoteError: true,\r\n })\r\n}\r\n\r\n/** 注入面用的那一处展开:`ok ? value : throw Object.assign(new Error(error.message), { code })`。 */\r\nexport function unwrapRemoteResult<T>(result: RemoteResult<T>): T {\r\n if (result.ok) return result.value\r\n throwRemoteFailure(result.error)\r\n}\r\n\r\n/** 一次远程调用的落地面:要么值,要么失败——调用方只做状态检查。 */\r\nexport type RemoteOutcome<T> = { ok: true; value: T } | { ok: false; failure: RemoteFailure }\r\n\r\n/**\r\n * 跑一次远程调用,把两种落地(值 / 抛出的失败)收成一个状态。\r\n *\r\n * 「返回的失败」在这里不再是一种落地:`{ ok: false, error }` 已在注入面被 `unwrapRemoteResult`\r\n * 抛成了异常,所以到这一层的只有裸值或 throw——判别留在 `failureOf` 里(带标记位的失败带出它自己的\r\n * code,其余按原文兜底)。\r\n */\r\nexport async function callRemote<T>(run: () => Promise<T>): Promise<RemoteOutcome<T>> {\r\n try {\r\n return { ok: true, value: await run() }\r\n } catch (reason) {\r\n return { ok: false, failure: failureOf(reason) }\r\n }\r\n}\r\n\r\n/** 本插件前缀路由那套**响应信封**的窄镜像(不是宿主服务 `ctx.remote.*`——那条 typert 通道见 §10):\r\n * 每个方法 resolve 成 `RemoteResult<T>`。 */\r\nexport interface RawSessionDeleteRemote {\r\n list(): Promise<RemoteResult<TrashEntryLike[]>>\r\n delete(sessionId: string): Promise<RemoteResult<TrashEntryLike>>\r\n restore(entryId: string): Promise<RemoteResult<TrashEntryLike>>\r\n purge(entryId?: string): Promise<RemoteResult<PurgeResultLike>>\r\n}\r\n\r\n/* ── 通道实现:同源 fetch 我们自己的前缀路由 ─────────────── */\r\n\r\n/**\r\n * 一次 HTTP 往返 → 信封。**`ok:false` 也是落地**(返回,不抛);只有往返本身失败才抛:\r\n * 打不通(宿主没起来 / 路由没挂)或回的不是信封(前缀路由缺席时请求会落到 SPA 兜底处理器上,\r\n * 回的是 index.html)。两种抛都带原文,组件侧的 `failureOf` 会把它们显示成 `client/unknown` + 原因。\r\n */\r\nasync function requestEnvelope<T>(path: string, init: RequestInit): Promise<RemoteResult<T>> {\r\n let response: Response\r\n try {\r\n response = await fetch(path, init)\r\n } catch (reason) {\r\n throw new Error(`取数通道打不通:${reason instanceof Error ? reason.message : String(reason)}`)\r\n }\r\n try {\r\n return (await response.json()) as RemoteResult<T>\r\n } catch {\r\n throw new Error(`取数通道回的不是信封(HTTP ${String(response.status)})`)\r\n }\r\n}\r\n\r\n/**\r\n * 前缀路由的 fetch 实现。**相对路径**:不拼 base、不认 origin——页面本身就是从宿主 web server\r\n * 加载的,同源是这条薄线的全部前提(也是它敢不带凭据的原因:宿主那行 URL 上的 browser-trust token\r\n * 不是这条通道的凭据,栅栏在 Node 半那一侧按 Origin/Referer 判)。\r\n */\r\nexport function createHttpRemote(prefix: string = SESSION_DELETE_API_PREFIX): RawSessionDeleteRemote {\r\n const post = (body: Record<string, unknown>): RequestInit => ({\r\n method: 'POST',\r\n headers: { 'content-type': 'application/json' },\r\n body: JSON.stringify(body),\r\n })\r\n return {\r\n list: () => requestEnvelope<TrashEntryLike[]>(prefix + ROUTES.list, { method: 'GET' }),\r\n delete: (sessionId) =>\r\n requestEnvelope<TrashEntryLike>(prefix + ROUTES.delete, post({ [PARAMS.sessionId]: sessionId })),\r\n restore: (entryId) =>\r\n requestEnvelope<TrashEntryLike>(prefix + ROUTES.restore, post({ [PARAMS.entryId]: entryId })),\r\n purge: (entryId) =>\r\n requestEnvelope<PurgeResultLike>(\r\n prefix + ROUTES.purge,\r\n post(entryId === undefined ? {} : { [PARAMS.entryId]: entryId }),\r\n ),\r\n }\r\n}\r\n\r\n/**\r\n * 取数通道探测:一次 `list` 走通(或走到)就是\"通道在\"。\r\n *\r\n * 判据是**「拿到信封」而不是「操作成功」**:`{ok:false, error}` 也是一次成功的往返(路由在、依赖接上了、\r\n * 错误映射也生效了),那时该做的是把入口显示出来、让原文上屏;只有往返本身失败才算通道缺席 ⇒\r\n * 一个槽位贡献都不注册(设计稿 §7「未接通态」)。\r\n */\r\nexport async function probeChannel(raw: RawSessionDeleteRemote): Promise<boolean> {\r\n try {\r\n await raw.list()\r\n return true\r\n } catch {\r\n return false\r\n }\r\n}\r\n\r\n/** 组件面(展开后):同一个面,失败以 throw 表达。成员与上面一字不差。 */\r\nexport interface SessionDeleteRemote {\r\n list(): Promise<TrashEntryLike[]>\r\n delete(sessionId: string): Promise<TrashEntryLike>\r\n restore(entryId: string): Promise<TrashEntryLike>\r\n purge(entryId?: string): Promise<PurgeResultLike>\r\n}\r\n\r\n/**\r\n * 注入面的**唯一一处适配**:`RemoteResult` → 裸值。\r\n *\r\n * **为什么必须有这一步**:通道那头的调用 resolve 成 `RemoteResult<T>`(`{ ok: true, value }` /\r\n * `{ ok: false, error }`),失败是**返回**而不是 reject。组件面要的是「拿到值 / 抛异常」,\r\n * 所以在这里展开一次:`ok ? value : throw`。放在这一处而不是组件里,是为了让三个组件的 props 契约\r\n * (以及钉住它们的用例)保持裸值形状不变。\r\n */\r\nexport function buildDeps(raw: RawSessionDeleteRemote): SessionDeleteRemote {\r\n return {\r\n list: () => raw.list().then((result) => unwrapRemoteResult(result)),\r\n delete: (sessionId) => raw.delete(sessionId).then((result) => unwrapRemoteResult(result)),\r\n restore: (entryId) => raw.restore(entryId).then((result) => unwrapRemoteResult(result)),\r\n purge: (entryId) => raw.purge(entryId).then((result) => unwrapRemoteResult(result)),\r\n }\r\n}\r\n\r\n/** 「删除」确认框的宿主只需要「删除」这一件(撤销提示要的 `restore` 在 `undo-toast.tsx` 自己的 deps 里)。 */\r\nexport type DeleteConfirmDeps = Pick<SessionDeleteRemote, 'delete'>\r\n","/**\r\n * 回收站清单的**失效通告台**:动过回收站的动作戳一下,清单面板据此重新取数。\r\n *\r\n * 为什么需要它(真机反馈):回收站面板是「挂载时取一次数」(`useEffect(reload)`),而删除发生在\r\n * **侧栏菜单**那边(`delete-confirm.tsx` 的常驻宿主),两者没有共同的父组件——删完之后中央那个\r\n * 面板(如果正开着)不会自己重读,用户得切走再切回来(= 重新挂载)才看得见新条目。撤销(`undo-toast.tsx`)\r\n * 同理。\r\n *\r\n * 形状与两个邻居同款(`undo-toast.tsx` 的通知台、`delete-confirm.tsx` 的现场):**模块级现场 +\r\n * `useSyncExternalStore`**,不为此引状态库、也不引入上下文——三样东西都挂在 `shell.overlay`/`main`\r\n * 这些各不相邻的座位上,模块级是它们唯一的共同点。\r\n *\r\n * 语义只有\"变过\"这一件事:一个单调递增的修订号。面板把它写进 effect 的依赖里,戳一次重读一次;\r\n * 不需要知道是谁动的、动了什么(清单本体永远是问宿主要)。\r\n */\r\nimport { useSyncExternalStore } from 'react'\r\n\r\nlet revision = 0\r\nconst listeners = new Set<() => void>()\r\n\r\n/** 通告一次\"回收站变过了\"(删除成功、撤销成功;面板自己的恢复/彻底删除则就地重读)。 */\r\nexport function invalidateTrash(): void {\r\n revision += 1\r\n for (const listener of listeners) listener()\r\n}\r\n\r\nfunction getTrashRevision(): number {\r\n return revision\r\n}\r\n\r\nfunction subscribeTrash(listener: () => void): () => void {\r\n listeners.add(listener)\r\n return () => {\r\n listeners.delete(listener)\r\n }\r\n}\r\n\r\n/** 供面板订阅。 */\r\nexport function useTrashRevision(): number {\r\n return useSyncExternalStore(subscribeTrash, getTrashRevision, getTrashRevision)\r\n}\r\n","export default \"/*\\n * 本插件自己的控件样式(**不再 require 宿主的 `@deepseek-ai/dsh-client-ui-primitives`**)。\\n *\\n * 出处:逐条抄自 `@deepseek-ai/dsh-client-ui-primitives@0.2.0-rc.2` 随包发布的明文 CSS\\n * (`lib/Button.module.css`、`Tag.module.css`、`Modal.module.css`、`Tooltip.module.css`、\\n * `Toast.module.css`、`Menu.module.css`)。这是官方给第三方 UI 插件的处方:\\n * 「copy markup, CSS, and behavior from the primitive into the plugin … Rename copied classes under\\n * your plugin's prefix, keep only `--dsw-alias-*` token references」(宿主技能\\n * `cordis-plugin-development/references/practices.md` 原文)。理由是那个包**随宿主换代而变、且不经类型检查**,\\n * 一个抛错的组件会把整个槽位条目打空(控制台 `slot entry crashed in '<slot>'`)。\\n *\\n * 三条改写规则:\\n * 1. **类名全部加本插件前缀 `sd-`**(原来是 CSS module 的哈希名,全局无冲突由前缀承担)。\\n * 2. **配色一律走 token**,一个颜色的字面量都没有——所以宿主换主题、改名 token 时最差是难看,\\n * 不会崩。几何量(圆角/阴影/层级)同样取 token;**只有字号/行高/尺寸是字面量 px**,与官方 CSS 逐字一致。\\n * 3. **`@keyframes` 全部改名**(`sd-` 前缀):它们是全局命名空间,撞名会互相覆盖。\\n *\\n * 只收本插件真正用到的部分:Button 的 `primary`/`ghost`/`outline` 三档与 `md`/`sm` 两号\\n * (没有 `toolbar` 变体、没有 `lg`)、Tag 的 `danger` 一档、Modal 的全部骨架、Tooltip 的三个方位、\\n * Toast 全部、Menu 行的全部(含 danger 态与 separator)。**没抄进来的就是本插件不用的**,\\n * 不预先铺开——宿主那套里还有几十个变体与控件,抄全了等于把它的表面积整个搬进来。\\n */\\n\\n/* ────────────────────────── Button ────────────────────────── */\\n\\n.sd-button {\\n box-sizing: border-box;\\n display: inline-flex;\\n align-items: center;\\n justify-content: center;\\n gap: 4px;\\n border: none;\\n border-radius: var(--dsw-radius-md);\\n cursor: pointer;\\n font-size: 14px;\\n line-height: 22px;\\n color: var(--dsw-alias-label-primary);\\n background: transparent;\\n padding: 0 14px;\\n}\\n\\n.sd-button:disabled {\\n cursor: not-allowed;\\n opacity: 0.4;\\n}\\n\\n.sd-button-md {\\n height: 36px;\\n}\\n\\n/* 紧凑档:尺寸不随填充/描边换档(官方原文的注释即此意)。 */\\n.sd-button-sm {\\n height: 28px;\\n font-size: 12px;\\n line-height: 18px;\\n padding: 0 10px;\\n border-radius: var(--dsw-radius-sm);\\n}\\n\\n.sd-button-primary {\\n background: var(--dsw-alias-button-primary-fill);\\n color: var(--dsw-alias-label-primary-foreground);\\n}\\n\\n.sd-button-primary:hover:not(:disabled) {\\n background: var(--dsw-alias-button-primary-hover);\\n}\\n\\n.sd-button-ghost:hover:not(:disabled) {\\n background: var(--dsw-alias-interactive-bg-hover);\\n}\\n\\n.sd-button-ghost:active:not(:disabled) {\\n background: var(--dsw-alias-interactive-bg-active);\\n}\\n\\n.sd-button-outline {\\n border: 0.5px solid var(--dsw-alias-border-l3);\\n background: transparent;\\n}\\n\\n.sd-button-outline:hover:not(:disabled) {\\n background: var(--dsw-alias-interactive-bg-hover);\\n}\\n\\n.sd-button-icon {\\n display: inline-flex;\\n width: 16px;\\n height: 16px;\\n align-items: center;\\n justify-content: center;\\n}\\n\\n/* ────────────────────────── Tag ────────────────────────── */\\n\\n/* 胶囊几何固定:一个 tag 在哪里都是同一个尺寸,只有色板随 tone 走。 */\\n.sd-tag {\\n display: inline-flex;\\n align-items: center;\\n border-radius: 999px;\\n corner-shape: round;\\n padding: 1px 8px;\\n font-size: 11px;\\n line-height: 17px;\\n font-weight: 500;\\n white-space: nowrap;\\n}\\n\\n/* 状态色自带填充:底色由自身颜色 10% 混出,换色板时底与字一起走,不需要第二个 token。 */\\n.sd-tag-danger {\\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);\\n color: var(--dsw-alias-state-error-primary);\\n}\\n\\n/* ────────────────────────── Modal ────────────────────────── */\\n\\n/* 整视口层(figma Mask + Dialog):蒙层 + 居中卡片。四边留 24px,上下按外壳公布的浮层内缩加宽,\\n 高卡片才不会顶到外壳。卡片按这个内边距盒取尺寸:消费方用 `max-height:100%` 收口,不自己算视口。 */\\n.sd-modal-root {\\n pointer-events: auto;\\n position: fixed;\\n inset: 0;\\n z-index: 1000;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n padding: max(24px, var(--dsh-frame-overlay-top, 24px)) 24px;\\n}\\n\\n/* 只有绘制避让原生标题栏;根仍然挡住背景点击。 */\\n.sd-modal-mask {\\n position: absolute;\\n inset: var(--dsh-frame-chrome-top, 0px) 0 0;\\n backdrop-filter: var(--dsw-mask-blur);\\n}\\n\\n.sd-modal-mask::after {\\n content: '';\\n position: absolute;\\n inset: 0;\\n background: var(--dsw-alias-bg-mask-1);\\n animation: sd-modal-enter var(--ds-transition-duration) var(--ds-ease-in-out);\\n}\\n\\n.sd-modal-dialog {\\n box-sizing: border-box;\\n position: relative;\\n z-index: 1;\\n display: flex;\\n flex-direction: column;\\n gap: 20px;\\n width: min(380px, 100%);\\n padding: 0 0 24px;\\n overflow: hidden;\\n border: 0;\\n border-radius: var(--dsw-radius-panel);\\n background: var(--dsw-alias-bg-layer-2);\\n box-shadow: var(--dsw-elevation-prominent);\\n animation: sd-modal-enter var(--ds-transition-duration) var(--ds-ease-in-out);\\n}\\n\\n@keyframes sd-modal-enter {\\n from { opacity: 0; }\\n to { opacity: 1; }\\n}\\n\\n@media (prefers-reduced-motion: reduce) {\\n .sd-modal-mask::after,\\n .sd-modal-dialog { animation: none; }\\n}\\n\\n.sd-modal-dialog:focus {\\n outline: none;\\n}\\n\\n.sd-modal-content {\\n display: flex;\\n flex-direction: column;\\n width: 100%;\\n}\\n\\n/* 头行:标题左、关闭钮右。 */\\n.sd-modal-header {\\n display: flex;\\n align-items: center;\\n justify-content: space-between;\\n gap: 8px;\\n padding: 22px 14px 12px 24px;\\n}\\n\\n.sd-modal-title {\\n margin: 0;\\n font-size: 16px;\\n line-height: 24px;\\n font-weight: 500;\\n color: var(--dsw-alias-label-primary);\\n}\\n\\n.sd-modal-close {\\n flex: none;\\n display: inline-flex;\\n align-items: center;\\n justify-content: center;\\n width: 28px;\\n height: 28px;\\n border: none;\\n border-radius: var(--dsw-radius-sm);\\n background: transparent;\\n cursor: pointer;\\n color: var(--dsw-alias-label-secondary);\\n}\\n\\n.sd-modal-close:hover {\\n background: var(--dsw-alias-interactive-bg-hover);\\n}\\n\\n.sd-modal-body {\\n display: flex;\\n flex-direction: column;\\n min-width: 0;\\n margin-top: 20px;\\n padding: 0 24px;\\n}\\n\\n.sd-modal-footer {\\n display: flex;\\n align-items: center;\\n justify-content: flex-end;\\n gap: 8px;\\n padding: 0 24px;\\n}\\n\\n/* ────────────────────────── Tooltip ────────────────────────── */\\n\\n.sd-tooltip-bubble {\\n display: inline-flex;\\n align-items: center;\\n gap: 8px;\\n position: fixed;\\n z-index: 100;\\n /* 定宽收缩只按 `left` 到视口右缘量,靠右的锚点会提前折行;\\n `max-content` 只按文案定宽,再用半个视口收口。 */\\n width: max-content;\\n max-width: 50vw;\\n padding: 3px 7px;\\n border-radius: var(--dsw-radius-sm);\\n background: var(--dsw-alias-tooltip-bg);\\n color: var(--dsw-static-neutral-bluish-00);\\n font-size: 13px;\\n line-height: 20px;\\n white-space: pre-line;\\n /* 不可断的长 token(URL、路径)不许顶破 max-width。 */\\n overflow-wrap: break-word;\\n pointer-events: none;\\n animation: sd-tooltip-in 150ms var(--ds-ease-in-out);\\n}\\n\\n.sd-tooltip-bubble[data-portal] { z-index: 1100; }\\n.sd-tooltip-label { min-width: 0; }\\n\\n.sd-tooltip-bubble[data-side='right'] {\\n transform: translateY(-50%);\\n}\\n\\n.sd-tooltip-bubble[data-side='bottom'] {\\n transform: translateX(-50%);\\n}\\n\\n.sd-tooltip-bubble[data-side='top'] {\\n transform: translate(-50%, -100%);\\n}\\n\\n@keyframes sd-tooltip-in {\\n from { opacity: 0; }\\n}\\n\\n@media (prefers-reduced-motion: reduce) {\\n .sd-tooltip-bubble {\\n animation: none;\\n }\\n}\\n\\n/* ────────────────────────── Toast ────────────────────────── */\\n\\n/* 淡出延迟由组件以 `--dsh-toast-hold` 给出 ⇒ 同一个值同时驱动卸载计时器与这段动画,\\n 两者不可能再对不上。淡出**时长**仍需与组件里的 FADE_MS 一致(没有 owner 会改它)。 */\\n.sd-toast {\\n position: fixed;\\n top: 40px;\\n left: 50%;\\n /* 高过图片灯箱底层的 1000:预览开着时报错也必须读得到。 */\\n z-index: 1100;\\n /* 通告不截点击。 */\\n pointer-events: none;\\n display: flex;\\n align-items: center;\\n gap: 10px;\\n /* 定宽盒设了 `left` 只按右侧空间收缩(-50% 的位移发生在定尺之后)⇒ 靠窗边的横幅会提前折行。\\n `max-content` 只按文案定宽,再由下面的 max-width 收口。 */\\n width: max-content;\\n max-width: min(640px, calc(100vw - 48px));\\n padding: 12px 16px;\\n border-radius: var(--dsw-radius-lg);\\n /* 设计平台的 toast 表面在**两套主题下都是深的**(浅色 bluish-800 / 深色 bluish-750),\\n 所以这里的字色与动作色是固定值,不随主题翻转。 */\\n background: var(--dsw-alias-toast-bg);\\n color: var(--dsw-alias-toast-label);\\n font-size: 14px;\\n line-height: 22px;\\n box-shadow: var(--dsw-shadow-lv3);\\n transform: translateX(-50%);\\n animation:\\n sd-toast-in 160ms ease-out,\\n sd-toast-fade 1000ms ease var(--dsh-toast-hold, 3000ms) forwards;\\n}\\n\\n.sd-toast-icon {\\n display: grid;\\n place-items: center;\\n flex: none;\\n color: var(--dsw-alias-state-warn-label);\\n}\\n\\n.sd-toast-icon-success {\\n color: var(--dsw-alias-state-success-primary);\\n}\\n\\n.sd-toast-text {\\n min-width: 0;\\n}\\n\\n.sd-toast-action {\\n display: inline;\\n padding: 0;\\n border: 0;\\n background: none;\\n color: var(--dsw-static-deepseek-400);\\n font: inherit;\\n cursor: pointer;\\n /* 动作文字与周围文案之间的呼吸位。 */\\n margin-inline: 3px;\\n /* 横幅表面整体是穿透点击的,只有动作文字接指针。 */\\n pointer-events: auto;\\n}\\n\\n.sd-toast-action:hover {\\n opacity: 0.8;\\n}\\n\\n@keyframes sd-toast-in {\\n from {\\n opacity: 0;\\n transform: translate(-50%, -6px);\\n }\\n\\n to {\\n opacity: 1;\\n transform: translate(-50%, 0);\\n }\\n}\\n\\n@keyframes sd-toast-fade {\\n to {\\n opacity: 0;\\n visibility: hidden;\\n }\\n}\\n\\n/* 减动效去掉滑入;延迟淡出(透明度变化、不是位移)仍然在定时卸载前收掉横幅。 */\\n@media (prefers-reduced-motion: reduce) {\\n .sd-toast {\\n animation: sd-toast-fade 1000ms ease var(--dsh-toast-hold, 3000ms) forwards;\\n }\\n}\\n\\n/* ────────────────────────── 菜单行 ────────────────────────── */\\n\\n.sd-menu-item-wrap {\\n position: relative;\\n}\\n\\n/* 菜单格:紧凑文字、图标与选中间距。 */\\n.sd-menu-item {\\n display: flex;\\n align-items: center;\\n gap: 6px;\\n width: 100%;\\n min-height: 34px;\\n padding: 6px 8px;\\n border: none;\\n border-radius: var(--dsw-radius-md);\\n background: transparent;\\n cursor: pointer;\\n font-size: 13px;\\n line-height: 20px;\\n color: var(--dsw-alias-label-primary);\\n text-align: left;\\n}\\n\\n.sd-menu-item:hover:not(:disabled) {\\n background: var(--dsw-alias-interactive-bg-hover);\\n}\\n\\n/* 方向键移动的是真焦点 ⇒ 键盘所在的那一行拿指针同款填充;填充就是这一行的焦点呈现,\\n 浏览器默认的 ring 再叠一层就重了。 */\\n.sd-menu-item:focus-visible:not(:disabled) {\\n background: var(--dsw-alias-interactive-bg-hover);\\n outline: none;\\n}\\n\\n.sd-menu-item:disabled {\\n opacity: 0.4;\\n cursor: not-allowed;\\n}\\n\\n.sd-menu-item-icon {\\n display: inline-flex;\\n flex: none;\\n width: 14px;\\n height: 14px;\\n align-items: center;\\n justify-content: center;\\n color: var(--dsw-alias-menu-icon);\\n}\\n\\n.sd-menu-item-icon svg {\\n width: 14px;\\n height: 14px;\\n}\\n\\n.sd-menu-item-label {\\n flex: 1;\\n min-width: 0;\\n overflow: hidden;\\n text-overflow: ellipsis;\\n white-space: nowrap;\\n}\\n\\n/* 破坏性行:error 字/图标 + danger 悬停底。 */\\n.sd-menu-item-danger {\\n color: var(--dsw-alias-state-error-primary);\\n}\\n\\n.sd-menu-item-danger .sd-menu-item-icon {\\n color: var(--dsw-alias-state-error-primary);\\n}\\n\\n.sd-menu-item-danger:hover:not(:disabled) {\\n background: var(--dsw-alias-interactive-bg-hover-danger);\\n}\\n\\n.sd-menu-item-danger:focus-visible:not(:disabled) {\\n background: var(--dsw-alias-interactive-bg-hover-danger);\\n outline: none;\\n}\\n\\n/* 分隔格:发丝线上下的间距。取 l2 而不是 l1——l1 在这个表面上几乎看不见。 */\\n.sd-menu-separator {\\n height: 0.5px;\\n margin: 3px 2px;\\n background: var(--dsw-alias-border-l2);\\n}\\n\"","/**\n * 本插件控件的样式装载:把 `ui.css` 的文本挂成一个 `<style>`,**每个 ui 组件 import 本模块**\n * (副作用导入),所以「组件在屏幕上」必然等于「样式在场」。\n *\n * 为什么是往 DOM 里注 `<style>`、而不是构建期抽成 `.css` 资产:\n * 宿主对客户端插件的样式**本来就有这条一等公民通道**——浏览器半的模块表在**工厂物化**\n * (factory materialization)时会把插件自己注入的 `<style>` 认领归档(真产物原文:*\"Claim and\n * inventory the `<style>` tags a factory injected during materialization: preset-emitted tags arrive\n * pre-tagged with `data-plugin`; any untagged tag is claimed for the materializing plugin (HMR\n * bookkeeping)\"*),卸载与换代时由它统一摘掉。这就是官方给插件 UI 的处方\n * (`references/ui-plugin.md`:「Component-local styles can render as React elements so unmounting\n * removes them」)。\n *\n * 因此装载点选在**模块作用域**而不是 `apply()` 里:模块作用域正好落在物化那一刻,能进宿主的认领表;\n * 挂在 `apply` 里就晚了一拍。反复装载由 `data-dsh-session-delete` 标记挡住(同一插件在同一文档里\n * 是单例,模块也只物化一次,这一层是给 HMR 换代留下的保险)。\n *\n * **不发布任何全局状态**:官方的 `pointerModality` 会往 `document.documentElement` 写\n * `data-input-modality` 属性,那是宿主自己那份 primitives 的职责(它已经在跑),我们只在本文件里\n * 记一个本地标志位(见 `Tooltip.tsx`)。\n */\nimport css from './ui.css'\n\n/** 认领标记:既是宿主 HMR 的归档线索,也是「已经装过」的幂等判据。 */\nconst STYLE_MARKER = 'dsh-session-delete'\n\nfunction install(): void {\n if (typeof document === 'undefined') return\n const head = document.head\n if (head === null) return\n if (head.querySelector(`style[data-dsh-session-delete=\"${STYLE_MARKER}\"]`) !== null) return\n const element = document.createElement('style')\n element.setAttribute('data-dsh-session-delete', STYLE_MARKER)\n element.textContent = css\n head.appendChild(element)\n}\n\ninstall()\n\n/**\n * 输入模态:最近一次输入是**指针**还是**键盘**。\n *\n * `Tooltip` 用它挡掉「指针交互之后紧接着到来的 focus」——菜单收起时宿主会把焦点还给触发按钮,\n * 那一拍不该把我们的提示顶出来(官方 `pointerModality()` 的同一条契约,实现也一样:\n * `pointerdown` 置真、`keydown` 置假)。官方还会据此发布 `data-input-modality` 去控制焦点环的\n * 显隐,**那件事不复制**:宿主自己那份 primitives 已经在同一份文档上发布了它,我们再来一份就是\n * 两个写入者抢同一个属性。\n */\nlet pointerModality = false\n\nif (typeof window !== 'undefined') {\n window.addEventListener(\n 'pointerdown',\n () => {\n pointerModality = true\n },\n true,\n )\n window.addEventListener(\n 'keydown',\n () => {\n pointerModality = false\n },\n true,\n )\n}\n\n/** 最近一次输入是否来自指针(仅本插件自用)。 */\nexport function isPointerModality(): boolean {\n return pointerModality\n}\n","/**\n * `Button`(**照抄** `@deepseek-ai/dsh-client-ui-primitives@0.2.0-rc.2` 的 Button,出处与三条改写规则\n * 见 `ui.css` 头注):`<button type=\"button\">`,`variant` × `size` 两个正交档,可带前导图标。\n *\n * 只收本插件用到的面:`primary` / `ghost` / `outline` 三档与 `md`(36px) / `sm`(28px) 两号;\n * 官方还有 `toolbar` 变体与 `className` 之外的转发面,这里不铺开。\n *\n * **转发 `ref`**:官方把 `ref` 交给原生按钮(\"for focus management and overlay anchors\"),\n * `Modal` 的初始焦点与外壳的对齐测量都吃这个。React 18 的 `forwardRef` 与官方实现同形。\n */\nimport { forwardRef, type ButtonHTMLAttributes, type ReactElement, type ReactNode } from 'react'\nimport './styles.js'\n\nexport interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n /** 视觉族;缺省 `ghost`。 */\n variant?: 'primary' | 'ghost' | 'outline'\n /** `md` = 36px 控件(12px 圆角)/`sm` = 28px 控件(8px 圆角)。 */\n size?: 'md' | 'sm'\n /** 前导 16px 图标节点。 */\n icon?: ReactNode\n}\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(\n { variant = 'ghost', size = 'md', icon, className, children, ...rest },\n ref,\n): ReactElement {\n const classes = ['sd-button', `sd-button-${variant}`, `sd-button-${size}`, className]\n .filter((name) => name !== undefined && name !== '')\n .join(' ')\n return (\n <button ref={ref} type=\"button\" className={classes} {...rest}>\n {icon === undefined || icon === null ? null : <span className=\"sd-button-icon\">{icon}</span>}\n {children}\n </button>\n )\n})\n","/**\n * 本插件 UI 层要用的六个图标(**逐条抄自** `@deepseek-ai/dsh-client-ui-primitives@0.2.0-rc.2`\n * 随包发布的明文产物 `lib/index.js`;出处与改写规则见 `ui.css` 头注)。\n *\n * 只留本插件用到的:垃圾桶(删除动作 / 回收站)、警示(失败面)、刷新、打开的项目文件夹、\n * 关闭叉(对话框)、圆圈对勾(撤销提示的 success 档)。**几何、`viewBox`、`strokeWidth` 逐字不动**\n * ——它们是官方图标集的形状,改了就不再与宿主同屏同形。\n *\n * 两条与官方实现的差异(都是减法,不是改写):\n * - 只暴露 `size`(官方是 `{size, className, strokeWidth}` 三件套 + `Medium` 1.3px 那一档):\n * 本插件六处调用只传 `size`,加别的等于摊开官方整个图标 API。\n * - 不做 `ICON_REGULAR_STROKE` / `ICON_MEDIUM_STROKE` 常量:只有一个档位,直接写 1。\n */\nimport type { ReactElement, ReactNode } from 'react'\n\n/** 宿主图标组件的实际使用面:一个方形边长,色走 `currentColor`。 */\nexport interface IconProps {\n size?: number\n}\n\n/** 六个图标共用的外框(官方每个 artwork 函数里逐字重复的那一段)。 */\nfunction IconFrame({ size = 16, children }: IconProps & { children: ReactNode }): ReactElement {\n return (\n <svg\n width={size}\n height={size}\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n strokeWidth={1}\n >\n {children}\n </svg>\n )\n}\n\nexport function IconTrashOutlineRegular({ size }: IconProps): ReactElement {\n return (\n <IconFrame size={size}>\n <path d=\"M1.28149 3.88831H14.7187\" stroke=\"currentColor\" />\n <path\n d=\"M5.41602 3.88833V2.47962C5.41602 2.29282 5.52492 2.11366 5.71876 1.98157C5.9126 1.84948 6.17551 1.77527 6.44964 1.77527H9.55053C9.82466 1.77527 10.0876 1.84948 10.2814 1.98157C10.4753 2.11366 10.5842 2.29282 10.5842 2.47962V3.88833\"\n stroke=\"currentColor\"\n />\n <path\n d=\"M2.57349 3.88831L3.19366 13.2943C3.21937 13.5502 3.33952 13.7872 3.53065 13.9593C3.72178 14.1313 3.97016 14.2259 4.22729 14.2246H11.7728C12.0299 14.2259 12.2783 14.1313 12.4694 13.9593C12.6605 13.7872 12.7807 13.5502 12.8064 13.2943L13.4266 3.88831\"\n stroke=\"currentColor\"\n />\n <path d=\"M6.44946 6.98926V11.1238\" stroke=\"currentColor\" />\n <path d=\"M9.55054 6.98926V11.1238\" stroke=\"currentColor\" />\n </IconFrame>\n )\n}\n\nexport function IconWarningOutlineRegular({ size }: IconProps): ReactElement {\n return (\n <IconFrame size={size}>\n <path\n d=\"M8 14.5C11.5899 14.5 14.5 11.5899 14.5 8C14.5 4.41015 11.5899 1.5 8 1.5C4.41015 1.5 1.5 4.41015 1.5 8C1.5 11.5899 4.41015 14.5 8 14.5Z\"\n stroke=\"currentColor\"\n />\n <path d=\"M8 4.29199V9.79199\" stroke=\"currentColor\" />\n <path d=\"M8 10.708V11.708\" stroke=\"currentColor\" />\n </IconFrame>\n )\n}\n\nexport function IconRefreshOutlineRegular({ size }: IconProps): ReactElement {\n return (\n <IconFrame size={size}>\n <path\n d=\"M14.5001 8C14.5 9.28552 14.1188 10.5422 13.4045 11.611C12.6903 12.6799 11.6752 13.5129 10.4875 14.0049C9.29982 14.4968 7.99295 14.6255 6.73212 14.3747C5.4713 14.124 4.31314 13.505 3.4041 12.596C2.49514 11.687 1.87614 10.5288 1.62537 9.26798C1.37459 8.00716 1.50331 6.70028 1.99525 5.51261C2.48719 4.32494 3.32025 3.30981 4.3891 2.59557C5.45795 1.88134 6.71458 1.50008 8.0001 1.5C9.9001 1.5 11.7001 2.3 13.0001 3.6L14.5001 5.1\"\n stroke=\"currentColor\"\n />\n <path d=\"M14.4999 1.5V5.1H10.8999\" stroke=\"currentColor\" />\n </IconFrame>\n )\n}\n\nexport function IconFolderOpenOutlineRegular({ size }: IconProps): ReactElement {\n return (\n <IconFrame size={size}>\n <path\n d=\"M12.3994 13.5986H2.04956C1.49728 13.5986 1.04956 13.1509 1.04956 12.5986V3.40137C1.04956 2.84908 1.49728 2.40137 2.04956 2.40137H4.76632C5.01016 2.40137 5.24561 2.49046 5.42836 2.6519L6.94088 3.98799C7.12364 4.14943 7.35908 4.23852 7.60293 4.23852H12.3994C12.9517 4.23852 13.3994 4.68624 13.3994 5.23852V7.16991\"\n stroke=\"currentColor\"\n />\n <path\n d=\"M2.55911 7.93683C2.67584 7.49906 3.07229 7.19446 3.52536 7.19446H13.6491C14.3061 7.19446 14.7846 7.81725 14.6153 8.45209L13.4411 12.856C13.3244 13.2938 12.9279 13.5984 12.4748 13.5984H2.35113C1.69411 13.5984 1.21562 12.9756 1.38489 12.3407L2.55911 7.93683Z\"\n stroke=\"currentColor\"\n />\n </IconFrame>\n )\n}\n\nexport function IconCloseOutlineRegular({ size }: IconProps): ReactElement {\n return (\n <IconFrame size={size}>\n <path d=\"M2.5 2.5L13.5 13.5\" stroke=\"currentColor\" />\n <path d=\"M13.5 2.5L2.5 13.5\" stroke=\"currentColor\" />\n </IconFrame>\n )\n}\n\n/** 撤销提示 success 档的前导 glyph(官方 `Toast` 在 `tone=\"success\"` 时自带的那一枚)。 */\nexport function IconCheckCircleOutlineRegular({ size }: IconProps): ReactElement {\n return (\n <IconFrame size={size}>\n <path\n d=\"M12.5303 6.53027L8.80273 10.2578C8.54967 10.5109 8.31796 10.7439 8.10645 10.9141C7.88375 11.0932 7.616 11.2602 7.27344 11.3145C7.09229 11.3431 6.90771 11.3431 6.72656 11.3145C6.384 11.2602 6.11625 11.0932 5.89355 10.9141C5.68204 10.7439 5.45033 10.5109 5.19727 10.2578L3.46973 8.53027L4.53027 7.46973L6.25781 9.19727C6.53457 9.47402 6.70036 9.63859 6.83398 9.74609C6.95637 9.84453 6.98241 9.83644 6.96094 9.83301C6.98679 9.83709 7.01321 9.83709 7.03906 9.83301C7.01759 9.83644 7.04363 9.84453 7.16602 9.74609C7.29964 9.63859 7.46543 9.47402 7.74219 9.19727L11.4697 5.46973L12.5303 6.53027Z\"\n fill=\"currentColor\"\n />\n <path\n d=\"M14.5996 8C14.5996 4.35492 11.6451 1.40039 8 1.40039C4.35492 1.40039 1.40039 4.35492 1.40039 8C1.40039 11.6451 4.35492 14.5996 8 14.5996C11.6451 14.5996 14.5996 11.6451 14.5996 8ZM15.9004 8C15.9004 12.363 12.363 15.9004 8 15.9004C3.63695 15.9004 0.0996094 12.363 0.0996094 8C0.0996094 3.63695 3.63695 0.0996094 8 0.0996094C12.363 0.0996094 15.9004 3.63695 15.9004 8Z\"\n fill=\"currentColor\"\n />\n </IconFrame>\n )\n}\n","/**\n * `Modal`(**照抄** `@deepseek-ai/dsh-client-ui-primitives@0.2.0-rc.2` 的 Modal 与 useModalLayer,\n * 出处与三条改写规则见 `ui.css` 头注):整视口蒙层 + 居中卡片,portal 到 `document.body`。\n *\n * **保留下来的行为**(官方\"用户真的依赖\"的那几条,`references/practices.md` 点名了 Modal 的焦点与\n * Escape):`role=\"dialog\"` + `aria-modal` + `aria-label`(标题即是可及名)、蒙层点击关闭、\n * Escape 关闭、Tab 在框内循环、打开时把焦点送进框(首个 `[data-modal-autofocus]`,没有就首个可聚焦\n * 元素,再没有就框本体)、关闭后把焦点还给打开它的那个元素、自动焦点不留焦点环\n * (`data-dsh-automatic-focus`,宿主主题读它来抑制 outline——键盘导航或 blur 即恢复)。\n *\n * **砍掉的**(本插件用不到,砍掉是为了不摊开官方整个 Modal API):\n * - `description` / `headless` / `className` / `contentClassName` / `onKeyDownCapture` /\n * `backdropBlur` / `shortcutModal`:本插件两个确认框都是\"标题 + 正文 + 两个按钮\"的同一形状。\n * - **官方的多层栈**(`layers` WeakMap + `closeTopModal` / `isBehindModal`):那是给\"对话框里还能再开\n * 对话框、且宿主菜单也在抢 Escape\"的场景准备的。本插件同一时刻只会有一个确认框,所以这里只保留它\n * 的**判据本身**——Escape 只在\"自己就是文档里最后一个 `[role=\"dialog\"][aria-modal=\"true\"]`\n * 或 `[role=\"menu\"]`\"时生效(`isTopLayer`)。这一条不能省:菜单/对话框先后打开时,\n * \"谁在最上面谁收 Escape\"是用户能看见的规矩。\n * - **输入法合成守卫**(`observeComposition`):本插件的对话框里没有文本输入框,不可能处在合成态。\n */\nimport { useLayoutEffect, useRef, type ReactElement, type ReactNode, type RefObject } from 'react'\nimport { createPortal } from 'react-dom'\nimport { IconCloseOutlineRegular } from './icons.js'\nimport './styles.js'\n\nconst FOCUSABLE =\n 'button:not(:disabled), input:not(:disabled), textarea:not(:disabled), select:not(:disabled), a[href], [tabindex=\"0\"]'\n\n/** 文档序里排在前面的层决定谁拿快捷键:对话框与菜单同一张名单(官方 `modalSelector` 逐字)。 */\nconst TOP_LAYER = '[role=\"dialog\"][aria-modal=\"true\"], [role=\"menu\"]'\n\n/** 这些键一旦按下就不再是\"自动焦点\",焦点环恢复正常。 */\nconst NAVIGATION_KEYS = new Set(['Tab', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End'])\n\n/** 自己是不是文档里最上面的那一层。 */\nfunction isTopLayer(element: HTMLElement): boolean {\n const layers = element.ownerDocument.querySelectorAll(TOP_LAYER)\n return layers.length > 0 && layers[layers.length - 1] === element\n}\n\n/** 自动聚焦不留环:宿主主题按 `data-dsh-automatic-focus` 抑制 outline,键盘导航或失焦即摘掉。 */\nfunction focusWithoutRing(element: HTMLElement): void {\n const release = (): void => {\n element.removeAttribute('data-dsh-automatic-focus')\n element.removeEventListener('blur', release)\n element.removeEventListener('keydown', navigate, true)\n }\n const navigate = (event: KeyboardEvent): void => {\n if (!event.isComposing && !event.ctrlKey && !event.altKey && !event.metaKey && NAVIGATION_KEYS.has(event.key)) {\n release()\n }\n }\n element.setAttribute('data-dsh-automatic-focus', '')\n element.addEventListener('blur', release)\n element.addEventListener('keydown', navigate, true)\n element.focus()\n if (!element.matches(':focus')) release()\n}\n\n/** Escape(最上层才生效)与 Tab 环绕;关闭时把焦点还给打开它的元素。 */\nfunction useModalLayer(dialog: RefObject<HTMLDivElement>, open: boolean, onClose: () => void): void {\n const close = useRef(onClose)\n close.current = onClose\n useLayoutEffect(() => {\n const element = dialog.current\n if (!open || element === null) return\n const doc = element.ownerDocument\n const previous = doc.activeElement\n const initial = element.querySelector<HTMLElement>('[data-modal-autofocus]') ??\n element.querySelector<HTMLElement>(FOCUSABLE) ??\n element\n if (!element.contains(doc.activeElement)) focusWithoutRing(initial)\n const keydown = (event: KeyboardEvent): void => {\n if (\n !isTopLayer(element) ||\n event.defaultPrevented ||\n event.ctrlKey ||\n event.altKey ||\n event.metaKey\n ) {\n return\n }\n if (event.key === 'Escape' && !event.shiftKey) {\n event.preventDefault()\n if (!event.repeat) close.current()\n return\n }\n if (event.key !== 'Tab') return\n // 框内的菜单自己收 Tab(它是更上层的浮层)。\n if (doc.activeElement?.closest('[role=\"menu\"]') != null) return\n const items = [...element.querySelectorAll<HTMLElement>(FOCUSABLE)].filter(\n (item) => item.closest('[inert], [hidden]') === null,\n )\n const first = items[0] ?? element\n const last = items[items.length - 1] ?? element\n const atEdge = event.shiftKey ? doc.activeElement === first : doc.activeElement === last\n if (doc.activeElement === element || !element.contains(doc.activeElement) || atEdge) {\n event.preventDefault()\n ;(event.shiftKey ? last : first).focus()\n }\n }\n doc.addEventListener('keydown', keydown)\n return () => {\n doc.removeEventListener('keydown', keydown)\n if (previous instanceof HTMLElement && previous.isConnected) previous.focus()\n }\n }, [dialog, open])\n}\n\nexport interface ModalProps {\n open: boolean\n /** 应用侧关闭(蒙层点击、Escape、关闭钮都走它)。 */\n onClose: () => void\n /** 标题:同时也是对话框的可及名。 */\n title: string\n /** 关闭钮的可及名(本地化文案,由调用面给)。 */\n closeLabel: string\n /** 动作行(取消/确认)。 */\n footer?: ReactNode\n children?: ReactNode\n}\n\nexport function Modal({ open, onClose, title, closeLabel, footer, children }: ModalProps): ReactElement | null {\n const dialog = useRef<HTMLDivElement>(null)\n useModalLayer(dialog, open, onClose)\n if (!open) return null\n return createPortal(\n <div className=\"sd-modal-root\" role=\"presentation\">\n <div className=\"sd-modal-mask\" aria-hidden=\"true\" onClick={onClose} />\n <div\n ref={dialog}\n tabIndex={-1}\n className=\"sd-modal-dialog\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={title}\n >\n <div className=\"sd-modal-content\">\n <div className=\"sd-modal-header\">\n <h2 className=\"sd-modal-title\">{title}</h2>\n <button type=\"button\" className=\"sd-modal-close\" aria-label={closeLabel} onClick={onClose}>\n <IconCloseOutlineRegular size={14} />\n </button>\n </div>\n {children === undefined ? null : <div className=\"sd-modal-body\">{children}</div>}\n </div>\n {footer === undefined ? null : <div className=\"sd-modal-footer\">{footer}</div>}\n </div>\n </div>,\n document.body,\n )\n}\n","/**\n * `Tag`(**照抄** `@deepseek-ai/dsh-client-ui-primitives@0.2.0-rc.2` 的 Tag,出处与三条改写规则见\n * `ui.css` 头注):只读的状态胶囊。\n *\n * 官方那套有八个 tone(outline/solid/neutral/quiet/success/info/warning/danger),本插件只用\n * **danger 一档**(确认框里的失败原文)⇒ 这里只有这一个 tone,`tone` 收成单值联合而不是\"先铺开、\n * 以后可能用得上\"。要加第二档时,把对应那条 CSS 与这个联合一起加。\n */\nimport type { ReactElement, ReactNode } from 'react'\nimport './styles.js'\n\nexport interface TagProps {\n /** 唯一的色板:破坏性/失败面(error 色的 10% 透明底 + error 字)。 */\n tone: 'danger'\n children: ReactNode\n}\n\nconst TONE_CLASS = { danger: 'sd-tag-danger' } as const\n\nexport function Tag({ tone, children }: TagProps): ReactElement {\n return <span className={`sd-tag ${TONE_CLASS[tone]}`}>{children}</span>\n}\n","/**\n * `Toast`(**照抄** `@deepseek-ai/dsh-client-ui-primitives@0.2.0-rc.2` 的 Toast,出处与三条改写规则\n * 见 `ui.css` 头注):顶部居中的一次性横幅——滑入、全不透明停留、淡出,然后回调 `onDone` 让 owner\n * 卸载它。用 body portal 渲染,免得被有 transform / filter 的祖先困在它的盒子里。\n *\n * **保留下来的行为**:\n * - 时长只有一个来源:`holdMs` 同时驱动卸载计时器与样式表的淡出延迟(后者以 `--dsh-toast-hold`\n * 自定义属性读它)⇒ 两者不可能再对不上、把横幅卡在淡出中途卸载。\n * - 同一个 `holdMs` 下,父组件重渲染**不延长**寿命(`onDone` 走 ref,不进 effect 依赖)。\n * - 完成回调拿到的永远是最新的 `onDone`;已经淡完的动作不再接输入(CSS 里那张表面本就穿透点击)。\n * - `tone=\"success\"` 自带官方那枚圆圈对勾(此时忽略传入的 `icon`)。\n *\n * **砍掉的**:`anchor`(把横幅对齐到某个元素的水平中心——本插件的撤销提示就是窗口居中)、\n * `actions[].prefix`(动作文案前的连接词,本插件只用「撤销」两个字)。**没砍 `text`/`icon`/`tone`/\n * `holdMs`/`actions`/`onDone`**:唯一的调用点这几样都在用。\n */\nimport {\n useEffect,\n useLayoutEffect,\n useRef,\n type CSSProperties,\n type ReactElement,\n type ReactNode,\n} from 'react'\nimport { createPortal } from 'react-dom'\nimport { IconCheckCircleOutlineRegular } from './icons.js'\nimport './styles.js'\n\n/** 不指定时全不透明的停留时长。 */\nconst DEFAULT_HOLD_MS = 3000\n/** 淡出时长。必须与样式表里 `sd-toast-fade` 的时长一致。 */\nconst FADE_MS = 1000\n\nexport interface ToastAction {\n label: string\n onClick: () => void\n}\n\nexport interface ToastProps {\n /** 已解析好的横幅文案(本地化由调用面负责)。 */\n text: string\n /** 前导 glyph;`tone=\"success\"` 时被忽略(那一档自带对勾)。 */\n icon?: ReactNode\n tone?: 'success'\n /** 全不透明停留时长;缺省 3000。 */\n holdMs?: number\n /** 句尾的行内动作(照着句子的顺序排)。 */\n actions?: readonly ToastAction[]\n /** 淡出完成时调用一次:在这里卸载。 */\n onDone: () => void\n}\n\nexport function Toast({ text, icon, tone, holdMs = DEFAULT_HOLD_MS, actions, onDone }: ToastProps): ReactElement {\n const latestOnDone = useRef(onDone)\n useLayoutEffect(() => {\n latestOnDone.current = onDone\n }, [onDone])\n useEffect(() => {\n const timer = setTimeout(() => {\n latestOnDone.current()\n }, holdMs + FADE_MS)\n return () => {\n clearTimeout(timer)\n }\n }, [holdMs])\n return createPortal(\n <div\n className=\"sd-toast\"\n role=\"alert\"\n style={{ '--dsh-toast-hold': `${String(holdMs)}ms` } as CSSProperties}\n >\n {tone === 'success' ? (\n <span className=\"sd-toast-icon sd-toast-icon-success\" aria-hidden=\"true\">\n <IconCheckCircleOutlineRegular />\n </span>\n ) : icon === undefined ? null : (\n <span className=\"sd-toast-icon\" aria-hidden=\"true\">\n {icon}\n </span>\n )}\n <span className=\"sd-toast-text\">\n {text}\n {actions?.map((action) => (\n <button\n key={action.label}\n type=\"button\"\n className=\"sd-toast-action\"\n onClick={action.onClick}\n >\n {action.label}\n </button>\n ))}\n </span>\n </div>,\n document.body,\n )\n}\n","/**\r\n * 撤销提示(设计稿 §7 状态 ③):删除成功后一条可撤销的横幅。\r\n *\r\n * 为什么是**独立组件 + 模块级通知台**,而不是死在菜单行里:宿主的菜单行随菜单**一起卸载**\r\n * (真产物 Menu 的注释原文 \"the rows unmount with the list\"),而提示要在菜单关掉之后还活着。\r\n * 宿主的官方配方同形——会话行的动作把请求写进一个模块级 store,`shell.overlay` 里的\r\n * `workspace.row-toast` 读它(asar 原文:`inject(\"shell.overlay\", … id: \"workspace.row-toast\" …\r\n * inject: rowToastInjected … }, RowActionToast)`,而\r\n * `rowToastInjected = () => ({ hooks: { toast: rowToast }, dismissToast, undoArchive: unarchiveSession })`)。\r\n * 本文件就是那份 store + 那个常驻组件。\r\n *\r\n * 状态源用 React 自带的 `useSyncExternalStore`——不引 `@deepseek-ai/dsh-client-store`\r\n * (它在平台表内,但这里用不着;React 就够,少一条平台依赖)。\r\n */\r\nimport { useSyncExternalStore } from 'react'\r\nimport { zhTranslate, type Translate } from './copy.js'\r\nimport { callRemote, type SessionDeleteRemote } from './remote.js'\r\nimport { invalidateTrash } from './trash-store.js'\r\nimport { IconWarningOutlineRegular } from './ui/icons.js'\r\nimport { Toast } from './ui/Toast.js'\r\n\r\nexport type UndoToastDeps = Pick<SessionDeleteRemote, 'restore'>\r\n\r\nexport interface UndoNotice {\r\n /** 每次发布递增:Toast 靠它重挂载,同一段文案连续出现两次也能重新计一次停留。 */\r\n seq: number\r\n /** 回收站条目 id——撤销就是拿它去 `restore`。 */\r\n entryId: string\r\n /** 删除那一刻的会话名(菜单槽投影的 `displayTitle`;取不到时是空串)。 */\r\n title: string\r\n /** 撤销失败时把原因顶上来:这时不再有「撤销」动作,只剩原文。 */\r\n failure?: string\r\n}\r\n\r\nlet notice: UndoNotice | null = null\r\nlet nextSeq = 0\r\nconst listeners = new Set<() => void>()\r\n\r\nconst emit = (): void => {\r\n for (const listener of listeners) listener()\r\n}\r\n\r\n/** 删除成功后发布一条撤销提示。 */\r\nexport function publishUndoNotice(input: { entryId: string; title: string }): void {\r\n notice = { seq: (nextSeq += 1), entryId: input.entryId, title: input.title }\r\n emit()\r\n}\r\n\r\n/** 撤销成功后把提示收掉(用户点「撤销」、或横幅自己走完停留时间)。 */\r\nexport function clearUndoNotice(): void {\r\n if (notice === null) return\r\n notice = null\r\n emit()\r\n}\r\n\r\nfunction getUndoNotice(): UndoNotice | null {\r\n return notice\r\n}\r\n\r\nfunction subscribe(listener: () => void): () => void {\r\n listeners.add(listener)\r\n return () => {\r\n listeners.delete(listener)\r\n }\r\n}\r\n\r\n/** 供组件订阅。 */\r\nexport function useUndoNotice(): UndoNotice | null {\r\n return useSyncExternalStore(subscribe, getUndoNotice, getUndoNotice)\r\n}\r\n\r\n/** 提示停留时长。设计稿 §7 定死:**6 秒**(Toast 自己的默认是 3 秒)。 */\r\nexport const UNDO_HOLD_MS = 6000\r\n\r\n/** 撤销:`restore(entryId)`;失败就把原因换到同一条横幅上,不静默。 */\r\nasync function undo(deps: UndoToastDeps, current: UndoNotice): Promise<void> {\r\n if (current.entryId === '') return\r\n const outcome = await callRemote(() => deps.restore(current.entryId))\r\n if (outcome.ok) {\r\n // 回收站少了一条:让正开着的清单面板重读(撤销也是\"从别处动回收站\"的一种)。\r\n invalidateTrash()\r\n clearUndoNotice()\r\n return\r\n }\r\n notice = { ...current, seq: (nextSeq += 1), failure: outcome.failure.message }\r\n emit()\r\n}\r\n\r\n/**\r\n * 常驻的撤销提示宿主。挂 `shell.overlay`(框架级浮层;槽位说明原文 \"a toast stack or a status\r\n * pill all belong here\",见 asar 里 `shell.overlay` 的声明):没有待撤销的事时什么都不渲染。\r\n */\r\nexport function UndoToast({ deps, t = zhTranslate }: { deps: UndoToastDeps; t?: Translate }) {\r\n const current = useUndoNotice()\r\n if (current === null) return null\r\n\r\n const { seq, entryId, title, failure } = current\r\n if (failure !== undefined) {\r\n return (\r\n <Toast\r\n key={seq}\r\n text={failure}\r\n icon={<IconWarningOutlineRegular size={16} />}\r\n holdMs={UNDO_HOLD_MS}\r\n onDone={clearUndoNotice}\r\n />\r\n )\r\n }\r\n return (\r\n <Toast\r\n key={seq}\r\n tone=\"success\"\r\n text={t('undo.moved', { title: title === '' ? t('undo.session') : title })}\r\n holdMs={UNDO_HOLD_MS}\r\n actions={\r\n entryId === ''\r\n ? undefined\r\n : [{ label: t('undo.undo'), onClick: () => void undo(deps, current) }]\r\n }\r\n onDone={clearUndoNotice}\r\n />\r\n )\r\n}\r\n","/**\r\n * 「删除」的确认框——**挂在 `shell.overlay` 上的宿主组件**,加它读的那个模块级现场。\r\n *\r\n * 为什么确认框不能住在菜单行里(这是本文件存在的唯一理由):**宿主的菜单行随菜单一起卸载**。\r\n * 真产物 `Menu` 的渲染是\r\n * `const list = open && jsxs(MenuSurface, { … children: [items.map(renderEntry), children] })`\r\n * (asar @22074291 逐字;`children` 就是本插件那一行所在的槽位投影)——`open === false` 时\r\n * **槽位行根本不渲染**,行连同它的整棵子树卸载。而按官方契约,按下这一行先 `setMenuOpen(false)`\r\n * 收起菜单正是正解(官方 README 的第三方行示例逐字 `onSelect={() => { setMenuOpen(false); … }}`,\r\n * 宿主自己的 pin / rename / fork / archive 四行同样这么做)。两条一起成立 ⇒ 确认框若住在行里,\r\n * 它会在菜单收起的那一帧被**连坐卸载**(`createPortal` 只改 DOM 归属,**不改 React 生命周期**),\r\n * 真机上「移入回收站」永远点不到。\r\n *\r\n * 所以按宿主自己的配方把\"请求\"与\"确认\"拆开(同一条路:宿主对会话行的撤销提示就是这么做的,\r\n * `ctx.slots.inject(\"shell.overlay\", … id: \"workspace.row-toast\" …, RowActionToast)`):\r\n *\r\n * - **菜单行只发请求**:`DeleteMenuItem` 把 `{ sessionId, title }` 写进本文件的模块级现场;\r\n * - **确认框由本文件的宿主渲染与提交**:`DeleteConfirmHost` 挂在 `shell.overlay` 上,从现场读请求,\r\n * 自己持有 busy / failure。于是「提交删除 → 失败原文上屏 → 再点一次重试 → 成功后发撤销提示」\r\n * 整条链路都在菜单子树之外,菜单关不关都不影响它。\r\n *\r\n * 现场是**模块级**的(本仓既有的客户端模式:视图内路由与跨卸载的现场都住在模块级 store,\r\n * 见 `undo-toast.tsx`),状态源用 React 自带的 `useSyncExternalStore`——不为此引新的状态库。\r\n */\r\nimport { useState, useSyncExternalStore } from 'react'\r\nimport { zhTranslate, type Translate } from './copy.js'\r\nimport { callRemote, type DeleteConfirmDeps } from './remote.js'\r\nimport { invalidateTrash } from './trash-store.js'\r\nimport { Button } from './ui/Button.js'\r\nimport { Modal } from './ui/Modal.js'\r\nimport { Tag } from './ui/Tag.js'\r\nimport { publishUndoNotice } from './undo-toast.js'\r\n\r\n/**\r\n * 被操作对象那张卡。`Modal`(`ui/Modal.tsx`,照官方抄的)的 `.sd-modal-body` 不设字号也不给层级 ⇒ 裸摆一行会话标题会和下面那句\r\n * 提示**同字同色**,读不出\"这才是要被删的东西\"。配方与「彻底删除」框里那张摘要卡同一套(宿主设置面\r\n * 的卡片 token:`bg-layer-2` + `border-l4` + `radius-lg`),两个对话框因此同形。\r\n * 浅色下 `bg-layer-2` 与对话框自身同色,分隔全靠那根描边——宿主自己的卡片在浅色下也是这样。\r\n */\r\nconst TARGET_CARD = {\r\n padding: '10px 12px',\r\n background: 'var(--dsw-alias-bg-layer-2)',\r\n border: '0.5px solid var(--dsw-alias-border-l4)',\r\n borderRadius: 'var(--dsw-radius-lg)',\r\n} as const\r\n\r\n/** 菜单行写进现场的一条删除请求(槽位投影原样带过来,浏览器半不自己拼 id)。 */\r\nexport interface DeleteRequest {\r\n /** 每次发布递增:换一个会话就重挂载确认框,busy / failure 不带着上一条请求的记忆。 */\r\n seq: number\r\n /** 要删的会话 id——原样透给 `sessiondelete/delete`。 */\r\n sessionId: string\r\n /** 删除那一刻的会话名(槽位投影的 `displayTitle`):只用于确认框文案与撤销提示。 */\r\n title: string\r\n}\r\n\r\nlet request: DeleteRequest | null = null\r\nlet nextSeq = 0\r\nconst listeners = new Set<() => void>()\r\n\r\nconst emit = (): void => {\r\n for (const listener of listeners) listener()\r\n}\r\n\r\n/** 菜单行调用:请求删除这个会话(确认框由 overlay 上的宿主渲染)。 */\r\nexport function requestDelete(input: { sessionId: string; title: string }): void {\r\n request = { seq: (nextSeq += 1), sessionId: input.sessionId, title: input.title }\r\n emit()\r\n}\r\n\r\n/** 收起确认框(用户点「取消」/「关闭」、或删除成功后收工)。 */\r\nexport function clearDeleteRequest(): void {\r\n if (request === null) return\r\n request = null\r\n emit()\r\n}\r\n\r\nfunction getDeleteRequest(): DeleteRequest | null {\r\n return request\r\n}\r\n\r\nfunction subscribe(listener: () => void): () => void {\r\n listeners.add(listener)\r\n return () => {\r\n listeners.delete(listener)\r\n }\r\n}\r\n\r\n/** 供宿主组件订阅。 */\r\nexport function useDeleteRequest(): DeleteRequest | null {\r\n return useSyncExternalStore(subscribe, getDeleteRequest, getDeleteRequest)\r\n}\r\n\r\n/**\r\n * 常驻的确认框宿主。挂 `shell.overlay`(框架级浮层;槽位说明原文 \"a toast stack or a status\r\n * pill all belong here\"):没有待确认的删除时什么都不渲染。\r\n */\r\nexport function DeleteConfirmHost({ deps, t = zhTranslate }: { deps: DeleteConfirmDeps; t?: Translate }) {\r\n const current = useDeleteRequest()\r\n if (current === null) return null\r\n // `key` 跟着 seq 走:新的一条请求是一个全新的对话框(官方 `Toast` 的 `key={seq}` 同一口径)。\r\n return <ConfirmDialog key={current.seq} request={current} deps={deps} t={t} />\r\n}\r\n\r\n/**\r\n * 确认框本体。视图与文案与设计稿 §7 一致(`Modal` + `variant=\"primary\"` 主按钮「移入回收站」;\r\n * 那个 primary 在宿主浅色下是近黑、深色下近白,**从来不是蓝**)。\r\n * 失败面**不吞**:原文以 `Tag tone=\"danger\"` 摆进对话框,且**确认失败后不关框**,\r\n * 用户可以再点一次(不是一次失败就永久锁死)。\r\n */\r\nfunction ConfirmDialog({\r\n request: current,\r\n deps,\r\n t,\r\n}: {\r\n request: DeleteRequest\r\n deps: DeleteConfirmDeps\r\n t: Translate\r\n}) {\r\n const [busy, setBusy] = useState(false)\r\n const [failure, setFailure] = useState<string | null>(null)\r\n\r\n const confirm = async (): Promise<void> => {\r\n setBusy(true)\r\n setFailure(null)\r\n const outcome = await callRemote(() => deps.delete(current.sessionId))\r\n setBusy(false)\r\n if (!outcome.ok) {\r\n setFailure(outcome.failure.message)\r\n return\r\n }\r\n clearDeleteRequest()\r\n // 回收站多了一条:让正开着的清单面板重读(它挂载时只取一次数,见 trash-store.ts 头注)。\r\n invalidateTrash()\r\n publishUndoNotice({ entryId: outcome.value.id, title: current.title })\r\n }\r\n\r\n return (\r\n <Modal\r\n open\r\n onClose={clearDeleteRequest}\r\n title={t('dialog.title')}\r\n closeLabel={t('common.close')}\r\n footer={\r\n <>\r\n <Button variant=\"outline\" onClick={clearDeleteRequest}>\r\n {t('common.cancel')}\r\n </Button>\r\n <Button variant=\"primary\" disabled={busy} onClick={() => void confirm()}>\r\n {t('dialog.confirm')}\r\n </Button>\r\n </>\r\n }\r\n >\r\n <div style={TARGET_CARD}>\r\n <div\r\n data-dsh-session-delete=\"target\"\r\n style={{\r\n fontSize: 14,\r\n fontWeight: 500,\r\n lineHeight: '20px',\r\n overflow: 'hidden',\r\n textOverflow: 'ellipsis',\r\n whiteSpace: 'nowrap',\r\n }}\r\n >\r\n {current.title === '' ? t('common.untitled') : current.title}\r\n </div>\r\n </div>\r\n <p>{t('dialog.hint')}</p>\r\n {failure === null ? null : <Tag tone=\"danger\">{failure}</Tag>}\r\n </Modal>\r\n )\r\n}\r\n","/**\n * `MenuItemButton`(**照抄** `@deepseek-ai/dsh-client-ui-primitives@0.2.0-rc.2` 的同名组件,出处与三条\n * 改写规则见 `ui.css` 头注):菜单里的一行,宿主菜单容器渲染它、键盘巡游也认它。\n *\n * **形状必须与宿主自己那四行(pin/rename/fork/archive)逐字同构**:`<div class=itemWrap>` 里先是可选\n * 的发丝线(`role=\"separator\"`),再是 `<button type=\"button\" role=\"menuitem\">`,按钮内是\n * 图标位 + 标签位(+ 官方的快捷键位)。少一层、换个标签,这一行就会在键盘顺序、hover 底色或\n * `.itemLabel` 的省略号上跟邻行错开。\n *\n * **置灰用原生 `disabled`**(不是 `aria-disabled`):宿主菜单行的契约就是原生属性——`MenuItemButton`\n * 只把它落到 `<button>` 上,本仓用例也按 `toBeDisabled()` 判。\n *\n * **砍掉的**:`shortcut`(本插件这一行没有快捷键,故不带 `aria-keyshortcuts`,也不渲染快捷键位)。\n * `danger` / `separatorBefore` / `disabled` / `icon` / `children` / `onSelect` 都在用,保留。\n */\nimport type { ReactElement, ReactNode } from 'react'\nimport './styles.js'\n\nexport interface MenuItemButtonProps {\n /** 可见的行标签。 */\n children: ReactNode\n /** 前导图标。 */\n icon?: ReactNode\n /** 这一行不能被激活。 */\n disabled?: boolean\n /** 用破坏性的行配色(error 字色 + danger 悬停底)。 */\n danger?: boolean\n /** 这一行另起一组(上方一条发丝线)。 */\n separatorBefore?: boolean\n /** 行被激活。 */\n onSelect: () => void\n}\n\nexport function MenuItemButton({\n children,\n icon,\n disabled = false,\n danger = false,\n separatorBefore = false,\n onSelect,\n}: MenuItemButtonProps): ReactElement {\n return (\n <div className=\"sd-menu-item-wrap\">\n {separatorBefore ? <div className=\"sd-menu-separator\" role=\"separator\" /> : null}\n <button\n type=\"button\"\n role=\"menuitem\"\n className={danger ? 'sd-menu-item sd-menu-item-danger' : 'sd-menu-item'}\n disabled={disabled}\n onClick={onSelect}\n >\n {icon === undefined ? null : <span className=\"sd-menu-item-icon\">{icon}</span>}\n <span className=\"sd-menu-item-label\">{children}</span>\n </button>\n </div>\n )\n}\n","/**\n * `Tooltip`(**照抄** `@deepseek-ai/dsh-client-ui-primitives@0.2.0-rc.2` 的 Tooltip,出处与三条改写\n * 规则见 `ui.css` 头注):把提示挂在锚点元素上,hover / 键盘 focus 才出。\n *\n * **保留下来的行为**:`side` 三个方位的定位(`right` 取锚点右缘再让出 10px,`bottom`/`top` 居中并按\n * `gap` 让位)、视口边界收口(`edgeMargin=12`、`bottom`/`top` 装不下就翻面)、等 `ResizeObserver`\n * 量到气泡尺寸再显示(所以不会先闪一个错位的框)、指针交互之后的 focus 不弹(见 `styles.ts` 的\n * `isPointerModality`)、`portal` 时挂到 `document.body`(宿主菜单有裁剪与层叠上下文,锚点在菜单里\n * 时必须 portal,否则气泡被裁掉)。\n *\n * **砍掉的**:`shortcutKeys`(本插件没有快捷键)、`openOnClick`/`pinned` 与随之而来的\n * `useDismissOnOutsidePointer`、`aria-describedby` 关联(官方只在 pinned 时挂)、\n * `TooltipSuppression`(那是给 HoverCard 预览用的互相压制)、`delayMs`/`focusDelayMs`/`maxWidth`\n * (本插件唯一一处用的是零延迟默认值)。**没有砍掉 `side`/`gap`**:定位数学是三行分支,\n * 留着才与官方同形。\n *\n * **与官方的一处差异**:官方把锚点自己的 `ref` 与它的 `ref` 合并后一起挂;这里要求锚点**不带\n * 自己的 ref**(本插件唯一的调用点是一个裸 `<span>`),所以直接挂。要合并再补。\n */\nimport {\n Fragment,\n cloneElement,\n useCallback,\n useEffect,\n useRef,\n useState,\n type FocusEvent as ReactFocusEvent,\n type MouseEvent as ReactMouseEvent,\n type ReactElement,\n} from 'react'\nimport { createPortal } from 'react-dom'\nimport { isPointerModality } from './styles.js'\nimport './styles.js'\n\nexport type TooltipSide = 'right' | 'bottom' | 'top'\n\nexport interface TooltipProps {\n /** 气泡文案;空串不显示文字(官方支持\"只显示快捷键\"的那种用法,本插件不用)。 */\n label: string\n /** 相对锚点的方位;缺省 `right`。 */\n side?: TooltipSide\n /** `bottom`/`top` 时锚点到气泡的距离(像素,缺省 8);`right` 不用它。 */\n gap?: number\n /** 为真时不出气泡;锚点渲染完全不变(不会因为切换而重挂,进而打断它自己的 CSS 过渡)。 */\n disabled?: boolean\n /** 把气泡挂到 `document.body` 下(锚点在被裁剪的容器里时必须开)。 */\n portal?: boolean\n /** 单一锚点元素。 */\n children: ReactElement\n}\n\ninterface AnchorRect {\n x: number\n top: number\n bottom: number\n}\n\nexport function Tooltip({ label, side = 'right', gap = 8, disabled = false, portal = false, children }: TooltipProps): ReactElement {\n const anchor = useRef<HTMLElement | null>(null)\n const bubble = useRef<HTMLSpanElement | null>(null)\n const [rect, setRect] = useState<AnchorRect | null>(null)\n const visible = rect !== null && !disabled\n\n useEffect(() => {\n const element = bubble.current\n if (rect === null || !visible || element === null) return\n const EDGE_MARGIN = 12\n let size: { inlineSize: number; blockSize: number } | undefined\n let placement: TooltipSide = side\n const fit = (): void => {\n if (size === undefined) return\n const { inlineSize: width, blockSize: height } = size\n const offset = side === 'right' ? 0 : width / 2\n const left = Math.max(EDGE_MARGIN, Math.min(rect.x - offset, window.innerWidth - EDGE_MARGIN - width))\n const fitsBelow = rect.bottom + gap + height <= window.innerHeight - EDGE_MARGIN\n const fitsAbove = rect.top - gap - height >= EDGE_MARGIN\n if (placement === 'bottom' && !fitsBelow && fitsAbove) placement = 'top'\n else if (placement === 'top' && !fitsAbove && fitsBelow) placement = 'bottom'\n element.style.left = `${left + offset}px`\n element.style.top = `${\n placement === 'right' ? (rect.top + rect.bottom) / 2 : placement === 'top' ? rect.top - gap : rect.bottom + gap\n }px`\n element.dataset.side = placement\n element.style.visibility = 'visible'\n }\n const observer = new ResizeObserver((entries) => {\n size = entries[0]?.borderBoxSize[0]\n fit()\n })\n observer.observe(element, { box: 'border-box' })\n window.addEventListener('resize', fit)\n return () => {\n observer.disconnect()\n window.removeEventListener('resize', fit)\n }\n }, [gap, rect, side, visible])\n\n const show = useCallback((): void => {\n const element = anchor.current\n /* v8 ignore next -- 事件是在克隆出来的锚点上冒泡上来的,触发时 ref 必已挂上 */\n if (element === null) return\n const box = element.getBoundingClientRect()\n setRect({\n x: side === 'right' ? box.right + 10 : box.left + box.width / 2,\n top: box.top,\n bottom: box.bottom,\n })\n }, [side])\n\n const withdraw = useCallback((): void => {\n setRect(null)\n }, [])\n\n useEffect(() => {\n if (disabled) setRect(null)\n }, [disabled])\n\n /** 锚点自己带的处理器要照常跑(我们只加不减)——官方也是先 `children.props.onX?.(e)` 再接管。 */\n const inner = children.props as {\n onMouseEnter?: (event: ReactMouseEvent) => void\n onMouseLeave?: (event: ReactMouseEvent) => void\n onClick?: (event: ReactMouseEvent) => void\n onFocus?: (event: ReactFocusEvent) => void\n onBlur?: (event: ReactFocusEvent) => void\n }\n\n const anchorElement = cloneElement(children, {\n ref: (element: HTMLElement | null) => {\n anchor.current = element\n },\n onMouseEnter: (event: ReactMouseEvent) => {\n inner.onMouseEnter?.(event)\n if (!disabled) show()\n },\n onMouseLeave: (event: ReactMouseEvent) => {\n inner.onMouseLeave?.(event)\n withdraw()\n },\n onClick: (event: ReactMouseEvent) => {\n inner.onClick?.(event)\n withdraw()\n },\n onFocus: (event: ReactFocusEvent) => {\n inner.onFocus?.(event)\n // 指针交互(点一下菜单行)之后紧接着到来的 focus 不该把提示顶出来。\n if (isPointerModality()) return\n if (!disabled) show()\n },\n onBlur: (event: ReactFocusEvent) => {\n inner.onBlur?.(event)\n withdraw()\n },\n })\n\n const content =\n visible ? (\n <span\n ref={bubble}\n className=\"sd-tooltip-bubble\"\n data-side={side}\n data-portal={portal ? 'true' : undefined}\n style={{ left: rect.x, top: rect.top, visibility: 'hidden' }}\n role=\"tooltip\"\n >\n {label === '' ? null : <span className=\"sd-tooltip-label\">{label}</span>}\n </span>\n ) : null\n\n return (\n <Fragment>\n {anchorElement}\n {content === null ? null : portal ? createPortal(content, document.body) : content}\n </Fragment>\n )\n}\n","/**\r\n * 会话条目「…」菜单里的「删除」行(设计稿 §7 状态 ①)。\r\n *\r\n * 这一行**只做两件事**:把「要删这个会话」写进 `delete-confirm.tsx` 的模块级现场,然后让菜单\r\n * owner 把菜单收起来(确认框不许叠在收起的菜单上——设计稿 §7 状态 ② 画的就是菜单已经收起之后\r\n * 的画面)。**确认框不在这棵子树里**——\r\n * 原因见 `delete-confirm.tsx` 的头注:宿主的菜单行随菜单一起卸载(真产物 `Menu` 是\r\n * `open && jsxs(MenuSurface, { … children: [items.map(renderEntry), children] })`,`open === false`\r\n * 时槽位行根本不渲染),住在行里的确认框会被这次的卸载连坐带走,真机上「移入回收站」永远点不到。\r\n * 所以\"请求\"与\"确认\"分居两处:请求走模块级现场,确认框住在 `shell.overlay` 上。\r\n *\r\n * 另外两件事说清:\r\n *\r\n * 1. **不自建菜单壳**。菜单行由宿主既有菜单容器渲染,我们只贡献一行 `MenuItemButton`\r\n * (`ui/MenuItemButton.tsx`,照官方同名组件抄的),由 `index.tsx` 注册进槽位\r\n * `sidebar.workspaces.session.menu.item`(该槽位只投影 `sessionId` / `displayTitle` 两个数据\r\n * 加 `menuOpenState` / `shortcuts` 两个 hook,见 asar @46391872 的槽位声明原文)。\r\n * 菜单的**开合归 owner**:槽位把 `useMenuOpenState` 交给这一行(`index.tsx` 的 `DeleteRow` 接住后\r\n * 当 `dismissMenu` 传下来),按下时先发请求、再收菜单——与官方 README 的示例同序\r\n * (`onSelect={() => { setMenuOpen(false); … }}`)。\r\n * 2. **运行中置灰**(设计稿 §7「运行中」)。置灰用**原生 `disabled`**:本平台的菜单行组件\r\n * `MenuItemButton` 只把 `disabled` 落在原生按钮属性上(真产物原文 `<button type=\"button\"\r\n * role=\"menuitem\" … disabled={disabled}>`),jest-dom 的 `toBeDisabled()` 也只认这个属性;\r\n * `aria-disabled` 在这条链上既渲染不出来也不算数。原因文案用 `ui/Tooltip.tsx`(照官方 Tooltip 抄的,\r\n * 行为同款:hover / 键盘 focus 才出)挂在行上\r\n * (anchor 用一层 `<span>`:`MenuItemButton` 是普通函数组件、接不住 `Tooltip` 注入的 ref)。\r\n */\r\nimport { requestDelete } from './delete-confirm.js'\r\nimport { zhTranslate, type Translate } from './copy.js'\r\nimport { IconTrashOutlineRegular } from './ui/icons.js'\r\nimport { MenuItemButton } from './ui/MenuItemButton.js'\r\nimport { Tooltip } from './ui/Tooltip.js'\r\n\r\nexport interface DeleteMenuItemProps {\r\n /** 宿主菜单槽投影的会话 id,原样透给 `sessiondelete/delete`(浏览器半不自己拼 id)。 */\r\n sessionId: string\r\n /** 宿主菜单槽投影的 `displayTitle`:只用于确认框与提示文案,不参与任何请求。 */\r\n title: string\r\n /** 会话在跑:这一行置灰,并把原因摆在行上。 */\r\n live?: boolean\r\n /**\r\n * 菜单 owner 交下来的「收起菜单」——槽位注入的 `useMenuOpenState`(`index.tsx` 的 `DeleteRow` 接住)。\r\n * 「关不关菜单是 owner 的决定」是官方对该槽位的契约;按下这一行时**先发请求、再收菜单**。\r\n * 缺省不动,方便单独测这一行。\r\n */\r\n dismissMenu?: () => void\r\n /** 文案(缺省 = 内置 zh,见 `copy.ts`:这不是\"忘了传\",而是\"这次没有 locale 服务\")。 */\r\n t?: Translate\r\n}\r\n\r\nexport function DeleteMenuItem({\r\n sessionId,\r\n title,\r\n live = false,\r\n dismissMenu,\r\n t = zhTranslate,\r\n}: DeleteMenuItemProps) {\r\n const select = (): void => {\r\n // 先把请求写进现场(确认框住在 overlay 上、不随这一行的卸载消失),再让 owner 收菜单——\r\n // 即使 owner 收菜单的动作把这一行卸了,请求也已经发出去了。\r\n requestDelete({ sessionId, title })\r\n dismissMenu?.()\r\n }\r\n\r\n return (\r\n <Tooltip label={t('menu.live')} side=\"right\" portal disabled={!live}>\r\n <span>\r\n <MenuItemButton\r\n separatorBefore\r\n danger\r\n disabled={live}\r\n icon={<IconTrashOutlineRegular size={14} />}\r\n onSelect={select}\r\n >\r\n {t('menu.delete')}\r\n </MenuItemButton>\r\n </span>\r\n </Tooltip>\r\n )\r\n}\r\n","/**\r\n * 侧栏面板列表里的「回收站」行图标(`sidebar.panellist` 的占用者;设计稿 §7 状态 ④ 的入口那一行)。\r\n *\r\n * **只出 glyph**——行本体由宿主 `PanelRow` 画:`<button class=panelRow aria-current\r\n * onClick=selectPanel(id)>` 里一个 `.panelGlyph`(`renderSlot(\"sidebar.panellist\", { size: wide ? 16 : 18,\r\n * active }, { only: id })`)加一个 `.panelTitle`(**名字从注册元数据来**:宿主 `syncPanels` 逐字\r\n * `label: resolveSlotLabel(options.label) ?? id`,asar @44795361),外加折叠时的 `Tooltip`、\r\n * hover 与选中底色(asar @44783481 逐字)。所以这里**不能**有按钮、文字或角标:多画一样,\r\n * 这一行就与「插件 / 任务 / 小说」三行错位。\r\n *\r\n * `active`(本行是否被中央列选中)由 owner 一并给出,**刻意不用**:选中呈现已经是宿主行自己的事\r\n * (`aria-current` + `.panelActive`),我们再加一层就会出现两种选中态。\r\n *\r\n * 计数角标**不做**(2026-09-30 裁决):这个座位只给一个 16/18px 的 glyph 盒(`.panelGlyph{flex:none}`),\r\n * 角标只能溢出到行外(本仓量过:15px 的角标右缘超出 glyph 7px、顶缘高出行顶 5px,宽态就压到上一行;\r\n * rail 态那行是 36px 圆、`padding:0`,更糟)。`label` 虽支持 thunk,但宿主只在**槽位条目变化或\r\n * 语言变化**时重读(同上 @44795361),拿它当活计数必然 stale。条数与合计占用改在面板头部给\r\n * (见 `trash-panel.tsx`)。\r\n */\r\nimport { IconTrashOutlineRegular } from './ui/icons.js'\r\n\r\n/** 宿主 `SidebarPanelIconOwnerProps`:请求的方形边长;`active` 收了但不用(见头注)。 */\r\nexport interface TrashPanelIconProps {\r\n size?: number\r\n active?: boolean\r\n}\r\n\r\nexport function TrashPanelIcon({ size = 16 }: TrashPanelIconProps) {\r\n return <IconTrashOutlineRegular size={size} />\r\n}\r\n","/**\n * 两个纯格式化函数(**逐条抄自** `@deepseek-ai/dsh-client-ui-primitives@0.2.0-rc.2` 的\n * `lib/index.js`;出处与改写规则见 `ui.css` 头注)。\n *\n * 它们是**纯函数、零 React、零 token**,所以「抄进来」在这里没有任何版本耦合的代价——真正随宿主换代\n * 会变的是组件与样式,不是这两段算术。抄而不是自己写,为的是**输出逐字相同**:本仓的用例与真机读数\n * 都钉在 `312B` / `4.2KB` / `1.5MB` 与「刚刚 / N分钟 / N小时 / N天 / N个月 / N年」这几个分桶上,\n * 自己重写一遍就等于换了一套措辞。\n */\n/** 时间分桶的单位词(官方 `relativeTime` 的返回联合,逐字相同)。 */\nexport type RelativeTimeUnit = 'now' | 'minutes' | 'hours' | 'days' | 'months' | 'years'\n\n/** 紧凑的字节数文本(`312B`、`4.2KB`、`1.5MB`、`2.4GB`):整单位,选中单位小于十时带一位小数。 */\nexport function fileSizeText(bytes: number): string {\n if (bytes < 1024) return `${bytes}B`\n const kb = bytes / 1024\n if (kb < 1024) return `${kb < 10 ? kb.toFixed(1) : Math.round(kb)}KB`\n const mb = kb / 1024\n if (mb < 1024) return `${mb < 10 ? mb.toFixed(1) : Math.round(mb)}MB`\n const gb = mb / 1024\n return `${gb < 10 ? gb.toFixed(1) : Math.round(gb)}GB`\n}\n\nconst MINUTE_MS = 60_000\nconst HOUR_MS = 3_600_000\nconst DAY_MS = 86_400_000\n\n/** 时间分桶(`now` / 分钟 / 小时 / 天 / 月 / 年);措辞由调用面自己组(见 `trash-panel.tsx`)。 */\nexport function relativeTime(at: number, now: number): { unit: RelativeTimeUnit; n: number } {\n const diff = Math.max(0, now - at)\n if (diff < MINUTE_MS) return { unit: 'now', n: 0 }\n if (diff < HOUR_MS) return { unit: 'minutes', n: Math.floor(diff / MINUTE_MS) }\n if (diff < DAY_MS) return { unit: 'hours', n: Math.floor(diff / HOUR_MS) }\n if (diff < 30 * DAY_MS) return { unit: 'days', n: Math.floor(diff / DAY_MS) }\n if (diff < 365 * DAY_MS) return { unit: 'months', n: Math.floor(diff / (30 * DAY_MS)) }\n return { unit: 'years', n: Math.floor(diff / (365 * DAY_MS)) }\n}\n","/**\r\n * 回收站清单面板(设计稿 §7 状态 ④⑤)——**中央面板的本体**(`main` 槽的占用者)。\r\n *\r\n * 面板自带页面骨架(标题 / 条数与合计 / 刷新 / 滚动区 / 页脚),因为宿主给中央列的那个盒子\r\n * (`AppFrame` 的 `.centerCol`)只有背景与圆角,**没有 padding、也不是滚动容器**——asar 里那条\r\n * `.BynINW_centerCol{background:var(--dsw-alias-bg-base);border-radius:…;corner-shape:round}` 逐字\r\n * 如此。回收站可以很长(本机 85 个会话合计 15M),不自己收滚动就会把整列顶长。\r\n *\r\n * **2026-10-01 视觉改版:四列表格 → 按项目分组的卡片列表。**三条真机读数决定的:\r\n * ① 「原项目」那一列摆的是宿主的 `projectKey`(`--D-develop-GitHub-…--`),96px 的列宽只够露出驱动器\r\n * 和半截路径,等于没有信息;② 「删除时间」96px 装不下 `toLocaleString()` ⇒ 折成两行,行高在\r\n * 40/56px 之间抖;③ 表格没有行分隔,条数一多就读成一面墙。现在项目名做**组头**(一个项目只出现\r\n * 一次,整条给得下),时间走 `relativeTime` 的分桶(**函数是从官方 `primitives` 逐字抄进\r\n * `ui/format.ts` 的**,与宿主侧栏同一套桶;措辞按该模块的约定留在 `copy.ts`),大小并到第二行\r\n * 元信息里,行与行的分隔交给卡片自己的边框。\r\n *\r\n * 卡片的底色/描边照抄宿主设置面自己的卡片配方(asar @28440420 逐字\r\n * `border:.5px solid var(--dsw-alias-settings-card-stroke);background:var(--dsw-alias-settings-card-fill);\r\n * border-radius:var(--dsw-radius-xl)`,而 `body` 上这两个别名就解析成 `--dsw-alias-bg-layer-2` 与\r\n * `--dsw-alias-border-l4`)。这里**直接写这两个底层 token**而不是那两个 settings 别名:别名挂在宿主\r\n * 应用的 `body` 上、不是随包发布的组件面的一部分,底层那两个才是官方组件 CSS 自己在用的。圆角取\r\n * `--dsw-radius-lg`(16px)而不是卡片面的 `xl`(20px)——行高只有 60px 上下,20px 会把卡片吹成胶囊。\r\n *\r\n * 三条界面规则没有因为改版让路:\r\n * - **空态**:`回收站是空的`,且底部**不出现**「清空回收站…」(没有东西可清)。\r\n * - **失败写在行上**:恢复/彻底删除失败的原因留在那一行(卡片第二行下方,error 色 + 警示 glyph),\r\n * 不弹全局错误、不静默跳过;行不消失,按钮可以再点。清单本身读不到时不再是一枚胶囊——单独一块\r\n * 把原文摆出来并给「重试」;那时不是空态(\"读不到\"与\"空的\"是两件事)。两块失败都标了\r\n * `role=\"status\"`:读屏走到那里会把它当状态消息念,但**插入即播报并不保证**(`aria-live` 的播报看的是\r\n * \"已在场的区域里发生了变更\",而这两块是连着内容一起冒出来的)。要稳得常驻一个只给读屏看的播报台,\r\n * 那件事本机没有 AT 可验,所以留在这里当已知边界,不写成已经做到。\r\n * - **彻底删除要二次确认**:单条与清空走同一个确认框,正文明写「之后没有任何副本可以恢复」。\r\n * **红色没有 danger 按钮变体**(官方那套 `Button` 只有 primary/ghost/outline/toolbar——我们抄来的\r\n * `ui.css` 同样只这三档,宿主主题的 `--dsw-alias-button-*` 里也没有 danger 家族),所以两档危险\r\n * 各自照抄宿主自己组红色的两条配方:\r\n * - **行内 = 安静的红**:只写 `color:--dsw-alias-state-error-primary`、**不写 background**——宿主设置面\r\n * 的行内 danger 按钮 @28442232 逐字就是 `color:…-error-primary;background:0 0;border:none`。不写底\r\n * 还有一层硬理由:作者态内联 `background` 永远赢过类规则,而 `ui.css` 里 `.sd-button-ghost:hover` /\r\n * `.sd-button-ghost:active` 都是普通类、无 `!important` ⇒ 铺了内联底的按钮**在点错之前不给任何视觉回应**。\r\n * - **批量与确认框 = 实心红**:`variant=\"primary\"` 并在元素上就地覆盖 `--dsw-alias-button-primary-fill`\r\n * 与 `--dsw-alias-button-primary-hover` 为 `--dsw-alias-state-error-primary`(宿主插件管理页的 danger\r\n * 按钮 @21658274 逐字就这么干)。`.sd-button-primary:hover` 的类规则照常生效,红也是主题自洽的实心红。\r\n * **层级**:整页只有一块实心红(「清空回收站…」,确认框里那块是它的后续),行内是安静的红——一次\r\n * 点错只损失一条,清空才是整页级的损失。(`--dsw-alias-interactive-bg-hover-danger` 是**hover 态**的\r\n * 透明红(浅色 5% / 深色 15% alpha;宿主拿它当底的是 @21305622 那条 error **块** `wq12jW_error`,\r\n * 不是芯片——`Tag[data-tone='danger']` 的底是 `color-mix(… 10%, transparent)`),当常置底既撑不起\r\n * \"实心\"也不是它的用途,所以这里两处都不用它。)\r\n *\r\n * 布局只用**结构**内联样式(display/flex/gap/padding/overflow/max-width),**配色与圆角一律走 token**;\r\n * **字号是字面量 px**、取宿主同类面的读数(它也这么写,如 `fO69Vq_crumb{font-size:12.5px}`)。结构与\r\n * 层级归设计稿 §7(本仓不另存像素稿),皮肤只由 token 决定。控件样式住在 `src/client/ui/ui.css`\r\n * (照官方明文 CSS 抄的),由 `ui/styles.ts` 在浏览器半模块物化时挂成一个 `<style>`——**这是宿主给\r\n * 客户端插件的一等公民通道**(模块表会认领工厂物化时注入的 `<style>` 并在卸载/HMR 时摘掉),\r\n * 详见那里与设计稿 §10。\r\n *\r\n * **浅色主题已在真机看过(2026-10-01,用户截图:3 条 / 2 组)**:浅色下 `--dsw-alias-bg-base` 与\r\n * `--dsw-alias-bg-layer-2` 是同一个值(都解析成 `bluish-00`;深色才分 `950` / `850`)⇒ 卡片确实只剩\r\n * `border-l4` 一根描边与页面分隔,分组靠组头 + 间距站住——读得下来,但比深色薄。宿主自己的 `rowCard`\r\n * 是同一配方,所以这是宿主的取舍、不是本面板的偏差。**深色下的新版还没看过**(同一批 token,风险低)。\r\n */\r\nimport { type CSSProperties, useCallback, useEffect, useMemo, useState } from 'react'\r\nimport { unescapeProjectKey } from '../shared/wire.js'\r\nimport { splitOn, zhTranslate, type CopyKey, type Translate } from './copy.js'\r\nimport { callRemote, type SessionDeleteRemote, type TrashEntryLike } from './remote.js'\r\nimport { useTrashRevision } from './trash-store.js'\r\nimport { Button } from './ui/Button.js'\r\nimport { fileSizeText, relativeTime, type RelativeTimeUnit } from './ui/format.js'\r\nimport {\r\n IconFolderOpenOutlineRegular,\r\n IconRefreshOutlineRegular,\r\n IconTrashOutlineRegular,\r\n IconWarningOutlineRegular,\r\n} from './ui/icons.js'\r\nimport { Modal } from './ui/Modal.js'\r\n\r\nexport type TrashPanelDeps = Pick<SessionDeleteRemote, 'list' | 'restore' | 'purge'>\r\n\r\nexport interface TrashPanelProps {\r\n deps: TrashPanelDeps\r\n /** 文案(缺省 = 内置 zh,见 `copy.ts`:这不是\"忘了传\",而是\"这次没有 locale 服务\")。 */\r\n t?: Translate\r\n}\r\n\r\n/** 待确认的彻底删除:一条记录,或整个回收站(`entryId` 缺省 = 清空)。 */\r\ntype PendingPurge = { kind: 'entry'; entry: TrashEntryLike } | { kind: 'all' }\r\n\r\n/**\r\n * 行内的危险动作:只有字色,没有底(见头注——铺内联底会把 `.ghost:hover` 打死)。\r\n * 配 `variant=\"ghost\"` 用。\r\n */\r\nconst ROW_DANGER = { color: 'var(--dsw-alias-state-error-primary)' } as const\r\n\r\n/**\r\n * 整页那唯一一块实心红:`variant=\"primary\"` + 在元素上覆盖 primary 那对填充 token。\r\n * 宿主插件管理页的 danger 按钮就是这个写法(asar @21658274),所以 hover 仍走 `.primary:hover` 类。\r\n */\r\nconst BULK_DANGER = {\r\n '--dsw-alias-button-primary-fill': 'var(--dsw-alias-state-error-primary)',\r\n '--dsw-alias-button-primary-hover': 'var(--dsw-alias-state-error-primary)',\r\n} as CSSProperties\r\n\r\n/** 回收站相对宿主**数据根**的位置。**不写 `~/.dsh/`**:数据根可以是 `$DSH_HOME` 指的别处\r\n * (隔离宿主就是这么起的),写死会让这条页脚文案在那些环境下说谎。也是页脚那句里的 `{dir}`。 */\r\nconst TRASH_DIR = 'session-trash/'\r\n\r\n/** 中央列在宽屏上就是整个对话区的宽度;清单不限宽会拉成一条横向的空。 */\r\nconst COLUMN_MAX_WIDTH = 760\r\n\r\n/**\r\n * 页面骨架:撑满中央列(`height:100%` 落在 `.centerCol` 这个有定高的网格项上),自收滚动。\r\n *\r\n * `boxSizing:'border-box'` **不是装饰**:宿主文档没有全局 border-box reset(220 处 `box-sizing`\r\n * 声明全是组件自己带的,如 `.P2izSq_card *`),默认是 content-box ⇒ `height:100%` 再加 20px 上边距\r\n * 会溢出 20px,把页脚整块推到中央列底边之外(真机浅色截图上页脚那行贴着窗口下缘就是这个)。\r\n */\r\nconst PAGE: CSSProperties = {\r\n display: 'flex',\r\n flexDirection: 'column',\r\n boxSizing: 'border-box',\r\n height: '100%',\r\n minWidth: 0,\r\n padding: '20px 24px 12px',\r\n}\r\n\r\n/** 限宽的正文列(`margin:'0 auto'` 居中,滚动区在它内部)。 */\r\nconst COLUMN: CSSProperties = {\r\n display: 'flex',\r\n flexDirection: 'column',\r\n flex: '1 1 auto',\r\n minHeight: 0,\r\n width: '100%',\r\n maxWidth: COLUMN_MAX_WIDTH,\r\n margin: '0 auto',\r\n}\r\n\r\n/**\r\n * 滚动区。\r\n *\r\n * **滚动条不需要注入任何 CSS 就已经是宿主的**:宿主在 `body` 上给出一组量(`--dsh-scrollbar-width:5px`、\r\n * thumb 圆角 999、色走 `--dsh-scrollbar-thumb`,默认 `--dsw-alias-scrollbar-bg-l1`),再用**全局**\r\n * `::-webkit-scrollbar*` 规则消费它们(asar @45064180 起,`ui-theme/src/styles/scrollbar.css`)\r\n * ⇒ 插件这里一个 `overflow:auto` 拿到的就是 5px 圆角 token 条,不是 Windows 默认的 15px 灰条。\r\n * 下面把 thumb 提到 `-l2` 那一档,与宿主自己的长滚动面同档(CodeCard、PopupSelectView 逐字设这两个\r\n * 自定义属性)。右内边距 10px 只是余量,不是修 bug。\r\n */\r\nconst SCROLLER = {\r\n display: 'flex',\r\n flexDirection: 'column',\r\n gap: 16,\r\n flex: '1 1 auto',\r\n minHeight: 0,\r\n overflow: 'auto',\r\n paddingRight: 10,\r\n paddingBottom: 16,\r\n // 自定义属性不在 csstype 的属性表里,走断言(与 `BULK_DANGER` 同一处写法)\r\n '--dsh-scrollbar-thumb': 'var(--dsw-alias-scrollbar-bg-l2)',\r\n '--dsh-scrollbar-thumb-hover': 'var(--dsw-alias-scrollbar-hover-l2)',\r\n} as CSSProperties\r\n\r\n/** 卡片配方:宿主设置面的 fill/stroke 底层 token + 官方 `lg` 圆角。 */\r\nconst CARD: CSSProperties = {\r\n display: 'flex',\r\n alignItems: 'center',\r\n gap: 14,\r\n minWidth: 0,\r\n padding: '9px 14px',\r\n background: 'var(--dsw-alias-bg-layer-2)',\r\n border: '0.5px solid var(--dsw-alias-border-l4)',\r\n borderRadius: 'var(--dsw-radius-lg)',\r\n}\r\n\r\nconst ELLIPSIS: CSSProperties = {\r\n overflow: 'hidden',\r\n textOverflow: 'ellipsis',\r\n whiteSpace: 'nowrap',\r\n}\r\n\r\n/**\r\n * `relativeTime`(`ui/format.ts`,逐字抄自官方)只交回桶与量(`{unit, n}`),措辞按它自己的约定留在本面——现在那份措辞住\r\n * `copy.ts` 的字典里(单位词与宿主 zh 字典逐字对齐:`time.minutes=\"{n}分钟\"`…\r\n * **数字与单位之间不空格**是它的写法)。`now` 桶不套模板——\"删除于 刚刚前\"不是话。\r\n */\r\nconst BUCKET_KEY: Record<Exclude<RelativeTimeUnit, 'now'>, CopyKey> = {\r\n minutes: 'trash.unit.minutes',\r\n hours: 'trash.unit.hours',\r\n days: 'trash.unit.days',\r\n months: 'trash.unit.months',\r\n years: 'trash.unit.years',\r\n}\r\n\r\nfunction deletedAtText(at: number): string {\r\n return new Date(at).toLocaleString()\r\n}\r\n\r\n/** 卡片第二行的时间短语;绝对时间另给 `title`。 */\r\nfunction deletedAgo(at: number, now: number, t: Translate): string {\r\n const { unit, n } = relativeTime(at, now)\r\n return unit === 'now'\r\n ? t('trash.row.deletedNow')\r\n : t('trash.row.deletedAgo', { n, unit: t(BUCKET_KEY[unit]) })\r\n}\r\n\r\n/**\r\n * 宿主的 `projectKey`(逐字镜像见 `src/ports/host-port.ts`)把 cwd 里的 `/`、`\\`、`:` 全折成**一个**\r\n * `-` ⇒ **分隔符那部分有损**:拆不回真实路径,也没法可靠认出哪几段是项目文件夹\r\n * (`dsh-session-delete` 自己就带连字符)。但**非分隔符那部分可逆**:不在 `[A-Za-z0-9._-]` 里、又不是\r\n * `~` 的字符编码成 `~` + 4 位大写十六进制码元,而字面 `~` 自己也被编成 `~007E` ⇒ 串里的 `~XXXX` 不含\r\n * 歧义(代理对拆成两个码元,逐个还原再拼起来仍是原字符)。所以中文目录能从 `D-develop-~-4E2D-6587`\r\n * 还原成 `D-develop-中文`。摘掉 `--…--` 裹边、还原这些转义,剩下的原样摆出来,完整串交给组头 `title`。\r\n * **字母表只有一份**:还原走 `shared/wire.ts` 的 `unescapeProjectKey`(编码那半在 Node 侧)。\r\n */\r\nfunction projectLabel(projectDir: string, t: Translate): string {\r\n const bare = unescapeProjectKey(projectDir.replace(/^--/, '').replace(/--$/, ''))\r\n return bare === '' || bare === '_no-cwd' ? t('trash.project.unknown') : bare\r\n}\r\n\r\ninterface ProjectGroup {\r\n projectDir: string\r\n label: string\r\n /** 组内最新一条的删除时间——组间排序的键。 */\r\n latest: number\r\n entries: readonly TrashEntryLike[]\r\n}\r\n\r\n/** 按项目分组:组内按删除时间倒序,组间按各组最新一条倒序(刚删掉的那个项目排在最上面)。 */\r\nfunction groupByProject(entries: readonly TrashEntryLike[], t: Translate): ProjectGroup[] {\r\n const byProject = new Map<string, TrashEntryLike[]>()\r\n for (const entry of [...entries].sort((a, b) => b.deletedAt - a.deletedAt)) {\r\n const list = byProject.get(entry.projectDir)\r\n if (list === undefined) byProject.set(entry.projectDir, [entry])\r\n else list.push(entry)\r\n }\r\n return [...byProject]\r\n // 进这条循环前整体按 `deletedAt` 倒序排过 ⇒ 每组的第一条就是该组最新的一条,组必非空。\r\n .map(([projectDir, list]) => ({\r\n projectDir,\r\n label: projectLabel(projectDir, t),\r\n latest: list[0].deletedAt,\r\n entries: list,\r\n }))\r\n .sort((a, b) => b.latest - a.latest)\r\n}\r\n\r\nexport function TrashPanel({ deps, t = zhTranslate }: TrashPanelProps) {\r\n const [entries, setEntries] = useState<readonly TrashEntryLike[] | null>(null)\r\n const [listFailure, setListFailure] = useState<string | null>(null)\r\n const [rowFailure, setRowFailure] = useState<Readonly<Record<string, string>>>({})\r\n const [pending, setPending] = useState<PendingPurge | null>(null)\r\n const [busy, setBusy] = useState(false)\r\n // 别处动过回收站(侧栏菜单里删了一条、或撤销了另一条)时,这一拍就把清单重读一遍,\r\n // 不用用户切走再切回来(失效通告台见 trash-store.ts 的头注)。\r\n const revision = useTrashRevision()\r\n\r\n const reload = useCallback(async (): Promise<void> => {\r\n const outcome = await callRemote(() => deps.list())\r\n if (!outcome.ok) {\r\n setListFailure(outcome.failure.message)\r\n setEntries(null)\r\n return\r\n }\r\n setListFailure(null)\r\n setEntries(outcome.value)\r\n }, [deps])\r\n\r\n useEffect(() => {\r\n void reload()\r\n }, [reload, revision])\r\n\r\n const failRow = (entryId: string, message: string): void => {\r\n setRowFailure((current) => ({ ...current, [entryId]: message }))\r\n }\r\n\r\n const restore = async (entry: TrashEntryLike): Promise<void> => {\r\n setRowFailure((current) => {\r\n const next = { ...current }\r\n delete next[entry.id]\r\n return next\r\n })\r\n const outcome = await callRemote(() => deps.restore(entry.id))\r\n if (!outcome.ok) {\r\n failRow(entry.id, outcome.failure.message)\r\n return\r\n }\r\n await reload()\r\n }\r\n\r\n const confirmPurge = async (): Promise<void> => {\r\n const target = pending\r\n if (target === null) return\r\n setBusy(true)\r\n const outcome = await callRemote(() =>\r\n target.kind === 'entry' ? deps.purge(target.entry.id) : deps.purge(undefined),\r\n )\r\n setBusy(false)\r\n if (!outcome.ok) {\r\n setPending(null)\r\n if (target.kind === 'entry') failRow(target.entry.id, outcome.failure.message)\r\n else setListFailure(outcome.failure.message)\r\n return\r\n }\r\n setPending(null)\r\n await reload()\r\n }\r\n\r\n const groups = useMemo(() => groupByProject(entries ?? [], t), [entries, t])\r\n const totalBytes = (entries ?? []).reduce((sum, entry) => sum + entry.sizeBytes, 0)\r\n // 分桶在**每次渲染**时按当前时刻算,但本面板**没有计时器**:一条\"59分钟\"可以挂到下次重读、\r\n // 交互或失效通告为止(桶最细到分钟,而回收站不是盯着看的界面)。这是取舍,不是\"不会停在旧读数\"。\r\n const now = Date.now()\r\n const hasEntries = entries !== null && entries.length > 0\r\n const noteParts = splitOn(t('trash.footer.note', { dir: TRASH_DIR }), TRASH_DIR)\r\n\r\n return (\r\n <div data-dsh-session-delete=\"trash-panel\" style={PAGE}>\r\n <div style={COLUMN}>\r\n <div style={{ display: 'flex', alignItems: 'flex-end', gap: 12, paddingBottom: 14 }}>\r\n <div style={{ ...ELLIPSIS, minWidth: 0 }}>\r\n <div style={{ fontSize: 16, fontWeight: 600, lineHeight: '24px' }}>{t('trash.title')}</div>\r\n {hasEntries ? (\r\n <div\r\n style={{\r\n fontSize: 12,\r\n lineHeight: '17px',\r\n color: 'var(--dsw-alias-label-tertiary)',\r\n }}\r\n >\r\n {t('trash.summary', { count: entries.length, size: fileSizeText(totalBytes) })}\r\n </div>\r\n ) : null}\r\n </div>\r\n <span style={{ marginLeft: 'auto', flex: 'none' }}>\r\n <Button\r\n variant=\"ghost\"\r\n size=\"sm\"\r\n icon={<IconRefreshOutlineRegular size={14} />}\r\n onClick={() => void reload()}\r\n >\r\n {t('common.refresh')}\r\n </Button>\r\n </span>\r\n </div>\r\n\r\n <div style={SCROLLER}>\r\n {listFailure !== null ? (\r\n <div role=\"status\" style={{ ...CARD, alignItems: 'flex-start', gap: 12, padding: '14px' }}>\r\n <span\r\n style={{\r\n display: 'inline-flex',\r\n flex: 'none',\r\n paddingTop: 2,\r\n color: 'var(--dsw-alias-state-error-primary)',\r\n }}\r\n aria-hidden=\"true\"\r\n >\r\n <IconWarningOutlineRegular size={16} />\r\n </span>\r\n <div\r\n style={{\r\n display: 'flex',\r\n flexDirection: 'column',\r\n gap: 3,\r\n flex: '1 1 auto',\r\n minWidth: 0,\r\n }}\r\n >\r\n <div style={{ fontSize: 13, lineHeight: '19px' }}>{t('trash.readFailed')}</div>\r\n <div\r\n style={{\r\n fontSize: 12,\r\n lineHeight: '17px',\r\n color: 'var(--dsw-alias-label-tertiary)',\r\n wordBreak: 'break-word',\r\n }}\r\n >\r\n {listFailure}\r\n </div>\r\n </div>\r\n <Button\r\n variant=\"outline\"\r\n size=\"sm\"\r\n style={{ flex: 'none' }}\r\n onClick={() => void reload()}\r\n >\r\n {t('common.retry')}\r\n </Button>\r\n </div>\r\n ) : entries === null ? (\r\n <div\r\n style={{\r\n padding: '20px 2px',\r\n fontSize: 12.5,\r\n color: 'var(--dsw-alias-label-tertiary)',\r\n }}\r\n >\r\n {t('trash.loading')}\r\n </div>\r\n ) : entries.length === 0 ? (\r\n <div\r\n style={{\r\n display: 'flex',\r\n flexDirection: 'column',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n gap: 10,\r\n flex: '1 1 auto',\r\n padding: '24px',\r\n textAlign: 'center',\r\n }}\r\n >\r\n <span\r\n style={{\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n width: 46,\r\n height: 46,\r\n flex: 'none',\r\n borderRadius: 999,\r\n background: 'var(--dsw-alias-bg-layer-2)',\r\n color: 'var(--dsw-alias-label-tertiary)',\r\n }}\r\n aria-hidden=\"true\"\r\n >\r\n <IconTrashOutlineRegular size={20} />\r\n </span>\r\n <div style={{ fontSize: 13.5, color: 'var(--dsw-alias-label-secondary)' }}>\r\n {t('trash.empty.title')}\r\n </div>\r\n <div\r\n style={{\r\n maxWidth: 340,\r\n fontSize: 12,\r\n lineHeight: '18px',\r\n color: 'var(--dsw-alias-label-tertiary)',\r\n }}\r\n >\r\n {t('trash.empty.hint')}\r\n </div>\r\n </div>\r\n ) : (\r\n groups.map((group) => (\r\n <section\r\n key={group.projectDir}\r\n data-dsh-session-delete=\"trash-group\"\r\n aria-label={group.label}\r\n style={{ display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}\r\n >\r\n <div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 }}>\r\n <span\r\n style={{\r\n display: 'inline-flex',\r\n flex: 'none',\r\n color: 'var(--dsw-alias-label-tertiary)',\r\n }}\r\n aria-hidden=\"true\"\r\n >\r\n <IconFolderOpenOutlineRegular size={13} />\r\n </span>\r\n <span\r\n style={{\r\n ...ELLIPSIS,\r\n fontSize: 12,\r\n fontWeight: 500,\r\n color: 'var(--dsw-alias-label-secondary)',\r\n }}\r\n title={group.projectDir}\r\n >\r\n {group.label}\r\n </span>\r\n <span\r\n style={{\r\n marginLeft: 'auto',\r\n flex: 'none',\r\n fontSize: 11.5,\r\n color: 'var(--dsw-alias-label-tertiary)',\r\n }}\r\n >\r\n {t('trash.group.count', { count: group.entries.length })}\r\n </span>\r\n </div>\r\n\r\n {group.entries.map((entry) => (\r\n <div\r\n key={entry.id}\r\n data-dsh-session-delete=\"trash-row\"\r\n data-entry-id={entry.id}\r\n style={CARD}\r\n >\r\n <div\r\n style={{\r\n display: 'flex',\r\n flexDirection: 'column',\r\n gap: 1,\r\n flex: '1 1 auto',\r\n minWidth: 0,\r\n }}\r\n >\r\n <div\r\n style={{ ...ELLIPSIS, fontSize: 14, lineHeight: '20px' }}\r\n title={entry.title === '' ? undefined : entry.title}\r\n >\r\n {entry.title === '' ? t('common.untitled') : entry.title}\r\n </div>\r\n <div\r\n style={{\r\n ...ELLIPSIS,\r\n fontSize: 12,\r\n lineHeight: '16px',\r\n color: 'var(--dsw-alias-label-tertiary)',\r\n }}\r\n title={deletedAtText(entry.deletedAt)}\r\n >\r\n {`${deletedAgo(entry.deletedAt, now, t)} · ${fileSizeText(entry.sizeBytes)}`}\r\n </div>\r\n {rowFailure[entry.id] === undefined ? null : (\r\n <div\r\n role=\"status\"\r\n style={{\r\n display: 'flex',\r\n alignItems: 'flex-start',\r\n gap: 6,\r\n paddingTop: 3,\r\n fontSize: 12,\r\n lineHeight: '16px',\r\n color: 'var(--dsw-alias-state-error-primary)',\r\n }}\r\n >\r\n <span style={{ display: 'inline-flex', flex: 'none' }} aria-hidden=\"true\">\r\n <IconWarningOutlineRegular size={13} />\r\n </span>\r\n <span>{rowFailure[entry.id]}</span>\r\n </div>\r\n )}\r\n </div>\r\n <div style={{ display: 'flex', gap: 6, flex: 'none' }}>\r\n <Button variant=\"outline\" size=\"sm\" onClick={() => void restore(entry)}>\r\n {t('trash.row.restore')}\r\n </Button>\r\n <Button\r\n variant=\"ghost\"\r\n size=\"sm\"\r\n style={ROW_DANGER}\r\n icon={<IconTrashOutlineRegular size={13} />}\r\n onClick={() => setPending({ kind: 'entry', entry })}\r\n >\r\n {t('trash.row.purge')}\r\n </Button>\r\n </div>\r\n </div>\r\n ))}\r\n </section>\r\n ))\r\n )}\r\n </div>\r\n\r\n {hasEntries ? (\r\n <div\r\n style={{\r\n display: 'flex',\r\n alignItems: 'center',\r\n gap: 12,\r\n padding: '12px 2px',\r\n borderTop: '0.5px solid var(--dsw-alias-border-l2)',\r\n }}\r\n >\r\n {/* 这句里的「清空后不可恢复」是**必须看得见**的告警,所以别处都用 ELLIPSIS、这一处不用:\r\n 520px 窗(正文列 405)量到这句 `scrollWidth 360 > clientWidth 280` ⇒ 套上省略号它就被截断,\r\n 窄幅下改成换行,尾巴不再取决于列宽。\r\n 句子整体一个 key(`trash.footer.note`,占位符 `{dir}`),渲染时按 `dir` 的值切一刀、\r\n 把那一截包成 code——**不拆成前后两段翻译**,免得两种语言各钉一次语序。 */}\r\n <span\r\n style={{\r\n minWidth: 0,\r\n fontSize: 11.5,\r\n lineHeight: '16px',\r\n color: 'var(--dsw-alias-label-tertiary)',\r\n }}\r\n >\r\n {noteParts[0]}\r\n <span style={{ fontFamily: 'var(--ds-font-family-code)', fontSize: 11 }}>\r\n {noteParts[1]}\r\n </span>\r\n {noteParts[2]}\r\n </span>\r\n <span style={{ marginLeft: 'auto', flex: 'none' }}>\r\n <Button\r\n variant=\"primary\"\r\n size=\"sm\"\r\n style={BULK_DANGER}\r\n icon={<IconTrashOutlineRegular size={13} />}\r\n onClick={() => setPending({ kind: 'all' })}\r\n >\r\n {t('trash.emptyAll')}\r\n </Button>\r\n </span>\r\n </div>\r\n ) : null}\r\n </div>\r\n\r\n {pending === null ? null : (\r\n <TrashPurgeConfirm\r\n pending={pending}\r\n busy={busy}\r\n t={t}\r\n onCancel={() => setPending(null)}\r\n onConfirm={() => void confirmPurge()}\r\n />\r\n )}\r\n </div>\r\n )\r\n}\r\n\r\nfunction TrashPurgeConfirm({\r\n pending,\r\n busy,\r\n t,\r\n onCancel,\r\n onConfirm,\r\n}: {\r\n pending: PendingPurge\r\n busy: boolean\r\n t: Translate\r\n onCancel: () => void\r\n onConfirm: () => void\r\n}) {\r\n const title = pending.kind === 'all' ? t('trash.confirm.all') : t('trash.confirm.one')\r\n const strong = t('trash.confirm.body.strong')\r\n const body = splitOn(t('trash.confirm.body', { strong }), strong)\r\n return (\r\n <Modal\r\n open\r\n onClose={onCancel}\r\n title={title}\r\n closeLabel={t('common.close')}\r\n footer={\r\n <>\r\n <Button variant=\"outline\" onClick={onCancel}>\r\n {t('common.cancel')}\r\n </Button>\r\n <Button variant=\"primary\" style={BULK_DANGER} disabled={busy} onClick={onConfirm}>\r\n {t('trash.confirm.purge')}\r\n </Button>\r\n </>\r\n }\r\n >\r\n {pending.kind === 'entry' ? (\r\n <div\r\n style={{\r\n ...CARD,\r\n flexDirection: 'column',\r\n alignItems: 'stretch',\r\n gap: 2,\r\n padding: '10px 12px',\r\n marginBlockEnd: 10,\r\n }}\r\n >\r\n <div style={{ ...ELLIPSIS, fontSize: 13.5, lineHeight: '19px' }}>\r\n {pending.entry.title === '' ? t('common.untitled') : pending.entry.title}\r\n </div>\r\n <div\r\n style={{\r\n ...ELLIPSIS,\r\n fontSize: 12,\r\n lineHeight: '16px',\r\n color: 'var(--dsw-alias-label-tertiary)',\r\n }}\r\n >\r\n {t('trash.confirm.one.meta', {\r\n project: projectLabel(pending.entry.projectDir, t),\r\n at: deletedAtText(pending.entry.deletedAt),\r\n size: fileSizeText(pending.entry.sizeBytes),\r\n })}\r\n </div>\r\n </div>\r\n ) : null}\r\n <p>\r\n {body[0]}\r\n <b style={{ color: 'var(--dsw-alias-state-error-primary)' }}>{body[1]}</b>\r\n {body[2]}\r\n </p>\r\n </Modal>\r\n )\r\n}\r\n","/**\r\n * 浏览器半入口(CJS 单文件闭包工厂的 `factory` 体,构建契约见 tsdown.config.ts)。\r\n *\r\n * 共交付五个槽位贡献(合起来是四个用户可见入口),分三类:\r\n *\r\n * - `sidebar.workspaces.session.menu.item` —— 「删除」菜单行(`DeleteMenuItem`)。这一行**只发请求**\r\n * (把 `{sessionId, title}` 写进 `delete-confirm.tsx` 的模块级现场)。\r\n * - `shell.overlay` —— 菜单之外的**两个**常驻宿主(各一次 `slots.inject`,与宿主自己对\r\n * `conversation.chat.node` 连写十几次 `inject` 的写法同形):\r\n * ① 撤销提示 `UndoToast`;② 删除确认框 `DeleteConfirmHost`。**它们不是额外入口**:两样东西都必须\r\n * 活在菜单之外(真产物 `Menu` 是 `open && jsxs(MenuSurface, { … children: [items.map(renderEntry),\r\n * children] })`,`open === false` 时槽位行根本不渲染、行随菜单一起卸载,见 delete-confirm.tsx 的头注)。\r\n * - **回收站入口 = 一对座位**(同一次探测之后、同一个同步块里注册):\r\n * ① `sidebar.panellist` —— 全局面板列表里的一行(占用者 `TrashPanelIcon`,只出 glyph);\r\n * ② `main` —— 同字 key 的中央面板(占用者 `TrashPanel`,清单本体)。\r\n * **两条必须同批**:`ctx.layout.selectPanel(panelId)` 逐字 `if (panelId !== null &&\r\n * !this.hasMainPanel(panelId)) throw new Error(...)`(asar @21199710)——只有行、没有面板时,\r\n * 用户点一下就是运行期抛错;反过来先有面板也没有入口。\r\n *\r\n * **为什么不是底栏(2026-09-30 实测裁决,设计稿 §10)**:`sidebar.footer.action` 的**惯用形**是\r\n * 「42px 整行 + 图标 + 文字 + 右对齐 12px 计数」——官方 `client-ui-cordis` 的占用者 CSS 是\r\n * `.layer{flex:none;width:100%;height:42px}`,而座位容器只有 `.footerActions{display:flex}`\r\n * (无 gap、无 wrap)⇒ 我们交的 `Button ghost/sm` 小药丸贴底栏,与下方 42px 的「设置」行不同形。\r\n * **判据随宿主组成走,不是常量**:2026-09-30 隔离活宿主实测里 `ui-cordis` 虽在组成树里,它的\r\n * 客户端半并没有挂上那条占用者(槽位渲染为空、零子节点)——所以\"被第二个租户整条挤出去\"只在\r\n * 占用者真挂上时才发生;不变的是我们**不是这条座位的惯用形**。\r\n *\r\n * 槽位名与注册形状是从宿主产物里**读出来**的,不是猜的:\r\n * - 菜单槽声明:asar @46391872 `ctx.slots.declare({ name: \"sidebar.workspaces\", children: {\r\n * \"sidebar.workspaces.session.menu.item\": { kind: \"list\", scope: \"root\", inject: { hooks: {\r\n * menuOpenState: menuOpenStateFactory, shortcuts: ctx.shortcuts.catalog } } } …`;\r\n * 数据面投影 @46271806 `renderSlot(\"sidebar.workspaces.session.menu.item\", { sessionId: node.id,\r\n * displayTitle: row.title }, { hookContext: menuOpenState })`——`menuOpenStateFactory = (_standard,\r\n * state) => () => state`(@46195835),所以这一行拿到的 prop 是 hook `useMenuOpenState`;\r\n * 宿主自己的四行注册 @46392466 起(pin 100 / rename 200 / fork 300 / archive 400)——本插件取\r\n * **order 900**,落在官方动作之后,并用 `separatorBefore` 与它们隔一条分隔线。\r\n * - 浮层槽声明:asar @21206189 `\"shell.overlay\": { kind: \"list\", scope: \"root\" }`。\r\n * - 面板行槽:座位目录原文「Global panel icons. Each list id addresses the matching main panel;\r\n * the sidebar owns the button and resolves its label from list metadata」;声明在 `ui-sidebar`\r\n * 的 `sidebar` 注册项 children 里(asar @44796980 区)`\"sidebar.panellist\": { kind: \"list\",\r\n * scope: \"root\" }`;行本体渲染 @44783481 `PanelRow` → `<Tooltip disabled={wide}>` 包一个\r\n * `<button class=panelRow aria-current={active ? \"page\" : undefined} onClick={selectPanel(id)}>`,\r\n * 内含 `.panelGlyph`(`renderSlot(\"sidebar.panellist\", { size: wide ? 16 : 18, active }, { only: id })`)\r\n * 与 `.panelTitle`(`wide` 时才有)。**名字取自注册元数据**:`syncPanels` 逐字\r\n * `label: resolveSlotLabel(options.label) ?? id`(@44795361)——所以本插件把 `label` 写进注册项。\r\n * - 中央面板槽:座位目录 `main`(`kind: \"keyed\"`, `scope: \"root\"`,\"Central panel selected by\r\n * sidebar entry id\",保留键 `conversation`,`replaceRisk: shadows-shipped-ui`);宿主渲染\r\n * @21185725 `renderSlot(\"main\", {}, { entryKey: usePanelInfo((info) => info.activePanelId) ??\r\n * \"conversation\" })`。**id 不得撞车**:座位目录原文「reusing a shipped id puts you in THAT cell\r\n * and replaces it」——静默替换比抛错更难发现,所以两个 id 都带插件专属前缀。\r\n * - **面板 id 与 main 的 key 是同一个字**(官方:list id 寻址同 id 的中央面板)。`selectPanel`\r\n * 只校验 main 里有没有注册、不校验 id 形状(@21199710),点号斜杠都不是问题。\r\n *\r\n * 宿主 ctx 用**本地窄镜像**(AGENTS.md:只声明用到的成员 + 一次强转;不装宿主类型包、\r\n * 不做 `declare module` 增强)。\r\n *\r\n * **`inject` 只声明一定存在的东西**:全仓只有 `slots` 一项——它是浏览器侧槽位注册表、\r\n * 宿主必给。**取数通道不进 `inject`**:通道是\"同源 fetch 我们自己的前缀路由\"(见 `remote.ts` 头注),\r\n * 它是不是在**只有探测才知道**(`await probeChannel(...)`),\r\n * 声明它既表达不了这件事,写错的代价又极高——前端启动自检对「有客户端模块但没激活成功」走的是\r\n * 致命路径(`Error: web boot: 1 entry did not activate` ⇒ 桌面壳 `reportFatal(\"web-boot\")`\r\n * 杀掉整个进程)。\r\n * 所以:**`apply` 同步返回、绝不抛**,注册挂在探测的续拍上;探测不过就一个贡献都不注册。\r\n */\r\nimport { type ClientContextLike } from './context.js'\r\nimport { createTranslate, type Translate } from './copy.js'\r\nimport { DeleteConfirmHost } from './delete-confirm.js'\r\nimport { DeleteMenuItem } from './delete-menu-item.js'\r\nimport {\r\n buildDeps,\r\n createHttpRemote,\r\n probeChannel,\r\n type SessionDeleteRemote,\r\n} from './remote.js'\r\nimport { TrashPanelIcon } from './trash-entry.js'\r\nimport { TrashPanel } from './trash-panel.js'\r\nimport { UndoToast } from './undo-toast.js'\r\n\r\nexport const name = '@xrn1997/dsh-session-delete/client'\r\n\r\n/**\r\n * 只列**宿主公开面保证存在**的成员:`slots` 是浏览器侧槽位注册表\r\n * (宿主外壳的冻结平台模块表里的静态键 `@deepseek-ai/dsh-client-ui-slots`),一定在。\r\n * 取数通道不在这里——理由见文件头注(它不是 inject 能表达的事,写错了代价是进程死)。\r\n * **`locale` 也不在这里**:宿主自己的 `ui-sidebar` 是硬注入它的,但那是\"宿主知道自己那一代\"的前提;\r\n * 本插件要跨代活着 ⇒ 走 `ctx.get('locale')` 可选读,缺席时降级到内置 zh 文案(见 `copy.ts` 头注)。\r\n * 写进 inject 就等于\"这一代没有 locale 就不让整个客户端条目激活\",而那等于宿主进程死。\r\n */\r\nexport const inject = ['slots']\r\n\r\nconst MENU_SLOT = 'sidebar.workspaces.session.menu.item'\r\nconst OVERLAY_SLOT = 'shell.overlay'\r\nconst PANEL_SLOT = 'sidebar.panellist'\r\nconst MAIN_SLOT = 'main'\r\n\r\n/** 本插件在槽位里的稳定 id 前缀(宿主对重名直接抛错、对重 id 静默替换,必须带插件专属前缀)。 */\r\nconst PLUGIN_ID = '@xrn1997/dsh-session-delete'\r\n\r\n/** 面板 id 与 `main` 的 key 同字:官方按 list id 去 `main` 里寻址同 id 的占用者。 */\r\nconst PANEL_ID = `${PLUGIN_ID}.trash`\r\n\r\n/* ── 菜单行的适配:槽位投影 → 组件 props ─────────────────── */\r\n/** `ui-session` 通过 `ctx.slots.provideRoot({ hooks: { sessions: ctx.sessions.list } })` 提供的根\r\n * hook(asar @22967283 原文)——宿主自己的框架组件就是这样消费它的\r\n * (`useSessions((state) => … state.byId[sessionId] …)`,asar @21178811)。 */\r\ntype UseSessions = (\r\n select: (state: { byId?: Record<string, { running?: boolean } | undefined> }) => boolean,\r\n) => boolean\r\n\r\n/** 菜单槽注入的 `useMenuOpenState`:官方 `menuOpenStateFactory` 交回的是一对开合状态\r\n * (asar @46195835 `const menuOpenStateFactory = (_standard, state) => () => state`),\r\n * 官方 README 的第三方行示例逐字 `const [, setMenuOpen] = useMenuOpenState()`。 */\r\ntype UseMenuOpenState = () => readonly [boolean, (open: boolean) => void]\r\n\r\ninterface MenuRowProps {\r\n sessionId: string\r\n displayTitle: string\r\n useSessions?: UseSessions\r\n useMenuOpenState?: UseMenuOpenState\r\n}\r\n\r\n/** 根 hook 缺失时的兜底:永不判 live。恒等身份,不会让 hook 顺序在两次渲染之间变化。 */\r\nconst NEVER_LIVE: UseSessions = () => false\r\n\r\n/** 槽位 hook 缺失时的兜底:什么都不关(同样恒等身份,hook 顺序不随渲染变)。 */\r\nconst NEVER_MENU_OPEN_STATE: UseMenuOpenState = () => [false, () => undefined]\r\n\r\n/** 菜单行的宿主:文案与远程面都从槽位载荷来(`live` 由这一行自己从 root hook 读)。 */\r\nfunction DeleteRow({ sessionId, displayTitle, useSessions, useMenuOpenState, t }: MenuRowProps & { t: Translate }) {\r\n const select = typeof useSessions === 'function' ? useSessions : NEVER_LIVE\r\n const live = select((state) => state?.byId?.[sessionId]?.running === true)\r\n // 菜单收不收是 owner(本槽位的宿主菜单)的决定,我们只消费它给的这对状态:按下这一行时先发请求、\r\n // 再让 owner 收菜单——确认框不叠在菜单上(设计稿 §7 状态 ②;官方 README 的示例同序)。\r\n const useMenuState = typeof useMenuOpenState === 'function' ? useMenuOpenState : NEVER_MENU_OPEN_STATE\r\n const [, setMenuOpen] = useMenuState()\r\n return (\r\n <DeleteMenuItem\r\n sessionId={sessionId}\r\n title={displayTitle}\r\n live={live}\r\n t={t}\r\n dismissMenu={() => setMenuOpen(false)}\r\n />\r\n )\r\n}\r\n\r\n/** 撤销提示的宿主:组件本身只吃 `restore` 与文案。 */\r\nfunction UndoToastSlot({ deps, t }: { deps: SessionDeleteRemote; t: Translate }) {\r\n return <UndoToast deps={deps} t={t} />\r\n}\r\n\r\n/** 确认框的宿主:组件只吃 `delete`(会话 id 来自模块级现场,不从这里走)。 */\r\nfunction DeleteConfirmSlot({ deps, t }: { deps: SessionDeleteRemote; t: Translate }) {\r\n return <DeleteConfirmHost deps={deps} t={t} />\r\n}\r\n\r\n/** 面板行的占用者:宿主给 `SidebarPanelIconOwnerProps { size, active }`,我们只回一个 glyph。 */\r\nfunction TrashPanelIconSlot({ size }: { size?: number }) {\r\n return <TrashPanelIcon size={size} />\r\n}\r\n\r\n/**\r\n * **探测 → 注册**:取数通道在不在,只有真发一次请求才知道。\r\n *\r\n * - 探测不过(打不通 / 回的不是信封)⇒ **早返回**:一个槽位贡献都不注册。页面上的表现是\"本插件\r\n * 不存在\",而不是点下去必然失败的入口——半吊子 UI 比没有 UI 更糟(设计稿 §7「未接通态」)。\r\n * - 探测过(哪怕 `list` 回的是 `{ok:false,error}`)⇒ 注册:那次往返证明路由在、依赖接上了、\r\n * 错误映射也生效了,失败该做的是把原文交给用户看。\r\n *\r\n * 面板那两条是**一对座位**:行的 `selectPanel(id)` 要求 `main` 里有同字 key,缺了面板那一头,\r\n * 用户点一下就是宿主自己的 `layout.selectPanel: main panel \"…\" is not registered`。\r\n * 两条分属 `sidebar.panellist` / `main` 两个槽位,只能各注册一次、**成不了原子事务**:\r\n * 谁先谁后都一样(两个 `inject` 的回调各跑各的)。所以这里不假装能防住半个座位——\r\n * 漏一半的代价是**宿主那句响亮的抛错**,不是静默降级。\r\n */\r\nasync function activate(client: ClientContextLike): Promise<void> {\r\n const raw = createHttpRemote()\r\n if (!(await probeChannel(raw))) return\r\n const deps = buildDeps(raw)\r\n // 文案:宿主 locale 服务在就注册字典并绑命名空间,不在就降级到内置 zh(见 `copy.ts` 头注)。\r\n const t = createTranslate(client)\r\n\r\n // ① 菜单行:只发请求,注入面一件都不需要(会话 id / 名字由槽位投影给)。\r\n client.slots.inject(MENU_SLOT, () =>\r\n client.slots.register(\r\n { name: MENU_SLOT, id: `${PLUGIN_ID}.delete`, order: 900, inject: () => ({ t }) },\r\n DeleteRow,\r\n ),\r\n )\r\n\r\n // ② 撤销提示的常驻宿主(不在菜单里,理由见 §头注)\r\n client.slots.inject(OVERLAY_SLOT, () =>\r\n client.slots.register(\r\n { name: OVERLAY_SLOT, id: `${PLUGIN_ID}.undo-toast`, inject: () => ({ deps, t }) },\r\n UndoToastSlot,\r\n ),\r\n )\r\n\r\n // ②' 确认框的常驻宿主(确认框必须脱离菜单子树的生命周期,见 delete-confirm.tsx)\r\n client.slots.inject(OVERLAY_SLOT, () =>\r\n client.slots.register(\r\n { name: OVERLAY_SLOT, id: `${PLUGIN_ID}.delete-confirm`, inject: () => ({ deps, t }) },\r\n DeleteConfirmSlot,\r\n ),\r\n )\r\n\r\n // ③ 面板行:名字给宿主(它画 `.panelTitle` 与折叠 tooltip),我们只出一个 glyph。\r\n // order 落在官方两个面板行(plugins 0 / schedules)与第三方「小说」(20)之后。\r\n // **名字走 thunk**:宿主 `resolveSlotLabel` 在每次重读时才求值,并在语言变化时重读\r\n // (`ctx.locale.subscribe(syncPanels)`)⇒ 切换语言这一行跟着走。**别改成活计数**:它只在\r\n // 条目变化或语言变化时重读,拿它当计数必然 stale(设计稿 §7)。\r\n client.slots.inject(PANEL_SLOT, () =>\r\n client.slots.register(\r\n { name: PANEL_SLOT, id: PANEL_ID, order: 900, label: () => t('trash.title') },\r\n TrashPanelIconSlot,\r\n ),\r\n )\r\n\r\n // ③' 中央面板:同字 key;清单本体(自带页面骨架与滚动,中央列那个盒子两样都不给)。\r\n client.slots.inject(MAIN_SLOT, () =>\r\n client.slots.register({ name: MAIN_SLOT, key: PANEL_ID, inject: () => ({ deps, t }) }, TrashPanel),\r\n )\r\n}\r\n\r\n/**\r\n * 入口:**同步返回、绝不抛**(见文件头注)。\r\n *\r\n * 注册挂在探测的续拍上,两条理由:① 前端启动自检只看 entry 声明的 `inject` 服务齐不齐(`slots`\r\n * 一定在),不看它注册了几行;② 槽位注册是注册表的一次 mutation,渲染层按微任务批量订阅\r\n * (`SlotCore` 的变更传播契约),晚一拍注册照常上屏——宿主侧栏对 `sidebar.panellist` 正是\r\n * `ctx.slots.subscribe(\"sidebar.panellist\", syncPanels)`(asar @44795361)。\r\n */\r\nexport function apply(ctx: unknown): void {\r\n void activate(ctx as ClientContextLike).catch((error: unknown) => {\r\n console.warn('[dsh-session-delete] 入口未注册(取数通道不可用):', error)\r\n })\r\n}\r\n"],"mappings":"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"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,65 @@
1
+ import { Context } from "@deepseek-ai/cordis";
2
+ //#region src/trash/entry.d.ts
3
+ /** 回收站的一条记录。`id` 由 `makeEntryId` 生成(`<删除时刻>-<原目录名>`)——**它不是随手起的键**:
4
+ * 回收站目录名就是它,恢复也按它反推出原目录名放回(设计稿 §3),所以 id 的形状有契约意义。
5
+ * 其余字段在删除那一刻就地取。 */
6
+ interface TrashEntry {
7
+ id: string;
8
+ sessionId: string;
9
+ projectDir: string;
10
+ /** 删除时它所属的工作区;**空串 = 删除时就没有账目**(「未分组」的会话,宿主的"删除工作区"会造出这种),
11
+ * 恢复时据此跳过 `attach`。 */
12
+ workspaceId: string;
13
+ /** 删除**之前**的归档态。删除会把"原本未归档"的会话归档(藏起来,免得它掉进「未分组」),
14
+ * 恢复时按这个字段还原可见性:true ⇒ 仍处归档态,false ⇒ 取消归档放回树里。 */
15
+ wasArchived: boolean;
16
+ deletedAt: number;
17
+ /** 删除时**尽力**取的会话标题(来源:`storages/session_projcache/sessions/<会话id>.json`
18
+ * 里的 `rows.title.val`);取不到存空串,绝不因此让删除失败。 */
19
+ title: string;
20
+ /** 删除时用 `fs.sizeOfDir` 量到的会话目录字节数。 */
21
+ sizeBytes: number;
22
+ }
23
+ //#endregion
24
+ //#region src/trash/manifest.d.ts
25
+ /** 回收站清单的**唯一**读法(动词只依赖这个端口,不认 storage domain 的形状)。 */
26
+ interface ManifestPort {
27
+ list(): Promise<TrashEntry[]>;
28
+ add(e: TrashEntry): Promise<void>;
29
+ remove(id: string): Promise<void>;
30
+ }
31
+ //#endregion
32
+ //#region src/index.d.ts
33
+ export declare const name = "@xrn1997/dsh-session-delete";
34
+ /**
35
+ * 插件依赖:**只写字符串数组**(隔离数据根上实测:写成 `{ required, optional }` 会让插件停在
36
+ * `pending (waiting for services: required, optional)`)。
37
+ *
38
+ * 三项都是在隔离数据根上实测注入成功、且本插件真正用到的宿主服务。三个**故意不写**的:
39
+ * - `remote`:那是**浏览器半**的服务(asar `dsh-api-gateway/lib/client.js`:`super(ctx, "remote")`),
40
+ * Node 半挂的是 `ctx.typertGateway`。把它写进 Node 半的 inject 会让插件永远 pending(同 `slots` 那次的形状)。
41
+ * - `sessions` / `sessionProjectionCache`:前者本实现不用(活跃判据走 waterfall,见设计稿 §5);后者是**可选**服务,
42
+ * 官方消费方也按可选读(`ctx.get('sessionProjectionCache')?.…`),inject 一个可能没挂的服务 = 整个插件 pending。
43
+ * 它取不到时 `titleOf` 返回空串,正是契约允许的降级。
44
+ *
45
+ * `webServer` 是**浏览器半取数通道的承载者**(那条薄线):插件用它注册 `/dsh-session-delete-api`
46
+ * 前缀路由,浏览器半同源 fetch 那四个端点。它是宿主 web 组成里的硬依赖(`dsh-host-webserver` 的
47
+ * `super(ctx, "webServer")`),服务名与 `register({kind:'prefix', path, handler})` 的形状按
48
+ * `dsh-host-webserver/lib/index.js` 与 `README.md` 原文读实,不是猜的。
49
+ */
50
+ export declare const inject: string[];
51
+ /**
52
+ * 清单的真实绑定:官方 storage domain → `ManifestPort`(`StorageDomainLike` 在动词层保持一种读法)。
53
+ * 域**懒开**且只开一次(`apply` 是同步的,而 `open()` 是异步的),并挂一条 `ctx.effect` 清理:
54
+ * 插件卸载时关掉句柄(句柄归调用方所有,这是 `open()` 的 JSDoc 明写的契约)。
55
+ */
56
+ export declare function createStorageManifest(ctx: Context): ManifestPort;
57
+ /**
58
+ * 装配顺序:**清单 → 端口 → 动词依赖 → 前缀路由**(`DeleteDeps` 就是前两者的合一)。
59
+ *
60
+ * 路由用 `ctx.effect` 挂、disposer 归装载它的那根 fiber:插件卸载即摘路由(`register` 返回的
61
+ * disposer 是官方契约)。**同一进程里只注册一次**这件事由宿主把住:重复的 `(kind, path)`
62
+ * 在 `register` 里直接抛(`webserver: duplicate prefix route "…"`),响亮失败而不是悄悄覆盖。
63
+ */
64
+ export declare function apply(ctx: Context): void;
65
+ //#endregion