dsh-life-pack 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.js CHANGED
@@ -132,6 +132,13 @@ window.__ModuleLoader__.load({
132
132
  ];
133
133
  //#endregion
134
134
  //#region src/update-contract.ts
135
+ /** 爱生活页签槽名(总管声明 `children`、各单品技能设置页注册进来;单段名,避开官方 `settings.*` 前缀)。
136
+ *
137
+ * 为什么住这里:这个名字**两侧都要用**——面板侧 `client.ts` 拿它注册/投影槽,
138
+ * 宿主侧(`update-env.ts` 的 `readPanelRegistered`)拿它判「已装产物里到底有没有注册代码」。
139
+ * 一处定义、两边引用(结构纪律「概念唯一」):名字改了只有这一行要动。
140
+ * 宿主判据为什么要读这个字面量、以及它的边界,见 `readPanelRegistered` 的头注。 */
141
+ const CONFIG_TAB_SLOT = "ilife.config-tab";
135
142
  /** 载体:宿主注册 `connection.fetch.register` 的路径 / 面板 `connection.rpc.call` 的两段参数。 */
136
143
  const MANAGER_RPC = {
137
144
  /** 载体基段(`connection.rpc.call` 的**第一段**参数)。传错第一段就是 404:真机上踩过一次。 */
@@ -147,7 +154,7 @@ window.__ModuleLoader__.load({
147
154
  const MANAGER_ACTIONS = {
148
155
  /** 装上缺席的目标包:入参 `{packageName, version}`,回包 `{packageName, version}`。 */
149
156
  install: "ilife-manager.install",
150
- /** 七个更新目标的表:入参 `{}`,回包 `{targets: [{key, title, packageName, phones, runningVersion, installedVersion, skill}]}`。
157
+ /** 七个更新目标的表:入参 `{}`,回包 `{targets: [{key, title, packageName, phones, runningVersion, installedVersion, panelRegistered, skill}]}`。
151
158
  *
152
159
  * 电话名为什么由宿主转交而不是面板侧写死:更新包的三个电话名只有它自己知道
153
160
  * (`update.phoneNames`,`host.ts:335`);面板侧既不能 import 更新包(浏览器产物纯度门)
@@ -170,6 +177,7 @@ window.__ModuleLoader__.load({
170
177
  "check-expired": "上次查新版的凭证过期了:重新查一次再点安装(不要重试旧编号)。",
171
178
  "update-busy": "同一个使用范围同时只装一个:等当前任务结束再点。",
172
179
  "install-failed": "安装没跑成:按下面那条手工命令在终端执行一次。",
180
+ "manager-unreachable": "总管的电话没接上(宿主半没注册或版本对不上):重启宿主后再试;仍不行看宿主日志。",
173
181
  "bad-request": "回执异常(请求形状不对):把宿主日志里的事件名报给作者。",
174
182
  internal: "回执异常(总管内部错误):把宿主日志里的事件名报给作者。"
175
183
  };
@@ -223,7 +231,7 @@ window.__ModuleLoader__.load({
223
231
  payload
224
232
  });
225
233
  } catch (error) {
226
- return failure("check-failed", { detail: String(error?.message ?? error) });
234
+ return failure("manager-unreachable", { detail: String(error?.message ?? error) });
227
235
  }
228
236
  if (!result || typeof result !== "object" || typeof result.ok !== "boolean") return failure("internal", { detail: "回执信封异常(非 ok 信封)" });
229
237
  if (result.ok) return {
@@ -307,6 +315,13 @@ window.__ModuleLoader__.load({
307
315
  function isAbsent(target) {
308
316
  return target.runningVersion === null && target.installedVersion === null;
309
317
  }
318
+ /** 一家页签此刻的状态:账本里已注册 → `connected`(面板显示它自己的设置页),否则按宿主装机读数分三态。 */
319
+ function slotStateOf(target, inLedger) {
320
+ if (inLedger) return "connected";
321
+ if (target === null || isAbsent(target)) return "absent";
322
+ if (target.panelRegistered === false) return "unregistered-product";
323
+ return "not-connected";
324
+ }
310
325
  /** 把快照翻成一行结论:还没查 / 已是最新 / 有新版 / 装不了(附人话原因)。 */
311
326
  function verdictOf(target, snapshot) {
312
327
  const absent = isAbsent(target);
@@ -563,20 +578,23 @@ window.__ModuleLoader__.load({
563
578
  const [loadError, setLoadError] = react.useState(null);
564
579
  const [checking, setChecking] = react.useState(false);
565
580
  const [open, setOpen] = react.useState(false);
566
- react.useEffect(() => {
567
- let alive = true;
568
- (async () => {
569
- const loaded = await loadTargets(getCall());
570
- if (!alive) return;
571
- if (loaded.ok) {
572
- setTargets(loaded.value.targets);
573
- setPollMs(loaded.value.pollMs);
574
- } else setLoadError(loaded.message);
575
- })();
576
- return () => {
577
- alive = false;
578
- };
581
+ const alive = react.useRef(true);
582
+ react.useEffect(() => () => {
583
+ alive.current = false;
584
+ }, []);
585
+ /** 重取七个目标的表(装机读数变了就重取:缺席卡的态是拿这张表算的,缓存住它会继续说旧话)。 */
586
+ const reload = react.useCallback(async () => {
587
+ const loaded = await loadTargets(getCall());
588
+ if (!alive.current) return;
589
+ if (loaded.ok) {
590
+ setTargets(loaded.value.targets);
591
+ setPollMs(loaded.value.pollMs);
592
+ setLoadError(null);
593
+ } else setLoadError(loaded.message);
579
594
  }, [getCall]);
595
+ react.useEffect(() => {
596
+ reload();
597
+ }, [reload]);
580
598
  const patch = react.useCallback((key, next) => {
581
599
  setRows((previous) => ({
582
600
  ...previous,
@@ -657,6 +675,7 @@ window.__ModuleLoader__.load({
657
675
  outcome: null,
658
676
  failure: refreshed
659
677
  });
678
+ await reload();
660
679
  return;
661
680
  }
662
681
  patch(target.key, {
@@ -672,6 +691,7 @@ window.__ModuleLoader__.load({
672
691
  getCall,
673
692
  patch,
674
693
  pollMs,
694
+ reload,
675
695
  rows
676
696
  ])
677
697
  };
@@ -770,15 +790,38 @@ window.__ModuleLoader__.load({
770
790
  "aria-label": "关闭"
771
791
  }, "×")), body, react.createElement("div", { style: PANEL_STYLE.dialogNote }, "装与更新都在宿主后台跑:关掉这块不影响正在进行的安装,再点「检查更新」可以看到最新状态。"))) : null);
772
792
  }
773
- /** 缺席卡:一句人话 +「装上」按钮(复制命令降为辅助展示);总管缺席时不显示此卡(面板也看不到)。 */
793
+ /** 缺席卡:**按两处独立事实分三态**,每态只给能改变状态的动作。
794
+ *
795
+ * - `absent`(磁盘上没有这个包)→「未安装」+「装上」按钮(本票原有的能力);
796
+ * - `unregistered-product`(装着,但产物里没有页签槽注册代码)→ 说实情、**不给按钮**:
797
+ * 重装改不了产物里有没有注册代码(真机实测:作息/居家/大厨/记账 的已发布 0.2.0
798
+ * 产物里根本没有注册代码,见票 #723);给可复制命令只是参考;
799
+ * - `not-connected`(装着、产物也有注册代码,设置页却没送到浏览器)→ 重启宿主;仍不行看日志;
800
+ * - `connected` 不归本卡管:那时面板显示的是那家自己的设置页。
801
+ *
802
+ * 原实现只看页签槽 ledger 就断言「未安装」——那对后两种情形是说假话,且点「装上」
803
+ * 会走进更新包的流程(它已经装着 ⇒ 没有新版 ⇒ 没有凭证)并报出误导的「查新版失败」。
804
+ */
774
805
  function AbsentCard(props) {
775
806
  const target = props.target;
776
807
  const row = target ? props.face.rows[target.key] : null;
777
808
  const busy = row?.phase === "installing";
778
809
  const manual = (target ? manualForDisplay(target, row?.failure?.manual ?? row?.outcome?.manual ?? null) : null) ?? props.fallbackCommand;
810
+ const installed = target ? target.installedVersion ?? target.runningVersion : null;
811
+ const state = slotStateOf(target, props.inLedger);
779
812
  let reason = row?.failure?.message ?? null;
780
813
  if (target && reason === null && row?.outcome && row.outcome.snapshot.blockedReason !== null) reason = verdictOf(target, row.outcome.snapshot).text;
781
- return react.createElement("div", { style: PANEL_STYLE.reco }, react.createElement("div", null, target ? "未安装[" + target.packageName + "],装上后这个页签就能用了。" : "这个页签对应的插件还没装。"), target ? react.createElement("button", {
814
+ let headline;
815
+ switch (state) {
816
+ case "absent":
817
+ headline = target ? "未安装[" + target.packageName + "],装上后这个页签就能用了。" : "这个页签对应的插件还没装。";
818
+ break;
819
+ case "unregistered-product":
820
+ headline = target.packageName + " " + String(installed) + " 装着,但它这一版的产物里没有爱生活页签的注册代码:得等它发一个带注册代码的新版才对得上。重启与重装都不会变(重装只是把同一版再装一次)。";
821
+ break;
822
+ default: headline = target.packageName + " " + String(installed) + " 装着、产物里也有注册代码,但它的设置页没送到浏览器:重启宿主后再看这个页签;仍然这样就去宿主日志里找它的报错。";
823
+ }
824
+ return react.createElement("div", { style: PANEL_STYLE.reco }, react.createElement("div", null, headline), state === "absent" ? react.createElement("button", {
782
825
  type: "button",
783
826
  style: busy ? {
784
827
  ...PANEL_STYLE.btn,
@@ -789,7 +832,9 @@ window.__ModuleLoader__.load({
789
832
  marginTop: 8
790
833
  },
791
834
  disabled: busy,
792
- onClick: () => props.face.act(target)
835
+ onClick: () => {
836
+ if (target) props.face.act(target);
837
+ }
793
838
  }, busy ? "正在装,请稍候…" : "装上") : null, reason ? react.createElement("div", { style: PANEL_STYLE.reason }, reason) : null, react.createElement("code", { style: PANEL_STYLE.cmd }, manual));
794
839
  }
795
840
  //#endregion
@@ -978,8 +1023,10 @@ window.__ModuleLoader__.load({
978
1023
  green: "var(--dsw-alias-state-success-primary, #4ec9a0)"
979
1024
  };
980
1025
  /** 整行底色的浅一档:饱和降一档,免得红黄行「刺眼」压过正文(评审第二轮的扣分点)。 */
1026
+ /** 红黄两档整行上底的底色。红灯要**明显重于**黄灯:复评实测原先两个底色亮度几乎一样
1027
+ * (红 `#FBF3F3` 对黄 `#FCF3E5`,只差蓝通道),最严重那一档反而最淡——红色提到 0.14。 */
981
1028
  const STATUS_TINT = {
982
- red: "rgba(192, 57, 43, 0.06)",
1029
+ red: "rgba(192, 57, 43, 0.14)",
983
1030
  yellow: "rgba(224, 138, 0, 0.10)",
984
1031
  green: "transparent"
985
1032
  };
@@ -994,6 +1041,12 @@ window.__ModuleLoader__.load({
994
1041
  yellow: "黄",
995
1042
  green: "绿"
996
1043
  };
1044
+ /** 档位的**形状**标记:颜色之外的第二条读数(灰度截图、色弱视角下也分得开谁要处理)。 */
1045
+ const STATUS_MARK = {
1046
+ red: "✕",
1047
+ yellow: "!",
1048
+ green: "✓"
1049
+ };
997
1050
  /** 面板上的文字色与分隔线(与 `client.ts` 同一套别名)。 */
998
1051
  const INK = "var(--dsw-alias-label-primary, inherit)";
999
1052
  const INK_DIM = "var(--dsw-alias-label-secondary, #9a9a9a)";
@@ -1024,8 +1077,9 @@ window.__ModuleLoader__.load({
1024
1077
  background: "transparent",
1025
1078
  color: INK,
1026
1079
  borderRadius: 8,
1027
- padding: "4px 12px",
1080
+ padding: "6px 16px",
1028
1081
  fontSize: 13,
1082
+ fontWeight: 600,
1029
1083
  cursor: "pointer"
1030
1084
  },
1031
1085
  lightsRow: {
@@ -1072,15 +1126,73 @@ window.__ModuleLoader__.load({
1072
1126
  const trimmed = action.trim();
1073
1127
  return trimmed.startsWith("去哪修:") ? trimmed.slice(4).trim() : trimmed;
1074
1128
  }
1129
+ /** 报文里能扫出来的每一段路径(盘符绝对路径;非 Windows 形状的退回 `/` 打头那一支)。 */
1130
+ function rawPathsIn(text) {
1131
+ return text.match(/[A-Za-z]:\/[^\s()「」,。;]+/g) ?? text.match(/\/[^\s()「」,。;]+/g) ?? [];
1132
+ }
1133
+ /** 一段落点的**根**(盘符):`C:/a/b` → `C:/`。用来把「同一个盘、但根下不同枝」的落点分开算,
1134
+ * 免得一个 D 盘上的旁证(比如「配了但目录不在:D:/media」)把 C 盘那几条的缩写全拖没。 */
1135
+ function rootOf(path) {
1136
+ const drive = /^[A-Za-z]:\//.exec(path);
1137
+ return drive === null ? "/" : drive[0];
1138
+ }
1139
+ /** 一段落点的公共目录前缀(按 `/` 切、整段比):`C:/a/b/c` + `C:/a/b/d` → `C:/a/b`。 */
1140
+ function commonDirOf(paths) {
1141
+ if (paths.length < 2) return "";
1142
+ const split = paths.map((path) => path.replace(/[.,。;]+$/, "").split("/"));
1143
+ const first = split[0];
1144
+ let size = 0;
1145
+ while (size < first.length && split.every((parts) => parts[size] === first[size])) size += 1;
1146
+ return size < 1 ? "" : first.slice(0, size).join("/");
1147
+ }
1148
+ /** 一份报文里**按根分开**的落点前缀(长的在前):只给「同一个根下至少两条落点」的根算前缀——
1149
+ * 这个根下只有一条落点时不缩(没有第二条可比,缩出来反而是瞎猜)。
1150
+ * 为什么要分根:一家报里常有别的盘的旁证(备忘录用 D 盘上那份媒体目录),只算一个总前缀,
1151
+ * 一条旁证就能把整张表的缩写全拖没(#706 出图复评:备忘录那张表整片没缩、记账那张缩了)。 */
1152
+ function dirPrefixesOf(report) {
1153
+ return dirPrefixesIn(pathsOf(report).join(" "));
1154
+ }
1155
+ /** `dirPrefixesOf` 的正文版:一段文字里(报文正文 + 表头两条混在一起)按根分组取公共目录前缀。 */
1156
+ function dirPrefixesIn(text) {
1157
+ const paths = rawPathsIn(text);
1158
+ const roots = [...new Set(paths.map(rootOf))];
1159
+ const prefixes = [];
1160
+ for (const root of roots) {
1161
+ const prefix = commonDirOf(paths.filter((path) => rootOf(path) === root));
1162
+ if (prefix !== "") prefixes.push(prefix);
1163
+ }
1164
+ return prefixes.sort((a, b) => b.length - a.length);
1165
+ }
1166
+ /** 一家里所有会印出来的路径。 */
1167
+ function pathsOf(report) {
1168
+ const out = [report.configPath, report.dataDir];
1169
+ for (const item of report.items) out.push(item.message);
1170
+ return out;
1171
+ }
1172
+ /** 把报文里那一长串路径按公共前缀缩短(只动打印,不动事实)。
1173
+ * 只按**扫得出来的那一段段落点**去缩,不做全串替换:既不误伤长路径,也不会把一段路劈成半截。 */
1174
+ function shortenPathsIn(text, prefixes) {
1175
+ const candidates = rawPathsIn(text);
1176
+ if (candidates.length === 0) return text;
1177
+ const longestFirst = [...new Set(candidates.map((path) => path.replace(/[.,。;]+$/, "")))].sort((a, b) => b.length - a.length);
1178
+ let out = text;
1179
+ for (const path of longestFirst) {
1180
+ const hit = prefixes.find((prefix) => path.startsWith(prefix + "/"));
1181
+ if (hit === void 0) continue;
1182
+ out = out.split(hit + "/").join("…/");
1183
+ }
1184
+ return out;
1185
+ }
1075
1186
  /** 从六份报告数出那一行的灯(每家按最严重那一档上色)。 */
1076
1187
  function lightsOf(tabs, reports) {
1077
1188
  return tabs.map((tab) => {
1078
1189
  const report = reports[tab.id];
1190
+ const items = report?.items ?? [];
1079
1191
  return {
1080
1192
  id: tab.id,
1081
1193
  title: tab.title,
1082
- status: report ? worstStatus(report.items) : null,
1083
- counts: countByStatus(report?.items ?? [])
1194
+ status: report ? worstStatus(items) : null,
1195
+ counts: countByStatus(items)
1084
1196
  };
1085
1197
  });
1086
1198
  }
@@ -1092,6 +1204,42 @@ window.__ModuleLoader__.load({
1092
1204
  if (parts.length === 0) return "绿";
1093
1205
  return parts.join(" · ");
1094
1206
  }
1207
+ /** 「红 2 · 黄 3」那一截**按档上色**:每一档用它自己的字色、分隔符走淡色。
1208
+ * 为什么必须拆开:原先整截塞进一个 span、染「最严重那一档」的颜色,六家都红时「黄 3」就跟着印成红字——
1209
+ * 本页自己说「黄=还没配」,文字层却把它涂成红(出图复评五份里三份独立指到,取色同为 `#c03828`)。 */
1210
+ function countSegments(light) {
1211
+ if (light.status === null) return [react.createElement("span", {
1212
+ key: "dash",
1213
+ style: {
1214
+ color: INK_DIM,
1215
+ fontWeight: 700
1216
+ }
1217
+ }, "—")];
1218
+ const parts = [];
1219
+ for (const status of ["red", "yellow"]) {
1220
+ const count = light.counts[status];
1221
+ if (count === 0) continue;
1222
+ if (parts.length > 0) parts.push(react.createElement("span", {
1223
+ key: status + "-sep",
1224
+ style: { color: INK_DIM }
1225
+ }, " · "));
1226
+ parts.push(react.createElement("span", {
1227
+ key: status,
1228
+ style: {
1229
+ color: STATUS_TEXT[status],
1230
+ fontWeight: 700
1231
+ }
1232
+ }, STATUS_LABEL[status] + " " + String(count)));
1233
+ }
1234
+ if (parts.length === 0) return [react.createElement("span", {
1235
+ key: "green",
1236
+ style: {
1237
+ color: INK_DIM,
1238
+ fontWeight: 700
1239
+ }
1240
+ }, "绿")];
1241
+ return parts;
1242
+ }
1095
1243
  /** 顶部那行总览:六家一盏灯 + 一个「体检一次」按钮。 */
1096
1244
  function HealthOverview(props) {
1097
1245
  return react.createElement("div", {
@@ -1136,10 +1284,33 @@ window.__ModuleLoader__.load({
1136
1284
  } }), react.createElement("span", { style: {
1137
1285
  color: INK,
1138
1286
  fontWeight: 650
1139
- } }, light.title), react.createElement("span", { style: {
1140
- color: light.status === null ? INK_DIM : STATUS_TEXT[light.status],
1141
- fontWeight: 700
1142
- } }, light.status === null ? "—" : countsText(light.counts))))), react.createElement("div", { style: HEALTH_STYLE.meta }, props.error !== null ? props.error : "只看不改 · 灯=那一家最严重的一档 · 点一下跳到那家配置页"));
1287
+ } }, light.title), ...countSegments(light)))), react.createElement("div", { style: HEALTH_STYLE.meta }, props.error !== null ? props.error : [
1288
+ react.createElement("span", {
1289
+ key: "r",
1290
+ style: {
1291
+ color: STATUS_TEXT.red,
1292
+ fontWeight: 700
1293
+ }
1294
+ }, "红"),
1295
+ react.createElement("span", { key: "r2" }, "=坏了 · "),
1296
+ react.createElement("span", {
1297
+ key: "y",
1298
+ style: {
1299
+ color: STATUS_TEXT.yellow,
1300
+ fontWeight: 700
1301
+ }
1302
+ }, "黄"),
1303
+ react.createElement("span", { key: "y2" }, "=还没配 · "),
1304
+ react.createElement("span", {
1305
+ key: "g",
1306
+ style: {
1307
+ color: STATUS_TEXT.green,
1308
+ fontWeight: 700
1309
+ }
1310
+ }, "绿"),
1311
+ react.createElement("span", { key: "g2" }, "=正常 | 只看不改:不建目录、不改配置、不自动重置"),
1312
+ react.createElement("span", { key: "tab" }, " · 上面那排圆点是体检灯,点一下跳到那家配置页;下面那排圆点是页签(实心=装上了)")
1313
+ ]));
1143
1314
  }
1144
1315
  /** 正正常常的那些:绿条只留「名字」,一句话与「去哪修」都不印(票面验收第一条:正常的收成一行)。 */
1145
1316
  function isAttentionItem(item) {
@@ -1149,13 +1320,18 @@ window.__ModuleLoader__.load({
1149
1320
  * **正常的**(绿)收成一行并列的小字,点得到、扫得完(票面验收第一条:正常的收成一行,有问题的才展开)。 */
1150
1321
  function HealthTable(props) {
1151
1322
  const { report } = props;
1323
+ const prefixes = report ? dirPrefixesOf(report) : [];
1152
1324
  return react.createElement("div", {
1153
1325
  style: HEALTH_STYLE.box,
1154
1326
  "data-ilife-health": "table"
1155
1327
  }, react.createElement("div", { style: HEALTH_STYLE.headRow }, react.createElement("div", { style: HEALTH_STYLE.head }, props.title + " · 体检"), report ? react.createElement("div", { style: {
1156
1328
  color: INK_DIM,
1157
1329
  fontSize: 12
1158
- } }, countsText(countByStatus(report.items))) : null), props.phase === "idle" ? react.createElement("div", { style: HEALTH_STYLE.meta }, "还没体检:点上面那个「体检一次」。") : null, props.phase === "running" ? react.createElement("div", { style: HEALTH_STYLE.meta }, "正在体检…") : null, props.error !== null ? react.createElement("div", { style: HEALTH_STYLE.error }, props.error) : null, report ? react.createElement("div", null, react.createElement("div", { style: HEALTH_STYLE.meta }, "配置文件 " + report.configPath + " · 数据目录 " + report.dataDir), report.items.filter(isAttentionItem).map((item) => react.createElement("div", {
1330
+ } }, countsText(countByStatus(report.items))) : null), props.phase === "idle" ? react.createElement("div", { style: HEALTH_STYLE.meta }, "还没体检:点上面那个「体检一次」。") : null, props.phase === "running" ? react.createElement("div", { style: HEALTH_STYLE.meta }, "正在体检…") : null, props.error !== null ? react.createElement("div", { style: HEALTH_STYLE.error }, props.error) : null, report ? react.createElement("div", null, report.dataDir !== "" ? react.createElement("div", { style: {
1331
+ color: INK_DIM,
1332
+ fontSize: 11.5,
1333
+ marginTop: 2
1334
+ } }, "落点根目录:" + report.dataDir + (report.configPath !== "" ? "(配置文件 " + report.configPath + ")" : "")) : null, report.items.filter(isAttentionItem).map((item) => react.createElement("div", {
1159
1335
  key: item.id,
1160
1336
  "data-ilife-health": "item",
1161
1337
  "data-status": item.status,
@@ -1173,19 +1349,24 @@ window.__ModuleLoader__.load({
1173
1349
  flexWrap: "wrap",
1174
1350
  alignItems: "center",
1175
1351
  gap: 6
1176
- } }, react.createElement("span", { style: {
1177
- width: 8,
1178
- height: 8,
1179
- borderRadius: "50%",
1180
- display: "inline-block",
1181
- background: STATUS_COLOR[item.status]
1182
- } }), react.createElement("span", { style: {
1352
+ } }, react.createElement("span", {
1353
+ style: {
1354
+ color: STATUS_TEXT[item.status],
1355
+ fontWeight: 700,
1356
+ fontSize: 13,
1357
+ lineHeight: 1
1358
+ },
1359
+ "aria-hidden": "true"
1360
+ }, STATUS_MARK[item.status]), react.createElement("span", { style: {
1183
1361
  fontWeight: 650,
1184
1362
  color: INK
1185
1363
  } }, item.title), react.createElement("span", { style: {
1186
1364
  color: STATUS_TEXT[item.status],
1187
1365
  fontWeight: 700
1188
- } }, STATUS_LABEL[item.status]), item.source ? react.createElement("span", { style: { color: INK_DIM } }, "· 来自" + item.source) : null), react.createElement("div", { style: { color: INK } }, item.message), item.action.length > 0 ? react.createElement("div", { style: { color: INK_DIM } }, "去哪修:" + actionText(item.action)) : null)), report.items.some((item) => !isAttentionItem(item)) ? react.createElement("div", {
1366
+ } }, STATUS_LABEL[item.status]), item.source && item.source !== "默认值" ? react.createElement("span", { style: { color: INK_DIM } }, "· 来自" + item.source) : null), react.createElement("div", {
1367
+ style: { color: INK },
1368
+ title: item.message
1369
+ }, shortenPathsIn(item.message, prefixes)), item.action.length > 0 ? react.createElement("div", { style: { color: INK_DIM } }, "去哪修:" + actionText(item.action)) : null)), report.items.some((item) => !isAttentionItem(item)) ? react.createElement("div", {
1189
1370
  "data-ilife-health": "ok",
1190
1371
  style: {
1191
1372
  display: "flex",
@@ -1193,16 +1374,27 @@ window.__ModuleLoader__.load({
1193
1374
  alignItems: "center",
1194
1375
  gap: "2px 10px",
1195
1376
  marginTop: 8,
1196
- paddingTop: 6,
1197
- borderTop: "1px solid " + BORDER,
1377
+ padding: "6px 8px",
1378
+ borderLeft: "3px solid " + STATUS_COLOR.green,
1379
+ borderRadius: 6,
1198
1380
  fontSize: 12,
1199
1381
  lineHeight: 1.6,
1200
1382
  color: INK_DIM
1201
1383
  }
1202
- }, react.createElement("span", { style: {
1384
+ }, react.createElement("span", {
1385
+ style: {
1386
+ width: 8,
1387
+ height: 8,
1388
+ borderRadius: "50%",
1389
+ display: "inline-block",
1390
+ background: STATUS_COLOR.green,
1391
+ flex: "0 0 auto"
1392
+ },
1393
+ "aria-hidden": "true"
1394
+ }), react.createElement("span", { style: {
1203
1395
  fontWeight: 650,
1204
1396
  color: INK
1205
- } }, "正常 " + String(countByStatus(report.items).green) + " 条"), report.items.filter((item) => !isAttentionItem(item)).map((item) => react.createElement("span", {
1397
+ } }, "正常 " + String(countByStatus(report.items).green) + " 条"), react.createElement("span", { style: { color: INK_DIM } }, "(下列各项都正常工作)"), report.items.filter((item) => !isAttentionItem(item)).map((item) => react.createElement("span", {
1206
1398
  key: item.id,
1207
1399
  "data-ilife-health": "ok-item"
1208
1400
  }, "· " + item.title))) : null) : null);
@@ -1228,8 +1420,6 @@ window.__ModuleLoader__.load({
1228
1420
  * 文案与网址都取自 nav.ts 的两张静态表,本文件只画不算。
1229
1421
  */
1230
1422
  const inject = ["slots", "connection"];
1231
- /** 爱生活页签槽(总管声明的 children,技能设置页注册进来;单段名,避开官方 settings.* 前缀)。 */
1232
- const CONFIG_TAB_SLOT = "ilife.config-tab";
1233
1423
  /** 总管视觉(内联 style;颜色走 DSH 主题别名,深浅主题自适应,写死值只做回退;与技能面板同语言)。 */
1234
1424
  const S = {
1235
1425
  headRow: {
@@ -1463,7 +1653,7 @@ window.__ModuleLoader__.load({
1463
1653
  setActiveId(next.plugin);
1464
1654
  tabRefs.current[nextIndex]?.focus();
1465
1655
  }
1466
- return react.createElement("div", null, react.createElement("div", { style: S.headRow }, react.createElement("div", { style: S.head }, "爱生活"), react.createElement("div", { style: S.headActions }, react.createElement(CheckUpdateButton, { face }), react.createElement(PanelActions, null))), react.createElement("div", { style: S.meta }, react.createElement("div", null, "总开关 · 开关(缺省启用,只读)"), react.createElement("div", null, "总管 dsh-life-pack · 0.2.4")), react.createElement(UpdateResults, { face }), react.createElement(HealthOverview, {
1656
+ return react.createElement("div", null, react.createElement("div", { style: S.headRow }, react.createElement("div", { style: S.head }, "爱生活"), react.createElement("div", { style: S.headActions }, react.createElement(CheckUpdateButton, { face }), react.createElement(PanelActions, null))), react.createElement("div", { style: S.meta }, react.createElement("div", null, "总开关 · 开关(缺省启用,只读)"), react.createElement("div", null, "总管 dsh-life-pack · 0.2.6")), react.createElement(UpdateResults, { face }), react.createElement(HealthOverview, {
1467
1657
  lights: lightsOf(rows.map((row) => ({
1468
1658
  id: row.id,
1469
1659
  title: labelForTab(row.id)
@@ -1516,6 +1706,7 @@ window.__ModuleLoader__.load({
1516
1706
  })) : react.createElement(AbsentCard, {
1517
1707
  target: targetForTab(tab),
1518
1708
  fallbackCommand: recoFor(tab).installCmd,
1709
+ inLedger: present.has(tab.plugin),
1519
1710
  face
1520
1711
  }));
1521
1712
  }), react.createElement(MorePluginsCard, null));