dsh-wisp 1.37.0 → 1.44.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/README.md +426 -8
- package/cordis.patch.yml +19 -19
- package/lib/client.js +722 -27
- package/lib/client.template.js +722 -27
- package/lib/index.js +312 -28
- package/package.json +2 -1
- package/tools/engine-probe.mjs +421 -0
- package/tools/smoke.mjs +272 -0
- package/verify-wisp.mjs +4751 -3719
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
DeepSeek Harness Web 界面的浮动陪伴插件:**DeepSeek娘** 桌宠。
|
|
4
4
|
|
|
5
|
-
**当前版本 `
|
|
5
|
+
**当前版本 `1.44.0`** · 零依赖 · 单文件客户端半包(精灵图内嵌为 data URI)
|
|
6
6
|
|
|
7
7
|
> 非官方插件,与 DeepSeek(深度求索)官方无关。角色形象与图片许可见 [NOTICE.md](NOTICE.md)。
|
|
8
8
|
|
|
@@ -30,8 +30,18 @@ DeepSeek Harness Web 界面的浮动陪伴插件:**DeepSeek娘** 桌宠。
|
|
|
30
30
|
| 先躲起来 | 菜单里选「先躲起来」后她整个消失,只在右下角留一个 56px 迷你按钮(用当前皮肤的图),点一下回来。状态记在本地,刷新后仍然躲着 |
|
|
31
31
|
| 拖动 | 按住本体拖动,带边界约束;**松手即记住位置**,下次刷新回到原处 |
|
|
32
32
|
| 说话 | 挂载问候、点击、睡着、醒来、跑完各有一组台词 |
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
| 按轮廓命中 | 只有**她自己的像素**可点:命中层带一层由 alpha 蒙版生成的 `clip-path`,透明处整层穿透到下面的应用(hover / 点击 / 右键 / 滚轮全都不受影响)。抓着光标也只在她的轮廓上出现 |
|
|
34
|
+
| 看余额 | 菜单「行为 → **看看余额**」(或 `__wisp.checkBalance()`)。**按需查,不轮询** —— 稳态开销那条承诺不破。数字来自平台自己的账户服务,**插件从头到尾不碰 API key**;未登录 / 刚没读到 / 这个壳里没有该服务,是三句不同的话 |
|
|
35
|
+
| 按下有反馈 | 按住她 → 身体压一下再弹回来(**按下那一刻**就有反应,不用等松手) |
|
|
36
|
+
| 拖着有重量 | 拖动中她朝**运动的反方向**侧倾(最多 8°),松手时顿一下再站稳。无过渡跟手,松手才走弹簧 |
|
|
37
|
+
| 她会注意你的指针 | 指针进到 **170px** 以内,她朝指针方向侧身(最多 3.5°,越近越多),出圈回正。睡着时不跟 |
|
|
38
|
+
| 换表情会动 | `happy` / `poked` / `attn` 等有表情的状态切换时弹一下;**同一个表情再来一次也弹**(第二下不出声会被读成卡住) |
|
|
39
|
+
| 溜达有方向 | 自己踱步时朝行进方向微微前倾(最多 2.5°),像"往那边去"而不是"被平移过去" |
|
|
40
|
+
| 动作幅度可调 | 菜单「行为 → 动作幅度」三选一:**灵动 / 克制(半幅)/ 静止**。`静止` 连呼吸与 `z` 一起停 |
|
|
41
|
+
|
|
42
|
+
外层 `pointer-events:none`,只有**她自己的轮廓**可点:命中层带一层由 alpha 蒙版生成的
|
|
43
|
+
`clip-path`,所以透明处是真的穿透 —— 她站在哪个按钮上都不影响你点它(1.40.0 之前
|
|
44
|
+
这句是错的:她的 560×840 盒子整块盖在应用上面,透明处的点击被吃掉)。
|
|
35
45
|
|
|
36
46
|
---
|
|
37
47
|
|
|
@@ -42,7 +52,7 @@ dsh-wisp-plugin/
|
|
|
42
52
|
├── package.json dsh.bundle.patch + dsh.client.platform,零依赖
|
|
43
53
|
├── cordis.patch.yml 插入一行 host 入口(指向 dsh-wisp,绝不能是 dsh-wisp/client)
|
|
44
54
|
├── lib/
|
|
45
|
-
│ ├── index.js host
|
|
55
|
+
│ ├── index.js host 半包:注册两个 handler(检查更新 / 查余额),不做别的
|
|
46
56
|
│ ├── client.template.js 浏览器半包源码(唯一需要编辑的文件)
|
|
47
57
|
│ └── client.js 由 build.mjs 生成,勿手改
|
|
48
58
|
├── assets/ 四张 256x384 透明 WebP(idle / happy / sleepy / work)
|
|
@@ -68,7 +78,7 @@ node verify-wisp.mjs # 预检;exit 0 = 两半包都符合契约
|
|
|
68
78
|
3. 产物**不得调用被陷阱的全局**(`setTimeout` / `setInterval` / `clearTimeout` / `clearInterval` / `fetch` / `require`);
|
|
69
79
|
4. `const VERSION` 必须与 `package.json` 的 `version` 一致(防止版本漂移)。
|
|
70
80
|
|
|
71
|
-
`verify-wisp.mjs` 在真实契约下执行浏览器半包:六个被陷阱的全局以**抛异常**的形式注入,虚拟时钟同时驱动两条调度路径,假 DOM / Blob / localStorage(含"抛异常的存储"这一档)齐全。当前 **
|
|
81
|
+
`verify-wisp.mjs` 在真实契约下执行浏览器半包:六个被陷阱的全局以**抛异常**的形式注入,虚拟时钟同时驱动两条调度路径,假 DOM / Blob / localStorage(含"抛异常的存储"这一档)齐全。当前 **685 项全 PASS,exit 0**(**这一行由预检自己核对** —— 数字对不上就红;顶部的版本号同样由它核对)。
|
|
72
82
|
|
|
73
83
|
---
|
|
74
84
|
|
|
@@ -78,10 +88,15 @@ node verify-wisp.mjs # 预检;exit 0 = 两半包都符合契约
|
|
|
78
88
|
`display:none` 是否真的让她不挡点击)全都只有真实浏览器能发现,所以这些检查留在仓库里:
|
|
79
89
|
|
|
80
90
|
```bash
|
|
81
|
-
node tools/smoke.mjs #
|
|
91
|
+
node tools/smoke.mjs # 挂载 / 菜单几何与键盘入口 / 躲起来 / 换皮肤 / attn / worried / 踱步 / 动作层(真引擎)/ 内存 / 体积
|
|
82
92
|
node tools/smoke.mjs --headed # 看着它跑
|
|
83
93
|
```
|
|
84
94
|
|
|
95
|
+
末尾会打印这一轮实际跑了多少项。**第 10 节(动作层)验的是假 DOM 验不了的东西**:
|
|
96
|
+
关键帧里的 `calc()`/`var()` 有没有真的解析、`getAnimations()` 里有没有那条动画、
|
|
97
|
+
`prefers-reduced-motion`(`page.emulateMedia`)下计算后的 `animationName` 是不是 `none`。
|
|
98
|
+
没有 Playwright 时整节跳过 —— 所以换机器后值得跑一次,别只看"预检全过"。
|
|
99
|
+
|
|
85
100
|
**退出码是有意义的**:`0` 通过;`1` 有检查没过;`2` 环境中断。
|
|
86
101
|
|
|
87
102
|
headless Chromium 在内存压力下会被关掉,表现为随机的 "Target page, context or browser has
|
|
@@ -92,6 +107,28 @@ been closed"。它与本插件的正确性无关,所以脚本**只对这类中
|
|
|
92
107
|
(这条是被真实崩溃逼出来的:原来踱步那节另开一个页面,随即开始随机崩溃。)
|
|
93
108
|
|
|
94
109
|
Playwright 是**可选依赖**:找不到就以 0 退出并说明原因 —— CI 上不该因为缺浏览器而红。
|
|
110
|
+
|
|
111
|
+
### 没装 Playwright 时:`tools/engine-probe.mjs`
|
|
112
|
+
|
|
113
|
+
上面那节的能力上限就是 Playwright 有没有装。**装不上时"预检全过 + 冒烟跳过"等于没有任何
|
|
114
|
+
视觉验证**,而假 DOM 恰恰验不了 CSS:关键帧里的 `calc()`/`var()` 有没有真的解析、
|
|
115
|
+
`rotate()` 有没有真的转、媒体查询有没有真的生效。
|
|
116
|
+
|
|
117
|
+
这个探针用 Node 自带的 `fetch` + `WebSocket` 直接走 CDP,驱动机器上已有的
|
|
118
|
+
Edge / Chrome —— **零依赖、零安装**:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
node tools/engine-probe.mjs # 找到浏览器就跑,找不到就说清楚并以 0 退出
|
|
122
|
+
WISP_CHROME=/path/to/chrome node tools/engine-probe.mjs
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
它读的是**引擎算出来的东西**,不是源码文本:计算后的 `matrix`(证明 `rotate(var())` 真的转)、
|
|
126
|
+
`getAnimations()` + `getKeyframes()`(证明关键帧**解析后有效** —— 动画"在跑"不等于有效,
|
|
127
|
+
`calc()` 写错时浏览器会静默丢掉那一条 keyframe,动画照跑但什么都不动)、
|
|
128
|
+
以及 `Emulation.setEmulatedMedia` 下的 `animationName` 与姿势变量。
|
|
129
|
+
|
|
130
|
+
退出码:`0` 通过或跳过 · `1` 有检查没过 · `2` 环境起不来。几何与布局仍由 `smoke.mjs` 负责,
|
|
131
|
+
两者**不互相替代**。
|
|
95
132
|
pnpm 布局下顶层没有 `playwright-core` 这个名字(只有 `.pnpm/playwright-core@<ver>/`),
|
|
96
133
|
所以脚本会显式扫 `.pnpm`。
|
|
97
134
|
|
|
@@ -191,6 +228,7 @@ node verify-wisp.mjs
|
|
|
191
228
|
| 指标 | 数值 |
|
|
192
229
|
|---|---|
|
|
193
230
|
| 动画帧使用量 | **0**(走 `timer` 服务,根本不挂帧循环) |
|
|
231
|
+
| 动作层(v1.38 新增) | **0 帧**:姿势与一次性动作都是"事件写一次属性,浏览器自己算"。指针靠近只做算术,出圈即归零,变化不到 0.05° 不写样式 |
|
|
194
232
|
| 定时器 | 1 个 `interval`(反应轮询)+ 3 个 `timeout` |
|
|
195
233
|
| 反应轮询成本 | 每轮 3 次 `querySelector`,10 s 内共 **7.2–8.3 ms**(≈ **0.86 ms/轮**) |
|
|
196
234
|
| 每帧平均间隔 | 无插件 **4.194 ms** vs 挂载后 **4.217 ms** → **+0.023 ms**(4 轮交替重复) |
|
|
@@ -233,11 +271,25 @@ node verify-wisp.mjs
|
|
|
233
271
|
| — | — | — | 菜单里调过的 `size` 会**记在本地**并活过刷新;但配置里显式写了 `size` 时以配置为准 |
|
|
234
272
|
| `sleepAfterMs` | number | `90000` | 静置多久打盹,限制 `5000–3600000` |
|
|
235
273
|
| `reactions` | boolean | `true` | 是否跟随 Agent 忙碌 / 输入框内容变情绪 |
|
|
274
|
+
| `motion` | string | `'full'` | 动作幅度:`full` 灵动 / `subtle` 克制(半幅)/ `off` 静止(连呼吸也停)。认不出的值退回 `full` |
|
|
236
275
|
| `persist` | boolean | `true` | 是否跨刷新记住位置 |
|
|
237
276
|
| `celebrate` | boolean | `true` | 一轮跑完是否庆祝一下 |
|
|
238
277
|
| `celebrateAfterMs` | number | `2500` | 只庆祝跑够这么久的轮次(避免每次小工具调用都跳) |
|
|
239
278
|
| `happyMs` | number | `1100` | 高兴状态持续多久 |
|
|
279
|
+
| `hungry` | boolean | `true` | 干饭:连续活动太久她会说饿了 |
|
|
280
|
+
| `hungerMs` | number | `2700000` | 连续活动多久算该吃一碗了(45 分钟);`0` = 关 |
|
|
281
|
+
| `night` | boolean | `true` | 深夜劝睡 |
|
|
282
|
+
| `bedtimeHour` | number | `23` | 几点算深夜(0–23) |
|
|
283
|
+
| `wakeHour` | number | `5` | 几点算天亮(0–23);`bedtimeHour > wakeHour` 时窗口跨零点 |
|
|
284
|
+
| `nightMs` | number | `1800000` | 深夜每隔这么久说一句(30 分钟);`0` = 关 |
|
|
285
|
+
| `backAfterMs` | number | `120000` | 离开超过这么久,回来时她会说一句(2 分钟);`0` = 关 |
|
|
286
|
+
| `focusMinutes` | number | `25` | 菜单里「专注 N 分钟」的默认时长 |
|
|
287
|
+
| `lang` | string | `'auto'` | 台词语言:`auto` 跟随界面 / `zh` 强制中文 / `en` 强制英文(菜单里也能选,等价) |
|
|
288
|
+
| `sound` | boolean | `false` | 音效开关。**默认关**,而且只有 `assets/audio/` 里有素材时这个开关才存在 |
|
|
289
|
+
| `soundVolume` | number | `0.7` | 音效音量,限制 `0–1` |
|
|
290
|
+
| `skin` | string | `''` | 点名一套皮肤(`assets/<skin>/`);`''` = 上次选的那套 |
|
|
240
291
|
| `hidden` | boolean | `false` | 挂载时就是躲起来的(只能强制隐藏,叫回来走菜单或 `__wisp.show()`) |
|
|
292
|
+
| `backdrop` | boolean | `false` | 清掉铺满视口的不透明底色,让窗口的 Mica/Acrylic 露出来。**影响整个应用的外观**,所以默认关 |
|
|
241
293
|
| `chatterMs` | number | `0` | `>0` 时按此间隔随机说句话;`0` = 安静 |
|
|
242
294
|
| `careAfterMs` | number | `5400000` | 连续活动多久提醒你起来动动(90 分钟);`0` = 关 |
|
|
243
295
|
| `milestones` | boolean | `true` | 今日轮次到里程碑时得意一下 |
|
|
@@ -269,6 +321,9 @@ window.__wisp.move(200, 300)
|
|
|
269
321
|
window.__wisp.position // { x, y }
|
|
270
322
|
window.__wisp.resetPosition() // 清掉记忆位置,回到右下角
|
|
271
323
|
window.__wisp.clock // 'timer-service' | 'animation-frame'
|
|
324
|
+
window.__wisp.checkBalance() // 查一次余额并说出来;返回 { state: 'ready' | 'signed-out' | 'unavailable' | 'failed' | 'unsupported', wallets, … }
|
|
325
|
+
window.__wisp.doctor().balance // 上一次余额查询的结果 + 有没有 host 座位(**从不含凭据**)
|
|
326
|
+
window.__wisp.doctor().motion // { level, amp, reduced, tilt, tiltLocal, accent, pressed }
|
|
272
327
|
window.__wisp.destroy()
|
|
273
328
|
```
|
|
274
329
|
|
|
@@ -313,6 +368,24 @@ ctx.get('timer').interval(fn, ms) // → disposer
|
|
|
313
368
|
|
|
314
369
|
两者都是壳为自家用户渲染的东西,比内部服务契约更抗版本升级。
|
|
315
370
|
|
|
371
|
+
### 6. 两个半包都没有"自己联网"的能力 —— 只能走服务
|
|
372
|
+
|
|
373
|
+
这一节是 1.39.0 加余额时用两条独立的实测(读发行版里的包 + 查运行时服务目录)确认下来的,
|
|
374
|
+
两条都会在别的地方继续咬人:
|
|
375
|
+
|
|
376
|
+
- **浏览器半包**:`fetch` / `XMLHttpRequest` 那一侧是陷阱,调用即抛。
|
|
377
|
+
- **宿主半包**:跑在 vm 沙箱里,`require` / `fetch` / `setTimeout` 全是抛异常的陷阱
|
|
378
|
+
(沙箱只给 `{ defineTool, registerTool, handle }`,外加平台注入的服务)。
|
|
379
|
+
- **平台给的网络通道 `ctx.web.fetch` 设不了请求头**:它的请求类型就是 `{ url: string }`,
|
|
380
|
+
provider 只读 `url`,方法硬编码 GET、请求头硬编码 UA/Accept。所以**任何需要鉴权头的接口
|
|
381
|
+
在插件里都打不通** —— 这和有没有 key 无关,是通道本身的形状。
|
|
382
|
+
- **能拿到的是服务**:`ctx.get('<name>')` 无需声明即可取;`ctx.<name>` 属性访问需要在
|
|
383
|
+
`inject` 里声明。两者都要**声明成 optional** —— 硬依赖一个没提供的服务会让 fiber 永远
|
|
384
|
+
`waiting`,她就直接从界面上消失了(`web` 与 `deepseekAccount` 都是这么声明的)。
|
|
385
|
+
- **凭据不属于插件**:余额走 `deepseekAccount`("only Host consumers can obtain a request
|
|
386
|
+
credential"),插件拿到的是归一化后的结果。`doctor().balance` 里只有数字和状态,
|
|
387
|
+
没有任何 token —— 这是设计,不是巧合。
|
|
388
|
+
|
|
316
389
|
---
|
|
317
390
|
|
|
318
391
|
## 安装
|
|
@@ -338,10 +411,11 @@ plugin_manager install_bundle https://github.com/969246694/dsh-wisp
|
|
|
338
411
|
### 装好后这样验
|
|
339
412
|
|
|
340
413
|
- [ ] 右下角出现发光桌宠
|
|
341
|
-
- [ ] Console 有 `[wisp] mounted — DeepSeek娘
|
|
414
|
+
- [ ] Console 有 `[wisp] mounted — DeepSeek娘 v1.39.0 (timers: …)`,且括号里是 `timer-service`
|
|
342
415
|
- [ ] 打字 → 变琥珀色;清空 → 回青色
|
|
343
416
|
- [ ] 让她跑一轮 → 结束时会庆祝一下
|
|
344
417
|
- [ ] 拖到别处 → 硬刷新 → **还在原处**
|
|
418
|
+
- [ ] 菜单 → 行为 → **看看余额** → 她报出数字;没登录时她会如实说没登录
|
|
345
419
|
- [ ] `window.__wisp` 存在
|
|
346
420
|
|
|
347
421
|
---
|
|
@@ -441,13 +515,357 @@ node tools/audit-log.mjs --confirm <manifest> # 生成后:并入代理登
|
|
|
441
515
|
2. 或对本包目录重新 `npm i` / `npm pack` 后安装;
|
|
442
516
|
3. 更新后重载页面 —— 她会自己发现版本变了并说一句。
|
|
443
517
|
|
|
518
|
+
**两个源都可以换**(1.43.0 起写进文档):宿主半包读 `config.urls`,所以镜像或私有
|
|
519
|
+
registry 只要在 patch 行里指过去就行。国内常见配置:
|
|
520
|
+
|
|
521
|
+
```yaml
|
|
522
|
+
- insert:
|
|
523
|
+
- id: wisp
|
|
524
|
+
name: "dsh-wisp"
|
|
525
|
+
config:
|
|
526
|
+
urls:
|
|
527
|
+
npm: "https://registry.npmmirror.com/dsh-wisp/latest"
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
只给 `npm` 一个键也可以:没给的源仍用内置默认值(`registry.npmjs.org` 和
|
|
531
|
+
`raw.githubusercontent.com/.../main/package.json`)。预检里有一条断言盯着"换了源
|
|
532
|
+
就真的只去问新地址",免得"支持镜像"只是文档里的一句话。
|
|
533
|
+
|
|
534
|
+
**她只报告,不安装**:宿主半包在 vm 沙箱里,没有网络、没有文件系统,"装"这件事
|
|
535
|
+
做不了 —— 所以 `canSelfUpdate` 恒为 `false`,`why` 里写明原因。
|
|
536
|
+
|
|
444
537
|
### 预检里的守卫
|
|
445
538
|
|
|
446
539
|
`WHATS_NEW` 必须写着**当前版本**,否则预检红 —— 这样"这一版做了什么"不会静默过期。
|
|
447
|
-
另外 `canSelfUpdate === false`、`hint`
|
|
540
|
+
另外 `canSelfUpdate === false`、`hint` 里必须含包名或仓库地址、`why` 里必须说明沙箱原因,
|
|
541
|
+
都有断言盯着。
|
|
448
542
|
|
|
449
543
|
## 变更
|
|
450
544
|
|
|
545
|
+
### 1.43.2
|
|
546
|
+
|
|
547
|
+
**台词只说人话;"为什么查不成"排进弹窗。**
|
|
548
|
+
|
|
549
|
+
1.43.1 把诊断直接拼进了台词 —— 真机上的效果是一句话后面挂一串
|
|
550
|
+
`ctx.get("web")→undefined; ctx.web→undefined`,读起来像乱码(用户的原话:
|
|
551
|
+
"还说一串英文")。现在:
|
|
552
|
+
|
|
553
|
+
- **台词回到一句人话**:「这个壳里我没有联网的通道,查不了更新。」
|
|
554
|
+
- **技术细节排进「关于她」弹窗**:取服务的方式、拿到的东西是什么形状、兜底通道
|
|
555
|
+
为什么也不行 —— 查失败时自动展开,可以看、可以复制、可以追问。
|
|
556
|
+
|
|
557
|
+
预检 680 → **682 项**,其中一条断言"台词里不许出现英文碎片"(失败台词与弹窗分工,
|
|
558
|
+
以后再有人往句子里塞 `ctx.get(...)` 就会红)。
|
|
559
|
+
|
|
560
|
+
### 1.43.1
|
|
561
|
+
|
|
562
|
+
**检查更新在"真的拿不到 web 服务"的壳里也能用了 —— 而且失败时说得清为什么。**
|
|
563
|
+
|
|
564
|
+
背景:1.43.0 装好、宿主半包也重启之后,她的原话仍是
|
|
565
|
+
「我是 1.43.0。这个壳里我没有联网的通道,查不了更新。」—— 也就是说
|
|
566
|
+
`ctx.get('web')` 与 `ctx.web` **两条路都拿不到可用的服务**。于是做了两件事:
|
|
567
|
+
|
|
568
|
+
1. **加一条兜底通道**:`pluginManager.inspect('dsh-wisp')`。插件管理器本来就是
|
|
569
|
+
"安装之前去 registry 问这个包是什么版本"的那个人 —— 所以这条路走的是**应用自己**
|
|
570
|
+
的网络出口,还尊重用户配的镜像源,而且它是平台文档里写明的宿主服务
|
|
571
|
+
(`ctx.get('pluginManager')`)。web 服务不可用时自动走它,`diag.via` 会写
|
|
572
|
+
`pluginManager`。**它比插件自己去抓 packument 更接近"装的时候会装到哪一版"。**
|
|
573
|
+
2. **失败必须能追问**:`resolveWebService()` 现在返回 `{ service, how, why }`,
|
|
574
|
+
"为什么拿不到"会跟着失败台词一起说出来:
|
|
575
|
+
|
|
576
|
+
> 这个壳里我没有联网的通道,查不了更新。(诊断:通道=none;ctx.get("web")→undefined; ctx.web→undefined)
|
|
577
|
+
|
|
578
|
+
一句话就能定案,不需要谁再去开控制台。顺带把"有服务但没有 `fetch`"
|
|
579
|
+
(`web-has-no-fetch`)从"没有通道"里分出来 —— "平台没给服务"和"服务没暴露这个方法"
|
|
580
|
+
是两件事,下一步动作也不同。
|
|
581
|
+
|
|
582
|
+
预检 673 → **680 项**,其中一条是端到端的:**没有 web 服务、只有插件管理器时,
|
|
583
|
+
检查更新照样给出答案,并注明走的是哪条通道。**
|
|
584
|
+
|
|
585
|
+
### 1.43.0
|
|
586
|
+
|
|
587
|
+
**一批"真的用鼠标从头点了一遍"才发现的问题。**
|
|
588
|
+
|
|
589
|
+
| # | 问题 | 修法 |
|
|
590
|
+
|---|---|---|
|
|
591
|
+
| 1 | **拨完开关,子面板消失** —— 而代码注释里明确写着"拨个开关面板就消失是最烦人的交互之一" | `refreshSurfaces()` 重画前先记下 `pinned` 再原样传下去(重画的 DOM 里鼠标并没有真的悬停过,"悬停态"重开必然被宽限期关掉) |
|
|
592
|
+
| 2 | **大小回不到出厂 250%** —— 区间 0.4–8、步长 0.25,`2.5` 落在 `0.4+0.25k` 格点外(最近 2.4 / 2.65) | 步长改 0.1(`0.4 + 21×0.1 = 2.5` ✓),并加断言:**默认值必须能从 lo 走整数步到达** |
|
|
593
|
+
| 3 | **更新提示语写死"按包名安装"** —— 新版只在 GitHub 上时那句话会把人送回旧版 | 她按 `from`/`sources` 判断:只有 GitHub 更新时改口,并把**仓库地址**放进剪贴板(`doctor().update.hint` 能看到用了哪条) |
|
|
594
|
+
| 4 | npm 源写死 `registry.npmjs.org`,镜像用户可能两个源一起废 | 宿主半包的 `config.urls` 本来就支持换源 —— 现在**写进文档**并加断言:换镜像真的只会去问新地址 |
|
|
595
|
+
| 5 | 触摸屏上手指划过也在驱动力姿势,抬手后姿势冻住 | 触摸一律归零(手指既不是"靠近"也不是"路过";该有的反馈是"按下压一下") |
|
|
596
|
+
| 6 | 指针移出窗口后她一直朝最后那个方向歪着 | `blur` 只覆盖"切走应用",补一个 document 级 `mouseleave` |
|
|
597
|
+
| 7 | 被边界夹住的拖动仍在"原地歪" | 位置没变就把姿势归零(她其实没动) |
|
|
598
|
+
| 8 | `motion: off` 与 `prefers-reduced-motion` **挡不住溜达** | CSS 关不掉 JS 写进去的坐标 —— 溜达调度器一起停 |
|
|
599
|
+
| 9 | 静止档下 "z" 停成满不透明,像一个卡住的字 | 静止版本给 0.5 透明度;减弱动画那档同样处理 |
|
|
600
|
+
| 10 | 脚底光晕挂在姿势层,她一倾斜连"地板"一起歪 | 移到 motion 层:跟着压扁(那是她压在地上),不跟着侧身 |
|
|
601
|
+
|
|
602
|
+
**评估后决定不改的一条**:`.wisp-tab` 那颗迷你按钮是 56×56 的**可见胶囊**(有边框、底色、阴影),整块就是按钮本身 —— 命中区和用户看到的一致,不属于"透明边距吃点击"那一类。
|
|
603
|
+
|
|
604
|
+
预检 654 → **673 项**:上面每一条都配了"再犯就会露出来"的断言,包括"拨开关后面板还在""默认值可达""静止档真的不动"这三条行为级的。
|
|
605
|
+
|
|
606
|
+
### 1.42.0
|
|
607
|
+
|
|
608
|
+
**查更新失败时,她会直接说清是哪一种失败。**
|
|
609
|
+
|
|
610
|
+
起因是那句话:*"我是 1.37.0,这次没查到更新"* —— 界面上只有这一句,而它背后可能是
|
|
611
|
+
三件完全不同的事:
|
|
612
|
+
|
|
613
|
+
| 实际情况 | 以前 | 现在 |
|
|
614
|
+
|---|---|---|
|
|
615
|
+
| 这个壳里没有联网通道(`no-web-service`) | "这次没查到更新" | **"这里没有能出去的网络通道。"** |
|
|
616
|
+
| 宿主那边没应答(`call-failed`) | "这次没查到更新" | **"宿主那边没应答,更新没查成。"** |
|
|
617
|
+
| 两个源都连不上(`fetch-failed`) | "这次没查到更新" | "更新没查成。"(保持原样) |
|
|
618
|
+
|
|
619
|
+
含糊地说成"没查到更新"就等于让人去猜 —— 这一次的排查全靠从代码里反推(先证明
|
|
620
|
+
"两个都失败才会说这句",再逐个排除)。她本来就知道 `reason`,没有理由不说。
|
|
621
|
+
英文覆盖层也补齐了这三句。
|
|
622
|
+
|
|
623
|
+
### 1.41.0
|
|
624
|
+
|
|
625
|
+
**检查更新更耐得住,而且失败时能追问。**
|
|
626
|
+
|
|
627
|
+
起因是一个真实的报错:界面上只说"更新检查没成功",没有任何办法知道为什么。查下来是两个
|
|
628
|
+
**真缺陷**(都不是"环境问题"):
|
|
629
|
+
|
|
630
|
+
1. **两个源共用一个 8 秒截止时间。** `readPublishedVersion` 把同一个 `AbortSignal` 传给
|
|
631
|
+
npm 与 GitHub 两条请求 —— 一个卡住的源到点会把这个信号 abort 掉,**另一个已经拿到
|
|
632
|
+
结果的源也跟着作废**,整次检查失败。而这台机器上 `raw.githubusercontent.com`
|
|
633
|
+
恰好就是连不上的那个(实测:同一时刻 npm 返回 200)。
|
|
634
|
+
现在**每个源有自己的截止时间**,谁卡住谁自己出局。
|
|
635
|
+
|
|
636
|
+
2. **`ctx.web` 是启动那一刻的快照。** 宿主半包由 bundle patch 插进来,可能比 `web` 服务
|
|
637
|
+
先就位;而 `inject.optional` 的依赖**在启动时缺席不会事后补上** —— `ctx.web` 会永远是
|
|
638
|
+
`undefined`,检查更新从此永久失效,且看起来像"今天网不好"。
|
|
639
|
+
现在服务在**调用时**解析:先 `ctx.get('web')`(调用时查表),再退回 `ctx.web`。
|
|
640
|
+
|
|
641
|
+
3. **失败不再是一句没法追的话。** 客户端以前把 `sources` 丢掉,只留一个 `reason`;
|
|
642
|
+
现在每个源各自的原因与 detail 都会留在 `doctor().update.lastCheck` 里 ——
|
|
643
|
+
"这个壳里没有网络服务"和"两个源都没连上"是两件完全不同的事。
|
|
644
|
+
|
|
645
|
+
顺带说清一件事实:**这台机器读不到 GitHub**(`raw.githubusercontent.com` 超时,
|
|
646
|
+
`registry.npmjs.org` 正常),所以在这台机器上 npm 是**唯一**可用的源 ——
|
|
647
|
+
更新能不能被看见,完全取决于有没有发到 npm。
|
|
648
|
+
|
|
649
|
+
### 1.40.0
|
|
650
|
+
|
|
651
|
+
**她的透明处真的点不到了。**
|
|
652
|
+
|
|
653
|
+
在这之前,"透明处不响应"只做了一半:`hitsBody()` 会按 alpha 蒙版做像素判定,但它判的是
|
|
654
|
+
**"要不要开始拖动"** —— 事件早已经被浏览器派给她那个 560×840 的盒子了(盒子是
|
|
655
|
+
`pointer-events:auto` 铺满的),JS 再 `return` 也追不回来。真引擎实测:
|
|
656
|
+
|
|
657
|
+
```
|
|
658
|
+
她盒子里抽样 363 格:只有 88 格是她(约 24%),其余全透明
|
|
659
|
+
在透明格 (615,165):elementFromPoint → wisp-body 下面那个按钮收到 0 次点击
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
也就是说她站在哪个按钮上,那个按钮的 hover 和点击就都没了。旧检查
|
|
663
|
+
(`a press on her transparent margin passes through to the app`)只断言
|
|
664
|
+
`defaultPrevented === false`——**那是"她没认领",不等于"应用收到了"**,一条典型的假绿。
|
|
665
|
+
|
|
666
|
+
#### 怎么修的:把命中判定交还给浏览器
|
|
667
|
+
|
|
668
|
+
新增一个**不画任何东西**的空盒子 `.wisp-hit` 铺在她的位置,带一层由**同一张 alpha 蒙版**
|
|
669
|
+
生成的 `clip-path`;`.wisp-body` 改成 `pointer-events:none`。
|
|
670
|
+
|
|
671
|
+
```
|
|
672
|
+
path("M245 0h46.7v5.8h-46.7z M239.2 5.8h64.2v5.8h-64.2z …") // 实测某套皮肤约 4 KB
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
形状是这么来的:蒙版是 96×144 的格,每行把连续的不透明格合并成矩形,再和上一行同列同宽的
|
|
676
|
+
矩形纵向合并 —— 段数通常几十段,只在**换皮肤或改尺寸**时重算(不是每帧的事)。
|
|
677
|
+
|
|
678
|
+
于是透明处是**浏览器**判定穿透的,一次修好四件事:hover、点击、右键、滚轮;顺带
|
|
679
|
+
`cursor:grab` 也只在她的轮廓上出现了(以前整个盒子都是抓手光标)。
|
|
680
|
+
|
|
681
|
+
**保住的旧行为**(每条都验过):点她仍然把键盘焦点给她(点击目标不再是那个
|
|
682
|
+
`tabindex=0` 的元素,所以在 `onDown` 里显式 `focusHer()`,否则"点一下她再按 Enter 开菜单"
|
|
683
|
+
就断了)、在她身上按下能拖动、在她身上右键仍然开她自己的菜单。
|
|
684
|
+
|
|
685
|
+
#### 验证
|
|
686
|
+
|
|
687
|
+
- 预检 636 → **642 项**:命中层存在、`body=none` / `hit=auto`、clip 由蒙版生成、
|
|
688
|
+
**形状精确等于"膨胀后的蒙版按当前盒子缩放"**(`M134.2 0.0h291.7v840.0h-291.7z`,
|
|
689
|
+
即蒙版第 23…72 列 × 5.833px)、改尺寸后重算、改回来还原。
|
|
690
|
+
- 真引擎探针 21 → **30 项**(这一台机器上跑得动的那个):透明处 `elementFromPoint`
|
|
691
|
+
返回的是**下面的应用**、真 CDP 点击**送达**、她身上返回的是 `wisp-hit`、点她**不**穿透、
|
|
692
|
+
点完焦点仍在她身上。冒烟第 10 节镜像了同一批。
|
|
693
|
+
- 边界如实说:蒙版是 96×144 再各膨胀一格,所以在默认尺寸下命中轮廓比精灵图**粗约 6px**
|
|
694
|
+
—— 这是**故意**的:看起来落在她边缘上的一下,应该算她的。
|
|
695
|
+
|
|
696
|
+
### 1.39.0
|
|
697
|
+
|
|
698
|
+
**她会看你的余额了。** 菜单「行为 → 看看余额」,点一下她说出来:
|
|
699
|
+
|
|
700
|
+
```
|
|
701
|
+
账上是 ¥110.00。其中赠送 ¥10.00。
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
**按需查,不轮询** —— 每 30 秒问一次平台,既有被限流的风险,也违反"稳态开销接近零"这条
|
|
705
|
+
一直守着的承诺。查不到的四种情况分成四句不同的话,因为把它们都说成"查不到"就是在骗人:
|
|
706
|
+
|
|
707
|
+
| 情况 | 她说 |
|
|
708
|
+
|---|---|
|
|
709
|
+
| 未登录 | 没登录,看不到余额。 |
|
|
710
|
+
| 登录着、但这一下没读到(授权在查询途中换了) | 这一下没读到,稍后再试。 |
|
|
711
|
+
| 平台答了 `failed` | 余额没查成。 |
|
|
712
|
+
| 这个壳里没有那个服务(没有 host 座位) | 这个壳里我查不了余额。 |
|
|
713
|
+
|
|
714
|
+
#### 为什么不是"拿 API key 打平台接口"
|
|
715
|
+
|
|
716
|
+
这是这一版最值得记下来的结论,两条都实测过:
|
|
717
|
+
|
|
718
|
+
1. **`ctx.web.fetch` 设不了请求头。** 它的请求类型只有 `{ url: string }`(服务契约原文),
|
|
719
|
+
provider 只读 `url`,方法硬编码为 GET —— 而余额接口要 `Authorization: Bearer …`。
|
|
720
|
+
所以**平台给的网络通道在物理上就打不通那个接口**,跟有没有 key 无关。
|
|
721
|
+
2. **根本不该由插件拿 key。** DSH 自己读余额走的是 `deepseekAccount` 服务,描述里写着
|
|
722
|
+
"only Host consumers can obtain a request credential" —— 凭据由平台持有,插件拿到的是
|
|
723
|
+
一个已经归一化的结果。于是:`lib/index.js` 只是把宿主半包 inject 上这个服务,
|
|
724
|
+
`harness.handle('checkBalance', …)`,浏览器半包用 `host.call` 问它。
|
|
725
|
+
**key 不会出现在插件里,更不会出现在页面上** —— `doctor().balance` 只报结果。
|
|
726
|
+
|
|
727
|
+
服务契约(从运行时的 Host Service 目录读出来的,不是猜的):
|
|
728
|
+
|
|
729
|
+
```
|
|
730
|
+
getBalance({ version, locale, timezoneOffsetSeconds })
|
|
731
|
+
-> { status: 'ready', value: [{ currency, balance }], bonusWallets: [...] }
|
|
732
|
+
| { status: 'failed' }
|
|
733
|
+
| null // 未登录 **或** 授权在查询途中换了
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
`null` 故意同时意味着两件事,所以真遇到时**再问一次 `getState()`** 分清是哪一种:
|
|
737
|
+
`'signed-out'` 说"没登录",`'credential-stored'` 说"刚才没读到"。
|
|
738
|
+
这个服务也不接受 `AbortSignal`,所以用一个竞速做 8 秒超时 —— 没有它,一个挂住的调用
|
|
739
|
+
就是"气泡永远停在'看一下……'"。
|
|
740
|
+
|
|
741
|
+
#### 顺带修掉一条我自己上一版引入的回归
|
|
742
|
+
|
|
743
|
+
1.38.0 为了让"连戳两下第二下也要弹",把 `accent('pop')` 放在了 `setMood` 的**去重之前**。
|
|
744
|
+
而 `attn` 也在会弹的那一组里,`poll` 只要看到待处理面板就每一轮重算 `setMood('attn')` ——
|
|
745
|
+
结果是你盯着审批卡片等的时候,她**每 1.2 秒抽一下**(真机实测:6 秒 5 次 `animationstart`)。
|
|
746
|
+
|
|
747
|
+
现在弹一下回到去重之后(只有**真的换了表情**才弹),"连戳两下"由 `flashHappy`/`flashPoked`
|
|
748
|
+
自己补 —— 那两条路只由手势触发,永远不会被轮询重算。两条检查分别钉住这两半。
|
|
749
|
+
|
|
750
|
+
#### 验证
|
|
751
|
+
|
|
752
|
+
- 预检 588 → **636 项**,全过。宿主半包当纯函数测(ready / failed / null 分两种 / 抛错 /
|
|
753
|
+
超时 / 服务缺失 / 脏数据 / 列表上限 / 两个 handler 一起摘掉),客户端用 `hostCall`
|
|
754
|
+
假座位走完整链路(含菜单那一行真的点一下、四种"没读到"各自的台词、英文覆盖层八条齐全
|
|
755
|
+
且无汉字)。
|
|
756
|
+
- **没验到的部分,如实说**:这条路的**真实端到端**还没跑过 —— 跑着的应用装的是旧宿主半包,
|
|
757
|
+
要重启后新的 `checkBalance` handler 才会在。重启后菜单里点一下,或者 Console 里
|
|
758
|
+
`__wisp.checkBalance()`;`__wisp.doctor().balance` 会直接告诉你卡在哪一步
|
|
759
|
+
(`hostSeat` 有没有、`state` 是什么、`reason` 是哪一个)。
|
|
760
|
+
|
|
761
|
+
#### 顺带:把"配置键的三个端"钉住(并修掉一个休眠 bug)
|
|
762
|
+
|
|
763
|
+
配置键在三个地方各出现一次:`CONFIG_SPEC`(声明有哪些、怎么夹紧)、`DEFAULTS`(默认值)、
|
|
764
|
+
README 的配置表(用户看到的那份)。这次核对发现**已经漂了**:
|
|
765
|
+
|
|
766
|
+
- `CONFIG_SPEC` 有 **31** 个键,README 表只写了 **18** 个,`cordis.patch.yml` 的注释只列了 **15** 个
|
|
767
|
+
—— `motion` 正是上一版加进 README、却忘了同步别处的那个键;
|
|
768
|
+
- `sound` / `soundVolume` / `lang` **只在 `CONFIG_SPEC` 里声明、`DEFAULTS` 里没有** →
|
|
769
|
+
默认值是 `undefined`。其中 `soundVolume` 是**真 bug**:`Number(undefined)` = `NaN` →
|
|
770
|
+
`clip.volume = NaN` 在浏览器里会抛,被 `playSound` 的 try/catch 吞掉 →
|
|
771
|
+
**"打开音效"整条路静默失效**(等 `assets/audio/` 有素材那天才会显形)。
|
|
772
|
+
|
|
773
|
+
修法不是"把三份抄齐",而是**删掉一份**:`cordis.patch.yml` 里那份清单改成指向 README 表
|
|
774
|
+
(重复清单本身就是漂移源),代码这边补齐默认值、音量显式兜底。预检加了四条不变式:
|
|
775
|
+
声明过的键必须有默认值、README 表必须齐全、README 表不许凭空发明键、
|
|
776
|
+
以及 **README 里那个项数必须等于真实项数**(这一条终于让"544/546/588/631…"那种
|
|
777
|
+
没人核对过的数字变成会红的检查)。
|
|
778
|
+
|
|
779
|
+
### 1.38.0
|
|
780
|
+
|
|
781
|
+
**她有身体了:这一版重做的是「交互动画」。**
|
|
782
|
+
|
|
783
|
+
在这之前,"她的动作"其实只有两件事:**换一张精灵图**,以及一条常驻的呼吸循环。
|
|
784
|
+
按下、拖动、松手这些**手势**在画面上没有任何回应 —— 她像一张贴在屏幕上的图,
|
|
785
|
+
而不是一个被碰到会有反应的东西。
|
|
786
|
+
|
|
787
|
+
#### 三个层,各管一件事
|
|
788
|
+
|
|
789
|
+
CSS 的 `transform` 在同一个元素上是**覆盖**而不是叠加,所以"分层"不是设计洁癖,
|
|
790
|
+
是唯一做法:
|
|
791
|
+
|
|
792
|
+
| 层 | 管什么 | 由谁驱动 |
|
|
793
|
+
|---|---|---|
|
|
794
|
+
| `.wisp-body` | 情绪动画(呼吸 / 跳 / 推 / 担心)—— 既有,一行没动 | `data-mood` |
|
|
795
|
+
| `.wisp-motion` | **一次性动作**:按下 / 落地 / 换表情弹一下 | `data-accent` + `--wisp-amp` |
|
|
796
|
+
| `.wisp-lean` | **持续姿势**:被拖动时的侧倾、朝指针侧身、溜达前倾 | `--wisp-tilt` |
|
|
797
|
+
|
|
798
|
+
`motion` 在外、`lean` 在内是刻意的:落地那一下该是**屏幕坐标**里的竖直压扁
|
|
799
|
+
(重力方向),不该跟着侧倾一起歪。旋转原点放在**脚底**,所以侧倾像钟摆而不是转盘。
|
|
800
|
+
|
|
801
|
+
#### 具体做了什么
|
|
802
|
+
|
|
803
|
+
- **按下就压一下** —— 反馈发生在按下那一刻,不是等松手。
|
|
804
|
+
- **拖着有重量** —— 拖动中朝运动的反方向倾(最多 8°,跟速度走、无过渡跟手);
|
|
805
|
+
松手时顿一下再站稳。跨过屏幕中线时她整个是镜像的(`scaleX(-1)`,镜像会连旋转
|
|
806
|
+
一起翻),所以写进样式前**先乘一次 facing** —— 少了这一步,她会只在半张屏幕上
|
|
807
|
+
朝反方向歪。
|
|
808
|
+
- **她会注意你的指针** —— 170px 内朝指针侧身(最多 3.5°,越近越多),出圈回正,
|
|
809
|
+
睡着时不跟。这是全插件唯一一处持续读指针的逻辑:纯算术、不查 DOM、不读布局,
|
|
810
|
+
出圈即归零,变化不到 **0.05°** 不写样式。
|
|
811
|
+
- **换表情会弹** —— 交叉淡入淡出只是两张图叠了一下,没有"她动了"的感觉。
|
|
812
|
+
弹一下挂在 `POPPY_MOODS` 的八个表情上,`idle`/`alert`/`sleep` 不给(稳态弹一下就成抖)。
|
|
813
|
+
- **溜达朝行进方向前倾** —— 1.6 秒的滑行里,这一点倾斜就是"往那边去"与"被平移过去"的区别。
|
|
814
|
+
- **气泡的淡出跟着它的寿命走** —— 样式表里写死 `4.2s`、代码里活 `4.4s`,气泡会提前
|
|
815
|
+
200ms 淡完再干等。现在时长由 `SAY_MS` 写进行内样式,两边共用一个数(和 `ACCENT_MS`
|
|
816
|
+
同一条规矩:CSS 管怎么动,代码管多久)。
|
|
817
|
+
|
|
818
|
+
#### 两个只有跑起来才知道的坑
|
|
819
|
+
|
|
820
|
+
1. **同一个 `data-accent` 值再写一次,浏览器不会重播动画。** 连戳两下必须看到两次弹,
|
|
821
|
+
所以 `pop-a` / `pop-b` 是同一件事的**两个名字**,交替使用(靠读一次 `offsetWidth`
|
|
822
|
+
强制重排也行,但那要在测试替身里也装一个布局引擎,而替身没有布局)。
|
|
823
|
+
2. **动作通道只留最后发生的那件事。** 最初给"松手没拖动"配了独立的 `release` 关键帧,
|
|
824
|
+
测试立刻证明它**永远不会被看到**:松手紧接着就是点击反应(换表情 → 弹),后到的
|
|
825
|
+
覆盖了它。于是删掉那个关键帧,而不是留一个演不到的动画。同理,`setMood` 的弹一下
|
|
826
|
+
放在"重复状态就返回"**之前** —— 真实引擎探针抓到"连戳两下,第二下完全没反应",
|
|
827
|
+
而那正是最容易被读成"卡住了"的反馈缺失。
|
|
828
|
+
|
|
829
|
+
#### 动作幅度可调(菜单里第三行三选一)
|
|
830
|
+
|
|
831
|
+
`灵动 / 克制(半幅)/ 静止`。**给这一档的理由**:`prefers-reduced-motion` 是系统级的
|
|
832
|
+
"所有动画都别放",而"我不想让这只桌宠动来动去"是另一件事,不该逼人去改系统设置。
|
|
833
|
+
`静止` 连呼吸和 `z` 一起停,她真的就是一张静态贴图。一个 `--wisp-amp` 变量管住全部幅度
|
|
834
|
+
(关键帧里用 `calc` 乘它),CSS 和 JS 读同一张表,不会各调各的。
|
|
835
|
+
|
|
836
|
+
#### reduced motion 有两道闸
|
|
837
|
+
|
|
838
|
+
CSS 的媒体查询关掉动画,但**姿势是 JS 写进去的样式变量**,媒体查询够不着它 ——
|
|
839
|
+
所以 `accent()` 与 `setTilt()` 里必须各问一次系统。真引擎实测(`Emulation.setEmulatedMedia`):
|
|
840
|
+
|
|
841
|
+
```
|
|
842
|
+
reduced: true --wisp-tilt=0deg accent=null body/motion animationName=none / none
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
#### 零动画帧这条承诺没变
|
|
846
|
+
|
|
847
|
+
所有动作都是"事件写一次属性,浏览器自己算",**一条 rAF 循环都没有加**。
|
|
848
|
+
预检里那条 `the whole motion layer runs without arming a single animation frame`
|
|
849
|
+
(`0 frame(s)`)就是盯着这件事的。
|
|
850
|
+
|
|
851
|
+
#### 验证
|
|
852
|
+
|
|
853
|
+
- 预检 546 → **588 项**,全过(新增 42 项:结构、CSS 契约、按下/落地/侧倾/指针/镜像/
|
|
854
|
+
幅度档/静止档/非法档位回退/reduced motion/监听器回收/气泡时长自洽)。
|
|
855
|
+
- **真实引擎**:本机没装 Playwright,`node tools/smoke.mjs` 走的是跳过分支(这不是失败),
|
|
856
|
+
所以顺手补了一个**零依赖的真引擎探针** `tools/engine-probe.mjs`(Node 自带的
|
|
857
|
+
fetch + WebSocket 走 CDP,驱动本机 Edge/Chrome)。它跑出来的数:
|
|
858
|
+
`rotate(var(--wisp-tilt))` 真的转了(2.3° → `matrix(0.999194, 0.0401318, …)`);
|
|
859
|
+
`wisp-press` 的**解析后关键帧**是 `scale(1) | scale(1.045, 0.94) | scale(1)` ——
|
|
860
|
+
也就是 `calc()`/`var()` 真的算出了数,不是"动画在跑但什么都没动";
|
|
861
|
+
`wisp-land` / `wisp-pop-a` / `wisp-pop-b` 在引擎里确实在跑(`getAnimations()`);
|
|
862
|
+
`静止` 档下 `.wisp-body` 的计算后 `animationName` 真的是 `none`;
|
|
863
|
+
`prefers-reduced-motion`(`Emulation.setEmulatedMedia`)下姿势与动作都不写;
|
|
864
|
+
插进两层空盒子之后**精灵图仍然精确铺满她的盒子**(偏移 `0.00,0.00`、尺寸差 `0.00×0.00`)
|
|
865
|
+
—— 少写一个 `inset:0`,假 DOM 那边照样全过,只有真页面会让她缩成半张图。
|
|
866
|
+
装了 Playwright 的机器上,同一批断言会由 `tools/smoke.mjs` 第 10 节自动跑。
|
|
867
|
+
- 顺手修掉 README 顶部那个过期的"当前版本 `0.6.0`"(0.6.0 谱系合并时留下的)。
|
|
868
|
+
|
|
451
869
|
### 1.37.0
|
|
452
870
|
|
|
453
871
|
**菜单里能选台词语言了**:行为组里多了一行三选一的胶囊 —— 跟随界面 / 中文 / English。
|
package/cordis.patch.yml
CHANGED
|
@@ -18,25 +18,25 @@
|
|
|
18
18
|
# `exports["./client"]` (dsh-client-modules reads that, it does not import it)
|
|
19
19
|
# and delivered to the page by the /plugins bundle route.
|
|
20
20
|
#
|
|
21
|
-
# Config keys the client half accepts
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
21
|
+
# Config keys the client half accepts: the FULL list (with types, defaults and
|
|
22
|
+
# clamps) lives in README.md's 「配置」 table — it is the single documented source,
|
|
23
|
+
# and the preflight fails if a key in CONFIG_SPEC is missing from it. Keeping a
|
|
24
|
+
# second copy here is how this file drifted before (a key was added to the README
|
|
25
|
+
# table and not to this comment).
|
|
26
|
+
#
|
|
27
|
+
# The four you are most likely to want:
|
|
28
|
+
# right distance from the right edge in px (默认 26)
|
|
29
|
+
# bottom distance from the bottom edge in px (默认 46)
|
|
30
|
+
# size render scale, 0.4–8 (默认 4 → 560x840 CSS px)
|
|
31
|
+
# motion 'full' | 'subtle' | 'off' (默认 'full')
|
|
32
|
+
#
|
|
33
|
+
# 更新检查的两个源也可以换(宿主半包读 config.urls)—— 国内镜像:
|
|
34
|
+
# urls:
|
|
35
|
+
# npm: "https://registry.npmmirror.com/dsh-wisp/latest"
|
|
36
|
+
# 没给的源仍用内置默认值;只给 npm 一个键也是合法的。
|
|
37
|
+
#
|
|
38
|
+
# 菜单里的「看看余额」**不需要任何配置或 API key**:余额由平台自己的账户服务
|
|
39
|
+
# 提供(宿主半包的 optional inject: deepseekAccount),凭据留在平台手里。
|
|
40
40
|
#
|
|
41
41
|
# Example — add a `config:` block to the row below:
|
|
42
42
|
#
|