@lmzhen/dsh-evolution-merge-ui 0.0.0-stage → 0.28.0

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,3 +1,12 @@
1
- # Temporary Holding Version
1
+ # @lmzhen/dsh-evolution-merge-ui
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 技能合并的只读浏览器面板:三队列计数、行列表与刷新;动作只展示为命令文本,不在浏览器写技能。
4
+
5
+ ## 数据面
6
+
7
+ 读取 `/api/dsh-evolution/merge/view` 与 `/api/dsh-evolution/merge/rows`,走生成的路由表和 route-client。失败按四态显示,不伪造空成功。
8
+
9
+ ## 已知限制
10
+
11
+ - 只读;approve/reject 等动作需复制命令执行。
12
+ - 面板仅在 `evolution-merge` host 行与其 bundle 同时挂载时可用。
package/lib/client.js ADDED
@@ -0,0 +1,442 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@lmzhen/dsh-evolution-merge-ui",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ //#region src/client/routes.generated.ts
9
+ /**
10
+ * Every family host path, generated from evolution-core's HOST_ROUTES (the ONE declaration).
11
+ * A browser half cannot import the host package at runtime (client bundles keep @deepseek-ai/*
12
+ * external), so the table is mirrored here mechanically; a rename in core regenerates all three
13
+ * client copies, and the generator's --check makes a stale copy red.
14
+ */
15
+ const ROUTES = {
16
+ approval: {
17
+ pending: "/api/dsh-evolution/approval/pending",
18
+ preview: "/api/dsh-evolution/approval/preview",
19
+ approve: "/api/dsh-evolution/approval/approve",
20
+ reject: "/api/dsh-evolution/approval/reject"
21
+ },
22
+ "skill-history": {
23
+ skills: "/api/dsh-evolution/skill-history/skills",
24
+ versions: "/api/dsh-evolution/skill-history/versions",
25
+ diff: "/api/dsh-evolution/skill-history/versions/diff",
26
+ body: "/api/dsh-evolution/skill-history/versions/body",
27
+ undo: "/api/dsh-evolution/skill-history/undo",
28
+ health: "/api/dsh-evolution/skill-history/health"
29
+ },
30
+ "skill-health": { rows: "/api/dsh-evolution/skill-health/rows" },
31
+ merge: {
32
+ view: "/api/dsh-evolution/merge/view",
33
+ rows: "/api/dsh-evolution/merge/rows",
34
+ run: "/api/dsh-evolution/merge/run",
35
+ runs: "/api/dsh-evolution/merge/runs"
36
+ },
37
+ diagnostics: { view: "/api/dsh-evolution/diagnostics" }
38
+ };
39
+ //#endregion
40
+ //#region src/client/route-client.generated.ts
41
+ /** Classify a non-2xx status: 401/403/404 keep their own kind, everything else is a server answer. */
42
+ function classify(status) {
43
+ if (status === 401) return {
44
+ kind: "unauthenticated",
45
+ status: 401
46
+ };
47
+ if (status === 403) return {
48
+ kind: "forbidden",
49
+ status: 403
50
+ };
51
+ if (status === 404) return {
52
+ kind: "unmounted",
53
+ status: 404
54
+ };
55
+ return {
56
+ kind: "server",
57
+ status
58
+ };
59
+ }
60
+ /**
61
+ * One request, no retries and no body parsing for the platform's admission answers.
62
+ * @param doFetch - the fetch in use (the browser's, or a spec's stub).
63
+ * @param path - the exact route path from the generated ROUTES table.
64
+ * @param init - standard RequestInit.
65
+ * @returns the data, or the classified failure (network/parse failures read as 'server').
66
+ */
67
+ async function requestJson(doFetch, path, init) {
68
+ let response;
69
+ try {
70
+ response = await doFetch(path, init);
71
+ } catch (error) {
72
+ return {
73
+ ok: false,
74
+ failure: {
75
+ kind: "server",
76
+ status: 0,
77
+ detail: error instanceof Error ? error.message : String(error)
78
+ }
79
+ };
80
+ }
81
+ if (!response.ok) return {
82
+ ok: false,
83
+ failure: classify(response.status)
84
+ };
85
+ let body;
86
+ try {
87
+ body = await response.json();
88
+ } catch {
89
+ return {
90
+ ok: false,
91
+ failure: {
92
+ kind: "server",
93
+ status: response.status
94
+ }
95
+ };
96
+ }
97
+ if (typeof body === "object" && body !== null) {
98
+ const record = body;
99
+ if (record.ok === true) return {
100
+ ok: true,
101
+ data: record.data
102
+ };
103
+ if (record.ok === false && typeof record.message === "string" && record.message !== "") return {
104
+ ok: false,
105
+ failure: {
106
+ kind: "refused",
107
+ code: typeof record.code === "string" && record.code !== "" ? record.code : "refused",
108
+ message: record.message
109
+ }
110
+ };
111
+ }
112
+ return {
113
+ ok: false,
114
+ failure: {
115
+ kind: "server",
116
+ status: response.status
117
+ }
118
+ };
119
+ }
120
+ //#endregion
121
+ //#region src/client/api.ts
122
+ /**
123
+ * The two read routes this panel uses, through the generated client route table and the
124
+ * shared route client. A refusal keeps the host's own sentence.
125
+ */
126
+ const HOST_ROUTES = ROUTES.merge;
127
+ async function view() {
128
+ const answer = await requestJson(fetch, HOST_ROUTES.view, { headers: { accept: "application/json" } });
129
+ return answer.ok ? {
130
+ ok: true,
131
+ payload: answer.data
132
+ } : {
133
+ ok: false,
134
+ failure: answer.failure
135
+ };
136
+ }
137
+ async function rows(queue, cursor = 0) {
138
+ const url = HOST_ROUTES.rows + "?queue=" + encodeURIComponent(queue) + "&cursor=" + String(cursor);
139
+ const answer = await requestJson(fetch, url, { headers: { accept: "application/json" } });
140
+ return answer.ok ? {
141
+ ok: true,
142
+ payload: answer.data
143
+ } : {
144
+ ok: false,
145
+ failure: answer.failure
146
+ };
147
+ }
148
+ //#endregion
149
+ //#region src/client/seam.ts
150
+ const PANEL_ID = "merge";
151
+ //#endregion
152
+ //#region src/client/messages.ts
153
+ const NS = "evolution-merge";
154
+ const zh = {
155
+ "entry.label": "技能合并",
156
+ "hint": "手动触发;后台逐对判定。这里只读,不写技能。",
157
+ "tab.pending": "待批准",
158
+ "tab.retry": "待重跑",
159
+ "tab.skipped": "无需合并",
160
+ "empty.pending": "没有待批准的合并。",
161
+ "empty.retry": "没有待重跑的对。",
162
+ "empty.skipped": "没有标记为无需合并的对。",
163
+ "loading": "正在读取…",
164
+ "refresh": "刷新",
165
+ "failed": "读取失败,保留上次结果。",
166
+ "stage.decide": "判定",
167
+ "stage.author": "合并",
168
+ "stage.validate": "检查",
169
+ "stage.commit": "提交"
170
+ };
171
+ const en = {
172
+ "entry.label": "Skill merge",
173
+ "hint": "Manual only; pairs are judged in the background. Read-only here.",
174
+ "tab.pending": "Pending",
175
+ "tab.retry": "Retry",
176
+ "tab.skipped": "Skipped",
177
+ "empty.pending": "No pending merges.",
178
+ "empty.retry": "No pairs waiting to retry.",
179
+ "empty.skipped": "No pairs marked as not worth merging.",
180
+ "loading": "Loading…",
181
+ "refresh": "Refresh",
182
+ "failed": "Read failed; keeping the last result.",
183
+ "stage.decide": "Decide",
184
+ "stage.author": "Author",
185
+ "stage.validate": "Validate",
186
+ "stage.commit": "Commit"
187
+ };
188
+ //#endregion
189
+ //#region src/client/styles.ts
190
+ const CSS_TAG_ID = "evolution-merge-panel";
191
+ const CSS = [
192
+ ".evolution-merge-panel { display:flex; flex-direction:column; gap:var(--evo-space-12); padding:var(--evo-space-16); }",
193
+ ".evolution-merge-panel [data-role=\"hint\"] { margin:0; color:var(--dsw-alias-label-tertiary); font-size:var(--evo-type-xs); }",
194
+ ".evolution-merge-panel [data-role=\"stats\"] { display:flex; gap:var(--evo-space-8); flex-wrap:wrap; }",
195
+ ".evolution-merge-panel [data-role=\"stat\"] { border:var(--evo-hairline-width) solid var(--dsw-alias-border-l2); border-radius:var(--evo-radius-control); padding:var(--evo-space-6) var(--evo-space-10); min-width:calc(var(--evo-space-16) * 4); }",
196
+ ".evolution-merge-panel [data-role=\"stat-value\"] { font-size:var(--evo-type-lg); font-weight:600; }",
197
+ ".evolution-merge-panel [data-role=\"stat-label\"] { font-size:var(--evo-type-xs); color:var(--dsw-alias-label-tertiary); }",
198
+ ".evolution-merge-panel [data-role=\"tabs\"] { display:flex; gap:var(--evo-space-6); border-bottom:var(--evo-hairline-width) solid var(--dsw-alias-border-l2); }",
199
+ ".evolution-merge-panel [data-role=\"tab\"] { background:none; border:0; border-bottom:var(--evo-hairline-width) solid transparent; padding:var(--evo-space-6) var(--evo-space-8); cursor:pointer; color:inherit; }",
200
+ ".evolution-merge-panel [data-role=\"tab\"][data-active=\"true\"] { border-bottom-color:var(--dsw-alias-brand-primary); }",
201
+ ".evolution-merge-panel [data-role=\"rows\"] { display:flex; flex-direction:column; gap:var(--evo-space-6); }",
202
+ ".evolution-merge-panel [data-role=\"row\"] { display:grid; grid-template-columns:1fr auto; gap:var(--evo-space-8); padding:var(--evo-space-8); border:var(--evo-hairline-width) solid var(--dsw-alias-border-l2); border-radius:var(--evo-radius-control); }",
203
+ ".evolution-merge-panel [data-role=\"row-label\"] { font-weight:600; }",
204
+ ".evolution-merge-panel [data-role=\"row-meta\"] { color:var(--dsw-alias-label-tertiary); font-size:var(--evo-type-xs); }",
205
+ ".evolution-merge-panel [data-role=\"note\"] { color:var(--dsw-alias-label-tertiary); font-size:var(--evo-type-xs); }",
206
+ ".evolution-merge-panel [data-role=\"refresh\"] { align-self:flex-start; }"
207
+ ].join("\n");
208
+ //#endregion
209
+ //#region src/client/states.generated.ts
210
+ /** A neutral/ warn/ fail block; a fail block is role=alert so assistive tech announces it. */
211
+ function Block(props) {
212
+ const tone = props.tone ?? "neutral";
213
+ return (0, react.createElement)("div", {
214
+ "data-tone": tone,
215
+ ...tone === "fail" ? { role: "alert" } : {}
216
+ }, props.title === void 0 ? null : (0, react.createElement)("div", { "data-tone-title": tone }, props.title), props.children);
217
+ }
218
+ /** One classified failure as a reader-facing sentence. */
219
+ function failureText(failure, t) {
220
+ switch (failure.kind) {
221
+ case "unauthenticated": return t("failureUnauthorized");
222
+ case "forbidden": return t("failureForbidden");
223
+ case "unmounted": return t("failureUnmounted");
224
+ case "server": return failure.status === 0 ? t("failureServer") : t("failureServer") + " (HTTP " + String(failure.status) + ")";
225
+ case "refused": return failure.message;
226
+ }
227
+ }
228
+ /** The failed-state block: sentence + (when the surface exists but is unreachable) the command fallback. */
229
+ function FailureBlock(props) {
230
+ const hint = props.fallback === void 0 ? null : (0, react.createElement)("div", { "data-tone-hint": "true" }, props.t("fallbackHint", { command: props.fallback.command }));
231
+ return Block({
232
+ tone: "fail",
233
+ title: failureText(props.failure, props.t),
234
+ children: hint
235
+ });
236
+ }
237
+ //#endregion
238
+ //#region src/client/fallbacks.generated.ts
239
+ /**
240
+ * The command-face fallback for every Host surface, generated from evolution-commands'
241
+ * COMMAND_FALLBACKS (the ONE home). When the platform's connection carrier is absent (E-325) the
242
+ * panels render this hint instead of a bare HTTP failure — same use case, command face.
243
+ */
244
+ const FALLBACKS = {
245
+ approval: { command: "/evolution pending … /evolution approve <id> … /evolution reject <id> … /evolution release <id>" },
246
+ "skill-history": { command: "/evolution restore <name> [v](查看:/evolution skills history <name>)" },
247
+ "skill-health": { command: "/evolution skills refresh(健康行:/evolution doctor)" },
248
+ merge: { command: "/evolution merge status / pending / approve <id> / report" },
249
+ diagnostics: {
250
+ command: "/evolution doctor",
251
+ platformCli: "dsh --json /evolution doctor"
252
+ }
253
+ };
254
+ //#endregion
255
+ //#region src/client/Panel.ts
256
+ /**
257
+ * The merge panel body: three queues, counts, and a read-only row list. Actions are
258
+ * intentionally copied commands, not buttons that write from the browser.
259
+ */
260
+ const QUEUES = [
261
+ "pending",
262
+ "retry",
263
+ "skipped"
264
+ ];
265
+ function failure(t, failure) {
266
+ return FailureBlock({
267
+ failure,
268
+ t,
269
+ fallback: FALLBACKS["merge"]
270
+ });
271
+ }
272
+ function MergePanel(face) {
273
+ const t = face.t;
274
+ const [view, setView] = (0, react.useState)(face.view);
275
+ const [tab, setTab] = (0, react.useState)("pending");
276
+ const [rows, setRows] = (0, react.useState)(face.rows);
277
+ const [loading, setLoading] = (0, react.useState)(false);
278
+ const [failed, setFailed] = (0, react.useState)(void 0);
279
+ const loadRows = (queue) => {
280
+ const load = face.loadRows;
281
+ if (load === void 0) return;
282
+ setLoading(true);
283
+ load(queue).then((outcome) => {
284
+ if (outcome.ok) {
285
+ setRows(outcome.payload.items);
286
+ setFailed(void 0);
287
+ } else setFailed(outcome.failure);
288
+ }).catch(() => {
289
+ setFailed({
290
+ kind: "server",
291
+ status: 0
292
+ });
293
+ }).finally(() => {
294
+ setLoading(false);
295
+ });
296
+ };
297
+ const loadView = () => {
298
+ const load = face.loadView;
299
+ if (load === void 0) return;
300
+ load().then((outcome) => {
301
+ if (outcome.ok) setView(outcome.payload);
302
+ }).catch(() => {});
303
+ };
304
+ (0, react.useEffect)(() => {
305
+ loadView();
306
+ loadRows("pending");
307
+ }, [face]);
308
+ (0, react.useEffect)(() => {
309
+ loadRows(tab);
310
+ }, [tab]);
311
+ const counts = view.counts;
312
+ const stats = (0, react.createElement)("div", { "data-role": "stats" }, QUEUES.map((queue) => (0, react.createElement)("div", {
313
+ key: queue,
314
+ "data-role": "stat",
315
+ "data-queue": queue
316
+ }, (0, react.createElement)("div", { "data-role": "stat-value" }, String(counts[queue])), (0, react.createElement)("div", { "data-role": "stat-label" }, t("tab." + queue)))));
317
+ const tabs = (0, react.createElement)("div", { "data-role": "tabs" }, QUEUES.map((queue) => (0, react.createElement)("button", {
318
+ key: queue,
319
+ type: "button",
320
+ "data-role": "tab",
321
+ "data-active": tab === queue ? "true" : "false",
322
+ onClick: () => {
323
+ setTab(queue);
324
+ }
325
+ }, t("tab." + queue) + " " + String(counts[queue]))));
326
+ const list = rows.length === 0 ? (0, react.createElement)("p", { "data-role": "note" }, t("empty." + tab)) : (0, react.createElement)("div", { "data-role": "rows" }, rows.map((row) => (0, react.createElement)("div", {
327
+ key: row.id,
328
+ "data-role": "row"
329
+ }, (0, react.createElement)("div", null, (0, react.createElement)("div", { "data-role": "row-label" }, row.label), (0, react.createElement)("div", { "data-role": "row-meta" }, row.state + (row.stage === null ? "" : " · " + t("stage." + row.stage)))), (0, react.createElement)("div", { "data-role": "row-meta" }, row.updatedAt))));
330
+ return (0, react.createElement)("section", { className: CSS_TAG_ID }, (0, react.createElement)("p", { "data-role": "hint" }, t("hint")), stats, tabs, loading ? (0, react.createElement)("p", { "data-role": "note" }, t("loading")) : null, failed === void 0 ? null : (0, react.createElement)("div", { "data-role": "note" }, failure(t, failed)), list, (0, react.createElement)("button", {
331
+ type: "button",
332
+ "data-role": "refresh",
333
+ onClick: () => {
334
+ loadView();
335
+ loadRows(tab);
336
+ }
337
+ }, t("refresh")));
338
+ }
339
+ //#endregion
340
+ //#region src/client/index.ts
341
+ const inject = ["slots", "locale"];
342
+ function PanelIcon(props = {}) {
343
+ const size = typeof props.size === "number" && props.size > 0 ? props.size : 16;
344
+ return (0, react.createElement)("svg", {
345
+ "aria-hidden": "true",
346
+ width: String(size),
347
+ height: String(size),
348
+ viewBox: "0 0 16 16",
349
+ fill: "none",
350
+ stroke: "currentColor",
351
+ strokeWidth: "1.5",
352
+ strokeLinecap: "round",
353
+ strokeLinejoin: "round"
354
+ }, (0, react.createElement)("path", { d: "M3 4h10M3 8h6M3 12h8" }));
355
+ }
356
+ function seedView() {
357
+ return {
358
+ counts: {
359
+ pending: 0,
360
+ retry: 0,
361
+ skipped: 0
362
+ },
363
+ tabs: [
364
+ {
365
+ queue: "pending",
366
+ count: 0
367
+ },
368
+ {
369
+ queue: "retry",
370
+ count: 0
371
+ },
372
+ {
373
+ queue: "skipped",
374
+ count: 0
375
+ }
376
+ ],
377
+ commands: []
378
+ };
379
+ }
380
+ function apply(ctx) {
381
+ const seam = ctx;
382
+ let registered = false;
383
+ ctx.effect(() => {
384
+ try {
385
+ const dispose = seam.locale.register(NS, {
386
+ zh,
387
+ en
388
+ });
389
+ registered = true;
390
+ return dispose;
391
+ } catch (error) {
392
+ ctx.logger.warn("evolution-merge-ui: locale seat refused \"evolution-merge\" (" + String(error) + ")");
393
+ return () => {};
394
+ }
395
+ }, "evolution-merge-ui: locale");
396
+ ctx.effect(() => {
397
+ if (document.querySelector("style[data-plugin-css=\"evolution-merge-panel\"]") !== null) return () => {};
398
+ const tag = document.createElement("style");
399
+ tag.setAttribute("data-plugin-css", CSS_TAG_ID);
400
+ tag.textContent = CSS;
401
+ document.head.append(tag);
402
+ return () => {
403
+ tag.remove();
404
+ };
405
+ }, "evolution-merge-ui: styles");
406
+ const t = (key) => {
407
+ const fallback = zh[key] ?? key;
408
+ if (!registered) return fallback;
409
+ try {
410
+ return seam.locale.bind(NS)(key);
411
+ } catch {
412
+ return fallback;
413
+ }
414
+ };
415
+ const label = () => t("entry.label");
416
+ seam.slots.inject("sidebar.panellist", function* () {
417
+ yield seam.slots.register({
418
+ name: "sidebar.panellist",
419
+ id: PANEL_ID,
420
+ order: 45,
421
+ label
422
+ }, PanelIcon);
423
+ yield seam.slots.register({
424
+ name: "main",
425
+ key: PANEL_ID,
426
+ inject: () => ({
427
+ view: seedView(),
428
+ rows: [],
429
+ loadView: view,
430
+ loadRows: rows,
431
+ t
432
+ })
433
+ }, MergePanel);
434
+ });
435
+ }
436
+ //#endregion
437
+ exports.apply = apply;
438
+ exports.inject = inject;
439
+
440
+ return module.exports;
441
+ }
442
+ });
package/lib/index.js ADDED
@@ -0,0 +1,11 @@
1
+ //#region lib/types/index.js
2
+ /**
3
+ * Host half of the merge UI package: it registers nothing. The Loader discovers the
4
+ * `dsh.client` manifest and serves `./client` to the page; this entry exists so the row
5
+ * is a valid plugin.
6
+ */
7
+ const name = "evolution-merge-ui";
8
+ const inject = [];
9
+ function apply() {}
10
+ //#endregion
11
+ export { apply, inject, name };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The merge panel body: three queues, counts, and a read-only row list. Actions are
3
+ * intentionally copied commands, not buttons that write from the browser.
4
+ */
5
+ import { type ReactNode } from 'react';
6
+ import type { MergeOutcome, MergeQueue, MergeRowView, MergeViewPayload, QueuePage } from './api.ts';
7
+ export interface MergeFace {
8
+ view: MergeViewPayload;
9
+ rows: MergeRowView[];
10
+ loadView?: () => Promise<MergeOutcome<MergeViewPayload>>;
11
+ loadRows?: (queue: MergeQueue, cursor?: number) => Promise<MergeOutcome<QueuePage>>;
12
+ t: (key: string) => string;
13
+ }
14
+ export declare function MergePanel(face: MergeFace): ReactNode;
15
+ //# sourceMappingURL=Panel.d.ts.map
@@ -0,0 +1,47 @@
1
+ import { type RouteFailure } from './route-client.generated.ts';
2
+ export declare const HOST_ROUTES: {
3
+ readonly view: "/api/dsh-evolution/merge/view";
4
+ readonly rows: "/api/dsh-evolution/merge/rows";
5
+ readonly run: "/api/dsh-evolution/merge/run";
6
+ readonly runs: "/api/dsh-evolution/merge/runs";
7
+ };
8
+ export type MergeQueue = 'pending' | 'retry' | 'skipped';
9
+ export interface MergeCounts {
10
+ pending: number;
11
+ retry: number;
12
+ skipped: number;
13
+ }
14
+ export interface MergeViewPayload {
15
+ counts: MergeCounts;
16
+ tabs: {
17
+ queue: MergeQueue;
18
+ count: number;
19
+ }[];
20
+ commands: string[];
21
+ }
22
+ export interface MergeRowView {
23
+ id: string;
24
+ pairKey: string;
25
+ a: string;
26
+ b: string;
27
+ state: string;
28
+ queue: MergeQueue;
29
+ stage: string | null;
30
+ label: string;
31
+ updatedAt: string;
32
+ }
33
+ export interface QueuePage {
34
+ queue: MergeQueue;
35
+ items: MergeRowView[];
36
+ nextCursor?: string;
37
+ }
38
+ export type MergeOutcome<T> = {
39
+ ok: true;
40
+ payload: T;
41
+ } | {
42
+ ok: false;
43
+ failure: RouteFailure;
44
+ };
45
+ export declare function view(): Promise<MergeOutcome<MergeViewPayload>>;
46
+ export declare function rows(queue: MergeQueue, cursor?: number): Promise<MergeOutcome<QueuePage>>;
47
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Literal mirror of the frozen Host diagnostics contract (W1 §2.8 / core diagnostics.ts). The browser
3
+ * build keeps every @deepseek-ai/* specifier external, so the client CANNOT import the host type — the
4
+ * shape is mirrored mechanically here instead. The key unions are rendered from core diagnostics.ts by
5
+ * the generator, so a key change regenerates all three copies (a stale copy fails --check).
6
+ */
7
+ export type DiagnosticsSchemaVersion = 2;
8
+ export type DiagnosticsFactKey = 'platform-line' | 'family-version' | 'install-form' | 'preset-ids' | 'readonly-contract' | 'upgrade-order' | 'fallback-map';
9
+ export type DiagnosticsRuntimeKey = 'subagent-channel' | 'restricted-preset' | 'host-surface' | 'session-fold' | 'platform-services' | 'runs' | 'platform-pairing' | 'install-form-detected';
10
+ export type DiagnosticStatus = 'ok' | 'warn' | 'fail' | 'unknown';
11
+ export interface DocFactRow {
12
+ readonly key: DiagnosticsFactKey;
13
+ readonly label: string;
14
+ readonly value: string;
15
+ readonly home: string;
16
+ }
17
+ export interface DiagnosticRow {
18
+ readonly key: DiagnosticsRuntimeKey;
19
+ readonly label: string;
20
+ readonly status: DiagnosticStatus;
21
+ readonly detail?: string;
22
+ readonly remedy?: string;
23
+ }
24
+ export interface DiagnosticsView {
25
+ readonly schemaVersion: DiagnosticsSchemaVersion;
26
+ readonly generatedAt: string;
27
+ readonly facts: readonly DocFactRow[];
28
+ readonly runtime: readonly DiagnosticRow[];
29
+ }
30
+ /** The installation form the doctor reports (W8 install-form-detected row). */
31
+ export type InstallFormId = 'M1' | 'M2' | 'M3' | 'M4';
32
+ /** One surface's command-face path(s) — the client `fallbacks.generated.ts` table's row type. */
33
+ export interface FallbackHint {
34
+ readonly command: string;
35
+ readonly platformCli?: string;
36
+ }
37
+ //# sourceMappingURL=contracts.generated.d.ts.map
@@ -0,0 +1,8 @@
1
+ import type { FallbackHint } from './contracts.generated.ts';
2
+ /**
3
+ * The command-face fallback for every Host surface, generated from evolution-commands'
4
+ * COMMAND_FALLBACKS (the ONE home). When the platform's connection carrier is absent (E-325) the
5
+ * panels render this hint instead of a bare HTTP failure — same use case, command face.
6
+ */
7
+ export declare const FALLBACKS: Readonly<Record<'approval' | 'skill-history' | 'skill-health' | 'merge' | 'diagnostics', FallbackHint>>;
8
+ //# sourceMappingURL=fallbacks.generated.d.ts.map
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Browser half: one sidebar row ("技能合并") and the read-only main body it opens.
3
+ * All facts arrive through the generated routes; the bundle holds no service and writes nothing.
4
+ */
5
+ import type { Context as ClientContext } from '@deepseek-ai/cordis';
6
+ export declare const inject: string[];
7
+ export declare function apply(ctx: ClientContext): void;
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,4 @@
1
+ export declare const NS = "evolution-merge";
2
+ export declare const zh: Record<string, string>;
3
+ export declare const en: Record<string, string>;
4
+ //# sourceMappingURL=messages.d.ts.map
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The four-state transport classifier every family client shares (W7 §2). The platform owns
3
+ * admission (401/403/404 are its answers, not ours), so the client reads the STATUS only for those
4
+ * three and never tries to interpret a platform body. Everything the family route itself answers
5
+ * keeps its business envelope: 2xx + {ok:true,data} or 2xx + {ok:false,code,message}.
6
+ */
7
+ export type RouteFailure = {
8
+ readonly kind: 'unauthenticated';
9
+ readonly status: 401;
10
+ } | {
11
+ readonly kind: 'forbidden';
12
+ readonly status: 403;
13
+ } | {
14
+ readonly kind: 'unmounted';
15
+ readonly status: 404;
16
+ } | {
17
+ readonly kind: 'server';
18
+ readonly status: number;
19
+ readonly detail?: string;
20
+ } | {
21
+ readonly kind: 'refused';
22
+ readonly code: string;
23
+ readonly message: string;
24
+ };
25
+ /** One route answer: the data, or the classified failure. */
26
+ export type RouteAnswer<T> = {
27
+ readonly ok: true;
28
+ readonly data: T;
29
+ } | {
30
+ readonly ok: false;
31
+ readonly failure: RouteFailure;
32
+ };
33
+ /** Classify a non-2xx status: 401/403/404 keep their own kind, everything else is a server answer. */
34
+ export declare function classify(status: number): RouteFailure;
35
+ /**
36
+ * One request, no retries and no body parsing for the platform's admission answers.
37
+ * @param doFetch - the fetch in use (the browser's, or a spec's stub).
38
+ * @param path - the exact route path from the generated ROUTES table.
39
+ * @param init - standard RequestInit.
40
+ * @returns the data, or the classified failure (network/parse failures read as 'server').
41
+ */
42
+ export declare function requestJson<T>(doFetch: typeof fetch, path: string, init?: RequestInit): Promise<RouteAnswer<T>>;
43
+ /** The one-sentence rendering legacy callers attach to a classified failure. */
44
+ export declare function failureMessage(failure: RouteFailure): string;
45
+ //# sourceMappingURL=route-client.generated.d.ts.map
@@ -0,0 +1,37 @@
1
+ /** One host data-plane surface the browser half can call. */
2
+ export type SurfaceId = 'approval' | 'skill-history' | 'skill-health' | 'merge' | 'diagnostics';
3
+ /**
4
+ * Every family host path, generated from evolution-core's HOST_ROUTES (the ONE declaration).
5
+ * A browser half cannot import the host package at runtime (client bundles keep @deepseek-ai/*
6
+ * external), so the table is mirrored here mechanically; a rename in core regenerates all three
7
+ * client copies, and the generator's --check makes a stale copy red.
8
+ */
9
+ export declare const ROUTES: {
10
+ readonly approval: {
11
+ readonly pending: "/api/dsh-evolution/approval/pending";
12
+ readonly preview: "/api/dsh-evolution/approval/preview";
13
+ readonly approve: "/api/dsh-evolution/approval/approve";
14
+ readonly reject: "/api/dsh-evolution/approval/reject";
15
+ };
16
+ readonly 'skill-history': {
17
+ readonly skills: "/api/dsh-evolution/skill-history/skills";
18
+ readonly versions: "/api/dsh-evolution/skill-history/versions";
19
+ readonly diff: "/api/dsh-evolution/skill-history/versions/diff";
20
+ readonly body: "/api/dsh-evolution/skill-history/versions/body";
21
+ readonly undo: "/api/dsh-evolution/skill-history/undo";
22
+ readonly health: "/api/dsh-evolution/skill-history/health";
23
+ };
24
+ readonly 'skill-health': {
25
+ readonly rows: "/api/dsh-evolution/skill-health/rows";
26
+ };
27
+ readonly merge: {
28
+ readonly view: "/api/dsh-evolution/merge/view";
29
+ readonly rows: "/api/dsh-evolution/merge/rows";
30
+ readonly run: "/api/dsh-evolution/merge/run";
31
+ readonly runs: "/api/dsh-evolution/merge/runs";
32
+ };
33
+ readonly diagnostics: {
34
+ readonly view: "/api/dsh-evolution/diagnostics";
35
+ };
36
+ };
37
+ //# sourceMappingURL=routes.generated.d.ts.map
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Structural view of the client seams this bundle consumes. The package ships outside the
3
+ * platform repository, so it declares the seats instead of importing platform client sources.
4
+ */
5
+ import type { ReactNode } from 'react';
6
+ export declare const PANEL_ID = "merge";
7
+ export declare const PANEL_ORDER = 45;
8
+ export interface PanelRowOptions {
9
+ name: 'sidebar.panellist';
10
+ id: string;
11
+ order: number;
12
+ label: () => string;
13
+ }
14
+ export interface PanelBodyOptions {
15
+ name: 'main';
16
+ key: string;
17
+ inject: () => unknown;
18
+ }
19
+ export interface LocaleSeat {
20
+ register(namespace: string, dictionaries: {
21
+ zh: Record<string, string>;
22
+ en: Record<string, string>;
23
+ }): () => void;
24
+ bind(namespace: string): (key: string) => string;
25
+ }
26
+ export interface SlotsSeat {
27
+ inject(name: string, body: () => Generator<() => void, void, void>): void;
28
+ register(options: PanelRowOptions | PanelBodyOptions, component: (props: never) => ReactNode): () => void;
29
+ }
30
+ export interface ClientSeam {
31
+ slots: SlotsSeat;
32
+ locale: LocaleSeat;
33
+ }
34
+ //# sourceMappingURL=seam.d.ts.map
@@ -0,0 +1,43 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { RouteAnswer, RouteFailure } from './route-client.generated.ts';
3
+ import type { FallbackHint } from './contracts.generated.ts';
4
+ /**
5
+ * The four-state base of every family card (W7 §2). Load states are read from the generated
6
+ * route-client's answer — a card owns no HTTP vocabulary and no second state machine.
7
+ */
8
+ export type LoadState<T> = {
9
+ readonly phase: 'loading';
10
+ } | {
11
+ readonly phase: 'ready';
12
+ readonly data: T;
13
+ } | {
14
+ readonly phase: 'empty';
15
+ } | {
16
+ readonly phase: 'failed';
17
+ readonly failure: RouteFailure;
18
+ };
19
+ /** Map one classified answer onto the card's four states. */
20
+ export declare function loadStateOf<T>(answer: RouteAnswer<T>, isEmpty: (data: T) => boolean): LoadState<T>;
21
+ /** The locale lookup every block calls (the platform's own `t`). */
22
+ export interface Translate {
23
+ (key: string, values?: Readonly<Record<string, string | number>>): string;
24
+ }
25
+ export type BlockTone = 'neutral' | 'warn' | 'fail';
26
+ export interface BlockProps {
27
+ readonly tone?: BlockTone;
28
+ readonly title?: string;
29
+ readonly children?: ReactNode;
30
+ }
31
+ /** A neutral/ warn/ fail block; a fail block is role=alert so assistive tech announces it. */
32
+ export declare function Block(props: BlockProps): ReactNode;
33
+ export interface FailureBlockProps {
34
+ readonly failure: RouteFailure;
35
+ readonly t: Translate;
36
+ /** The command-face fallback for this surface (rendered on unmounted/failed answers). */
37
+ readonly fallback?: FallbackHint | undefined;
38
+ }
39
+ /** One classified failure as a reader-facing sentence. */
40
+ export declare function failureText(failure: RouteFailure, t: Translate): string;
41
+ /** The failed-state block: sentence + (when the surface exists but is unreachable) the command fallback. */
42
+ export declare function FailureBlock(props: FailureBlockProps): ReactNode;
43
+ //# sourceMappingURL=states.generated.d.ts.map
@@ -0,0 +1,3 @@
1
+ export declare const CSS_TAG_ID = "evolution-merge-panel";
2
+ export declare const CSS: string;
3
+ //# sourceMappingURL=styles.d.ts.map
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The family client scale - GENERATED from packages/scripts/client-tokens.json. Do not edit.
3
+ *
4
+ * Both browser halves of the family carry this file with identical bytes, because the scale is one
5
+ * fact: `verify-client-tokens.mjs` (a gate step) fails when either copy drifts from the source. The
6
+ * scale cannot be shared at runtime - a browser half may not import evolution-core, since every
7
+ * `@deepseek-ai/*` specifier stays external in the client build and the platform module table has no
8
+ * row for it - so the values are generated into each package instead.
9
+ *
10
+ * Usage: put `tokenVars(...)` for your root class in the package stylesheet, then read
11
+ * `var(--evo-group-name)` in its rules.
12
+ * Regenerate with: node packages/scripts/gen-client-tokens.mjs packages
13
+ * @module @deepseek-ai/dsh-evolution-client-scale
14
+ */
15
+ /** Every custom property of the scale, in the source order. */
16
+ export declare const TOKENS: Readonly<Record<string, string>>;
17
+ /** The declarations, without a selector. */
18
+ export declare const TOKEN_CSS: string;
19
+ /**
20
+ * The scale, declared on one element so it never leaks into the host shell.
21
+ * @param scope - the selector the custom properties are declared on.
22
+ * @returns the declaration block to put in the stylesheet.
23
+ */
24
+ export declare function tokenVars(scope: string): string;
25
+ //# sourceMappingURL=tokens.d.ts.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Host half of the merge UI package: it registers nothing. The Loader discovers the
3
+ * `dsh.client` manifest and serves `./client` to the page; this entry exists so the row
4
+ * is a valid plugin.
5
+ */
6
+ export declare const name = "evolution-merge-ui";
7
+ export declare const inject: string[];
8
+ export declare function apply(): void;
9
+ //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,56 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-merge-ui",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "description": "Web surface for skill merge: pending, retry and skipped queues (community build)",
4
+ "dsh": {
5
+ "client": {
6
+ "platform": "web",
7
+ "inject": [
8
+ "@deepseek-ai/dsh-client-ui-sidebar",
9
+ "@deepseek-ai/dsh-client-ui-layout",
10
+ "@deepseek-ai/dsh-client-locale"
11
+ ]
12
+ }
13
+ },
14
+ "version": "0.28.0",
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/lmzhen/dsh-evolution.git",
21
+ "directory": "packages/evolution-merge-ui"
22
+ },
23
+ "type": "module",
24
+ "main": "lib/index.js",
25
+ "types": "lib/types/index.d.ts",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./lib/types/index.d.ts",
29
+ "default": "./lib/index.js"
30
+ },
31
+ "./client": {
32
+ "types": "./lib/types/client/index.d.ts",
33
+ "default": "./lib/client.js"
34
+ },
35
+ "./package.json": "./package.json"
36
+ },
37
+ "files": [
38
+ "lib/*.js",
39
+ "lib/types/**/*.d.ts"
40
+ ],
41
+ "license": "MIT",
42
+ "peerDependencies": {
43
+ "@deepseek-ai/cordis": "^4.0.5",
44
+ "react": "^18.2.0"
45
+ },
46
+ "devDependencies": {
47
+ "@deepseek-ai/cordis": "^4.0.5",
48
+ "@types/react": "~18.3.1",
49
+ "@types/react-dom": "^18.3.1",
50
+ "react": "^18.2.0",
51
+ "react-dom": "^18.2.0"
52
+ },
53
+ "engines": {
54
+ "dsh": ">=0.2.1-rc.1 <0.2.2-0"
55
+ }
56
+ }