@lmzhen/dsh-evolution-ui 0.33.0 → 0.34.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/lib/client.js CHANGED
@@ -858,6 +858,7 @@ window.__ModuleLoader__.load({
858
858
  resetTo: "复位为 {value}",
859
859
  saved: "已保存。",
860
860
  saveReadbackFailed: "写入已被接受,但回读失败;点刷新重读后再确认。",
861
+ readbackStale: "显示的是最后一次成功读取的值;当前回读失败,刷新后可能变化。",
861
862
  saveConflict: "这个值没有被接受:设置已被其他页面修改。请重读后再保存。",
862
863
  resetDone: "已恢复默认值。",
863
864
  refused: "这个值没有被接受(界面上保持原样)。想看原因:在对话里让它说明。",
@@ -926,6 +927,8 @@ window.__ModuleLoader__.load({
926
927
  diagStatusUnknown: "未知",
927
928
  diagRemedy: "建议:",
928
929
  diagNoRows: "没有可显示的运行状态",
930
+ diagPurpose: "用途:检查插件挂载、平台服务和数据面是否正常,不修改任何状态。",
931
+ diagFooter: "诊断 schema v{version} · 生成于 {at}",
929
932
  stateLoading: "正在读取…",
930
933
  failureUnauthorized: "需要登录:重新打开这个页面完成登录后再试。",
931
934
  failureForbidden: "平台拒绝了这个请求(身份不是 owner)。",
@@ -963,6 +966,7 @@ window.__ModuleLoader__.load({
963
966
  resetTo: "Restores {value}",
964
967
  saved: "Saved.",
965
968
  saveReadbackFailed: "The write was accepted, but reading it back failed; refresh and confirm.",
969
+ readbackStale: "Showing the last successfully read values; the current re-read failed and may change after refresh.",
966
970
  saveConflict: "The value was not accepted: settings changed in another page. Re-read before saving again.",
967
971
  resetDone: "Default restored.",
968
972
  refused: "The value was not accepted (the page keeps what you see). For the reason, ask in the conversation.",
@@ -1031,6 +1035,8 @@ window.__ModuleLoader__.load({
1031
1035
  diagStatusUnknown: "unknown",
1032
1036
  diagRemedy: "Suggested: ",
1033
1037
  diagNoRows: "no runtime status to show",
1038
+ diagPurpose: "Purpose: check plugin mounting, platform services and data planes; nothing is changed.",
1039
+ diagFooter: "diagnostics schema v{version} · generated {at}",
1034
1040
  stateLoading: "reading…",
1035
1041
  failureUnauthorized: "Sign-in is required: reopen this page and sign in, then retry.",
1036
1042
  failureForbidden: "The platform refused this request (not the owner principal).",
@@ -1426,7 +1432,11 @@ window.__ModuleLoader__.load({
1426
1432
  onClick: () => {
1427
1433
  setOpen(!open);
1428
1434
  }
1429
- }, (0, react.createElement)("span", { className: "evolution-param-card-title" }, title), changed === 0 ? null : (0, react.createElement)("span", { className: "evolution-param-count" }, t("changed", { n: changed })), (0, react.createElement)("span", { className: "evolution-param-chevron" }, open ? "▲" : "▼")), open ? (0, react.createElement)("div", { className: "evolution-param-body" }, hintKey === void 0 ? null : (0, react.createElement)("p", { className: "evolution-param-note" }, t(hintKey)), disabled ? (0, react.createElement)("p", {
1435
+ }, (0, react.createElement)("span", { className: "evolution-param-card-title" }, title), changed === 0 ? null : (0, react.createElement)("span", { className: "evolution-param-count" }, t("changed", { n: changed })), (0, react.createElement)("span", { className: "evolution-param-chevron" }, open ? "▲" : "▼")), open ? (0, react.createElement)("div", { className: "evolution-param-body" }, hintKey === void 0 ? null : (0, react.createElement)("p", { className: "evolution-param-note" }, t(hintKey)), snapshot.stale === true ? (0, react.createElement)("p", {
1436
+ className: "evolution-param-note",
1437
+ role: "status",
1438
+ "data-role": "stale-note"
1439
+ }, t("readbackStale")) : null, disabled ? (0, react.createElement)("p", {
1430
1440
  className: "evolution-param-note",
1431
1441
  role: "status"
1432
1442
  }, t("readonly")) : null, fields.length === 0 ? (0, react.createElement)("p", {
@@ -1677,34 +1687,51 @@ window.__ModuleLoader__.load({
1677
1687
  ...EMPTY,
1678
1688
  reason: "seat-missing"
1679
1689
  };
1690
+ let lastReady;
1691
+ let generation = 0;
1680
1692
  const listeners = /* @__PURE__ */ new Set();
1681
- let started = false;
1682
- let listening = false;
1693
+ let remoteOff;
1683
1694
  const notify = () => {
1684
1695
  for (const listener of listeners) listener();
1685
1696
  };
1686
- const refresh = async () => {
1687
- snapshot = await describe(namespace);
1697
+ const absorb = (next) => {
1698
+ if (next.status === "ready") {
1699
+ lastReady = next;
1700
+ snapshot = next;
1701
+ } else if (lastReady !== void 0) snapshot = {
1702
+ ...lastReady,
1703
+ stale: true,
1704
+ reason: next.reason ?? "readback-failed"
1705
+ };
1706
+ else snapshot = next;
1688
1707
  notify();
1689
1708
  };
1690
- const start = () => {
1691
- if (started) return;
1692
- started = true;
1693
- if (!listening) {
1694
- listening = true;
1695
- (ctx.get?.("remote"))?.$on?.("settings/document-updated", (changed) => {
1696
- if (changed === namespace) refresh();
1697
- });
1698
- }
1699
- refresh();
1709
+ const refresh = async () => {
1710
+ const ticket = ++generation;
1711
+ const next = await describe(namespace);
1712
+ if (ticket !== generation) return;
1713
+ absorb(next);
1714
+ };
1715
+ const remote = () => ctx.get?.("remote");
1716
+ const stopRemote = () => {
1717
+ remoteOff?.();
1718
+ remoteOff = void 0;
1719
+ };
1720
+ const startRemote = () => {
1721
+ if (remoteOff !== void 0) return;
1722
+ remoteOff = remote()?.$on?.("settings/document-updated", (changed) => {
1723
+ if (changed === namespace) refresh();
1724
+ });
1700
1725
  };
1701
1726
  const form = {
1702
1727
  getSnapshot: () => snapshot,
1703
1728
  subscribe: (listener) => {
1704
1729
  listeners.add(listener);
1705
- start();
1730
+ startRemote();
1731
+ refresh();
1706
1732
  return () => {
1707
1733
  listeners.delete(listener);
1734
+ if (listeners.size === 0) stopRemote();
1708
1735
  };
1709
1736
  },
1710
1737
  set: async (field, value) => await form.mutate([{
@@ -1718,35 +1745,39 @@ window.__ModuleLoader__.load({
1718
1745
  }]),
1719
1746
  mutate: async (ops, expectedRevision) => {
1720
1747
  const outcome = await write(namespace, ops, expectedRevision ?? snapshot.revision);
1721
- if (outcome.snapshot !== void 0) {
1722
- snapshot = outcome.snapshot;
1723
- notify();
1724
- } else await refresh();
1748
+ if (outcome.snapshot !== void 0) absorb(outcome.snapshot);
1749
+ else await refresh();
1725
1750
  return outcome.ok;
1751
+ },
1752
+ refresh,
1753
+ dispose: () => {
1754
+ stopRemote();
1755
+ listeners.clear();
1726
1756
  }
1727
1757
  };
1728
1758
  return form;
1729
1759
  }
1730
1760
  /**
1731
- * Build one cached form per family namespace over the family settings routes.
1732
- * @param ctx - the client plugin context (remote subscription is optional).
1733
- * @returns the seat; each namespace is served by /api/dsh-evolution/settings/*.
1761
+ * Build one seat per client registration; every form's remote listener is released by
1762
+ * the caller's effect. No module-level WeakMap: a replaced registration must not share
1763
+ * a dead seat with the previous one.
1734
1764
  */
1735
- const seats = /* @__PURE__ */ new WeakMap();
1736
1765
  function createSettingsSeat(ctx) {
1737
- const cached = seats.get(ctx);
1738
- if (cached !== void 0) return cached;
1739
1766
  const forms = /* @__PURE__ */ new Map();
1740
- const seat = { get(namespace) {
1741
- let form = forms.get(namespace);
1742
- if (form === void 0) {
1743
- form = createForm(namespace, ctx);
1744
- forms.set(namespace, form);
1767
+ return {
1768
+ get(namespace) {
1769
+ let form = forms.get(namespace);
1770
+ if (form === void 0) {
1771
+ form = createForm(namespace, ctx);
1772
+ forms.set(namespace, form);
1773
+ }
1774
+ return form;
1775
+ },
1776
+ dispose: () => {
1777
+ for (const form of forms.values()) form.dispose?.();
1778
+ forms.clear();
1745
1779
  }
1746
- return form;
1747
- } };
1748
- seats.set(ctx, seat);
1749
- return seat;
1780
+ };
1750
1781
  }
1751
1782
  //#endregion
1752
1783
  //#region ../evolution-client-kit/src/action.ts
@@ -1848,7 +1879,8 @@ window.__ModuleLoader__.load({
1848
1879
  user: snapshot.user,
1849
1880
  writable: snapshot.writable,
1850
1881
  base: isRecord(snapshot.base) ? snapshot.base : void 0,
1851
- ...snapshot.status === "unavailable" ? { reason: snapshot.reason ?? "not-served" } : {}
1882
+ ...snapshot.status === "unavailable" ? { reason: snapshot.reason ?? "not-served" } : {},
1883
+ ...snapshot.stale === true ? { stale: true } : {}
1852
1884
  };
1853
1885
  }
1854
1886
  /**
@@ -1958,7 +1990,6 @@ window.__ModuleLoader__.load({
1958
1990
  readback: "failed",
1959
1991
  kind: "transport"
1960
1992
  };
1961
- const before = seat.getSnapshot().revision;
1962
1993
  let written = false;
1963
1994
  try {
1964
1995
  written = await seat.mutate(ops);
@@ -1970,12 +2001,15 @@ window.__ModuleLoader__.load({
1970
2001
  failure
1971
2002
  };
1972
2003
  }
1973
- const snapshot = seat.getSnapshot();
1974
2004
  if (!written) return {
1975
2005
  written: false,
1976
2006
  readback: "failed",
1977
2007
  kind: "conflict"
1978
2008
  };
2009
+ try {
2010
+ await seat.refresh?.();
2011
+ } catch {}
2012
+ const snapshot = seat.getSnapshot();
1979
2013
  if (snapshot.status !== "ready") return {
1980
2014
  written: true,
1981
2015
  readback: "failed",
@@ -1983,7 +2017,8 @@ window.__ModuleLoader__.load({
1983
2017
  };
1984
2018
  return {
1985
2019
  written: true,
1986
- readback: snapshot.revision !== before ? "ok" : "failed"
2020
+ readback: snapshot.stale === true ? "failed" : "ok",
2021
+ ...snapshot.stale === true ? { kind: "transport" } : {}
1987
2022
  };
1988
2023
  };
1989
2024
  return {
@@ -2586,6 +2621,8 @@ window.__ModuleLoader__.load({
2586
2621
  * read, because a half-list would read as a complete window.
2587
2622
  */
2588
2623
  const readTicket = (0, react.useRef)(0);
2624
+ const previewTicket = (0, react.useRef)(0);
2625
+ const actionTickets = (0, react.useRef)(/* @__PURE__ */ new Map());
2589
2626
  const load = async () => {
2590
2627
  apply(loading);
2591
2628
  const ticket = readTicket.current + 1;
@@ -2613,9 +2650,13 @@ window.__ModuleLoader__.load({
2613
2650
  };
2614
2651
  }, []);
2615
2652
  const decide = (id, action) => {
2653
+ if (latest.current.busy.includes(id)) return;
2654
+ const ticket = (actionTickets.current.get(id) ?? 0) + 1;
2655
+ actionTickets.current.set(id, ticket);
2616
2656
  apply((current) => beginAction(current, id));
2617
2657
  (async () => {
2618
2658
  const answer = action === "approve" ? await api.approve(id) : action === "reject" ? await api.reject(id) : await api.release(id);
2659
+ if (actionTickets.current.get(id) !== ticket) return;
2619
2660
  if (!answer.ok) {
2620
2661
  apply((current) => noticed(endAction(current, id), failureText(answer.failure, translate)));
2621
2662
  return;
@@ -2631,7 +2672,9 @@ window.__ModuleLoader__.load({
2631
2672
  };
2632
2673
  /** Ask the host what this record would store; the answer lands only if the row is still open. */
2633
2674
  const loadPreview = async (id) => {
2675
+ const ticket = ++previewTicket.current;
2634
2676
  const answer = await api.preview(id);
2677
+ if (ticket !== previewTicket.current || latest.current.open !== id) return;
2635
2678
  const view = answer.ok ? answer.data.available ? {
2636
2679
  kind: "ready",
2637
2680
  answer: answer.data
@@ -2685,7 +2728,13 @@ window.__ModuleLoader__.load({
2685
2728
  }, t("approvalDiffCut"))] : []
2686
2729
  ];
2687
2730
  const lines = [];
2731
+ const multiHunk = view.answer.hunks.length > 1 || view.answer.hunks.some((hunk) => hunk.path !== view.answer.path);
2688
2732
  for (const [hunkIndex, hunk] of view.answer.hunks.entries()) {
2733
+ if (multiHunk) lines.push((0, react.createElement)("div", {
2734
+ key: "hunk-" + String(hunkIndex),
2735
+ className: "evolution-param-hunk-path",
2736
+ "data-role": "hunk-path"
2737
+ }, hunk.path));
2689
2738
  for (const [index, line] of hunk.oldText.split("\n").entries()) {
2690
2739
  if (hunk.oldText === "" && line === "") continue;
2691
2740
  lines.push((0, react.createElement)("div", {
@@ -4038,7 +4087,16 @@ window.__ModuleLoader__.load({
4038
4087
  onClick: refresh,
4039
4088
  disabled: refreshing,
4040
4089
  "data-action": "diag-refresh"
4041
- }, t(refreshing ? "diagRefreshing" : "diagRefresh"))), runtime(view), view.runtime.length === 0 ? (0, react.createElement)("p", { className: "evolution-param-note" }, t("diagNoRows")) : null);
4090
+ }, t(refreshing ? "diagRefreshing" : "diagRefresh"))), runtime(view), view.runtime.length === 0 ? (0, react.createElement)("p", { className: "evolution-param-note" }, t("diagNoRows")) : null, (0, react.createElement)("p", {
4091
+ className: "evolution-param-note",
4092
+ "data-role": "diag-purpose"
4093
+ }, t("diagPurpose")), (0, react.createElement)("p", {
4094
+ className: "evolution-param-hint",
4095
+ "data-role": "diag-footer"
4096
+ }, t("diagFooter", {
4097
+ version: String(view.schemaVersion),
4098
+ at: new Date(view.generatedAt).toLocaleString()
4099
+ })));
4042
4100
  };
4043
4101
  return (0, react.createElement)("section", {
4044
4102
  className: "evolution-param-card",
@@ -4061,16 +4119,23 @@ window.__ModuleLoader__.load({
4061
4119
  * @returns the page surface.
4062
4120
  */
4063
4121
  function WorkbenchPage(props) {
4064
- const [selectedSkill, setSelectedSkill] = (0, react.useState)(void 0);
4065
- const [view, setView] = (0, react.useState)("history");
4066
- const [focusRevision, setFocusRevision] = (0, react.useState)(0);
4122
+ const state = (0, react.useSyncExternalStore)(props.store.subscribe, props.store.getState, props.store.getState);
4123
+ const view = state.view;
4124
+ const selectedSkill = state.selectedSkill;
4125
+ const focusRevision = state.focusRevision;
4067
4126
  const openHistory = (skill) => {
4068
- setSelectedSkill(skill);
4069
- setView("history");
4070
- setFocusRevision((value) => value + 1);
4127
+ props.store.openHistory(skill);
4071
4128
  };
4072
4129
  const hasMerge = props.hasView?.("merge") ?? true;
4073
4130
  const hasHealth = props.hasView?.("health") ?? true;
4131
+ (0, react.useEffect)(() => {
4132
+ if (view === "merge" && !hasMerge || view === "health" && !hasHealth) props.store.setView("history");
4133
+ }, [
4134
+ view,
4135
+ hasMerge,
4136
+ hasHealth,
4137
+ props.store
4138
+ ]);
4074
4139
  const viewOrder = [
4075
4140
  "history",
4076
4141
  "pending",
@@ -4084,7 +4149,7 @@ window.__ModuleLoader__.load({
4084
4149
  event.preventDefault();
4085
4150
  const index = viewOrder.indexOf(current);
4086
4151
  const next = event.key === "Home" ? viewOrder[0] : event.key === "End" ? viewOrder[viewOrder.length - 1] : viewOrder[(index + delta + viewOrder.length) % viewOrder.length];
4087
- setView(next);
4152
+ props.store.setView(next);
4088
4153
  requestAnimationFrame(() => {
4089
4154
  document.getElementById("evo-workbench-tab-" + next)?.focus();
4090
4155
  });
@@ -4105,7 +4170,7 @@ window.__ModuleLoader__.load({
4105
4170
  "aria-controls": "evo-workbench-panel",
4106
4171
  "data-active": view === "history" ? "true" : void 0,
4107
4172
  onClick: () => {
4108
- setView("history");
4173
+ props.store.setView("history");
4109
4174
  },
4110
4175
  onKeyDown: (event) => {
4111
4176
  onTabKey(event, "history");
@@ -4119,7 +4184,7 @@ window.__ModuleLoader__.load({
4119
4184
  "aria-controls": "evo-workbench-panel",
4120
4185
  "data-active": view === "pending" ? "true" : void 0,
4121
4186
  onClick: () => {
4122
- setView("pending");
4187
+ props.store.setView("pending");
4123
4188
  },
4124
4189
  onKeyDown: (event) => {
4125
4190
  onTabKey(event, "pending");
@@ -4133,7 +4198,7 @@ window.__ModuleLoader__.load({
4133
4198
  "aria-controls": "evo-workbench-panel",
4134
4199
  "data-active": view === "merge" ? "true" : void 0,
4135
4200
  onClick: () => {
4136
- setView("merge");
4201
+ props.store.setView("merge");
4137
4202
  },
4138
4203
  onKeyDown: (event) => {
4139
4204
  onTabKey(event, "merge");
@@ -4147,7 +4212,7 @@ window.__ModuleLoader__.load({
4147
4212
  "aria-controls": "evo-workbench-panel",
4148
4213
  "data-active": view === "health" ? "true" : void 0,
4149
4214
  onClick: () => {
4150
- setView("health");
4215
+ props.store.setView("health");
4151
4216
  },
4152
4217
  onKeyDown: (event) => {
4153
4218
  onTabKey(event, "health");
@@ -4161,7 +4226,7 @@ window.__ModuleLoader__.load({
4161
4226
  "aria-controls": "evo-workbench-panel",
4162
4227
  "data-active": view === "diagnostics" ? "true" : void 0,
4163
4228
  onClick: () => {
4164
- setView("diagnostics");
4229
+ props.store.setView("diagnostics");
4165
4230
  },
4166
4231
  onKeyDown: (event) => {
4167
4232
  onTabKey(event, "diagnostics");
@@ -4211,12 +4276,51 @@ window.__ModuleLoader__.load({
4211
4276
  ".evo-workbench-title{margin:0;font-size:var(--evo-type-lg);font-weight:600;line-height:var(--evo-leading-base)}",
4212
4277
  ".evo-workbench-body{display:flex;flex:1;min-height:0;min-width:0;overflow:hidden}",
4213
4278
  ".evo-workbench-body>.evolution-param-card{flex:1;min-width:0;overflow:auto}",
4214
- ".evo-workbench-nav{display:flex;width:100%;align-items:center;gap:var(--evo-space-8);padding:var(--evo-space-6) var(--evo-space-12);border:0;border-radius:var(--evo-radius-control);background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:var(--evo-type-sm);text-align:left;cursor:pointer}",
4215
- ".evo-workbench-nav:hover,.evo-workbench-nav[data-active=\"true\"]{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}",
4216
- ".evo-workbench-nav:focus-visible{outline:var(--evo-focus-ring);outline-offset:var(--evo-focus-offset)}",
4217
4279
  "@media (max-width:720px){.evo-workbench-head{flex-wrap:wrap}.evo-workbench-tabs{flex-wrap:wrap}}"
4218
4280
  ].join("");
4219
4281
  //#endregion
4282
+ //#region src/panels/history/client/WorkbenchStore.ts
4283
+ function createWorkbenchStore(initial = {
4284
+ view: "history",
4285
+ focusRevision: 0
4286
+ }) {
4287
+ let state = initial;
4288
+ const listeners = /* @__PURE__ */ new Set();
4289
+ const emit = (next) => {
4290
+ state = next;
4291
+ for (const listener of listeners) listener();
4292
+ };
4293
+ return {
4294
+ getState: () => state,
4295
+ subscribe(listener) {
4296
+ listeners.add(listener);
4297
+ return () => {
4298
+ listeners.delete(listener);
4299
+ };
4300
+ },
4301
+ setView(view) {
4302
+ if (state.view !== view) emit({
4303
+ ...state,
4304
+ view
4305
+ });
4306
+ },
4307
+ openHistory(skill) {
4308
+ emit({
4309
+ view: "history",
4310
+ ...skill === void 0 ? {} : { selectedSkill: skill },
4311
+ focusRevision: state.focusRevision + 1
4312
+ });
4313
+ },
4314
+ clearSelection() {
4315
+ const { selectedSkill: _selected, ...rest } = state;
4316
+ emit({
4317
+ ...rest,
4318
+ focusRevision: state.focusRevision + 1
4319
+ });
4320
+ }
4321
+ };
4322
+ }
4323
+ //#endregion
4220
4324
  //#region src/panels/history/client/index.ts
4221
4325
  /**
4222
4326
  * Register the locale namespace, the workbench main occupant and its sidebar row.
@@ -4225,6 +4329,9 @@ window.__ModuleLoader__.load({
4225
4329
  function apply$1(ctx) {
4226
4330
  const api = createSkillHistoryApi();
4227
4331
  const settingsSeat = createSettingsSeat(ctx);
4332
+ ctx.effect(() => () => {
4333
+ settingsSeat.dispose?.();
4334
+ }, "evolution-ui: settings seat");
4228
4335
  let approvalConfirmMode = "ask";
4229
4336
  const approvalForm = settingsSeat.get("evolution-approval");
4230
4337
  const stopApprovalWatch = approvalForm?.subscribe(() => {
@@ -4273,6 +4380,7 @@ window.__ModuleLoader__.load({
4273
4380
  api: createDiagnosticsApi((input, init) => fetch(input, init)),
4274
4381
  fallback: FALLBACKS.diagnostics
4275
4382
  });
4383
+ const workbenchStore = createWorkbenchStore();
4276
4384
  registerSidebarPanel(ctx, {
4277
4385
  id: WORKBENCH_ID,
4278
4386
  order: 35,
@@ -4289,6 +4397,7 @@ window.__ModuleLoader__.load({
4289
4397
  component: WorkbenchPage,
4290
4398
  body: (t) => ({
4291
4399
  t,
4400
+ store: workbenchStore,
4292
4401
  history: historyFace(t),
4293
4402
  pending: pendingFace(),
4294
4403
  diagnostics: diagnosticsFace(),
@@ -7,23 +7,20 @@ import { type ReactNode } from 'react';
7
7
  import { type PanelFace } from './Panel.ts';
8
8
  import { type PendingCardFace } from '../../settings/PendingCard.ts';
9
9
  import { type DiagnosticsCardFace } from '../../settings/DiagnosticsCard.ts';
10
+ import type { WorkbenchStore } from './WorkbenchStore.ts';
10
11
  /** Stable page id used by the sidebar row, the page slot key, and navigation params. */
11
12
  export declare const WORKBENCH_ID = "evolution.workbench";
12
13
  /** Injected face: copy plus the history view's data callbacks. */
13
14
  export interface WorkbenchFace {
14
15
  readonly t: (key: string) => string;
16
+ readonly store: WorkbenchStore;
15
17
  readonly history: PanelFace;
16
18
  readonly pending: PendingCardFace;
17
19
  readonly diagnostics: DiagnosticsCardFace;
18
20
  /** Whether a feature view is actually mounted (health is disabled by default). */
19
21
  readonly hasView?: (view: 'merge' | 'health') => boolean;
20
22
  }
21
- /** The two views the workbench owns today. */
22
- export type WorkbenchView = 'history' | 'pending' | 'merge' | 'health' | 'diagnostics';
23
- /** Sidebar row injected face. */
24
- export interface WorkbenchNavFace {
25
- readonly t: (key: string) => string;
26
- }
23
+ export type { WorkbenchView } from './WorkbenchStore.ts';
27
24
  /**
28
25
  * Render the workbench page. The platform keeps it mounted; this component only
29
26
  * consumes a new explicit revision exactly once.
@@ -0,0 +1,16 @@
1
+ /** Registration-owned workbench navigation state. No module-level singleton. */
2
+ export type WorkbenchView = 'history' | 'pending' | 'merge' | 'health' | 'diagnostics';
3
+ export interface WorkbenchState {
4
+ readonly view: WorkbenchView;
5
+ readonly selectedSkill?: string;
6
+ readonly focusRevision: number;
7
+ }
8
+ export interface WorkbenchStore {
9
+ getState(): WorkbenchState;
10
+ subscribe(listener: () => void): () => void;
11
+ setView(view: WorkbenchView): void;
12
+ openHistory(skill?: string): void;
13
+ clearSelection(): void;
14
+ }
15
+ export declare function createWorkbenchStore(initial?: WorkbenchState): WorkbenchStore;
16
+ //# sourceMappingURL=WorkbenchStore.d.ts.map
@@ -5,7 +5,7 @@
5
5
  * description, so the settings surface can state both without a hand-written copy.
6
6
  */
7
7
  /** The family version this surface belongs to. */
8
- export declare const FAMILY_VERSION = "0.33.0";
8
+ export declare const FAMILY_VERSION = "0.34.0";
9
9
  /** What this surface is for, in one line. */
10
10
  export declare const PACKAGE_PURPOSE = "Web UI umbrella for the evolution family: settings and skill-history panels";
11
11
  //# sourceMappingURL=generated-about.d.ts.map
@@ -40,6 +40,7 @@ export declare const zh: {
40
40
  readonly resetTo: "复位为 {value}";
41
41
  readonly saved: "已保存。";
42
42
  readonly saveReadbackFailed: "写入已被接受,但回读失败;点刷新重读后再确认。";
43
+ readonly readbackStale: "显示的是最后一次成功读取的值;当前回读失败,刷新后可能变化。";
43
44
  readonly saveConflict: "这个值没有被接受:设置已被其他页面修改。请重读后再保存。";
44
45
  readonly resetDone: "已恢复默认值。";
45
46
  readonly refused: "这个值没有被接受(界面上保持原样)。想看原因:在对话里让它说明。";
@@ -108,6 +109,8 @@ export declare const zh: {
108
109
  readonly diagStatusUnknown: "未知";
109
110
  readonly diagRemedy: "建议:";
110
111
  readonly diagNoRows: "没有可显示的运行状态";
112
+ readonly diagPurpose: "用途:检查插件挂载、平台服务和数据面是否正常,不修改任何状态。";
113
+ readonly diagFooter: "诊断 schema v{version} · 生成于 {at}";
111
114
  readonly stateLoading: "正在读取…";
112
115
  readonly failureUnauthorized: "需要登录:重新打开这个页面完成登录后再试。";
113
116
  readonly failureForbidden: "平台拒绝了这个请求(身份不是 owner)。";
@@ -45,7 +45,9 @@ export interface ParamSectionSnapshot {
45
45
  * all, or the seat is there and serves no such row. The two read differently to the
46
46
  * operator (one is the UI's own gap, the other is a row that is not composed).
47
47
  */
48
- reason?: 'seat-missing' | 'not-served' | 'projection-failed';
48
+ reason?: 'seat-missing' | 'not-served' | 'projection-failed' | 'readback-failed';
49
+ /** Last-known values are still shown; the row could not be re-read. */
50
+ stale?: boolean;
49
51
  }
50
52
  /** The reactive source the renderer binds to a `use<Name>` seat. */
51
53
  export interface ParamSectionSource {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-ui",
3
3
  "description": "Web UI umbrella for the evolution family: settings and skill-history panels (community build)",
4
- "version": "0.33.0",
4
+ "version": "0.34.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -45,9 +45,9 @@
45
45
  "react": "^18.2.0"
46
46
  },
47
47
  "dependencies": {
48
- "@lmzhen/dsh-evolution-client-kit": "^0.33.0",
49
- "@lmzhen/dsh-evolution-core": "^0.33.0",
50
- "@lmzhen/dsh-evolution-curator": "^0.33.0"
48
+ "@lmzhen/dsh-evolution-client-kit": "^0.34.0",
49
+ "@lmzhen/dsh-evolution-core": "^0.34.0",
50
+ "@lmzhen/dsh-evolution-curator": "^0.34.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/react": "~18.3.1",