dsh-life-pack 0.2.6 → 0.2.7

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.
@@ -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 readonly title: string;\n /** 提供该 tab 的单品插件包名(仅文档级引用,不 import)。 */\n readonly plugin: 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/** 总管版本号(与 package.json 同步,release 时一起 bump;设置页总设置区版本行唯一来源)。 */\nexport const MANAGER_VERSION = '0.2.6' as const;\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' },\n { skill: 'calorie', slotId: 'ilife:calorie', order: 75, title: '卡路里', plugin: 'dsh-calorie' },\n { skill: 'schedule', slotId: 'ilife:schedule', order: 80, title: '作息', plugin: 'dsh-schedule-ilife' },\n { skill: 'home', slotId: 'ilife:home', order: 85, title: '居家', plugin: 'dsh-home-ilife' },\n { skill: 'chef', slotId: 'ilife:chef', order: 90, title: '大厨', plugin: 'dsh-chef' },\n { skill: 'bill', slotId: 'ilife:cookie', order: 95, title: '记账', plugin: 'dsh-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.fetch.register` 的路径 / 面板 `connection.rpc.call` 的两段参数。 */\nexport const MANAGER_RPC = {\n /** 载体基段(`connection.rpc.call` 的**第一段**参数)。传错第一段就是 404:真机上踩过一次。 */\n base: '/api',\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} 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 */\nimport { MANAGER_ACTIONS, MANAGER_RPC, 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/** 查一家:默认查新版(`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 } 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 fontSize: 13,\n lineHeight: 1.7,\n } as React.CSSProperties,\n rowHead: { fontWeight: 700, marginBottom: 2 } as React.CSSProperties,\n rowMeta: { color: 'var(--dsw-alias-label-secondary, #9a9a9a)', fontSize: 12 } as React.CSSProperties,\n reason: { color: 'var(--dsw-alias-state-error-primary, #ff6b6b)', fontSize: 12, marginTop: 4 } as React.CSSProperties,\n skill: { color: 'var(--dsw-alias-label-tertiary, #8a8a8a)', fontSize: 12, marginTop: 4 } 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 dialog: {\n width: '100%',\n maxWidth: 560,\n maxHeight: '80vh',\n overflow: 'auto',\n padding: '12px 14px',\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 marginBottom: 8,\n } as React.CSSProperties,\n dialogTitle: { fontSize: 14, fontWeight: 700 } 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/** 一行的运行时状态:还没查 / 查着 / 有结果 / 装着 / 出错。 */\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 return React.createElement(\n 'div',\n { key: target.key, style: PANEL_STYLE.row },\n React.createElement('div', { style: PANEL_STYLE.rowHead }, target.title + ' · ' + target.packageName),\n React.createElement(\n 'div',\n { style: PANEL_STYLE.rowMeta },\n '当前 ' +\n String(snapshot?.runningVersion ?? target.runningVersion ?? '未安装') +\n ' · 最新 ' +\n String(snapshot?.latestVersion ?? '未查'),\n ),\n React.createElement('div', null, busy ? '正在装,请稍候…' : (verdict?.text ?? (checking ? '检查中…' : ''))),\n verdict?.action\n ? React.createElement(\n 'button',\n {\n type: 'button',\n style: busy ? { ...PANEL_STYLE.btn, opacity: 0.55 } : { ...PANEL_STYLE.btn, marginTop: 6 },\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 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 body,\n React.createElement(\n 'div',\n { style: PANEL_STYLE.dialogNote },\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(channel, HEALTH_ENDPOINT, …)`)——总管不认任何一家、也不加\n * 任何单品的依赖;通道名从「爱生活页签槽」的账本条目带出来(各家注册时写进 options)。\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';\n\n/** 传输口(DSH 载体 `connection.rpc.call` 的形状,cookbook §6)。 */\nexport type HealthCallFace = (\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\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(row.channel, HEALTH_ENDPOINT, {}, AbortSignal.timeout(HEALTH_TIMEOUT_MS));\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 *\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 * 绿不给底色:它是「正常」,不该和要处理的那两档抢注意力。 */\nconst 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/** 档位词(灯里那几个字)用的色:红黄要**够深**才读得清,故另取一枚比灯更深的值。 */\nconst 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 /** 「体检一次」不挤在标题右端:它单独占一行、与标题同一起始边(视觉复评两轮点「孤立在右上缺层次」)。 */\n runRow: { margin: '8px 0 10px' } as React.CSSProperties,\n btn: {\n border: '1px solid ' + BORDER,\n background: 'transparent',\n color: INK,\n borderRadius: 8,\n padding: '6px 16px',\n fontSize: 13,\n fontWeight: 600,\n cursor: 'pointer',\n } as React.CSSProperties,\n lightsRow: { display: 'flex', flexWrap: 'wrap', gap: 8 } as React.CSSProperties,\n light: {\n display: 'inline-flex',\n alignItems: 'center',\n gap: 4,\n background: 'transparent',\n border: '1px solid ' + BORDER,\n borderRadius: 999,\n padding: '3px 10px',\n fontSize: 12.5,\n color: INK,\n cursor: 'pointer',\n } as React.CSSProperties,\n lightIdle: { cursor: 'default', color: INK_DIM } as React.CSSProperties,\n meta: { color: INK_DIM, fontSize: 12, lineHeight: 1.7, marginTop: 12 } 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/** 一盏灯:一家一名一档(数到几个红黄绿)。点一下把面板切到那家的页签。 */\nexport interface HealthLightRow {\n readonly id: string;\n readonly title: string;\n readonly status: HealthStatus | null;\n readonly counts: Readonly<Record<HealthStatus, number>>;\n}\n\n/** 从六份报告数出那一行的灯(每家按最严重那一档上色)。 */\nexport function lightsOf(\n tabs: readonly { readonly id: string; readonly title: string }[],\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 // 这里曾短期印过「最严重那一项的名字」当区分度,**已撤回**:实测六家常坏在同一条通用检查项上\n // (都是「数据目录」),印出来六家一模一样,是噪声不是区分度(出图复评实测逐字抄了六个「数据目录」)。\n // 「哪一项坏了」本来就由那家自己那张表回答,灯上不再重复。\n };\n });\n}\n\n/** 一家的一句话读数:「红 1 黄 2」,全绿就是「绿」。 */\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」那一截**按档上色**:每一档用它自己的字色、分隔符走淡色。\n * 为什么必须拆开:原先整截塞进一个 span、染「最严重那一档」的颜色,六家都红时「黄 3」就跟着印成红字——\n * 本页自己说「黄=还没配」,文字层却把它涂成红(出图复评五份里三份独立指到,取色同为 `#c03828`)。 */\nfunction countSegments(light: HealthLightRow): React.ReactElement[] {\n if (light.status === null) {\n return [React.createElement('span', { key: 'dash', style: { color: INK_DIM, fontWeight: 700 } }, '—')];\n }\n const parts: React.ReactElement[] = [];\n for (const status of ['red', 'yellow'] as const) {\n const count = light.counts[status];\n if (count === 0) continue;\n if (parts.length > 0) {\n parts.push(React.createElement('span', { key: status + '-sep', style: { color: INK_DIM } }, ' · '));\n }\n parts.push(\n React.createElement(\n 'span',\n { key: status, style: { color: STATUS_TEXT[status], fontWeight: 700 } },\n STATUS_LABEL[status] + ' ' + String(count),\n ),\n );\n }\n if (parts.length === 0) return [React.createElement('span', { key: 'green', style: { color: INK_DIM, fontWeight: 700 } }, '绿')];\n return parts;\n}\n\n/** 顶部那行总览:六家一盏灯 + 一个「体检一次」按钮。 */\nexport function HealthOverview(props: {\n readonly lights: readonly HealthLightRow[];\n readonly running: boolean;\n readonly error: string | null;\n readonly onRun: () => void;\n readonly onJump: (id: string) => void;\n}): React.ReactElement {\n return React.createElement(\n 'div',\n { style: HEALTH_STYLE.box, 'data-ilife-health': 'overview' },\n React.createElement(\n 'div',\n { style: HEALTH_STYLE.headRow },\n React.createElement('div', { style: HEALTH_STYLE.head }, '配置体检'),\n ),\n React.createElement(\n 'div',\n { style: HEALTH_STYLE.runRow },\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 title: '对六家各跑一次配置体检(只看不改:不建目录、不改配置)',\n },\n props.running ? '体检中…' : '体检一次',\n ),\n ),\n React.createElement(\n 'div',\n { style: HEALTH_STYLE.lightsRow, 'data-ilife-health': 'lights' },\n props.lights.map((light) =>\n React.createElement(\n 'button',\n {\n key: light.id,\n type: 'button',\n 'data-ilife-health': 'light',\n 'data-skill': light.id,\n // 档位落到按钮自身的边框与底色上:一眼扫过去先看到颜色,再读家名(票面验收:一屏看红黄)。\n style: light.status === null\n ? { ...HEALTH_STYLE.light, ...HEALTH_STYLE.lightIdle }\n : {\n ...HEALTH_STYLE.light,\n borderColor: STATUS_COLOR[light.status],\n borderWidth: 1.5,\n background: STATUS_TINT[light.status],\n },\n disabled: light.status === null,\n onClick: () => props.onJump(light.id),\n title: light.status === null\n ? '还没体检(或这一家没装)'\n : light.title + ':' + countsText(light.counts),\n },\n React.createElement('span', {\n style: {\n width: 10,\n height: 10,\n borderRadius: '50%',\n background: light.status === null ? INK_DIM : STATUS_COLOR[light.status],\n display: 'inline-block',\n },\n }),\n React.createElement('span', { style: { color: INK, fontWeight: 650 } }, light.title),\n ...countSegments(light),\n ),\n ),\n ),\n React.createElement(\n 'div',\n { style: HEALTH_STYLE.meta },\n // 三档的含义要**写在屏上**(复评五份里三份独立指出「全屏没有图例,红黄绿只能靠猜」),\n // 且三个词要**按档上色**——「定义红黄绿的那把钥匙自己不上色」是复评点名过的一处。\n props.error !== null\n ? props.error\n : [\n React.createElement('span', { key: 'r', style: { color: STATUS_TEXT.red, fontWeight: 700 } }, '红'),\n React.createElement('span', { key: 'r2' }, '=坏了 · '),\n React.createElement('span', { key: 'y', style: { color: STATUS_TEXT.yellow, fontWeight: 700 } }, '黄'),\n React.createElement('span', { key: 'y2' }, '=还没配 · '),\n React.createElement('span', { key: 'g', style: { color: STATUS_TEXT.green, fontWeight: 700 } }, '绿'),\n React.createElement('span', { key: 'g2' }, '=正常 | 只看不改:不建目录、不改配置、不自动重置'),\n // 页签那排也用圆点(`● 装上了/○ 没装`),这里把**顶部那排**的语义写清楚,\n // 免得两排同构的圆点互相抢解释权(复评两份点名)。\n React.createElement('span', { key: 'tab' }, ' · 上面那排圆点是体检灯,点一下跳到那家配置页;下面那排圆点是页签(实心=装上了)'),\n ],\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_TABS, MANAGER_VERSION, MORE_PLUGINS, PANEL_LINKS, recoFor } from './nav.js';\nimport type { ManagerTab } from './nav.js';\nimport { AbsentCard, CheckUpdateButton, UpdateResults, useUpdateRows } from './update-panel.js';\nimport type { CallFace } from './update-client.js';\nimport { CONFIG_TAB_SLOT } from './update-contract.js';\nimport { useHealthPanel } from './health-panel.js';\nimport { HealthOverview, HealthTable, lightsOf } 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/** 总管视觉(内联 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/** 爱生活面板:总设置区 + 检查更新(七家)+ 爱生活页签条(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 // 配置体检(#706):一张表六份报告,总览那行与各家那张表都从它读(同一份数据)。\n // 通道名由各家注册时写进页签槽 options,总管源码里不出现任何一家的通道名。\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 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 /** 按包名取页签标题(总览那行按账本行过,账本里没有的退到导航表的标题)。 */\n function labelForTab(id: string): string {\n const row = rows.find((r) => r.id === id);\n if (row && row.label.length > 0) return row.label;\n const tab = MANAGER_TABS.find((t) => t.plugin === id);\n return tab ? tab.title : id;\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 React.createElement('div', null, '总开关 · 开关(缺省启用,只读)'),\n React.createElement('div', null, '总管 dsh-life-pack · ' + MANAGER_VERSION),\n ),\n React.createElement(UpdateResults, { face }),\n React.createElement(HealthOverview, {\n lights: lightsOf(\n rows.map((row) => ({ id: row.id, title: labelForTab(row.id) })),\n Object.fromEntries(Object.entries(health.rows).map(([id, row]) => [id, row.report ?? undefined])),\n ),\n running: health.running,\n error: health.error,\n onRun: health.run,\n onJump: setActiveId,\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 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 },\n (present.has(tab.plugin) ? '● ' : '○ ') + labelFor(tab),\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 // #706 配置体检的通道:各家注册时写进 options,缺席即这一家没有体检出口(灯显 —)。\n channel: entry.options.channel ?? '',\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 readonly title: string;\n /** 提供该 tab 的单品插件包名(仅文档级引用,不 import)。 */\n readonly plugin: 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/** 总管版本号(与 package.json 同步,release 时一起 bump;设置页总设置区版本行唯一来源)。 */\nexport const MANAGER_VERSION = '0.2.6' as const;\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' },\n { skill: 'calorie', slotId: 'ilife:calorie', order: 75, title: '卡路里', plugin: 'dsh-calorie' },\n { skill: 'schedule', slotId: 'ilife:schedule', order: 80, title: '作息', plugin: 'dsh-schedule-ilife' },\n { skill: 'home', slotId: 'ilife:home', order: 85, title: '居家', plugin: 'dsh-home-ilife' },\n { skill: 'chef', slotId: 'ilife:chef', order: 90, title: '大厨', plugin: 'dsh-chef' },\n { skill: 'bill', slotId: 'ilife:cookie', order: 95, title: '记账', plugin: 'dsh-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.fetch.register` 的路径 / 面板 `connection.rpc.call` 的两段参数。 */\nexport const MANAGER_RPC = {\n /** 载体基段(`connection.rpc.call` 的**第一段**参数)。传错第一段就是 404:真机上踩过一次。 */\n base: '/api',\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} 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 */\nimport { MANAGER_ACTIONS, MANAGER_RPC, 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/** 查一家:默认查新版(`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(channel, HEALTH_ENDPOINT, …)`)——总管不认任何一家、也不加\n * 任何单品的依赖;通道名从「爱生活页签槽」的账本条目带出来(各家注册时写进 options)。\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';\n\n/** 传输口(DSH 载体 `connection.rpc.call` 的形状,cookbook §6)。 */\nexport type HealthCallFace = (\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\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(row.channel, HEALTH_ENDPOINT, {}, AbortSignal.timeout(HEALTH_TIMEOUT_MS));\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 *\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/** 汇总行那一句:绿要显眼(它是「正常」这件事本身),没通道的那几家不冒充绿。 */\nfunction overallText(lights: readonly HealthLightRow[]): React.ReactElement {\n const pending = lights.filter((light) => !light.hasChannel).length;\n const withReport = lights.filter((light) => light.hasChannel);\n const tail = pending > 0 ? ' · ' + String(pending) + ' 家还没体检' : '';\n if (withReport.length > 0 && withReport.every((light) => light.status === 'green')) {\n return React.createElement('span', { style: { color: STATUS_COLOR.green, fontWeight: 700 } }, '六家正常' + tail);\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 return React.createElement('span', { style: { color: INK_DIM } }, '还没体检' + 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 const hasLights = props.lights.length > 0;\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 props.error !== null ? React.createElement('span', { style: { color: STATUS_TEXT.red } }, props.error)\n : hasLights ? overallText(props.lights) : React.createElement('span', { style: { color: INK_DIM } }, '还没体检'),\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_TABS, MANAGER_VERSION, MORE_PLUGINS, PANEL_LINKS, recoFor } from './nav.js';\nimport type { ManagerTab } from './nav.js';\nimport { AbsentCard, CheckUpdateButton, UpdateResults, useUpdateRows } from './update-panel.js';\nimport type { CallFace } from './update-client.js';\nimport { CONFIG_TAB_SLOT } from './update-contract.js';\nimport { 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/** 总管视觉(内联 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/** 爱生活面板:总设置区 + 检查更新(七家)+ 爱生活页签条(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 // 配置体检(#706):一张表六份报告,顶部那行汇总与各家那张表都从它读(同一份数据)。\n // 通道名由各家注册时写进页签槽 options,总管源码里不出现任何一家的通道名。\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 React.createElement('div', null, '总开关 · 开关(缺省启用,只读)'),\n React.createElement('div', null, '总管 dsh-life-pack · ' + MANAGER_VERSION),\n ),\n React.createElement(UpdateResults, { face }),\n React.createElement(HealthSummaryLine, {\n lights,\n running: health.running,\n error: health.error,\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 // #706 配置体检的通道:各家注册时写进 options,缺席即这一家没有体检出口(灯显 —)。\n channel: entry.options.channel ?? '',\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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"}