dsh-any-background 0.2.10 → 0.3.0

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
@@ -52,8 +52,11 @@ window.__ModuleLoader__.load({
52
52
  uiSop: "设置界面透明度",
53
53
  uiChatRegion: "对话文本框",
54
54
  uiTrajectory: "轨迹页",
55
- uiPanelRegion: "better-sidebar 工作台",
55
+ uiPanelRegion: "bettersidebar",
56
+ uiPanelNative: "右方侧边栏",
57
+ guideDesc: "管理主题色、壁纸与界面透明度",
56
58
  uiProduced: "产出物/高亮内容",
59
+ uiHeader: "顶栏选项",
57
60
  fontPageTitle: "字体与描边",
58
61
  fontTitle: "自定义字体",
59
62
  fontHint: "支持 ttf/otf/woff/woff2 字体文件(上限 100MB),将应用到整个界面的文字;代码块的等宽字体保持不变。中文字体通常有几十 MB,首次上传与刷新加载需要片刻",
@@ -241,8 +244,11 @@ window.__ModuleLoader__.load({
241
244
  uiSop: "Settings interface opacity",
242
245
  uiChatRegion: "Conversation text frame",
243
246
  uiTrajectory: "Trajectory view",
244
- uiPanelRegion: "better-sidebar workbench",
247
+ uiPanelRegion: "bettersidebar",
248
+ uiPanelNative: "Right sidebar",
249
+ guideDesc: "Theme color, wallpaper and interface opacity in the right sidebar",
245
250
  uiProduced: "Produced / highlights",
251
+ uiHeader: "Header popovers",
246
252
  fontPageTitle: "Font & outline",
247
253
  fontTitle: "Custom font",
248
254
  fontHint: "Accepts ttf/otf/woff/woff2 files (up to 100MB) and applies them to the whole interface; code blocks keep their monospace stack. CJK fonts routinely span tens of MB, so the first upload and each reload take a moment",
@@ -421,7 +427,8 @@ window.__ModuleLoader__.load({
421
427
  trajectory: 30,
422
428
  input: 30,
423
429
  panel: 30,
424
- produced: 30
430
+ produced: 30,
431
+ header: 30
425
432
  },
426
433
  strokes: {
427
434
  bg: {
@@ -468,6 +475,11 @@ window.__ModuleLoader__.load({
468
475
  width: 0,
469
476
  color: "auto",
470
477
  customColor: "#808080"
478
+ },
479
+ header: {
480
+ width: 0,
481
+ color: "auto",
482
+ customColor: "#808080"
471
483
  }
472
484
  },
473
485
  settingsOpacity: .5,
@@ -498,6 +510,7 @@ window.__ModuleLoader__.load({
498
510
  trajectoryOpacity: .5,
499
511
  panelOpacity: .5,
500
512
  producedOpacity: .5,
513
+ headerOpacity: .5,
501
514
  profiles: [],
502
515
  rotation: {
503
516
  enabled: false,
@@ -637,7 +650,8 @@ window.__ModuleLoader__.load({
637
650
  "trajectory",
638
651
  "input",
639
652
  "panel",
640
- "produced"
653
+ "produced",
654
+ "header"
641
655
  ]) {
642
656
  const v = b[k];
643
657
  out[k] = typeof v === "number" ? Math.min(60, Math.max(0, v)) : DEFAULT_CONFIG.blurs[k];
@@ -653,7 +667,8 @@ window.__ModuleLoader__.load({
653
667
  "trajectory",
654
668
  "input",
655
669
  "panel",
656
- "produced"
670
+ "produced",
671
+ "header"
657
672
  ];
658
673
  const STROKE_COLOR_KEYS = [
659
674
  "auto",
@@ -702,6 +717,9 @@ window.__ModuleLoader__.load({
702
717
  function rProducedOpacity() {
703
718
  return clamp01(cfg.producedOpacity, DEFAULT_CONFIG.producedOpacity);
704
719
  }
720
+ function rHeaderOpacity() {
721
+ return clamp01(cfg.headerOpacity, DEFAULT_CONFIG.headerOpacity);
722
+ }
705
723
  function rBgState() {
706
724
  return cfg.bgState;
707
725
  }
@@ -765,7 +783,8 @@ window.__ModuleLoader__.load({
765
783
  chatTextOpacity: rChatTextOpacity(),
766
784
  trajectoryOpacity: rTrajectoryOpacity(),
767
785
  panelOpacity: rPanelOpacity(),
768
- producedOpacity: rProducedOpacity()
786
+ producedOpacity: rProducedOpacity(),
787
+ headerOpacity: rHeaderOpacity()
769
788
  };
770
789
  }
771
790
  /** Apply an appearance snapshot onto cfg (meta fields untouched). */
@@ -787,6 +806,7 @@ window.__ModuleLoader__.load({
787
806
  cfg.trajectoryOpacity = clamp01(ap.trajectoryOpacity, DEFAULT_CONFIG.trajectoryOpacity);
788
807
  cfg.panelOpacity = clamp01(ap.panelOpacity, DEFAULT_CONFIG.panelOpacity);
789
808
  cfg.producedOpacity = clamp01(ap.producedOpacity, DEFAULT_CONFIG.producedOpacity);
809
+ cfg.headerOpacity = clamp01(ap.headerOpacity, DEFAULT_CONFIG.headerOpacity);
790
810
  }
791
811
  const num = (n, def) => typeof n === "number" ? n : def;
792
812
  const cl = (n, lo, hi, def) => typeof n === "number" ? Math.min(hi, Math.max(lo, n)) : def;
@@ -820,7 +840,8 @@ window.__ModuleLoader__.load({
820
840
  chatTextOpacity: clamp01(ac.chatTextOpacity, DEFAULT_CONFIG.chatTextOpacity),
821
841
  trajectoryOpacity: clamp01(ac.trajectoryOpacity, DEFAULT_CONFIG.trajectoryOpacity),
822
842
  panelOpacity: clamp01(ac.panelOpacity, DEFAULT_CONFIG.panelOpacity),
823
- producedOpacity: clamp01(ac.producedOpacity, DEFAULT_CONFIG.producedOpacity)
843
+ producedOpacity: clamp01(ac.producedOpacity, DEFAULT_CONFIG.producedOpacity),
844
+ headerOpacity: clamp01(ac.headerOpacity, DEFAULT_CONFIG.headerOpacity)
824
845
  }
825
846
  });
826
847
  }
@@ -885,7 +906,8 @@ window.__ModuleLoader__.load({
885
906
  "trajectory",
886
907
  "input",
887
908
  "panel",
888
- "produced"
909
+ "produced",
910
+ "header"
889
911
  ]) blurs[k] = num(bl[k], DEFAULT_CONFIG.blurs[k]);
890
912
  const bgType = [
891
913
  "video",
@@ -928,6 +950,7 @@ window.__ModuleLoader__.load({
928
950
  trajectoryOpacity: clamp01(c.trajectoryOpacity, DEFAULT_CONFIG.trajectoryOpacity),
929
951
  panelOpacity: clamp01(c.panelOpacity, DEFAULT_CONFIG.panelOpacity),
930
952
  producedOpacity: clamp01(c.producedOpacity, DEFAULT_CONFIG.producedOpacity),
953
+ headerOpacity: clamp01(c.headerOpacity, DEFAULT_CONFIG.headerOpacity),
931
954
  profiles: adoptProfiles(c.profiles),
932
955
  rotation: adoptRotation(c.rotation),
933
956
  schedule: adoptSchedule(c.schedule),
@@ -971,6 +994,95 @@ window.__ModuleLoader__.load({
971
994
  };
972
995
  }
973
996
  //#endregion
997
+ //#region src/client/host.ts
998
+ /**
999
+ * Host release detection + feature classification.
1000
+ *
1001
+ * WHY THIS EXISTS
1002
+ * The client context exposes no host version: there is no `hostVersion` /
1003
+ * `dshVersion` field on `ctx`, and `window.__DSH_BOOT__.version` is the
1004
+ * module-table format tag (`'client'`), not a release. Capability probing alone
1005
+ * is also not sufficient, because the obvious probes do not separate the
1006
+ * generations — `ctx.sidebarRightTabs` and the native `[data-sidebar-right-panel]`
1007
+ * marker both exist on 0.1.5-rc.2 already, so using either as a "0.1.6+" test
1008
+ * mis-classifies 0.1.5 as newer.
1009
+ *
1010
+ * So the release is resolved on the Node half (where the launcher's on-disk
1011
+ * layout is visible) and handed to this half through the `read` RPC payload.
1012
+ * This module stores that verdict and turns it into the feature flags the rest
1013
+ * of the plugin gates on. When the release cannot be determined the flags fall
1014
+ * back to capability probing, never to a guess.
1015
+ *
1016
+ * @module
1017
+ */
1018
+ let current = {
1019
+ version: null,
1020
+ generation: "unknown"
1021
+ };
1022
+ /** False until the server verdict lands; feature gates must not treat "not
1023
+ * asked yet" as "old host". */
1024
+ let resolved = false;
1025
+ const subs$1 = /* @__PURE__ */ new Set();
1026
+ /** Adopt the verdict delivered by the server's `read` payload. */
1027
+ function adoptHostInfo(raw) {
1028
+ if (raw === null || typeof raw !== "object") return;
1029
+ const h = raw;
1030
+ const version = typeof h.version === "string" && h.version.trim() !== "" ? h.version.trim() : null;
1031
+ const generation = h.generation === "0.1.5" || h.generation === "0.1.6" || h.generation === "0.1.7" ? h.generation : "unknown";
1032
+ const changed = !resolved || current.version !== version || current.generation !== generation;
1033
+ current = {
1034
+ version,
1035
+ generation
1036
+ };
1037
+ resolved = true;
1038
+ if (changed) subs$1.forEach((cb) => cb());
1039
+ }
1040
+ /** Subscribe to a verdict landing (or changing). */
1041
+ function subscribeHostInfo(cb) {
1042
+ subs$1.add(cb);
1043
+ return () => {
1044
+ subs$1.delete(cb);
1045
+ };
1046
+ }
1047
+ /** Ordered generation rank, for "at least this generation" tests. `unknown`
1048
+ * has no rank and must be handled by the caller's fallback. */
1049
+ const RANK = {
1050
+ "0.1.5": 5,
1051
+ "0.1.6": 6,
1052
+ "0.1.7": 7
1053
+ };
1054
+ /**
1055
+ * Whether the host is at least `gen`. An unknown generation answers `false`:
1056
+ * callers treat that as "do not assume the newer behaviour" and use whatever
1057
+ * capability probe they own, which is the safe direction for every gate here.
1058
+ */
1059
+ function hostAtLeast(gen) {
1060
+ const rank = RANK[current.generation];
1061
+ return rank !== void 0 && rank >= RANK[gen];
1062
+ }
1063
+ /**
1064
+ * Whether this host's own right Sidebar already carries the appearance page,
1065
+ * which makes the plugin's dsh-better-sidebar page a duplicate entry on the same
1066
+ * guide surface.
1067
+ *
1068
+ * `true` — 0.1.6 or newer: the better-sidebar page stands down.
1069
+ * `false` — 0.1.5 or an undetermined-but-answered generation: keep it.
1070
+ * `null` — the verdict has not landed yet (the probe is a Node round-trip).
1071
+ *
1072
+ * Callers must treat `null` as "not yet known": register nothing eagerly on a
1073
+ * `true`, and arm a withdrawal for a later `true` rather than blocking on it.
1074
+ *
1075
+ * NOTE: 0.1.5-rc.2 ALSO ships the native right Sidebar (verified: the
1076
+ * `ui-sidebar-right` entry is in that release's `dsh-web-app/cordis.patch.yml`),
1077
+ * so this gate deliberately keys off the release rather than off the presence of
1078
+ * the native sidebar service — the two are not the same fact.
1079
+ */
1080
+ function suppressesBetterSidebar() {
1081
+ if (!resolved) return null;
1082
+ if (current.generation === "unknown") return false;
1083
+ return hostAtLeast("0.1.6");
1084
+ }
1085
+ //#endregion
974
1086
  //#region src/client/rpc.ts
975
1087
  const RPC_CHANNEL = "/dsh-any-background";
976
1088
  /** Same-origin serve URL of the persisted video (enough for <video src>/fetch). */
@@ -1035,6 +1147,7 @@ window.__ModuleLoader__.load({
1035
1147
  const data = await rpcCall("read", {});
1036
1148
  if (data && typeof data === "object") {
1037
1149
  const d = data;
1150
+ adoptHostInfo(d.host);
1038
1151
  if (d.config) adoptConfig(d.config);
1039
1152
  if (typeof d.wallpaperUrl === "string") setWpImageUrl(d.wallpaperUrl);
1040
1153
  else if (d.wallpaperUrl === null) setWpImageUrl(null);
@@ -2711,6 +2824,7 @@ window.__ModuleLoader__.load({
2711
2824
  lastBgKey = bgKey;
2712
2825
  applyPartOpacities(ops);
2713
2826
  }
2827
+ applyExemptDefaults();
2714
2828
  } catch {}
2715
2829
  }
2716
2830
  const SETTINGS_PANEL_SEL = "[role=\"dialog\"][aria-modal=\"true\"][aria-labelledby]";
@@ -2729,17 +2843,98 @@ window.__ModuleLoader__.load({
2729
2843
  if (px > 0) document.documentElement.style.setProperty("--dsh-any-part-blur-global", `blur(${px}px)`);
2730
2844
  else document.documentElement.style.removeProperty("--dsh-any-part-blur-global");
2731
2845
  }
2732
- const PANEL_TOKEN_RULE = "[data-dsh-bottom-panel],[data-sidebar-right-panel]{--dsw-alias-bg-base:var(--dsh-any-panel-bg-base);--dsw-alias-bg-layer-1:var(--dsh-any-panel-layer-1);--dsw-alias-bg-layer-2:var(--dsh-any-panel-layer-2);--dsw-alias-bg-layer-3:var(--dsh-any-panel-layer-3);position:fixed!important;z-index:26!important}[data-sidebar-right-panel][data-sidebar-right-panel=\"fullscreen\"]{z-index:40!important}";
2733
- const PANEL_BLUR_RULE = "[data-dsh-bottom-panel],[data-sidebar-right-panel]{-webkit-backdrop-filter:var(--dsh-any-blur-panel,none);backdrop-filter:var(--dsh-any-blur-panel,none)}";
2734
- const PRODUCED_RULE = "[data-code-block-content] pre,[data-code-block-banner],[data-composer-chip],:not(pre)>code{-webkit-backdrop-filter:var(--dsh-any-blur-prod,none);backdrop-filter:var(--dsh-any-blur-prod,none)}@supports (background:color-mix(in srgb,red 50%,transparent)){.md-code-block{background:transparent!important}.md-code-block>div{background-color:transparent!important}.md-code-block [data-code-block-content] pre{background-color:color-mix(in srgb,var(--dsl-code-block-background,var(--dsw-alias-markdown-code-block,transparent)) var(--dsh-any-prod-pct,100%),transparent)!important}.md-code-block [data-code-block-banner]{background-color:color-mix(in srgb,var(--dsl-code-block-banner-background-color,var(--dsw-alias-markdown-code-block-banner,transparent)) var(--dsh-any-prod-pct,100%),transparent)!important}[data-code-block-content]{--shiki-background:color-mix(in srgb,var(--dsw-alias-markdown-code-block,var(--dsl-code-block-background,transparent)) var(--dsh-any-prod-pct,100%),transparent)}:not(pre)>code{background-color:color-mix(in srgb,var(--dsw-alias-markdown-inline-code,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-composer-chip]>*{background-color:color-mix(in srgb,var(--dsw-alias-interactive-bg-hover,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}}";
2846
+ const PANEL_TOKEN_RULE = "[data-dsh-bottom-panel],[data-sidebar-right-panel]{--dsw-alias-bg-base:var(--dsh-any-panel-bg-base);--dsw-alias-bg-layer-1:var(--dsh-any-panel-layer-1);--dsw-alias-bg-layer-2:var(--dsh-any-panel-layer-2);--dsw-alias-bg-layer-3:var(--dsh-any-panel-layer-3)}";
2847
+ const NO_DOCKKIT = ":not(:has([data-dockkit-host],[data-dockkit-empty]))";
2848
+ const PANEL_BLUR_RULE = `[data-sidebar-right-panel] [data-dockkit-host="dock"],[data-sidebar-right-panel] [data-dockkit-empty]{-webkit-backdrop-filter:var(--dsh-any-blur-panel,none);backdrop-filter:var(--dsh-any-blur-panel,none)}[data-sidebar-right-panel]${NO_DOCKKIT}{-webkit-backdrop-filter:var(--dsh-any-blur-panel,none);backdrop-filter:var(--dsh-any-blur-panel,none)}[data-dsh-bottom-panel]{-webkit-backdrop-filter:var(--dsh-any-blur-panel,none);backdrop-filter:var(--dsh-any-blur-panel,none)}`;
2849
+ const PANEL_PROMOTION_RULE = `[data-sidebar-right-panel]${NO_DOCKKIT}{position:fixed!important;z-index:26!important}[data-sidebar-right-panel="fullscreen"]${NO_DOCKKIT}{z-index:40!important}`;
2850
+ const HEADER_POPOVER_RULE = "[data-dsh-any-header-popover][role],ul[class*=\"_menu\"],div[role=\"dialog\"][class*=\"_panel\"]:not([aria-modal=\"true\"]),[role=\"menu\"][class*=\"_denseList\"]:not([class*=\"_portal\"]),[role=\"tree\"][class*=\"_menu\"]{-webkit-backdrop-filter:var(--dsh-any-blur-header,none);backdrop-filter:var(--dsh-any-blur-header,none)}";
2851
+ const EXEMPT_DEFAULT_RULE = "[role=\"dialog\"][aria-modal=\"true\"]:not([aria-labelledby]){backdrop-filter:none;-webkit-backdrop-filter:none}[role=\"menu\"]:has([class*=\"_danger\"]){backdrop-filter:none;-webkit-backdrop-filter:none;-webkit-text-stroke-width:0}";
2852
+ /** Selector the dynamic header token rule targets — kept in sync with the
2853
+ * static HEADER_POPOVER_RULE selector list (specificity equal or higher than
2854
+ * the card group's `[role="menu"]:not(...)` so it wins). */
2855
+ const HEADER_TOKEN_SELECTOR = "[data-dsh-any-header-popover][role],ul[class*=\"_menu\"],div[role=\"dialog\"][class*=\"_panel\"]:not([aria-modal=\"true\"]),[role=\"menu\"][class*=\"_denseList\"]:not([class*=\"_portal\"]),[role=\"tree\"][class*=\"_menu\"]";
2856
+ /** Header-popover surfaces: retint the menu token and (re)write the blur.
2857
+ * Mirrors applyProduced: the static rule above never changes, so a drag only
2858
+ * rewrites the blur variable and this one dynamic rule. */
2859
+ function applyHeaderPopovers() {
2860
+ const root = document.documentElement;
2861
+ const px = rBlurs().header;
2862
+ if (px > 0) root.style.setProperty("--dsh-any-blur-header", `blur(${px}px)`);
2863
+ else root.style.removeProperty("--dsh-any-blur-header");
2864
+ let opacity = rHeaderOpacity();
2865
+ if (opacity < 0) opacity = 0;
2866
+ if (opacity > 1) opacity = 1;
2867
+ const menu = (paletteTokens() ?? readHostOpacityTokens())?.["--dsw-specific-menu"];
2868
+ const el = ensureHeaderStyle();
2869
+ const css = menu === void 0 ? "" : `${HEADER_TOKEN_SELECTOR}{--dsw-specific-menu:${toRgba(menu, opacity)}}`;
2870
+ if (el.textContent !== css) el.textContent = css;
2871
+ }
2872
+ let headerStyleEl = null;
2873
+ function ensureHeaderStyle() {
2874
+ if (headerStyleEl?.isConnected) return headerStyleEl;
2875
+ headerStyleEl = document.createElement("style");
2876
+ headerStyleEl.dataset.plugin = "dsh-any-background-header";
2877
+ document.head.appendChild(headerStyleEl);
2878
+ return headerStyleEl;
2879
+ }
2880
+ /** Pin the exempt groups (confirm dialogs and the session-row menu) so no
2881
+ * appearance slider moves them — while KEEPING the active theme color.
2882
+ *
2883
+ * "Default" here means "not faded, not blurred", NOT "host-palette original":
2884
+ * an earlier version restored the :root host colors, which also stripped the
2885
+ * picked theme color and made the menu look un-themed. The correct source is
2886
+ * the same one every other slider uses (the plugin palette when there is one,
2887
+ * else the host's resolved tokens) with alpha pinned to 1 — so the surface
2888
+ * keeps whatever color the theme currently dictates and only loses the slider's
2889
+ * translucency and blur.
2890
+ *
2891
+ * Emitted as REAL literals into a dedicated stylesheet, never as
2892
+ * `var(--x, var(--x))`: a self-referencing fallback is a CSS cycle that
2893
+ * computes to the guaranteed-invalid value and turns every consuming
2894
+ * `background:var(--dsw-alias-bg-layer-2)` fully transparent. Literals also
2895
+ * mean that when no color can be resolved we emit NO declaration at all, so
2896
+ * the surface keeps the re-scoped value rather than going transparent. */
2897
+ function applyExemptDefaults() {
2898
+ if (typeof document === "undefined") return;
2899
+ const source = paletteTokens() ?? readHostOpacityTokens();
2900
+ const el = ensureExemptStyle();
2901
+ const decls = [];
2902
+ const add = (token, value) => {
2903
+ if (value === void 0 || value === "") return;
2904
+ decls.push(`${token}:${toRgba(value, 1)}`);
2905
+ };
2906
+ add("--dsw-alias-bg-layer-1", source?.["--dsw-alias-bg-layer-1"]);
2907
+ add("--dsw-alias-bg-layer-2", source?.["--dsw-alias-bg-layer-2"]);
2908
+ add("--dsw-alias-bg-layer-3", source?.["--dsw-alias-bg-layer-3"]);
2909
+ add("--dsw-specific-menu", source?.["--dsw-specific-menu"]);
2910
+ if (decls.length === 0) {
2911
+ if (el.textContent !== "") el.textContent = "";
2912
+ return;
2913
+ }
2914
+ const dialog = "[role=\"dialog\"][aria-modal=\"true\"]:not([aria-labelledby])";
2915
+ const body = decls.join(";");
2916
+ const css = `${dialog},${dialog} *{${body}}[role="menu"]:has([class*="_danger"]){${body}}`;
2917
+ if (el.textContent !== css) el.textContent = css;
2918
+ }
2919
+ let exemptStyleEl = null;
2920
+ function ensureExemptStyle() {
2921
+ if (exemptStyleEl?.isConnected) return exemptStyleEl;
2922
+ exemptStyleEl = document.createElement("style");
2923
+ exemptStyleEl.dataset.plugin = "dsh-any-background-exempt";
2924
+ document.head.appendChild(exemptStyleEl);
2925
+ return exemptStyleEl;
2926
+ }
2927
+ const PRODUCED_RULE = "[data-code-block-content] pre,[data-code-block-banner],[data-composer-chip],[data-changed-files]>button:first-child,[data-terminal],[data-read],[data-context-injection-body],[data-search],[data-web],[class*=\"_ioCard\"]{-webkit-backdrop-filter:var(--dsh-any-blur-prod,none);backdrop-filter:var(--dsh-any-blur-prod,none)}@supports (background:color-mix(in srgb,red 50%,transparent)){.md-code-block{background:transparent!important}.md-code-block>div{background-color:transparent!important}.md-code-block [data-code-block-content] pre{background-color:color-mix(in srgb,var(--dsl-code-block-background,var(--dsw-alias-markdown-code-block,transparent)) var(--dsh-any-prod-pct,100%),transparent)!important}.md-code-block [data-code-block-banner]{background-color:color-mix(in srgb,var(--dsl-code-block-banner-background-color,var(--dsw-alias-markdown-code-block-banner,transparent)) var(--dsh-any-prod-pct,100%),transparent)!important}[data-code-block-content]{--shiki-background:color-mix(in srgb,var(--dsw-alias-markdown-code-block,var(--dsl-code-block-background,transparent)) var(--dsh-any-prod-pct,100%),transparent)}:not(pre)>code{background-color:color-mix(in srgb,var(--dsw-alias-markdown-inline-code,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-composer-chip]>*{background-color:color-mix(in srgb,var(--dsw-alias-interactive-bg-hover,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-terminal]{background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-terminal] button{background-color:transparent!important}[data-read]{background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-read]>div:first-child{background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block-banner,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-context-injection-body]{background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-search]{background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-web]{background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[class*=\"_ioCard\"]{background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}}";
2735
2928
  const STROKE_RULE = [
2736
2929
  `${SETTINGS_PANEL_SEL}{-webkit-text-stroke:var(--dsh-any-stroke-settings-w,0px) var(--dsh-any-stroke-settings-c,transparent);paint-order:stroke fill}`,
2737
2930
  "[role=\"menu\"]:not([data-dockkit-tab-menu]),[role=\"listbox\"],body>[role=\"tree\"],body>[role=\"dialog\"]:not([aria-modal=\"true\"]){-webkit-text-stroke:var(--dsh-any-stroke-card-w,0px) var(--dsh-any-stroke-card-c,transparent);paint-order:stroke fill}",
2738
2931
  "[data-composer-card],[data-cordis-panel],[data-composer-card] textarea,[data-composer-card] input,[data-composer-card] [contenteditable],[data-cordis-panel] input,[data-cordis-panel] textarea{-webkit-text-stroke:var(--dsh-any-stroke-input-w,0px) var(--dsh-any-stroke-input-c,transparent);paint-order:stroke fill}",
2739
2932
  `[data-chat-flow]{-webkit-text-stroke:var(--dsh-any-stroke-chat-w,0px) var(--dsh-any-stroke-chat-c,transparent);paint-order:stroke fill}`,
2740
2933
  `[data-conversation-composer-overlay]{-webkit-text-stroke:var(--dsh-any-stroke-trajectory-w,0px) var(--dsh-any-stroke-trajectory-c,transparent);paint-order:stroke fill}`,
2741
- "[data-code-block-content] pre,[data-code-block-content],[data-code-block-banner],[data-composer-chip],:not(pre)>code{-webkit-text-stroke:var(--dsh-any-stroke-produced-w,0px) var(--dsh-any-stroke-produced-c,transparent);paint-order:stroke fill}",
2934
+ "[data-code-block-content] pre,[data-code-block-content],[data-code-block-banner],[data-composer-chip],:not(pre)>code,[data-changed-files],[data-terminal],[data-read],[data-context-injection-body],[data-search],[data-web],[class*=\"_ioCard\"]{-webkit-text-stroke:var(--dsh-any-stroke-produced-w,0px) var(--dsh-any-stroke-produced-c,transparent);paint-order:stroke fill}",
2742
2935
  `[data-dsh-bottom-panel],[data-sidebar-right-panel]{-webkit-text-stroke:var(--dsh-any-stroke-panel-w,0px) var(--dsh-any-stroke-panel-c,transparent);paint-order:stroke fill}`,
2936
+ "[data-dsh-any-header-popover],ul[class*=\"_menu\"],div[role=\"dialog\"][class*=\"_panel\"]:not([aria-modal=\"true\"]),[role=\"menu\"][class*=\"_denseList\"]:not([class*=\"_portal\"]),[role=\"tree\"][class*=\"_menu\"]{-webkit-text-stroke:var(--dsh-any-stroke-header-w,0px) var(--dsh-any-stroke-header-c,transparent);paint-order:stroke fill}",
2937
+ "[data-chat-flow] [role=\"status\"],[data-chat-flow] [data-turn-process],[data-chat-flow] [data-text-shimmer],[data-chat-flow] [style*=\"--dsh-text-shimmer-spread\"]{-webkit-text-stroke-width:0!important;paint-order:normal!important}",
2743
2938
  `svg{-webkit-text-stroke-width:0!important;paint-order:normal!important}`,
2744
2939
  `::placeholder{-webkit-text-stroke-width:0!important}`
2745
2940
  ].join("");
@@ -2753,7 +2948,8 @@ window.__ModuleLoader__.load({
2753
2948
  "trajectory",
2754
2949
  "input",
2755
2950
  "panel",
2756
- "produced"
2951
+ "produced",
2952
+ "header"
2757
2953
  ];
2758
2954
  /** Resolve one group's stroke color key into a concrete CSS color.
2759
2955
  * 'auto' contrasts the FONT direction (white fonts → black stroke and vice
@@ -3167,6 +3363,7 @@ window.__ModuleLoader__.load({
3167
3363
  applyInputBlur(blurs.input);
3168
3364
  applyPanelBlur(blurs.panel);
3169
3365
  applyProduced();
3366
+ applyHeaderPopovers();
3170
3367
  applyViewCards();
3171
3368
  }
3172
3369
  /** Produced/artifact surfaces (conversation code blocks + their banner +
@@ -3207,6 +3404,10 @@ window.__ModuleLoader__.load({
3207
3404
  applyProduced();
3208
3405
  return;
3209
3406
  }
3407
+ if (part === "header") {
3408
+ applyHeaderPopovers();
3409
+ return;
3410
+ }
3210
3411
  if (part === "chat" || part === "trajectory") {
3211
3412
  applyViewCards();
3212
3413
  return;
@@ -3808,6 +4009,9 @@ window.__ModuleLoader__.load({
3808
4009
  applyTrajectoryOverrides(rTrajectoryOpacity());
3809
4010
  }
3810
4011
  applyPanelOverrides(rPanelOpacity());
4012
+ applyProduced();
4013
+ applyHeaderPopovers();
4014
+ applyExemptDefaults();
3811
4015
  applyPartBlurs(rBlurs());
3812
4016
  applyStrokes();
3813
4017
  }
@@ -3844,6 +4048,11 @@ window.__ModuleLoader__.load({
3844
4048
  document.documentElement.style.removeProperty("--dsh-any-blur-panel");
3845
4049
  document.documentElement.style.removeProperty("--dsh-any-blur-prod");
3846
4050
  document.documentElement.style.removeProperty("--dsh-any-prod-pct");
4051
+ document.documentElement.style.removeProperty("--dsh-any-blur-header");
4052
+ headerStyleEl?.remove();
4053
+ headerStyleEl = null;
4054
+ exemptStyleEl?.remove();
4055
+ exemptStyleEl = null;
3847
4056
  for (const v of Object.values(OPACITY_VARS)) document.documentElement.style.removeProperty(v);
3848
4057
  baseTokenKey = "";
3849
4058
  lastBgKey = "";
@@ -5589,12 +5798,16 @@ window.__ModuleLoader__.load({
5589
5798
  * signal and the body childList observer fires exactly when it appears. The
5590
5799
  * panel surfaces we actually style are listed only as reinforcement: they are
5591
5800
  * conditionally rendered (collapsed panels drop them), so they must never be
5592
- * the sole signal. */
5801
+ * the sole signal.
5802
+ *
5803
+ * `[data-sidebar-right-panel]` is deliberately ABSENT: on every host generation
5804
+ * it marks the host's own right Sidebar (always present once a session opens),
5805
+ * so counting it would make the better-sidebar verdict permanently true and the
5806
+ * panel slider could never drop back to its native "右方侧边栏" identity. */
5593
5807
  const BETTER_SIDEBAR_MARKERS = [
5594
5808
  "[data-dsh-better-sidebar]",
5595
5809
  "[data-dsh-panel-host]",
5596
- "[data-dsh-bottom-panel]",
5597
- "[data-sidebar-right-panel]"
5810
+ "[data-dsh-bottom-panel]"
5598
5811
  ];
5599
5812
  let betterSidebar = false;
5600
5813
  const subs = /* @__PURE__ */ new Set();
@@ -5630,6 +5843,15 @@ window.__ModuleLoader__.load({
5630
5843
  observer.observe(document.body, { childList: true });
5631
5844
  return () => observer.disconnect();
5632
5845
  }
5846
+ let nativeTabs = false;
5847
+ const nativeSubs = /* @__PURE__ */ new Set();
5848
+ /** Mark the native right-Sidebar service as confirmed. Sticky — the service
5849
+ * never goes away within a session. */
5850
+ function markNativeSidebarTabs() {
5851
+ if (nativeTabs) return;
5852
+ nativeTabs = true;
5853
+ nativeSubs.forEach((cb) => cb());
5854
+ }
5633
5855
  //#endregion
5634
5856
  //#region src/client/components/pages/InterfacePage.tsx
5635
5857
  const PARTS$1 = [
@@ -5673,6 +5895,11 @@ window.__ModuleLoader__.load({
5673
5895
  labelKey: "uiProduced",
5674
5896
  Icon: TextIcon
5675
5897
  },
5898
+ {
5899
+ isHeader: true,
5900
+ labelKey: "uiHeader",
5901
+ Icon: PanelIcon
5902
+ },
5676
5903
  {
5677
5904
  isPanel: true,
5678
5905
  labelKey: "uiPanelRegion",
@@ -5683,7 +5910,6 @@ window.__ModuleLoader__.load({
5683
5910
  const { t, setOps, setBlurs, setSop, setPanelOp, useStore } = p;
5684
5911
  useStore((s) => s.metaRev);
5685
5912
  const hasBetterSidebar = useBetterSidebar();
5686
- const parts = PARTS$1.filter((part) => !(part.isPanel && !hasBetterSidebar));
5687
5913
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
5688
5914
  className: "dab-head dab-rise",
5689
5915
  style: { "--d": 0 },
@@ -5703,11 +5929,12 @@ window.__ModuleLoader__.load({
5703
5929
  ]
5704
5930
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5705
5931
  className: "dab-grid-parts",
5706
- children: parts.map((part, i) => {
5707
- const { labelKey, Icon, isSettings, isChat, isTrajectory, isPanel, isProduced } = part;
5932
+ children: PARTS$1.map((part, i) => {
5933
+ const { labelKey, Icon, isSettings, isChat, isTrajectory, isPanel, isProduced, isHeader } = part;
5708
5934
  const opKey = part.opKey;
5709
- const blurKey = isChat ? "chat" : isTrajectory ? "trajectory" : isSettings ? "settings" : isPanel ? "panel" : isProduced ? "produced" : opKey;
5710
- const opacity = isChat ? rChatTextOpacity() : isTrajectory ? rTrajectoryOpacity() : isSettings ? rSop() : isPanel ? rPanelOpacity() : isProduced ? rProducedOpacity() : rOps()[opKey];
5935
+ const label = isPanel ? t(hasBetterSidebar ? "uiPanelRegion" : "uiPanelNative") : t(labelKey);
5936
+ const blurKey = isChat ? "chat" : isTrajectory ? "trajectory" : isSettings ? "settings" : isPanel ? "panel" : isProduced ? "produced" : isHeader ? "header" : opKey;
5937
+ const opacity = isChat ? rChatTextOpacity() : isTrajectory ? rTrajectoryOpacity() : isSettings ? rSop() : isPanel ? rPanelOpacity() : isProduced ? rProducedOpacity() : isHeader ? rHeaderOpacity() : rOps()[opKey];
5711
5938
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
5712
5939
  className: "dab-card dab-card-hover dab-rise",
5713
5940
  style: { "--d": i + 1 },
@@ -5721,7 +5948,7 @@ window.__ModuleLoader__.load({
5721
5948
  }),
5722
5949
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5723
5950
  className: "dab-part-name",
5724
- children: t(labelKey)
5951
+ children: label
5725
5952
  }),
5726
5953
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5727
5954
  className: "dab-part-badge",
@@ -5753,6 +5980,9 @@ window.__ModuleLoader__.load({
5753
5980
  } else if (isProduced) {
5754
5981
  cfg.producedOpacity = op;
5755
5982
  applyProduced();
5983
+ } else if (isHeader) {
5984
+ cfg.headerOpacity = op;
5985
+ applyHeaderPopovers();
5756
5986
  } else {
5757
5987
  const ops = { ...rOps() };
5758
5988
  ops[opKey] = op;
@@ -5777,6 +6007,10 @@ window.__ModuleLoader__.load({
5777
6007
  cfg.producedOpacity = op;
5778
6008
  applyProduced();
5779
6009
  saveConfig();
6010
+ } else if (isHeader) {
6011
+ cfg.headerOpacity = op;
6012
+ applyHeaderPopovers();
6013
+ saveConfig();
5780
6014
  } else {
5781
6015
  const ops = { ...rOps() };
5782
6016
  ops[opKey] = op;
@@ -5852,6 +6086,11 @@ window.__ModuleLoader__.load({
5852
6086
  labelKey: "uiProduced",
5853
6087
  Icon: TextIcon
5854
6088
  },
6089
+ {
6090
+ key: "header",
6091
+ labelKey: "uiHeader",
6092
+ Icon: PanelIcon
6093
+ },
5855
6094
  {
5856
6095
  key: "panel",
5857
6096
  labelKey: "uiPanelRegion",
@@ -7224,7 +7463,8 @@ window.__ModuleLoader__.load({
7224
7463
  trajectory: 0,
7225
7464
  input: 0,
7226
7465
  panel: 0,
7227
- produced: 0
7466
+ produced: 0,
7467
+ header: 0
7228
7468
  };
7229
7469
  /** Presets ship with strokes off — text outlines are an opt-in look. Fresh
7230
7470
  * objects per call so a profile restore can never alias another preset's
@@ -7244,7 +7484,8 @@ window.__ModuleLoader__.load({
7244
7484
  trajectory: off(),
7245
7485
  input: off(),
7246
7486
  panel: off(),
7247
- produced: off()
7487
+ produced: off(),
7488
+ header: off()
7248
7489
  };
7249
7490
  };
7250
7491
  const BUILTIN_PRESETS = [
@@ -7266,7 +7507,8 @@ window.__ModuleLoader__.load({
7266
7507
  chatTextOpacity: 0,
7267
7508
  trajectoryOpacity: 1,
7268
7509
  panelOpacity: 1,
7269
- producedOpacity: 1
7510
+ producedOpacity: 1,
7511
+ headerOpacity: 1
7270
7512
  }
7271
7513
  },
7272
7514
  {
@@ -7300,7 +7542,8 @@ window.__ModuleLoader__.load({
7300
7542
  chatTextOpacity: 0,
7301
7543
  trajectoryOpacity: .85,
7302
7544
  panelOpacity: .85,
7303
- producedOpacity: 1
7545
+ producedOpacity: 1,
7546
+ headerOpacity: 1
7304
7547
  }
7305
7548
  },
7306
7549
  {
@@ -7321,7 +7564,8 @@ window.__ModuleLoader__.load({
7321
7564
  chatTextOpacity: 0,
7322
7565
  trajectoryOpacity: 1,
7323
7566
  panelOpacity: 1,
7324
- producedOpacity: 1
7567
+ producedOpacity: 1,
7568
+ headerOpacity: 1
7325
7569
  }
7326
7570
  },
7327
7571
  {
@@ -7355,7 +7599,8 @@ window.__ModuleLoader__.load({
7355
7599
  chatTextOpacity: 0,
7356
7600
  trajectoryOpacity: .8,
7357
7601
  panelOpacity: .8,
7358
- producedOpacity: 1
7602
+ producedOpacity: 1,
7603
+ headerOpacity: 1
7359
7604
  }
7360
7605
  },
7361
7606
  {
@@ -7389,7 +7634,8 @@ window.__ModuleLoader__.load({
7389
7634
  chatTextOpacity: 0,
7390
7635
  trajectoryOpacity: .78,
7391
7636
  panelOpacity: .75,
7392
- producedOpacity: 1
7637
+ producedOpacity: 1,
7638
+ headerOpacity: 1
7393
7639
  }
7394
7640
  },
7395
7641
  {
@@ -7414,7 +7660,8 @@ window.__ModuleLoader__.load({
7414
7660
  chatTextOpacity: 0,
7415
7661
  trajectoryOpacity: 1,
7416
7662
  panelOpacity: 1,
7417
- producedOpacity: 1
7663
+ producedOpacity: 1,
7664
+ headerOpacity: 1
7418
7665
  }
7419
7666
  }
7420
7667
  ];
@@ -8027,6 +8274,9 @@ window.__ModuleLoader__.load({
8027
8274
  const read = (0, react.useCallback)(() => locale.getSnapshot().active, [locale]);
8028
8275
  return (0, react.useSyncExternalStore)(subscribe, read, read);
8029
8276
  }
8277
+ /** Rendered inside a sidebar tab: the five pages, `surface: 'sidebar'` shell.
8278
+ * Shared by the better-sidebar page and the native right-Sidebar tab — both
8279
+ * hand a full-height column that owns its own scroll. */
8030
8280
  function ThemeTab({ face, useStore, locale }) {
8031
8281
  const active = useActiveLocale(locale);
8032
8282
  const props = (0, react.useMemo)(() => ({
@@ -8067,22 +8317,258 @@ window.__ModuleLoader__.load({
8067
8317
  ctx.inject?.(["betterSidebar"], (scope) => {
8068
8318
  const service = scope.betterSidebar;
8069
8319
  if (service === void 0) return;
8320
+ const suppress = suppressesBetterSidebar();
8321
+ if (suppress === true) return;
8322
+ let disposeTab = null;
8323
+ const unsubscribeHost = suppress === null ? subscribeHostInfo(() => {
8324
+ if (suppressesBetterSidebar() === true) {
8325
+ disposeTab?.();
8326
+ disposeTab = null;
8327
+ unsubscribeHost();
8328
+ }
8329
+ }) : () => {};
8070
8330
  const face = opts.face();
8071
- scope.effect(() => service.registerTab({
8072
- id: "dsh-any-background:theme",
8073
- title: () => t("nav"),
8074
- icon: (size) => (0, react.createElement)(SunIcon, { size }),
8075
- order: ORDER,
8076
- single: true,
8077
- component: () => (0, react.createElement)(ThemeTab, {
8078
- face,
8079
- useStore,
8080
- locale
8081
- })
8082
- }));
8331
+ scope.effect(() => {
8332
+ disposeTab = service.registerTab({
8333
+ id: "dsh-any-background:theme",
8334
+ title: () => t("nav"),
8335
+ icon: (size) => (0, react.createElement)(SunIcon, { size }),
8336
+ order: ORDER,
8337
+ single: true,
8338
+ component: () => (0, react.createElement)(ThemeTab, {
8339
+ face,
8340
+ useStore,
8341
+ locale
8342
+ })
8343
+ }) ?? null;
8344
+ return () => {
8345
+ unsubscribeHost();
8346
+ disposeTab?.();
8347
+ disposeTab = null;
8348
+ };
8349
+ });
8083
8350
  });
8084
8351
  }
8085
8352
  //#endregion
8353
+ //#region src/client/sidebar/native-tab.tsx
8354
+ /**
8355
+ * dsh-any-background — native right-Sidebar integration (DSH 0.1.6+).
8356
+ *
8357
+ * DSH 0.1.6 ships an official right Sidebar (`dsh-client-ui-sidebar-right`)
8358
+ * with a public two-stage extension path: a static tab type into
8359
+ * `ctx.sidebarRightTabs` — whose `guide` entry draws a card on the Sidebar's
8360
+ * guide page — and a body under the keyed `sidebar.right.pane.tab` seat. This
8361
+ * module registers the plugin's appearance pages through that path, so the
8362
+ * official Sidebar gains a "主题" card that opens the very same five pages the
8363
+ * settings panel shows (one implementation, one state store; editing in either
8364
+ * place shows up in the other).
8365
+ *
8366
+ * The dependency is optional at every level, mirroring `sidebar/tab.tsx`:
8367
+ * · the registry is waited on with `ctx.inject`, so a host without the
8368
+ * right Sidebar (or an older DSH) silently registers nothing, and a
8369
+ * Sidebar that initializes after this plugin still gets the card;
8370
+ * · the service shape is restated as a minimal local contract instead of
8371
+ * imported — a value import would be rejected by the host's client-bundle
8372
+ * purity gate, and even a type-only import would make the package a
8373
+ * build-time dependency for everyone who does not run it.
8374
+ *
8375
+ * When dsh-better-sidebar is installed it brings its own "主题" page (registered
8376
+ * by `sidebar/tab.tsx`), which would duplicate this card on the same guide
8377
+ * page. The native card therefore yields: it is not registered while
8378
+ * better-sidebar is detected, and it withdraws itself if better-sidebar mounts
8379
+ * later (the presence verdict is sticky-true, so no re-registration path is
8380
+ * needed). The Interface page's panel slider follows the same split:
8381
+ * "右方侧边栏" natively, "bettersidebar" when that plugin is present.
8382
+ */
8383
+ /** The tab implementation's identity, and the key the body registers under. */
8384
+ const NATIVE_TAB_ID = "dsh-any-background:theme";
8385
+ /** The page kind: what `openTab` names and the guide entry opens. */
8386
+ const NATIVE_TAB_KIND = "danybg-theme";
8387
+ /**
8388
+ * Register the appearance pages with the native right Sidebar, if one exists.
8389
+ *
8390
+ * Call from the client `apply`. Both registrations sit inside the injected
8391
+ * scope's effects, so unloading the plugin (or HMR) withdraws them — without
8392
+ * that, the next activation would throw on the duplicate id.
8393
+ * @param ctx - client context.
8394
+ * @param opts.face - lazy accessor for the shared business face.
8395
+ * @param opts.store - the plugin's store instance, or null on a host where the
8396
+ * store module never resolved (in that case there is nothing to bind and no
8397
+ * page is registered).
8398
+ */
8399
+ function registerNativeSidebarTab(ctx, opts) {
8400
+ const { store } = opts;
8401
+ if (store === null || typeof store.getSnapshot !== "function" || typeof store.subscribe !== "function") {
8402
+ console.warn("dsh-any-background: no observable store instance on this host; the native sidebar page stays unregistered");
8403
+ return;
8404
+ }
8405
+ const locale = ctx.locale;
8406
+ const t = locale.bind(NS);
8407
+ const useStore = createStoreHook(store);
8408
+ ctx.inject?.(["sidebarRightTabs"], (scope) => {
8409
+ const tabs = scope.sidebarRightTabs;
8410
+ if (tabs === void 0 || typeof tabs.register !== "function") return;
8411
+ markNativeSidebarTabs();
8412
+ const face = opts.face();
8413
+ const ThemeBody = () => (0, react.createElement)(ThemeTab, {
8414
+ face,
8415
+ useStore,
8416
+ locale
8417
+ });
8418
+ scope.effect(() => {
8419
+ const yieldTo = () => suppressesBetterSidebar() === false && rBetterSidebar();
8420
+ if (yieldTo()) return;
8421
+ let disposeTab = null;
8422
+ const withdraw = () => {
8423
+ disposeTab?.();
8424
+ disposeTab = null;
8425
+ };
8426
+ const unsubscribeBetter = subscribe(() => {
8427
+ if (yieldTo()) withdraw();
8428
+ });
8429
+ const unsubscribeHost = subscribeHostInfo(() => {
8430
+ if (yieldTo()) withdraw();
8431
+ });
8432
+ disposeTab = tabs.register({
8433
+ id: NATIVE_TAB_ID,
8434
+ kind: NATIVE_TAB_KIND,
8435
+ title: () => t("nav"),
8436
+ guide: [{
8437
+ id: "theme",
8438
+ order: 55,
8439
+ title: () => t("nav"),
8440
+ description: () => t("guideDesc"),
8441
+ icon: SunIcon
8442
+ }]
8443
+ });
8444
+ return () => {
8445
+ unsubscribeBetter();
8446
+ unsubscribeHost();
8447
+ withdraw();
8448
+ };
8449
+ }, "dsh-any-background: native sidebar theme tab");
8450
+ scope.effect(() => ctx.slots.inject("sidebar.right.pane.tab", () => ctx.slots.register({
8451
+ name: "sidebar.right.pane.tab",
8452
+ key: NATIVE_TAB_ID
8453
+ }, ThemeBody)), "dsh-any-background: native sidebar theme body");
8454
+ });
8455
+ }
8456
+ //#endregion
8457
+ //#region src/client/header-tag.ts
8458
+ /**
8459
+ * Header-surface tagging.
8460
+ *
8461
+ * WHY A RUNTIME TAG IS NEEDED
8462
+ * The session-header dropdowns (open-in-app, session-log, Agent Team, the job
8463
+ * list, the subagent lineage tree) render their popover through the Menu/tree
8464
+ * primitives, which PORTAL the list to `document.body`. A portaled list is not a
8465
+ * DOM descendant of the header — no ancestor selector can reach it — and the
8466
+ * primitive emits no `id`/`aria-controls` back-link to its anchor either. Pure
8467
+ * CSS class matching was the remaining option, but 0.1.7 broke the shapes this
8468
+ * plugin relied on: open-in-app moved from in-place to `portal` (so the old
8469
+ * `:not(_portal)` discriminator no longer selected it), and the session row menu
8470
+ * became a dynamic `sidebar.workspaces.session.menu.item` slot whose rows are no
8471
+ * longer guaranteed to include a destructive one.
8472
+ *
8473
+ * THE SIGNAL
8474
+ * The slot renderer stamps every registered slot with `data-slot="<key>"`
8475
+ * (ui-renderer `scoped-slots.tsx`), and the session header's own slots are
8476
+ * `conversation.session.header{,.actions,.utilities,.corner,.lineage}` — present
8477
+ * on 0.1.5 through 0.1.7 alike. Those anchors live in the header subtree, so the
8478
+ * plugin can detect when a header trigger is expanded.
8479
+ *
8480
+ * WHY NOT GEOMETRY
8481
+ * A tempting link is the inline position the primitive writes on a portaled list
8482
+ * (`style="left: …; top: …"`). It was tried and REJECTED: an `align="end"` list
8483
+ * is flush to the trigger's right edge while the trigger itself sits at the far
8484
+ * right of the header, so the two boxes often do not overlap horizontally (in
8485
+ * the reported DOM the list is at `left:1012` with the trigger near x=1400). Any
8486
+ * purely geometric proximity test therefore misses real header menus.
8487
+ *
8488
+ * THE ACTUAL RULE (structural, no geometry, no timing)
8489
+ * A header dropdown can only be open while its trigger reports
8490
+ * `aria-expanded="true"`, and opening one is what mounts the popover. So a
8491
+ * popover tag is maintained as: "there is at least one expanded header trigger,
8492
+ * AND this popover is not owned by another group". Popovers are only ever
8493
+ * tagged/released on a change of that combined state, which makes the tag
8494
+ * self-consistent and idempotent.
8495
+ *
8496
+ * The one hazard is a popover that is open for an unrelated reason (a composer
8497
+ * menu, a hover card) *while* a header trigger stays expanded. Such a popover
8498
+ * is not a candidate: it is excluded by `ownsForeignSurface`, which skips any
8499
+ * popover carrying markers of a different slider group. In practice header
8500
+ * dropdowns are transient (opening one closes the others), so this is a guard
8501
+ * rather than the common path.
8502
+ *
8503
+ * @module
8504
+ */
8505
+ /** Marker attribute the stylesheet keys on. */
8506
+ const HEADER_POPOVER_ATTR = "data-dsh-any-header-popover";
8507
+ /** Slot anchors that identify the session header's own surfaces. */
8508
+ const HEADER_SLOT_SELECTOR = [
8509
+ "[data-slot=\"conversation.session.header\"]",
8510
+ "[data-slot=\"conversation.session.header.actions\"]",
8511
+ "[data-slot=\"conversation.session.header.utilities\"]",
8512
+ "[data-slot=\"conversation.session.header.corner\"]",
8513
+ "[data-slot=\"conversation.session.header.lineage\"]"
8514
+ ].join(",");
8515
+ /** Portaled popovers a header trigger can own. */
8516
+ const POPOVER_SELECTOR = "[role=\"menu\"], [role=\"tree\"], [role=\"dialog\"]";
8517
+ /** Whether any header slot anchor has an expanded trigger. */
8518
+ function headerTriggerOpen() {
8519
+ for (const anchor of document.querySelectorAll(HEADER_SLOT_SELECTOR)) if (anchor.querySelector("[aria-expanded=\"true\"]") !== null) return true;
8520
+ return false;
8521
+ }
8522
+ /** Surfaces that belong to a different slider group and must never be claimed.
8523
+ * `_denseList` without `_portal` is the in-place card-side dense menu, an
8524
+ * `_ioCard`/`_menu`-classed body-level list belongs elsewhere, and a modal is
8525
+ * its own surface. */
8526
+ function ownsForeignSurface(el) {
8527
+ if (el.getAttribute("aria-modal") === "true") return true;
8528
+ return el.parentElement !== document.body;
8529
+ }
8530
+ let observer = null;
8531
+ let raf = null;
8532
+ /** Recompute tags from the current open state. */
8533
+ function refreshHeaderPopovers() {
8534
+ const open = headerTriggerOpen();
8535
+ const candidates = [...document.querySelectorAll(POPOVER_SELECTOR)].filter((el) => !ownsForeignSurface(el));
8536
+ if (!open) {
8537
+ for (const el of document.querySelectorAll(`[${HEADER_POPOVER_ATTR}]`)) el.removeAttribute(HEADER_POPOVER_ATTR);
8538
+ return;
8539
+ }
8540
+ for (const el of candidates) el.setAttribute(HEADER_POPOVER_ATTR, "");
8541
+ for (const el of document.querySelectorAll(`[${HEADER_POPOVER_ATTR}]`)) if (!candidates.includes(el)) el.removeAttribute(HEADER_POPOVER_ATTR);
8542
+ }
8543
+ function schedule() {
8544
+ if (raf !== null) return;
8545
+ raf = window.requestAnimationFrame(() => {
8546
+ raf = null;
8547
+ refreshHeaderPopovers();
8548
+ });
8549
+ }
8550
+ /** Start tagging. Returns a teardown that stops observing and clears markings. */
8551
+ function startHeaderPopoverTagging() {
8552
+ if (typeof MutationObserver === "undefined" || typeof document === "undefined") return () => {};
8553
+ refreshHeaderPopovers();
8554
+ observer = new MutationObserver(schedule);
8555
+ observer.observe(document.body, {
8556
+ childList: true,
8557
+ subtree: true,
8558
+ attributes: true,
8559
+ attributeFilter: ["aria-expanded"]
8560
+ });
8561
+ return () => {
8562
+ observer?.disconnect();
8563
+ observer = null;
8564
+ if (raf !== null) {
8565
+ window.cancelAnimationFrame(raf);
8566
+ raf = null;
8567
+ }
8568
+ for (const el of document.querySelectorAll(`[${HEADER_POPOVER_ATTR}]`)) el.removeAttribute(HEADER_POPOVER_ATTR);
8569
+ };
8570
+ }
8571
+ //#endregion
8086
8572
  //#region src/client/index.tsx
8087
8573
  /**
8088
8574
  * dsh-any-background — browser half entry.
@@ -8183,7 +8669,7 @@ window.__ModuleLoader__.load({
8183
8669
  }, "dsh-any-background: skin dispose");
8184
8670
  const styleEl = document.createElement("style");
8185
8671
  styleEl.dataset.plugin = "dsh-any-background";
8186
- styleEl.textContent = `body[data-ds-dark-theme="dsh-any-background"]::before{content:'';position:fixed;inset:0;z-index:-1;pointer-events:none;background:radial-gradient(ellipse 80% 60% at 50% 0%,rgba(255,255,255,0.03) 0%,transparent 60%)}${SETTINGS_STYLE_RULE}${POPOVER_BLUR_RULE}${TRAJECTORY_STYLE_RULE}${INPUT_BLUR_RULE}${MOBILE_HEADER_RULE}${PANEL_TOKEN_RULE}${PANEL_BLUR_RULE}${PRODUCED_RULE}${STROKE_RULE}[data-composer-card] textarea::placeholder,[data-composer-card] input::placeholder,[data-composer-card] [contenteditable]::placeholder,[data-cordis-panel] input::placeholder,[data-cordis-panel] textarea::placeholder,.dab-input::placeholder,.dab-input textarea::placeholder,.dab-input input::placeholder{color:var(--dsh-any-placeholder,var(--dsw-alias-label-caption,#8a8f98))!important;font-style:italic;opacity:.85}`;
8672
+ styleEl.textContent = `body[data-ds-dark-theme="dsh-any-background"]::before{content:'';position:fixed;inset:0;z-index:-1;pointer-events:none;background:radial-gradient(ellipse 80% 60% at 50% 0%,rgba(255,255,255,0.03) 0%,transparent 60%)}${SETTINGS_STYLE_RULE}${POPOVER_BLUR_RULE}${TRAJECTORY_STYLE_RULE}${INPUT_BLUR_RULE}${MOBILE_HEADER_RULE}${PANEL_TOKEN_RULE}${PANEL_PROMOTION_RULE}${PANEL_BLUR_RULE}${PRODUCED_RULE}${STROKE_RULE}${HEADER_POPOVER_RULE}${EXEMPT_DEFAULT_RULE}[data-composer-card] textarea::placeholder,[data-composer-card] input::placeholder,[data-composer-card] [contenteditable]::placeholder,[data-cordis-panel] input::placeholder,[data-cordis-panel] textarea::placeholder,.dab-input::placeholder,.dab-input textarea::placeholder,.dab-input input::placeholder{color:var(--dsh-any-placeholder,var(--dsw-alias-label-caption,#8a8f98))!important;font-style:italic;opacity:.85}`;
8187
8673
  document.head.appendChild(styleEl);
8188
8674
  ctx.effect(() => () => {
8189
8675
  styleEl?.parentNode?.removeChild(styleEl);
@@ -8461,6 +8947,7 @@ window.__ModuleLoader__.load({
8461
8947
  teardownWp();
8462
8948
  }, "dsh-any-background: wp cleanup");
8463
8949
  ctx.effect(() => startBetterSidebarWatch(), "dsh-any-background: better-sidebar watch");
8950
+ ctx.effect(() => startHeaderPopoverTagging(), "dsh-any-background: header popover tagging");
8464
8951
  ctx.effect(() => ctx.on("theme/change", () => {
8465
8952
  if (rHasColor() || rBgDark() !== null || rSchemeOverride() !== "auto") {
8466
8953
  const snapshot = ctx.theme.getTheme();
@@ -8953,6 +9440,10 @@ window.__ModuleLoader__.load({
8953
9440
  face: buildFace,
8954
9441
  store: storeInstance
8955
9442
  });
9443
+ registerNativeSidebarTab(ctx, {
9444
+ face: buildFace,
9445
+ store: storeInstance
9446
+ });
8956
9447
  const navLabel = () => ctx.locale.bind(NS)("nav");
8957
9448
  const applyNavIcon = () => {
8958
9449
  const nav = document.querySelector("[role=\"dialog\"][aria-modal=\"true\"][aria-labelledby]")?.querySelector("nav");