dsh-life-pack 0.2.3 → 0.2.5

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
@@ -170,6 +170,7 @@ window.__ModuleLoader__.load({
170
170
  "check-expired": "上次查新版的凭证过期了:重新查一次再点安装(不要重试旧编号)。",
171
171
  "update-busy": "同一个使用范围同时只装一个:等当前任务结束再点。",
172
172
  "install-failed": "安装没跑成:按下面那条手工命令在终端执行一次。",
173
+ "manager-unreachable": "总管的电话没接上(宿主半没注册或版本对不上):重启宿主后再试;仍不行看宿主日志。",
173
174
  "bad-request": "回执异常(请求形状不对):把宿主日志里的事件名报给作者。",
174
175
  internal: "回执异常(总管内部错误):把宿主日志里的事件名报给作者。"
175
176
  };
@@ -223,7 +224,7 @@ window.__ModuleLoader__.load({
223
224
  payload
224
225
  });
225
226
  } catch (error) {
226
- return failure("check-failed", { detail: String(error?.message ?? error) });
227
+ return failure("manager-unreachable", { detail: String(error?.message ?? error) });
227
228
  }
228
229
  if (!result || typeof result !== "object" || typeof result.ok !== "boolean") return failure("internal", { detail: "回执信封异常(非 ok 信封)" });
229
230
  if (result.ok) return {
@@ -499,6 +500,55 @@ window.__ModuleLoader__.load({
499
500
  color: "var(--dsw-alias-label-tertiary, #8a8a8a)",
500
501
  fontSize: 12,
501
502
  marginTop: 4
503
+ },
504
+ overlay: {
505
+ position: "fixed",
506
+ inset: 0,
507
+ zIndex: 40,
508
+ display: "flex",
509
+ alignItems: "center",
510
+ justifyContent: "center",
511
+ padding: 16,
512
+ background: "rgba(0,0,0,.45)"
513
+ },
514
+ dialog: {
515
+ width: "100%",
516
+ maxWidth: 560,
517
+ maxHeight: "80vh",
518
+ overflow: "auto",
519
+ padding: "12px 14px",
520
+ borderRadius: 12,
521
+ border: "1px solid var(--dsw-alias-border, rgba(128,128,128,.4))",
522
+ background: "var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #222))",
523
+ color: "var(--dsw-alias-label-primary, inherit)",
524
+ boxShadow: "0 12px 32px rgba(0,0,0,.35)"
525
+ },
526
+ dialogHead: {
527
+ display: "flex",
528
+ alignItems: "center",
529
+ justifyContent: "space-between",
530
+ gap: 8,
531
+ marginBottom: 8
532
+ },
533
+ dialogTitle: {
534
+ fontSize: 14,
535
+ fontWeight: 700
536
+ },
537
+ close: {
538
+ border: "1px solid var(--dsw-alias-border, rgba(128,128,128,.35))",
539
+ background: "transparent",
540
+ color: "var(--dsw-alias-label-primary, inherit)",
541
+ borderRadius: 8,
542
+ width: 26,
543
+ height: 26,
544
+ lineHeight: 1,
545
+ fontSize: 14,
546
+ cursor: "pointer"
547
+ },
548
+ dialogNote: {
549
+ color: "var(--dsw-alias-label-tertiary, #8a8a8a)",
550
+ fontSize: 12,
551
+ marginTop: 4
502
552
  }
503
553
  };
504
554
  const IDLE$1 = {
@@ -513,6 +563,7 @@ window.__ModuleLoader__.load({
513
563
  const [pollMs, setPollMs] = react.useState(1e3);
514
564
  const [loadError, setLoadError] = react.useState(null);
515
565
  const [checking, setChecking] = react.useState(false);
566
+ const [open, setOpen] = react.useState(false);
516
567
  react.useEffect(() => {
517
568
  let alive = true;
518
569
  (async () => {
@@ -539,8 +590,11 @@ window.__ModuleLoader__.load({
539
590
  pollMs,
540
591
  loadError,
541
592
  checking,
593
+ open,
594
+ close: () => setOpen(false),
542
595
  checkAll: react.useCallback(() => {
543
596
  setChecking(true);
597
+ setOpen(true);
544
598
  const list = targets;
545
599
  for (const target of list) patch(target.key, {
546
600
  phase: "checking",
@@ -651,19 +705,31 @@ window.__ModuleLoader__.load({
651
705
  if (lines.length === 0) return null;
652
706
  return react.createElement("div", { style: PANEL_STYLE.banner }, lines.map((line, index) => react.createElement("div", { key: String(index) }, line)));
653
707
  }
654
- /** 七家结果行:一家一行(版本行两行、结论一句、按钮一个、装不了给原因与手工命令)。 */
708
+ /** 七家结果:装不了的与待重启的常驻(横幅),详细结果放**居中浮层**(点「检查更新」开)。
709
+ *
710
+ * 为什么是浮层:内联展开会把面板本体撑长、且它自身没有关闭控件(真机第一次做成内联后用户反馈
711
+ * 「打开就无法收回了」)。参照实现也是浮层:`dsh-mattpocock-skills-deck/src/client/views/SettingsPage.js`
712
+ * 的 `upd.dialog`。关法三种:右上角 ×、点遮罩、Esc(Esc 靠浮层自己拿焦点后收 keydown,
713
+ * 不摸 window——客户端纪律禁直写 DOM 全局)。
714
+ * 手工命令只在「装不了」或「装失败」的行显示:正常用户用不到,摊在每行上既吵又误导。
715
+ */
655
716
  function UpdateResults(props) {
656
- const { targets, rows, loadError, checking } = props.face;
717
+ const { targets, rows, loadError, checking, open, close } = props.face;
718
+ const dialogRef = react.useRef(null);
719
+ react.useEffect(() => {
720
+ if (open) dialogRef.current?.focus();
721
+ }, [open]);
657
722
  const shown = targets.filter((target) => rows[target.key] && rows[target.key].phase !== "idle");
658
723
  if (loadError) return react.createElement("div", { style: PANEL_STYLE.reason }, "更新能力没接上:" + loadError);
659
724
  if (targets.length === 0) return null;
660
- if (shown.length === 0) return null;
661
- return react.createElement("div", null, react.createElement(RestartBanners, { face: props.face }), shown.map((target) => {
725
+ if (shown.length === 0 && !open) return null;
726
+ const body = shown.map((target) => {
662
727
  const row = rows[target.key];
663
728
  const snapshot = row.outcome?.snapshot ?? null;
664
729
  const verdict = snapshot ? verdictOf(target, snapshot) : null;
665
730
  const manual = manualForDisplay(target, row.failure?.manual ?? row.outcome?.manual ?? null);
666
731
  const busy = row.phase === "installing";
732
+ const showManual = manual !== null && (row.phase === "failed" || verdict?.kind === "blocked");
667
733
  return react.createElement("div", {
668
734
  key: target.key,
669
735
  style: PANEL_STYLE.row
@@ -681,18 +747,50 @@ window.__ModuleLoader__.load({
681
747
  }, verdict.action === "install" ? "装上" : "装上更新") : null, row.failure ? react.createElement("div", { style: PANEL_STYLE.reason }, "装不上:" + row.failure.message) : null, versionLines(target, snapshot).map((line, index) => react.createElement("div", {
682
748
  key: String(index),
683
749
  style: PANEL_STYLE.skill
684
- }, line)), manual ? react.createElement("code", { style: PANEL_STYLE.cmd }, manual) : null);
685
- }));
750
+ }, line)), showManual ? react.createElement("code", { style: PANEL_STYLE.cmd }, manual) : null);
751
+ });
752
+ return react.createElement("div", null, react.createElement(RestartBanners, { face: props.face }), open ? react.createElement("div", {
753
+ style: PANEL_STYLE.overlay,
754
+ role: "presentation",
755
+ onClick: () => close()
756
+ }, react.createElement("div", {
757
+ ref: dialogRef,
758
+ tabIndex: -1,
759
+ role: "dialog",
760
+ "aria-label": "检查更新结果",
761
+ style: PANEL_STYLE.dialog,
762
+ onClick: (event) => event.stopPropagation(),
763
+ onKeyDown: (event) => {
764
+ if (event.key === "Escape") close();
765
+ }
766
+ }, react.createElement("div", { style: PANEL_STYLE.dialogHead }, react.createElement("div", { style: PANEL_STYLE.dialogTitle }, "检查更新(七家)"), react.createElement("button", {
767
+ type: "button",
768
+ style: PANEL_STYLE.close,
769
+ onClick: () => close(),
770
+ title: "关闭",
771
+ "aria-label": "关闭"
772
+ }, "×")), body, react.createElement("div", { style: PANEL_STYLE.dialogNote }, "装与更新都在宿主后台跑:关掉这块不影响正在进行的安装,再点「检查更新」可以看到最新状态。"))) : null);
686
773
  }
687
- /** 缺席卡:一句人话 +「装上」按钮(复制命令降为辅助展示);总管缺席时不显示此卡(面板也看不到)。 */
774
+ /** 缺席卡:**按宿主的装机读数分两态**,不让层与层的结论互相顶替。
775
+ *
776
+ * - 宿主说磁盘上没有这个包 →「未安装」+「装上」按钮(这是本票原有的能力);
777
+ * - 宿主说装着(`installedVersion` 非空)却没进页签槽 → 说实情、**不给「装上」**:
778
+ * 重装改不了产物里有没有注册代码这件事(真机实测:作息/居家/大厨/记账 的已发布 0.2.0
779
+ * 产物里根本没有页签槽注册,见票 #723)。给出可复制命令仅作参考。
780
+ *
781
+ * 原实现只看页签槽 ledger 就断言「未安装」——那对上面第二种情形是说假话,且点「装上」
782
+ * 会走进更新包的流程(它已经装着 ⇒ 没有新版 ⇒ 没有凭证)并报出误导的「查新版失败」。
783
+ */
688
784
  function AbsentCard(props) {
689
785
  const target = props.target;
690
786
  const row = target ? props.face.rows[target.key] : null;
691
787
  const busy = row?.phase === "installing";
692
788
  const manual = (target ? manualForDisplay(target, row?.failure?.manual ?? row?.outcome?.manual ?? null) : null) ?? props.fallbackCommand;
789
+ const installed = target ? target.installedVersion ?? target.runningVersion : null;
693
790
  let reason = row?.failure?.message ?? null;
694
791
  if (target && reason === null && row?.outcome && row.outcome.snapshot.blockedReason !== null) reason = verdictOf(target, row.outcome.snapshot).text;
695
- return react.createElement("div", { style: PANEL_STYLE.reco }, react.createElement("div", null, target ? "未安装[" + target.packageName + "],装上后这个页签就能用了。" : "这个页签对应的插件还没装。"), target ? react.createElement("button", {
792
+ const headline = !target ? "这个页签对应的插件还没装。" : installed === null ? "未安装[" + target.packageName + "],装上后这个页签就能用了。" : "插件已装(" + installed + "),但它这一版没把设置页接上爱生活面板:要等它发新版才对得上(重启与重装都不会变)。";
793
+ return react.createElement("div", { style: PANEL_STYLE.reco }, react.createElement("div", null, headline), target && installed === null ? react.createElement("button", {
696
794
  type: "button",
697
795
  style: busy ? {
698
796
  ...PANEL_STYLE.btn,
@@ -893,8 +991,8 @@ window.__ModuleLoader__.load({
893
991
  };
894
992
  /** 整行底色的浅一档:饱和降一档,免得红黄行「刺眼」压过正文(评审第二轮的扣分点)。 */
895
993
  const STATUS_TINT = {
896
- red: "rgba(192, 57, 43, 0.08)",
897
- yellow: "rgba(224, 138, 0, 0.14)",
994
+ red: "rgba(192, 57, 43, 0.06)",
995
+ yellow: "rgba(224, 138, 0, 0.10)",
898
996
  green: "transparent"
899
997
  };
900
998
  /** 档位词(灯里那几个字)用的色:红黄要**够深**才读得清,故另取一枚比灯更深的值。 */
@@ -908,6 +1006,12 @@ window.__ModuleLoader__.load({
908
1006
  yellow: "黄",
909
1007
  green: "绿"
910
1008
  };
1009
+ /** 档位的**形状**标记:颜色之外的第二条读数(灰度截图、色弱视角下也分得开谁要处理)。 */
1010
+ const STATUS_MARK = {
1011
+ red: "✕",
1012
+ yellow: "!",
1013
+ green: "✓"
1014
+ };
911
1015
  /** 面板上的文字色与分隔线(与 `client.ts` 同一套别名)。 */
912
1016
  const INK = "var(--dsw-alias-label-primary, inherit)";
913
1017
  const INK_DIM = "var(--dsw-alias-label-secondary, #9a9a9a)";
@@ -924,21 +1028,23 @@ window.__ModuleLoader__.load({
924
1028
  flexWrap: "wrap",
925
1029
  alignItems: "center",
926
1030
  gap: 8,
927
- marginBottom: 6
1031
+ marginBottom: 4
928
1032
  },
929
1033
  head: {
930
1034
  fontSize: 13,
931
1035
  fontWeight: 700,
932
1036
  color: INK
933
1037
  },
1038
+ /** 「体检一次」不挤在标题右端:它单独占一行、与标题同一起始边(视觉复评两轮点「孤立在右上缺层次」)。 */
1039
+ runRow: { margin: "8px 0 10px" },
934
1040
  btn: {
935
- marginLeft: "auto",
936
1041
  border: "1px solid " + BORDER,
937
1042
  background: "transparent",
938
1043
  color: INK,
939
1044
  borderRadius: 8,
940
- padding: "3px 10px",
941
- fontSize: 12.5,
1045
+ padding: "6px 16px",
1046
+ fontSize: 13,
1047
+ fontWeight: 600,
942
1048
  cursor: "pointer"
943
1049
  },
944
1050
  lightsRow: {
@@ -985,6 +1091,68 @@ window.__ModuleLoader__.load({
985
1091
  const trimmed = action.trim();
986
1092
  return trimmed.startsWith("去哪修:") ? trimmed.slice(4).trim() : trimmed;
987
1093
  }
1094
+ /** 报文里能扫出来的每一段路径(盘符绝对路径;非 Windows 形状的退回 `/` 打头那一支)。 */
1095
+ function rawPathsIn(text) {
1096
+ return text.match(/[A-Za-z]:\/[^\s()「」,。;]+/g) ?? text.match(/\/[^\s()「」,。;]+/g) ?? [];
1097
+ }
1098
+ /** 一段落点的**根**(盘符):`C:/a/b` → `C:/`。用来把「同一个盘、但根下不同枝」的落点分开算,
1099
+ * 免得一个 D 盘上的旁证(比如「配了但目录不在:D:/media」)把 C 盘那几条的缩写全拖没。 */
1100
+ function rootOf(path) {
1101
+ const drive = /^[A-Za-z]:\//.exec(path);
1102
+ return drive === null ? "/" : drive[0];
1103
+ }
1104
+ /** 一段落点的公共目录前缀(按 `/` 切、整段比):`C:/a/b/c` + `C:/a/b/d` → `C:/a/b`。 */
1105
+ function commonDirOf(paths) {
1106
+ if (paths.length < 2) return "";
1107
+ const split = paths.map((path) => path.replace(/[.,。;]+$/, "").split("/"));
1108
+ const first = split[0];
1109
+ let size = 0;
1110
+ while (size < first.length && split.every((parts) => parts[size] === first[size])) size += 1;
1111
+ return size < 1 ? "" : first.slice(0, size).join("/");
1112
+ }
1113
+ /** 一份报文里**按根分开**的落点前缀(长的在前):只给「同一个根下至少两条落点」的根算前缀——
1114
+ * 这个根下只有一条落点时不缩(没有第二条可比,缩出来反而是瞎猜)。
1115
+ * 为什么要分根:一家报里常有别的盘的旁证(备忘录用 D 盘上那份媒体目录),只算一个总前缀,
1116
+ * 一条旁证就能把整张表的缩写全拖没(#706 出图复评:备忘录那张表整片没缩、记账那张缩了)。 */
1117
+ function dirPrefixesOf(report) {
1118
+ return dirPrefixesIn(pathsOf(report).join(" "));
1119
+ }
1120
+ /** `dirPrefixesOf` 的正文版:一段文字里(报文正文 + 表头两条混在一起)按根分组取公共目录前缀。 */
1121
+ function dirPrefixesIn(text) {
1122
+ const paths = rawPathsIn(text);
1123
+ const roots = [...new Set(paths.map(rootOf))];
1124
+ const prefixes = [];
1125
+ for (const root of roots) {
1126
+ const prefix = commonDirOf(paths.filter((path) => rootOf(path) === root));
1127
+ if (prefix !== "") prefixes.push(prefix);
1128
+ }
1129
+ return prefixes.sort((a, b) => b.length - a.length);
1130
+ }
1131
+ /** 那一行「配置文件 … · 数据目录 …」+ 各条报文里的长路径,都缩成「公共前缀 + 短尾巴」。 */
1132
+ function shorten(path, prefixes) {
1133
+ const hit = prefixes.find((prefix) => path.startsWith(prefix + "/"));
1134
+ return hit === void 0 ? path : "…/" + path.slice(hit.length + 1);
1135
+ }
1136
+ /** 一家里所有会印出来的路径。 */
1137
+ function pathsOf(report) {
1138
+ const out = [report.configPath, report.dataDir];
1139
+ for (const item of report.items) out.push(item.message);
1140
+ return out;
1141
+ }
1142
+ /** 把报文里那一长串路径按公共前缀缩短(只动打印,不动事实)。
1143
+ * 只按**扫得出来的那一段段落点**去缩,不做全串替换:既不误伤长路径,也不会把一段路劈成半截。 */
1144
+ function shortenPathsIn(text, prefixes) {
1145
+ const candidates = rawPathsIn(text);
1146
+ if (candidates.length === 0) return text;
1147
+ const longestFirst = [...new Set(candidates.map((path) => path.replace(/[.,。;]+$/, "")))].sort((a, b) => b.length - a.length);
1148
+ let out = text;
1149
+ for (const path of longestFirst) {
1150
+ const hit = prefixes.find((prefix) => path.startsWith(prefix + "/"));
1151
+ if (hit === void 0) continue;
1152
+ out = out.split(hit + "/").join("…/");
1153
+ }
1154
+ return out;
1155
+ }
988
1156
  /** 从六份报告数出那一行的灯(每家按最严重那一档上色)。 */
989
1157
  function lightsOf(tabs, reports) {
990
1158
  return tabs.map((tab) => {
@@ -1010,7 +1178,7 @@ window.__ModuleLoader__.load({
1010
1178
  return react.createElement("div", {
1011
1179
  style: HEALTH_STYLE.box,
1012
1180
  "data-ilife-health": "overview"
1013
- }, react.createElement("div", { style: HEALTH_STYLE.headRow }, react.createElement("div", { style: HEALTH_STYLE.head }, "配置体检"), react.createElement("button", {
1181
+ }, react.createElement("div", { style: HEALTH_STYLE.headRow }, react.createElement("div", { style: HEALTH_STYLE.head }, "配置体检")), react.createElement("div", { style: HEALTH_STYLE.runRow }, react.createElement("button", {
1014
1182
  type: "button",
1015
1183
  style: props.running ? {
1016
1184
  ...HEALTH_STYLE.btn,
@@ -1054,32 +1222,51 @@ window.__ModuleLoader__.load({
1054
1222
  fontWeight: 700
1055
1223
  } }, light.status === null ? "—" : countsText(light.counts))))), react.createElement("div", { style: HEALTH_STYLE.meta }, props.error !== null ? props.error : "只看不改 · 灯=那一家最严重的一档 · 点一下跳到那家配置页"));
1056
1224
  }
1057
- /** 一家那张表:每条一行,正常的也都在(绿行字淡一点)。 */
1225
+ /** 正正常常的那些:绿条只留「名字」,一句话与「去哪修」都不印(票面验收第一条:正常的收成一行)。 */
1226
+ function isAttentionItem(item) {
1227
+ return isAttention(item.status);
1228
+ }
1229
+ /** 一家那张表:**要处理的**(红黄)一条一块、整行上底带一句话与「去哪修」;
1230
+ * **正常的**(绿)收成一行并列的小字,点得到、扫得完(票面验收第一条:正常的收成一行,有问题的才展开)。 */
1058
1231
  function HealthTable(props) {
1059
1232
  const { report } = props;
1233
+ const prefixes = report ? dirPrefixesOf(report) : [];
1060
1234
  return react.createElement("div", {
1061
1235
  style: HEALTH_STYLE.box,
1062
1236
  "data-ilife-health": "table"
1063
1237
  }, react.createElement("div", { style: HEALTH_STYLE.headRow }, react.createElement("div", { style: HEALTH_STYLE.head }, props.title + " · 体检"), report ? react.createElement("div", { style: {
1064
1238
  color: INK_DIM,
1065
1239
  fontSize: 12
1066
- } }, 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.map((item) => react.createElement("div", {
1240
+ } }, 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", {
1241
+ style: HEALTH_STYLE.meta,
1242
+ title: prefixes.join(" ")
1243
+ }, "配置文件 " + shorten(report.configPath, prefixes) + " · 数据目录 " + shorten(report.dataDir, prefixes)), report.items.filter(isAttentionItem).map((item) => react.createElement("div", {
1067
1244
  key: item.id,
1245
+ "data-ilife-health": "item",
1246
+ "data-status": item.status,
1068
1247
  style: {
1069
1248
  padding: "7px 8px",
1070
1249
  margin: "2px 0",
1071
1250
  borderTop: "1px solid " + BORDER,
1072
1251
  borderRadius: 6,
1073
- fontSize: 12.5,
1074
- lineHeight: 1.7,
1075
- background: isAttention(item.status) ? "color-mix(in srgb, " + STATUS_COLOR[item.status] + " 14%, transparent)" : "transparent"
1252
+ fontSize: 13,
1253
+ lineHeight: 1.75,
1254
+ background: STATUS_TINT[item.status]
1076
1255
  }
1077
1256
  }, react.createElement("div", { style: {
1078
1257
  display: "flex",
1079
1258
  flexWrap: "wrap",
1080
1259
  alignItems: "center",
1081
1260
  gap: 6
1082
- } }, react.createElement("span", { style: {
1261
+ } }, react.createElement("span", {
1262
+ style: {
1263
+ color: STATUS_TEXT[item.status],
1264
+ fontWeight: 700,
1265
+ fontSize: 13,
1266
+ lineHeight: 1
1267
+ },
1268
+ "aria-hidden": "true"
1269
+ }, STATUS_MARK[item.status]), react.createElement("span", { style: {
1083
1270
  width: 8,
1084
1271
  height: 8,
1085
1272
  borderRadius: "50%",
@@ -1089,9 +1276,32 @@ window.__ModuleLoader__.load({
1089
1276
  fontWeight: 650,
1090
1277
  color: INK
1091
1278
  } }, item.title), react.createElement("span", { style: {
1092
- color: isAttention(item.status) ? STATUS_TEXT[item.status] : INK_DIM,
1279
+ color: STATUS_TEXT[item.status],
1093
1280
  fontWeight: 700
1094
- } }, STATUS_LABEL[item.status]), item.source ? react.createElement("span", { style: { color: INK_DIM } }, "· 来自" + item.source) : null), react.createElement("div", { style: { color: isAttention(item.status) ? INK : INK_DIM } }, item.message), item.action.length > 0 ? react.createElement("div", { style: { color: INK_DIM } }, "去哪修:" + actionText(item.action)) : null))) : null);
1281
+ } }, STATUS_LABEL[item.status]), item.source ? react.createElement("span", { style: { color: INK_DIM } }, "· 来自" + item.source) : null), react.createElement("div", {
1282
+ style: { color: INK },
1283
+ title: item.message
1284
+ }, 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", {
1285
+ "data-ilife-health": "ok",
1286
+ style: {
1287
+ display: "flex",
1288
+ flexWrap: "wrap",
1289
+ alignItems: "center",
1290
+ gap: "2px 10px",
1291
+ marginTop: 8,
1292
+ paddingTop: 6,
1293
+ borderTop: "1px solid " + BORDER,
1294
+ fontSize: 12,
1295
+ lineHeight: 1.6,
1296
+ color: INK_DIM
1297
+ }
1298
+ }, react.createElement("span", { style: {
1299
+ fontWeight: 650,
1300
+ color: INK
1301
+ } }, "正常 " + String(countByStatus(report.items).green) + " 条"), report.items.filter((item) => !isAttentionItem(item)).map((item) => react.createElement("span", {
1302
+ key: item.id,
1303
+ "data-ilife-health": "ok-item"
1304
+ }, "· " + item.title))) : null) : null);
1095
1305
  }
1096
1306
  //#endregion
1097
1307
  //#region src/client.ts
@@ -1349,7 +1559,7 @@ window.__ModuleLoader__.load({
1349
1559
  setActiveId(next.plugin);
1350
1560
  tabRefs.current[nextIndex]?.focus();
1351
1561
  }
1352
- 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.3")), react.createElement(UpdateResults, { face }), react.createElement(HealthOverview, {
1562
+ 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.5")), react.createElement(UpdateResults, { face }), react.createElement(HealthOverview, {
1353
1563
  lights: lightsOf(rows.map((row) => ({
1354
1564
  id: row.id,
1355
1565
  title: labelForTab(row.id)