dsh-mpkg-wallpaper 3.16.4 → 3.16.6

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
@@ -3413,6 +3413,40 @@ function mpwThumbFail(el, why) {
3413
3413
  // ═══════════════════════════════════════════════════════════════════
3414
3414
  // 背景 DOM(一个固定 img 层;用 <img> 保证 GIF 动画可靠播放,⑪)
3415
3415
  // ═══════════════════════════════════════════════════════════════════
3416
+ /** 我们**整屏 / 可见**浮层的宿主容器(`#mpw-layers`)。
3417
+ *
3418
+ * 为什么需要它(上游同族缺陷:macOS 桌面端"整屏 body 浮层把窗口可拖区整片挖掉"):
3419
+ * 宿主前端在 darwin 下有一条 `html[data-platform=darwin] body>:not(#root){-webkit-app-region:no-drag}`,
3420
+ * 而 Electron 对 `no-drag` 的语义是**几何挖除** —— 把这块矩形从可拖区域里减掉,
3421
+ * **与绘制顺序、z-index、pointer-events 都无关**(官方文档 custom-window-interactions)。
3422
+ * ⇒ 我们直接挂在 `body` 上的整屏浮层(壁纸层/遮罩/时钟)会把整条顶栏的可拖几何挖空:
3423
+ * 真机症状 = 在顶栏空白处按下拖动,窗口纹丝不动(关掉插件立刻恢复)。
3424
+ *
3425
+ * 修法:所有整屏浮层挂进这一个容器。容器自己的盒子是 **0×0** ⇒ 被挖掉的面积是 0;
3426
+ * 子元素不在 `body>*` 集合里 ⇒ 宿主那条规则不再命中它们(`-webkit-app-region` 不是继承属性)。
3427
+ * 副作用面为零:`position:absolute` + 0 尺寸 = 脱离文档流(不是 flex/grid 项、不占位)。
3428
+ *
3429
+ * 三条**不许有**(否则等于把老问题换个地方犯,见 docs/BGWRAP-VISIBILITY.md 的"层宿主"一节):
3430
+ * · 不许 `z-index`(非 auto 会建层叠上下文 ⇒ `#mpw-bgWrap` 的 `z-index:-1` 会被困在容器里);
3431
+ * · 不许 `transform`/`filter`/`backdrop-filter`/`contain`/`will-change`(会变成 fixed 后代的
3432
+ * **containing block** ⇒ 整屏浮层按 0×0 的容器定位,直接消失);
3433
+ * · 不许参与布局(`position:absolute` 就是为此;别改成 static/fixed —— static 会变成 flex/grid 项,
3434
+ * fixed 会自建层叠上下文)。
3435
+ * `data-plugin` 标记让 `mpwIsOurs()` 认它(我们自己的突变不再喂回自己的观察器)。 */
3436
+ const LAYER_HOST_ID = "mpw-layers";
3437
+ function mpwLayerHost() {
3438
+ try {
3439
+ const found = document.getElementById(LAYER_HOST_ID);
3440
+ if (found && found.parentElement) return found;
3441
+ const host = document.createElement("div");
3442
+ host.id = LAYER_HOST_ID;
3443
+ try { host.setAttribute("data-plugin", "dsh-mpkg-wallpaper") } catch (e) {}
3444
+ host.style.cssText = "position:absolute;left:0;top:0;width:0;height:0;";
3445
+ (document.body || document.documentElement).appendChild(host);
3446
+ return host;
3447
+ } catch (e) { return (document.body || document.documentElement); }
3448
+ }
3449
+
3416
3450
  function ensureBgDom() {
3417
3451
  let wrap = document.getElementById(BG_WRAP_ID);
3418
3452
  if (!wrap) {
@@ -3515,7 +3549,8 @@ function mpwThumbFail(el, why) {
3515
3549
  canvas.className = "mpw-bgCanvas";
3516
3550
  canvas.style.display = "none";
3517
3551
  wrap.appendChild(canvas);
3518
- (document.body || document.documentElement).appendChild(wrap);
3552
+ /* ①(2026-10-07 上游同族轮) 走层宿主:直接挂 body 的整屏浮层会挖掉 macOS 的窗口可拖区(见 mpwLayerHost)。 */
3553
+ mpwLayerHost().appendChild(wrap);
3519
3554
  }
3520
3555
  return wrap;
3521
3556
  }
@@ -3619,7 +3654,7 @@ function mpwThumbFail(el, why) {
3619
3654
  clockEl = document.createElement("div");
3620
3655
  clockEl.id = "mpw-clock";
3621
3656
  clockEl.style.cssText = "position:fixed;z-index:2000;pointer-events:none;font-family:ui-monospace,SFMono-Regular,monospace;font-variant-numeric:tabular-nums;text-shadow:0 1px 6px rgba(0,0,0,.55);color:var(--dsw-alias-label-primary,#e8eaf0);";
3622
- (document.body || document.documentElement).appendChild(clockEl);
3657
+ mpwLayerHost().appendChild(clockEl); // 同上:整屏浮层一律走层宿主
3623
3658
  return clockEl;
3624
3659
  }
3625
3660
  function clockText() {
@@ -6724,6 +6759,14 @@ function mpwThumbFail(el, why) {
6724
6759
  if (d.op === "ready") mpwWebDiag("shim-ready", { v: Number(d.v) || 0, href: String(d.href || "").slice(0, 200), caps: d.caps || null });
6725
6760
  return;
6726
6761
  }
6762
+ if (d.op === "focus-guard") {
6763
+ /* ①(上游同族缺陷:网页壁纸帧级夺焦)帧内**夺焦围栏**第一次真拦下 `window.focus()` 时的回执。
6764
+ 不透明源下父页读不到帧内 window ⇒ 计数只能由 shim/围栏上报;这条落进 /diag,
6765
+ 让"这张壁纸在从 DSH 手里拿键盘"变成**宿主侧可见**的证据(不是错误,是记账)。 */
6766
+ if (frame) { try { frame.__mpwFocusGuard = { blocked: Number(d.blocked) || 0, calls: Number(d.calls) || 0 } } catch (e) {} }
6767
+ mpwWebDiag("focus-guard", { blocked: Number(d.blocked) || 0, calls: Number(d.calls) || 0 });
6768
+ return;
6769
+ }
6727
6770
  if (d.op === "error") {
6728
6771
  const info = mpwWebSrcFromUrl(frame && frame.getAttribute("src"));
6729
6772
  try { console.warn("[dsh-mpkg-wallpaper] 网页壁纸脚本错误(已兜住):", String(d.kind || "error"), String(d.message || ""), String(d.stack || "").split("\n")[1] || ""); } catch {}
@@ -7579,11 +7622,22 @@ function mpwThumbFail(el, why) {
7579
7622
  * 顺序重要:裸 `header` 会命中页面上任意 header(真机 diag 曾抓到错误节点)。 */
7580
7623
  function findHostHeader() {
7581
7624
  try {
7582
- return document.querySelector(".wSkVaW_header")
7625
+ const h = document.querySelector(".wSkVaW_header")
7583
7626
  || document.querySelector('header[class*="_header_"]')
7584
7627
  || document.querySelector('[class*="wSkVaW_header"]')
7585
7628
  || document.querySelector("header")
7586
7629
  || null;
7630
+ /* ①(2026-10-09 DSH 0.2.0 真机) 空标题栏状态在**所有**分支都广播一次:
7631
+ 新会话 headerBlank / 会话加载中 headerSessionless 两个态下,宿主把标题栏留成一条空白,
7632
+ 我们的底色/磨砂/描边/悬浮几何一律不许画(真机症状:顶部一条很细的圆角条)。
7633
+ 挂在这里而不是只挂 ensureHeaderFrost:伪元素兜底(?hdrblur=pseudo)与 cleanup 分支
7634
+ 不经过 ensureHeaderFrost,只在那里设会让空栏又被 ::before 糊上。 */
7635
+ try {
7636
+ const blank = !!(h && h.classList && (h.classList.contains("wSkVaW_headerBlank") || h.classList.contains("wSkVaW_headerSessionless")));
7637
+ if (blank) document.body.setAttribute("data-mpw-hdr-blank", "");
7638
+ else document.body.removeAttribute("data-mpw-hdr-blank");
7639
+ } catch (e) { /* 忽略 */ }
7640
+ return h;
7587
7641
  } catch { return null }
7588
7642
  }
7589
7643
  /* ═══ ①(2026-09-17 真机 bug:在标题栏切换"主会话 ↔ 子代理会话"时磨砂先消失、约 3 秒才回来) ═══
@@ -7698,6 +7752,24 @@ function mpwThumbFail(el, why) {
7698
7752
  try {
7699
7753
  const hdr = findHostHeader();
7700
7754
  if (!hdr) return null;
7755
+ /* ①(2026-10-09 DSH 0.2.0 真机) 空标题栏**绝不给磨砂与底色**:
7756
+ 宿主在"新会话"(wSkVaW_headerBlank)与"会话加载中"(wSkVaW_headerSessionless)
7757
+ 两个态下把标题栏留成一条空白 —— 我们再往里插 blur(30px) 的层 + 给它半透明底,
7758
+ 真机症状就是用户报的"最上面一条很细的圆角条"(太细了基本成弧)。
7759
+ 判据用宿主这两个类名(0.2.0 真机 outerHTML 实测),命中就摘层收手;
7760
+ 状态同时广播到 body(data-mpw-hdr-blank)给 CSS 兜底。 */
7761
+ try {
7762
+ const blank = !!(hdr.classList && (hdr.classList.contains("wSkVaW_headerBlank") || hdr.classList.contains("wSkVaW_headerSessionless")));
7763
+ if (blank) {
7764
+ try { const cur = hdr.querySelector(":scope > .mpw-hdrFrost"); if (cur) cur.remove() } catch {}
7765
+ try { hdr.removeAttribute("data-mpw-hdr-frost") } catch {}
7766
+ try { document.body.removeAttribute("data-mpw-hdr-frost-el") } catch {}
7767
+ try { document.body.setAttribute("data-mpw-hdr-blank", "") } catch {}
7768
+ try { lastFrostReason = "blank-header" } catch {}
7769
+ return null;
7770
+ }
7771
+ try { document.body.removeAttribute("data-mpw-hdr-blank") } catch {}
7772
+ } catch (e) { /* 读不到类名就按正常态走(宁可多一层磨砂,也不误伤正常标题栏) */ }
7701
7773
  let el = null;
7702
7774
  try { el = hdr.querySelector(":scope > .mpw-hdrFrost") } catch { el = null }
7703
7775
  if (!el) {
@@ -9261,6 +9333,27 @@ function mpwThumbFail(el, why) {
9261
9333
  const MPW_SIDE_MARK_ATTR = "data-mpw-side-mark";
9262
9334
  /** 弹层/浮层容器候选(与既有弹层判定同一套锚点,另加"面板型"类名)。 */
9263
9335
  const MPW_LAYER_SEL = '[class*="_overlay"], [class*="_modal"], [role="dialog"], [role="alertdialog"], [aria-modal="true"]';
9336
+ /** 「宽面板」候选:dockkit 的右栏面板/浮层单元(用**宿主属性名**,不是哈希类名 ⇒ 跨版本稳定)。
9337
+ * 为什么要单列一类:上游同族缺陷(侧栏全屏时右栏面板与对话窗口重叠)的形态是**没有 dialog
9338
+ * 角色、也没有 overlay 类名**的纯布局态 —— 只认 MPW_LAYER_SEL 的判据对它一条都不命中,
9339
+ * 而左栏玻璃的 `backdrop-filter` 会同时干两件事:① 让左栏变成 `position:fixed` 后代的
9340
+ * **containing block**(面板按左栏的盒子定位,真机读数 {x:13,w:254} = 被压进左栏);
9341
+ * ② 给左栏建层叠上下文,把我们自己钉的 `z-index:12`(dockkit 面板)连同面板一起困在里面。
9342
+ * ⇒ 判据必须能把"左栏里那个整屏面板"算成"开着的层",`data-mpw-holds-layer` 才会摘掉玻璃。
9343
+ * 只按**宽度**认整屏态:半屏右栏是常见态(左栏还露在旁边,摘它玻璃属于观感回归)⇒ 不触发;
9344
+ * 量不到视口宽时**不猜**(宁可不抬,也不误摘玻璃)。 */
9345
+ const MPW_WIDE_PANEL_SEL = '[data-dockkit-pane], [data-dockkit-surface]';
9346
+ function mpwIsWidePanel(el) {
9347
+ try {
9348
+ if (!el || el.nodeType !== 1) return false;
9349
+ const cs = getComputedStyle(el);
9350
+ if (!cs || cs.display === "none" || cs.visibility === "hidden" || Number(cs.opacity) === 0) return false;
9351
+ const vw = Number(window.innerWidth) || 0;
9352
+ if (!(vw > 0)) return false;
9353
+ const r = el.getBoundingClientRect();
9354
+ return !!r && r.width > 0 && r.width >= vw * 0.6;
9355
+ } catch (e) { return false }
9356
+ }
9264
9357
  /** 弹层容器的**类名/角色**特征:这些节点绝不允许被我们打上侧栏类(会吃到侧栏 !important 规则)。 */
9265
9358
  const MPW_LAYERISH_CLASS_RE = /(overlay|_modal|modal_|dialog|sheet|drawer|popover|panel|menu|portal)/i;
9266
9359
  /** 该节点自己是不是"弹层/面板型"容器(不看子树,只看自身)。 */
@@ -9293,6 +9386,10 @@ function mpwThumbFail(el, why) {
9293
9386
  try {
9294
9387
  const want = new Set();
9295
9388
  const all = Array.from(document.querySelectorAll(MPW_LAYER_SEL));
9389
+ /* ①(上游同族缺陷:侧栏全屏时面板与对话重叠) 宽面板也算"开着的层":它没有 dialog
9390
+ 角色/overlay 类名,MPW_LAYER_SEL 一条都命中不了 ⇒ 玻璃规则会继续把左栏当
9391
+ containing block + 层叠上下文(判据与理由见 MPW_WIDE_PANEL_SEL)。 */
9392
+ for (const el of Array.from(document.querySelectorAll(MPW_WIDE_PANEL_SEL))) { if (mpwIsWidePanel(el)) all.push(el) }
9296
9393
  for (const el of all) {
9297
9394
  let open = false;
9298
9395
  try {
@@ -11146,7 +11243,7 @@ function mpwThumbFail(el, why) {
11146
11243
  aquaMaskEl = document.createElement("div");
11147
11244
  aquaMaskEl.id = "mpw-aqua-mask";
11148
11245
  aquaMaskEl.style.cssText = "position:fixed;inset:0;z-index:-1;pointer-events:none;display:none;";
11149
- (document.body || document.documentElement).appendChild(aquaMaskEl);
11246
+ mpwLayerHost().appendChild(aquaMaskEl); // 同上:整屏浮层一律走层宿主
11150
11247
  return aquaMaskEl;
11151
11248
  }
11152
11249
  function aquaParseHex(hex) {
@@ -11668,6 +11765,16 @@ function mpwThumbFail(el, why) {
11668
11765
  "--dsw-alias-bg-layer-1": {
11669
11766
  light: toAlpha("--dsw-static-neutral-bluish-50", a),
11670
11767
  dark: toAlpha("--dsw-static-neutral-bluish-850", a)
11768
+ },
11769
+ /* ①(2026-10-09 DSH 0.2.0 真机) 输入框里的「+」:宿主唯一一枚**不透明**小 chip
11770
+ (.uV2eYG_add { background: var(--dsw-specific-selector) },宿主值 #f5f6f7、无 blur),
11771
+ 落在我们 55% 的磨砂卡上就是一块"死白"(用户读数:"加号的底变成透明状态、只有一层白色的模糊")。
11772
+ 这一枚 token 全宿主**只有 2 处**使用(0.2.0-rc.2 产物实测),按弹层同档半透明化即可
11773
+ 让它读作玻璃;关档/无壁纸时整个 overrides 在下面 early-return 前根本不生效。
11774
+ 值形态与其余条目一致(toAlpha(静态色, a)),不引入新的宿主 token 依赖。 */
11775
+ "--dsw-specific-selector": {
11776
+ light: toAlpha("--dsw-static-neutral-bluish-50", a),
11777
+ dark: toAlpha("--dsw-static-neutral-bluish-850", a)
11671
11778
  }
11672
11779
  };
11673
11780
  if (!(enabled && hasImage)) {
@@ -13360,6 +13467,12 @@ function installAudioBus(win, opts = {}) {
13360
13467
  startCompatObserver: () => { try { startCompatObserver(); return true } catch (e) { return String(e && e.message || e) } },
13361
13468
  untagInjectedCompat: (why) => { try { return mpwUntagInjectedCompat(why || "test") } catch (e) { return -1 } },
13362
13469
  holdsLayer: (el) => { try { return mpwHoldsLayer(el) } catch (e) { return null } },
13470
+ syncLayerHosts: () => { try { return mpwSyncLayerHosts() } catch (e) { return String(e && e.message || e) } },
13471
+ layerHost: () => { try { return mpwLayerHost() } catch (e) { return null } },
13472
+ layerHostId: LAYER_HOST_ID,
13473
+ sideLiftSync: () => { try { return mpwSideLiftSync() } catch (e) { return String(e && e.message || e) } },
13474
+ isWidePanel: (el) => { try { return mpwIsWidePanel(el) } catch (e) { return null } },
13475
+ widePanelSel: MPW_WIDE_PANEL_SEL,
13363
13476
  looksLikeLayer: (el) => { try { return mpwLooksLikeLayer(el) } catch (e) { return null } },
13364
13477
  compatBridges: COMPAT_BRIDGES.map((b) => ({ sel: b[0], cls: b[1], mode: b[2] })),
13365
13478
  layerHostAttr: MPW_LAYER_HOST_ATTR,
@@ -17340,6 +17453,44 @@ html body[data-ds-dark-theme] [class*="sidebarCol"]:not([class*="_tab"]) {
17340
17453
  // 小弹窗**(上下文占用 264px 面板等)必须实体(用户实测 aqua 全关时它也被
17341
17454
  // dialogBlur 磨砂成透明);磨砂只在 B 块给 overlay 内的真正对话框覆盖。
17342
17455
  css += `
17456
+ /* ①(2026-10-09 第三批真机) 带 data-mpw-holds-layer 的 role=dialog 面板(Token 用量 / 会话统计
17457
+ bRhRbq_panel、智能体团队 VoX2oq_panel)**不是**要挡内容的设置窗,而是右栏/标题栏那一族的
17458
+ 浮层面板:给它们不透明兜底等于把宿主自带的 blur(40px) 废掉(真机读数:bg=rgb(255,255,255)
17459
+ + bf=blur(40px) saturate(1.5) ⇒ "有模糊但一点看不出来")。改走霜化档(80% + 我们的弹层 blur)。
17460
+ 判据用**我们自己的** data-mpw-holds-layer 标记(这两个面板上都有,真机读数确认)。
17461
+ ②(2026-10-10 第四批真机) **内层容器一律不准再画底**:宿主自己的
17462
+ .bRhRbq_details{...}(grid 容器)与 ._7KE1Ra_cell/_option 全都是 background:0 0
17463
+ —— 也就是说用户看到的"内层纯白"**是我们叠出来的**:在已经 80% 霜化的面板里再给内层
17464
+ 刷一层 80% ⇒ 合成 1-(1-0.8)² = 96% ≈ 纯白。所以这里只保留**面板本身**两条选择器,
17465
+ [data-session-stats-usage] / [data-session-stats-details] 从名单里摘除(内层交给宿主
17466
+ 的透明 + 外层面板的霜化)。 */
17467
+ html body [data-team-panel],
17468
+ html body [class*="bRhRbq_panel"] {
17469
+ background-color: var(--mpw-surface-panel-frost) !important;
17470
+ /* 半径写**常量**:这一族面板属于"四表面"常量档,token-namespace 判据要求它在
17471
+ 615 组设置 × 亮/暗下逐键相等 —— 挂 var(--mpw-pop-blur)(跟"弹层虚化"开关走)会让
17472
+ 同一表面在不同档位下取值不同 ⇒ 判定为"表面取值不恒定"(真机判据 4290 键不一致)。
17473
+ 玻璃观感靠 80% 霜化底 + 这枚常量半径;Token 用量/会话统计面板本来还带宿主自己的
17474
+ blur(40px)(我们只把它的不透明底换成霜化底,模糊是它自带的)。 */
17475
+ -webkit-backdrop-filter: blur(11px) !important;
17476
+ backdrop-filter: blur(11px) !important;
17477
+ }
17478
+ /* ①(2026-10-10 第四批真机) 智能体团队面板的**真正底**在伪元素上:宿主
17479
+ .VoX2oq_panel:before{content:"";z-index:-1;background:var(--dsw-specific-menu);
17480
+ backdrop-filter:var(--dsw-menu-backdrop-filter);border-radius:12px;position:absolute;inset:0}
17481
+ —— 它画在(我们的)元素背景**之上**,所以只改元素 background-color 永远看不到效果
17482
+ (用户:"智能体团队的背景模糊还是没有修好")。把这一层交还给我们:伪元素透明 + 不模糊,
17483
+ 霜化由上面的元素级规则提供(一层,不叠加)。 */
17484
+ html body [data-team-panel]::before,
17485
+ html body [data-team-panel]::after {
17486
+ background: transparent !important;
17487
+ -webkit-backdrop-filter: none !important;
17488
+ backdrop-filter: none !important;
17489
+ }
17490
+ html body[data-ds-dark-theme] [data-team-panel],
17491
+ html body[data-ds-dark-theme] [class*="bRhRbq_panel"] {
17492
+ background-color: var(--mpw-surface-panel-frost-dark) !important;
17493
+ }
17343
17494
  /* 通用居中窗口不透明兜底(防透出;overlay 内的对话框由 B 块磨砂覆盖) */
17344
17495
  [role="dialog"],
17345
17496
  [role="alertdialog"] {
@@ -17690,6 +17841,57 @@ html body[data-mpw-rsblur="on"] [data-mpw-rs-surface][data-dockkit-strip] {
17690
17841
  backdrop-filter: ${rsBlurCss} !important;
17691
17842
  background-color: var(--mpw-surface-rs-dock) !important;
17692
17843
  }`}
17844
+ /* ①(2026-10-09 DSH 0.2.0 真机) 收纳态停靠件兜底:宿主把收起的右栏面板留在 DOM 里并标
17845
+ aria-hidden="true"(rect 仍在视口内,Chromium 上会真的画出来)⇒ 我们**一个像素的玻璃都不许画在它上面**。
17846
+ 标记由 JS(mpwTagSurfaceOuter 的 parked 分支)摘,这里再兜一道:即使标记来不及摘,
17847
+ aria-hidden 的表面也保持透明无模糊 —— 用户报的"右半边被糊掉"就是这条缺失造成的。
17848
+ 反向判据:展开态的面板**不带** aria-hidden(真机读数),所以这条不会误伤正常右栏。 */
17849
+ html body[data-mpw-rightbar-parked] [data-mpw-rs-surface] {
17850
+ -webkit-backdrop-filter: none !important;
17851
+ backdrop-filter: none !important;
17852
+ background-color: transparent !important;
17853
+ }
17854
+ /* ①(2026-10-09 第二轮真机) 收纳态停靠件**整棵子树**都不许画东西:
17855
+ 用户读数"右边还是有一个和展开态一样大的框,有边缘、框内壁纸色与框外明显不同"——
17856
+ 那是宿主的停靠件子树自己画的底(_tabHost_* / _emptyTabHost_* 带 --dsw-alias-bg-base),
17857
+ 我们摘掉 rs-surface 标记只解决了"我们的玻璃",没解决"它的底"。
17858
+ data-mpw-rightbar-parked 由 JS 在收纳态广播(展开即摘),命中时把这棵子树的
17859
+ 背景/边框/阴影/玻璃/层级全部交还(transparent + none + auto),展开动画本身不受影响
17860
+ (宿主用的是 transform/尺寸过渡,这里只清绘制属性)。 */
17861
+ html body[data-mpw-rightbar-parked] [data-sidebar-right-panel],
17862
+ html body[data-mpw-rightbar-parked] [data-sidebar-right-float-host],
17863
+ html body[data-mpw-rightbar-parked] [data-dockkit-surface],
17864
+ html body[data-mpw-rightbar-parked] [data-dockkit-pane],
17865
+ html body[data-mpw-rightbar-parked] [data-dockkit-strip],
17866
+ html body[data-mpw-rightbar-parked] [class*="P3OORG_panel"],
17867
+ html body[data-mpw-rightbar-parked] [class*="_tabHost"],
17868
+ html body[data-mpw-rightbar-parked] [class*="_emptyTabHost"] {
17869
+ background-color: transparent !important;
17870
+ border-color: transparent !important;
17871
+ box-shadow: none !important;
17872
+ -webkit-backdrop-filter: none !important;
17873
+ backdrop-filter: none !important;
17874
+ z-index: auto !important;
17875
+ }
17876
+ /* ①(2026-10-09 第三批真机) 收纳态再加固两层,两个真机症状一起治:
17877
+ · "有一个 1px 的描边"(Android/Chromium 才看得见)—— 我们的发丝线/伪元素描边不在
17878
+ border-color 的管辖内(伪元素有自己的 border/box-shadow)⇒ 整棵停靠件子树**直接不画**:
17879
+ opacity:0(不影响布局、不参与绘制;伪元素的描边也一并消失)。
17880
+ 注:这里**不再**逐项加 background-image/outline/transition/animation —— 那几条会撞
17881
+ token-namespace 的"四表面生效值逐键相等"基线(真机报 gated|side|background-image 不一致);
17882
+ · "背景模糊最后才收起"(内容先没了、玻璃还留一会儿)—— 宿主用 transition 把面板挪走,
17883
+ 我们的 backdrop-filter 跟着一起走 ⇒ 用户看到"模糊停留"。transition:none + opacity:0
17884
+ 让它在标记落地的那一帧就消失(标记由 mpwTagSurfaceOuter 在同一个 mutation 批次里写)。 */
17885
+ html body[data-mpw-rightbar-parked] [data-sidebar-right-panel],
17886
+ html body[data-mpw-rightbar-parked] [data-sidebar-right-float-host],
17887
+ html body[data-mpw-rightbar-parked] [data-dockkit-surface],
17888
+ html body[data-mpw-rightbar-parked] [data-dockkit-pane],
17889
+ html body[data-mpw-rightbar-parked] [data-dockkit-strip],
17890
+ html body[data-mpw-rightbar-parked] [class*="P3OORG_panel"],
17891
+ html body[data-mpw-rightbar-parked] [class*="_tabHost"],
17892
+ html body[data-mpw-rightbar-parked] [class*="_emptyTabHost"] {
17893
+ opacity: 0 !important;
17894
+ }
17693
17895
  /* 抑制:弹窗/设置面板打开时(backdrop-filter 会成为 fixed 弹窗的 containing block → 必须摘除) */
17694
17896
  html body[data-mpw-sblur-off] [class*="sidebarCol"],
17695
17897
  html body[data-mpw-sblur-off] header[class*="_header_"],
@@ -18027,6 +18229,15 @@ html body[data-ds-dark-theme][data-mpw-unify] [data-mpw-rs-surface][data-dockkit
18027
18229
  html body[data-ds-dark-theme][data-mpw-unify] [data-mpw-rs-surface][data-dockkit-float] {
18028
18230
  background-color: var(--mpw-unify-surface-dark) !important;
18029
18231
  }
18232
+ /* ①(2026-10-09 DSH 0.2.0 真机) dockkit 新结构里 **面板本体**(_tabHost_*)自带
18233
+ background: var(--dsw-alias-bg-base)(用户档位下算出来是 35% 白、且**没有 blur**),
18234
+ 它盖在我们刷好的玻璃之上 ⇒ 看到的是"一层死白的底、完全没有模糊"(Sub Agent 面板就是这样变不透明的)。
18235
+ 既然面板已经被我们刷了玻璃,这层结构底就该让位(同一条件门控:右侧玻璃开 / 统一虚化开 + 已打 rs-surface)。
18236
+ [class*="_tabHost"] 同时覆盖 _tabHost_* / _tabHostHeader_* / _tabHostBody_* / _emptyTabHost_*。 */
18237
+ html body[data-mpw-rsblur="on"] [data-mpw-rs-surface] [class*="_tabHost"],
18238
+ html body[data-mpw-unify] [data-mpw-rs-surface] [class*="_tabHost"] {
18239
+ background-color: transparent !important;
18240
+ }
18030
18241
  /* better-sidebar 的面板 / 底部工作台:**只跟雾色与厚度,不给 backdrop-filter**。
18031
18242
  依据它自己的适配笔记(docs/BETTER-SIDEBAR-COMPAT.md / bsReveal 段注释):
18032
18243
  面板带 backdrop-filter 会成为内部 fixed 内容的 containing block("困内容")⇒ 只改底色。
@@ -18115,6 +18326,11 @@ html body[data-mpw-sblur-off] [data-mpw-rs] {
18115
18326
  清掉了模糊 ⇒ 纯白。把它(以及同类小面板)纳入弹层表面集合。 */
18116
18327
  const popSurfaceSel = `[role="menu"]${POP_NOT}, [role="listbox"]${POP_NOT}, [role="combobox"]${POP_NOT}, [data-dsh-surface]${POP_NOT}, [class*="_menu"]${POP_NOT}, [class*="_popover"]${POP_NOT}, [class*="_dropdown"]${POP_NOT}, [class*="_denseList"]${POP_NOT}, [class*="JObwrW_panel"]${POP_NOT}`;
18117
18328
  const popTipSel = `[role="tooltip"]:not([data-mpw-pop-untrunc]), [role="alert"]:not([data-mpw-pop-untrunc])`;
18329
+ /* ①(2026-10-10 第五批真机) 子智能体会话树(docs/subagentouterhtml.md 的 ZKlsPq_* 家族):
18330
+ 类名以 _menu **结尾**,进不了上面那串 [class*="_menu_"](那是给带下划线的表面用的)
18331
+ ⇒ 它一直没吃到我们的模糊;有壁纸时又落在带 blur 的祖先里 ⇒ 纯白。
18332
+ 这里单独补一条,**只补模糊、不补底**(底由宿主自己的 material/菜单底负责,避免叠两层白)。 */
18333
+ const popTreeSel = `[class*="ZKlsPq_menu"]:not([data-mpw-pop-untrunc])`;
18118
18334
  /* ①(2026-10-02 用户第 3 条:「把这个好用的模糊效果独立成 token,因为还有几个地方也要实现」)
18119
18335
  弹层表面 = **底色 + 模糊半径 + 圆角** 三件套,全部由这几枚 token 定义;任何弹层只要被
18120
18336
  下面的选择器命中,就自动同一套效果(不再各处写死 rgba/blur)。
@@ -18144,6 +18360,7 @@ html body[data-ds-dark-theme] :is(${popSurfaceSel}) {
18144
18360
  open-in-app 的下拉是 div._denseList_),否则它们会"半透明又无模糊"。
18145
18361
  表面选择器与上面的底色规则共用(POP_NOT 已排除 anchor/icon/status 等非表面类)。 */
18146
18362
  ${popTipSel},
18363
+ html body :is(${popTreeSel}),
18147
18364
  ${popSurfaceSel} {
18148
18365
  /* ①(2026-10-02) 半径/模糊统一读自有 token(--mpw-pop-blur 是唯一来源;关掉弹层虚化时它就是 none) */
18149
18366
  backdrop-filter: var(--mpw-pop-blur, ${popFilter}) !important;
@@ -18153,7 +18370,7 @@ ${popSurfaceSel} {
18153
18370
  兜底底**只刷宿主自己没有底色的弹层**(JS 扫 computed backgroundColor,α=0 才打 data-mpw-pop-bg);
18154
18371
  有宿主玻璃的弹层(标题栏子代理展开框 / VS Code 菜单 / 模型选择…)一个像素都不动。
18155
18372
  独立 token:--mpw-pop-surface(-dark)(不再共用面板表面 token --mpw-surface-pop)。 */
18156
- html body [data-mpw-pop-bg] { background-color: var(--mpw-pop-surface) !important; }
18373
+ html body [data-mpw-pop-bg]:not([data-menu-material]) { background-color: var(--mpw-pop-surface) !important; }
18157
18374
  /* ②(2026-10-05) 弹层处在一个"自己有底的玻璃祖先"里面(backdrop root 被截断 ⇒ 我们糊不到页面背后):
18158
18375
  与其"半透明却没模糊"(真机被读成"只有白色滤镜"),不如**做实在**——表面不透明度抬到 88%,
18159
18376
  并把 backdrop-filter 撤掉(白算一遍)。注意:只动我们自己的标记属性,宿主元素一个都不碰。
@@ -18175,7 +18392,66 @@ html body [data-mpw-pop-untrunc][data-mpw-pop-untrunc] {
18175
18392
  挂 body 的层又会压住弹层——弹层在卡片的层叠上下文里)。data-mpw-pop-glass 现在只是"B 类、
18176
18393
  祖先截断已被我们摘掉"的**诊断标记**,没有任何 CSS 效果(弹层自己的 blur 照常工作)。 */
18177
18394
  html body [data-mpw-pop-frost] { background-color: transparent !important; }
18178
- html body[data-ds-dark-theme] [data-mpw-pop-bg] { background-color: var(--mpw-pop-surface-dark) !important; }
18395
+ html body[data-ds-dark-theme] [data-mpw-pop-bg]:not([data-menu-material]) { background-color: var(--mpw-pop-surface-dark) !important; }
18396
+ /* ①(2026-10-09 第三批真机) 0.2.0 新菜单外壳(加号展开的"触发候选建议" / 选择工作区 / 选择 agent 预设):
18397
+ 外壳 ._surface_ri079_1 自带 isolation: isolate ⇒ **它自己就是 backdrop root**,内层
18398
+ ._material_ri079_21 的宿主 blur(40px) 只能采样外壳内部(外壳自身透明)⇒ 一点模糊都出不来。
18399
+ 真机原话:"加号点开之后展开的这个列表…它的模糊好像是不生效的"。
18400
+ 修法:把模糊补在**外壳自己**身上(外壳的 backdrop 是整页,不受它自己的 isolation 影响),
18401
+ 底色仍由宿主的 material 层画 —— 我们一个像素都不抢(这也是 host-material 判据的姊妹条款:
18402
+ 我们不画底,但必须补模糊,否则宿主那层 40px 被它自己的 isolation 废掉后整块是死的)。
18403
+ 关掉"弹层虚化"时 --mpw-pop-blur = none ⇒ 这条自动让位(审计提醒:别把 material 家族一起 none)。 */
18404
+ html body [data-menu-material],
18405
+ html body [data-trigger-menu] {
18406
+ -webkit-backdrop-filter: var(--mpw-pop-blur, blur(10px)) !important;
18407
+ backdrop-filter: var(--mpw-pop-blur, blur(10px)) !important;
18408
+ }
18409
+ /* ①(2026-10-10 第六批真机) 宿主 material 菜单的**内层底让位**:._material_ri079_21 是
18410
+ 58% 白 + 它自己的 blur(40px),但那个 blur 被外壳 ._surface_ri079_1{isolation:isolate}
18411
+ 废掉(外壳自己就是 backdrop root,内层只能采样外壳内部)⇒ 真机上就只剩一块"死白板"
18412
+ (用户:"选择模型的地方里面还有一个白色的块")。真机读数(--only-menu):模型/推理菜单
18413
+ 整棵子树里画了底的节点**只有这一个**(bg=rgba(248,249,250,.58) bf=blur(40px)saturate)。
18414
+ 修法:让它透明、模糊交还给外壳上我们那条 blur;底色改用我们自己的弹层表面 token
18415
+ ⇒ 模型/推理/工作区/权限这些菜单与其它弹层观感一致(不再是一块比周围更白的板)。 */
18416
+ /* ②(2026-10-10 用户截图 + 像素分析定案) 上一版把内层 material 底**整个透明掉**、改由外壳画底,
18417
+ 结果外壳自己没有圆角 ⇒ 截图里出现"外面一圈圆角玻璃 + 里面一块直角白矩形"(像素分析:白区
18418
+ 铺满菜单整宽 x=30→650,只有最外一圈是圆角)。
18419
+ 正解是**颜色换掉、形状保留**:带圆角的是内层 material 层,就让它继续画底(换成我们的弹层表面
18420
+ token),外壳只负责模糊、不画底 —— 这样圆角由宿主维持,模糊由外壳(它的 backdrop 是整页,
18421
+ 不受自身 isolation 影响)提供,内层自己的 blur40 被 isolation 废掉这件事也就不重要了。 */
18422
+ html body :is([data-menu-material], [data-trigger-menu]) [class*="_material_ri079_"] {
18423
+ background-color: var(--mpw-pop-surface) !important;
18424
+ /* ③(2026-10-10 用户截图 + outerHTML 定案) 这一层是 inset:0 的**方形填充**(宿主只给它
18425
+ position/inset/背景,没有圆角),而外壳 ._surface_ri079_1 是带圆角的 ⇒ 真机上就是
18426
+ "外面一圈圆角玻璃 + 里面一块直角白矩形"(用户原话)。让它继承外壳的圆角即可对齐形状。 */
18427
+ border-radius: inherit !important;
18428
+ }
18429
+ html body[data-ds-dark-theme] :is([data-menu-material], [data-trigger-menu]) [class*="_material_ri079_"] {
18430
+ background-color: var(--mpw-pop-surface-dark) !important;
18431
+ border-radius: inherit !important;
18432
+ }
18433
+ html body :is([data-menu-material], [data-trigger-menu]) {
18434
+ background-color: transparent !important;
18435
+ }
18436
+ /* ③(2026-10-10 真机层次读数定案) 宿主 material 菜单里的**内层容器**(_7KE1Ra_groups,radius 0
18437
+ 的直角滚动容器)会被**两条**弹层规则命中:我们新写的那条(有 !important)与"老 G 块"那条
18438
+ ([role="menu"],无 !important)。只排除一条,另一条就接管(真机读数:groups 仍是
18439
+ blur(11px) saturate(1.5))⇒ 它把身后的 58% 白底在方形区域内再糊一遍,就是用户说的
18440
+ "外面圆角那圈正常、里面一块直角白矩形"。这里**只新增**一条 !important 中和规则一次压住
18441
+ 所有来源,绝不改既有规则文本(改文本会扰动 token-namespace 的四表面基线,实测 612/615
18442
+ 组不一致)。 */
18443
+ html body [data-mpw-menu-inner][data-mpw-menu-inner][data-mpw-menu-inner] {
18444
+ -webkit-backdrop-filter: none !important;
18445
+ backdrop-filter: none !important;
18446
+ background-color: transparent !important;
18447
+ }
18448
+ /* 特异性说明(2026-10-10 真机规则归属定案):普通弹层规则那条是
18449
+ html body :is(popTipSel..., popSurfaceSel) { backdrop-filter: var(--mpw-pop-blur) !important },
18450
+ 而 :is() 取**参数里最高**的特异性 —— popSurfaceSel 含 [class*="_menu_"]:not(...) ⇒ (0,2,0),
18451
+ 加上 html body ⇒ **(0,2,2)**。写两遍的我方规则同样 (0,2,2) ⇒ 平手,而它在样式表里更靠后 ⇒ 它赢
18452
+ (真机读数:标记已落地但 bf 仍是 blur(11px),规则归属里能看到那条 !important)。
18453
+ 所以这里把同一个属性写**三**遍 ⇒ (0,3,2),与顺序无关地稳压。 */
18454
+
18179
18455
  [role="menu"] [class*="card"],
18180
18456
  [role="tooltip"] [class*="card"] {
18181
18457
  backdrop-filter: none !important;
@@ -18962,6 +19238,38 @@ html body[data-ds-dark-theme] header[class*="_header_"][data-mpw-hdr-translucent
18962
19238
  .wSkVaW_header:has(> .mpw-hdrFrost) > :not(.mpw-hdrFrost) { position: relative; z-index: 1; }
18963
19239
  .mpw-hdrFrost:first-child ~ * { position: relative; z-index: 1; }
18964
19240
  .wSkVaW_header > .mpw-hdrFrost { z-index: 0 !important; }
19241
+ /* ①(2026-10-09 DSH 0.2.0 真机) 面包屑文字"加载不出来"的真根因:0.2.0 在标题栏里插了一层
19242
+ div[data-slot="conversation.session.header"][style="display:contents"] —— **display:contents 不生成盒子**,
19243
+ 上面那条"直接子节点抬升"落在它身上等于没落(position/z-index 对无盒元素无效),
19244
+ 于是它里面的 .wSkVaW_titleRow(标题/面包屑文字)仍是 static,被我们的 .mpw-hdrFrost
19245
+ (absolute + z-index:0)**整片盖在下面**(模糊层盖住文字 ⇒ 看起来"文字加载不出来")。
19246
+ 这里把真正的盒子层补抬一次:只针对标题行与面包屑(都是普通 div/span,position:relative 不改布局,
19247
+ 不加偏移就不会移位)。 */
19248
+ .wSkVaW_header:has(> .mpw-hdrFrost) .wSkVaW_titleRow,
19249
+ .wSkVaW_header:has(> .mpw-hdrFrost) .wSkVaW_crumbs,
19250
+ .wSkVaW_header:has(> .mpw-hdrFrost) .wSkVaW_crumb { position: relative; z-index: 1; }
19251
+ /* ①(2026-10-09 DSH 0.2.0) 空标题栏兜底(body[data-mpw-hdr-blank] 由 JS 在 headerBlank /
19252
+ headerSessionless 两个态下广播):底色、底色图、磨砂、阴影一律不下发 ——
19253
+ 否则刷新后新会话界面顶部会留一条"很细的圆角条"(那就是我们的磨砂层 + 半透明底)。 */
19254
+ html body[data-mpw-hdr-blank][data-mpw-hdr-blank] .wSkVaW_header,
19255
+ html body[data-mpw-hdr-blank][data-mpw-hdr-blank] .wSkVaW_header::before,
19256
+ html body[data-mpw-hdr-blank][data-mpw-hdr-blank] .wSkVaW_header::after,
19257
+ html body[data-mpw-hdr-blank][data-mpw-hdr-blank] .wSkVaW_header > .mpw-hdrFrost {
19258
+ background-color: transparent !important;
19259
+ background-image: none !important;
19260
+ -webkit-backdrop-filter: none !important;
19261
+ backdrop-filter: none !important;
19262
+ box-shadow: none !important;
19263
+ border-radius: 0 !important;
19264
+ padding: 0 !important;
19265
+ margin: 0 !important;
19266
+ min-height: 0 !important;
19267
+ border-width: 0 !important;
19268
+ }
19269
+ /* 特异性说明(2026-10-09 真机第二轮):既有规则里有 (0,3,3) 的暗色底(header[class*="_header_"] 那条)
19270
+ 与 (0,3,1) 的 aqua 底、以及 float 档的 margin/padding/border/radius —— 单写 [data-mpw-hdr-blank] 只有
19271
+ (0,2,x),压不过它们(空栏仍被画出来)。这里把同一个属性写两遍把特异性翻到 (0,4,x),既压得住,
19272
+ 又不用去改那几条既有规则的文本(css-matrix 按源码文本判 ::before 磨砂,改文本会破断言)。 */
18965
19273
  /* ①(2026-09-16 bug①) 右侧「轮次导航条」(DSH 0.1.5 的 TurnNavigator rail / .eGxaPq_*)对比兜底。
18966
19274
  判据:宿主 .eGxaPq_mark::before 用 --dsw-alias-border-l4(#00000029 / #fff3 = 16%/20% alpha),
18967
19275
  假定条背后是**不透明表面**;壁纸模式把表面透明化后,条与壁纸混色 ⇒ 用户观感"条变透明"。
@@ -19153,7 +19461,6 @@ ${(section.bsFloat !== void 0 ? !!section.bsFloat : false) ? `
19153
19461
  (不透明),在已被透明的 .pane 内又铺一层与圆角壳(bg-layer-1)不同的 token → 双层、
19154
19462
  两层透明度(用户实测"圆角里套直角、两层透明度不同"的根源,子代理定位)。置透明后
19155
19463
  让圆角壳单层显示。xterm 内部文字/光标类用 foreground 色,不受背景影响。 */
19156
- [data-dsh-better-sidebar] [class*="_terminalWrap"],
19157
19464
  [data-dsh-better-sidebar] [class*="_terminal"] {
19158
19465
  background-color: transparent !important;
19159
19466
  }
@@ -19182,7 +19489,6 @@ ${(section.bsAlpha !== void 0 ? !!section.bsAlpha : false) ? `
19182
19489
  [data-dsh-better-sidebar] [data-dsh-pane],
19183
19490
  [data-dsh-better-sidebar] [class*="_tabBar"],
19184
19491
  [data-dsh-better-sidebar] [class*="_editorHeader"],
19185
- [data-dsh-better-sidebar] [class*="_terminalWrap"],
19186
19492
  [data-dsh-better-sidebar] [class*="_browserBar"] {
19187
19493
  background-color: color-mix(in srgb, var(--dsw-alias-bg-base) 52%, transparent) !important;
19188
19494
  }
@@ -19211,8 +19517,7 @@ body[data-ds-dark-theme] [data-dsh-better-sidebar] [class*="_pane"]:not([class*=
19211
19517
  body[data-ds-dark-theme] [data-dsh-better-sidebar] [data-dsh-pane],
19212
19518
  body[data-ds-dark-theme] [data-dsh-better-sidebar] [class*="_tabBar"],
19213
19519
  body[data-ds-dark-theme] [data-dsh-better-sidebar] [class*="_editorHeader"],
19214
- body[data-ds-dark-theme] [data-dsh-better-sidebar] [class*="_browserBar"],
19215
- body[data-ds-dark-theme] [data-dsh-better-sidebar] [class*="_terminalWrap"] {
19520
+ body[data-ds-dark-theme] [data-dsh-better-sidebar] [class*="_browserBar"] {
19216
19521
  background-color: color-mix(in srgb, var(--dsw-static-neutral-bluish-950) 28%, transparent) !important;
19217
19522
  }
19218
19523
  body[data-ds-dark-theme] [data-dsh-better-sidebar] [class*="_addBar"],
@@ -19254,7 +19559,6 @@ ${(section.bsAqua !== void 0 ? !!section.bsAqua : false) ? `
19254
19559
  [data-dsh-better-sidebar] [data-dsh-pane],
19255
19560
  [data-dsh-better-sidebar] [class*="_tabBar"],
19256
19561
  [data-dsh-better-sidebar] [class*="_editorHeader"],
19257
- [data-dsh-better-sidebar] [class*="_terminalWrap"],
19258
19562
  [data-dsh-better-sidebar] [class*="_browserBar"] {
19259
19563
  background-color: var(--dsw-alias-bg-base) !important;
19260
19564
  }
@@ -21563,11 +21867,13 @@ body[data-mpw-modal] [role="dialog"] {
21563
21867
 
21564
21868
  // ①(2026-09-12) 原 `const h` 声明在 1600 行之后(使用点之前)→ TDZ/未定义;
21565
21869
  // 已上移到函数开头(见 MpkgSectionImpl 首行),此处删除避免重复声明。
21566
- // ①(修正) 弹窗遮罩 portal:渲染到 body,避开设置面板包含块/backdrop root 困局
21870
+ // ①(修正) 弹窗遮罩 portal:渲染到**层宿主**(不是 body),避开设置面板包含块/backdrop root 困局;
21871
+ // ①(2026-10-07 上游同族轮) 改挂层宿主:`.mpw_mask` 是 `position:fixed;inset:0` 的整屏浮层,
21872
+ // 直接挂 body 会把 macOS 的窗口可拖区整片挖掉(见 mpwLayerHost)。
21567
21873
  const MaskPortal = (props) => {
21568
21874
  const node = h("div", { className: "mpw_mask", onClick: props.onClick }, props.children);
21569
- if (ReactDOM && ReactDOM.createPortal && typeof document !== "undefined" && document.body) {
21570
- try { return ReactDOM.createPortal(node, document.body); } catch { /* 回退原位渲染 */ }
21875
+ if (ReactDOM && ReactDOM.createPortal && typeof document !== "undefined" && (document.body || document.documentElement)) {
21876
+ try { return ReactDOM.createPortal(node, mpwLayerHost()); } catch { /* 回退原位渲染 */ }
21571
21877
  }
21572
21878
  return node;
21573
21879
  };
@@ -24912,10 +25218,44 @@ body[data-mpw-modal] [role="dialog"] {
24912
25218
  `data-mpw-rs-surface`,CSS 只认这个标记(与 `data-mpw-holds-layer` 同一手法)。
24913
25219
  幂等、无布局读取(只用 matches/parentElement),可随突变跑。 */
24914
25220
  const MPW_RS_FAMILY = '[data-sidebar-right-panel], [data-dockkit-pane], [data-dockkit-strip], [data-dockkit-surface], [data-dockkit-float], [data-sidebar-right-float-host], [class*="P3OORG_panel"]';
25221
+ /** 右栏**收纳态/停靠件**判据(DSH 0.2.0 真机结构):
25222
+ * 宿主在 `pI_x6G_frame` 上打 `data-rightbar-collapsed="true"`(展开时该属性被移除),
25223
+ * 并把收起的停靠面板留在 DOM 里且 `aria-hidden="true"` —— **它的 rect 仍在视口内**
25224
+ * (实测 `[780,10,650,880]`),只是内核裁剪行为不同:Android Via/Chromium 上会真的画出来,
25225
+ * Firefox 上命中测试打不到。用户看到的就是"右半屏被糊了一整块"。
25226
+ * ⇒ 判定成立时**一个 rs-surface 标记都不许留**(这是"停靠件",不是用户要看的表面)。
25227
+ * 两条判据都认:属性名优先(跨版本更稳),`aria-hidden` 兜底。 */
25228
+ function mpwRightbarParked() {
25229
+ try {
25230
+ const f = document.querySelector('[data-rightbar-collapsed]');
25231
+ if (f && String(f.getAttribute('data-rightbar-collapsed')) !== 'false') return true;
25232
+ const p = document.querySelector('[data-sidebar-right-panel]');
25233
+ if (p && p.getAttribute('aria-hidden') === 'true') return true;
25234
+ } catch (e) { /* 读不到就按"没收起"处理(宁可不摘,也不误摘展开态) */ }
25235
+ return false;
25236
+ }
24915
25237
  function mpwTagSurfaceOuter() {
24916
25238
  try {
24917
- const all = Array.from(document.querySelectorAll(MPW_RS_FAMILY));
24918
- if (!all.length) return 0;
25239
+ /* ①(2026-10-09 第二轮真机) 收纳态还要**一个像素都不画**:只摘标记不够 ——
25240
+ 宿主的停靠件子树自己会画底(`_tabHost_*` / `_emptyTabHost_*` 带 --dsw-alias-bg-base),
25241
+ 于是用户仍看到"和展开态一样大的框、有边缘、框内壁纸色和框外不一样"。
25242
+ ⇒ 把状态广播到 body(`data-mpw-rightbar-parked`),由 CSS 把那一整棵子树的底/边/影/玻璃全清。 */
25243
+ const parkedNow = mpwRightbarParked();
25244
+ try {
25245
+ if (parkedNow) document.body.setAttribute('data-mpw-rightbar-parked', '');
25246
+ else document.body.removeAttribute('data-mpw-rightbar-parked');
25247
+ } catch (e) { /* 忽略 */ }
25248
+ const all = Array.from(document.querySelectorAll(MPW_RS_FAMILY))
25249
+ .filter((el) => { try { return el.getAttribute('aria-hidden') !== 'true' } catch (e) { return true } });
25250
+ if (!all.length || parkedNow) {
25251
+ /* 收纳态:把已有标记摘干净(否则玻璃会留在停靠件上 = 右半屏被糊) */
25252
+ let removed = 0;
25253
+ for (const el of Array.from(document.querySelectorAll('[data-mpw-rs-surface]'))) {
25254
+ try { el.removeAttribute('data-mpw-rs-surface'); removed++ } catch (e) { /* 忽略 */ }
25255
+ }
25256
+ if (removed) { try { mpwTrace('rs-surface:parked', { removed }) } catch (e) {} }
25257
+ return removed;
25258
+ }
24919
25259
  const set = new Set(all);
24920
25260
  const isDock = (el) => { try { return el.matches('[data-dockkit-pane], [data-dockkit-strip], [data-dockkit-surface], [data-dockkit-float]') } catch (e) { return false } };
24921
25261
  const hasMatchedAncestor = (el) => {
@@ -24968,6 +25308,14 @@ body[data-mpw-modal] [role="dialog"] {
24968
25308
  want = true; break;
24969
25309
  } catch (e) { /* 单个读不到就跳过 */ }
24970
25310
  }
25311
+ /* ①(上游同族缺陷) 整屏 dockkit 面板:没有 dialog 角色 ⇒ 上面那条一个都不命中,
25312
+ 而左栏的 z-index:1 会把面板的 z-index:12 一起困在左栏的层叠上下文里(真机症状:
25313
+ 面板与对话重叠)。抬升左栏 ⇒ 面板随子树回到宿主为它设计的层。半屏不抬。 */
25314
+ if (!want) {
25315
+ for (const p of Array.from(side.querySelectorAll(MPW_WIDE_PANEL_SEL))) {
25316
+ if (mpwIsWidePanel(p)) { want = true; break }
25317
+ }
25318
+ }
24971
25319
  const has = side.hasAttribute("data-mpw-side-lift");
24972
25320
  if (want && !has) { try { side.setAttribute("data-mpw-side-lift", ""); changed++ } catch (e) {} }
24973
25321
  else if (!want && has) { try { side.removeAttribute("data-mpw-side-lift"); changed++ } catch (e) {} }
@@ -24987,6 +25335,47 @@ body[data-mpw-modal] [role="dialog"] {
24987
25335
  } catch (e) {}
24988
25336
  return false;
24989
25337
  }
25338
+ /** ③(2026-10-10 真机定案) **快路打标**:material 外壳里的内层 [role="menu"](真机是
25339
+ * `._7KE1Ra_groups`,radius=0 的直角滚动容器)会被两条弹层规则糊到,从而在方形区域内
25340
+ * 把身后的白底再糊一遍 = 用户看到的"外面圆角正常、里面一块直角白矩形"。
25341
+ * 压制规则(html body [data-mpw-menu-inner] { backdrop-filter:none !important })在 CSS 里,
25342
+ * 但打标若只放在**节流的重活同步**里,开菜单那一帧标记还没落地 ⇒ 用户仍会看到白块
25343
+ * (实测:开菜单 ~0.5s 后读数仍是 blur(11px))。所以这里在**突变快路**上(微任务、渲染前)
25344
+ * 立刻打标;重活同步里那份保留,作为兜底与"失效标记回收"。 */
25345
+ const MPW_MENU_SHELL_SEL = '[data-menu-material], [data-trigger-menu], [class*="ZKlsPq_menu"]';
25346
+ function mpwMarkMenuInner(root) {
25347
+ try {
25348
+ if (!root || root.nodeType !== 1) return 0;
25349
+ const shells = [];
25350
+ try { if (root.matches && root.matches(MPW_MENU_SHELL_SEL)) shells.push(root) } catch (e) {}
25351
+ try {
25352
+ if (root.querySelectorAll) {
25353
+ const found = root.querySelectorAll(MPW_MENU_SHELL_SEL);
25354
+ for (let i = 0; i < found.length && i < 8; i++) shells.push(found[i]);
25355
+ }
25356
+ } catch (e) {}
25357
+ let n = 0;
25358
+ for (const sh of shells) {
25359
+ let inner = [];
25360
+ try { inner = sh.querySelectorAll('[role="menu"], [role="listbox"], [class*="_groups"]') } catch (e) { inner = [] }
25361
+ for (let k = 0; k < inner.length && k < 12; k++) {
25362
+ try {
25363
+ const el2 = inner[k];
25364
+ if (!el2.hasAttribute("data-mpw-menu-inner")) { el2.setAttribute("data-mpw-menu-inner", ""); n++ }
25365
+ /* ④(2026-10-10 规则归属定案) **必须用内联 !important**:压我们的那条弹层规则是
25366
+ html body :is(popTipSel…, popSurfaceSel) { backdrop-filter: var(--mpw-pop-blur) !important },
25367
+ 而 popSurfaceSel 的首项带着 POP_NOT 里 **23 个 :not()** ⇒ :is() 取最高 ⇒ 该规则
25368
+ 特异性约 (0,25,2),靠属性重复(0,3,2)根本压不过(真机读数:三遍重复后仍 blur(11px))。
25369
+ 作者**内联** important 在层叠里高于任何作者样式表 important ⇒ 无特异性问题。
25370
+ 清理:标记被摘掉时(下面的回收分支)同步 removeProperty,宿主逐字还原。 */
25371
+ try { el2.style.setProperty("backdrop-filter", "none", "important") } catch (e) {}
25372
+ try { el2.style.setProperty("-webkit-backdrop-filter", "none", "important") } catch (e) {}
25373
+ } catch (e) {}
25374
+ }
25375
+ }
25376
+ return n;
25377
+ } catch (e) { return 0 }
25378
+ }
24990
25379
  function mpwTagPopoverFast(records) {
24991
25380
  try {
24992
25381
  let n = 0;
@@ -25000,6 +25389,8 @@ body[data-mpw-modal] [role="dialog"] {
25000
25389
  if (rec.type === "attributes") {
25001
25390
  const t = rec.target;
25002
25391
  if (!t || t.nodeType !== 1) continue;
25392
+ /* ③(2026-10-10) 宿主菜单显形/换页签走的是属性突变 ⇒ 这里顺手把内层容器标记上 */
25393
+ try { mpwMarkMenuInner(t) } catch (e) {}
25003
25394
  let hit = false;
25004
25395
  try { hit = t.matches && t.matches(MPW_POP_TAG_SEL) } catch (e) {}
25005
25396
  if (hit) { if (mpwTagOnePopover(t)) n++; }
@@ -25007,6 +25398,8 @@ body[data-mpw-modal] [role="dialog"] {
25007
25398
  }
25008
25399
  for (const node of (rec.addedNodes || [])) {
25009
25400
  if (!node || node.nodeType !== 1) continue;
25401
+ /* ③(2026-10-10) 菜单是"先插入 DOM 再显形",插入那一刻就打标 ⇒ 渲染前已生效 */
25402
+ try { mpwMarkMenuInner(node) } catch (e) {}
25010
25403
  const cands = [];
25011
25404
  try {
25012
25405
  if (node.matches && node.matches(MPW_POP_TAG_SEL)) cands.push(node);
@@ -25023,6 +25416,50 @@ body[data-mpw-modal] [role="dialog"] {
25023
25416
  } catch (e) { return 0 }
25024
25417
  }
25025
25418
  /** 单个元素:该不该有我们的兜底底;返回是否发生了变化。 */
25419
+ /** 宿主新菜单(DSH 0.2.0)**自带内层 material 底**:表面 `._surface_ri079_1` 自身 α=0
25420
+ * (我们只看自身底色 ⇒ 判成 empty,垫上我们那层 58%),而真正的底画在直接子层
25421
+ * `._material_ri079_21`(`--dsw-menu-surface-fill` 58% + `--dsw-menu-backdrop-filter` blur(40px))。
25422
+ * 两层叠起来 ≈82% 近白 ⇒ 用户看到"白、没有模糊"。
25423
+ * 判据:宿主显式声明 material 家族(属性 `data-menu-material` 优先),或直接子层就是那个
25424
+ * material 底且自己画了底/模糊 ⇒ need=false(**撤我们的底、留我们的 blur**)。 */
25425
+ function mpwPopHostMaterial(el) {
25426
+ try {
25427
+ if (!el || el.nodeType !== 1) return false;
25428
+ if (el.hasAttribute && el.hasAttribute("data-menu-material")) return true;
25429
+ const kids = el.children || [];
25430
+ for (let i = 0; i < kids.length; i++) {
25431
+ const c = kids[i];
25432
+ let cls = "";
25433
+ try { cls = String((c.className && c.className.baseVal !== void 0 ? c.className.baseVal : c.className) || "") } catch (e) { cls = "" }
25434
+ if (cls.indexOf("_material_") < 0) continue;
25435
+ try {
25436
+ const cs = getComputedStyle(c);
25437
+ const a = mpwColorAlpha(cs.backgroundColor);
25438
+ const bf = cs.backdropFilter;
25439
+ if (a >= 0.05 || (bf && bf !== "none")) return true;
25440
+ } catch (e) { /* 单个读不到就跳过 */ }
25441
+ }
25442
+ } catch (e) { /* 忽略 */ }
25443
+ return false;
25444
+ }
25445
+ /** ①(2026-10-10 第四批真机) 外层已经是"玻璃外壳"时,**内层**不许再吃我们的兜底底:
25446
+ * 真机症状——模型选择器弹框里"提供商/模型"那一小块是白的(用户:"弹框里面套了一个框,
25447
+ * 里面这个框没有模糊"),而 `._7KE1Ra_cell/_option/_groups` 这些内层类在宿主 CSS 里
25448
+ * 全是 background:0 0;白底是我们自己给内层 [role="listbox"] 刷的兜底层
25449
+ * (--mpw-pop-surface 58%)叠在宿主 58% 的 material 层之上 ⇒ ≈82% 近白。
25450
+ * 同一片区域只许留一层底(与 `mpwTagSurfaceOuter` 同一条纪律)。 */
25451
+ function mpwPopInsideMenuShell(el) {
25452
+ try {
25453
+ for (let p = el && el.parentElement, i = 0; p && i < 8; p = p.parentElement, i++) {
25454
+ if (p.hasAttribute && (p.hasAttribute("data-menu-material")
25455
+ || p.hasAttribute("data-trigger-menu")
25456
+ || p.hasAttribute("data-mpw-pop-bg")
25457
+ || p.hasAttribute("data-mpw-rs-surface"))) return true;
25458
+ try { if (p.matches && p.matches('[class*="_surface_ri079_"]')) return true } catch (e) { /* 忽略 */ }
25459
+ }
25460
+ } catch (e) { /* 忽略 */ }
25461
+ return false;
25462
+ }
25026
25463
  function mpwTagOnePopover(el) {
25027
25464
  let need = false;
25028
25465
  let why = "";
@@ -25042,8 +25479,13 @@ body[data-mpw-modal] [role="dialog"] {
25042
25479
  宿主底回来 ⇒ 再打标 …… 逐帧自激(真机:"底色时有时无")。
25043
25480
  属性值就是当初的理由:empty = 宿主没画底;painted = 宿主画了不透明底。 */
25044
25481
  const reason = el.getAttribute("data-mpw-pop-bg");
25045
- if (reason === "empty") { need = !mpwPopHasPaintedAncestor(el); why = "empty" }
25482
+ /* ①(2026-10-09 DSH 0.2.0) 宿主自带 material 底的菜单(data-menu-material)⇒ 撤我们的底。
25483
+ ⚠ sticky 复核**只许走结构判据**(属性在不在),绝不重读底色/子层 computed —— 那正是
25484
+ A6"反自激"钉的事(标记一生效底色就变我们画的,重读会打-撤-打地闪)。 */
25485
+ if (el.hasAttribute && el.hasAttribute("data-menu-material")) { need = false; why = "host-material" }
25486
+ else if (reason === "empty") { need = !mpwPopHasPaintedAncestor(el); why = "empty" }
25046
25487
  else if (reason === "painted") { need = mpwPopIsFloating(el); why = "painted" }
25488
+ else if (mpwPopHostMaterial(el) || mpwPopInsideMenuShell(el)) { need = false; why = "host-material" }
25047
25489
  else {
25048
25490
  const a = mpwColorAlpha(cs.backgroundColor);
25049
25491
  /* ②(2026-10-05) 两类分开判:
@@ -25178,6 +25620,66 @@ body[data-mpw-modal] [role="dialog"] {
25178
25620
  };
25179
25621
  } catch (e) {}
25180
25622
 
25623
+ /** ②(2026-10-10 第五批真机) **专用去截断趟**:宿主 material 菜单(data-menu-material /
25624
+ * data-trigger-menu,我们按 H 判据故意不给底)与子智能体会话树(ZKlsPq_menu)**自己**也有
25625
+ * blur(我们补的或宿主自带的),但它们同样会被祖先的 backdrop root 截断 —— 有壁纸时输入框
25626
+ * 卡片 uV2eYG_card(blur 14px)/侧栏/面板都有我们的模糊,弹层的 blur 只能采样祖先内部
25627
+ * ⇒ 用户看到的正是"没壁纸时模糊正常,一上壁纸只剩白底、没有模糊"。
25628
+ *
25629
+ * 为什么**不**复用上面的 A/B/C 分类:那套里的 C 路("祖先不是包装容器")刻意"一个都不摘",
25630
+ * 是为"用户正在看的面板"设计的;而这类弹层的祖先恰恰就是包装容器,必须摘。
25631
+ * 这里只做一件事:找**最近的**一个带 blur 的祖先(或已被我们标记过的),交给同一趟
25632
+ * mpwPopUntruncApply ⇒ 与主路共用同一份幂等状态,不会互相撤标记。
25633
+ * 只往 need 里**加**、从不删 ⇒ 主路(尤其 popover-glass 的 B1 输入框卡片 case)不受影响。 */
25634
+ function mpwPopUntruncExtra(need) {
25635
+ let added = 0;
25636
+ /* ③(2026-10-10 真机层次读数定案) 给"宿主 material 外壳里的内层容器"打标记:
25637
+ 真机读数——shell radius=16px / material radius=16px(58% 白) / **groups radius=0px**
25638
+ 且 groups 拿到了 blur(11px)(我们的规则 + 老 G 块两条都按 [role="menu"] 命中它)
25639
+ ⇒ 它把自己身后的白底在**方形区域内**又糊一遍 = 用户看到的"外面圆角正常、里面直角白矩形"。
25640
+ 这里只做标记,压制交给 CSS 一条 !important 中和规则(不改任何既有规则文本,
25641
+ 避免扰动 token-namespace 的四表面基线)。 */
25642
+ try {
25643
+ const want = new Set();
25644
+ const shells = document.querySelectorAll('[data-menu-material], [data-trigger-menu], [class*="ZKlsPq_menu"]');
25645
+ for (let i = 0; i < shells.length; i++) {
25646
+ const inner = shells[i].querySelectorAll('[role="menu"], [role="listbox"], [class*="_groups"]');
25647
+ for (let k = 0; k < inner.length; k++) want.add(inner[k]);
25648
+ }
25649
+ const stale = document.querySelectorAll("[data-mpw-menu-inner]");
25650
+ for (let i = 0; i < stale.length; i++) {
25651
+ if (want.has(stale[i])) continue;
25652
+ try { stale[i].removeAttribute("data-mpw-menu-inner") } catch (e) {}
25653
+ /* 同步清掉内联(宿主逐字还原,不给它留残留样式) */
25654
+ try { stale[i].style.removeProperty("backdrop-filter") } catch (e) {}
25655
+ try { stale[i].style.removeProperty("-webkit-backdrop-filter") } catch (e) {}
25656
+ }
25657
+ for (const el of want) { if (!el.hasAttribute("data-mpw-menu-inner")) { try { el.setAttribute("data-mpw-menu-inner", "") } catch (e) {} } }
25658
+ } catch (e) { /* 忽略 */ }
25659
+ try {
25660
+ const els = document.querySelectorAll('[data-menu-material], [data-trigger-menu], [class*="ZKlsPq_menu"]');
25661
+ for (let i = 0; i < els.length; i++) {
25662
+ const el = els[i];
25663
+ try {
25664
+ const cs = getComputedStyle(el);
25665
+ if (!cs || cs.display === "none" || cs.visibility === "hidden") continue;
25666
+ /* 自己没有模糊 ⇒ 没有"被截断"这回事(别去动祖先) */
25667
+ if (!cs.backdropFilter || String(cs.backdropFilter).indexOf("blur") < 0) continue;
25668
+ for (let p = el.parentElement, k = 0; p && p !== document.body && p !== document.documentElement && k < 12; p = p.parentElement, k++) {
25669
+ let acs = null;
25670
+ try { acs = getComputedStyle(p) } catch (e) { break }
25671
+ const declared = !!(p.hasAttribute && p.hasAttribute("data-mpw-pop-untrunc"));
25672
+ const ablur = declared || (acs && acs.backdropFilter && acs.backdropFilter !== "none" && String(acs.backdropFilter).indexOf("blur") >= 0);
25673
+ if (!ablur) continue;
25674
+ if (mpwPopCanCtx(p, acs)) { need.add(p); added++ }
25675
+ break; // 只处理最近的一个(与主路"第一个截断祖先"语义一致)
25676
+ }
25677
+ } catch (e) { /* 单个元素失败不影响其它 */ }
25678
+ }
25679
+ } catch (e) { /* 忽略 */ }
25680
+ return added;
25681
+ }
25682
+
25181
25683
  function mpwPopClassifySync() {
25182
25684
  try {
25183
25685
  const surfaces = document.querySelectorAll("[data-mpw-pop-bg]");
@@ -25228,6 +25730,9 @@ body[data-mpw-modal] [role="dialog"] {
25228
25730
  };
25229
25731
  sync("data-mpw-pop-glass", glasses);
25230
25732
  sync("data-mpw-pop-trunc", truncated);
25733
+ /* ②(第五批) 宿主 material 菜单 / 触发候选菜单 / 子智能体会话树:只**补**祖先进 need,
25734
+ 不参与上面的 glasses/truncated 打标(那会让它们吃到我们的底,回到"叠两层白"的老问题)。 */
25735
+ try { mpwPopUntruncExtra(neutralize) } catch (e) {}
25231
25736
  mpwPopUntruncApply(neutralize);
25232
25737
  return glasses.size + truncated.size;
25233
25738
  } catch (e) { return 0 }
package/lib/index.js CHANGED
@@ -680,10 +680,36 @@ function isWsl(release, env, exists) {
680
680
  } catch { return false; }
681
681
  }
682
682
 
683
+ /** WE「已安装」标记(相对安装目录的路径,**两级 × 两个位宽**逐项探测)。
684
+ *
685
+ * 为什么不是单个顶层 `wallpaper32.exe`:Wallpaper Engine 2.x 起把可执行文件从安装目录顶层移进了
686
+ * `distribution/` 子目录(顶层只剩 `ChromaAppInfo.xml` / `installer.exe` / `launcher.exe`),只探顶层
687
+ * ⇒ 明明装了也判成"未安装" ⇒ `/steam-inventory` 永远回 `installDir:null` + 空清单(`projects/*` 与
688
+ * 工坊库一起扫不到)。上游同缺陷("安装目录探测失败:`wallpaper32.exe` 位于 `distribution/` 子目录")
689
+ * 的建议修法就是逐级回退,这里取同一口径。
690
+ *
691
+ * 为什么不用 `version.json` 当标记:那是"某个像 WE 的目录"的弱证据,而本判据决定是否去读该目录下的
692
+ * `project.json` 与工坊库 —— 读错目录等于把无关目录当壁纸库。宁可用可执行文件这种强标记
693
+ * (上游报告者给出的应急绕行也是"在顶层给 `distribution/wallpaper32.exe` 建软链")。
694
+ *
695
+ * 纯函数(`exists` 可注入):判断是契约,要能在**没装 WE** 的机器上逐项断言。 */
696
+ export const WE_INSTALL_MARKERS = [
697
+ 'distribution/wallpaper32.exe',
698
+ 'distribution/wallpaper64.exe',
699
+ 'wallpaper32.exe',
700
+ 'wallpaper64.exe',
701
+ ];
702
+ export function weInstallMarker(dir, exists = existsSync) {
703
+ for (const rel of WE_INSTALL_MARKERS) {
704
+ try { if (exists(join(dir, ...rel.split('/')))) return rel; } catch { /* 注入的 exists 抛 ⇒ 换下一个标记,绝不外抛 */ }
705
+ }
706
+ return null;
707
+ }
708
+
683
709
  /** ③ 自动发现:定位壁纸引擎安装目录(Steam libraryfolders.vdf + 常见路径探测)。
684
710
  * ①(修正) 补非 Windows 平台探测:macOS/Linux/WSL 也各自有 Steam 安装路径。
685
711
  * 候选清单本身在纯函数 steamProbeDirs() 里(平台分支都在那里,可注入、可在非 WSL 机器上断言);
686
- * 本函数只负责"注册表优先 → 逐个候选读 libraryfolders.vdf → 只认 wallpaper32.exe 存在的那一个"。
712
+ * 本函数只负责"注册表优先 → 逐个候选读 libraryfolders.vdf → 只认安装标记(见 WE_INSTALL_MARKERS)存在的那一个"。
687
713
  * 路径是纯字符串,平台用不到时 existsSync 自然为 false,无副作用。 */
688
714
  function locateWallpaperEngine() {
689
715
  const probes = [];
@@ -709,10 +735,10 @@ function locateWallpaperEngine() {
709
735
  const roots = [...new Set([...probes, ...libraries])];
710
736
  for (const root of roots) {
711
737
  const dir = join(root, 'steamapps', 'common', 'wallpaper_engine');
712
- if (existsSync(join(dir, 'wallpaper32.exe'))) return dir;
738
+ if (weInstallMarker(dir)) return dir;
713
739
  }
714
740
  const alt = 'C:\\Program Files (x86)\\Wallpaper Engine';
715
- return existsSync(join(alt, 'wallpaper32.exe')) ? alt : null;
741
+ return weInstallMarker(alt) ? alt : null;
716
742
  }
717
743
 
718
744
  // ①(修正) 硬依赖 webServer:cordis 等待 HTTP 服务挂载后再 apply(dsh-wallpaper-engine 同款)。
@@ -9,7 +9,7 @@
9
9
  // 本文件做七件事(都在**宿主侧或帧内 shim 里**,渲染器零改动):
10
10
  // ① `detectWebWallpaperKind`:按**内容**判定 web/scene/video/unknown(声明只是线索,不是依据)
11
11
  // ② `WEB_SHIM_SOURCE`:WE API shim(注入到 iframe 内,**在作者脚本之前**执行)
12
- // ③ `rewriteWebEntryHtml`:把 shim(可选种子脚本)插到入口 HTML 的 `<head>` 最前
12
+ // ③ `rewriteWebEntryHtml`:把帧级夺焦围栏(`mpwFocusGuardSource`)+ shim(可选种子脚本)插到入口 HTML 的 `<head>` 最前
13
13
  // ④ `webAssetCorsHeaders`:跨源策略——只给「沙箱帧的不透明源(Origin: null)」放行读取
14
14
  // ⑤ 交互注入(第 11 条):shim 侧把父页推来的指针/滚轮/键盘按 WE 语义还原成帧内合成 DOM 事件
15
15
  // (父页侧的舞台、事件整形与开关状态机在 `lib/web-interaction.js`)
@@ -54,6 +54,81 @@ export const SHIM_MSG = 'mpw:web';
54
54
  /** shim 版本(父页握手校验用) */
55
55
  export const SHIM_VERSION = 1;
56
56
 
57
+ /** 帧级夺焦围栏 `<script>` 的标记属性(见 mpwFocusGuardSource) */
58
+ export const FOCUS_GUARD_ATTR = 'data-mpw-focus-guard';
59
+
60
+ /** 网页壁纸的**帧级夺焦围栏** —— 一段以文本形式注入壁纸文档的经典脚本。
61
+ *
62
+ * 现象(上游同族缺陷 + 机制可静态闭合):播某些网页类壁纸时,DSH 自己的输入框 / 下拉框 /
63
+ * 账号菜单**每被点一次就失去焦点**(窗口仍在焦点、`relatedTarget` 为 null ⇒ 焦点被搬进了
64
+ * **另一个文档**)。与点击位置无关,只有"必须持有键盘焦点才正常"的那类控件看得出来。
65
+ *
66
+ * 机制(三步,本插件的架构逐条对得上):
67
+ * ① 壁纸层在宿主页里是**背景**(`pointer-events:none`),鼠标事件由 DSH UI 消费,我们再把
68
+ * pointer 事件经 `window.__wp.pushPointer` / postMessage 注入壁纸帧(`lib/web-interaction.js`);
69
+ * ② 注入落点 = 帧内 shim 用 `elementFromPoint` 命中元素后 `dispatchEvent` 的**合成事件**
70
+ * (`isTrusted === false`,与用户真点壁纸可区分);
71
+ * ③ 作者为了在 WE 桌面模式下"让键盘有处可去",常在**捕获相 mousedown** 里调 `window.focus()`
72
+ * ⇒ 宿主里**任何位置**点一下,焦点就被搬进壁纸帧,DSH 的 composer / 下拉框当场丢焦点。
73
+ *
74
+ * 为什么可以整条废掉:本插件把键盘送进帧靠的是**父页捕获相监听 + shim 合成 KeyboardEvent**
75
+ * (`op:"key"`,仅交互 full 档),帧级焦点**不是**必要条件;反过来,帧一旦抢到焦点,父页的
76
+ * keydown 监听就不再触发 ⇒ 交互注入自己也会静默失效。围栏只拦**帧级** `window.focus()`;
77
+ * 元素级 `HTMLElement.prototype.focus()` 原样放行 —— 壁纸自己的编辑框、模态输入框、软键盘
78
+ * 照常工作(那才是它真正需要的焦点)。
79
+ *
80
+ * 实现纪律:
81
+ * · 注入体是**源码文本**(`mpwFocusGuardSource()` 用 `Function.prototype.toString` 取下面那个
82
+ * 函数的原文 ⇒ 函数体必须**自足**:只碰 `window` 与标准内建,不引用本模块作用域的名字、
83
+ * 不用 import/export(它被当经典脚本执行);
84
+ * · 只随 `?mpwshim=1` 那条路注入(= auto/sandbox 档,帧是不透明源、键盘本来就走我们的合成通道)。
85
+ * **compat 档不注入**:那一档用户显式要"等价裸 iframe",作者页可能要真的拿键盘,围栏会让它静默失灵;
86
+ * · 绝不抛:壁纸文档里的任何异常都会毁掉作者脚本,而围栏的意义恰恰是"别让宿主被第三方代码拖下水"
87
+ * ⇒ 每步各自 try/catch,失败只体现为 `__mpwFocusGuard.installed === false`;
88
+ * · 留计数 + 一条回执:`window.__mpwFocusGuard = { version, calls, blocked, allowed, installed, allow }`
89
+ * (帧内控制台一眼能看到拦了多少次);**第一次真拦下**时给父页回一条
90
+ * `{mpw:'mpw:web', op:'focus-guard', blocked, calls}` ⇒ 宿主诊断里能直接看到"这张壁纸在夺焦"。
91
+ * `allow = true` 是**逃生门**(给对比测试用,不是用户开关):置 true 后原函数照旧调用、`allowed` 累加。
92
+ *
93
+ * 已知残留(不在围栏射程内):跨源 WindowProxy 上的 `top.focus()` / `parent.focus()` 无法从壁纸
94
+ * 文档侧改写(同源策略允许调用、禁止改写),要拦只能在宿主半拦,代价是与作者页拉锯,本仓不做;
95
+ * 元素级 `el.focus()` 按上面的理由**故意放行**。真出现这两类时 `__mpwFocusGuard.blocked` 不涨而
96
+ * 症状仍在 —— 那就是残留,别把它读成"围栏没生效"。 */
97
+ export function mpwFocusGuardSource(msgName) {
98
+ return '(' + mpwFocusGuardInstall.toString() + ')(' + JSON.stringify(String(msgName || SHIM_MSG)) + ');';
99
+ }
100
+
101
+ /** 注入体本身(自足;注释会随源码一起进 HTML,写短) */
102
+ function mpwFocusGuardInstall(msg) {
103
+ var w = window;
104
+ if (w.__mpwFocusGuard) return; // 幂等:同一文档重复注入只装一次(计数不重置)
105
+ var guard = { version: 1, calls: 0, blocked: 0, allowed: 0, installed: false, allow: false, reported: false };
106
+ w.__mpwFocusGuard = guard;
107
+ var report = function () { // 第一次真拦下时给父页一条回执(宿主 /diag 可见)
108
+ if (guard.reported) return;
109
+ guard.reported = true;
110
+ try { if (w.parent && w.parent !== w) w.parent.postMessage({ mpw: msg, op: "focus-guard", blocked: guard.blocked, calls: guard.calls }, "*"); } catch (e) { /* 父页不可达:忽略 */ }
111
+ };
112
+ var raw = null;
113
+ try { raw = w.focus; } catch (e) { raw = null; }
114
+ if (typeof raw !== "function") return; // 没有可拦的 focus:如实退场(installed 仍 false)
115
+ var patched = function () {
116
+ guard.calls += 1;
117
+ if (guard.allow) { guard.allowed += 1; return raw.apply(w, arguments); }
118
+ guard.blocked += 1; // 帧级夺焦:吞掉,不转交原函数
119
+ report();
120
+ return undefined;
121
+ };
122
+ // ① 直接赋值:Window 上的 focus 是原型上的可写操作 ⇒ 赋值产生自有属性并遮蔽它。
123
+ try { w.focus = patched; } catch (e) { /* 只读/访问器:交给 ② */ }
124
+ // ② 赋值没生效(继承来的不可写属性 / 访问器)时改用 defineProperty —— 自有属性的创建
125
+ // 不受继承链约束,所以这条腿覆盖 ① 覆盖不到的形态。
126
+ if (w.focus !== patched) {
127
+ try { Object.defineProperty(w, "focus", { configurable: true, writable: true, value: patched }); } catch (e) { /* 如实记账 */ }
128
+ }
129
+ guard.installed = w.focus === patched;
130
+ }
131
+
57
132
  /** 帧内存储 facade 的查询开关:缺省开(`mpwstore=0` 关 = 完全不装 facade,退回旧行为)。
58
133
  * 为什么需要开关:① 逐字节负面对照(关掉时注入的种子与帧内行为与改动前一致);
59
134
  * ② 极端页面上作者的存储语义与我们的内存 facade 冲突时的逃生门(不影响渲染主路径)。 */
@@ -644,7 +719,11 @@ export function rewriteWebEntryHtml(html, opts = {}) {
644
719
  if (/data-mpw-we-shim(=|[\s>"'])/i.test(src)) return src;
645
720
  if (opts.fileUrlBase) src = rewriteWebFileUrlsInHtml(src, { basePath: opts.fileUrlBase }).html;
646
721
  const shim = typeof opts.shimSource === 'string' && opts.shimSource ? opts.shimSource : WEB_SHIM_SOURCE;
647
- const inject =
722
+ // 帧级夺焦围栏**排在 shim 前**:即使 shim 自己抛错,焦点围栏也已经装好(见 mpwFocusGuardSource)。
723
+ // `opts.focusGuard === false` 是逐字节对照口(只给判据用;生产调用点不传)。
724
+ const guard = opts.focusGuard === false ? '' :
725
+ '<script ' + FOCUS_GUARD_ATTR + '="' + SHIM_VERSION + '">\n' + escapeScriptClose(mpwFocusGuardSource(opts.shimMsg)) + '\n</script>';
726
+ const inject = guard +
648
727
  '<script ' + SHIM_ATTR + '="' + SHIM_VERSION + '">\n' + escapeScriptClose(shim) + '\n</script>' +
649
728
  (opts.seedScript ? '\n<script ' + SHIM_ATTR + '-seed="' + SHIM_VERSION + '">\n' + escapeScriptClose(opts.seedScript) + '\n</script>' : '');
650
729
  const head = /<head(\s[^>]*)?>/i.exec(src);
@@ -810,7 +889,10 @@ export const WEB_SHIM_SOURCE = `(function () {
810
889
  这里在帧内如实探一次并上报,父页据此(配合 error 上报)给出**可判定的降级**:
811
890
  自动切到兼容模式(同源)重载一次,并留下可查状态。探测本身全部 try/catch,绝不影响页面。 */
812
891
  function probeCaps() {
813
- var out = { version: 1, opaque: false, storage: null, storageReal: null, worker: null, blobUrl: null, offscreen: null, module: null };
892
+ var out = { version: 1, opaque: false, storage: null, storageReal: null, worker: null, blobUrl: null, offscreen: null, module: null, focusGuard: null };
893
+ /* 帧级夺焦围栏是否在位(父页据此知道"这张壁纸的 window.focus() 已被吞掉";计数在帧内
894
+ window.__mpwFocusGuard,越权夺焦时另有一条 op:"focus-guard" 回执 —— 见 mpwFocusGuardSource)。 */
895
+ try { var fg = W.__mpwFocusGuard; out.focusGuard = fg ? { version: Number(fg.version) || 0, installed: !!fg.installed, allow: !!fg.allow } : null; } catch (e) { out.focusGuard = "error"; }
814
896
  try { out.opaque = (W.location && W.location.origin === "null") || W.origin === "null"; } catch (e) { out.opaque = null; }
815
897
  try { out.storage = (function () { try { W.localStorage.setItem("__mpwProbe", "1"); var v = W.localStorage.getItem("__mpwProbe"); W.localStorage.removeItem("__mpwProbe"); return v === "1" ? "ok" : "mismatch" } catch (e) { return "blocked:" + String(e && e.name || e) } })(); } catch (e) { out.storage = "error"; }
816
898
  try { out.storageReal = !!(W.__mpwWebStore && W.__mpwWebStore.real === true); } catch (e) { out.storageReal = null; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mpkg-wallpaper",
3
- "version": "3.16.4",
3
+ "version": "3.16.6",
4
4
  "description": "DSH Web 壁纸引擎 Wallpaper Engine mpkg 背景插件:浏览器内直接解析 .mpkg(preview.gif 动态背景/内嵌 mp4 视频/多时段切换),整屏统一虚化/对话框/弹层/遮罩独立虚化、镜头缩放平移、时钟、冲突检测。",
5
5
  "private": false,
6
6
  "type": "module",