@lmzhen/dsh-evolution-skill-health 0.25.3 → 0.25.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/lib/client.js CHANGED
@@ -5,6 +5,36 @@ 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 src/client/api.ts
9
+ /** Route paths, mirrored from the host half. */
10
+ const HOST_ROUTES = { rows: "/api/dsh-evolution/skill-health/rows" };
11
+ /**
12
+ * Read the scan result.
13
+ * @returns the payload, or the host's own refusal sentence.
14
+ */
15
+ async function rows() {
16
+ try {
17
+ const response = await fetch(HOST_ROUTES.rows, { headers: { accept: "application/json" } });
18
+ const body = await response.json();
19
+ if (!response.ok) return {
20
+ ok: false,
21
+ message: body.message ?? "skill-health: the route refused the request"
22
+ };
23
+ return {
24
+ ok: true,
25
+ payload: {
26
+ rows: body.rows ?? [],
27
+ truncated: body.truncated === true
28
+ }
29
+ };
30
+ } catch (error) {
31
+ return {
32
+ ok: false,
33
+ message: "skill-health: " + String(error)
34
+ };
35
+ }
36
+ }
37
+ //#endregion
8
38
  //#region src/client/seam.ts
9
39
  /** The panel id. The row and the `main` body MUST share it: the sidebar resolves the body by it. */
10
40
  const PANEL_ID = "skill-health";
@@ -116,10 +146,50 @@ window.__ModuleLoader__.load({
116
146
  */
117
147
  function SkillHealthPanel(props) {
118
148
  const t = props.locale.bind("evolution-skill-health");
119
- const counts = new Map(KINDS.map((kind) => [kind, props.face.rows.filter((row) => row.verdict === kind).length]));
120
- 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))), props.face.truncated ? (0, react.createElement)("p", null, t("loading.failed")) : null, props.face.rows.length === 0 ? (0, react.createElement)("p", null, t("empty.noneReported")) : null, (0, react.createElement)("h3", null, t("group.byRule")), ruleTable(props.face, t), props.face.rows.filter((row) => row.verdict === "unreadable").map((row) => (0, react.createElement)("p", { key: row.name + ":unreadable" }, row.name + " — " + t("unreadable.note"))));
149
+ const [face, setFace] = (0, react.useState)(props.face);
150
+ const [failed, setFailed] = (0, react.useState)(void 0);
151
+ (0, react.useEffect)(() => {
152
+ const load = props.face.loadRows;
153
+ if (load === void 0) return;
154
+ let live = true;
155
+ load().then((outcome) => {
156
+ if (!live) return;
157
+ if (outcome.ok) setFace({
158
+ ...props.face,
159
+ rows: outcome.payload.rows,
160
+ truncated: outcome.payload.truncated
161
+ });
162
+ else setFailed(outcome.message);
163
+ }).catch((error) => {
164
+ if (live) setFailed(String(error));
165
+ });
166
+ return () => {
167
+ live = false;
168
+ };
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"))));
121
172
  }
122
173
  //#endregion
174
+ //#region src/client/styles.ts
175
+ /**
176
+ * The panel stylesheet. The bundle is built outside the platform CSS-Modules pipeline, so it carries
177
+ * the text itself and injects one tagged <style> element; the tag id keeps a hot reload from stacking
178
+ * copies. Only the panel body is styled — the sidebar owns the row and its glyph slot.
179
+ *
180
+ * Every value comes from the generated family scale (`./tokens.ts`), so this file carries no literal.
181
+ * @module @lmzhen/dsh-evolution-skill-health/client
182
+ */
183
+ /** Tag id for the injected <style>, so a hot reload replaces rather than stacks it. */
184
+ const CSS_TAG_ID = "dsh-evolution-skill-health";
185
+ /** The panel stylesheet text. */
186
+ const CSS = [
187
+ ":where(.dsh-evolution-skill-health) { display: flex; flex-direction: column; gap: var(--evo-space-8); padding: var(--evo-space-12); }",
188
+ ":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); }"
191
+ ].join(String.fromCharCode(10));
192
+ //#endregion
123
193
  //#region src/client/index.ts
124
194
  /** Required client services: the slot registry and the locale seat. */
125
195
  const inject = ["slots", "locale"];
@@ -127,6 +197,26 @@ window.__ModuleLoader__.load({
127
197
  * Register the row and the body.
128
198
  * @param ctx - the client context; it must carry the two seats above.
129
199
  */
200
+ /**
201
+ * The row glyph. The sidebar hands every `sidebar.panellist` occupant `{ size, active }` and owns the
202
+ * slot around it, so this is a plain line glyph drawn in `currentColor`.
203
+ * @param props - the seat-provided size and active flag.
204
+ * @returns the glyph element.
205
+ */
206
+ function PanelIcon(props = {}) {
207
+ const size = typeof props.size === "number" && props.size > 0 ? props.size : 16;
208
+ return (0, react.createElement)("svg", {
209
+ "aria-hidden": "true",
210
+ width: String(size),
211
+ height: String(size),
212
+ viewBox: "0 0 16 16",
213
+ fill: "none",
214
+ stroke: "currentColor",
215
+ strokeWidth: "1.5",
216
+ strokeLinecap: "round",
217
+ strokeLinejoin: "round"
218
+ }, (0, react.createElement)("path", { d: "M2 8h3l1.5-3.5L9 11.5 10.5 8H14" }));
219
+ }
130
220
  function apply(ctx) {
131
221
  const seam = ctx;
132
222
  let registered = false;
@@ -143,6 +233,18 @@ window.__ModuleLoader__.load({
143
233
  return () => {};
144
234
  }
145
235
  }, "evolution-skill-health: locale");
236
+ ctx.effect(() => {
237
+ if (document.querySelector("style[data-plugin-css=\"dsh-evolution-skill-health\"]") === null) {
238
+ const tag = document.createElement("style");
239
+ tag.setAttribute("data-plugin-css", CSS_TAG_ID);
240
+ tag.textContent = CSS;
241
+ document.head.append(tag);
242
+ return () => {
243
+ tag.remove();
244
+ };
245
+ }
246
+ return () => {};
247
+ }, "evolution-skill-health: styles");
146
248
  const label = () => {
147
249
  const fallback = zh["entry.label"] ?? "技能健康";
148
250
  if (!registered) return fallback;
@@ -158,13 +260,14 @@ window.__ModuleLoader__.load({
158
260
  id: PANEL_ID,
159
261
  order: 40,
160
262
  label
161
- }, () => (0, react.createElement)("span", null, label()));
263
+ }, PanelIcon);
162
264
  yield seam.slots.register({
163
265
  name: "main",
164
266
  key: PANEL_ID,
165
267
  inject: () => ({
166
268
  rows: [],
167
- truncated: false
269
+ truncated: false,
270
+ loadRows: rows
168
271
  })
169
272
  }, (props) => (0, react.createElement)(SkillHealthPanel, {
170
273
  face: props.face,
@@ -8,6 +8,7 @@
8
8
  * @module @lmzhen/dsh-evolution-skill-health/client
9
9
  */
10
10
  import { type ReactNode } from 'react';
11
+ import type { HealthOutcome } from './api.ts';
11
12
  import type { LocaleSeat } from './seam.ts';
12
13
  /** One finding, as the host reports it: `line` is null when the position cannot be named. */
13
14
  export interface HealthFinding {
@@ -30,6 +31,8 @@ export interface HealthFace {
30
31
  truncated: boolean;
31
32
  /** Opens the skill-history panel for one skill, when that panel is present. */
32
33
  openHistory?: (name: string) => void;
34
+ /** Reads the scan result from the host route. Absent faces render their seeded rows unchanged. */
35
+ loadRows?: () => Promise<HealthOutcome>;
33
36
  }
34
37
  /**
35
38
  * Render the panel.
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The host route this bundle reads, and the ONE place a request is made.
3
+ *
4
+ * The path mirrors the host half's `ROUTES.rows` in `src/routes.ts` (a spec asserts the two sides
5
+ * agree). A refusal arrives as `ok:false` with the host's own sentence, so the panel shows what the
6
+ * route actually said instead of inventing a second vocabulary.
7
+ * @module @lmzhen/dsh-evolution-skill-health/client
8
+ */
9
+ import type { HealthRow } from './Panel.ts';
10
+ /** Route paths, mirrored from the host half. */
11
+ export declare const HOST_ROUTES: {
12
+ readonly rows: "/api/dsh-evolution/skill-health/rows";
13
+ };
14
+ /** One scan result, as the route reports it. */
15
+ export interface HealthPayload {
16
+ rows: HealthRow[];
17
+ /** True when the host could not finish the scan: the counts below are then a lower bound. */
18
+ truncated: boolean;
19
+ }
20
+ /** Either the payload or the host's refusal sentence — never a thrown error the panel must guess at. */
21
+ export type HealthOutcome = {
22
+ ok: true;
23
+ payload: HealthPayload;
24
+ } | {
25
+ ok: false;
26
+ message: string;
27
+ };
28
+ /**
29
+ * Read the scan result.
30
+ * @returns the payload, or the host's own refusal sentence.
31
+ */
32
+ export declare function rows(): Promise<HealthOutcome>;
33
+ //# sourceMappingURL=api.d.ts.map
@@ -12,9 +12,5 @@ import { type HealthFace } from './Panel.ts';
12
12
  export declare const inject: string[];
13
13
  /** The host half hands the panel a way to read its rows (a read-only route on the host side). */
14
14
  export type HealthFaceLoader = () => Promise<HealthFace>;
15
- /**
16
- * Register the row and the body.
17
- * @param ctx - the client context; it must carry the two seats above.
18
- */
19
15
  export declare function apply(ctx: ClientContext): void;
20
16
  //# sourceMappingURL=index.d.ts.map
@@ -40,7 +40,7 @@ export interface LocaleSeat {
40
40
  /** The slot registry: a generator body may register (and is disposed when it unmounts). */
41
41
  export interface SlotsSeat {
42
42
  inject(name: string, body: () => Generator<() => void, void, void>): void;
43
- register(options: PanelRowOptions | PanelBodyOptions, component: (props: unknown) => ReactNode): () => void;
43
+ register(options: PanelRowOptions | PanelBodyOptions, component: (props: never) => ReactNode): () => void;
44
44
  }
45
45
  /** Everything this bundle asks the shell for. */
46
46
  export interface ClientSeam {
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The panel stylesheet. The bundle is built outside the platform CSS-Modules pipeline, so it carries
3
+ * the text itself and injects one tagged <style> element; the tag id keeps a hot reload from stacking
4
+ * copies. Only the panel body is styled — the sidebar owns the row and its glyph slot.
5
+ *
6
+ * Every value comes from the generated family scale (`./tokens.ts`), so this file carries no literal.
7
+ * @module @lmzhen/dsh-evolution-skill-health/client
8
+ */
9
+ /** Tag id for the injected <style>, so a hot reload replaces rather than stacks it. */
10
+ export declare const CSS_TAG_ID = "dsh-evolution-skill-health";
11
+ /** The panel stylesheet text. */
12
+ export declare const CSS: string;
13
+ //# sourceMappingURL=styles.d.ts.map
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  ]
12
12
  }
13
13
  },
14
- "version": "0.25.3",
14
+ "version": "0.25.5",
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.3"
46
+ "@lmzhen/dsh-evolution-curator": "^0.25.5"
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.3"
53
+ "@lmzhen/dsh-evolution-curator": "^0.25.5"
54
54
  }
55
55
  }