@sky_wing_dn/dsh-fairy-zzz 2.0.0 → 2.0.2
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.en.md +19 -12
- package/README.md +19 -12
- package/client.js +220 -21
- package/docs/CHANGELOG.md +28 -6
- package/docs/TECHNICAL.md +79 -3
- package/docs/UI-DESIGN.md +16 -3
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -8,7 +8,7 @@ Boot animation → greeting → docked eye badge, plus a "Wire-Net Terminal" hos
|
|
|
8
8
|
|
|
9
9
|
[中文](README.md) · [Changelog](docs/CHANGELOG.md) · [Technical notes](docs/TECHNICAL.md) · [Performance report](docs/PERFORMANCE.md)
|
|
10
10
|
|
|
11
|
-

|
|
12
12
|
[](https://www.npmjs.com/package/@sky_wing_dn/dsh-fairy-zzz)
|
|
13
13
|

|
|
14
14
|

|
|
@@ -32,7 +32,7 @@ It is not a pure theme pack. The plugin ships a small host half too: static asse
|
|
|
32
32
|
|
|
33
33
|
**Zero runtime dependencies.** The host half only uses Node built-ins; the client half is a single hand-written ESM file with no bundler and no `node_modules`.
|
|
34
34
|
|
|
35
|
-
**Self-update (since v2.0.0).** The plugin asks npm whether a newer version exists; a small 「可更新 / update available」 pill shows next to the Fairy wordmark in the sidebar, and one click on the confirmation popover installs it — refresh the page or restart DSH when prompted. This only works for copies installed from npm via `dsh plugin --profile <name> add ...`; a `link:` install pointing at a local folder has no registry source and never shows the button (edit the source and refresh the page instead).
|
|
35
|
+
**Self-update (since v2.0.0).** The plugin asks npm whether a newer version exists; a small 「可更新 / update available」 pill shows next to the Fairy wordmark in the sidebar, and one click on the confirmation popover installs it — refresh the page or restart DSH when prompted. This only works for copies installed from npm via `dsh plugin --profile <name> add ...`; a `link:` install pointing at a local folder has no registry source and never shows the button (edit the source and refresh the page instead). Failed updates explain the reason (network, unavailable or incompatible version, host refusing to replace a running plugin) and offer a re-check button; versions incompatible with the current DSH are skipped automatically. **2.0.2 fixes the chain itself** — in 2.0.0 / 2.0.1 the check never ran and the install always failed, so a machine on either version needs one manual install of 2.0.2 before the button can update it.
|
|
36
36
|
|
|
37
37
|
## Credits
|
|
38
38
|
|
|
@@ -44,14 +44,9 @@ Since v1.1.0 the rendering, placement, voice and skin paths have largely been re
|
|
|
44
44
|
| --- | --- |
|
|
45
45
|
| Plugin skeleton, H.D.D skin concept, boot video and its audio, 5 base greeting recordings | AssassinVIX/fairy-zzz-plugin (MIT) |
|
|
46
46
|
| Eye glyph artwork, motion geometry and timings | Chengzhibense/Fairy-DSH (Apache-2.0) |
|
|
47
|
-
| Performance layer (rAF → CSS timelines, static eyelid, no per-frame filters, asset compression, ETag/304) | This repo, v1.1 – v1.6 |
|
|
48
|
-
|
|
|
49
|
-
|
|
|
50
|
-
| "Wire-Net Terminal" host skin | This repo, v1.7.0 |
|
|
51
|
-
| Fee readout, letter wall, badge gestures, one-shot completion animation | This repo, v1.7.5 |
|
|
52
|
-
| Five-layer eye breathing, thinking lid breathing | This repo, v1.7.6 |
|
|
53
|
-
| Boot animation restored to source colours | This repo, v1.7.7 |
|
|
54
|
-
| One-click self-update, account-menu shortcut contrast fix | This repo, v2.0.0 |
|
|
47
|
+
| Performance layer (rAF → CSS timelines, static eyelid, no per-frame filters, asset compression, ETag/304) and the eye runtime (single SVG, 15 fps redraw, dock dragging) | This repo, v1.1 – v1.6 |
|
|
48
|
+
| "Wire-Net Terminal" host skin, greeting library, fee readout, letter wall | This repo, v1.4 – v1.7 |
|
|
49
|
+
| One-click self-update | This repo, v2.0 |
|
|
55
50
|
| `scripts/build-client.mjs` (single-source skin CSS inlining) | This repo, v1.7.1 |
|
|
56
51
|
|
|
57
52
|
Full third-party license texts live in [licenses/](licenses/); per-item attribution in [NOTICE-fairy-dsh.md](NOTICE-fairy-dsh.md).
|
|
@@ -260,7 +255,19 @@ node --check client.js # syntax check
|
|
|
260
255
|
|
|
261
256
|
The script locates the skin block through two markers and leaves every other hand-written region of `client.js` untouched.
|
|
262
257
|
|
|
263
|
-
|
|
258
|
+
### Publishing
|
|
259
|
+
|
|
260
|
+
The version number lives in `package.json`, the README badge and `docs/CHANGELOG.md`. To cut a release:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
node scripts/build-client.mjs --check
|
|
264
|
+
node --check client.js
|
|
265
|
+
npm version patch # or minor / major; also tags the commit
|
|
266
|
+
npm publish --access public
|
|
267
|
+
git push --follow-tags
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Attach the tarball from `npm pack` to the GitHub Release; npm-installed users will see the「可更新」pill on their next visit.
|
|
264
271
|
|
|
265
272
|
- The official wordmark is an inline SVG with no replaceable text node, and `sidebar.brand.mark/name` is a `single` slot won by the first registrant. The skin can only hide the official wordmark and overlay its own in the same place, so a host DOM change may require selector updates.
|
|
266
273
|
- The desktop shell's window title, menus and some sidebar labels belong to the app; the plugin cannot change them.
|
|
@@ -281,7 +288,7 @@ The script locates the skin block through two markers and leaves every other han
|
|
|
281
288
|
|
|
282
289
|
## Changelog
|
|
283
290
|
|
|
284
|
-
User-visible changes per release live in [docs/CHANGELOG.md](docs/CHANGELOG.md); implementation notes and trade-offs in [docs/TECHNICAL.md](docs/TECHNICAL.md).
|
|
291
|
+
Milestones: v1.1–v1.6 performance and eye-runtime rewrite; v1.7 Wire-Net Terminal skin, fee readout, boot colour correction; v2.0 one-click self-update. User-visible changes per release live in [docs/CHANGELOG.md](docs/CHANGELOG.md); implementation notes and trade-offs in [docs/TECHNICAL.md](docs/TECHNICAL.md).
|
|
285
292
|
|
|
286
293
|
## License
|
|
287
294
|
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
[English](README.en.md) · [变更记录](docs/CHANGELOG.md) · [技术说明](docs/TECHNICAL.md) · [性能报告](docs/PERFORMANCE.md)
|
|
10
10
|
|
|
11
|
-

|
|
12
12
|
[](https://www.npmjs.com/package/@sky_wing_dn/dsh-fairy-zzz)
|
|
13
13
|

|
|
14
14
|

|
|
@@ -32,7 +32,7 @@ dsh plugin --profile desktop add @sky_wing_dn/dsh-fairy-zzz
|
|
|
32
32
|
|
|
33
33
|
**运行时零依赖**:宿主半边只用 Node 内置模块,客户端半边是一份手写 ESM,不经过打包器,也不需要 `node_modules`。
|
|
34
34
|
|
|
35
|
-
**自更新(v2.0.0 起)**:插件会自己问 npm 有没有新版本——侧栏 Fairy 字标右上出现「可更新」胶囊,点开确认就能装,装完按提示刷新页面或重启 DSH。只对 `dsh plugin --profile <name> add ...` 从 npm 装进来的插件生效;用 `link:` 指向本地目录的安装没有 registry
|
|
35
|
+
**自更新(v2.0.0 起)**:插件会自己问 npm 有没有新版本——侧栏 Fairy 字标右上出现「可更新」胶囊,点开确认就能装,装完按提示刷新页面或重启 DSH。只对 `dsh plugin --profile <name> add ...` 从 npm 装进来的插件生效;用 `link:` 指向本地目录的安装没有 registry 来源,不会显示这个按钮(改完源码照旧刷新页面即生效)。更新失败会说清原因(网络、版本不可用 / 与当前 DSH 不兼容、宿主正在使用等),可以点「重新检查」丢掉缓存重查;和当前 DSH 不兼容的新版本会被自动跳过。**2.0.2 修好了这条链路本身**(2.0.0 / 2.0.1 的检查从未真正执行、安装也必然失败),装插件的那台机器需要手动装到 2.0.2 一次,之后才能用按钮更新。
|
|
36
36
|
|
|
37
37
|
## 来源与致谢
|
|
38
38
|
|
|
@@ -44,14 +44,9 @@ dsh plugin --profile desktop add @sky_wing_dn/dsh-fairy-zzz
|
|
|
44
44
|
| --- | --- |
|
|
45
45
|
| 插件骨架、H.D.D 皮肤概念、开机视频与开场音效、5 条基础问候录音 | AssassinVIX/fairy-zzz-plugin(MIT) |
|
|
46
46
|
| 眼徽 SVG 美术、动效几何与时长参数 | Chengzhibense/Fairy-DSH(Apache-2.0) |
|
|
47
|
-
| 性能层(rAF → CSS 时间线、静态眼睑、去掉逐帧滤镜、资源压缩与 ETag/304
|
|
48
|
-
|
|
|
49
|
-
|
|
|
50
|
-
| 「绳网终端 / WIRE-NET TERMINAL」宿主皮肤 | 本仓库 v1.7.0 |
|
|
51
|
-
| 电费读数、画布字墙、眼徽手势与一次性完成动效 | 本仓库 v1.7.5 |
|
|
52
|
-
| 眼徽五层呼吸、思考态眼皮呼吸 | 本仓库 v1.7.6 |
|
|
53
|
-
| 开场动画恢复原片颜色 | 本仓库 v1.7.7 |
|
|
54
|
-
| 一键自更新、账户菜单快捷键对比度修复 | 本仓库 v2.0.0 |
|
|
47
|
+
| 性能层(rAF → CSS 时间线、静态眼睑、去掉逐帧滤镜、资源压缩与 ETag/304)与眼徽运行时(单 SVG 15fps 重绘、dock 拖拽) | 本仓库 v1.1 – v1.6 |
|
|
48
|
+
| 「绳网终端 / WIRE-NET TERMINAL」宿主皮肤、问候语音扩充、电费读数、画布字墙 | 本仓库 v1.4 – v1.7 |
|
|
49
|
+
| 一键自更新 | 本仓库 v2.0 |
|
|
55
50
|
| `scripts/build-client.mjs`(皮肤 CSS 单源内联) | 本仓库 v1.7.1 |
|
|
56
51
|
|
|
57
52
|
第三方许可全文在 [licenses/](licenses/),逐项署名见 [NOTICE-fairy-dsh.md](NOTICE-fairy-dsh.md)。
|
|
@@ -260,7 +255,19 @@ node --check client.js # 语法检查
|
|
|
260
255
|
|
|
261
256
|
`scripts/build-client.mjs` 用文件里两处标记定位皮肤段(`assets/zzz-pattern.webp` 字墙规则与 `START/END` 注释),手工改动 `client.js` 里的其它区域不会被覆盖。
|
|
262
257
|
|
|
263
|
-
|
|
258
|
+
### 发布
|
|
259
|
+
|
|
260
|
+
版本号同时出现在 `package.json`、README 徽章和 `docs/CHANGELOG.md`。发一版:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
node scripts/build-client.mjs --check
|
|
264
|
+
node --check client.js
|
|
265
|
+
npm version patch # 或 minor / major,自动打 tag
|
|
266
|
+
npm publish --access public
|
|
267
|
+
git push --follow-tags
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
GitHub Release 附 `npm pack` 生成的 tarball;从 npm 装的用户下次打开时会看到「可更新」胶囊。
|
|
264
271
|
|
|
265
272
|
- 官方字标是内联 SVG、没有可替换的文本节点,`sidebar.brand.mark/name` 又是宿主先注册者常驻的 `single` 槽位;皮肤只能「隐藏官方字标 + 原位叠自己的字标」,宿主 DOM 一改就要跟着调选择器。
|
|
266
273
|
- 桌面壳的窗口标题、菜单、侧栏条目文案属于应用本身,插件改不了。
|
|
@@ -281,7 +288,7 @@ node --check client.js # 语法检查
|
|
|
281
288
|
|
|
282
289
|
## 变更记录
|
|
283
290
|
|
|
284
|
-
|
|
291
|
+
主要节点:v1.1–v1.6 性能与眼徽重写;v1.7 绳网终端皮肤、电费读数、开机颜色校正;v2.0 一键自更新。逐版本的用户可见改动见 [docs/CHANGELOG.md](docs/CHANGELOG.md);实现细节与设计取舍在 [docs/TECHNICAL.md](docs/TECHNICAL.md)。
|
|
285
292
|
|
|
286
293
|
## 许可
|
|
287
294
|
|
package/client.js
CHANGED
|
@@ -428,6 +428,8 @@ window.__ModuleLoader__.load({
|
|
|
428
428
|
(not a third-party package installed in this profile)——查询静默失败,UI 不出按钮,
|
|
429
429
|
不做假按钮。
|
|
430
430
|
结果缓存 6 小时;失败静默(离线 / 未登录 / 宿主没暴露服务都不打扰用户)。
|
|
431
|
+
v2.0.1:安装失败按宿主返回的 error.code 翻成中文(updateFailureMessage),
|
|
432
|
+
区分查版本 / 移除 / 安装三步与回滚结果;与当前 DSH 不兼容的新版本在查询阶段就跳过。
|
|
431
433
|
调试时可以往 localStorage 里放一份假结果来演 UI:
|
|
432
434
|
localStorage['fairy.zzz.update.debug'] =
|
|
433
435
|
JSON.stringify({ latest: '9.9.9', installed: '2.0.0' })
|
|
@@ -446,6 +448,34 @@ window.__ModuleLoader__.load({
|
|
|
446
448
|
}
|
|
447
449
|
}
|
|
448
450
|
|
|
451
|
+
/* ————————————————————————————————————————————————————————————————
|
|
452
|
+
* v2.0.2:更新会把正在运行的插件「卸掉再装回来」,React 树随之重建,
|
|
453
|
+
* 浮层里的「安装完成 / 失败」在宿主 HMR 下会跟着消失(实测 2.0.0 → 2.0.1 时,
|
|
454
|
+
* 点完更新约 3 秒后浮层就没了,用户看不到结果)。
|
|
455
|
+
* 所以把这一次更新的上下文写进 localStorage:
|
|
456
|
+
* - 安装前写 phase:'running';
|
|
457
|
+
* - 拿到宿主结果后写 phase:'done'(application + 目标版本)或 phase:'error'(中文原因);
|
|
458
|
+
* - 重挂载(新版代码,或安装失败后回滚回来的旧版)读到就补一条结果浮层。
|
|
459
|
+
* 超过 30 分钟的旧记录直接丢弃,免得下次启动凭空弹出「刚更新完」。
|
|
460
|
+
* ———————————————————————————————————————————————————————————————— */
|
|
461
|
+
const UPDATE_RESUME_KEY = 'fairy.zzz.update.resume'
|
|
462
|
+
const UPDATE_RESUME_TTL = 30 * 60 * 1000
|
|
463
|
+
|
|
464
|
+
function writeUpdateResume(payload) {
|
|
465
|
+
try { writeStored(UPDATE_RESUME_KEY, payload) } catch (e) {}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function readUpdateResume() {
|
|
469
|
+
const raw = readStored(UPDATE_RESUME_KEY, null)
|
|
470
|
+
if (!raw || typeof raw !== 'object') return null
|
|
471
|
+
if (!Number.isFinite(raw.at) || Date.now() - raw.at > UPDATE_RESUME_TTL) return null
|
|
472
|
+
return raw
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function clearUpdateResume() {
|
|
476
|
+
try { localStorage.removeItem(UPDATE_RESUME_KEY) } catch (e) {}
|
|
477
|
+
}
|
|
478
|
+
|
|
449
479
|
function rememberUpdate(info) {
|
|
450
480
|
updateCache.info = info
|
|
451
481
|
updateCache.at = info.at
|
|
@@ -454,16 +484,39 @@ window.__ModuleLoader__.load({
|
|
|
454
484
|
}
|
|
455
485
|
|
|
456
486
|
function updateInfoFromValue(value) {
|
|
457
|
-
if (!value || value.status !== 'listed') return null
|
|
458
|
-
|
|
487
|
+
if (!value || value.status !== 'listed' || !Array.isArray(value.versions)) return null
|
|
488
|
+
/* v2.0.1:宿主的 versions 按新→旧排列,并会给每个版本附 incompatible(与当前 DSH 不兼容时)。
|
|
489
|
+
取第一个兼容的版本;更新的版本不兼容就跳过,全都装不了就当没有可用更新(静默)。 */
|
|
490
|
+
const versions = value.versions.filter((item) => item && item.version)
|
|
491
|
+
const usable = versions.filter((item) => !item.incompatible)
|
|
492
|
+
if (!usable.length) return null
|
|
493
|
+
const pick = usable[0]
|
|
494
|
+
const skipped = versions.slice(0, versions.indexOf(pick)).map((item) => String(item.version))
|
|
459
495
|
return rememberUpdate({
|
|
460
496
|
at: Date.now(),
|
|
461
|
-
latest:
|
|
497
|
+
latest: String(pick.version),
|
|
462
498
|
installed: value.installed ? String(value.installed) : '',
|
|
463
499
|
registry: value.registry ? String(value.registry) : '',
|
|
500
|
+
skipped: skipped.length ? skipped : undefined,
|
|
464
501
|
})
|
|
465
502
|
}
|
|
466
503
|
|
|
504
|
+
/* 宿主把插件管理服务挂在 remote 命名空间上(服务名 remote.pluginManager)。
|
|
505
|
+
v2.0.2 修复:2.0.0/2.0.1 只 inject 了 remote.account,访问 ctx.remote.pluginManager
|
|
506
|
+
时 cordis 会抛 `cannot get property "remote.pluginManager" without inject`,
|
|
507
|
+
结果检查永远静默失败、胶囊从来不出现。这里除了补 inject,也做一层兜底。 */
|
|
508
|
+
function pluginManagerOf(ctx) {
|
|
509
|
+
try {
|
|
510
|
+
const direct = ctx && ctx.remote && ctx.remote.pluginManager
|
|
511
|
+
if (direct) return direct
|
|
512
|
+
} catch (e) {}
|
|
513
|
+
try {
|
|
514
|
+
return (ctx && ctx['remote.pluginManager']) || null
|
|
515
|
+
} catch (e) {
|
|
516
|
+
return null
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
467
520
|
function checkPluginUpdate(force) {
|
|
468
521
|
const debug = readUpdateDebug()
|
|
469
522
|
if (debug) return Promise.resolve(debug)
|
|
@@ -479,7 +532,7 @@ window.__ModuleLoader__.load({
|
|
|
479
532
|
}
|
|
480
533
|
if (updateCache.promise) return updateCache.promise
|
|
481
534
|
const ctx = runtime.ctx
|
|
482
|
-
const manager = ctx
|
|
535
|
+
const manager = pluginManagerOf(ctx)
|
|
483
536
|
if (!manager || typeof manager.listVersions !== 'function') return Promise.resolve(null)
|
|
484
537
|
updateCache.promise = Promise.resolve()
|
|
485
538
|
.then(() => manager.listVersions(PKG, {}))
|
|
@@ -493,6 +546,72 @@ window.__ModuleLoader__.load({
|
|
|
493
546
|
return updateCache.promise
|
|
494
547
|
}
|
|
495
548
|
|
|
549
|
+
/* v2.0.1:失败原因映射。宿主只给 { code, diagnostic?, incompatible? },没有 message 字段;
|
|
550
|
+
2.0.0 直接把英文 code 甩到界面上,这里翻成中文并补一段可读的细节。
|
|
551
|
+
失败可能发生在三步上:查版本(check)/ 移除旧版(remove)/ 装新版(install);
|
|
552
|
+
HMR 下装新版失败宿主会自动回滚(restore),回滚本身失败要单独提醒。 */
|
|
553
|
+
const UPDATE_ERROR_TEXT = {
|
|
554
|
+
'invalid-spec': '目标版本不可用',
|
|
555
|
+
'operation-error': '和 npm 通讯失败',
|
|
556
|
+
'not-bundle': '新版本没有声明 DSH 插件包',
|
|
557
|
+
'incompatible-version': '新版本与当前 DSH 版本不兼容',
|
|
558
|
+
'unsupported-platform': '新版本不支持当前系统',
|
|
559
|
+
'unknown-plugin': 'profile 里没有这个插件',
|
|
560
|
+
'already-installed': '这个版本已经装过',
|
|
561
|
+
'ambiguous-install': '安装来源不唯一',
|
|
562
|
+
'not-removable': '插件管理器不能移除这个插件',
|
|
563
|
+
'stop-profile': '需要先停止当前 profile',
|
|
564
|
+
'bundle-in-use': '插件正在使用中',
|
|
565
|
+
'management-required': '需要由插件管理器执行',
|
|
566
|
+
'stale-approval': '操作授权已过期',
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function clipText(text, max) {
|
|
570
|
+
const flat = String(text == null ? '' : text).replace(/\s+/g, ' ').trim()
|
|
571
|
+
if (!flat) return ''
|
|
572
|
+
const limit = max || 150
|
|
573
|
+
return flat.length > limit ? flat.slice(0, limit) + '…' : flat
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function updateFailureMessage(value) {
|
|
577
|
+
const result = (value && value.result) || {}
|
|
578
|
+
const error = result.error || {}
|
|
579
|
+
const restore = value && value.restore
|
|
580
|
+
const code = error.code ? String(error.code) : ''
|
|
581
|
+
const detail = clipText(error.diagnostic || error.message, 150)
|
|
582
|
+
let text
|
|
583
|
+
if (code === 'incompatible-version') {
|
|
584
|
+
const first = (Array.isArray(error.incompatible) && error.incompatible[0]) || {}
|
|
585
|
+
const need = Array.isArray(first.requirements) ? first.requirements.join('、') : first.requirements
|
|
586
|
+
text = '新版本与当前 DSH 版本不兼容'
|
|
587
|
+
if (need) text += '(要求 ' + clipText(need, 80) + (first.runtimeVersion ? ',当前 ' + first.runtimeVersion : '') + ')'
|
|
588
|
+
text += ',已取消安装。'
|
|
589
|
+
} else if (code === 'not-bundle') {
|
|
590
|
+
text = '新版本没有声明 DSH 插件包,已取消安装。'
|
|
591
|
+
} else if (code === 'operation-error') {
|
|
592
|
+
text = '和 npm 通讯失败或目标版本不存在'
|
|
593
|
+
text += detail ? ':' + detail : ',请检查网络后重试。'
|
|
594
|
+
} else if (code === 'invalid-spec') {
|
|
595
|
+
text = '目标版本不可用' + (detail ? ':' + detail : '') + '。'
|
|
596
|
+
} else if (code === 'bundle-in-use' || code === 'not-removable' || code === 'stop-profile' || code === 'management-required') {
|
|
597
|
+
text = '插件正在使用中,宿主不允许在这里替换 —— 完全退出 DSH 后重试,或用 dsh plugin 手动升级。'
|
|
598
|
+
} else if (code) {
|
|
599
|
+
text = '更新失败:' + (UPDATE_ERROR_TEXT[code] || code)
|
|
600
|
+
if (detail) text += '(' + detail + ')'
|
|
601
|
+
} else {
|
|
602
|
+
text = '更新没有完成,请重试一次。'
|
|
603
|
+
}
|
|
604
|
+
if (restore) {
|
|
605
|
+
if (!/[。!?]$/.test(text)) text += '。'
|
|
606
|
+
if (restore.application === 'failed' || restore.application === 'cancelled') {
|
|
607
|
+
text += ' 旧版本回滚也失败了,Fairy 现在可能不可用 —— 请重启 DSH;仍然不行就用 dsh plugin 重新装一次。'
|
|
608
|
+
} else {
|
|
609
|
+
text += ' 已自动回滚到更新前的版本,Fairy 仍可继续使用。'
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
return text
|
|
613
|
+
}
|
|
614
|
+
|
|
496
615
|
function yuan(value) {
|
|
497
616
|
if (!Number.isFinite(value)) return '—'
|
|
498
617
|
if (value === 0) return '¥0'
|
|
@@ -2978,7 +3097,8 @@ html[data-fz3-theme="light"] [class*="_shortcut_"] [class*="_key"]{color:rgba(16
|
|
|
2978
3097
|
}
|
|
2979
3098
|
const from = info.installed || '当前版本'
|
|
2980
3099
|
const to = info.latest || ''
|
|
2981
|
-
const
|
|
3100
|
+
const cancelled = failed && run.cancelled
|
|
3101
|
+
const title = done ? (run.applied ? '更新完成' : '安装完成') : failed ? (cancelled ? '更新已取消' : '更新失败') : '发现新版本'
|
|
2982
3102
|
let desc
|
|
2983
3103
|
if (done) {
|
|
2984
3104
|
desc = run.applied
|
|
@@ -2991,6 +3111,10 @@ html[data-fz3-theme="light"] [class*="_shortcut_"] [class*="_key"]{color:rgba(16
|
|
|
2991
3111
|
} else {
|
|
2992
3112
|
desc = '从 npm 下载并替换当前插件。安装过程会短暂断开 Fairy,装完刷新页面即可。'
|
|
2993
3113
|
}
|
|
3114
|
+
/* v2.0.1:更新的版本里若有和当前 DSH 不兼容的,说明为什么跳到稍旧的版本 */
|
|
3115
|
+
if (!done && !failed && info.skipped && info.skipped.length) {
|
|
3116
|
+
desc += '(更新的 ' + info.skipped.join('、') + ' 与当前 DSH 不兼容,已跳过)'
|
|
3117
|
+
}
|
|
2994
3118
|
return h(
|
|
2995
3119
|
'div',
|
|
2996
3120
|
{ className: 'fz3-upd', role: 'dialog', 'aria-label': '插件更新' },
|
|
@@ -3022,6 +3146,8 @@ html[data-fz3-theme="light"] [class*="_shortcut_"] [class*="_key"]{color:rgba(16
|
|
|
3022
3146
|
? h('button', { type: 'button', className: 'fz3-upd-btn is-primary', onClick: () => { try { window.location.reload() } catch (e) {} } }, '刷新页面')
|
|
3023
3147
|
: h('button', { type: 'button', className: 'fz3-upd-btn is-primary', onClick: close }, '知道了')
|
|
3024
3148
|
: h('button', { type: 'button', className: 'fz3-upd-btn is-primary', onClick: props.onUpdate, disabled: running }, running ? '更新中…' : failed ? '重试' : '更新'),
|
|
3149
|
+
/* v2.0.1:失败(多半是缓存过期 / 来源变了)时允许重新查一次 registry */
|
|
3150
|
+
failed ? h('button', { type: 'button', className: 'fz3-upd-btn is-ghost', onClick: props.onRecheck, disabled: running }, '重新检查') : null,
|
|
3025
3151
|
done || failed
|
|
3026
3152
|
? null
|
|
3027
3153
|
: h('button', { type: 'button', className: 'fz3-upd-btn is-ghost', onClick: close, disabled: running }, '稍后'),
|
|
@@ -3116,6 +3242,40 @@ html[data-fz3-theme="light"] [class*="_shortcut_"] [class*="_key"]{color:rgba(16
|
|
|
3116
3242
|
}
|
|
3117
3243
|
}, [phase])
|
|
3118
3244
|
|
|
3245
|
+
/* v2.0.2:安装期间插件会被卸载,浮层跟着消失 —— 挂载后读一次落盘的结果,
|
|
3246
|
+
补一条「安装完成 / 失败」浮层。还在安装中(phase:'running')就隔几秒再看,
|
|
3247
|
+
等宿主的最终结果或回滚后旧代码写下的错误原因。 */
|
|
3248
|
+
useEffect(() => {
|
|
3249
|
+
let alive = true
|
|
3250
|
+
let done = false
|
|
3251
|
+
let timers = []
|
|
3252
|
+
const tryApply = () => {
|
|
3253
|
+
if (!alive || done) return true
|
|
3254
|
+
const resume = readUpdateResume()
|
|
3255
|
+
if (!resume) return true
|
|
3256
|
+
if (resume.phase === 'running') return false
|
|
3257
|
+
done = true
|
|
3258
|
+
clearUpdateResume()
|
|
3259
|
+
setUpdateInfo({ installed: resume.from || '', latest: resume.to || '', resume: true })
|
|
3260
|
+
if (resume.phase === 'error') {
|
|
3261
|
+
setUpdateRun({ phase: 'error', cancelled: !!resume.cancelled, message: resume.message || '更新没有完成,请重试一次。' })
|
|
3262
|
+
} else {
|
|
3263
|
+
setUpdateRun({ phase: 'done', applied: resume.application === 'applied', restart: resume.application === 'restart-required', to: resume.to || '' })
|
|
3264
|
+
}
|
|
3265
|
+
setUpdateOpen(true)
|
|
3266
|
+
return true
|
|
3267
|
+
}
|
|
3268
|
+
if (!tryApply()) {
|
|
3269
|
+
timers = [800, 2000, 4000, 8000, 15000].map((ms) => setTimeout(() => {
|
|
3270
|
+
if (tryApply()) timers.forEach(clearTimeout)
|
|
3271
|
+
}, ms))
|
|
3272
|
+
}
|
|
3273
|
+
return () => {
|
|
3274
|
+
alive = false
|
|
3275
|
+
timers.forEach(clearTimeout)
|
|
3276
|
+
}
|
|
3277
|
+
}, [])
|
|
3278
|
+
|
|
3119
3279
|
/* 浮层打开时订阅宿主的安装进度:requestId 对得上才认(可能同时有别的东西在装) */
|
|
3120
3280
|
useEffect(() => {
|
|
3121
3281
|
if (!updateOpen) return undefined
|
|
@@ -3162,31 +3322,65 @@ html[data-fz3-theme="light"] [class*="_shortcut_"] [class*="_key"]{color:rgba(16
|
|
|
3162
3322
|
return
|
|
3163
3323
|
}
|
|
3164
3324
|
const ctx = runtime.ctx
|
|
3165
|
-
const manager = ctx
|
|
3166
|
-
const fail = (message) => setUpdateRun({ phase: 'error', message: message || '更新失败' })
|
|
3325
|
+
const manager = pluginManagerOf(ctx)
|
|
3167
3326
|
if (!manager || typeof manager.updateBundle !== 'function') {
|
|
3168
|
-
return
|
|
3327
|
+
return setUpdateRun({ phase: 'error', message: '宿主没有提供插件更新接口,请在 DSH 桌面端里重试。' })
|
|
3169
3328
|
}
|
|
3329
|
+
/* v2.0.2:安装期间插件会被卸载,这个组件(连同浮层)随时可能消失;
|
|
3330
|
+
先把上下文落盘,重挂载后由下面的 effect 补上结果。 */
|
|
3331
|
+
const resumeBase = { at: Date.now(), from: info.installed || '', to: info.latest, requestId }
|
|
3332
|
+
writeUpdateResume({ ...resumeBase, phase: 'running' })
|
|
3170
3333
|
let answer
|
|
3171
3334
|
try {
|
|
3172
|
-
|
|
3335
|
+
/* v2.0.2:registry 不能传空串 —— 宿主会把任何 string 当 URL 正规化,
|
|
3336
|
+
空串直接抛 "a registry must be an http(s) URL:"(gateway/internal)。
|
|
3337
|
+
只有查询确实报告了自定义 registry 时才带上,否则让宿主用默认配置。 */
|
|
3338
|
+
const opts = { requestId }
|
|
3339
|
+
if (info.registry) opts.registry = info.registry
|
|
3340
|
+
answer = await manager.updateBundle(PKG, info.latest, opts)
|
|
3173
3341
|
} catch (cause) {
|
|
3174
|
-
|
|
3342
|
+
const message = '调用更新接口失败:' + clipText(cause && cause.message ? cause.message : cause, 140)
|
|
3343
|
+
writeUpdateResume({ ...resumeBase, at: Date.now(), phase: 'error', message })
|
|
3344
|
+
return setUpdateRun({ phase: 'error', message })
|
|
3175
3345
|
}
|
|
3176
|
-
if (!answer
|
|
3177
|
-
|
|
3346
|
+
if (!answer) {
|
|
3347
|
+
const message = '宿主没有返回更新结果,请重试一次。'
|
|
3348
|
+
writeUpdateResume({ ...resumeBase, at: Date.now(), phase: 'error', message })
|
|
3349
|
+
return setUpdateRun({ phase: 'error', message })
|
|
3350
|
+
}
|
|
3351
|
+
if (answer.ok === false) {
|
|
3352
|
+
const message = updateFailureMessage({ result: { error: answer.error } })
|
|
3353
|
+
writeUpdateResume({ ...resumeBase, at: Date.now(), phase: 'error', message })
|
|
3354
|
+
return setUpdateRun({ phase: 'error', message })
|
|
3178
3355
|
}
|
|
3179
|
-
/* 版本已经变了:清缓存,免得下次还提示同一个版本 */
|
|
3180
|
-
try { localStorage.removeItem(UPDATE_KEY) } catch (e) {}
|
|
3181
|
-
updateCache.info = null
|
|
3182
|
-
updateCache.at = 0
|
|
3183
3356
|
const value = answer.value || {}
|
|
3184
3357
|
const result = value.result || {}
|
|
3185
3358
|
const to = value.to || info.latest
|
|
3186
|
-
|
|
3187
|
-
if (result.application === '
|
|
3188
|
-
|
|
3359
|
+
/* v2.0.1:只有真的换掉版本才清缓存;失败时保留,重试不用再查一遍 registry */
|
|
3360
|
+
if (result.application === 'applied' || result.application === 'restart-required') {
|
|
3361
|
+
try { localStorage.removeItem(UPDATE_KEY) } catch (e) {}
|
|
3362
|
+
updateCache.info = null
|
|
3363
|
+
updateCache.at = 0
|
|
3364
|
+
writeUpdateResume({ ...resumeBase, at: Date.now(), to, phase: 'done', application: result.application })
|
|
3365
|
+
return setUpdateRun({ phase: 'done', applied: result.application === 'applied', restart: result.application === 'restart-required', to })
|
|
3366
|
+
}
|
|
3367
|
+
const message = result.application === 'cancelled' ? '更新被取消,当前版本没有变化。' : updateFailureMessage(value)
|
|
3368
|
+
writeUpdateResume({ ...resumeBase, at: Date.now(), phase: 'error', cancelled: result.application === 'cancelled', message })
|
|
3369
|
+
if (result.application === 'cancelled') return setUpdateRun({ phase: 'error', cancelled: true, message })
|
|
3370
|
+
return setUpdateRun({ phase: 'error', message })
|
|
3189
3371
|
}, [updateInfo, updateRun])
|
|
3372
|
+
|
|
3373
|
+
/* v2.0.1:失败后「重新检查」—— 丢掉缓存(含内存里的)再问一次 registry */
|
|
3374
|
+
const recheckPluginUpdate = useCallback(() => {
|
|
3375
|
+
try { localStorage.removeItem(UPDATE_KEY) } catch (e) {}
|
|
3376
|
+
updateCache.info = null
|
|
3377
|
+
updateCache.at = 0
|
|
3378
|
+
setUpdateRun(null)
|
|
3379
|
+
checkPluginUpdate(true).then((info) => {
|
|
3380
|
+
setUpdateInfo(info || null)
|
|
3381
|
+
if (!info) setUpdateOpen(false)
|
|
3382
|
+
})
|
|
3383
|
+
}, [])
|
|
3190
3384
|
const dragRef = useRef(null)
|
|
3191
3385
|
const coreRef = useRef(null)
|
|
3192
3386
|
/* 刚结束一次右键拖动的时间戳:用来吞掉 mouseup 之后补发的那一个 contextmenu */
|
|
@@ -4053,7 +4247,12 @@ html[data-fz3-theme="light"] [class*="_shortcut_"] [class*="_key"]{color:rgba(16
|
|
|
4053
4247
|
info: updateInfo,
|
|
4054
4248
|
run: updateRun,
|
|
4055
4249
|
onUpdate: runPluginUpdate,
|
|
4056
|
-
|
|
4250
|
+
onRecheck: recheckPluginUpdate,
|
|
4251
|
+
onClose: () => {
|
|
4252
|
+
/* 关掉浮层就把「待补结果」清掉,免得下次重挂载又弹一遍 */
|
|
4253
|
+
if (!(updateRun && updateRun.phase === 'running')) clearUpdateResume()
|
|
4254
|
+
setUpdateOpen(false)
|
|
4255
|
+
},
|
|
4057
4256
|
})
|
|
4058
4257
|
: null,
|
|
4059
4258
|
|
|
@@ -4081,7 +4280,7 @@ html[data-fz3-theme="light"] [class*="_shortcut_"] [class*="_key"]{color:rgba(16
|
|
|
4081
4280
|
* 挂载
|
|
4082
4281
|
* ------------------------------------------------------------------ */
|
|
4083
4282
|
return {
|
|
4084
|
-
inject: ['slots', 'locale', 'theme', 'remote', 'remote.account'],
|
|
4283
|
+
inject: ['slots', 'locale', 'theme', 'remote', 'remote.account', 'remote.pluginManager'],
|
|
4085
4284
|
apply(ctx) {
|
|
4086
4285
|
/* 账户余额等接口要经过 ctx 上的远程服务,气泡里用 runtime.ctx 取 */
|
|
4087
4286
|
runtime.ctx = ctx
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -2,23 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
本文件记录用户可见的变化。实现细节、测量数据与设计取舍在 [TECHNICAL.md](TECHNICAL.md) 与 [PERFORMANCE.md](PERFORMANCE.md)。
|
|
4
4
|
|
|
5
|
-
格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)
|
|
5
|
+
格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。记录原则:小改动一行带过,大版本 / 大功能才展开。
|
|
6
|
+
|
|
7
|
+
## [2.0.2] — 2026-11-08
|
|
8
|
+
|
|
9
|
+
### 修复
|
|
10
|
+
|
|
11
|
+
- **自更新从来没生效(检查永远静默失败)**:插件 `apply` 的 inject 少声明 `remote.pluginManager`,取 manager 时 cordis 直接抛 `cannot get property "remote.pluginManager" without inject`,异常被外层吞成「没有新版本」。2.0.0 / 2.0.1 的「可更新」胶囊因此从未出现过;现在补上 inject 并加兜底取值。
|
|
12
|
+
- **点「更新」必失败**:`updateBundle` 把查询结果里的空 registry 原样传给宿主,被 `normalizeRegistry` 当 URL 拒绝(`gateway/internal`)。现在只有非空才传。
|
|
13
|
+
- **装完看不到结果**:宿主 HMR 会先卸载旧版再装新版,插件界面连同结果浮层一起消失。现在安装上下文写入 `localStorage`,新版本挂载后补出「安装完成 / 更新失败」(30 分钟内有效)。
|
|
14
|
+
|
|
15
|
+
### 变更
|
|
16
|
+
|
|
17
|
+
- 版本号 2.0.1 → 2.0.2。
|
|
18
|
+
|
|
19
|
+
## [2.0.1] — 2026-11-08
|
|
20
|
+
|
|
21
|
+
### 修复
|
|
22
|
+
|
|
23
|
+
- **更新失败只显示英文错误码**:宿主的失败结果只有 `error.code`(如 `invalid-spec`),2.0.0 直接原样显示。现在按 code 翻成中文,区分「查版本 / 移除旧版 / 安装新版」三个阶段,并说明 HMR 回滚成功还是失败;失败后可以点「重新检查」丢掉缓存重查。
|
|
24
|
+
- **和当前 DSH 不兼容的新版本也提示「可更新」**:现在查询阶段就跳过带 `incompatible` 的版本,取最新的兼容版本;全都不兼容时不显示胶囊。
|
|
25
|
+
|
|
26
|
+
### 变更
|
|
27
|
+
|
|
28
|
+
- 版本号 2.0.0 → 2.0.1。
|
|
6
29
|
|
|
7
30
|
## [2.0.0] — 2026-11-08
|
|
8
31
|
|
|
9
32
|
### 新增
|
|
10
33
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
- 预览调试:`localStorage['fairy.zzz.update.debug'] = JSON.stringify({ latest: '9.9.9', installed: '2.0.0' })` 可以直接演一遍胶囊与浮层(只走假进度,不会真的安装)。
|
|
34
|
+
- **一键自更新**:字标右上出现「可更新」胶囊,确认后从 npm 装新版,进度分步显示,装完提示刷新页面或重启 DSH。
|
|
35
|
+
- 调试预览:`localStorage['fairy.zzz.update.debug']` 可假演胶囊与浮层,不会真的安装。
|
|
14
36
|
|
|
15
37
|
### 修复
|
|
16
38
|
|
|
17
|
-
-
|
|
39
|
+
- 账户菜单「设置」右侧的 Ctrl+, 几乎看不见:皮肤里快捷键选择器漏写容器前缀,把新会话按钮的墨黑字色刷到了菜单上;现在限定作用域并单独抬到可读(仍弱于菜单项标题)。
|
|
18
40
|
|
|
19
41
|
### 变更
|
|
20
42
|
|
|
21
|
-
- 版本号 1.7.7 → 2.0.0
|
|
43
|
+
- 版本号 1.7.7 → 2.0.0。自更新只对 npm 安装生效,`link:` 本地目录安装不显示按钮。
|
|
22
44
|
|
|
23
45
|
## [1.7.7] — 2026-11-07
|
|
24
46
|
|
package/docs/TECHNICAL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
# dsh-fairy-zzz 插件技术说明(v2.0.
|
|
1
|
+
# dsh-fairy-zzz 插件技术说明(v2.0.2)
|
|
2
2
|
|
|
3
3
|
> 目标读者:**没有参与过本项目、但要继续维护或修改这个插件的人 / AI 智能体**。读完这篇文档,应该能独立定位代码、改功能、打版本、发布到 GitHub,并复现验证。
|
|
4
|
-
> 最后更新:2026-11-08,对应版本 v2.0.0。v2.0.0 新增插件自更新(§22:查 npm 版本 + 走宿主 `pluginManager.updateBundle` 安装;`link:` 安装不可更新),并修账户菜单快捷键的对比度(根因是 skin 里 kbd 选择器漏写容器前缀,见 §22.4)。v1.7.4–v1.7.6 的完整改动见 README 的中英版本段落,本节只留结构性的几条(§19 顶部两条)。v1.7.6 把眼徽呼吸拆成五层(巩膜 / 虹膜外圈 / 虹膜 / 瞳仁 / 高光各自幅度与相位,周期 1.4s),思考态眼皮随同一相位开合;上一轮基线 v1.7.3(v1.7.3 修底部状态栏透字:composer 座位渐变的终点改为不透明画布色;并把只被覆盖层子树消费的 `--fz3-dock-*` / `--fz3-brand-*` 从 `:root` 改写到 `.fz3-layer`、hero 标题同步改挂载 / 卸载事件、dock 测量改锚点退避重试、dock 相氛围层降为普通合成——4K 下打开 21k 字大会话 style 637→567 ms、long task 1254→1140 ms,见 §21.9;v1.7.2 修 v1.7.1 遗留的两处选中态边线伪影:侧栏 active 胶囊左端青柠月牙、顶栏 active tab 下缘青柠下划线,见 §21.8;v1.7.1 修 v1.7.0 皮肤期的 6 类用户回归:删 boot 左上 `// H.D.D. NORTH SEEK DATA SYSTEM`;问候 kicker / 中文标题回 Fairy 冰蓝;禁用插件后 `--dsw-*` token 残留改为自己挂 `ctx.effect` 释放 `overrideTokens` 的 disposer;重启用插件不再重播开机动画(`bootPlayed` 存到 `window`);设置弹窗几何恢复宿主默认、皮肤只换色;附件缩略图焦点环由外扩改内嵌、重绘移除按钮。v1.7.0 新增「绳网终端」宿主皮肤层,见 §21。此前:v1.6.2 删掉设置页「外观三档」,皮肤始终自动跟随宿主深浅色;「试听问候」改为每次点击都从本时段 5 条语音里重新随机抽一条并显示台词,不再复用开机抽中的那条。v1.6.0 把眼徽渲染从 v1.5.0 的「4 份 HTML 合成层 + CSS steps 动画」换回 **单 SVG + 15fps JS 向量重绘**:合成层的旋转 / 缩放动的是已经栅格化的位图副本,眼睑 clip 边、深蓝眼眶这类高对比边缘在重采样时会出现台阶 / 发虚(用户截图报「像素没覆盖到」);v1.6.0 每 66.7ms 直接写 `#corners` / `#fz3-lens` / `#fz3-drift` 的 SVG transform 属性,由 Skia 按设备像素重新抗锯齿,边缘始终干净。代价:隔离 harness GPU 0.99% → 1.52%(+0.5 点)、主线程 0% → 0.8%;真实页面同轮 A/B 在噪声内(2.28% → 2.10 / 1.98%),相对 v1.4.0 的 9.3–11.8% / 47–54% 仍是约 1/6 与 1/50;192px 原生截图清晰度 +2%–4%。没有新增常驻层,动画数量与 v1.5.0 相同(4 个,全部 15fps)。v1.5.1 修问候语音 greet-night-2 的断句停顿:v1.4.0 那 20 条 mp3 本就按早期草稿台词合成,其中全角「;」不在引擎 cut5 的切分符号里,导致「我值班 / 您不睡」连读;现按同一份草稿台词重新合成,把该处「;」改成「。」,并在合成后于该句号的自然停顿处补 0.20s 静音(总停顿约 0.44s);注意引擎 `merge_short_text_in_array(threshold=5)` 会把「我值班。」(4 字)与「您不睡,」(4 字)合并回同一片段,仅改标点只有模型自然的 ~0.2s 微停顿;屏幕字幕仍保留简洁版、与音频不强制逐字一致,V1 原版 5 条未动;v1.5.0 眼徽动效重做:4 份静态 SVG 副本 + 叶片公转 / 目光微动,持续动画用 steps() 量化到 15fps,GPU / CPU 再降约 80% / 90%;v1.4.0 新增问候语音库:每个时段 5 条(原 1 条 + 新录 4 条),按 Fairy 人设撰写 / 录制,随机播放且避免连续重复,屏幕台词为音频的简洁理解版;v1.3.4 修气泡脚注半句与眼标悬停原生提示;v1.3.0 新增:眼徽用量 / 花费气泡、眼徽拖拽与隐藏、外观三档(跟随 / 深色 / 浅色)、欢迎页文案改写,以及 host 侧 GET /fairy-zzz/usage 路由与 5 个稳定性修复;v1.3.1 把拖拽重写为 transform-only + 四边吸附 + 遮挡让位、用量改为预取缓存(点开即显)、气泡支持点外 / Esc 关闭、设置页删掉「(本地 CPU 推理)」后缀;v1.3.2 把拖拽与吸附范围从整个视口收进**会话内容区**(左 = 侧栏右缘、上 = 标题栏底缘,右 / 下 = 窗口边缘),边界在每次 pointerdown 时实时重量;v1.2.1 的死代码清理成果保留;v1.3.3 只改文案:气泡里的「用量 / 花费 / 峰谷」统一成「电费」口径(用量→电费、余额→电费余额、今日花费→今日电费、峰谷→五倍电费 / 十倍电费,峰段窗口提示为「十倍电费」),客户端对旧宿主的「高峰 / 空闲」字样做兜底改写)。
|
|
4
|
+
> 最后更新:2026-11-08,对应版本 v2.0.2。v2.0.2 修好自更新从发布起就没真正跑通过的两个根因:cordis 的 inject 少声明 `remote.pluginManager`(访问即抛 `cannot get property "remote.pluginManager" without inject`,检查永远静默失败、胶囊从不出现),以及 `updateBundle` 传了空 registry 被宿主当 URL 正规化而拒绝(`gateway/internal`);顺带把「安装期间插件被卸载、结果浮层跟着消失」改成结果落盘、重挂载后补显示(§22.7)。v2.0.1 修更新失败的提示(宿主只回 `error.code`,现在翻成中文、分三步、带上回滚结果)并在查询阶段跳过与当前 DSH 不兼容的新版本,见 §22.6。v2.0.0 新增插件自更新(§22:查 npm 版本 + 走宿主 `pluginManager.updateBundle` 安装;`link:` 安装不可更新),并修账户菜单快捷键的对比度(根因是 skin 里 kbd 选择器漏写容器前缀,见 §22.4)。v1.7.4–v1.7.6 的完整改动见 README 的中英版本段落,本节只留结构性的几条(§19 顶部两条)。v1.7.6 把眼徽呼吸拆成五层(巩膜 / 虹膜外圈 / 虹膜 / 瞳仁 / 高光各自幅度与相位,周期 1.4s),思考态眼皮随同一相位开合;上一轮基线 v1.7.3(v1.7.3 修底部状态栏透字:composer 座位渐变的终点改为不透明画布色;并把只被覆盖层子树消费的 `--fz3-dock-*` / `--fz3-brand-*` 从 `:root` 改写到 `.fz3-layer`、hero 标题同步改挂载 / 卸载事件、dock 测量改锚点退避重试、dock 相氛围层降为普通合成——4K 下打开 21k 字大会话 style 637→567 ms、long task 1254→1140 ms,见 §21.9;v1.7.2 修 v1.7.1 遗留的两处选中态边线伪影:侧栏 active 胶囊左端青柠月牙、顶栏 active tab 下缘青柠下划线,见 §21.8;v1.7.1 修 v1.7.0 皮肤期的 6 类用户回归:删 boot 左上 `// H.D.D. NORTH SEEK DATA SYSTEM`;问候 kicker / 中文标题回 Fairy 冰蓝;禁用插件后 `--dsw-*` token 残留改为自己挂 `ctx.effect` 释放 `overrideTokens` 的 disposer;重启用插件不再重播开机动画(`bootPlayed` 存到 `window`);设置弹窗几何恢复宿主默认、皮肤只换色;附件缩略图焦点环由外扩改内嵌、重绘移除按钮。v1.7.0 新增「绳网终端」宿主皮肤层,见 §21。此前:v1.6.2 删掉设置页「外观三档」,皮肤始终自动跟随宿主深浅色;「试听问候」改为每次点击都从本时段 5 条语音里重新随机抽一条并显示台词,不再复用开机抽中的那条。v1.6.0 把眼徽渲染从 v1.5.0 的「4 份 HTML 合成层 + CSS steps 动画」换回 **单 SVG + 15fps JS 向量重绘**:合成层的旋转 / 缩放动的是已经栅格化的位图副本,眼睑 clip 边、深蓝眼眶这类高对比边缘在重采样时会出现台阶 / 发虚(用户截图报「像素没覆盖到」);v1.6.0 每 66.7ms 直接写 `#corners` / `#fz3-lens` / `#fz3-drift` 的 SVG transform 属性,由 Skia 按设备像素重新抗锯齿,边缘始终干净。代价:隔离 harness GPU 0.99% → 1.52%(+0.5 点)、主线程 0% → 0.8%;真实页面同轮 A/B 在噪声内(2.28% → 2.10 / 1.98%),相对 v1.4.0 的 9.3–11.8% / 47–54% 仍是约 1/6 与 1/50;192px 原生截图清晰度 +2%–4%。没有新增常驻层,动画数量与 v1.5.0 相同(4 个,全部 15fps)。v1.5.1 修问候语音 greet-night-2 的断句停顿:v1.4.0 那 20 条 mp3 本就按早期草稿台词合成,其中全角「;」不在引擎 cut5 的切分符号里,导致「我值班 / 您不睡」连读;现按同一份草稿台词重新合成,把该处「;」改成「。」,并在合成后于该句号的自然停顿处补 0.20s 静音(总停顿约 0.44s);注意引擎 `merge_short_text_in_array(threshold=5)` 会把「我值班。」(4 字)与「您不睡,」(4 字)合并回同一片段,仅改标点只有模型自然的 ~0.2s 微停顿;屏幕字幕仍保留简洁版、与音频不强制逐字一致,V1 原版 5 条未动;v1.5.0 眼徽动效重做:4 份静态 SVG 副本 + 叶片公转 / 目光微动,持续动画用 steps() 量化到 15fps,GPU / CPU 再降约 80% / 90%;v1.4.0 新增问候语音库:每个时段 5 条(原 1 条 + 新录 4 条),按 Fairy 人设撰写 / 录制,随机播放且避免连续重复,屏幕台词为音频的简洁理解版;v1.3.4 修气泡脚注半句与眼标悬停原生提示;v1.3.0 新增:眼徽用量 / 花费气泡、眼徽拖拽与隐藏、外观三档(跟随 / 深色 / 浅色)、欢迎页文案改写,以及 host 侧 GET /fairy-zzz/usage 路由与 5 个稳定性修复;v1.3.1 把拖拽重写为 transform-only + 四边吸附 + 遮挡让位、用量改为预取缓存(点开即显)、气泡支持点外 / Esc 关闭、设置页删掉「(本地 CPU 推理)」后缀;v1.3.2 把拖拽与吸附范围从整个视口收进**会话内容区**(左 = 侧栏右缘、上 = 标题栏底缘,右 / 下 = 窗口边缘),边界在每次 pointerdown 时实时重量;v1.2.1 的死代码清理成果保留;v1.3.3 只改文案:气泡里的「用量 / 花费 / 峰谷」统一成「电费」口径(用量→电费、余额→电费余额、今日花费→今日电费、峰谷→五倍电费 / 十倍电费,峰段窗口提示为「十倍电费」),客户端对旧宿主的「高峰 / 空闲」字样做兜底改写)。
|
|
5
5
|
> 姊妹文档:[docs/PERFORMANCE.md](PERFORMANCE.md)(性能优化报告:为什么这么改、数据怎么量出来的)。
|
|
6
6
|
|
|
7
7
|
---
|
|
@@ -746,6 +746,13 @@ v1.3.0 的回归 harness(`tmp/merge/`,一次性):`t7-routes.mjs`(8 个
|
|
|
746
746
|
|
|
747
747
|
## 19. 变更记录
|
|
748
748
|
|
|
749
|
+
### 2.0.2(2026-11-08)
|
|
750
|
+
|
|
751
|
+
- **修「胶囊永远不出现」**:插件 `apply` 返回的 inject 只有 `remote.account`,而访问 `ctx.remote.pluginManager` 是 cordis 注入的硬检查——没有声明就抛 `cannot get property "remote.pluginManager" without inject`,`checkPluginUpdate()` 在取 manager 这一步就中断,2.0.0 / 2.0.1 的更新检查从来没有执行过。现在 inject 加上 `remote.pluginManager`,并加 `pluginManagerOf(ctx)` 兜底(先试 `ctx.remote.pluginManager`,抛错再试 `ctx['remote.pluginManager']`)。
|
|
752
|
+
- **修「点更新必失败」**:`updateBundle` 的 options 传了 `registry: info.registry`,而查询结果里 registry 为空串;宿主 `normalizeRegistry('')` 直接抛 `a registry must be an http(s) URL:`(包成 `gateway/internal`)。现在只有 registry 非空才带,否则用宿主默认 registry。
|
|
753
|
+
- **补「安装结果」浮层**:宿主 HMR 的更新会先卸载旧版再装新版,插件 React 树随之重建,`.fz3-upd` 浮层里的「安装完成 / 失败」跟着消失。现在安装前把上下文写进 `localStorage['fairy.zzz.update.resume']`(running / done / error),重挂载后读出来补一条结果浮层,30 分钟过期。
|
|
754
|
+
- `package.json` 2.0.1 → 2.0.2。
|
|
755
|
+
|
|
749
756
|
### 2.0.0(2026-11-08)
|
|
750
757
|
|
|
751
758
|
- **插件自更新**:`client.js` 新增 `checkPluginUpdate()` / `runPluginUpdate()` 与 `.fz3-upd-pill` 胶囊、`.fz3-upd` 浮层(样式在 base CSS 段)。查询走 `ctx.remote.pluginManager.listVersions(PKG, {})`,安装走 `updateBundle(PKG, version, { registry, requestId })`;结果缓存 6 小时,失败静默;`status === 'refused'`(`link:` 安装)时不出按钮。面板打开时订阅 `plugin-manager/update-state` 显示 checking / removing / installing / restoring,按 `value.result.application` 区分「刷新页面」(applied)与「重启 DSH」(restart-required)。细节见 §22。
|
|
@@ -1068,7 +1075,7 @@ v1.7.0 皮肤上线后用户报 6 类问题(截图编号 1–7)。逐条根
|
|
|
1068
1075
|
|
|
1069
1076
|
---
|
|
1070
1077
|
|
|
1071
|
-
## 22. 插件自更新(v2.0.0
|
|
1078
|
+
## 22. 插件自更新(v2.0.0 起)
|
|
1072
1079
|
|
|
1073
1080
|
### 22.1 为什么只有「从 npm 安装」的插件能更新
|
|
1074
1081
|
|
|
@@ -1099,6 +1106,8 @@ dsh plugin --profile desktop add @sky_wing_dn/dsh-fairy-zzz
|
|
|
1099
1106
|
- `FairySkin`:dock 相 4.2s 后查一次;`updateInfo` / `updateOpen` / `updateRun` 三个 state;面板打开时订阅 `update-state`、Esc 关闭。
|
|
1100
1107
|
- `runPluginUpdate()`:debug 走假进度(checking → removing → installing,各 650ms);真实调 `updateBundle`,成功后清缓存,按 `application` 落到 `done(applied)` / `done(restart-required)` / `error`。
|
|
1101
1108
|
- UI:`.fz3-upd-pill`(`.fz3-sb-brand` 内,`pointer-events:auto`)+ `.fz3-upd` 浮层(fixed 定位在字标正下方,用 `--fz3-brand-left/top/h` 对齐)。两者样式都在 base CSS 段(`const CSS`),不会在重新内联皮肤 CSS 时被覆盖。
|
|
1109
|
+
- **v2.0.1 版本选择**:`updateInfoFromValue` 先丢掉没有 `version` 的项,再 `filter(item => !item.incompatible)`;取第一个可用的,前面被跳过的写进 `info.skipped`(浮层里提示「…与当前 DSH 不兼容,已跳过」)。如果没有可用版本就返回 `null`,胶囊不出现。
|
|
1110
|
+
- **v2.0.1 失败提示**:`updateFailureMessage(value)` + `UPDATE_ERROR_TEXT` 把宿主的 `error.code` 翻成中文;`clipText()` 折叠空白并截到 150 字。`bundle-in-use` / `not-removable` / `stop-profile` / `management-required` 归纳为「插件正在使用中」;`result.restore` 存在时再追加「已自动回滚」或「回滚也失败,请重启 DSH」。`application === 'cancelled'` 单独显示「更新被取消」。失败后浮层多一个「重新检查」按钮(`recheckPluginUpdate`:清 localStorage + 内存缓存,强制 `listVersions`)。
|
|
1102
1111
|
|
|
1103
1112
|
### 22.4 为什么快捷键提示之前几乎看不见
|
|
1104
1113
|
|
|
@@ -1115,5 +1124,72 @@ v2.0.0 两处改动(都在 `docs/skin/wire-net-terminal.css`):
|
|
|
1115
1124
|
|
|
1116
1125
|
- **假版本预览**(不发布也能演 UI):`localStorage['fairy.zzz.update.debug'] = JSON.stringify({ latest: '9.9.9', installed: '2.0.0' })` → 胶囊出现、点开走假进度、不会真的安装;删掉这个键刷新即恢复真实检查。
|
|
1117
1126
|
- **真实更新**:只有「npm 安装 + registry 上有更新版本」时才能验证。`link:` 安装会静默不显示胶囊,这是预期行为,不是 bug。
|
|
1127
|
+
- **失败路径**(不用发版):往 `localStorage['fairy.zzz.update']` 写一条假缓存 `{"at": Date.now(), "latest": "9.9.9", "installed": "2.0.0", "registry": "https://registry.npmjs.org"}` 再刷新,胶囊会出现并走真实 `updateBundle` —— registry 上没有 9.9.9,宿主返回 `operation-error`;把 `latest` 改成 `0.0.1` 触发 `invalid-spec`;把 `registry` 改成 `http://127.0.0.1:1` 触发网络失败。测完删掉这个键。
|
|
1118
1128
|
- **改完 CSS 必跑** `node scripts/build-client.mjs`(皮肤 CSS 会被内联回 `client.js`),再 `node --check client.js`。
|
|
1119
1129
|
|
|
1130
|
+
|
|
1131
|
+
### 22.6 失败路径与提示(v2.0.1)
|
|
1132
|
+
|
|
1133
|
+
宿主 `updateBundle` 的失败结果长这样:`{ from, to, step, result: { application: 'failed', error: { code, diagnostic?, incompatible? } }, restore?: {...} }`。`managementError()` 只给 `code`,没有 `message`,所以用户看到的每个字都得客户端自己翻:
|
|
1134
|
+
|
|
1135
|
+
| 阶段 | 可能的 `error.code` | 浮层里的说法 |
|
|
1136
|
+
| --- | --- | --- |
|
|
1137
|
+
| 查版本(`step: 'check'`) | `invalid-spec`(`link:` 安装 / 版本不升)、`operation-error`(registry 拒绝或网络失败)、`not-bundle`、`incompatible-version` | 「目标版本不可用」「和 npm 通讯失败」「新版本没有声明 DSH 插件包」「新版本与当前 DSH 版本不兼容(列要求版本)」 |
|
|
1138
|
+
| 移除旧版(`step: 'remove'`) | `bundle-in-use` / `not-removable` / `stop-profile` / `management-required` 等 | 「插件正在使用中,宿主不允许在这里替换 —— 完全退出 DSH 后重试,或用 `dsh plugin` 手动升级」 |
|
|
1139
|
+
| 安装新版(`step: 'install'`) | 同上 + `operation-error` | 同上;带 `diagnostic` 时附在括号里 |
|
|
1140
|
+
| 回滚(`restore`) | `restore.application` ∈ `applied` / `restart-required` / `failed` / `cancelled` | 成功:「已自动回滚到更新前的版本,Fairy 仍可继续使用」;失败:「旧版本回滚也失败了…请重启 DSH;仍然不行就用 `dsh plugin` 重新装一次」 |
|
|
1141
|
+
|
|
1142
|
+
典型场景与走向:
|
|
1143
|
+
|
|
1144
|
+
- **网络不通 / registry 抽风**:`listVersions` 静默返回 `null`(不显示胶囊);用户主动点更新时才会看到 `operation-error` 的具体报文。
|
|
1145
|
+
- **目标版本不存在**(假缓存、或被删包):`check` 阶段 `operation-error`,没有改动任何文件。
|
|
1146
|
+
- **版本不升 / 来源不对**:`invalid-spec`,没有改动任何文件。
|
|
1147
|
+
- **新版本与当前 DSH 不兼容**:查询阶段就被过滤,不会提示更新;若用户手改缓存硬更新,`check` 阶段报 `incompatible-version` 并带上 `requirements` / `runtimeVersion`。
|
|
1148
|
+
- **更新中途失败**:宿主有 HMR 时会自动 `installBundle(previous@from)` 回滚,`restore` 字段告诉客户端回滚结果;用户看到的是「旧版本已回滚 / 回滚失败」两档文案。
|
|
1149
|
+
- **用户取消**(`InstallCancelledError`):`application === 'cancelled'`,浮层显示「更新被取消,当前版本没有变化」。
|
|
1150
|
+
|
|
1151
|
+
重试入口:失败态下按钮从「更新」变「重试」,另有「重新检查」清缓存后重新 `listVersions`(例如用户先发了新包、20 分钟后 CDN 才生效)。
|
|
1152
|
+
|
|
1153
|
+
### 22.7 v2.0.2:为什么 2.0.0 / 2.0.1 的自更新从来没生效
|
|
1154
|
+
|
|
1155
|
+
三个独立的坑:前两个让功能完全不可用,第三个只在真安装时才暴露。
|
|
1156
|
+
|
|
1157
|
+
**坑一:inject 少一个服务名。** 插件 `apply` 返回的 `inject` 里只有 `['slots', 'locale', 'theme', 'remote', 'remote.account']`。`ctx.remote.pluginManager` 是 cordis 按服务名注入的,属性访问本身就是检查:没声明时 traceable ctx 的 get 直接抛 `cannot get property "remote.pluginManager" without inject`(错误串在 `@deepseek-ai/cordis/lib/index.js` 的 static handler 里)。`checkPluginUpdate()` 在 `const manager = ctx.remote.pluginManager` 那一行抛异常,后面的 `listVersions` 根本不执行;外层 `.catch(() => null)` 又把异常吃成「没有新版本」,于是胶囊永远不出现、localStorage 缓存也永远是 null。
|
|
1158
|
+
|
|
1159
|
+
定位方法:在函数里按行埋 localStorage 探针(入口 → 读缓存 → 取 ctx → 取 manager),二分到「取 manager」那步为 false,再把异常字符串记下来。
|
|
1160
|
+
|
|
1161
|
+
修复:inject 里补 `'remote.pluginManager'`,并写 `pluginManagerOf(ctx)` 做兜底(服务名带点,两种属性写法都可能出现):
|
|
1162
|
+
|
|
1163
|
+
```js
|
|
1164
|
+
function pluginManagerOf(ctx) {
|
|
1165
|
+
try {
|
|
1166
|
+
const direct = ctx && ctx.remote && ctx.remote.pluginManager
|
|
1167
|
+
if (direct) return direct
|
|
1168
|
+
} catch (e) {}
|
|
1169
|
+
try {
|
|
1170
|
+
return (ctx && ctx['remote.pluginManager']) || null
|
|
1171
|
+
} catch (e) {
|
|
1172
|
+
return null
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
```
|
|
1176
|
+
|
|
1177
|
+
**坑二:空 registry 被当成 URL。** 宿主 `updateBundle(name, version, options)` 里:
|
|
1178
|
+
|
|
1179
|
+
```js
|
|
1180
|
+
const plan = registryPlan(this.registryFor(name, options?.registry), await this.registries())
|
|
1181
|
+
```
|
|
1182
|
+
|
|
1183
|
+
`normalizeRegistry(url)` 对任何**字符串**都做 `new URL(url)`,失败就抛 `a registry must be an http(s) URL: ${url}`,外层包成 `gateway/internal`。查询结果里 `value.registry` 在默认 registry 下是空串,客户端却无条件传了 `registry: ''`,于是每次点「更新」都在 check 阶段失败、什么都没装。修复:只有非空才放进 options,否则整个字段不传。
|
|
1184
|
+
|
|
1185
|
+
**坑三:安装期间浮层消失。** 有 HMR 时宿主会把已安装版本先卸载再装新版(见 `updateBundle` 注释),插件的 React 树连同 `.fz3-upd` 浮层一起被拆掉;`updateBundle` 的 Promise 在旧模块里继续 resolve,但已经没有 UI 显示结果——用户点完「更新」只看到浮层消失,不知道成没成。修复:安装前写 `localStorage['fairy.zzz.update.resume']`:
|
|
1186
|
+
|
|
1187
|
+
| 时机 | 写入内容 |
|
|
1188
|
+
| --- | --- |
|
|
1189
|
+
| 调 `updateBundle` 之前 | `{ phase: 'running', from, to, requestId, at }` |
|
|
1190
|
+
| 拿到 `applied` / `restart-required` | `{ phase: 'done', application, from, to, at }` |
|
|
1191
|
+
| 任何失败 / 取消 | `{ phase: 'error', message, cancelled?, at }` |
|
|
1192
|
+
|
|
1193
|
+
`FairySkin` 挂载后读这个键:`phase` 是 `done` / `error` 就直接补一条结果浮层;还是 `running` 就 0.8 / 2 / 4 / 8 / 15 秒各再看一次(等旧模块在卸载后写完最终结果)。超过 30 分钟的记录当作过期丢弃,避免下次启动凭空弹「刚更新完」;浮层关闭时顺手清掉这个键。
|
|
1194
|
+
|
|
1195
|
+
验证记录(隔离 DSH_HOME + 独立 profile + 无头 Chrome CDP,2026-11-08):2.0.0 → 2.0.1 真实安装成功,磁盘 `node_modules/@sky_wing_dn/dsh-fairy-zzz/package.json` 版本从 2.0.0 变 2.0.1;安装瞬间页面里的 `.fz3-upd` 消失,新版本挂载后由 resume 记录补出「安装完成」。
|
package/docs/UI-DESIGN.md
CHANGED
|
@@ -561,7 +561,7 @@ v1.7.2 的两处修复都是「边线装饰放错了形状」:
|
|
|
561
561
|
- 附件缩略图焦点(内嵌环,右侧为移除按钮):
|
|
562
562
|
- 插件页关闭再启用后(无覆盖层重播、侧栏选中行为纯纸白胶囊,v1.7.1 截图里的左端青柠弧已在 v1.7.2 移除):
|
|
563
563
|
|
|
564
|
-
## 16. v2.0.0
|
|
564
|
+
## 16. v2.0.0 起:自更新胶囊与浮层
|
|
565
565
|
|
|
566
566
|
### 16.1 胶囊(.fz3-upd-pill)
|
|
567
567
|
|
|
@@ -575,10 +575,23 @@ v1.7.2 的两处修复都是「边线装饰放错了形状」:
|
|
|
575
575
|
- 位置:固定在字标正下方(`left = --fz3-brand-left`,`top = --fz3-brand-top + --fz3-brand-h + 10`),宽 304px,会压住侧栏前两行导航——这是有意的浮层语义,不做遮罩。
|
|
576
576
|
- 材质:墨黑面板 `rgba(12,14,12,.98)`、1px 青柠描边(26%)、12px 圆角、大投影;点 × 或 Esc 关闭。
|
|
577
577
|
- 内容顺序:标题(发现新版本 / 安装完成 / 更新失败)+ ×;版本行 `1.7.7 → 2.0.0`(等宽,旧版 62% 白、新版纸白、箭头青柠);说明两行;安装中加一条 2px 扫描条;调试态加一行琥珀提示;按钮行(主按钮青柠实底,次按钮幽灵)。
|
|
578
|
-
- 文案口径:`applied` → 主按钮「刷新页面」;`restart-required` → 说明「重启 DSH
|
|
578
|
+
- 文案口径:`applied` → 主按钮「刷新页面」;`restart-required` → 说明「重启 DSH 后生效」、主按钮「知道了」;失败给中文原因并允许「重试」和「重新检查」;`cancelled` 标题是「更新已取消」。
|
|
579
579
|
- 纪律:只在 dock 相出现(boot / greet 不打扰),不加遮罩层,不抢焦点。
|
|
580
580
|
|
|
581
|
-
### 16.3
|
|
581
|
+
### 16.3 失败态(v2.0.1)
|
|
582
|
+
|
|
583
|
+
- 标题从「更新失败」细分为「更新失败 / 更新已取消」;正文是客户端翻好的中文,最多 150 字(宿主只给英文 code)。
|
|
584
|
+
- 三步失败各说各的:查版本失败不改文件;移除旧版失败提示「插件正在使用中,完全退出 DSH 后重试」;安装失败会带宿主的 `diagnostic`。
|
|
585
|
+
- HMR 回滚结果追加在正文末尾:「已自动回滚到更新前的版本」或「旧版本回滚也失败了,请重启 DSH」。
|
|
586
|
+
- 按钮:主按钮「重试」,次按钮「重新检查」(清缓存重查 registry),不再显示「稍后」。
|
|
587
|
+
|
|
588
|
+
### 16.4 安装结果的补显示(v2.0.2)
|
|
589
|
+
|
|
590
|
+
- 宿主更新走 HMR:先卸载旧版再装新版,插件的 React 树重建,正在显示的浮层会跟着消失(用户只看到「点一下就没了」)。
|
|
591
|
+
- 现在安装前把 `{phase, from, to, requestId, at}` 写进 `localStorage['fairy.zzz.update.resume']`;新版本挂载后读到 `done` / `error` 就补出同样的结果浮层,仍在 `running` 则 0.8 / 2 / 4 / 8 / 15 秒各再看一次,30 分钟过期。
|
|
592
|
+
- 视觉与 §16.2 完全一致:主按钮 `applied` 是「刷新页面」、`restart-required` 是「知道了」;失败态沿用 §16.3。
|
|
593
|
+
|
|
594
|
+
### 16.5 账户菜单快捷键对比度
|
|
582
595
|
|
|
583
596
|
- 「设置」右侧的 Ctrl+, 从几乎不可见抬到 `rgba(242,242,238,.56)`(浅色 `rgba(16,17,16,.52)`);菜单项标题仍是 label-primary `#f2f2ee`,快捷键低一个层级,不抢「设置」。
|
|
584
597
|
- 根因记录:skin 里 `.wtg9ga_newSession [class*="_kbd"],[class*="_key"],[class*="_separator"]` 的后两个选择器漏了容器前缀,全局把菜单快捷键刷成墨黑;v2.0.0 补前缀 + K 段统一抬对比度(技术细节见 TECHNICAL §22.4)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sky_wing_dn/dsh-fairy-zzz",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "Unofficial Zenless Zone Zero \"Fairy\" H.D.D skin for the DSH Web UI: boot animation, animated eye badge, hour-based greeting, Wire-Net Terminal host skin, always-on fee readout, and optional local GPT-SoVITS voice. Starts from AssassinVIX/fairy-zzz-plugin (MIT); eye artwork from Chengzhibense/Fairy-DSH (Apache-2.0); changelog in docs/CHANGELOG.md.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|