dsh-life-pack 0.3.6 → 0.3.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.js +217 -26
- package/dist/client.js.map +1 -1
- package/dist/config-panel-api.d.ts +21 -0
- package/dist/config-panel-api.d.ts.map +1 -0
- package/dist/config-panel-api.js +20 -0
- package/dist/config-panel-api.js.map +1 -0
- package/dist/config-panel-contract.d.ts +90 -0
- package/dist/config-panel-contract.d.ts.map +1 -0
- package/dist/config-panel-contract.js +32 -0
- package/dist/config-panel-contract.js.map +1 -0
- package/dist/config-panel-value.d.ts +68 -0
- package/dist/config-panel-value.d.ts.map +1 -0
- package/dist/config-panel-value.js +190 -0
- package/dist/config-panel-value.js.map +1 -0
- package/dist/config-panel-view.d.ts +294 -0
- package/dist/config-panel-view.d.ts.map +1 -0
- package/dist/config-panel-view.js +615 -0
- package/dist/config-panel-view.js.map +1 -0
- package/dist/config-panel.d.ts +48 -0
- package/dist/config-panel.d.ts.map +1 -0
- package/dist/config-panel.js +273 -0
- package/dist/config-panel.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/manager-version.d.ts +11 -0
- package/dist/manager-version.d.ts.map +1 -1
- package/dist/manager-version.js +39 -6
- package/dist/manager-version.js.map +1 -1
- package/dist/update-queue.d.ts +42 -0
- package/dist/update-queue.d.ts.map +1 -0
- package/dist/update-queue.js +91 -0
- package/dist/update-queue.js.map +1 -0
- package/package.json +2 -1
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":["IDLE","React","React","React","S","React","React","S","React"],"sources":["../src/nav.ts","../src/update-contract.ts","../src/update-client.ts","../src/update-view.ts","../src/update-panel.ts","../src/health-contract.ts","../src/health-fetch.ts","../src/health-panel.ts","../src/health-view.ts","../src/directory-browser-contract.ts","../src/directory-browser-state.ts","../src/directory-browser-parts.ts","../src/directory-browser-ui.ts","../src/directory-browser-roots.ts","../src/client.ts"],"sourcesContent":["/** dsh-life-pack 导航表(P10 脚手架,P3 #4 定案 order)。\n *\n * 总管只导航:6 tab 描述子仅做 openTab 路径导航,内容由各单品包自注册\n * slot 提供;总管不 import 任何单品页/单品包(见 boundaries 断言)。\n * 缺席纯条件渲染:present 缺谁就回谁的 reco(推荐安装+补装提示),不轮询。\n * 子页不占栏:卡路里 diet/exercise/goal 等 ilife:calorie:* 后缀只在单品\n * 主面板内导航,绝不在此表另起 tab 行。\n * 60 deck:map 为 matt 探针已占位,本表不动它。\n *\n * 面板外壳的两张文案表也在本文件(票 #679):右上角两个入口 PANEL_LINKS、\n * 底部「作者其他插件」卡 MORE_PLUGINS。文案与网址集中在这里,\n * 组件只负责画——改文案不碰组件(map #671 的 Q11 决议)。\n */\n\nexport interface ManagerTab {\n readonly skill: string;\n readonly slotId: string;\n readonly order: number;\n /** 页签名(屏上逐字印这个)=**产品名**(票 #738:作息管家/居家管家/私家大厨/饼干记账)。\n *\n * 它是各家自己那个 `SLOT_TITLE` 的镜像,两处必须同值:页签注册缺席时由这一格兜底,\n * 更新列表那一行的行首也用它(`update-targets.ts` 原样取 `title`)。\n * 对齐由 `test/plugin-p10-boundaries.test.mjs` 逐家咬住——真 import 两边的产物来比,不搜字符串。 */\n readonly title: string;\n /** 提供该 tab 的单品插件包名(仅文档级引用,不 import)。 */\n readonly plugin: string;\n /** 本家自己那条客户端通道(单段,例 `/ilife-calorie`):面板按它取配置体检(体检那个端点挂在上面)。\n *\n * 这条事实为什么住在这里(票 #735):各家注册页签槽时**也**会交一份(各包契约件的 `RPC_CHANNEL`),\n * 但装机上的槽位面**不透传自定义注册选项**——条目 options 只留 `{key,id,order,label,priority}`,\n * 总管从账本上读到的那一格恒是空串。后果是六家恒被判成「没有体检出口」,\n * 面板的体检目标表恒为空,「体检一次」按下去毫无反应(第 1 版的实现就是这么哑掉的)。\n * 这份镜像由 `test/plugin-p10-boundaries.test.mjs` 逐家与各包契约件的常量对齐守着。\n * 各家注册里那一格**留着不删**:上游哪天开始透传自定义选项,可以一行切回账本。 */\n readonly channel: string;\n}\n\nexport interface RecoTab extends ManagerTab {\n readonly kind: 'reco';\n /** 补装命令(单命令双包口径,总管必须已在位)。 */\n readonly installCmd: string;\n readonly hint: string;\n}\n\nexport const MANAGER_PLUGIN = 'dsh-life-pack' as const;\n\n/** 版本号**不在本文件**(票 #737):面板那行「总管 dsh-life-pack · x.y.z」的版本号由宿主半\n * `manager-version.ts` 运行时读自己这份已安装包的 `package.json`,面板经电话\n * `ilife-manager.version` 取来只做渲染——手写常量会与包版本无声漂开一整版(#737 实测过),\n * 读完盘这条路把「屏上那行 ≡ 装机包版本」变成构造性事实,定版只剩改 `package.json` 一处。 */\n\nexport const DUAL_ADD_PREFIX = 'dsh plugin add dsh-life-pack' as const;\n\nexport function dualInstallCmd(singlePlugin: string): string {\n return DUAL_ADD_PREFIX + ' ' + singlePlugin;\n}\n\n/** P3 定案 6 tab(order 升序即 + 号菜单顺序;已打开条不受 order 控制)。 */\nexport const MANAGER_TABS: readonly ManagerTab[] = [\n { skill: 'bill', slotId: 'ilife:cookie', order: 70, title: '饼干记账', plugin: 'dsh-bill-ilife', channel: '/ilife-bill-ilife' },\n { skill: 'calorie', slotId: 'ilife:calorie', order: 75, title: '卡路里', plugin: 'dsh-calorie', channel: '/ilife-calorie' },\n { skill: 'memo', slotId: 'ilife:memo', order: 80, title: '备忘录', plugin: 'dsh-memo-ilife', channel: '/ilife-memo' },\n { skill: 'schedule', slotId: 'ilife:schedule', order: 85, title: '作息管家', plugin: 'dsh-schedule-ilife', channel: '/ilife-schedule-ilife' },\n { skill: 'home', slotId: 'ilife:home', order: 90, title: '居家管家', plugin: 'dsh-home-ilife', channel: '/ilife-home-ilife' },\n { skill: 'chef', slotId: 'ilife:chef', order: 95, title: '私家大厨', plugin: 'dsh-chef', channel: '/ilife-chef' },\n];\n\nexport function recoFor(tab: ManagerTab): RecoTab {\n const installCmd = dualInstallCmd(tab.plugin);\n return {\n ...tab,\n kind: 'reco',\n installCmd,\n hint: '未安装[' + tab.plugin + '],请补装后使用:' + installCmd,\n };\n}\n\nexport type Presence = ReadonlySet<string>;\n\nexport function tabsForPresence(present: Presence): readonly (ManagerTab | RecoTab)[] {\n return MANAGER_TABS.map((t) => (present.has(t.slotId) ? t : recoFor(t)));\n}\n\nexport interface OpenPort {\n openTab(seed: { readonly type: string; readonly path: string }, scope?: { readonly sessionId?: string }): void;\n}\n\nexport interface HasTabsPort extends OpenPort {\n hasTabs(): boolean;\n}\n\n/** 总管导航:只做 openTab 路径导航(内容型打开),缺席由调用方渲染 reco。 */\nexport function openManagerTab(port: HasTabsPort, slotId: string, sessionId?: string): void {\n if (!port.hasTabs()) return;\n port.openTab({ type: slotId, path: slotId }, sessionId ? { sessionId } : undefined);\n}\n\n/** 面板右上角一个入口:图标 + 悬停说明 + 目标网址。 */\nexport interface PanelLinkRow {\n readonly key: 'star' | 'feedback';\n /** 图标字形(照参照实现 dsh-mattpocock-skills-deck,星星只留 ⭐、反馈只留 💬)。 */\n readonly glyph: string;\n /** 鼠标悬停给出的说明文字。 */\n readonly tip: string;\n readonly url: string;\n}\n\n/** 面板右上角两个入口:星(去本仓点 star)+ 气泡(去本仓开 issue)。\n *\n * 两处都指本仓 FeatherHunter/ilife——这是 map #671 裁定过的(「星与气泡指向哪个仓」\n * 一问:反馈要落在本仓的需求池,「引流到作者其他技能」是底部那张卡的事)。\n * 三件并排里的第三件「检查更新」住隔壁票(#678),本表不管它。\n */\nexport const PANEL_LINKS: readonly PanelLinkRow[] = [\n { key: 'star', glyph: '⭐', tip: '去 GitHub 点 Star', url: 'https://github.com/FeatherHunter/ilife' },\n { key: 'feedback', glyph: '💬', tip: '反馈问题', url: 'https://github.com/FeatherHunter/ilife/issues/new' },\n];\n\n/** 底部「作者其他插件」卡里的一行:包名 + 一句说明 + 行尾外链图标指向的仓库。 */\nexport interface MorePluginRow {\n readonly pkg: string;\n readonly desc: string;\n readonly url: string;\n}\n\n/** 「作者其他插件」四行:包名 + 一句说明 + 仓库地址。\n *\n * 文案口径(用户 2026-09-18 拍板):**不写数字**——数字会过期(用户截图里的「34 款」实测已到 38 款),\n * 写错最伤推荐位的可信度;第一行也不用 deck 自己面板里的自指「本面板自己」,改成不自指的说法。\n * 只收真实存在的仓库,网址已逐个验真。第一行的说明描述的是 deck 那个包(工程技能面板),不是爱生活面板。\n */\nexport const MORE_PLUGINS: readonly MorePluginRow[] = [\n {\n pkg: 'dsh-mattpocock-skills-deck',\n desc: '工程技能面板:装好就能在右侧直接用',\n url: 'https://github.com/FeatherHunter/dsh-mattpocock-skills-deck',\n },\n {\n pkg: 'dsh-opencode-palette',\n desc: '多款长时间编程护眼配色,一键换上',\n url: 'https://github.com/FeatherHunter/dsh-opencode-palette',\n },\n {\n pkg: 'dsh-prompt',\n desc: '常用提示模板随手点,不用来回复制粘贴',\n url: 'https://github.com/FeatherHunter/dsh-prompt',\n },\n {\n pkg: 'dsh-im-companion',\n desc: '聊天机器人的伴侣插件:扫码或填凭据就把飞书、微信等聊天接进来',\n url: 'https://github.com/FeatherHunter/dsh-im-companion',\n },\n];\n","/** 总管与面板之间的定案形状:载体、电话名、原因人话表、手工兜底命令。\n *\n * 分工:本文件只管「两侧都认的常量与文案」;宿主半见 `update-host.ts`,面板见 `client.ts`。\n * 电话名分两类:\n * - 七个**更新电话组**(每组三个 `updateStatus` / `updateCheck` / `updateInstall`)由更新包\n * 自己拼(`update.phoneNames`),面板侧取值见构建期派生的 `update-phones.ts`;\n * - 两个**总管自有电话**(装上缺席包、取版本行数据)是本包定义的,见 `MANAGER_ACTIONS`。\n *\n * 原因码的人话文案逐条抄更新包 README 第 8 节「用户该做什么」列与第 12 节排错表;\n * 手工命令形状抄第 9 节(`commands.ts:104-122` 的 `manualCommand`)。\n */\nimport type { BlockedReason } from 'dsh-plugin-update';\n\n/** 爱生活页签槽名(总管声明 `children`、各单品技能设置页注册进来;单段名,避开官方 `settings.*` 前缀)。\n *\n * 为什么住这里:这个名字**两侧都要用**——面板侧 `client.ts` 拿它注册/投影槽,\n * 宿主侧(`update-env.ts` 的 `readPanelRegistered`)拿它判「已装产物里到底有没有注册代码」。\n * 一处定义、两边引用(结构纪律「概念唯一」):名字改了只有这一行要动。\n * 宿主判据为什么要读这个字面量、以及它的边界,见 `readPanelRegistered` 的头注。 */\nexport const CONFIG_TAB_SLOT = 'ilife.config-tab' as const;\n\n/** 载体基段:`connection.rpc.call` 的**第一段**参数,宿主注册路径与它同源(`'/api' + 通道名`)。\n *\n * 一处定义(#735):总管自己的电话、以及面板到各家的每一通电话,第一段都必须是它。\n * 真机实现(`dsh-client-connection/lib/client.js`):URL = `${第一段}/${第二段}`、信封 `method` = 第二段。\n * 传错第一段就是 404:总管自己的电话在 #678 栽过一次,各家的配置体检在 #735 又栽过一次\n * (那次传的是通道名 `/ilife-<技能>`,拼出来的地址上没有注册过那条路由,六家全 404)。 */\nexport const CARRIER_BASE = '/api' as const;\n\n/** 载体:宿主注册 `connection.fetch.register` 的路径 / 面板 `connection.rpc.call` 的两段参数。 */\nexport const MANAGER_RPC = {\n /** 载体基段(`connection.rpc.call` 的**第一段**参数)。传错第一段就是 404:真机上踩过一次。 */\n base: CARRIER_BASE,\n /** 单段通道名,须 match `/^\\/[A-Za-z0-9._~-]+$/`(cookbook §6)。 */\n channel: '/ilife-manager',\n /** 面板侧第二段参数(等于通道名去掉前导斜杠那一段)。 */\n endpoint: 'ilife-manager',\n /** 宿主侧注册的完整路径(= `base` + `channel`)。 */\n path: '/api/ilife-manager',\n} as const;\n\n/** 总管自有电话名:更新包的三个动作名是冻结集合(`config.ts:14`),这里另起名字不撞车。 */\nexport const MANAGER_ACTIONS = {\n /** 装上缺席的目标包:入参 `{packageName, version}`,回包 `{packageName, version}`。 */\n install: 'ilife-manager.install',\n /** 七个更新目标的表:入参 `{}`,回包 `{targets: [{key, title, packageName, phones, runningVersion, installedVersion, panelRegistered, skill}]}`。\n *\n * 电话名为什么由宿主转交而不是面板侧写死:更新包的三个电话名只有它自己知道\n * (`update.phoneNames`,`host.ts:335`);面板侧既不能 import 更新包(浏览器产物纯度门)\n * 也不该把名字再写一份(写两份必然走散)。宿主把名字表连同版本行一起交出去,\n * 面板一行字面量都不留。 */\n targets: 'ilife-manager.targets',\n /** 总管自述版本(票 #737):入参 `{}`,回包 `{version}`。\n *\n * 宿主读**自己这份已安装包**的 `package.json`(`manager-version.ts`),面板只渲染读到的值。\n * 为什么走电话:面板是浏览器产物,禁 node 内建(`test/client-bundle-48.test.mjs` 看门),\n * 读盘只许在宿主半;与卡路里 #130 同一条路(host 读 → RPC → client 纯渲染)。 */\n version: 'ilife-manager.version',\n /** 本机「根」清单(票 #744):入参 `{}`,回包 `{roots: [{path, kind}]}`。\n *\n * 为什么这条能力住在总管:浏览器里没有卷清单接口,而宿主那条目录选择接缝只有\n * `list`/`createDirectory` 两格——「这台机器上有哪些盘」只能由宿主去问操作系统。\n * 六家的目录浏览器都取这一通电话,故名字与实现都只有这一处(清单本身见 `roots.ts`)。\n * 回包**恒成功**:列不出来是空清单,不是错误码(界面据此只是不画那一行)。 */\n roots: 'ilife-manager.roots',\n} as const;\n\n/** 版本读不到时两侧共用的降级字面量(面板照原样显示,不假装知道版本)。\n *\n * 一处定义、两侧引用:宿主半 `manager-version.ts` 读失败时回它,面板半 `update-client.ts`\n * 归一化时也认它——面板侧不许 import 宿主半(会把 node 内建带进浏览器束)。 */\nexport const VERSION_UNKNOWN = 'unknown' as const;\n\n/** 更新包默认官方源(`config.ts:20`,总管侧拼手工命令时用同一个值)。 */\nexport const DEFAULT_REGISTRY = 'https://registry.npmjs.org/' as const;\n\n/** 装不了 / 装不成的人话原因表:八种 `blockedReason` + 过程错误码(更新包 README 第 8、12 节)。\n *\n * 每条都要三段齐全(票 #740 的文案判据):**事实**(哪里对不上)→ **后果**(这家现在能不能装)\n * → **动作**(用户做得到、且面板上真有的那一步;面板代劳不了的走「下面这条命令」)。\n * 禁用口语与自造词:「半截任务」「电话没接上」「回执异常」「还出现就重装」「查宿主日志」一律不写。\n *\n * 点名的按钮必须是**这一行上真够得着**的那颗(票 #740 第三轮):结果浮层一开就盖住题头的\n * 「检查更新」,所以「改完再读一次」这类下一步一律写「重新检查」(画在那一行上);\n * 只有发生在**重启之后**的下一步才写「检查更新」。反过来,凡是写着「下面这条命令」的那几句,\n * 那一行的命令块必须真出来(判据 `Verdict.manualHint`)——两处都由门禁守着。 */\nconst REASON_TEXT: Record<string, string> = {\n 'unknown-profile': '认不出这台机器上的安装位置(使用范围的目录不存在,或名字不合法)。把使用范围目录建好,再点「重新检查」。',\n 'source-install': '这一份不是从 npm 发行包装的(源码装),面板不能更新它。要更新,照下面这条命令重装,装完再点「重新检查」。',\n 'invalid-installation': '磁盘上这一份装得不完整(包名或版本对不上、入口文件缺失)。照下面这条命令重装,装完再点「重新检查」。',\n 'installation-changed': '这台机器的安装清单变了(在别处装过、或清单被手工改过),这一家现在不能装。点「重新检查」重新读一次,再点「装上更新」。',\n 'pending-restart': '新版已装到磁盘,正在运行的是旧版本。重启 DSH(退出后重新打开)后生效。',\n 'registry-conflict': '使用范围清单里写的版本与磁盘上的实际版本对不上。打开使用范围的 `package.json`,把这一行改成磁盘上的版本号,再点「重新检查」。',\n 'incompatible-node': '新版本要求 Node 22 或更高,当前运行的是更低版本。升级 Node、重启 DSH 后,再点「检查更新」。',\n // 这条只在「磁盘上的版本与正在运行的一致(或磁盘那份读不到)」时才会印出来——「磁盘 ≠ 运行中」\n // 那一态被事实判据先截走,印的是 `restartLine`。故这里**不许**再教人去重启(那在眼下没用)。\n 'recovery-required': '上次安装没有收尾。点「重试安装」再跑一次;还出现这一条,就用下面这条命令重装。',\n 'check-failed': '查不到最新版本:联网失败,或官方源(registry.npmjs.org)不可达。确认网络与源地址后,再点「重新检查」。',\n 'invalid-release': '官方源上这个版本的元数据不完整(包名或版本不合法、包地址或校验值缺失)。换一个版本,或稍后重试。',\n 'check-expired': '上次检查的结果已过期。点「重新检查」重查一次,再点「装上更新」。',\n 'update-busy': '同一时间只能安装一个插件。等这次装完,再点「装上更新」。',\n 'install-failed': '安装没有完成。在终端里执行下面这条命令把它装完,装完再点「重新检查」。',\n 'manager-unreachable': '面板连不上总管插件(宿主里没注册,或版本对不上)。重启 DSH 后重试;仍不行就在 DSH 日志里找 `[dsh-life-pack]` 的报错。',\n 'bad-request': '面板与总管之间的请求出错了(属程序缺陷)。请在 DSH 日志里找 `[dsh-life-pack]` 的报错,连同这家插件的名字报到本仓 issue。',\n internal: '面板与总管之间的请求出错了(属程序缺陷)。请在 DSH 日志里找 `[dsh-life-pack]` 的报错,连同这家插件的名字报到本仓 issue。',\n};\n\n/** 原因码 → 人话(认不出的码不猜、原样回码并说明这是未知原因)。 */\nexport function reasonText(code: string): string {\n return REASON_TEXT[code] ?? '面板不认识这个原因码(' + code + ')。请在 DSH 日志里找同一处报错,报到本仓 issue。';\n}\n\n/** 手工兜底命令(更新包 README 第 9 节形状):使用范围名含特殊字符时加引号。 */\nexport function manualInstallCommand(input: {\n profileName: string | null;\n packageName: string;\n version: string;\n registryUrl?: string;\n}): string | null {\n const name = typeof input.profileName === 'string' ? input.profileName.trim() : '';\n if (!name || name.length > 255 || name.startsWith('-') || ['.', '..', 'node_modules'].includes(name)) return null;\n const version = typeof input.version === 'string' && /^\\d+\\.\\d+\\.\\d+$/.test(input.version) ? input.version : '';\n if (!version) return null;\n const registry = input.registryUrl ?? DEFAULT_REGISTRY;\n const profileArg = /^[A-Za-z0-9_.-]+$/.test(name) ? name : JSON.stringify(name);\n return 'dsh plugin --profile ' + profileArg + ' add --save-exact ' + input.packageName + '@' + version + ' --registry=' + registry;\n}\n\n/** 八种装不了原因码(给门禁与测试对表用;值就是 `BlockedReason` 全集)。 */\nexport const BLOCKED_REASONS: readonly BlockedReason[] = [\n 'unknown-profile',\n 'source-install',\n 'invalid-installation',\n 'installation-changed',\n 'pending-restart',\n 'registry-conflict',\n 'incompatible-node',\n 'recovery-required',\n];\n","/** 面板到宿主的四条流程(纯异步函数:无 DOM、无 react;传输口由调用方注入,便于单测)。\n *\n * 四条流程与更新包 README 的对应关系:\n * - 检查更新:`updateCheck`(第 3 节第 3 步的 `host.call(UPD_CHECK, {})`);\n * - 装上更新:`updateCheck` 拿凭证 → `updateInstall` 提交 → **按间隔轮询 `updateStatus`**\n * 直到任务离开 `installing`/`verifying`(第 3 节第 3 步与第 9 节:安装是后台跑,\n * 调用方不干等;凭证过期 `check-expired` 是正常错误码,重查一次再提交,见第 11 节);\n * - 装上缺席的包:走总管自有的 `ilife-manager.install`(更新包的三个电话只管已装的包);\n * - 总管自述版本:走总管自有的 `ilife-manager.version`(宿主读自己那份包描述文件,见 `manager-version.ts`)。\n */\nimport { MANAGER_ACTIONS, MANAGER_RPC, VERSION_UNKNOWN, reasonText } from './update-contract.js';\nimport type { CheckOutcome, TargetInfo } from './update-view.js';\n\n/** 传输口(DSH 载体 `connection.rpc.call` 的形状,cookbook §6)。 */\nexport type CallFace = (\n channel: string,\n endpoint: string,\n payload: unknown,\n signal?: AbortSignal,\n) => Promise<\n | { readonly ok: true; readonly value: unknown }\n | { readonly ok: false; readonly error: { readonly code: string; readonly message: string; readonly details: Record<string, unknown> } }\n>;\n\nexport interface CallFailure {\n readonly ok: false;\n readonly code: string;\n readonly message: string;\n readonly details: Record<string, unknown>;\n /** 能给的话给一条可复制的手工命令(更新包每次回包都带,见其 README 第 9 节)。 */\n readonly manual: string | null;\n}\n\nexport type CallOutcome<T> = { readonly ok: true; readonly value: T } | CallFailure;\n\n/** 轮询上限:1 秒一次、最多 900 次(覆盖更新包 15 分钟的安装时限)。 */\nconst MAX_POLL_TRIES = 900;\n/** 轮询默认间隔(只有在宿主没给 `pollMs` 时才用;正常取值由宿主转交更新包的 panelPollMs)。 */\nconst FALLBACK_POLL_MS = 1000;\nlet requestSeq = 0;\n\nfunction failure(code: string, details: Record<string, unknown> = {}, manual: string | null = null): CallFailure {\n return { ok: false, code, message: reasonText(code), details, manual };\n}\n\n/** 取数间隔:settle 即清 timer。定时器有界重试的例外依据(p10 静态门三件套):\n * 这里有清理(clearTimeout)、有次数上限(MAX_POLL_TRIES)、间隔取自宿主转交的更新包\n * `panelPollMs`(不写死);用途是更新包 README 第 3 节要求的状态轮询(安装是后台跑,\n * 面板每秒问一次进度),不是无界重试。 */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n const timer = setTimeout(() => {\n clearTimeout(timer);\n resolve();\n }, ms);\n });\n}\n\nasync function callManager<T>(call: CallFace | null, method: string, payload: Record<string, unknown>): Promise<CallOutcome<T>> {\n if (typeof call !== 'function') return failure('internal', { detail: '宿主连接缺席:connection.rpc.call 不可用' });\n let result: Awaited<ReturnType<CallFace>>;\n try {\n // 第一段是**载体基段**(`/api`)、第二段才是本包端点;传错就是打到一个不存在的路由(真机踩过)。\n result = await call(MANAGER_RPC.base, MANAGER_RPC.endpoint, { method, payload });\n } catch (error) {\n // 传输层失败(路由没注册、宿主半版本对不上、请求挂了):不许套用「查新版失败」那句——\n // 它对这种情形是误导(真机上就这么误报过一次)。各层的失败各有各的话。\n return failure('manager-unreachable', { detail: String((error as Error)?.message ?? error) });\n }\n if (!result || typeof result !== 'object' || typeof (result as { ok?: unknown }).ok !== 'boolean') {\n return failure('internal', { detail: '回执信封异常(非 ok 信封)' });\n }\n if (result.ok) return { ok: true, value: result.value as T };\n const error = (result as { error?: { code?: unknown; details?: unknown } }).error;\n const code = typeof error?.code === 'string' ? error.code : 'internal';\n const details = error && typeof error.details === 'object' && error.details !== null ? (error.details as Record<string, unknown>) : {};\n return failure(code, details);\n}\n\n/** 取七个更新目标的表(宿主转交更新包的三个电话名与版本行,面板不写死任何电话名)。 */\nexport async function loadTargets(call: CallFace | null): Promise<CallOutcome<{ targets: TargetInfo[]; pollMs: number }>> {\n const out = await callManager<{ targets?: TargetInfo[]; pollMs?: number }>(call, MANAGER_ACTIONS.targets, {});\n if (!out.ok) return out;\n const targets = Array.isArray(out.value?.targets) ? out.value.targets : [];\n const pollMs = typeof out.value?.pollMs === 'number' && out.value.pollMs > 0 ? out.value.pollMs : FALLBACK_POLL_MS;\n return { ok: true, value: { targets, pollMs } };\n}\n\n/** 取总管自述版本(票 #737):宿主读**自己这份已安装包**的 `package.json`,面板只渲染读到的值。\n *\n * 单次、无轮询;宿主读不到会回 `unknown`,传输层失败也回同一个字面量——面板照原样显示,\n * 不假装知道版本(与卡路里 #130 的降级口径一致)。 */\nexport async function loadManagerVersion(call: CallFace | null): Promise<string> {\n const out = await callManager<{ version?: unknown }>(call, MANAGER_ACTIONS.version, {});\n if (!out.ok) return VERSION_UNKNOWN;\n const raw = out.value?.version;\n return typeof raw === 'string' && raw.trim().length > 0 ? raw.trim() : VERSION_UNKNOWN;\n}\n\n/** 查一家:默认查新版(`updateCheck`,顺带回最新版本与凭证);给 `phoneName` 时查那个电话。 */\nexport async function checkTarget(\n call: CallFace | null,\n target: TargetInfo,\n phoneName?: string,\n): Promise<CallOutcome<CheckOutcome>> {\n const name = phoneName ?? target.phones?.check;\n if (!target.phones || !name) return failure('bad-request', { target: target.key });\n return callManager<CheckOutcome>(call, name, {});\n}\n\n/** 装上缺席的包:版本取自查新版拿到的「最新版本」(宿主用更新包的执行器真装)。 */\nexport async function installAbsent(\n call: CallFace | null,\n target: TargetInfo,\n version: string,\n): Promise<CallOutcome<{ packageName: string; version: string }>> {\n return callManager(call, MANAGER_ACTIONS.install, { packageName: target.packageName, version });\n}\n\n/** 装上更新:查新版 → 提交安装 → 有界轮询查状态到任务收尾;凭证过期重查一次再提交。\n *\n * **第一句那次 `checkTarget` 不是多余的**(票 #740 第四轮):凭证是「这一刻的安装态」的凭据,\n * 更新包提交时拿它跟当时读到的安装态比(`service.js:309`/`:325`),不一样就拒。\n * 而面板**从来只提交这一刻现签的凭证**——这就是「刚在本面板装过别家,这一家照样点得动\n * 「装上更新」」的全部依据:旧快照只用来显示版本,不参与提交。(第四轮以前面板还把别家整行\n * 换成「重新检查」——那是白挡一次本来会成功的点击,已删。) */\nexport async function updateInstalled(\n call: CallFace | null,\n target: TargetInfo,\n pollMs: number,\n): Promise<CallOutcome<CheckOutcome>> {\n if (!target.phones) return failure('bad-request', { target: target.key });\n const first = await checkTarget(call, target);\n if (!first.ok) return first;\n let checked = first.value;\n if (!checked.receipt) {\n const code = checked.snapshot.blockedReason ?? 'check-failed';\n return failure(code, { target: target.key }, checked.manual);\n }\n requestSeq += 1;\n const requestId = 'ilife-manager-' + Date.now().toString(36) + '-' + String(requestSeq);\n let submitted = await callManager<CheckOutcome>(call, target.phones.install, {\n checkId: checked.receipt.checkId,\n requestId,\n });\n if (!submitted.ok && submitted.code === 'check-expired') {\n const again = await checkTarget(call, target);\n if (!again.ok) return again;\n checked = again.value;\n if (!checked.receipt) {\n return failure(checked.snapshot.blockedReason ?? 'check-failed', { target: target.key }, checked.manual);\n }\n submitted = await callManager<CheckOutcome>(call, target.phones.install, {\n checkId: checked.receipt.checkId,\n requestId: requestId + 'r',\n });\n }\n if (!submitted.ok) return { ...submitted, manual: submitted.manual ?? checked.manual };\n // 安装是后台跑(`service.ts:434-436`:落盘后后台跑,调用方不干等),故按间隔轮询到任务收尾。\n let latest = submitted.value;\n const interval = pollMs > 0 ? pollMs : FALLBACK_POLL_MS;\n for (let tries = 0; ; tries += 1) {\n const job = latest.snapshot.job;\n if (job === null || (job.state !== 'installing' && job.state !== 'verifying')) break;\n if (tries >= MAX_POLL_TRIES) break;\n await sleep(interval);\n const status = await checkTarget(call, target, target.phones.status);\n if (!status.ok) return status;\n latest = status.value;\n }\n return { ok: true, value: latest };\n}\n","/** 面板的视图逻辑(纯函数:无 DOM、无 node、无 react),客户端与单测共用同一份。\n *\n * 判定「有没有新版」不自己算版本号大小:更新核心已经在快照里给了结论\n * (`canInstall` 为真即有新版可装,`blockedReason` 非空即为什么装不了,见更新包 README 第 8 节),\n * 本文件只把结论翻成三种状态与一句话,不另起一套版本比较。\n */\nimport type { CheckReceipt, BlockedReason, UpdateSnapshot } from 'dsh-plugin-update';\nimport { dualInstallCmd } from './nav.js';\nimport { reasonText } from './update-contract.js';\n\n/** 一个更新目标的面板数据(宿主 `ilife-manager.targets` 的每行)。 */\nexport interface TargetInfo {\n readonly key: string;\n readonly title: string;\n readonly packageName: string;\n readonly phones: { readonly status: string; readonly check: string; readonly install: string } | null;\n /** 宿主启动那一刻磁盘上的版本;null = 启动时这个包还没装。 */\n readonly runningVersion: string | null;\n readonly installedVersion: string | null;\n /** 已装产物里有没有爱生活页签槽的注册代码;**宿主没给这一行时为 undefined**(老宿主)。\n * 见 `slotStateOf`:判据缺席不许猜,退回「装了但我们不知道接没接上」那一态。 */\n readonly panelRegistered?: boolean;\n /** 技能包随插件的那条精确 pin(插件包 manifest 里的 `skill-*` 依赖)。 */\n readonly skill: { readonly packageName: string; readonly version: string } | null;\n}\n\n/** 更新电话查到的结果(`updateCheck` / `updateStatus` 回包体)。 */\nexport interface CheckOutcome {\n readonly snapshot: UpdateSnapshot;\n readonly manual: string | null;\n readonly receipt: CheckReceipt | null;\n}\n\n/** 缺席:宿主启动时没装、现在磁盘上也没有(`runningVersion`/`installedVersion` 都是 null)。 */\nexport function isAbsent(target: TargetInfo): boolean {\n return target.runningVersion === null && target.installedVersion === null;\n}\n\n/** 缺席卡要说清的四态(前两态给动作,后两态**不给动作**——重装与重启都不会改变那两个状态)。\n *\n * 判据是**两处独立事实**,不许互相顶替:宿主的装机读数(磁盘上有没有、产物里有没有注册代码),\n * 与浏览器侧的页签槽账本(这一家的设置页此刻在不在浏览器里)。前者答「装没装、重装有没有用」,\n * 后者答「接没接上」。用账本代理「装没装」会说假话:真机实测四家已装却显示「未安装」(0.2.5 已修)。\n */\nexport type SlotState = 'connected' | 'absent' | 'unregistered-product' | 'not-connected';\n\n/** 一家页签此刻的状态:账本里已注册 → `connected`(面板显示它自己的设置页),否则按宿主装机读数分三态。 */\nexport function slotStateOf(target: TargetInfo | null, inLedger: boolean): SlotState {\n if (inLedger) return 'connected';\n if (target === null || isAbsent(target)) return 'absent';\n if (target.panelRegistered === false) return 'unregistered-product';\n return 'not-connected';\n}\n\nexport type VerdictKind = 'unknown' | 'up-to-date' | 'update-available' | 'blocked';\n\n/** 这一行给的按钮(票 #740):装上/装上更新/重试安装/重新检查。\n * 前两个是「做新的事」,后两个是**把卡住的状态推回可做**——拦截态不许没有出路。 */\nexport type VerdictAction = 'install' | 'update' | 'retry' | 'recheck';\n\nexport interface Verdict {\n readonly kind: VerdictKind;\n /** 一句话结论(面板那行正文):事实 + 后果 + 动作,三段齐全。 */\n readonly text: string;\n readonly action: VerdictAction | null;\n /** 这句话是否指着「下面这条命令」:指着就必须真把命令块摊出来(票 #740 第三轮)。\n * 没有这一格时出现过失配:`recovery-required` 那句写着「就用下面这条命令重装」,\n * 而命令块只在「拦截态且没按钮」时才画,那句下面什么都没有。 */\n readonly manualHint?: boolean;\n}\n\n/** 这一家是不是「装好了、只差重启」——判据是**事实**(磁盘版本 ≠ 正在跑的版本),不是原因码。\n *\n * 为什么不许按原因码判(票 #740 真机):同一种事实在更新核心里可能报成两种码——\n * `pending-restart`,或者被上一次中断的任务盖成 `recovery-required`。按码判会把「该重启」\n * 漏成一句「装不上」,用户看到的正是那种读不懂的屏。 */\nexport function restartPendingOf(snapshot: UpdateSnapshot): boolean {\n const installed = snapshot.installedVersion;\n return installed !== null && installed !== snapshot.runningVersion;\n}\n\n/** 待重启那一句(三段齐全:事实 → 后果 → 动作)。\n *\n * 措辞不写「新版」:磁盘上那份也可能比正在跑的**低**(手工降级),「新版」就不实了;\n * 只照事实说「磁盘上装的是 X、正在运行的是 Y」。\n * 动作只给「重启 DSH」:这一态下面板**做不了别的**——更新包的可装判据要求最新版严格高于\n * **正在运行**的版本(`service.js:219`),而凭证只发在可装态(`service.js:256`:`receipt` 只在\n * `canInstall` 时给),所以这一态里「重试安装」点了也是白点。 */\nexport function restartLine(snapshot: UpdateSnapshot): string {\n const installed = snapshot.installedVersion ?? snapshot.job?.targetVersion ?? '新版本';\n return '磁盘上装的是 ' + String(installed) + ';正在运行的是 ' + snapshot.runningVersion + '。重启 DSH(退出后重新打开)后生效。';\n}\n\n/** 「条件在别处(目录/清单/别处装的),改完回来重读一次」的原因码:面板能给的下一步就是「重新检查」。\n *\n * 为什么要逐码列(票 #740 第三轮的朗读表):`unknown-profile`/`registry-conflict`/`invalid-installation`/\n * `source-install` 这四态原先一个动作都不给,屏上那句却写着「再点「检查更新」」——那颗按钮住在面板题头,\n * 结果浮层一开(点「检查更新」就开)就被浮层盖住,够不着。跟两张「还没查过」的卡是同一类病。 */\nconst RECHECK_REASONS: ReadonlySet<string> = new Set(['installation-changed', 'unknown-profile', 'registry-conflict', 'invalid-installation', 'source-install']);\n\n/** 「这一步面板代劳不了、得照下面那条命令做」的原因码:这些话下面必须真有命令块。 */\nconst NEEDS_MANUAL: ReadonlySet<string> = new Set(['source-install', 'invalid-installation', 'recovery-required']);\n\n/** 拦截态各自给得出的下一步:面板能代劳的给按钮,代劳不了的给 null(那种走手工命令块)。 */\nfunction actionForBlocked(reason: BlockedReason): VerdictAction | null {\n if (RECHECK_REASONS.has(reason)) return 'recheck';\n if (reason === 'recovery-required') return 'retry';\n // `incompatible-node`(要升级 Node 并重启 DSH)与 `pending-restart`(要重启,且已被上面那条事实判据截走)\n // 这两态面板一步也做不了,按定义给 null;它们那句里点名的「检查更新」发生在重启之后,那时浮层早已关掉。\n return null;\n}\n\n/** 这一行此刻该画哪颗按钮:结论自带的动作优先,**失败的行一律给「重新检查」**(票 #740 第三轮)。\n *\n * 失败的行(查不到最新版/检查过期/装失败)没有快照、也就没有结论,原先一颗按钮都不画:\n * 屏上写着「再点「检查更新」」,那颗按钮却在浮层底下 ⇒ 无路可走。重新查一次对每一种失败都成立。 */\nexport function cardActionOf(verdict: Verdict | null, phase: 'idle' | 'checking' | 'ready' | 'installing' | 'failed'): VerdictAction | null {\n if (verdict?.action) return verdict.action;\n return phase === 'failed' ? 'recheck' : null;\n}\n\n/** 这一行要不要摊出那条手工命令(票 #740 第三轮定的形状,第四轮补的这一格)。\n *\n * 三种情形摊:① 上一次装失败(`failed`);② 这句话自己指着「下面这条命令」(`manualHint`)。\n * **待重启**与 `incompatible-node` 不摊:它们那时该做的是**重启**(或升级 Node),不是重装一次。\n * (第四轮维护者真机截图逮到:待重启那张卡下面挂着一条「dsh plugin … add」——点它等于把刚装好的\n * 那一版再装一遍,纯噪声,还会让人以为状态不对。) */\nexport function showManualOf(verdict: Verdict | null, phase: 'idle' | 'checking' | 'ready' | 'installing' | 'failed'): boolean {\n return phase === 'failed' || verdict?.manualHint === true;\n}\n\n/** 把快照翻成一行结论:还没查 / 已是最新 / 有新版 / 待重启 / 装不了(附人话原因与下一步)。 */\nexport function verdictOf(target: TargetInfo, snapshot: UpdateSnapshot): Verdict {\n const absent = isAbsent(target);\n const latest = snapshot.latestVersion;\n if (absent && latest === null) {\n // 动作给「重新检查」而不是让人去点面板顶上那颗「检查更新」:浮层开着的时候,顶上那颗被盖住了\n // (票 #740 对抗式审查第三轮的朗读表逮到)⇒ **每张卡自己的动作必须在这张卡上够得着**。\n return { kind: 'unknown', text: '未安装。点「重新检查」查最新版本,再点「装上」。', action: 'recheck' };\n }\n if (absent) {\n return { kind: 'update-available', text: '未安装;最新版本 ' + String(latest) + '。点「装上」安装。', action: 'install' };\n }\n // ① 装了没重启:先说这件事——它压过一切「装不上」(那时用户该做的只有重启)。\n if (restartPendingOf(snapshot)) {\n return { kind: 'blocked', text: restartLine(snapshot), action: null };\n }\n // ② 有东西可装、却被守卫或环境拦下:说清原因 + 给一个能执行的下一步。\n // 「没东西可装」的那些拦截不在这里说——那时守卫与用户无关,不该在屏上占一行(真机实测:\n // 已经是最新的一家挂着「安装清单变了」+一条重装命令,纯噪声)。\n const installed = snapshot.installedVersion;\n const hasNewer = latest !== null && latest !== installed;\n if (snapshot.blockedReason !== null && hasNewer) {\n const blocked = snapshot.blockedReason as BlockedReason;\n return { kind: 'blocked', text: reasonText(blocked), action: actionForBlocked(blocked), manualHint: NEEDS_MANUAL.has(blocked) };\n }\n if (latest === null) {\n return { kind: 'unknown', text: '还没查过最新版本。点「重新检查」。', action: 'recheck' };\n }\n if (snapshot.canInstall) {\n // 把「正在运行的是哪个版本」写进同一行:用户不必回头去版本行里找。\n return { kind: 'update-available', text: '有新版本 ' + String(latest) + '(正在运行 ' + snapshot.runningVersion + ')。点「装上更新」。', action: 'update' };\n }\n if (hasNewer) {\n // 有新版、却没有可用凭证(这一通的凭证过期,或压根没签发)⇒ **不许说「已是最新」**:\n // 那是假话(最新版就在官方源上、磁盘上还是旧的),正确答案是重新检查一次拿新凭证。\n return { kind: 'update-available', text: '有新版本 ' + String(latest) + '(正在运行 ' + snapshot.runningVersion + ')。点「重新检查」,再点「装上更新」。', action: 'recheck' };\n }\n return { kind: 'up-to-date', text: '已是最新版本 ' + String(latest) + '。', action: null };\n}\n\n/** 待重启横幅:**一行总账**,只点名哪几家要做「重启 DSH」这件事。\n *\n * 为什么不再逐家重复那句话(票 #740 对抗式审查第三轮的朗读表逮到):同一句「重启 DSH…后生效」\n * 会同时出现在横幅和那家卡片上,一屏两遍。分工定死:**横幅答「谁要重启」,卡片答「为什么、怎么做」**。 */\nexport function restartBannerText(titles: readonly string[]): string | null {\n if (titles.length === 0) return null;\n return '⚠️ 待重启 DSH(退出后重新打开):' + titles.join('、');\n}\n\n/** 面板展示的那条可复制命令(用户会照着敲的那一条)。\n *\n * 缺席包给人人都会用的**单命令双包**口径(#674 冻结的装入口径:不带版本号、不带任何开关);\n * 已装包给更新包回包里那条(`--save-exact ... --registry=` 是它 README 第 9 节的冻结合同,\n * 本仓不动它——要改只能提上游)。规范词见 docs/plugins/plugin-manager/t678-缺席即装与检查更新.md。 */\nexport function manualForDisplay(target: TargetInfo, manual: string | null): string | null {\n if (isAbsent(target)) return dualInstallCmd(target.packageName);\n return manual;\n}\n\n/** 版本行两行文字:插件包(本家) + 技能包(随插件,不单独查更新)。 */\nexport function versionLines(target: TargetInfo, snapshot: UpdateSnapshot | null): readonly string[] {\n const running = snapshot?.runningVersion ?? target.runningVersion ?? 'unknown';\n const installed = snapshot?.installedVersion ?? target.installedVersion;\n const lines = [target.packageName + ' ' + running + (installed && installed !== running ? '(磁盘已装 ' + installed + ')' : '')];\n if (target.skill) lines.push(target.skill.packageName + ' ' + target.skill.version + '(随插件,不单独查更新)');\n else lines.push('技能包随插件(不单独查更新)');\n return lines;\n}\n","/** 爱生活面板的更新界面:检查更新按钮、七家结果行、待重启横幅、缺席卡的「装上」。\n *\n * 面板侧纪律(沿 `client.ts` 既有口径):React.createElement 手写(禁 JSX)、内联 style、\n * 颜色走 DSH 主题别名(深浅主题自适应,写死值只做回退)、只用 `dsh-ctx` 镜像内的上下文成员、\n * 不 import 更新包(电话名与轮询间隔由宿主转交,见 `update-contract.ts` 的 `MANAGER_ACTIONS.targets`)。\n *\n * 视觉对照(用户给的第二张截图,参照实现 `dsh-mattpocock-skills-deck/src/client/views/SettingsPage.js:152-168`):\n * 标题右侧并排若干件——本文件出「检查更新」一件,隔壁票(爱生活面板头部与底部)在同一个\n * `actions` 容器里补星与气泡两件,故这里把容器单独留出来。\n */\nimport * as React from 'react';\nimport { checkTarget, installAbsent, loadTargets, updateInstalled } from './update-client.js';\nimport type { CallFace, CallFailure } from './update-client.js';\nimport { cardActionOf, isAbsent, manualForDisplay, restartBannerText, restartPendingOf, showManualOf, slotStateOf, verdictOf, versionLines } from './update-view.js';\nimport type { CheckOutcome, TargetInfo, Verdict, VerdictAction } from './update-view.js';\n\n/** 面板视觉(沿用总管既有语言:内联 style,主题别名带回退)。 */\nexport const PANEL_STYLE = {\n head: { fontSize: 15, fontWeight: 700, margin: '2px 0 8px', color: 'var(--dsw-alias-label-primary, inherit)' } as React.CSSProperties,\n headRow: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, flexWrap: 'wrap' } as React.CSSProperties,\n actions: { display: 'inline-flex', alignItems: 'center', gap: 6 } as React.CSSProperties,\n meta: { color: 'var(--dsw-alias-label-secondary, #9a9a9a)', fontSize: 12, lineHeight: 1.7, marginBottom: 10 } as React.CSSProperties,\n tablist: { display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: 10 } as React.CSSProperties,\n tab: {\n border: '1px solid var(--dsw-alias-border, rgba(128,128,128,.35))',\n background: 'transparent',\n color: 'var(--dsw-alias-label-primary, inherit)',\n borderRadius: 999,\n padding: '4px 12px',\n fontSize: 13,\n cursor: 'pointer',\n } as React.CSSProperties,\n tabActive: {\n background: 'var(--dsw-alias-brand-primary, #0a84ff)',\n borderColor: 'transparent',\n color: '#fff',\n fontWeight: 700,\n } as React.CSSProperties,\n reco: {\n padding: '12px 14px',\n borderRadius: 10,\n border: '1px dashed var(--dsw-alias-border, rgba(128,128,128,.45))',\n color: 'var(--dsw-alias-label-secondary, #9a9a9a)',\n fontSize: 13,\n lineHeight: 1.7,\n } as React.CSSProperties,\n cmd: {\n display: 'block',\n marginTop: 8,\n padding: '8px 10px',\n borderRadius: 8,\n background: 'var(--dsw-alias-bg-base, rgba(128,128,128,.12))',\n color: 'var(--dsw-alias-label-primary, inherit)',\n fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',\n fontSize: 12,\n userSelect: 'all',\n } as React.CSSProperties,\n btn: {\n border: '1px solid var(--dsw-alias-border, rgba(128,128,128,.35))',\n background: 'transparent',\n color: 'var(--dsw-alias-label-primary, inherit)',\n borderRadius: 8,\n padding: '4px 12px',\n fontSize: 13,\n cursor: 'pointer',\n } as React.CSSProperties,\n banner: {\n padding: '10px 12px',\n marginBottom: 10,\n borderRadius: 8,\n border: '1px solid var(--dsw-alias-state-warning-primary, #d8a300)',\n background: 'var(--dsw-alias-bg-base, rgba(216,163,0,.12))',\n color: 'var(--dsw-alias-label-primary, inherit)',\n fontSize: 13,\n lineHeight: 1.7,\n } as React.CSSProperties,\n row: {\n padding: '10px 12px',\n marginBottom: 8,\n borderRadius: 10,\n border: '1px solid var(--dsw-alias-border, rgba(128,128,128,.3))',\n borderLeftWidth: 3,\n fontSize: 13,\n lineHeight: 1.7,\n } as React.CSSProperties,\n rowHead: { display: 'flex', alignItems: 'baseline', gap: 6, marginBottom: 2 } as React.CSSProperties,\n rowTitle: { fontWeight: 700, color: 'var(--dsw-alias-label-primary, inherit)' } as React.CSSProperties,\n rowPkg: {\n color: 'var(--dsw-alias-label-tertiary, #8a8a8a)',\n fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',\n fontSize: 12,\n } as React.CSSProperties,\n rowMeta: { color: 'var(--dsw-alias-label-secondary, #9a9a9a)', fontSize: 12 } as React.CSSProperties,\n /** 版本号用等宽字:数字要能对齐着读(「当前」「最新」两列一眼比大小)。 */\n ver: {\n fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',\n color: 'var(--dsw-alias-label-primary, inherit)',\n fontWeight: 600,\n } as React.CSSProperties,\n /** 结论那一行:**只有要动的那几档上色**(绿/灰不抢注意力,照 health-view 的三档口径)。 */\n verdictLine: { fontSize: 12.5, marginTop: 2 } as React.CSSProperties,\n reason: { color: 'var(--dsw-alias-state-error-primary, #ff6b6b)', fontSize: 12, marginTop: 4 } as React.CSSProperties,\n skill: {\n color: 'var(--dsw-alias-label-tertiary, #8a8a8a)',\n fontSize: 12,\n marginTop: 4,\n fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',\n } as React.CSSProperties,\n /** 行内主按钮(装上/装上更新):品牌底色 —— 全屏只有「能点的」这一种是这个颜色。 */\n btnPrimary: {\n border: '1px solid transparent',\n background: 'var(--dsw-alias-brand-primary, #0a84ff)',\n color: '#fff',\n borderRadius: 8,\n padding: '5px 14px',\n fontSize: 13,\n fontWeight: 700,\n cursor: 'pointer',\n marginTop: 6,\n } as React.CSSProperties,\n /** 分档标记:**形状是第二条读数**(灰度截图、色弱视角下也分得开哪一行要动)。 */\n mark: { fontWeight: 700, fontSize: 13, lineHeight: 1 } as React.CSSProperties,\n overlay: {\n position: 'fixed',\n inset: 0,\n zIndex: 40,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n padding: 16,\n background: 'rgba(0,0,0,.45)',\n } as React.CSSProperties,\n /** 浮层是**竖排两段**:题头固定、结果列表自己滚(题头跟着滚会把关闭键滚出视野 —— 用户实测反馈)。 */\n dialog: {\n width: '100%',\n maxWidth: 560,\n maxHeight: '80vh',\n display: 'flex',\n flexDirection: 'column',\n overflow: 'hidden',\n borderRadius: 12,\n border: '1px solid var(--dsw-alias-border, rgba(128,128,128,.4))',\n background: 'var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #222))',\n color: 'var(--dsw-alias-label-primary, inherit)',\n boxShadow: '0 12px 32px rgba(0,0,0,.35)',\n } as React.CSSProperties,\n dialogHead: {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n gap: 8,\n flex: '0 0 auto',\n padding: '12px 14px',\n borderBottom: '1px solid var(--dsw-alias-border, rgba(128,128,128,.3))',\n } as React.CSSProperties,\n dialogTitle: { fontSize: 14, fontWeight: 700 } as React.CSSProperties,\n /** 唯一会滚的那一段。 */\n dialogBody: {\n flex: '1 1 auto',\n minHeight: 0,\n overflowY: 'auto',\n padding: '12px 14px',\n } as React.CSSProperties,\n close: {\n border: '1px solid var(--dsw-alias-border, rgba(128,128,128,.35))',\n background: 'transparent',\n color: 'var(--dsw-alias-label-primary, inherit)',\n borderRadius: 8,\n width: 26,\n height: 26,\n lineHeight: 1,\n fontSize: 14,\n cursor: 'pointer',\n } as React.CSSProperties,\n dialogNote: { color: 'var(--dsw-alias-label-tertiary, #8a8a8a)', fontSize: 12, marginTop: 4 } as React.CSSProperties,\n};\n\n/** 一行的分档:**要动的那几档才上色**(`ok`/`idle` 保持素色,不跟红黄抢注意力 —— 照 `health-view.ts` 的三档口径)。\n * 每档同时给**形状**(`↑ ! ✕ ✓`):颜色之外的第二条读数,灰度截图与色弱视角下同样分得开。 */\ntype RowTone = 'ok' | 'update' | 'blocked' | 'failed' | 'idle';\n\nconst ROW_TONE: Readonly<Record<RowTone, { readonly mark: string; readonly color: string; readonly tint: string }>> = {\n update: { mark: '↑', color: 'var(--dsw-alias-brand-primary, #0a84ff)', tint: 'rgba(10,132,255,.08)' },\n blocked: { mark: '!', color: 'var(--dsw-alias-state-warning-primary, #d8a300)', tint: 'rgba(216,163,0,.10)' },\n failed: { mark: '✕', color: 'var(--dsw-alias-state-error-primary, #ff6b6b)', tint: 'rgba(255,107,107,.10)' },\n ok: { mark: '✓', color: 'var(--dsw-alias-state-success-primary, #4ec9a0)', tint: 'transparent' },\n idle: { mark: '·', color: 'var(--dsw-alias-label-tertiary, #8a8a8a)', tint: 'transparent' },\n};\n\n/** 这一行算哪一档:取数失败 > 装不上 > 有新版 > 待重启/源码装 > 已是最新 > 未查。 */\nexport function rowToneOf(row: UpdateRowState, verdict: Verdict | null): RowTone {\n if (row.failure !== null || row.phase === 'failed') return 'failed';\n if (row.phase === 'installing') return 'update';\n if (verdict === null) return 'idle';\n switch (verdict.kind) {\n case 'update-available': return 'update';\n case 'blocked': return 'blocked';\n case 'up-to-date': return 'ok';\n default: return 'idle';\n }\n}\n\n/** 一行的运行时状态:还没查 / 查着 / 有结果 / 装着 / 出错。 */\nexport interface UpdateRowState {\n readonly phase: 'idle' | 'checking' | 'ready' | 'installing' | 'failed';\n readonly outcome: CheckOutcome | null;\n readonly failure: CallFailure | null;\n}\n\nexport interface UpdateRowsFace {\n readonly targets: readonly TargetInfo[];\n readonly rows: Readonly<Record<string, UpdateRowState>>;\n readonly pollMs: number;\n readonly loadError: string | null;\n readonly checking: boolean;\n /** 结果浮层是否打开(点「检查更新」开,关法三种:×、点遮罩、Esc)。 */\n readonly open: boolean;\n close(): void;\n checkAll(): void;\n /** 执行这一行按钮对应的动作(装上/装上更新/重试安装/重新检查)。 */\n act(target: TargetInfo, action: VerdictAction): void;\n}\n\n/** 四个动作在屏上的名字:按钮文案与判据同源(票 #740)。 */\nconst ACTION_LABEL: Readonly<Record<VerdictAction, string>> = {\n install: '装上',\n update: '装上更新',\n retry: '重试安装',\n recheck: '重新检查',\n};\n\nconst IDLE: UpdateRowState = { phase: 'idle', outcome: null, failure: null };\n\n/** 面板的更新状态源:挂载时取七个目标的表,之后按需查/装(组件与按钮共用一个 face)。 */\nexport function useUpdateRows(getCall: () => CallFace | null): UpdateRowsFace {\n const [targets, setTargets] = React.useState<readonly TargetInfo[]>([]);\n const [rows, setRows] = React.useState<Record<string, UpdateRowState>>({});\n const [pollMs, setPollMs] = React.useState(1000);\n const [loadError, setLoadError] = React.useState<string | null>(null);\n const [checking, setChecking] = React.useState(false);\n const [open, setOpen] = React.useState(false);\n const alive = React.useRef(true);\n React.useEffect(\n () => () => {\n alive.current = false;\n },\n [],\n );\n /** 重取七个目标的表(装机读数变了就重取:缺席卡的态是拿这张表算的,缓存住它会继续说旧话)。 */\n const reload = React.useCallback(async () => {\n const loaded = await loadTargets(getCall());\n if (!alive.current) return;\n if (loaded.ok) {\n setTargets(loaded.value.targets);\n setPollMs(loaded.value.pollMs);\n setLoadError(null);\n } else {\n setLoadError(loaded.message);\n }\n }, [getCall]);\n React.useEffect(() => {\n void reload();\n }, [reload]);\n const patch = React.useCallback((key: string, next: UpdateRowState) => {\n setRows((previous) => ({ ...previous, [key]: next }));\n }, []);\n const checkAll = React.useCallback(() => {\n setChecking(true);\n setOpen(true);\n const list = targets;\n for (const target of list) patch(target.key, { phase: 'checking', outcome: rows[target.key]?.outcome ?? null, failure: null });\n void Promise.all(\n list.map(async (target) => {\n const result = await checkTarget(getCall(), target);\n patch(target.key, result.ok ? { phase: 'ready', outcome: result.value, failure: null } : { phase: 'failed', outcome: null, failure: result });\n }),\n ).finally(() => setChecking(false));\n }, [getCall, patch, rows, targets]);\n const act = React.useCallback(\n (target: TargetInfo, action: VerdictAction) => {\n const current = rows[target.key] ?? IDLE;\n // 「重新检查」:只重读这一家(重新绑一次安装态指纹)。\n if (action === 'recheck') {\n patch(target.key, { phase: 'checking', outcome: current.outcome, failure: null });\n void (async () => {\n const checked = await checkTarget(getCall(), target);\n patch(target.key, checked.ok\n ? { phase: 'ready', outcome: checked.value, failure: null }\n : { phase: 'failed', outcome: null, failure: checked });\n })();\n return;\n }\n patch(target.key, { ...current, phase: 'installing', failure: null });\n void (async () => {\n const absent = isAbsent(target);\n let version = current.outcome?.snapshot.latestVersion ?? null;\n if (absent && version === null) {\n const checked = await checkTarget(getCall(), target);\n if (!checked.ok) {\n patch(target.key, { phase: 'failed', outcome: null, failure: checked });\n return;\n }\n patch(target.key, { phase: 'installing', outcome: checked.value, failure: null });\n version = checked.value.snapshot.latestVersion;\n }\n // 已装的那条路,`updateInstalled` **自己会先查一次**再提交(凭证是那一查现签的)——\n // 这就是「装了别家之后,这一家的「装上更新」照样点得动」的原因:面板从不拿旧凭证去提交。\n const result = absent\n ? await installAbsent(getCall(), target, version ?? '')\n : await updateInstalled(getCall(), target, pollMs);\n if (result.ok) {\n // 装完不自己编快照:向宿主问一次真实状态(装到磁盘但宿主还跑着旧版 ⇒ 待重启)。\n const refreshed = await checkTarget(getCall(), target, target.phones?.status);\n patch(target.key, refreshed.ok ? { phase: 'ready', outcome: refreshed.value, failure: null } : { phase: 'failed', outcome: null, failure: refreshed });\n // 装机读数变了(磁盘上多/换了一个包):重取目标表,缺席卡的态跟着变,\n // 否则它会拿着挂载时那份读数继续说「未安装」,直到用户刷新页面。\n await reload();\n return;\n }\n patch(target.key, {\n phase: 'failed',\n outcome: current.outcome,\n failure: { ...result, manual: result.manual ?? current.outcome?.manual ?? null },\n });\n })();\n },\n [getCall, patch, pollMs, reload, rows],\n );\n return { targets, rows, pollMs, loadError, checking, open, close: () => setOpen(false), checkAll, act };\n}\n\n/** 标题右侧的「检查更新」:一个按钮一次查七家(吃 `useUpdateRows` 的表)。 */\nexport function CheckUpdateButton(props: { readonly face: UpdateRowsFace }): React.ReactElement {\n const { checking, checkAll, targets } = props.face;\n const disabled = checking || targets.length === 0;\n return React.createElement(\n 'button',\n {\n type: 'button',\n style: disabled ? { ...PANEL_STYLE.btn, opacity: 0.55, cursor: 'default' } : PANEL_STYLE.btn,\n disabled,\n onClick: checkAll,\n title: '检查总管与六家插件的版本',\n },\n checking ? '检查中…' : '检查更新',\n );\n}\n\n/** 待重启横幅:一行总账,只点名哪几家要重启(理由与做法写在各家卡片上,见 `restartBannerText`)。 */\nfunction RestartBanners(props: { readonly face: UpdateRowsFace }): React.ReactElement | null {\n const titles: string[] = [];\n for (const target of props.face.targets) {\n const outcome = props.face.rows[target.key]?.outcome;\n if (!outcome) continue;\n if (restartPendingOf(outcome.snapshot)) titles.push(target.title);\n }\n const text = restartBannerText(titles);\n if (text === null) return null;\n return React.createElement('div', { style: PANEL_STYLE.banner }, text);\n}\n\n/** 七家结果:装不了的与待重启的常驻(横幅),详细结果放**居中浮层**(点「检查更新」开)。\n *\n * 为什么是浮层:内联展开会把面板本体撑长、且它自身没有关闭控件(真机第一次做成内联后用户反馈\n * 「打开就无法收回了」)。参照实现也是浮层:`dsh-mattpocock-skills-deck/src/client/views/SettingsPage.js`\n * 的 `upd.dialog`。关法三种:右上角 ×、点遮罩、Esc(Esc 靠浮层自己拿焦点后收 keydown,\n * 不摸 window——客户端纪律禁直写 DOM 全局)。\n * 手工命令只在「装不了」或「装失败」的行显示:正常用户用不到,摊在每行上既吵又误导。\n */\nexport function UpdateResults(props: { readonly face: UpdateRowsFace }): React.ReactElement | null {\n const { targets, rows, loadError, checking, open, close } = props.face;\n const dialogRef = React.useRef<HTMLDivElement | null>(null);\n React.useEffect(() => {\n if (open) dialogRef.current?.focus();\n }, [open]);\n const shown = targets.filter((target) => rows[target.key] && rows[target.key].phase !== 'idle');\n if (loadError) return React.createElement('div', { style: PANEL_STYLE.reason }, '更新能力没接上:' + loadError);\n if (targets.length === 0) return null;\n if (shown.length === 0 && !open) return null;\n const body = shown.map((target) => {\n const row = rows[target.key];\n const snapshot = row.outcome?.snapshot ?? null;\n // 快照在就按它画卡。(第四轮改:装成一家之后**不再**把别家换成「重新检查」——\n // 装的那条路本来就会在提交前重查一次拿新凭证,旧快照从不到达守卫,那句作废提示只是白挡一次点击。)\n const verdict = snapshot ? verdictOf(target, snapshot) : null;\n const manual = manualForDisplay(target, row.failure?.manual ?? row.outcome?.manual ?? null);\n const busy = row.phase === 'installing';\n // 手工命令只在**面板代劳不了**时才摊出来(判据收在 `showManualOf` 里,与单测同一份)。\n const showManual = manual !== null && showManualOf(verdict, row.phase);\n // 失败的行没有快照、也就没有结论,但照样要给一颗够得着的按钮(票 #740 第三轮,见 `cardActionOf`)。\n const buttonAction = cardActionOf(verdict, row.phase);\n const tone = rowToneOf(row, verdict);\n const paint = ROW_TONE[tone];\n /** 这一行「当前/最新」两个版本号:不等才算有新版,把「最新」那格上色(用户扫一眼先看有没有箭头)。 */\n const running = snapshot?.runningVersion ?? target.runningVersion ?? null;\n const latest = snapshot?.latestVersion ?? null;\n const newer = running !== null && latest !== null && latest !== running;\n const lit = tone === 'update' || tone === 'blocked' || tone === 'failed';\n return React.createElement(\n 'div',\n { key: target.key, style: { ...PANEL_STYLE.row, borderLeftColor: paint.color, background: paint.tint } },\n React.createElement(\n 'div',\n { style: PANEL_STYLE.rowHead },\n React.createElement('span', { style: { ...PANEL_STYLE.mark, color: paint.color }, 'aria-hidden': 'true' }, paint.mark),\n React.createElement('span', { style: PANEL_STYLE.rowTitle }, target.title),\n React.createElement('span', { style: PANEL_STYLE.rowPkg }, '· ' + target.packageName),\n ),\n React.createElement(\n 'div',\n { style: PANEL_STYLE.rowMeta },\n '当前 ',\n React.createElement('b', { style: PANEL_STYLE.ver }, String(running ?? '未安装')),\n ' · 最新 ',\n React.createElement(\n 'b',\n { style: newer ? { ...PANEL_STYLE.ver, color: paint.color } : PANEL_STYLE.ver },\n String(latest ?? '未查'),\n ),\n ),\n React.createElement(\n 'div',\n { style: lit ? { ...PANEL_STYLE.verdictLine, color: paint.color, fontWeight: 600 } : { ...PANEL_STYLE.verdictLine, color: 'var(--dsw-alias-label-secondary, #9a9a9a)' } },\n busy ? '正在装,请稍候…' : (verdict?.text ?? (checking ? '检查中…' : '')),\n ),\n buttonAction\n ? React.createElement(\n 'button',\n {\n type: 'button',\n style: busy ? { ...PANEL_STYLE.btnPrimary, opacity: 0.55, cursor: 'default' } : PANEL_STYLE.btnPrimary,\n disabled: busy,\n onClick: () => {\n props.face.act(target, buttonAction);\n },\n },\n ACTION_LABEL[buttonAction],\n )\n : null,\n row.failure ? React.createElement('div', { style: PANEL_STYLE.reason }, '装不上:' + row.failure.message) : null,\n versionLines(target, snapshot).map((line, index) =>\n React.createElement('div', { key: String(index), style: PANEL_STYLE.skill }, line),\n ),\n // 命令块先说它是干什么的(票 #740):给一条命令不给用途,用户不知道该不该敲。\n showManual ? React.createElement('div', { style: PANEL_STYLE.skill }, '在终端里执行这条命令可以手动完成这一步:') : null,\n showManual ? React.createElement('code', { style: PANEL_STYLE.cmd }, manual) : null,\n );\n });\n return React.createElement(\n 'div',\n null,\n React.createElement(RestartBanners, { face: props.face }),\n open\n ? React.createElement(\n 'div',\n {\n style: PANEL_STYLE.overlay,\n role: 'presentation',\n onClick: () => close(),\n },\n React.createElement(\n 'div',\n {\n ref: dialogRef,\n tabIndex: -1,\n role: 'dialog',\n 'aria-label': '检查更新结果',\n style: PANEL_STYLE.dialog,\n onClick: (event: React.MouseEvent) => event.stopPropagation(),\n onKeyDown: (event: React.KeyboardEvent) => {\n if (event.key === 'Escape') close();\n },\n },\n React.createElement(\n 'div',\n { style: PANEL_STYLE.dialogHead },\n // 标题只说「检查更新」:查的是几个包由每行自己交代,不必在标题里数数。\n React.createElement('div', { style: PANEL_STYLE.dialogTitle }, '检查更新'),\n React.createElement(\n 'button',\n { type: 'button', style: PANEL_STYLE.close, onClick: () => close(), title: '关闭', 'aria-label': '关闭' },\n '×',\n ),\n ),\n React.createElement(\n 'div',\n { style: PANEL_STYLE.dialogBody },\n body,\n React.createElement(\n 'div',\n { style: PANEL_STYLE.dialogNote },\n '安装在宿主后台继续:关掉这块不会中断它。再点「检查更新」看最新进度。',\n ),\n ),\n ),\n )\n : null,\n );\n}\n\n/** 缺席卡:**按两处独立事实分三态**,每态只给能改变状态的动作。\n *\n * - `absent`(磁盘上没有这个包)→「未安装」+「装上」按钮(本票原有的能力);\n * - `unregistered-product`(装着,但产物里没有页签槽注册代码)→ 说实情、**不给按钮**:\n * 重装改不了产物里有没有注册代码(真机实测:作息/居家/大厨/记账 的已发布 0.2.0\n * 产物里根本没有注册代码,见票 #723);给可复制命令只是参考;\n * - `not-connected`(装着、产物也有注册代码,设置页却没送到浏览器)→ 重启宿主;仍不行看日志;\n * - `connected` 不归本卡管:那时面板显示的是那家自己的设置页。\n *\n * 原实现只看页签槽 ledger 就断言「未安装」——那对后两种情形是说假话,且点「装上」\n * 会走进更新包的流程(它已经装着 ⇒ 没有新版 ⇒ 没有凭证)并报出误导的「查新版失败」。\n */\nexport function AbsentCard(props: {\n readonly target: TargetInfo | null;\n readonly fallbackCommand: string;\n readonly inLedger: boolean;\n readonly face: UpdateRowsFace;\n}): React.ReactElement {\n const target = props.target;\n const row = target ? props.face.rows[target.key] : null;\n const busy = row?.phase === 'installing';\n const manual = (target ? manualForDisplay(target, row?.failure?.manual ?? row?.outcome?.manual ?? null) : null) ?? props.fallbackCommand;\n const installed = target ? (target.installedVersion ?? target.runningVersion) : null;\n const state = slotStateOf(target, props.inLedger);\n const blockedVerdict = target && row?.outcome && row.outcome.snapshot.blockedReason !== null ? verdictOf(target, row.outcome.snapshot) : null;\n let reason: string | null = row?.failure?.message ?? null;\n if (reason === null && blockedVerdict) reason = blockedVerdict.text;\n // 这张卡也要有一颗够得着的按钮(票 #740 第三轮):上一次失败之后,屏上那句写着「再点「重新检查」」,\n // 这张卡原先一颗都不给(absent 态只给「装上」)。判据与结果行同一份(`cardActionOf`)。\n const retryAction = row ? cardActionOf(blockedVerdict, row.phase) : null;\n let headline: string;\n switch (state) {\n case 'absent':\n headline = target\n ? '未安装[' + target.packageName + '],装上后这个页签就能用了。'\n : '这个页签对应的插件还没装。';\n break;\n case 'unregistered-product':\n headline =\n target!.packageName +\n ' ' +\n String(installed) +\n ' 已安装,但这一版的插件包里没有「爱生活页签」的注册代码:要等它发布带注册代码的新版本。重启和重装都不会改变这一点。';\n break;\n default:\n headline =\n target!.packageName +\n ' ' +\n String(installed) +\n ' 已安装,插件包里也有注册代码,但它的设置页没有加载到面板:重启 DSH 后再看这个页签;仍然这样,就在 DSH 日志里找它的报错。';\n break;\n }\n return React.createElement(\n 'div',\n { style: PANEL_STYLE.reco },\n React.createElement('div', null, headline),\n state === 'absent'\n ? React.createElement(\n 'button',\n {\n type: 'button',\n style: busy ? { ...PANEL_STYLE.btn, marginTop: 8, opacity: 0.55 } : { ...PANEL_STYLE.btn, marginTop: 8 },\n disabled: busy,\n onClick: () => {\n if (target) props.face.act(target, 'install');\n },\n },\n busy ? '正在装,请稍候…' : '装上',\n )\n : null,\n // 「重新检查」只在上面那句真写了它、且这张卡上还没有同一颗按钮时补出来(absent 态的「装上」不动)。\n retryAction === 'recheck' && target\n ? React.createElement(\n 'button',\n {\n type: 'button',\n style: busy ? { ...PANEL_STYLE.btn, marginTop: 8, marginLeft: 8, opacity: 0.55 } : { ...PANEL_STYLE.btn, marginTop: 8, marginLeft: 8 },\n disabled: busy,\n onClick: () => props.face.act(target, 'recheck'),\n },\n ACTION_LABEL.recheck,\n )\n : null,\n reason ? React.createElement('div', { style: PANEL_STYLE.reason }, reason) : null,\n React.createElement('code', { style: PANEL_STYLE.cmd }, manual),\n );\n}\n","/** 配置体检:各家技能回给面板的那份报告的形状(唯一真相在技能侧,本件只是面板侧镜像)。\n *\n * 为什么形状这样定(票 #706「开工前的形状裁定」第一条):**判据住技能侧**。\n * 每条检查项的有效值只有技能源码算得出来(例:`resolveDbDir()` 里「空串=按默认落点」),\n * 插件包对技能的全部能力是 spawn `cmd_read` 与读 stdout 信封,看不到那些算式。所以:\n *\n * 技能侧出**判据**(每条给红/黄/绿 + 一句话 + 去哪修);\n * 插件包出**透传**(把技能回执原样交回,不重写一个字);\n * 总管出**总览与渲染**(零特例:不认任何一家,只认下面这个形状)。\n *\n * 于是「同一份数据、不许两处各算一遍」是结构上的性质:面板一次取数拿到六份报告,\n * 总览一行与各家那张表都从同一个快照读(见 `health-panel.ts` 的单一状态源)。\n *\n * 三档判据的语义(照检查表 `docs/research/check-table-671-life-panel-20260917.html`):\n * · `red`=现在用不了,必须去修;\n * · `yellow`=现在这状态能用,但有人真去用那个功能时会撞上(例:照片目录没配);\n * · `green`=正常。\n */\n\n/** 三档判据。 */\nexport type HealthStatus = 'red' | 'yellow' | 'green';\n\n/** 一条检查项的回执。 */\nexport interface HealthItem {\n /** 稳定标识:配置项就是它在配置文件里的键路径(`db.dir`),非配置项用点式名(`xunji.cli`)。 */\n readonly id: string;\n /** 页面上那一行的中文标题(技能侧定,面板不重写)。 */\n readonly title: string;\n readonly status: HealthStatus;\n /** 一句话:现在是什么状态,带上路径或门槛这类实证。 */\n readonly message: string;\n /** 去哪修。`green` 时可为空。 */\n readonly action: string;\n /** 这个值从哪来(只陈述,不判好坏):`配置文件`/`默认值`/`环境`。非取值类检查项可省。 */\n readonly source?: string;\n}\n\n/** 一家的体检报告。 */\nexport interface HealthReport {\n /** 技能名(`calorie`/`bill`/…),等于该家配置文件的主体名。 */\n readonly skill: string;\n /** 实际生效的那份配置文件路径。 */\n readonly configPath: string;\n /** 实际生效的数据目录。 */\n readonly dataDir: string;\n readonly items: readonly HealthItem[];\n}\n\n/** 体检出口:技能侧在**进分派层之前**拦下的那一类只读命令名(照三个配置命令的先例)。\n *\n * 载荷:入参 `{}`;回包 `{report}`(技能侧 envelope 的 `data`)。**只报不改**:\n * 不建目录、不写文件、不落默认配置——凡是要建目录才算得出的结论,一律当「不在」报。 */\nexport const HEALTH_CHECK_KEY = 'config.check' as const;\n\n/** 面板第二段参数(与各家的端点名同值,和各家的 `RPC_CHANNEL` 配成一次调用)。 */\nexport const HEALTH_ENDPOINT = 'config.check' as const;\n\n/** 三档排序:数字大的更严重(总览一行取**最严重**那档当那一家的灯)。 */\nconst RANK: Readonly<Record<HealthStatus, number>> = { green: 0, yellow: 1, red: 2 };\n\n/** 一家的总判据:全部项里最严重的那一档;一条都没有时回 `null`(不冒充绿)。 */\nexport function worstStatus(items: readonly HealthItem[]): HealthStatus | null {\n let worst: HealthStatus | null = null;\n for (const item of items) {\n if (worst === null || RANK[item.status] > RANK[worst]) worst = item.status;\n }\n return worst;\n}\n\n/** 三档计数(面板那一行「红 N 黄 N」用的就是它)。 */\nexport function countByStatus(items: readonly HealthItem[]): Readonly<Record<HealthStatus, number>> {\n const out = { red: 0, yellow: 0, green: 0 };\n for (const item of items) out[item.status] += 1;\n return out;\n}\n\n/** 回执校验:面板只渲染自己认得的形状,认不出即报「回执异常」,不猜、不返空冒充正常。 */\nexport function isHealthReport(raw: unknown): raw is HealthReport {\n if (typeof raw !== 'object' || raw === null) return false;\n const report = raw as Partial<HealthReport>;\n if (typeof report.skill !== 'string' || report.skill.length === 0) return false;\n if (typeof report.configPath !== 'string' || typeof report.dataDir !== 'string') return false;\n if (!Array.isArray(report.items)) return false;\n return report.items.every((item: unknown) => {\n if (typeof item !== 'object' || item === null) return false;\n const row = item as Partial<HealthItem>;\n return typeof row.id === 'string' && typeof row.title === 'string'\n && (row.status === 'red' || row.status === 'yellow' || row.status === 'green')\n && typeof row.message === 'string' && typeof row.action === 'string';\n });\n}\n","/** 面板到各家的配置体检:一次取数、六份报告(纯异步函数:无 DOM、无 react;传输口注入,便于单测)。\n *\n * 三条纪律:\n * · **只走各家自己的电话**(`rpc.call(CARRIER_BASE, <电话名>, {method, payload}, …)`)——总管不认任何\n * 一家的端点、也不加任何单品的依赖;电话名与端点名都是各家契约件公开的事实。\n * ⚠ **载体形状**(#735 真机踩到):第一段必须是**载体基段** `/api`,第二段是那家的电话名\n * (=它注册在 `/api<通道>` 上的那一段),端点名写进载荷的 `method`。写错第一段就是 404,\n * 而 404 只落成「那一家取不到数」,屏上看起来像按钮坏了(先例:总管自己的电话在 #678 栽过同一处,\n * 见 `update-contract.ts` 的 `CARRIER_BASE`)。\n * · **一次取数,六份报告**:总览那行与各家那张表读的是同一份结果(见 `health-panel.ts` 的单一状态源),\n * 不许两处各算一遍。\n * · 有界超时:一家不回不该拖死整屏(技能侧 spawn 上限 20 s,这里给 25 s)。\n */\nimport { HEALTH_ENDPOINT, isHealthReport } from './health-contract.js';\nimport type { HealthReport } from './health-contract.js';\nimport { CARRIER_BASE } from './update-contract.js';\nimport type { CallFace } from './update-client.js';\n\n/** 传输口:与更新面**同一张脸**(`connection.rpc.call` 的四参形状,cookbook §6)——一张脸一处定义。 */\nexport type HealthCallFace = CallFace;\n\n/** 一家的电话名:通道名去掉前导斜杠那一段(各家宿主半注册在 `/api` + 通道名,信封 `method` 就是它)。 */\nexport function phoneOf(channel: string): string {\n return channel.startsWith('/') ? channel.slice(1) : channel;\n}\n\n/** 整批取数的时限(毫秒),**不是**一家一份:六家的活是在**同一个宿主线程**上排队干的。\n *\n * 为什么不是「技能侧 spawn 上限 20 s + 一点余量」(票 #740 对抗式审查第二轮逮到):\n * 各家的宿主半用的是 `spawnSync`(各单品的 `src/bridge.ts` 里 `SPAWN_TIMEOUT_MS = 20_000`),\n * 它**阻塞**宿主的事件循环——六通电话同时打进来,真正干活的是一个接一个串起来的。\n * 于是「六家之和」才是这一批的真实上界:按一家 20 s 算最坏 120 s,常见情形(一家 1~3 s)约 10 s。\n * 60 s 取两者之间:给常见情形留足余量,又不至于让一次卡死把按钮按成两分钟不动。\n * 单家的错照样逐家报(那家页签里的体检表会写「体检超时(60s)…」)。 */\nexport const HEALTH_TIMEOUT_MS = 60_000 as const;\n\n/** 一家的一行:报告取到了就是 `report`,取不到就是 `error`(缺席的包不进这张表)。 */\nexport interface HealthFetchRow {\n /** 该家插件包名(=爱生活页签槽 id,也是缺席判定的那个 join 键)。 */\n readonly id: string;\n readonly report: HealthReport | null;\n readonly error: string | null;\n}\n\n/** 一次取数的结果:每家一行(顺序照页签顺序)。 */\nexport interface HealthFetchResult {\n readonly rows: readonly HealthFetchRow[];\n /** 取数过程本身的错(通道缺席之类),不是某一家的问题。 */\n readonly error: string | null;\n}\n\n/** 账本条目里本票要用的那一段(页签槽注册时写进 options)。 */\nexport interface HealthTabRow {\n readonly id: string;\n readonly channel: string;\n}\n\nfunction timeoutText(): string {\n return '体检超时(' + String(Math.round(HEALTH_TIMEOUT_MS / 1000)) + 's):技能没在时限内回话。';\n}\n\nasync function fetchOne(call: HealthCallFace, row: HealthTabRow): Promise<{ readonly report: HealthReport | null; readonly error: string | null }> {\n try {\n const result = await call(\n CARRIER_BASE,\n phoneOf(row.channel),\n { method: HEALTH_ENDPOINT, payload: {} },\n AbortSignal.timeout(HEALTH_TIMEOUT_MS),\n );\n if (!result || typeof result !== 'object' || typeof (result as { ok?: unknown }).ok !== 'boolean') {\n return { report: null, error: '回执信封异常(非 ok 信封)。' };\n }\n if (!result.ok) {\n const error = (result as { error?: { code?: unknown; message?: unknown } }).error;\n const message = typeof error?.message === 'string' && error.message.length > 0 ? error.message : '体检没跑起来。';\n return { report: null, error: message };\n }\n const value = (result as { value?: unknown }).value;\n if (!isHealthReport(value)) return { report: null, error: '体检报告形状认不出(技能包与总管版本对不上?)。' };\n return { report: value, error: null };\n } catch (error) {\n if (error instanceof Error && (error.name === 'AbortError' || error.name === 'TimeoutError')) {\n return { report: null, error: timeoutText() };\n }\n return { report: null, error: '体检没跑起来:' + (error instanceof Error ? error.message : String(error)) };\n }\n}\n\n/** 取六家的体检报告(缺席的包不在此列,由调用方按账本判断)。\n *\n * `onRow`(票 #741):某一家一落定就回调一次——面板据此**按家增量**画,不再等齐全部。\n * 六家的电话是并发打出去的,但宿主里干活的只有一个线程(各单品宿主半 `spawnSync`),\n * 所以真实到达顺序就是它们被串行干完的顺序;先回来的那家先画出来,用户才看得见进度。 */\nexport async function loadHealthReports(\n call: HealthCallFace | null,\n tabs: readonly HealthTabRow[],\n onRow?: (row: HealthFetchRow) => void,\n): Promise<HealthFetchResult> {\n if (typeof call !== 'function') return { rows: [], error: '宿主连接缺席:connection.rpc.call 不可用' };\n const rows = await Promise.all(tabs.map(async (tab): Promise<HealthFetchRow> => {\n const one = await fetchOne(call, tab);\n const row: HealthFetchRow = { id: tab.id, report: one.report, error: one.error };\n // 回调里的异常不许把整批拖下水:这是画图的旁路,不是取数本身。\n try {\n onRow?.(row);\n } catch {\n /* 旁路失败不影响整批结果 */\n }\n return row;\n }));\n return { rows, error: null };\n}\n","/** 配置体检的状态源:**一份快照**喂两处(面板顶部那行总览 + 各家页签那张表)。\n *\n * 为什么是一个 hook 而不是两处各取一次(票 #706 验收第三条「同一份数据,不许两处各算一遍」):\n * 这一处取数、这一处存状态,总览与各家表都从它读 —— 那条验收因此是**结构上的性质**,不是靠自觉。\n *\n * 取数是**按需**的(点「体检一次」才跑):没有轮询、没有定时器(静态门那条「无数据轮询」照旧)。\n */\nimport * as React from 'react';\nimport { loadHealthReports } from './health-fetch.js';\nimport type { HealthCallFace, HealthFetchRow, HealthTabRow } from './health-fetch.js';\nimport { worstStatus } from './health-contract.js';\nimport type { HealthReport, HealthStatus } from './health-contract.js';\n\n/** 一家的状态:没跑过/正在跑/有报告/这一家出错。 */\nexport type HealthPhase = 'idle' | 'running' | 'ready' | 'failed';\n\nexport interface HealthPanelFace {\n /** 一次取数的结果:包名 → 该家的状态(缺席的包不在表里)。 */\n readonly rows: Readonly<Record<string, { readonly phase: HealthPhase; readonly report: HealthReport | null; readonly error: string | null }>>;\n /** 取数过程本身的错(通道缺席之类)。 */\n readonly error: string | null;\n /** 正在跑。 */\n readonly running: boolean;\n /** 跑一次(六家一起)。 */\n run(): void;\n}\n\nconst IDLE = { phase: 'idle' as HealthPhase, report: null, error: null };\n\n/** 一家的灯:没报告=`null`(不冒充绿)。 */\nexport function lightOf(row: { readonly report: HealthReport | null } | undefined): HealthStatus | null {\n return row?.report ? worstStatus(row.report.items) : null;\n}\n\n/** 体检状态源:挂载时不自动跑(体检要读盘、要起子进程,按需跑),点一次跑六家。\n *\n * **按家增量**(票 #741):某一家一落定就画那一家——不再等全部齐了才一次性 setRows。\n * 取数口仍然只有一处(`loadHealthReports`),它把「一家好了」的回调交给这里 patch 状态。 */\nexport function useHealthPanel(getCall: () => HealthCallFace | null, tabs: readonly HealthTabRow[]): HealthPanelFace {\n const [rows, setRows] = React.useState<HealthPanelFace['rows']>({});\n const [error, setError] = React.useState<string | null>(null);\n const [running, setRunning] = React.useState(false);\n const run = React.useCallback(() => {\n const diag = (globalThis as { __T706_RUN__?: string[] }).__T706_RUN__;\n if (Array.isArray(diag)) diag.push('run:tabs=' + String(tabs.length));\n setRunning(true);\n setError(null);\n const targets = tabs;\n setRows(Object.fromEntries(targets.map((tab) => [tab.id, { ...IDLE, phase: 'running' as HealthPhase }])));\n void (async () => {\n const patch = (row: HealthFetchRow): void => {\n // 函数式更新:这家落定时别家可能也刚落定,直接读旧 state 会丢掉那一次。\n const next = { phase: (row.report ? 'ready' : 'failed') as HealthPhase, report: row.report, error: row.error };\n setRows((previous) => ({ ...previous, [row.id]: next }));\n // 只记条数与第一条的错(渲染台的调试图;浏览器里没人写它,写入失败无所谓)。\n try {\n const diag = (globalThis as { __T706_RUN__?: string[] }).__T706_RUN__;\n if (Array.isArray(diag)) diag.push('row=' + row.id + ' ok=' + String(row.report !== null) + ' err=' + String(row.error));\n } catch {\n /* 调试图写不进去不影响正经事 */\n }\n };\n const loaded = await loadHealthReports(getCall(), targets, patch);\n try {\n const diag = (globalThis as { __T706_RUN__?: string[] }).__T706_RUN__;\n if (Array.isArray(diag)) diag.push('rows=' + String(loaded.rows.length) + ' err=' + String(loaded.error) + ' first=' + String(loaded.rows[0]?.error ?? 'null'));\n } catch {\n /* 调试图写不进去不影响正经事 */\n }\n setError(loaded.error);\n setRunning(false);\n })();\n }, [getCall, tabs]);\n const face: HealthPanelFace = { rows, error, running, run };\n // 出图页的取数口(渲染台的 hooks 替身没有真事件环,页内点按钮那条路不落定取数)。\n // 只在有人预先挂了 `__T706_FACE_SINK__` 时写一次,浏览器里没人挂它,等于没有这一行。\n const sink = (globalThis as { __T706_FACE_SINK__?: (value: HealthPanelFace) => void }).__T706_FACE_SINK__;\n if (typeof sink === 'function') {\n try {\n sink(face);\n } catch {\n /* 出图口的错不影响正经渲染 */\n }\n }\n return face;\n}\n\n/** 汇总行该显示哪一句错(票 #735):取数过程本身的错优先;**一家报告都没回来**时,把第一条家错误顶上来。\n *\n * 为什么要有这一格:各家的错只画在那家页签里的体检表上,一行摘要看不见 ⇒ 用户点完只见「体检中…」闪一下、\n * 接着仍旧「还没体检」,会以为按钮坏了(真机上就是这么被误判的;那次六家全 404,汇总行一声不吭)。\n * 有报告回来时不顶:那时各家那张表里已经把错说清楚了,摘要行只管读数。 */\nexport function summaryErrorOf(face: Pick<HealthPanelFace, 'rows' | 'error'>): string | null {\n if (face.error !== null) return face.error;\n const rows = Object.values(face.rows);\n if (rows.some((row) => row.report !== null)) return null;\n return rows.find((row) => row.error !== null)?.error ?? null;\n}\n","/** 配置体检的两个画法:面板顶部那行总览(六家一盏灯)+ 各家那张表。\n *\n * 面板侧纪律(沿 `client.ts`/`update-panel.ts` 既有口径):`createElement` 手写(禁 JSX)、内联 style、\n * 颜色走 DSH 主题别名(深浅主题自适应,写死值只做回退)、不 import 任何单品包、不 import 技能实现。\n *\n * 判据与文案**全部来自各家技能的报告**,本件一个字都不重写——只按三档上色、按状态挑文案。\n */\nimport * as React from 'react';\nimport { countByStatus, worstStatus } from './health-contract.js';\nimport type { HealthItem, HealthReport, HealthStatus } from './health-contract.js';\n\n/** 三档的配色(绿走主题别名;红黄用实色,理由见下)。\n *\n * 红与黄为什么必须落在**不同明度带**上:这两档要一起出现在同一行灯里,用户扫一眼要能分开\n * 「红了(用不了)」与「黄了(能用但要撞上)」。评审连着两轮点「黄灯偏暗与红相近」,所以:\n * · 红=深红(暗、重);\n * · 黄=高饱和的琥珀(亮、跳)——它在白底上的对比度仍然够(`#b45309` 对白 ≈ 4.9:1)。\n * 绿不给底色:它是「正常」,不该和要处理的那两档抢注意力。 */\nexport const STATUS_COLOR: Readonly<Record<HealthStatus, string>> = {\n red: '#c0392b',\n yellow: '#e08a00',\n green: 'var(--dsw-alias-state-success-primary, #4ec9a0)',\n};\n\n/** 整行底色的浅一档:饱和降一档,免得红黄行「刺眼」压过正文(评审第二轮的扣分点)。 */\n/** 红黄两档整行上底的底色。红灯要**明显重于**黄灯:复评实测原先两个底色亮度几乎一样\n * (红 `#FBF3F3` 对黄 `#FCF3E5`,只差蓝通道),最严重那一档反而最淡——红色提到 0.14。 */\nconst STATUS_TINT: Readonly<Record<HealthStatus, string>> = {\n red: 'rgba(192, 57, 43, 0.14)',\n yellow: 'rgba(224, 138, 0, 0.10)',\n green: 'transparent',\n};\n\n/** 档位词(灯里那几个字)用的色:红黄要**够深**才读得清,故另取一枚比灯更深的值。 */\nexport const STATUS_TEXT: Readonly<Record<HealthStatus, string>> = {\n red: '#a5281b',\n yellow: '#8a5200',\n green: 'var(--dsw-alias-state-success-primary, #4ec9a0)',\n};\n\nconst STATUS_LABEL: Readonly<Record<HealthStatus, string>> = { red: '红', yellow: '黄', green: '绿' };\n\n/** 档位的**形状**标记:颜色之外的第二条读数(灰度截图、色弱视角下也分得开谁要处理)。 */\nconst STATUS_MARK: Readonly<Record<HealthStatus, string>> = { red: '✕', yellow: '!', green: '✓' };\n\n/** 面板上的文字色与分隔线(与 `client.ts` 同一套别名)。 */\nconst INK = 'var(--dsw-alias-label-primary, inherit)';\nconst INK_DIM = 'var(--dsw-alias-label-secondary, #9a9a9a)';\nconst BORDER = 'var(--dsw-alias-border, rgba(128,128,128,.35))';\n\nexport const HEALTH_STYLE = {\n box: {\n padding: '10px 12px',\n marginBottom: 10,\n borderRadius: 10,\n border: '1px solid ' + BORDER,\n } as React.CSSProperties,\n headRow: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 8, marginBottom: 4 } as React.CSSProperties,\n head: { fontSize: 13, fontWeight: 700, color: INK } as React.CSSProperties,\n /** 顶部那一行汇总(票 #732):一行装下「配置体检 + 一句总账 + 体检按钮」,三样都不独占行。\n * 它替掉了原先「标题行 + 单独一行的按钮 + 一排灯按钮 + 两行图例」(同屏实测 153px → 一行)。 */\n summaryRow: {\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n marginBottom: 10,\n } as React.CSSProperties,\n summaryText: { flex: '1 1 auto', minWidth: 0, fontSize: 12.5, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' } as React.CSSProperties,\n /** 各家那张表里的说明行(「还没体检:点上面那个…」那几句)。 */\n meta: { color: INK_DIM, fontSize: 12, lineHeight: 1.7, marginTop: 12 } as React.CSSProperties,\n btn: {\n border: '1px solid ' + BORDER,\n background: 'transparent',\n color: INK,\n borderRadius: 8,\n flex: '0 0 auto',\n padding: '3px 10px',\n fontSize: 12.5,\n fontWeight: 600,\n cursor: 'pointer',\n } as React.CSSProperties,\n error: { color: STATUS_COLOR.red, fontSize: 12, lineHeight: 1.7, marginTop: 6 } as React.CSSProperties,\n} as const;\n\n/** 值得一眼看见的那两档(档位名与整行文字都上色;绿行保持淡)。 */\nfunction isAttention(status: HealthStatus): boolean {\n return status === 'red' || status === 'yellow';\n}\n\n/** 「去哪修」那一行的正文:各家交回来的 action 里若已自带「去哪修:」,这里剥掉一层——\n * 面板已经带了这个前缀,两边都写就会印成「去哪修:去哪修:…」。只剥开头那一次,别的一字不动。 */\nexport function actionText(action: string): string {\n const trimmed = action.trim();\n return trimmed.startsWith('去哪修:') ? trimmed.slice(4).trim() : trimmed;\n}\n\n/** 报文里能扫出来的每一段路径(盘符绝对路径;非 Windows 形状的退回 `/` 打头那一支)。 */\nfunction rawPathsIn(text: string): readonly string[] {\n return text.match(/[A-Za-z]:\\/[^\\s()「」,。;]+/g) ?? text.match(/\\/[^\\s()「」,。;]+/g) ?? [];\n}\n\n/** 一段落点的**根**(盘符):`C:/a/b` → `C:/`。用来把「同一个盘、但根下不同枝」的落点分开算,\n * 免得一个 D 盘上的旁证(比如「配了但目录不在:D:/media」)把 C 盘那几条的缩写全拖没。 */\nfunction rootOf(path: string): string {\n const drive = /^[A-Za-z]:\\//.exec(path);\n return drive === null ? '/' : drive[0];\n}\n\n/** 一段落点的公共目录前缀(按 `/` 切、整段比):`C:/a/b/c` + `C:/a/b/d` → `C:/a/b`。 */\nfunction commonDirOf(paths: readonly string[]): string {\n if (paths.length < 2) return '';\n const split = paths.map((path) => path.replace(/[.,。;]+$/, '').split('/'));\n const first = split[0] as string[];\n let size = 0;\n while (size < first.length && split.every((parts) => parts[size] === first[size])) size += 1;\n return size < 1 ? '' : first.slice(0, size).join('/');\n}\n\n/** 一份报文里**按根分开**的落点前缀(长的在前):只给「同一个根下至少两条落点」的根算前缀——\n * 这个根下只有一条落点时不缩(没有第二条可比,缩出来反而是瞎猜)。\n * 为什么要分根:一家报里常有别的盘的旁证(备忘录用 D 盘上那份媒体目录),只算一个总前缀,\n * 一条旁证就能把整张表的缩写全拖没(#706 出图复评:备忘录那张表整片没缩、记账那张缩了)。 */\nexport function dirPrefixesOf(report: HealthReport): readonly string[] {\n return dirPrefixesIn(pathsOf(report).join(' '));\n}\n\n/** `dirPrefixesOf` 的正文版:一段文字里(报文正文 + 表头两条混在一起)按根分组取公共目录前缀。 */\nexport function dirPrefixesIn(text: string): readonly string[] {\n const paths = rawPathsIn(text);\n const roots = [...new Set(paths.map(rootOf))];\n const prefixes: string[] = [];\n for (const root of roots) {\n const prefix = commonDirOf(paths.filter((path) => rootOf(path) === root));\n if (prefix !== '') prefixes.push(prefix);\n }\n // 长的在前:一个落点命中多条前缀时,最长的那个才是它真正的目录(缩短后不歧义)。\n return prefixes.sort((a, b) => b.length - a.length);\n}\n\n/** 那一行「配置文件 … · 数据目录 …」+ 各条报文里的长路径,都缩成「公共前缀 + 短尾巴」。 */\nfunction shorten(path: string, prefixes: readonly string[]): string {\n const hit = prefixes.find((prefix) => path.startsWith(prefix + '/'));\n return hit === undefined ? path : '…/' + path.slice(hit.length + 1);\n}\n\n/** 一家里所有会印出来的路径。 */\nexport function pathsOf(report: HealthReport): readonly string[] {\n const out: string[] = [report.configPath, report.dataDir];\n for (const item of report.items) out.push(item.message);\n return out;\n}\n\n/** 把报文里那一长串路径按公共前缀缩短(只动打印,不动事实)。\n * 只按**扫得出来的那一段段落点**去缩,不做全串替换:既不误伤长路径,也不会把一段路劈成半截。 */\nexport function shortenPathsIn(text: string, prefixes: readonly string[]): string {\n const candidates = rawPathsIn(text);\n if (candidates.length === 0) return text;\n // 长的在前:先缩长的那条,缩完它自己就挡住了它的父目录,不会二次缩。\n const longestFirst = [...new Set(candidates.map((path) => path.replace(/[.,。;]+$/, '')))].sort(\n (a, b) => b.length - a.length,\n );\n let out = text;\n for (const path of longestFirst) {\n const hit = prefixes.find((prefix) => path.startsWith(prefix + '/'));\n if (hit === undefined) continue;\n out = out.split(hit + '/').join('…/');\n }\n return out;\n}\n\n/** 一盏灯:一家一名一档(数到几个红黄绿)。落点是那家页签(票 #732:面板不再单开一排灯按钮)。 */\nexport interface HealthLightRow {\n readonly id: string;\n readonly title: string;\n readonly status: HealthStatus | null;\n readonly counts: Readonly<Record<HealthStatus, number>>;\n /** 这一家交没交体检通道(页签槽 `options.channel`)。false=这家没装/没接上:圆点保持缺席态,不许点。 */\n readonly hasChannel: boolean;\n}\n\n/** 从六份报告数出那一行的灯(每家按最严重那一档上色)。 */\nexport function lightsOf(\n tabs: readonly { readonly id: string; readonly title: string; readonly hasChannel?: boolean }[],\n reports: Readonly<Record<string, HealthReport | undefined>>,\n): readonly HealthLightRow[] {\n return tabs.map((tab) => {\n const report = reports[tab.id];\n const items = report?.items ?? [];\n return {\n id: tab.id,\n title: tab.title,\n status: report ? worstStatus(items) : null,\n counts: countByStatus(items),\n // 缺席与「装了但还没体检」在报告上都长成 null,分开只能靠这一格(票 #732)。\n hasChannel: tab.hasChannel ?? true,\n // 这里曾短期印过「最严重那一项的名字」当区分度,**已撤回**:实测六家常坏在同一条通用检查项上\n // (都是「数据目录」),印出来六家一模一样,是噪声不是区分度(出图复评实测逐字抄了六个「数据目录」)。\n // 「哪一项坏了」本来就由那家自己那张表回答,灯上不再重复。\n };\n });\n}\n\n/** 一家的一句话读数:「红 1 黄 2」,全绿就是「绿」。面板侧要**上色**的那几处不用它(见 `countSegments`)。 */\nfunction countsText(counts: Readonly<Record<HealthStatus, number>>): string {\n const parts: string[] = [];\n if (counts.red > 0) parts.push('红 ' + String(counts.red));\n if (counts.yellow > 0) parts.push('黄 ' + String(counts.yellow));\n if (parts.length === 0) return '绿';\n return parts.join(' · ');\n}\n\n/** 一截读数:`红 2`/`黄 3`/`绿`/`—`(缺席没报告)。`status` 为 null 的那些走淡色。 */\nexport interface HealthCountSeg {\n readonly status: HealthStatus | null;\n readonly text: string;\n}\n\n/** 把一份计数拆成**按档分色**的几截。红黄各自一截、各自上色,互不串色;\n * 「黄 3」这一截为什么不能被染成红——见 `countSegments` 的注释(出图复评逮到的真缺陷)。 */\nexport function countSegsOf(light: HealthLightRow): readonly HealthCountSeg[] {\n if (light.status === null) return [{ status: null, text: '—' }];\n const segs: HealthCountSeg[] = [];\n for (const status of ['red', 'yellow'] as const) {\n const count = light.counts[status];\n if (count > 0) segs.push({ status, text: STATUS_LABEL[status] + ' ' + String(count) });\n }\n return segs.length > 0 ? segs : [{ status: 'green', text: '绿' }];\n}\n\n/** 几截读数之间那个分隔符:不许染档位色(它不属于任何一档)。 */\nconst SEG_SEP = ' · ';\n\n/** 一家的读数**写成一行**(纯文本版:给页签上那枚小数字用)。 */\nfunction countText(light: HealthLightRow): string {\n return countSegsOf(light).map((piece) => piece.text).join(SEG_SEP);\n}\n\n/** 汇总行那一句:绿要显眼(它是「正常」这件事本身),没通道的那几家不冒充绿。\n *\n * 「没有体检出口」与「还没跑过体检」是**两件事**(票 #735):前者那颗「体检一次」按下去不会有任何反应,\n * 必须自己说出来——两态共用一句「还没体检」时,用户与诊断都会被引到「点了没跑」上去。 */\nfunction overallText(lights: readonly HealthLightRow[]): React.ReactElement {\n const pending = lights.filter((light) => !light.hasChannel).length;\n const withChannel = lights.filter((light) => light.hasChannel);\n const tail = pending > 0 ? ' · ' + String(pending) + ' 家没有体检出口' : '';\n if (withChannel.length === 0) {\n const what = lights.length === 0\n ? '还没有任何一家登记体检出口'\n : String(lights.length) + ' 家都没有体检出口(通道名没登记上)';\n return React.createElement('span', { style: { color: STATUS_TEXT.red, fontWeight: 700 } }, '没有可体检的家:' + what);\n }\n const allGreen = withChannel.every((light) => light.status === 'green');\n if (allGreen && pending === 0) {\n return React.createElement('span', { style: { color: STATUS_COLOR.green, fontWeight: 700 } }, '六家正常');\n }\n const parts: React.ReactElement[] = [];\n for (const status of ['red', 'yellow'] as const) {\n let count = 0;\n for (const light of lights) count += light.counts[status];\n if (count === 0) continue;\n if (parts.length > 0) parts.push(React.createElement('span', { key: status + '-sep', style: { color: INK_DIM } }, SEG_SEP));\n parts.push(\n React.createElement('span', { key: status, style: { color: STATUS_TEXT[status], fontWeight: 700 } }, STATUS_LABEL[status] + ' ' + String(count)),\n );\n }\n if (parts.length === 0) {\n // 一份报告都没跑过;有出口的已经全绿、只是还有没出口的家时,不许把「还没体检」安到它们头上。\n return React.createElement('span', { style: { color: INK_DIM } }, (allGreen ? '有出口的都正常' : '还没体检') + tail);\n }\n parts.push(React.createElement('span', { key: 'todo', style: { color: INK } }, ' 要处理' + tail));\n return React.createElement('span', null, parts);\n}\n\n/** 那枚小数字用的底色(比灯色浅一档,压在页签上不抢眼)。 */\nconst STATUS_DOT: Readonly<Record<HealthStatus, string>> = {\n red: STATUS_COLOR.red,\n yellow: STATUS_COLOR.yellow,\n green: STATUS_COLOR.green,\n};\n\n/** 页签圆点的颜色与语义(票 #732):从「实心/空心=装没装」改成「颜色=体检档位」。 */\nexport const TAB_DOT = {\n /** 没装(页签槽账本里没有这一家)。 */\n absent: STATUS_DOT.green,\n /** 装了、但还没跑过体检。 */\n unchecked: INK_DIM,\n} as const;\n\n/** 页签圆点该用什么色:装了按档位(红黄绿),没装/没跑过走缺席态。 */\nexport function tabDotColor(light: HealthLightRow): string {\n if (!light.hasChannel) return TAB_DOT.absent;\n return light.status === null ? TAB_DOT.unchecked : STATUS_DOT[light.status];\n}\n\n/** 页签上那枚小数字(票 #732):红黄那几家,家名后带一条计数;全绿与没跑过不带。 */\nexport const TAB_NOTE_STYLE: React.CSSProperties = {\n marginLeft: 5,\n border: '1px solid ' + BORDER,\n borderRadius: 999,\n padding: '0 5px',\n fontSize: 11,\n fontWeight: 700,\n} as const;\n\n/** 那枚小数字写什么:只报「要处理」的那两档;一家里红黄都有时先报红那一档(圆点已经按最严重的档上色了)。 */\nexport function tabNote(light: HealthLightRow): HealthCountSeg | null {\n if (!light.hasChannel || light.status === null || light.status === 'green') return null;\n return countSegsOf(light)[0] ?? null;\n}\n\n/** 顶部那一行汇总(票 #732:不再一家一盏灯按钮,一行总账 + 一个体检按钮)。\n *\n * 与页签的分工:这行只答「一共几条要处理」;「哪几家、各几条」由页签的圆点与小数字答;\n * 「具体哪一条坏了、去哪修」由那家页签里的 `HealthTable` 答。 */\nexport function HealthSummaryLine(props: {\n readonly lights: readonly HealthLightRow[];\n readonly running: boolean;\n readonly error: string | null;\n readonly onRun: () => void;\n}): React.ReactElement {\n return React.createElement(\n 'div',\n { style: HEALTH_STYLE.summaryRow, 'data-ilife-health': 'summary' },\n React.createElement('div', { style: HEALTH_STYLE.head }, '配置体检'),\n React.createElement(\n 'div',\n { style: HEALTH_STYLE.summaryText },\n // 一家都没有出口这一态由 overallText 自己说(#735):这里不再用一句「还没体检」把两件事糊成一件。\n props.error !== null ? React.createElement('span', { style: { color: STATUS_TEXT.red } }, props.error) : overallText(props.lights),\n ),\n React.createElement(\n 'button',\n {\n type: 'button',\n style: props.running ? { ...HEALTH_STYLE.btn, opacity: 0.55, cursor: 'default' } : HEALTH_STYLE.btn,\n disabled: props.running,\n onClick: props.onRun,\n // 「只看不改」那三条口径从屏上撤下来了(它原先和两排圆点的图例挤在一行),\n // 但它**仍然要说出口**:按钮悬停是它能待的地方(票 #732;测试按产物里的字符串守着)。\n title: '对六家各跑一次配置体检(只看不改:不建目录、不改配置、不自动重置)',\n },\n props.running ? '体检中…' : '体检一次',\n ),\n );\n}\n\n/** 正正常常的那些:绿条只留「名字」,一句话与「去哪修」都不印(票面验收第一条:正常的收成一行)。 */\nfunction isAttentionItem(item: HealthItem): boolean {\n return isAttention(item.status);\n}\n\n/** 一家那张表:**要处理的**(红黄)一条一块、整行上底带一句话与「去哪修」;\n * **正常的**(绿)收成一行并列的小字,点得到、扫得完(票面验收第一条:正常的收成一行,有问题的才展开)。 */\nexport function HealthTable(props: {\n readonly title: string;\n readonly phase: 'idle' | 'running' | 'ready' | 'failed';\n readonly report: HealthReport | null;\n readonly error: string | null;\n}): React.ReactElement {\n const { report } = props;\n const prefixes: readonly string[] = report ? dirPrefixesOf(report) : [];\n return React.createElement(\n 'div',\n { style: HEALTH_STYLE.box, 'data-ilife-health': 'table' },\n React.createElement(\n 'div',\n { style: HEALTH_STYLE.headRow },\n React.createElement('div', { style: HEALTH_STYLE.head }, props.title + ' · 体检'),\n report ? React.createElement('div', { style: { color: INK_DIM, fontSize: 12 } }, countsText(countByStatus(report.items))) : null,\n ),\n props.phase === 'idle'\n ? React.createElement('div', { style: HEALTH_STYLE.meta }, '还没体检:点上面那个「体检一次」。')\n : null,\n props.phase === 'running' ? React.createElement('div', { style: HEALTH_STYLE.meta }, '正在体检…') : null,\n props.error !== null ? React.createElement('div', { style: HEALTH_STYLE.error }, props.error) : null,\n report\n ? React.createElement(\n 'div',\n null,\n // 行内落点缩成「…/短尾」是为了不把每行撑成两行;但「去哪修:先建这个目录」要照着建,\n // 就得把**它到底在哪儿**说清楚(出图复评五份里两份点名「照屏修不了」)。\n // 措辞有讲究:不能写成「下面行内的「…/」都接在这里」——行里本来就有个「…/data」,\n // 接上基目录会拼成「…/data/data」,自相矛盾(复评原话)。故只**点明缩写代表哪一层**。\n report.dataDir !== ''\n ? React.createElement('div', { style: { color: INK_DIM, fontSize: 11.5, marginTop: 2 } },\n '落点根目录:' + report.dataDir\n + (report.configPath !== '' ? '(配置文件 ' + report.configPath + ')' : ''))\n : null,\n report.items.filter(isAttentionItem).map((item) =>\n React.createElement(\n 'div',\n {\n key: item.id,\n 'data-ilife-health': 'item',\n 'data-status': item.status,\n // 红黄两档整行上底:扫一眼先看见「要处理的」。\n style: {\n padding: '7px 8px',\n margin: '2px 0',\n borderTop: '1px solid ' + BORDER,\n borderRadius: 6,\n fontSize: 13,\n lineHeight: 1.75,\n background: STATUS_TINT[item.status],\n },\n },\n React.createElement(\n 'div',\n { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 6 } },\n // 档位**不只靠颜色**认:形状标记(✕/!)+档位字,色弱与灰度截图下也分得开。\n // 原先这里还有一个同色的实心圆点,与标记、档位字三者同义同色(复评两份点名「没有主次」),\n // 且页签那排(`client.ts:349`)也用同样的圆点表示「这家装没装」——同屏两套圆点语义,\n // 故本件的条目里**去掉圆点**,把圆点这一格语汇留给页签。\n React.createElement('span', {\n style: { color: STATUS_TEXT[item.status], fontWeight: 700, fontSize: 13, lineHeight: 1 },\n 'aria-hidden': 'true',\n }, STATUS_MARK[item.status]),\n React.createElement('span', { style: { fontWeight: 650, color: INK } }, item.title),\n React.createElement('span', {\n style: { color: STATUS_TEXT[item.status], fontWeight: 700 },\n }, STATUS_LABEL[item.status]),\n // 来源那一格**只在它不只是「默认值」时印**:四行全印「来自默认值」等于零区分度,\n // 而且「值从哪来」这件事正文里已经写全了(复评原话:「这四个字零区分度」)。\n item.source && item.source !== '默认值'\n ? React.createElement('span', { style: { color: INK_DIM } }, '· 来自' + item.source)\n : null,\n ),\n React.createElement('div', { style: { color: INK }, title: item.message }, shortenPathsIn(item.message, prefixes)),\n item.action.length > 0\n ? React.createElement('div', { style: { color: INK_DIM } }, '去哪修:' + actionText(item.action))\n : null,\n ),\n ),\n // 正常的那一档:一条都不用点开,名字排成一行(`data-ilife-health=\"ok\"` 供判据核对「一条不少」)。\n // 给它**一条绿边+一层极淡底**,让它读起来是「一组正常项」,而不是四行问题之后的一句脚注\n // (复评原话:「占多数的那一档在视觉上几乎退成脚注」)——条目仍然收成一行(票面第一条)。\n report.items.some((item) => !isAttentionItem(item))\n ? React.createElement(\n 'div',\n {\n 'data-ilife-health': 'ok',\n style: {\n display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: '2px 10px',\n marginTop: 8, padding: '6px 8px',\n borderLeft: '3px solid ' + STATUS_COLOR.green,\n borderRadius: 6,\n fontSize: 12, lineHeight: 1.6, color: INK_DIM,\n },\n },\n // 绿档也要**看得见**:只给一行灰字时,五份出图复评里三份独立指到「全屏没有绿色、\n // 绿=正常这一档等于不存在」。故这一档前面加一个绿点(与红黄同一个「点」语汇),\n // 说明它们就是正常项——条目**仍然收成一行**(票面第一条:正常的收成一行)。\n React.createElement('span', {\n style: {\n width: 8, height: 8, borderRadius: '50%', display: 'inline-block',\n background: STATUS_COLOR.green, flex: '0 0 auto',\n },\n 'aria-hidden': 'true',\n }),\n React.createElement('span', { style: { fontWeight: 650, color: INK } }, '正常 ' + String(countByStatus(report.items).green) + ' 条'),\n React.createElement('span', { style: { color: INK_DIM } }, '(下列各项都正常工作)'),\n report.items.filter((item) => !isAttentionItem(item)).map((item) =>\n React.createElement('span', { key: item.id, 'data-ilife-health': 'ok-item' }, '· ' + item.title),\n ),\n )\n : null,\n )\n : null,\n );\n}\n","/** 目录浏览器的纯逻辑半(**不认识宿主**:只认两格原语)。\n *\n * 为什么在这里:六个单品插件的设置页各有一批「值是一个目录」的行,它们要的界面是同一个东西。\n * 界面的**取数**来自宿主给的两个原语(`list`/`createDirectory`),而这两个原语的形状是**调用方喂进来的**\n * ——本件不 import 宿主的任何实现,也不认识 `remote.directoryPicker` 这个名字(那两件住在各家的\n * `dsh-ctx.ts` 镜像里)。于是本件在 Windows 桌面、远程浏览器、SSH 三种部署下是同一份代码。\n *\n * 装什么:本次要浏览的那一层怎么走(进子目录/回上一级/跳面包屑)、路径怎么切出「目录部分」与\n * 「筛选词」、回执怎么认。**不装**任何 IO、不装任何持久化——`list`/`createDirectory` 由调用方执行。\n *\n * 与平台自带浏览器件的关系:`@deepseek-ai/dsh-client-ui-directory-picker-browse` 的 `DirectoryBrowser`\n * 是包内私有件(只经 `ui-workspace` 的流程空位暴露),插件侧取不到,所以这里是一份独立实现,\n * 只对齐它的**交互结果**(进/选/新建),不对齐它的 DOM。\n */\n\n/** 一层目录里的一个子目录。`hidden` 由宿主判,本件只照传。 */\nexport interface DirectoryEntry {\n readonly name: string;\n readonly path: string;\n readonly hidden: boolean;\n}\n\n/** 面包屑的一格:从文件系统根到当前层,每一格都是一个可跳转目标。 */\nexport interface DirectoryCrumb {\n readonly name: string;\n readonly path: string;\n readonly hidden: boolean;\n}\n\n/** 一次列举的回执(形状照宿主 browse 原语:`path`/`home`/`crumbs`/`entries`/`truncated`)。 */\nexport interface DirectoryListing {\n readonly path: string;\n readonly home: string;\n readonly crumbs: readonly DirectoryCrumb[];\n readonly entries: readonly DirectoryEntry[];\n readonly truncated: boolean;\n}\n\n/** 取数面:由调用方注入。本件只调这两个方法,别的什么都不调。 */\nexport interface DirectoryBrowseFace {\n list(path?: string, signal?: AbortSignal): Promise<DirectoryListing>;\n createDirectory(path: string, name: string): Promise<string>;\n}\n\n/** 浏览失败的人话(`code` 保留给用例断言,`message` 直接上图)。 */\nexport interface BrowseFailure {\n readonly code: string;\n readonly message: string;\n}\n\n/** 宿主拒了一条路的说法随宿主变化,**本件不认识**:调用方把它的回执码传进 `openRowBrowser`。\n *\n * 它为什么是唯一信号:一次组合里只服务一种能力(native 或 browse),另一条路上的动词一律被拒\n * (`@deepseek-ai/dsh-api-workspace-controller/lib/types/directory-picker.js` 的 `requireCapability`);\n * 而三条动词在客户端命名空间上都在(都由 `TYPERT_REMOTE.descriptors` 生成),\n * 所以「有没有 `pick`/有没有 `list`」问不出组合里服务哪种能力,只有真调一次、看它拒没拒才知道。 */\n\n/** 目录行入口的三态判定结果:**按命名空间上有什么动词**看一眼,不是问组合里服务哪种能力。 */\nexport type PickerMode = 'native' | 'browse' | 'none';\n\n/** 一个「根」是什么类型:界面只拿它写悬停说明,不拿它做任何判断。\n *\n * 这五个值由**取数方**(宿主半)归一:Windows 上是 `DriveInfo.DriveType` 映过来的;\n * 别的根(挂载点等)落 `other`。本件不认识任何宿主名词,只认识这五个值。 */\nexport type RootKind = 'fixed' | 'network' | 'removable' | 'optical' | 'other';\n\n/** 一个可跳转的根:`path` 是绝对路径(Windows 上形如 `D:\\`),`kind` 只用于显示。 */\nexport interface RootRow {\n readonly path: string;\n readonly kind: RootKind;\n}\n\n/** 平台回执的信封(各家的 `DirectoryPickerAnswer` 与此同形;本件只认这三格,不认别家的类名)。 */\nexport interface PickEnvelope {\n readonly ok: boolean;\n readonly value?: string | null;\n readonly error?: { readonly code?: string; readonly message?: string };\n}\n\n/** 一次「唤起选择器」的归一结果:选中/取消/供不了,永不抛。\n *\n * `code` 只在平台给了的时候带上(就是 `error.code`):调用方据它分辨「宿主没给这条路」与\n * 「别的失败」——前者该换一条路走,后者才该报出来。那个码由调用方从自己的宿主镜像里传进来\n * (见 `openRowBrowser` 的 `refusalCode`),本件不认它的名字。 */\nexport type PickOutcome =\n | { readonly kind: 'picked'; readonly path: string }\n | { readonly kind: 'cancelled' }\n | { readonly kind: 'unavailable'; readonly code?: string; readonly message: string };\n\n/** 认一认某个值是不是可用的目录取数面(软依赖守卫用:认不出就当没有,绝不把页面带下来)。 */\nexport function isBrowseFace(raw: unknown): raw is DirectoryBrowseFace {\n if (typeof raw !== 'object' || raw === null) return false;\n const face = raw as Partial<DirectoryBrowseFace>;\n return typeof face.list === 'function' && typeof face.createDirectory === 'function';\n}\n\n/** 认一认某个值有没有 `pick`(对应宿主 composition 里的 `native` 能力)。 */\nexport function hasPickFn(raw: unknown): boolean {\n if (typeof raw !== 'object' || raw === null) return false;\n return typeof (raw as { pick?: unknown }).pick === 'function';\n}\n\n/** 三态判定:**先看两格浏览原语**(都在=应用内浏览器这条路在),再看单独的 `pick`,都没有=`none`。\n *\n * 为什么浏览优先:客户端命名空间上三条动词一定都在(由 `TYPERT_REMOTE.descriptors` 生成,\n * 与组合里服务哪种能力无关),所以两格原语在=这台宿主**可能**给浏览;真调一次才知道给不给,\n * 不给({@link CAPABILITY_REFUSED})再由调用方换系统对话框。反过来先认 `pick` 就会在\n * Desktop 这种「只服务 browse」的宿主上每次都去唤一次系统对话框、被拒一次(#744 实测)。 */\nexport function pickerModeOf(raw: unknown): PickerMode {\n if (isBrowseFace(raw)) return 'browse';\n return hasPickFn(raw) ? 'native' : 'none';\n}\n\n/** 平台回执 → 三态(**永不抛**):成功回的是信封里的 `value` 不是路径;`ok:false` 是「供不了」不是「取消」。\n *\n * 出处:`@deepseek-ai/dsh-api-gateway/lib/client.js` 的 `invoke()`(失败不抛、成功也不回裸值),\n * 第一方消费方 `@deepseek-ai/dsh-client-ui-workspace/lib/client.js:99-103` 就照这个信封拆。\n * 裸串照收(老形状兜底),认不出的形状当「供不了」报出来,不当取消吞掉(#743 的教训)。 */\nexport function readPickAnswer(raw: unknown): PickOutcome {\n if (typeof raw === 'string') return raw.trim() === '' ? { kind: 'cancelled' } : { kind: 'picked', path: raw };\n const answer = (typeof raw === 'object' && raw !== null ? raw : {}) as PickEnvelope;\n if (answer.ok === true) {\n const value = answer.value;\n return typeof value === 'string' && value.trim() !== '' ? { kind: 'picked', path: value } : { kind: 'cancelled' };\n }\n const detail = answer.error?.message?.trim() ?? '';\n const code = answer.error?.code;\n return {\n kind: 'unavailable',\n ...(typeof code === 'string' && code !== '' ? { code } : {}),\n message: '打不开系统文件夹对话框' + (detail === '' ? '' : '(' + detail + ')') + ':请直接在框里填绝对路径。',\n };\n}\n\n/** 取数原语失败:带上宿主给的回执码,好让调用方分辨「没这条路」与「这一层读不出来」。\n *\n * `code` 就是 `humanize`(状态那半)读的那一格,人话直接上图。 */\nexport class BrowseAnswerError extends Error {\n readonly code: string;\n constructor(code: string, message: string) {\n super(message);\n this.name = 'BrowseAnswerError';\n this.code = code;\n }\n}\n\n/** 宿主 browse 原语的**信封** → 值,或按信封里的 `error` 抛。\n *\n * 为什么必须走这一道:`list`/`createDirectory` 与 `pick` 一样是 Remote 动词,客户端拿到的是\n * `{ok:true,value}`/`{ok:false,error}`(`@deepseek-ai/dsh-api-gateway/lib/client.js` 的 `invoke()`)。\n * 把信封当值用,图上就会是一层「读出来了但一行都没有」的空目录(#744 实测),\n * 而不是报错——所以宁可在这里抛,也不许把它当列举结果传下去。\n * 第一方消费方同口径:`@deepseek-ai/dsh-client-ui-workspace/lib/client.js:105-113` 也是这么拆的。 */\nexport function readBrowseAnswer<T>(raw: unknown, what: string): T {\n const answer = (typeof raw === 'object' && raw !== null ? raw : {}) as {\n ok?: unknown;\n value?: unknown;\n error?: { code?: string; message?: string };\n };\n if (answer.ok === true && answer.value !== undefined && answer.value !== null) return answer.value as T;\n const detail = answer.error?.message?.trim() ?? '';\n const code = answer.error?.code;\n throw new BrowseAnswerError(\n typeof code === 'string' && code !== '' ? code : 'browse-failed',\n detail === '' ? what + '没有回执' : detail,\n );\n}\n\n/** 宿主命名空间 → 本件的取数面:把两条原语的信封拆开,成功给值、失败按码抛。\n *\n * 命名空间上两格原语不在就回 null(调用方据此不画入口)。**透传参数个数照宿主的规矩**:\n * 描述符声明了可选 `AbortSignal`(`cancellation: {parameter:'signal'}`),而客户端按\n * `values.length === 业务参数个数 + 1` 判有没有 signal——显式传一个 `undefined` 会被当成 signal\n * 去 `AbortSignal.any([…, undefined])` 而炸。本件的界面不做取消,故**从不传 signal**。 */\nexport function browseFaceOf(raw: unknown): DirectoryBrowseFace | null {\n if (!isBrowseFace(raw)) return null;\n const namespace = raw as DirectoryBrowseFace;\n return {\n list: async (path) =>\n readBrowseAnswer<DirectoryListing>(path === undefined ? await namespace.list() : await namespace.list(path), '列举目录'),\n createDirectory: async (path, name) => readBrowseAnswer<string>(await namespace.createDirectory(path, name), '新建文件夹'),\n };\n}\n\n/** 一段路径里最后那个分隔符的位置(Windows 上 `\\` 与 `/` 都算;POSIX 上 `\\` 是合法文件名字符,不算)。 */\nfunction lastSeparator(path: string): number {\n const back = path.lastIndexOf('\\\\');\n const slash = path.lastIndexOf('/');\n // 只含 `\\` 的路径按 Windows 规则解;两者都有时取更靠后的那个。\n return back > slash ? back : slash;\n}\n\n/** 上一层:`'C:\\\\a\\\\b\\\\'` → `'C:\\\\a'`;已经是根则回自身;相对名(没有任何分隔符)也回自身。\n *\n * 尾分隔符先吃掉再切——否则「回上一级」会在原地打转。**盘根单独认**:`C:\\` 掐掉尾分隔符会剩成\n * `C:`,而 `C:` 是「当前盘」不是「上一层」,回它就会多跑一趟、还可能跨到别的盘上去。\n * UNC(`\\\\server\\share`)不特殊处理:切到头就是共享名本身,浏览停在共享根。 */\nexport function parentOf(path: string): string {\n const trimmed = path.replace(/[\\\\/]+$/, '');\n if (trimmed === '') return path;\n const sep = path.includes('\\\\') ? '\\\\' : '/';\n if (/^[A-Za-z]:$/.test(trimmed)) return trimmed + sep;\n const cut = lastSeparator(trimmed);\n if (cut === -1) return trimmed;\n if (cut === 0) return path.slice(0, 1);\n if (cut === 2 && /^[A-Za-z]:/.test(trimmed)) return trimmed.slice(0, 2) + sep;\n return trimmed.slice(0, cut);\n}\n\n/** 把「当前路径 + 一段输入」拼成一个目标路径。 */\nexport function joinPath(directory: string, name: string): string {\n const sep = directory.includes('\\\\') ? '\\\\' : '/';\n const base = directory.replace(/[\\\\/]+$/, '');\n if (base === '') return sep + name;\n return base + sep + name;\n}\n\n/** 一层列举里,哪些行可进:隐藏目录按开关过滤,其余照宿主给的顺序。 */\nexport function visibleEntries(listing: DirectoryListing, showHidden: boolean): readonly DirectoryEntry[] {\n return showHidden ? listing.entries : listing.entries.filter((entry) => !entry.hidden);\n}\n\n/** 这一层有多少个被藏起来的目录(用来决定「显示隐藏目录」那个开关要不要出现在图上)。 */\nexport function hiddenCount(listing: DirectoryListing): number {\n return listing.entries.filter((entry) => entry.hidden).length;\n}\n\n/** 拿一段文本按「最后一个分隔符」切成「目录部分 + 筛选词」。\n *\n * 没打过任何分隔符时目录部分=null(那段文字还不指任何目录,只当筛选词用)。\n * Windows 上 `/` 也当分隔符(宿主那边两种都收),POSIX 上不。 */\nexport function splitDraft(draft: string): { readonly directory: string | null; readonly filter: string } {\n const cut = lastSeparator(draft);\n if (cut === -1) return { directory: null, filter: draft };\n return { directory: draft.slice(0, cut + 1), filter: draft.slice(cut + 1) };\n}\n\n/** 按筛选词过一层目录(大小写不敏感;空筛选词=全留)。 */\nexport function filterEntries(entries: readonly DirectoryEntry[], filter: string): readonly DirectoryEntry[] {\n const needle = filter.trim().toLowerCase();\n if (needle === '') return entries;\n return entries.filter((entry) => entry.name.toLowerCase().includes(needle));\n}\n\n/** 新建文件夹的名字合不合法(单个路径段:不许分隔符、不许空、不许 `.`/`..`)。\n *\n * 与宿主 browse 后端的校验同口径——这里先拦一道,是为了在图上给得出人话,不是替代宿主校验。 */\nexport function validateFolderName(name: string): { readonly ok: true } | { readonly ok: false; readonly reason: string } {\n const trimmed = name.trim();\n if (trimmed === '') return { ok: false, reason: '文件夹名不能为空。' };\n if (/[\\\\/]/.test(trimmed)) return { ok: false, reason: '文件夹名里不能带路径分隔符。' };\n if (trimmed === '.' || trimmed === '..') return { ok: false, reason: '「.」与「..」不是文件夹名。' };\n return { ok: true };\n}\n","/** 文件浏览器的操作半:把「进哪一层、这一层显示什么、选到了什么」算成状态,**IO 全部由调用方注入**。\n *\n * 分两层的原因:本件(操作)在 Node 里就能直测——喂一个假的取数面,就能把「进目录/回上一级/\n * 跳面包屑/筛选/新建文件夹/失败出人话」全跑一遍;视图那半(`directory-browser-ui.ts`)\n * 只负责把这份状态画成 DOM。两边都不认识宿主。\n *\n * 数据来源只有两格:`list(path?)` 与 `createDirectory(path, name)`(调用方注入的 `DirectoryBrowseFace`)。\n */\n\nimport type {\n BrowseFailure,\n DirectoryBrowseFace,\n DirectoryEntry,\n DirectoryListing,\n RootRow,\n} from './directory-browser-contract.js';\nimport {\n browseFaceOf,\n filterEntries,\n joinPath,\n parentOf,\n splitDraft,\n validateFolderName,\n visibleEntries,\n} from './directory-browser-contract.js';\n\n/** 一层列举的三态:加载中/拿到了/这一层读不出来。 */\nexport type BrowsePhase = 'idle' | 'loading' | 'ready' | 'failed';\n\n/** 图上的全部状态。视图只读它,不改它。 */\nexport interface BrowseState {\n readonly phase: BrowsePhase;\n readonly listing: DirectoryListing | null;\n /** 这一层读失败时的人话;`phase==='failed'` 时一定有。 */\n readonly failure: BrowseFailure | null;\n /** 当前选中的子目录(点行选中;选中后再点「打开」就是它)。 */\n readonly selected: string | null;\n /** 面包屑上那一层的编号,只用于画图。 */\n readonly showHidden: boolean;\n /** 路径框里的草稿(用户可能正在编辑)。 */\n readonly draft: string;\n /** 草稿指向的那一层已经列举过之后,用来按末段过滤的词。 */\n readonly filter: string;\n /** 正在建的文件夹名;null=没在建。 */\n readonly creating: string | null;\n /** 上一次操作的失败人话(新建失败等);看一眼就好,下一次操作清掉。 */\n readonly notice: string | null;\n /** 可跳转的根(Windows 上的盘符等):取数方给什么就是什么,空数组=不画那一行。 */\n readonly roots: readonly RootRow[];\n}\n\n/** 订阅者拿到的状态(不可变,每次变更是新对象)。 */\nexport type BrowseListener = (state: BrowseState) => void;\n\n/** 操作面:视图与用例都只用这些方法。 */\nexport interface BrowseController {\n getState(): BrowseState;\n subscribe(listener: BrowseListener): () => void;\n /** 开图:先试配置里那个目录(空则直接家目录),读不出来(不存在、没权限)退回宿主家目录。\n *\n * 回**第一份落定的状态**:调用方据此分辨「这条路宿主没给」与「这一层读不出来」。 */\n open(): Promise<BrowseState>;\n /** 进某一层(绝对路径,或 `list()` 的默认家目录当入参为 null)。 */\n enter(path: string | null): Promise<void>;\n /** 回上一级(当前层就是根时原地不动)。 */\n up(): Promise<void>;\n /** 采纳当前选择(选中行,没有选中就用当前层)并回调 `onPicked`。 */\n pick(): void;\n /** 取消:关图,什么都不回调。 */\n cancel(): void;\n /** 选中一个子目录(再点一次同一条=取消选中)。 */\n select(path: string): void;\n /** 切换隐藏目录。 */\n toggleHidden(): void;\n /** 改路径框草稿(不列举,只更新草稿与筛选词)。 */\n setDraft(draft: string): void;\n /** 提交路径框草稿:指到哪一层就去哪一层。 */\n commitDraft(): Promise<void>;\n /** 开「新建文件夹」;传 null 收起。 */\n setCreating(name: string | null): void;\n /** 在当前层(或选中的那一层)下建一个新文件夹,成功后进它并选中它。 */\n createFolder(name: string): Promise<void>;\n /** 关图(与 `cancel` 同义,供宿主侧统一收口)。 */\n close(): void;\n}\n\n/** 建一个浏览器控制器。取数、回调、初值全部由调用方给。 */\nexport function createBrowseController(deps: {\n readonly face: DirectoryBrowseFace;\n readonly initialPath: string;\n readonly onPicked: (path: string) => void;\n readonly onClose: () => void;\n /** 失败人话的前缀,默认「浏览失败」。 */\n readonly failureLabel?: string;\n /** 可跳转的根从哪来;不给就是没有这一行(都是空清单,一样不画)。 */\n readonly rootsSource?: () => Promise<readonly RootRow[]>;\n}): BrowseController {\n const label = deps.failureLabel ?? '浏览失败';\n const listeners = new Set<BrowseListener>();\n let state: BrowseState = {\n phase: 'idle',\n listing: null,\n failure: null,\n selected: null,\n showHidden: false,\n draft: deps.initialPath,\n filter: '',\n creating: null,\n notice: null,\n roots: [],\n };\n\n /** 每次列举带一个序号:晚回来的旧回执直接丢掉,不许覆盖新一层。 */\n let generation = 0;\n\n const emit = (next: Partial<BrowseState>): void => {\n state = { ...state, ...next };\n for (const listener of [...listeners]) listener(state);\n };\n\n /** 根清单只问一次:取不到就是空(不报错、不重试——它只是图上的一行入口)。\n *\n * 「问一次」钉在这里而不是图里:视图那半每次重画都会重新求值,问在这里才谈得上一次。 */\n let rootsAsked = false;\n const loadRoots = async (): Promise<void> => {\n if (rootsAsked || deps.rootsSource === undefined) return;\n rootsAsked = true;\n try {\n const roots = await deps.rootsSource();\n if (roots.length > 0) emit({ roots });\n } catch {\n /* 取不到就当没有这一行:浏览本身照常。 */\n }\n };\n\n const humanize = (cause: unknown): BrowseFailure => {\n const raw = (typeof cause === 'object' && cause !== null ? cause : {}) as {\n code?: unknown;\n message?: unknown;\n name?: unknown;\n };\n const message = typeof raw.message === 'string' && raw.message.trim() !== '' ? raw.message.trim() : String(cause);\n const code = typeof raw.code === 'string' && raw.code !== '' ? raw.code : 'browse-failed';\n return { code, message: `${label}:${message}` };\n };\n\n const load = async (path: string | null): Promise<void> => {\n const mine = ++generation;\n emit({ phase: 'loading', failure: null, notice: null });\n try {\n const listing = path === null ? await deps.face.list() : await deps.face.list(path);\n if (mine !== generation) return;\n emit({\n phase: 'ready',\n listing,\n failure: null,\n selected: null,\n draft: listing.path,\n filter: '',\n });\n } catch (cause) {\n if (mine !== generation) return;\n emit({ phase: 'failed', listing: null, failure: humanize(cause), selected: null });\n }\n };\n\n /** 草稿指到的那一层已经列举过时,按末段筛当前这一层的行;否则不过滤。\n *\n * 两边都先掐掉尾分隔符再比:草稿那边切开后天然带尾分隔符(`C:\\a\\` + `b`),\n * 而这一层的 `path` 由宿主给、不带尾分隔符。 */\n const applyDraft = (draft: string): void => {\n const { directory, filter } = splitDraft(draft);\n const listing = state.listing;\n const sameLevel = listing !== null && directory !== null && normalize(directory) === normalize(listing.path);\n emit({ draft, filter: sameLevel ? filter : '' });\n };\n\n const normalize = (path: string): string => path.replace(/[\\\\/]+$/, '');\n\n return {\n getState: () => state,\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n async open(): Promise<BrowseState> {\n // 根清单一并去问,但**不等它**:它只是图上的一行入口,落定了自会推一次重画;\n // 等它就等于让「宿主那台机器列盘符快不快」决定对话框什么时候开。\n void loadRoots();\n const wanted = deps.initialPath.trim();\n if (wanted !== '') {\n await load(wanted);\n if (state.phase === 'ready') return state;\n }\n await load(null);\n return state;\n },\n enter: (path) => load(path),\n async up() {\n const listing = state.listing;\n if (listing === null) return;\n const parent = parentOf(listing.path);\n if (parent === listing.path) return;\n await load(parent);\n },\n pick() {\n const target = state.selected ?? state.listing?.path ?? null;\n if (target === null) return;\n deps.onPicked(target);\n },\n cancel() {\n deps.onClose();\n },\n close() {\n deps.onClose();\n },\n select(path) {\n emit({ selected: state.selected === path ? null : path, notice: null });\n },\n toggleHidden() {\n emit({ showHidden: !state.showHidden });\n },\n setDraft(draft) {\n applyDraft(draft);\n },\n async commitDraft() {\n const trimmed = state.draft.trim();\n if (trimmed === '') return;\n await load(trimmed);\n },\n setCreating(name) {\n emit({ creating: name, notice: null });\n },\n async createFolder(name) {\n const checked = validateFolderName(name);\n if (!checked.ok) {\n emit({ notice: checked.reason });\n return;\n }\n const base = state.selected ?? state.listing?.path ?? null;\n if (base === null) {\n emit({ notice: '还没有打开任何目录,无法新建文件夹。' });\n return;\n }\n try {\n const created = await deps.face.createDirectory(base, name.trim());\n emit({ creating: null, notice: null });\n await load(created);\n emit({ selected: created });\n } catch (cause) {\n const failure = humanize(cause);\n emit({ notice: failure.message, creating: null });\n }\n },\n };\n}\n\n/** 图上这一层要画的行(按隐藏开关与草稿末段过滤之后的)。 */\nexport function rowsOf(state: BrowseState): readonly DirectoryEntry[] {\n if (state.listing === null) return [];\n return filterEntries(visibleEntries(state.listing, state.showHidden), state.filter);\n}\n\n/** 图上这一层能不能回上一级(根目录不能)。 */\nexport function canGoUp(state: BrowseState): boolean {\n if (state.listing === null) return false;\n return parentOf(state.listing.path) !== state.listing.path;\n}\n\n/** 「打开」这颗按钮的目标:选中行优先,没选中就是当前层。 */\nexport function targetOf(state: BrowseState): string | null {\n return state.selected ?? state.listing?.path ?? null;\n}\n\n/** 新建文件夹那一行要展示的位置(\"在 <这里> 下新建\")。 */\nexport function createBaseOf(state: BrowseState): string | null {\n return state.selected ?? state.listing?.path ?? null;\n}\n\n/** 一段路径拼成可读的一句话(给用例与调试用;图上不直接显示)。 */\nexport function describe(state: BrowseState): string {\n if (state.phase === 'failed') return `${state.failure?.code ?? 'browse-failed'} @ ${state.draft}`;\n if (state.listing === null) return `${state.phase}`;\n return `${state.listing.path}(${rowsOf(state).length} 行)`;\n}\n\n/** 一行子目录的完整路径(图上点行时用)。 */\nexport function entryPath(directory: string, entry: DirectoryEntry): string {\n return entry.path === '' ? joinPath(directory, entry.name) : entry.path;\n}\n\n/** 目录行接线的返回:**不碰 React**——`actions` 原样喂给 `directory-browser-ui` 的组件 props,\n * `isOpen`/`setOpen` 由调用方自己接进它的状态(宿主怎么重画是它的事,本件不管)。 */\nexport interface DirectoryRowBrowser {\n readonly actions: {\n readonly onPick: () => void;\n readonly onClose: () => void;\n readonly onEnter: (path: string) => void;\n readonly onUp: () => void;\n readonly onSelect: (path: string) => void;\n readonly onToggleHidden: () => void;\n readonly onDraft: (draft: string) => void;\n readonly onCommitDraft: () => void;\n readonly onCreate: (name: string) => void;\n readonly onCreatingChange: (name: string | null) => void;\n };\n /** 开图(先试配置里那个目录,读不出来退回家目录),回第一份落定的状态。 */\n open(): Promise<BrowseState>;\n /** 状态变更订阅:视图那半靠 `useSyncExternalStore` 用它重画。\n *\n * **没有它图上只有第一帧**——列举是异步落定的,React 不订阅就永远停在开图那一刻的状态。 */\n subscribe(listener: BrowseListener): () => void;\n state(): BrowseState;\n /** 一段可读的当前状态(用例与调试用)。 */\n summary(): string;\n}\n\n/** 建一份「目录行接线」:给一个取数面与初值,回一组动作。\n *\n * 只做接线:把控制器算出来的状态与回调打包好,不取数、不画图、不认宿主。 */\nexport function createDirectoryRowBrowser(deps: {\n readonly face: DirectoryBrowseFace;\n readonly initialPath: string;\n readonly onPicked: (path: string) => void;\n readonly onClosed?: () => void;\n readonly failureLabel?: string;\n /** 可跳转的根从哪来(各家给的是同一份「总管电话」的取数器);不给就没有那一行。 */\n readonly rootsSource?: () => Promise<readonly RootRow[]>;\n}): DirectoryRowBrowser {\n const controller = createBrowseController({\n face: deps.face,\n initialPath: deps.initialPath,\n onPicked: deps.onPicked,\n onClose: () => deps.onClosed?.(),\n ...(deps.failureLabel === undefined ? {} : { failureLabel: deps.failureLabel }),\n ...(deps.rootsSource === undefined ? {} : { rootsSource: deps.rootsSource }),\n });\n return {\n open: () => controller.open(),\n subscribe: (listener) => controller.subscribe(listener),\n state: () => controller.getState(),\n summary: () => describe(controller.getState()),\n actions: {\n onPick: () => controller.pick(),\n onClose: () => controller.cancel(),\n onEnter: (path) => void controller.enter(path),\n onUp: () => void controller.up(),\n onSelect: (path) => controller.select(path),\n onToggleHidden: () => controller.toggleHidden(),\n onDraft: (draft) => controller.setDraft(draft),\n onCommitDraft: () => void controller.commitDraft(),\n onCreate: (name) => void controller.createFolder(name),\n onCreatingChange: (name) => controller.setCreating(name),\n },\n };\n}\n\n/** 一条目录行点下去该走哪条路(**六家共用这一处策略**,单独写在各家就会各错各的)。\n *\n * 顺序是「先应用内浏览,被拒再换系统对话框」,因为客户端命名空间上三条动词都在、问不出组合里服务哪种\n * 能力(见 `pickerModeOf`)。回执:\n * - `undefined`=连两格浏览原语都没有(这条路没有,调用方自己想办法);\n * - `'refused'`=宿主回了 `refusalCode`(组合里是系统对话框),图已收起;\n * - `'open'`=图开着,后面的事都在图里。\n *\n * **状态由调用方持有**:`onRow` 收到刚建好的那条(或 null=收起),React 那边据此决定画不画图。\n *\n * `refusalCode` 是**宿主的回执码**(各家自己从宿主镜像里拿,本件只照比不认名):\n * 它换名字本件照样跑,六家一起换。 */\nexport function openRowBrowser(input: {\n readonly picker: unknown;\n readonly initialPath: string;\n readonly onChange: (next: string) => void;\n readonly onRow: (row: DirectoryRowBrowser | null) => void;\n readonly refusalCode: string;\n /** 可跳转的根从哪来(可选:不给就是图上没有那一行)。 */\n readonly rootsSource?: () => Promise<readonly RootRow[]>;\n}): Promise<'open' | 'refused'> | undefined {\n const face = browseFaceOf(input.picker);\n if (face === null) return undefined;\n const row = createDirectoryRowBrowser({\n face,\n initialPath: input.initialPath,\n onPicked: (picked) => {\n input.onChange(picked);\n input.onRow(null);\n },\n onClosed: () => input.onRow(null),\n ...(input.rootsSource === undefined ? {} : { rootsSource: input.rootsSource }),\n });\n input.onRow(row);\n return row.open().then((settled) => {\n if (settled.phase === 'failed' && settled.failure?.code === input.refusalCode) {\n input.onRow(null);\n return 'refused' as const;\n }\n return 'open' as const;\n });\n}\n","/** 文件浏览器视图半的**零件**:样式表与四行控件(面包屑/根/一行子目录/新建行)。\n *\n * 为什么拆出来:整张对话框的组版与这些零件的变化频率不同——零件跟着「图上多一行/少一行」动,\n * 组版跟着「对话框长什么样」动。拆开之后两边都在 350 行告警线内,而对外面不变\n * (`directory-browser-ui.ts` 照旧转出组件与两个接线函数)。\n *\n * 本件与视图半同样**不认识宿主**:只认状态与文案。\n */\n\nimport * as React from 'react';\nimport type { BrowseState } from './directory-browser-state.js';\nimport type { DirectoryEntry, RootKind } from './directory-browser-contract.js';\n\n/** 图上出现的每一句文案;调用方给(本件不自造口径)。 */\nexport interface DirectoryBrowserLabels {\n /** 对话框标题,例:「选择数据目录」。 */\n readonly title: string;\n readonly close: string;\n readonly up: string;\n readonly pathPlaceholder: string;\n readonly go: string;\n readonly showHidden: (count: number) => string;\n readonly empty: string;\n readonly loading: string;\n readonly newFolder: string;\n readonly createConfirm: string;\n readonly createCancel: string;\n readonly select: string;\n readonly selected: string;\n readonly open: string;\n readonly cancel: string;\n /** 选中预览那一行的前缀,例:「将选定:」。 */\n readonly willPick: string;\n}\n\n/** 根的类型说明(悬停可见):**本件自己的词汇**,与取数方无关。 */\nconst ROOT_KIND_TEXT: Record<RootKind, string> = {\n fixed: '固定磁盘',\n network: '网络驱动器',\n removable: '可移动磁盘',\n optical: '光驱',\n other: '磁盘',\n};\n\nexport const S = {\n scrim: {\n position: 'fixed',\n inset: 0,\n background: 'rgba(0,0,0,0.35)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n zIndex: 9999,\n } as React.CSSProperties,\n dialog: {\n width: 680,\n maxWidth: '92vw',\n height: 500,\n maxHeight: '86vh',\n display: 'flex',\n flexDirection: 'column',\n background: 'var(--dsw-alias-bg-layer-1, #1f1f23)',\n color: 'var(--dsw-alias-label-primary, inherit)',\n border: '1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.35))',\n borderRadius: 10,\n boxShadow: '0 12px 32px rgba(0,0,0,0.35)',\n overflow: 'hidden',\n } as React.CSSProperties,\n head: {\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n padding: '10px 12px',\n borderBottom: '1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.25))',\n } as React.CSSProperties,\n title: { fontSize: 14, fontWeight: 700 } as React.CSSProperties,\n crumbs: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 2, padding: '8px 12px 0', fontSize: 12 } as React.CSSProperties,\n chromeButton: {\n background: 'transparent',\n border: 'none',\n color: 'var(--dsw-alias-label-primary, inherit)',\n cursor: 'pointer',\n padding: '2px 4px',\n fontSize: 12,\n opacity: 0.85,\n } as React.CSSProperties,\n crumbSep: { opacity: 0.45, fontSize: 12 } as React.CSSProperties,\n roots: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 6, padding: '0 12px 8px', fontSize: 12 } as React.CSSProperties,\n rootButton: {\n border: '1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.35))',\n borderRadius: 6,\n padding: '3px 8px',\n opacity: 1,\n } as React.CSSProperties,\n pathRow: { display: 'flex', gap: 6, padding: '8px 12px' } as React.CSSProperties,\n input: {\n flex: 1,\n minWidth: 0,\n fontSize: 12,\n padding: '5px 8px',\n background: 'var(--dsw-alias-bg-layer-2, rgba(128,128,128,0.12))',\n color: 'inherit',\n border: '1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.35))',\n borderRadius: 6,\n } as React.CSSProperties,\n button: {\n fontSize: 12,\n padding: '5px 10px',\n cursor: 'pointer',\n background: 'var(--dsw-alias-bg-layer-2, rgba(128,128,128,0.12))',\n color: 'inherit',\n border: '1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.35))',\n borderRadius: 6,\n } as React.CSSProperties,\n buttonOff: { opacity: 0.45, cursor: 'default' } as React.CSSProperties,\n list: {\n flex: 1,\n minHeight: 0,\n overflowY: 'auto',\n margin: '0 12px',\n border: '1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.25))',\n borderRadius: 6,\n } as React.CSSProperties,\n row: {\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n width: '100%',\n padding: '6px 10px',\n textAlign: 'left',\n background: 'transparent',\n color: 'inherit',\n border: 'none',\n borderBottom: '1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.12))',\n cursor: 'pointer',\n fontSize: 13,\n } as React.CSSProperties,\n rowSelected: { background: 'var(--dsw-alias-bg-layer-2, rgba(128,128,128,0.18))' } as React.CSSProperties,\n empty: { padding: '14px 12px', fontSize: 12, opacity: 0.7 } as React.CSSProperties,\n footNote: { padding: '6px 12px 0', fontSize: 12, minHeight: 20 } as React.CSSProperties,\n foot: {\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n padding: '10px 12px',\n borderTop: '1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.25))',\n } as React.CSSProperties,\n target: { marginRight: 'auto', fontSize: 12, opacity: 0.8, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } as React.CSSProperties,\n error: { color: 'var(--dsw-alias-label-error, #e66)' } as React.CSSProperties,\n} as const;\n\n/** 面包屑:从文件系统根到当前层,每一格都是跳转目标(`listing.crumbs` 由取数方给)。 */\nexport function crumbRow(state: BrowseState, onEnter: (path: string) => void): React.ReactElement | null {\n const listing = state.listing;\n if (listing === null || listing.crumbs.length === 0) return null;\n const parts: React.ReactNode[] = [];\n listing.crumbs.forEach((crumb, index) => {\n if (index > 0) parts.push(React.createElement('span', { key: 'sep-' + crumb.path, style: S.crumbSep }, '›'));\n const isCurrent = crumb.path === listing.path;\n parts.push(\n React.createElement(\n 'button',\n {\n key: crumb.path,\n type: 'button',\n style: { ...S.chromeButton, fontWeight: isCurrent ? 700 : 400 },\n title: crumb.path,\n onClick: () => onEnter(crumb.path),\n },\n // 每一格都写**它自己那一层的名字**:第一格是文件系统根(`C:\\`),不是「上一级」——\n // 「上一级」是路径行那颗按钮的活(#744 返修:这一格原先串了那颗按钮的文案)。\n crumb.name,\n ),\n );\n });\n return React.createElement('div', { style: S.crumbs }, parts);\n}\n\n/** 可跳转的根那一行(Windows 上的盘符等):**就是本机全部磁盘**,故不加行首说明。\n * **少于两个根就不画**:一个根没有「跳到别处」可言。 */\nexport function rootsRow(state: BrowseState, onEnter: (path: string) => void): React.ReactElement | null {\n if (state.roots.length < 2) return null;\n const parts: React.ReactNode[] = state.roots.map((root) =>\n React.createElement(\n 'button',\n {\n key: root.path,\n type: 'button',\n style: { ...S.chromeButton, ...S.rootButton },\n title: ROOT_KIND_TEXT[root.kind] ?? ROOT_KIND_TEXT.other,\n onClick: () => onEnter(root.path),\n },\n root.path,\n ),\n );\n return React.createElement('div', { style: S.roots }, parts);\n}\n\n/** 一行子目录:点名字进它,点「选」把它当目标(「选」再点一次取消)。 */\nexport function entryRow(\n entry: DirectoryEntry,\n selected: boolean,\n labels: DirectoryBrowserLabels,\n onToggleSelect: (path: string) => void,\n onEnter: (path: string) => void,\n): React.ReactElement {\n return React.createElement(\n 'div',\n { key: entry.path, style: { ...S.row, padding: 0, ...(selected ? S.rowSelected : {}) } },\n React.createElement(\n 'button',\n { type: 'button', style: { ...S.row, flex: 1, border: 'none' }, onClick: () => onEnter(entry.path) },\n '📁 ' + entry.name,\n ),\n React.createElement(\n 'button',\n {\n type: 'button',\n style: { ...S.button, marginRight: 8, padding: '2px 8px' },\n 'aria-pressed': selected,\n title: selected ? labels.selected : labels.select,\n onClick: () => onToggleSelect(entry.path),\n },\n selected ? '✓' : labels.select,\n ),\n );\n}\n\n/** 「新建文件夹」那一行(`creatingName === null` 时收起)。 */\nexport function createRow(\n labels: DirectoryBrowserLabels,\n onName: (name: string) => void,\n onCancel: () => void,\n onCreate: (name: string) => void,\n creatingName: string | null,\n): React.ReactElement | null {\n if (creatingName === null) return null;\n return React.createElement(\n 'div',\n { style: { ...S.pathRow, borderTop: '1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.25))' } },\n React.createElement('input', {\n style: S.input,\n value: creatingName,\n placeholder: labels.newFolder,\n 'aria-label': labels.newFolder,\n onChange: (event: React.ChangeEvent<HTMLInputElement>) => onName(event.currentTarget.value),\n onKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => {\n if (event.key === 'Enter') onCreate(creatingName);\n if (event.key === 'Escape') onCancel();\n },\n }),\n React.createElement('button', { type: 'button', style: S.button, onClick: () => onCreate(creatingName) }, labels.createConfirm),\n React.createElement('button', { type: 'button', style: S.button, onClick: onCancel }, labels.createCancel),\n );\n}\n","/** 文件浏览器的视图半:把 `BrowseState` 画成一张对话框。**不认识宿主**,也不自己做 IO。\n *\n * 手写 `React.createElement`(本包 client 束禁 JSX,见 `tsdown.config.ts` 的既成口径)。\n * 只读状态、只回调;进哪一层、选到什么由操作半(`directory-browser-state.ts`)算。\n * 样式与四行控件在 `directory-browser-parts.ts`(那里跟着「多一行/少一行」动,本件只管组版)。\n */\n\nimport * as React from 'react';\nimport { rowsOf, targetOf } from './directory-browser-state.js';\nimport type { BrowseState, DirectoryRowBrowser } from './directory-browser-state.js';\nimport { hiddenCount } from './directory-browser-contract.js';\nimport type { DirectoryListing } from './directory-browser-contract.js';\nimport { S, createRow, crumbRow, entryRow, rootsRow } from './directory-browser-parts.js';\nimport type { DirectoryBrowserLabels } from './directory-browser-parts.js';\n\nexport type { DirectoryBrowserLabels } from './directory-browser-parts.js';\n\n/** 组件 props:样式只吃这一格,别的一律不给。 */\nexport interface DirectoryBrowserProps {\n readonly open: boolean;\n readonly state: BrowseState;\n readonly labels: DirectoryBrowserLabels;\n readonly onPick: () => void;\n readonly onClose: () => void;\n readonly onEnter: (path: string) => void;\n readonly onUp: () => void;\n readonly onSelect: (path: string) => void;\n readonly onToggleHidden: () => void;\n readonly onDraft: (draft: string) => void;\n readonly onCommitDraft: () => void;\n readonly onCreate: (name: string) => void;\n readonly onCreatingChange: (name: string | null) => void;\n}\n\nconst EMPTY_LISTING: DirectoryListing = { path: '', home: '', crumbs: [], entries: [], truncated: false };\n\n/** 画一张文件浏览器对话框。`open=false` 时返回 null(本件不挂 portal,由调用方决定挂在哪)。 */\nexport function DirectoryBrowser(props: DirectoryBrowserProps): React.ReactElement | null {\n const { open, state, labels } = props;\n if (!open) return null;\n const rows = rowsOf(state);\n const target = targetOf(state);\n const listing = state.listing ?? EMPTY_LISTING;\n const hidden = hiddenCount(listing);\n const body: React.ReactNode[] = [];\n body.push(crumbRow(state, props.onEnter));\n body.push(rootsRow(state, props.onEnter));\n body.push(\n React.createElement(\n 'div',\n { style: S.pathRow },\n React.createElement('input', {\n style: S.input,\n value: state.draft,\n placeholder: labels.pathPlaceholder,\n 'aria-label': labels.pathPlaceholder,\n onChange: (event: React.ChangeEvent<HTMLInputElement>) => props.onDraft(event.currentTarget.value),\n onKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => {\n if (event.key === 'Enter') props.onCommitDraft();\n },\n }),\n React.createElement('button', { type: 'button', style: S.button, onClick: props.onUp }, labels.up),\n React.createElement('button', { type: 'button', style: S.button, onClick: props.onCommitDraft }, labels.go),\n ),\n );\n body.push(\n React.createElement(\n 'div',\n { style: S.list, role: 'listbox', 'aria-label': labels.title },\n rows.length === 0\n ? React.createElement('div', { style: S.empty }, state.phase === 'loading' ? labels.loading : labels.empty)\n : rows.map((entry) => entryRow(entry, state.selected === entry.path, labels, props.onSelect, props.onEnter)),\n ),\n );\n body.push(createRow(labels, props.onDraft, () => props.onCreatingChange(null), props.onCreate, state.creating));\n body.push(\n React.createElement(\n 'div',\n { style: S.footNote },\n hidden > 0\n ? React.createElement(\n 'label',\n { style: { fontSize: 12, cursor: 'pointer', marginRight: 10 } },\n React.createElement('input', { type: 'checkbox', checked: state.showHidden, onChange: props.onToggleHidden }),\n ' ' + labels.showHidden(hidden),\n )\n : null,\n state.creating === null\n ? React.createElement('button', { type: 'button', style: { ...S.chromeButton, textDecoration: 'underline' }, onClick: () => props.onCreatingChange('') }, labels.newFolder)\n : null,\n state.notice !== null ? React.createElement('div', { style: S.error }, state.notice) : null,\n state.phase === 'failed' && state.failure !== null\n ? React.createElement('div', { style: S.error }, state.failure.message)\n : null,\n ),\n );\n body.push(\n React.createElement(\n 'div',\n { style: S.foot },\n React.createElement('span', { style: S.target, title: target ?? '' }, target === null ? '' : labels.willPick + target),\n React.createElement(\n 'button',\n { type: 'button', style: { ...S.button, ...(target === null ? S.buttonOff : {}) }, onClick: props.onPick },\n labels.open,\n ),\n React.createElement('button', { type: 'button', style: S.button, onClick: props.onClose }, labels.cancel),\n ),\n );\n return React.createElement(\n 'div',\n {\n style: S.scrim,\n role: 'presentation',\n onMouseDown: (event: React.MouseEvent<HTMLDivElement>) => {\n if (event.target === event.currentTarget) props.onClose();\n },\n },\n React.createElement(\n 'div',\n { style: S.dialog, role: 'dialog', 'aria-modal': true, 'aria-label': labels.title },\n React.createElement(\n 'div',\n { style: S.head },\n React.createElement('span', { style: S.title }, labels.title),\n // 标题右侧**不再重复当前路径**:同一条路径下面已经出现两次(面包屑与路径框),\n // 而盘符那行也要地方——头一行越干净越好(#744 第三轮现场反馈)。\n React.createElement(\n 'button',\n { type: 'button', style: { ...S.button, marginLeft: 'auto' }, 'aria-label': labels.close, title: labels.close, onClick: props.onClose },\n '✕',\n ),\n ),\n body,\n ),\n );\n}\n\n/** 目录行接线的 React 半边:把操作半那组动作直接喂给组件。\n *\n * 操作半(`directory-browser-state.ts` 的 `createDirectoryRowBrowser`)不碰 React,\n * 这里只把它的 `actions` 摊进 props —— 于是「进哪一层」那套在 Node 里可测,这里只做接线。\n *\n * `useSyncExternalStore` 是**必需**的:列举是异步落定的,不订阅的话这一帧画完就再没有重画,\n * 图上会永远停在「正在读取…」(#744 实测)。 */\nexport function DirectoryBrowserFromRow(props: {\n readonly open: boolean;\n readonly row: DirectoryRowBrowser;\n readonly labels: DirectoryBrowserLabels;\n}): React.ReactElement | null {\n const state = React.useSyncExternalStore(props.row.subscribe, props.row.state);\n return DirectoryBrowser({\n open: props.open,\n state,\n labels: props.labels,\n ...props.row.actions,\n });\n}\n\nexport { createBrowseController, createDirectoryRowBrowser, describe, rowsOf, targetOf } from './directory-browser-state.js';\nexport type { BrowseController, BrowseState, DirectoryRowBrowser } from './directory-browser-state.js';\nexport type { DirectoryEntry, DirectoryListing, RootKind, RootRow } from './directory-browser-contract.js';\n","/** 「本机根清单」的取数接线(客户端半):把一通电话变成图上那一行可跳转的根。\n *\n * **不认识宿主**:载体基段、通道名(第二段)、电话名全由调用方传进来——各家从自己的宿主镜像里拿\n * 那几个字面量(和 `openRowBrowser` 的 `refusalCode` 同一条规矩)。本件只做两件事:\n * 发一次调用、把回执信封拆成根清单;**任何失败都当空清单**(图上少画一行,不报错、不挡路)。\n */\n\nimport type { RootKind, RootRow } from './directory-browser-contract.js';\n\n/** 取数口:`connection.rpc.call` 的形状,每次现取(连接后到也不永久缺席)。 */\nexport type RootsCall = (\n base: string,\n endpoint: string,\n payload: unknown,\n signal?: AbortSignal,\n) => Promise<unknown>;\n\n/** 认一认某个值像不像一个根(认不出的行丢掉:图的干净比多画一行重要)。 */\nfunction isRootRow(raw: unknown): raw is RootRow {\n if (typeof raw !== 'object' || raw === null) return false;\n const row = raw as { path?: unknown; kind?: unknown };\n return typeof row.path === 'string' && row.path !== '' && typeof row.kind === 'string';\n}\n\n/** 信封 → 根清单(纯函数,**永不抛**):`{ok:true, value:{roots:[…]}}` 之外的形状一律当空。 */\nexport function readRootsAnswer(raw: unknown): readonly RootRow[] {\n const answer = (typeof raw === 'object' && raw !== null ? raw : {}) as { ok?: unknown; value?: unknown };\n if (answer.ok !== true) return [];\n const value = (typeof answer.value === 'object' && answer.value !== null ? answer.value : {}) as { roots?: unknown };\n if (!Array.isArray(value.roots)) return [];\n return value.roots.filter(isRootRow).map((row) => ({ path: row.path, kind: row.kind as RootKind }));\n}\n\n/** 建一个「根清单取数器」:给调用口与三个名字,回一个 `() => Promise<RootRow[]>`。\n *\n * 界面按需调它(开图一次),不在渲染里调,也不缓存到这里——缓存是宿主半的事。 */\nexport function createRootsSource(input: {\n readonly getCall: () => unknown;\n readonly base: string;\n readonly endpoint: string;\n readonly method: string;\n}): () => Promise<readonly RootRow[]> {\n return async () => {\n const call = input.getCall();\n if (typeof call !== 'function') return [];\n try {\n return readRootsAnswer(\n await (call as RootsCall)(input.base, input.endpoint, { method: input.method, payload: {} }),\n );\n } catch {\n return [];\n }\n };\n}\n","/** dsh-life-pack client 适配器(六边形:browser 侧;爱生活单卡方案)。\n *\n * 真实 DSH 契约(出处见 docs/agents/dsh-client-contract.md §5/§11):\n * - 本文件经 tsdown 打成 loader 工厂包(browser/CJS + 注册包裹),classic 执行\n * 只注册,副作用全在 factory 内;传递闭包禁 node 内建与 ESM 语法(回路\n * test/client-bundle-48.test.mjs 看门)。\n * - inject 短名 ['slots','connection']:本包现在有 RPC(装与更新的能力由宿主半提供,\n * 电话名与轮询间隔由宿主转交,面板不写死),读了 ctx.connection 就必须声明 'connection'(#48 血例)。\n * - 爱生活页签条走 slot 驱动(settings-plugins 先例,禁纯 useState 手画 tab):\n * section 声明 children 爱生活页签槽,各技能往里注册自家技能设置页;\n * tab 行从 ledger 投影(entries+getVersion+subscribe),面板用 props.renderSlot\n * 按 `{only}` 投影(一次只挂载一个)+ visited 缓存;ledger 无某技能=缺席,\n * 显示推荐安装只读文本(不返空冒充)。\n * - 组件 React.createElement 手写(禁 JSX),无数据轮询、无 document/window/process。\n * - 面板外壳两处(票 #679):标题行右上角两个入口(星/气泡,悬停出说明文字,\n * 窄窗口折到标题下方);页签面板之后是底部「作者其他插件」引流卡(四行,点开新窗口)。\n * 文案与网址都取自 nav.ts 的两张静态表,本文件只画不算。\n */\n\nimport * as React from 'react';\nimport { MANAGER_PLUGIN, MANAGER_TABS, MORE_PLUGINS, PANEL_LINKS, recoFor } from './nav.js';\nimport type { ManagerTab } from './nav.js';\nimport { AbsentCard, CheckUpdateButton, UpdateResults, useUpdateRows } from './update-panel.js';\nimport { loadManagerVersion } from './update-client.js';\nimport type { CallFace } from './update-client.js';\nimport { CONFIG_TAB_SLOT, VERSION_UNKNOWN } from './update-contract.js';\nimport { summaryErrorOf, useHealthPanel } from './health-panel.js';\nimport { HealthSummaryLine, HealthTable, STATUS_TEXT, TAB_DOT, TAB_NOTE_STYLE, lightsOf, tabDotColor, tabNote } from './health-view.js';\nimport { HEALTH_ENDPOINT } from './health-contract.js';\nimport type {\n ClientCtx,\n ConfigTabRow,\n LifePackSectionProps,\n SlotLedgerEntry,\n} from './dsh-ctx.js';\n\nexport const inject = ['slots', 'connection'];\n\n/** 爱生活页签槽名:定义在 `update-contract.ts`(宿主判「已装产物有没有注册代码」也用这个名字,一处定义)。 */\nexport { CONFIG_TAB_SLOT } from './update-contract.js';\n\n/** 共用件「目录浏览器」从包门转出(票 #744):六个单品插件的设置页要用的就是这两样——\n * 「开图接线 + 入口三态判定」(纯逻辑,`./directory-browser` 子路径)与\n * 「对话框组件」(`./directory-browser-ui` 子路径,吃 React)。\n *\n * 六家的 client 束**不能**从本条 `./client` 取:这一条是 loader 工厂包\n * (`window.__ModuleLoader__.load(...)` 的注册壳),`require` 拿到的是空 exports。\n * 所以对外要用的是那两个普通子路径;这里只是同源转出,供本包内部与用例使用。 */\nexport { DirectoryBrowserFromRow } from './directory-browser-ui.js';\nexport type { DirectoryBrowserLabels, DirectoryBrowserProps } from './directory-browser-ui.js';\nexport { createBrowseController, createDirectoryRowBrowser, rowsOf, canGoUp, targetOf, entryPath } from './directory-browser-state.js';\nexport type { BrowseController, BrowseState, DirectoryRowBrowser } from './directory-browser-state.js';\nexport { pickerModeOf, readPickAnswer } from './directory-browser-contract.js';\nexport type { DirectoryBrowseFace, DirectoryListing, PickOutcome, RootKind, RootRow } from './directory-browser-contract.js';\nexport { createRootsSource, readRootsAnswer } from './directory-browser-roots.js';\n\n/** 包名 → 本家那条客户端通道(票 #735)。取值面是导航表那份镜像,不再从页签槽账本的自定义选项里读:\n * 装机槽位面不透传自定义键,读了恒是空串(详见 `nav.ts` 上 `ManagerTab.channel` 的注释)。 */\nconst CHANNEL_BY_PLUGIN = new Map(MANAGER_TABS.map((tab) => [tab.plugin, tab.channel]));\n\n/** 总管视觉(内联 style;颜色走 DSH 主题别名,深浅主题自适应,写死值只做回退;与技能面板同语言)。 */\nconst S = {\n headRow: {\n display: 'flex',\n flexWrap: 'wrap',\n alignItems: 'center',\n gap: 8,\n margin: '2px 0 8px',\n } as React.CSSProperties,\n head: { fontSize: 15, fontWeight: 700, flex: '0 0 auto', color: 'var(--dsw-alias-label-primary, inherit)' } as React.CSSProperties,\n headActions: {\n display: 'inline-flex',\n alignItems: 'center',\n gap: 2,\n marginLeft: 'auto',\n flex: '0 0 auto',\n } as React.CSSProperties,\n iconLink: {\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: 26,\n height: 26,\n borderRadius: 6,\n border: '1px solid transparent',\n color: 'var(--dsw-alias-label-primary, inherit)',\n textDecoration: 'none',\n fontSize: 14,\n lineHeight: 1,\n } as React.CSSProperties,\n meta: { color: 'var(--dsw-alias-label-secondary, #9a9a9a)', fontSize: 12, lineHeight: 1.7, marginBottom: 10 } as React.CSSProperties,\n tablist: { display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: 10 } as React.CSSProperties,\n tab: {\n border: '1px solid var(--dsw-alias-border, rgba(128,128,128,.35))',\n background: 'transparent',\n color: 'var(--dsw-alias-label-primary, inherit)',\n borderRadius: 999,\n padding: '4px 12px',\n fontSize: 13,\n cursor: 'pointer',\n } as React.CSSProperties,\n tabActive: {\n background: 'var(--dsw-alias-brand-primary, #0a84ff)',\n borderColor: 'transparent',\n color: '#fff',\n fontWeight: 700,\n } as React.CSSProperties,\n reco: {\n padding: '12px 14px',\n borderRadius: 10,\n border: '1px dashed var(--dsw-alias-border, rgba(128,128,128,.45))',\n color: 'var(--dsw-alias-label-secondary, #9a9a9a)',\n fontSize: 13,\n lineHeight: 1.7,\n } as React.CSSProperties,\n cmd: {\n display: 'block',\n marginTop: 8,\n padding: '8px 10px',\n borderRadius: 8,\n background: 'var(--dsw-alias-bg-base, rgba(128,128,128,.12))',\n color: 'var(--dsw-alias-label-primary, inherit)',\n fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',\n fontSize: 12,\n userSelect: 'all',\n } as React.CSSProperties,\n moreCard: {\n marginTop: 16,\n padding: '10px 12px',\n borderRadius: 10,\n border: '1px solid var(--dsw-alias-border, rgba(128,128,128,.35))',\n } as React.CSSProperties,\n moreTitle: {\n fontSize: 13,\n fontWeight: 700,\n marginBottom: 4,\n color: 'var(--dsw-alias-label-primary, inherit)',\n } as React.CSSProperties,\n moreRow: {\n display: 'flex',\n flexWrap: 'wrap',\n alignItems: 'center',\n gap: '2px 8px',\n padding: '7px 4px',\n textDecoration: 'none',\n color: 'inherit',\n } as React.CSSProperties,\n morePkg: {\n flex: '0 1 auto',\n minWidth: 0,\n overflowWrap: 'anywhere',\n fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',\n fontSize: 12,\n fontWeight: 650,\n } as React.CSSProperties,\n moreDesc: {\n flex: '1 1 180px',\n minWidth: 0,\n fontSize: 11.5,\n lineHeight: 1.6,\n color: 'var(--dsw-alias-label-secondary, #9a9a9a)',\n } as React.CSSProperties,\n moreIcon: {\n flex: '0 0 auto',\n marginLeft: 'auto',\n display: 'inline-flex',\n color: 'var(--dsw-alias-label-secondary, #9a9a9a)',\n } as React.CSSProperties,\n};\n\n/** 缺席卡的画法与流程住 `update-panel.ts`(票 #678:一句人话 +「装上」按钮 + 可复制命令),\n * `recoFor` 的补装命令仍是卡里那条辅助展示(B11 口径不变)。 */\n\n/** 标签解析(resolveSlotLabel 同形:thunk 跟活,无则空字串;见 slots lib:27-29)。 */\nfunction resolveLabel(label: SlotLedgerEntry['options']['label']): string {\n if (typeof label === 'function') return label();\n return label ?? '';\n}\n\n/** 外链图标(行尾那个;纯装饰,语义在链接自己的 aria-label/文字上)。 */\nfunction ExternalIcon(): React.ReactElement {\n return React.createElement(\n 'svg',\n {\n viewBox: '0 0 24 24',\n width: 13,\n height: 13,\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: 1.9,\n strokeLinecap: 'round',\n strokeLinejoin: 'round',\n 'aria-hidden': 'true',\n focusable: 'false',\n },\n React.createElement('path', { d: 'M13.5 4.5H19.5V10.5' }),\n React.createElement('path', { d: 'M19.5 4.5L11 13' }),\n React.createElement('path', { d: 'M18 14.5V18A2 2 0 0 1 16 20H6A2 2 0 0 1 4 18V8A2 2 0 0 1 6 6H9.5' }),\n );\n}\n\n/** 标题行右上角两件:星(去本仓点 Star)+ 气泡(去本仓开 issue),悬停出说明文字。\n *\n * 三件并排里的第三件「检查更新」住隔壁票(#678):本票只留位子——它就接在本组件之前,\n * 不画一个点了没反应的假按钮。窄窗口靠 headRow 的 flexWrap 折到标题下方,\n * 标题与两件都是 flex:'0 0 auto',谁也不挤谁。\n */\nfunction PanelActions(): React.ReactElement {\n return React.createElement(\n 'div',\n { style: S.headActions },\n PANEL_LINKS.map((link) =>\n React.createElement(\n 'a',\n {\n key: link.key,\n href: link.url,\n target: '_blank',\n rel: 'noreferrer',\n title: link.tip,\n 'aria-label': link.tip,\n style: S.iconLink,\n },\n React.createElement('span', { 'aria-hidden': 'true' }, link.glyph),\n ),\n ),\n );\n}\n\n/** 底部「作者其他插件」引流卡:四行(包名 + 一句说明 + 行尾外链图标),点开新窗口。\n *\n * 卡片在页签面板之后,六页签切换它都在(它不属于任何一个页签)。\n */\nfunction MorePluginsCard(): React.ReactElement {\n return React.createElement(\n 'div',\n { style: S.moreCard },\n React.createElement('div', { style: S.moreTitle }, '作者其他插件'),\n MORE_PLUGINS.map((row) =>\n React.createElement(\n 'a',\n {\n key: row.pkg,\n href: row.url,\n target: '_blank',\n rel: 'noreferrer',\n title: row.pkg + ' 的 GitHub 仓库',\n style: S.moreRow,\n },\n React.createElement('span', { style: S.morePkg }, row.pkg),\n React.createElement('span', { style: S.moreDesc }, row.desc),\n React.createElement('span', { style: S.moreIcon }, React.createElement(ExternalIcon, null)),\n ),\n ),\n );\n}\n\n/** 总管自述版本(票 #737):**不手写**,问宿主——宿主读自己这份已安装包的 `package.json`。\n *\n * 单次取数(`loadManagerVersion`),无轮询;初值 `unknown`,读不到也停在 `unknown`。\n * 与卡路里 #130 同一条路:面板是浏览器产物、禁 node 内建,读盘只许在宿主半。 */\nfunction useManagerVersion(getCall: () => CallFace | null): string {\n const [version, setVersion] = React.useState<string>(VERSION_UNKNOWN);\n React.useEffect(() => {\n let alive = true;\n void loadManagerVersion(getCall()).then((next) => {\n if (alive) setVersion(next);\n });\n return () => {\n alive = false;\n };\n }, [getCall]);\n return version;\n}\n\n/** 爱生活面板:总设置区 + 检查更新(七家)+ 爱生活页签条(slot 驱动)+ 技能设置页投影/缺席卡。 */\nfunction LifePackSection(props: LifePackSectionProps & { getCall: () => CallFace | null }): React.ReactElement {\n const tabsId = React.useId();\n const tabRefs = React.useRef<Array<HTMLButtonElement | null>>([]);\n const rows: ConfigTabRow[] = props.useTabs((value) => value);\n const present = new Set(rows.map((r) => r.id));\n const face = useUpdateRows(props.getCall);\n const managerVersion = useManagerVersion(props.getCall);\n // 配置体检(#706):一张表六份报告,顶部那行汇总与各家那张表都从它读(同一份数据)。\n // 通道名的来源见 CHANNEL_BY_PLUGIN(#735:账本那一格读不到,改取导航表那份镜像)。\n const healthTabs = React.useMemo(\n () => rows.filter((row) => row.channel.length > 0).map((row) => ({ id: row.id, channel: row.channel })),\n [rows],\n );\n const health = useHealthPanel(() => props.getCall(), healthTabs);\n /** 六家的灯(票 #732):按**页签槽账本**那一排过(装了但没体检通道的也在内,它显缺席态)。 */\n const lights = React.useMemo(\n () => lightsOf(\n rows.map((row) => ({ id: row.id, title: row.label, hasChannel: row.channel.length > 0 })),\n Object.fromEntries(Object.entries(health.rows).map(([id, row]) => [id, row.report ?? undefined])),\n ),\n [rows, health.rows],\n );\n const lightOf = (id: string) => lights.find((light) => light.id === id);\n const [activeId, setActiveId] = React.useState<string | undefined>(undefined);\n const [visitedIds, setVisitedIds] = React.useState<ReadonlySet<string>>(() => new Set());\n const active = MANAGER_TABS.some((t) => t.plugin === activeId)\n ? (activeId as string)\n : MANAGER_TABS[0].plugin;\n React.useEffect(() => {\n setVisitedIds((previous) => {\n if (previous.has(active)) return previous;\n return new Set([...previous, active]);\n });\n }, [active]);\n function labelFor(tab: ManagerTab): string {\n const row = rows.find((r) => r.id === tab.plugin);\n return row && row.label.length > 0 ? row.label : tab.title;\n }\n function targetForTab(tab: ManagerTab) {\n return face.targets.find((t) => t.packageName === tab.plugin) ?? null;\n }\n function onTabKeyDown(event: React.KeyboardEvent, index: number): void {\n let nextIndex: number | undefined;\n switch (event.key) {\n case 'ArrowRight': nextIndex = (index + 1) % MANAGER_TABS.length; break;\n case 'ArrowLeft': nextIndex = (index - 1 + MANAGER_TABS.length) % MANAGER_TABS.length; break;\n case 'Home': nextIndex = 0; break;\n case 'End': nextIndex = MANAGER_TABS.length - 1; break;\n default: return;\n }\n event.preventDefault();\n const next = MANAGER_TABS[nextIndex];\n setActiveId(next.plugin);\n tabRefs.current[nextIndex]?.focus();\n }\n return React.createElement(\n 'div',\n null,\n React.createElement(\n 'div',\n { style: S.headRow },\n React.createElement('div', { style: S.head }, '爱生活'),\n // 三件并排:本票的「检查更新」在左,隔壁票(#679)的星与气泡在右,整组靠右(窄窗口折行)。\n React.createElement(\n 'div',\n { style: S.headActions },\n React.createElement(CheckUpdateButton, { face }),\n React.createElement(PanelActions, null),\n ),\n ),\n React.createElement(\n 'div',\n { style: S.meta },\n // 版本行(#737):包名取自 nav.ts 那处唯一定义,版本号取自宿主读到的装机包版本。\n // 这段 meta 区此前还挂着一行静态文本,说有个「缺省启用、只读的开关」——那个开关今天并不存在\n // (全仓没有任何代码读它或写它,设置页的行表里也没有「启用」这一项),票 #738 原地删除。\n React.createElement('div', null, '总管 ' + MANAGER_PLUGIN + ' · ' + managerVersion),\n ),\n React.createElement(UpdateResults, { face }),\n React.createElement(HealthSummaryLine, {\n lights,\n running: health.running,\n // #735:取数失败也要在这一行看得见(各家的错只画在那家页签里的表上,摘要行沉默=用户以为按钮坏了)。\n error: summaryErrorOf(health),\n onRun: health.run,\n }),\n React.createElement(\n 'div',\n { role: 'tablist', 'aria-label': '爱生活技能页签', style: S.tablist },\n MANAGER_TABS.map((tab, index) => {\n const selected = tab.plugin === active;\n const light = lightOf(tab.plugin);\n // 圆点从「实心/空心=装没装」换成「颜色=体检档位」(票 #732):装了就是实心、\n // 颜色按档位走;没装的保持缺席态、不许点(点它只会跳到一张缺席卡)。\n const dotColor = light === undefined\n ? (present.has(tab.plugin) ? TAB_DOT.unchecked : TAB_DOT.absent)\n : tabDotColor(light);\n const note = light === undefined ? null : tabNote(light);\n return React.createElement(\n 'button',\n {\n key: tab.plugin,\n ref: (element: HTMLButtonElement | null) => {\n tabRefs.current[index] = element;\n },\n id: tabsId + '-tab-' + tab.plugin,\n type: 'button',\n role: 'tab',\n 'aria-selected': selected,\n 'aria-controls': tabsId + '-panel-' + tab.plugin,\n tabIndex: selected ? 0 : -1,\n style: selected ? { ...S.tab, ...S.tabActive } : S.tab,\n onClick: () => {\n setActiveId(tab.plugin);\n },\n onKeyDown: (event: React.KeyboardEvent) => {\n onTabKeyDown(event, index);\n },\n title: light === undefined\n ? '这一家没装(或产物没注册页签槽)'\n : light.title + ':' + (light.hasChannel\n ? (light.status === null ? '还没体检' : '体检读数见本页签下方的表')\n : '没有配置体检出口'),\n },\n React.createElement('span', {\n 'data-ilife-health': 'tab-dot',\n style: {\n width: 6,\n height: 6,\n borderRadius: '50%',\n background: dotColor,\n display: 'inline-block',\n flex: '0 0 auto',\n },\n }),\n labelFor(tab),\n note === null\n ? null\n : React.createElement(\n 'span',\n {\n 'data-ilife-health': 'tab-note',\n // 红黄各自那一截带**自己**的档位色,不许一个色管两档(#706 复评逮过的缺陷)。\n style: { ...TAB_NOTE_STYLE, color: note.status === null ? undefined : STATUS_TEXT[note.status] },\n },\n note.text,\n ),\n );\n }),\n ),\n MANAGER_TABS.filter((tab) => tab.plugin === active || visitedIds.has(tab.plugin)).map((tab) => {\n const selected = tab.plugin === active;\n return React.createElement(\n 'div',\n {\n key: tab.plugin,\n id: tabsId + '-panel-' + tab.plugin,\n role: 'tabpanel',\n 'aria-labelledby': tabsId + '-tab-' + tab.plugin,\n hidden: !selected,\n },\n present.has(tab.plugin)\n ? React.createElement(\n 'div',\n null,\n props.renderSlot(CONFIG_TAB_SLOT, {}, { only: tab.plugin }) as React.ReactNode,\n // 那家的体检表:与总览那行读同一份快照(`health.rows`),不是各算一遍。\n React.createElement(HealthTable, {\n title: labelFor(tab),\n phase: health.rows[tab.plugin]?.phase ?? 'idle',\n report: health.rows[tab.plugin]?.report ?? null,\n error: health.rows[tab.plugin]?.error ?? null,\n }),\n )\n : React.createElement(AbsentCard, {\n target: targetForTab(tab),\n fallbackCommand: recoFor(tab).installCmd,\n // 账本事实与装机读数分开给:缺席卡按两处事实分三态,不许互相顶替(见 update-view.ts 的 slotStateOf)。\n inLedger: present.has(tab.plugin),\n face,\n }),\n );\n }),\n React.createElement(MorePluginsCard, null),\n );\n}\n\nexport function apply(ctx: ClientCtx): void {\n // 调用口取用器:每次取数时现取(connection 后到也不永久缺席),透传给面板组件。\n const getCall = (): CallFace | null => (ctx.connection?.rpc?.call as CallFace | undefined) ?? null;\n // ledger 观测源(settings-plugins:1744-1767 同形;无 locale 面声明,故只订 ledger)。\n let tabsVersion = -1;\n let tabs: ConfigTabRow[] = [];\n const sectionInjected = () => ({\n hooks: {\n tabs: {\n getSnapshot: () => {\n const version = ctx.slots.getVersion(CONFIG_TAB_SLOT);\n if (version !== tabsVersion) {\n tabsVersion = version;\n tabs = ctx.slots\n .entries(CONFIG_TAB_SLOT)\n .map((entry) => ({\n id: entry.options.id ?? '',\n order: entry.options.order ?? 0,\n label: resolveLabel(entry.options.label),\n // #735 配置体检的通道:取自导航表(账本那一格读不到)。表里没有这家 ⇒ 空串 ⇒\n // 这家没有体检出口(灯显缺席态、按钮不会为它取数)。\n channel: CHANNEL_BY_PLUGIN.get(entry.options.id ?? '') ?? '',\n }))\n .sort((a, b) => a.order - b.order);\n }\n return tabs;\n },\n subscribe: (listener: () => void) => ctx.slots.subscribe(CONFIG_TAB_SLOT, listener),\n },\n },\n });\n ctx.slots.inject('settings.section', () =>\n ctx.slots.register(\n {\n name: 'settings.section',\n id: 'dsh-life-pack',\n order: 21,\n label: () => '爱生活',\n inject: sectionInjected,\n children: { 'ilife.config-tab': { kind: 'list', scope: 'root' } },\n },\n (props: LifePackSectionProps) => React.createElement(LifePackSection, { ...props, getCall }),\n ),\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":["IDLE","React","React","React","S","React","React","S","React"],"sources":["../src/nav.ts","../src/update-contract.ts","../src/update-client.ts","../src/update-view.ts","../src/update-queue.ts","../src/update-panel.ts","../src/health-contract.ts","../src/health-fetch.ts","../src/health-panel.ts","../src/health-view.ts","../src/directory-browser-contract.ts","../src/directory-browser-state.ts","../src/directory-browser-parts.ts","../src/directory-browser-ui.ts","../src/directory-browser-roots.ts","../src/client.ts"],"sourcesContent":["/** dsh-life-pack 导航表(P10 脚手架,P3 #4 定案 order)。\n *\n * 总管只导航:6 tab 描述子仅做 openTab 路径导航,内容由各单品包自注册\n * slot 提供;总管不 import 任何单品页/单品包(见 boundaries 断言)。\n * 缺席纯条件渲染:present 缺谁就回谁的 reco(推荐安装+补装提示),不轮询。\n * 子页不占栏:卡路里 diet/exercise/goal 等 ilife:calorie:* 后缀只在单品\n * 主面板内导航,绝不在此表另起 tab 行。\n * 60 deck:map 为 matt 探针已占位,本表不动它。\n *\n * 面板外壳的两张文案表也在本文件(票 #679):右上角两个入口 PANEL_LINKS、\n * 底部「作者其他插件」卡 MORE_PLUGINS。文案与网址集中在这里,\n * 组件只负责画——改文案不碰组件(map #671 的 Q11 决议)。\n */\n\nexport interface ManagerTab {\n readonly skill: string;\n readonly slotId: string;\n readonly order: number;\n /** 页签名(屏上逐字印这个)=**产品名**(票 #738:作息管家/居家管家/私家大厨/饼干记账)。\n *\n * 它是各家自己那个 `SLOT_TITLE` 的镜像,两处必须同值:页签注册缺席时由这一格兜底,\n * 更新列表那一行的行首也用它(`update-targets.ts` 原样取 `title`)。\n * 对齐由 `test/plugin-p10-boundaries.test.mjs` 逐家咬住——真 import 两边的产物来比,不搜字符串。 */\n readonly title: string;\n /** 提供该 tab 的单品插件包名(仅文档级引用,不 import)。 */\n readonly plugin: string;\n /** 本家自己那条客户端通道(单段,例 `/ilife-calorie`):面板按它取配置体检(体检那个端点挂在上面)。\n *\n * 这条事实为什么住在这里(票 #735):各家注册页签槽时**也**会交一份(各包契约件的 `RPC_CHANNEL`),\n * 但装机上的槽位面**不透传自定义注册选项**——条目 options 只留 `{key,id,order,label,priority}`,\n * 总管从账本上读到的那一格恒是空串。后果是六家恒被判成「没有体检出口」,\n * 面板的体检目标表恒为空,「体检一次」按下去毫无反应(第 1 版的实现就是这么哑掉的)。\n * 这份镜像由 `test/plugin-p10-boundaries.test.mjs` 逐家与各包契约件的常量对齐守着。\n * 各家注册里那一格**留着不删**:上游哪天开始透传自定义选项,可以一行切回账本。 */\n readonly channel: string;\n}\n\nexport interface RecoTab extends ManagerTab {\n readonly kind: 'reco';\n /** 补装命令(单命令双包口径,总管必须已在位)。 */\n readonly installCmd: string;\n readonly hint: string;\n}\n\nexport const MANAGER_PLUGIN = 'dsh-life-pack' as const;\n\n/** 版本号**不在本文件**(票 #737):面板那行「总管 dsh-life-pack · x.y.z」的版本号由宿主半\n * `manager-version.ts` 运行时读自己这份已安装包的 `package.json`,面板经电话\n * `ilife-manager.version` 取来只做渲染——手写常量会与包版本无声漂开一整版(#737 实测过),\n * 读完盘这条路把「屏上那行 ≡ 装机包版本」变成构造性事实,定版只剩改 `package.json` 一处。 */\n\nexport const DUAL_ADD_PREFIX = 'dsh plugin add dsh-life-pack' as const;\n\nexport function dualInstallCmd(singlePlugin: string): string {\n return DUAL_ADD_PREFIX + ' ' + singlePlugin;\n}\n\n/** P3 定案 6 tab(order 升序即 + 号菜单顺序;已打开条不受 order 控制)。 */\nexport const MANAGER_TABS: readonly ManagerTab[] = [\n { skill: 'bill', slotId: 'ilife:cookie', order: 70, title: '饼干记账', plugin: 'dsh-bill-ilife', channel: '/ilife-bill-ilife' },\n { skill: 'calorie', slotId: 'ilife:calorie', order: 75, title: '卡路里', plugin: 'dsh-calorie', channel: '/ilife-calorie' },\n { skill: 'memo', slotId: 'ilife:memo', order: 80, title: '备忘录', plugin: 'dsh-memo-ilife', channel: '/ilife-memo' },\n { skill: 'schedule', slotId: 'ilife:schedule', order: 85, title: '作息管家', plugin: 'dsh-schedule-ilife', channel: '/ilife-schedule-ilife' },\n { skill: 'home', slotId: 'ilife:home', order: 90, title: '居家管家', plugin: 'dsh-home-ilife', channel: '/ilife-home-ilife' },\n { skill: 'chef', slotId: 'ilife:chef', order: 95, title: '私家大厨', plugin: 'dsh-chef', channel: '/ilife-chef' },\n];\n\nexport function recoFor(tab: ManagerTab): RecoTab {\n const installCmd = dualInstallCmd(tab.plugin);\n return {\n ...tab,\n kind: 'reco',\n installCmd,\n hint: '未安装[' + tab.plugin + '],请补装后使用:' + installCmd,\n };\n}\n\nexport type Presence = ReadonlySet<string>;\n\nexport function tabsForPresence(present: Presence): readonly (ManagerTab | RecoTab)[] {\n return MANAGER_TABS.map((t) => (present.has(t.slotId) ? t : recoFor(t)));\n}\n\nexport interface OpenPort {\n openTab(seed: { readonly type: string; readonly path: string }, scope?: { readonly sessionId?: string }): void;\n}\n\nexport interface HasTabsPort extends OpenPort {\n hasTabs(): boolean;\n}\n\n/** 总管导航:只做 openTab 路径导航(内容型打开),缺席由调用方渲染 reco。 */\nexport function openManagerTab(port: HasTabsPort, slotId: string, sessionId?: string): void {\n if (!port.hasTabs()) return;\n port.openTab({ type: slotId, path: slotId }, sessionId ? { sessionId } : undefined);\n}\n\n/** 面板右上角一个入口:图标 + 悬停说明 + 目标网址。 */\nexport interface PanelLinkRow {\n readonly key: 'star' | 'feedback';\n /** 图标字形(照参照实现 dsh-mattpocock-skills-deck,星星只留 ⭐、反馈只留 💬)。 */\n readonly glyph: string;\n /** 鼠标悬停给出的说明文字。 */\n readonly tip: string;\n readonly url: string;\n}\n\n/** 面板右上角两个入口:星(去本仓点 star)+ 气泡(去本仓开 issue)。\n *\n * 两处都指本仓 FeatherHunter/ilife——这是 map #671 裁定过的(「星与气泡指向哪个仓」\n * 一问:反馈要落在本仓的需求池,「引流到作者其他技能」是底部那张卡的事)。\n * 三件并排里的第三件「检查更新」住隔壁票(#678),本表不管它。\n */\nexport const PANEL_LINKS: readonly PanelLinkRow[] = [\n { key: 'star', glyph: '⭐', tip: '去 GitHub 点 Star', url: 'https://github.com/FeatherHunter/ilife' },\n { key: 'feedback', glyph: '💬', tip: '反馈问题', url: 'https://github.com/FeatherHunter/ilife/issues/new' },\n];\n\n/** 底部「作者其他插件」卡里的一行:包名 + 一句说明 + 行尾外链图标指向的仓库。 */\nexport interface MorePluginRow {\n readonly pkg: string;\n readonly desc: string;\n readonly url: string;\n}\n\n/** 「作者其他插件」四行:包名 + 一句说明 + 仓库地址。\n *\n * 文案口径(用户 2026-09-18 拍板):**不写数字**——数字会过期(用户截图里的「34 款」实测已到 38 款),\n * 写错最伤推荐位的可信度;第一行也不用 deck 自己面板里的自指「本面板自己」,改成不自指的说法。\n * 只收真实存在的仓库,网址已逐个验真。第一行的说明描述的是 deck 那个包(工程技能面板),不是爱生活面板。\n */\nexport const MORE_PLUGINS: readonly MorePluginRow[] = [\n {\n pkg: 'dsh-mattpocock-skills-deck',\n desc: '工程技能面板:装好就能在右侧直接用',\n url: 'https://github.com/FeatherHunter/dsh-mattpocock-skills-deck',\n },\n {\n pkg: 'dsh-opencode-palette',\n desc: '多款长时间编程护眼配色,一键换上',\n url: 'https://github.com/FeatherHunter/dsh-opencode-palette',\n },\n {\n pkg: 'dsh-prompt',\n desc: '常用提示模板随手点,不用来回复制粘贴',\n url: 'https://github.com/FeatherHunter/dsh-prompt',\n },\n {\n pkg: 'dsh-im-companion',\n desc: '聊天机器人的伴侣插件:扫码或填凭据就把飞书、微信等聊天接进来',\n url: 'https://github.com/FeatherHunter/dsh-im-companion',\n },\n];\n","/** 总管与面板之间的定案形状:载体、电话名、原因人话表、手工兜底命令。\n *\n * 分工:本文件只管「两侧都认的常量与文案」;宿主半见 `update-host.ts`,面板见 `client.ts`。\n * 电话名分两类:\n * - 七个**更新电话组**(每组三个 `updateStatus` / `updateCheck` / `updateInstall`)由更新包\n * 自己拼(`update.phoneNames`),面板侧取值见构建期派生的 `update-phones.ts`;\n * - 两个**总管自有电话**(装上缺席包、取版本行数据)是本包定义的,见 `MANAGER_ACTIONS`。\n *\n * 原因码的人话文案逐条抄更新包 README 第 8 节「用户该做什么」列与第 12 节排错表;\n * 手工命令形状抄第 9 节(`commands.ts:104-122` 的 `manualCommand`)。\n */\nimport type { BlockedReason } from 'dsh-plugin-update';\n\n/** 爱生活页签槽名(总管声明 `children`、各单品技能设置页注册进来;单段名,避开官方 `settings.*` 前缀)。\n *\n * 为什么住这里:这个名字**两侧都要用**——面板侧 `client.ts` 拿它注册/投影槽,\n * 宿主侧(`update-env.ts` 的 `readPanelRegistered`)拿它判「已装产物里到底有没有注册代码」。\n * 一处定义、两边引用(结构纪律「概念唯一」):名字改了只有这一行要动。\n * 宿主判据为什么要读这个字面量、以及它的边界,见 `readPanelRegistered` 的头注。 */\nexport const CONFIG_TAB_SLOT = 'ilife.config-tab' as const;\n\n/** 载体基段:`connection.rpc.call` 的**第一段**参数,宿主注册路径与它同源(`'/api' + 通道名`)。\n *\n * 一处定义(#735):总管自己的电话、以及面板到各家的每一通电话,第一段都必须是它。\n * 真机实现(`dsh-client-connection/lib/client.js`):URL = `${第一段}/${第二段}`、信封 `method` = 第二段。\n * 传错第一段就是 404:总管自己的电话在 #678 栽过一次,各家的配置体检在 #735 又栽过一次\n * (那次传的是通道名 `/ilife-<技能>`,拼出来的地址上没有注册过那条路由,六家全 404)。 */\nexport const CARRIER_BASE = '/api' as const;\n\n/** 载体:宿主注册 `connection.fetch.register` 的路径 / 面板 `connection.rpc.call` 的两段参数。 */\nexport const MANAGER_RPC = {\n /** 载体基段(`connection.rpc.call` 的**第一段**参数)。传错第一段就是 404:真机上踩过一次。 */\n base: CARRIER_BASE,\n /** 单段通道名,须 match `/^\\/[A-Za-z0-9._~-]+$/`(cookbook §6)。 */\n channel: '/ilife-manager',\n /** 面板侧第二段参数(等于通道名去掉前导斜杠那一段)。 */\n endpoint: 'ilife-manager',\n /** 宿主侧注册的完整路径(= `base` + `channel`)。 */\n path: '/api/ilife-manager',\n} as const;\n\n/** 总管自有电话名:更新包的三个动作名是冻结集合(`config.ts:14`),这里另起名字不撞车。 */\nexport const MANAGER_ACTIONS = {\n /** 装上缺席的目标包:入参 `{packageName, version}`,回包 `{packageName, version}`。 */\n install: 'ilife-manager.install',\n /** 七个更新目标的表:入参 `{}`,回包 `{targets: [{key, title, packageName, phones, runningVersion, installedVersion, panelRegistered, skill}]}`。\n *\n * 电话名为什么由宿主转交而不是面板侧写死:更新包的三个电话名只有它自己知道\n * (`update.phoneNames`,`host.ts:335`);面板侧既不能 import 更新包(浏览器产物纯度门)\n * 也不该把名字再写一份(写两份必然走散)。宿主把名字表连同版本行一起交出去,\n * 面板一行字面量都不留。 */\n targets: 'ilife-manager.targets',\n /** 总管自述版本(票 #737):入参 `{}`,回包 `{version}`。\n *\n * 宿主读**自己这份已安装包**的 `package.json`(`manager-version.ts`),面板只渲染读到的值。\n * 为什么走电话:面板是浏览器产物,禁 node 内建(`test/client-bundle-48.test.mjs` 看门),\n * 读盘只许在宿主半;与卡路里 #130 同一条路(host 读 → RPC → client 纯渲染)。 */\n version: 'ilife-manager.version',\n /** 本机「根」清单(票 #744):入参 `{}`,回包 `{roots: [{path, kind}]}`。\n *\n * 为什么这条能力住在总管:浏览器里没有卷清单接口,而宿主那条目录选择接缝只有\n * `list`/`createDirectory` 两格——「这台机器上有哪些盘」只能由宿主去问操作系统。\n * 六家的目录浏览器都取这一通电话,故名字与实现都只有这一处(清单本身见 `roots.ts`)。\n * 回包**恒成功**:列不出来是空清单,不是错误码(界面据此只是不画那一行)。 */\n roots: 'ilife-manager.roots',\n} as const;\n\n/** 版本读不到时两侧共用的降级字面量(面板照原样显示,不假装知道版本)。\n *\n * 一处定义、两侧引用:宿主半 `manager-version.ts` 读失败时回它,面板半 `update-client.ts`\n * 归一化时也认它——面板侧不许 import 宿主半(会把 node 内建带进浏览器束)。 */\nexport const VERSION_UNKNOWN = 'unknown' as const;\n\n/** 更新包默认官方源(`config.ts:20`,总管侧拼手工命令时用同一个值)。 */\nexport const DEFAULT_REGISTRY = 'https://registry.npmjs.org/' as const;\n\n/** 装不了 / 装不成的人话原因表:八种 `blockedReason` + 过程错误码(更新包 README 第 8、12 节)。\n *\n * 每条都要三段齐全(票 #740 的文案判据):**事实**(哪里对不上)→ **后果**(这家现在能不能装)\n * → **动作**(用户做得到、且面板上真有的那一步;面板代劳不了的走「下面这条命令」)。\n * 禁用口语与自造词:「半截任务」「电话没接上」「回执异常」「还出现就重装」「查宿主日志」一律不写。\n *\n * 点名的按钮必须是**这一行上真够得着**的那颗(票 #740 第三轮):结果浮层一开就盖住题头的\n * 「检查更新」,所以「改完再读一次」这类下一步一律写「重新检查」(画在那一行上);\n * 只有发生在**重启之后**的下一步才写「检查更新」。反过来,凡是写着「下面这条命令」的那几句,\n * 那一行的命令块必须真出来(判据 `Verdict.manualHint`)——两处都由门禁守着。 */\nconst REASON_TEXT: Record<string, string> = {\n 'unknown-profile': '认不出这台机器上的安装位置(使用范围的目录不存在,或名字不合法)。把使用范围目录建好,再点「重新检查」。',\n 'source-install': '这一份不是从 npm 发行包装的(源码装),面板不能更新它。要更新,照下面这条命令重装,装完再点「重新检查」。',\n 'invalid-installation': '磁盘上这一份装得不完整(包名或版本对不上、入口文件缺失)。照下面这条命令重装,装完再点「重新检查」。',\n 'installation-changed': '这台机器的安装清单变了(在别处装过、或清单被手工改过),这一家现在不能装。点「重新检查」重新读一次,再点「装上更新」。',\n 'pending-restart': '新版已装到磁盘,正在运行的是旧版本。重启 DSH(退出后重新打开)后生效。',\n 'registry-conflict': '使用范围清单里写的版本与磁盘上的实际版本对不上。打开使用范围的 `package.json`,把这一行改成磁盘上的版本号,再点「重新检查」。',\n 'incompatible-node': '新版本要求 Node 22 或更高,当前运行的是更低版本。升级 Node、重启 DSH 后,再点「检查更新」。',\n // 这条只在「磁盘上的版本与正在运行的一致(或磁盘那份读不到)」时才会印出来——「磁盘 ≠ 运行中」\n // 那一态被事实判据先截走,印的是 `restartLine`。故这里**不许**再教人去重启(那在眼下没用)。\n 'recovery-required': '上次安装没有收尾。点「重试安装」再跑一次;还出现这一条,就用下面这条命令重装。',\n 'check-failed': '查不到最新版本:联网失败,或官方源(registry.npmjs.org)不可达。确认网络与源地址后,再点「重新检查」。',\n 'invalid-release': '官方源上这个版本的元数据不完整(包名或版本不合法、包地址或校验值缺失)。换一个版本,或稍后重试。',\n 'check-expired': '上次检查的结果已过期。点「重新检查」重查一次,再点「装上更新」。',\n 'update-busy': '同一时间只能安装一个插件。等这次装完,再点「装上更新」。',\n 'install-failed': '安装没有完成。在终端里执行下面这条命令把它装完,装完再点「重新检查」。',\n 'manager-unreachable': '面板连不上总管插件(宿主里没注册,或版本对不上)。重启 DSH 后重试;仍不行就在 DSH 日志里找 `[dsh-life-pack]` 的报错。',\n 'bad-request': '面板与总管之间的请求出错了(属程序缺陷)。请在 DSH 日志里找 `[dsh-life-pack]` 的报错,连同这家插件的名字报到本仓 issue。',\n internal: '面板与总管之间的请求出错了(属程序缺陷)。请在 DSH 日志里找 `[dsh-life-pack]` 的报错,连同这家插件的名字报到本仓 issue。',\n};\n\n/** 原因码 → 人话(认不出的码不猜、原样回码并说明这是未知原因)。 */\nexport function reasonText(code: string): string {\n return REASON_TEXT[code] ?? '面板不认识这个原因码(' + code + ')。请在 DSH 日志里找同一处报错,报到本仓 issue。';\n}\n\n/** 手工兜底命令(更新包 README 第 9 节形状):使用范围名含特殊字符时加引号。 */\nexport function manualInstallCommand(input: {\n profileName: string | null;\n packageName: string;\n version: string;\n registryUrl?: string;\n}): string | null {\n const name = typeof input.profileName === 'string' ? input.profileName.trim() : '';\n if (!name || name.length > 255 || name.startsWith('-') || ['.', '..', 'node_modules'].includes(name)) return null;\n const version = typeof input.version === 'string' && /^\\d+\\.\\d+\\.\\d+$/.test(input.version) ? input.version : '';\n if (!version) return null;\n const registry = input.registryUrl ?? DEFAULT_REGISTRY;\n const profileArg = /^[A-Za-z0-9_.-]+$/.test(name) ? name : JSON.stringify(name);\n return 'dsh plugin --profile ' + profileArg + ' add --save-exact ' + input.packageName + '@' + version + ' --registry=' + registry;\n}\n\n/** 八种装不了原因码(给门禁与测试对表用;值就是 `BlockedReason` 全集)。 */\nexport const BLOCKED_REASONS: readonly BlockedReason[] = [\n 'unknown-profile',\n 'source-install',\n 'invalid-installation',\n 'installation-changed',\n 'pending-restart',\n 'registry-conflict',\n 'incompatible-node',\n 'recovery-required',\n];\n","/** 面板到宿主的四条流程(纯异步函数:无 DOM、无 react;传输口由调用方注入,便于单测)。\n *\n * 四条流程与更新包 README 的对应关系:\n * - 检查更新:`updateCheck`(第 3 节第 3 步的 `host.call(UPD_CHECK, {})`);\n * - 装上更新:`updateCheck` 拿凭证 → `updateInstall` 提交 → **按间隔轮询 `updateStatus`**\n * 直到任务离开 `installing`/`verifying`(第 3 节第 3 步与第 9 节:安装是后台跑,\n * 调用方不干等;凭证过期 `check-expired` 是正常错误码,重查一次再提交,见第 11 节);\n * - 装上缺席的包:走总管自有的 `ilife-manager.install`(更新包的三个电话只管已装的包);\n * - 总管自述版本:走总管自有的 `ilife-manager.version`(宿主读自己那份包描述文件,见 `manager-version.ts`)。\n */\nimport { MANAGER_ACTIONS, MANAGER_RPC, VERSION_UNKNOWN, reasonText } from './update-contract.js';\nimport type { CheckOutcome, TargetInfo } from './update-view.js';\n\n/** 传输口(DSH 载体 `connection.rpc.call` 的形状,cookbook §6)。 */\nexport type CallFace = (\n channel: string,\n endpoint: string,\n payload: unknown,\n signal?: AbortSignal,\n) => Promise<\n | { readonly ok: true; readonly value: unknown }\n | { readonly ok: false; readonly error: { readonly code: string; readonly message: string; readonly details: Record<string, unknown> } }\n>;\n\nexport interface CallFailure {\n readonly ok: false;\n readonly code: string;\n readonly message: string;\n readonly details: Record<string, unknown>;\n /** 能给的话给一条可复制的手工命令(更新包每次回包都带,见其 README 第 9 节)。 */\n readonly manual: string | null;\n}\n\nexport type CallOutcome<T> = { readonly ok: true; readonly value: T } | CallFailure;\n\n/** 轮询上限:1 秒一次、最多 900 次(覆盖更新包 15 分钟的安装时限)。 */\nconst MAX_POLL_TRIES = 900;\n/** 轮询默认间隔(只有在宿主没给 `pollMs` 时才用;正常取值由宿主转交更新包的 panelPollMs)。 */\nconst FALLBACK_POLL_MS = 1000;\nlet requestSeq = 0;\n\nfunction failure(code: string, details: Record<string, unknown> = {}, manual: string | null = null): CallFailure {\n return { ok: false, code, message: reasonText(code), details, manual };\n}\n\n/** 取数间隔:settle 即清 timer。定时器有界重试的例外依据(p10 静态门三件套):\n * 这里有清理(clearTimeout)、有次数上限(MAX_POLL_TRIES)、间隔取自宿主转交的更新包\n * `panelPollMs`(不写死);用途是更新包 README 第 3 节要求的状态轮询(安装是后台跑,\n * 面板每秒问一次进度),不是无界重试。 */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n const timer = setTimeout(() => {\n clearTimeout(timer);\n resolve();\n }, ms);\n });\n}\n\nasync function callManager<T>(call: CallFace | null, method: string, payload: Record<string, unknown>): Promise<CallOutcome<T>> {\n if (typeof call !== 'function') return failure('internal', { detail: '宿主连接缺席:connection.rpc.call 不可用' });\n let result: Awaited<ReturnType<CallFace>>;\n try {\n // 第一段是**载体基段**(`/api`)、第二段才是本包端点;传错就是打到一个不存在的路由(真机踩过)。\n result = await call(MANAGER_RPC.base, MANAGER_RPC.endpoint, { method, payload });\n } catch (error) {\n // 传输层失败(路由没注册、宿主半版本对不上、请求挂了):不许套用「查新版失败」那句——\n // 它对这种情形是误导(真机上就这么误报过一次)。各层的失败各有各的话。\n return failure('manager-unreachable', { detail: String((error as Error)?.message ?? error) });\n }\n if (!result || typeof result !== 'object' || typeof (result as { ok?: unknown }).ok !== 'boolean') {\n return failure('internal', { detail: '回执信封异常(非 ok 信封)' });\n }\n if (result.ok) return { ok: true, value: result.value as T };\n const error = (result as { error?: { code?: unknown; details?: unknown } }).error;\n const code = typeof error?.code === 'string' ? error.code : 'internal';\n const details = error && typeof error.details === 'object' && error.details !== null ? (error.details as Record<string, unknown>) : {};\n return failure(code, details);\n}\n\n/** 取七个更新目标的表(宿主转交更新包的三个电话名与版本行,面板不写死任何电话名)。 */\nexport async function loadTargets(call: CallFace | null): Promise<CallOutcome<{ targets: TargetInfo[]; pollMs: number }>> {\n const out = await callManager<{ targets?: TargetInfo[]; pollMs?: number }>(call, MANAGER_ACTIONS.targets, {});\n if (!out.ok) return out;\n const targets = Array.isArray(out.value?.targets) ? out.value.targets : [];\n const pollMs = typeof out.value?.pollMs === 'number' && out.value.pollMs > 0 ? out.value.pollMs : FALLBACK_POLL_MS;\n return { ok: true, value: { targets, pollMs } };\n}\n\n/** 取总管自述版本(票 #737):宿主读**自己这份已安装包**的 `package.json`,面板只渲染读到的值。\n *\n * 单次、无轮询;宿主读不到会回 `unknown`,传输层失败也回同一个字面量——面板照原样显示,\n * 不假装知道版本(与卡路里 #130 的降级口径一致)。 */\nexport async function loadManagerVersion(call: CallFace | null): Promise<string> {\n const out = await callManager<{ version?: unknown }>(call, MANAGER_ACTIONS.version, {});\n if (!out.ok) return VERSION_UNKNOWN;\n const raw = out.value?.version;\n return typeof raw === 'string' && raw.trim().length > 0 ? raw.trim() : VERSION_UNKNOWN;\n}\n\n/** 查一家:默认查新版(`updateCheck`,顺带回最新版本与凭证);给 `phoneName` 时查那个电话。 */\nexport async function checkTarget(\n call: CallFace | null,\n target: TargetInfo,\n phoneName?: string,\n): Promise<CallOutcome<CheckOutcome>> {\n const name = phoneName ?? target.phones?.check;\n if (!target.phones || !name) return failure('bad-request', { target: target.key });\n return callManager<CheckOutcome>(call, name, {});\n}\n\n/** 装上缺席的包:版本取自查新版拿到的「最新版本」(宿主用更新包的执行器真装)。 */\nexport async function installAbsent(\n call: CallFace | null,\n target: TargetInfo,\n version: string,\n): Promise<CallOutcome<{ packageName: string; version: string }>> {\n return callManager(call, MANAGER_ACTIONS.install, { packageName: target.packageName, version });\n}\n\n/** 装上更新:查新版 → 提交安装 → 有界轮询查状态到任务收尾;凭证过期重查一次再提交。\n *\n * **第一句那次 `checkTarget` 不是多余的**(票 #740 第四轮):凭证是「这一刻的安装态」的凭据,\n * 更新包提交时拿它跟当时读到的安装态比(`service.js:309`/`:325`),不一样就拒。\n * 而面板**从来只提交这一刻现签的凭证**——这就是「刚在本面板装过别家,这一家照样点得动\n * 「装上更新」」的全部依据:旧快照只用来显示版本,不参与提交。(第四轮以前面板还把别家整行\n * 换成「重新检查」——那是白挡一次本来会成功的点击,已删。) */\nexport async function updateInstalled(\n call: CallFace | null,\n target: TargetInfo,\n pollMs: number,\n): Promise<CallOutcome<CheckOutcome>> {\n if (!target.phones) return failure('bad-request', { target: target.key });\n const first = await checkTarget(call, target);\n if (!first.ok) return first;\n let checked = first.value;\n if (!checked.receipt) {\n const code = checked.snapshot.blockedReason ?? 'check-failed';\n return failure(code, { target: target.key }, checked.manual);\n }\n requestSeq += 1;\n const requestId = 'ilife-manager-' + Date.now().toString(36) + '-' + String(requestSeq);\n let submitted = await callManager<CheckOutcome>(call, target.phones.install, {\n checkId: checked.receipt.checkId,\n requestId,\n });\n if (!submitted.ok && submitted.code === 'check-expired') {\n const again = await checkTarget(call, target);\n if (!again.ok) return again;\n checked = again.value;\n if (!checked.receipt) {\n return failure(checked.snapshot.blockedReason ?? 'check-failed', { target: target.key }, checked.manual);\n }\n submitted = await callManager<CheckOutcome>(call, target.phones.install, {\n checkId: checked.receipt.checkId,\n requestId: requestId + 'r',\n });\n }\n if (!submitted.ok) return { ...submitted, manual: submitted.manual ?? checked.manual };\n // 安装是后台跑(`service.ts:434-436`:落盘后后台跑,调用方不干等),故按间隔轮询到任务收尾。\n let latest = submitted.value;\n const interval = pollMs > 0 ? pollMs : FALLBACK_POLL_MS;\n for (let tries = 0; ; tries += 1) {\n const job = latest.snapshot.job;\n if (job === null || (job.state !== 'installing' && job.state !== 'verifying')) break;\n if (tries >= MAX_POLL_TRIES) break;\n await sleep(interval);\n const status = await checkTarget(call, target, target.phones.status);\n if (!status.ok) return status;\n latest = status.value;\n }\n return { ok: true, value: latest };\n}\n","/** 面板的视图逻辑(纯函数:无 DOM、无 node、无 react),客户端与单测共用同一份。\n *\n * 判定「有没有新版」不自己算版本号大小:更新核心已经在快照里给了结论\n * (`canInstall` 为真即有新版可装,`blockedReason` 非空即为什么装不了,见更新包 README 第 8 节),\n * 本文件只把结论翻成三种状态与一句话,不另起一套版本比较。\n */\nimport type { CheckReceipt, BlockedReason, UpdateSnapshot } from 'dsh-plugin-update';\nimport { dualInstallCmd } from './nav.js';\nimport { reasonText } from './update-contract.js';\n\n/** 一个更新目标的面板数据(宿主 `ilife-manager.targets` 的每行)。 */\nexport interface TargetInfo {\n readonly key: string;\n readonly title: string;\n readonly packageName: string;\n readonly phones: { readonly status: string; readonly check: string; readonly install: string } | null;\n /** 宿主启动那一刻磁盘上的版本;null = 启动时这个包还没装。 */\n readonly runningVersion: string | null;\n readonly installedVersion: string | null;\n /** 已装产物里有没有爱生活页签槽的注册代码;**宿主没给这一行时为 undefined**(老宿主)。\n * 见 `slotStateOf`:判据缺席不许猜,退回「装了但我们不知道接没接上」那一态。 */\n readonly panelRegistered?: boolean;\n /** 技能包随插件的那条精确 pin(插件包 manifest 里的 `skill-*` 依赖)。 */\n readonly skill: { readonly packageName: string; readonly version: string } | null;\n}\n\n/** 更新电话查到的结果(`updateCheck` / `updateStatus` 回包体)。 */\nexport interface CheckOutcome {\n readonly snapshot: UpdateSnapshot;\n readonly manual: string | null;\n readonly receipt: CheckReceipt | null;\n}\n\n/** 缺席:宿主启动时没装、现在磁盘上也没有(`runningVersion`/`installedVersion` 都是 null)。 */\nexport function isAbsent(target: TargetInfo): boolean {\n return target.runningVersion === null && target.installedVersion === null;\n}\n\n/** 缺席卡要说清的四态(前两态给动作,后两态**不给动作**——重装与重启都不会改变那两个状态)。\n *\n * 判据是**两处独立事实**,不许互相顶替:宿主的装机读数(磁盘上有没有、产物里有没有注册代码),\n * 与浏览器侧的页签槽账本(这一家的设置页此刻在不在浏览器里)。前者答「装没装、重装有没有用」,\n * 后者答「接没接上」。用账本代理「装没装」会说假话:真机实测四家已装却显示「未安装」(0.2.5 已修)。\n */\nexport type SlotState = 'connected' | 'absent' | 'unregistered-product' | 'not-connected';\n\n/** 一家页签此刻的状态:账本里已注册 → `connected`(面板显示它自己的设置页),否则按宿主装机读数分三态。 */\nexport function slotStateOf(target: TargetInfo | null, inLedger: boolean): SlotState {\n if (inLedger) return 'connected';\n if (target === null || isAbsent(target)) return 'absent';\n if (target.panelRegistered === false) return 'unregistered-product';\n return 'not-connected';\n}\n\nexport type VerdictKind = 'unknown' | 'up-to-date' | 'update-available' | 'blocked';\n\n/** 这一行给的按钮(票 #740):装上/装上更新/重试安装/重新检查。\n * 前两个是「做新的事」,后两个是**把卡住的状态推回可做**——拦截态不许没有出路。 */\nexport type VerdictAction = 'install' | 'update' | 'retry' | 'recheck';\n\nexport interface Verdict {\n readonly kind: VerdictKind;\n /** 一句话结论(面板那行正文):事实 + 后果 + 动作,三段齐全。 */\n readonly text: string;\n readonly action: VerdictAction | null;\n /** 这句话是否指着「下面这条命令」:指着就必须真把命令块摊出来(票 #740 第三轮)。\n * 没有这一格时出现过失配:`recovery-required` 那句写着「就用下面这条命令重装」,\n * 而命令块只在「拦截态且没按钮」时才画,那句下面什么都没有。 */\n readonly manualHint?: boolean;\n}\n\n/** 这一家是不是「装好了、只差重启」——判据是**事实**(磁盘版本 ≠ 正在跑的版本),不是原因码。\n *\n * 为什么不许按原因码判(票 #740 真机):同一种事实在更新核心里可能报成两种码——\n * `pending-restart`,或者被上一次中断的任务盖成 `recovery-required`。按码判会把「该重启」\n * 漏成一句「装不上」,用户看到的正是那种读不懂的屏。 */\nexport function restartPendingOf(snapshot: UpdateSnapshot): boolean {\n const installed = snapshot.installedVersion;\n return installed !== null && installed !== snapshot.runningVersion;\n}\n\n/** 待重启那一句(三段齐全:事实 → 后果 → 动作)。\n *\n * 措辞不写「新版」:磁盘上那份也可能比正在跑的**低**(手工降级),「新版」就不实了;\n * 只照事实说「磁盘上装的是 X、正在运行的是 Y」。\n * 动作只给「重启 DSH」:这一态下面板**做不了别的**——更新包的可装判据要求最新版严格高于\n * **正在运行**的版本(`service.js:219`),而凭证只发在可装态(`service.js:256`:`receipt` 只在\n * `canInstall` 时给),所以这一态里「重试安装」点了也是白点。 */\nexport function restartLine(snapshot: UpdateSnapshot): string {\n const installed = snapshot.installedVersion ?? snapshot.job?.targetVersion ?? '新版本';\n return '磁盘上装的是 ' + String(installed) + ';正在运行的是 ' + snapshot.runningVersion + '。重启 DSH(退出后重新打开)后生效。';\n}\n\n/** 「条件在别处(目录/清单/别处装的),改完回来重读一次」的原因码:面板能给的下一步就是「重新检查」。\n *\n * 为什么要逐码列(票 #740 第三轮的朗读表):`unknown-profile`/`registry-conflict`/`invalid-installation`/\n * `source-install` 这四态原先一个动作都不给,屏上那句却写着「再点「检查更新」」——那颗按钮住在面板题头,\n * 结果浮层一开(点「检查更新」就开)就被浮层盖住,够不着。跟两张「还没查过」的卡是同一类病。 */\nconst RECHECK_REASONS: ReadonlySet<string> = new Set(['installation-changed', 'unknown-profile', 'registry-conflict', 'invalid-installation', 'source-install']);\n\n/** 「这一步面板代劳不了、得照下面那条命令做」的原因码:这些话下面必须真有命令块。 */\nconst NEEDS_MANUAL: ReadonlySet<string> = new Set(['source-install', 'invalid-installation', 'recovery-required']);\n\n/** 拦截态各自给得出的下一步:面板能代劳的给按钮,代劳不了的给 null(那种走手工命令块)。 */\nfunction actionForBlocked(reason: BlockedReason): VerdictAction | null {\n if (RECHECK_REASONS.has(reason)) return 'recheck';\n if (reason === 'recovery-required') return 'retry';\n // `incompatible-node`(要升级 Node 并重启 DSH)与 `pending-restart`(要重启,且已被上面那条事实判据截走)\n // 这两态面板一步也做不了,按定义给 null;它们那句里点名的「检查更新」发生在重启之后,那时浮层早已关掉。\n return null;\n}\n\n/** 这一行此刻该画哪颗按钮:结论自带的动作优先,**失败的行一律给「重新检查」**(票 #740 第三轮)。\n *\n * 失败的行(查不到最新版/检查过期/装失败)没有快照、也就没有结论,原先一颗按钮都不画:\n * 屏上写着「再点「检查更新」」,那颗按钮却在浮层底下 ⇒ 无路可走。重新查一次对每一种失败都成立。 */\nexport function cardActionOf(verdict: Verdict | null, phase: 'idle' | 'checking' | 'ready' | 'installing' | 'failed'): VerdictAction | null {\n if (verdict?.action) return verdict.action;\n return phase === 'failed' ? 'recheck' : null;\n}\n\n/** 这一行要不要摊出那条手工命令(票 #740 第三轮定的形状,第四轮补的这一格)。\n *\n * 三种情形摊:① 上一次装失败(`failed`);② 这句话自己指着「下面这条命令」(`manualHint`)。\n * **待重启**与 `incompatible-node` 不摊:它们那时该做的是**重启**(或升级 Node),不是重装一次。\n * (第四轮维护者真机截图逮到:待重启那张卡下面挂着一条「dsh plugin … add」——点它等于把刚装好的\n * 那一版再装一遍,纯噪声,还会让人以为状态不对。) */\nexport function showManualOf(verdict: Verdict | null, phase: 'idle' | 'checking' | 'ready' | 'installing' | 'failed'): boolean {\n return phase === 'failed' || verdict?.manualHint === true;\n}\n\n/** 把快照翻成一行结论:还没查 / 已是最新 / 有新版 / 待重启 / 装不了(附人话原因与下一步)。 */\nexport function verdictOf(target: TargetInfo, snapshot: UpdateSnapshot): Verdict {\n const absent = isAbsent(target);\n const latest = snapshot.latestVersion;\n if (absent && latest === null) {\n // 动作给「重新检查」而不是让人去点面板顶上那颗「检查更新」:浮层开着的时候,顶上那颗被盖住了\n // (票 #740 对抗式审查第三轮的朗读表逮到)⇒ **每张卡自己的动作必须在这张卡上够得着**。\n return { kind: 'unknown', text: '未安装。点「重新检查」查最新版本,再点「装上」。', action: 'recheck' };\n }\n if (absent) {\n return { kind: 'update-available', text: '未安装;最新版本 ' + String(latest) + '。点「装上」安装。', action: 'install' };\n }\n // ① 装了没重启:先说这件事——它压过一切「装不上」(那时用户该做的只有重启)。\n if (restartPendingOf(snapshot)) {\n return { kind: 'blocked', text: restartLine(snapshot), action: null };\n }\n // ② 有东西可装、却被守卫或环境拦下:说清原因 + 给一个能执行的下一步。\n // 「没东西可装」的那些拦截不在这里说——那时守卫与用户无关,不该在屏上占一行(真机实测:\n // 已经是最新的一家挂着「安装清单变了」+一条重装命令,纯噪声)。\n const installed = snapshot.installedVersion;\n const hasNewer = latest !== null && latest !== installed;\n if (snapshot.blockedReason !== null && hasNewer) {\n const blocked = snapshot.blockedReason as BlockedReason;\n return { kind: 'blocked', text: reasonText(blocked), action: actionForBlocked(blocked), manualHint: NEEDS_MANUAL.has(blocked) };\n }\n if (latest === null) {\n return { kind: 'unknown', text: '还没查过最新版本。点「重新检查」。', action: 'recheck' };\n }\n if (snapshot.canInstall) {\n // 把「正在运行的是哪个版本」写进同一行:用户不必回头去版本行里找。\n return { kind: 'update-available', text: '有新版本 ' + String(latest) + '(正在运行 ' + snapshot.runningVersion + ')。点「装上更新」。', action: 'update' };\n }\n if (hasNewer) {\n // 有新版、却没有可用凭证(这一通的凭证过期,或压根没签发)⇒ **不许说「已是最新」**:\n // 那是假话(最新版就在官方源上、磁盘上还是旧的),正确答案是重新检查一次拿新凭证。\n return { kind: 'update-available', text: '有新版本 ' + String(latest) + '(正在运行 ' + snapshot.runningVersion + ')。点「重新检查」,再点「装上更新」。', action: 'recheck' };\n }\n return { kind: 'up-to-date', text: '已是最新版本 ' + String(latest) + '。', action: null };\n}\n\n/** 待重启横幅:**一行总账**,只点名哪几家要做「重启 DSH」这件事。\n *\n * 为什么不再逐家重复那句话(票 #740 对抗式审查第三轮的朗读表逮到):同一句「重启 DSH…后生效」\n * 会同时出现在横幅和那家卡片上,一屏两遍。分工定死:**横幅答「谁要重启」,卡片答「为什么、怎么做」**。 */\nexport function restartBannerText(titles: readonly string[]): string | null {\n if (titles.length === 0) return null;\n return '⚠️ 待重启 DSH(退出后重新打开):' + titles.join('、');\n}\n\n/** 面板展示的那条可复制命令(用户会照着敲的那一条)。\n *\n * 缺席包给人人都会用的**单命令双包**口径(#674 冻结的装入口径:不带版本号、不带任何开关);\n * 已装包给更新包回包里那条(`--save-exact ... --registry=` 是它 README 第 9 节的冻结合同,\n * 本仓不动它——要改只能提上游)。规范词见 docs/plugins/plugin-manager/t678-缺席即装与检查更新.md。 */\nexport function manualForDisplay(target: TargetInfo, manual: string | null): string | null {\n if (isAbsent(target)) return dualInstallCmd(target.packageName);\n return manual;\n}\n\n/** 版本行两行文字:插件包(本家) + 技能包(随插件,不单独查更新)。 */\nexport function versionLines(target: TargetInfo, snapshot: UpdateSnapshot | null): readonly string[] {\n const running = snapshot?.runningVersion ?? target.runningVersion ?? 'unknown';\n const installed = snapshot?.installedVersion ?? target.installedVersion;\n const lines = [target.packageName + ' ' + running + (installed && installed !== running ? '(磁盘已装 ' + installed + ')' : '')];\n if (target.skill) lines.push(target.skill.packageName + ' ' + target.skill.version + '(随插件,不单独查更新)');\n else lines.push('技能包随插件(不单独查更新)');\n return lines;\n}\n","/** 一键全部更新的串行队列与并发门禁(纯函数:无 DOM、无 react;面板钩子与单测共用同一份)。\n *\n * 拍板出处(#925 决议,用户已逐条确认,不可改语义):\n * 1. 一键排队串行:按目标顺序逐家查→装→轮询到收尾,一家收尾才起下一家;失败一家记 failed\n * 继续下一家;单家重试走行按钮 recheck/retry;删并行发起与交叉轮询。\n * 2. 安装中题头点不得:任一行 installing 时题头检查更新禁用;人话沿用 reasonText('update-busy') 原句。\n * 3. 行按钮全局互斥:installing 行自锁,其余行禁用不进队列;act 入口有忙拒 update-busy 且不改 rows。\n * 4. 进度各行自显:每行各自显示,他行保持 phase;题头不汇总排队数;待重启跳过安装继续下一家。\n *\n * 本文件只管「排队形状与忙判据」:传输动作由调用方注入(查/装/轮询的真实现住 `update-client.ts`,\n * 面板钩子转交,单测给假件),故改坏串行形状必被门禁逮到。\n */\nimport { reasonText } from './update-contract.js';\nimport { isAbsent, restartPendingOf, verdictOf } from './update-view.js';\nimport type { CheckOutcome, TargetInfo } from './update-view.js';\nimport type { CallFailure, CallOutcome } from './update-client.js';\n\n/** 行相位(与 `update-panel.ts` 的 `UpdateRowState['phase']` 同形;结构上兼容,不另起第二套名字)。 */\nexport type QueuePhase = 'idle' | 'checking' | 'ready' | 'installing' | 'failed';\n\n/** 队列读写的一行(结构型:面板那行原样传进来即可,不必 import 面板)。 */\nexport interface QueueRowState {\n readonly phase: QueuePhase;\n readonly outcome: CheckOutcome | null;\n readonly failure: CallFailure | null;\n}\n\n/** 任一行 installing 即忙:题头检查更新禁用、行按钮全局互斥、checkAll/act 入口直接返回的同一判据。 */\nexport function hasInstallingRow(rows: Readonly<Record<string, { readonly phase: string }>>): boolean {\n for (const key of Object.keys(rows)) {\n if (rows[key]?.phase === 'installing') return true;\n }\n return false;\n}\n\n/** 忙时拒绝的那枚失败:码与人话都取 `update-busy` 原句(#678/#740 冻结文案,本文件不写第二套话)。 */\nexport function updateBusyFailure(targetKey: string): CallFailure {\n return { ok: false, code: 'update-busy', message: reasonText('update-busy'), details: { target: targetKey }, manual: null };\n}\n\n/** 串行一轮的传输注入(面板传真实现,单测传假件;每一步的顺序断言即并发门禁)。 */\nexport interface SerialDeps {\n /** 查一家最新版(面板:`checkTarget(getCall(), target)`)。 */\n readonly check: (target: TargetInfo) => Promise<CallOutcome<CheckOutcome>>;\n /** 装完后重读真实状态(面板:`checkTarget(getCall(), target, target.phones?.status)`)。 */\n readonly checkStatus: (target: TargetInfo) => Promise<CallOutcome<CheckOutcome>>;\n /** 装上缺席包(面板:`installAbsent(getCall(), target, version)`)。 */\n readonly installAbsent: (target: TargetInfo, version: string) => Promise<CallOutcome<{ readonly packageName: string; readonly version: string }>>;\n /** 装上已装包:查→装→轮询到收尾(面板:`updateInstalled(getCall(), target, pollMs)`,形状冻结)。 */\n readonly installPresent: (target: TargetInfo) => Promise<CallOutcome<CheckOutcome>>;\n /** 只改这一行的补丁(他行保持各自 phase:各行自显的全部依据)。 */\n readonly patch: (key: string, next: QueueRowState) => void;\n /** 读这一行当前态(串行起步的那次 checking 要带着旧 outcome)。 */\n readonly getRow: (key: string) => QueueRowState | undefined;\n}\n\n/** 按目标顺序逐家查→装→收尾:一家收尾才起下一家;失败记 failed 继续;待重启跳过安装。\n *\n * 跳过安装只有一种:快照事实已是待重启(`restartPendingOf`)——由横幅点名,不在这里装第二遍。\n * 不可装(已是最新/拦下且无安装动作)记 ready 收尾;缺席且无最新版本号记 ready 收尾(等下一次检查)。\n * 本函数不汇总排队数、不碰他行(`patch` 只收本家 key)。\n */\nexport async function runSerialUpdateAll(targets: readonly TargetInfo[], deps: SerialDeps): Promise<void> {\n for (const target of targets) {\n const current = deps.getRow(target.key);\n deps.patch(target.key, { phase: 'checking', outcome: current?.outcome ?? null, failure: null });\n const checked = await deps.check(target);\n if (!checked.ok) {\n deps.patch(target.key, { phase: 'failed', outcome: null, failure: checked });\n continue;\n }\n if (restartPendingOf(checked.value.snapshot)) {\n deps.patch(target.key, { phase: 'ready', outcome: checked.value, failure: null });\n continue;\n }\n const verdict = verdictOf(target, checked.value.snapshot);\n const needInstall = verdict.action === 'update' || verdict.action === 'install' || verdict.action === 'retry';\n if (!needInstall) {\n deps.patch(target.key, { phase: 'ready', outcome: checked.value, failure: null });\n continue;\n }\n deps.patch(target.key, { phase: 'installing', outcome: checked.value, failure: null });\n if (isAbsent(target)) {\n const version = checked.value.snapshot.latestVersion;\n if (!version) {\n deps.patch(target.key, { phase: 'ready', outcome: checked.value, failure: null });\n continue;\n }\n const done = await deps.installAbsent(target, version);\n if (done.ok) {\n const refreshed = await deps.checkStatus(target);\n deps.patch(\n target.key,\n refreshed.ok\n ? { phase: 'ready', outcome: refreshed.value, failure: null }\n : { phase: 'failed', outcome: null, failure: refreshed },\n );\n } else {\n deps.patch(target.key, {\n phase: 'failed',\n outcome: checked.value,\n failure: { ...done, manual: done.manual ?? checked.value.manual ?? null },\n });\n }\n continue;\n }\n const done = await deps.installPresent(target);\n if (done.ok) {\n const refreshed = await deps.checkStatus(target);\n deps.patch(\n target.key,\n refreshed.ok\n ? { phase: 'ready', outcome: refreshed.value, failure: null }\n : { phase: 'failed', outcome: null, failure: refreshed },\n );\n } else {\n deps.patch(target.key, {\n phase: 'failed',\n outcome: checked.value,\n failure: { ...done, manual: done.manual ?? checked.value.manual ?? null },\n });\n }\n }\n}\n","/** 爱生活面板的更新界面:检查更新按钮、七家结果行、待重启横幅、缺席卡的「装上」。\n *\n * 面板侧纪律(沿 `client.ts` 既有口径):React.createElement 手写(禁 JSX)、内联 style、\n * 颜色走 DSH 主题别名(深浅主题自适应,写死值只做回退)、只用 `dsh-ctx` 镜像内的上下文成员、\n * 不 import 更新包(电话名与轮询间隔由宿主转交,见 `update-contract.ts` 的 `MANAGER_ACTIONS.targets`)。\n *\n * 视觉对照(用户给的第二张截图,参照实现 `dsh-mattpocock-skills-deck/src/client/views/SettingsPage.js:152-168`):\n * 标题右侧并排若干件——本文件出「检查更新」一件,隔壁票(爱生活面板头部与底部)在同一个\n * `actions` 容器里补星与气泡两件,故这里把容器单独留出来。\n */\nimport * as React from 'react';\nimport { checkTarget, installAbsent, loadTargets, updateInstalled } from './update-client.js';\nimport type { CallFace, CallFailure } from './update-client.js';\nimport { reasonText } from './update-contract.js';\nimport { hasInstallingRow, runSerialUpdateAll } from './update-queue.js';\nimport { cardActionOf, isAbsent, manualForDisplay, restartBannerText, restartPendingOf, showManualOf, slotStateOf, verdictOf, versionLines } from './update-view.js';\nimport type { CheckOutcome, TargetInfo, Verdict, VerdictAction } from './update-view.js';\n\n/** 面板视觉(沿用总管既有语言:内联 style,主题别名带回退)。 */\nexport const PANEL_STYLE = {\n head: { fontSize: 15, fontWeight: 700, margin: '2px 0 8px', color: 'var(--dsw-alias-label-primary, inherit)' } as React.CSSProperties,\n headRow: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, flexWrap: 'wrap' } as React.CSSProperties,\n actions: { display: 'inline-flex', alignItems: 'center', gap: 6 } as React.CSSProperties,\n meta: { color: 'var(--dsw-alias-label-secondary, #9a9a9a)', fontSize: 12, lineHeight: 1.7, marginBottom: 10 } as React.CSSProperties,\n tablist: { display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: 10 } as React.CSSProperties,\n tab: {\n border: '1px solid var(--dsw-alias-border, rgba(128,128,128,.35))',\n background: 'transparent',\n color: 'var(--dsw-alias-label-primary, inherit)',\n borderRadius: 999,\n padding: '4px 12px',\n fontSize: 13,\n cursor: 'pointer',\n } as React.CSSProperties,\n tabActive: {\n background: 'var(--dsw-alias-brand-primary, #0a84ff)',\n borderColor: 'transparent',\n color: '#fff',\n fontWeight: 700,\n } as React.CSSProperties,\n reco: {\n padding: '12px 14px',\n borderRadius: 10,\n border: '1px dashed var(--dsw-alias-border, rgba(128,128,128,.45))',\n color: 'var(--dsw-alias-label-secondary, #9a9a9a)',\n fontSize: 13,\n lineHeight: 1.7,\n } as React.CSSProperties,\n cmd: {\n display: 'block',\n marginTop: 8,\n padding: '8px 10px',\n borderRadius: 8,\n background: 'var(--dsw-alias-bg-base, rgba(128,128,128,.12))',\n color: 'var(--dsw-alias-label-primary, inherit)',\n fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',\n fontSize: 12,\n userSelect: 'all',\n } as React.CSSProperties,\n btn: {\n border: '1px solid var(--dsw-alias-border, rgba(128,128,128,.35))',\n background: 'transparent',\n color: 'var(--dsw-alias-label-primary, inherit)',\n borderRadius: 8,\n padding: '4px 12px',\n fontSize: 13,\n cursor: 'pointer',\n } as React.CSSProperties,\n banner: {\n padding: '10px 12px',\n marginBottom: 10,\n borderRadius: 8,\n border: '1px solid var(--dsw-alias-state-warning-primary, #d8a300)',\n background: 'var(--dsw-alias-bg-base, rgba(216,163,0,.12))',\n color: 'var(--dsw-alias-label-primary, inherit)',\n fontSize: 13,\n lineHeight: 1.7,\n } as React.CSSProperties,\n row: {\n padding: '10px 12px',\n marginBottom: 8,\n borderRadius: 10,\n border: '1px solid var(--dsw-alias-border, rgba(128,128,128,.3))',\n borderLeftWidth: 3,\n fontSize: 13,\n lineHeight: 1.7,\n } as React.CSSProperties,\n rowHead: { display: 'flex', alignItems: 'baseline', gap: 6, marginBottom: 2 } as React.CSSProperties,\n rowTitle: { fontWeight: 700, color: 'var(--dsw-alias-label-primary, inherit)' } as React.CSSProperties,\n rowPkg: {\n color: 'var(--dsw-alias-label-tertiary, #8a8a8a)',\n fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',\n fontSize: 12,\n } as React.CSSProperties,\n rowMeta: { color: 'var(--dsw-alias-label-secondary, #9a9a9a)', fontSize: 12 } as React.CSSProperties,\n /** 版本号用等宽字:数字要能对齐着读(「当前」「最新」两列一眼比大小)。 */\n ver: {\n fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',\n color: 'var(--dsw-alias-label-primary, inherit)',\n fontWeight: 600,\n } as React.CSSProperties,\n /** 结论那一行:**只有要动的那几档上色**(绿/灰不抢注意力,照 health-view 的三档口径)。 */\n verdictLine: { fontSize: 12.5, marginTop: 2 } as React.CSSProperties,\n reason: { color: 'var(--dsw-alias-state-error-primary, #ff6b6b)', fontSize: 12, marginTop: 4 } as React.CSSProperties,\n skill: {\n color: 'var(--dsw-alias-label-tertiary, #8a8a8a)',\n fontSize: 12,\n marginTop: 4,\n fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',\n } as React.CSSProperties,\n /** 行内主按钮(装上/装上更新):品牌底色 —— 全屏只有「能点的」这一种是这个颜色。 */\n btnPrimary: {\n border: '1px solid transparent',\n background: 'var(--dsw-alias-brand-primary, #0a84ff)',\n color: '#fff',\n borderRadius: 8,\n padding: '5px 14px',\n fontSize: 13,\n fontWeight: 700,\n cursor: 'pointer',\n marginTop: 6,\n } as React.CSSProperties,\n /** 分档标记:**形状是第二条读数**(灰度截图、色弱视角下也分得开哪一行要动)。 */\n mark: { fontWeight: 700, fontSize: 13, lineHeight: 1 } as React.CSSProperties,\n overlay: {\n position: 'fixed',\n inset: 0,\n zIndex: 40,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n padding: 16,\n background: 'rgba(0,0,0,.45)',\n } as React.CSSProperties,\n /** 浮层是**竖排两段**:题头固定、结果列表自己滚(题头跟着滚会把关闭键滚出视野 —— 用户实测反馈)。 */\n dialog: {\n width: '100%',\n maxWidth: 560,\n maxHeight: '80vh',\n display: 'flex',\n flexDirection: 'column',\n overflow: 'hidden',\n borderRadius: 12,\n border: '1px solid var(--dsw-alias-border, rgba(128,128,128,.4))',\n background: 'var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #222))',\n color: 'var(--dsw-alias-label-primary, inherit)',\n boxShadow: '0 12px 32px rgba(0,0,0,.35)',\n } as React.CSSProperties,\n dialogHead: {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n gap: 8,\n flex: '0 0 auto',\n padding: '12px 14px',\n borderBottom: '1px solid var(--dsw-alias-border, rgba(128,128,128,.3))',\n } as React.CSSProperties,\n dialogTitle: { fontSize: 14, fontWeight: 700 } as React.CSSProperties,\n /** 唯一会滚的那一段。 */\n dialogBody: {\n flex: '1 1 auto',\n minHeight: 0,\n overflowY: 'auto',\n padding: '12px 14px',\n } as React.CSSProperties,\n close: {\n border: '1px solid var(--dsw-alias-border, rgba(128,128,128,.35))',\n background: 'transparent',\n color: 'var(--dsw-alias-label-primary, inherit)',\n borderRadius: 8,\n width: 26,\n height: 26,\n lineHeight: 1,\n fontSize: 14,\n cursor: 'pointer',\n } as React.CSSProperties,\n dialogNote: { color: 'var(--dsw-alias-label-tertiary, #8a8a8a)', fontSize: 12, marginTop: 4 } as React.CSSProperties,\n};\n\n/** 一行的分档:**要动的那几档才上色**(`ok`/`idle` 保持素色,不跟红黄抢注意力 —— 照 `health-view.ts` 的三档口径)。\n * 每档同时给**形状**(`↑ ! ✕ ✓`):颜色之外的第二条读数,灰度截图与色弱视角下同样分得开。 */\ntype RowTone = 'ok' | 'update' | 'blocked' | 'failed' | 'idle';\n\nconst ROW_TONE: Readonly<Record<RowTone, { readonly mark: string; readonly color: string; readonly tint: string }>> = {\n update: { mark: '↑', color: 'var(--dsw-alias-brand-primary, #0a84ff)', tint: 'rgba(10,132,255,.08)' },\n blocked: { mark: '!', color: 'var(--dsw-alias-state-warning-primary, #d8a300)', tint: 'rgba(216,163,0,.10)' },\n failed: { mark: '✕', color: 'var(--dsw-alias-state-error-primary, #ff6b6b)', tint: 'rgba(255,107,107,.10)' },\n ok: { mark: '✓', color: 'var(--dsw-alias-state-success-primary, #4ec9a0)', tint: 'transparent' },\n idle: { mark: '·', color: 'var(--dsw-alias-label-tertiary, #8a8a8a)', tint: 'transparent' },\n};\n\n/** 这一行算哪一档:取数失败 > 装不上 > 有新版 > 待重启/源码装 > 已是最新 > 未查。 */\nexport function rowToneOf(row: UpdateRowState, verdict: Verdict | null): RowTone {\n if (row.failure !== null || row.phase === 'failed') return 'failed';\n if (row.phase === 'installing') return 'update';\n if (verdict === null) return 'idle';\n switch (verdict.kind) {\n case 'update-available': return 'update';\n case 'blocked': return 'blocked';\n case 'up-to-date': return 'ok';\n default: return 'idle';\n }\n}\n\n/** 一行的运行时状态:还没查 / 查着 / 有结果 / 装着 / 出错。 */\nexport interface UpdateRowState {\n readonly phase: 'idle' | 'checking' | 'ready' | 'installing' | 'failed';\n readonly outcome: CheckOutcome | null;\n readonly failure: CallFailure | null;\n}\n\nexport interface UpdateRowsFace {\n readonly targets: readonly TargetInfo[];\n readonly rows: Readonly<Record<string, UpdateRowState>>;\n readonly pollMs: number;\n readonly loadError: string | null;\n readonly checking: boolean;\n /** 一键全部更新是否在串行中(逐家查→装→收尾,一家收尾才起下一家)。 */\n readonly updatingAll: boolean;\n /** 结果浮层是否打开(点「检查更新」开,关法三种:×、点遮罩、Esc)。 */\n readonly open: boolean;\n close(): void;\n checkAll(): void;\n /** 一键全部更新:按目标顺序串行收尾,失败一家记 failed 继续下一家。 */\n updateAll(): void;\n /** 执行这一行按钮对应的动作(装上/装上更新/重试安装/重新检查)。 */\n act(target: TargetInfo, action: VerdictAction): void;\n}\n\n/** 四个动作在屏上的名字:按钮文案与判据同源(票 #740)。 */\nconst ACTION_LABEL: Readonly<Record<VerdictAction, string>> = {\n install: '装上',\n update: '装上更新',\n retry: '重试安装',\n recheck: '重新检查',\n};\n\nconst IDLE: UpdateRowState = { phase: 'idle', outcome: null, failure: null };\n\n/** 面板的更新状态源:挂载时取七个目标的表,之后按需查/装(组件与按钮共用一个 face)。\n *\n * 并发形状(#925 拍板四条,用户已确认):\n * - 一键全部更新按目标顺序串行(查→装→轮询到收尾,一家收尾才起下一家;失败记 failed 继续);\n * - 任一行 installing 时 checkAll 入口直接返回、题头禁用;\n * - act 入口有忙直接返回(有忙拒 update-busy 且不改 rows,含同家第二点);\n * - 每行各自显示(patch 只写本家 key),题头不汇总排队数。\n */\nexport function useUpdateRows(getCall: () => CallFace | null): UpdateRowsFace {\n const [targets, setTargets] = React.useState<readonly TargetInfo[]>([]);\n const [rows, setRows] = React.useState<Record<string, UpdateRowState>>({});\n const [pollMs, setPollMs] = React.useState(1000);\n const [loadError, setLoadError] = React.useState<string | null>(null);\n const [checking, setChecking] = React.useState(false);\n const [updatingAll, setUpdatingAll] = React.useState(false);\n const [open, setOpen] = React.useState(false);\n const alive = React.useRef(true);\n React.useEffect(\n () => () => {\n alive.current = false;\n },\n [],\n );\n /** 重取七个目标的表(装机读数变了就重取:缺席卡的态是拿这张表算的,缓存住它会继续说旧话)。 */\n const reload = React.useCallback(async () => {\n const loaded = await loadTargets(getCall());\n if (!alive.current) return;\n if (loaded.ok) {\n setTargets(loaded.value.targets);\n setPollMs(loaded.value.pollMs);\n setLoadError(null);\n } else {\n setLoadError(loaded.message);\n }\n }, [getCall]);\n React.useEffect(() => {\n void reload();\n }, [reload]);\n const patch = React.useCallback((key: string, next: UpdateRowState) => {\n setRows((previous) => ({ ...previous, [key]: next }));\n }, []);\n // 回调闭包里读到的 rows/targets 可能是旧的:忙守卫必须看最新值,故用 ref 镜像。\n const rowsRef = React.useRef(rows);\n rowsRef.current = rows;\n const targetsRef = React.useRef(targets);\n targetsRef.current = targets;\n const pollMsRef = React.useRef(pollMs);\n pollMsRef.current = pollMs;\n const updatingAllRef = React.useRef(false);\n const checkAll = React.useCallback(() => {\n // 安装中题头点不得:任一行 installing 直接返回,不改任何行。\n if (hasInstallingRow(rowsRef.current) || updatingAllRef.current) return;\n setChecking(true);\n setOpen(true);\n const list = targetsRef.current;\n const snapshot = rowsRef.current;\n for (const target of list) patch(target.key, { phase: 'checking', outcome: snapshot[target.key]?.outcome ?? null, failure: null });\n void Promise.all(\n list.map(async (target) => {\n const result = await checkTarget(getCall(), target);\n patch(target.key, result.ok ? { phase: 'ready', outcome: result.value, failure: null } : { phase: 'failed', outcome: null, failure: result });\n }),\n ).finally(() => {\n if (alive.current) setChecking(false);\n });\n }, [getCall, patch]);\n const updateAll = React.useCallback(() => {\n // 一键串行期间不重入;任一行 installing 也不重入(不进队列)。\n if (hasInstallingRow(rowsRef.current) || updatingAllRef.current) return;\n const list = targetsRef.current;\n if (list.length === 0) return;\n updatingAllRef.current = true;\n setUpdatingAll(true);\n setOpen(true);\n void (async () => {\n try {\n await runSerialUpdateAll(list, {\n check: (target) => checkTarget(getCall(), target),\n checkStatus: (target) => checkTarget(getCall(), target, target.phones?.status),\n installAbsent: (target, version) => installAbsent(getCall(), target, version),\n installPresent: (target) => updateInstalled(getCall(), target, pollMsRef.current),\n patch,\n getRow: (key) => rowsRef.current[key],\n });\n // 装机读数变了(磁盘上多/换了一个包):重取目标表,缺席卡的态跟着变。\n await reload();\n } finally {\n updatingAllRef.current = false;\n if (alive.current) setUpdatingAll(false);\n }\n })();\n }, [getCall, patch, reload]);\n const act = React.useCallback(\n (target: TargetInfo, action: VerdictAction) => {\n // 行按钮全局互斥:有忙拒 update-busy 且不改 rows(含同家第二点,不进队列)。\n if (hasInstallingRow(rowsRef.current) || updatingAllRef.current) return;\n const current = rowsRef.current[target.key] ?? IDLE;\n // 「重新检查」:只重读这一家(重新绑一次安装态指纹)。\n if (action === 'recheck') {\n patch(target.key, { phase: 'checking', outcome: current.outcome, failure: null });\n void (async () => {\n const checked = await checkTarget(getCall(), target);\n patch(target.key, checked.ok\n ? { phase: 'ready', outcome: checked.value, failure: null }\n : { phase: 'failed', outcome: null, failure: checked });\n })();\n return;\n }\n patch(target.key, { ...current, phase: 'installing', failure: null });\n void (async () => {\n const absent = isAbsent(target);\n let version = current.outcome?.snapshot.latestVersion ?? null;\n if (absent && version === null) {\n const checked = await checkTarget(getCall(), target);\n if (!checked.ok) {\n patch(target.key, { phase: 'failed', outcome: null, failure: checked });\n return;\n }\n patch(target.key, { phase: 'installing', outcome: checked.value, failure: null });\n version = checked.value.snapshot.latestVersion;\n }\n // 已装的那条路,`updateInstalled` **自己会先查一次**再提交(凭证是那一查现签的)——\n // 这就是「装了别家之后,这一家的「装上更新」照样点得动」的原因:面板从不拿旧凭证去提交。\n const result = absent\n ? await installAbsent(getCall(), target, version ?? '')\n : await updateInstalled(getCall(), target, pollMsRef.current);\n if (result.ok) {\n // 装完不自己编快照:向宿主问一次真实状态(装到磁盘但宿主还跑着旧版 ⇒ 待重启)。\n const refreshed = await checkTarget(getCall(), target, target.phones?.status);\n patch(target.key, refreshed.ok ? { phase: 'ready', outcome: refreshed.value, failure: null } : { phase: 'failed', outcome: null, failure: refreshed });\n // 装机读数变了(磁盘上多/换了一个包):重取目标表,缺席卡的态跟着变,\n // 否则它会拿着挂载时那份读数继续说「未安装」,直到用户刷新页面。\n await reload();\n return;\n }\n patch(target.key, {\n phase: 'failed',\n outcome: current.outcome,\n failure: { ...result, manual: result.manual ?? current.outcome?.manual ?? null },\n });\n })();\n },\n [getCall, patch, reload],\n );\n return { targets, rows, pollMs, loadError, checking, updatingAll, open, close: () => setOpen(false), checkAll, updateAll, act };\n}\n\n/** 标题右侧两件:「检查更新」一次查七家 + 「全部更新」按顺序串行装(吃 `useUpdateRows` 的表)。\n *\n * 禁用形状(#925 第 2 条):任一行 installing 时两件都点不得;人话沿用 `update-busy` 原句\n * (挂在 title 上,按钮文本仍是动作名)。题头不汇总排队数:串行进度只在各行自己显示。\n */\nexport function CheckUpdateButton(props: { readonly face: UpdateRowsFace }): React.ReactElement {\n const { checking, updatingAll, checkAll, updateAll, targets, rows } = props.face;\n const busy = hasInstallingRow(rows) || updatingAll;\n const checkDisabled = checking || updatingAll || busy || targets.length === 0;\n const allDisabled = checking || updatingAll || busy || targets.length === 0;\n const busyText = reasonText('update-busy');\n return React.createElement(\n 'span',\n { style: PANEL_STYLE.actions },\n React.createElement(\n 'button',\n {\n type: 'button',\n style: checkDisabled ? { ...PANEL_STYLE.btn, opacity: 0.55, cursor: 'default' } : PANEL_STYLE.btn,\n disabled: checkDisabled,\n onClick: checkAll,\n title: busy ? busyText : '检查总管与六家插件的版本',\n },\n checking ? '检查中…' : '检查更新',\n ),\n React.createElement(\n 'button',\n {\n type: 'button',\n style: allDisabled ? { ...PANEL_STYLE.btn, opacity: 0.55, cursor: 'default' } : PANEL_STYLE.btn,\n disabled: allDisabled,\n onClick: updateAll,\n title: busy ? busyText : '按顺序逐家装上更新(一家收尾才起下一家)',\n },\n updatingAll ? '更新中…' : '全部更新',\n ),\n );\n}\n\n/** 待重启横幅:一行总账,只点名哪几家要重启(理由与做法写在各家卡片上,见 `restartBannerText`)。 */\nfunction RestartBanners(props: { readonly face: UpdateRowsFace }): React.ReactElement | null {\n const titles: string[] = [];\n for (const target of props.face.targets) {\n const outcome = props.face.rows[target.key]?.outcome;\n if (!outcome) continue;\n if (restartPendingOf(outcome.snapshot)) titles.push(target.title);\n }\n const text = restartBannerText(titles);\n if (text === null) return null;\n return React.createElement('div', { style: PANEL_STYLE.banner }, text);\n}\n\n/** 七家结果:装不了的与待重启的常驻(横幅),详细结果放**居中浮层**(点「检查更新」开)。\n *\n * 为什么是浮层:内联展开会把面板本体撑长、且它自身没有关闭控件(真机第一次做成内联后用户反馈\n * 「打开就无法收回了」)。参照实现也是浮层:`dsh-mattpocock-skills-deck/src/client/views/SettingsPage.js`\n * 的 `upd.dialog`。关法三种:右上角 ×、点遮罩、Esc(Esc 靠浮层自己拿焦点后收 keydown,\n * 不摸 window——客户端纪律禁直写 DOM 全局)。\n * 手工命令只在「装不了」或「装失败」的行显示:正常用户用不到,摊在每行上既吵又误导。\n */\nexport function UpdateResults(props: { readonly face: UpdateRowsFace }): React.ReactElement | null {\n const { targets, rows, loadError, checking, updatingAll, open, close } = props.face;\n const dialogRef = React.useRef<HTMLDivElement | null>(null);\n React.useEffect(() => {\n if (open) dialogRef.current?.focus();\n }, [open]);\n const shown = targets.filter((target) => rows[target.key] && rows[target.key].phase !== 'idle');\n if (loadError) return React.createElement('div', { style: PANEL_STYLE.reason }, '更新能力没接上:' + loadError);\n if (targets.length === 0) return null;\n if (shown.length === 0 && !open) return null;\n // 行按钮全局互斥(#925 第 3 条):任一行 installing 或一键串行中,其余行禁用、不进队列;\n // installing 行自己同样自锁。各行进度自显:他行保持各自 phase,这里只读不改。\n const locked = hasInstallingRow(rows) || updatingAll;\n const body = shown.map((target) => {\n const row = rows[target.key];\n const snapshot = row.outcome?.snapshot ?? null;\n // 快照在就按它画卡。(第四轮改:装成一家之后**不再**把别家换成「重新检查」——\n // 装的那条路本来就会在提交前重查一次拿新凭证,旧快照从不到达守卫,那句作废提示只是白挡一次点击。)\n const verdict = snapshot ? verdictOf(target, snapshot) : null;\n const manual = manualForDisplay(target, row.failure?.manual ?? row.outcome?.manual ?? null);\n const busy = row.phase === 'installing';\n // 手工命令只在**面板代劳不了**时才摊出来(判据收在 `showManualOf` 里,与单测同一份)。\n const showManual = manual !== null && showManualOf(verdict, row.phase);\n // 失败的行没有快照、也就没有结论,但照样要给一颗够得着的按钮(票 #740 第三轮,见 `cardActionOf`)。\n const buttonAction = cardActionOf(verdict, row.phase);\n const tone = rowToneOf(row, verdict);\n const paint = ROW_TONE[tone];\n /** 这一行「当前/最新」两个版本号:不等才算有新版,把「最新」那格上色(用户扫一眼先看有没有箭头)。 */\n const running = snapshot?.runningVersion ?? target.runningVersion ?? null;\n const latest = snapshot?.latestVersion ?? null;\n const newer = running !== null && latest !== null && latest !== running;\n const lit = tone === 'update' || tone === 'blocked' || tone === 'failed';\n return React.createElement(\n 'div',\n { key: target.key, style: { ...PANEL_STYLE.row, borderLeftColor: paint.color, background: paint.tint } },\n React.createElement(\n 'div',\n { style: PANEL_STYLE.rowHead },\n React.createElement('span', { style: { ...PANEL_STYLE.mark, color: paint.color }, 'aria-hidden': 'true' }, paint.mark),\n React.createElement('span', { style: PANEL_STYLE.rowTitle }, target.title),\n React.createElement('span', { style: PANEL_STYLE.rowPkg }, '· ' + target.packageName),\n ),\n React.createElement(\n 'div',\n { style: PANEL_STYLE.rowMeta },\n '当前 ',\n React.createElement('b', { style: PANEL_STYLE.ver }, String(running ?? '未安装')),\n ' · 最新 ',\n React.createElement(\n 'b',\n { style: newer ? { ...PANEL_STYLE.ver, color: paint.color } : PANEL_STYLE.ver },\n String(latest ?? '未查'),\n ),\n ),\n React.createElement(\n 'div',\n { style: lit ? { ...PANEL_STYLE.verdictLine, color: paint.color, fontWeight: 600 } : { ...PANEL_STYLE.verdictLine, color: 'var(--dsw-alias-label-secondary, #9a9a9a)' } },\n busy ? '正在装,请稍候…' : (verdict?.text ?? (checking ? '检查中…' : '')),\n ),\n buttonAction\n ? React.createElement(\n 'button',\n {\n type: 'button',\n style: locked ? { ...PANEL_STYLE.btnPrimary, opacity: 0.55, cursor: 'default' } : PANEL_STYLE.btnPrimary,\n disabled: locked,\n title: locked ? reasonText('update-busy') : undefined,\n onClick: () => {\n props.face.act(target, buttonAction);\n },\n },\n ACTION_LABEL[buttonAction],\n )\n : null,\n row.failure ? React.createElement('div', { style: PANEL_STYLE.reason }, '装不上:' + row.failure.message) : null,\n versionLines(target, snapshot).map((line, index) =>\n React.createElement('div', { key: String(index), style: PANEL_STYLE.skill }, line),\n ),\n // 命令块先说它是干什么的(票 #740):给一条命令不给用途,用户不知道该不该敲。\n showManual ? React.createElement('div', { style: PANEL_STYLE.skill }, '在终端里执行这条命令可以手动完成这一步:') : null,\n showManual ? React.createElement('code', { style: PANEL_STYLE.cmd }, manual) : null,\n );\n });\n return React.createElement(\n 'div',\n null,\n React.createElement(RestartBanners, { face: props.face }),\n open\n ? React.createElement(\n 'div',\n {\n style: PANEL_STYLE.overlay,\n role: 'presentation',\n onClick: () => close(),\n },\n React.createElement(\n 'div',\n {\n ref: dialogRef,\n tabIndex: -1,\n role: 'dialog',\n 'aria-label': '检查更新结果',\n style: PANEL_STYLE.dialog,\n onClick: (event: React.MouseEvent) => event.stopPropagation(),\n onKeyDown: (event: React.KeyboardEvent) => {\n if (event.key === 'Escape') close();\n },\n },\n React.createElement(\n 'div',\n { style: PANEL_STYLE.dialogHead },\n // 标题只说「检查更新」:查的是几个包由每行自己交代,不必在标题里数数。\n React.createElement('div', { style: PANEL_STYLE.dialogTitle }, '检查更新'),\n React.createElement(\n 'button',\n { type: 'button', style: PANEL_STYLE.close, onClick: () => close(), title: '关闭', 'aria-label': '关闭' },\n '×',\n ),\n ),\n React.createElement(\n 'div',\n { style: PANEL_STYLE.dialogBody },\n body,\n React.createElement(\n 'div',\n { style: PANEL_STYLE.dialogNote },\n '安装在宿主后台继续:关掉这块不会中断它。再点「检查更新」看最新进度。',\n ),\n ),\n ),\n )\n : null,\n );\n}\n\n/** 缺席卡:**按两处独立事实分三态**,每态只给能改变状态的动作。\n *\n * - `absent`(磁盘上没有这个包)→「未安装」+「装上」按钮(本票原有的能力);\n * - `unregistered-product`(装着,但产物里没有页签槽注册代码)→ 说实情、**不给按钮**:\n * 重装改不了产物里有没有注册代码(真机实测:作息/居家/大厨/记账 的已发布 0.2.0\n * 产物里根本没有注册代码,见票 #723);给可复制命令只是参考;\n * - `not-connected`(装着、产物也有注册代码,设置页却没送到浏览器)→ 重启宿主;仍不行看日志;\n * - `connected` 不归本卡管:那时面板显示的是那家自己的设置页。\n *\n * 原实现只看页签槽 ledger 就断言「未安装」——那对后两种情形是说假话,且点「装上」\n * 会走进更新包的流程(它已经装着 ⇒ 没有新版 ⇒ 没有凭证)并报出误导的「查新版失败」。\n */\nexport function AbsentCard(props: {\n readonly target: TargetInfo | null;\n readonly fallbackCommand: string;\n readonly inLedger: boolean;\n readonly face: UpdateRowsFace;\n}): React.ReactElement {\n const target = props.target;\n const row = target ? props.face.rows[target.key] : null;\n const busy = row?.phase === 'installing';\n // 缺席卡同理全局互斥(#925 第 3 条):别家 installing 或一键串行中,这张卡的装上/重新检查都不进队列。\n const locked = busy || hasInstallingRow(props.face.rows) || props.face.updatingAll;\n const manual = (target ? manualForDisplay(target, row?.failure?.manual ?? row?.outcome?.manual ?? null) : null) ?? props.fallbackCommand;\n const installed = target ? (target.installedVersion ?? target.runningVersion) : null;\n const state = slotStateOf(target, props.inLedger);\n const blockedVerdict = target && row?.outcome && row.outcome.snapshot.blockedReason !== null ? verdictOf(target, row.outcome.snapshot) : null;\n let reason: string | null = row?.failure?.message ?? null;\n if (reason === null && blockedVerdict) reason = blockedVerdict.text;\n // 这张卡也要有一颗够得着的按钮(票 #740 第三轮):上一次失败之后,屏上那句写着「再点「重新检查」」,\n // 这张卡原先一颗都不给(absent 态只给「装上」)。判据与结果行同一份(`cardActionOf`)。\n const retryAction = row ? cardActionOf(blockedVerdict, row.phase) : null;\n let headline: string;\n switch (state) {\n case 'absent':\n headline = target\n ? '未安装[' + target.packageName + '],装上后这个页签就能用了。'\n : '这个页签对应的插件还没装。';\n break;\n case 'unregistered-product':\n headline =\n target!.packageName +\n ' ' +\n String(installed) +\n ' 已安装,但这一版的插件包里没有「爱生活页签」的注册代码:要等它发布带注册代码的新版本。重启和重装都不会改变这一点。';\n break;\n default:\n headline =\n target!.packageName +\n ' ' +\n String(installed) +\n ' 已安装,插件包里也有注册代码,但它的设置页没有加载到面板:重启 DSH 后再看这个页签;仍然这样,就在 DSH 日志里找它的报错。';\n break;\n }\n return React.createElement(\n 'div',\n { style: PANEL_STYLE.reco },\n React.createElement('div', null, headline),\n state === 'absent'\n ? React.createElement(\n 'button',\n {\n type: 'button',\n style: locked ? { ...PANEL_STYLE.btn, marginTop: 8, opacity: 0.55 } : { ...PANEL_STYLE.btn, marginTop: 8 },\n disabled: locked,\n title: locked ? reasonText('update-busy') : undefined,\n onClick: () => {\n if (target) props.face.act(target, 'install');\n },\n },\n busy ? '正在装,请稍候…' : '装上',\n )\n : null,\n // 「重新检查」只在上面那句真写了它、且这张卡上还没有同一颗按钮时补出来(absent 态的「装上」不动)。\n retryAction === 'recheck' && target\n ? React.createElement(\n 'button',\n {\n type: 'button',\n style: locked ? { ...PANEL_STYLE.btn, marginTop: 8, marginLeft: 8, opacity: 0.55 } : { ...PANEL_STYLE.btn, marginTop: 8, marginLeft: 8 },\n disabled: locked,\n title: locked ? reasonText('update-busy') : undefined,\n onClick: () => props.face.act(target, 'recheck'),\n },\n ACTION_LABEL.recheck,\n )\n : null,\n reason ? React.createElement('div', { style: PANEL_STYLE.reason }, reason) : null,\n React.createElement('code', { style: PANEL_STYLE.cmd }, manual),\n );\n}\n","/** 配置体检:各家技能回给面板的那份报告的形状(唯一真相在技能侧,本件只是面板侧镜像)。\n *\n * 为什么形状这样定(票 #706「开工前的形状裁定」第一条):**判据住技能侧**。\n * 每条检查项的有效值只有技能源码算得出来(例:`resolveDbDir()` 里「空串=按默认落点」),\n * 插件包对技能的全部能力是 spawn `cmd_read` 与读 stdout 信封,看不到那些算式。所以:\n *\n * 技能侧出**判据**(每条给红/黄/绿 + 一句话 + 去哪修);\n * 插件包出**透传**(把技能回执原样交回,不重写一个字);\n * 总管出**总览与渲染**(零特例:不认任何一家,只认下面这个形状)。\n *\n * 于是「同一份数据、不许两处各算一遍」是结构上的性质:面板一次取数拿到六份报告,\n * 总览一行与各家那张表都从同一个快照读(见 `health-panel.ts` 的单一状态源)。\n *\n * 三档判据的语义(照检查表 `docs/research/check-table-671-life-panel-20260917.html`):\n * · `red`=现在用不了,必须去修;\n * · `yellow`=现在这状态能用,但有人真去用那个功能时会撞上(例:照片目录没配);\n * · `green`=正常。\n */\n\n/** 三档判据。 */\nexport type HealthStatus = 'red' | 'yellow' | 'green';\n\n/** 一条检查项的回执。 */\nexport interface HealthItem {\n /** 稳定标识:配置项就是它在配置文件里的键路径(`db.dir`),非配置项用点式名(`xunji.cli`)。 */\n readonly id: string;\n /** 页面上那一行的中文标题(技能侧定,面板不重写)。 */\n readonly title: string;\n readonly status: HealthStatus;\n /** 一句话:现在是什么状态,带上路径或门槛这类实证。 */\n readonly message: string;\n /** 去哪修。`green` 时可为空。 */\n readonly action: string;\n /** 这个值从哪来(只陈述,不判好坏):`配置文件`/`默认值`/`环境`。非取值类检查项可省。 */\n readonly source?: string;\n}\n\n/** 一家的体检报告。 */\nexport interface HealthReport {\n /** 技能名(`calorie`/`bill`/…),等于该家配置文件的主体名。 */\n readonly skill: string;\n /** 实际生效的那份配置文件路径。 */\n readonly configPath: string;\n /** 实际生效的数据目录。 */\n readonly dataDir: string;\n readonly items: readonly HealthItem[];\n}\n\n/** 体检出口:技能侧在**进分派层之前**拦下的那一类只读命令名(照三个配置命令的先例)。\n *\n * 载荷:入参 `{}`;回包 `{report}`(技能侧 envelope 的 `data`)。**只报不改**:\n * 不建目录、不写文件、不落默认配置——凡是要建目录才算得出的结论,一律当「不在」报。 */\nexport const HEALTH_CHECK_KEY = 'config.check' as const;\n\n/** 面板第二段参数(与各家的端点名同值,和各家的 `RPC_CHANNEL` 配成一次调用)。 */\nexport const HEALTH_ENDPOINT = 'config.check' as const;\n\n/** 三档排序:数字大的更严重(总览一行取**最严重**那档当那一家的灯)。 */\nconst RANK: Readonly<Record<HealthStatus, number>> = { green: 0, yellow: 1, red: 2 };\n\n/** 一家的总判据:全部项里最严重的那一档;一条都没有时回 `null`(不冒充绿)。 */\nexport function worstStatus(items: readonly HealthItem[]): HealthStatus | null {\n let worst: HealthStatus | null = null;\n for (const item of items) {\n if (worst === null || RANK[item.status] > RANK[worst]) worst = item.status;\n }\n return worst;\n}\n\n/** 三档计数(面板那一行「红 N 黄 N」用的就是它)。 */\nexport function countByStatus(items: readonly HealthItem[]): Readonly<Record<HealthStatus, number>> {\n const out = { red: 0, yellow: 0, green: 0 };\n for (const item of items) out[item.status] += 1;\n return out;\n}\n\n/** 回执校验:面板只渲染自己认得的形状,认不出即报「回执异常」,不猜、不返空冒充正常。 */\nexport function isHealthReport(raw: unknown): raw is HealthReport {\n if (typeof raw !== 'object' || raw === null) return false;\n const report = raw as Partial<HealthReport>;\n if (typeof report.skill !== 'string' || report.skill.length === 0) return false;\n if (typeof report.configPath !== 'string' || typeof report.dataDir !== 'string') return false;\n if (!Array.isArray(report.items)) return false;\n return report.items.every((item: unknown) => {\n if (typeof item !== 'object' || item === null) return false;\n const row = item as Partial<HealthItem>;\n return typeof row.id === 'string' && typeof row.title === 'string'\n && (row.status === 'red' || row.status === 'yellow' || row.status === 'green')\n && typeof row.message === 'string' && typeof row.action === 'string';\n });\n}\n","/** 面板到各家的配置体检:一次取数、六份报告(纯异步函数:无 DOM、无 react;传输口注入,便于单测)。\n *\n * 三条纪律:\n * · **只走各家自己的电话**(`rpc.call(CARRIER_BASE, <电话名>, {method, payload}, …)`)——总管不认任何\n * 一家的端点、也不加任何单品的依赖;电话名与端点名都是各家契约件公开的事实。\n * ⚠ **载体形状**(#735 真机踩到):第一段必须是**载体基段** `/api`,第二段是那家的电话名\n * (=它注册在 `/api<通道>` 上的那一段),端点名写进载荷的 `method`。写错第一段就是 404,\n * 而 404 只落成「那一家取不到数」,屏上看起来像按钮坏了(先例:总管自己的电话在 #678 栽过同一处,\n * 见 `update-contract.ts` 的 `CARRIER_BASE`)。\n * · **一次取数,六份报告**:总览那行与各家那张表读的是同一份结果(见 `health-panel.ts` 的单一状态源),\n * 不许两处各算一遍。\n * · 有界超时:一家不回不该拖死整屏(技能侧 spawn 上限 20 s,这里给 25 s)。\n */\nimport { HEALTH_ENDPOINT, isHealthReport } from './health-contract.js';\nimport type { HealthReport } from './health-contract.js';\nimport { CARRIER_BASE } from './update-contract.js';\nimport type { CallFace } from './update-client.js';\n\n/** 传输口:与更新面**同一张脸**(`connection.rpc.call` 的四参形状,cookbook §6)——一张脸一处定义。 */\nexport type HealthCallFace = CallFace;\n\n/** 一家的电话名:通道名去掉前导斜杠那一段(各家宿主半注册在 `/api` + 通道名,信封 `method` 就是它)。 */\nexport function phoneOf(channel: string): string {\n return channel.startsWith('/') ? channel.slice(1) : channel;\n}\n\n/** 整批取数的时限(毫秒),**不是**一家一份:六家的活是在**同一个宿主线程**上排队干的。\n *\n * 为什么不是「技能侧 spawn 上限 20 s + 一点余量」(票 #740 对抗式审查第二轮逮到):\n * 各家的宿主半用的是 `spawnSync`(各单品的 `src/bridge.ts` 里 `SPAWN_TIMEOUT_MS = 20_000`),\n * 它**阻塞**宿主的事件循环——六通电话同时打进来,真正干活的是一个接一个串起来的。\n * 于是「六家之和」才是这一批的真实上界:按一家 20 s 算最坏 120 s,常见情形(一家 1~3 s)约 10 s。\n * 60 s 取两者之间:给常见情形留足余量,又不至于让一次卡死把按钮按成两分钟不动。\n * 单家的错照样逐家报(那家页签里的体检表会写「体检超时(60s)…」)。 */\nexport const HEALTH_TIMEOUT_MS = 60_000 as const;\n\n/** 一家的一行:报告取到了就是 `report`,取不到就是 `error`(缺席的包不进这张表)。 */\nexport interface HealthFetchRow {\n /** 该家插件包名(=爱生活页签槽 id,也是缺席判定的那个 join 键)。 */\n readonly id: string;\n readonly report: HealthReport | null;\n readonly error: string | null;\n}\n\n/** 一次取数的结果:每家一行(顺序照页签顺序)。 */\nexport interface HealthFetchResult {\n readonly rows: readonly HealthFetchRow[];\n /** 取数过程本身的错(通道缺席之类),不是某一家的问题。 */\n readonly error: string | null;\n}\n\n/** 账本条目里本票要用的那一段(页签槽注册时写进 options)。 */\nexport interface HealthTabRow {\n readonly id: string;\n readonly channel: string;\n}\n\nfunction timeoutText(): string {\n return '体检超时(' + String(Math.round(HEALTH_TIMEOUT_MS / 1000)) + 's):技能没在时限内回话。';\n}\n\nasync function fetchOne(call: HealthCallFace, row: HealthTabRow): Promise<{ readonly report: HealthReport | null; readonly error: string | null }> {\n try {\n const result = await call(\n CARRIER_BASE,\n phoneOf(row.channel),\n { method: HEALTH_ENDPOINT, payload: {} },\n AbortSignal.timeout(HEALTH_TIMEOUT_MS),\n );\n if (!result || typeof result !== 'object' || typeof (result as { ok?: unknown }).ok !== 'boolean') {\n return { report: null, error: '回执信封异常(非 ok 信封)。' };\n }\n if (!result.ok) {\n const error = (result as { error?: { code?: unknown; message?: unknown } }).error;\n const message = typeof error?.message === 'string' && error.message.length > 0 ? error.message : '体检没跑起来。';\n return { report: null, error: message };\n }\n const value = (result as { value?: unknown }).value;\n if (!isHealthReport(value)) return { report: null, error: '体检报告形状认不出(技能包与总管版本对不上?)。' };\n return { report: value, error: null };\n } catch (error) {\n if (error instanceof Error && (error.name === 'AbortError' || error.name === 'TimeoutError')) {\n return { report: null, error: timeoutText() };\n }\n return { report: null, error: '体检没跑起来:' + (error instanceof Error ? error.message : String(error)) };\n }\n}\n\n/** 取六家的体检报告(缺席的包不在此列,由调用方按账本判断)。\n *\n * `onRow`(票 #741):某一家一落定就回调一次——面板据此**按家增量**画,不再等齐全部。\n * 六家的电话是并发打出去的,但宿主里干活的只有一个线程(各单品宿主半 `spawnSync`),\n * 所以真实到达顺序就是它们被串行干完的顺序;先回来的那家先画出来,用户才看得见进度。 */\nexport async function loadHealthReports(\n call: HealthCallFace | null,\n tabs: readonly HealthTabRow[],\n onRow?: (row: HealthFetchRow) => void,\n): Promise<HealthFetchResult> {\n if (typeof call !== 'function') return { rows: [], error: '宿主连接缺席:connection.rpc.call 不可用' };\n const rows = await Promise.all(tabs.map(async (tab): Promise<HealthFetchRow> => {\n const one = await fetchOne(call, tab);\n const row: HealthFetchRow = { id: tab.id, report: one.report, error: one.error };\n // 回调里的异常不许把整批拖下水:这是画图的旁路,不是取数本身。\n try {\n onRow?.(row);\n } catch {\n /* 旁路失败不影响整批结果 */\n }\n return row;\n }));\n return { rows, error: null };\n}\n","/** 配置体检的状态源:**一份快照**喂两处(面板顶部那行总览 + 各家页签那张表)。\n *\n * 为什么是一个 hook 而不是两处各取一次(票 #706 验收第三条「同一份数据,不许两处各算一遍」):\n * 这一处取数、这一处存状态,总览与各家表都从它读 —— 那条验收因此是**结构上的性质**,不是靠自觉。\n *\n * 取数是**按需**的(点「体检一次」才跑):没有轮询、没有定时器(静态门那条「无数据轮询」照旧)。\n */\nimport * as React from 'react';\nimport { loadHealthReports } from './health-fetch.js';\nimport type { HealthCallFace, HealthFetchRow, HealthTabRow } from './health-fetch.js';\nimport { worstStatus } from './health-contract.js';\nimport type { HealthReport, HealthStatus } from './health-contract.js';\n\n/** 一家的状态:没跑过/正在跑/有报告/这一家出错。 */\nexport type HealthPhase = 'idle' | 'running' | 'ready' | 'failed';\n\nexport interface HealthPanelFace {\n /** 一次取数的结果:包名 → 该家的状态(缺席的包不在表里)。 */\n readonly rows: Readonly<Record<string, { readonly phase: HealthPhase; readonly report: HealthReport | null; readonly error: string | null }>>;\n /** 取数过程本身的错(通道缺席之类)。 */\n readonly error: string | null;\n /** 正在跑。 */\n readonly running: boolean;\n /** 跑一次(六家一起)。 */\n run(): void;\n}\n\nconst IDLE = { phase: 'idle' as HealthPhase, report: null, error: null };\n\n/** 一家的灯:没报告=`null`(不冒充绿)。 */\nexport function lightOf(row: { readonly report: HealthReport | null } | undefined): HealthStatus | null {\n return row?.report ? worstStatus(row.report.items) : null;\n}\n\n/** 体检状态源:挂载时不自动跑(体检要读盘、要起子进程,按需跑),点一次跑六家。\n *\n * **按家增量**(票 #741):某一家一落定就画那一家——不再等全部齐了才一次性 setRows。\n * 取数口仍然只有一处(`loadHealthReports`),它把「一家好了」的回调交给这里 patch 状态。 */\nexport function useHealthPanel(getCall: () => HealthCallFace | null, tabs: readonly HealthTabRow[]): HealthPanelFace {\n const [rows, setRows] = React.useState<HealthPanelFace['rows']>({});\n const [error, setError] = React.useState<string | null>(null);\n const [running, setRunning] = React.useState(false);\n const run = React.useCallback(() => {\n const diag = (globalThis as { __T706_RUN__?: string[] }).__T706_RUN__;\n if (Array.isArray(diag)) diag.push('run:tabs=' + String(tabs.length));\n setRunning(true);\n setError(null);\n const targets = tabs;\n setRows(Object.fromEntries(targets.map((tab) => [tab.id, { ...IDLE, phase: 'running' as HealthPhase }])));\n void (async () => {\n const patch = (row: HealthFetchRow): void => {\n // 函数式更新:这家落定时别家可能也刚落定,直接读旧 state 会丢掉那一次。\n const next = { phase: (row.report ? 'ready' : 'failed') as HealthPhase, report: row.report, error: row.error };\n setRows((previous) => ({ ...previous, [row.id]: next }));\n // 只记条数与第一条的错(渲染台的调试图;浏览器里没人写它,写入失败无所谓)。\n try {\n const diag = (globalThis as { __T706_RUN__?: string[] }).__T706_RUN__;\n if (Array.isArray(diag)) diag.push('row=' + row.id + ' ok=' + String(row.report !== null) + ' err=' + String(row.error));\n } catch {\n /* 调试图写不进去不影响正经事 */\n }\n };\n const loaded = await loadHealthReports(getCall(), targets, patch);\n try {\n const diag = (globalThis as { __T706_RUN__?: string[] }).__T706_RUN__;\n if (Array.isArray(diag)) diag.push('rows=' + String(loaded.rows.length) + ' err=' + String(loaded.error) + ' first=' + String(loaded.rows[0]?.error ?? 'null'));\n } catch {\n /* 调试图写不进去不影响正经事 */\n }\n setError(loaded.error);\n setRunning(false);\n })();\n }, [getCall, tabs]);\n const face: HealthPanelFace = { rows, error, running, run };\n // 出图页的取数口(渲染台的 hooks 替身没有真事件环,页内点按钮那条路不落定取数)。\n // 只在有人预先挂了 `__T706_FACE_SINK__` 时写一次,浏览器里没人挂它,等于没有这一行。\n const sink = (globalThis as { __T706_FACE_SINK__?: (value: HealthPanelFace) => void }).__T706_FACE_SINK__;\n if (typeof sink === 'function') {\n try {\n sink(face);\n } catch {\n /* 出图口的错不影响正经渲染 */\n }\n }\n return face;\n}\n\n/** 汇总行该显示哪一句错(票 #735):取数过程本身的错优先;**一家报告都没回来**时,把第一条家错误顶上来。\n *\n * 为什么要有这一格:各家的错只画在那家页签里的体检表上,一行摘要看不见 ⇒ 用户点完只见「体检中…」闪一下、\n * 接着仍旧「还没体检」,会以为按钮坏了(真机上就是这么被误判的;那次六家全 404,汇总行一声不吭)。\n * 有报告回来时不顶:那时各家那张表里已经把错说清楚了,摘要行只管读数。 */\nexport function summaryErrorOf(face: Pick<HealthPanelFace, 'rows' | 'error'>): string | null {\n if (face.error !== null) return face.error;\n const rows = Object.values(face.rows);\n if (rows.some((row) => row.report !== null)) return null;\n return rows.find((row) => row.error !== null)?.error ?? null;\n}\n","/** 配置体检的两个画法:面板顶部那行总览(六家一盏灯)+ 各家那张表。\n *\n * 面板侧纪律(沿 `client.ts`/`update-panel.ts` 既有口径):`createElement` 手写(禁 JSX)、内联 style、\n * 颜色走 DSH 主题别名(深浅主题自适应,写死值只做回退)、不 import 任何单品包、不 import 技能实现。\n *\n * 判据与文案**全部来自各家技能的报告**,本件一个字都不重写——只按三档上色、按状态挑文案。\n */\nimport * as React from 'react';\nimport { countByStatus, worstStatus } from './health-contract.js';\nimport type { HealthItem, HealthReport, HealthStatus } from './health-contract.js';\n\n/** 三档的配色(绿走主题别名;红黄用实色,理由见下)。\n *\n * 红与黄为什么必须落在**不同明度带**上:这两档要一起出现在同一行灯里,用户扫一眼要能分开\n * 「红了(用不了)」与「黄了(能用但要撞上)」。评审连着两轮点「黄灯偏暗与红相近」,所以:\n * · 红=深红(暗、重);\n * · 黄=高饱和的琥珀(亮、跳)——它在白底上的对比度仍然够(`#b45309` 对白 ≈ 4.9:1)。\n * 绿不给底色:它是「正常」,不该和要处理的那两档抢注意力。 */\nexport const STATUS_COLOR: Readonly<Record<HealthStatus, string>> = {\n red: '#c0392b',\n yellow: '#e08a00',\n green: 'var(--dsw-alias-state-success-primary, #4ec9a0)',\n};\n\n/** 整行底色的浅一档:饱和降一档,免得红黄行「刺眼」压过正文(评审第二轮的扣分点)。 */\n/** 红黄两档整行上底的底色。红灯要**明显重于**黄灯:复评实测原先两个底色亮度几乎一样\n * (红 `#FBF3F3` 对黄 `#FCF3E5`,只差蓝通道),最严重那一档反而最淡——红色提到 0.14。 */\nconst STATUS_TINT: Readonly<Record<HealthStatus, string>> = {\n red: 'rgba(192, 57, 43, 0.14)',\n yellow: 'rgba(224, 138, 0, 0.10)',\n green: 'transparent',\n};\n\n/** 档位词(灯里那几个字)用的色:红黄要**够深**才读得清,故另取一枚比灯更深的值。 */\nexport const STATUS_TEXT: Readonly<Record<HealthStatus, string>> = {\n red: '#a5281b',\n yellow: '#8a5200',\n green: 'var(--dsw-alias-state-success-primary, #4ec9a0)',\n};\n\nconst STATUS_LABEL: Readonly<Record<HealthStatus, string>> = { red: '红', yellow: '黄', green: '绿' };\n\n/** 档位的**形状**标记:颜色之外的第二条读数(灰度截图、色弱视角下也分得开谁要处理)。 */\nconst STATUS_MARK: Readonly<Record<HealthStatus, string>> = { red: '✕', yellow: '!', green: '✓' };\n\n/** 面板上的文字色与分隔线(与 `client.ts` 同一套别名)。 */\nconst INK = 'var(--dsw-alias-label-primary, inherit)';\nconst INK_DIM = 'var(--dsw-alias-label-secondary, #9a9a9a)';\nconst BORDER = 'var(--dsw-alias-border, rgba(128,128,128,.35))';\n\nexport const HEALTH_STYLE = {\n box: {\n padding: '10px 12px',\n marginBottom: 10,\n borderRadius: 10,\n border: '1px solid ' + BORDER,\n } as React.CSSProperties,\n headRow: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 8, marginBottom: 4 } as React.CSSProperties,\n head: { fontSize: 13, fontWeight: 700, color: INK } as React.CSSProperties,\n /** 顶部那一行汇总(票 #732):一行装下「配置体检 + 一句总账 + 体检按钮」,三样都不独占行。\n * 它替掉了原先「标题行 + 单独一行的按钮 + 一排灯按钮 + 两行图例」(同屏实测 153px → 一行)。 */\n summaryRow: {\n display: 'flex',\n alignItems: 'center',\n gap: 10,\n marginBottom: 10,\n } as React.CSSProperties,\n summaryText: { flex: '1 1 auto', minWidth: 0, fontSize: 12.5, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' } as React.CSSProperties,\n /** 各家那张表里的说明行(「还没体检:点上面那个…」那几句)。 */\n meta: { color: INK_DIM, fontSize: 12, lineHeight: 1.7, marginTop: 12 } as React.CSSProperties,\n btn: {\n border: '1px solid ' + BORDER,\n background: 'transparent',\n color: INK,\n borderRadius: 8,\n flex: '0 0 auto',\n padding: '3px 10px',\n fontSize: 12.5,\n fontWeight: 600,\n cursor: 'pointer',\n } as React.CSSProperties,\n error: { color: STATUS_COLOR.red, fontSize: 12, lineHeight: 1.7, marginTop: 6 } as React.CSSProperties,\n} as const;\n\n/** 值得一眼看见的那两档(档位名与整行文字都上色;绿行保持淡)。 */\nfunction isAttention(status: HealthStatus): boolean {\n return status === 'red' || status === 'yellow';\n}\n\n/** 「去哪修」那一行的正文:各家交回来的 action 里若已自带「去哪修:」,这里剥掉一层——\n * 面板已经带了这个前缀,两边都写就会印成「去哪修:去哪修:…」。只剥开头那一次,别的一字不动。 */\nexport function actionText(action: string): string {\n const trimmed = action.trim();\n return trimmed.startsWith('去哪修:') ? trimmed.slice(4).trim() : trimmed;\n}\n\n/** 报文里能扫出来的每一段路径(盘符绝对路径;非 Windows 形状的退回 `/` 打头那一支)。 */\nfunction rawPathsIn(text: string): readonly string[] {\n return text.match(/[A-Za-z]:\\/[^\\s()「」,。;]+/g) ?? text.match(/\\/[^\\s()「」,。;]+/g) ?? [];\n}\n\n/** 一段落点的**根**(盘符):`C:/a/b` → `C:/`。用来把「同一个盘、但根下不同枝」的落点分开算,\n * 免得一个 D 盘上的旁证(比如「配了但目录不在:D:/media」)把 C 盘那几条的缩写全拖没。 */\nfunction rootOf(path: string): string {\n const drive = /^[A-Za-z]:\\//.exec(path);\n return drive === null ? '/' : drive[0];\n}\n\n/** 一段落点的公共目录前缀(按 `/` 切、整段比):`C:/a/b/c` + `C:/a/b/d` → `C:/a/b`。 */\nfunction commonDirOf(paths: readonly string[]): string {\n if (paths.length < 2) return '';\n const split = paths.map((path) => path.replace(/[.,。;]+$/, '').split('/'));\n const first = split[0] as string[];\n let size = 0;\n while (size < first.length && split.every((parts) => parts[size] === first[size])) size += 1;\n return size < 1 ? '' : first.slice(0, size).join('/');\n}\n\n/** 一份报文里**按根分开**的落点前缀(长的在前):只给「同一个根下至少两条落点」的根算前缀——\n * 这个根下只有一条落点时不缩(没有第二条可比,缩出来反而是瞎猜)。\n * 为什么要分根:一家报里常有别的盘的旁证(备忘录用 D 盘上那份媒体目录),只算一个总前缀,\n * 一条旁证就能把整张表的缩写全拖没(#706 出图复评:备忘录那张表整片没缩、记账那张缩了)。 */\nexport function dirPrefixesOf(report: HealthReport): readonly string[] {\n return dirPrefixesIn(pathsOf(report).join(' '));\n}\n\n/** `dirPrefixesOf` 的正文版:一段文字里(报文正文 + 表头两条混在一起)按根分组取公共目录前缀。 */\nexport function dirPrefixesIn(text: string): readonly string[] {\n const paths = rawPathsIn(text);\n const roots = [...new Set(paths.map(rootOf))];\n const prefixes: string[] = [];\n for (const root of roots) {\n const prefix = commonDirOf(paths.filter((path) => rootOf(path) === root));\n if (prefix !== '') prefixes.push(prefix);\n }\n // 长的在前:一个落点命中多条前缀时,最长的那个才是它真正的目录(缩短后不歧义)。\n return prefixes.sort((a, b) => b.length - a.length);\n}\n\n/** 那一行「配置文件 … · 数据目录 …」+ 各条报文里的长路径,都缩成「公共前缀 + 短尾巴」。 */\nfunction shorten(path: string, prefixes: readonly string[]): string {\n const hit = prefixes.find((prefix) => path.startsWith(prefix + '/'));\n return hit === undefined ? path : '…/' + path.slice(hit.length + 1);\n}\n\n/** 一家里所有会印出来的路径。 */\nexport function pathsOf(report: HealthReport): readonly string[] {\n const out: string[] = [report.configPath, report.dataDir];\n for (const item of report.items) out.push(item.message);\n return out;\n}\n\n/** 把报文里那一长串路径按公共前缀缩短(只动打印,不动事实)。\n * 只按**扫得出来的那一段段落点**去缩,不做全串替换:既不误伤长路径,也不会把一段路劈成半截。 */\nexport function shortenPathsIn(text: string, prefixes: readonly string[]): string {\n const candidates = rawPathsIn(text);\n if (candidates.length === 0) return text;\n // 长的在前:先缩长的那条,缩完它自己就挡住了它的父目录,不会二次缩。\n const longestFirst = [...new Set(candidates.map((path) => path.replace(/[.,。;]+$/, '')))].sort(\n (a, b) => b.length - a.length,\n );\n let out = text;\n for (const path of longestFirst) {\n const hit = prefixes.find((prefix) => path.startsWith(prefix + '/'));\n if (hit === undefined) continue;\n out = out.split(hit + '/').join('…/');\n }\n return out;\n}\n\n/** 一盏灯:一家一名一档(数到几个红黄绿)。落点是那家页签(票 #732:面板不再单开一排灯按钮)。 */\nexport interface HealthLightRow {\n readonly id: string;\n readonly title: string;\n readonly status: HealthStatus | null;\n readonly counts: Readonly<Record<HealthStatus, number>>;\n /** 这一家交没交体检通道(页签槽 `options.channel`)。false=这家没装/没接上:圆点保持缺席态,不许点。 */\n readonly hasChannel: boolean;\n}\n\n/** 从六份报告数出那一行的灯(每家按最严重那一档上色)。 */\nexport function lightsOf(\n tabs: readonly { readonly id: string; readonly title: string; readonly hasChannel?: boolean }[],\n reports: Readonly<Record<string, HealthReport | undefined>>,\n): readonly HealthLightRow[] {\n return tabs.map((tab) => {\n const report = reports[tab.id];\n const items = report?.items ?? [];\n return {\n id: tab.id,\n title: tab.title,\n status: report ? worstStatus(items) : null,\n counts: countByStatus(items),\n // 缺席与「装了但还没体检」在报告上都长成 null,分开只能靠这一格(票 #732)。\n hasChannel: tab.hasChannel ?? true,\n // 这里曾短期印过「最严重那一项的名字」当区分度,**已撤回**:实测六家常坏在同一条通用检查项上\n // (都是「数据目录」),印出来六家一模一样,是噪声不是区分度(出图复评实测逐字抄了六个「数据目录」)。\n // 「哪一项坏了」本来就由那家自己那张表回答,灯上不再重复。\n };\n });\n}\n\n/** 一家的一句话读数:「红 1 黄 2」,全绿就是「绿」。面板侧要**上色**的那几处不用它(见 `countSegments`)。 */\nfunction countsText(counts: Readonly<Record<HealthStatus, number>>): string {\n const parts: string[] = [];\n if (counts.red > 0) parts.push('红 ' + String(counts.red));\n if (counts.yellow > 0) parts.push('黄 ' + String(counts.yellow));\n if (parts.length === 0) return '绿';\n return parts.join(' · ');\n}\n\n/** 一截读数:`红 2`/`黄 3`/`绿`/`—`(缺席没报告)。`status` 为 null 的那些走淡色。 */\nexport interface HealthCountSeg {\n readonly status: HealthStatus | null;\n readonly text: string;\n}\n\n/** 把一份计数拆成**按档分色**的几截。红黄各自一截、各自上色,互不串色;\n * 「黄 3」这一截为什么不能被染成红——见 `countSegments` 的注释(出图复评逮到的真缺陷)。 */\nexport function countSegsOf(light: HealthLightRow): readonly HealthCountSeg[] {\n if (light.status === null) return [{ status: null, text: '—' }];\n const segs: HealthCountSeg[] = [];\n for (const status of ['red', 'yellow'] as const) {\n const count = light.counts[status];\n if (count > 0) segs.push({ status, text: STATUS_LABEL[status] + ' ' + String(count) });\n }\n return segs.length > 0 ? segs : [{ status: 'green', text: '绿' }];\n}\n\n/** 几截读数之间那个分隔符:不许染档位色(它不属于任何一档)。 */\nconst SEG_SEP = ' · ';\n\n/** 一家的读数**写成一行**(纯文本版:给页签上那枚小数字用)。 */\nfunction countText(light: HealthLightRow): string {\n return countSegsOf(light).map((piece) => piece.text).join(SEG_SEP);\n}\n\n/** 汇总行那一句:绿要显眼(它是「正常」这件事本身),没通道的那几家不冒充绿。\n *\n * 「没有体检出口」与「还没跑过体检」是**两件事**(票 #735):前者那颗「体检一次」按下去不会有任何反应,\n * 必须自己说出来——两态共用一句「还没体检」时,用户与诊断都会被引到「点了没跑」上去。 */\nfunction overallText(lights: readonly HealthLightRow[]): React.ReactElement {\n const pending = lights.filter((light) => !light.hasChannel).length;\n const withChannel = lights.filter((light) => light.hasChannel);\n const tail = pending > 0 ? ' · ' + String(pending) + ' 家没有体检出口' : '';\n if (withChannel.length === 0) {\n const what = lights.length === 0\n ? '还没有任何一家登记体检出口'\n : String(lights.length) + ' 家都没有体检出口(通道名没登记上)';\n return React.createElement('span', { style: { color: STATUS_TEXT.red, fontWeight: 700 } }, '没有可体检的家:' + what);\n }\n const allGreen = withChannel.every((light) => light.status === 'green');\n if (allGreen && pending === 0) {\n return React.createElement('span', { style: { color: STATUS_COLOR.green, fontWeight: 700 } }, '六家正常');\n }\n const parts: React.ReactElement[] = [];\n for (const status of ['red', 'yellow'] as const) {\n let count = 0;\n for (const light of lights) count += light.counts[status];\n if (count === 0) continue;\n if (parts.length > 0) parts.push(React.createElement('span', { key: status + '-sep', style: { color: INK_DIM } }, SEG_SEP));\n parts.push(\n React.createElement('span', { key: status, style: { color: STATUS_TEXT[status], fontWeight: 700 } }, STATUS_LABEL[status] + ' ' + String(count)),\n );\n }\n if (parts.length === 0) {\n // 一份报告都没跑过;有出口的已经全绿、只是还有没出口的家时,不许把「还没体检」安到它们头上。\n return React.createElement('span', { style: { color: INK_DIM } }, (allGreen ? '有出口的都正常' : '还没体检') + tail);\n }\n parts.push(React.createElement('span', { key: 'todo', style: { color: INK } }, ' 要处理' + tail));\n return React.createElement('span', null, parts);\n}\n\n/** 那枚小数字用的底色(比灯色浅一档,压在页签上不抢眼)。 */\nconst STATUS_DOT: Readonly<Record<HealthStatus, string>> = {\n red: STATUS_COLOR.red,\n yellow: STATUS_COLOR.yellow,\n green: STATUS_COLOR.green,\n};\n\n/** 页签圆点的颜色与语义(票 #732):从「实心/空心=装没装」改成「颜色=体检档位」。 */\nexport const TAB_DOT = {\n /** 没装(页签槽账本里没有这一家)。 */\n absent: STATUS_DOT.green,\n /** 装了、但还没跑过体检。 */\n unchecked: INK_DIM,\n} as const;\n\n/** 页签圆点该用什么色:装了按档位(红黄绿),没装/没跑过走缺席态。 */\nexport function tabDotColor(light: HealthLightRow): string {\n if (!light.hasChannel) return TAB_DOT.absent;\n return light.status === null ? TAB_DOT.unchecked : STATUS_DOT[light.status];\n}\n\n/** 页签上那枚小数字(票 #732):红黄那几家,家名后带一条计数;全绿与没跑过不带。 */\nexport const TAB_NOTE_STYLE: React.CSSProperties = {\n marginLeft: 5,\n border: '1px solid ' + BORDER,\n borderRadius: 999,\n padding: '0 5px',\n fontSize: 11,\n fontWeight: 700,\n} as const;\n\n/** 那枚小数字写什么:只报「要处理」的那两档;一家里红黄都有时先报红那一档(圆点已经按最严重的档上色了)。 */\nexport function tabNote(light: HealthLightRow): HealthCountSeg | null {\n if (!light.hasChannel || light.status === null || light.status === 'green') return null;\n return countSegsOf(light)[0] ?? null;\n}\n\n/** 顶部那一行汇总(票 #732:不再一家一盏灯按钮,一行总账 + 一个体检按钮)。\n *\n * 与页签的分工:这行只答「一共几条要处理」;「哪几家、各几条」由页签的圆点与小数字答;\n * 「具体哪一条坏了、去哪修」由那家页签里的 `HealthTable` 答。 */\nexport function HealthSummaryLine(props: {\n readonly lights: readonly HealthLightRow[];\n readonly running: boolean;\n readonly error: string | null;\n readonly onRun: () => void;\n}): React.ReactElement {\n return React.createElement(\n 'div',\n { style: HEALTH_STYLE.summaryRow, 'data-ilife-health': 'summary' },\n React.createElement('div', { style: HEALTH_STYLE.head }, '配置体检'),\n React.createElement(\n 'div',\n { style: HEALTH_STYLE.summaryText },\n // 一家都没有出口这一态由 overallText 自己说(#735):这里不再用一句「还没体检」把两件事糊成一件。\n props.error !== null ? React.createElement('span', { style: { color: STATUS_TEXT.red } }, props.error) : overallText(props.lights),\n ),\n React.createElement(\n 'button',\n {\n type: 'button',\n style: props.running ? { ...HEALTH_STYLE.btn, opacity: 0.55, cursor: 'default' } : HEALTH_STYLE.btn,\n disabled: props.running,\n onClick: props.onRun,\n // 「只看不改」那三条口径从屏上撤下来了(它原先和两排圆点的图例挤在一行),\n // 但它**仍然要说出口**:按钮悬停是它能待的地方(票 #732;测试按产物里的字符串守着)。\n title: '对六家各跑一次配置体检(只看不改:不建目录、不改配置、不自动重置)',\n },\n props.running ? '体检中…' : '体检一次',\n ),\n );\n}\n\n/** 正正常常的那些:绿条只留「名字」,一句话与「去哪修」都不印(票面验收第一条:正常的收成一行)。 */\nfunction isAttentionItem(item: HealthItem): boolean {\n return isAttention(item.status);\n}\n\n/** 一家那张表:**要处理的**(红黄)一条一块、整行上底带一句话与「去哪修」;\n * **正常的**(绿)收成一行并列的小字,点得到、扫得完(票面验收第一条:正常的收成一行,有问题的才展开)。 */\nexport function HealthTable(props: {\n readonly title: string;\n readonly phase: 'idle' | 'running' | 'ready' | 'failed';\n readonly report: HealthReport | null;\n readonly error: string | null;\n}): React.ReactElement {\n const { report } = props;\n const prefixes: readonly string[] = report ? dirPrefixesOf(report) : [];\n return React.createElement(\n 'div',\n { style: HEALTH_STYLE.box, 'data-ilife-health': 'table' },\n React.createElement(\n 'div',\n { style: HEALTH_STYLE.headRow },\n React.createElement('div', { style: HEALTH_STYLE.head }, props.title + ' · 体检'),\n report ? React.createElement('div', { style: { color: INK_DIM, fontSize: 12 } }, countsText(countByStatus(report.items))) : null,\n ),\n props.phase === 'idle'\n ? React.createElement('div', { style: HEALTH_STYLE.meta }, '还没体检:点上面那个「体检一次」。')\n : null,\n props.phase === 'running' ? React.createElement('div', { style: HEALTH_STYLE.meta }, '正在体检…') : null,\n props.error !== null ? React.createElement('div', { style: HEALTH_STYLE.error }, props.error) : null,\n report\n ? React.createElement(\n 'div',\n null,\n // 行内落点缩成「…/短尾」是为了不把每行撑成两行;但「去哪修:先建这个目录」要照着建,\n // 就得把**它到底在哪儿**说清楚(出图复评五份里两份点名「照屏修不了」)。\n // 措辞有讲究:不能写成「下面行内的「…/」都接在这里」——行里本来就有个「…/data」,\n // 接上基目录会拼成「…/data/data」,自相矛盾(复评原话)。故只**点明缩写代表哪一层**。\n report.dataDir !== ''\n ? React.createElement('div', { style: { color: INK_DIM, fontSize: 11.5, marginTop: 2 } },\n '落点根目录:' + report.dataDir\n + (report.configPath !== '' ? '(配置文件 ' + report.configPath + ')' : ''))\n : null,\n report.items.filter(isAttentionItem).map((item) =>\n React.createElement(\n 'div',\n {\n key: item.id,\n 'data-ilife-health': 'item',\n 'data-status': item.status,\n // 红黄两档整行上底:扫一眼先看见「要处理的」。\n style: {\n padding: '7px 8px',\n margin: '2px 0',\n borderTop: '1px solid ' + BORDER,\n borderRadius: 6,\n fontSize: 13,\n lineHeight: 1.75,\n background: STATUS_TINT[item.status],\n },\n },\n React.createElement(\n 'div',\n { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 6 } },\n // 档位**不只靠颜色**认:形状标记(✕/!)+档位字,色弱与灰度截图下也分得开。\n // 原先这里还有一个同色的实心圆点,与标记、档位字三者同义同色(复评两份点名「没有主次」),\n // 且页签那排(`client.ts:349`)也用同样的圆点表示「这家装没装」——同屏两套圆点语义,\n // 故本件的条目里**去掉圆点**,把圆点这一格语汇留给页签。\n React.createElement('span', {\n style: { color: STATUS_TEXT[item.status], fontWeight: 700, fontSize: 13, lineHeight: 1 },\n 'aria-hidden': 'true',\n }, STATUS_MARK[item.status]),\n React.createElement('span', { style: { fontWeight: 650, color: INK } }, item.title),\n React.createElement('span', {\n style: { color: STATUS_TEXT[item.status], fontWeight: 700 },\n }, STATUS_LABEL[item.status]),\n // 来源那一格**只在它不只是「默认值」时印**:四行全印「来自默认值」等于零区分度,\n // 而且「值从哪来」这件事正文里已经写全了(复评原话:「这四个字零区分度」)。\n item.source && item.source !== '默认值'\n ? React.createElement('span', { style: { color: INK_DIM } }, '· 来自' + item.source)\n : null,\n ),\n React.createElement('div', { style: { color: INK }, title: item.message }, shortenPathsIn(item.message, prefixes)),\n item.action.length > 0\n ? React.createElement('div', { style: { color: INK_DIM } }, '去哪修:' + actionText(item.action))\n : null,\n ),\n ),\n // 正常的那一档:一条都不用点开,名字排成一行(`data-ilife-health=\"ok\"` 供判据核对「一条不少」)。\n // 给它**一条绿边+一层极淡底**,让它读起来是「一组正常项」,而不是四行问题之后的一句脚注\n // (复评原话:「占多数的那一档在视觉上几乎退成脚注」)——条目仍然收成一行(票面第一条)。\n report.items.some((item) => !isAttentionItem(item))\n ? React.createElement(\n 'div',\n {\n 'data-ilife-health': 'ok',\n style: {\n display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: '2px 10px',\n marginTop: 8, padding: '6px 8px',\n borderLeft: '3px solid ' + STATUS_COLOR.green,\n borderRadius: 6,\n fontSize: 12, lineHeight: 1.6, color: INK_DIM,\n },\n },\n // 绿档也要**看得见**:只给一行灰字时,五份出图复评里三份独立指到「全屏没有绿色、\n // 绿=正常这一档等于不存在」。故这一档前面加一个绿点(与红黄同一个「点」语汇),\n // 说明它们就是正常项——条目**仍然收成一行**(票面第一条:正常的收成一行)。\n React.createElement('span', {\n style: {\n width: 8, height: 8, borderRadius: '50%', display: 'inline-block',\n background: STATUS_COLOR.green, flex: '0 0 auto',\n },\n 'aria-hidden': 'true',\n }),\n React.createElement('span', { style: { fontWeight: 650, color: INK } }, '正常 ' + String(countByStatus(report.items).green) + ' 条'),\n React.createElement('span', { style: { color: INK_DIM } }, '(下列各项都正常工作)'),\n report.items.filter((item) => !isAttentionItem(item)).map((item) =>\n React.createElement('span', { key: item.id, 'data-ilife-health': 'ok-item' }, '· ' + item.title),\n ),\n )\n : null,\n )\n : null,\n );\n}\n","/** 目录浏览器的纯逻辑半(**不认识宿主**:只认两格原语)。\n *\n * 为什么在这里:六个单品插件的设置页各有一批「值是一个目录」的行,它们要的界面是同一个东西。\n * 界面的**取数**来自宿主给的两个原语(`list`/`createDirectory`),而这两个原语的形状是**调用方喂进来的**\n * ——本件不 import 宿主的任何实现,也不认识 `remote.directoryPicker` 这个名字(那两件住在各家的\n * `dsh-ctx.ts` 镜像里)。于是本件在 Windows 桌面、远程浏览器、SSH 三种部署下是同一份代码。\n *\n * 装什么:本次要浏览的那一层怎么走(进子目录/回上一级/跳面包屑)、路径怎么切出「目录部分」与\n * 「筛选词」、回执怎么认。**不装**任何 IO、不装任何持久化——`list`/`createDirectory` 由调用方执行。\n *\n * 与平台自带浏览器件的关系:`@deepseek-ai/dsh-client-ui-directory-picker-browse` 的 `DirectoryBrowser`\n * 是包内私有件(只经 `ui-workspace` 的流程空位暴露),插件侧取不到,所以这里是一份独立实现,\n * 只对齐它的**交互结果**(进/选/新建),不对齐它的 DOM。\n */\n\n/** 一层目录里的一个子目录。`hidden` 由宿主判,本件只照传。 */\nexport interface DirectoryEntry {\n readonly name: string;\n readonly path: string;\n readonly hidden: boolean;\n}\n\n/** 面包屑的一格:从文件系统根到当前层,每一格都是一个可跳转目标。 */\nexport interface DirectoryCrumb {\n readonly name: string;\n readonly path: string;\n readonly hidden: boolean;\n}\n\n/** 一次列举的回执(形状照宿主 browse 原语:`path`/`home`/`crumbs`/`entries`/`truncated`)。 */\nexport interface DirectoryListing {\n readonly path: string;\n readonly home: string;\n readonly crumbs: readonly DirectoryCrumb[];\n readonly entries: readonly DirectoryEntry[];\n readonly truncated: boolean;\n}\n\n/** 取数面:由调用方注入。本件只调这两个方法,别的什么都不调。 */\nexport interface DirectoryBrowseFace {\n list(path?: string, signal?: AbortSignal): Promise<DirectoryListing>;\n createDirectory(path: string, name: string): Promise<string>;\n}\n\n/** 浏览失败的人话(`code` 保留给用例断言,`message` 直接上图)。 */\nexport interface BrowseFailure {\n readonly code: string;\n readonly message: string;\n}\n\n/** 宿主拒了一条路的说法随宿主变化,**本件不认识**:调用方把它的回执码传进 `openRowBrowser`。\n *\n * 它为什么是唯一信号:一次组合里只服务一种能力(native 或 browse),另一条路上的动词一律被拒\n * (`@deepseek-ai/dsh-api-workspace-controller/lib/types/directory-picker.js` 的 `requireCapability`);\n * 而三条动词在客户端命名空间上都在(都由 `TYPERT_REMOTE.descriptors` 生成),\n * 所以「有没有 `pick`/有没有 `list`」问不出组合里服务哪种能力,只有真调一次、看它拒没拒才知道。 */\n\n/** 目录行入口的三态判定结果:**按命名空间上有什么动词**看一眼,不是问组合里服务哪种能力。 */\nexport type PickerMode = 'native' | 'browse' | 'none';\n\n/** 一个「根」是什么类型:界面只拿它写悬停说明,不拿它做任何判断。\n *\n * 这五个值由**取数方**(宿主半)归一:Windows 上是 `DriveInfo.DriveType` 映过来的;\n * 别的根(挂载点等)落 `other`。本件不认识任何宿主名词,只认识这五个值。 */\nexport type RootKind = 'fixed' | 'network' | 'removable' | 'optical' | 'other';\n\n/** 一个可跳转的根:`path` 是绝对路径(Windows 上形如 `D:\\`),`kind` 只用于显示。 */\nexport interface RootRow {\n readonly path: string;\n readonly kind: RootKind;\n}\n\n/** 平台回执的信封(各家的 `DirectoryPickerAnswer` 与此同形;本件只认这三格,不认别家的类名)。 */\nexport interface PickEnvelope {\n readonly ok: boolean;\n readonly value?: string | null;\n readonly error?: { readonly code?: string; readonly message?: string };\n}\n\n/** 一次「唤起选择器」的归一结果:选中/取消/供不了,永不抛。\n *\n * `code` 只在平台给了的时候带上(就是 `error.code`):调用方据它分辨「宿主没给这条路」与\n * 「别的失败」——前者该换一条路走,后者才该报出来。那个码由调用方从自己的宿主镜像里传进来\n * (见 `openRowBrowser` 的 `refusalCode`),本件不认它的名字。 */\nexport type PickOutcome =\n | { readonly kind: 'picked'; readonly path: string }\n | { readonly kind: 'cancelled' }\n | { readonly kind: 'unavailable'; readonly code?: string; readonly message: string };\n\n/** 认一认某个值是不是可用的目录取数面(软依赖守卫用:认不出就当没有,绝不把页面带下来)。 */\nexport function isBrowseFace(raw: unknown): raw is DirectoryBrowseFace {\n if (typeof raw !== 'object' || raw === null) return false;\n const face = raw as Partial<DirectoryBrowseFace>;\n return typeof face.list === 'function' && typeof face.createDirectory === 'function';\n}\n\n/** 认一认某个值有没有 `pick`(对应宿主 composition 里的 `native` 能力)。 */\nexport function hasPickFn(raw: unknown): boolean {\n if (typeof raw !== 'object' || raw === null) return false;\n return typeof (raw as { pick?: unknown }).pick === 'function';\n}\n\n/** 三态判定:**先看两格浏览原语**(都在=应用内浏览器这条路在),再看单独的 `pick`,都没有=`none`。\n *\n * 为什么浏览优先:客户端命名空间上三条动词一定都在(由 `TYPERT_REMOTE.descriptors` 生成,\n * 与组合里服务哪种能力无关),所以两格原语在=这台宿主**可能**给浏览;真调一次才知道给不给,\n * 不给({@link CAPABILITY_REFUSED})再由调用方换系统对话框。反过来先认 `pick` 就会在\n * Desktop 这种「只服务 browse」的宿主上每次都去唤一次系统对话框、被拒一次(#744 实测)。 */\nexport function pickerModeOf(raw: unknown): PickerMode {\n if (isBrowseFace(raw)) return 'browse';\n return hasPickFn(raw) ? 'native' : 'none';\n}\n\n/** 平台回执 → 三态(**永不抛**):成功回的是信封里的 `value` 不是路径;`ok:false` 是「供不了」不是「取消」。\n *\n * 出处:`@deepseek-ai/dsh-api-gateway/lib/client.js` 的 `invoke()`(失败不抛、成功也不回裸值),\n * 第一方消费方 `@deepseek-ai/dsh-client-ui-workspace/lib/client.js:99-103` 就照这个信封拆。\n * 裸串照收(老形状兜底),认不出的形状当「供不了」报出来,不当取消吞掉(#743 的教训)。 */\nexport function readPickAnswer(raw: unknown): PickOutcome {\n if (typeof raw === 'string') return raw.trim() === '' ? { kind: 'cancelled' } : { kind: 'picked', path: raw };\n const answer = (typeof raw === 'object' && raw !== null ? raw : {}) as PickEnvelope;\n if (answer.ok === true) {\n const value = answer.value;\n return typeof value === 'string' && value.trim() !== '' ? { kind: 'picked', path: value } : { kind: 'cancelled' };\n }\n const detail = answer.error?.message?.trim() ?? '';\n const code = answer.error?.code;\n return {\n kind: 'unavailable',\n ...(typeof code === 'string' && code !== '' ? { code } : {}),\n message: '打不开系统文件夹对话框' + (detail === '' ? '' : '(' + detail + ')') + ':请直接在框里填绝对路径。',\n };\n}\n\n/** 取数原语失败:带上宿主给的回执码,好让调用方分辨「没这条路」与「这一层读不出来」。\n *\n * `code` 就是 `humanize`(状态那半)读的那一格,人话直接上图。 */\nexport class BrowseAnswerError extends Error {\n readonly code: string;\n constructor(code: string, message: string) {\n super(message);\n this.name = 'BrowseAnswerError';\n this.code = code;\n }\n}\n\n/** 宿主 browse 原语的**信封** → 值,或按信封里的 `error` 抛。\n *\n * 为什么必须走这一道:`list`/`createDirectory` 与 `pick` 一样是 Remote 动词,客户端拿到的是\n * `{ok:true,value}`/`{ok:false,error}`(`@deepseek-ai/dsh-api-gateway/lib/client.js` 的 `invoke()`)。\n * 把信封当值用,图上就会是一层「读出来了但一行都没有」的空目录(#744 实测),\n * 而不是报错——所以宁可在这里抛,也不许把它当列举结果传下去。\n * 第一方消费方同口径:`@deepseek-ai/dsh-client-ui-workspace/lib/client.js:105-113` 也是这么拆的。 */\nexport function readBrowseAnswer<T>(raw: unknown, what: string): T {\n const answer = (typeof raw === 'object' && raw !== null ? raw : {}) as {\n ok?: unknown;\n value?: unknown;\n error?: { code?: string; message?: string };\n };\n if (answer.ok === true && answer.value !== undefined && answer.value !== null) return answer.value as T;\n const detail = answer.error?.message?.trim() ?? '';\n const code = answer.error?.code;\n throw new BrowseAnswerError(\n typeof code === 'string' && code !== '' ? code : 'browse-failed',\n detail === '' ? what + '没有回执' : detail,\n );\n}\n\n/** 宿主命名空间 → 本件的取数面:把两条原语的信封拆开,成功给值、失败按码抛。\n *\n * 命名空间上两格原语不在就回 null(调用方据此不画入口)。**透传参数个数照宿主的规矩**:\n * 描述符声明了可选 `AbortSignal`(`cancellation: {parameter:'signal'}`),而客户端按\n * `values.length === 业务参数个数 + 1` 判有没有 signal——显式传一个 `undefined` 会被当成 signal\n * 去 `AbortSignal.any([…, undefined])` 而炸。本件的界面不做取消,故**从不传 signal**。 */\nexport function browseFaceOf(raw: unknown): DirectoryBrowseFace | null {\n if (!isBrowseFace(raw)) return null;\n const namespace = raw as DirectoryBrowseFace;\n return {\n list: async (path) =>\n readBrowseAnswer<DirectoryListing>(path === undefined ? await namespace.list() : await namespace.list(path), '列举目录'),\n createDirectory: async (path, name) => readBrowseAnswer<string>(await namespace.createDirectory(path, name), '新建文件夹'),\n };\n}\n\n/** 一段路径里最后那个分隔符的位置(Windows 上 `\\` 与 `/` 都算;POSIX 上 `\\` 是合法文件名字符,不算)。 */\nfunction lastSeparator(path: string): number {\n const back = path.lastIndexOf('\\\\');\n const slash = path.lastIndexOf('/');\n // 只含 `\\` 的路径按 Windows 规则解;两者都有时取更靠后的那个。\n return back > slash ? back : slash;\n}\n\n/** 上一层:`'C:\\\\a\\\\b\\\\'` → `'C:\\\\a'`;已经是根则回自身;相对名(没有任何分隔符)也回自身。\n *\n * 尾分隔符先吃掉再切——否则「回上一级」会在原地打转。**盘根单独认**:`C:\\` 掐掉尾分隔符会剩成\n * `C:`,而 `C:` 是「当前盘」不是「上一层」,回它就会多跑一趟、还可能跨到别的盘上去。\n * UNC(`\\\\server\\share`)不特殊处理:切到头就是共享名本身,浏览停在共享根。 */\nexport function parentOf(path: string): string {\n const trimmed = path.replace(/[\\\\/]+$/, '');\n if (trimmed === '') return path;\n const sep = path.includes('\\\\') ? '\\\\' : '/';\n if (/^[A-Za-z]:$/.test(trimmed)) return trimmed + sep;\n const cut = lastSeparator(trimmed);\n if (cut === -1) return trimmed;\n if (cut === 0) return path.slice(0, 1);\n if (cut === 2 && /^[A-Za-z]:/.test(trimmed)) return trimmed.slice(0, 2) + sep;\n return trimmed.slice(0, cut);\n}\n\n/** 把「当前路径 + 一段输入」拼成一个目标路径。 */\nexport function joinPath(directory: string, name: string): string {\n const sep = directory.includes('\\\\') ? '\\\\' : '/';\n const base = directory.replace(/[\\\\/]+$/, '');\n if (base === '') return sep + name;\n return base + sep + name;\n}\n\n/** 一层列举里,哪些行可进:隐藏目录按开关过滤,其余照宿主给的顺序。 */\nexport function visibleEntries(listing: DirectoryListing, showHidden: boolean): readonly DirectoryEntry[] {\n return showHidden ? listing.entries : listing.entries.filter((entry) => !entry.hidden);\n}\n\n/** 这一层有多少个被藏起来的目录(用来决定「显示隐藏目录」那个开关要不要出现在图上)。 */\nexport function hiddenCount(listing: DirectoryListing): number {\n return listing.entries.filter((entry) => entry.hidden).length;\n}\n\n/** 拿一段文本按「最后一个分隔符」切成「目录部分 + 筛选词」。\n *\n * 没打过任何分隔符时目录部分=null(那段文字还不指任何目录,只当筛选词用)。\n * Windows 上 `/` 也当分隔符(宿主那边两种都收),POSIX 上不。 */\nexport function splitDraft(draft: string): { readonly directory: string | null; readonly filter: string } {\n const cut = lastSeparator(draft);\n if (cut === -1) return { directory: null, filter: draft };\n return { directory: draft.slice(0, cut + 1), filter: draft.slice(cut + 1) };\n}\n\n/** 按筛选词过一层目录(大小写不敏感;空筛选词=全留)。 */\nexport function filterEntries(entries: readonly DirectoryEntry[], filter: string): readonly DirectoryEntry[] {\n const needle = filter.trim().toLowerCase();\n if (needle === '') return entries;\n return entries.filter((entry) => entry.name.toLowerCase().includes(needle));\n}\n\n/** 新建文件夹的名字合不合法(单个路径段:不许分隔符、不许空、不许 `.`/`..`)。\n *\n * 与宿主 browse 后端的校验同口径——这里先拦一道,是为了在图上给得出人话,不是替代宿主校验。 */\nexport function validateFolderName(name: string): { readonly ok: true } | { readonly ok: false; readonly reason: string } {\n const trimmed = name.trim();\n if (trimmed === '') return { ok: false, reason: '文件夹名不能为空。' };\n if (/[\\\\/]/.test(trimmed)) return { ok: false, reason: '文件夹名里不能带路径分隔符。' };\n if (trimmed === '.' || trimmed === '..') return { ok: false, reason: '「.」与「..」不是文件夹名。' };\n return { ok: true };\n}\n","/** 文件浏览器的操作半:把「进哪一层、这一层显示什么、选到了什么」算成状态,**IO 全部由调用方注入**。\n *\n * 分两层的原因:本件(操作)在 Node 里就能直测——喂一个假的取数面,就能把「进目录/回上一级/\n * 跳面包屑/筛选/新建文件夹/失败出人话」全跑一遍;视图那半(`directory-browser-ui.ts`)\n * 只负责把这份状态画成 DOM。两边都不认识宿主。\n *\n * 数据来源只有两格:`list(path?)` 与 `createDirectory(path, name)`(调用方注入的 `DirectoryBrowseFace`)。\n */\n\nimport type {\n BrowseFailure,\n DirectoryBrowseFace,\n DirectoryEntry,\n DirectoryListing,\n RootRow,\n} from './directory-browser-contract.js';\nimport {\n browseFaceOf,\n filterEntries,\n joinPath,\n parentOf,\n splitDraft,\n validateFolderName,\n visibleEntries,\n} from './directory-browser-contract.js';\n\n/** 一层列举的三态:加载中/拿到了/这一层读不出来。 */\nexport type BrowsePhase = 'idle' | 'loading' | 'ready' | 'failed';\n\n/** 图上的全部状态。视图只读它,不改它。 */\nexport interface BrowseState {\n readonly phase: BrowsePhase;\n readonly listing: DirectoryListing | null;\n /** 这一层读失败时的人话;`phase==='failed'` 时一定有。 */\n readonly failure: BrowseFailure | null;\n /** 当前选中的子目录(点行选中;选中后再点「打开」就是它)。 */\n readonly selected: string | null;\n /** 面包屑上那一层的编号,只用于画图。 */\n readonly showHidden: boolean;\n /** 路径框里的草稿(用户可能正在编辑)。 */\n readonly draft: string;\n /** 草稿指向的那一层已经列举过之后,用来按末段过滤的词。 */\n readonly filter: string;\n /** 正在建的文件夹名;null=没在建。 */\n readonly creating: string | null;\n /** 上一次操作的失败人话(新建失败等);看一眼就好,下一次操作清掉。 */\n readonly notice: string | null;\n /** 可跳转的根(Windows 上的盘符等):取数方给什么就是什么,空数组=不画那一行。 */\n readonly roots: readonly RootRow[];\n}\n\n/** 订阅者拿到的状态(不可变,每次变更是新对象)。 */\nexport type BrowseListener = (state: BrowseState) => void;\n\n/** 操作面:视图与用例都只用这些方法。 */\nexport interface BrowseController {\n getState(): BrowseState;\n subscribe(listener: BrowseListener): () => void;\n /** 开图:先试配置里那个目录(空则直接家目录),读不出来(不存在、没权限)退回宿主家目录。\n *\n * 回**第一份落定的状态**:调用方据此分辨「这条路宿主没给」与「这一层读不出来」。 */\n open(): Promise<BrowseState>;\n /** 进某一层(绝对路径,或 `list()` 的默认家目录当入参为 null)。 */\n enter(path: string | null): Promise<void>;\n /** 回上一级(当前层就是根时原地不动)。 */\n up(): Promise<void>;\n /** 采纳当前选择(选中行,没有选中就用当前层)并回调 `onPicked`。 */\n pick(): void;\n /** 取消:关图,什么都不回调。 */\n cancel(): void;\n /** 选中一个子目录(再点一次同一条=取消选中)。 */\n select(path: string): void;\n /** 切换隐藏目录。 */\n toggleHidden(): void;\n /** 改路径框草稿(不列举,只更新草稿与筛选词)。 */\n setDraft(draft: string): void;\n /** 提交路径框草稿:指到哪一层就去哪一层。 */\n commitDraft(): Promise<void>;\n /** 开「新建文件夹」;传 null 收起。 */\n setCreating(name: string | null): void;\n /** 在当前层(或选中的那一层)下建一个新文件夹,成功后进它并选中它。 */\n createFolder(name: string): Promise<void>;\n /** 关图(与 `cancel` 同义,供宿主侧统一收口)。 */\n close(): void;\n}\n\n/** 建一个浏览器控制器。取数、回调、初值全部由调用方给。 */\nexport function createBrowseController(deps: {\n readonly face: DirectoryBrowseFace;\n readonly initialPath: string;\n readonly onPicked: (path: string) => void;\n readonly onClose: () => void;\n /** 失败人话的前缀,默认「浏览失败」。 */\n readonly failureLabel?: string;\n /** 可跳转的根从哪来;不给就是没有这一行(都是空清单,一样不画)。 */\n readonly rootsSource?: () => Promise<readonly RootRow[]>;\n}): BrowseController {\n const label = deps.failureLabel ?? '浏览失败';\n const listeners = new Set<BrowseListener>();\n let state: BrowseState = {\n phase: 'idle',\n listing: null,\n failure: null,\n selected: null,\n showHidden: false,\n draft: deps.initialPath,\n filter: '',\n creating: null,\n notice: null,\n roots: [],\n };\n\n /** 每次列举带一个序号:晚回来的旧回执直接丢掉,不许覆盖新一层。 */\n let generation = 0;\n\n const emit = (next: Partial<BrowseState>): void => {\n state = { ...state, ...next };\n for (const listener of [...listeners]) listener(state);\n };\n\n /** 根清单只问一次:取不到就是空(不报错、不重试——它只是图上的一行入口)。\n *\n * 「问一次」钉在这里而不是图里:视图那半每次重画都会重新求值,问在这里才谈得上一次。 */\n let rootsAsked = false;\n const loadRoots = async (): Promise<void> => {\n if (rootsAsked || deps.rootsSource === undefined) return;\n rootsAsked = true;\n try {\n const roots = await deps.rootsSource();\n if (roots.length > 0) emit({ roots });\n } catch {\n /* 取不到就当没有这一行:浏览本身照常。 */\n }\n };\n\n const humanize = (cause: unknown): BrowseFailure => {\n const raw = (typeof cause === 'object' && cause !== null ? cause : {}) as {\n code?: unknown;\n message?: unknown;\n name?: unknown;\n };\n const message = typeof raw.message === 'string' && raw.message.trim() !== '' ? raw.message.trim() : String(cause);\n const code = typeof raw.code === 'string' && raw.code !== '' ? raw.code : 'browse-failed';\n return { code, message: `${label}:${message}` };\n };\n\n const load = async (path: string | null): Promise<void> => {\n const mine = ++generation;\n emit({ phase: 'loading', failure: null, notice: null });\n try {\n const listing = path === null ? await deps.face.list() : await deps.face.list(path);\n if (mine !== generation) return;\n emit({\n phase: 'ready',\n listing,\n failure: null,\n selected: null,\n draft: listing.path,\n filter: '',\n });\n } catch (cause) {\n if (mine !== generation) return;\n emit({ phase: 'failed', listing: null, failure: humanize(cause), selected: null });\n }\n };\n\n /** 草稿指到的那一层已经列举过时,按末段筛当前这一层的行;否则不过滤。\n *\n * 两边都先掐掉尾分隔符再比:草稿那边切开后天然带尾分隔符(`C:\\a\\` + `b`),\n * 而这一层的 `path` 由宿主给、不带尾分隔符。 */\n const applyDraft = (draft: string): void => {\n const { directory, filter } = splitDraft(draft);\n const listing = state.listing;\n const sameLevel = listing !== null && directory !== null && normalize(directory) === normalize(listing.path);\n emit({ draft, filter: sameLevel ? filter : '' });\n };\n\n const normalize = (path: string): string => path.replace(/[\\\\/]+$/, '');\n\n return {\n getState: () => state,\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n async open(): Promise<BrowseState> {\n // 根清单一并去问,但**不等它**:它只是图上的一行入口,落定了自会推一次重画;\n // 等它就等于让「宿主那台机器列盘符快不快」决定对话框什么时候开。\n void loadRoots();\n const wanted = deps.initialPath.trim();\n if (wanted !== '') {\n await load(wanted);\n if (state.phase === 'ready') return state;\n }\n await load(null);\n return state;\n },\n enter: (path) => load(path),\n async up() {\n const listing = state.listing;\n if (listing === null) return;\n const parent = parentOf(listing.path);\n if (parent === listing.path) return;\n await load(parent);\n },\n pick() {\n const target = state.selected ?? state.listing?.path ?? null;\n if (target === null) return;\n deps.onPicked(target);\n },\n cancel() {\n deps.onClose();\n },\n close() {\n deps.onClose();\n },\n select(path) {\n emit({ selected: state.selected === path ? null : path, notice: null });\n },\n toggleHidden() {\n emit({ showHidden: !state.showHidden });\n },\n setDraft(draft) {\n applyDraft(draft);\n },\n async commitDraft() {\n const trimmed = state.draft.trim();\n if (trimmed === '') return;\n await load(trimmed);\n },\n setCreating(name) {\n emit({ creating: name, notice: null });\n },\n async createFolder(name) {\n const checked = validateFolderName(name);\n if (!checked.ok) {\n emit({ notice: checked.reason });\n return;\n }\n const base = state.selected ?? state.listing?.path ?? null;\n if (base === null) {\n emit({ notice: '还没有打开任何目录,无法新建文件夹。' });\n return;\n }\n try {\n const created = await deps.face.createDirectory(base, name.trim());\n emit({ creating: null, notice: null });\n await load(created);\n emit({ selected: created });\n } catch (cause) {\n const failure = humanize(cause);\n emit({ notice: failure.message, creating: null });\n }\n },\n };\n}\n\n/** 图上这一层要画的行(按隐藏开关与草稿末段过滤之后的)。 */\nexport function rowsOf(state: BrowseState): readonly DirectoryEntry[] {\n if (state.listing === null) return [];\n return filterEntries(visibleEntries(state.listing, state.showHidden), state.filter);\n}\n\n/** 图上这一层能不能回上一级(根目录不能)。 */\nexport function canGoUp(state: BrowseState): boolean {\n if (state.listing === null) return false;\n return parentOf(state.listing.path) !== state.listing.path;\n}\n\n/** 「打开」这颗按钮的目标:选中行优先,没选中就是当前层。 */\nexport function targetOf(state: BrowseState): string | null {\n return state.selected ?? state.listing?.path ?? null;\n}\n\n/** 新建文件夹那一行要展示的位置(\"在 <这里> 下新建\")。 */\nexport function createBaseOf(state: BrowseState): string | null {\n return state.selected ?? state.listing?.path ?? null;\n}\n\n/** 一段路径拼成可读的一句话(给用例与调试用;图上不直接显示)。 */\nexport function describe(state: BrowseState): string {\n if (state.phase === 'failed') return `${state.failure?.code ?? 'browse-failed'} @ ${state.draft}`;\n if (state.listing === null) return `${state.phase}`;\n return `${state.listing.path}(${rowsOf(state).length} 行)`;\n}\n\n/** 一行子目录的完整路径(图上点行时用)。 */\nexport function entryPath(directory: string, entry: DirectoryEntry): string {\n return entry.path === '' ? joinPath(directory, entry.name) : entry.path;\n}\n\n/** 目录行接线的返回:**不碰 React**——`actions` 原样喂给 `directory-browser-ui` 的组件 props,\n * `isOpen`/`setOpen` 由调用方自己接进它的状态(宿主怎么重画是它的事,本件不管)。 */\nexport interface DirectoryRowBrowser {\n readonly actions: {\n readonly onPick: () => void;\n readonly onClose: () => void;\n readonly onEnter: (path: string) => void;\n readonly onUp: () => void;\n readonly onSelect: (path: string) => void;\n readonly onToggleHidden: () => void;\n readonly onDraft: (draft: string) => void;\n readonly onCommitDraft: () => void;\n readonly onCreate: (name: string) => void;\n readonly onCreatingChange: (name: string | null) => void;\n };\n /** 开图(先试配置里那个目录,读不出来退回家目录),回第一份落定的状态。 */\n open(): Promise<BrowseState>;\n /** 状态变更订阅:视图那半靠 `useSyncExternalStore` 用它重画。\n *\n * **没有它图上只有第一帧**——列举是异步落定的,React 不订阅就永远停在开图那一刻的状态。 */\n subscribe(listener: BrowseListener): () => void;\n state(): BrowseState;\n /** 一段可读的当前状态(用例与调试用)。 */\n summary(): string;\n}\n\n/** 建一份「目录行接线」:给一个取数面与初值,回一组动作。\n *\n * 只做接线:把控制器算出来的状态与回调打包好,不取数、不画图、不认宿主。 */\nexport function createDirectoryRowBrowser(deps: {\n readonly face: DirectoryBrowseFace;\n readonly initialPath: string;\n readonly onPicked: (path: string) => void;\n readonly onClosed?: () => void;\n readonly failureLabel?: string;\n /** 可跳转的根从哪来(各家给的是同一份「总管电话」的取数器);不给就没有那一行。 */\n readonly rootsSource?: () => Promise<readonly RootRow[]>;\n}): DirectoryRowBrowser {\n const controller = createBrowseController({\n face: deps.face,\n initialPath: deps.initialPath,\n onPicked: deps.onPicked,\n onClose: () => deps.onClosed?.(),\n ...(deps.failureLabel === undefined ? {} : { failureLabel: deps.failureLabel }),\n ...(deps.rootsSource === undefined ? {} : { rootsSource: deps.rootsSource }),\n });\n return {\n open: () => controller.open(),\n subscribe: (listener) => controller.subscribe(listener),\n state: () => controller.getState(),\n summary: () => describe(controller.getState()),\n actions: {\n onPick: () => controller.pick(),\n onClose: () => controller.cancel(),\n onEnter: (path) => void controller.enter(path),\n onUp: () => void controller.up(),\n onSelect: (path) => controller.select(path),\n onToggleHidden: () => controller.toggleHidden(),\n onDraft: (draft) => controller.setDraft(draft),\n onCommitDraft: () => void controller.commitDraft(),\n onCreate: (name) => void controller.createFolder(name),\n onCreatingChange: (name) => controller.setCreating(name),\n },\n };\n}\n\n/** 一条目录行点下去该走哪条路(**六家共用这一处策略**,单独写在各家就会各错各的)。\n *\n * 顺序是「先应用内浏览,被拒再换系统对话框」,因为客户端命名空间上三条动词都在、问不出组合里服务哪种\n * 能力(见 `pickerModeOf`)。回执:\n * - `undefined`=连两格浏览原语都没有(这条路没有,调用方自己想办法);\n * - `'refused'`=宿主回了 `refusalCode`(组合里是系统对话框),图已收起;\n * - `'open'`=图开着,后面的事都在图里。\n *\n * **状态由调用方持有**:`onRow` 收到刚建好的那条(或 null=收起),React 那边据此决定画不画图。\n *\n * `refusalCode` 是**宿主的回执码**(各家自己从宿主镜像里拿,本件只照比不认名):\n * 它换名字本件照样跑,六家一起换。 */\nexport function openRowBrowser(input: {\n readonly picker: unknown;\n readonly initialPath: string;\n readonly onChange: (next: string) => void;\n readonly onRow: (row: DirectoryRowBrowser | null) => void;\n readonly refusalCode: string;\n /** 可跳转的根从哪来(可选:不给就是图上没有那一行)。 */\n readonly rootsSource?: () => Promise<readonly RootRow[]>;\n}): Promise<'open' | 'refused'> | undefined {\n const face = browseFaceOf(input.picker);\n if (face === null) return undefined;\n const row = createDirectoryRowBrowser({\n face,\n initialPath: input.initialPath,\n onPicked: (picked) => {\n input.onChange(picked);\n input.onRow(null);\n },\n onClosed: () => input.onRow(null),\n ...(input.rootsSource === undefined ? {} : { rootsSource: input.rootsSource }),\n });\n input.onRow(row);\n return row.open().then((settled) => {\n if (settled.phase === 'failed' && settled.failure?.code === input.refusalCode) {\n input.onRow(null);\n return 'refused' as const;\n }\n return 'open' as const;\n });\n}\n","/** 文件浏览器视图半的**零件**:样式表与四行控件(面包屑/根/一行子目录/新建行)。\n *\n * 为什么拆出来:整张对话框的组版与这些零件的变化频率不同——零件跟着「图上多一行/少一行」动,\n * 组版跟着「对话框长什么样」动。拆开之后两边都在 350 行告警线内,而对外面不变\n * (`directory-browser-ui.ts` 照旧转出组件与两个接线函数)。\n *\n * 本件与视图半同样**不认识宿主**:只认状态与文案。\n */\n\nimport * as React from 'react';\nimport type { BrowseState } from './directory-browser-state.js';\nimport type { DirectoryEntry, RootKind } from './directory-browser-contract.js';\n\n/** 图上出现的每一句文案;调用方给(本件不自造口径)。 */\nexport interface DirectoryBrowserLabels {\n /** 对话框标题,例:「选择数据目录」。 */\n readonly title: string;\n readonly close: string;\n readonly up: string;\n readonly pathPlaceholder: string;\n readonly go: string;\n readonly showHidden: (count: number) => string;\n readonly empty: string;\n readonly loading: string;\n readonly newFolder: string;\n readonly createConfirm: string;\n readonly createCancel: string;\n readonly select: string;\n readonly selected: string;\n readonly open: string;\n readonly cancel: string;\n /** 选中预览那一行的前缀,例:「将选定:」。 */\n readonly willPick: string;\n}\n\n/** 根的类型说明(悬停可见):**本件自己的词汇**,与取数方无关。 */\nconst ROOT_KIND_TEXT: Record<RootKind, string> = {\n fixed: '固定磁盘',\n network: '网络驱动器',\n removable: '可移动磁盘',\n optical: '光驱',\n other: '磁盘',\n};\n\nexport const S = {\n scrim: {\n position: 'fixed',\n inset: 0,\n background: 'rgba(0,0,0,0.35)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n zIndex: 9999,\n } as React.CSSProperties,\n dialog: {\n width: 680,\n maxWidth: '92vw',\n height: 500,\n maxHeight: '86vh',\n display: 'flex',\n flexDirection: 'column',\n background: 'var(--dsw-alias-bg-layer-1, #1f1f23)',\n color: 'var(--dsw-alias-label-primary, inherit)',\n border: '1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.35))',\n borderRadius: 10,\n boxShadow: '0 12px 32px rgba(0,0,0,0.35)',\n overflow: 'hidden',\n } as React.CSSProperties,\n head: {\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n padding: '10px 12px',\n borderBottom: '1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.25))',\n } as React.CSSProperties,\n title: { fontSize: 14, fontWeight: 700 } as React.CSSProperties,\n crumbs: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 2, padding: '8px 12px 0', fontSize: 12 } as React.CSSProperties,\n chromeButton: {\n background: 'transparent',\n border: 'none',\n color: 'var(--dsw-alias-label-primary, inherit)',\n cursor: 'pointer',\n padding: '2px 4px',\n fontSize: 12,\n opacity: 0.85,\n } as React.CSSProperties,\n crumbSep: { opacity: 0.45, fontSize: 12 } as React.CSSProperties,\n roots: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 6, padding: '0 12px 8px', fontSize: 12 } as React.CSSProperties,\n rootButton: {\n border: '1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.35))',\n borderRadius: 6,\n padding: '3px 8px',\n opacity: 1,\n } as React.CSSProperties,\n pathRow: { display: 'flex', gap: 6, padding: '8px 12px' } as React.CSSProperties,\n input: {\n flex: 1,\n minWidth: 0,\n fontSize: 12,\n padding: '5px 8px',\n background: 'var(--dsw-alias-bg-layer-2, rgba(128,128,128,0.12))',\n color: 'inherit',\n border: '1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.35))',\n borderRadius: 6,\n } as React.CSSProperties,\n button: {\n fontSize: 12,\n padding: '5px 10px',\n cursor: 'pointer',\n background: 'var(--dsw-alias-bg-layer-2, rgba(128,128,128,0.12))',\n color: 'inherit',\n border: '1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.35))',\n borderRadius: 6,\n } as React.CSSProperties,\n buttonOff: { opacity: 0.45, cursor: 'default' } as React.CSSProperties,\n list: {\n flex: 1,\n minHeight: 0,\n overflowY: 'auto',\n margin: '0 12px',\n border: '1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.25))',\n borderRadius: 6,\n } as React.CSSProperties,\n row: {\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n width: '100%',\n padding: '6px 10px',\n textAlign: 'left',\n background: 'transparent',\n color: 'inherit',\n border: 'none',\n borderBottom: '1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.12))',\n cursor: 'pointer',\n fontSize: 13,\n } as React.CSSProperties,\n rowSelected: { background: 'var(--dsw-alias-bg-layer-2, rgba(128,128,128,0.18))' } as React.CSSProperties,\n empty: { padding: '14px 12px', fontSize: 12, opacity: 0.7 } as React.CSSProperties,\n footNote: { padding: '6px 12px 0', fontSize: 12, minHeight: 20 } as React.CSSProperties,\n foot: {\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n padding: '10px 12px',\n borderTop: '1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.25))',\n } as React.CSSProperties,\n target: { marginRight: 'auto', fontSize: 12, opacity: 0.8, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } as React.CSSProperties,\n error: { color: 'var(--dsw-alias-label-error, #e66)' } as React.CSSProperties,\n} as const;\n\n/** 面包屑:从文件系统根到当前层,每一格都是跳转目标(`listing.crumbs` 由取数方给)。 */\nexport function crumbRow(state: BrowseState, onEnter: (path: string) => void): React.ReactElement | null {\n const listing = state.listing;\n if (listing === null || listing.crumbs.length === 0) return null;\n const parts: React.ReactNode[] = [];\n listing.crumbs.forEach((crumb, index) => {\n if (index > 0) parts.push(React.createElement('span', { key: 'sep-' + crumb.path, style: S.crumbSep }, '›'));\n const isCurrent = crumb.path === listing.path;\n parts.push(\n React.createElement(\n 'button',\n {\n key: crumb.path,\n type: 'button',\n style: { ...S.chromeButton, fontWeight: isCurrent ? 700 : 400 },\n title: crumb.path,\n onClick: () => onEnter(crumb.path),\n },\n // 每一格都写**它自己那一层的名字**:第一格是文件系统根(`C:\\`),不是「上一级」——\n // 「上一级」是路径行那颗按钮的活(#744 返修:这一格原先串了那颗按钮的文案)。\n crumb.name,\n ),\n );\n });\n return React.createElement('div', { style: S.crumbs }, parts);\n}\n\n/** 可跳转的根那一行(Windows 上的盘符等):**就是本机全部磁盘**,故不加行首说明。\n * **少于两个根就不画**:一个根没有「跳到别处」可言。 */\nexport function rootsRow(state: BrowseState, onEnter: (path: string) => void): React.ReactElement | null {\n if (state.roots.length < 2) return null;\n const parts: React.ReactNode[] = state.roots.map((root) =>\n React.createElement(\n 'button',\n {\n key: root.path,\n type: 'button',\n style: { ...S.chromeButton, ...S.rootButton },\n title: ROOT_KIND_TEXT[root.kind] ?? ROOT_KIND_TEXT.other,\n onClick: () => onEnter(root.path),\n },\n root.path,\n ),\n );\n return React.createElement('div', { style: S.roots }, parts);\n}\n\n/** 一行子目录:点名字进它,点「选」把它当目标(「选」再点一次取消)。 */\nexport function entryRow(\n entry: DirectoryEntry,\n selected: boolean,\n labels: DirectoryBrowserLabels,\n onToggleSelect: (path: string) => void,\n onEnter: (path: string) => void,\n): React.ReactElement {\n return React.createElement(\n 'div',\n { key: entry.path, style: { ...S.row, padding: 0, ...(selected ? S.rowSelected : {}) } },\n React.createElement(\n 'button',\n { type: 'button', style: { ...S.row, flex: 1, border: 'none' }, onClick: () => onEnter(entry.path) },\n '📁 ' + entry.name,\n ),\n React.createElement(\n 'button',\n {\n type: 'button',\n style: { ...S.button, marginRight: 8, padding: '2px 8px' },\n 'aria-pressed': selected,\n title: selected ? labels.selected : labels.select,\n onClick: () => onToggleSelect(entry.path),\n },\n selected ? '✓' : labels.select,\n ),\n );\n}\n\n/** 「新建文件夹」那一行(`creatingName === null` 时收起)。 */\nexport function createRow(\n labels: DirectoryBrowserLabels,\n onName: (name: string) => void,\n onCancel: () => void,\n onCreate: (name: string) => void,\n creatingName: string | null,\n): React.ReactElement | null {\n if (creatingName === null) return null;\n return React.createElement(\n 'div',\n { style: { ...S.pathRow, borderTop: '1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.25))' } },\n React.createElement('input', {\n style: S.input,\n value: creatingName,\n placeholder: labels.newFolder,\n 'aria-label': labels.newFolder,\n onChange: (event: React.ChangeEvent<HTMLInputElement>) => onName(event.currentTarget.value),\n onKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => {\n if (event.key === 'Enter') onCreate(creatingName);\n if (event.key === 'Escape') onCancel();\n },\n }),\n React.createElement('button', { type: 'button', style: S.button, onClick: () => onCreate(creatingName) }, labels.createConfirm),\n React.createElement('button', { type: 'button', style: S.button, onClick: onCancel }, labels.createCancel),\n );\n}\n","/** 文件浏览器的视图半:把 `BrowseState` 画成一张对话框。**不认识宿主**,也不自己做 IO。\n *\n * 手写 `React.createElement`(本包 client 束禁 JSX,见 `tsdown.config.ts` 的既成口径)。\n * 只读状态、只回调;进哪一层、选到什么由操作半(`directory-browser-state.ts`)算。\n * 样式与四行控件在 `directory-browser-parts.ts`(那里跟着「多一行/少一行」动,本件只管组版)。\n */\n\nimport * as React from 'react';\nimport { rowsOf, targetOf } from './directory-browser-state.js';\nimport type { BrowseState, DirectoryRowBrowser } from './directory-browser-state.js';\nimport { hiddenCount } from './directory-browser-contract.js';\nimport type { DirectoryListing } from './directory-browser-contract.js';\nimport { S, createRow, crumbRow, entryRow, rootsRow } from './directory-browser-parts.js';\nimport type { DirectoryBrowserLabels } from './directory-browser-parts.js';\n\nexport type { DirectoryBrowserLabels } from './directory-browser-parts.js';\n\n/** 组件 props:样式只吃这一格,别的一律不给。 */\nexport interface DirectoryBrowserProps {\n readonly open: boolean;\n readonly state: BrowseState;\n readonly labels: DirectoryBrowserLabels;\n readonly onPick: () => void;\n readonly onClose: () => void;\n readonly onEnter: (path: string) => void;\n readonly onUp: () => void;\n readonly onSelect: (path: string) => void;\n readonly onToggleHidden: () => void;\n readonly onDraft: (draft: string) => void;\n readonly onCommitDraft: () => void;\n readonly onCreate: (name: string) => void;\n readonly onCreatingChange: (name: string | null) => void;\n}\n\nconst EMPTY_LISTING: DirectoryListing = { path: '', home: '', crumbs: [], entries: [], truncated: false };\n\n/** 画一张文件浏览器对话框。`open=false` 时返回 null(本件不挂 portal,由调用方决定挂在哪)。 */\nexport function DirectoryBrowser(props: DirectoryBrowserProps): React.ReactElement | null {\n const { open, state, labels } = props;\n if (!open) return null;\n const rows = rowsOf(state);\n const target = targetOf(state);\n const listing = state.listing ?? EMPTY_LISTING;\n const hidden = hiddenCount(listing);\n const body: React.ReactNode[] = [];\n body.push(crumbRow(state, props.onEnter));\n body.push(rootsRow(state, props.onEnter));\n body.push(\n React.createElement(\n 'div',\n { style: S.pathRow },\n React.createElement('input', {\n style: S.input,\n value: state.draft,\n placeholder: labels.pathPlaceholder,\n 'aria-label': labels.pathPlaceholder,\n onChange: (event: React.ChangeEvent<HTMLInputElement>) => props.onDraft(event.currentTarget.value),\n onKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => {\n if (event.key === 'Enter') props.onCommitDraft();\n },\n }),\n React.createElement('button', { type: 'button', style: S.button, onClick: props.onUp }, labels.up),\n React.createElement('button', { type: 'button', style: S.button, onClick: props.onCommitDraft }, labels.go),\n ),\n );\n body.push(\n React.createElement(\n 'div',\n { style: S.list, role: 'listbox', 'aria-label': labels.title },\n rows.length === 0\n ? React.createElement('div', { style: S.empty }, state.phase === 'loading' ? labels.loading : labels.empty)\n : rows.map((entry) => entryRow(entry, state.selected === entry.path, labels, props.onSelect, props.onEnter)),\n ),\n );\n body.push(createRow(labels, props.onDraft, () => props.onCreatingChange(null), props.onCreate, state.creating));\n body.push(\n React.createElement(\n 'div',\n { style: S.footNote },\n hidden > 0\n ? React.createElement(\n 'label',\n { style: { fontSize: 12, cursor: 'pointer', marginRight: 10 } },\n React.createElement('input', { type: 'checkbox', checked: state.showHidden, onChange: props.onToggleHidden }),\n ' ' + labels.showHidden(hidden),\n )\n : null,\n state.creating === null\n ? React.createElement('button', { type: 'button', style: { ...S.chromeButton, textDecoration: 'underline' }, onClick: () => props.onCreatingChange('') }, labels.newFolder)\n : null,\n state.notice !== null ? React.createElement('div', { style: S.error }, state.notice) : null,\n state.phase === 'failed' && state.failure !== null\n ? React.createElement('div', { style: S.error }, state.failure.message)\n : null,\n ),\n );\n body.push(\n React.createElement(\n 'div',\n { style: S.foot },\n React.createElement('span', { style: S.target, title: target ?? '' }, target === null ? '' : labels.willPick + target),\n React.createElement(\n 'button',\n { type: 'button', style: { ...S.button, ...(target === null ? S.buttonOff : {}) }, onClick: props.onPick },\n labels.open,\n ),\n React.createElement('button', { type: 'button', style: S.button, onClick: props.onClose }, labels.cancel),\n ),\n );\n return React.createElement(\n 'div',\n {\n style: S.scrim,\n role: 'presentation',\n onMouseDown: (event: React.MouseEvent<HTMLDivElement>) => {\n if (event.target === event.currentTarget) props.onClose();\n },\n },\n React.createElement(\n 'div',\n { style: S.dialog, role: 'dialog', 'aria-modal': true, 'aria-label': labels.title },\n React.createElement(\n 'div',\n { style: S.head },\n React.createElement('span', { style: S.title }, labels.title),\n // 标题右侧**不再重复当前路径**:同一条路径下面已经出现两次(面包屑与路径框),\n // 而盘符那行也要地方——头一行越干净越好(#744 第三轮现场反馈)。\n React.createElement(\n 'button',\n { type: 'button', style: { ...S.button, marginLeft: 'auto' }, 'aria-label': labels.close, title: labels.close, onClick: props.onClose },\n '✕',\n ),\n ),\n body,\n ),\n );\n}\n\n/** 目录行接线的 React 半边:把操作半那组动作直接喂给组件。\n *\n * 操作半(`directory-browser-state.ts` 的 `createDirectoryRowBrowser`)不碰 React,\n * 这里只把它的 `actions` 摊进 props —— 于是「进哪一层」那套在 Node 里可测,这里只做接线。\n *\n * `useSyncExternalStore` 是**必需**的:列举是异步落定的,不订阅的话这一帧画完就再没有重画,\n * 图上会永远停在「正在读取…」(#744 实测)。 */\nexport function DirectoryBrowserFromRow(props: {\n readonly open: boolean;\n readonly row: DirectoryRowBrowser;\n readonly labels: DirectoryBrowserLabels;\n}): React.ReactElement | null {\n const state = React.useSyncExternalStore(props.row.subscribe, props.row.state);\n return DirectoryBrowser({\n open: props.open,\n state,\n labels: props.labels,\n ...props.row.actions,\n });\n}\n\nexport { createBrowseController, createDirectoryRowBrowser, describe, rowsOf, targetOf } from './directory-browser-state.js';\nexport type { BrowseController, BrowseState, DirectoryRowBrowser } from './directory-browser-state.js';\nexport type { DirectoryEntry, DirectoryListing, RootKind, RootRow } from './directory-browser-contract.js';\n","/** 「本机根清单」的取数接线(客户端半):把一通电话变成图上那一行可跳转的根。\n *\n * **不认识宿主**:载体基段、通道名(第二段)、电话名全由调用方传进来——各家从自己的宿主镜像里拿\n * 那几个字面量(和 `openRowBrowser` 的 `refusalCode` 同一条规矩)。本件只做两件事:\n * 发一次调用、把回执信封拆成根清单;**任何失败都当空清单**(图上少画一行,不报错、不挡路)。\n */\n\nimport type { RootKind, RootRow } from './directory-browser-contract.js';\n\n/** 取数口:`connection.rpc.call` 的形状,每次现取(连接后到也不永久缺席)。 */\nexport type RootsCall = (\n base: string,\n endpoint: string,\n payload: unknown,\n signal?: AbortSignal,\n) => Promise<unknown>;\n\n/** 认一认某个值像不像一个根(认不出的行丢掉:图的干净比多画一行重要)。 */\nfunction isRootRow(raw: unknown): raw is RootRow {\n if (typeof raw !== 'object' || raw === null) return false;\n const row = raw as { path?: unknown; kind?: unknown };\n return typeof row.path === 'string' && row.path !== '' && typeof row.kind === 'string';\n}\n\n/** 信封 → 根清单(纯函数,**永不抛**):`{ok:true, value:{roots:[…]}}` 之外的形状一律当空。 */\nexport function readRootsAnswer(raw: unknown): readonly RootRow[] {\n const answer = (typeof raw === 'object' && raw !== null ? raw : {}) as { ok?: unknown; value?: unknown };\n if (answer.ok !== true) return [];\n const value = (typeof answer.value === 'object' && answer.value !== null ? answer.value : {}) as { roots?: unknown };\n if (!Array.isArray(value.roots)) return [];\n return value.roots.filter(isRootRow).map((row) => ({ path: row.path, kind: row.kind as RootKind }));\n}\n\n/** 建一个「根清单取数器」:给调用口与三个名字,回一个 `() => Promise<RootRow[]>`。\n *\n * 界面按需调它(开图一次),不在渲染里调,也不缓存到这里——缓存是宿主半的事。 */\nexport function createRootsSource(input: {\n readonly getCall: () => unknown;\n readonly base: string;\n readonly endpoint: string;\n readonly method: string;\n}): () => Promise<readonly RootRow[]> {\n return async () => {\n const call = input.getCall();\n if (typeof call !== 'function') return [];\n try {\n return readRootsAnswer(\n await (call as RootsCall)(input.base, input.endpoint, { method: input.method, payload: {} }),\n );\n } catch {\n return [];\n }\n };\n}\n","/** dsh-life-pack client 适配器(六边形:browser 侧;爱生活单卡方案)。\n *\n * 真实 DSH 契约(出处见 docs/agents/dsh-client-contract.md §5/§11):\n * - 本文件经 tsdown 打成 loader 工厂包(browser/CJS + 注册包裹),classic 执行\n * 只注册,副作用全在 factory 内;传递闭包禁 node 内建与 ESM 语法(回路\n * test/client-bundle-48.test.mjs 看门)。\n * - inject 短名 ['slots','connection']:本包现在有 RPC(装与更新的能力由宿主半提供,\n * 电话名与轮询间隔由宿主转交,面板不写死),读了 ctx.connection 就必须声明 'connection'(#48 血例)。\n * - 爱生活页签条走 slot 驱动(settings-plugins 先例,禁纯 useState 手画 tab):\n * section 声明 children 爱生活页签槽,各技能往里注册自家技能设置页;\n * tab 行从 ledger 投影(entries+getVersion+subscribe),面板用 props.renderSlot\n * 按 `{only}` 投影(一次只挂载一个)+ visited 缓存;ledger 无某技能=缺席,\n * 显示推荐安装只读文本(不返空冒充)。\n * - 组件 React.createElement 手写(禁 JSX),无数据轮询、无 document/window/process。\n * - 面板外壳两处(票 #679):标题行右上角两个入口(星/气泡,悬停出说明文字,\n * 窄窗口折到标题下方);页签面板之后是底部「作者其他插件」引流卡(四行,点开新窗口)。\n * 文案与网址都取自 nav.ts 的两张静态表,本文件只画不算。\n */\n\nimport * as React from 'react';\nimport { MANAGER_PLUGIN, MANAGER_TABS, MORE_PLUGINS, PANEL_LINKS, recoFor } from './nav.js';\nimport type { ManagerTab } from './nav.js';\nimport { AbsentCard, CheckUpdateButton, UpdateResults, useUpdateRows } from './update-panel.js';\nimport { loadManagerVersion } from './update-client.js';\nimport type { CallFace } from './update-client.js';\nimport { CONFIG_TAB_SLOT, VERSION_UNKNOWN } from './update-contract.js';\nimport { summaryErrorOf, useHealthPanel } from './health-panel.js';\nimport { HealthSummaryLine, HealthTable, STATUS_TEXT, TAB_DOT, TAB_NOTE_STYLE, lightsOf, tabDotColor, tabNote } from './health-view.js';\nimport { HEALTH_ENDPOINT } from './health-contract.js';\nimport type {\n ClientCtx,\n ConfigTabRow,\n LifePackSectionProps,\n SlotLedgerEntry,\n} from './dsh-ctx.js';\n\nexport const inject = ['slots', 'connection'];\n\n/** 爱生活页签槽名:定义在 `update-contract.ts`(宿主判「已装产物有没有注册代码」也用这个名字,一处定义)。 */\nexport { CONFIG_TAB_SLOT } from './update-contract.js';\n\n/** 共用件「目录浏览器」从包门转出(票 #744):六个单品插件的设置页要用的就是这两样——\n * 「开图接线 + 入口三态判定」(纯逻辑,`./directory-browser` 子路径)与\n * 「对话框组件」(`./directory-browser-ui` 子路径,吃 React)。\n *\n * 六家的 client 束**不能**从本条 `./client` 取:这一条是 loader 工厂包\n * (`window.__ModuleLoader__.load(...)` 的注册壳),`require` 拿到的是空 exports。\n * 所以对外要用的是那两个普通子路径;这里只是同源转出,供本包内部与用例使用。 */\nexport { DirectoryBrowserFromRow } from './directory-browser-ui.js';\nexport type { DirectoryBrowserLabels, DirectoryBrowserProps } from './directory-browser-ui.js';\nexport { createBrowseController, createDirectoryRowBrowser, rowsOf, canGoUp, targetOf, entryPath } from './directory-browser-state.js';\nexport type { BrowseController, BrowseState, DirectoryRowBrowser } from './directory-browser-state.js';\nexport { pickerModeOf, readPickAnswer } from './directory-browser-contract.js';\nexport type { DirectoryBrowseFace, DirectoryListing, PickOutcome, RootKind, RootRow } from './directory-browser-contract.js';\nexport { createRootsSource, readRootsAnswer } from './directory-browser-roots.js';\n\n/** 包名 → 本家那条客户端通道(票 #735)。取值面是导航表那份镜像,不再从页签槽账本的自定义选项里读:\n * 装机槽位面不透传自定义键,读了恒是空串(详见 `nav.ts` 上 `ManagerTab.channel` 的注释)。 */\nconst CHANNEL_BY_PLUGIN = new Map(MANAGER_TABS.map((tab) => [tab.plugin, tab.channel]));\n\n/** 总管视觉(内联 style;颜色走 DSH 主题别名,深浅主题自适应,写死值只做回退;与技能面板同语言)。 */\nconst S = {\n headRow: {\n display: 'flex',\n flexWrap: 'wrap',\n alignItems: 'center',\n gap: 8,\n margin: '2px 0 8px',\n } as React.CSSProperties,\n head: { fontSize: 15, fontWeight: 700, flex: '0 0 auto', color: 'var(--dsw-alias-label-primary, inherit)' } as React.CSSProperties,\n headActions: {\n display: 'inline-flex',\n alignItems: 'center',\n gap: 2,\n marginLeft: 'auto',\n flex: '0 0 auto',\n } as React.CSSProperties,\n iconLink: {\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n width: 26,\n height: 26,\n borderRadius: 6,\n border: '1px solid transparent',\n color: 'var(--dsw-alias-label-primary, inherit)',\n textDecoration: 'none',\n fontSize: 14,\n lineHeight: 1,\n } as React.CSSProperties,\n meta: { color: 'var(--dsw-alias-label-secondary, #9a9a9a)', fontSize: 12, lineHeight: 1.7, marginBottom: 10 } as React.CSSProperties,\n tablist: { display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: 10 } as React.CSSProperties,\n tab: {\n border: '1px solid var(--dsw-alias-border, rgba(128,128,128,.35))',\n background: 'transparent',\n color: 'var(--dsw-alias-label-primary, inherit)',\n borderRadius: 999,\n padding: '4px 12px',\n fontSize: 13,\n cursor: 'pointer',\n } as React.CSSProperties,\n tabActive: {\n background: 'var(--dsw-alias-brand-primary, #0a84ff)',\n borderColor: 'transparent',\n color: '#fff',\n fontWeight: 700,\n } as React.CSSProperties,\n reco: {\n padding: '12px 14px',\n borderRadius: 10,\n border: '1px dashed var(--dsw-alias-border, rgba(128,128,128,.45))',\n color: 'var(--dsw-alias-label-secondary, #9a9a9a)',\n fontSize: 13,\n lineHeight: 1.7,\n } as React.CSSProperties,\n cmd: {\n display: 'block',\n marginTop: 8,\n padding: '8px 10px',\n borderRadius: 8,\n background: 'var(--dsw-alias-bg-base, rgba(128,128,128,.12))',\n color: 'var(--dsw-alias-label-primary, inherit)',\n fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',\n fontSize: 12,\n userSelect: 'all',\n } as React.CSSProperties,\n moreCard: {\n marginTop: 16,\n padding: '10px 12px',\n borderRadius: 10,\n border: '1px solid var(--dsw-alias-border, rgba(128,128,128,.35))',\n } as React.CSSProperties,\n moreTitle: {\n fontSize: 13,\n fontWeight: 700,\n marginBottom: 4,\n color: 'var(--dsw-alias-label-primary, inherit)',\n } as React.CSSProperties,\n moreRow: {\n display: 'flex',\n flexWrap: 'wrap',\n alignItems: 'center',\n gap: '2px 8px',\n padding: '7px 4px',\n textDecoration: 'none',\n color: 'inherit',\n } as React.CSSProperties,\n morePkg: {\n flex: '0 1 auto',\n minWidth: 0,\n overflowWrap: 'anywhere',\n fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',\n fontSize: 12,\n fontWeight: 650,\n } as React.CSSProperties,\n moreDesc: {\n flex: '1 1 180px',\n minWidth: 0,\n fontSize: 11.5,\n lineHeight: 1.6,\n color: 'var(--dsw-alias-label-secondary, #9a9a9a)',\n } as React.CSSProperties,\n moreIcon: {\n flex: '0 0 auto',\n marginLeft: 'auto',\n display: 'inline-flex',\n color: 'var(--dsw-alias-label-secondary, #9a9a9a)',\n } as React.CSSProperties,\n};\n\n/** 缺席卡的画法与流程住 `update-panel.ts`(票 #678:一句人话 +「装上」按钮 + 可复制命令),\n * `recoFor` 的补装命令仍是卡里那条辅助展示(B11 口径不变)。 */\n\n/** 标签解析(resolveSlotLabel 同形:thunk 跟活,无则空字串;见 slots lib:27-29)。 */\nfunction resolveLabel(label: SlotLedgerEntry['options']['label']): string {\n if (typeof label === 'function') return label();\n return label ?? '';\n}\n\n/** 外链图标(行尾那个;纯装饰,语义在链接自己的 aria-label/文字上)。 */\nfunction ExternalIcon(): React.ReactElement {\n return React.createElement(\n 'svg',\n {\n viewBox: '0 0 24 24',\n width: 13,\n height: 13,\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: 1.9,\n strokeLinecap: 'round',\n strokeLinejoin: 'round',\n 'aria-hidden': 'true',\n focusable: 'false',\n },\n React.createElement('path', { d: 'M13.5 4.5H19.5V10.5' }),\n React.createElement('path', { d: 'M19.5 4.5L11 13' }),\n React.createElement('path', { d: 'M18 14.5V18A2 2 0 0 1 16 20H6A2 2 0 0 1 4 18V8A2 2 0 0 1 6 6H9.5' }),\n );\n}\n\n/** 标题行右上角两件:星(去本仓点 Star)+ 气泡(去本仓开 issue),悬停出说明文字。\n *\n * 三件并排里的第三件「检查更新」住隔壁票(#678):本票只留位子——它就接在本组件之前,\n * 不画一个点了没反应的假按钮。窄窗口靠 headRow 的 flexWrap 折到标题下方,\n * 标题与两件都是 flex:'0 0 auto',谁也不挤谁。\n */\nfunction PanelActions(): React.ReactElement {\n return React.createElement(\n 'div',\n { style: S.headActions },\n PANEL_LINKS.map((link) =>\n React.createElement(\n 'a',\n {\n key: link.key,\n href: link.url,\n target: '_blank',\n rel: 'noreferrer',\n title: link.tip,\n 'aria-label': link.tip,\n style: S.iconLink,\n },\n React.createElement('span', { 'aria-hidden': 'true' }, link.glyph),\n ),\n ),\n );\n}\n\n/** 底部「作者其他插件」引流卡:四行(包名 + 一句说明 + 行尾外链图标),点开新窗口。\n *\n * 卡片在页签面板之后,六页签切换它都在(它不属于任何一个页签)。\n */\nfunction MorePluginsCard(): React.ReactElement {\n return React.createElement(\n 'div',\n { style: S.moreCard },\n React.createElement('div', { style: S.moreTitle }, '作者其他插件'),\n MORE_PLUGINS.map((row) =>\n React.createElement(\n 'a',\n {\n key: row.pkg,\n href: row.url,\n target: '_blank',\n rel: 'noreferrer',\n title: row.pkg + ' 的 GitHub 仓库',\n style: S.moreRow,\n },\n React.createElement('span', { style: S.morePkg }, row.pkg),\n React.createElement('span', { style: S.moreDesc }, row.desc),\n React.createElement('span', { style: S.moreIcon }, React.createElement(ExternalIcon, null)),\n ),\n ),\n );\n}\n\n/** 总管自述版本(票 #737):**不手写**,问宿主——宿主读自己这份已安装包的 `package.json`。\n *\n * 单次取数(`loadManagerVersion`),无轮询;初值 `unknown`,读不到也停在 `unknown`。\n * 与卡路里 #130 同一条路:面板是浏览器产物、禁 node 内建,读盘只许在宿主半。 */\nfunction useManagerVersion(getCall: () => CallFace | null): string {\n const [version, setVersion] = React.useState<string>(VERSION_UNKNOWN);\n React.useEffect(() => {\n let alive = true;\n void loadManagerVersion(getCall()).then((next) => {\n if (alive) setVersion(next);\n });\n return () => {\n alive = false;\n };\n }, [getCall]);\n return version;\n}\n\n/** 爱生活面板:总设置区 + 检查更新(七家)+ 爱生活页签条(slot 驱动)+ 技能设置页投影/缺席卡。 */\nfunction LifePackSection(props: LifePackSectionProps & { getCall: () => CallFace | null }): React.ReactElement {\n const tabsId = React.useId();\n const tabRefs = React.useRef<Array<HTMLButtonElement | null>>([]);\n const rows: ConfigTabRow[] = props.useTabs((value) => value);\n const present = new Set(rows.map((r) => r.id));\n const face = useUpdateRows(props.getCall);\n const managerVersion = useManagerVersion(props.getCall);\n // 配置体检(#706):一张表六份报告,顶部那行汇总与各家那张表都从它读(同一份数据)。\n // 通道名的来源见 CHANNEL_BY_PLUGIN(#735:账本那一格读不到,改取导航表那份镜像)。\n const healthTabs = React.useMemo(\n () => rows.filter((row) => row.channel.length > 0).map((row) => ({ id: row.id, channel: row.channel })),\n [rows],\n );\n const health = useHealthPanel(() => props.getCall(), healthTabs);\n /** 六家的灯(票 #732):按**页签槽账本**那一排过(装了但没体检通道的也在内,它显缺席态)。 */\n const lights = React.useMemo(\n () => lightsOf(\n rows.map((row) => ({ id: row.id, title: row.label, hasChannel: row.channel.length > 0 })),\n Object.fromEntries(Object.entries(health.rows).map(([id, row]) => [id, row.report ?? undefined])),\n ),\n [rows, health.rows],\n );\n const lightOf = (id: string) => lights.find((light) => light.id === id);\n const [activeId, setActiveId] = React.useState<string | undefined>(undefined);\n const [visitedIds, setVisitedIds] = React.useState<ReadonlySet<string>>(() => new Set());\n const active = MANAGER_TABS.some((t) => t.plugin === activeId)\n ? (activeId as string)\n : MANAGER_TABS[0].plugin;\n React.useEffect(() => {\n setVisitedIds((previous) => {\n if (previous.has(active)) return previous;\n return new Set([...previous, active]);\n });\n }, [active]);\n /** 预热:section 一挂载,就把其余几家**错峰**挂上(隐藏面板照常挂载、只切 `hidden`),\n * 让各家那一次配置读提前跑掉——用户点哪个页签,多半值已经在了。\n * 错峰(每 150ms 一家)是为了不让六家子进程同时抢开工:首帧那一口气优先留给框架。\n * 只在页签账本非空时起一次(`rows` 要等各家 slot 注入后才齐)。\n *\n * **一次性**(p10 定时器门的例外依据):每个页签只挂一次(`warmed` 闸门 + 一批固定数量的定时器),\n * 不是轮询、也不是重试——它没有「次数」可言;卸载即 `clearTimeout` 清干净。\n * 不做轮询是本意:这里只补一次提前量,取数与刷新仍旧由各家用例自己的通道走。 */\n const warmed = React.useRef(false);\n const tabCount = rows.length;\n React.useEffect(() => {\n if (warmed.current || tabCount === 0) return;\n warmed.current = true;\n const STAGGER_MS = 150;\n const timers = MANAGER_TABS\n .filter((tab) => rows.some((row) => row.id === tab.plugin))\n .map((tab, index) => setTimeout(() => {\n setVisitedIds((previous) => (previous.has(tab.plugin) ? previous : new Set([...previous, tab.plugin])));\n }, STAGGER_MS * (index + 1)));\n return () => {\n for (const timer of timers) clearTimeout(timer);\n };\n }, [tabCount]);\n function labelFor(tab: ManagerTab): string {\n const row = rows.find((r) => r.id === tab.plugin);\n return row && row.label.length > 0 ? row.label : tab.title;\n }\n function targetForTab(tab: ManagerTab) {\n return face.targets.find((t) => t.packageName === tab.plugin) ?? null;\n }\n function onTabKeyDown(event: React.KeyboardEvent, index: number): void {\n let nextIndex: number | undefined;\n switch (event.key) {\n case 'ArrowRight': nextIndex = (index + 1) % MANAGER_TABS.length; break;\n case 'ArrowLeft': nextIndex = (index - 1 + MANAGER_TABS.length) % MANAGER_TABS.length; break;\n case 'Home': nextIndex = 0; break;\n case 'End': nextIndex = MANAGER_TABS.length - 1; break;\n default: return;\n }\n event.preventDefault();\n const next = MANAGER_TABS[nextIndex];\n setActiveId(next.plugin);\n tabRefs.current[nextIndex]?.focus();\n }\n return React.createElement(\n 'div',\n null,\n React.createElement(\n 'div',\n { style: S.headRow },\n React.createElement('div', { style: S.head }, '爱生活'),\n // 三件并排:本票的「检查更新」在左,隔壁票(#679)的星与气泡在右,整组靠右(窄窗口折行)。\n React.createElement(\n 'div',\n { style: S.headActions },\n React.createElement(CheckUpdateButton, { face }),\n React.createElement(PanelActions, null),\n ),\n ),\n React.createElement(\n 'div',\n { style: S.meta },\n // 版本行(#737):包名取自 nav.ts 那处唯一定义,版本号取自宿主读到的装机包版本。\n // 这段 meta 区此前还挂着一行静态文本,说有个「缺省启用、只读的开关」——那个开关今天并不存在\n // (全仓没有任何代码读它或写它,设置页的行表里也没有「启用」这一项),票 #738 原地删除。\n React.createElement('div', null, '总管 ' + MANAGER_PLUGIN + ' · ' + managerVersion),\n ),\n React.createElement(UpdateResults, { face }),\n React.createElement(HealthSummaryLine, {\n lights,\n running: health.running,\n // #735:取数失败也要在这一行看得见(各家的错只画在那家页签里的表上,摘要行沉默=用户以为按钮坏了)。\n error: summaryErrorOf(health),\n onRun: health.run,\n }),\n React.createElement(\n 'div',\n { role: 'tablist', 'aria-label': '爱生活技能页签', style: S.tablist },\n MANAGER_TABS.map((tab, index) => {\n const selected = tab.plugin === active;\n const light = lightOf(tab.plugin);\n // 圆点从「实心/空心=装没装」换成「颜色=体检档位」(票 #732):装了就是实心、\n // 颜色按档位走;没装的保持缺席态、不许点(点它只会跳到一张缺席卡)。\n const dotColor = light === undefined\n ? (present.has(tab.plugin) ? TAB_DOT.unchecked : TAB_DOT.absent)\n : tabDotColor(light);\n const note = light === undefined ? null : tabNote(light);\n return React.createElement(\n 'button',\n {\n key: tab.plugin,\n ref: (element: HTMLButtonElement | null) => {\n tabRefs.current[index] = element;\n },\n id: tabsId + '-tab-' + tab.plugin,\n type: 'button',\n role: 'tab',\n 'aria-selected': selected,\n 'aria-controls': tabsId + '-panel-' + tab.plugin,\n tabIndex: selected ? 0 : -1,\n style: selected ? { ...S.tab, ...S.tabActive } : S.tab,\n onClick: () => {\n setActiveId(tab.plugin);\n },\n onKeyDown: (event: React.KeyboardEvent) => {\n onTabKeyDown(event, index);\n },\n title: light === undefined\n ? '这一家没装(或产物没注册页签槽)'\n : light.title + ':' + (light.hasChannel\n ? (light.status === null ? '还没体检' : '体检读数见本页签下方的表')\n : '没有配置体检出口'),\n },\n React.createElement('span', {\n 'data-ilife-health': 'tab-dot',\n style: {\n width: 6,\n height: 6,\n borderRadius: '50%',\n background: dotColor,\n display: 'inline-block',\n flex: '0 0 auto',\n },\n }),\n labelFor(tab),\n note === null\n ? null\n : React.createElement(\n 'span',\n {\n 'data-ilife-health': 'tab-note',\n // 红黄各自那一截带**自己**的档位色,不许一个色管两档(#706 复评逮过的缺陷)。\n style: { ...TAB_NOTE_STYLE, color: note.status === null ? undefined : STATUS_TEXT[note.status] },\n },\n note.text,\n ),\n );\n }),\n ),\n MANAGER_TABS.filter((tab) => tab.plugin === active || visitedIds.has(tab.plugin)).map((tab) => {\n const selected = tab.plugin === active;\n return React.createElement(\n 'div',\n {\n key: tab.plugin,\n id: tabsId + '-panel-' + tab.plugin,\n role: 'tabpanel',\n 'aria-labelledby': tabsId + '-tab-' + tab.plugin,\n hidden: !selected,\n },\n present.has(tab.plugin)\n ? React.createElement(\n 'div',\n null,\n props.renderSlot(CONFIG_TAB_SLOT, {}, { only: tab.plugin }) as React.ReactNode,\n // 那家的体检表:与总览那行读同一份快照(`health.rows`),不是各算一遍。\n React.createElement(HealthTable, {\n title: labelFor(tab),\n phase: health.rows[tab.plugin]?.phase ?? 'idle',\n report: health.rows[tab.plugin]?.report ?? null,\n error: health.rows[tab.plugin]?.error ?? null,\n }),\n )\n : React.createElement(AbsentCard, {\n target: targetForTab(tab),\n fallbackCommand: recoFor(tab).installCmd,\n // 账本事实与装机读数分开给:缺席卡按两处事实分三态,不许互相顶替(见 update-view.ts 的 slotStateOf)。\n inLedger: present.has(tab.plugin),\n face,\n }),\n );\n }),\n React.createElement(MorePluginsCard, null),\n );\n}\n\nexport function apply(ctx: ClientCtx): void {\n // 调用口取用器:每次取数时现取(connection 后到也不永久缺席),透传给面板组件。\n const getCall = (): CallFace | null => (ctx.connection?.rpc?.call as CallFace | undefined) ?? null;\n // ledger 观测源(settings-plugins:1744-1767 同形;无 locale 面声明,故只订 ledger)。\n let tabsVersion = -1;\n let tabs: ConfigTabRow[] = [];\n const sectionInjected = () => ({\n hooks: {\n tabs: {\n getSnapshot: () => {\n const version = ctx.slots.getVersion(CONFIG_TAB_SLOT);\n if (version !== tabsVersion) {\n tabsVersion = version;\n tabs = ctx.slots\n .entries(CONFIG_TAB_SLOT)\n .map((entry) => ({\n id: entry.options.id ?? '',\n order: entry.options.order ?? 0,\n label: resolveLabel(entry.options.label),\n // #735 配置体检的通道:取自导航表(账本那一格读不到)。表里没有这家 ⇒ 空串 ⇒\n // 这家没有体检出口(灯显缺席态、按钮不会为它取数)。\n channel: CHANNEL_BY_PLUGIN.get(entry.options.id ?? '') ?? '',\n }))\n .sort((a, b) => a.order - b.order);\n }\n return tabs;\n },\n subscribe: (listener: () => void) => ctx.slots.subscribe(CONFIG_TAB_SLOT, listener),\n },\n },\n });\n ctx.slots.inject('settings.section', () =>\n ctx.slots.register(\n {\n name: 'settings.section',\n id: 'dsh-life-pack',\n order: 21,\n label: () => '爱生活',\n inject: sectionInjected,\n children: { 'ilife.config-tab': { kind: 'list', scope: 'root' } },\n },\n (props: LifePackSectionProps) => React.createElement(LifePackSection, { ...props, getCall }),\n ),\n );\n}\n"],"mappings":"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"}
|