dsh-input-traffic 0.7.0 → 0.7.3

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/CHANGELOG.md CHANGED
@@ -2,16 +2,37 @@
2
2
 
3
3
  所有重要变更与 bug 修复记录于此。版本遵循语义化版本(`dsh plugin --profile web add dsh-input-traffic` 安装)。
4
4
 
5
- ## 0.5.5 — 2026-09-30
6
-
7
- ### 修复:队列行改读 inbox 投影(真实宿主渲染修复,实测 0.1.7-rc.2)
8
-
9
- - 真实宿主的排队行不在会话快照上:组件改从 useProjection('inbox') 读取(next-turn=排队/绿档,next-step=插话/黄档),与官方 QueueDock 同源。此前读取的 s.queue 为类型镜像自造字段,行渲染必崩并回退官方队列。
10
- - queueMutable 与官方判定对齐:continuable 子代理上下文同样可操作(此前整块操作按钮会在子代理会话中消失)。
11
- - 移除真机上失效的「打回输入框」按钮(setDraft 为未文档化面);黄色档位是唯一插话入口。
12
- - 行文本改由 content 块派生(textOf,对齐官方 textOf/previewOf),修复行预览空白。
13
- - 三色档位圆点固定字面量配色:自定义皮肤(如 ui-skin-maid-atelier)重定义 warn 别名 token 会把黄点洗成不可见。
14
- - CSS 压制官方 QueueDock 容器(仅崩溃回退态的可见性兜底);修复 verify-assembly 陈旧断言(settingsScope → configForms)。
5
+ ## 0.7.3 — 2026-10-08
6
+
7
+ ### 新增:settings 数据桥(T20-b,perm-gate 5260fe6 泛化形移植)
8
+
9
+ - 老线(≤0.1.5)上 client settings 句柄永不 resolve(settingsScope 死路),家族卡
10
+ 降级「设置服务不可用」、busyEnter 钉值静默失效。node 半区从 no-op 转正:loader
11
+ `inject=['webServer']` + `settings` 子注入挂活引用,暴露
12
+ `/api/ui-conversation/settings/{describe,mutate}`;client 半区 BridgeDocHandle
13
+ 双轨(原生句柄优先、桥兜底),钉值在原生缺席时由桥首拉 ready 后补发一次。
14
+ 前提实证:0.1.0 宿主 settings 服务全款具备 describe/mutate/redactSecrets 面,
15
+ 最老线无需兜底。五格验收:四条桥线卡片真数据、0.1.7 原生无回归、桥写回落盘。
16
+ - 顺修 family-card 条件 hook(useSyncExternalStore 无条件调用;降级快照走模块级
17
+ 常量)。
18
+
19
+ ### 0.7.1 / 0.7.2 未单独发版(tgz 直铺五格),随本版一并进 npm
20
+
21
+ - 0.7.0:15rc 枚举单版本线(六 peer × 15 rc 全存在),latest + 四线 tag 收敛(已发)。
22
+ - 0.7.1:`dsh-family.tab` 注册提顶层 + scopeRef 活引用(≤0.1.5 家族节贡献卡缺席修复)。
23
+ - 0.7.2:settings waist 回调按 cordis 契约从 scope 对象按名读服务(裸服务调用 =
24
+ 每代 TypeError、纤维暗死,busyEnter 钉值因此从未生效)。
25
+
26
+ ## 0.5.5 — 2026-09-30
27
+
28
+ ### 修复:队列行改读 inbox 投影(真实宿主渲染修复,实测 0.1.7-rc.2)
29
+
30
+ - 真实宿主的排队行不在会话快照上:组件改从 useProjection('inbox') 读取(next-turn=排队/绿档,next-step=插话/黄档),与官方 QueueDock 同源。此前读取的 s.queue 为类型镜像自造字段,行渲染必崩并回退官方队列。
31
+ - queueMutable 与官方判定对齐:continuable 子代理上下文同样可操作(此前整块操作按钮会在子代理会话中消失)。
32
+ - 移除真机上失效的「打回输入框」按钮(setDraft 为未文档化面);黄色档位是唯一插话入口。
33
+ - 行文本改由 content 块派生(textOf,对齐官方 textOf/previewOf),修复行预览空白。
34
+ - 三色档位圆点固定字面量配色:自定义皮肤(如 ui-skin-maid-atelier)重定义 warn 别名 token 会把黄点洗成不可见。
35
+ - CSS 压制官方 QueueDock 容器(仅崩溃回退态的可见性兜底);修复 verify-assembly 陈旧断言(settingsScope → configForms)。
15
36
  ## 0.5.4 — 2026-09-29
16
37
 
17
38
  ### 修复:0.1.7 线 devDeps 基线对齐 + dock 图标渲染崩溃
package/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-external/dsh-input-traffic",
3
- "version": "0.7.0",
3
+ "version": "0.7.3",
4
4
  "main": "./lib/index.js",
5
5
  "description": "Busy-time input queue: three-tier planning, drag-to-reorder, interject/interrupt, batch clear, session freeze.",
6
6
  "engines": {
package/lib/client.js CHANGED
@@ -600,42 +600,42 @@ window.__ModuleLoader__.load({
600
600
  document.head.appendChild(tag);
601
601
  }
602
602
  var steer_queue_dock_module_css_default = {
603
- "dock": "FNESVa_dock",
603
+ "badgeLabel": "FNESVa_badgeLabel",
604
604
  "list": "FNESVa_list",
605
- "toolbar": "FNESVa_toolbar",
605
+ "clearConfirm": "FNESVa_clearConfirm",
606
+ "freeze": "FNESVa_freeze",
607
+ "row": "FNESVa_row",
608
+ "header": "FNESVa_header",
609
+ "preview": "FNESVa_preview",
610
+ "tierNow": "FNESVa_tierNow",
611
+ "frozenRow": "FNESVa_frozenRow",
612
+ "clear": "FNESVa_clear",
613
+ "clearLabel": "FNESVa_clearLabel",
614
+ "tier": "FNESVa_tier",
615
+ "tierNext": "FNESVa_tierNext",
616
+ "tierLater": "FNESVa_tierLater",
617
+ "badge": "FNESVa_badge",
618
+ "panel": "FNESVa_panel",
606
619
  "actions": "FNESVa_actions",
607
- "badgeLabel": "FNESVa_badgeLabel",
620
+ "chevron": "FNESVa_chevron",
621
+ "toolbar": "FNESVa_toolbar",
608
622
  "editor": "FNESVa_editor",
609
- "count": "FNESVa_count",
610
- "header": "FNESVa_header",
611
- "frozenBanner": "FNESVa_frozenBanner",
612
- "clearCancel": "FNESVa_clearCancel",
613
623
  "rowDragOver": "FNESVa_rowDragOver",
624
+ "frozenBanner": "FNESVa_frozenBanner",
625
+ "toolbarActions": "FNESVa_toolbarActions",
626
+ "frozenList": "FNESVa_frozenList",
627
+ "frozenMark": "FNESVa_frozenMark",
628
+ "badgeNow": "FNESVa_badgeNow",
629
+ "badgeLater": "FNESVa_badgeLater",
614
630
  "steeringList": "FNESVa_steeringList",
615
- "clear": "FNESVa_clear",
631
+ "lead": "FNESVa_lead",
632
+ "dock": "FNESVa_dock",
616
633
  "action": "FNESVa_action",
617
- "frozenRow": "FNESVa_frozenRow",
618
- "frozenMark": "FNESVa_frozenMark",
619
634
  "plan": "FNESVa_plan",
620
- "toolbarActions": "FNESVa_toolbarActions",
621
- "freeze": "FNESVa_freeze",
622
- "lead": "FNESVa_lead",
623
- "preview": "FNESVa_preview",
624
- "badgeLater": "FNESVa_badgeLater",
625
- "tierNow": "FNESVa_tierNow",
626
- "row": "FNESVa_row",
627
- "panel": "FNESVa_panel",
628
- "badgeNow": "FNESVa_badgeNow",
629
635
  "dot": "FNESVa_dot",
630
- "tierLater": "FNESVa_tierLater",
636
+ "count": "FNESVa_count",
631
637
  "badgeNext": "FNESVa_badgeNext",
632
- "tier": "FNESVa_tier",
633
- "tierNext": "FNESVa_tierNext",
634
- "chevron": "FNESVa_chevron",
635
- "badge": "FNESVa_badge",
636
- "frozenList": "FNESVa_frozenList",
637
- "clearLabel": "FNESVa_clearLabel",
638
- "clearConfirm": "FNESVa_clearConfirm"
638
+ "clearCancel": "FNESVa_clearCancel"
639
639
  };
640
640
  //#endregion
641
641
  //#region src/client/steer-queue-dock.tsx
@@ -1722,9 +1722,23 @@ window.__ModuleLoader__.load({
1722
1722
  fontSize: 12,
1723
1723
  lineHeight: 1.5
1724
1724
  };
1725
+ /** 无 scope 时的降级快照(模块级常量:useSyncExternalStore 要求稳定引用)。 */
1726
+ const DEGRADED_SNAPSHOT = {
1727
+ status: "unavailable",
1728
+ value: void 0,
1729
+ writable: false
1730
+ };
1725
1731
  function InputTrafficFamilyCard({ scope, t }) {
1726
- const pinned = (0, react.useSyncExternalStore)((listener) => scope.subscribe(listener), () => scope.getSnapshot()).value?.busyEnter ?? "queue";
1732
+ const snapshot = (0, react.useSyncExternalStore)((listener) => scope?.subscribe(listener) ?? (() => {}), () => scope?.getSnapshot() ?? DEGRADED_SNAPSHOT);
1727
1733
  const tr = (key) => t ? t(key) : FALLBACK_ZH[key] ?? key;
1734
+ if (!scope) return (0, react.createElement)("div", { style: {
1735
+ display: "grid",
1736
+ gap: 6
1737
+ } }, (0, react.createElement)("p", { style: {
1738
+ ...dim,
1739
+ margin: 0
1740
+ } }, `${tr("family.desc")}(当前宿主设置服务不可用 — 只读)`));
1741
+ const pinned = snapshot.value?.busyEnter ?? "queue";
1728
1742
  return (0, react.createElement)("div", { style: {
1729
1743
  display: "grid",
1730
1744
  gap: 6
@@ -1745,6 +1759,107 @@ window.__ModuleLoader__.load({
1745
1759
  return null;
1746
1760
  }
1747
1761
  //#endregion
1762
+ //#region src/client/bridge-scope.ts
1763
+ /** Must match src/bridge.ts bridgeRoutesFor character for character. */
1764
+ function bridgeRoutesFor(ns) {
1765
+ return {
1766
+ describe: `/api/${ns}/settings/describe`,
1767
+ mutate: `/api/${ns}/settings/mutate`
1768
+ };
1769
+ }
1770
+ var BridgeDocHandle = class {
1771
+ snapshot = {
1772
+ status: "pending",
1773
+ value: void 0,
1774
+ writable: true
1775
+ };
1776
+ listeners = /* @__PURE__ */ new Set();
1777
+ revision;
1778
+ inflight;
1779
+ routes;
1780
+ constructor(ns) {
1781
+ this.routes = bridgeRoutesFor(ns);
1782
+ this.refresh();
1783
+ }
1784
+ /** The bridge transport is shape-agnostic; the ns payload narrows at this boundary. */
1785
+ getSnapshot() {
1786
+ return this.snapshot;
1787
+ }
1788
+ subscribe(listener) {
1789
+ this.listeners.add(listener);
1790
+ return () => {
1791
+ this.listeners.delete(listener);
1792
+ };
1793
+ }
1794
+ async set(field, value) {
1795
+ await this.mutate([{
1796
+ op: "set",
1797
+ path: [field],
1798
+ value
1799
+ }]);
1800
+ }
1801
+ notify() {
1802
+ for (const l of [...this.listeners]) l();
1803
+ }
1804
+ async request(path, body) {
1805
+ const response = await fetch(path, {
1806
+ method: "POST",
1807
+ headers: { "content-type": "application/json" },
1808
+ body: JSON.stringify(body ?? {})
1809
+ });
1810
+ if (!response.ok) throw new Error(`bridge ${path} HTTP ${response.status}`);
1811
+ return await response.json();
1812
+ }
1813
+ async refresh() {
1814
+ if (this.inflight !== void 0) return this.inflight;
1815
+ this.inflight = (async () => {
1816
+ try {
1817
+ const body = await this.request(this.routes.describe);
1818
+ if (body.ok && body.value?.descriptor !== void 0) {
1819
+ const d = body.value.descriptor;
1820
+ this.revision = typeof d.revision === "number" ? d.revision : void 0;
1821
+ this.snapshot = {
1822
+ status: "ready",
1823
+ value: d.value,
1824
+ writable: body.value.writable !== false
1825
+ };
1826
+ } else this.snapshot = {
1827
+ status: "error",
1828
+ value: void 0,
1829
+ writable: false
1830
+ };
1831
+ } catch {
1832
+ this.snapshot = {
1833
+ status: "error",
1834
+ value: void 0,
1835
+ writable: false
1836
+ };
1837
+ } finally {
1838
+ this.inflight = void 0;
1839
+ }
1840
+ this.notify();
1841
+ })();
1842
+ return this.inflight;
1843
+ }
1844
+ async mutate(ops) {
1845
+ const body = await this.request(this.routes.mutate, {
1846
+ ops,
1847
+ expectedRevision: this.revision
1848
+ });
1849
+ if (body.ok && body.value?.descriptor !== void 0) {
1850
+ const d = body.value.descriptor;
1851
+ this.revision = typeof d.revision === "number" ? d.revision : void 0;
1852
+ this.snapshot = {
1853
+ status: "ready",
1854
+ value: d.value,
1855
+ writable: this.snapshot.writable
1856
+ };
1857
+ } else if (body.code === "settings-conflict") await this.refresh();
1858
+ else throw new Error(`bridge mutate failed: ${body.code ?? "unknown"} ${body.message ?? ""}`);
1859
+ this.notify();
1860
+ }
1861
+ };
1862
+ //#endregion
1748
1863
  //#region src/client/compat.ts
1749
1864
  /**
1750
1865
  * Pin the busy-Enter field through whichever durable-settings service this
@@ -1755,15 +1870,20 @@ window.__ModuleLoader__.load({
1755
1870
  * as the family tab's slot-inject when the family holder is absent).
1756
1871
  */
1757
1872
  function registerSettingsFaces(ctx, deps) {
1758
- ctx.inject(["configForms"], (configForms) => {
1759
- const scope = configForms.get(deps.namespace);
1760
- scope.set(deps.field, deps.value);
1761
- deps.onScope(scope);
1873
+ const scopeInject = ctx.inject;
1874
+ scopeInject(["configForms"], (scope) => {
1875
+ const forms = scope.configForms;
1876
+ if (forms === void 0) return;
1877
+ const handle = forms.get(deps.namespace);
1878
+ handle.set(deps.field, deps.value);
1879
+ deps.onScope(handle);
1762
1880
  });
1763
- ctx.inject(["settingsScope"], (settingsScope) => {
1764
- const scope = settingsScope.bind({ namespace: deps.namespace });
1765
- scope.set(deps.field, deps.value);
1766
- deps.onScope(scope);
1881
+ scopeInject(["settingsScope"], (scope) => {
1882
+ const settingsScope = scope.settingsScope;
1883
+ if (settingsScope === void 0) return;
1884
+ const handle = settingsScope.bind({ namespace: deps.namespace });
1885
+ handle.set(deps.field, deps.value);
1886
+ deps.onScope(handle);
1767
1887
  });
1768
1888
  }
1769
1889
  /**
@@ -1831,19 +1951,33 @@ window.__ModuleLoader__.load({
1831
1951
  function apply(ctx) {
1832
1952
  ctx.effect(() => ctx.locale.register(NS, dictionaries), "dsh-input-traffic: dictionaries");
1833
1953
  const tFamily = ctx.locale.bind(NS);
1954
+ const familyScopeRef = {};
1955
+ const bridgeScope = new BridgeDocHandle(CONVERSATION_SETTINGS_NAMESPACE);
1956
+ let nativeScopeResolved = false;
1957
+ let bridgePinned = false;
1958
+ bridgeScope.subscribe(() => {
1959
+ if (bridgePinned || nativeScopeResolved) return;
1960
+ if (bridgeScope.getSnapshot().status !== "ready") return;
1961
+ bridgePinned = true;
1962
+ bridgeScope.set(BUSY_ENTER_FIELD, "queue").catch((e) => {
1963
+ console.warn("[dsh-input-traffic] bridge busyEnter pin failed:", e);
1964
+ });
1965
+ });
1966
+ ctx.slots.inject("dsh-family.tab", () => ctx.slots.register({
1967
+ name: "dsh-family.tab",
1968
+ id: "input-traffic",
1969
+ order: 50,
1970
+ label: () => tFamily("family.title"),
1971
+ locale: NS,
1972
+ inject: () => ({ scope: familyScopeRef.scope ?? bridgeScope })
1973
+ }, InputTrafficFamilyCard));
1834
1974
  registerSettingsFaces(ctx, {
1835
1975
  namespace: CONVERSATION_SETTINGS_NAMESPACE,
1836
1976
  field: BUSY_ENTER_FIELD,
1837
1977
  value: "queue",
1838
1978
  onScope: (scope) => {
1839
- ctx.slots.inject("dsh-family.tab", () => ctx.slots.register({
1840
- name: "dsh-family.tab",
1841
- id: "input-traffic",
1842
- order: 50,
1843
- label: () => tFamily("family.title"),
1844
- locale: NS,
1845
- inject: () => ({ scope })
1846
- }, InputTrafficFamilyCard));
1979
+ familyScopeRef.scope = scope;
1980
+ nativeScopeResolved = true;
1847
1981
  }
1848
1982
  });
1849
1983
  ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({