@lmzhen/dsh-evolution-skill-health 0.25.5 → 0.25.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.
package/lib/client.js CHANGED
@@ -74,10 +74,12 @@ window.__ModuleLoader__.load({
74
74
  "line": "位置:{path}",
75
75
  "line.unknown": "位置:未知",
76
76
  "unreadable.note": "读不到内容,因此无法判断(不是「通过」)。",
77
- "loading.failed": "读取失败——现在判断不了;点「刷新」重试。"
77
+ "loading.failed": "读取失败——现在判断不了;点「刷新」重试。",
78
+ "read.failed": "这次没读到数据(下面一行是原始信息):"
78
79
  };
79
80
  /** English dictionary. */
80
81
  const en = {
82
+ "read.failed": "This read failed (raw message below):",
81
83
  "entry.label": "Skill Health",
82
84
  "title": "Skill Health",
83
85
  "hint": "What the health engine found, aggregated by rule. A rule whose threshold is not calibrated yet is named without a number; skills that cannot be read are listed as unreadable, never as fine.",
@@ -134,28 +136,58 @@ window.__ModuleLoader__.load({
134
136
  }
135
137
  return [...table.values()].sort((a, b) => b.skills.length - a.skills.length || a.rule.localeCompare(b.rule));
136
138
  }
137
- /** The rule table: name always, count only when the threshold behind it is calibrated. */
139
+ /** The verdict a rule's row speaks in: the worst one among the skills that carry it. */
140
+ function worstVerdict(face, skills) {
141
+ const order = [
142
+ "error",
143
+ "warn",
144
+ "unreadable",
145
+ "ok"
146
+ ];
147
+ let worst = "ok";
148
+ for (const row of face.rows) {
149
+ if (!skills.includes(row.name)) continue;
150
+ if (order.indexOf(row.verdict) < order.indexOf(worst)) worst = row.verdict;
151
+ }
152
+ return worst;
153
+ }
154
+ /** The rule table: a grid so counts line up, with a tone bar per row and skill pills. */
138
155
  function ruleTable(face, t) {
139
156
  const rows = byRule(face.rows);
140
- return (0, react.createElement)("table", null, (0, react.createElement)("thead", null, (0, react.createElement)("tr", null, (0, react.createElement)("th", null, t("col.rule")), (0, react.createElement)("th", null, t("col.skills")), (0, react.createElement)("th", null, t("col.count")))), (0, react.createElement)("tbody", null, rows.map((entry) => (0, react.createElement)("tr", { key: entry.rule }, (0, react.createElement)("td", null, entry.rule), (0, react.createElement)("td", null, entry.skills.join(", ")), (0, react.createElement)("td", null, entry.count === 0 ? t("count.notCalibrated") : String(entry.count))))));
157
+ const cell = (role, children, key) => (0, react.createElement)("div", key === void 0 ? { "data-role": role } : {
158
+ "data-role": role,
159
+ key
160
+ }, children);
161
+ return (0, react.createElement)("div", { "data-role": "rule-table" }, (0, react.createElement)("div", {
162
+ "data-role": "rule-row",
163
+ "data-role-head": "true"
164
+ }, (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", {
165
+ key: entry.rule,
166
+ "data-role": "rule-row",
167
+ "data-tone": worstVerdict(face, entry.skills)
168
+ }, cell("rule-name", entry.rule), cell("skills", entry.skills.map((name, i) => (0, react.createElement)("span", {
169
+ "data-role": "skill-pill",
170
+ key: name + ":" + String(i),
171
+ title: name
172
+ }, name))), cell("rule-count", entry.count === 0 ? t("count.notCalibrated") : String(entry.count)))));
141
173
  }
142
174
  /**
143
175
  * Render the panel.
144
176
  * @param props - the face (`{ rows, truncated, openHistory? }`) and the locale seat's translator.
145
177
  * @returns the panel body.
146
178
  */
147
- function SkillHealthPanel(props) {
148
- const t = props.locale.bind("evolution-skill-health");
149
- const [face, setFace] = (0, react.useState)(props.face);
179
+ function SkillHealthPanel(face) {
180
+ const t = face.t;
181
+ const [view, setView] = (0, react.useState)(face);
150
182
  const [failed, setFailed] = (0, react.useState)(void 0);
151
183
  (0, react.useEffect)(() => {
152
- const load = props.face.loadRows;
184
+ const load = face.loadRows;
153
185
  if (load === void 0) return;
154
186
  let live = true;
155
187
  load().then((outcome) => {
156
188
  if (!live) return;
157
- if (outcome.ok) setFace({
158
- ...props.face,
189
+ if (outcome.ok) setView({
190
+ ...face,
159
191
  rows: outcome.payload.rows,
160
192
  truncated: outcome.payload.truncated
161
193
  });
@@ -166,9 +198,17 @@ window.__ModuleLoader__.load({
166
198
  return () => {
167
199
  live = false;
168
200
  };
169
- }, [props.face]);
170
- const counts = new Map(KINDS.map((kind) => [kind, face.rows.filter((row) => row.verdict === kind).length]));
171
- return (0, react.createElement)("section", null, (0, react.createElement)("p", null, t("counts").replace("{ok}", String(counts.get("ok") ?? 0)).replace("{warn}", String(counts.get("warn") ?? 0)).replace("{error}", String(counts.get("error") ?? 0)).replace("{unreadable}", String(counts.get("unreadable") ?? 0))), face.truncated ? (0, react.createElement)("p", null, t("loading.failed")) : null, failed === void 0 ? null : (0, react.createElement)("p", null, failed), face.rows.length === 0 ? (0, react.createElement)("p", null, t("empty.noneReported")) : null, (0, react.createElement)("h3", null, t("group.byRule")), ruleTable(face, t), face.rows.filter((row) => row.verdict === "unreadable").map((row) => (0, react.createElement)("p", { key: row.name + ":unreadable" }, row.name + " — " + t("unreadable.note"))));
201
+ }, [face]);
202
+ const counts = new Map(KINDS.map((kind) => [kind, view.rows.filter((row) => row.verdict === kind).length]));
203
+ const stats = (0, react.createElement)("div", { "data-role": "stats" }, KINDS.map((kind) => (0, react.createElement)("div", {
204
+ key: kind,
205
+ "data-role": "stat",
206
+ "data-tone": kind
207
+ }, (0, react.createElement)("div", { "data-role": "stat-value" }, String(counts.get(kind) ?? 0)), (0, react.createElement)("div", { "data-role": "stat-label" }, t("verdict." + kind)))));
208
+ return (0, react.createElement)("section", null, (0, react.createElement)("p", { "data-role": "hint" }, t("hint")), stats, view.truncated ? (0, react.createElement)("p", { "data-role": "note" }, t("loading.failed")) : null, failed === void 0 ? null : (0, react.createElement)("p", { "data-role": "note" }, t("read.failed") + " " + failed), view.rows.length === 0 ? (0, react.createElement)("p", { "data-role": "note" }, t("empty.noneReported")) : null, (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", {
209
+ key: row.name + ":unreadable",
210
+ "data-role": "note"
211
+ }, row.name + " — " + t("unreadable.note"))));
172
212
  }
173
213
  //#endregion
174
214
  //#region src/client/styles.ts
@@ -177,17 +217,56 @@ window.__ModuleLoader__.load({
177
217
  * the text itself and injects one tagged <style> element; the tag id keeps a hot reload from stacking
178
218
  * copies. Only the panel body is styled — the sidebar owns the row and its glyph slot.
179
219
  *
180
- * Every value comes from the generated family scale (`./tokens.ts`), so this file carries no literal.
220
+ * Token discipline (the family gate `verify-client-tokens --strict` enforces it): every `--evo-*` name
221
+ * below is DECLARED in `packages/scripts/client-tokens.json` — the family scale — and every `--dsw-*`
222
+ * name is a platform semantic alias (packages/client/ui-theme/src/styles). No type step, space, radius,
223
+ * hairline or colour is invented here, and no pixel geometry literal appears.
224
+ *
225
+ * Metrics are COPIED from the platform’s own components rather than invented, each cited here:
226
+ *
227
+ * - hover and current-row fills (`--dsw-alias-interactive-bg-hover`) follow the platform sidebar row
228
+ * (client/ui-sidebar/…/SidebarRoot.module.css), as the sibling panel does;
229
+ * - label steps (`--dsw-alias-label-primary/secondary/tertiary`) and the card hairline
230
+ * (`--evo-hairline-width`, the scale’s copy of the platform’s 0.5px card border) come from
231
+ * client/ui-chat/…/GenericCommandCard.module.css;
232
+ * - the control radius (`--evo-radius-control`, the scale’s copy of the platform field radius) comes
233
+ * from client/ui-primitives/Input.module.css; the capsule uses `--evo-radius-pill`;
234
+ * - verdict colours are the platform STATE aliases (state-success / state-warn / state-error) plus the
235
+ * scale’s `--evo-tone-quiet`; the validity bar is an inset shadow, not a border-width literal.
236
+ *
237
+ * Layout: four verdict cards, then the by-rule table as a grid (rule / skills / count) so numbers sit
238
+ * on tabular figures and long skill lists wrap into pills instead of ragged comma text.
181
239
  * @module @lmzhen/dsh-evolution-skill-health/client
182
240
  */
183
241
  /** Tag id for the injected <style>, so a hot reload replaces rather than stacks it. */
184
242
  const CSS_TAG_ID = "dsh-evolution-skill-health";
185
243
  /** The panel stylesheet text. */
186
244
  const CSS = [
187
- ":where(.dsh-evolution-skill-health) { display: flex; flex-direction: column; gap: var(--evo-space-8); padding: var(--evo-space-12); }",
188
245
  ":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 }",
189
- ":where(.dsh-evolution-skill-health [data-role=\"row\"]) { display: flex; align-items: baseline; justify-content: space-between; gap: var(--evo-space-8); border-bottom: var(--evo-hairline-width) solid var(--evo-tone-quiet); }",
190
- ":where(.dsh-evolution-skill-health [data-role=\"note\"]) { color: var(--evo-tone-quiet); }"
246
+ ":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); }",
247
+ ":where(.dsh-evolution-skill-health [data-role=\"hint\"]) { margin: 0; color: var(--dsw-alias-label-secondary); }",
248
+ ":where(.dsh-evolution-skill-health [data-role=\"stats\"]) { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--evo-space-8); }",
249
+ ":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-l); border-radius: var(--evo-radius-control); }",
250
+ ":where(.dsh-evolution-skill-health [data-role=\"stat\"][data-tone=\"ok\"]) { box-shadow: inset 2px 0 0 var(--dsw-alias-state-success-primary); }",
251
+ ":where(.dsh-evolution-skill-health [data-role=\"stat\"][data-tone=\"warn\"]) { box-shadow: inset 2px 0 0 var(--dsw-alias-state-warn-primary); }",
252
+ ":where(.dsh-evolution-skill-health [data-role=\"stat\"][data-tone=\"error\"]) { box-shadow: inset 2px 0 0 var(--dsw-alias-state-error-primary); }",
253
+ ":where(.dsh-evolution-skill-health [data-role=\"stat\"][data-tone=\"unreadable\"]) { box-shadow: inset 2px 0 0 var(--evo-tone-quiet); }",
254
+ ":where(.dsh-evolution-skill-health [data-role=\"stat-value\"]) { font-size: var(--evo-type-lg); font-variant-numeric: tabular-nums; line-height: 1; }",
255
+ ":where(.dsh-evolution-skill-health [data-role=\"stat-label\"]) { color: var(--dsw-alias-label-tertiary); }",
256
+ ":where(.dsh-evolution-skill-health [data-role=\"group-title\"]) { margin: 0; font-weight: 500; }",
257
+ ":where(.dsh-evolution-skill-health [data-role=\"rule-table\"]) { display: flex; flex-direction: column; border: var(--evo-hairline-width) solid var(--dsw-alias-border-l); border-radius: var(--evo-radius-control); overflow: hidden; }",
258
+ ":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-l); }",
259
+ ":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); }",
260
+ ":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); }",
261
+ ":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); }",
262
+ ":where(.dsh-evolution-skill-health [data-role=\"rule-row\"]:hover) { background: var(--dsw-alias-interactive-bg-hover); }",
263
+ ":where(.dsh-evolution-skill-health [data-role=\"rule-row\"]:focus-visible) { outline: var(--evo-focus-ring); outline-offset: var(--evo-focus-offset); }",
264
+ ":where(.dsh-evolution-skill-health [data-role=\"rule-head\"]) { border-top: 0; color: var(--dsw-alias-label-tertiary); }",
265
+ ":where(.dsh-evolution-skill-health [data-role=\"rule-name\"]) { overflow-wrap: anywhere; }",
266
+ ":where(.dsh-evolution-skill-health [data-role=\"rule-count\"]) { text-align: right; font-variant-numeric: tabular-nums; color: var(--dsw-alias-label-secondary); }",
267
+ ":where(.dsh-evolution-skill-health [data-role=\"skills\"]) { display: flex; flex-wrap: wrap; gap: var(--evo-space-4); }",
268
+ ":where(.dsh-evolution-skill-health [data-role=\"skill-pill\"]) { padding: 0 var(--evo-space-6); border: var(--evo-hairline-width) solid var(--dsw-alias-border-l); border-radius: var(--evo-radius-pill); color: var(--dsw-alias-label-secondary); white-space: nowrap; }",
269
+ ":where(.dsh-evolution-skill-health [data-role=\"note\"]) { margin: 0; color: var(--dsw-alias-label-secondary); }"
191
270
  ].join(String.fromCharCode(10));
192
271
  //#endregion
193
272
  //#region src/client/index.ts
@@ -254,6 +333,15 @@ window.__ModuleLoader__.load({
254
333
  return fallback;
255
334
  }
256
335
  };
336
+ const t = (key) => {
337
+ const fallback = zh[key] ?? key;
338
+ if (!registered) return fallback;
339
+ try {
340
+ return seam.locale.bind(NS)(key);
341
+ } catch {
342
+ return fallback;
343
+ }
344
+ };
257
345
  seam.slots.inject("sidebar.panellist", function* () {
258
346
  yield seam.slots.register({
259
347
  name: "sidebar.panellist",
@@ -267,12 +355,10 @@ window.__ModuleLoader__.load({
267
355
  inject: () => ({
268
356
  rows: [],
269
357
  truncated: false,
270
- loadRows: rows
358
+ loadRows: rows,
359
+ t
271
360
  })
272
- }, (props) => (0, react.createElement)(SkillHealthPanel, {
273
- face: props.face,
274
- locale: seam.locale
275
- }));
361
+ }, SkillHealthPanel);
276
362
  });
277
363
  }
278
364
  //#endregion
@@ -9,7 +9,6 @@
9
9
  */
10
10
  import { type ReactNode } from 'react';
11
11
  import type { HealthOutcome } from './api.ts';
12
- import type { LocaleSeat } from './seam.ts';
13
12
  /** One finding, as the host reports it: `line` is null when the position cannot be named. */
14
13
  export interface HealthFinding {
15
14
  rule: string;
@@ -33,14 +32,13 @@ export interface HealthFace {
33
32
  openHistory?: (name: string) => void;
34
33
  /** Reads the scan result from the host route. Absent faces render their seeded rows unchanged. */
35
34
  loadRows?: () => Promise<HealthOutcome>;
35
+ /** The seat's translator for this bundle's dictionary — the panel never reaches for a service. */
36
+ t: (key: string) => string;
36
37
  }
37
38
  /**
38
39
  * Render the panel.
39
40
  * @param props - the face (`{ rows, truncated, openHistory? }`) and the locale seat's translator.
40
41
  * @returns the panel body.
41
42
  */
42
- export declare function SkillHealthPanel(props: {
43
- face: HealthFace;
44
- locale: LocaleSeat;
45
- }): ReactNode;
43
+ export declare function SkillHealthPanel(face: HealthFace): ReactNode;
46
44
  //# sourceMappingURL=Panel.d.ts.map
@@ -3,7 +3,25 @@
3
3
  * the text itself and injects one tagged <style> element; the tag id keeps a hot reload from stacking
4
4
  * copies. Only the panel body is styled — the sidebar owns the row and its glyph slot.
5
5
  *
6
- * Every value comes from the generated family scale (`./tokens.ts`), so this file carries no literal.
6
+ * Token discipline (the family gate `verify-client-tokens --strict` enforces it): every `--evo-*` name
7
+ * below is DECLARED in `packages/scripts/client-tokens.json` — the family scale — and every `--dsw-*`
8
+ * name is a platform semantic alias (packages/client/ui-theme/src/styles). No type step, space, radius,
9
+ * hairline or colour is invented here, and no pixel geometry literal appears.
10
+ *
11
+ * Metrics are COPIED from the platform’s own components rather than invented, each cited here:
12
+ *
13
+ * - hover and current-row fills (`--dsw-alias-interactive-bg-hover`) follow the platform sidebar row
14
+ * (client/ui-sidebar/…/SidebarRoot.module.css), as the sibling panel does;
15
+ * - label steps (`--dsw-alias-label-primary/secondary/tertiary`) and the card hairline
16
+ * (`--evo-hairline-width`, the scale’s copy of the platform’s 0.5px card border) come from
17
+ * client/ui-chat/…/GenericCommandCard.module.css;
18
+ * - the control radius (`--evo-radius-control`, the scale’s copy of the platform field radius) comes
19
+ * from client/ui-primitives/Input.module.css; the capsule uses `--evo-radius-pill`;
20
+ * - verdict colours are the platform STATE aliases (state-success / state-warn / state-error) plus the
21
+ * scale’s `--evo-tone-quiet`; the validity bar is an inset shadow, not a border-width literal.
22
+ *
23
+ * Layout: four verdict cards, then the by-rule table as a grid (rule / skills / count) so numbers sit
24
+ * on tabular figures and long skill lists wrap into pills instead of ragged comma text.
7
25
  * @module @lmzhen/dsh-evolution-skill-health/client
8
26
  */
9
27
  /** Tag id for the injected <style>, so a hot reload replaces rather than stacks it. */
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  ]
12
12
  }
13
13
  },
14
- "version": "0.25.5",
14
+ "version": "0.25.7",
15
15
  "publishConfig": {
16
16
  "access": "public"
17
17
  },
@@ -43,13 +43,13 @@
43
43
  "@deepseek-ai/cordis": "^4.0.1",
44
44
  "@deepseek-ai/dsh-host-webserver": "^0.2.0-rc.1",
45
45
  "react": "^18.2.0",
46
- "@lmzhen/dsh-evolution-curator": "^0.25.5"
46
+ "@lmzhen/dsh-evolution-curator": "^0.25.7"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@deepseek-ai/cordis": "^4.0.1",
50
50
  "@deepseek-ai/dsh-host-webserver": "^0.2.0-rc.1",
51
51
  "@types/react": "~18.3.1",
52
52
  "react": "^18.2.0",
53
- "@lmzhen/dsh-evolution-curator": "^0.25.5"
53
+ "@lmzhen/dsh-evolution-curator": "^0.25.7"
54
54
  }
55
55
  }