dsh-plugin-t-expert 0.4.53 → 0.4.57
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 +102 -1
- package/lib/client.js +37 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -16,7 +16,108 @@
|
|
|
16
16
|
> 发布时**不要再跑 `npm version`**(那会把已提交好的版本又升一级,发出去的号与 CHANGELOG、tag 就对不上),
|
|
17
17
|
> 只补一个 annotated tag(`git tag -a`,因为 `git push --follow-tags` 只推带注释的 tag)。
|
|
18
18
|
|
|
19
|
-
## [0.4.
|
|
19
|
+
## [0.4.57] — 未发布
|
|
20
|
+
|
|
21
|
+
**清理残留的诊断 alert** —— 0.4.55 那次「移除诊断」的字符串替换没匹配上,`alert` 留到了源码里。
|
|
22
|
+
|
|
23
|
+
- `settings.jsx` 删除 `alert('[头像诊断]…')` 残留(否则每次取头像都会弹窗)。
|
|
24
|
+
- 更新过时注释:头像 effect 的依赖早已改成 `[avatarRev]`,注释却还写着「依赖用 [](只跑一次)」。
|
|
25
|
+
|
|
26
|
+
### 教训
|
|
27
|
+
|
|
28
|
+
用字符串替换「删除调试代码」不可靠 —— 匹配串与实际代码对不上时会**静默失败**,
|
|
29
|
+
留下一段每次执行都弹窗的代码。删调试代码后要 `grep` 全库确认,而不是只看替换的返回。
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
**点选专家下载完头像后,面板自动显示头像**(之前必须重开面板)。
|
|
34
|
+
|
|
35
|
+
### 现象
|
|
36
|
+
|
|
37
|
+
用户实测:点选专家 → 头像确实下载到了本地 → **但面板还是 emoji,要重开面板/重启才看得到**。
|
|
38
|
+
|
|
39
|
+
### 原因
|
|
40
|
+
|
|
41
|
+
`warmupAssets()`(点选预热)下载完成时只做了 `refresh(remote, true)` —— **刷新名册**;
|
|
42
|
+
而**头像是在面板/弹窗挂载时单独取一次的**(`getAvatars`)—— 两者互不知情。
|
|
43
|
+
结果名册刷新了、`assets` 状态更新了,但头像那份 state 还是旧的。
|
|
44
|
+
|
|
45
|
+
### 修复
|
|
46
|
+
|
|
47
|
+
`warmupAssets` 完成后额外广播 `t-team:avatars-changed`;面板与 `@` 弹窗各加一个监听,
|
|
48
|
+
收到就把 `avatarRev` +1,带动取头像的 effect 重跑。
|
|
49
|
+
|
|
50
|
+
### 教训
|
|
51
|
+
|
|
52
|
+
**同一份数据被两处独立取用时,刷新必须一起刷** —— 只刷了名册、漏了头像,
|
|
53
|
+
表现就是「东西明明下好了,界面纹丝不动」,看起来像功能根本没生效。
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
**头像显示的真凶:RPC 返回值外面套着 `{ok, value}` 信封,我没解。**
|
|
58
|
+
|
|
59
|
+
### 根因
|
|
60
|
+
|
|
61
|
+
DSH 的 remote 调用返回值形如 `{ ok: true, value: 真实数据 }`,**必须用 `unwrap()` 解掉**
|
|
62
|
+
(`src/client/catalog.js:21`,`getCatalog` 等所有既有调用都这么写)。新加的 `getAvatars` 漏了这一步,
|
|
63
|
+
客户端拿到的是**信封本身** —— 诊断时打印 `Object.keys(map)` 得到 `["ok", "value"]`,
|
|
64
|
+
而 `map["novel-generator"]` 永远是 `undefined` → 卡片全部回退 emoji。
|
|
65
|
+
|
|
66
|
+
**把 `Object.keys(map)` 弹出来这一步是一击定位的关键**:那两个字段名直接暴露了信封结构。
|
|
67
|
+
|
|
68
|
+
### 修复
|
|
69
|
+
|
|
70
|
+
`settings.jsx` / `summon.jsx` 改用 `unwrap(await remote?.getAvatars?.(), "getAvatars")`,
|
|
71
|
+
失败兜底成 `{}`;诊断代码移除。
|
|
72
|
+
|
|
73
|
+
### 这一轮叠在一起的三个坑
|
|
74
|
+
|
|
75
|
+
1. **`useEffect` 依赖 `[remote]` 不稳定** → effect 反复重挂 → `alive` 恒为 false →
|
|
76
|
+
`setAvatars` 一次都没执行(真 bug,0.4.54 已修)。
|
|
77
|
+
2. **pnpm 的 `file:` 依赖是「拷贝」不是符号链接** → 改源码 + build 后 `desktop/node_modules`
|
|
78
|
+
里那份纹丝不动 → **前两次修复根本没到 App**,一直在看旧 client.js。必须
|
|
79
|
+
`rm -rf node_modules/dsh-plugin-t-expert && pnpm add file:...` 强制重挂
|
|
80
|
+
(`realpath` 看着像链接,很容易误判成软链 —— 这次就被它骗了一轮)。
|
|
81
|
+
3. **RPC 返回值信封没解**(本条)。
|
|
82
|
+
|
|
83
|
+
### 附带发现
|
|
84
|
+
|
|
85
|
+
`~/.t-team/experts` 是 325(目录形态)+ 323(单文件)= **648**,而 `data/experts` 是 300 + 323 = **623** ——
|
|
86
|
+
运行时目录**多 25 位历史残留**(删过但没同步回运行时),面板上的「专家 648」就是这么来的。
|
|
87
|
+
不影响功能,要清需单独处理。
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
**修掉「头像数据取到了却显示不出来」的 bug** —— 用户实测:诊断弹窗显示 RPC 返回 2 条,界面却全是 emoji。
|
|
92
|
+
|
|
93
|
+
### 根因
|
|
94
|
+
|
|
95
|
+
`settings.jsx` / `summon.jsx` 里取头像的 `useEffect` 依赖写成了 `[remote]`:
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
React.useEffect(() => {
|
|
99
|
+
let alive = true;
|
|
100
|
+
(async () => { ... if (alive && ...) setAvatars(map); })();
|
|
101
|
+
return () => { alive = false; };
|
|
102
|
+
}, [remote]); // ← desktop 上 remote 走 /api 网关,引用不稳定
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
desktop 的 remote 是经 `/api` API Gateway 构造的,**引用不稳定** → 每次重渲染 effect 都重挂 →
|
|
106
|
+
cleanup 把 `alive` 置回 `false` → **数据取到了也被 `if (alive …)` 丢掉**,state 永远停在空对象。
|
|
107
|
+
|
|
108
|
+
`alert` 写在 `.then` 里、不看 `alive`,所以照样弹「OK 返回 2 条」—— 这个反差正是定位根因的关键。
|
|
109
|
+
|
|
110
|
+
### 修复
|
|
111
|
+
|
|
112
|
+
依赖改 `[]`(只跑一次)。`remote` 在组件生命周期内是稳定的,本来就不需要跟。
|
|
113
|
+
|
|
114
|
+
### 教训
|
|
115
|
+
|
|
116
|
+
**用 `alive` 防 setState-on-unmounted 时,依赖数组必须是稳定的** —— 否则它防的不是「卸载后 setState」,
|
|
117
|
+
而是「一切 setState」,而且**完全静默**。另外这次能一步定位,靠的是把被 `catch(() => void 0)`
|
|
118
|
+
吞掉的结果/错误暴露出来:**给静默失败装一个能看见的出口**,比反复猜快得多。
|
|
119
|
+
|
|
120
|
+
|
|
20
121
|
|
|
21
122
|
**面板与 @ 弹窗显示专家头像** —— 0.4.52 只做到数据链路,这一版接上最后一环。
|
|
22
123
|
|
package/lib/client.js
CHANGED
|
@@ -67,7 +67,7 @@ var SectionBoundary = class extends import_react.default.Component {
|
|
|
67
67
|
|
|
68
68
|
// src/client/state.js
|
|
69
69
|
var NS = "t-team";
|
|
70
|
-
var CLIENT_BUILD_VERSION = "0.4.
|
|
70
|
+
var CLIENT_BUILD_VERSION = "0.4.57";
|
|
71
71
|
var PLUGIN_ID = "dsh-plugin-t-expert";
|
|
72
72
|
var DIVISION_FALLBACK = "specialized";
|
|
73
73
|
var NEW_DIVISION = "__new";
|
|
@@ -118,7 +118,13 @@ var warming = /* @__PURE__ */ new Set();
|
|
|
118
118
|
function warmupAssets(remote, expert) {
|
|
119
119
|
if (expert?.assets !== "missing" || warming.has(expert.slug)) return;
|
|
120
120
|
warming.add(expert.slug);
|
|
121
|
-
void remote.fetchAssets(expert.slug).then(() =>
|
|
121
|
+
void remote.fetchAssets(expert.slug).then(() => {
|
|
122
|
+
refresh(remote, true);
|
|
123
|
+
try {
|
|
124
|
+
window.dispatchEvent(new CustomEvent("t-team:avatars-changed"));
|
|
125
|
+
} catch {
|
|
126
|
+
}
|
|
127
|
+
}).catch(() => void 0).finally(() => warming.delete(expert.slug));
|
|
122
128
|
}
|
|
123
129
|
function displayDescription(expert, active) {
|
|
124
130
|
return active === "en" ? expert.descriptionEn || expert.description : expert.description;
|
|
@@ -21579,6 +21585,7 @@ function SettingsPanel({ t, ctx, remote, skillRemote, getActive, initialTab, vie
|
|
|
21579
21585
|
const [snapshot, setSnapshot] = import_react5.default.useState(() => snapshotOf(remote) ?? null);
|
|
21580
21586
|
const [query, setQuery] = import_react5.default.useState("");
|
|
21581
21587
|
const [avatars, setAvatars] = import_react5.default.useState({});
|
|
21588
|
+
const [avatarRev, setAvatarRev] = import_react5.default.useState(0);
|
|
21582
21589
|
const [division, setDivision] = import_react5.default.useState("");
|
|
21583
21590
|
const [status, setStatus] = import_react5.default.useState("");
|
|
21584
21591
|
const [error62, setError] = import_react5.default.useState("");
|
|
@@ -21610,16 +21617,28 @@ function SettingsPanel({ t, ctx, remote, skillRemote, getActive, initialTab, vie
|
|
|
21610
21617
|
alive = false;
|
|
21611
21618
|
};
|
|
21612
21619
|
}, [reload]);
|
|
21620
|
+
import_react5.default.useEffect(() => {
|
|
21621
|
+
const onAvatarsChanged = () => setAvatarRev((n) => n + 1);
|
|
21622
|
+
window.addEventListener("t-team:avatars-changed", onAvatarsChanged);
|
|
21623
|
+
return () => window.removeEventListener("t-team:avatars-changed", onAvatarsChanged);
|
|
21624
|
+
}, []);
|
|
21613
21625
|
import_react5.default.useEffect(() => {
|
|
21614
21626
|
let alive = true;
|
|
21615
21627
|
(async () => {
|
|
21616
|
-
|
|
21617
|
-
|
|
21628
|
+
let map2 = {};
|
|
21629
|
+
try {
|
|
21630
|
+
map2 = unwrap(await remote?.getAvatars?.(), "getAvatars") ?? {};
|
|
21631
|
+
} catch {
|
|
21632
|
+
map2 = {};
|
|
21633
|
+
}
|
|
21634
|
+
if (alive && map2 !== void 0 && map2 !== null) {
|
|
21635
|
+
setAvatars(map2);
|
|
21636
|
+
}
|
|
21618
21637
|
})();
|
|
21619
21638
|
return () => {
|
|
21620
21639
|
alive = false;
|
|
21621
21640
|
};
|
|
21622
|
-
}, [
|
|
21641
|
+
}, [avatarRev]);
|
|
21623
21642
|
const active = getActive();
|
|
21624
21643
|
async function openPrompt(expert) {
|
|
21625
21644
|
setCopyState("");
|
|
@@ -22036,16 +22055,27 @@ function SummonButton({ t, ctx, remote, getActive, insertReference, insertSkillH
|
|
|
22036
22055
|
const [popTab, setPopTab] = import_react6.default.useState(preview?.tab ?? "experts");
|
|
22037
22056
|
const [skills, setSkills] = import_react6.default.useState(null);
|
|
22038
22057
|
const [avatars, setAvatars] = import_react6.default.useState({});
|
|
22058
|
+
const [avatarRev, setAvatarRev] = import_react6.default.useState(0);
|
|
22059
|
+
import_react6.default.useEffect(() => {
|
|
22060
|
+
const onAvatarsChanged = () => setAvatarRev((n) => n + 1);
|
|
22061
|
+
window.addEventListener("t-team:avatars-changed", onAvatarsChanged);
|
|
22062
|
+
return () => window.removeEventListener("t-team:avatars-changed", onAvatarsChanged);
|
|
22063
|
+
}, []);
|
|
22039
22064
|
import_react6.default.useEffect(() => {
|
|
22040
22065
|
let alive = true;
|
|
22041
22066
|
(async () => {
|
|
22042
|
-
|
|
22067
|
+
let map2 = {};
|
|
22068
|
+
try {
|
|
22069
|
+
map2 = unwrap(await remote?.getAvatars?.(), "getAvatars") ?? {};
|
|
22070
|
+
} catch {
|
|
22071
|
+
map2 = {};
|
|
22072
|
+
}
|
|
22043
22073
|
if (alive && map2 !== void 0 && map2 !== null) setAvatars(map2);
|
|
22044
22074
|
})();
|
|
22045
22075
|
return () => {
|
|
22046
22076
|
alive = false;
|
|
22047
22077
|
};
|
|
22048
|
-
}, [
|
|
22078
|
+
}, [avatarRev]);
|
|
22049
22079
|
const [skillQuery, setSkillQuery] = import_react6.default.useState("");
|
|
22050
22080
|
const [skillsDiag, setSkillsDiag] = import_react6.default.useState("");
|
|
22051
22081
|
import_react6.default.useEffect(() => {
|
package/package.json
CHANGED