@lmzhen/dsh-evolution-skill-health 0.32.5 → 0.32.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/README.md CHANGED
@@ -1,19 +1,9 @@
1
1
  # @lmzhen/dsh-evolution-skill-health
2
2
 
3
- > 技能健康面板(可选)。
3
+ Optional read-only skill-health route and a view inside the shared evolution workbench. The `all` bundle ships it disabled by default.
4
4
 
5
- ## 你会看到
5
+ The view projects curator health findings, proposal reports, protection markers, hard-cap hits, and available dimensions such as body size, support files, churn, and stamp density. It reports structural problems and duplicates; it does not repair skills. The by-skill queue is the only skill list; history and copy actions live on each row, and proposal provenance is under a details disclosure.
6
6
 
7
- `/evolution skills health`;侧栏「技能健康」。
7
+ Unreadable skills are not presented as healthy. An unavailable scan or proposal read is marked unavailable/truncated rather than returned as a clean empty result. The `references-sprawl` rule is named but its count is suppressed until its threshold is calibrated.
8
8
 
9
- ## 什么时候会用到
10
-
11
- 你想看结构问题/重复/超长时。
12
-
13
- ## 默认 / 前提
14
-
15
- 完整包里默认关闭。
16
-
17
- ## 边界
18
-
19
- 不修技能,只报告。
9
+ The host package owns one GET route. The browser package registers the view into `evolution.workbench.view`; applying fixes requires the relevant curator or skill-management surface, not this view.
package/lib/client.js CHANGED
@@ -5,28 +5,24 @@ window.__ModuleLoader__.load({
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  let react = require("react");
8
- //#region ../evolution-client-kit/src/panel.ts
9
- function interpolate(text, values) {
10
- if (values === void 0) return text;
11
- return text.replace(/\{(\w+)\}/g, (_match, key) => String(values[key] ?? ""));
12
- }
8
+ //#region ../evolution-client-kit/src/workbench-view.ts
13
9
  function injectStyles(doc, css, tagId) {
14
10
  if (doc === void 0) return () => {};
15
11
  doc.querySelector("style[data-plugin-css=\"" + tagId + "\"]")?.remove();
16
12
  const tag = doc.createElement("style");
17
13
  tag.setAttribute("data-plugin-css", tagId);
18
14
  tag.textContent = css;
19
- doc.head.append(tag);
15
+ doc.head.appendChild(tag);
20
16
  return () => {
21
17
  tag.remove();
22
18
  };
23
19
  }
24
20
  /**
25
- * Register one sidebar row and its main body under one lifecycle.
26
- * @param ctx - the client context carrying the slots and locale seats.
27
- * @param spec - the feature-specific row/body descriptor.
21
+ * Register one view into `evolution.workbench.view`.
22
+ * @param ctx - client cordis context.
23
+ * @param spec - view identity, copy, styles, component and face factory.
28
24
  */
29
- function registerSidebarPanel(ctx, spec) {
25
+ function registerWorkbenchView(ctx, spec) {
30
26
  const seam = ctx;
31
27
  let registered = false;
32
28
  ctx.effect(() => {
@@ -35,12 +31,12 @@ window.__ModuleLoader__.load({
35
31
  registered = true;
36
32
  return dispose;
37
33
  } catch (error) {
38
- ctx.logger.warn(spec.namespace + ": the locale seat refused the dictionary (" + String(error) + "); the panel falls back to the bundle copy");
34
+ ctx.logger.warn(spec.namespace + ": locale refused (" + String(error) + "); the view falls back to the bundle copy");
39
35
  return () => {};
40
36
  }
41
37
  }, spec.namespace + ": locale");
42
38
  ctx.effect(() => injectStyles(typeof document === "undefined" ? void 0 : document, spec.styles.css, spec.styles.tagId), spec.namespace + ": styles");
43
- const fallback = spec.dictionaries[spec.fallback ?? "zh"];
39
+ const fallback = spec.dictionaries.zh;
44
40
  const lookup = (key) => {
45
41
  const own = fallback[key] ?? key;
46
42
  if (!registered) return own;
@@ -50,19 +46,18 @@ window.__ModuleLoader__.load({
50
46
  return own;
51
47
  }
52
48
  };
53
- const t = (key, values) => interpolate(lookup(key), values);
54
- const label = () => lookup("entry.label");
55
- seam.slots.inject("sidebar.panellist", function* () {
49
+ const t = (key, values) => {
50
+ const template = lookup(key);
51
+ if (values === void 0) return template;
52
+ return template.replace(/\{(\w+)\}/g, (match, slot) => values[slot] === void 0 ? match : String(values[slot]));
53
+ };
54
+ seam.slots.inject("evolution.workbench.view", function* () {
56
55
  yield seam.slots.register({
57
- name: "sidebar.panellist",
56
+ name: "evolution.workbench.view",
58
57
  id: spec.id,
59
58
  order: spec.order,
60
- label
61
- }, spec.icon);
62
- yield seam.slots.register({
63
- name: "main",
64
- key: spec.id,
65
- inject: () => spec.body(t)
59
+ label: () => t("entry.label"),
60
+ inject: () => spec.face(t)
66
61
  }, spec.component);
67
62
  });
68
63
  }
@@ -79,7 +74,8 @@ window.__ModuleLoader__.load({
79
74
  pending: "/api/dsh-evolution/approval/pending",
80
75
  preview: "/api/dsh-evolution/approval/preview",
81
76
  approve: "/api/dsh-evolution/approval/approve",
82
- reject: "/api/dsh-evolution/approval/reject"
77
+ reject: "/api/dsh-evolution/approval/reject",
78
+ release: "/api/dsh-evolution/approval/release"
83
79
  },
84
80
  "skill-history": {
85
81
  skills: "/api/dsh-evolution/skill-history/skills",
@@ -98,8 +94,30 @@ window.__ModuleLoader__.load({
98
94
  body: "/api/dsh-evolution/merge/body",
99
95
  history: "/api/dsh-evolution/merge/history"
100
96
  },
101
- diagnostics: { view: "/api/dsh-evolution/diagnostics" }
97
+ diagnostics: { view: "/api/dsh-evolution/diagnostics" },
98
+ settings: {
99
+ describe: "/api/dsh-evolution/settings/describe",
100
+ mutate: "/api/dsh-evolution/settings/mutate"
101
+ }
102
102
  };
103
+ ROUTES.settings.describe;
104
+ ROUTES.settings.mutate;
105
+ Object.freeze({
106
+ status: "unavailable",
107
+ value: void 0,
108
+ user: void 0,
109
+ writable: false,
110
+ base: void 0,
111
+ revision: void 0,
112
+ reason: "transport"
113
+ });
114
+ //#endregion
115
+ //#region ../evolution-client-kit/src/action.ts
116
+ /**
117
+ * Command-only actions. Approval decisions are a separate host API and must never
118
+ * be represented as a copyable command here.
119
+ * @module @lmzhen/dsh-evolution-client-kit
120
+ */
103
121
  //#endregion
104
122
  //#region ../evolution-client-kit/src/generated/fallbacks.generated.ts
105
123
  /**
@@ -109,13 +127,14 @@ window.__ModuleLoader__.load({
109
127
  */
110
128
  const FALLBACKS = {
111
129
  approval: { command: "/evolution pending … /evolution approve <id> … /evolution reject <id> … /evolution release <id>" },
112
- "skill-history": { command: "/evolution restore <name> [v](查看:/evolution skills history <name>)" },
130
+ "skill-history": { command: "/evolution skill undo <name> [--to v<N>](查看:/evolution skill history <name>)" },
113
131
  "skill-health": { command: "/evolution skills refresh(健康行:/evolution doctor)" },
114
132
  merge: { command: "/evolution merge status / pending / approve <id> / report" },
115
133
  diagnostics: {
116
134
  command: "/evolution doctor",
117
135
  platformCli: "dsh --json /evolution doctor"
118
- }
136
+ },
137
+ settings: { command: "/evolution policy set <id> <value>(查看:/evolution policy)" }
119
138
  };
120
139
  //#endregion
121
140
  //#region ../evolution-client-kit/src/generated/states.generated.ts
@@ -316,6 +335,7 @@ window.__ModuleLoader__.load({
316
335
  "proposal.kept": "保留正文",
317
336
  "proposal.warnings": "警告",
318
337
  "proposal.copy": "复制命令",
338
+ "proposal.provenance": "生成来源",
319
339
  "counts": "{ok} 正常 · {warn} 警告 · {error} 错误 · {unreadable} 不可读",
320
340
  "col.skill": "技能",
321
341
  "col.findings": "证据",
@@ -340,8 +360,15 @@ window.__ModuleLoader__.load({
340
360
  "failure.unmounted": "该功能没有挂载。",
341
361
  "failure.server": "读取失败,可以重试。",
342
362
  "fallback.hint": "可以改用命令:{command}",
363
+ "failureUnauthorized": "需要登录:重新打开这个页面完成登录后再试。",
364
+ "failureForbidden": "平台拒绝了这个请求(身份不是 owner)。",
365
+ "failureUnmounted": "该功能没有挂载。",
366
+ "failureServer": "读取失败,可以重试。",
367
+ "fallbackHint": "可以改用命令:{command}",
343
368
  "action.history": "查看历史",
344
369
  "action.copy": "复制命令",
370
+ "copy.copied": "已复制",
371
+ "copy.failed": "复制失败",
345
372
  "action.extract": "提取",
346
373
  "action.extract.reason": "命令/预览面未就绪,暂不可用。"
347
374
  };
@@ -380,6 +407,7 @@ window.__ModuleLoader__.load({
380
407
  "proposal.kept": "kept body",
381
408
  "proposal.warnings": "warnings",
382
409
  "proposal.copy": "Copy command",
410
+ "proposal.provenance": "Provenance",
383
411
  "counts": "{ok} ok · {warn} warn · {error} error · {unreadable} unreadable",
384
412
  "col.skill": "Skill",
385
413
  "col.findings": "Evidence",
@@ -403,8 +431,15 @@ window.__ModuleLoader__.load({
403
431
  "failure.unmounted": "This feature is not mounted.",
404
432
  "failure.server": "The read failed; you can retry.",
405
433
  "fallback.hint": "Command fallback: {command}",
434
+ "failureUnauthorized": "Sign-in is required: reopen this page and sign in, then retry.",
435
+ "failureForbidden": "The platform refused this request (not the owner principal).",
436
+ "failureUnmounted": "This feature is not mounted.",
437
+ "failureServer": "The read failed; you can retry.",
438
+ "fallbackHint": "Command fallback: {command}",
406
439
  "action.history": "History",
407
440
  "action.copy": "Copy command",
441
+ "copy.copied": "Copied",
442
+ "copy.failed": "Copy failed",
408
443
  "action.extract": "Extract",
409
444
  "action.extract.reason": "The command/preview surface is not ready yet."
410
445
  };
@@ -443,7 +478,7 @@ window.__ModuleLoader__.load({
443
478
  ":where(.dsh-evolution-skill-health) { --evo-type-lg:calc(1em + 2px);--evo-type-body:1em;--evo-type-sm:calc(1em - 1px);--evo-type-xs:calc(1em - 2px);--evo-space-2:2px;--evo-space-4:4px;--evo-space-6:6px;--evo-space-8:8px;--evo-space-10:10px;--evo-space-12:12px;--evo-space-14:14px;--evo-space-16:16px;--evo-space-20:20px;--evo-radius-chip:4px;--evo-radius-control:8px;--evo-radius-surface:12px;--evo-radius-pill:999px;--evo-hairline-width:.5px;--evo-hairline-marker:2px;--evo-measure-read:80ch;--evo-leading-base:1.5;--evo-tone-quiet:var(--dsw-alias-label-secondary);--evo-tone-primary:var(--dsw-alias-brand-primary);--evo-tone-danger:var(--dsw-alias-state-error-primary);--evo-tone-success:var(--dsw-alias-state-success-primary);--evo-focus-ring:2px solid var(--dsw-alias-label-primary);--evo-focus-offset:-2px;--evo-cap-aside:280px;--evo-cap-block:260px }",
444
479
  ":where(.dsh-evolution-skill-health) { display: flex; flex-direction: column; gap: var(--evo-space-12); padding: var(--evo-space-16); color: var(--dsw-alias-label-primary); font-size: var(--evo-type-sm); line-height: var(--evo-leading-base); }",
445
480
  ":where(.dsh-evolution-skill-health [data-role=\"hint\"]) { margin: 0; color: var(--dsw-alias-label-secondary); }",
446
- ":where(.dsh-evolution-skill-health [data-role=\"stats\"]) { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--evo-space-8); }",
481
+ ":where(.dsh-evolution-skill-health [data-role=\"stats\"]) { display: grid; grid-template-columns: repeat(auto-fit, minmax(136px, 1fr)); gap: var(--evo-space-8); }",
447
482
  ":where(.dsh-evolution-skill-health [data-role=\"stat\"]) { display: flex; flex-direction: column; gap: var(--evo-space-2); padding: var(--evo-space-8) var(--evo-space-10); border: var(--evo-hairline-width) solid var(--dsw-alias-border-l1); border-radius: var(--evo-radius-control); }",
448
483
  ":where(.dsh-evolution-skill-health [data-role=\"stat\"][data-tone=\"ok\"]) { box-shadow: inset 2px 0 0 var(--dsw-alias-state-success-primary); }",
449
484
  ":where(.dsh-evolution-skill-health [data-role=\"stat\"][data-tone=\"warn\"]) { box-shadow: inset 2px 0 0 var(--dsw-alias-state-warn-primary); }",
@@ -452,8 +487,20 @@ window.__ModuleLoader__.load({
452
487
  ":where(.dsh-evolution-skill-health [data-role=\"stat-value\"]) { font-size: var(--evo-type-lg); font-variant-numeric: tabular-nums; line-height: 1; }",
453
488
  ":where(.dsh-evolution-skill-health [data-role=\"stat-label\"]) { color: var(--dsw-alias-label-tertiary); }",
454
489
  ":where(.dsh-evolution-skill-health [data-role=\"group-title\"]) { margin: 0; font-weight: 500; }",
455
- ":where(.dsh-evolution-skill-health [data-role=\"rule-table\"]) { display: flex; flex-direction: column; border: var(--evo-hairline-width) solid var(--dsw-alias-border-l1); border-radius: var(--evo-radius-control); overflow: hidden; }",
456
- ":where(.dsh-evolution-skill-health [data-role=\"rule-row\"]) { display: grid; grid-template-columns: minmax(9rem, 13rem) minmax(0, 1fr) 4rem; gap: var(--evo-space-10); align-items: center; padding: var(--evo-space-6) var(--evo-space-10); border-top: var(--evo-hairline-width) solid var(--dsw-alias-border-l1); }",
490
+ ":where(.dsh-evolution-skill-health [data-role=\"rule-table\"]) { display: table; width: 100%; border-collapse: collapse; border: var(--evo-hairline-width) solid var(--dsw-alias-border-l1); border-radius: var(--evo-radius-control); overflow: hidden; }",
491
+ ":where(.dsh-evolution-skill-health [data-role=\"rule-row\"]) { border-top: var(--evo-hairline-width) solid var(--dsw-alias-border-l1); }",
492
+ ":where(.dsh-evolution-skill-health [data-role=\"rule-row\"] > th, .dsh-evolution-skill-health [data-role=\"rule-row\"] > td) { padding: var(--evo-space-6) var(--evo-space-10); text-align: left; vertical-align: top; }",
493
+ ":where(.dsh-evolution-skill-health [data-role=\"rule-row\"] > th:first-child) { width: minmax(9rem, 13rem); }",
494
+ ":where(.dsh-evolution-skill-health [data-role=\"rule-row\"] > th:last-child, .dsh-evolution-skill-health [data-role=\"rule-row\"] > td:last-child) { width: 4rem; text-align: right; }",
495
+ ":where(.dsh-evolution-skill-health [data-role=\"rule-row\"][data-tone=\"ok\"]) { box-shadow: inset var(--evo-hairline-marker) 0 0 var(--dsw-alias-state-success-primary); }",
496
+ ":where(.dsh-evolution-skill-health [data-role=\"skill-queue\"]) { display: flex; flex-direction: column; gap: var(--evo-space-6); }",
497
+ ":where(.dsh-evolution-skill-health [data-role=\"skill-row\"]) { display: grid; grid-template-columns: minmax(8rem, 12rem) 6rem minmax(0, 1fr) auto; gap: var(--evo-space-8); align-items: center; padding: var(--evo-space-6) var(--evo-space-10); border: var(--evo-hairline-width) solid var(--dsw-alias-border-l1); border-radius: var(--evo-radius-control); }",
498
+ ":where(.dsh-evolution-skill-health [data-role=\"skill-name\"]) { overflow-wrap: anywhere; font-weight: 500; }",
499
+ ":where(.dsh-evolution-skill-health [data-role=\"skill-verdict\"], .dsh-evolution-skill-health [data-role=\"skill-dims\"], .dsh-evolution-skill-health [data-role=\"skill-findings\"]) { color: var(--dsw-alias-label-secondary); font-size: var(--evo-type-xs); }",
500
+ ":where(.dsh-evolution-skill-health [data-role=\"skill-row-actions\"]) { display: flex; gap: var(--evo-space-4); }",
501
+ ":where(.dsh-evolution-skill-health [data-role=\"proposals\"]) { display: flex; flex-direction: column; gap: var(--evo-space-8); }",
502
+ ":where(.dsh-evolution-skill-health [data-role=\"proposal\"]) { padding: var(--evo-space-8) var(--evo-space-10); border: var(--evo-hairline-width) solid var(--dsw-alias-border-l1); border-radius: var(--evo-radius-control); }",
503
+ ":where(.dsh-evolution-skill-health [data-role=\"proposal\"] p) { margin: var(--evo-space-2) 0; }",
457
504
  ":where(.dsh-evolution-skill-health [data-role=\"rule-row\"][data-tone=\"warn\"]) { box-shadow: inset var(--evo-hairline-marker) 0 0 var(--dsw-alias-state-warn-primary); }",
458
505
  ":where(.dsh-evolution-skill-health [data-role=\"rule-row\"][data-tone=\"error\"]) { box-shadow: inset var(--evo-hairline-marker) 0 0 var(--dsw-alias-state-error-primary); }",
459
506
  ":where(.dsh-evolution-skill-health [data-role=\"rule-row\"][data-tone=\"unreadable\"]) { box-shadow: inset var(--evo-hairline-marker) 0 0 var(--evo-tone-quiet); }",
@@ -485,6 +532,14 @@ window.__ModuleLoader__.load({
485
532
  "error",
486
533
  "unreadable"
487
534
  ];
535
+ /** UI tone per verdict; the CSS keys are ok/warn/error/unreadable. */
536
+ const TONE = Object.freeze({
537
+ healthy: "ok",
538
+ warn: "warn",
539
+ "needs-restructure": "warn",
540
+ error: "error",
541
+ unreadable: "unreadable"
542
+ });
488
543
  /** One row per rule, with the skills that carry it — the view the report is actually read in. */
489
544
  function byRule(rows) {
490
545
  const table = /* @__PURE__ */ new Map();
@@ -516,6 +571,28 @@ window.__ModuleLoader__.load({
516
571
  }
517
572
  return worst;
518
573
  }
574
+ /** A copy button that reports the clipboard verdict instead of swallowing it. */
575
+ function CopyButton(props) {
576
+ const [state, setState] = (0, react.useState)("idle");
577
+ const copy = async () => {
578
+ try {
579
+ if (typeof navigator === "undefined" || navigator.clipboard === void 0) throw new Error("clipboard unavailable");
580
+ await navigator.clipboard.writeText(props.command);
581
+ setState("ok");
582
+ } catch {
583
+ setState("fail");
584
+ }
585
+ };
586
+ const text = state === "ok" ? props.t("copy.copied") : state === "fail" ? props.t("copy.failed") : props.label;
587
+ return (0, react.createElement)("button", {
588
+ type: "button",
589
+ "data-role": props.role,
590
+ "data-copy-state": state,
591
+ onClick: () => {
592
+ copy();
593
+ }
594
+ }, text);
595
+ }
519
596
  /** H4: the evidence queue by skill, before the by-rule summary. */
520
597
  function skillQueue(face, t) {
521
598
  const order = [
@@ -530,52 +607,45 @@ window.__ModuleLoader__.load({
530
607
  key: row.name,
531
608
  "data-role": "skill-row",
532
609
  "data-tone": row.verdict
533
- }, (0, react.createElement)("span", { "data-role": "skill-name" }, row.name), (0, react.createElement)("span", { "data-role": "skill-verdict" }, t("verdict." + row.verdict)), (0, react.createElement)("span", { "data-role": "skill-findings" }, row.findings.length === 0 ? t("findings.none") : row.findings.map((finding) => finding.rule).join(" / ")), (0, react.createElement)("span", { "data-role": "skill-dims" }, t("dims.body") + ": " + String(row.dims?.bodyChars ?? t("dims.unknown")) + " · " + t("dims.support") + ": " + String(row.dims?.supportFiles ?? t("dims.unknown"))))));
610
+ }, (0, react.createElement)("span", { "data-role": "skill-name" }, row.name), (0, react.createElement)("span", { "data-role": "skill-verdict" }, t("verdict." + row.verdict)), (0, react.createElement)("span", { "data-role": "skill-findings" }, row.findings.length === 0 ? t("findings.none") : row.findings.map((finding) => finding.rule).join(" / ")), (0, react.createElement)("span", { "data-role": "skill-dims" }, t("dims.body") + ": " + String(row.dims?.bodyChars ?? t("dims.unknown")) + " · " + t("dims.support") + ": " + String(row.dims?.supportFiles ?? t("dims.unknown"))), (0, react.createElement)("span", { "data-role": "skill-row-actions" }, (0, react.createElement)("button", {
611
+ type: "button",
612
+ "data-role": "action-history",
613
+ disabled: face.openHistory === void 0,
614
+ onClick: () => {
615
+ face.openHistory?.(row.name);
616
+ }
617
+ }, t("action.history")), (0, react.createElement)(CopyButton, {
618
+ command: "/evolution skill history " + row.name,
619
+ t,
620
+ role: "action-copy",
621
+ label: t("action.copy")
622
+ })))));
534
623
  }
535
624
  /** The rule table: a grid so counts line up, with a tone bar per row and skill pills. */
536
625
  function ruleTable(face, t) {
537
626
  const rows = byRule(face.rows);
538
- const cell = (role, children, key) => (0, react.createElement)("div", key === void 0 ? { "data-role": role } : {
627
+ const cell = (role, children, key) => (0, react.createElement)("td", key === void 0 ? { "data-role": role } : {
539
628
  "data-role": role,
540
629
  key
541
630
  }, children);
542
- return (0, react.createElement)("div", { "data-role": "rule-table" }, (0, react.createElement)("div", {
543
- "data-role": "rule-row",
544
- "data-role-head": "true"
545
- }, (0, react.createElement)("div", { "data-role": "rule-name" }, t("col.rule")), (0, react.createElement)("div", { "data-role": "skills" }, t("col.skills")), (0, react.createElement)("div", { "data-role": "rule-count" }, t("col.count"))), rows.map((entry) => (0, react.createElement)("div", {
631
+ return (0, react.createElement)("table", { "data-role": "rule-table" }, (0, react.createElement)("thead", null, (0, react.createElement)("tr", { "data-role": "rule-head" }, (0, react.createElement)("th", {
632
+ scope: "col",
633
+ "data-role": "rule-name"
634
+ }, t("col.rule")), (0, react.createElement)("th", {
635
+ scope: "col",
636
+ "data-role": "skills"
637
+ }, t("col.skills")), (0, react.createElement)("th", {
638
+ scope: "col",
639
+ "data-role": "rule-count"
640
+ }, t("col.count")))), (0, react.createElement)("tbody", null, rows.map((entry) => (0, react.createElement)("tr", {
546
641
  key: entry.rule,
547
642
  "data-role": "rule-row",
548
- "data-tone": worstVerdict(face, entry.skills)
643
+ "data-tone": TONE[worstVerdict(face, entry.skills)]
549
644
  }, cell("rule-name", entry.rule), cell("skills", entry.skills.map((name, i) => (0, react.createElement)("span", {
550
645
  "data-role": "skill-pill",
551
646
  key: name + ":" + String(i),
552
647
  title: name
553
- }, name))), cell("rule-count", entry.count === 0 ? t("count.notCalibrated") : String(entry.count)))));
554
- }
555
- /** Per-skill actions: open history, copy the CLI command, and the disabled extract entry. */
556
- function skillActions(face, t) {
557
- const names = [...new Set(face.rows.map((row) => row.name))];
558
- const copy = (name) => {
559
- const text = "/evolution skill history " + name;
560
- if (typeof navigator !== "undefined" && navigator.clipboard !== void 0) navigator.clipboard.writeText(text).catch(() => {});
561
- };
562
- return (0, react.createElement)("div", { "data-role": "skill-actions" }, ...names.map((name) => (0, react.createElement)("div", {
563
- key: name,
564
- "data-role": "skill-action-row"
565
- }, (0, react.createElement)("span", { "data-role": "skill-action-name" }, name), (0, react.createElement)("button", {
566
- type: "button",
567
- "data-role": "action-history",
568
- disabled: face.openHistory === void 0,
569
- onClick: () => {
570
- face.openHistory?.(name);
571
- }
572
- }, t("action.history")), (0, react.createElement)("button", {
573
- type: "button",
574
- "data-role": "action-copy",
575
- onClick: () => {
576
- copy(name);
577
- }
578
- }, t("action.copy")))));
648
+ }, name))), cell("rule-count", entry.count === 0 ? t("count.notCalibrated") : String(entry.count))))));
579
649
  }
580
650
  /**
581
651
  * Render the panel.
@@ -628,6 +698,7 @@ window.__ModuleLoader__.load({
628
698
  });
629
699
  setLastReadAt(payload.readAt);
630
700
  setScan(payload.scan);
701
+ setFailed(void 0);
631
702
  } else setFailed(outcome.failure);
632
703
  }).catch(() => {
633
704
  if (live) setFailed({
@@ -646,7 +717,7 @@ window.__ModuleLoader__.load({
646
717
  const stats = (0, react.createElement)("div", { "data-role": "stats" }, KINDS.map((kind) => (0, react.createElement)("div", {
647
718
  key: kind,
648
719
  "data-role": "stat",
649
- "data-tone": kind
720
+ "data-tone": TONE[kind]
650
721
  }, (0, react.createElement)("div", { "data-role": "stat-value" }, String(counts.get(kind) ?? 0)), (0, react.createElement)("div", { "data-role": "stat-label" }, t("verdict." + kind)))));
651
722
  return (0, react.createElement)("section", { className: CSS_TAG_ID }, (0, react.createElement)("div", { "data-role": "header" }, (0, react.createElement)("span", { "data-role": "read-at" }, lastReadAt === void 0 ? t("readAt.unknown") : new Date(lastReadAt).toLocaleString()), (0, react.createElement)("span", { "data-role": "scan-status" }, t("scan." + (scan?.status ?? "unavailable"))), (0, react.createElement)("button", {
652
723
  type: "button",
@@ -655,22 +726,20 @@ window.__ModuleLoader__.load({
655
726
  onClick: () => {
656
727
  load();
657
728
  }
658
- }, t("refresh"))), (0, react.createElement)("p", { "data-role": "hint" }, t("hint")), stats, loading ? (0, react.createElement)("p", { "data-role": "note" }, t("loading")) : null, view.truncated ? (0, react.createElement)("p", { "data-role": "note" }, t("scan.truncated")) : null, failed === void 0 ? null : (0, react.createElement)("div", { "data-role": "note" }, failureBlock(t, failed)), view.loaded === true && view.rows.length === 0 ? (0, react.createElement)("p", { "data-role": "note" }, t("empty.noneReported")) : null, view.proposalStatus === "unavailable" && (view.proposals ?? []).length === 0 ? (0, react.createElement)("p", { "data-role": "note" }, t("proposal.unavailable")) : null, (view.proposals ?? []).length > 0 ? (0, react.createElement)("div", { "data-role": "proposals" }, (0, react.createElement)("h3", { "data-role": "group-title" }, t("group.proposals")), ...(view.proposals ?? []).map((proposal) => {
729
+ }, t("refresh"))), (0, react.createElement)("p", { "data-role": "hint" }, t("hint")), loading || failed !== void 0 || scan?.status === "unavailable" ? null : stats, loading ? (0, react.createElement)("p", { "data-role": "note" }, t("loading")) : null, view.truncated ? (0, react.createElement)("p", { "data-role": "note" }, t("scan.truncated")) : null, failed === void 0 ? null : (0, react.createElement)("div", { "data-role": "note" }, failureBlock(t, failed)), view.loaded === true && view.rows.length === 0 ? (0, react.createElement)("p", { "data-role": "note" }, t("empty.noneReported")) : null, view.proposalStatus === "unavailable" && (view.proposals ?? []).length === 0 ? (0, react.createElement)("p", { "data-role": "note" }, t("proposal.unavailable")) : null, (view.proposals ?? []).length > 0 ? (0, react.createElement)("div", { "data-role": "proposals" }, (0, react.createElement)("h3", { "data-role": "group-title" }, t("group.proposals")), ...(view.proposals ?? []).map((proposal) => {
659
730
  const heading = proposal.parts?.[0]?.heading;
660
731
  const target = proposal.owner.mode === "covered" ? proposal.owner.name : proposal.owner.neededOwner;
661
732
  const command = heading === void 0 || target === void 0 ? void 0 : "/evolution extract " + proposal.source + " " + target + " \"" + heading + "\" --plan";
662
- const copyCommand = () => {
663
- if (command !== void 0 && typeof navigator !== "undefined" && navigator.clipboard !== void 0) navigator.clipboard.writeText(command).catch(() => {});
664
- };
665
733
  return (0, react.createElement)("div", {
666
734
  key: proposal.id,
667
735
  "data-role": "proposal"
668
- }, (0, react.createElement)("p", { "data-role": "proposal-summary" }, proposal.source + " — " + t("proposal.outcome." + proposal.outcome) + " · " + (proposal.owner.mode === "covered" ? t("proposal.owner.covered") + (proposal.owner.name === void 0 ? "" : ": " + proposal.owner.name) : t("proposal.owner.owner-gap") + (proposal.owner.neededOwner === void 0 ? "" : ": " + proposal.owner.neededOwner))), (0, react.createElement)("p", { "data-role": "proposal-status" }, t("proposal.status." + proposal.status)), proposal.provenance === void 0 ? null : (0, react.createElement)("p", { "data-role": "proposal-provenance" }, proposal.provenance.promptVersion + (proposal.provenance.model === void 0 ? "" : " · " + proposal.provenance.model) + " · " + proposal.provenance.runId + " · " + new Date(proposal.provenance.at).toLocaleString() + (proposal.provenance.promptHash === void 0 ? "" : " · #" + proposal.provenance.promptHash.slice(0, 8))), proposal.residual === void 0 || proposal.residual.trim() === "" ? null : (0, react.createElement)("p", { "data-role": "proposal-kept" }, t("proposal.kept") + ": " + proposal.residual.trim().slice(0, 160)), (proposal.parts ?? []).length === 0 ? null : (0, react.createElement)("p", { "data-role": "proposal-reusable" }, t("proposal.reusable") + ": " + (proposal.parts ?? []).map((part) => part.heading).join("、")), (proposal.warnings ?? []).length === 0 ? null : (0, react.createElement)("p", { "data-role": "proposal-warnings" }, t("proposal.warnings") + ": " + (proposal.warnings ?? []).join(";")), command === void 0 ? null : (0, react.createElement)("p", { "data-role": "proposal-command" }, (0, react.createElement)("code", {}, command), (0, react.createElement)("button", {
669
- type: "button",
670
- "data-role": "proposal-copy",
671
- onClick: copyCommand
672
- }, t("proposal.copy"))));
673
- })) : null, skillQueue(view, t), (0, react.createElement)("h3", { "data-role": "group-title" }, t("group.byRule")), ruleTable(view, t), skillActions(view, t), view.rows.filter((row) => row.verdict === "unreadable").map((row) => (0, react.createElement)("p", {
736
+ }, (0, react.createElement)("p", { "data-role": "proposal-summary" }, proposal.source + " — " + t("proposal.outcome." + proposal.outcome) + " · " + (proposal.owner.mode === "covered" ? t("proposal.owner.covered") + (proposal.owner.name === void 0 ? "" : ": " + proposal.owner.name) : t("proposal.owner.owner-gap") + (proposal.owner.neededOwner === void 0 ? "" : ": " + proposal.owner.neededOwner))), (0, react.createElement)("p", { "data-role": "proposal-status" }, t("proposal.status." + proposal.status)), proposal.provenance === void 0 ? null : (0, react.createElement)("details", { "data-role": "proposal-provenance" }, (0, react.createElement)("summary", null, t("proposal.provenance")), (0, react.createElement)("p", null, proposal.provenance.promptVersion + (proposal.provenance.model === void 0 ? "" : " · " + proposal.provenance.model) + " · " + proposal.provenance.runId + " · " + new Date(proposal.provenance.at).toLocaleString() + (proposal.provenance.promptHash === void 0 ? "" : " · #" + proposal.provenance.promptHash.slice(0, 8)))), proposal.residual === void 0 || proposal.residual.trim() === "" ? null : (0, react.createElement)("p", { "data-role": "proposal-kept" }, t("proposal.kept") + ": " + proposal.residual.trim().slice(0, 160)), (proposal.parts ?? []).length === 0 ? null : (0, react.createElement)("p", { "data-role": "proposal-reusable" }, t("proposal.reusable") + ": " + (proposal.parts ?? []).map((part) => part.heading).join("、")), (proposal.warnings ?? []).length === 0 ? null : (0, react.createElement)("p", { "data-role": "proposal-warnings" }, t("proposal.warnings") + ": " + (proposal.warnings ?? []).join(";")), command === void 0 ? null : (0, react.createElement)("p", { "data-role": "proposal-command" }, (0, react.createElement)("code", {}, command), (0, react.createElement)(CopyButton, {
737
+ command,
738
+ t,
739
+ role: "proposal-copy",
740
+ label: t("proposal.copy")
741
+ })));
742
+ })) : null, skillQueue(view, t), (0, react.createElement)("h3", { "data-role": "group-title" }, t("group.byRule")), ruleTable(view, t), view.rows.filter((row) => row.verdict === "unreadable").map((row) => (0, react.createElement)("p", {
674
743
  key: row.name + ":unreadable",
675
744
  "data-role": "note"
676
745
  }, row.name + " — " + t("unreadable.note"))), view.rows.filter((row) => row.protection !== void 0 && row.protection !== null).map((row) => (0, react.createElement)("p", {
@@ -685,38 +754,31 @@ window.__ModuleLoader__.load({
685
754
  if (ctx.slots === void 0) throw new Error("skill-health: the client slots seat is not mounted");
686
755
  }
687
756
  /** The one place the panel crosses into the platform layout seat. */
688
- function openSkillHistory(ctx) {
689
- ctx.layout.selectPanel("skill-history");
757
+ function openSkillHistory(ctx, name) {
758
+ ctx.layout.openPage("evolution.workbench", name === void 0 ? { view: "history" } : {
759
+ view: "history",
760
+ skillName: name
761
+ });
690
762
  }
691
763
  //#endregion
692
764
  //#region src/client/index.ts
693
- /** Required client services: the slot registry and the locale seat. */
765
+ /** Required client services: the slot registry, locale seat and page navigation. */
694
766
  const inject = [
695
767
  "slots",
696
768
  "locale",
697
769
  "layout"
698
770
  ];
699
- function PanelIcon(props = {}) {
700
- const size = typeof props.size === "number" && props.size > 0 ? props.size : 16;
701
- return (0, react.createElement)("svg", {
702
- "aria-hidden": "true",
703
- width: String(size),
704
- height: String(size),
705
- viewBox: "0 0 16 16",
706
- fill: "none",
707
- stroke: "currentColor",
708
- strokeWidth: "1.5",
709
- strokeLinecap: "round",
710
- strokeLinejoin: "round"
711
- }, (0, react.createElement)("path", { d: "M2 8h3l1.5-3.5L9 11.5 10.5 8H14" }));
771
+ /** The view component receives the workbench's activeView marker alongside its face. */
772
+ function HealthView(props) {
773
+ return props.activeView === "health" ? SkillHealthPanel(props) : null;
712
774
  }
713
775
  /**
714
- * Register the row and the body through the shared panel lifecycle.
715
- * @param ctx - the client context; it must carry the two seats above.
776
+ * Register the health view on the workbench page.
777
+ * @param ctx - the client context; it must carry the seats above.
716
778
  */
717
779
  function apply(ctx) {
718
780
  requireHealthSeats(ctx);
719
- registerSidebarPanel(ctx, {
781
+ registerWorkbenchView(ctx, {
720
782
  id: PANEL_ID,
721
783
  order: 40,
722
784
  namespace: NS,
@@ -728,17 +790,16 @@ window.__ModuleLoader__.load({
728
790
  css: CSS,
729
791
  tagId: CSS_TAG_ID
730
792
  },
731
- icon: PanelIcon,
732
- component: SkillHealthPanel,
733
- body: (t) => ({
793
+ component: HealthView,
794
+ face: (t) => ({
734
795
  rows: [],
735
796
  proposals: [],
736
797
  truncated: false,
737
798
  loaded: false,
738
799
  proposalStatus: "unavailable",
739
800
  loadRows: rows,
740
- openHistory: () => {
741
- openSkillHistory(ctx);
801
+ openHistory: (name) => {
802
+ openSkillHistory(ctx, name);
742
803
  },
743
804
  t
744
805
  })
@@ -1,18 +1,18 @@
1
1
  /**
2
- * Browser half: one left-sidebar row (技能健康) and the main-column body it opens.
2
+ * Browser half: the health view inside the shared evolution workbench.
3
3
  * @module @lmzhen/dsh-evolution-skill-health/client
4
4
  */
5
5
  import type { Context as ClientContext } from '@deepseek-ai/cordis';
6
6
  import { PANEL_ID, PANEL_ORDER } from './ids.ts';
7
7
  import { type HealthFace } from './Panel.ts';
8
- /** Required client services: the slot registry and the locale seat. */
8
+ /** Required client services: the slot registry, locale seat and page navigation. */
9
9
  export declare const inject: string[];
10
10
  /** The host half hands the panel a way to read its rows (a read-only route on the host side). */
11
11
  export type HealthFaceLoader = () => Promise<HealthFace>;
12
12
  export { PANEL_ID, PANEL_ORDER };
13
13
  /**
14
- * Register the row and the body through the shared panel lifecycle.
15
- * @param ctx - the client context; it must carry the two seats above.
14
+ * Register the health view on the workbench page.
15
+ * @param ctx - the client context; it must carry the seats above.
16
16
  */
17
17
  export declare function apply(ctx: ClientContext): void;
18
18
  //# sourceMappingURL=index.d.ts.map
@@ -2,5 +2,5 @@ import type { Context as ClientContext } from '@deepseek-ai/cordis';
2
2
  /** Fail loud when the injected client seats are absent, instead of leaving a PENDING row. */
3
3
  export declare function requireHealthSeats(ctx: ClientContext): void;
4
4
  /** The one place the panel crosses into the platform layout seat. */
5
- export declare function openSkillHistory(ctx: ClientContext): void;
5
+ export declare function openSkillHistory(ctx: ClientContext, name?: string): void;
6
6
  //# sourceMappingURL=seats.d.ts.map
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  ]
12
12
  }
13
13
  },
14
- "version": "0.32.5",
14
+ "version": "0.32.6",
15
15
  "publishConfig": {
16
16
  "access": "public"
17
17
  },
@@ -42,18 +42,18 @@
42
42
  "peerDependencies": {
43
43
  "@deepseek-ai/cordis": "^4.0.5",
44
44
  "react": "^18.2.0",
45
- "@lmzhen/dsh-evolution-core": "^0.32.5",
46
- "@lmzhen/dsh-evolution-curator": "^0.32.5"
45
+ "@lmzhen/dsh-evolution-core": "^0.32.6",
46
+ "@lmzhen/dsh-evolution-curator": "^0.32.6"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@deepseek-ai/cordis": "^4.0.5",
50
50
  "@types/react": "~18.3.1",
51
51
  "react": "^18.2.0",
52
- "@lmzhen/dsh-evolution-core": "^0.32.5",
53
- "@lmzhen/dsh-evolution-curator": "^0.32.5"
52
+ "@lmzhen/dsh-evolution-core": "^0.32.6",
53
+ "@lmzhen/dsh-evolution-curator": "^0.32.6"
54
54
  },
55
55
  "dependencies": {
56
- "@lmzhen/dsh-evolution-client-kit": "^0.32.5"
56
+ "@lmzhen/dsh-evolution-client-kit": "^0.32.6"
57
57
  },
58
58
  "engines": {
59
59
  "dsh": ">=0.2.1-rc.2 <0.2.2-0"