dsh-mpkg-wallpaper 3.8.0 → 3.8.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/lib/client.js CHANGED
@@ -306,6 +306,12 @@ function mpwTrace(ev, data) {
306
306
  * · 源字段(image/webUrl/sceneKey)粘性 ⇒ 谁都没带就保持原样,绝不因为"这次没提"而消失。
307
307
  * 合并结果**同时**回写两处(旧写法只写 localStorage,宿主那份半残档永远修不回来)。 */
308
308
  function initHostSettings() {
309
+ // ①(2026-09-19 设置持久化轮) **一次性**:apply() 会因 RTC 重连/热重注入重跑,而每一次都会
310
+ // 重新 GET 一次宿主设置。旧实现里重复的 GET 只是重复合并(幂等,代价小);现在合并带
311
+ // "两处存储时间戳裁决 + 回写两处",重复跑会让"后到的那次 GET"拿着**更旧**的宿主快照
312
+ // 参与裁决(极端情况下把刚写下的本地新值判给宿主)。所以这一轮明确只跑一次。
313
+ if (window.__mpwHostInitOnce) return;
314
+ window.__mpwHostInitOnce = true;
309
315
  try {
310
316
  fetch(HOST_BASE + "/settings", { method: "GET" }).then(async (r) => {
311
317
  if (!r.ok) { try { mpwBootSettle("host-get-not-ok") } catch {} return }
@@ -407,7 +413,15 @@ function sectionSigNow() {
407
413
  return o;
408
414
  }
409
415
  function readSection() {
410
- if (sectionCache) return sectionCache;
416
+ if (sectionCache) {
417
+ // ①(2026-09-19 设置持久化轮) **读口一律不见元键**:内部缓存为了落盘会带
418
+ // `__mpwLocalAt` 这类元键(时间戳/自愈标记),但"用户档"的读数口(面板/buildCss/
419
+ // 导出/字段集合比较/测试的逐字段 diff)必须看不到它们 —— 否则元键会被当成
420
+ // "用户的设置项"参与比较(真机上表现为"莫名多出一个键",测试里表现为假红)。
421
+ const clean = mpwStripMeta(sectionCache);
422
+ if (clean !== sectionCache) sectionCache = clean;
423
+ return sectionCache;
424
+ }
411
425
  sectionCache = mpwNormalizeSection(loadSection());
412
426
  return sectionCache;
413
427
  }
@@ -872,8 +886,26 @@ function sectionSigNow() {
872
886
  (typeof mpwVal(s, "source") === "string" && mpwVal(s, "source"));
873
887
  return !src && !!clue;
874
888
  }
889
+ /** 不变量Ⅲ(半边):**补空缺、不覆盖** —— 保留 `win` 已有的每一个键的值,
890
+ * 只把 `lose` 里 `win` **没有**的键补进来。
891
+ * ⚠ 这里连踩过两次坑(都由 B1/B4 断言抓出来,故单独成函数 + 明确语义):
892
+ * · 用 `mpwMergeSection(lose, win)` ⇒ win 的键逐个覆盖 lose ⇒ 变成"整档赢",
893
+ * 等于退化成旧写法的整档覆盖语义(字段级裁决名存实亡);
894
+ * · 用"谁值不同就取另一份"⇒ 两边都有的键仍然由**一方**决定,同样是整档赢。
895
+ * 只有"以赢家为底、输家只填空缺"才同时满足:两端都有的键由时间戳裁决 + 只有一端有的键一定保留。 */
896
+ function mpwOverlayFill(win, lose) {
897
+ const out = Object.assign({}, (win && typeof win === "object") ? win : {});
898
+ if (!lose || typeof lose !== "object") return out;
899
+ for (const k of Object.keys(lose)) {
900
+ if (out[k] !== void 0) continue; // 赢家已有 ⇒ 一律用赢家的
901
+ const v = lose[k];
902
+ if (v === void 0 || v === null) continue; // 输家的"缺/显式删"都不该补进赢家
903
+ out[k] = v;
904
+ }
905
+ return out;
906
+ }
875
907
  /** 不变量Ⅲ:启动时把两处存储合成一份 —— 新者优先(各自的内嵌时间戳),
876
- * 再走三路合并("与 base 相同 = 这一层没动过它"⇒ 用另一层,字段级不会互相抹)。 */
908
+ * 输家只补赢家没有的键(`mpwOverlayFill`),源字段另有粘性保护(谁都没有时不抹)。 */
877
909
  function mpwReconcileStores(localIn, hostIn) {
878
910
  const local = mpwStripMeta(localIn || {});
879
911
  const host = mpwStripMeta(hostIn || {});
@@ -894,12 +926,10 @@ function sectionSigNow() {
894
926
  newer = "unknown";
895
927
  reason = "no-stamps(local=" + localKeys.length + " keys, host=" + hostKeys.length + " keys)";
896
928
  }
897
- const A = winner === "local" ? local : host;
898
- const B = winner === "local" ? host : local;
899
- // 三路合并:A 为基准;B 里"与 A 不同"的键(= 只有 B 那一层动过)也带上(B 不覆盖 A 已给的键)。
900
- const patch = {};
901
- for (const k of mpwSortedKeys(B)) if (mpwVal(B, k) !== mpwVal(A, k)) patch[k] = mpwVal(B, k);
902
- let merged = mpwMergeSection(A, patch);
929
+ /* 裁决后合成:**赢家已有的键一律用赢家的值**,输家只补赢家没有的键(见 mpwOverlayFill)。 */
930
+ const win = winner === "local" ? local : host;
931
+ const lose = winner === "local" ? host : local;
932
+ let merged = mpwOverlayFill(win, lose);
903
933
  // 源字段粘性 + 一致性标记:两处都没有源、却都有来源线索 ⇒ 半残档(交给自愈)
904
934
  merged = mpwStickySource(local, merged);
905
935
  merged = mpwStickySource(host, merged);
@@ -6886,6 +6916,10 @@ function sectionSigNow() {
6886
6916
  legacy: () => mpwPersistLegacy(),
6887
6917
  state: () => Object.assign({}, mpwPersistState),
6888
6918
  apply: () => { try { applyFromStorage() } catch (e) { return String((e && e.message) || e) } return "ok" },
6919
+ /* ①(2026-09-19) "部分档写入"的真实入口:`writeSection` 拿到**没带源字段**的 next 时,
6920
+ 粘性护栏必须把源保住(真机上 `initHostSettings` 的宿主合成结果、`mpwMarkPersistFail`
6921
+ 的留痕写都会经过这条路)。暴露它才能把这条护栏变成断言而不是"看代码觉得对"。 */
6922
+ writePartial: (next) => { try { writeSection(next || {}, true); return true } catch (e) { return String(e && e.message || e) } },
6889
6923
  onMsg: (fn) => mpwPersistOnMsg(fn),
6890
6924
  takePending: () => mpwPersistTakePending(),
6891
6925
  failKey: MPW_PERSIST_FAIL_KEY,
@@ -7771,7 +7805,7 @@ module.exports = {
7771
7805
  组件本体(几何/形变/那套"一个数就是全部状态"的设计)来自 **Bencho** 的 "Now playing"
7772
7806
  (上游文件 `Sound.tsx` + 随附样式表),上游作者 Bencho,许可 **MIT**(bencho.dev/licence)。
7773
7807
  移植方式:**逐行移植、注释原文保留**(用户原话:"它们解释了这些数字为什么是这个值,
7774
- 也是这份代码值得照抄而不是重写的主要原因")。归属见 `THIRD-PARTY.md` §13;
7808
+ 也是这份代码值得照抄而不是重写的主要原因")。归属见 `THIRD-PARTY.md` §6(①2026-09-19 复核更正:原先指向 §13,该节实际是别的条目);
7775
7809
  跨仓台账见 `../docs/COPYING-RULES.md` §4。
7776
7810
  ⚠ **本文件不含任何 GPL 代码**:`we-scene-demo/demo/now-playing/**`(GPL-3.0-or-later)
7777
7811
  只被**读来核对数学**,一行都没有拷进来(`../docs/COPYING-RULES.md` §2.2 "GPL 永不进插件")。
@@ -10,7 +10,7 @@
10
10
  组件本体(几何/形变/那套"一个数就是全部状态"的设计)来自 **Bencho** 的 "Now playing"
11
11
  (上游文件 `Sound.tsx` + 随附样式表),上游作者 Bencho,许可 **MIT**(bencho.dev/licence)。
12
12
  移植方式:**逐行移植、注释原文保留**(用户原话:"它们解释了这些数字为什么是这个值,
13
- 也是这份代码值得照抄而不是重写的主要原因")。归属见 `THIRD-PARTY.md` §13;
13
+ 也是这份代码值得照抄而不是重写的主要原因")。归属见 `THIRD-PARTY.md` §6(①2026-09-19 复核更正:原先指向 §13,该节实际是别的条目);
14
14
  跨仓台账见 `../docs/COPYING-RULES.md` §4。
15
15
  ⚠ **本文件不含任何 GPL 代码**:`we-scene-demo/demo/now-playing/**`(GPL-3.0-or-later)
16
16
  只被**读来核对数学**,一行都没有拷进来(`../docs/COPYING-RULES.md` §2.2 "GPL 永不进插件")。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mpkg-wallpaper",
3
- "version": "3.8.0",
3
+ "version": "3.8.1",
4
4
  "description": "DSH Web 壁纸引擎 Wallpaper Engine mpkg 背景插件:浏览器内直接解析 .mpkg(preview.gif 动态背景/内嵌 mp4 视频/多时段切换),整屏统一虚化/对话框/弹层/遮罩独立虚化、镜头缩放平移、时钟、冲突检测。",
5
5
  "private": false,
6
6
  "type": "module",