dsh-plugin-t-expert 0.4.9 → 0.4.26

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +494 -0
  2. package/lib/client.js +543 -107
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -16,6 +16,500 @@
16
16
  > 发布时**不要再跑 `npm version`**(那会把已提交好的版本又升一级,发出去的号与 CHANGELOG、tag 就对不上),
17
17
  > 只补一个 annotated tag(`git tag -a`,因为 `git push --follow-tags` 只推带注释的 tag)。
18
18
 
19
+ ## [0.4.26] — 未发布
20
+
21
+ **放大头像 emoji、并让淡色底看得见** —— 用户口径:「给这些图标加一个淡淡的背景,现在 emoji 都很小,
22
+ 看着很不协调」。
23
+
24
+ ### 变更
25
+
26
+ - emoji 字号 **22 → 26px**(44px 的圆里更饱满,上下各留 9px)。
27
+ - 专家的淡色圆底 alpha **14% → 24%**(`color-mix`):14% 铺在白卡片上几乎看不出圆 ——
28
+ 圆没边、里面又是个小 emoji,整体就显得飘。
29
+ - 没有 `color` 时的兜底底色从近白的 `--dsw-alias-bg-layer-3` 换成淡灰的
30
+ `--dsw-alias-interactive-bg-hover`(老数据、或颜色名在宿主上不被支持时,也还看得见一个圆);
31
+ 技能卡的 🧰 本来就用这档淡灰,两边因此是同一套观感。
32
+
33
+ ### 自检
34
+
35
+ - 同一条断言改写:`tintOf` 的 alpha 24%、头像字号 26px,并加了一条否定条件 ——
36
+ 兜底底色不许再回到 `bg-layer-3`(那正是"看不见圆"的老写法)。
37
+ - typecheck / check-client(13 个文件 0 处未定义标识符)/ render-smoke(9/9)/
38
+ verify(**518/518**)全绿。
39
+
40
+ ## [0.4.25] — 未发布
41
+
42
+ **面板改成不透明实色、去掉毛玻璃** —— 用户口径:在「保持官方材质 / 改不透明实色 / 缩小模糊半径」
43
+ 三选一里选了 **B**。
44
+
45
+ ### 变更
46
+
47
+ - `.t-team-pop-official`(官方 `MenuSurface` 那条路径)与 `.t-team-pop`(缺官方件时的兜底)都改成
48
+ **实色 `--dsw-alias-bg-layer-3`**,两条规则里的 `backdrop-filter` 全部拿掉。
49
+ - **只换父级底色不够**:官方材质层是 `MenuSurface` 内部那个 `aria-hidden` 的绝对定位兄弟
50
+ (`inset:0` 铺满,`background` 与 `backdrop-filter` 都挂在它身上),所以补了一条
51
+ `.t-team-pop-official > [aria-hidden="true"]{display:none}` 把它关掉 —— 否则它照样在跑 `blur(40px)`。
52
+ - 这是面板上**仅剩的大合成开销**:面板内容是实心卡片,本来也不需要透出背后内容。代价是失去
53
+ 「与官方菜单同款毛玻璃」的观感(最初「材质交给官方」时就明说过这条代价)。
54
+ - 顺带更新 css.js 里「用 `--dsw-specific-menu` 填色就必须配 `backdrop-filter`」那条历史注释:
55
+ 规矩仍适用于别处还在用菜单材质的地方(如 `.t-team-expert-pick-panel`),但面板自己不再走这条路。
56
+
57
+ ### 自检
58
+
59
+ - 改写 1 条断言(官方材质那条从「清掉自绘材质」改成「铺实色 + 关掉官方材质层」),新增 1 条:
60
+ **两条容器规则里都不许再有真的 `backdrop-filter`**(允许 `backdrop-filter:none`)。
61
+ - typecheck / check-client(13 个文件 0 处未定义标识符)/ render-smoke(9/9)/
62
+ verify(**518/518**,`ci.yml` 同步)全绿。
63
+
64
+ ## [0.4.24] — 未发布
65
+
66
+ **回到系统 emoji 头像** —— 用户口径:「还是采用系统 emoji,(太)卡了」。
67
+
68
+ ### 回退
69
+
70
+ - 撤销 0.4.23 的 Fluent Emoji 方案(`git revert 5731cbb`):删掉内联的 22 枚 SVG
71
+ (`src/client/fluent-icons.jsx`)、`DivisionIcon`、`.t-team-div-icon`,连同它们的许可声明与自检断言。
72
+ 头像回到 **专家自己的 emoji + 名册 `color` 的淡色圆底**(0.4.21 / 0.4.22 那一版),
73
+ 回退链简化为 emoji → 🧩。
74
+ - **为什么卡**(回退时的分析):那 22 枚是 Fluent 的 **Color** 档,每枚 5–27 KB,内含大量
75
+ `feGaussianBlur` / `feColorMatrix` 复合滤镜与渐变;它们以 data URI 形式各是一份**独立 SVG 文档**,
76
+ 每张卡片一个 `<img>` —— 浏览器要逐张解析 XML、解码并光栅化,还叠在面板已有的
77
+ `backdrop-filter` 毛玻璃上。系统 emoji 是**字体字形**,走字体缓存、几乎零成本;
78
+ 这也是「换上去就卡、换回来就好」的直接原因。客户端产物同时从 1.29 MB 回到约 950 KB。
79
+ - **保留** `tools/sync-fluent-icons.mjs`(22 枚的映射表与上游 sha256 都在里面):将来若想要
80
+ 统一插画风,跑一次就能重新生成;脚本头部已注明「当前未启用」与原因。
81
+
82
+ ### 自检
83
+
84
+ - 随回退一并还原:断言数 519 → **517**(`ci.yml` 同步),`[13b]` 的随包许可清单不再列 Fluent。
85
+ - typecheck / check-client(13 个文件 0 处未定义标识符)/ render-smoke(9/9)/
86
+ verify(**517/517**)全绿。
87
+
88
+ ## [0.4.22] — 未发布
89
+
90
+ **补上「颜色名」形态的色底** —— 0.4.21 的淡色圆底只认 `#RRGGBB`,而名册里 308 位专家有 **182 位**
91
+ 写的是 CSS 颜色名(`blue` / `teal` / `navy` / `gold`…),这些人的头像当时会退回中性灰底。
92
+
93
+ ### 修复
94
+
95
+ - `tintOf` 改用 `color-mix(in srgb, <color> 14%, transparent)`:hex 与颜色名两种写法都吃,
96
+ 也不用自己算 rgba(颜色名后面拼 alpha 后缀是无效值,整条声明会被丢掉)。
97
+ 只放行「#RRGGBB 或纯字母颜色名」两种形态。
98
+ - 老宿主不支持 `color-mix` 时该声明失效,退回 CSS 的中性底色(不影响布局,只是少了那个性色)。
99
+
100
+ ### 自检
101
+
102
+ - 断言补上颜色名那条正则与 `color-mix` 写法 —— 原来钉的是 `${value}24`,那正是 0.4.21 漏掉
103
+ 颜色名的原因:断言把实现钉死在了 hex 上。
104
+ - typecheck / check-client / render-smoke 9/9 / verify(**517/517**)全绿。
105
+
106
+ ## [0.4.21] — 未发布
107
+
108
+ **卡片头像改回彩色 emoji(配专家专属色底)** —— 用户口径:「有没有更好的图标库,最好带颜色的」。
109
+
110
+ ### 变更
111
+
112
+ - 头像内容**优先用专家自己的 emoji**:系统 emoji 本身就是彩色的,而且名册里 323 位专家各自带一个
113
+ (跟分区无关、每人不同),配上名册 `color` 压出的淡色圆底,正是参考图那种「淡色圆 + 彩色图形」。
114
+ 0.4.20 换上的官方线性图标退居**回退位**:没有 emoji 的老数据才用它,最后兜 🧩。
115
+ - 技能卡头像从官方 `Skill` 图标换回彩色 emoji 🧰(技能没有 color,保留固定灰底)。
116
+ - 淡色底 alpha 12%→14%(`1f`→`24`):里面放的是彩色图形,底色要再显一点才看得出圆。
117
+ - emoji 字号 20→22px(44px 的圆里更饱满)。顺带删掉不再使用的 `SKILL_ICON` 导出。
118
+
119
+ ### 彩色图标库调研(都是第一手许可文件核过的)
120
+
121
+ | 库 | 许可 | 单枚体积(实测) | 风格 |
122
+ |---|---|---|---|
123
+ | **Fluent Emoji — Flat** | MIT | **1–3 KB** | 扁平彩色(推荐,22 枚约 30–50KB) |
124
+ | Fluent Emoji — Color | MIT | 5–22 KB | 微软 3D 感插画(22 枚约 200–350KB) |
125
+ | Twemoji | 图形 CC-BY 4.0(需署名) | 1–3 KB | 扁平 |
126
+ | OpenMoji | CC BY-SA 4.0 | — | ShareAlike 对插件许可有传染性,不建议 |
127
+ | DiceBear | MIT | — | 生成式头像,但 323 位要生成/内联 323 张,太重 |
128
+
129
+ 本轮先落地**零依赖那一条**(专家自带 emoji)。要统一插画风的话,下一步可把 Fluent Emoji 的
130
+ 22 枚按分区内联(Flat 约 30–50KB / Color 约 200–350KB,MIT,加一条 THIRD-PARTY-NOTICES)——
131
+ 预览图已给用户看过,等他定。
132
+
133
+ ### 自检
134
+
135
+ - 更新 2 条断言:头像改为「emoji 优先 + 回退分区图标 → 🧩」(含 22px 字号与 `24` alpha),
136
+ 技能卡改为「彩色 emoji + 固定灰底」;图标替身数量断言 23 → 22(`SKILL_ICON` 已删)。
137
+ - typecheck / check-client(13 个文件 0 处未定义标识符)/ render-smoke(9/9)/
138
+ verify(**517/517**)全绿。
139
+
140
+ ## [0.4.20] — 未发布
141
+
142
+ **专家卡照新参考图重做(官方图标头像 + 大名字 + 底部标签),并修掉「名称不在左边」** ——
143
+ 用户口径:「对话框 t 专家弹窗里面的卡片都设计成这种风格,名称都靠左,更换图标,网上找找有没有精美点的图标」。
144
+
145
+ ### 新增
146
+
147
+ - **头像换成官方图标**:22 个分区各配一个宿主自带的官方产品图标(`Icon*OutlineMedium`),
148
+ 技能卡固定用官方 `Skill` 图标;底色用名册 frontmatter 里的 `color` 压一层 12% 的淡色圆、
149
+ 图标本身用同色实色 —— 就是参考图那种「淡色圆 + 同色图形」。
150
+ 逐级回退:分区图标 → 专家自己的 emoji → 🧩(宿主换图标命名也不会空着)。
151
+ 为什么不去外链图标库:宿主 `primitives` 的 280 个导出里本来就有一百多个 `Icon*`
152
+ (用它们等于跟官方同一套视觉语言,本插件一贯的口径);引 CDN 在离线/内网会白给;
153
+ 第三方库(Lucide / Tabler / Phosphor)内联几十 KB 虽然可行,但要连带许可署名与后续升级,
154
+ 收益只是「换一套线性风格」。真要换我再动。
155
+ - **底部标签行**(参考图里那排胶囊):专家卡放「分区 [+ 自建]」、技能卡放「技能 [+ 仅用户]」;
156
+ 「仅用户」从副标题挪到这里,不再两处重复。
157
+ - 尺寸照参考图再放大一轮:圆角 12→16、头像与头部 40→44、名称 14→15、底高 114→146
158
+ (14+44+8+36+8+20+14+2),`contain-intrinsic-size` 同步到 146px。
159
+
160
+ ### 修复
161
+
162
+ - **「名称都靠左」是一处真 bug,不是排版口味**:`.t-team-pop-head` 这个名字同时被**面板标题行**
163
+ (顶栏「T专家 + 关闭」)用着,而那条规则带 `justify-content:space-between` —— 于是卡片里的
164
+ 「头像 + 名称」被推成两端对齐,名称跑到右边去了。现在用 `.t-team-pop-item .t-team-pop-head`
165
+ 这条更高特异性的规则把它掰回 `flex-start`。断言把两件事同时钉住:卡片头必须覆盖它,
166
+ 而面板标题行那条 `space-between` 也必须在(那才是顶栏该有的样子)。
167
+
168
+ ### 自检
169
+
170
+ - 更新 3 条(头像 40→44、卡片 114→146、底部标签"回来"了所以那条否定断言改为"带标签但不带召唤按钮");
171
+ 新增 5 条:图标 + `color` 淡色底与逐级回退、技能卡头像、名称靠左的修正、底部标签内容、
172
+ **替身补齐全部 23 个官方图标**(缺一个,那个分区就会静默走 emoji,「图标能渲染」这条等于没覆盖)。
173
+ - typecheck / check-client(13 个文件 0 处未定义标识符)/ render-smoke(9/9)/
174
+ verify(**517/517**,`ci.yml` 计数同步)全绿。
175
+ - 又双叒踩了一次 CSS 模板的裸反引号(这次是在注释里写 `justify-content:…`),
176
+ `check-client-refs` 报出行号、esbuild 直接报 `Expected ";" but found "justify"` —— 已改。
177
+
178
+ ## [0.4.19] — 未发布
179
+
180
+ **箭头圆改成实色、并且真正是正圆** —— 用户口径:「为什么是半透明的?还有要正圆」。
181
+
182
+ ### 修复
183
+
184
+ - **半透明的来源是圆自己,不是面板**:上一版底色用了 `--dsw-alias-interactive-bg-hover`,
185
+ 这个 token 本身就是 alpha 色(fallback `#0000000d`,约 5% 黑)—— 叠在半透明的官方菜单材质上,
186
+ 整个圆都透出底下的内容。现在换成**实色**的 `--dsw-alias-bg-layer-3`(fallback `#f7f8fa`)。
187
+ - **正圆两处**:
188
+ · 分类标签与圆**同高 32px**(原先标签 30px、圆 32px)。圆是绝对定位、不参与布局,行高由标签决定
189
+ —— 两者不等时圆会上下各溢出一部分到行外,看着就是个「被切过的圆」;
190
+ · 垂直居中去掉 `top:50% + translateY(-50%)`,改成 `top:0;bottom:0;margin:auto 0`:半像素高度下
191
+ `translateY` 会把圆落在 .5px 上、边缘被重采样,这是「看着不圆」的另一个原因。
192
+
193
+ ### 说明(面板本身的半透明)
194
+
195
+ 如果问的是**整块弹窗**透出下面内容:那是官方菜单材质(`--dsw-menu-surface-fill` 是半透明色
196
+ + `backdrop-filter: blur(40px)`),属于上一轮「材质交给官方 `MenuSurface`」的既定行为,与这次的圆无关。
197
+ 要改成不透明实色完全可行(在 `.t-team-pop-official` 上覆盖背景并去掉 backdrop-filter),
198
+ 代价是观感与官方菜单不再一致、也会失去 macOS 下的 vibrancy 底层 —— 这一条等确认后再动。
199
+
200
+ ### 自检
201
+
202
+ - 「箭头」那条断言重写为钉住这次的两件事:底色必须是 `bg-layer-3`(并加了一条**否定**条件:
203
+ 圆底色不许再出现 `interactive-bg-hover`),垂直居中必须是 `top:0;bottom:0;margin:auto 0`
204
+ 且规则里不许出现 `translateY`;同一断言里再钉住标签与圆同高(`height:32px`)。
205
+ - typecheck / check-client(13 个文件 0 处未定义标识符)/ render-smoke(9/9)/
206
+ verify(**512/512**)全绿。
207
+
208
+ ## [0.4.18] — 未发布
209
+
210
+ **分类行两端的箭头照参考图放大成实心圆** —— 用户口径:「这种圆形风格行吗,再大一点点」
211
+ (附参考图:浅灰实心圆 + 深灰箭头,没有明显描边)。
212
+
213
+ ### 变更
214
+
215
+ - 直径 **26px → 32px**,箭头字号 **15px → 18px**(参考图里箭头约占圆的 40%)。
216
+ - 底色 `--dsw-alias-bg-layer-2` → `--dsw-alias-interactive-bg-hover`:参考图里那层灰比底板明显深,
217
+ 而 `layer-2` 铺在半透明菜单材质上几乎融进背景 —— 这其实是上一轮「看着不明显」的另一半原因
218
+ (不只是没有边线)。
219
+ - 描边随之**收淡**:`border-l2` → `border-l1`(保留一条收边、不再抢眼),hover 时回到 `border-l2`
220
+ 并把箭头颜色提到 `label-primary`。
221
+ - 让位内边距 **32px → 38px**(= 直径 32 + 6px 间隙):箭头是绝对定位的浮层元素、不占布局高度,
222
+ 这条 padding 是唯一能把它压住的最后一个标签让出来的手段,尺寸一变它必须跟着变。
223
+
224
+ ### 自检
225
+
226
+ - 「对齐」那条断言里的让位值同步改成 38px;「箭头」那条断言重写为钉住参考图的四件:
227
+ `box-sizing:border-box` + `width/height:32px`、`border:1px solid var(--dsw-alias-border-l1)`、
228
+ `background:var(--dsw-alias-interactive-bg-hover…)`、`font-size:18px`,以及 hover 收边到 `border-l2`。
229
+ - typecheck / check-client(13 个文件 0 处未定义标识符)/ render-smoke(9/9)/
230
+ verify(**512/512**)全绿。
231
+
232
+ ## [0.4.17] — 未发布
233
+
234
+ **分类行两端的箭头加上描边** —— 用户口径:「左右滚动的图标 < > 最好添加一个表边线,现在看着不明显」。
235
+
236
+ ### 修复
237
+
238
+ - `.t-team-div-scroll` 原先 `border:0` + 只有一层浅灰底:它浮在半透明的官方菜单材质上,圆的边界
239
+ 与底板糊在一起,看着像一个"没画完"的灰点。现在改成与卡片同一套描边 —— `1px solid
240
+ var(--dsw-alias-border-l2)`(浅色下约 10% 黑),hover 时加深到 `border-l3`
241
+ (与 `.t-team-pop-item:hover` 同一档)。
242
+ - 加描边必须同时补 `box-sizing:border-box`:这个圆是 `width/height:26px` 且原本没有
243
+ `box-sizing`,边框会把外径撑到 28px、把分类行的高度顶起来 —— 分类行的高度直接决定面板高度上限。
244
+
245
+ ### 自检
246
+
247
+ - 新增 1 条断言钉住这两点:`.t-team-div-scroll` 规则里必须同时有 `box-sizing:border-box` 与
248
+ `border:1px solid var(--dsw-alias-border-l2)`,且 hover 规则要加深到 `border-l3`。
249
+ - typecheck / check-client(13 个文件 0 处未定义标识符)/ render-smoke(9/9)/
250
+ verify(**512/512**,`ci.yml` 计数同步到 512)全绿。
251
+ - 又踩了一次 CSS 模板的裸反引号(这次是把 `border:0` 括进了反引号里),`check-client-refs` 立刻报出行号。
252
+
253
+ ## [0.4.16] — 未发布
254
+
255
+ **分类行支持鼠标滚轮左右滚;顺手治了「弹窗有点卡」** —— 用户口径:「分类这一行要支持鼠标滚轮滚动,
256
+ 就是分类可以左右滚动,还有我感觉弹窗页面有点卡,也分析检查下」。
257
+
258
+ ### 新增
259
+
260
+ - 分类行支持**鼠标滚轮左右滚**(触控板横滑也认,`deltaX` 优先)。用**原生**
261
+ `addEventListener("wheel", …, { passive: false })` 而不是 React 的 `onWheel`:React 17 起 wheel 是
262
+ passive 委托,回调里 `preventDefault()` 不生效(还会在控制台告警),纵向滚轮会在滚完分类行之后
263
+ 继续滚下面的列表。现在只在**真的还能往那个方向滚**时才拦,滚到两端就放手 —— 滚轮自然回到列表上,
264
+ 不会「卡住不动」。
265
+ - 分类行去掉了 `scroll-behavior:smooth`:滚轮要的是「转多少走多少」的跟手,平滑只留给那两颗箭头按钮
266
+ (它们各自传 `scrollBy({ behavior: "smooth" })`)。
267
+
268
+ ### 性能(「弹窗有点卡」的四处收紧)
269
+
270
+ 先说清**卡在哪**,按影响从大到小(每一条都能在代码里指到位置):
271
+
272
+ 1. **浮层位置每帧 setState,把 300 多张卡片整棵树重新 diff 一遍**(最大的一条)。官方
273
+ `useAnchoredPosition` 每次重算都是 `setPosition({ left, top })` —— **新对象**,React 必然重渲染
274
+ 拥有这个 state 的组件;而这个 state 原先住在 `SummonButton` 里。更糟的是它监听 `scroll`(捕获阶段),
275
+ **列表自己滚动也算** —— 「在专家列表里滚一下」于是等于每帧重渲染整棵面板。两处收紧:
276
+ ① `ui.jsx` 新增 `FloatingPop` 组件把外壳(定位 / 材质 / 关闭)收进去,位置变化只重渲染它,
277
+ 而 `children` 是父级传进来的**同一个 element 引用**,React 直接跳过它的子树(同引用 bailout);
278
+ ② 本插件自己的几何重算跳过「浮层内部滚动」(`panelRef.current.contains(event.target)` 就 return)。
279
+ 2. **派生数据每个 state 变化都重算**:`experts` 过滤、分类表、`visible`、`grouped`、`visibleSkills`
280
+ 原先都写在渲染函数体里 —— 浮层位置、分类行溢出标记这类完全无关的 state 也会把 300+ 项重新过滤、
281
+ 重新分组。现在六处全部 `useMemo`,依赖写清楚。
282
+ 3. **搜索串每次输入都重拼**:`matchExpert` 原本每次调用都把五项字段拼起来再 `toLowerCase()`,
283
+ 323 项 × 每敲一个字符 = 上千次字符串拼接。改成按对象缓存的 `WeakMap`(名册刷新后旧对象自动回收)。
284
+ 本机量化(临时基准 `/tmp/panel-bench.mjs`,323 项 × 200 轮 = 6.46 万次匹配):
285
+ **旧 37.1 ms → 新 5.5 ms(约 6.7×)**。
286
+ 4. **首屏把 323 张卡片全画出来**:面板一打开就是 323 张卡片 / 2621 个元素 / 191 KB HTML
287
+ (`renderToString` 中位数 6.2 ms;浏览器还要建真实 DOM、跑 grid 布局、合成毛玻璃,量级更高)。
288
+ 等高卡片(114px,见卡片那段的说明)正好适合 `content-visibility:auto`
289
+ + `contain-intrinsic-size:auto 114px`:屏幕外的卡片跳过样式 / 布局 / 绘制,真正要画的是可视区那十几张。
290
+
291
+ **仍可能让人觉得"卡"、这次刻意没动的**:官方菜单材质 `backdrop-filter: blur(40px)` 铺满整块面板,
292
+ 大面积毛玻璃合成本身就贵(这是"与官方观感一致"的代价);面板也没有上真正的虚拟列表
293
+ (`content-visibility` 是低风险的等效手段)。如果实机还是卡,下一步的候选是:材质降级为不透明底色、
294
+ 或给列表上虚拟滚动 —— 都等你实机感受后再定。
295
+
296
+ ### 自检
297
+
298
+ - 新增 7 条断言:滚轮横向滚(原生非 passive + 两端放手)、滚轮跟手 / 箭头平滑(分类行不许再写
299
+ `scroll-behavior`)、`FloatingPop` 收口(且父级不许写成 `{open && <FloatingPop`)、内部滚动不重算几何、
300
+ 六处 `useMemo`、卡片 `content-visibility`、`matchExpert` 的 `WeakMap` 缓存。
301
+ - typecheck / check-client(13 个文件 0 处未定义标识符)/ render-smoke(9/9)/
302
+ verify(**511/511**,`ci.yml` 计数同步到 511)全绿。
303
+
304
+ ## [0.4.15] — 未发布
305
+
306
+ **分类行两端都能滚** —— 用户口径:「点更多分类按钮后,左边也应该有这个按钮,这样用户才能回到左侧」。
307
+
308
+ ### 修复
309
+
310
+ - 分类行原来只有右端的圆形箭头,滚到中间之后**没有任何回到左侧的入口**(只能手动横向拖)。
311
+ 现在左侧会出现一个同款的 `‹`:
312
+ · 左端在「已经滚过去了」时出现(`scrollLeft > 1`),右端在「右边还有没露出来的」时出现
313
+ (`scrollLeft + clientWidth < scrollWidth - 1`),滚到两端时对应的箭头自己消失;
314
+ · 两个方向各滚一屏的 80%(左为负、右为正),带 `behavior:"smooth"`;
315
+ · 两个箭头都是 26px 圆形、各贴一侧(`.t-team-div-scroll[data-dir="prev"|"next"]`)。
316
+ - 给箭头让位的内边距**只在那一侧的箭头真的出现时才加**(`.t-team-divbar[data-back="true"]` /
317
+ `[data-more="true"]`):没滚动时左侧不加 `padding-left`,第一个标签仍与搜索框左边缘对齐 ——
318
+ 上一轮用户点的那条「对齐宽度」不受影响。
319
+
320
+ ### 自检
321
+
322
+ - 「对齐」那条断言补上左侧让位规则;「溢出」那条扩成两端:两个 `data-dir` 规则、两个方向的滚动量
323
+ (一正一负)、`setDivBack(node.scrollLeft > 1)` 与 `setDivMore(...)` 都在;文案断言补 `pop.divisionsPrev`。
324
+ - typecheck / check-client(13 个文件 0 处未定义标识符)/ render-smoke(9/9)/ verify(**504/504**)全绿。
325
+ - 顺手记一笔:这条断言的标签里第一次误用了英文双引号,把 JS 字符串截断了 —— 本仓库中文文案一律用「」。
326
+
327
+ ## [0.4.14] — 未发布
328
+
329
+ **输入区面板的搜索框下面加一行分类标签** —— 用户口径:「对话框的 T专家 弹窗在搜索框下面增加一行
330
+ 分类标签,注意对齐宽度」(附了一张分类标签行的参考图:圆角标签、选中项浅灰底、右端一个圆形箭头)。
331
+
332
+ ### 新增
333
+
334
+ - 专家标签页的搜索框下方新增**分类筛选行**:第一项「全部」,后面依次是名册里出现过的分区
335
+ (学术、公司经营、设计、工程、金融… 就是列表里那些分组标题,随界面语言显示中文或英文名)。
336
+ 点一个只看那一类;与搜索词**叠加**生效(选了分类还能继续搜),切换时列表回到顶部。
337
+ - 选项**从名册现算**并保持首次出现的顺序,与下面列表里分组标题的顺序、文案完全一致
338
+ (列表本来就按 `snap.experts` 的顺序分组);不学设置页那样按 slug 排序,免得同一屏里两处顺序对不上。
339
+ 名册还没到、或一位专家都没有时不渲染这一行。
340
+ - **横向滚动**:22 个分区在一屏里放不下(浮层宽度跟输入框走),所以这一行横向滚动、隐藏滚动条,
341
+ 并在溢出时于右端露出一个圆形箭头按钮(点一下滚一屏的 80%)。箭头挂在行外层 `.t-team-divbar` 上
342
+ 而不是滚动容器里 —— 滚动容器的绝对定位子元素会跟着内容一起滚走。
343
+ - 只在**专家**标签里出现:技能没有分类概念,技能页保持原样。
344
+
345
+ ### 对齐宽度(用户特别点的那句)
346
+
347
+ - 这一行与搜索框、下面的卡片对齐靠的是**结构**而不是数值:它就是 `.t-team-pop` 的直接子元素,
348
+ 和别的行一样吃 `> *:not([aria-hidden])` 的 10px 右内边距,左边同样由容器自己的 `padding-left` 提供。
349
+ 所以这一行(以及里面两个容器)都**不许**写左右内边距 —— 写下就会比搜索框窄一截或偏一截。
350
+ 唯一允许的一处 padding 是给右端箭头让位的 `padding-right:32px`,且它挂在 `[data-more="true"]` 上,
351
+ 没溢出时根本不生效。
352
+ - 自检里把这两件事都钉住了:三处选择器的**精确**写法(`.t-team-divbar{…min-width:0}` /
353
+ `.t-team-pop-divs{…overflow-x:auto` / 那条 `> *:not([aria-hidden])` 规则),
354
+ 以及"唯一那条 data-more 上的 padding"。
355
+
356
+ ### 自检
357
+
358
+ - 新增 5 条断言:分类行在搜索框下面且只在专家标签、选项从名册现算(「全部」+ 分区 + 叠加筛选)、
359
+ 对齐宽度(上面那组精确写法)、溢出处理(横向滚动 + 箭头在 bar 上 + 点击滚动量)、
360
+ 两种语言的文案都在且被字面量引用。
361
+ - typecheck / check-client(13 个文件 0 处未定义标识符)/ render-smoke(9/9)/
362
+ verify(**504/504**,`ci.yml` 计数同步到 504)全绿。中途又踩了一次 CSS 模板的裸反引号
363
+ (注释里写「> *」时手滑加成反引号),`check-client-refs` 立刻报出行号。
364
+
365
+ ## [0.4.13] — 未发布
366
+
367
+ **输入区「T专家」面板的外壳改用官方 primitives** —— 用户口径:这个弹窗要拉齐官方。
368
+ 定位与跟随、视口钳制、外部点击关闭、菜单材质四件事交回官方;官方**没有**的那几件仍由本插件负责。
369
+
370
+ ### 变更
371
+
372
+ - 外壳(`ui.jsx` 新增 `useFloatingShell` / `PopSurface`)走官方三件:
373
+ · `useAnchoredPosition` —— 坐标与跟随(`scroll` 捕获 / `resize` / 面板自身 `ResizeObserver`);
374
+ · `useDismissOnOutsidePointer` —— `pointerdown` 落在浮层外就关(顺带覆盖触屏与笔);
375
+ · `MenuSurface` —— 菜单材质,外加 macOS 下给 Chromium 用的 vibrancy 底层。
376
+ 为什么只换外壳、不整块照搬官方:官方 primitives 里**没有浮层组件**,只有 hooks 与材质容器,
377
+ 官方自己也按需组合 —— `dsh-client-ui-conversation` 的 ContextMeter 是
378
+ `useAnchoredPosition`(`side` 写死 `"top"`)+ `useDismissOnOutsidePointer` + **手写 Escape**;
379
+ `dsh-client-ui-input-trigger` 的 `/` 菜单则把位置交给布局,只用 `useAnchoredMaxHeight` 钳高度。
380
+ - **翻转、宽度、高度上限、Escape 四件仍由本插件负责**,逐条都有理由(都对着上游源码核过):
381
+ · **翻转**:官方 `side` 是静态入参,全部 4 个 `useAnchoredPosition` 调用点都是字面量,没有
382
+ 「上方不够就翻下去」的先例;而这条正是我们修过的 bug(新会话输入框居中时面板顶部被裁)。
383
+ 判据沿用改造前的行为(哪边空间多朝哪边、相等时朝上),交互不变;
384
+ · **宽度**:官方 `useAnchoredPosition` 不管宽度(只用面板自身宽度钳右边界),「和输入框一样宽」
385
+ 继续走 `--t-team-pop-w`;
386
+ · **高度上限**:官方 `useAnchoredMaxHeight` 是给「底边由布局固定」的浮层用的(读自身 `bottom` 算上界),
387
+ 而我们的位置由高度反推(`top = 锚点顶 - 间距 - 高度`),拿它算会因为 `bottom` 随位置一起动而
388
+ **不收敛**,所以自己按**同一套上界规则**算;
389
+ · **Escape**:官方 hook 不管它。
390
+ - **上边界改按官方规则让位**:新增 `overlayTopMargin`(算法照抄 primitives:框架顶部占用区
391
+ `--dsh-frame-top-clearance` + 20px,原生全屏时不算占用区)。桌面端向上弹时面板因此比之前矮一点、
392
+ 不再顶到窗口框架那一带。
393
+ - **兜底与老宿主**:三件官方件任一缺席(老宿主、自检替身)就退回本插件的等价实现(同样的算法与重算时机),
394
+ 材质退回 `.t-team-pop` 自绘那一套 —— 与 `ui.jsx` 顶部「官方优先、缺了退回自绘」同一条约定。
395
+ 走官方材质时容器多加 `t-team-pop-official`,把自绘的边框/底色/滤镜清掉:官方材质是父级内部一个
396
+ `aria-hidden`、`z-index:-1` 的兄弟层,父级自己再铺一层底色就会把它整个盖住。
397
+ - 顺带修一处会被材质层踩到的 CSS:`.t-team-pop > *` 补 10px 右内边距那条要排除 `[aria-hidden]`
398
+ (否则官方材质层少吃一条、右边露出底色)。
399
+
400
+ ### 自检
401
+
402
+ - 新增 6 条断言:官方三件都被探测并使用、翻转仍自写、顶部让位按官方规则、官方材质模式下自绘材质被清掉、
403
+ Escape 仍自写、**替身补齐三件**(缺一件,官方分支在自检里就等于没有覆盖)。
404
+ - 改写 2 条旧断言(手算的 `place()` / `wantedLeft` 已删):宽度与左边缘对齐改为钉「宽度变量 + 官方
405
+ `align:"start"`」;右边界钳制改为钉「官方 hook + 兜底实现里同一句钳制」。
406
+ - typecheck / check-client(13 个文件 0 处未定义标识符)/ render-smoke(9/9)/ verify(**499/499**,
407
+ `ci.yml` 里的计数同步到 499)全绿。
408
+
409
+ ### 待真机确认(本机没有真实布局,离线复现不了的三件事)
410
+
411
+ - 面板打开时**不抖动**:定位 hook 的 `top` 依赖面板高度、高度上限又由本插件算,两者靠 `ResizeObserver` 收敛;
412
+ - 向上弹时按官方顶部让位(桌面 48+20)后的高度,观感是否可接受;
413
+ - 官方 `MenuSurface` 的 macOS vibrancy 底层在桌面壳里没有黑块或闪烁。
414
+
415
+ ## [0.4.12] — 未发布
416
+
417
+ ### 新增
418
+
419
+ - **提示词弹窗加了「复制」按钮**(用户要求:放在关闭按钮旁边)。官方 `Modal` 的标题栏只有
420
+ 「标题 + 关闭」两个位置、**没有插槽**,所以按钮渲染在正文之前,靠 CSS 绝对定位"落"到关闭
421
+ 按钮左侧:定位基准是官方 `.dialog`(`Modal.module.css` 给它 `position: relative`),
422
+ 因此正文滚动时按钮不动;偏移量照着官方 `.header` 的 padding 与 `.close` 的 28×28 算 ——
423
+ `top:22px`(header 的 padding-top)、`right:50px`(14px 右内边距 + 28px 关闭按钮 + 8px 间隔)。
424
+ 点一下变「已复制」,写不进去变「复制失败」,1.6s 后回到「复制」。
425
+ **失败也要出声**:Clipboard API 在非安全上下文里缺席是常态,点了毫无反应只会被当成按钮坏了。
426
+ 复制走官方的 `writeClipboard`(异步 Clipboard API + `execCommand` 兜底),不是裸
427
+ `navigator.clipboard`。正文还在读、读失败、或本身为空时按钮禁用 —— 复制一段"正在读取…"
428
+ 没有意义。换一位专家会重置反馈,免得把上一位的"已复制"看成这一位的。
429
+
430
+ ### 修复
431
+
432
+ - **新增一道构建前门禁:CSS 模板里的裸反引号**。`css.js` 整个 CSS 是一个 JS 模板字符串
433
+ (`export const CSS = ...`),注释里再写一个反引号就会把它提前终止;
434
+ 而 esbuild 报的是 `Expected ";" but found "max"` 并指向 **CSS 内容本身**,完全看不出是
435
+ 模板字符串被截断。而且它只在真正构建时炸,`lib/client.js` 又是提交进仓库的产物 ——
436
+ 构建失败意味着产物悄悄停在上一版。2026-09-28 一次会话里连踩三次(顺手把
437
+ `max-height: 100%`、`min(380px,100%)`、`position: relative` 用反引号括了起来)。
438
+ 现在 `check-client-refs.mjs`(build / verify / prepublishOnly 都会先跑它)会扫描
439
+ `css.js` 的模板体并报出首个反引号的行号;已用「故意插一个」反向验证过能拦住。
440
+
441
+ ### 自检
442
+
443
+ - 490 → **493 项**:新增三条 —— 复制按钮接入且文案走字典(禁硬编码)、
444
+ 定位规则在位(丢了按钮会掉进正文按钮流,不在关闭按钮旁边)、
445
+ 复制优先走官方 `writeClipboard`(退回裸 API 会在非安全上下文静默失败)。
446
+ 「CSS 模板裸反引号」那道门禁落在 `check-client-refs.mjs` 里(build 的前置),不在 verify 计数内。
447
+ ci.yml 计数同步到 493。
448
+ - typecheck / check-client / build / verify(493/493)全绿;`lib/client.js` 已重建(对齐 0.4.12)。
449
+
450
+ ## [0.4.11] — 未发布
451
+
452
+ 弹窗**形状**:0.4.10 治了「漫出屏幕」,但没治「长得不对」—— 用户接着反馈「太长 太窄」。
453
+
454
+ ### 修复
455
+
456
+ - **提示词弹窗恢复成适合阅读的形状**。官方 `.dialog` 的 `min(380px, 100%)` 宽 + 不设高度
457
+ 上限,是给**确认框**设计的尺寸(一句话 + 两个按钮);拿它装专家 persona 全文(动辄上万行)
458
+ 就成了又高又窄的窄条:380px 宽意味着长行几乎每句都折行,再撑满视口高度,读起来像从门缝
459
+ 里看。0.4.10 只解决了"内容漫出视口",形状问题当时没看见 —— 这两件事得分开修。
460
+ 现在按内容类型给尺寸:
461
+ - 提示词弹窗(纯阅读)→ `width:min(960px,94vw)` + `max-height:min(84vh,800px)`;
462
+ - 编辑器弹窗(有表单)→ `width:min(860px,94vw)` + `max-height:min(84vh,800px)`
463
+ (宽度是 0.4.10 恢复的那条,这次补上高度上限)。
464
+ 两档高度一致,来回切不会跳。
465
+ ⚠️ 这两条**必须写在 `.t-team-modal-fit` 之后**:同为单类选择器、特异性相同,`max-height`
466
+ 谁在后面谁生效;顺序一反,`max-height:100%` 就会盖掉弹窗自己的高度上限,表现正是
467
+ "又占满整屏"。这条顺序本身也进了自检。
468
+
469
+ ### 自检
470
+
471
+ - 488 → **490 项**,新增两条:提示词弹窗的宽高约束在位、两条尺寸规则确实写在
472
+ `.t-team-modal-fit` 之后。第二条**当场抓到一处真问题** —— 编辑器那条规则被写了两遍
473
+ (第一轮的 860px 留在文件开头,第二轮又写了一份),开头那份会让顺序约束失效。
474
+ ci.yml 计数同步到 490。
475
+ - typecheck / build / verify(490/490)全绿;`lib/client.js` 已重建(版本对齐 0.4.11)。
476
+
477
+ ## [0.4.10] — 未发布
478
+
479
+ 两个**弹窗**的回归:都是「改用官方 `Modal`」那一批留下的,用户看到的是"弹窗样式出问题"。
480
+
481
+ ### 修复
482
+
483
+ - **查看提示词时弹窗漫出屏幕**(用户 2026-09-28 报)。官方 `Modal` 的 `.dialog`
484
+ **没有 `max-height`** —— `Modal.module.css` 的注释把这件事明确交给调用方
485
+ (*cards size against this padding box: consumers cap growth with `max-height: 100%`*),
486
+ 而它同时 `createPortal(…, document.body)`、`.root` 只负责居中不做滚动。于是内容一长就
487
+ 整体顶出视口:专家 persona 动辄上万行,弹窗被撑到远超屏幕高度,看起来就是"样式崩了、
488
+ 文字漫出来",而不是一个能滚的对话框。
489
+ 修法按官方约定分三层,缺一层都不生效:
490
+ - `.t-team-modal-fit` → dialog 相对 `.root` 的 padding box 兜住高度(`max-height:100%`);
491
+ - `.t-team-modal-fit-content` → 官方 `.content` 允许收缩(flex 子项默认 `min-height:auto`
492
+ 会把父级继续撑破);
493
+ - `.content > div:last-child`(官方 `.body`)自己 `overflow:auto` —— 标题栏与关闭按钮
494
+ 因此固定在顶部、始终可点。
495
+ 这一层做在 `ui.jsx` 的 `Modal` 封装里,**所有调用方自动受益**,不必各自处理高度。
496
+ - **自建专家编辑器被压窄到 380px**(同一批改动的遗漏)。`.t-team-editor-modal`
497
+ (`width:min(860px,94vw)`)是自绘 Modal 时代的类名,改用官方封装后**没有任何代码再引用它**
498
+ —— 而官方 `.dialog` 默认只有 `min(380px,100%)` 宽,编辑器要编辑 persona 正文(多行
499
+ Markdown),380px 挤不下。现已把该类名重新接到 editor 弹窗上(死代码复活)。
500
+ 同类死规则 `.t-team-editor-body`(含它的 `.t-team-picker-list` 上限)确认无引用者,删除。
501
+ - 顺带修正 `.t-team-prompt-body` 的职责:提示词正文原先复用自绘兜底 Modal 的内部类名
502
+ `.t-team-modal-body`,会带上不属于它的 `padding:14px`(官方 `.body` 已有 `padding:0 24px`,
503
+ 叠加成 38px)。现在两者各用各的名字,滚动交给上面那层 `.body`。
504
+
505
+ ### 自检
506
+
507
+ - 483 → **488 项**,[6b] 新增 5 条专钉这次的两个回归与一个死代码:官方 Modal 的三层高度约束
508
+ 齐备、`Modal` 封装**真把限高类交给了官方组件**(只断言"CSS 里有这个类名"挡不住
509
+ "封装没传",那正是这次的成因)、提示词正文不再复用兜底类名、编辑器宽度约束在位、
510
+ `.t-team-editor-body` 死样式没长回来。ci.yml 两处计数同步到 488。
511
+ - typecheck / build / verify(488/488)全绿;`lib/client.js` 已重建(内嵌版本常量对齐 0.4.10)。
512
+
19
513
  ## [0.4.9] — 未发布
20
514
 
21
515
  对宿主版本的 peer 范围收敛为**只保留下限**:内核以后升级,这一行不用再改。