dsh-any-background 0.3.5 → 0.3.7

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,36 +1014,15 @@ 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
- {
1028
- channel: "0.1.5-rc",
1029
- release: "0.1.5-rc.2"
1030
- },
1031
- {
1032
- channel: "0.1.6-alpha",
1033
- release: "0.1.6-alpha.2"
1034
- },
1035
- {
1036
- channel: "0.1.7-alpha",
1037
- release: "0.1.7-alpha.2"
1038
- },
1039
- {
1040
- channel: "0.1.7-alpha",
1041
- release: "0.1.7-rc.1"
1042
- },
1043
- {
1044
- channel: "0.1.7-rc",
1045
- release: "0.1.7-rc.2"
1046
- },
1047
1026
  {
1048
1027
  channel: "0.2.0-rc",
1049
1028
  release: "0.2.0-rc.1"
@@ -1055,6 +1034,10 @@ window.__ModuleLoader__.load({
1055
1034
  {
1056
1035
  channel: "0.2.1-alpha",
1057
1036
  release: "0.2.1-alpha.1"
1037
+ },
1038
+ {
1039
+ channel: "0.2.1-alpha",
1040
+ release: "0.2.1-alpha.2"
1058
1041
  }
1059
1042
  ];
1060
1043
  const UNKNOWN_HOST_INFO = {
@@ -1110,8 +1093,8 @@ window.__ModuleLoader__.load({
1110
1093
  /** Pick the adapter for a release, clamping to the nearest verified line when the
1111
1094
  * release is outside everything this plugin has seen.
1112
1095
  *
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
1096
+ * A release is matched by PATCH LINE before anything else: `0.2.1-alpha.3` and a
1097
+ * future `0.2.1` stable both belong to the line verified at `0.2.1-alpha.1/2`, and
1115
1098
  * handing them that adapter beats giving up — the DOM probes only cover panel
1116
1099
  * mechanics, not the surface decisions the line is known for.
1117
1100
  *
@@ -1157,14 +1140,6 @@ window.__ModuleLoader__.load({
1157
1140
  function channelOf(version) {
1158
1141
  return classifyRelease(version).channel;
1159
1142
  }
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
1143
  //#endregion
1169
1144
  //#region src/client/host-compat/versions/shared.ts
1170
1145
  /** The host's own right Sidebar wrapper. */
@@ -1183,7 +1158,7 @@ window.__ModuleLoader__.load({
1183
1158
  * renders the elected takeover beside the fallback bar (verified per tag in
1184
1159
  * `ConversationContent.tsx` and both packages' `slots.inject('conversation.composer')`)
1185
1160
  * — 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`.
1161
+ * `dsh-v0.1.5-rc.2` through `dsh-v0.2.1-alpha.2`.
1187
1162
  *
1188
1163
  * The marker sits on the wrapper, the fill on its child card:
1189
1164
  * `[data-approval-key] > div`, `[data-plan-review-key] > section`,
@@ -1268,33 +1243,22 @@ window.__ModuleLoader__.load({
1268
1243
  * fades the non-`MenuSurface` overlays and nothing else. */
1269
1244
  const MENU_FILL_TOKEN = "--dsw-specific-menu";
1270
1245
  /** 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. */
1246
+ * thing that paints. Both prefixes because Safari needs the `-webkit-` one and the
1247
+ * cascade must not let them disagree. */
1274
1248
  function elementFrostDecl(value) {
1275
1249
  return `-webkit-backdrop-filter:${value};backdrop-filter:${value}`;
1276
1250
  }
1277
1251
  /**
1278
1252
  * ── Where a menu's paint lives ────────────────────────────────────────────────
1279
1253
  * Verified by reading the emitting source at each tag, not inferred from a changelog.
1254
+ * The era this table now starts in (`0.2.0-rc.1`) already presents the shape below;
1255
+ * the 0.1.5 / 0.1.6 / 0.1.7-alpha line, where the menu ELEMENT painted itself and the
1256
+ * host frosted nothing at all before `0.1.7-alpha.1`, is recorded in
1257
+ * `LAYERED_MENU_SURFACE`'s first bullet rather than in an adapter, because no adapter
1258
+ * answers for it any more.
1280
1259
  *
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
1260
+ * `0.1.7-rc.2` and `dsh-v0.2.0-rc.1`: `TabMenu`, `MenuView`, `PopupSelectView`,
1261
+ * `ModelSelect` and `Menu` all render through the new
1298
1262
  * `ui-primitives/src/MenuSurface.tsx` (byte-identical between the two tags),
1299
1263
  * which leaves the element unpainted and draws on a `z-index:-1` child instead
1300
1264
  * (`MenuSurface.module.css` `.material`: `background:var(--dsw-menu-surface-fill)`
@@ -1312,18 +1276,23 @@ window.__ModuleLoader__.load({
1312
1276
  * backdrop root", and the surface sets `isolation:isolate`. Filtering the
1313
1277
  * element anyway installs the backdrop root the layer was written to avoid.
1314
1278
  * 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.
1279
+ * the same 14 stylesheets at every tag in the table (stat dialog, queue dock,
1280
+ * context meter, todo panel, goal bar, job list, `HoverCard`, `PickerPopover`/
1281
+ * `TaskMenu`/`ScheduleCatalogAction`, the subagent lineage, `TeamAction`,
1282
+ * `CordisPanel`, `MenuSurface` itself) — re-declaring the token reaches the frost
1283
+ * whoever ends up applying it, and does not depend on any element being the
1284
+ * painting element. A 15th file names the same token without consuming it:
1285
+ * `ui-theme/src/styles/gradient-shadow-text.css` DECLARES it on `body` —
1286
+ * `blur(40px) saturate(150%)`, no media query — which is exactly why writing the
1287
+ * token on a menu with a `none` fallback deletes a frost the host always had
1288
+ * (see `CARD_FROST_GATE` in `wallpaper.ts`). `MenuSurface.tsx` / `.module.css` are
1289
+ * byte-identical from rc.2 through `0.2.1-alpha.2`, and the consumer set moved only
1290
+ * in line numbers (the same 15 files at every tag in the table, checked per tag).
1320
1291
  *
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;
1292
+ * `dsh-v0.2.0-rc.2` … `dsh-v0.2.1-alpha.2`: the sticky GROUP HEADING inside those
1293
+ * menus changed owner. Through rc.1 the only grouped menu was `ModelSelect`, whose
1294
+ * own stylesheet painted that heading from the menu token
1295
+ * (`ModelSelect.module.css` `.groupTitle{position:sticky;
1327
1296
  * background:var(--dsw-specific-menu)}`), so the card slider drove it and a picked
1328
1297
  * wallpaper color tinted it. At rc.2 the rule is deleted and both grouped popups
1329
1298
  * (`ModelSelect`, `PopupSelectView`) render the new `ui-primitives/MenuGroup.tsx`,
@@ -1335,13 +1304,7 @@ window.__ModuleLoader__.load({
1335
1304
  * is the single menu element the fade no longer reaches: an untinted, near-opaque
1336
1305
  * band sitting on top of every row in the list the moment that list scrolls.
1337
1306
  */
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
1307
+ /** The answer for `0.2.0-rc` and for `0.2.1-alpha`. The old token
1345
1308
  * stays in the list on purpose: 14 surfaces in the same claim set still paint their
1346
1309
  * fill from it — the lineage tree, `CordisPanel`, the stat dialog, `QueueDock`,
1347
1310
  * `TodoPanel`, `ContextMeter`, `GoalBar`, `JobListAction`, `HoverCard`, `TeamAction`,
@@ -1406,307 +1369,6 @@ window.__ModuleLoader__.load({
1406
1369
  "conversation.session.header.lineage"
1407
1370
  ];
1408
1371
  //#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
1372
  //#region src/client/host-compat/versions/v0-2-0-rc-1/adapter.ts
1711
1373
  /**
1712
1374
  * Adapter for the DSH `0.2.0-rc` channel, verified at `0.2.0-rc.1`.
@@ -1848,9 +1510,10 @@ window.__ModuleLoader__.load({
1848
1510
  };
1849
1511
  }
1850
1512
  //#endregion
1851
- //#region src/client/host-compat/versions/v0-2-1-alpha-1/adapter.ts
1513
+ //#region src/client/host-compat/versions/v0-2-1-alpha-1-2/adapter.ts
1852
1514
  /**
1853
- * Adapter for the DSH `0.2.1-alpha` line, verified at `0.2.1-alpha.1`.
1515
+ * Adapter for the DSH `0.2.1-alpha` line, verified at `0.2.1-alpha.1` AND
1516
+ * `0.2.1-alpha.2`.
1854
1517
  *
1855
1518
  * A new MINOR line opened with a prerelease, and the answer is the `0.2.0-rc.2`
1856
1519
  * folder's — which is a statement this file has to earn, because "same as the
@@ -1874,6 +1537,18 @@ window.__ModuleLoader__.load({
1874
1537
  * · `TextShimmer` still marks its root `data-shimmer`, so the stroke exemption
1875
1538
  * written for rc.1 stands.
1876
1539
  *
1540
+ * Re-checked at `0.2.1-alpha.2`, same list: `design-platform.css` still byte-identical
1541
+ * (`--dsw-menu-surface-fill` / `--dsw-specific-menu` / `--dsw-alias-menu-group-header-fill`
1542
+ * on both scheme blocks); `MenuSurface.tsx`/`.module.css` byte-identical (blob hashes
1543
+ * match); the `--dsw-specific-menu` and `--dsw-menu-backdrop-filter` file sets are the
1544
+ * same 15 at both tags (the frost side being 14 consumers plus the `body` declaration
1545
+ * in `gradient-shadow-text.css`);
1546
+ * `[data-sidebar-right-panel]` and the dockkit slide unchanged; the five header slot
1547
+ * keys unchanged; `AppFrame.tsx` / `AppFrame.module.css` untouched. The plugin-manager
1548
+ * page gained a third `[data-plugin-scope]` group (`data-plugin-group="more"`) inside
1549
+ * the same `[data-plugin-panel]`, which the existing rule already frames, so no new
1550
+ * fact lands here.
1551
+ *
1877
1552
  * What did move, and why none of it needs a fact from this folder:
1878
1553
  *
1879
1554
  * 1. `ui-layout/AppFrame` gains a second grid row, `div.bottomRow[data-shell-bottom]`
@@ -1895,6 +1570,38 @@ window.__ModuleLoader__.load({
1895
1570
  * looked for a plugin's `invariant` export. This plugin's no-op companion was
1896
1571
  * therefore already dead weight here, and it goes rather than claiming a peer
1897
1572
  * version for a package the host no longer ships.
1573
+ * 5. `ui-theme/src/styles/base.css` restructured the font tokens: the built-in stacks
1574
+ * moved to `--dsh-font-family-text-default` / `-code-default`, and the host now
1575
+ * composes the tokens itself on `body` —
1576
+ * `--dsw-font-family: var(--dsh-font-family-text, …), …` — fed by the per-role user
1577
+ * font lists `ThemePresenter` publishes (`--dsh-font-family-text` / `-code` /
1578
+ * `-terminal`, new at this tag). That is a NEW `body`-level declaration of the very
1579
+ * token this plugin's custom-font feature writes, at the same specificity as its
1580
+ * rule — so which one wins became a question of stylesheet order. `applyFontFace`
1581
+ * now asserts its `--dsw-font-family` with `!important`, the way the token sheet
1582
+ * already does, so a host re-injecting its base styles cannot quietly drop the
1583
+ * picked font. Code typography (`--ds-font-family-code`, plus the new
1584
+ * `--dsh-code-font-size` axis) stays untouched — this plugin never owned it.
1585
+ * 6. AT `0.2.1-alpha.2`, `ui-jobs/JobListAction` moved its background-task list out of
1586
+ * the header row and into a `createPortal(…, document.body)` painted
1587
+ * `position:fixed; z-index:100` — and the list is a bare `<ul>` with NO `role`, so
1588
+ * `header-tag.ts`'s `[role="menu"|"tree"|"dialog"]` probe can never claim it and the
1589
+ * `ul[class*="_menu"]` class shape is the only thing that still styles it.
1590
+ * Through `0.2.1-alpha.1` the same list is IN-PLACE — `{open ? <ul className={css.menu}
1591
+ * style={{left: menuShift}}/> : null}` with `.menu{position:absolute; top:calc(100% +
1592
+ * 5px); z-index:100}` under a `.root{position:relative}` — which is the exact DOM the
1593
+ * reported bug was captured from (`style="left: 0px;"`, no `top`, no role). The portal
1594
+ * is therefore what HID the bug on the newest tag, not what fixed it: an absolutely
1595
+ * positioned `z-index:100` child of the header row is only buried by the page once
1596
+ * something makes that row a stacking context, and the thing that did was this
1597
+ * plugin's own 「标题栏」 frost. Nothing here is a per-release fact to route on —
1598
+ * `0.2.1-alpha.2` merely swapped which header component gets caught by it, because
1599
+ * `ui-schedule/TaskMenu` (registered into `…header.utilities` on every release in
1600
+ * range) is `role="menu"` + `position:absolute; z-index:100` in place whenever the
1601
+ * Menu primitive is not in portal mode. The compensation lives with the injury: see
1602
+ * the `z-index` note on `HEADER_BAR_RULE` in `wallpaper.ts`. (Checked and rejected:
1603
+ * `ui-conversation/ContextMeter`'s `position:fixed` panel is NOT row chrome —
1604
+ * `InputBar.tsx` mounts it in the composer, so the row never re-anchored it.)
1898
1605
  *
1899
1606
  * @module
1900
1607
  */
@@ -1906,7 +1613,7 @@ window.__ModuleLoader__.load({
1906
1613
  }
1907
1614
  function createAdapter$1(host) {
1908
1615
  return {
1909
- id: "0.2.1-alpha.1",
1616
+ id: "0.2.1-alpha.1/alpha.2",
1910
1617
  channel: "0.2.1-alpha",
1911
1618
  host,
1912
1619
  panelFragments: panelFragments$1,
@@ -1963,10 +1670,6 @@ window.__ModuleLoader__.load({
1963
1670
  //#endregion
1964
1671
  //#region src/client/host-compat/versions/registry.ts
1965
1672
  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
1673
  "0.2.0-rc": createAdapter$3,
1971
1674
  "0.2.0-rc.2": createAdapter$2,
1972
1675
  "0.2.1-alpha": createAdapter$1,
@@ -2372,7 +2075,7 @@ window.__ModuleLoader__.load({
2372
2075
  *
2373
2076
  * Everything downstream asks a QUESTION ("who owns the guide surface?", "which CSS
2374
2077
  * 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
2078
+ * release. That is what keeps the per-version folders honest: when a newer line needs a
2376
2079
  * new behaviour, a consumer changes by reading a new adapter field, not by growing
2377
2080
  * another version comparison.
2378
2081
  *
@@ -3943,6 +3646,14 @@ window.__ModuleLoader__.load({
3943
3646
  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
3647
  const CARD_BLUR_VAR = "--dsh-any-blur-card-panels";
3945
3648
  const POPOVER_MENU_SEL = `[role="menu"]:not([data-dockkit-tab-menu])`;
3649
+ /** The `MenuSurface` root's own marker (`ui-primitives/MenuSurface.tsx`), present on
3650
+ * every release whose menus paint through that layer. It joins the card group because
3651
+ * the role set cannot reach all of them: `ModelSelect` renders its model pane as
3652
+ * `role="group"` (ModelSelect.tsx:486), so keyed on `[role="menu"]` alone that card
3653
+ * fades with the slider — its fill comes off `body` — and never frosts with it.
3654
+ * The dockkit exclusion has to be spelled again here: a tab menu IS a `MenuSurface`
3655
+ * now, and it stays owned by its own layer token, not by the menu one. */
3656
+ const MENU_MATERIAL_SEL = `[data-menu-material]:not([data-dockkit-tab-menu])`;
3946
3657
  const POPOVER_OTHER_SEL = "[role=\"listbox\"],body > [role=\"tree\"],body > [role=\"dialog\"]:not([aria-modal=\"true\"])";
3947
3658
  /** The frost declaration this release's menus want, driven by the named plugin var.
3948
3659
  * Every menu-shaped surface group asks through here, so the card group, the header
@@ -3950,6 +3661,34 @@ window.__ModuleLoader__.load({
3950
3661
  function menuFrost(blurVar) {
3951
3662
  return hostAdapter().menus.frostDecl(`var(${blurVar},none)`);
3952
3663
  }
3664
+ /** The `<html>` classes that let the two menu groups write their frost token at all.
3665
+ *
3666
+ * WHY A GATE IS NEEDED AND `:where()` IS ENOUGH: the host declares its own menu frost
3667
+ * on `body` (`ui-theme/src/styles/gradient-shadow-text.css` —
3668
+ * `--dsw-menu-backdrop-filter: blur(40px) saturate(150%)`, no media query), and every
3669
+ * menu frosts from that INHERITED value, whether it filters itself (the background-job
3670
+ * `<ul>`) or hands the value to a `MenuSurface` `.material` child. An element's own
3671
+ * declaration always beats what it inherits, so writing the token with a `none`
3672
+ * fallback does not "add no blur" at slider 0 — it DELETES the host's frost. The
3673
+ * result is a faded menu with nothing behind it: the page shows through it crisp and
3674
+ * bright, so the popup stops reading as a raised surface at all. (This is a DIFFERENT
3675
+ * defect from the reported "the background-job list is under the conversation" one,
3676
+ * which is a stacking-context injury the row takes with its own frost — see the
3677
+ * `z-index` note on `HEADER_BAR_RULE`. Both were live at once on `0.2.0-rc.1`.)
3678
+ * `none` stays correct for the exempt group: `applyExemptDefaults` pins those
3679
+ * surfaces at alpha 1 alongside the cleared frost, so they are opaque by design.
3680
+ *
3681
+ * `:where()` keeps the gate free: this plugin's menu cascade is ORDERED (card group →
3682
+ * header group → exemptions) at equal specificity, and a class prefix with real
3683
+ * specificity would have lifted the card group over the exemption it must lose to. */
3684
+ const CARD_FROST_GATE = "dab-card-frost";
3685
+ const HEADER_FROST_GATE = "dab-header-frost";
3686
+ /** Prefix every arm of a selector list with a `:where()` gate. A joined list is one
3687
+ * selector per entry, so prefixing the string once would gate only the first arm and
3688
+ * leave the rest always on. */
3689
+ function gated(gate, selectors) {
3690
+ return selectors.split(",").map((sel) => `:where(.${gate}) ${sel}`).join(",");
3691
+ }
3953
3692
  /** Declare EVERY token this release's menus can paint their fill from with the same
3954
3693
  * value. One slider, one value, every layer reached — and a token the host does not
3955
3694
  * read is inert, so this is safe to emit on a line that only needs one.
@@ -3963,7 +3702,7 @@ window.__ModuleLoader__.load({
3963
3702
  return hostAdapter().menus.fillTokens.map((name) => `${name}:${value}`).join(";");
3964
3703
  }
3965
3704
  function popoverBlurRule() {
3966
- return `${POPOVER_MENU_SEL}{${menuFrost(CARD_BLUR_VAR)}}${POPOVER_OTHER_SEL}{${elementFrostDecl(`var(${CARD_BLUR_VAR},none)`)}}`;
3705
+ 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
3706
  }
3968
3707
  const INPUT_FROST_CLASS = "dab-input-frost";
3969
3708
  const TAKEOVER_FROST_CLASS = "dab-takeover-frost";
@@ -4017,37 +3756,107 @@ window.__ModuleLoader__.load({
4017
3756
  const MOBILE_HEADER_RULE = ".dsh-mobile-app-header{background:var(--dsh-any-op-bg,transparent)!important;backdrop-filter:var(--dsh-any-part-blur-global,none);-webkit-backdrop-filter:var(--dsh-any-part-blur-global,none)}";
4018
3757
  const HEADER_BAR_CLASS = "dab-header-bar";
4019
3758
  const HEADER_BAR_ANCHOR = "[data-slot^=\"conversation.session.header\"]";
4020
- /** The session header bar, or null where this host has no such element. */
3759
+ /** The session header bar, or null where this host has no such element.
3760
+ *
3761
+ * A BLANK session is excluded. `ConversationHeader` marks that row
3762
+ * `headerBlank`, whose rule zeroes its min-height and drops its bottom rule, so
3763
+ * the host paints nothing there and only the row's padding plus the corner
3764
+ * control remain. Claiming it paints a bar over a row the host deliberately
3765
+ * leaves empty — the box that appeared on a fresh session (0.2.0-rc.2). Matched
3766
+ * by the CSS-module LOCAL name: the hash prefix is build-specific, the suffix is
3767
+ * not, and a host that never emits the marker keeps the old behaviour. */
4021
3768
  function discoverHeaderBar() {
4022
3769
  if (centerEl === null || !document.body.contains(centerEl)) return null;
4023
3770
  const bar = centerEl.querySelector(HEADER_BAR_ANCHOR)?.closest("header");
4024
- return bar instanceof HTMLElement ? bar : null;
3771
+ if (!(bar instanceof HTMLElement)) return null;
3772
+ if (bar.className.includes("headerBlank")) return null;
3773
+ return bar;
3774
+ }
3775
+ /** Exactly one owner: the row the current conversation mounted. A row that went
3776
+ * blank, or one the host replaced, must not keep a class from an earlier pass. */
3777
+ let headerBarEl = null;
3778
+ function claimHeaderBar(bar) {
3779
+ if (headerBarEl !== null && headerBarEl !== bar) headerBarEl.classList.remove(HEADER_BAR_CLASS);
3780
+ headerBarEl = bar;
3781
+ if (bar !== null) bar.classList.add(HEADER_BAR_CLASS);
4025
3782
  }
4026
3783
  /** The bar's paint. The variables are written by `applyHeaderBar`; the class is
4027
3784
  * only ever added when they are, so a host the plugin is not theming keeps its
4028
- * own header untouched. */
4029
- 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)}`;
3785
+ * own header untouched.
3786
+ *
3787
+ * WHY THE ROW NEEDS A `z-index`: the 「标题栏」 card frosts the row, and a
3788
+ * `backdrop-filter` other than `none` makes the element a STACKING CONTEXT — which the
3789
+ * row cannot avoid wanting, since the frost is the feature. The row is not an empty
3790
+ * strip: the host mounts its header chrome INTO it (`conversation.session.header
3791
+ * .actions|.utilities|.corner`), and that chrome raises its overlays from inside the
3792
+ * row:
3793
+ * · `ui-jobs/JobListAction.module.css` — `.root{position:relative}` +
3794
+ * `.menu{position:absolute; top:calc(100% + 5px); z-index:100}` on every release
3795
+ * through `0.2.1-alpha.1` (the `{open ? <ul style={{left: menuShift}}/> : null}`
3796
+ * the bug was captured from; `0.2.1-alpha.2` portaled it to `body`, which is why
3797
+ * that build hid the symptom rather than fixed it);
3798
+ * · `ui-schedule/TaskMenu.module.css` — the list root carries `role="menu"` and is
3799
+ * `position:absolute; top:calc(100% + 4px); z-index:100` whenever the primitive is
3800
+ * NOT in portal mode, and it sits in `…header.utilities` on every release in range.
3801
+ * Once the row is a stacking context those `z-index:100`s stop being page-level values:
3802
+ * they only order the overlays WITHIN the row, and the row itself then paints at its
3803
+ * own level — under the conversation body, the composer seat and `.root`'s background,
3804
+ * all of which follow it in the column. The menu was not demoted by the host; it was
3805
+ * confined by us, and confining it is what "the list is behind the chat" meant.
3806
+ *
3807
+ * THE COMPENSATION IS THE ROW'S OWN `z-index`, not a release of the frost: a `:has()`
3808
+ * valve dropping the blur while an overlay is open was the first attempt, and it
3809
+ * visibly re-tints the whole title bar every time anyone opens a header menu — the
3810
+ * part's two sliders appear to change state on their own. Raising the row instead keeps
3811
+ * the frost steady and restores the order the host assumed. The value has to clear the
3812
+ * in-column chrome the row hangs over — `.seat` 9 (composer), the back-to-bottom control
3813
+ * 8, the footer 7, `.tabs` 1 (all measured from `ConversationRoot.module.css`) — while
3814
+ * staying UNDER every page-level surface, because the centre column is not itself a
3815
+ * stacking context unless the background blur is on: portaled menus/tooltips are 100,
3816
+ * modal overlays 1000, stat and context panels 1100 (`Menu.module.css`, `Modal.module.css`,
3817
+ * `ContextMeter.module.css` — the last lives in `InputBar`, NOT in this row, so the row's
3818
+ * filter never had to be blamed for its anchoring). 10 sits in that gap: above 9, far
3819
+ * below 100, so a portaled list still covers the title row exactly as the host intends.
3820
+ *
3821
+ * `z-index` needs no `position` here and none is written: the row is a flex ITEM of
3822
+ * `ConversationRoot .root{display:flex;flex-direction:column}` on every release in range,
3823
+ * and z-index applies to flex items as they are. Writing `position:relative` would also
3824
+ * make the row a containing block — and deciding a host box's positioning is the exact
3825
+ * race this plugin lost once already (issue #24). */
3826
+ 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}`;
3827
+ /** The 「标题栏」card's paint: the card's material at the card's opacity, empty
3828
+ * where the plugin is not theming. The conversation header row, the Windows
3829
+ * caption band, and — through the desktop preload's probe — the native
3830
+ * minimize/maximize/close block all read this one value. */
3831
+ function headerBarPaint(src) {
3832
+ const base = (src === void 0 ? paletteTokens() ?? readHostOpacityTokens() : src)?.["--dsw-alias-bg-base"];
3833
+ return base === void 0 ? "" : toRgba(base, rHeaderBarOpacity());
3834
+ }
4030
3835
  /** Claim the bar and write its material + frost. `surfaces` arrives from the token
4031
3836
  * funnel, which already has the resolved source list; the settings sliders and the
4032
3837
  * parts pass call it without one and the sources are re-resolved here. */
4033
3838
  function applyHeaderBar(surfaces) {
4034
- const base = (surfaces === void 0 ? paletteTokens() ?? readHostOpacityTokens() : surfaces)?.["--dsw-alias-bg-base"];
3839
+ const src = surfaces === void 0 ? paletteTokens() ?? readHostOpacityTokens() : surfaces;
4035
3840
  const root = document.documentElement;
4036
3841
  discoverParts();
4037
3842
  const bar = discoverHeaderBar();
4038
- if (base === void 0) {
4039
- if (bar !== null) bar.classList.remove(HEADER_BAR_CLASS);
3843
+ const paint = headerBarPaint(src);
3844
+ if (paint === "") {
3845
+ claimHeaderBar(null);
4040
3846
  root.style.removeProperty("--dsh-any-op-header-bar");
4041
3847
  root.style.removeProperty("--dsh-any-blur-header-bar");
3848
+ applyNativeCaption(surfaces);
4042
3849
  return;
4043
3850
  }
4044
- root.style.setProperty("--dsh-any-op-header-bar", toRgba(base, rHeaderBarOpacity()));
3851
+ root.style.setProperty("--dsh-any-op-header-bar", paint);
4045
3852
  const px = rBlurs().headerBar;
4046
3853
  if (px > 0) root.style.setProperty("--dsh-any-blur-header-bar", `blur(${px}px)`);
4047
3854
  else root.style.removeProperty("--dsh-any-blur-header-bar");
4048
- if (bar !== null) bar.classList.add(HEADER_BAR_CLASS);
3855
+ claimHeaderBar(bar);
3856
+ applyNativeCaption(surfaces);
4049
3857
  }
4050
3858
  const CAPTION_FRAME_CLASS = "dab-frame-caption";
3859
+ const FLAT_CENTER_CLASS = "dab-center-flat";
4051
3860
  const WCO_PROBE_SEL = "body > span[style*=\"--dsw-specific-sidebar-fill\"]";
4052
3861
  let wcoStyleEl = null;
4053
3862
  function ensureWcoStyle() {
@@ -4073,17 +3882,55 @@ window.__ModuleLoader__.load({
4073
3882
  * plugin's own discovery (`discoverParts`), not a guessed hashed class name. The
4074
3883
  * probe selector is a verified shape: an inline style that mentions the sidebar
4075
3884
  * token on a body-level span is the desktop preload's caption probe and nothing
4076
- * else, so no match means no desktop caption and the rule is inert. */
3885
+ * else, so no match means no desktop caption and the rule is inert.
3886
+ *
3887
+ * THE BAND IS TWO REGIONS, NOT ONE. `AppFrame.module.css` paints the whole caption
3888
+ * with one box (`.frame::before`, `inset: 0 0 auto` at `--dsh-windows-titlebar-height`)
3889
+ * in `--dsw-specific-sidebar-fill` — the SIDEBAR's token — while the sidebar column
3890
+ * below it (`AppFrame.tsx`'s first grid track, `${cols.sidebar}px`) is the surface
3891
+ * that slider actually owns. The 标题栏 card took the band over in 0.3.6, which made
3892
+ * the strip above the sidebar follow the session title row instead of the sidebar. So
3893
+ * the band is now cut at the sidebar's own width:
3894
+ * · `::before` keeps the host's box (and its `-webkit-app-region: drag`) and is
3895
+ * CLIPPED to everything right of that width — `clip-path:inset(0 0 0 …)` clips
3896
+ * the paint AND the backdrop-filter's effect, so the region under the cut is
3897
+ * genuinely untouched rather than frosted twice.
3898
+ * · `::after` (a box `.frame` does not use on any release in range) paints exactly
3899
+ * that left segment from `--dsh-any-op-sidebar` — the same literal the sidebar
3900
+ * column is painted with — and frosts from the sidebar blur's root mirror, so the
3901
+ * column and the strip above it are one surface under one opacity + blur pair.
3902
+ * `--dsh-windows-sidebar-width` is published INLINE ON THE FRAME by the host
3903
+ * (`AppFrame.tsx`, Windows builds only, `${cols.sidebar}px`) and both pseudo-elements
3904
+ * inherit it, so the split tracks a dragged or collapsed sidebar with no observer of
3905
+ * our own. Collapsed is `0px`: the `::after` box vanishes and `::before` un-clips, so
3906
+ * the band goes back to being entirely the 标题栏 card's.
3907
+ * The native button block still reads the probe as `transparent`, and it sits at the
3908
+ * window's right edge — inside the region that stays 标题栏-owned. */
4077
3909
  function applyNativeCaption(surfaces) {
4078
3910
  if (!document.documentElement.hasAttribute("data-windows-titlebar")) return;
4079
- const fill = captionFill(surfaces === void 0 ? paletteTokens() ?? readHostOpacityTokens() : surfaces);
4080
- const css = fill === "" ? "" : `${WCO_PROBE_SEL}{--dsw-specific-sidebar-fill:${fill}}[data-windows-titlebar] .${CAPTION_FRAME_CLASS}::before{background:${fill}!important}`;
3911
+ const src = surfaces === void 0 ? paletteTokens() ?? readHostOpacityTokens() : surfaces;
3912
+ const fill = captionFill(src);
3913
+ const paint = headerBarPaint(src) || fill;
3914
+ const width = "var(--dsh-windows-sidebar-width,0px)";
3915
+ 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;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);-webkit-app-region:drag}[data-windows-titlebar] .${FLAT_CENTER_CLASS}{border-top-left-radius:0!important}`;
4081
3916
  const el = ensureWcoStyle();
4082
3917
  if (el.textContent !== css) el.textContent = css;
4083
3918
  discoverParts();
4084
3919
  if (frameEl === null) return;
4085
- if (css === "") frameEl.classList.remove(CAPTION_FRAME_CLASS);
4086
- else frameEl.classList.add(CAPTION_FRAME_CLASS);
3920
+ if (css === "") {
3921
+ frameEl.classList.remove(CAPTION_FRAME_CLASS);
3922
+ centerEl?.classList.remove(FLAT_CENTER_CLASS);
3923
+ } else {
3924
+ frameEl.classList.add(CAPTION_FRAME_CLASS);
3925
+ centerEl?.classList.add(FLAT_CENTER_CLASS);
3926
+ }
3927
+ }
3928
+ /** Mirror of the sidebar-part blur on `:root`, for the caption segment above the
3929
+ * sidebar column. `--dsh-any-part-blur` is written INLINE on the column and the band
3930
+ * is a pseudo-element of the FRAME, one level up, so it can never inherit it. */
3931
+ function applySidebarBandBlur(px) {
3932
+ if (px > 0) document.documentElement.style.setProperty("--dsh-any-blur-sidebar-band", `blur(${px}px)`);
3933
+ else document.documentElement.style.removeProperty("--dsh-any-blur-sidebar-band");
4087
3934
  }
4088
3935
  /** Mirror of the bg-part blur on :root. --dsh-any-part-blur is element-scoped
4089
3936
  * to the columns, so surfaces outside their subtree (the mobile header, or
@@ -4123,7 +3970,7 @@ window.__ModuleLoader__.load({
4123
3970
  * backdrop root each of those files documents avoiding. */
4124
3971
  const HEADER_BLUR_VAR = "--dsh-any-blur-header";
4125
3972
  function headerPopoverRule() {
4126
- return `${HEADER_SURFACES_PADDED.join(",")}{${menuFrost(HEADER_BLUR_VAR)}}`;
3973
+ return `${gated(HEADER_FROST_GATE, HEADER_SURFACES_PADDED.join(","))}{${menuFrost(HEADER_BLUR_VAR)}}`;
4127
3974
  }
4128
3975
  function exemptDefaultRule() {
4129
3976
  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}`;
@@ -4137,8 +3984,13 @@ window.__ModuleLoader__.load({
4137
3984
  function applyHeaderPopovers() {
4138
3985
  const root = document.documentElement;
4139
3986
  const px = rBlurs().header;
4140
- if (px > 0) root.style.setProperty("--dsh-any-blur-header", `blur(${px}px)`);
4141
- else root.style.removeProperty("--dsh-any-blur-header");
3987
+ if (px > 0) {
3988
+ root.style.setProperty(HEADER_BLUR_VAR, `blur(${px}px)`);
3989
+ root.classList.add(HEADER_FROST_GATE);
3990
+ } else {
3991
+ root.style.removeProperty(HEADER_BLUR_VAR);
3992
+ root.classList.remove(HEADER_FROST_GATE);
3993
+ }
4142
3994
  let opacity = rHeaderOpacity();
4143
3995
  if (opacity < 0) opacity = 0;
4144
3996
  if (opacity > 1) opacity = 1;
@@ -4206,7 +4058,7 @@ window.__ModuleLoader__.load({
4206
4058
  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}}`;
4207
4059
  const STROKE_RULE = [
4208
4060
  `${SETTINGS_PANEL_SEL}{-webkit-text-stroke:var(--dsh-any-stroke-settings-w,0px) var(--dsh-any-stroke-settings-c,transparent);paint-order:stroke fill}`,
4209
- "[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}",
4061
+ "[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}",
4210
4062
  `[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}`,
4211
4063
  `.${HEADER_BAR_CLASS}{-webkit-text-stroke:var(--dsh-any-stroke-headerBar-w,0px) var(--dsh-any-stroke-headerBar-c,transparent);paint-order:stroke fill}`,
4212
4064
  `[data-chat-flow]{-webkit-text-stroke:var(--dsh-any-stroke-chat-w,0px) var(--dsh-any-stroke-chat-c,transparent);paint-order:stroke fill}`,
@@ -4345,7 +4197,7 @@ window.__ModuleLoader__.load({
4345
4197
  if (typeof getComputedStyle !== "undefined") stack = getComputedStyle(document.documentElement).getPropertyValue("--dsw-font-family").trim();
4346
4198
  if (stack === "") stack = "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif";
4347
4199
  const fmt = fontFormatForMime(mime);
4348
- 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)}`;
4200
+ 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)}`;
4349
4201
  }
4350
4202
  /** Teardown only: drop the @font-face + token override. */
4351
4203
  function removeFontFace() {
@@ -4668,10 +4520,17 @@ window.__ModuleLoader__.load({
4668
4520
  /** Blur of the option panels inside the settings dialog (.dab-card), owned by
4669
4521
  * the "dialog option panel" (card) blur slider. Written as a plugin-owned
4670
4522
  * variable consumed by SETTINGS_STYLE_RULE — deliberately NOT applied to the
4671
- * homepage center column, which this slider must never touch. */
4523
+ * homepage center column, which this slider must never touch. The gate class is
4524
+ * what hands the menu frost token over at all (see `CARD_FROST_GATE`). */
4672
4525
  function applyCardPanelsBlur(px) {
4673
- if (px > 0) document.documentElement.style.setProperty("--dsh-any-blur-card-panels", `blur(${px}px)`);
4674
- else document.documentElement.style.removeProperty("--dsh-any-blur-card-panels");
4526
+ const root = document.documentElement;
4527
+ if (px > 0) {
4528
+ root.style.setProperty(CARD_BLUR_VAR, `blur(${px}px)`);
4529
+ root.classList.add(CARD_FROST_GATE);
4530
+ } else {
4531
+ root.style.removeProperty(CARD_BLUR_VAR);
4532
+ root.classList.remove(CARD_FROST_GATE);
4533
+ }
4675
4534
  }
4676
4535
  /** Apply per-part interface blur to the AppFrame columns + settings panel. */
4677
4536
  function applyPartBlurs(blurs) {
@@ -4681,6 +4540,7 @@ window.__ModuleLoader__.load({
4681
4540
  setBlur(centerEl, blurs.bg);
4682
4541
  setBlur(rightEl, 0);
4683
4542
  applyBgBlurGlobal(blurs.bg);
4543
+ applySidebarBandBlur(blurs.sidebar);
4684
4544
  applyCardPanelsBlur(blurs.card);
4685
4545
  applySettingsBlur(blurs.settings);
4686
4546
  applyInputBlur(blurs.input);
@@ -4751,7 +4611,10 @@ window.__ModuleLoader__.load({
4751
4611
  if (part === "bg") {
4752
4612
  setBlur(centerEl, v);
4753
4613
  applyBgBlurGlobal(v);
4754
- } else setBlur(sidebarEl, v);
4614
+ return;
4615
+ }
4616
+ setBlur(sidebarEl, v);
4617
+ applySidebarBandBlur(v);
4755
4618
  }
4756
4619
  const VIEW_CARDS = [{
4757
4620
  sel: "[data-chat-flow]",
@@ -5398,12 +5261,15 @@ window.__ModuleLoader__.load({
5398
5261
  const name = set[k];
5399
5262
  if (name) document.documentElement.style.removeProperty(name);
5400
5263
  }
5401
- document.documentElement.classList.remove(INPUT_FROST_CLASS);
5264
+ document.documentElement.classList.remove(INPUT_FROST_CLASS, CARD_FROST_GATE, HEADER_FROST_GATE);
5402
5265
  applyTakeoverFrost(0);
5403
5266
  document.documentElement.style.removeProperty("--dsh-any-op-header-bar");
5404
5267
  document.documentElement.style.removeProperty("--dsh-any-blur-header-bar");
5268
+ document.documentElement.style.removeProperty("--dsh-any-blur-sidebar-band");
5405
5269
  document.querySelectorAll(`.${HEADER_BAR_CLASS}`).forEach((el) => el.classList.remove(HEADER_BAR_CLASS));
5270
+ headerBarEl = null;
5406
5271
  frameEl?.classList.remove(CAPTION_FRAME_CLASS);
5272
+ document.querySelectorAll(`.${FLAT_CENTER_CLASS}`).forEach((el) => el.classList.remove(FLAT_CENTER_CLASS));
5407
5273
  wcoStyleEl?.remove();
5408
5274
  wcoStyleEl = null;
5409
5275
  document.documentElement.style.removeProperty("--dsh-any-blur-panel");