dsh-any-background 0.3.6 → 0.3.8

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
@@ -1014,47 +1014,50 @@ window.__ModuleLoader__.load({
1014
1014
  //#region src/host-compat/channel.ts
1015
1015
  /** What this plugin supports, in upgrade order. One row = one build whose facts
1016
1016
  * were checked; several rows may share a `channel` when a newer build was
1017
- * verified as needing no change to that folder's adapter (as `0.1.7-rc.1` was) —
1017
+ * verified as needing no change to that folder's adapter (as `0.2.1-alpha.2` was) —
1018
1018
  * a row then reports `exact` instead of `line`, and no new folder appears.
1019
1019
  * Rows may ALSO share a patch line while carrying different channels, which is
1020
- * how a line says "the facts changed halfway through it" (`0.1.7-alpha.2` and
1021
- * `0.1.7-rc.1` speak for `0.1.7-alpha`, `0.1.7-rc.2` opens `0.1.7-rc`, and
1022
- * `0.2.0-rc.2` opens a second answer on a line that had one — see the note on
1023
- * `HostChannel` for why that row is keyed to the build).
1020
+ * how a line says "the facts changed halfway through it" (`0.2.0-rc.2` opens a
1021
+ * second answer on a line that had one — see the note on `HostChannel` for why
1022
+ * that row is keyed to the build).
1024
1023
  * Adding an ADAPTER means adding a channel plus a folder in
1025
1024
  * `client/host-compat/versions/`. */
1026
1025
  const SUPPORTED_RELEASES = [
1027
1026
  {
1028
- channel: "0.1.5-rc",
1029
- release: "0.1.5-rc.2"
1027
+ channel: "0.2.0-rc",
1028
+ release: "0.2.0-rc.1"
1030
1029
  },
1031
1030
  {
1032
- channel: "0.1.6-alpha",
1033
- release: "0.1.6-alpha.2"
1031
+ channel: "0.2.0-rc.2",
1032
+ release: "0.2.0-rc.2"
1034
1033
  },
1035
1034
  {
1036
- channel: "0.1.7-alpha",
1037
- release: "0.1.7-alpha.2"
1035
+ channel: "0.2.1",
1036
+ release: "0.2.1-alpha.1"
1038
1037
  },
1039
1038
  {
1040
- channel: "0.1.7-alpha",
1041
- release: "0.1.7-rc.1"
1039
+ channel: "0.2.1",
1040
+ release: "0.2.1-alpha.2"
1042
1041
  },
1043
1042
  {
1044
- channel: "0.1.7-rc",
1045
- release: "0.1.7-rc.2"
1043
+ channel: "0.2.1",
1044
+ release: "0.2.1-alpha.3"
1046
1045
  },
1047
1046
  {
1048
- channel: "0.2.0-rc",
1049
- release: "0.2.0-rc.1"
1047
+ channel: "0.2.1",
1048
+ release: "0.2.1-alpha.4"
1050
1049
  },
1051
1050
  {
1052
- channel: "0.2.0-rc.2",
1053
- release: "0.2.0-rc.2"
1051
+ channel: "0.2.1",
1052
+ release: "0.2.1-alpha.5"
1054
1053
  },
1055
1054
  {
1056
- channel: "0.2.1-alpha",
1057
- release: "0.2.1-alpha.1"
1055
+ channel: "0.2.1",
1056
+ release: "0.2.1-rc.1"
1057
+ },
1058
+ {
1059
+ channel: "0.2.1",
1060
+ release: "0.2.1-rc.2"
1058
1061
  }
1059
1062
  ];
1060
1063
  const UNKNOWN_HOST_INFO = {
@@ -1110,8 +1113,8 @@ window.__ModuleLoader__.load({
1110
1113
  /** Pick the adapter for a release, clamping to the nearest verified line when the
1111
1114
  * release is outside everything this plugin has seen.
1112
1115
  *
1113
- * A release is matched by PATCH LINE before anything else: `0.1.6-alpha.4` and a
1114
- * future `0.1.6` stable both belong to the line verified at `0.1.6-alpha.2`, and
1116
+ * A release is matched by PATCH LINE before anything else: `0.2.1-alpha.3` and a
1117
+ * future `0.2.1` stable both belong to the line verified at `0.2.1-alpha.1…rc.2`, and
1115
1118
  * handing them that adapter beats giving up — the DOM probes only cover panel
1116
1119
  * mechanics, not the surface decisions the line is known for.
1117
1120
  *
@@ -1157,14 +1160,6 @@ window.__ModuleLoader__.load({
1157
1160
  function channelOf(version) {
1158
1161
  return classifyRelease(version).channel;
1159
1162
  }
1160
- /** Prerelease tag of a release string (`alpha.2` in `0.1.6-alpha.2`), or null
1161
- * when the version is bare/undetermined. Needed because a channel is coarser
1162
- * than reality: `conversation.session.header.leading` ships in exactly one
1163
- * build within the 0.1.6-alpha line. */
1164
- function prereleaseOf(version) {
1165
- if (version === null) return null;
1166
- return /^\d+\.\d+\.\d+-([0-9A-Za-z.-]+)$/.exec(version)?.[1] ?? null;
1167
- }
1168
1163
  //#endregion
1169
1164
  //#region src/client/host-compat/versions/shared.ts
1170
1165
  /** The host's own right Sidebar wrapper. */
@@ -1183,7 +1178,7 @@ window.__ModuleLoader__.load({
1183
1178
  * renders the elected takeover beside the fallback bar (verified per tag in
1184
1179
  * `ConversationContent.tsx` and both packages' `slots.inject('conversation.composer')`)
1185
1180
  * — and each paints the capsule's own `--dsw-specific-input-major`, from
1186
- * `dsh-v0.1.5-rc.2` through `dsh-v0.2.1-alpha.1`.
1181
+ * `dsh-v0.1.5-rc.2` through `dsh-v0.2.1-alpha.2`.
1187
1182
  *
1188
1183
  * The marker sits on the wrapper, the fill on its child card:
1189
1184
  * `[data-approval-key] > div`, `[data-plan-review-key] > section`,
@@ -1268,33 +1263,22 @@ window.__ModuleLoader__.load({
1268
1263
  * fades the non-`MenuSurface` overlays and nothing else. */
1269
1264
  const MENU_FILL_TOKEN = "--dsw-specific-menu";
1270
1265
  /** Frost by filtering the element itself — the only option where the element is the
1271
- * thing that paints, and the shape every release up to `0.1.7-rc.1` presents. Both
1272
- * prefixes because Safari needs the `-webkit-` one and the cascade must not let
1273
- * them disagree. */
1266
+ * thing that paints. Both prefixes because Safari needs the `-webkit-` one and the
1267
+ * cascade must not let them disagree. */
1274
1268
  function elementFrostDecl(value) {
1275
1269
  return `-webkit-backdrop-filter:${value};backdrop-filter:${value}`;
1276
1270
  }
1277
1271
  /**
1278
1272
  * ── Where a menu's paint lives ────────────────────────────────────────────────
1279
1273
  * Verified by reading the emitting source at each tag, not inferred from a changelog.
1274
+ * The era this table now starts in (`0.2.0-rc.1`) already presents the shape below;
1275
+ * the 0.1.5 / 0.1.6 / 0.1.7-alpha line, where the menu ELEMENT painted itself and the
1276
+ * host frosted nothing at all before `0.1.7-alpha.1`, is recorded in
1277
+ * `LAYERED_MENU_SURFACE`'s first bullet rather than in an adapter, because no adapter
1278
+ * answers for it any more.
1280
1279
  *
1281
- * `dsh-v0.1.5-rc.2` … `dsh-v0.1.7-rc.1`: the menu ELEMENT paints itself. Its own
1282
- * CSS module declares `background:var(--dsw-specific-menu)` — 11 files at
1283
- * 0.1.5-rc.2/rc.3 and 0.1.6-alpha.1/alpha.2, 15 at 0.1.7-alpha.1/alpha.2, 16 at
1284
- * rc.1 (`Menu`, `MenuView`, `PopupSelectView`, `ModelSelect`, `dockkit` TabMenu,
1285
- * `stat-dialog`, `QueueDock`, `ContextMeter`, `TodoPanel`, `GoalBar`,
1286
- * `JobListAction`, `HoverCard`, `ScheduleCatalogAction`, `SubagentHeaderLineage`,
1287
- * `TeamAction`, `CordisPanel`). One token therefore paints every menu, and
1288
- * filtering the element is the arm that reaches it.
1289
- * Frost is a separate, later fact, and worth recording because it changes what
1290
- * the plugin is doing on the older tags: `--dsw-menu-backdrop-filter` has ZERO
1291
- * consumers before `0.1.7-alpha.1` (15 there, 16 at rc.1). On `0.1.5`/`0.1.6` the
1292
- * host frosts no menu at all, so the card/header blur sliders are the only reason
1293
- * a menu has a frost there — which is why the declaration goes on the element
1294
- * rather than into a host token the host does not read.
1295
- *
1296
- * `dsh-v0.1.7-rc.2` (npm `latest`) and `dsh-v0.2.0-rc.1`: `TabMenu`, `MenuView`,
1297
- * `PopupSelectView`, `ModelSelect` and `Menu` all render through the new
1280
+ * `0.1.7-rc.2` and `dsh-v0.2.0-rc.1`: `TabMenu`, `MenuView`, `PopupSelectView`,
1281
+ * `ModelSelect` and `Menu` all render through the new
1298
1282
  * `ui-primitives/src/MenuSurface.tsx` (byte-identical between the two tags),
1299
1283
  * which leaves the element unpainted and draws on a `z-index:-1` child instead
1300
1284
  * (`MenuSurface.module.css` `.material`: `background:var(--dsw-menu-surface-fill)`
@@ -1312,18 +1296,23 @@ window.__ModuleLoader__.load({
1312
1296
  * backdrop root", and the surface sets `isolation:isolate`. Filtering the
1313
1297
  * element anyway installs the backdrop root the layer was written to avoid.
1314
1298
  * The frost therefore rides `--dsw-menu-backdrop-filter`, whose consumer set is
1315
- * the same 14 stylesheets at both tags (stat dialog, queue dock, context meter,
1316
- * todo panel, goal bar, job list, `HoverCard`, `PickerPopover`/`TaskMenu`/
1317
- * `ScheduleCatalogAction`, the subagent lineage, `TeamAction`, `CordisPanel`,
1318
- * `MenuSurface` itself) — re-declaring the token reaches the frost whoever ends up
1319
- * applying it, and does not depend on any element being the painting element.
1299
+ * the same 14 stylesheets at every tag in the table (stat dialog, queue dock,
1300
+ * context meter, todo panel, goal bar, job list, `HoverCard`, `PickerPopover`/
1301
+ * `TaskMenu`/`ScheduleCatalogAction`, the subagent lineage, `TeamAction`,
1302
+ * `CordisPanel`, `MenuSurface` itself) — re-declaring the token reaches the frost
1303
+ * whoever ends up applying it, and does not depend on any element being the
1304
+ * painting element. A 15th file names the same token without consuming it:
1305
+ * `ui-theme/src/styles/gradient-shadow-text.css` DECLARES it on `body` —
1306
+ * `blur(40px) saturate(150%)`, no media query — which is exactly why writing the
1307
+ * token on a menu with a `none` fallback deletes a frost the host always had
1308
+ * (see `CARD_FROST_GATE` in `wallpaper.ts`). `MenuSurface.tsx` / `.module.css` are
1309
+ * byte-identical from rc.2 through `0.2.1-alpha.2`, and the consumer set moved only
1310
+ * in line numbers (the same 15 files at every tag in the table, checked per tag).
1320
1311
  *
1321
- * `dsh-v0.2.0-rc.2` (npm `latest`) and `dsh-v0.2.1-alpha.1`: the layer and the frost
1322
- * are the same files — `MenuSurface.tsx` and `MenuSurface.module.css` are
1323
- * byte-identical to rc.1, and every `--dsw-menu-backdrop-filter` consumer is too —
1324
- * but the sticky GROUP HEADING inside those menus changed owner. Through rc.1 the
1325
- * only grouped menu was `ModelSelect`, whose own stylesheet painted that heading
1326
- * from the menu token (`ModelSelect.module.css` `.groupTitle{position:sticky;
1312
+ * `dsh-v0.2.0-rc.2` … `dsh-v0.2.1-alpha.2`: the sticky GROUP HEADING inside those
1313
+ * menus changed owner. Through rc.1 the only grouped menu was `ModelSelect`, whose
1314
+ * own stylesheet painted that heading from the menu token
1315
+ * (`ModelSelect.module.css` `.groupTitle{position:sticky;
1327
1316
  * background:var(--dsw-specific-menu)}`), so the card slider drove it and a picked
1328
1317
  * wallpaper color tinted it. At rc.2 the rule is deleted and both grouped popups
1329
1318
  * (`ModelSelect`, `PopupSelectView`) render the new `ui-primitives/MenuGroup.tsx`,
@@ -1335,13 +1324,7 @@ window.__ModuleLoader__.load({
1335
1324
  * is the single menu element the fade no longer reaches: an untinted, near-opaque
1336
1325
  * band sitting on top of every row in the list the moment that list scrolls.
1337
1326
  */
1338
- /** The answer for the releases whose menus paint themselves: `0.1.5-rc`,
1339
- * `0.1.6-alpha`, `0.1.7-alpha`. The unresolved bucket is its own variant below. */
1340
- const LEGACY_MENU_SURFACE = {
1341
- fillTokens: [MENU_FILL_TOKEN],
1342
- frostDecl: elementFrostDecl
1343
- };
1344
- /** The answer for `0.1.7-rc` (`0.1.7-rc.2` onward) and `0.2.0-rc`. The old token
1327
+ /** The answer for `0.2.0-rc` and for `0.2.1-alpha`. The old token
1345
1328
  * stays in the list on purpose: 14 surfaces in the same claim set still paint their
1346
1329
  * fill from it — the lineage tree, `CordisPanel`, the stat dialog, `QueueDock`,
1347
1330
  * `TodoPanel`, `ContextMeter`, `GoalBar`, `JobListAction`, `HoverCard`, `TeamAction`,
@@ -1406,307 +1389,6 @@ window.__ModuleLoader__.load({
1406
1389
  "conversation.session.header.lineage"
1407
1390
  ];
1408
1391
  //#endregion
1409
- //#region src/client/host-compat/versions/v0-1-5-rc-2-3/adapter.ts
1410
- /**
1411
- * Adapter for the DSH 0.1.5-rc channel (`0.1.5-rc.2`, `0.1.5-rc.3`).
1412
- *
1413
- * Nothing here is inferred from a neighbouring release — each fact below was
1414
- * checked against the harness tags `dsh-v0.1.5-rc.2` / `dsh-v0.1.5-rc.3`.
1415
- *
1416
- * Host shape this line presents:
1417
- * `ui-sidebar-right/shell/SidebarRight.module.css`
1418
- * .panel { position:absolute; z-index:10; transform:translateX(100%); visibility:hidden }
1419
- * .panel[data-sidebar-right-open] { transform:none }
1420
- * .panel[data-sidebar-right-panel='fullscreen'] { position:fixed; inset:0; z-index:40 }
1421
- * `ui-dockkit` ships WITHOUT `TabLayout.tsx`, so `[data-dockkit-host]` and
1422
- * `[data-dockkit-empty]` are never rendered on this line.
1423
- * `ui-plugin-manager` (the page behind `PLUGIN_PAGE_FROST_RULE`) is not a package
1424
- * on this line at all, and neither tag emits `[data-plugin-panel]`.
1425
- *
1426
- * Consequence for the panel slider: the WRAPPER is what slides, so the
1427
- * `backdrop-filter` belongs on the wrapper — and because the wrapper sits inside
1428
- * an animated track with its own stacking context, it must be promoted out of it
1429
- * or the filter has no wallpaper to sample.
1430
- *
1431
- * Menus paint themselves here: 11 stylesheets declare
1432
- * `background:var(--dsw-specific-menu)` on the element (`Menu`, `MenuView`,
1433
- * `PopupSelectView`, `stat-dialog`, `ModelSelect`, `JobListAction`, `ContextMeter`,
1434
- * `ScheduleCatalogAction`, `SubagentHeaderLineage`, `TeamAction`, `CordisPanel`),
1435
- * and `--dsw-menu-backdrop-filter`
1436
- * has NO consumer on this line at all — the host frosts no menu, so the element is
1437
- * both the paint and the only place a frost can go. Hence `LEGACY_MENU_SURFACE`.
1438
- *
1439
- * @module
1440
- */
1441
- /** The wrapper carries the slide transform on this line, so it carries the blur. */
1442
- function panelFragments$7() {
1443
- return {
1444
- promotion: `${PANEL_WRAPPER}{position:fixed!important;z-index:26!important}${PANEL_FULLSCREEN}{z-index:40!important}`,
1445
- blur: panelBlurRule(PANEL_WRAPPER)
1446
- };
1447
- }
1448
- function createAdapter$7(host) {
1449
- return {
1450
- id: "0.1.5-rc.2/rc.3",
1451
- channel: "0.1.5-rc",
1452
- host,
1453
- panelFragments: panelFragments$7,
1454
- pluginPageRule: "",
1455
- menus: LEGACY_MENU_SURFACE,
1456
- surface: {
1457
- ownsSidebarGuideSurface: false,
1458
- headerSlotKeys: BASE_HEADER_SLOT_KEYS
1459
- }
1460
- };
1461
- }
1462
- //#endregion
1463
- //#region src/client/host-compat/versions/v0-1-6-alpha-1-2/adapter.ts
1464
- /**
1465
- * Adapter for the DSH 0.1.6-alpha channel (`0.1.6-alpha.1`, `0.1.6-alpha.2`).
1466
- *
1467
- * Facts checked per tag against `dsh-v0.1.6-alpha.1` / `dsh-v0.1.6-alpha.2` —
1468
- * deliberately not copied over from the 0.1.5 folder, even where the two lines
1469
- * turned out to agree. If a future 0.1.6 patch diverges, only this file moves.
1470
- *
1471
- * Host shape this line presents:
1472
- * `SidebarRight.module.css` is unchanged from the 0.1.5 line for the parts that
1473
- * matter: `.panel` still carries `position:absolute` + `transform:translateX(100%)`,
1474
- * `data-sidebar-right-open` still clears it with `transform:none`, and the panel
1475
- * width is still an inline `width` with no `--dsh-sidebar-width` variable.
1476
- * alpha.2 appends fullscreen window chrome (`.63` windows / `.77` darwin), which
1477
- * does not move the blur target, AND the `ui-plugin-manager` package — so the
1478
- * plugin-page frost below is prerelease-gated rather than channel-wide.
1479
- * `ui-dockkit` still has no `TabLayout.tsx`, so no `[data-dockkit-host]` /
1480
- * `[data-dockkit-empty]` on this line either.
1481
- *
1482
- * So the panel mechanics match 0.1.5: the wrapper slides, the wrapper takes the
1483
- * blur, and the wrapper needs promoting.
1484
- *
1485
- * Menus too — checked separately at both tags, because that is what this folder is
1486
- * for: 11 stylesheets paint the menu element from `--dsw-specific-menu` on alpha.1
1487
- * and alpha.2 alike, and `--dsw-menu-backdrop-filter` has no consumer on either, so
1488
- * `LEGACY_MENU_SURFACE` serves the whole line. The element is both the paint and
1489
- * the frost's only possible home.
1490
- *
1491
- * @module
1492
- */
1493
- /** The extra session-header anchor this line introduces: the macOS titlebar work
1494
- * added `conversation.session.header.leading`. It was deleted again at
1495
- * 0.1.7-alpha.1 with no shim, so it is unique to this line — which is why the
1496
- * channel alone is not enough to answer it and the prerelease is read.
1497
- * `alpha.1` predates it.
1498
- *
1499
- * Erring toward "included": a slot key the host never emits simply matches
1500
- * nothing, whereas a key we omit hides a header area from the plugin for the
1501
- * whole session. */
1502
- function hasLeadingHeaderSlot(version) {
1503
- const pre = prereleaseOf(version);
1504
- if (pre === null) return true;
1505
- const m = /^alpha\.(\d+)$/.exec(pre);
1506
- return m === null || Number(m[1]) >= 2;
1507
- }
1508
- /** The other thing this line splits on: `ui-plugin-manager` — the package that
1509
- * renders `设置 → 插件` and stamps `[data-plugin-panel]` — was CREATED at
1510
- * alpha.2. Until then the plugin list was a settings tab in
1511
- * `ui-settings-plugin-inventory`, which carries `[data-plugin-scope]` but no
1512
- * card list to frame, so alpha.1 has nothing for the frost rule to apply to.
1513
- *
1514
- * Same erring-toward-included convention as `hasLeadingHeaderSlot`: the rule's
1515
- * own `[data-plugin-panel]` prefix means an over-broad arm cannot paint, while
1516
- * an over-narrow one leaves a page bare for the whole session. */
1517
- function hasPluginManagerPage(version) {
1518
- const pre = prereleaseOf(version);
1519
- if (pre === null) return true;
1520
- const m = /^alpha\.(\d+)$/.exec(pre);
1521
- return m === null || Number(m[1]) >= 2;
1522
- }
1523
- function panelFragments$6() {
1524
- return {
1525
- promotion: `${PANEL_WRAPPER}{position:fixed!important;z-index:26!important}${PANEL_FULLSCREEN}{z-index:40!important}`,
1526
- blur: panelBlurRule(PANEL_WRAPPER)
1527
- };
1528
- }
1529
- function createAdapter$6(host) {
1530
- return {
1531
- id: "0.1.6-alpha.1/alpha.2",
1532
- channel: "0.1.6-alpha",
1533
- host,
1534
- panelFragments: panelFragments$6,
1535
- pluginPageRule: hasPluginManagerPage(host.version) ? PLUGIN_PAGE_FROST_RULE : "",
1536
- menus: LEGACY_MENU_SURFACE,
1537
- surface: {
1538
- ownsSidebarGuideSurface: true,
1539
- headerSlotKeys: hasLeadingHeaderSlot(host.version) ? [...BASE_HEADER_SLOT_KEYS, "conversation.session.header.leading"] : BASE_HEADER_SLOT_KEYS
1540
- }
1541
- };
1542
- }
1543
- //#endregion
1544
- //#region src/client/host-compat/versions/v0-1-7-alpha-1-2-rc-1/adapter.ts
1545
- /**
1546
- * Adapter for the DSH 0.1.7-alpha channel (`0.1.7-alpha.1`, `0.1.7-alpha.2`),
1547
- * re-verified unchanged at `0.1.7-rc.1`.
1548
- *
1549
- * This is the line the panel mechanics actually changed on — verified against
1550
- * `dsh-v0.1.6-alpha.2` → `dsh-v0.1.7-alpha.1`, where `SidebarRight.module.css`
1551
- * was rewritten (116 lines changed) and `ui-dockkit/src/components/TabLayout.tsx`
1552
- * was ADDED. Both facts were checked, not assumed.
1553
- *
1554
- * `0.1.7-rc.1` was checked the same way, by diffing every package that emits an
1555
- * anchor this plugin matches (the panel CSS, dockkit, the plugin-manager page, the
1556
- * slot renderer, the theme tokens): no anchor line moved, and the two UI changes it
1557
- * carries do not reach us —
1558
- * · `ui-plugin-manager`: a new `InstallDialog` failure screen plus compatibility
1559
- * wording. The card block (`section` → `div.groupHead` + `ul.cards`) is
1560
- * untouched, and the dialog is a body-level `Modal`, so the `:has()` valve of
1561
- * `PLUGIN_PAGE_FROST_RULE` never even has to fire for it.
1562
- * · `ui-conversation`: `.titleRow` gains `container-type:inline-size`, which makes
1563
- * it a stacking context and a containing block. Header slots sit inside that
1564
- * row, but this plugin reaches them through background/blur tokens, and the
1565
- * dropdowns they trigger portal to `document.body` (see `header-tag.ts`), so
1566
- * nothing of ours lands inside the new containment.
1567
- *
1568
- * Host shape this line presents:
1569
- * .panel { position:absolute; --dsh-dockkit-dock-layer:10; pointer-events:none }
1570
- * — no transform, no visibility, and no z-index of its own any more.
1571
- * .panel :global([data-dockkit-host='dock']),
1572
- * .panel :global([data-dockkit-empty]),
1573
- * .panel :global([data-dockkit-divider]) { transform:translateX(var(--dsh-sidebar-width)); visibility:hidden }
1574
- * .panel[data-sidebar-right-open] :global(…) { transform:none }
1575
- * — the DOCKED CHILDREN are what slide.
1576
- * .panel[data-sidebar-right-panel='fullscreen'] { --dsh-dockkit-dock-layer:40 }
1577
- * — fullscreen no longer flips the panel to `position:fixed`; it raises a
1578
- * layer variable, and the width becomes `100vw` on the absolute panel.
1579
- *
1580
- * Consequences encoded below:
1581
- * 1. The blur belongs on the children. On the wrapper it frosted a stationary
1582
- * frame — "the blur stays put while the sidebar moves".
1583
- * 2. The promotion must NOT be emitted. `position:fixed` detaches the panel from
1584
- * the track the host animates, which is the whole reason 2 the blur stopped
1585
- * travelling. The host's own sheet documents the content root as owning "no
1586
- * stacking context or transform", so `backdrop-filter` resolves against the
1587
- * page and samples the wallpaper unaided.
1588
- * 3. `[data-dockkit-empty]` must be included alongside `[data-dockkit-host]`: a
1589
- * pane with no tabs renders the empty host instead, and keying on `host`
1590
- * alone mis-detects that (transient) shape.
1591
- *
1592
- * Also true of this line, recorded so the next reader does not re-derive it:
1593
- * · `conversation.session.header.leading` was DELETED here with no shim, so this
1594
- * folder deliberately does not list it (see the 0.1.6 folder).
1595
- * · `--dsw-specific-menu` was severed from `--dsw-alias-bg-layer-3` and became a
1596
- * literal translucent rgba, plus a `html[data-platform='darwin']` re-override.
1597
- * The plugin already samples and writes that token directly, so the severance
1598
- * needs no adapter of its own — but note the consequence: `toRgba()` REPLACES
1599
- * alpha, so the header slider now overrides the host's own 0.58 rather than
1600
- * fading an opaque layer. Intended (the slider means "this opacity"), worth
1601
- * knowing if the 0.1.7 header ever looks too thin.
1602
- * · `[data-chat-flow]` is stamped on a nested `ChatGroupSeat` content element
1603
- * too. Checked against every use in this plugin: the stroke group is inherited
1604
- * so a second match changes nothing, and the view-card discovery takes the
1605
- * first match, which is still the real flow root. No arm needed.
1606
- * · menus still paint THEMSELVES on this line — 15 stylesheets declare
1607
- * `background:var(--dsw-specific-menu)` on the element at alpha.1/alpha.2 and 16
1608
- * at rc.1, so `LEGACY_MENU_SURFACE` is the answer and the `MenuSurface` child
1609
- * layer does not exist yet. Worth noting what DID arrive here:
1610
- * `--dsw-menu-backdrop-filter` goes from zero consumers at 0.1.6-alpha.2 to 15 at
1611
- * alpha.1 (16 at rc.1), i.e. this is the line where the host started frosting
1612
- * menus on the element itself. The plugin's own filter sits on the same element
1613
- * and wins the cascade by sheet order, so the element is still the only place a
1614
- * frost can go — which is exactly why re-declaring the HOST's token instead
1615
- * would be inert on this line and correct on the next one.
1616
- *
1617
- * @module
1618
- */
1619
- function panelFragments$5() {
1620
- return {
1621
- promotion: "",
1622
- blur: panelBlurRule(DOCKKIT_SLIDERS)
1623
- };
1624
- }
1625
- function createAdapter$5(host) {
1626
- return {
1627
- id: "0.1.7-alpha.1/alpha.2/rc.1",
1628
- channel: "0.1.7-alpha",
1629
- host,
1630
- panelFragments: panelFragments$5,
1631
- pluginPageRule: PLUGIN_PAGE_FROST_RULE,
1632
- menus: LEGACY_MENU_SURFACE,
1633
- surface: {
1634
- ownsSidebarGuideSurface: true,
1635
- headerSlotKeys: BASE_HEADER_SLOT_KEYS
1636
- }
1637
- };
1638
- }
1639
- //#endregion
1640
- //#region src/client/host-compat/versions/v0-1-7-rc-2/adapter.ts
1641
- /**
1642
- * Adapter for the DSH `0.1.7-rc` channel, verified at `0.1.7-rc.2` (npm `latest`).
1643
- *
1644
- * Same patch line as `v0-1-7-alpha-1-2-rc-1`, DIFFERENT ANSWERS — which is why this
1645
- * folder exists at all rather than another row on that one. Everything the alpha
1646
- * folder says about panel mechanics still holds here (checked by diffing the panel
1647
- * CSS, dockkit, the slot renderer and the plugin-manager page tag to tag: no anchor
1648
- * line moved, and `TabLayout.tsx` / `SidebarRight.module.css` are unchanged from
1649
- * rc.1). What moved is where a MENU gets its paint.
1650
- *
1651
- * Two host changes reach this plugin:
1652
- *
1653
- * 1. `ui-primitives/src/MenuSurface.tsx` is NEW, and `TabMenu`, `MenuView`,
1654
- * `PopupSelectView`, `ModelSelect` and `Menu` all render through it. The element
1655
- * that carries `role="menu"` no longer paints anything: a `z-index:-1` child
1656
- * layer does, reading `--dsw-menu-surface-fill` for the fill and
1657
- * `--dsw-menu-backdrop-filter` for the frost. And `design-platform.css` flipped
1658
- * its alias — `--dsw-specific-menu:var(--dsw-menu-surface-fill)`, where every
1659
- * earlier release declared it the other way round. Consequence, read off those
1660
- * two files rather than guessed: retinting `--dsw-specific-menu` alone now fades
1661
- * nothing on a shared menu, so the card and header opacity sliders go inert on
1662
- * exactly the surfaces they were built for. `LAYERED_MENU_SURFACE` is the answer.
1663
- * Filtering the menu element would also have kept working, sort of — but the
1664
- * child layer's own comment says it exists so that no ANCESTOR becomes a
1665
- * backdrop root, so that arm stops being offered here.
1666
- *
1667
- * 2. `ui-settings-general/src/client/SettingsRoot.tsx` now renders the dialog
1668
- * through `createPortal`, beside `#root`, instead of inline in the column. It
1669
- * costs this plugin nothing, recorded so nobody re-derives it:
1670
- * `SETTINGS_PANEL_SEL` tests the dialog's own identity (its hashed `_panel`
1671
- * class plus `role`/`aria-modal`/`aria-labelledby`, all unchanged), never an
1672
- * ancestor, so the retint and the blur land the same way one level higher up.
1673
- * The one rule that DID care — `PART_BLUR_RULE`'s
1674
- * `:has([role="dialog"]){isolation:auto}` valve, written because a modal mounted
1675
- * inside a column kept its z-index local to that column — now never matches, and
1676
- * that is fine: the modal it existed to release is no longer inside a column
1677
- * either, so the trap and the valve left together.
1678
- *
1679
- * Still `0.1.7` facts, restated for the reader who only opens this folder:
1680
- * · the panel is a stationary frame; the dockkit children slide, so the blur goes
1681
- * on the children and nothing is promoted.
1682
- * · `conversation.session.header.leading` is gone; `--dsw-specific-menu` is a
1683
- * literal translucent rgba, not an alias of a layer token.
1684
- * · the shimmer primitive is STILL `data-text-shimmer` (checked: `data-shimmer`
1685
- * does not exist at this tag) — that rename belongs to 0.2.0.
1686
- *
1687
- * @module
1688
- */
1689
- function panelFragments$4() {
1690
- return {
1691
- promotion: "",
1692
- blur: panelBlurRule(DOCKKIT_SLIDERS)
1693
- };
1694
- }
1695
- function createAdapter$4(host) {
1696
- return {
1697
- id: "0.1.7-rc.2",
1698
- channel: "0.1.7-rc",
1699
- host,
1700
- panelFragments: panelFragments$4,
1701
- pluginPageRule: PLUGIN_PAGE_FROST_RULE,
1702
- menus: LAYERED_MENU_SURFACE,
1703
- surface: {
1704
- ownsSidebarGuideSurface: true,
1705
- headerSlotKeys: BASE_HEADER_SLOT_KEYS
1706
- }
1707
- };
1708
- }
1709
- //#endregion
1710
1392
  //#region src/client/host-compat/versions/v0-2-0-rc-1/adapter.ts
1711
1393
  /**
1712
1394
  * Adapter for the DSH `0.2.0-rc` channel, verified at `0.2.0-rc.1`.
@@ -1848,9 +1530,10 @@ window.__ModuleLoader__.load({
1848
1530
  };
1849
1531
  }
1850
1532
  //#endregion
1851
- //#region src/client/host-compat/versions/v0-2-1-alpha-1/adapter.ts
1533
+ //#region src/client/host-compat/versions/v0-2-1/adapter.ts
1852
1534
  /**
1853
- * Adapter for the DSH `0.2.1-alpha` line, verified at `0.2.1-alpha.1`.
1535
+ * Adapter for the DSH `0.2.1` patch line, verified at `0.2.1-alpha.1`,
1536
+ * `alpha.2`, `alpha.3`, `alpha.4`, `alpha.5`, `rc.1` and `rc.2`.
1854
1537
  *
1855
1538
  * A new MINOR line opened with a prerelease, and the answer is the `0.2.0-rc.2`
1856
1539
  * folder's — which is a statement this file has to earn, because "same as the
@@ -1874,6 +1557,33 @@ window.__ModuleLoader__.load({
1874
1557
  * · `TextShimmer` still marks its root `data-shimmer`, so the stroke exemption
1875
1558
  * written for rc.1 stands.
1876
1559
  *
1560
+ * Re-checked again across the rest of the line (`alpha.3` … `rc.2`, `git diff
1561
+ * <tag> rc.2` on every file above): the menu token contract, both menu stylesheets
1562
+ * (`MenuSurface`, `MenuGroup`), `gradient-shadow-text.css` / `base.css`,
1563
+ * `AppFrame.*`, `ConversationRoot`, `TaskMenu`, `preload-windows` and the five
1564
+ * header slot keys are unchanged through `rc.2`. Two deltas, neither a fact here:
1565
+ * · `ui-sidebar-right/SidebarRight.module.css` gained `overflow:clip` on the
1566
+ * panel wrapper (hidden docked items no longer widen the frame's scrollable
1567
+ * overflow; fixed floating children still escape). The slide owner and the
1568
+ * element the frost rides are untouched, and the plugin never promotes the
1569
+ * wrapper on this line.
1570
+ * · `PluginManagerPage.tsx` was refactored and now emits `data-plugin-scope`
1571
+ * groups for `global`, `preset` and `more`. The anchors this plugin frames
1572
+ * (`[data-plugin-panel]`, `[data-plugin-scope] > ul`, `[data-plugin-loading] >
1573
+ * ul`) are still all there, and the rule is scope-value agnostic.
1574
+ *
1575
+ * Re-checked at `0.2.1-alpha.2`, same list: `design-platform.css` still byte-identical
1576
+ * (`--dsw-menu-surface-fill` / `--dsw-specific-menu` / `--dsw-alias-menu-group-header-fill`
1577
+ * on both scheme blocks); `MenuSurface.tsx`/`.module.css` byte-identical (blob hashes
1578
+ * match); the `--dsw-specific-menu` and `--dsw-menu-backdrop-filter` file sets are the
1579
+ * same 15 at both tags (the frost side being 14 consumers plus the `body` declaration
1580
+ * in `gradient-shadow-text.css`);
1581
+ * `[data-sidebar-right-panel]` and the dockkit slide unchanged; the five header slot
1582
+ * keys unchanged; `AppFrame.tsx` / `AppFrame.module.css` untouched. The plugin-manager
1583
+ * page gained a third `[data-plugin-scope]` group (`data-plugin-group="more"`) inside
1584
+ * the same `[data-plugin-panel]`, which the existing rule already frames, so no new
1585
+ * fact lands here.
1586
+ *
1877
1587
  * What did move, and why none of it needs a fact from this folder:
1878
1588
  *
1879
1589
  * 1. `ui-layout/AppFrame` gains a second grid row, `div.bottomRow[data-shell-bottom]`
@@ -1895,6 +1605,38 @@ window.__ModuleLoader__.load({
1895
1605
  * looked for a plugin's `invariant` export. This plugin's no-op companion was
1896
1606
  * therefore already dead weight here, and it goes rather than claiming a peer
1897
1607
  * version for a package the host no longer ships.
1608
+ * 5. `ui-theme/src/styles/base.css` restructured the font tokens: the built-in stacks
1609
+ * moved to `--dsh-font-family-text-default` / `-code-default`, and the host now
1610
+ * composes the tokens itself on `body` —
1611
+ * `--dsw-font-family: var(--dsh-font-family-text, …), …` — fed by the per-role user
1612
+ * font lists `ThemePresenter` publishes (`--dsh-font-family-text` / `-code` /
1613
+ * `-terminal`, new at this tag). That is a NEW `body`-level declaration of the very
1614
+ * token this plugin's custom-font feature writes, at the same specificity as its
1615
+ * rule — so which one wins became a question of stylesheet order. `applyFontFace`
1616
+ * now asserts its `--dsw-font-family` with `!important`, the way the token sheet
1617
+ * already does, so a host re-injecting its base styles cannot quietly drop the
1618
+ * picked font. Code typography (`--ds-font-family-code`, plus the new
1619
+ * `--dsh-code-font-size` axis) stays untouched — this plugin never owned it.
1620
+ * 6. AT `0.2.1-alpha.2`, `ui-jobs/JobListAction` moved its background-task list out of
1621
+ * the header row and into a `createPortal(…, document.body)` painted
1622
+ * `position:fixed; z-index:100` — and the list is a bare `<ul>` with NO `role`, so
1623
+ * `header-tag.ts`'s `[role="menu"|"tree"|"dialog"]` probe can never claim it and the
1624
+ * `ul[class*="_menu"]` class shape is the only thing that still styles it.
1625
+ * Through `0.2.1-alpha.1` the same list is IN-PLACE — `{open ? <ul className={css.menu}
1626
+ * style={{left: menuShift}}/> : null}` with `.menu{position:absolute; top:calc(100% +
1627
+ * 5px); z-index:100}` under a `.root{position:relative}` — which is the exact DOM the
1628
+ * reported bug was captured from (`style="left: 0px;"`, no `top`, no role). The portal
1629
+ * is therefore what HID the bug on the newest tag, not what fixed it: an absolutely
1630
+ * positioned `z-index:100` child of the header row is only buried by the page once
1631
+ * something makes that row a stacking context, and the thing that did was this
1632
+ * plugin's own 「标题栏」 frost. Nothing here is a per-release fact to route on —
1633
+ * `0.2.1-alpha.2` merely swapped which header component gets caught by it, because
1634
+ * `ui-schedule/TaskMenu` (registered into `…header.utilities` on every release in
1635
+ * range) is `role="menu"` + `position:absolute; z-index:100` in place whenever the
1636
+ * Menu primitive is not in portal mode. The compensation lives with the injury: see
1637
+ * the `z-index` note on `HEADER_BAR_RULE` in `wallpaper.ts`. (Checked and rejected:
1638
+ * `ui-conversation/ContextMeter`'s `position:fixed` panel is NOT row chrome —
1639
+ * `InputBar.tsx` mounts it in the composer, so the row never re-anchored it.)
1898
1640
  *
1899
1641
  * @module
1900
1642
  */
@@ -1906,8 +1648,8 @@ window.__ModuleLoader__.load({
1906
1648
  }
1907
1649
  function createAdapter$1(host) {
1908
1650
  return {
1909
- id: "0.2.1-alpha.1",
1910
- channel: "0.2.1-alpha",
1651
+ id: "0.2.1-alpha.1…rc.2",
1652
+ channel: "0.2.1",
1911
1653
  host,
1912
1654
  panelFragments: panelFragments$1,
1913
1655
  pluginPageRule: PLUGIN_PAGE_FROST_RULE,
@@ -1963,13 +1705,9 @@ window.__ModuleLoader__.load({
1963
1705
  //#endregion
1964
1706
  //#region src/client/host-compat/versions/registry.ts
1965
1707
  const ADAPTERS = {
1966
- "0.1.5-rc": createAdapter$7,
1967
- "0.1.6-alpha": createAdapter$6,
1968
- "0.1.7-alpha": createAdapter$5,
1969
- "0.1.7-rc": createAdapter$4,
1970
1708
  "0.2.0-rc": createAdapter$3,
1971
1709
  "0.2.0-rc.2": createAdapter$2,
1972
- "0.2.1-alpha": createAdapter$1,
1710
+ "0.2.1": createAdapter$1,
1973
1711
  unknown: createAdapter
1974
1712
  };
1975
1713
  /** Pick the adapter for a resolved host. `host.channel` is already narrowed to
@@ -2372,7 +2110,7 @@ window.__ModuleLoader__.load({
2372
2110
  *
2373
2111
  * Everything downstream asks a QUESTION ("who owns the guide surface?", "which CSS
2374
2112
  * arm applies?") and gets an answer from the current adapter. No consumer names a
2375
- * release. That is what keeps the per-version folders honest: when 0.1.8 needs a
2113
+ * release. That is what keeps the per-version folders honest: when a newer line needs a
2376
2114
  * new behaviour, a consumer changes by reading a new adapter field, not by growing
2377
2115
  * another version comparison.
2378
2116
  *
@@ -2434,12 +2172,24 @@ window.__ModuleLoader__.load({
2434
2172
  * dropdowns are transient (opening one closes the others), so this is a guard
2435
2173
  * rather than the common path.
2436
2174
  *
2175
+ * THE SURFACE MARKER IS PART OF THE CANDIDATE SET (`0.2.1-alpha.3` and up).
2176
+ * That release moved the background-job list onto the shared `MenuSurface`, whose
2177
+ * root carries NO role — the `<ul>` inside it is now a paint-free `.menuBody`
2178
+ * (`JobListAction.module.css` dropped its own `background`/`backdrop-filter`), and
2179
+ * the fill plus the frost live on the surface's `.material` child. Keyed on roles
2180
+ * alone the tagger went blind exactly when the paint moved one level up, and the
2181
+ * job list kept the stock look. `data-menu-material` is the surface's own semantic
2182
+ * marker (`MenuSurface.tsx`), so the tag rides it: the tokens land on the surface
2183
+ * root, which is the nearest ancestor of `.material`, and inherit down into it.
2184
+ * The pre-migration shape (a self-painting `<ul class*="_menu">`) stays matched by
2185
+ * the header group's class-shape arm, so both eras are styled by one rule set.
2186
+ *
2437
2187
  * @module
2438
2188
  */
2439
2189
  /** Marker attribute the stylesheet keys on. */
2440
2190
  const HEADER_POPOVER_ATTR = "data-dsh-any-header-popover";
2441
2191
  /** Portaled popovers a header trigger can own. */
2442
- const POPOVER_SELECTOR = "[role=\"menu\"], [role=\"tree\"], [role=\"dialog\"]";
2192
+ const POPOVER_SELECTOR = "[role=\"menu\"], [role=\"tree\"], [role=\"dialog\"], [data-menu-material]";
2443
2193
  /** Which session-header slot anchors to watch — supplied by the host adapter,
2444
2194
  * because the set is not the same on every release: 0.1.6-alpha.2 registers a
2445
2195
  * sixth (`.leading`) that neither the line before nor the line after has.
@@ -3943,6 +3693,14 @@ window.__ModuleLoader__.load({
3943
3693
  const SETTINGS_STYLE_RULE = `${SETTINGS_PANEL_SEL}{background:var(--dsh-any-bg-settings-surface,var(--dsw-alias-bg-layer-2));backdrop-filter:var(--dsh-any-blur-settings,none);--dsw-alias-bg-layer-1:var(--dsh-any-bg-settings-layer-1);--dsw-alias-bg-layer-2:var(--dsh-any-bg-settings-layer-2);--dsw-alias-bg-layer-3:var(--dsh-any-bg-settings-layer-3)}${SETTINGS_PANEL_SEL} .dab-card{backdrop-filter:var(--dsh-any-blur-card-panels,none);-webkit-backdrop-filter:var(--dsh-any-blur-card-panels,none)}`;
3944
3694
  const CARD_BLUR_VAR = "--dsh-any-blur-card-panels";
3945
3695
  const POPOVER_MENU_SEL = `[role="menu"]:not([data-dockkit-tab-menu])`;
3696
+ /** The `MenuSurface` root's own marker (`ui-primitives/MenuSurface.tsx`), present on
3697
+ * every release whose menus paint through that layer. It joins the card group because
3698
+ * the role set cannot reach all of them: `ModelSelect` renders its model pane as
3699
+ * `role="group"` (ModelSelect.tsx:486), so keyed on `[role="menu"]` alone that card
3700
+ * fades with the slider — its fill comes off `body` — and never frosts with it.
3701
+ * The dockkit exclusion has to be spelled again here: a tab menu IS a `MenuSurface`
3702
+ * now, and it stays owned by its own layer token, not by the menu one. */
3703
+ const MENU_MATERIAL_SEL = `[data-menu-material]:not([data-dockkit-tab-menu])`;
3946
3704
  const POPOVER_OTHER_SEL = "[role=\"listbox\"],body > [role=\"tree\"],body > [role=\"dialog\"]:not([aria-modal=\"true\"])";
3947
3705
  /** The frost declaration this release's menus want, driven by the named plugin var.
3948
3706
  * Every menu-shaped surface group asks through here, so the card group, the header
@@ -3950,6 +3708,34 @@ window.__ModuleLoader__.load({
3950
3708
  function menuFrost(blurVar) {
3951
3709
  return hostAdapter().menus.frostDecl(`var(${blurVar},none)`);
3952
3710
  }
3711
+ /** The `<html>` classes that let the two menu groups write their frost token at all.
3712
+ *
3713
+ * WHY A GATE IS NEEDED AND `:where()` IS ENOUGH: the host declares its own menu frost
3714
+ * on `body` (`ui-theme/src/styles/gradient-shadow-text.css` —
3715
+ * `--dsw-menu-backdrop-filter: blur(40px) saturate(150%)`, no media query), and every
3716
+ * menu frosts from that INHERITED value, whether it filters itself (the background-job
3717
+ * `<ul>`) or hands the value to a `MenuSurface` `.material` child. An element's own
3718
+ * declaration always beats what it inherits, so writing the token with a `none`
3719
+ * fallback does not "add no blur" at slider 0 — it DELETES the host's frost. The
3720
+ * result is a faded menu with nothing behind it: the page shows through it crisp and
3721
+ * bright, so the popup stops reading as a raised surface at all. (This is a DIFFERENT
3722
+ * defect from the reported "the background-job list is under the conversation" one,
3723
+ * which is a stacking-context injury the row takes with its own frost — see the
3724
+ * `z-index` note on `HEADER_BAR_RULE`. Both were live at once on `0.2.0-rc.1`.)
3725
+ * `none` stays correct for the exempt group: `applyExemptDefaults` pins those
3726
+ * surfaces at alpha 1 alongside the cleared frost, so they are opaque by design.
3727
+ *
3728
+ * `:where()` keeps the gate free: this plugin's menu cascade is ORDERED (card group →
3729
+ * header group → exemptions) at equal specificity, and a class prefix with real
3730
+ * specificity would have lifted the card group over the exemption it must lose to. */
3731
+ const CARD_FROST_GATE = "dab-card-frost";
3732
+ const HEADER_FROST_GATE = "dab-header-frost";
3733
+ /** Prefix every arm of a selector list with a `:where()` gate. A joined list is one
3734
+ * selector per entry, so prefixing the string once would gate only the first arm and
3735
+ * leave the rest always on. */
3736
+ function gated(gate, selectors) {
3737
+ return selectors.split(",").map((sel) => `:where(.${gate}) ${sel}`).join(",");
3738
+ }
3953
3739
  /** Declare EVERY token this release's menus can paint their fill from with the same
3954
3740
  * value. One slider, one value, every layer reached — and a token the host does not
3955
3741
  * read is inert, so this is safe to emit on a line that only needs one.
@@ -3963,7 +3749,7 @@ window.__ModuleLoader__.load({
3963
3749
  return hostAdapter().menus.fillTokens.map((name) => `${name}:${value}`).join(";");
3964
3750
  }
3965
3751
  function popoverBlurRule() {
3966
- return `${POPOVER_MENU_SEL}{${menuFrost(CARD_BLUR_VAR)}}${POPOVER_OTHER_SEL}{${elementFrostDecl(`var(${CARD_BLUR_VAR},none)`)}}`;
3752
+ return `${gated(CARD_FROST_GATE, `${POPOVER_MENU_SEL},${MENU_MATERIAL_SEL}`)}{${menuFrost(CARD_BLUR_VAR)}}${POPOVER_OTHER_SEL}{${elementFrostDecl(`var(${CARD_BLUR_VAR},none)`)}}`;
3967
3753
  }
3968
3754
  const INPUT_FROST_CLASS = "dab-input-frost";
3969
3755
  const TAKEOVER_FROST_CLASS = "dab-takeover-frost";
@@ -4043,8 +3829,48 @@ window.__ModuleLoader__.load({
4043
3829
  }
4044
3830
  /** The bar's paint. The variables are written by `applyHeaderBar`; the class is
4045
3831
  * only ever added when they are, so a host the plugin is not theming keeps its
4046
- * own header untouched. */
4047
- const HEADER_BAR_RULE = `.${HEADER_BAR_CLASS}{background:var(--dsh-any-op-header-bar,transparent)!important;backdrop-filter:var(--dsh-any-blur-header-bar,none);-webkit-backdrop-filter:var(--dsh-any-blur-header-bar,none)}`;
3832
+ * own header untouched.
3833
+ *
3834
+ * WHY THE ROW NEEDS A `z-index`: the 「标题栏」 card frosts the row, and a
3835
+ * `backdrop-filter` other than `none` makes the element a STACKING CONTEXT — which the
3836
+ * row cannot avoid wanting, since the frost is the feature. The row is not an empty
3837
+ * strip: the host mounts its header chrome INTO it (`conversation.session.header
3838
+ * .actions|.utilities|.corner`), and that chrome raises its overlays from inside the
3839
+ * row:
3840
+ * · `ui-jobs/JobListAction.module.css` — `.root{position:relative}` +
3841
+ * `.menu{position:absolute; top:calc(100% + 5px); z-index:100}` on every release
3842
+ * through `0.2.1-alpha.1` (the `{open ? <ul style={{left: menuShift}}/> : null}`
3843
+ * the bug was captured from; `0.2.1-alpha.2` portaled it to `body`, which is why
3844
+ * that build hid the symptom rather than fixed it);
3845
+ * · `ui-schedule/TaskMenu.module.css` — the list root carries `role="menu"` and is
3846
+ * `position:absolute; top:calc(100% + 4px); z-index:100` whenever the primitive is
3847
+ * NOT in portal mode, and it sits in `…header.utilities` on every release in range.
3848
+ * Once the row is a stacking context those `z-index:100`s stop being page-level values:
3849
+ * they only order the overlays WITHIN the row, and the row itself then paints at its
3850
+ * own level — under the conversation body, the composer seat and `.root`'s background,
3851
+ * all of which follow it in the column. The menu was not demoted by the host; it was
3852
+ * confined by us, and confining it is what "the list is behind the chat" meant.
3853
+ *
3854
+ * THE COMPENSATION IS THE ROW'S OWN `z-index`, not a release of the frost: a `:has()`
3855
+ * valve dropping the blur while an overlay is open was the first attempt, and it
3856
+ * visibly re-tints the whole title bar every time anyone opens a header menu — the
3857
+ * part's two sliders appear to change state on their own. Raising the row instead keeps
3858
+ * the frost steady and restores the order the host assumed. The value has to clear the
3859
+ * in-column chrome the row hangs over — `.seat` 9 (composer), the back-to-bottom control
3860
+ * 8, the footer 7, `.tabs` 1 (all measured from `ConversationRoot.module.css`) — while
3861
+ * staying UNDER every page-level surface, because the centre column is not itself a
3862
+ * stacking context unless the background blur is on: portaled menus/tooltips are 100,
3863
+ * modal overlays 1000, stat and context panels 1100 (`Menu.module.css`, `Modal.module.css`,
3864
+ * `ContextMeter.module.css` — the last lives in `InputBar`, NOT in this row, so the row's
3865
+ * filter never had to be blamed for its anchoring). 10 sits in that gap: above 9, far
3866
+ * below 100, so a portaled list still covers the title row exactly as the host intends.
3867
+ *
3868
+ * `z-index` needs no `position` here and none is written: the row is a flex ITEM of
3869
+ * `ConversationRoot .root{display:flex;flex-direction:column}` on every release in range,
3870
+ * and z-index applies to flex items as they are. Writing `position:relative` would also
3871
+ * make the row a containing block — and deciding a host box's positioning is the exact
3872
+ * race this plugin lost once already (issue #24). */
3873
+ const HEADER_BAR_RULE = `.${HEADER_BAR_CLASS}{background:var(--dsh-any-op-header-bar,transparent)!important;backdrop-filter:var(--dsh-any-blur-header-bar,none);-webkit-backdrop-filter:var(--dsh-any-blur-header-bar,none);z-index:10}`;
4048
3874
  /** The 「标题栏」card's paint: the card's material at the card's opacity, empty
4049
3875
  * where the plugin is not theming. The conversation header row, the Windows
4050
3876
  * caption band, and — through the desktop preload's probe — the native
@@ -4079,6 +3905,70 @@ window.__ModuleLoader__.load({
4079
3905
  const CAPTION_FRAME_CLASS = "dab-frame-caption";
4080
3906
  const FLAT_CENTER_CLASS = "dab-center-flat";
4081
3907
  const WCO_PROBE_SEL = "body > span[style*=\"--dsw-specific-sidebar-fill\"]";
3908
+ /** Marks the sidebar's own caption controls — the toggle and, while collapsed,
3909
+ * the new-session button. Both are `position:fixed` and float over the band, so
3910
+ * they inherit the band's ink instead of the sidebar's. */
3911
+ const CAPTION_INK_ATTR = "data-dab-caption-ink";
3912
+ /** The strip's chrome ink, and its collapsed-state variant.
3913
+ *
3914
+ * WHY THIS IS NOT THE LABEL INK: the plugin flips the label tokens against the
3915
+ * PICKED PALETTE (white on a dark pick), but the caption band is that palette at
3916
+ * the sidebar/header-bar opacity OVER the wallpaper. At a low opacity the strip
3917
+ * is mostly wallpaper, so white ink lands on a light band and the toggle
3918
+ * disappears (reported on the desktop build, on a 22%-opacity sidebar over a bright
3919
+ * wallpaper). The strip therefore derives its own ink from what it is actually
3920
+ * painted on: past ~85% opacity the surface dominates and the palette direction
3921
+ * is the truth; below it the wallpaper's brightness verdict decides.
3922
+ *
3923
+ * Returned as `{ ink, collapsedInk }` because the strip's material changes with
3924
+ * the sidebar: expanded it is the sidebar segment, collapsed it is the 「标题栏」
3925
+ * card's band. `null` means "no verdict and no palette direction" — leave the
3926
+ * host's ink alone. */
3927
+ function captionInkPair() {
3928
+ const verdict = rBgDark();
3929
+ const inkIsWhite = rSchemeOverride() === "auto" ? rHasColor() ? rColorScheme() === "dark" : verdict : rScheme() === "dark";
3930
+ if (inkIsWhite === null || inkIsWhite === void 0) return null;
3931
+ const stripInk = (alpha) => {
3932
+ return (verdict === null || verdict === void 0 || alpha >= .85 ? inkIsWhite : verdict) ? "#fff" : "#000";
3933
+ };
3934
+ return {
3935
+ ink: stripInk(rOps().sidebar),
3936
+ collapsedInk: stripInk(rHeaderBarOpacity())
3937
+ };
3938
+ }
3939
+ /** The fixed caption controls of the sidebar root, re-inked (and released) as the
3940
+ * strip's material changes. Throttled: this runs on every caption re-apply, and
3941
+ * the header-bar slider drags through that path frame by frame. */
3942
+ let captionInkEls = [];
3943
+ let captionInkSidebar = null;
3944
+ let captionScanAt = 0;
3945
+ function releaseCaptionInk() {
3946
+ for (const el of captionInkEls) {
3947
+ el.style.removeProperty("--dsw-alias-label-secondary");
3948
+ el.removeAttribute(CAPTION_INK_ATTR);
3949
+ }
3950
+ captionInkEls = [];
3951
+ }
3952
+ function applyCaptionInk(enabled) {
3953
+ if (!enabled || sidebarEl === null) {
3954
+ releaseCaptionInk();
3955
+ return;
3956
+ }
3957
+ const now = typeof performance === "undefined" ? Date.now() : performance.now();
3958
+ if (captionInkEls.some((el) => !el.isConnected) || sidebarEl !== captionInkSidebar || now - captionScanAt > 250) {
3959
+ captionScanAt = now;
3960
+ captionInkSidebar = sidebarEl;
3961
+ releaseCaptionInk();
3962
+ for (const btn of sidebarEl.querySelectorAll("button")) {
3963
+ if (getComputedStyle(btn).position !== "fixed") continue;
3964
+ captionInkEls.push(btn);
3965
+ }
3966
+ }
3967
+ for (const el of captionInkEls) {
3968
+ el.style.setProperty("--dsw-alias-label-secondary", "var(--dsh-any-caption-ink, #fff)");
3969
+ el.setAttribute(CAPTION_INK_ATTR, "");
3970
+ }
3971
+ }
4082
3972
  let wcoStyleEl = null;
4083
3973
  function ensureWcoStyle() {
4084
3974
  if (wcoStyleEl?.isConnected) return wcoStyleEl;
@@ -4103,16 +3993,57 @@ window.__ModuleLoader__.load({
4103
3993
  * plugin's own discovery (`discoverParts`), not a guessed hashed class name. The
4104
3994
  * probe selector is a verified shape: an inline style that mentions the sidebar
4105
3995
  * token on a body-level span is the desktop preload's caption probe and nothing
4106
- * else, so no match means no desktop caption and the rule is inert. */
3996
+ * else, so no match means no desktop caption and the rule is inert.
3997
+ *
3998
+ * THE BAND IS TWO REGIONS, NOT ONE. `AppFrame.module.css` paints the whole caption
3999
+ * with one box (`.frame::before`, `inset: 0 0 auto` at `--dsh-windows-titlebar-height`)
4000
+ * in `--dsw-specific-sidebar-fill` — the SIDEBAR's token — while the sidebar column
4001
+ * below it (`AppFrame.tsx`'s first grid track, `${cols.sidebar}px`) is the surface
4002
+ * that slider actually owns. The 标题栏 card took the band over in 0.3.6, which made
4003
+ * the strip above the sidebar follow the session title row instead of the sidebar. So
4004
+ * the band is now cut at the sidebar's own width:
4005
+ * · `::before` keeps the host's box (and its `-webkit-app-region: drag`) and is
4006
+ * CLIPPED to everything right of that width — `clip-path:inset(0 0 0 …)` clips
4007
+ * the paint AND the backdrop-filter's effect, so the region under the cut is
4008
+ * genuinely untouched rather than frosted twice.
4009
+ * · `::after` (a box `.frame` does not use on any release in range) paints exactly
4010
+ * that left segment from `--dsh-any-op-sidebar` — the same literal the sidebar
4011
+ * column is painted with — and frosts from the sidebar blur's root mirror, so the
4012
+ * column and the strip above it are one surface under one opacity + blur pair.
4013
+ * It is PAINT ONLY: `pointer-events:none`, and deliberately NO
4014
+ * `-webkit-app-region: drag`. Chromium collects app-region items in paint order,
4015
+ * and a drag annotation on a generated layer overrides the `no-drag` holes the
4016
+ * native caption menubar punched for itself (`preload-menu.ts` marks its host
4017
+ * `no-drag`), which left the top-left controls unclickable while the window still
4018
+ * dragged (issue #29). The host's own `.frame::before` keeps its drag region, so
4019
+ * the band remains movable.
4020
+ * `--dsh-windows-sidebar-width` is published INLINE ON THE FRAME by the host
4021
+ * (`AppFrame.tsx`, Windows builds only, `${cols.sidebar}px`) and both pseudo-elements
4022
+ * inherit it, so the split tracks a dragged or collapsed sidebar with no observer of
4023
+ * our own. Collapsed is `0px`: the `::after` box vanishes and `::before` un-clips, so
4024
+ * the band goes back to being entirely the 标题栏 card's.
4025
+ * The native button block still reads the probe as `transparent`, and it sits at the
4026
+ * window's right edge — inside the region that stays 标题栏-owned. */
4107
4027
  function applyNativeCaption(surfaces) {
4108
4028
  if (!document.documentElement.hasAttribute("data-windows-titlebar")) return;
4109
4029
  const src = surfaces === void 0 ? paletteTokens() ?? readHostOpacityTokens() : surfaces;
4110
4030
  const fill = captionFill(src);
4111
4031
  const paint = headerBarPaint(src) || fill;
4112
- const css = fill === "" ? "" : `${WCO_PROBE_SEL}{--dsw-specific-sidebar-fill:transparent}[data-windows-titlebar] .${CAPTION_FRAME_CLASS}::before{background:var(--dsh-any-op-header-bar,${paint})!important;backdrop-filter:var(--dsh-any-blur-header-bar,none);-webkit-backdrop-filter:var(--dsh-any-blur-header-bar,none)}[data-windows-titlebar] .${FLAT_CENTER_CLASS}{border-top-left-radius:0!important}`;
4032
+ const width = "var(--dsh-windows-sidebar-width,0px)";
4033
+ const css = fill === "" ? "" : `${WCO_PROBE_SEL}{--dsw-specific-sidebar-fill:transparent}[data-windows-titlebar] .${CAPTION_FRAME_CLASS}::before{background:var(--dsh-any-op-header-bar,${paint})!important;backdrop-filter:var(--dsh-any-blur-header-bar,none);-webkit-backdrop-filter:var(--dsh-any-blur-header-bar,none);clip-path:inset(0 0 0 ${width});-webkit-clip-path:inset(0 0 0 ${width})}[data-windows-titlebar] .${CAPTION_FRAME_CLASS}::after{content:"";position:absolute;top:0;left:0;pointer-events:none;z-index:-1;width:${width};height:var(--dsh-windows-titlebar-height,40px);background:var(--dsh-any-op-sidebar,${fill});backdrop-filter:var(--dsh-any-blur-sidebar-band,none);-webkit-backdrop-filter:var(--dsh-any-blur-sidebar-band,none)}[data-windows-titlebar]:has([role="dialog"][aria-modal="true"]) .${CAPTION_FRAME_CLASS}::before,[data-windows-titlebar]:has([role="dialog"][aria-modal="true"]) .${CAPTION_FRAME_CLASS}::after{box-shadow:inset 0 0 0 9999px var(--dsw-alias-bg-mask-1,rgba(0,0,0,.24))}:root:has([data-sidebar-collapsed="true"]){--dsh-any-caption-ink:var(--dsh-any-caption-ink-collapsed,#fff)!important}[data-windows-titlebar] [data-windows-menu]{--dsw-alias-label-secondary:var(--dsh-any-caption-ink,#fff);--dsw-alias-label-primary:var(--dsh-any-caption-ink,#fff)}[data-windows-titlebar] .${FLAT_CENTER_CLASS}{border-top-left-radius:0!important}`;
4113
4034
  const el = ensureWcoStyle();
4114
4035
  if (el.textContent !== css) el.textContent = css;
4036
+ const ink = css === "" ? null : captionInkPair();
4037
+ const root = document.documentElement;
4038
+ if (ink === null) {
4039
+ root.style.removeProperty("--dsh-any-caption-ink");
4040
+ root.style.removeProperty("--dsh-any-caption-ink-collapsed");
4041
+ } else {
4042
+ root.style.setProperty("--dsh-any-caption-ink", ink.ink);
4043
+ root.style.setProperty("--dsh-any-caption-ink-collapsed", ink.collapsedInk);
4044
+ }
4115
4045
  discoverParts();
4046
+ applyCaptionInk(ink !== null);
4116
4047
  if (frameEl === null) return;
4117
4048
  if (css === "") {
4118
4049
  frameEl.classList.remove(CAPTION_FRAME_CLASS);
@@ -4122,6 +4053,13 @@ window.__ModuleLoader__.load({
4122
4053
  centerEl?.classList.add(FLAT_CENTER_CLASS);
4123
4054
  }
4124
4055
  }
4056
+ /** Mirror of the sidebar-part blur on `:root`, for the caption segment above the
4057
+ * sidebar column. `--dsh-any-part-blur` is written INLINE on the column and the band
4058
+ * is a pseudo-element of the FRAME, one level up, so it can never inherit it. */
4059
+ function applySidebarBandBlur(px) {
4060
+ if (px > 0) document.documentElement.style.setProperty("--dsh-any-blur-sidebar-band", `blur(${px}px)`);
4061
+ else document.documentElement.style.removeProperty("--dsh-any-blur-sidebar-band");
4062
+ }
4125
4063
  /** Mirror of the bg-part blur on :root. --dsh-any-part-blur is element-scoped
4126
4064
  * to the columns, so surfaces outside their subtree (the mobile header, or
4127
4065
  * third-party styles) can never inherit it. */
@@ -4150,7 +4088,11 @@ window.__ModuleLoader__.load({
4150
4088
  * appears in the sheet. That is exactly how the tagged open-in-app menu kept
4151
4089
  * taking the card blur. `[role]` is a no-op presence test that raises only the
4152
4090
  * tagged arm to (0,2,0); the other three already qualify at (0,2,0)+. */
4153
- const HEADER_SURFACES_PADDED = [`${HEADER_TAG_SELECTOR}[role]`, ...HEADER_SURFACES.slice(1)];
4091
+ const HEADER_SURFACES_PADDED = [
4092
+ `${HEADER_TAG_SELECTOR}[role]`,
4093
+ `${HEADER_TAG_SELECTOR}[data-menu-material]`,
4094
+ ...HEADER_SURFACES.slice(1)
4095
+ ];
4154
4096
  /** The header blur variable, and the rule that frosts the header's dropdown set.
4155
4097
  * The declaration comes from the same adapter answer as the card group's menu arm:
4156
4098
  * every surface in this set paints its skin from the host's own menu-blur token
@@ -4160,7 +4102,7 @@ window.__ModuleLoader__.load({
4160
4102
  * backdrop root each of those files documents avoiding. */
4161
4103
  const HEADER_BLUR_VAR = "--dsh-any-blur-header";
4162
4104
  function headerPopoverRule() {
4163
- return `${HEADER_SURFACES_PADDED.join(",")}{${menuFrost(HEADER_BLUR_VAR)}}`;
4105
+ return `${gated(HEADER_FROST_GATE, HEADER_SURFACES_PADDED.join(","))}{${menuFrost(HEADER_BLUR_VAR)}}`;
4164
4106
  }
4165
4107
  function exemptDefaultRule() {
4166
4108
  return `[role="dialog"][aria-modal="true"]:not([aria-labelledby]){backdrop-filter:none;-webkit-backdrop-filter:none}[role="menu"]:has([class*="_danger"]){${hostAdapter().menus.frostDecl("none")};-webkit-text-stroke-width:0}`;
@@ -4174,8 +4116,13 @@ window.__ModuleLoader__.load({
4174
4116
  function applyHeaderPopovers() {
4175
4117
  const root = document.documentElement;
4176
4118
  const px = rBlurs().header;
4177
- if (px > 0) root.style.setProperty("--dsh-any-blur-header", `blur(${px}px)`);
4178
- else root.style.removeProperty("--dsh-any-blur-header");
4119
+ if (px > 0) {
4120
+ root.style.setProperty(HEADER_BLUR_VAR, `blur(${px}px)`);
4121
+ root.classList.add(HEADER_FROST_GATE);
4122
+ } else {
4123
+ root.style.removeProperty(HEADER_BLUR_VAR);
4124
+ root.classList.remove(HEADER_FROST_GATE);
4125
+ }
4179
4126
  let opacity = rHeaderOpacity();
4180
4127
  if (opacity < 0) opacity = 0;
4181
4128
  if (opacity > 1) opacity = 1;
@@ -4228,8 +4175,9 @@ window.__ModuleLoader__.load({
4228
4175
  return;
4229
4176
  }
4230
4177
  const dialog = "[role=\"dialog\"][aria-modal=\"true\"]:not([aria-labelledby])";
4178
+ const registry = "[data-install-registry]";
4231
4179
  const body = decls.join(";");
4232
- const css = `${dialog},${dialog} *{${body}}[role="menu"]:has([class*="_danger"]){${body}}`;
4180
+ const css = `${dialog},${dialog} *{${body}}[role="menu"]:has([class*="_danger"]){${body}}${registry},${registry} *{${body}}`;
4233
4181
  if (el.textContent !== css) el.textContent = css;
4234
4182
  }
4235
4183
  let exemptStyleEl = null;
@@ -4243,7 +4191,7 @@ window.__ModuleLoader__.load({
4243
4191
  const PRODUCED_RULE = `[data-code-block-content] pre,[data-code-block-banner],[data-composer-chip],[data-changed-files],[data-terminal],[data-read],[data-diff],[data-context-injection-body],[data-search],[data-web],[class*="_ioCard"],[data-inspect],[data-turn-trigger],[style*="--dsh-hover-preview-fade"],:not(pre)>code{-webkit-backdrop-filter:var(--dsh-any-blur-prod,none);backdrop-filter:var(--dsh-any-blur-prod,none)}[data-markdown-variant="compact"] :not(pre)>code{-webkit-backdrop-filter:none;backdrop-filter:none}${BETTER_SIDEBAR_PANEL}{-webkit-backdrop-filter:var(--dsh-any-blur-prod,none);backdrop-filter:var(--dsh-any-blur-prod,none)}${BETTER_SIDEBAR_PANEL}{--dsw-alias-bg-base:var(--dsh-any-prod-panel-bg-base);--dsw-alias-bg-layer-1:var(--dsh-any-prod-panel-layer-1);--dsw-alias-bg-layer-2:var(--dsh-any-prod-panel-layer-2);--dsw-alias-bg-layer-3:var(--dsh-any-prod-panel-layer-3)}@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-banner]{background-color:color-mix(in srgb,var(--dsl-code-block-banner-background-color,var(--dsl-code-block-background,var(--dsw-alias-markdown-code-block,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],[data-diff]{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:not([data-code-block-banner]),[data-diff]>div:first-child:not([data-code-block-banner]){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-inspect]{background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-turn-trigger]{background-color:color-mix(in srgb,var(--dsw-alias-turn-trigger-bg,var(--dsw-alias-markdown-code-block,transparent)) var(--dsh-any-prod-pct,100%),transparent)!important}[data-turn-trigger]:hover{background-color:color-mix(in srgb,var(--dsw-alias-turn-trigger-bg-hover,var(--dsw-alias-interactive-bg-hover,transparent)) var(--dsh-any-prod-pct,100%),transparent)!important}[data-changed-files]{background-color:var(--dsh-any-prod-layer-1,var(--dsw-alias-bg-layer-1,transparent))!important}[data-changed-files]>button:first-child{background-color:color-mix(in srgb,var(--changes-fill,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-changed-files]>button:first-child:hover{background-color:color-mix(in srgb,var(--changes-hover,var(--changes-fill,transparent)) var(--dsh-any-prod-pct,100%),transparent)!important}[data-changed-files]>button:first-child>span:first-child{background-color:color-mix(in srgb,var(--changes-fill,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[style*="--dsh-hover-preview-fade"]{background-color:color-mix(in srgb,var(--dsw-hovercard-bg,#2c2c2e) var(--dsh-any-prod-pct,100%),transparent)!important}[style*="--dsh-hover-preview-fade"][class*="_preview"]{background-color:var(--dsh-any-prod-layer-1,var(--dsw-alias-bg-layer-1,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}}`;
4244
4192
  const STROKE_RULE = [
4245
4193
  `${SETTINGS_PANEL_SEL}{-webkit-text-stroke:var(--dsh-any-stroke-settings-w,0px) var(--dsh-any-stroke-settings-c,transparent);paint-order:stroke fill}`,
4246
- "[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}",
4194
+ "[role=\"menu\"]:not([data-dockkit-tab-menu]),[data-menu-material]: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}",
4247
4195
  `[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,${COMPOSER_TAKEOVER_CARD},${COMPOSER_TAKEOVER_EDITOR}{-webkit-text-stroke:var(--dsh-any-stroke-input-w,0px) var(--dsh-any-stroke-input-c,transparent);paint-order:stroke fill}`,
4248
4196
  `.${HEADER_BAR_CLASS}{-webkit-text-stroke:var(--dsh-any-stroke-headerBar-w,0px) var(--dsh-any-stroke-headerBar-c,transparent);paint-order:stroke fill}`,
4249
4197
  `[data-chat-flow]{-webkit-text-stroke:var(--dsh-any-stroke-chat-w,0px) var(--dsh-any-stroke-chat-c,transparent);paint-order:stroke fill}`,
@@ -4382,7 +4330,7 @@ window.__ModuleLoader__.load({
4382
4330
  if (typeof getComputedStyle !== "undefined") stack = getComputedStyle(document.documentElement).getPropertyValue("--dsw-font-family").trim();
4383
4331
  if (stack === "") stack = "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif";
4384
4332
  const fmt = fontFormatForMime(mime);
4385
- fontStyleEl.textContent = `@font-face{font-family:'${FONT_FAMILY}';src:url('${url}') format('${fmt}');font-display:swap}body{--dsw-font-family:'${FONT_FAMILY}',${stack}}input,textarea,select,button{font-family:var(--dsw-font-family)}`;
4333
+ fontStyleEl.textContent = `@font-face{font-family:'${FONT_FAMILY}';src:url('${url}') format('${fmt}');font-display:swap}body{--dsw-font-family:'${FONT_FAMILY}',${stack}!important}input,textarea,select,button{font-family:var(--dsw-font-family)}`;
4386
4334
  }
4387
4335
  /** Teardown only: drop the @font-face + token override. */
4388
4336
  function removeFontFace() {
@@ -4616,6 +4564,43 @@ window.__ModuleLoader__.load({
4616
4564
  * clear cannot be re-clobbered and there is nothing to re-apply. */
4617
4565
  const PART_FRAME_CLASS = "dab-frame-clear";
4618
4566
  const FRAME_CLEAR_RULE = `.${PART_FRAME_CLASS}{background:transparent!important}`;
4567
+ /** The sidebar column paints its fill ONCE, no matter how many nested elements
4568
+ * ask for it.
4569
+ *
4570
+ * WHY THIS IS NOT COSMETICS: `--dsw-specific-sidebar-fill` is a theme token the
4571
+ * host ships OPAQUE (`design-platform.css`: light `--dsw-static-neutral-bluish-50`,
4572
+ * dark `-900`), and three elements stacked in the sidebar's region each declare
4573
+ * `background:var(--dsw-specific-sidebar-fill)` on top of one another —
4574
+ * `AppFrame.module.css` `.sidebarCol` (the grid track), `ui-sidebar/SidebarRoot.module.css:16`
4575
+ * `.root` (the shell it fills) and `ui-workspace/WorkspaceBrowser.module.css:335`
4576
+ * `.fade` (the tree's 24px bottom gradient). Over an opaque color that layering is
4577
+ * invisible; the moment this plugin's 「侧栏」 slider makes the token translucent
4578
+ * every copy adds its alpha again, so the region stops reading as one surface. On
4579
+ * the desktop build it is outright visible: the caption strip above the sidebar is
4580
+ * the plugin's own `::after`, painted with the SAME variable at the SAME alpha but
4581
+ * exactly once (`applyNativeCaption`), so the strip and the column under it come out
4582
+ * at two different densities and the sidebar looks split horizontally.
4583
+ *
4584
+ * The column is the one that keeps the paint — it is the outermost box, it is the
4585
+ * box whose width the caption strip is cut to, and the host already treats it as the
4586
+ * owner on macOS, where `SidebarRoot.module.css:27` sets `.root{background:transparent}`
4587
+ * for exactly this reason ("the AppFrame column owns the translucent sidebar tint").
4588
+ * The host retires the gradient the same way for the same reason:
4589
+ * `WorkspaceBrowser.module.css:341` hides `.fade` on darwin because "the opaque fill
4590
+ * gradient would paint a solid smear instead of a fade" once the sidebar is
4591
+ * translucent over the window — which is the state this slider puts the sidebar in on
4592
+ * every platform. So the token is re-declared `transparent` for the column's
4593
+ * DESCENDANTS only: an element's own declaration beats what it inherits, and `body`'s
4594
+ * `!important` re-scope reaches these elements by inheritance only, so no `!important`
4595
+ * is needed here and none of the three consumers can win it back. The column itself
4596
+ * still resolves the faded value it inherits, which is why `*` is a descendant
4597
+ * combinator and not part of the column's own selector.
4598
+ *
4599
+ * Bound by the CSS-Module class shape, same as `[class*="_ioCard"]`: the built
4600
+ * bundle names these `pI_x6G_sidebarCol` — prefix first, local name last, and the
4601
+ * pair is identical across `0.2.0-rc.1`, `0.2.1-alpha.3` and `0.2.1-rc.2`. A build
4602
+ * that renames the track simply stops matching, which is the untouched host look. */
4603
+ const SIDEBAR_ONE_LAYER_RULE = "[class*=\"_sidebarCol\"] *{--dsw-specific-sidebar-fill:transparent}";
4619
4604
  const PART_BLUR_RULE = `.${PART_BLUR_CLASS}{isolation:isolate}.${PART_BLUR_CLASS}:has([role="dialog"]){isolation:auto}.${PART_UNDERLAY_CLASS}{position:absolute;inset:0;z-index:-1;pointer-events:none;border-radius:inherit;backdrop-filter:var(--dsh-any-part-blur,none);-webkit-backdrop-filter:var(--dsh-any-part-blur,none)}`;
4620
4605
  let partBlurStyleEl = null;
4621
4606
  function ensurePartBlurStyle() {
@@ -4705,10 +4690,17 @@ window.__ModuleLoader__.load({
4705
4690
  /** Blur of the option panels inside the settings dialog (.dab-card), owned by
4706
4691
  * the "dialog option panel" (card) blur slider. Written as a plugin-owned
4707
4692
  * variable consumed by SETTINGS_STYLE_RULE — deliberately NOT applied to the
4708
- * homepage center column, which this slider must never touch. */
4693
+ * homepage center column, which this slider must never touch. The gate class is
4694
+ * what hands the menu frost token over at all (see `CARD_FROST_GATE`). */
4709
4695
  function applyCardPanelsBlur(px) {
4710
- if (px > 0) document.documentElement.style.setProperty("--dsh-any-blur-card-panels", `blur(${px}px)`);
4711
- else document.documentElement.style.removeProperty("--dsh-any-blur-card-panels");
4696
+ const root = document.documentElement;
4697
+ if (px > 0) {
4698
+ root.style.setProperty(CARD_BLUR_VAR, `blur(${px}px)`);
4699
+ root.classList.add(CARD_FROST_GATE);
4700
+ } else {
4701
+ root.style.removeProperty(CARD_BLUR_VAR);
4702
+ root.classList.remove(CARD_FROST_GATE);
4703
+ }
4712
4704
  }
4713
4705
  /** Apply per-part interface blur to the AppFrame columns + settings panel. */
4714
4706
  function applyPartBlurs(blurs) {
@@ -4718,6 +4710,7 @@ window.__ModuleLoader__.load({
4718
4710
  setBlur(centerEl, blurs.bg);
4719
4711
  setBlur(rightEl, 0);
4720
4712
  applyBgBlurGlobal(blurs.bg);
4713
+ applySidebarBandBlur(blurs.sidebar);
4721
4714
  applyCardPanelsBlur(blurs.card);
4722
4715
  applySettingsBlur(blurs.settings);
4723
4716
  applyInputBlur(blurs.input);
@@ -4788,7 +4781,10 @@ window.__ModuleLoader__.load({
4788
4781
  if (part === "bg") {
4789
4782
  setBlur(centerEl, v);
4790
4783
  applyBgBlurGlobal(v);
4791
- } else setBlur(sidebarEl, v);
4784
+ return;
4785
+ }
4786
+ setBlur(sidebarEl, v);
4787
+ applySidebarBandBlur(v);
4792
4788
  }
4793
4789
  const VIEW_CARDS = [{
4794
4790
  sel: "[data-chat-flow]",
@@ -5435,10 +5431,14 @@ window.__ModuleLoader__.load({
5435
5431
  const name = set[k];
5436
5432
  if (name) document.documentElement.style.removeProperty(name);
5437
5433
  }
5438
- document.documentElement.classList.remove(INPUT_FROST_CLASS);
5434
+ document.documentElement.classList.remove(INPUT_FROST_CLASS, CARD_FROST_GATE, HEADER_FROST_GATE);
5439
5435
  applyTakeoverFrost(0);
5440
5436
  document.documentElement.style.removeProperty("--dsh-any-op-header-bar");
5441
5437
  document.documentElement.style.removeProperty("--dsh-any-blur-header-bar");
5438
+ document.documentElement.style.removeProperty("--dsh-any-blur-sidebar-band");
5439
+ document.documentElement.style.removeProperty("--dsh-any-caption-ink");
5440
+ document.documentElement.style.removeProperty("--dsh-any-caption-ink-collapsed");
5441
+ releaseCaptionInk();
5442
5442
  document.querySelectorAll(`.${HEADER_BAR_CLASS}`).forEach((el) => el.classList.remove(HEADER_BAR_CLASS));
5443
5443
  headerBarEl = null;
5444
5444
  frameEl?.classList.remove(CAPTION_FRAME_CLASS);
@@ -5537,7 +5537,7 @@ window.__ModuleLoader__.load({
5537
5537
  function buildStaticStyles() {
5538
5538
  const adapter = rHostAdapter();
5539
5539
  const panel = adapter.panelFragments();
5540
- return DARK_GRADIENT + FRAME_CLEAR_RULE + SETTINGS_STYLE_RULE + popoverBlurRule() + TRAJECTORY_STYLE_RULE + inputBlurRule() + MOBILE_HEADER_RULE + HEADER_BAR_RULE + PANEL_TOKEN_RULE + panel.promotion + panel.blur + adapter.pluginPageRule + PRODUCED_RULE + STROKE_RULE + headerPopoverRule() + exemptDefaultRule() + PLACEHOLDER_RULE;
5540
+ return DARK_GRADIENT + FRAME_CLEAR_RULE + SIDEBAR_ONE_LAYER_RULE + SETTINGS_STYLE_RULE + popoverBlurRule() + TRAJECTORY_STYLE_RULE + inputBlurRule() + MOBILE_HEADER_RULE + HEADER_BAR_RULE + PANEL_TOKEN_RULE + panel.promotion + panel.blur + adapter.pluginPageRule + PRODUCED_RULE + STROKE_RULE + headerPopoverRule() + exemptDefaultRule() + PLACEHOLDER_RULE;
5541
5541
  }
5542
5542
  /** Append the plugin's stylesheet and keep it matched to the host.
5543
5543
  * Returns a teardown for the plugin's effect scope. */