dlaoshi-outfit 2.1.3 → 2.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +30 -0
- package/client.js +65 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.1.4 — 2026-10-22
|
|
4
|
+
|
|
5
|
+
按 DSH 的「可逆副作用」规范重写挂载流程,支持热重组。
|
|
6
|
+
|
|
7
|
+
- 所有副作用都成对:样式表、`html` 皮肤类、主题 token、槽位占用全部由 `ctx.effect` 托管,
|
|
8
|
+
卸载时逐项回滚,不再依赖页面刷新
|
|
9
|
+
- 二次 `apply()` 幂等:先释放上一批槽位占用再挂载,避免热重组后出现两只桌宠
|
|
10
|
+
- 新增自愈:槽位容器还在、本插件节点消失时(HMR 驱逐),约 5 秒内自动重新挂载
|
|
11
|
+
- 测试新增「重复 apply 不重复占用槽位」断言
|
|
12
|
+
|
|
13
|
+
> 注意:全新安装插件仍需重启一次 Host——DSH 的插件图在启动时组合,这不是插件能绕过的。
|
|
14
|
+
|
|
3
15
|
## 2.1.3 — 2026-10-22
|
|
4
16
|
|
|
5
17
|
修复:网页端加载失败(`__ModuleLoader__` 未注册)。
|
package/README.md
CHANGED
|
@@ -123,6 +123,36 @@ Copy-Item "C:\Users\你\Documents\dlaoshi-outfit\*" `
|
|
|
123
123
|
└── examples/ # profile 配置示例
|
|
124
124
|
```
|
|
125
125
|
|
|
126
|
+
## 常见问题
|
|
127
|
+
|
|
128
|
+
### 装完之后没反应 / 第一次启动不生效
|
|
129
|
+
|
|
130
|
+
**这是 DSH 的加载机制,不是插件的问题。** 本插件已按「可逆副作用」实现:卸载即回滚、重复挂载不会叠加、被 HMR 驱逐后会自动重挂,但**全新安装仍需重启一次 Host**(插件图只在启动时组合)。 DSH 的插件图(loader entries)在 **Host 启动时组合**;热更新只对已经加载过的客户端 bundle 生效。全新安装一个插件等于往插件图里加一个新 entry,**必须完整重启一次 DSH** 才会生效。
|
|
131
|
+
|
|
132
|
+
排查顺序:
|
|
133
|
+
|
|
134
|
+
1. 完全退出 DSH(不是关窗口,要从托盘/任务管理器退出)
|
|
135
|
+
2. 重新启动
|
|
136
|
+
3. 设置 → 插件,确认 `dlaoshi-outfit` 存在且已启用
|
|
137
|
+
4. 仍不出现 → 再重启一次(首次安装有时需要两轮:第一轮装包、第二轮组合)
|
|
138
|
+
5. 打开浏览器控制台(F12),看有没有 `__ModuleLoader__` 相关报错
|
|
139
|
+
|
|
140
|
+
### 桌宠突然消失
|
|
141
|
+
|
|
142
|
+
通常是客户端热更新(HMR)把插件卸载后没有重新挂载。按 `Ctrl+R` 刷新页面即可;刷新后仍不在就重启 DSH。
|
|
143
|
+
|
|
144
|
+
### 网页端提示「加载插件失败 / 未注册」
|
|
145
|
+
|
|
146
|
+
确认安装的是 **2.1.3 或更高版本**。`2.1.0` 和 `2.1.1` 的客户端注册 id 与包名不一致,已被标记 deprecated:
|
|
147
|
+
|
|
148
|
+
```powershell
|
|
149
|
+
dsh plugin add dlaoshi-outfit@2.1.3
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### 开发时想热更新
|
|
153
|
+
|
|
154
|
+
客户端代码改动只有在 `pnpm run dev:web` 同时运行时才会自动重建并热替换;否则改完 `client.js` 需要重启 DSH。
|
|
155
|
+
|
|
126
156
|
## 开发
|
|
127
157
|
|
|
128
158
|
```powershell
|
package/client.js
CHANGED
|
@@ -78,6 +78,10 @@ window.__ModuleLoader__.load({
|
|
|
78
78
|
listener(outfitEnabled)
|
|
79
79
|
return () => { outfitListeners.delete(listener) }
|
|
80
80
|
}
|
|
81
|
+
|
|
82
|
+
/** Disposer of the currently mounted occupant set, shared across apply()
|
|
83
|
+
* calls so a second mount replaces the first instead of stacking on it. */
|
|
84
|
+
let mountedOccupants = null
|
|
81
85
|
/** Host route that reports the live turn's tokens and think latency. */
|
|
82
86
|
const STATS_PATH = '/fatfish/stats'
|
|
83
87
|
|
|
@@ -1152,18 +1156,34 @@ html.${SKIN_CLASS} body[data-ds-dark-theme] [data-turn-process-answer] {
|
|
|
1152
1156
|
window.__petDebug = { starts: 0, ends: 0, happy: 0, sad: 0, balance: null }
|
|
1153
1157
|
}
|
|
1154
1158
|
|
|
1159
|
+
// Reversible, idempotent mount. Every side effect below is paired with a
|
|
1160
|
+
// disposer owned by this plugin's fiber, so the loader can apply this
|
|
1161
|
+
// module again (hot recompose) without a restart and without leaving a
|
|
1162
|
+
// duplicate stylesheet, class or slot occupant behind.
|
|
1163
|
+
if (typeof document.querySelectorAll === 'function') {
|
|
1164
|
+
for (const stale of document.querySelectorAll('style[data-plugin="dlaoshi-outfit"]')) {
|
|
1165
|
+
try { stale.remove() } catch (err) { /* already detached */ }
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1155
1169
|
// Apply the remembered outfit state before the stylesheet mounts so the
|
|
1156
1170
|
// page never flashes the beautified look on a "switched off" session.
|
|
1157
1171
|
outfitEnabled = readOutfitSetting()
|
|
1158
|
-
|
|
1172
|
+
ctx.effect(() => {
|
|
1173
|
+
applyOutfitClass(outfitEnabled)
|
|
1174
|
+
return () => {
|
|
1175
|
+
try { document.documentElement.classList.remove(SKIN_CLASS) } catch (err) { /* no document */ }
|
|
1176
|
+
}
|
|
1177
|
+
}, 'dlaoshi-pet: skin class')
|
|
1159
1178
|
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1179
|
+
ctx.effect(() => {
|
|
1180
|
+
const tag = document.createElement('style')
|
|
1181
|
+
tag.dataset.plugin = 'dlaoshi-outfit'
|
|
1182
|
+
tag.textContent = CSS
|
|
1183
|
+
document.head.appendChild(tag)
|
|
1184
|
+
return () => {
|
|
1185
|
+
try { tag.remove() } catch (err) { /* already detached */ }
|
|
1186
|
+
}
|
|
1167
1187
|
}, 'dlaoshi-pet: stylesheet')
|
|
1168
1188
|
|
|
1169
1189
|
ctx.effect(() => ctx.theme.overrideTokens('dlaoshi-outfit', TOKENS),
|
|
@@ -1172,7 +1192,14 @@ html.${SKIN_CLASS} body[data-ds-dark-theme] [data-turn-process-answer] {
|
|
|
1172
1192
|
const occupy = (name, id, order, component) => ctx.slots.inject(name, () =>
|
|
1173
1193
|
ctx.slots.register({ name, id, order }, component))
|
|
1174
1194
|
|
|
1175
|
-
|
|
1195
|
+
// A second mount must replace the first, never stack on top of it: the
|
|
1196
|
+
// loader may re-apply this module while a previous occupant set is still
|
|
1197
|
+
// registered (hot recompose), and two pets would otherwise fight.
|
|
1198
|
+
const mountOccupants = () => {
|
|
1199
|
+
if (typeof mountedOccupants === 'function') {
|
|
1200
|
+
try { mountedOccupants() } catch (err) { /* already gone */ }
|
|
1201
|
+
mountedOccupants = null
|
|
1202
|
+
}
|
|
1176
1203
|
const stop = [
|
|
1177
1204
|
occupy('shell.overlay', 'dlaoshi-companion', 20, Companion),
|
|
1178
1205
|
occupy('sidebar.brand.mark', 'dlaoshi-sidebar', 5, SidebarMark),
|
|
@@ -1181,7 +1208,36 @@ html.${SKIN_CLASS} body[data-ds-dark-theme] [data-turn-process-answer] {
|
|
|
1181
1208
|
occupy('settings.general.item', 'dlaoshi-theme', 40, ThemeSettingsRow),
|
|
1182
1209
|
]
|
|
1183
1210
|
return () => { for (const fn of stop) if (typeof fn === 'function') fn() }
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
mountedOccupants = mountOccupants()
|
|
1214
|
+
ctx.effect(() => () => {
|
|
1215
|
+
if (typeof mountedOccupants === 'function') mountedOccupants()
|
|
1216
|
+
mountedOccupants = null
|
|
1184
1217
|
}, 'dlaoshi-pet: slot occupants')
|
|
1218
|
+
|
|
1219
|
+
// Self heal for hot recomposition: when the loader evicts this module's
|
|
1220
|
+
// occupants but the overlay slot itself stays mounted, remount instead of
|
|
1221
|
+
// leaving the user with a vanished pet until they refresh.
|
|
1222
|
+
ctx.effect(() => {
|
|
1223
|
+
if (typeof window === 'undefined' || typeof document === 'undefined') return () => {}
|
|
1224
|
+
let misses = 0
|
|
1225
|
+
const timer = setInterval(() => {
|
|
1226
|
+
try {
|
|
1227
|
+
const slot = document.querySelector('[data-slot="shell.overlay"]')
|
|
1228
|
+
if (!slot || typeof slot.querySelector !== 'function') { misses = 0; return }
|
|
1229
|
+
if (slot.querySelector(`.${PREFIX}-pet`)) { misses = 0; return }
|
|
1230
|
+
misses += 1
|
|
1231
|
+
if (misses < 2) return
|
|
1232
|
+
misses = 0
|
|
1233
|
+
mountedOccupants = mountOccupants()
|
|
1234
|
+
} catch (err) {
|
|
1235
|
+
misses = 0
|
|
1236
|
+
}
|
|
1237
|
+
}, 2500)
|
|
1238
|
+
if (timer && typeof timer.unref === 'function') timer.unref()
|
|
1239
|
+
return () => clearInterval(timer)
|
|
1240
|
+
}, 'dlaoshi-pet: hot-recompose self heal')
|
|
1185
1241
|
},
|
|
1186
1242
|
}
|
|
1187
1243
|
},
|