dsh-theme-palette 1.3.0 → 1.3.1
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 +46 -0
- package/client.js +71 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,6 +7,48 @@ DSH Web / Desktop 的主题配色插件:**4 套配色 × 明暗两模式**,
|
|
|
7
7
|
|
|
8
8
|
当前版本 **0.8.2**(0.8.1 按深色专项审计重做深色侧,0.8.2 把强调色换成设计稿标准值;见 §修订记录)。
|
|
9
9
|
|
|
10
|
+
## 为什么配色会自己跳回默认(v1.3.1)
|
|
11
|
+
|
|
12
|
+
现象:设了暖砂,用了一阵(跑模型操作、切窗口、长时间放着),**突然变成黑白**,但设置页里
|
|
13
|
+
「暖砂」还是选中的。原因在宿主的 override 生命周期:
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
// ThemeRuntime(宿主)
|
|
17
|
+
overrideTokens(source, tokens) {
|
|
18
|
+
const layer = { seq: this.overrideSeq++, tokens: validateOverrides(source, tokens) }
|
|
19
|
+
this.overrides.set(source, layer)
|
|
20
|
+
this.publish()
|
|
21
|
+
return () => { if (this.overrides.get(source) !== layer) return; this.overrides.delete(source); this.publish() }
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
而插件侧拿到的是被 runner 包装过的版本 —— **每一层的 disposer 都被挂在调用方的 fiber 上**:
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
const dispose = Reflect.apply(method, target, [id, tokens])
|
|
29
|
+
ctx.effect(() => dispose, 'cordis-client-runner: dynamic theme override layer')
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
也就是说:**这一层的生命周期 = 插件 fiber 的生命周期**。插件被卸载/重载、或 fiber 的
|
|
33
|
+
effect 被清理时,层就没了;`composeActive` 在 `overrides.size === 0` 时直接返回内置主题,
|
|
34
|
+
于是页面**静默**变回默认色 —— localStorage 里的选择还在,设置页自然还显示暖砂。
|
|
35
|
+
|
|
36
|
+
会触发 fiber 重载的典型情况:动态插件被宿主重载(改过插件文件/profile、宿主自我更新)、
|
|
37
|
+
渲染进程恢复、以及某些"模型操作"引起的插件重载。
|
|
38
|
+
|
|
39
|
+
**v1.3.1 的处理**:加了一个自愈环。窗口重新可见 / 获焦 / `pageshow` / `theme/change` /
|
|
40
|
+
每 4 秒,回读 `body` 上的 5 个关键 token;不在了就把最后一层重放:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
[dsh-theme-palette] 配色层不见了(visibility),已重放第 1 次
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
设置页顶部会显示"配色层被宿主丢过一次,已自动恢复 N 次";健康时不产生任何写入。
|
|
47
|
+
|
|
48
|
+
**另一种可能**(值得先分辨):如果是 **localStorage 被清**(换 profile、清缓存),设置页会回到
|
|
49
|
+
「黑白」选中态 —— 那种情况自愈救不回来,因为连"你想用哪套"都丢了。判据就是**设置页里选中的
|
|
50
|
+
是不是你原来那套**。
|
|
51
|
+
|
|
10
52
|
## 设置页重排(v1.3.0)
|
|
11
53
|
|
|
12
54
|
原来的设置页是六行"工程参数"堆在一起、每行都是同一种小药丸。v1.3.0 重做成四段:
|
|
@@ -570,6 +612,10 @@ dsh plugin --profile desktop add "E:\Documents\deepseek-harness\default-workspac
|
|
|
570
612
|
- 苔绿强调 `155°→165°` —— 与底色(h≈152)从 2° 拉开到 13°,链接不再只靠明度识别
|
|
571
613
|
- 深色对比度:正文 14.40–14.98、三级 4.79 起、链接 4.65 起(0 处低于下限)
|
|
572
614
|
|
|
615
|
+
**1.3.1**
|
|
616
|
+
- 自愈:override 层被宿主丢掉时(插件 fiber 重载等)自动重放,设置页显示恢复次数
|
|
617
|
+
- 触发点:visibilitychange / focus / pageshow / theme/change / 4s 定时;健康时零写入
|
|
618
|
+
|
|
573
619
|
**1.3.0**
|
|
574
620
|
- 设置页重排:2×2 缩略图卡片(随明暗切换)+ 段落式设置行 + 分段控件 + 版本徽标
|
|
575
621
|
- 样式从内联改为独立注入的 `settings.css`(支持 hover / 窄屏 / `body[data-ds-dark-theme]`)
|
package/client.js
CHANGED
|
@@ -27,7 +27,7 @@ window.__ModuleLoader__.load({
|
|
|
27
27
|
const React = require('react')
|
|
28
28
|
const h = React.createElement
|
|
29
29
|
|
|
30
|
-
const PLUGIN_VERSION = '1.3.
|
|
30
|
+
const PLUGIN_VERSION = '1.3.1'
|
|
31
31
|
const STORE_KEY = 'dsh-theme-palette.v1'
|
|
32
32
|
const OVERRIDE_SOURCE = 'dsh-theme-palette'
|
|
33
33
|
|
|
@@ -1119,6 +1119,11 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1119
1119
|
row(txt.sidebar, txt.sidebarSub, seg(SIDEBAR_MODES, sidebarMode, props.setSidebarMode)),
|
|
1120
1120
|
row(txt.rightbar, txt.rightbarSub, seg(RIGHTBAR_MODES, rightbarMode, props.setRightbarMode)),
|
|
1121
1121
|
health && !health.ok && h('p', { className: 'dsh-tp-status is-bad' }, txt.bad),
|
|
1122
|
+
health && health.ok && health.reapplies > 0 && h(
|
|
1123
|
+
'p',
|
|
1124
|
+
{ className: 'dsh-tp-status' },
|
|
1125
|
+
(isZh() ? '配色层被宿主丢过一次,已自动恢复 ' : 'Palette layer was dropped by the host; restored ') + health.reapplies + (isZh() ? ' 次' : 'x'),
|
|
1126
|
+
),
|
|
1122
1127
|
h('button', {
|
|
1123
1128
|
type: 'button',
|
|
1124
1129
|
className: 'dsh-tp-more',
|
|
@@ -1210,7 +1215,13 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1210
1215
|
|
|
1211
1216
|
let disposeLayer = () => {}
|
|
1212
1217
|
/** 按当前配色 + 侧栏档位重建覆盖层(换配色与换侧栏档位走同一条路)。 */
|
|
1213
|
-
|
|
1218
|
+
// 最近一次成功应用的 token 表与原因:自愈时据此重放,也用来解释"为什么又变回去了"。
|
|
1219
|
+
let lastTokens = null
|
|
1220
|
+
let lastAppliedAt = 0
|
|
1221
|
+
let lastReason = 'init'
|
|
1222
|
+
let reapplyCount = 0
|
|
1223
|
+
|
|
1224
|
+
const applyAll = (reason) => {
|
|
1214
1225
|
const scheme = SCHEMES.find((s) => s.id === current) || SCHEMES[0]
|
|
1215
1226
|
const tokens = buildTokens(scheme, sidebarMode, textMode, rightbarMode, scrollMode, controlMode, mdMode)
|
|
1216
1227
|
let next = () => {}
|
|
@@ -1226,8 +1237,38 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1226
1237
|
const prev = disposeLayer
|
|
1227
1238
|
disposeLayer = next
|
|
1228
1239
|
prev()
|
|
1240
|
+
if (reason !== undefined) lastReason = reason
|
|
1241
|
+
lastTokens = tokens
|
|
1242
|
+
lastAppliedAt = Date.now()
|
|
1229
1243
|
scheduleProbe(tokens)
|
|
1230
1244
|
}
|
|
1245
|
+
/**
|
|
1246
|
+
* 自愈。
|
|
1247
|
+
*
|
|
1248
|
+
* 宿主的 theme.overrideTokens 把每一层的 disposer 挂在**调用方 fiber** 上
|
|
1249
|
+
* (`ctx.effect(() => dispose, ...)`):插件被卸载/重载、或 fiber 的 effect 被清理时,
|
|
1250
|
+
* 这一层就没了 —— 表现正是"配色自己跳回默认",而 localStorage 里的选择还在。
|
|
1251
|
+
* 这里定期(以及窗口重新可见/获焦时)回读 body:token 不在了就把最后一层重放。
|
|
1252
|
+
*/
|
|
1253
|
+
const ensureApplied = (reason) => {
|
|
1254
|
+
if (lastTokens === null) return
|
|
1255
|
+
if (Date.now() - lastAppliedAt < 900) return
|
|
1256
|
+
let missing = false
|
|
1257
|
+
try {
|
|
1258
|
+
const style = getComputedStyle(document.body)
|
|
1259
|
+
for (const name of TOKEN_PROBE) {
|
|
1260
|
+
if (!(name in lastTokens)) continue
|
|
1261
|
+
if (style.getPropertyValue(name).trim() === '') { missing = true; break }
|
|
1262
|
+
}
|
|
1263
|
+
} catch (err) {
|
|
1264
|
+
missing = true
|
|
1265
|
+
}
|
|
1266
|
+
if (!missing) return
|
|
1267
|
+
reapplyCount += 1
|
|
1268
|
+
console.info('[dsh-theme-palette] 配色层不见了(' + reason + '),已重放第 ' + reapplyCount + ' 次')
|
|
1269
|
+
applyAll('self-heal:' + reason)
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1231
1272
|
let health = null
|
|
1232
1273
|
const healthListeners = new Set()
|
|
1233
1274
|
const getHealth = () => health
|
|
@@ -1243,7 +1284,7 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1243
1284
|
const next = probeApplied(tokens)
|
|
1244
1285
|
if (next === null) return
|
|
1245
1286
|
if (health !== null && health.ok === next.ok && health.checked === next.checked) return
|
|
1246
|
-
health = next
|
|
1287
|
+
health = { ...next, reapplies: reapplyCount }
|
|
1247
1288
|
if (!next.ok) console.warn('[dsh-theme-palette] 有 token 没写进 body,宿主可能改名了')
|
|
1248
1289
|
emitHealth()
|
|
1249
1290
|
}
|
|
@@ -1321,11 +1362,36 @@ body[data-ds-dark-theme] .dsh-tp-check{background:var(--t-accent-d);color:var(--
|
|
|
1321
1362
|
console.warn('[dsh-theme-palette] dev hook attach 失败:', err)
|
|
1322
1363
|
}
|
|
1323
1364
|
|
|
1365
|
+
// 自愈的触发点:窗口回前台、获焦、页面恢复、主题事件、以及一个低频定时器。
|
|
1366
|
+
const heal = (why) => () => { try { ensureApplied(why) } catch (err) { /* 自愈失败不影响主流程 */ } }
|
|
1367
|
+
const healOnVisible = heal('visibility')
|
|
1368
|
+
const healOnFocus = heal('focus')
|
|
1369
|
+
const healOnShow = heal('pageshow')
|
|
1370
|
+
const healOnTheme = heal('theme-change')
|
|
1371
|
+
let healTimer = null
|
|
1372
|
+
try {
|
|
1373
|
+
document.addEventListener('visibilitychange', healOnVisible)
|
|
1374
|
+
window.addEventListener('focus', healOnFocus)
|
|
1375
|
+
window.addEventListener('pageshow', healOnShow)
|
|
1376
|
+
} catch (err) { /* 无 DOM 时跳过 */ }
|
|
1377
|
+
try { ctx.on('theme/change', healOnTheme) } catch (err) { /* 事件名变了就只靠定时器 */ }
|
|
1378
|
+
try { healTimer = setInterval(() => ensureApplied('interval'), 4000) } catch (err) { /* 无定时器环境 */ }
|
|
1379
|
+
|
|
1324
1380
|
ctx.effect(() => {
|
|
1325
|
-
applyAll()
|
|
1381
|
+
applyAll('boot')
|
|
1326
1382
|
const removeStyle = installRightbarStyle()
|
|
1327
1383
|
const removeSettings = installSettingsStyle()
|
|
1328
|
-
return () => {
|
|
1384
|
+
return () => {
|
|
1385
|
+
disposeLayer()
|
|
1386
|
+
removeStyle()
|
|
1387
|
+
removeSettings()
|
|
1388
|
+
try {
|
|
1389
|
+
document.removeEventListener('visibilitychange', healOnVisible)
|
|
1390
|
+
window.removeEventListener('focus', healOnFocus)
|
|
1391
|
+
window.removeEventListener('pageshow', healOnShow)
|
|
1392
|
+
} catch (err) { /* 同上 */ }
|
|
1393
|
+
if (healTimer !== null) { clearInterval(healTimer); healTimer = null }
|
|
1394
|
+
}
|
|
1329
1395
|
}, 'dsh-theme-palette: token layer + 右栏样式')
|
|
1330
1396
|
|
|
1331
1397
|
ctx.slots.inject('settings.section', () =>
|