dsh-life-pack 0.3.0 → 0.3.2
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 +199 -63
- package/dist/client.js.map +1 -1
- package/dist/health-fetch.d.ts +9 -2
- package/dist/health-fetch.d.ts.map +1 -1
- package/dist/health-fetch.js +9 -2
- package/dist/health-fetch.js.map +1 -1
- package/dist/update-client.d.ts +7 -1
- package/dist/update-client.d.ts.map +1 -1
- package/dist/update-client.js +7 -1
- package/dist/update-client.js.map +1 -1
- package/dist/update-contract.d.ts.map +1 -1
- package/dist/update-contract.js +29 -18
- package/dist/update-contract.js.map +1 -1
- package/dist/update-env.d.ts.map +1 -1
- package/dist/update-env.js +7 -11
- package/dist/update-env.js.map +1 -1
- package/dist/update-view.d.ts +41 -6
- package/dist/update-view.d.ts.map +1 -1
- package/dist/update-view.js +88 -21
- package/dist/update-view.js.map +1 -1
- package/package.json +1 -1
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":["IDLE","React","React","React","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/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: 'memo', slotId: 'ilife:memo', order: 70, title: '备忘录', plugin: 'dsh-memo-ilife', channel: '/ilife-memo' },\n { skill: 'calorie', slotId: 'ilife:calorie', order: 75, title: '卡路里', plugin: 'dsh-calorie', channel: '/ilife-calorie' },\n { skill: 'schedule', slotId: 'ilife:schedule', order: 80, title: '作息管家', plugin: 'dsh-schedule-ilife', channel: '/ilife-schedule-ilife' },\n { skill: 'home', slotId: 'ilife:home', order: 85, title: '居家管家', plugin: 'dsh-home-ilife', channel: '/ilife-home-ilife' },\n { skill: 'chef', slotId: 'ilife:chef', order: 90, title: '私家大厨', plugin: 'dsh-chef', channel: '/ilife-chef' },\n { skill: 'bill', slotId: 'ilife:cookie', order: 95, title: '饼干记账', plugin: 'dsh-bill-ilife', channel: '/ilife-bill-ilife' },\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} 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 节)。 */\nconst REASON_TEXT: Record<string, string> = {\n 'unknown-profile': '使用范围认不出(名字非法或目录不存在):先检查使用范围名与目录,这种情形不给手工命令。',\n 'source-install': '当前是从源码装的,不是按版本号装的:想走更新,先按版本号重装一次。',\n 'invalid-installation': '已装的包不完整(名字对不上、版本非法、入口文件缺失):重装当前版本,修好已装目录再查。',\n 'installation-changed': '安装位置在使用中途变了:重新打开宿主再查一次;还出现就重装。',\n 'pending-restart': '新版已装到磁盘,正在跑的还是旧版:重启宿主后生效。',\n 'registry-conflict': '本地声明的版本与磁盘实际版本互相矛盾:打开使用范围的清单,把目标包名那行改成版本号再试。',\n 'incompatible-node': '新版要求的 Node 与当前运行的 Node 对不上:先把 Node 升到 22 或更高,再查。',\n 'recovery-required': '上次安装被打断,留下一个半截任务:重新点一次安装;一直出现就查宿主日志。',\n 'check-failed': '查新版失败(联网不通或官方源不可达):检查网络与官方源地址,再查一次。',\n 'invalid-release': '远端发行信息不合规(名字对不上、版本非法、包地址或完整性校验不过):换个版本再查。',\n 'check-expired': '上次查新版的凭证过期了:重新查一次再点安装(不要重试旧编号)。',\n 'update-busy': '同一个使用范围同时只装一个:等当前任务结束再点。',\n 'install-failed': '安装没跑成:按下面那条手工命令在终端执行一次。',\n 'manager-unreachable': '总管的电话没接上(宿主半没注册或版本对不上):重启宿主后再试;仍不行看宿主日志。',\n 'bad-request': '回执异常(请求形状不对):把宿主日志里的事件名报给作者。',\n internal: '回执异常(总管内部错误):把宿主日志里的事件名报给作者。',\n};\n\n/** 原因码 → 人话(认不出的码不猜、原样回码并说明这是未知原因)。 */\nexport function reasonText(code: string): string {\n return REASON_TEXT[code] ?? '未知原因(' + code + '):把宿主日志里的事件名报给作者。';\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/** 装上更新:查新版 → 提交安装 → 有界轮询查状态到任务收尾;凭证过期重查一次再提交。 */\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\nexport interface Verdict {\n readonly kind: VerdictKind;\n /** 一句话结论(面板那行正文)。 */\n readonly text: string;\n /** 这一行给不给按钮:缺席给「装上」,有新版给「装上更新」,其余不给。 */\n readonly action: 'install' | 'update' | null;\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 return { kind: 'unknown', text: '未安装;点「检查更新」查最新版本后装上。', action: null };\n }\n if (absent) {\n return { kind: 'update-available', text: '未安装;最新版本 ' + String(latest) + '。', action: 'install' };\n }\n if (snapshot.blockedReason !== null) {\n const blocked = snapshot.blockedReason as BlockedReason;\n if (blocked === 'pending-restart') {\n return { kind: 'blocked', text: reasonText(blocked), action: null };\n }\n return { kind: 'blocked', text: reasonText(blocked), action: null };\n }\n if (latest === null) {\n return { kind: 'unknown', text: '还没查过最新版本;点「检查更新」。', action: null };\n }\n if (snapshot.canInstall) {\n return { kind: 'update-available', text: '有新版:' + String(latest) + ',点「装上更新」。', action: 'update' };\n }\n return { kind: 'up-to-date', text: '已是最新(' + String(latest) + ')。', action: null };\n}\n\n/** 待重启横幅文案(更新包 README 第 10 节:说清新版号与「重启后才生效」两件事)。 */\nexport function pendingRestartText(target: TargetInfo, snapshot: UpdateSnapshot): string | null {\n if (snapshot.blockedReason !== 'pending-restart') return null;\n const installed = snapshot.installedVersion ?? snapshot.job?.targetVersion ?? null;\n const running = snapshot.runningVersion;\n if (installed === null) return '⚠️ 新版已装好,重启宿主后生效。';\n if (installed === running) return '⚠️ 新版 ' + installed + ' 已装好,重启宿主后生效。';\n return '⚠️ 新版 ' + installed + ' 已装好,正在跑的还是 ' + running + ',重启宿主后生效。';\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 { isAbsent, manualForDisplay, pendingRestartText, slotStateOf, verdictOf, versionLines } from './update-view.js';\nimport type { CheckOutcome, TargetInfo, Verdict } 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 act(target: TargetInfo): void;\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) => {\n const current = rows[target.key] ?? IDLE;\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 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/** 待重启横幅:任何一家成 `pending-restart` 就常驻显示,重启宿主后自动消失(更新包 README 第 10 节)。 */\nfunction RestartBanners(props: { readonly face: UpdateRowsFace }): React.ReactElement | null {\n const lines: string[] = [];\n for (const target of props.face.targets) {\n const outcome = props.face.rows[target.key]?.outcome;\n if (!outcome) continue;\n const text = pendingRestartText(target, outcome.snapshot);\n if (text) lines.push(text);\n }\n if (lines.length === 0) return null;\n return React.createElement(\n 'div',\n { style: PANEL_STYLE.banner },\n lines.map((line, index) => React.createElement('div', { key: String(index) }, line)),\n );\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 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 const showManual = manual !== null && (row.phase === 'failed' || verdict?.kind === 'blocked');\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 verdict?.action\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: () => props.face.act(target),\n },\n verdict.action === 'install' ? '装上' : '装上更新',\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 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 let reason: string | null = row?.failure?.message ?? null;\n if (target && reason === null && row?.outcome && row.outcome.snapshot.blockedReason !== null) {\n reason = verdictOf(target, row.outcome.snapshot).text;\n }\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 ' 装着、产物里也有注册代码,但它的设置页没送到浏览器:重启宿主后再看这个页签;仍然这样就去宿主日志里找它的报错。';\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);\n },\n },\n busy ? '正在装,请稍候…' : '装上',\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/** 一次取数的时限(毫秒):技能侧 spawn 上限 20 s,这里留一点余量。 */\nexport const HEALTH_TIMEOUT_MS = 25_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/** 取六家的体检报告(缺席的包不在此列,由调用方按账本判断)。 */\nexport async function loadHealthReports(call: HealthCallFace | null, tabs: readonly HealthTabRow[]): 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 return { id: tab.id, report: one.report, error: one.error };\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, 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/** 体检状态源:挂载时不自动跑(体检要读盘、要起子进程,按需跑),点一次跑六家。 */\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 loaded = await loadHealthReports(getCall(), targets);\n // 渲染台的调试图(浏览器里没人写它,写入失败无所谓):只记条数与第一条的错。\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 const next: Record<string, { phase: HealthPhase; report: HealthReport | null; error: string | null }> = {};\n for (const row of loaded.rows) {\n next[row.id] = { phase: row.report ? 'ready' : 'failed', report: row.report, error: row.error };\n }\n setRows(next);\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","/** 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/** 包名 → 本家那条客户端通道(票 #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","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/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: 'memo', slotId: 'ilife:memo', order: 70, title: '备忘录', plugin: 'dsh-memo-ilife', channel: '/ilife-memo' },\n { skill: 'calorie', slotId: 'ilife:calorie', order: 75, title: '卡路里', plugin: 'dsh-calorie', channel: '/ilife-calorie' },\n { skill: 'schedule', slotId: 'ilife:schedule', order: 80, title: '作息管家', plugin: 'dsh-schedule-ilife', channel: '/ilife-schedule-ilife' },\n { skill: 'home', slotId: 'ilife:home', order: 85, title: '居家管家', plugin: 'dsh-home-ilife', channel: '/ilife-home-ilife' },\n { skill: 'chef', slotId: 'ilife:chef', order: 90, title: '私家大厨', plugin: 'dsh-chef', channel: '/ilife-chef' },\n { skill: 'bill', slotId: 'ilife:cookie', order: 95, title: '饼干记账', plugin: 'dsh-bill-ilife', channel: '/ilife-bill-ilife' },\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} 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/** 取六家的体检报告(缺席的包不在此列,由调用方按账本判断)。 */\nexport async function loadHealthReports(call: HealthCallFace | null, tabs: readonly HealthTabRow[]): 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 return { id: tab.id, report: one.report, error: one.error };\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, 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/** 体检状态源:挂载时不自动跑(体检要读盘、要起子进程,按需跑),点一次跑六家。 */\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 loaded = await loadHealthReports(getCall(), targets);\n // 渲染台的调试图(浏览器里没人写它,写入失败无所谓):只记条数与第一条的错。\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 const next: Record<string, { phase: HealthPhase; report: HealthReport | null; error: string | null }> = {};\n for (const row of loaded.rows) {\n next[row.id] = { phase: row.report ? 'ready' : 'failed', report: row.report, error: row.error };\n }\n setRows(next);\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","/** 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/** 包名 → 本家那条客户端通道(票 #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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"}
|