dsh-dock 0.9.8 → 0.9.9

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/client.js CHANGED
@@ -2138,13 +2138,17 @@ var FeatureBoundary = class extends import_react.default.Component {
2138
2138
  static getDerivedStateFromError(error) {
2139
2139
  return { error };
2140
2140
  }
2141
+ componentDidCatch(error) {
2142
+ console.error("[dsh-dock] \u529F\u80FD\u89C6\u56FE\u6E32\u67D3\u51FA\u9519\uFF1A", error);
2143
+ }
2141
2144
  render() {
2142
2145
  if (this.state.error) {
2143
2146
  const msg = this.state.error && this.state.error.message ? this.state.error.message : String(this.state.error);
2147
+ const who = this.props && this.props.label ? "\u300C" + this.props.label + "\u300D" : "\u529F\u80FD\u89C6\u56FE";
2144
2148
  return import_react.default.createElement(
2145
2149
  "div",
2146
2150
  { className: "dockm-note dockm-err" },
2147
- "\u529F\u80FD\u89C6\u56FE\u6E32\u67D3\u51FA\u9519\uFF1A" + msg + "\uFF08\u8BE5\u529F\u80FD\u6765\u81EA\u5916\u90E8\u5305\uFF0C\u4E0D\u5F71\u54CD\u9762\u677F\u5176\u4ED6\u529F\u80FD\uFF09"
2151
+ who + "\u6E32\u67D3\u51FA\u9519\uFF1A" + msg + "\uFF08\u5DF2\u9694\u79BB\uFF0C\u4E0D\u5F71\u54CD\u9762\u677F\u5176\u4ED6\u529F\u80FD\uFF1B\u8BE6\u60C5\u89C1\u6D4F\u89C8\u5668\u63A7\u5236\u53F0\uFF09"
2148
2152
  );
2149
2153
  }
2150
2154
  return this.props.children;
@@ -5378,12 +5382,12 @@ function AnimationView(props) {
5378
5382
  const st = snap.status;
5379
5383
  const active = st && st.active ? st.active : [];
5380
5384
  const recent = st && st.recent ? st.recent.slice(0, 6) : [];
5385
+ const waitingCount = active.reduce((n, t) => n + (Array.isArray(t.approvals) ? t.approvals.length : 0), 0);
5381
5386
  const permNote = typeof Notification === "undefined" ? "\u5F53\u524D\u6D4F\u89C8\u5668\u4E0D\u652F\u6301\u7CFB\u7EDF\u901A\u77E5" : Notification.permission === "granted" ? "\u5DF2\u6388\u6743 \xB7 \u4EC5\u9875\u9762\u540E\u53F0\u65F6\u63A8\u9001" : Notification.permission === "denied" ? "\u5DF2\u88AB\u6D4F\u89C8\u5668\u62D2\u7EDD\uFF08\u9700\u5728\u6D4F\u89C8\u5668\u6743\u9650\u8BBE\u7F6E\u91CC\u91CD\u65B0\u5141\u8BB8\uFF09" : "\u672A\u6388\u6743 \xB7 \u5F00\u542F\u65F6\u4F1A\u8BF7\u6C42\u6388\u6743\uFF0C\u4EC5\u9875\u9762\u540E\u53F0\u65F6\u63A8\u9001";
5382
5387
  const rows = [];
5383
5388
  if (!cfg) {
5384
5389
  rows.push(/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dkan-note", children: snap.error ? "\u72B6\u6001\u4E0D\u53EF\u7528\uFF1A" + snap.error : snap.loading ? "\u6B63\u5728\u62C9\u53D6\u4EFB\u52A1\u72B6\u6001\u2026" : "\u7B49\u5F85\u4EFB\u52A1\u72B6\u6001" }, "load"));
5385
5390
  } else {
5386
- const waitingCount2 = active.reduce((n, t) => n + (Array.isArray(t.approvals) ? t.approvals.length : 0), 0);
5387
5391
  rows.push(
5388
5392
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dkan-sec", children: [
5389
5393
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dkan-sec-head", children: [
@@ -10141,7 +10145,7 @@ var feature8 = {
10141
10145
  };
10142
10146
 
10143
10147
  // src/client.jsx
10144
- var DOCK_VERSION = "0.9.7";
10148
+ var DOCK_VERSION = "0.9.9";
10145
10149
  var BUILTIN_FEATURES = [feature, feature2, feature3, feature4, feature5, feature6, feature7, feature8];
10146
10150
  var PLANNED_FEATURES = [];
10147
10151
  var PLANNED_NOTES = {};
@@ -10447,8 +10451,8 @@ function DockModal() {
10447
10451
  "div",
10448
10452
  { className: "dockm-view" },
10449
10453
  import_react20.default.createElement(
10450
- mod.external ? FeatureBoundary : import_react20.default.Fragment,
10451
- null,
10454
+ FeatureBoundary,
10455
+ { key: mod.id, label: mod.name },
10452
10456
  import_react20.default.createElement(View, { ctx: ctxRef.current, feature: mod, params: navParams })
10453
10457
  )
10454
10458
  ) : null;
@@ -10614,8 +10618,8 @@ function HomeView(props) {
10614
10618
  const enabled = !!(st && st.enabled);
10615
10619
  const Stat = m.HomeStat;
10616
10620
  const statNode = m.planned ? import_react20.default.createElement("span", null, PLANNED_NOTES[m.id] || "\u5F85\u63A5\u5165\uFF1A\u89C1 README \u8DEF\u7EBF\u56FE") : enabled && Stat ? import_react20.default.createElement(
10617
- m.external ? FeatureBoundary : import_react20.default.Fragment,
10618
- null,
10621
+ FeatureBoundary,
10622
+ { key: m.id, label: m.name },
10619
10623
  import_react20.default.createElement(Stat, { ctx })
10620
10624
  ) : import_react20.default.createElement("span", null, "\u5DF2\u505C\u7528\uFF0C\u542F\u7528\u540E\u5728\u6B64\u5C55\u793A\u8FD0\u884C\u6982\u8981");
10621
10625
  return import_react20.default.createElement(
@@ -10697,8 +10701,8 @@ function DockPanel() {
10697
10701
  "div",
10698
10702
  { className: "dock-body" },
10699
10703
  import_react20.default.createElement(
10700
- f.external ? FeatureBoundary : import_react20.default.Fragment,
10701
- null,
10704
+ FeatureBoundary,
10705
+ { key: f.id, label: f.name },
10702
10706
  import_react20.default.createElement(View, { ctx, feature: f })
10703
10707
  )
10704
10708
  ) : null;
@@ -10750,14 +10754,17 @@ function DockChips(props) {
10750
10754
  const items = [];
10751
10755
  for (const f of allModules()) {
10752
10756
  if (f.planned || !stateOf(f.id).enabled || !chipShown(f.id) || typeof f.Chip !== "function") continue;
10753
- items.push(import_react20.default.createElement(f.Chip, {
10754
- key: f.id,
10755
- ctx: props.ctx,
10756
- feature: f,
10757
- session: props.session,
10758
- sessionId: props.sessionId,
10759
- input: props.input
10760
- }));
10757
+ items.push(import_react20.default.createElement(
10758
+ FeatureBoundary,
10759
+ { key: f.id, label: f.name },
10760
+ import_react20.default.createElement(f.Chip, {
10761
+ ctx: props.ctx,
10762
+ feature: f,
10763
+ session: props.session,
10764
+ sessionId: props.sessionId,
10765
+ input: props.input
10766
+ })
10767
+ ));
10761
10768
  }
10762
10769
  if (items.length === 0) return null;
10763
10770
  return import_react20.default.createElement("div", { className: "dockchip-row" }, items);
@@ -10771,7 +10778,7 @@ function FeatureOverlays() {
10771
10778
  if (f.planned || !stateOf(f.id).enabled || typeof f.Overlay !== "function") continue;
10772
10779
  items.push(import_react20.default.createElement(
10773
10780
  FeatureBoundary,
10774
- { key: f.id },
10781
+ { key: f.id, label: f.name },
10775
10782
  import_react20.default.createElement(f.Overlay, { ctx: ctxRef.current, feature: f })
10776
10783
  ));
10777
10784
  }
@@ -1553,6 +1553,10 @@ function AnimationView(props) {
1553
1553
  const st = snap.status;
1554
1554
  const active = st && st.active ? st.active : [];
1555
1555
  const recent = st && st.recent ? st.recent.slice(0, 6) : [];
1556
+ // 等待确认的审批项总数:运行状态标题与页脚都用,必须在 if/else 之外声明。
1557
+ // ⚠️ 曾误写在 if(!cfg) 的 else 块内、却在块外引用 → ReferenceError: waitingCount is not defined,
1558
+ // 整个视图渲染抛错、宿主把面板插槽换成空 div(点「任务动画」界面直接消失)。
1559
+ const waitingCount = active.reduce((n, t) => n + (Array.isArray(t.approvals) ? t.approvals.length : 0), 0);
1556
1560
  const permNote = typeof Notification === "undefined"
1557
1561
  ? "当前浏览器不支持系统通知"
1558
1562
  : Notification.permission === "granted" ? "已授权 · 仅页面后台时推送"
@@ -1562,7 +1566,6 @@ function AnimationView(props) {
1562
1566
  if (!cfg) {
1563
1567
  rows.push(<div key="load" className="dkan-note">{snap.error ? "状态不可用:" + snap.error : (snap.loading ? "正在拉取任务状态…" : "等待任务状态")}</div>);
1564
1568
  } else {
1565
- const waitingCount = active.reduce((n, t) => n + (Array.isArray(t.approvals) ? t.approvals.length : 0), 0);
1566
1569
  rows.push(
1567
1570
  <div key="anim" className="dkan-sec">
1568
1571
  <div className="dkan-sec-head">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-dock",
3
- "version": "0.9.8",
3
+ "version": "0.9.9",
4
4
  "description": "DeepSeek Harness 功能坞插件(dsh-dock):模块化功能中枢——每个功能一个 features/ 模块(可单独提取成包发布,又可经 dockBridge 装回面板)。含用量记录(LLM 调用记账/费用统计)、模型设置、图片理解代理、模型余额、任务动画(19 种运行动画 + 桌面伙伴 3D 场景、完成/需确认通知与提示音、钉钉/飞书推送,开关独立、配置持久化);侧栏入口 + 可拖拽弹窗(最大化/最小化/缩放)。",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -40,6 +40,10 @@
40
40
  ],
41
41
  "author": "wycto",
42
42
  "license": "MIT",
43
+ "scripts": {
44
+ "build:client": "node scripts/build-client.mjs",
45
+ "test:client": "node scripts/test-client-views.mjs"
46
+ },
43
47
  "repository": {
44
48
  "type": "git",
45
49
  "url": "git+https://github.com/wycto/dsh-dock.git"
package/src/client.jsx CHANGED
@@ -28,7 +28,7 @@ import { feature as fGames } from "../features/games/view.jsx";
28
28
  import { feature as fMobileRelay } from "../features/mobile-relay/view.jsx";
29
29
 
30
30
  const name = "dsh-dock";
31
- const DOCK_VERSION = "0.9.7";
31
+ const DOCK_VERSION = "0.9.9";
32
32
 
33
33
  // ---- 内置功能注册表:新功能 = features/<id>/ 加模块 + 这里 import 一行 ----
34
34
  const BUILTIN_FEATURES = [fTokenlog, fModelconfig, fHeartbeat, fTheme, fBalance, fAnimation, fGames, fMobileRelay];
@@ -357,12 +357,14 @@ function DockModal() {
357
357
  const isHome = active === "home";
358
358
  const mod = isHome ? null : (MODULES.find((m) => m.id === active) || MODULES[0]);
359
359
  const st = mod ? stateOf(mod.id) : null;
360
- // 外部包视图包错误边界;视图统一收到 { ctx, feature, params } props
360
+ // 视图统一收到 { ctx, feature, params } props
361
361
  //(params 来自导航总线:如 { provider } 高亮余额行、{ sessionId } 按会话筛选用量)
362
362
  const View = mod ? mod.View : null;
363
+ // 所有视图都包错误边界(含内置):视图抛错只降级为一行提示,不能把整块面板打没。
364
+ // key 用 mod.id:切换功能时重建边界,避免上一个功能的错误态留在新页面上。
363
365
  const viewNode = mod && View
364
366
  ? react.createElement("div", { className: "dockm-view" },
365
- react.createElement(mod.external ? FeatureBoundary : react.Fragment, null,
367
+ react.createElement(FeatureBoundary, { key: mod.id, label: mod.name },
366
368
  react.createElement(View, { ctx: ctxRef.current, feature: mod, params: navParams })))
367
369
  : null;
368
370
  const enabledCount = MODULES.filter((m) => { const s = stateOf(m.id); return !!(s && s.enabled); }).length;
@@ -500,7 +502,7 @@ function HomeView(props) {
500
502
  const statNode = m.planned
501
503
  ? react.createElement("span", null, PLANNED_NOTES[m.id] || "待接入:见 README 路线图")
502
504
  : enabled && Stat
503
- ? react.createElement(m.external ? FeatureBoundary : react.Fragment, null,
505
+ ? react.createElement(FeatureBoundary, { key: m.id, label: m.name },
504
506
  react.createElement(Stat, { ctx: ctx }))
505
507
  : react.createElement("span", null, "已停用,启用后在此展示运行概要");
506
508
  return react.createElement("div", {
@@ -560,7 +562,7 @@ function DockPanel() {
560
562
  const View = f.View;
561
563
  const viewNode = (!f.planned && st.enabled && View)
562
564
  ? react.createElement("div", { className: "dock-body" },
563
- react.createElement(f.external ? FeatureBoundary : react.Fragment, null,
565
+ react.createElement(FeatureBoundary, { key: f.id, label: f.name },
564
566
  react.createElement(View, { ctx: ctx, feature: f })))
565
567
  : null;
566
568
  return react.createElement("div", { className: "dock-card", key: f.id },
@@ -608,10 +610,11 @@ function DockChips(props) {
608
610
  const items = [];
609
611
  for (const f of allModules()) {
610
612
  if (f.planned || !stateOf(f.id).enabled || !chipShown(f.id) || typeof f.Chip !== "function") continue;
611
- items.push(react.createElement(f.Chip, {
612
- key: f.id, ctx: props.ctx, feature: f,
613
- session: props.session, sessionId: props.sessionId, input: props.input,
614
- }));
613
+ items.push(react.createElement(FeatureBoundary, { key: f.id, label: f.name },
614
+ react.createElement(f.Chip, {
615
+ ctx: props.ctx, feature: f,
616
+ session: props.session, sessionId: props.sessionId, input: props.input,
617
+ })));
615
618
  }
616
619
  if (items.length === 0) return null;
617
620
  return react.createElement("div", { className: "dockchip-row" }, items);
@@ -627,7 +630,7 @@ function FeatureOverlays() {
627
630
  const items = [];
628
631
  for (const f of allModules()) {
629
632
  if (f.planned || !stateOf(f.id).enabled || typeof f.Overlay !== "function") continue;
630
- items.push(react.createElement(FeatureBoundary, { key: f.id },
633
+ items.push(react.createElement(FeatureBoundary, { key: f.id, label: f.name },
631
634
  react.createElement(f.Overlay, { ctx: ctxRef.current, feature: f })));
632
635
  }
633
636
  if (items.length === 0) return null;
package/src/shared.js CHANGED
@@ -2,8 +2,12 @@
2
2
  import react from "react";
3
3
 
4
4
  /**
5
- * 功能视图错误边界:外部功能包注册进来的视图渲染抛错时降级为错误提示,
6
- * 不拖垮整个功能坞面板(内置视图不包——它们与外壳同包发布、同生命周期)。
5
+ * 功能视图错误边界:视图渲染抛错时降级为一行错误提示,不拖垮整块面板 UI。
6
+ *
7
+ * ⚠️ 内置视图也必须包(v0.9.9 起):宿主把每个插槽条目包在 SlotErrorBoundary 里,
8
+ * 条目内任何渲染抛错都会被宿主换成空 div——表现为「点开功能坞界面整个没了」而侧栏
9
+ * 入口按钮仍是选中态。v0.9.3~0.9.8 的 animation 视图就是这样被一个 ReferenceError 打没的。
10
+ * label 用于提示里点明是哪个功能;调用处应带 key(切换功能时重建,否则错误态会跟着串页)。
7
11
  */
8
12
  export class FeatureBoundary extends react.Component {
9
13
  constructor(props) {
@@ -13,11 +17,15 @@ export class FeatureBoundary extends react.Component {
13
17
  static getDerivedStateFromError(error) {
14
18
  return { error: error };
15
19
  }
20
+ componentDidCatch(error) {
21
+ console.error("[dsh-dock] 功能视图渲染出错:", error);
22
+ }
16
23
  render() {
17
24
  if (this.state.error) {
18
25
  const msg = this.state.error && this.state.error.message ? this.state.error.message : String(this.state.error);
26
+ const who = this.props && this.props.label ? "「" + this.props.label + "」" : "功能视图";
19
27
  return react.createElement("div", { className: "dockm-note dockm-err" },
20
- "功能视图渲染出错:" + msg + "(该功能来自外部包,不影响面板其他功能)");
28
+ who + "渲染出错:" + msg + "(已隔离,不影响面板其他功能;详情见浏览器控制台)");
21
29
  }
22
30
  return this.props.children;
23
31
  }