dsh-background-by-model 0.4.2 → 0.4.4

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
@@ -1123,2237 +1123,6 @@ window.__ModuleLoader__.load({
1123
1123
  Math.round((b + m) * 255)
1124
1124
  ];
1125
1125
  }
1126
- //#endregion
1127
- //#region src/client/wallpaper.ts
1128
- let wpEl = null;
1129
- let appliedTokenNames = [];
1130
- let tokenStyleEl = null;
1131
- function ensureTokenStyle() {
1132
- if (tokenStyleEl?.isConnected) return tokenStyleEl;
1133
- tokenStyleEl = document.createElement("style");
1134
- tokenStyleEl.dataset.plugin = "dsh-background-by-model-tokens";
1135
- document.head.appendChild(tokenStyleEl);
1136
- return tokenStyleEl;
1137
- }
1138
- function clearCustomTokens() {
1139
- if (tokenStyleEl) tokenStyleEl.textContent = "";
1140
- for (const name of appliedTokenNames) document.body.style.removeProperty(name);
1141
- appliedTokenNames = [];
1142
- }
1143
- const OPACITY_TOKEN_GROUPS = [
1144
- {
1145
- part: "bg",
1146
- names: ["--dsw-alias-bg-base"]
1147
- },
1148
- {
1149
- part: "sidebar",
1150
- names: ["--dsw-specific-sidebar-fill"]
1151
- },
1152
- {
1153
- part: "card",
1154
- names: [
1155
- "--dsw-alias-bg-layer-1",
1156
- "--dsw-alias-bg-layer-2",
1157
- "--dsw-alias-bg-layer-3",
1158
- "--dsw-specific-menu",
1159
- "--dsw-menu-surface-fill"
1160
- ]
1161
- },
1162
- {
1163
- part: "input",
1164
- names: ["--dsw-specific-input-major"]
1165
- }
1166
- ];
1167
- const OPACITY_VARS = {
1168
- "--dsw-alias-bg-base": "--dsh-any-op-bg",
1169
- "--dsw-specific-sidebar-fill": "--dsh-any-op-sidebar",
1170
- "--dsw-alias-bg-layer-1": "--dsh-any-op-card-1",
1171
- "--dsw-alias-bg-layer-2": "--dsh-any-op-card-2",
1172
- "--dsw-alias-bg-layer-3": "--dsh-any-op-card-3",
1173
- "--dsw-specific-input-major": "--dsh-any-op-input",
1174
- "--dsw-specific-menu": "--dsh-any-op-menu",
1175
- "--dsw-menu-surface-fill": "--dsh-any-op-menu"
1176
- };
1177
- let baseTokenKey = "";
1178
- let pendingOps = null;
1179
- let tokensRaf = null;
1180
- function applyCustomTokens(ops) {
1181
- pendingOps = ops;
1182
- if (tokensRaf !== null) return;
1183
- tokensRaf = requestAnimationFrame(() => {
1184
- tokensRaf = null;
1185
- if (pendingOps === null) return;
1186
- const o = pendingOps;
1187
- pendingOps = null;
1188
- applyCustomTokensNow(o);
1189
- });
1190
- }
1191
- let lastBgKey = "";
1192
- function applyCustomTokensNow(ops) {
1193
- const [h, s, l] = rColor();
1194
- const { tokens } = genTokens(h, s, l);
1195
- try {
1196
- const forceDark = l < .55;
1197
- if (`${h}|${s}|${l}` !== baseTokenKey) {
1198
- baseTokenKey = `${h}|${s}|${l}`;
1199
- if (forceDark) document.body.setAttribute("data-ds-dark-theme", "dsh-background-by-model");
1200
- else document.body.removeAttribute("data-ds-dark-theme");
1201
- const decls = [`color-scheme:${forceDark ? "dark" : "light"}`];
1202
- for (const [name, value] of Object.entries(tokens)) {
1203
- const opVar = OPACITY_VARS[name];
1204
- decls.push(`${name}:${opVar !== void 0 ? `var(${opVar})` : value}!important`);
1205
- }
1206
- ensureTokenStyle().textContent = `body{${decls.join(";")}}`;
1207
- for (const name of appliedTokenNames) document.body.style.removeProperty(name);
1208
- appliedTokenNames = Object.keys(tokens);
1209
- }
1210
- const root = document.documentElement;
1211
- for (const g of OPACITY_TOKEN_GROUPS) for (const name of g.names) root.style.setProperty(OPACITY_VARS[name], toRgba(tokens[name] ?? "#000", ops[g.part]));
1212
- root.style.setProperty("--dsh-any-op-menu-cordis", toRgba(tokens["--dsw-specific-menu"] ?? "#000", ops.input));
1213
- const bgKey = `${baseTokenKey}|${ops.bg}`;
1214
- if (bgKey !== lastBgKey) {
1215
- lastBgKey = bgKey;
1216
- applyPartOpacities(ops);
1217
- }
1218
- } catch {}
1219
- }
1220
- const SETTINGS_PANEL_SEL = "[role=\"dialog\"][aria-modal=\"true\"][aria-labelledby]";
1221
- 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)}`;
1222
- const INPUT_BLUR_RULE = "[data-composer-card],[data-cordis-panel]{-webkit-backdrop-filter:var(--dsh-any-input-blur,none);backdrop-filter:var(--dsh-any-input-blur,none)}[data-cordis-panel]{--dsw-specific-menu:var(--dsh-any-op-menu-cordis)!important}";
1223
- function applyInputBlur(px) {
1224
- if (px > 0) document.documentElement.style.setProperty("--dsh-any-input-blur", `blur(${px}px)`);
1225
- else document.documentElement.style.removeProperty("--dsh-any-input-blur");
1226
- }
1227
- function applySettingsOverrides(op) {
1228
- const [h, s, l] = rColor();
1229
- const tokens = genTokens(h, s, l).tokens;
1230
- const layer1 = tokens["--dsw-alias-bg-layer-1"];
1231
- const layer2 = tokens["--dsw-alias-bg-layer-2"];
1232
- const layer3 = tokens["--dsw-alias-bg-layer-3"];
1233
- if (layer2 !== void 0) document.documentElement.style.setProperty("--dsh-any-bg-settings-surface", toRgba(layer2, op));
1234
- if (layer1 !== void 0) document.documentElement.style.setProperty("--dsh-any-bg-settings-layer-1", toRgba(layer1, op));
1235
- if (layer2 !== void 0) document.documentElement.style.setProperty("--dsh-any-bg-settings-layer-2", toRgba(layer2, op));
1236
- if (layer3 !== void 0) document.documentElement.style.setProperty("--dsh-any-bg-settings-layer-3", toRgba(layer3, op));
1237
- }
1238
- const TRAJECTORY_STYLE_RULE = "[data-conversation-composer-overlay]{--dsw-alias-bg-layer-1:var(--dsh-any-traj-layer-1);--dsw-alias-bg-layer-2:var(--dsh-any-traj-layer-2);--dsw-alias-bg-layer-3:var(--dsh-any-traj-layer-3)}";
1239
- function applyTrajectoryOverrides(op) {
1240
- const [h, s, l] = rColor();
1241
- const tokens = genTokens(h, s, l).tokens;
1242
- const layer1 = tokens["--dsw-alias-bg-layer-1"];
1243
- const layer2 = tokens["--dsw-alias-bg-layer-2"];
1244
- const layer3 = tokens["--dsw-alias-bg-layer-3"];
1245
- if (layer1 !== void 0) document.documentElement.style.setProperty("--dsh-any-traj-layer-1", toRgba(layer1, op));
1246
- if (layer2 !== void 0) document.documentElement.style.setProperty("--dsh-any-traj-layer-2", toRgba(layer2, op));
1247
- if (layer3 !== void 0) document.documentElement.style.setProperty("--dsh-any-traj-layer-3", toRgba(layer3, op));
1248
- }
1249
- const RIGHTBAR_STYLE_RULE = "div[data-sidebar-right-panel]:not(:has([data-dockkit-content],[data-dockkit-empty]))[data-sidebar-right-open],div[data-sidebar-right-panel] [data-dockkit-content]:not([data-dockkit-float]),div[data-sidebar-right-panel] [data-dockkit-empty]{background:var(--dsh-any-bg-rightbar,var(--dsw-alias-bg-base))!important}div[data-sidebar-right-panel][data-sidebar-right-open]{-webkit-backdrop-filter:var(--dsh-any-blur-rightbar,none);backdrop-filter:var(--dsh-any-blur-rightbar,none)}";
1250
- /** Paint the file-preview panel's surface from its own slider. */
1251
- function applyRightbarOverrides(op) {
1252
- const root = document.documentElement;
1253
- if (!rHasColor()) {
1254
- root.style.removeProperty("--dsh-any-bg-rightbar");
1255
- return;
1256
- }
1257
- const [h, s, l] = rColor();
1258
- const base = genTokens(h, s, l).tokens["--dsw-alias-bg-base"];
1259
- if (base !== void 0) root.style.setProperty("--dsh-any-bg-rightbar", toRgba(base, op));
1260
- }
1261
- /** Extra backdrop frost for the file-preview panel (0 = no frost of its own). */
1262
- function applyRightbarBlur(px) {
1263
- if (px > 0) document.documentElement.style.setProperty("--dsh-any-blur-rightbar", `blur(${px}px)`);
1264
- else document.documentElement.style.removeProperty("--dsh-any-blur-rightbar");
1265
- }
1266
- let frameEl = null;
1267
- let sidebarEl = null;
1268
- let centerEl = null;
1269
- let detailsEl = null;
1270
- const PART_BLUR_CLASS = "dab-part-blur";
1271
- const PART_UNDERLAY_CLASS = "dab-part-underlay";
1272
- const PART_BLUR_RULE = `${PART_BLUR_CLASS}{isolation:isolate}.${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)}`;
1273
- let partBlurStyleEl = null;
1274
- function ensurePartBlurStyle() {
1275
- if (partBlurStyleEl?.isConnected) return;
1276
- partBlurStyleEl = document.createElement("style");
1277
- partBlurStyleEl.dataset.plugin = "dsh-background-by-model-parts";
1278
- partBlurStyleEl.textContent = PART_BLUR_RULE;
1279
- document.head.appendChild(partBlurStyleEl);
1280
- }
1281
- function discoverParts() {
1282
- const overlay = document.querySelector("[data-shell-overlay]");
1283
- if (overlay === null) return;
1284
- const frame = overlay.parentElement;
1285
- if (frame === null) return;
1286
- frameEl = frame;
1287
- const idx = Array.from(frame.children).indexOf(overlay);
1288
- sidebarEl = frame.children[idx - 3] ?? null;
1289
- centerEl = frame.children[idx - 2] ?? null;
1290
- detailsEl = frame.children[idx - 1] ?? null;
1291
- }
1292
- function setBlur(el, px) {
1293
- if (el === null) return;
1294
- const underlay = el.querySelector(`:scope > .${PART_UNDERLAY_CLASS}`);
1295
- if (px > 0) {
1296
- ensurePartBlurStyle();
1297
- if (!el.classList.contains(PART_BLUR_CLASS) && getComputedStyle(el).position === "static") {
1298
- el.style.position = "relative";
1299
- el.setAttribute("data-dab-pos-patched", "1");
1300
- }
1301
- el.classList.add(PART_BLUR_CLASS);
1302
- if (underlay === null) {
1303
- const node = document.createElement("div");
1304
- node.className = PART_UNDERLAY_CLASS;
1305
- el.prepend(node);
1306
- }
1307
- el.style.setProperty("--dsh-any-part-blur", `blur(${px}px)`);
1308
- } else {
1309
- el.classList.remove(PART_BLUR_CLASS);
1310
- el.style.removeProperty("--dsh-any-part-blur");
1311
- underlay?.remove();
1312
- if (el.getAttribute("data-dab-pos-patched") === "1") {
1313
- el.style.removeProperty("position");
1314
- el.removeAttribute("data-dab-pos-patched");
1315
- }
1316
- }
1317
- }
1318
- function applySettingsBlur(px) {
1319
- if (px > 0) document.documentElement.style.setProperty("--dsh-any-blur-settings", `blur(${px}px)`);
1320
- else document.documentElement.style.removeProperty("--dsh-any-blur-settings");
1321
- }
1322
- /** Apply the main-background opacity to the center/details columns instead of
1323
- * the frame. The frame's translucent bg-base sits UNDER the sidebar, so
1324
- * reducing the main-bg opacity stacked a second alpha onto the sidebar; moving
1325
- * the alpha onto the columns keeps the sidebar owned by its own slider. */
1326
- function applyPartOpacities(ops) {
1327
- if (!rHasColor()) return;
1328
- discoverParts();
1329
- if (frameEl === null) return;
1330
- const [h, s, l] = rColor();
1331
- const base = genTokens(h, s, l).tokens["--dsw-alias-bg-base"];
1332
- frameEl.style.background = "transparent";
1333
- if (centerEl !== null) centerEl.style.background = base !== void 0 ? toRgba(base, ops.bg) : "transparent";
1334
- if (detailsEl !== null) detailsEl.style.background = base !== void 0 ? toRgba(base, ops.bg) : "transparent";
1335
- }
1336
- /** Blur of the option panels inside the settings dialog (.dab-card), owned by
1337
- * the "dialog option panel" (card) blur slider. Written as a plugin-owned
1338
- * variable consumed by SETTINGS_STYLE_RULE — deliberately NOT applied to the
1339
- * homepage center/details columns, which this slider must never touch. */
1340
- function applyCardPanelsBlur(px) {
1341
- if (px > 0) document.documentElement.style.setProperty("--dsh-any-blur-card-panels", `blur(${px}px)`);
1342
- else document.documentElement.style.removeProperty("--dsh-any-blur-card-panels");
1343
- }
1344
- /** Apply per-part interface blur to the AppFrame columns + settings panel. */
1345
- function applyPartBlurs(blurs) {
1346
- discoverParts();
1347
- setBlur(frameEl, 0);
1348
- setBlur(sidebarEl, blurs.sidebar);
1349
- setBlur(centerEl, blurs.bg);
1350
- setBlur(detailsEl, blurs.bg);
1351
- applyCardPanelsBlur(blurs.card);
1352
- applySettingsBlur(blurs.settings);
1353
- applyInputBlur(blurs.input);
1354
- applyRightbarBlur(blurs.rightbar);
1355
- applyViewCards();
1356
- }
1357
- /** Live per-part blur update during slider drag (no full re-apply). */
1358
- function setPartBlur(part, v) {
1359
- if (part === "settings") {
1360
- applySettingsBlur(v);
1361
- return;
1362
- }
1363
- if (part === "card") {
1364
- applyCardPanelsBlur(v);
1365
- return;
1366
- }
1367
- if (part === "input") {
1368
- applyInputBlur(v);
1369
- return;
1370
- }
1371
- if (part === "rightbar") {
1372
- applyRightbarBlur(v);
1373
- return;
1374
- }
1375
- if (part === "chat" || part === "trajectory") {
1376
- applyViewCards();
1377
- return;
1378
- }
1379
- discoverParts();
1380
- if (part === "bg") {
1381
- setBlur(centerEl, v);
1382
- setBlur(detailsEl, v);
1383
- } else setBlur(sidebarEl, v);
1384
- }
1385
- const VIEW_CARDS = [{
1386
- sel: "[data-chat-flow]",
1387
- mark: "data-dab-chat-card",
1388
- prev: "dabChatPrev",
1389
- opacity: rChatTextOpacity,
1390
- blur: () => rBlurs().chat,
1391
- fallback: true
1392
- }, {
1393
- sel: "[data-conversation-composer-overlay]",
1394
- mark: "data-dab-traj-card",
1395
- prev: "dabTrajPrev",
1396
- opacity: rTrajectoryOpacity,
1397
- blur: () => rBlurs().trajectory,
1398
- plain: true
1399
- }];
1400
- const viewTargets = VIEW_CARDS.map(() => null);
1401
- function isScrollableY(el) {
1402
- const oy = getComputedStyle(el).overflowY;
1403
- return oy === "auto" || oy === "scroll" || oy === "overlay";
1404
- }
1405
- /** Whether the subtree hosts the chat input (textarea / contenteditable /
1406
- * textbox role) — used to keep the card off the input row. */
1407
- function containsChatEditor(el) {
1408
- return el.querySelector("textarea,[contenteditable=\"true\"],[contenteditable=\"\"],[contenteditable=\"plaintext-only\"],[role=\"textbox\"]") !== null;
1409
- }
1410
- /** Walk down from a coarse candidate toward the actual message column. */
1411
- function refineMessageColumn(start) {
1412
- let cur = start;
1413
- for (let depth = 0; depth < 10; depth++) {
1414
- if (isScrollableY(cur)) break;
1415
- const kids = Array.from(cur.children).filter((k) => k instanceof HTMLElement);
1416
- if (kids.length === 0) break;
1417
- const tallest = kids.reduce((a, b) => b.clientHeight > a.clientHeight ? b : a);
1418
- if (containsChatEditor(cur)) {
1419
- const candidates = kids.filter((k) => !containsChatEditor(k) && k.clientHeight >= cur.clientHeight * .4);
1420
- if (candidates.length === 0) break;
1421
- cur = candidates.reduce((a, b) => b.clientHeight > a.clientHeight ? b : a);
1422
- continue;
1423
- }
1424
- if (kids.length > 1 && tallest.clientHeight >= cur.clientHeight * .85) {
1425
- cur = tallest;
1426
- continue;
1427
- }
1428
- break;
1429
- }
1430
- return cur;
1431
- }
1432
- function discoverViewTarget(idx, spec) {
1433
- if (centerEl === null || !document.body.contains(centerEl)) {
1434
- viewTargets[idx] = null;
1435
- return null;
1436
- }
1437
- const marked = centerEl.querySelector(spec.sel);
1438
- const cached = viewTargets[idx];
1439
- if (marked !== null) {
1440
- if (cached !== null && cached !== marked) {
1441
- setBlur(cached, 0);
1442
- restoreCardHost(cached, spec.mark, spec.prev, spec.plain === true);
1443
- }
1444
- viewTargets[idx] = marked;
1445
- return marked;
1446
- }
1447
- if (cached !== null && centerEl.contains(cached)) return cached;
1448
- viewTargets[idx] = null;
1449
- if (spec.fallback !== true) return null;
1450
- if (centerEl.querySelector("[data-conversation-scroll]") !== null) return null;
1451
- if (spec.opacity() <= 0 && spec.blur() <= 0) return null;
1452
- let best = null;
1453
- let bestArea = 0;
1454
- for (const el of Array.from(centerEl.querySelectorAll("*"))) {
1455
- if (!isScrollableY(el)) continue;
1456
- if (el.clientHeight < centerEl.clientHeight * .35) continue;
1457
- const area = el.clientWidth * el.clientHeight;
1458
- if (area > bestArea) {
1459
- bestArea = area;
1460
- best = el;
1461
- }
1462
- }
1463
- if (best === null) for (const el of Array.from(centerEl.children)) {
1464
- if (!(el instanceof HTMLElement)) continue;
1465
- if (el.clientHeight < centerEl.clientHeight * .5) continue;
1466
- if (el.clientHeight > (best?.clientHeight ?? 0)) best = el;
1467
- }
1468
- const refined = best !== null ? refineMessageColumn(best) : null;
1469
- viewTargets[idx] = refined;
1470
- return refined;
1471
- }
1472
- /** Stash the host's own inline values so teardown restores them exactly. */
1473
- function stashCardPrev(el, prev, plain) {
1474
- const ds = el.dataset;
1475
- ds[prev + "Bg"] = el.style.getPropertyValue("background");
1476
- if (plain) return;
1477
- ds[prev + "Border"] = el.style.getPropertyValue("border");
1478
- ds[prev + "Radius"] = el.style.getPropertyValue("border-radius");
1479
- ds[prev + "Padding"] = el.style.getPropertyValue("padding");
1480
- }
1481
- /** Undo the inline styling, restoring the host's previous inline values. */
1482
- function restoreCardHost(el, mark, prev, plain) {
1483
- if (!el.hasAttribute(mark)) return;
1484
- const ds = el.dataset;
1485
- const restore = (prop, v) => {
1486
- if (v !== void 0 && v !== "") el.style.setProperty(prop, v);
1487
- else el.style.removeProperty(prop);
1488
- };
1489
- restore("background", ds[prev + "Bg"]);
1490
- if (!plain) {
1491
- restore("border", ds[prev + "Border"]);
1492
- restore("border-radius", ds[prev + "Radius"]);
1493
- restore("padding", ds[prev + "Padding"]);
1494
- delete ds[prev + "Border"];
1495
- delete ds[prev + "Radius"];
1496
- delete ds[prev + "Padding"];
1497
- }
1498
- delete ds[prev + "Bg"];
1499
- el.removeAttribute(mark);
1500
- }
1501
- /** Teardown only: strip every view treatment and hand the hosts back untouched. */
1502
- function removeViewCards() {
1503
- VIEW_CARDS.forEach((spec, i) => {
1504
- const el = viewTargets[i];
1505
- if (el !== null) {
1506
- setBlur(el, 0);
1507
- restoreCardHost(el, spec.mark, spec.prev, spec.plain === true);
1508
- }
1509
- viewTargets[i] = null;
1510
- });
1511
- }
1512
- const TABLE_FIX_RULE = [
1513
- ".md-table-wide {",
1514
- " --dsh-table-spare: 0px !important;",
1515
- " --dsh-table-lead: 0px !important;",
1516
- " box-sizing: border-box !important;",
1517
- " width: 100% !important;",
1518
- " max-width: 100% !important;",
1519
- " margin-left: 0 !important;",
1520
- " padding-left: 0 !important;",
1521
- " padding-bottom: 0 !important;",
1522
- " overflow-x: auto !important;",
1523
- "}"
1524
- ].join("\n");
1525
- let tableFixStyleEl = null;
1526
- /** Toggle the wide-table clamp according to the chat region's opacity & blur. */
1527
- function syncTableFix() {
1528
- if (!(rChatTextOpacity() > 0 || rBlurs().chat > 0)) {
1529
- if (tableFixStyleEl !== null) {
1530
- tableFixStyleEl.remove();
1531
- tableFixStyleEl = null;
1532
- }
1533
- return;
1534
- }
1535
- if (tableFixStyleEl === null) {
1536
- tableFixStyleEl = document.createElement("style");
1537
- tableFixStyleEl.dataset.plugin = "dsh-background-by-model-table-fix";
1538
- tableFixStyleEl.textContent = TABLE_FIX_RULE;
1539
- }
1540
- if (!tableFixStyleEl.isConnected) document.head.appendChild(tableFixStyleEl);
1541
- }
1542
- /** Re-derive the conversation view cards from the current config. */
1543
- function applyViewCards() {
1544
- discoverParts();
1545
- if (centerEl === null) return;
1546
- const [h, s, l] = rColor();
1547
- const surface = genTokens(h, s, l).tokens["--dsw-alias-bg-layer-1"];
1548
- VIEW_CARDS.forEach((spec, i) => {
1549
- const target = discoverViewTarget(i, spec);
1550
- if (target === null) return;
1551
- const plain = spec.plain === true;
1552
- const opacity = spec.opacity();
1553
- const blurPx = spec.blur();
1554
- if (!plain) {
1555
- if (!target.hasAttribute(spec.mark)) stashCardPrev(target, spec.prev, false);
1556
- const borderAlpha = opacity > 0 ? Math.min(1, opacity * 1.5) : blurPx > 0 ? .35 : 0;
1557
- target.style.background = surface !== void 0 ? toRgba(surface, opacity) : "transparent";
1558
- target.style.border = surface !== void 0 ? `1px solid ${toRgba(surface, borderAlpha)}` : "1px solid transparent";
1559
- target.style.borderRadius = "16px";
1560
- target.style.padding = "18px";
1561
- }
1562
- target.setAttribute(spec.mark, "1");
1563
- setBlur(target, blurPx);
1564
- });
1565
- syncTableFix();
1566
- }
1567
- let partsObserver = null;
1568
- /** Watch for the AppFrame mounting so persisted blurs land even when the shell
1569
- * renders after this plugin's apply. */
1570
- function watchParts() {
1571
- if (partsObserver !== null || typeof MutationObserver === "undefined") return;
1572
- partsObserver = new MutationObserver(() => {
1573
- if (frameEl !== null && sidebarEl !== null && centerEl !== null && detailsEl !== null && document.body.contains(frameEl) && viewTargets.every((el) => el !== null && document.body.contains(el))) return;
1574
- applyPartBlurs(rBlurs());
1575
- applyPartOpacities(rOps());
1576
- });
1577
- partsObserver.observe(document.body, {
1578
- childList: true,
1579
- subtree: true
1580
- });
1581
- }
1582
- function stopWatchingParts() {
1583
- partsObserver?.disconnect();
1584
- partsObserver = null;
1585
- }
1586
- let themeObserver = null;
1587
- let themeRaf = 0;
1588
- function reassertScheme() {
1589
- const [, , l] = rColor();
1590
- if (l < .55) document.body.setAttribute("data-ds-dark-theme", "dsh-background-by-model");
1591
- else document.body.removeAttribute("data-ds-dark-theme");
1592
- applyCustomTokens(rOps());
1593
- }
1594
- /** Re-assert the plugin's forced scheme whenever the host strips it, so a
1595
- * refresh / cold-load / set-change never flashes a light frame. */
1596
- function watchThemeResets() {
1597
- if (themeObserver !== null || typeof MutationObserver === "undefined") return () => void 0;
1598
- themeObserver = new MutationObserver(() => {
1599
- if (document.body.getAttribute("data-ds-dark-theme") === "dsh-background-by-model") return;
1600
- if (!rHasColor()) return;
1601
- if (themeRaf !== 0) return;
1602
- themeRaf = requestAnimationFrame(() => {
1603
- themeRaf = 0;
1604
- if (document.body.getAttribute("data-ds-dark-theme") === "dsh-background-by-model") return;
1605
- reassertScheme();
1606
- });
1607
- });
1608
- themeObserver.observe(document.body, {
1609
- attributes: true,
1610
- attributeFilter: ["data-ds-dark-theme"]
1611
- });
1612
- themeObserver.observe(document.documentElement, {
1613
- attributes: true,
1614
- attributeFilter: ["data-ds-dark-theme"]
1615
- });
1616
- return () => {
1617
- themeObserver?.disconnect();
1618
- themeObserver = null;
1619
- };
1620
- }
1621
- const FADE_MS = 320;
1622
- let layers = null;
1623
- /** Index of the layer the user is looking at. */
1624
- let front = 0;
1625
- /** Index of the layer fading in right now, if any. */
1626
- let pending = null;
1627
- let fadeTimer = null;
1628
- function createLayer() {
1629
- const el = document.createElement("div");
1630
- el.dataset.dabWpLayer = "";
1631
- el.style.cssText = "position:absolute;inset:0;opacity:0;background-repeat:no-repeat;background-position:center;";
1632
- return {
1633
- el,
1634
- url: null
1635
- };
1636
- }
1637
- function ensureWpContainer() {
1638
- if (wpEl !== null && layers !== null && document.body.contains(wpEl)) return;
1639
- if (fadeTimer !== null) {
1640
- window.clearTimeout(fadeTimer);
1641
- fadeTimer = null;
1642
- }
1643
- const a = createLayer();
1644
- const b = createLayer();
1645
- wpEl = document.createElement("div");
1646
- wpEl.dataset.dabWp = "";
1647
- wpEl.style.cssText = "position:fixed;inset:0;z-index:-1;pointer-events:none;overflow:hidden;";
1648
- wpEl.append(a.el, b.el);
1649
- layers = [a, b];
1650
- front = 0;
1651
- pending = null;
1652
- document.body.prepend(wpEl);
1653
- }
1654
- /** Drop the whole wallpaper layer (no rule carries an image). */
1655
- function dropWpContainer() {
1656
- if (fadeTimer !== null) {
1657
- window.clearTimeout(fadeTimer);
1658
- fadeTimer = null;
1659
- }
1660
- pending = null;
1661
- front = 0;
1662
- layers = null;
1663
- wpEl?.remove();
1664
- wpEl = null;
1665
- }
1666
- /** Release one layer's image — the layer is fully covered when this is called. */
1667
- function clearLayer(l) {
1668
- if (l.url === null) return;
1669
- l.url = null;
1670
- l.el.style.backgroundImage = "";
1671
- l.el.style.backgroundSize = "";
1672
- l.el.style.backgroundPosition = "";
1673
- }
1674
- /** Intrinsic-size cache for the center mode (native pixels of the current image). */
1675
- let imgNat = null;
1676
- function imageNatSize(url, cb) {
1677
- if (imgNat !== null && imgNat.url === url) {
1678
- cb(imgNat.w, imgNat.h);
1679
- return;
1680
- }
1681
- const img = new Image();
1682
- img.onload = () => {
1683
- imgNat = {
1684
- url,
1685
- w: img.naturalWidth,
1686
- h: img.naturalHeight
1687
- };
1688
- cb(img.naturalWidth, img.naturalHeight);
1689
- };
1690
- img.onerror = () => cb(0, 0);
1691
- img.src = url;
1692
- }
1693
- /** Paint one layer's image and its placement. The placement always follows the
1694
- * ACTIVE rule: the incoming layer is painted right after a switch, and a
1695
- * repaint of the same URL (framing edit, viewport resize) must use the rule's
1696
- * current framing. */
1697
- function paintLayer(l, url) {
1698
- const el = l.el;
1699
- l.url = url;
1700
- const bg = rBgState();
1701
- const mode = rBgMode();
1702
- const next = `url("${url}")`;
1703
- if (el.style.backgroundImage !== next) el.style.backgroundImage = next;
1704
- if (mode === "fit") {
1705
- el.style.backgroundRepeat = "no-repeat";
1706
- if (bg.iw > 0) {
1707
- const fit = Math.min(window.innerWidth / bg.iw, window.innerHeight / bg.ih);
1708
- const w = bg.iw * fit * bg.zoom;
1709
- const h = bg.ih * fit * bg.zoom;
1710
- el.style.backgroundSize = `${w}px ${h}px`;
1711
- el.style.backgroundPosition = `${bg.x * window.innerWidth - w / 2}px ${bg.y * window.innerHeight - h / 2}px`;
1712
- } else {
1713
- el.style.backgroundSize = "contain";
1714
- el.style.backgroundPosition = "center";
1715
- }
1716
- } else if (mode === "fill") {
1717
- el.style.backgroundRepeat = "no-repeat";
1718
- el.style.backgroundSize = "cover";
1719
- el.style.backgroundPosition = "center";
1720
- } else if (mode === "stretch") {
1721
- el.style.backgroundRepeat = "no-repeat";
1722
- el.style.backgroundSize = "100% 100%";
1723
- el.style.backgroundPosition = "center";
1724
- } else if (mode === "tile") {
1725
- el.style.backgroundRepeat = "repeat";
1726
- el.style.backgroundSize = "auto";
1727
- el.style.backgroundPosition = "0px 0px";
1728
- } else {
1729
- el.style.backgroundRepeat = "no-repeat";
1730
- el.style.backgroundSize = "contain";
1731
- el.style.backgroundPosition = "center";
1732
- imageNatSize(url, (w, h) => {
1733
- if (l.url !== url || el.style.backgroundImage !== next || rBgMode() !== "center") return;
1734
- if (w > 0 && h > 0) {
1735
- el.style.backgroundSize = `${w}px ${h}px`;
1736
- el.style.backgroundPosition = "center";
1737
- }
1738
- });
1739
- }
1740
- }
1741
- /** Whether a switch should animate: nothing to fade from, an invisible
1742
- * wallpaper and a reduced-motion preference all switch instantly. */
1743
- function canFade(from) {
1744
- if (from.url === null) return false;
1745
- if (rWop() <= .01) return false;
1746
- const mm = typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)") : null;
1747
- return mm === null || !mm.matches;
1748
- }
1749
- /** End an in-flight fade: promote the incoming layer, free the one under it. */
1750
- function finishFade(idx) {
1751
- if (layers === null || pending !== idx) return;
1752
- if (fadeTimer !== null) {
1753
- window.clearTimeout(fadeTimer);
1754
- fadeTimer = null;
1755
- }
1756
- pending = null;
1757
- front = idx;
1758
- const el = layers[idx].el;
1759
- el.style.opacity = "1";
1760
- el.style.willChange = "";
1761
- clearLayer(layers[idx === 0 ? 1 : 0]);
1762
- }
1763
- /** Land an in-flight fade on its end state at once (another switch arrived). */
1764
- function settleFade() {
1765
- if (layers === null || pending === null) return;
1766
- const idx = pending;
1767
- if (fadeTimer !== null) {
1768
- window.clearTimeout(fadeTimer);
1769
- fadeTimer = null;
1770
- }
1771
- pending = null;
1772
- front = idx;
1773
- const el = layers[idx].el;
1774
- el.style.transition = "none";
1775
- el.style.opacity = "1";
1776
- el.style.willChange = "";
1777
- clearLayer(layers[idx === 0 ? 1 : 0]);
1778
- }
1779
- /** Abandon an in-flight fade: the layer it was leaving holds the wanted image. */
1780
- function cancelFade() {
1781
- if (layers === null || pending === null) return;
1782
- const idx = pending;
1783
- if (fadeTimer !== null) {
1784
- window.clearTimeout(fadeTimer);
1785
- fadeTimer = null;
1786
- }
1787
- pending = null;
1788
- const el = layers[idx].el;
1789
- el.style.transition = "none";
1790
- el.style.opacity = "0";
1791
- el.style.willChange = "";
1792
- clearLayer(layers[idx]);
1793
- }
1794
- /** Show `url` on the wallpaper layer, cross-fading from what it paints now. */
1795
- function applyImageWp(url) {
1796
- ensureWpContainer();
1797
- const ls = layers;
1798
- if (pending !== null) {
1799
- if (ls[pending].url === url) {
1800
- paintLayer(ls[pending], url);
1801
- applyWpEffects();
1802
- return;
1803
- }
1804
- if (ls[front].url === url) {
1805
- cancelFade();
1806
- paintLayer(ls[front], url);
1807
- applyWpEffects();
1808
- return;
1809
- }
1810
- settleFade();
1811
- }
1812
- if (ls[front].url === url) {
1813
- paintLayer(ls[front], url);
1814
- applyWpEffects();
1815
- return;
1816
- }
1817
- const from = ls[front];
1818
- const idx = front === 0 ? 1 : 0;
1819
- const target = ls[idx];
1820
- const el = target.el;
1821
- el.style.transition = "none";
1822
- el.style.opacity = "0";
1823
- el.style.zIndex = "1";
1824
- from.el.style.zIndex = "0";
1825
- paintLayer(target, url);
1826
- applyWpEffects();
1827
- if (!canFade(from)) {
1828
- el.style.opacity = "1";
1829
- front = idx;
1830
- clearLayer(from);
1831
- return;
1832
- }
1833
- el.style.willChange = "opacity";
1834
- pending = idx;
1835
- requestAnimationFrame(() => {
1836
- if (pending !== idx || layers === null) return;
1837
- el.style.transition = `opacity ${FADE_MS}ms ease`;
1838
- el.style.opacity = "1";
1839
- });
1840
- fadeTimer = window.setTimeout(() => finishFade(idx), 480);
1841
- }
1842
- function applyWpEffects() {
1843
- if (wpEl === null || layers === null) return;
1844
- wpEl.style.opacity = String(rWop());
1845
- const blur = rBl();
1846
- const filter = blur > 0 ? `blur(${blur}px)` : "";
1847
- layers[0].el.style.filter = filter;
1848
- layers[1].el.style.filter = filter;
1849
- }
1850
- /** Repaint the wallpaper layer, the token palette and every interface part from
1851
- * the ACTIVE rule. This is the single entry point a rule switch goes through. */
1852
- function applyWp() {
1853
- const url = rWp();
1854
- if (url) applyImageWp(url);
1855
- else if (wpEl !== null) dropWpContainer();
1856
- if (rHasColor()) {
1857
- applyCustomTokens(rOps());
1858
- applySettingsOverrides(rSop());
1859
- applyTrajectoryOverrides(rTrajectoryOpacity());
1860
- } else {
1861
- clearCustomTokens();
1862
- document.body.removeAttribute("data-ds-dark-theme");
1863
- baseTokenKey = "";
1864
- lastBgKey = "";
1865
- }
1866
- applyRightbarOverrides(rRightbarOpacity());
1867
- applyPartBlurs(rBlurs());
1868
- }
1869
- function teardownWp() {
1870
- dropWpContainer();
1871
- clearCustomTokens();
1872
- tokenStyleEl?.remove();
1873
- tokenStyleEl = null;
1874
- removeViewCards();
1875
- document.body.removeAttribute("data-ds-dark-theme");
1876
- document.body.style.removeProperty("color-scheme");
1877
- document.documentElement.style.removeProperty("--dsh-any-bg-settings-surface");
1878
- document.documentElement.style.removeProperty("--dsh-any-bg-settings-layer-1");
1879
- document.documentElement.style.removeProperty("--dsh-any-bg-settings-layer-2");
1880
- document.documentElement.style.removeProperty("--dsh-any-bg-settings-layer-3");
1881
- document.documentElement.style.removeProperty("--dsh-any-traj-layer-1");
1882
- document.documentElement.style.removeProperty("--dsh-any-traj-layer-2");
1883
- document.documentElement.style.removeProperty("--dsh-any-traj-layer-3");
1884
- document.documentElement.style.removeProperty("--dsh-any-blur-settings");
1885
- document.documentElement.style.removeProperty("--dsh-any-blur-card-panels");
1886
- document.documentElement.style.removeProperty("--dsh-any-input-blur");
1887
- document.documentElement.style.removeProperty("--dsh-any-bg-rightbar");
1888
- document.documentElement.style.removeProperty("--dsh-any-blur-rightbar");
1889
- for (const v of Object.values(OPACITY_VARS)) document.documentElement.style.removeProperty(v);
1890
- baseTokenKey = "";
1891
- lastBgKey = "";
1892
- if (tokensRaf !== null) {
1893
- cancelAnimationFrame(tokensRaf);
1894
- tokensRaf = null;
1895
- }
1896
- pendingOps = null;
1897
- tableFixStyleEl?.remove();
1898
- tableFixStyleEl = null;
1899
- setBlur(frameEl, 0);
1900
- setBlur(sidebarEl, 0);
1901
- setBlur(centerEl, 0);
1902
- setBlur(detailsEl, 0);
1903
- if (frameEl !== null) frameEl.style.removeProperty("background");
1904
- if (centerEl !== null) centerEl.style.removeProperty("background");
1905
- if (detailsEl !== null) detailsEl.style.removeProperty("background");
1906
- stopWatchingParts();
1907
- }
1908
- //#endregion
1909
- //#region src/client/modelbg.ts
1910
- /** Rules that can actually paint: enabled and carrying an image slot. */
1911
- function usable(rules) {
1912
- return rules.filter((r) => r.enabled && r.slot !== "");
1913
- }
1914
- /**
1915
- * Resolve the rule for one model text.
1916
- *
1917
- * Top→bottom, first case-insensitive substring hit wins; an empty `match` never
1918
- * hits on its own. When nothing hits, the FIRST usable rule is the fallback
1919
- * (rule 1 deliberately doubles as both a matcher and the fallback).
1920
- */
1921
- function matchRule(rules, modelText) {
1922
- const list = usable(rules);
1923
- if (list.length === 0) return {
1924
- rule: null,
1925
- matched: false
1926
- };
1927
- const hay = modelText.toLowerCase();
1928
- if (hay.trim() !== "") for (const rule of list) {
1929
- const needle = rule.match.trim().toLowerCase();
1930
- if (needle === "") continue;
1931
- if (hay.includes(needle)) return {
1932
- rule,
1933
- matched: true
1934
- };
1935
- }
1936
- return {
1937
- rule: list[0],
1938
- matched: false
1939
- };
1940
- }
1941
- /** Look the current model's display name up in the shared catalog. */
1942
- function displayNameOf(state, provider, model) {
1943
- if (model === "" || !Array.isArray(state?.groups)) return "";
1944
- for (const group of state.groups) {
1945
- if (group.id !== provider) continue;
1946
- for (const entry of group.models ?? []) if (entry.id === model) return typeof entry.name === "string" ? entry.name : "";
1947
- }
1948
- return "";
1949
- }
1950
- /** How often to look for the sessions service that mounts after this plugin. */
1951
- const ATTACH_RETRY_MS = 400;
1952
- /** Stop looking after this many tries (~2 minutes). */
1953
- const ATTACH_RETRY_LIMIT = 300;
1954
- /** Safety re-read of every source, so a missed notification cannot pin a stale model. */
1955
- const POLL_MS = 1500;
1956
- /** One selection out of a `modelSelection` projection value. */
1957
- function selectionOf(value) {
1958
- const pick = value?.next ?? value?.lastUsed ?? null;
1959
- if (pick === null || typeof pick !== "object") return null;
1960
- const provider = typeof pick.provider === "string" ? pick.provider : "";
1961
- const model = typeof pick.model === "string" ? pick.model : "";
1962
- if (provider === "" && model === "") return null;
1963
- return {
1964
- provider,
1965
- model
1966
- };
1967
- }
1968
- /** A non-empty string id, or null. */
1969
- function asId(value) {
1970
- return typeof value === "string" && value !== "" ? value : null;
1971
- }
1972
- /**
1973
- * The id of the session the main view is showing.
1974
- *
1975
- * The model is read per session, so this hop has to answer before any of the
1976
- * others can. 0.1.7 moved the answer: `sessions.list` stopped publishing a single
1977
- * `current` id in favour of a row table (`ids` / `byId` / `phase`), and the main
1978
- * view's session is now whatever holds a `mainView` retention — the same row the
1979
- * host's own `uiSession.current` binding source is built from. Reading only
1980
- * `list.current` therefore finds nothing on 0.1.7, and a plugin with no session
1981
- * id can only ever fall back to rule 1: the background stops following the model.
1982
- *
1983
- * The hops are tried in order of authority and every one is optional, so this
1984
- * answers on both host generations — and while `uiSession` is still mounting.
1985
- */
1986
- function resolveMainSessionId(uiCurrent, snapshot) {
1987
- const fromUiSession = asId(uiCurrent?.key);
1988
- if (fromUiSession !== null) return fromUiSession;
1989
- const legacy = asId(snapshot?.current);
1990
- if (legacy !== null) return legacy;
1991
- const byId = snapshot?.byId;
1992
- if (byId !== void 0 && byId !== null) {
1993
- for (const row of Object.values(byId)) if ((row?.retainedBy?.mainView ?? 0) > 0) {
1994
- const id = asId(row?.id);
1995
- if (id !== null) return id;
1996
- }
1997
- }
1998
- return null;
1999
- }
2000
- /** Read `uiSession.current` (if that service is on this host) and resolve. */
2001
- function mainSessionId(ctx, list) {
2002
- let uiCurrent;
2003
- try {
2004
- uiCurrent = ctx.get("uiSession")?.current?.getSnapshot?.();
2005
- } catch {}
2006
- return resolveMainSessionId(uiCurrent, list?.getSnapshot());
2007
- }
2008
- /**
2009
- * Watch the current session's model selection.
2010
- *
2011
- * @param ctx - client root context.
2012
- * @param onChange - called with the match text (provider + id + display name),
2013
- * the best available label, which source answered, and — when nothing could be
2014
- * read — the hop that failed.
2015
- * @returns disposer.
2016
- */
2017
- function watchModel(ctx, onChange) {
2018
- let service = null;
2019
- let list = null;
2020
- let offList = null;
2021
- let sessionId = null;
2022
- let directory = null;
2023
- let offDirectory = null;
2024
- /** The session's own `modelSelection` projection face. */
2025
- let projected = null;
2026
- let offProjected = null;
2027
- let lastKey = "\0";
2028
- let poll = null;
2029
- let retry = null;
2030
- let retries = 0;
2031
- const release = () => {
2032
- if (offDirectory !== null) {
2033
- offDirectory();
2034
- offDirectory = null;
2035
- }
2036
- if (offProjected !== null) {
2037
- offProjected();
2038
- offProjected = null;
2039
- }
2040
- directory = null;
2041
- projected = null;
2042
- };
2043
- /** Bind the session's own durable model selection (the authoritative source). */
2044
- const bindProjection = (id) => {
2045
- const svc = service;
2046
- if (svc === null) return;
2047
- try {
2048
- let binding = typeof svc.binding === "function" ? svc.binding(id) : void 0;
2049
- if (binding === void 0 && typeof svc.scope === "function") {
2050
- svc.scope(id);
2051
- binding = typeof svc.binding === "function" ? svc.binding(id) : void 0;
2052
- }
2053
- const face = binding?.session?.projections?.faceOf?.("modelSelection");
2054
- if (face === void 0 || typeof face.subscribe !== "function" || typeof face.getSnapshot !== "function") return;
2055
- projected = face;
2056
- offProjected = face.subscribe(() => {
2057
- emit(false);
2058
- });
2059
- } catch {
2060
- projected = null;
2061
- offProjected = null;
2062
- }
2063
- };
2064
- const note = () => {
2065
- if (service === null) return "no-service";
2066
- if (sessionId === null) return "no-session";
2067
- if (projected === null && directory === null) return "no-projection";
2068
- return "empty-selection";
2069
- };
2070
- const emit = (force) => {
2071
- const state = directory?.store.getSnapshot();
2072
- const fromDirectory = state?.current ?? null;
2073
- const sel = fromDirectory !== null ? {
2074
- provider: fromDirectory.provider ?? "",
2075
- model: fromDirectory.model ?? ""
2076
- } : (() => {
2077
- try {
2078
- return selectionOf(projected?.getSnapshot());
2079
- } catch {
2080
- return null;
2081
- }
2082
- })();
2083
- const provider = sel?.provider ?? "";
2084
- const model = sel?.model ?? "";
2085
- const name = displayNameOf(state, provider, model);
2086
- const text = [
2087
- provider,
2088
- model,
2089
- name
2090
- ].filter((p) => p !== "").join(" ").trim();
2091
- const noteValue = text === "" ? note() : "";
2092
- const key = `${text}\u0001${name}\u0001${noteValue}`;
2093
- if (!force && key === lastKey) return;
2094
- lastKey = key;
2095
- onChange(text, name !== "" ? name : model !== "" ? model : provider, "session", noteValue);
2096
- };
2097
- const bind = () => {
2098
- const id = mainSessionId(ctx, list);
2099
- if (id === sessionId && (directory !== null || projected !== null)) {
2100
- emit(false);
2101
- return;
2102
- }
2103
- release();
2104
- sessionId = id;
2105
- if (id !== null) {
2106
- bindProjection(id);
2107
- if (directory === null) {
2108
- const directories = ctx.get("modelDirectories");
2109
- if (directories !== void 0 && typeof directories.directoryFor === "function") try {
2110
- directory = directories.directoryFor(id);
2111
- offDirectory = directory.store.subscribe(() => {
2112
- emit(false);
2113
- });
2114
- const pending = directory.load?.();
2115
- if (pending !== void 0 && pending !== null && typeof pending.catch === "function") pending.catch(() => void 0);
2116
- } catch {
2117
- directory = null;
2118
- offDirectory = null;
2119
- }
2120
- }
2121
- }
2122
- emit(true);
2123
- };
2124
- const stopRetry = () => {
2125
- if (retry === null) return;
2126
- window.clearInterval(retry);
2127
- retry = null;
2128
- };
2129
- const tick = () => {
2130
- if (list === null) return;
2131
- if (mainSessionId(ctx, list) !== sessionId || directory === null && projected === null) bind();
2132
- else emit(false);
2133
- };
2134
- /**
2135
- * Look the sessions service up. It arrives after this plugin's bundle on this
2136
- * deployment, so the first attempt usually fails — which is exactly why this
2137
- * is a retry loop rather than a one-shot lookup.
2138
- */
2139
- const attach = () => {
2140
- const svc = ctx.get("sessions");
2141
- if (svc === void 0 || svc === null) return false;
2142
- const face = svc.list;
2143
- if (face === void 0 || typeof face.subscribe !== "function" || typeof face.getSnapshot !== "function") return false;
2144
- service = svc;
2145
- list = face;
2146
- offList = face.subscribe(bind);
2147
- if (poll === null) poll = window.setInterval(tick, POLL_MS);
2148
- bind();
2149
- return true;
2150
- };
2151
- if (!attach()) retry = window.setInterval(() => {
2152
- retries++;
2153
- if (attach() || retries >= ATTACH_RETRY_LIMIT) stopRetry();
2154
- }, ATTACH_RETRY_MS);
2155
- return () => {
2156
- stopRetry();
2157
- if (poll !== null) {
2158
- window.clearInterval(poll);
2159
- poll = null;
2160
- }
2161
- if (offList !== null) {
2162
- offList();
2163
- offList = null;
2164
- }
2165
- service = null;
2166
- list = null;
2167
- release();
2168
- };
2169
- }
2170
- //#endregion
2171
- //#region src/host-contracts.ts
2172
- const SIDEBAR_RIGHT = "dsh-client-ui-sidebar-right";
2173
- /**
2174
- * The contract table.
2175
- *
2176
- * Ordered by group and then by how visible a break is: the model hop first (the
2177
- * plugin's whole purpose), then the surfaces it paints, then the tokens it
2178
- * re-emits, then the regions it decorates.
2179
- */
2180
- const HOST_CONTRACTS = [
2181
- {
2182
- id: "sessions.list",
2183
- group: "model",
2184
- kind: "host-service",
2185
- criticality: "core",
2186
- since: "0.1.7",
2187
- label: {
2188
- en: "Session list service",
2189
- zh: "会话列表服务"
2190
- },
2191
- symptom: {
2192
- en: "no session id can be read, so the background is stuck on rule 1 and never follows the model",
2193
- zh: "读不到会话 id,背景会一直停在规则 1,不再跟着模型切换"
2194
- },
2195
- target: "sessions.list",
2196
- checks: [
2197
- {
2198
- kind: "service",
2199
- id: "sessions"
2200
- },
2201
- {
2202
- kind: "path",
2203
- service: "sessions",
2204
- path: "list",
2205
- expect: "observable"
2206
- },
2207
- {
2208
- kind: "path",
2209
- service: "sessions",
2210
- path: "list.getSnapshot",
2211
- expect: "function"
2212
- }
2213
- ],
2214
- sources: [{
2215
- path: "dsh-api-session-controller/lib/client.js",
2216
- literal: "retainedBy"
2217
- }],
2218
- usedBy: "src/client/modelbg.ts"
2219
- },
2220
- {
2221
- id: "sessions.list.current",
2222
- group: "model",
2223
- kind: "host-service",
2224
- criticality: "label",
2225
- since: "<=0.1.6",
2226
- label: {
2227
- en: "Session list: legacy current id",
2228
- zh: "会话列表:旧版 current 字段"
2229
- },
2230
- symptom: {
2231
- en: "harmless on its own — the 0.1.7 hops below take over",
2232
- zh: "本身无害 —— 0.1.7 上由下面的兜底路径接管"
2233
- },
2234
- target: "list.current",
2235
- checks: [],
2236
- sources: [{
2237
- path: "dsh-api-session-controller/lib/client.js",
2238
- literal: "retainedBy"
2239
- }],
2240
- usedBy: "src/client/modelbg.ts"
2241
- },
2242
- {
2243
- id: "sessions.retainedBy.mainView",
2244
- group: "model",
2245
- kind: "host-service",
2246
- criticality: "label",
2247
- since: "0.1.7",
2248
- label: {
2249
- en: "Main-view retention on the session row",
2250
- zh: "会话行的主视图占用标记"
2251
- },
2252
- symptom: {
2253
- en: "while uiSession is not mounted yet, no session id can be derived",
2254
- zh: "uiSession 尚未挂载时推导不出会话 id"
2255
- },
2256
- target: "byId[].retainedBy.mainView",
2257
- checks: [],
2258
- sources: [{
2259
- path: "dsh-api-session-controller/lib/client.js",
2260
- literal: "retainedBy"
2261
- }],
2262
- usedBy: "src/client/modelbg.ts"
2263
- },
2264
- {
2265
- id: "uiSession.current",
2266
- group: "model",
2267
- kind: "host-service",
2268
- criticality: "core",
2269
- since: "0.1.7",
2270
- label: {
2271
- en: "uiSession service (main view session)",
2272
- zh: "uiSession 服务(主视图会话)"
2273
- },
2274
- symptom: {
2275
- en: "the session id can only be guessed from the list retention",
2276
- zh: "会话 id 只能退回用列表占用标记去猜"
2277
- },
2278
- target: "uiSession.current",
2279
- checks: [{
2280
- kind: "path",
2281
- service: "uiSession",
2282
- path: "current",
2283
- expect: "observable"
2284
- }],
2285
- sources: [{
2286
- path: "dsh-client-ui-session/lib/client.js",
2287
- literal: "uiSession"
2288
- }],
2289
- usedBy: "src/client/modelbg.ts"
2290
- },
2291
- {
2292
- id: "sessions.binding.projections",
2293
- group: "model",
2294
- kind: "host-service",
2295
- criticality: "core",
2296
- since: "<=0.1.6",
2297
- label: {
2298
- en: "Session binding + projections",
2299
- zh: "会话 binding 与 projections"
2300
- },
2301
- symptom: {
2302
- en: "this session's own model selection is unreadable — only the host default is left",
2303
- zh: "读不到本会话自己的模型选择,只剩宿主默认模型可用"
2304
- },
2305
- target: "binding(id).session.projections.faceOf(\"modelSelection\")",
2306
- checks: [{
2307
- kind: "path",
2308
- service: "sessions",
2309
- path: "binding",
2310
- expect: "function"
2311
- }],
2312
- sources: [{
2313
- path: "dsh-api-session-controller/lib/client.js",
2314
- literal: "faceOf"
2315
- }],
2316
- usedBy: "src/client/modelbg.ts"
2317
- },
2318
- {
2319
- id: "modelDirectories.directoryFor",
2320
- group: "model",
2321
- kind: "host-service",
2322
- criticality: "label",
2323
- since: "<=0.1.6",
2324
- label: {
2325
- en: "Model directory (display names)",
2326
- zh: "模型目录(显示名)"
2327
- },
2328
- symptom: {
2329
- en: "the model is shown as provider/model id instead of its display name",
2330
- zh: "模型只会显示 provider/模型 id,读不到显示名"
2331
- },
2332
- target: "modelDirectories.directoryFor(id)",
2333
- checks: [{
2334
- kind: "path",
2335
- service: "modelDirectories",
2336
- path: "directoryFor",
2337
- expect: "function"
2338
- }],
2339
- sources: [{
2340
- path: "dsh-client-ui-model-selection/lib/client.js",
2341
- literal: "modelSelection"
2342
- }],
2343
- usedBy: "src/client/modelbg.ts"
2344
- },
2345
- {
2346
- id: "shell.overlay",
2347
- group: "surface",
2348
- kind: "host-attr",
2349
- criticality: "looks",
2350
- since: "<=0.1.6",
2351
- label: {
2352
- en: "AppFrame overlay + its three columns",
2353
- zh: "主框架浮层与三列"
2354
- },
2355
- symptom: {
2356
- en: "per-part blur and the main-background opacity cannot find the columns at all",
2357
- zh: "分区域模糊与主背景透明度完全找不到那几列"
2358
- },
2359
- target: "[data-shell-overlay]",
2360
- checks: [{
2361
- kind: "attr",
2362
- attr: "data-shell-overlay"
2363
- }],
2364
- sources: [{
2365
- path: "dsh-client-ui-layout/lib/client.js",
2366
- literal: "data-shell-overlay"
2367
- }],
2368
- usedBy: "src/client/wallpaper.ts"
2369
- },
2370
- {
2371
- id: "settings.dialog",
2372
- group: "surface",
2373
- kind: "dom-selector",
2374
- criticality: "looks",
2375
- since: "<=0.1.6",
2376
- label: {
2377
- en: "Settings dialog (aria-modal)",
2378
- zh: "设置弹窗(aria-modal)"
2379
- },
2380
- symptom: {
2381
- en: "the settings panel opacity and blur sliders stop having any effect",
2382
- zh: "设置界面透明度与模糊失效"
2383
- },
2384
- target: "[role=\"dialog\"][aria-modal=\"true\"][aria-labelledby]",
2385
- checks: [{
2386
- kind: "selector",
2387
- selector: "div[role=\"dialog\"][aria-modal=\"true\"][aria-labelledby]",
2388
- optionalWhen: "settingsClosed"
2389
- }],
2390
- sources: [{
2391
- path: "dsh-client-ui-primitives/lib/index.js",
2392
- literal: "aria-modal"
2393
- }, {
2394
- path: "src/client/components/ui.css.ts",
2395
- literal: ".dab-card"
2396
- }],
2397
- usedBy: "src/client/wallpaper.ts"
2398
- },
2399
- {
2400
- id: "settings.card",
2401
- group: "surface",
2402
- kind: "host-style",
2403
- criticality: "looks",
2404
- since: "<=0.1.6",
2405
- label: {
2406
- en: "Option card inside the dialog (.dab-card)",
2407
- zh: "弹窗内的选项卡(.dab-card)"
2408
- },
2409
- symptom: {
2410
- en: "the dialog option-panel blur has nothing to attach to",
2411
- zh: "弹窗内选项卡的模糊没有附着对象"
2412
- },
2413
- target: ".dab-card",
2414
- checks: [{
2415
- kind: "rule",
2416
- match: ".dab-card",
2417
- hint: "settings dialog card class"
2418
- }],
2419
- sources: [{
2420
- path: "src/client/components/ui.css.ts",
2421
- literal: ".dab-card"
2422
- }],
2423
- usedBy: "src/client/wallpaper.ts"
2424
- },
2425
- {
2426
- id: "composer.card",
2427
- group: "surface",
2428
- kind: "host-attr",
2429
- criticality: "looks",
2430
- since: "<=0.1.6",
2431
- label: {
2432
- en: "Composer card",
2433
- zh: "输入框卡片"
2434
- },
2435
- symptom: {
2436
- en: "the composer loses its backdrop — the input blur slider does nothing",
2437
- zh: "输入框失去毛玻璃背板,输入区模糊滑块失效"
2438
- },
2439
- target: "[data-composer-card]",
2440
- checks: [{
2441
- kind: "attr",
2442
- attr: "data-composer-card",
2443
- optionalWhen: "chatNotMounted"
2444
- }],
2445
- sources: [{
2446
- path: "dsh-client-ui-conversation/lib/client.js",
2447
- literal: "data-composer-card"
2448
- }],
2449
- usedBy: "src/client/wallpaper.ts"
2450
- },
2451
- {
2452
- id: "cordis.panel",
2453
- group: "surface",
2454
- kind: "host-attr",
2455
- criticality: "label",
2456
- since: "<=0.1.6",
2457
- label: {
2458
- en: "Cordis panel",
2459
- zh: "Cordis 面板"
2460
- },
2461
- symptom: {
2462
- en: "the Cordis panel keeps the host surface instead of the input slider's alpha",
2463
- zh: "Cordis 面板拿不到输入区滑块的透明度,仍是宿主表面"
2464
- },
2465
- target: "[data-cordis-panel]",
2466
- checks: [{
2467
- kind: "attr",
2468
- attr: "data-cordis-panel",
2469
- optionalWhen: "cordisClosed"
2470
- }],
2471
- sources: [{
2472
- path: "dsh-client-ui-cordis/lib/client.js",
2473
- literal: "data-cordis-panel"
2474
- }],
2475
- usedBy: "src/client/wallpaper.ts"
2476
- },
2477
- {
2478
- id: "rightbar.panel",
2479
- group: "surface",
2480
- kind: "host-attr",
2481
- criticality: "looks",
2482
- since: "0.1.7",
2483
- label: {
2484
- en: "File-preview panel host",
2485
- zh: "文件预览面板宿主"
2486
- },
2487
- symptom: {
2488
- en: "the right panel loses its own opacity/blur card and falls back to the main background",
2489
- zh: "右侧栏失去自己的透明度/模糊控制,退回跟随主背景"
2490
- },
2491
- target: "[data-sidebar-right-panel]",
2492
- checks: [{
2493
- kind: "selector",
2494
- selector: "div[data-sidebar-right-panel]",
2495
- optionalWhen: "noRightPanel"
2496
- }],
2497
- sources: [{
2498
- path: `${SIDEBAR_RIGHT}/lib/client.js`,
2499
- literal: "data-sidebar-right-panel"
2500
- }],
2501
- usedBy: "src/client/wallpaper.ts"
2502
- },
2503
- {
2504
- id: "rightbar.openFlag",
2505
- group: "surface",
2506
- kind: "host-attr",
2507
- criticality: "looks",
2508
- since: "0.1.7",
2509
- label: {
2510
- en: "Right panel open/closed flag",
2511
- zh: "右侧栏开合标记"
2512
- },
2513
- symptom: {
2514
- en: "the panel's backdrop cannot tell open from closed — a frosted plate covers the empty half",
2515
- zh: "分不清右侧栏开着还是关着,空白的右半屏会出现磨砂板"
2516
- },
2517
- target: "[data-sidebar-right-open]",
2518
- checks: [{
2519
- kind: "rule",
2520
- match: "[data-sidebar-right-open]",
2521
- hint: "right panel open flag"
2522
- }],
2523
- sources: [{
2524
- path: `${SIDEBAR_RIGHT}/lib/client.js`,
2525
- literal: "data-sidebar-right-open"
2526
- }],
2527
- usedBy: "src/client/wallpaper.ts"
2528
- },
2529
- {
2530
- id: "dockkit.content",
2531
- group: "surface",
2532
- kind: "host-attr",
2533
- criticality: "core",
2534
- since: "0.1.7",
2535
- label: {
2536
- en: "Dock tab host (the panel's painted surface)",
2537
- zh: "停靠页宿主(面板真正的表面)"
2538
- },
2539
- symptom: {
2540
- en: "nothing carries the panel surface any more — the right-panel card is painted on a container that is always full width",
2541
- zh: "面板表面不再落在任何元素上,右面板卡片会被画在一个恒定满宽的容器上"
2542
- },
2543
- target: "[data-dockkit-content]",
2544
- checks: [{
2545
- kind: "rule",
2546
- match: "[data-dockkit-content]",
2547
- hint: "dock tab host"
2548
- }],
2549
- sources: [{
2550
- path: ".",
2551
- literal: "dockkit-content",
2552
- root: "frontend"
2553
- }],
2554
- usedBy: "src/client/wallpaper.ts"
2555
- },
2556
- {
2557
- id: "dockkit.empty",
2558
- group: "surface",
2559
- kind: "host-attr",
2560
- criticality: "looks",
2561
- since: "0.1.7",
2562
- label: {
2563
- en: "Dock empty seat",
2564
- zh: "停靠空位"
2565
- },
2566
- symptom: {
2567
- en: "an empty preview pane paints the host surface over the wallpaper",
2568
- zh: "空的预览格会用宿主表面盖住壁纸"
2569
- },
2570
- target: "[data-dockkit-empty]",
2571
- checks: [{
2572
- kind: "rule",
2573
- match: "[data-dockkit-empty]",
2574
- hint: "dock empty seat"
2575
- }],
2576
- sources: [{
2577
- path: `${SIDEBAR_RIGHT}/lib/client.js`,
2578
- literal: "data-dockkit-empty"
2579
- }],
2580
- usedBy: "src/client/wallpaper.ts"
2581
- },
2582
- {
2583
- id: "dockkit.float",
2584
- group: "surface",
2585
- kind: "host-attr",
2586
- criticality: "label",
2587
- since: "0.1.7",
2588
- label: {
2589
- en: "Floating dock pane flag",
2590
- zh: "浮动停靠面板标记"
2591
- },
2592
- symptom: {
2593
- en: "floating panes would be painted like docked ones, frosting a panel that should stay the host's",
2594
- zh: "浮动面板会被当作停靠面板上色,本该保持宿主样式的面板被磨砂"
2595
- },
2596
- target: "[data-dockkit-float]",
2597
- checks: [{
2598
- kind: "rule",
2599
- match: "[data-dockkit-float]",
2600
- hint: "floating pane flag"
2601
- }],
2602
- sources: [{
2603
- path: `${SIDEBAR_RIGHT}/lib/client.js`,
2604
- literal: "data-dockkit-float"
2605
- }],
2606
- usedBy: "src/client/wallpaper.ts"
2607
- },
2608
- {
2609
- id: "chat.flow",
2610
- group: "region",
2611
- kind: "dom-selector",
2612
- criticality: "looks",
2613
- since: "<=0.1.6",
2614
- label: {
2615
- en: "Chat message column",
2616
- zh: "对话消息列"
2617
- },
2618
- symptom: {
2619
- en: "the conversation card (and its opacity/blur) cannot find the message column",
2620
- zh: "对话卡片(及其透明度/模糊)找不到消息列"
2621
- },
2622
- target: "[data-chat-flow]",
2623
- checks: [{
2624
- kind: "selector",
2625
- selector: "[data-chat-flow]",
2626
- optionalWhen: "chatNotMounted"
2627
- }],
2628
- sources: [{
2629
- path: "dsh-client-ui-chat/lib/client.js",
2630
- literal: "data-chat-flow"
2631
- }],
2632
- usedBy: "src/client/wallpaper.ts"
2633
- },
2634
- {
2635
- id: "conversation.scroll",
2636
- group: "region",
2637
- kind: "dom-selector",
2638
- criticality: "looks",
2639
- since: "<=0.1.6",
2640
- label: {
2641
- en: "Conversation scrollport",
2642
- zh: "对话滚动区"
2643
- },
2644
- symptom: {
2645
- en: "the marker-less fallback would wrap the whole scrollport in the conversation card",
2646
- zh: "标记缺失时的兜底会把整个滚动区套上对话卡片"
2647
- },
2648
- target: "[data-conversation-scroll]",
2649
- checks: [{
2650
- kind: "selector",
2651
- selector: "[data-conversation-scroll]",
2652
- optionalWhen: "chatNotMounted"
2653
- }],
2654
- sources: [{
2655
- path: "dsh-client-ui-conversation/lib/client.js",
2656
- literal: "data-conversation-scroll"
2657
- }],
2658
- usedBy: "src/client/wallpaper.ts"
2659
- },
2660
- {
2661
- id: "trajectory.root",
2662
- group: "region",
2663
- kind: "dom-selector",
2664
- criticality: "looks",
2665
- since: "<=0.1.6",
2666
- label: {
2667
- en: "Trajectory view root",
2668
- zh: "轨迹页根节点"
2669
- },
2670
- symptom: {
2671
- en: "the trajectory opacity/blur sliders stop reaching the trajectory page",
2672
- zh: "轨迹页的透明度与模糊滑块失效"
2673
- },
2674
- target: "[data-conversation-composer-overlay]",
2675
- checks: [{
2676
- kind: "selector",
2677
- selector: "[data-conversation-composer-overlay]",
2678
- optionalWhen: "trajectoryNotMounted"
2679
- }],
2680
- sources: [{
2681
- path: "dsh-client-ui-trajectory/lib/client.js",
2682
- literal: "data-conversation-composer-overlay"
2683
- }],
2684
- usedBy: "src/client/wallpaper.ts"
2685
- },
2686
- {
2687
- id: "markdown.wideTable",
2688
- group: "region",
2689
- kind: "host-style",
2690
- criticality: "label",
2691
- since: "<=0.1.6",
2692
- label: {
2693
- en: "Wide markdown table clamp",
2694
- zh: "宽表格收拢样式"
2695
- },
2696
- symptom: {
2697
- en: "a wide table bleeds outside the conversation card instead of scrolling inside it",
2698
- zh: "宽表格会溢出到对话卡片外,而不是在卡片内横向滚动"
2699
- },
2700
- target: ".md-table-wide",
2701
- checks: [{
2702
- kind: "rule",
2703
- match: ".md-table-wide",
2704
- hint: "markdown table class"
2705
- }],
2706
- sources: [{
2707
- path: "dsh-client-ui-chat/lib/client.js",
2708
- literal: "md-table-wide"
2709
- }],
2710
- usedBy: "src/client/wallpaper.ts"
2711
- },
2712
- {
2713
- id: "token.bgBase",
2714
- group: "token",
2715
- kind: "host-token",
2716
- criticality: "looks",
2717
- since: "<=0.1.6",
2718
- label: {
2719
- en: "Main background surface",
2720
- zh: "主背景表面"
2721
- },
2722
- symptom: {
2723
- en: "the main-background slider and the right-panel card lose their surface",
2724
- zh: "主背景透明度与右侧栏卡片失去表面色"
2725
- },
2726
- target: "--dsw-alias-bg-base",
2727
- checks: [{
2728
- kind: "computed",
2729
- token: "--dsw-alias-bg-base"
2730
- }],
2731
- sources: [{
2732
- path: "dsh-client-ui-theme/lib/client.js",
2733
- literal: "--dsw-alias-bg-base"
2734
- }],
2735
- usedBy: "src/client/wallpaper.ts"
2736
- },
2737
- {
2738
- id: "token.layers",
2739
- group: "token",
2740
- kind: "host-token",
2741
- criticality: "looks",
2742
- since: "<=0.1.6",
2743
- label: {
2744
- en: "Layered surfaces (cards)",
2745
- zh: "层次表面(卡片)"
2746
- },
2747
- symptom: {
2748
- en: "the card slider, the conversation card and the trajectory slider go blank",
2749
- zh: "卡片滑块、对话卡片与轨迹滑块失去颜色"
2750
- },
2751
- target: "--dsw-alias-bg-layer-1/2/3",
2752
- checks: [
2753
- {
2754
- kind: "computed",
2755
- token: "--dsw-alias-bg-layer-1"
2756
- },
2757
- {
2758
- kind: "computed",
2759
- token: "--dsw-alias-bg-layer-2"
2760
- },
2761
- {
2762
- kind: "computed",
2763
- token: "--dsw-alias-bg-layer-3"
2764
- }
2765
- ],
2766
- sources: [{
2767
- path: "dsh-client-ui-theme/lib/client.js",
2768
- literal: "--dsw-alias-bg-layer-1"
2769
- }],
2770
- usedBy: "src/client/wallpaper.ts"
2771
- },
2772
- {
2773
- id: "token.menuSurface",
2774
- group: "token",
2775
- kind: "host-token",
2776
- criticality: "core",
2777
- since: "0.1.7",
2778
- label: {
2779
- en: "Menu material fill (0.1.7 split)",
2780
- zh: "菜单材质填充(0.1.7 拆分)"
2781
- },
2782
- symptom: {
2783
- en: "the menu panel and its sticky group headings take two different fills — the solid band across the model picker",
2784
- zh: "菜单面板与吸顶分组标题用了两种填充 —— 模型选择器上那道白道"
2785
- },
2786
- target: "--dsw-menu-surface-fill",
2787
- checks: [{
2788
- kind: "rule",
2789
- match: "--dsw-menu-surface-fill",
2790
- hint: "MenuSurface material fill"
2791
- }, {
2792
- kind: "computed",
2793
- token: "--dsw-menu-surface-fill",
2794
- on: ".dab-root"
2795
- }],
2796
- sources: [{
2797
- path: "dsh-client-ui-theme/lib/client.js",
2798
- literal: "--dsw-menu-surface-fill"
2799
- }, {
2800
- path: "dsh-client-ui-primitives/lib/MenuSurface.module.css",
2801
- literal: "var(--dsw-menu-surface-fill)"
2802
- }],
2803
- usedBy: "src/client/utils/color.ts, src/client/wallpaper.ts"
2804
- },
2805
- {
2806
- id: "token.menuAlias",
2807
- group: "token",
2808
- kind: "host-token",
2809
- criticality: "looks",
2810
- since: "<=0.1.6",
2811
- label: {
2812
- en: "Menu overlay fill",
2813
- zh: "菜单浮层填充"
2814
- },
2815
- symptom: {
2816
- en: "dropdowns, the slash menu and popovers keep the host fill while the panel follows the plugin",
2817
- zh: "下拉、斜杠菜单与浮层保持宿主填充,而面板跟随插件,两边不一致"
2818
- },
2819
- target: "--dsw-specific-menu",
2820
- checks: [{
2821
- kind: "rule",
2822
- match: "--dsw-specific-menu",
2823
- hint: "menu overlay fill"
2824
- }, {
2825
- kind: "computed",
2826
- token: "--dsw-specific-menu",
2827
- on: ".dab-root"
2828
- }],
2829
- sources: [{
2830
- path: "dsh-client-ui-theme/lib/client.js",
2831
- literal: "--dsw-specific-menu"
2832
- }, {
2833
- path: "dsh-client-ui-chat/lib/client.js",
2834
- literal: "--dsw-specific-menu"
2835
- }],
2836
- usedBy: "src/client/utils/color.ts, src/client/wallpaper.ts"
2837
- },
2838
- {
2839
- id: "token.sidebarFill",
2840
- group: "token",
2841
- kind: "host-token",
2842
- criticality: "looks",
2843
- since: "<=0.1.6",
2844
- label: {
2845
- en: "Sidebar fill",
2846
- zh: "左侧栏填充"
2847
- },
2848
- symptom: {
2849
- en: "the sidebar opacity slider stops tinting the sidebar",
2850
- zh: "左侧栏透明度滑块失效"
2851
- },
2852
- target: "--dsw-specific-sidebar-fill",
2853
- checks: [{
2854
- kind: "computed",
2855
- token: "--dsw-specific-sidebar-fill"
2856
- }],
2857
- sources: [{
2858
- path: "dsh-client-ui-theme/lib/client.js",
2859
- literal: "--dsw-specific-sidebar-fill"
2860
- }],
2861
- usedBy: "src/client/utils/color.ts, src/client/wallpaper.ts"
2862
- },
2863
- {
2864
- id: "token.inputFill",
2865
- group: "token",
2866
- kind: "host-token",
2867
- criticality: "looks",
2868
- since: "<=0.1.6",
2869
- label: {
2870
- en: "Input/control surface",
2871
- zh: "输入与控件表面"
2872
- },
2873
- symptom: {
2874
- en: "the input slider stops tinting the composer and the controls",
2875
- zh: "输入区透明度滑块失效"
2876
- },
2877
- target: "--dsw-specific-input-major",
2878
- checks: [{
2879
- kind: "computed",
2880
- token: "--dsw-specific-input-major"
2881
- }],
2882
- sources: [{
2883
- path: "dsh-client-ui-theme/lib/client.js",
2884
- literal: "--dsw-specific-input-major"
2885
- }],
2886
- usedBy: "src/client/utils/color.ts, src/client/wallpaper.ts"
2887
- },
2888
- {
2889
- id: "token.schemeFlag",
2890
- group: "token",
2891
- kind: "host-attr",
2892
- criticality: "core",
2893
- since: "<=0.1.6",
2894
- label: {
2895
- en: "Dark-scheme flag on <body>",
2896
- zh: "<body> 上的深色标记"
2897
- },
2898
- symptom: {
2899
- en: "a dark rule can no longer force the dark palette — light surfaces flash through",
2900
- zh: "深色规则无法强制深色调色板,会闪出浅色表面"
2901
- },
2902
- target: "body[data-ds-dark-theme]",
2903
- checks: [{
2904
- kind: "rule",
2905
- match: "data-ds-dark-theme",
2906
- hint: "dark scheme attribute"
2907
- }],
2908
- sources: [{
2909
- path: "dsh-client-ui-theme/lib/client.js",
2910
- literal: "data-ds-dark-theme"
2911
- }, {
2912
- path: ".",
2913
- literal: "data-ds-dark-theme",
2914
- root: "frontend"
2915
- }],
2916
- usedBy: "src/client/wallpaper.ts"
2917
- },
2918
- {
2919
- id: "token.labelCaption",
2920
- group: "token",
2921
- kind: "host-token",
2922
- criticality: "label",
2923
- since: "<=0.1.6",
2924
- label: {
2925
- en: "Caption label token",
2926
- zh: "弱化文字令牌"
2927
- },
2928
- symptom: {
2929
- en: "placeholder text falls back to a hard-coded grey instead of the theme's caption colour",
2930
- zh: "占位文字退回固定灰色,不再跟随主题的弱化色"
2931
- },
2932
- target: "--dsw-alias-label-caption",
2933
- checks: [{
2934
- kind: "computed",
2935
- token: "--dsw-alias-label-caption"
2936
- }],
2937
- sources: [{
2938
- path: "dsh-client-ui-theme/lib/client.js",
2939
- literal: "--dsw-alias-label-caption"
2940
- }],
2941
- usedBy: "src/client/wallpaper.ts"
2942
- }
2943
- ];
2944
- /** Localized label lookup for a contract. */
2945
- function labelOf(c, lang) {
2946
- return lang === "zh" ? c.label.zh : c.label.en;
2947
- }
2948
- /** Localized symptom lookup for a contract. */
2949
- function symptomOf(c, lang) {
2950
- return lang === "zh" ? c.symptom.zh : c.symptom.en;
2951
- }
2952
- /** Groups the panel renders, in order. */
2953
- const CONTRACT_GROUPS = [
2954
- {
2955
- id: "model",
2956
- label: {
2957
- en: "Model → rule resolution",
2958
- zh: "模型 → 规则匹配"
2959
- }
2960
- },
2961
- {
2962
- id: "surface",
2963
- label: {
2964
- en: "Surfaces the plugin paints",
2965
- zh: "插件着色的界面区域"
2966
- }
2967
- },
2968
- {
2969
- id: "token",
2970
- label: {
2971
- en: "Design tokens re-emitted",
2972
- zh: "被重新发出的设计令牌"
2973
- }
2974
- },
2975
- {
2976
- id: "region",
2977
- label: {
2978
- en: "Conversation views",
2979
- zh: "对话视图"
2980
- }
2981
- }
2982
- ];
2983
- /** Overall verdict of a report, for the panel's headline. */
2984
- function verdictOf(report) {
2985
- if (report.results.length === 0) return "unknown";
2986
- if (report.summary.fail > 0) return report.summary.pass > 0 ? "partial" : "broken";
2987
- return report.summary.pass > 0 ? "ok" : "unknown";
2988
- }
2989
- /** Markdown rendering of a report — the thing a user pastes into an issue. */
2990
- function reportToMarkdown(report, lang) {
2991
- const zh = lang === "zh";
2992
- const lines = [];
2993
- lines.push(zh ? "## dsh-background-by-model 宿主自检" : "## dsh-background-by-model host check");
2994
- lines.push("");
2995
- lines.push(`- ${zh ? "阶段" : "phase"}: ${report.phase}`);
2996
- if (report.plugin.version !== "") lines.push(`- ${zh ? "插件" : "plugin"}: ${report.plugin.version} (dsh >=${report.plugin.floor})`);
2997
- lines.push(`- dsh: ${report.host.version !== "" ? report.host.version : zh ? "未读到" : "unreadable"}${report.host.compatible === false ? zh ? "(低于插件下限)" : " (below the plugin floor)" : ""}`);
2998
- if (report.host.node !== "") lines.push(`- node: ${report.host.node}`);
2999
- if (report.host.root !== "") lines.push(`- host root: ${report.host.root}`);
3000
- if (report.host.note !== "") lines.push(`- note: ${report.host.note}`);
3001
- if (report.host.model !== void 0) {
3002
- const m = report.host.model;
3003
- lines.push(`- ${zh ? "模型" : "model"}: \`${m.text !== "" ? m.text : "(none)"}\` · ${m.source}${m.note !== "" ? ` · ${m.note}` : ""} · ${zh ? "规则" : "rule"} ${m.rule !== "" ? m.rule : "-"}${m.matched ? "" : zh ? "(兜底)" : " (fallback)"}`);
3004
- }
3005
- lines.push("");
3006
- lines.push(`### ${zh ? "结论" : "Summary"}: ${report.summary.pass} pass / ${report.summary.fail} fail / ${report.summary.skip} n/a`);
3007
- lines.push("");
3008
- for (const r of report.results) {
3009
- const mark = r.status === "pass" ? "PASS" : r.status === "fail" ? "**FAIL**" : r.status === "skip" ? "n/a" : "info";
3010
- lines.push(`- ${mark} \`${r.id}\` — ${r.label} · \`${r.target}\``);
3011
- if (r.status === "fail") {
3012
- lines.push(` - ${zh ? "现象" : "symptom"}: ${r.symptom}`);
3013
- lines.push(` - ${zh ? "原因" : "reason"}: ${r.reason ?? r.detail}`);
3014
- lines.push(` - ${zh ? "期望位置" : "expected in"}: ${r.sources.map((s) => s.path).join(", ")}`);
3015
- } else if (r.detail !== "") lines.push(` - ${r.detail}`);
3016
- }
3017
- return lines.join("\n");
3018
- }
3019
- //#endregion
3020
- //#region src/client/judge.ts
3021
- /** The real browser environment. */
3022
- function browserEnv(ctx) {
3023
- return {
3024
- querySelector: (selector) => document.querySelector(selector),
3025
- computedValue: (token, on) => {
3026
- if (typeof document === "undefined") return "";
3027
- const host = on ?? document.documentElement;
3028
- return getComputedStyle(host).getPropertyValue(token).trim();
3029
- },
3030
- styleSheets: () => typeof document === "undefined" ? [] : Array.from(document.styleSheets),
3031
- ctx
3032
- };
3033
- }
3034
- const ANCHORS = {
3035
- settingsClosed: "div[role=\"dialog\"][aria-modal=\"true\"][aria-labelledby]",
3036
- chatNotMounted: "[data-chat-flow],[data-conversation-scroll]",
3037
- trajectoryNotMounted: "[data-conversation-composer-overlay]",
3038
- noRightPanel: "div[data-sidebar-right-panel],[data-dockkit-strip],[data-dockkit-pane]",
3039
- cordisClosed: "[data-cordis-panel]"
3040
- };
3041
- function mounted(env, anchorId) {
3042
- const sel = ANCHORS[anchorId];
3043
- if (sel === void 0) return true;
3044
- try {
3045
- return env.querySelector(sel) !== null;
3046
- } catch {
3047
- return false;
3048
- }
3049
- }
3050
- /** Walk a dotted path off a root object, tolerating a missing first hop. */
3051
- function walkPath(root, path) {
3052
- let cur = root;
3053
- for (const key of path.split(".")) {
3054
- if (cur === null || cur === void 0) return void 0;
3055
- cur = cur[key];
3056
- }
3057
- return cur;
3058
- }
3059
- function looksObservable(v) {
3060
- return typeof v === "object" && v !== null && typeof v.subscribe === "function" && typeof v.getSnapshot === "function";
3061
- }
3062
- /**
3063
- * A stylesheet mention of a literal.
3064
- *
3065
- * The host's rules are the only place a renamed token still answers honestly:
3066
- * a token can be *declared* by this plugin while the host's rule that consumed
3067
- * it is gone. Cross-origin sheets (none on a local dsh, but the probe must not
3068
- * throw if one ever appears) are skipped.
3069
- */
3070
- function ruleMentioned(env, literal) {
3071
- for (const sheet of env.styleSheets()) {
3072
- if (sheet.ownerNode?.dataset?.plugin !== void 0) continue;
3073
- let rules = null;
3074
- try {
3075
- rules = sheet.cssRules;
3076
- } catch {
3077
- continue;
3078
- }
3079
- if (rules === null) continue;
3080
- for (const rule of Array.from(rules)) {
3081
- const text = rule.cssText;
3082
- if (typeof text === "string" && text.includes(literal)) return true;
3083
- const inner = rule.cssRules;
3084
- if (inner === void 0 || inner === null) continue;
3085
- for (const child of Array.from(inner)) {
3086
- const childText = child.cssText;
3087
- if (typeof childText === "string" && childText.includes(literal)) return true;
3088
- }
3089
- }
3090
- }
3091
- return false;
3092
- }
3093
- /** Whether any reachable host stylesheet could be read (false ⇒ the check is blind). */
3094
- function stylesReadable(env) {
3095
- for (const sheet of env.styleSheets()) try {
3096
- if (sheet.cssRules !== null) return true;
3097
- } catch {}
3098
- return false;
3099
- }
3100
- function evaluate(env, check) {
3101
- switch (check.kind) {
3102
- case "service": {
3103
- let svc;
3104
- try {
3105
- svc = env.ctx.get(check.id);
3106
- } catch {
3107
- svc = void 0;
3108
- }
3109
- return svc === void 0 || svc === null ? {
3110
- status: "fail",
3111
- name: `ctx.get('${check.id}')`,
3112
- detail: "service not published",
3113
- reason: `the host does not publish the "${check.id}" service`
3114
- } : {
3115
- status: "pass",
3116
- name: `ctx.get('${check.id}')`,
3117
- detail: "service present"
3118
- };
3119
- }
3120
- case "path": {
3121
- let svc;
3122
- try {
3123
- svc = env.ctx.get(check.service);
3124
- } catch {
3125
- svc = void 0;
3126
- }
3127
- if (svc === void 0 || svc === null) return {
3128
- status: "skip",
3129
- name: `${check.service}.${check.path}`,
3130
- detail: "service not published yet"
3131
- };
3132
- const value = walkPath(svc, check.path);
3133
- const name = `${check.service}.${check.path}`;
3134
- if (check.expect === "function") return typeof value === "function" ? {
3135
- status: "pass",
3136
- name,
3137
- detail: "is a function"
3138
- } : {
3139
- status: "fail",
3140
- name,
3141
- detail: `expected a function, got ${typeof value}`,
3142
- reason: `"${name}" is not a function on this host`
3143
- };
3144
- if (check.expect === "observable") return looksObservable(value) ? {
3145
- status: "pass",
3146
- name,
3147
- detail: "is an observable face"
3148
- } : {
3149
- status: "fail",
3150
- name,
3151
- detail: "not an observable face",
3152
- reason: `"${name}" no longer exposes subscribe()/getSnapshot()`
3153
- };
3154
- return value === void 0 ? {
3155
- status: "fail",
3156
- name,
3157
- detail: "undefined",
3158
- reason: `"${name}" is gone`
3159
- } : {
3160
- status: "pass",
3161
- name,
3162
- detail: typeof value
3163
- };
3164
- }
3165
- case "selector": {
3166
- const present = env.querySelector(check.selector) !== null;
3167
- const name = `querySelector(${JSON.stringify(check.selector)})`;
3168
- if (present) return {
3169
- status: "pass",
3170
- name,
3171
- detail: "element found"
3172
- };
3173
- if (check.optionalWhen !== void 0 && !mounted(env, check.optionalWhen)) return {
3174
- status: "skip",
3175
- name,
3176
- detail: "not on screen right now"
3177
- };
3178
- return {
3179
- status: "fail",
3180
- name,
3181
- detail: "no element matches",
3182
- reason: `no element matches ${check.selector} while its surface is on screen`
3183
- };
3184
- }
3185
- case "attr": {
3186
- const present = env.querySelector(`[${check.attr}]`) !== null;
3187
- const name = `querySelector([${check.attr}])`;
3188
- if (present) return {
3189
- status: "pass",
3190
- name,
3191
- detail: "attribute found"
3192
- };
3193
- if (check.optionalWhen !== void 0 && !mounted(env, check.optionalWhen)) return {
3194
- status: "skip",
3195
- name,
3196
- detail: "not on screen right now"
3197
- };
3198
- return {
3199
- status: "fail",
3200
- name,
3201
- detail: "no element carries it",
3202
- reason: `nothing in the DOM carries ${check.attr} while its surface is on screen`
3203
- };
3204
- }
3205
- case "computed": {
3206
- const host = check.on !== void 0 ? env.querySelector(check.on) : null;
3207
- const name = `computed(${check.token}${check.on !== void 0 ? ` on ${check.on}` : ""})`;
3208
- if (check.on !== void 0 && host === null) return {
3209
- status: "skip",
3210
- name,
3211
- detail: `${check.on} not mounted`
3212
- };
3213
- const value = env.computedValue(check.token, host);
3214
- return value !== "" ? {
3215
- status: "pass",
3216
- name,
3217
- detail: value
3218
- } : {
3219
- status: "fail",
3220
- name,
3221
- detail: "resolves to nothing",
3222
- reason: `${check.token} resolves to an empty value`
3223
- };
3224
- }
3225
- case "rule": {
3226
- const name = `stylesheet includes ${JSON.stringify(check.match)}`;
3227
- if (!stylesReadable(env)) return {
3228
- status: "skip",
3229
- name,
3230
- detail: "no stylesheet is readable"
3231
- };
3232
- return ruleMentioned(env, check.match) ? {
3233
- status: "pass",
3234
- name,
3235
- detail: "declared by a host rule"
3236
- } : {
3237
- status: "fail",
3238
- name,
3239
- detail: "no rule mentions it",
3240
- reason: `no stylesheet declares ${check.match}`
3241
- };
3242
- }
3243
- default: return {
3244
- status: "fail",
3245
- name: "unknown check",
3246
- detail: String(check)
3247
- };
3248
- }
3249
- }
3250
- /** A contract with no checks is documentation only (a legacy alias we still accept). */
3251
- function noChecks(c, lang) {
3252
- return {
3253
- id: c.id,
3254
- label: labelOf(c, lang),
3255
- symptom: symptomOf(c, lang),
3256
- status: "info",
3257
- detail: lang === "zh" ? "无需运行时检查 —— 仅记录兼容用法" : "no runtime check — accepted for compatibility",
3258
- target: c.target,
3259
- sources: c.sources,
3260
- usedBy: c.usedBy,
3261
- checks: []
3262
- };
3263
- }
3264
- /**
3265
- * Run every contract against a probe environment.
3266
- *
3267
- * Only the FIRST failing check of a contract is reported: the later ones usually
3268
- * depend on it (no service ⇒ no methods on it), so listing them all would turn
3269
- * one rename into five red lines.
3270
- */
3271
- function probeContracts(env, lang) {
3272
- return HOST_CONTRACTS.map((c) => {
3273
- const result = {
3274
- id: c.id,
3275
- label: labelOf(c, lang),
3276
- symptom: symptomOf(c, lang),
3277
- status: "pass",
3278
- detail: "",
3279
- target: c.target,
3280
- sources: c.sources,
3281
- usedBy: c.usedBy,
3282
- checks: []
3283
- };
3284
- if (c.checks.length === 0) return noChecks(c, lang);
3285
- const outcomes = [];
3286
- let firstFail;
3287
- for (const check of c.checks) {
3288
- let outcome;
3289
- try {
3290
- outcome = evaluate(env, check);
3291
- } catch (e) {
3292
- outcome = {
3293
- status: "fail",
3294
- name: check.kind,
3295
- detail: String(e),
3296
- reason: `${check.kind} check threw`
3297
- };
3298
- }
3299
- outcomes.push(outcome);
3300
- if (outcome.status === "fail" && firstFail === void 0) firstFail = outcome;
3301
- }
3302
- result.checks = outcomes.map((o) => `${o.status} · ${o.name} — ${o.detail}`);
3303
- if (firstFail !== void 0) {
3304
- result.status = "fail";
3305
- result.detail = `${firstFail.name} — ${firstFail.detail}`;
3306
- result.reason = firstFail.reason;
3307
- return result;
3308
- }
3309
- const ran = outcomes.filter((o) => o.status !== "skip");
3310
- if (ran.length === 0) {
3311
- result.status = "skip";
3312
- result.detail = outcomes.map((o) => `${o.name} — ${o.detail}`).join("; ");
3313
- return result;
3314
- }
3315
- result.status = ran.some((o) => o.status === "info") && ran.every((o) => o.status === "info") ? "info" : "pass";
3316
- const last = ran[ran.length - 1];
3317
- result.detail = `${last.name} — ${last.detail}`;
3318
- return result;
3319
- });
3320
- }
3321
- /** Assemble the panel's report around a set of probe results. */
3322
- function buildClientReport(input, host) {
3323
- const results = probeContracts(browserEnv(input.ctx), input.lang);
3324
- const pass = results.filter((r) => r.status === "pass").length;
3325
- const fail = results.filter((r) => r.status === "fail").length;
3326
- const skip = results.filter((r) => r.status === "skip" || r.status === "info").length;
3327
- return {
3328
- phase: "client",
3329
- at: (/* @__PURE__ */ new Date()).toISOString(),
3330
- plugin: {
3331
- version: input.pluginVersion,
3332
- floor: input.floor
3333
- },
3334
- host: {
3335
- version: host.version,
3336
- compatible: host.compatible,
3337
- node: "",
3338
- root: "",
3339
- note: host.note,
3340
- model: input.model
3341
- },
3342
- results,
3343
- summary: {
3344
- pass,
3345
- fail,
3346
- skip,
3347
- total: results.length
3348
- }
3349
- };
3350
- }
3351
- //#endregion
3352
- //#region src/client/build-info.ts
3353
- /** This plugin's version at build time ('0.0.0' when built unbundled). */
3354
- const PLUGIN_VERSION = "0.4.2";
3355
- /** The dsh floor this build declares, straight from `engines.dsh`. */
3356
- const DSH_FLOOR = "0.1.7-rc.2";
3357
1126
  const UI_CSS = `
3358
1127
  /* The section is rendered INLINE inside the host settings dialog's content
3359
1128
  * column: the host provides the modal chrome (backdrop, centering, closing).
@@ -3611,6 +1380,22 @@ window.__ModuleLoader__.load({
3611
1380
  }
3612
1381
  `;
3613
1382
  const CSS_ID = "dab-ui-css";
1383
+ /**
1384
+ * The attribute that marks a `<style>` element as OURS.
1385
+ *
1386
+ * The host's own CSS modules also set `data-plugin` on their `<style>` elements
1387
+ * (every one of them), so `data-plugin` cannot distinguish this plugin's sheets
1388
+ * from the host's — and the host-contract probe has to make exactly that
1389
+ * distinction: a token this plugin re-emits must never be allowed to make a
1390
+ * *host* check pass. This attribute is the honest marker, and it is set on every
1391
+ * stylesheet this plugin injects.
1392
+ */
1393
+ const OWN_SHEET_ATTR = "data-dab-side";
1394
+ /** Mark a stylesheet this plugin injected, so the host probe can tell it apart. */
1395
+ function markOwnSheet(el) {
1396
+ el.setAttribute(OWN_SHEET_ATTR, "own");
1397
+ return el;
1398
+ }
3614
1399
  /** Inject the design-system stylesheet once per document (HMR-safe). */
3615
1400
  function ensureUiCss() {
3616
1401
  if (typeof document === "undefined") return;
@@ -3618,11 +1403,2302 @@ window.__ModuleLoader__.load({
3618
1403
  if (!el) {
3619
1404
  el = document.createElement("style");
3620
1405
  el.id = CSS_ID;
1406
+ markOwnSheet(el);
3621
1407
  document.head.appendChild(el);
3622
1408
  }
3623
- if (el.textContent !== UI_CSS) el.textContent = UI_CSS;
1409
+ if (el.textContent !== UI_CSS) el.textContent = UI_CSS;
1410
+ }
1411
+ //#endregion
1412
+ //#region src/client/wallpaper.ts
1413
+ let wpEl = null;
1414
+ let appliedTokenNames = [];
1415
+ let tokenStyleEl = null;
1416
+ function ensureTokenStyle() {
1417
+ if (tokenStyleEl?.isConnected) return tokenStyleEl;
1418
+ tokenStyleEl = document.createElement("style");
1419
+ tokenStyleEl.dataset.plugin = "dsh-background-by-model-tokens";
1420
+ markOwnSheet(tokenStyleEl);
1421
+ document.head.appendChild(tokenStyleEl);
1422
+ return tokenStyleEl;
1423
+ }
1424
+ function clearCustomTokens() {
1425
+ if (tokenStyleEl) tokenStyleEl.textContent = "";
1426
+ for (const name of appliedTokenNames) document.body.style.removeProperty(name);
1427
+ appliedTokenNames = [];
1428
+ }
1429
+ const OPACITY_TOKEN_GROUPS = [
1430
+ {
1431
+ part: "bg",
1432
+ names: ["--dsw-alias-bg-base"]
1433
+ },
1434
+ {
1435
+ part: "sidebar",
1436
+ names: ["--dsw-specific-sidebar-fill"]
1437
+ },
1438
+ {
1439
+ part: "card",
1440
+ names: [
1441
+ "--dsw-alias-bg-layer-1",
1442
+ "--dsw-alias-bg-layer-2",
1443
+ "--dsw-alias-bg-layer-3",
1444
+ "--dsw-specific-menu",
1445
+ "--dsw-menu-surface-fill"
1446
+ ]
1447
+ },
1448
+ {
1449
+ part: "input",
1450
+ names: ["--dsw-specific-input-major"]
1451
+ }
1452
+ ];
1453
+ const OPACITY_VARS = {
1454
+ "--dsw-alias-bg-base": "--dsh-any-op-bg",
1455
+ "--dsw-specific-sidebar-fill": "--dsh-any-op-sidebar",
1456
+ "--dsw-alias-bg-layer-1": "--dsh-any-op-card-1",
1457
+ "--dsw-alias-bg-layer-2": "--dsh-any-op-card-2",
1458
+ "--dsw-alias-bg-layer-3": "--dsh-any-op-card-3",
1459
+ "--dsw-specific-input-major": "--dsh-any-op-input",
1460
+ "--dsw-specific-menu": "--dsh-any-op-menu",
1461
+ "--dsw-menu-surface-fill": "--dsh-any-op-menu"
1462
+ };
1463
+ let baseTokenKey = "";
1464
+ let pendingOps = null;
1465
+ let tokensRaf = null;
1466
+ function applyCustomTokens(ops) {
1467
+ pendingOps = ops;
1468
+ if (tokensRaf !== null) return;
1469
+ tokensRaf = requestAnimationFrame(() => {
1470
+ tokensRaf = null;
1471
+ if (pendingOps === null) return;
1472
+ const o = pendingOps;
1473
+ pendingOps = null;
1474
+ applyCustomTokensNow(o);
1475
+ });
1476
+ }
1477
+ let lastBgKey = "";
1478
+ function applyCustomTokensNow(ops) {
1479
+ const [h, s, l] = rColor();
1480
+ const { tokens } = genTokens(h, s, l);
1481
+ try {
1482
+ const forceDark = l < .55;
1483
+ if (`${h}|${s}|${l}` !== baseTokenKey) {
1484
+ baseTokenKey = `${h}|${s}|${l}`;
1485
+ if (forceDark) document.body.setAttribute("data-ds-dark-theme", "dsh-background-by-model");
1486
+ else document.body.removeAttribute("data-ds-dark-theme");
1487
+ const decls = [`color-scheme:${forceDark ? "dark" : "light"}`];
1488
+ for (const [name, value] of Object.entries(tokens)) {
1489
+ const opVar = OPACITY_VARS[name];
1490
+ decls.push(`${name}:${opVar !== void 0 ? `var(${opVar})` : value}!important`);
1491
+ }
1492
+ ensureTokenStyle().textContent = `body{${decls.join(";")}}`;
1493
+ for (const name of appliedTokenNames) document.body.style.removeProperty(name);
1494
+ appliedTokenNames = Object.keys(tokens);
1495
+ }
1496
+ const root = document.documentElement;
1497
+ for (const g of OPACITY_TOKEN_GROUPS) for (const name of g.names) root.style.setProperty(OPACITY_VARS[name], toRgba(tokens[name] ?? "#000", ops[g.part]));
1498
+ root.style.setProperty("--dsh-any-op-menu-cordis", toRgba(tokens["--dsw-specific-menu"] ?? "#000", ops.input));
1499
+ const bgKey = `${baseTokenKey}|${ops.bg}`;
1500
+ if (bgKey !== lastBgKey) {
1501
+ lastBgKey = bgKey;
1502
+ applyPartOpacities(ops);
1503
+ }
1504
+ } catch {}
1505
+ }
1506
+ const SETTINGS_PANEL_SEL = "[role=\"dialog\"][aria-modal=\"true\"][aria-labelledby]";
1507
+ 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)}`;
1508
+ const INPUT_BLUR_RULE = "[data-composer-card],[data-cordis-panel]{-webkit-backdrop-filter:var(--dsh-any-input-blur,none);backdrop-filter:var(--dsh-any-input-blur,none)}[data-cordis-panel]{--dsw-specific-menu:var(--dsh-any-op-menu-cordis)!important}";
1509
+ function applyInputBlur(px) {
1510
+ if (px > 0) document.documentElement.style.setProperty("--dsh-any-input-blur", `blur(${px}px)`);
1511
+ else document.documentElement.style.removeProperty("--dsh-any-input-blur");
1512
+ }
1513
+ function applySettingsOverrides(op) {
1514
+ const [h, s, l] = rColor();
1515
+ const tokens = genTokens(h, s, l).tokens;
1516
+ const layer1 = tokens["--dsw-alias-bg-layer-1"];
1517
+ const layer2 = tokens["--dsw-alias-bg-layer-2"];
1518
+ const layer3 = tokens["--dsw-alias-bg-layer-3"];
1519
+ if (layer2 !== void 0) document.documentElement.style.setProperty("--dsh-any-bg-settings-surface", toRgba(layer2, op));
1520
+ if (layer1 !== void 0) document.documentElement.style.setProperty("--dsh-any-bg-settings-layer-1", toRgba(layer1, op));
1521
+ if (layer2 !== void 0) document.documentElement.style.setProperty("--dsh-any-bg-settings-layer-2", toRgba(layer2, op));
1522
+ if (layer3 !== void 0) document.documentElement.style.setProperty("--dsh-any-bg-settings-layer-3", toRgba(layer3, op));
1523
+ }
1524
+ const TRAJECTORY_STYLE_RULE = "[data-conversation-composer-overlay]{--dsw-alias-bg-layer-1:var(--dsh-any-traj-layer-1);--dsw-alias-bg-layer-2:var(--dsh-any-traj-layer-2);--dsw-alias-bg-layer-3:var(--dsh-any-traj-layer-3)}";
1525
+ function applyTrajectoryOverrides(op) {
1526
+ const [h, s, l] = rColor();
1527
+ const tokens = genTokens(h, s, l).tokens;
1528
+ const layer1 = tokens["--dsw-alias-bg-layer-1"];
1529
+ const layer2 = tokens["--dsw-alias-bg-layer-2"];
1530
+ const layer3 = tokens["--dsw-alias-bg-layer-3"];
1531
+ if (layer1 !== void 0) document.documentElement.style.setProperty("--dsh-any-traj-layer-1", toRgba(layer1, op));
1532
+ if (layer2 !== void 0) document.documentElement.style.setProperty("--dsh-any-traj-layer-2", toRgba(layer2, op));
1533
+ if (layer3 !== void 0) document.documentElement.style.setProperty("--dsh-any-traj-layer-3", toRgba(layer3, op));
1534
+ }
1535
+ const RIGHTBAR_STYLE_RULE = "div[data-sidebar-right-panel]:not(:has([data-dockkit-content],[data-dockkit-empty]))[data-sidebar-right-open],div[data-sidebar-right-panel] [data-dockkit-content]:not([data-dockkit-float]),div[data-sidebar-right-panel] [data-dockkit-empty]{background:var(--dsh-any-bg-rightbar,var(--dsw-alias-bg-base))!important}div[data-sidebar-right-panel][data-sidebar-right-open]{-webkit-backdrop-filter:var(--dsh-any-blur-rightbar,none);backdrop-filter:var(--dsh-any-blur-rightbar,none)}";
1536
+ /** Paint the file-preview panel's surface from its own slider. */
1537
+ function applyRightbarOverrides(op) {
1538
+ const root = document.documentElement;
1539
+ if (!rHasColor()) {
1540
+ root.style.removeProperty("--dsh-any-bg-rightbar");
1541
+ return;
1542
+ }
1543
+ const [h, s, l] = rColor();
1544
+ const base = genTokens(h, s, l).tokens["--dsw-alias-bg-base"];
1545
+ if (base !== void 0) root.style.setProperty("--dsh-any-bg-rightbar", toRgba(base, op));
1546
+ }
1547
+ /** Extra backdrop frost for the file-preview panel (0 = no frost of its own). */
1548
+ function applyRightbarBlur(px) {
1549
+ if (px > 0) document.documentElement.style.setProperty("--dsh-any-blur-rightbar", `blur(${px}px)`);
1550
+ else document.documentElement.style.removeProperty("--dsh-any-blur-rightbar");
1551
+ }
1552
+ let frameEl = null;
1553
+ let sidebarEl = null;
1554
+ let centerEl = null;
1555
+ let detailsEl = null;
1556
+ const PART_BLUR_CLASS = "dab-part-blur";
1557
+ const PART_UNDERLAY_CLASS = "dab-part-underlay";
1558
+ const PART_BLUR_RULE = `${PART_BLUR_CLASS}{isolation:isolate}.${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)}`;
1559
+ let partBlurStyleEl = null;
1560
+ function ensurePartBlurStyle() {
1561
+ if (partBlurStyleEl?.isConnected) return;
1562
+ partBlurStyleEl = document.createElement("style");
1563
+ partBlurStyleEl.dataset.plugin = "dsh-background-by-model-parts";
1564
+ markOwnSheet(partBlurStyleEl);
1565
+ partBlurStyleEl.textContent = PART_BLUR_RULE;
1566
+ document.head.appendChild(partBlurStyleEl);
1567
+ }
1568
+ function discoverParts() {
1569
+ const overlay = document.querySelector("[data-shell-overlay]");
1570
+ if (overlay === null) return;
1571
+ const frame = overlay.parentElement;
1572
+ if (frame === null) return;
1573
+ frameEl = frame;
1574
+ const idx = Array.from(frame.children).indexOf(overlay);
1575
+ sidebarEl = frame.children[idx - 3] ?? null;
1576
+ centerEl = frame.children[idx - 2] ?? null;
1577
+ detailsEl = frame.children[idx - 1] ?? null;
1578
+ }
1579
+ function setBlur(el, px) {
1580
+ if (el === null) return;
1581
+ const underlay = el.querySelector(`:scope > .${PART_UNDERLAY_CLASS}`);
1582
+ if (px > 0) {
1583
+ ensurePartBlurStyle();
1584
+ if (!el.classList.contains(PART_BLUR_CLASS) && getComputedStyle(el).position === "static") {
1585
+ el.style.position = "relative";
1586
+ el.setAttribute("data-dab-pos-patched", "1");
1587
+ }
1588
+ el.classList.add(PART_BLUR_CLASS);
1589
+ if (underlay === null) {
1590
+ const node = document.createElement("div");
1591
+ node.className = PART_UNDERLAY_CLASS;
1592
+ el.prepend(node);
1593
+ }
1594
+ el.style.setProperty("--dsh-any-part-blur", `blur(${px}px)`);
1595
+ } else {
1596
+ el.classList.remove(PART_BLUR_CLASS);
1597
+ el.style.removeProperty("--dsh-any-part-blur");
1598
+ underlay?.remove();
1599
+ if (el.getAttribute("data-dab-pos-patched") === "1") {
1600
+ el.style.removeProperty("position");
1601
+ el.removeAttribute("data-dab-pos-patched");
1602
+ }
1603
+ }
1604
+ }
1605
+ function applySettingsBlur(px) {
1606
+ if (px > 0) document.documentElement.style.setProperty("--dsh-any-blur-settings", `blur(${px}px)`);
1607
+ else document.documentElement.style.removeProperty("--dsh-any-blur-settings");
1608
+ }
1609
+ /** Apply the main-background opacity to the center/details columns instead of
1610
+ * the frame. The frame's translucent bg-base sits UNDER the sidebar, so
1611
+ * reducing the main-bg opacity stacked a second alpha onto the sidebar; moving
1612
+ * the alpha onto the columns keeps the sidebar owned by its own slider. */
1613
+ function applyPartOpacities(ops) {
1614
+ if (!rHasColor()) return;
1615
+ discoverParts();
1616
+ if (frameEl === null) return;
1617
+ const [h, s, l] = rColor();
1618
+ const base = genTokens(h, s, l).tokens["--dsw-alias-bg-base"];
1619
+ frameEl.style.background = "transparent";
1620
+ if (centerEl !== null) centerEl.style.background = base !== void 0 ? toRgba(base, ops.bg) : "transparent";
1621
+ if (detailsEl !== null) detailsEl.style.background = base !== void 0 ? toRgba(base, ops.bg) : "transparent";
1622
+ }
1623
+ /** Blur of the option panels inside the settings dialog (.dab-card), owned by
1624
+ * the "dialog option panel" (card) blur slider. Written as a plugin-owned
1625
+ * variable consumed by SETTINGS_STYLE_RULE — deliberately NOT applied to the
1626
+ * homepage center/details columns, which this slider must never touch. */
1627
+ function applyCardPanelsBlur(px) {
1628
+ if (px > 0) document.documentElement.style.setProperty("--dsh-any-blur-card-panels", `blur(${px}px)`);
1629
+ else document.documentElement.style.removeProperty("--dsh-any-blur-card-panels");
1630
+ }
1631
+ /** Apply per-part interface blur to the AppFrame columns + settings panel. */
1632
+ function applyPartBlurs(blurs) {
1633
+ discoverParts();
1634
+ setBlur(frameEl, 0);
1635
+ setBlur(sidebarEl, blurs.sidebar);
1636
+ setBlur(centerEl, blurs.bg);
1637
+ setBlur(detailsEl, blurs.bg);
1638
+ applyCardPanelsBlur(blurs.card);
1639
+ applySettingsBlur(blurs.settings);
1640
+ applyInputBlur(blurs.input);
1641
+ applyRightbarBlur(blurs.rightbar);
1642
+ applyViewCards();
1643
+ }
1644
+ /** Live per-part blur update during slider drag (no full re-apply). */
1645
+ function setPartBlur(part, v) {
1646
+ if (part === "settings") {
1647
+ applySettingsBlur(v);
1648
+ return;
1649
+ }
1650
+ if (part === "card") {
1651
+ applyCardPanelsBlur(v);
1652
+ return;
1653
+ }
1654
+ if (part === "input") {
1655
+ applyInputBlur(v);
1656
+ return;
1657
+ }
1658
+ if (part === "rightbar") {
1659
+ applyRightbarBlur(v);
1660
+ return;
1661
+ }
1662
+ if (part === "chat" || part === "trajectory") {
1663
+ applyViewCards();
1664
+ return;
1665
+ }
1666
+ discoverParts();
1667
+ if (part === "bg") {
1668
+ setBlur(centerEl, v);
1669
+ setBlur(detailsEl, v);
1670
+ } else setBlur(sidebarEl, v);
1671
+ }
1672
+ const VIEW_CARDS = [{
1673
+ sel: "[data-chat-flow]",
1674
+ mark: "data-dab-chat-card",
1675
+ prev: "dabChatPrev",
1676
+ opacity: rChatTextOpacity,
1677
+ blur: () => rBlurs().chat,
1678
+ fallback: true
1679
+ }, {
1680
+ sel: "[data-conversation-composer-overlay]",
1681
+ mark: "data-dab-traj-card",
1682
+ prev: "dabTrajPrev",
1683
+ opacity: rTrajectoryOpacity,
1684
+ blur: () => rBlurs().trajectory,
1685
+ plain: true
1686
+ }];
1687
+ const viewTargets = VIEW_CARDS.map(() => null);
1688
+ function isScrollableY(el) {
1689
+ const oy = getComputedStyle(el).overflowY;
1690
+ return oy === "auto" || oy === "scroll" || oy === "overlay";
1691
+ }
1692
+ /** Whether the subtree hosts the chat input (textarea / contenteditable /
1693
+ * textbox role) — used to keep the card off the input row. */
1694
+ function containsChatEditor(el) {
1695
+ return el.querySelector("textarea,[contenteditable=\"true\"],[contenteditable=\"\"],[contenteditable=\"plaintext-only\"],[role=\"textbox\"]") !== null;
1696
+ }
1697
+ /** Walk down from a coarse candidate toward the actual message column. */
1698
+ function refineMessageColumn(start) {
1699
+ let cur = start;
1700
+ for (let depth = 0; depth < 10; depth++) {
1701
+ if (isScrollableY(cur)) break;
1702
+ const kids = Array.from(cur.children).filter((k) => k instanceof HTMLElement);
1703
+ if (kids.length === 0) break;
1704
+ const tallest = kids.reduce((a, b) => b.clientHeight > a.clientHeight ? b : a);
1705
+ if (containsChatEditor(cur)) {
1706
+ const candidates = kids.filter((k) => !containsChatEditor(k) && k.clientHeight >= cur.clientHeight * .4);
1707
+ if (candidates.length === 0) break;
1708
+ cur = candidates.reduce((a, b) => b.clientHeight > a.clientHeight ? b : a);
1709
+ continue;
1710
+ }
1711
+ if (kids.length > 1 && tallest.clientHeight >= cur.clientHeight * .85) {
1712
+ cur = tallest;
1713
+ continue;
1714
+ }
1715
+ break;
1716
+ }
1717
+ return cur;
1718
+ }
1719
+ function discoverViewTarget(idx, spec) {
1720
+ if (centerEl === null || !document.body.contains(centerEl)) {
1721
+ viewTargets[idx] = null;
1722
+ return null;
1723
+ }
1724
+ const marked = centerEl.querySelector(spec.sel);
1725
+ const cached = viewTargets[idx];
1726
+ if (marked !== null) {
1727
+ if (cached !== null && cached !== marked) {
1728
+ setBlur(cached, 0);
1729
+ restoreCardHost(cached, spec.mark, spec.prev, spec.plain === true);
1730
+ }
1731
+ viewTargets[idx] = marked;
1732
+ return marked;
1733
+ }
1734
+ if (cached !== null && centerEl.contains(cached)) return cached;
1735
+ viewTargets[idx] = null;
1736
+ if (spec.fallback !== true) return null;
1737
+ if (centerEl.querySelector("[data-conversation-scroll]") !== null) return null;
1738
+ if (spec.opacity() <= 0 && spec.blur() <= 0) return null;
1739
+ let best = null;
1740
+ let bestArea = 0;
1741
+ for (const el of Array.from(centerEl.querySelectorAll("*"))) {
1742
+ if (!isScrollableY(el)) continue;
1743
+ if (el.clientHeight < centerEl.clientHeight * .35) continue;
1744
+ const area = el.clientWidth * el.clientHeight;
1745
+ if (area > bestArea) {
1746
+ bestArea = area;
1747
+ best = el;
1748
+ }
1749
+ }
1750
+ if (best === null) for (const el of Array.from(centerEl.children)) {
1751
+ if (!(el instanceof HTMLElement)) continue;
1752
+ if (el.clientHeight < centerEl.clientHeight * .5) continue;
1753
+ if (el.clientHeight > (best?.clientHeight ?? 0)) best = el;
1754
+ }
1755
+ const refined = best !== null ? refineMessageColumn(best) : null;
1756
+ viewTargets[idx] = refined;
1757
+ return refined;
1758
+ }
1759
+ /** Stash the host's own inline values so teardown restores them exactly. */
1760
+ function stashCardPrev(el, prev, plain) {
1761
+ const ds = el.dataset;
1762
+ ds[prev + "Bg"] = el.style.getPropertyValue("background");
1763
+ if (plain) return;
1764
+ ds[prev + "Border"] = el.style.getPropertyValue("border");
1765
+ ds[prev + "Radius"] = el.style.getPropertyValue("border-radius");
1766
+ ds[prev + "Padding"] = el.style.getPropertyValue("padding");
1767
+ }
1768
+ /** Undo the inline styling, restoring the host's previous inline values. */
1769
+ function restoreCardHost(el, mark, prev, plain) {
1770
+ if (!el.hasAttribute(mark)) return;
1771
+ const ds = el.dataset;
1772
+ const restore = (prop, v) => {
1773
+ if (v !== void 0 && v !== "") el.style.setProperty(prop, v);
1774
+ else el.style.removeProperty(prop);
1775
+ };
1776
+ restore("background", ds[prev + "Bg"]);
1777
+ if (!plain) {
1778
+ restore("border", ds[prev + "Border"]);
1779
+ restore("border-radius", ds[prev + "Radius"]);
1780
+ restore("padding", ds[prev + "Padding"]);
1781
+ delete ds[prev + "Border"];
1782
+ delete ds[prev + "Radius"];
1783
+ delete ds[prev + "Padding"];
1784
+ }
1785
+ delete ds[prev + "Bg"];
1786
+ el.removeAttribute(mark);
1787
+ }
1788
+ /** Teardown only: strip every view treatment and hand the hosts back untouched. */
1789
+ function removeViewCards() {
1790
+ VIEW_CARDS.forEach((spec, i) => {
1791
+ const el = viewTargets[i];
1792
+ if (el !== null) {
1793
+ setBlur(el, 0);
1794
+ restoreCardHost(el, spec.mark, spec.prev, spec.plain === true);
1795
+ }
1796
+ viewTargets[i] = null;
1797
+ });
1798
+ }
1799
+ const TABLE_FIX_RULE = [
1800
+ ".md-table-wide {",
1801
+ " --dsh-table-spare: 0px !important;",
1802
+ " --dsh-table-lead: 0px !important;",
1803
+ " box-sizing: border-box !important;",
1804
+ " width: 100% !important;",
1805
+ " max-width: 100% !important;",
1806
+ " margin-left: 0 !important;",
1807
+ " padding-left: 0 !important;",
1808
+ " padding-bottom: 0 !important;",
1809
+ " overflow-x: auto !important;",
1810
+ "}"
1811
+ ].join("\n");
1812
+ let tableFixStyleEl = null;
1813
+ /** Toggle the wide-table clamp according to the chat region's opacity & blur. */
1814
+ function syncTableFix() {
1815
+ if (!(rChatTextOpacity() > 0 || rBlurs().chat > 0)) {
1816
+ if (tableFixStyleEl !== null) {
1817
+ tableFixStyleEl.remove();
1818
+ tableFixStyleEl = null;
1819
+ }
1820
+ return;
1821
+ }
1822
+ if (tableFixStyleEl === null) {
1823
+ tableFixStyleEl = document.createElement("style");
1824
+ tableFixStyleEl.dataset.plugin = "dsh-background-by-model-table-fix";
1825
+ markOwnSheet(tableFixStyleEl);
1826
+ tableFixStyleEl.textContent = TABLE_FIX_RULE;
1827
+ }
1828
+ if (!tableFixStyleEl.isConnected) document.head.appendChild(tableFixStyleEl);
1829
+ }
1830
+ /** Re-derive the conversation view cards from the current config. */
1831
+ function applyViewCards() {
1832
+ discoverParts();
1833
+ if (centerEl === null) return;
1834
+ const [h, s, l] = rColor();
1835
+ const surface = genTokens(h, s, l).tokens["--dsw-alias-bg-layer-1"];
1836
+ VIEW_CARDS.forEach((spec, i) => {
1837
+ const target = discoverViewTarget(i, spec);
1838
+ if (target === null) return;
1839
+ const plain = spec.plain === true;
1840
+ const opacity = spec.opacity();
1841
+ const blurPx = spec.blur();
1842
+ if (!plain) {
1843
+ if (!target.hasAttribute(spec.mark)) stashCardPrev(target, spec.prev, false);
1844
+ const borderAlpha = opacity > 0 ? Math.min(1, opacity * 1.5) : blurPx > 0 ? .35 : 0;
1845
+ target.style.background = surface !== void 0 ? toRgba(surface, opacity) : "transparent";
1846
+ target.style.border = surface !== void 0 ? `1px solid ${toRgba(surface, borderAlpha)}` : "1px solid transparent";
1847
+ target.style.borderRadius = "16px";
1848
+ target.style.padding = "18px";
1849
+ }
1850
+ target.setAttribute(spec.mark, "1");
1851
+ setBlur(target, blurPx);
1852
+ });
1853
+ syncTableFix();
1854
+ }
1855
+ let partsObserver = null;
1856
+ /** Watch for the AppFrame mounting so persisted blurs land even when the shell
1857
+ * renders after this plugin's apply. */
1858
+ function watchParts() {
1859
+ if (partsObserver !== null || typeof MutationObserver === "undefined") return;
1860
+ partsObserver = new MutationObserver(() => {
1861
+ if (frameEl !== null && sidebarEl !== null && centerEl !== null && detailsEl !== null && document.body.contains(frameEl) && viewTargets.every((el) => el !== null && document.body.contains(el))) return;
1862
+ applyPartBlurs(rBlurs());
1863
+ applyPartOpacities(rOps());
1864
+ });
1865
+ partsObserver.observe(document.body, {
1866
+ childList: true,
1867
+ subtree: true
1868
+ });
1869
+ }
1870
+ function stopWatchingParts() {
1871
+ partsObserver?.disconnect();
1872
+ partsObserver = null;
1873
+ }
1874
+ let themeObserver = null;
1875
+ let themeRaf = 0;
1876
+ function reassertScheme() {
1877
+ const [, , l] = rColor();
1878
+ if (l < .55) document.body.setAttribute("data-ds-dark-theme", "dsh-background-by-model");
1879
+ else document.body.removeAttribute("data-ds-dark-theme");
1880
+ applyCustomTokens(rOps());
1881
+ }
1882
+ /** Re-assert the plugin's forced scheme whenever the host strips it, so a
1883
+ * refresh / cold-load / set-change never flashes a light frame. */
1884
+ function watchThemeResets() {
1885
+ if (themeObserver !== null || typeof MutationObserver === "undefined") return () => void 0;
1886
+ themeObserver = new MutationObserver(() => {
1887
+ if (document.body.getAttribute("data-ds-dark-theme") === "dsh-background-by-model") return;
1888
+ if (!rHasColor()) return;
1889
+ if (themeRaf !== 0) return;
1890
+ themeRaf = requestAnimationFrame(() => {
1891
+ themeRaf = 0;
1892
+ if (document.body.getAttribute("data-ds-dark-theme") === "dsh-background-by-model") return;
1893
+ reassertScheme();
1894
+ });
1895
+ });
1896
+ themeObserver.observe(document.body, {
1897
+ attributes: true,
1898
+ attributeFilter: ["data-ds-dark-theme"]
1899
+ });
1900
+ themeObserver.observe(document.documentElement, {
1901
+ attributes: true,
1902
+ attributeFilter: ["data-ds-dark-theme"]
1903
+ });
1904
+ return () => {
1905
+ themeObserver?.disconnect();
1906
+ themeObserver = null;
1907
+ };
1908
+ }
1909
+ const FADE_MS = 320;
1910
+ let layers = null;
1911
+ /** Index of the layer the user is looking at. */
1912
+ let front = 0;
1913
+ /** Index of the layer fading in right now, if any. */
1914
+ let pending = null;
1915
+ let fadeTimer = null;
1916
+ function createLayer() {
1917
+ const el = document.createElement("div");
1918
+ el.dataset.dabWpLayer = "";
1919
+ el.style.cssText = "position:absolute;inset:0;opacity:0;background-repeat:no-repeat;background-position:center;";
1920
+ return {
1921
+ el,
1922
+ url: null
1923
+ };
1924
+ }
1925
+ function ensureWpContainer() {
1926
+ if (wpEl !== null && layers !== null && document.body.contains(wpEl)) return;
1927
+ if (fadeTimer !== null) {
1928
+ window.clearTimeout(fadeTimer);
1929
+ fadeTimer = null;
1930
+ }
1931
+ const a = createLayer();
1932
+ const b = createLayer();
1933
+ wpEl = document.createElement("div");
1934
+ wpEl.dataset.dabWp = "";
1935
+ wpEl.style.cssText = "position:fixed;inset:0;z-index:-1;pointer-events:none;overflow:hidden;";
1936
+ wpEl.append(a.el, b.el);
1937
+ layers = [a, b];
1938
+ front = 0;
1939
+ pending = null;
1940
+ document.body.prepend(wpEl);
1941
+ }
1942
+ /** Drop the whole wallpaper layer (no rule carries an image). */
1943
+ function dropWpContainer() {
1944
+ if (fadeTimer !== null) {
1945
+ window.clearTimeout(fadeTimer);
1946
+ fadeTimer = null;
1947
+ }
1948
+ pending = null;
1949
+ front = 0;
1950
+ layers = null;
1951
+ wpEl?.remove();
1952
+ wpEl = null;
1953
+ }
1954
+ /** Release one layer's image — the layer is fully covered when this is called. */
1955
+ function clearLayer(l) {
1956
+ if (l.url === null) return;
1957
+ l.url = null;
1958
+ l.el.style.backgroundImage = "";
1959
+ l.el.style.backgroundSize = "";
1960
+ l.el.style.backgroundPosition = "";
1961
+ }
1962
+ /** Intrinsic-size cache for the center mode (native pixels of the current image). */
1963
+ let imgNat = null;
1964
+ function imageNatSize(url, cb) {
1965
+ if (imgNat !== null && imgNat.url === url) {
1966
+ cb(imgNat.w, imgNat.h);
1967
+ return;
1968
+ }
1969
+ const img = new Image();
1970
+ img.onload = () => {
1971
+ imgNat = {
1972
+ url,
1973
+ w: img.naturalWidth,
1974
+ h: img.naturalHeight
1975
+ };
1976
+ cb(img.naturalWidth, img.naturalHeight);
1977
+ };
1978
+ img.onerror = () => cb(0, 0);
1979
+ img.src = url;
1980
+ }
1981
+ /** Paint one layer's image and its placement. The placement always follows the
1982
+ * ACTIVE rule: the incoming layer is painted right after a switch, and a
1983
+ * repaint of the same URL (framing edit, viewport resize) must use the rule's
1984
+ * current framing. */
1985
+ function paintLayer(l, url) {
1986
+ const el = l.el;
1987
+ l.url = url;
1988
+ const bg = rBgState();
1989
+ const mode = rBgMode();
1990
+ const next = `url("${url}")`;
1991
+ if (el.style.backgroundImage !== next) el.style.backgroundImage = next;
1992
+ if (mode === "fit") {
1993
+ el.style.backgroundRepeat = "no-repeat";
1994
+ if (bg.iw > 0) {
1995
+ const fit = Math.min(window.innerWidth / bg.iw, window.innerHeight / bg.ih);
1996
+ const w = bg.iw * fit * bg.zoom;
1997
+ const h = bg.ih * fit * bg.zoom;
1998
+ el.style.backgroundSize = `${w}px ${h}px`;
1999
+ el.style.backgroundPosition = `${bg.x * window.innerWidth - w / 2}px ${bg.y * window.innerHeight - h / 2}px`;
2000
+ } else {
2001
+ el.style.backgroundSize = "contain";
2002
+ el.style.backgroundPosition = "center";
2003
+ }
2004
+ } else if (mode === "fill") {
2005
+ el.style.backgroundRepeat = "no-repeat";
2006
+ el.style.backgroundSize = "cover";
2007
+ el.style.backgroundPosition = "center";
2008
+ } else if (mode === "stretch") {
2009
+ el.style.backgroundRepeat = "no-repeat";
2010
+ el.style.backgroundSize = "100% 100%";
2011
+ el.style.backgroundPosition = "center";
2012
+ } else if (mode === "tile") {
2013
+ el.style.backgroundRepeat = "repeat";
2014
+ el.style.backgroundSize = "auto";
2015
+ el.style.backgroundPosition = "0px 0px";
2016
+ } else {
2017
+ el.style.backgroundRepeat = "no-repeat";
2018
+ el.style.backgroundSize = "contain";
2019
+ el.style.backgroundPosition = "center";
2020
+ imageNatSize(url, (w, h) => {
2021
+ if (l.url !== url || el.style.backgroundImage !== next || rBgMode() !== "center") return;
2022
+ if (w > 0 && h > 0) {
2023
+ el.style.backgroundSize = `${w}px ${h}px`;
2024
+ el.style.backgroundPosition = "center";
2025
+ }
2026
+ });
2027
+ }
2028
+ }
2029
+ /** Whether a switch should animate: nothing to fade from, an invisible
2030
+ * wallpaper and a reduced-motion preference all switch instantly. */
2031
+ function canFade(from) {
2032
+ if (from.url === null) return false;
2033
+ if (rWop() <= .01) return false;
2034
+ const mm = typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)") : null;
2035
+ return mm === null || !mm.matches;
2036
+ }
2037
+ /** End an in-flight fade: promote the incoming layer, free the one under it. */
2038
+ function finishFade(idx) {
2039
+ if (layers === null || pending !== idx) return;
2040
+ if (fadeTimer !== null) {
2041
+ window.clearTimeout(fadeTimer);
2042
+ fadeTimer = null;
2043
+ }
2044
+ pending = null;
2045
+ front = idx;
2046
+ const el = layers[idx].el;
2047
+ el.style.opacity = "1";
2048
+ el.style.willChange = "";
2049
+ clearLayer(layers[idx === 0 ? 1 : 0]);
2050
+ }
2051
+ /** Land an in-flight fade on its end state at once (another switch arrived). */
2052
+ function settleFade() {
2053
+ if (layers === null || pending === null) return;
2054
+ const idx = pending;
2055
+ if (fadeTimer !== null) {
2056
+ window.clearTimeout(fadeTimer);
2057
+ fadeTimer = null;
2058
+ }
2059
+ pending = null;
2060
+ front = idx;
2061
+ const el = layers[idx].el;
2062
+ el.style.transition = "none";
2063
+ el.style.opacity = "1";
2064
+ el.style.willChange = "";
2065
+ clearLayer(layers[idx === 0 ? 1 : 0]);
2066
+ }
2067
+ /** Abandon an in-flight fade: the layer it was leaving holds the wanted image. */
2068
+ function cancelFade() {
2069
+ if (layers === null || pending === null) return;
2070
+ const idx = pending;
2071
+ if (fadeTimer !== null) {
2072
+ window.clearTimeout(fadeTimer);
2073
+ fadeTimer = null;
2074
+ }
2075
+ pending = null;
2076
+ const el = layers[idx].el;
2077
+ el.style.transition = "none";
2078
+ el.style.opacity = "0";
2079
+ el.style.willChange = "";
2080
+ clearLayer(layers[idx]);
2081
+ }
2082
+ /** Show `url` on the wallpaper layer, cross-fading from what it paints now. */
2083
+ function applyImageWp(url) {
2084
+ ensureWpContainer();
2085
+ const ls = layers;
2086
+ if (pending !== null) {
2087
+ if (ls[pending].url === url) {
2088
+ paintLayer(ls[pending], url);
2089
+ applyWpEffects();
2090
+ return;
2091
+ }
2092
+ if (ls[front].url === url) {
2093
+ cancelFade();
2094
+ paintLayer(ls[front], url);
2095
+ applyWpEffects();
2096
+ return;
2097
+ }
2098
+ settleFade();
2099
+ }
2100
+ if (ls[front].url === url) {
2101
+ paintLayer(ls[front], url);
2102
+ applyWpEffects();
2103
+ return;
2104
+ }
2105
+ const from = ls[front];
2106
+ const idx = front === 0 ? 1 : 0;
2107
+ const target = ls[idx];
2108
+ const el = target.el;
2109
+ el.style.transition = "none";
2110
+ el.style.opacity = "0";
2111
+ el.style.zIndex = "1";
2112
+ from.el.style.zIndex = "0";
2113
+ paintLayer(target, url);
2114
+ applyWpEffects();
2115
+ if (!canFade(from)) {
2116
+ el.style.opacity = "1";
2117
+ front = idx;
2118
+ clearLayer(from);
2119
+ return;
2120
+ }
2121
+ el.style.willChange = "opacity";
2122
+ pending = idx;
2123
+ requestAnimationFrame(() => {
2124
+ if (pending !== idx || layers === null) return;
2125
+ el.style.transition = `opacity ${FADE_MS}ms ease`;
2126
+ el.style.opacity = "1";
2127
+ });
2128
+ fadeTimer = window.setTimeout(() => finishFade(idx), 480);
2129
+ }
2130
+ function applyWpEffects() {
2131
+ if (wpEl === null || layers === null) return;
2132
+ wpEl.style.opacity = String(rWop());
2133
+ const blur = rBl();
2134
+ const filter = blur > 0 ? `blur(${blur}px)` : "";
2135
+ layers[0].el.style.filter = filter;
2136
+ layers[1].el.style.filter = filter;
2137
+ }
2138
+ /** Repaint the wallpaper layer, the token palette and every interface part from
2139
+ * the ACTIVE rule. This is the single entry point a rule switch goes through. */
2140
+ function applyWp() {
2141
+ const url = rWp();
2142
+ if (url) applyImageWp(url);
2143
+ else if (wpEl !== null) dropWpContainer();
2144
+ if (rHasColor()) {
2145
+ applyCustomTokens(rOps());
2146
+ applySettingsOverrides(rSop());
2147
+ applyTrajectoryOverrides(rTrajectoryOpacity());
2148
+ } else {
2149
+ clearCustomTokens();
2150
+ document.body.removeAttribute("data-ds-dark-theme");
2151
+ baseTokenKey = "";
2152
+ lastBgKey = "";
2153
+ }
2154
+ applyRightbarOverrides(rRightbarOpacity());
2155
+ applyPartBlurs(rBlurs());
2156
+ }
2157
+ function teardownWp() {
2158
+ dropWpContainer();
2159
+ clearCustomTokens();
2160
+ tokenStyleEl?.remove();
2161
+ tokenStyleEl = null;
2162
+ removeViewCards();
2163
+ document.body.removeAttribute("data-ds-dark-theme");
2164
+ document.body.style.removeProperty("color-scheme");
2165
+ document.documentElement.style.removeProperty("--dsh-any-bg-settings-surface");
2166
+ document.documentElement.style.removeProperty("--dsh-any-bg-settings-layer-1");
2167
+ document.documentElement.style.removeProperty("--dsh-any-bg-settings-layer-2");
2168
+ document.documentElement.style.removeProperty("--dsh-any-bg-settings-layer-3");
2169
+ document.documentElement.style.removeProperty("--dsh-any-traj-layer-1");
2170
+ document.documentElement.style.removeProperty("--dsh-any-traj-layer-2");
2171
+ document.documentElement.style.removeProperty("--dsh-any-traj-layer-3");
2172
+ document.documentElement.style.removeProperty("--dsh-any-blur-settings");
2173
+ document.documentElement.style.removeProperty("--dsh-any-blur-card-panels");
2174
+ document.documentElement.style.removeProperty("--dsh-any-input-blur");
2175
+ document.documentElement.style.removeProperty("--dsh-any-bg-rightbar");
2176
+ document.documentElement.style.removeProperty("--dsh-any-blur-rightbar");
2177
+ for (const v of Object.values(OPACITY_VARS)) document.documentElement.style.removeProperty(v);
2178
+ baseTokenKey = "";
2179
+ lastBgKey = "";
2180
+ if (tokensRaf !== null) {
2181
+ cancelAnimationFrame(tokensRaf);
2182
+ tokensRaf = null;
2183
+ }
2184
+ pendingOps = null;
2185
+ tableFixStyleEl?.remove();
2186
+ tableFixStyleEl = null;
2187
+ setBlur(frameEl, 0);
2188
+ setBlur(sidebarEl, 0);
2189
+ setBlur(centerEl, 0);
2190
+ setBlur(detailsEl, 0);
2191
+ if (frameEl !== null) frameEl.style.removeProperty("background");
2192
+ if (centerEl !== null) centerEl.style.removeProperty("background");
2193
+ if (detailsEl !== null) detailsEl.style.removeProperty("background");
2194
+ stopWatchingParts();
2195
+ }
2196
+ //#endregion
2197
+ //#region src/client/modelbg.ts
2198
+ /** Rules that can actually paint: enabled and carrying an image slot. */
2199
+ function usable(rules) {
2200
+ return rules.filter((r) => r.enabled && r.slot !== "");
2201
+ }
2202
+ /**
2203
+ * Resolve the rule for one model text.
2204
+ *
2205
+ * Top→bottom, first case-insensitive substring hit wins; an empty `match` never
2206
+ * hits on its own. When nothing hits, the FIRST usable rule is the fallback
2207
+ * (rule 1 deliberately doubles as both a matcher and the fallback).
2208
+ */
2209
+ function matchRule(rules, modelText) {
2210
+ const list = usable(rules);
2211
+ if (list.length === 0) return {
2212
+ rule: null,
2213
+ matched: false
2214
+ };
2215
+ const hay = modelText.toLowerCase();
2216
+ if (hay.trim() !== "") for (const rule of list) {
2217
+ const needle = rule.match.trim().toLowerCase();
2218
+ if (needle === "") continue;
2219
+ if (hay.includes(needle)) return {
2220
+ rule,
2221
+ matched: true
2222
+ };
2223
+ }
2224
+ return {
2225
+ rule: list[0],
2226
+ matched: false
2227
+ };
2228
+ }
2229
+ /** Look the current model's display name up in the shared catalog. */
2230
+ function displayNameOf(state, provider, model) {
2231
+ if (model === "" || !Array.isArray(state?.groups)) return "";
2232
+ for (const group of state.groups) {
2233
+ if (group.id !== provider) continue;
2234
+ for (const entry of group.models ?? []) if (entry.id === model) return typeof entry.name === "string" ? entry.name : "";
2235
+ }
2236
+ return "";
2237
+ }
2238
+ /** How often to look for the sessions service that mounts after this plugin. */
2239
+ const ATTACH_RETRY_MS = 400;
2240
+ /** Stop looking after this many tries (~2 minutes). */
2241
+ const ATTACH_RETRY_LIMIT = 300;
2242
+ /** Safety re-read of every source, so a missed notification cannot pin a stale model. */
2243
+ const POLL_MS = 1500;
2244
+ /** One selection out of a `modelSelection` projection value. */
2245
+ function selectionOf(value) {
2246
+ const pick = value?.next ?? value?.lastUsed ?? null;
2247
+ if (pick === null || typeof pick !== "object") return null;
2248
+ const provider = typeof pick.provider === "string" ? pick.provider : "";
2249
+ const model = typeof pick.model === "string" ? pick.model : "";
2250
+ if (provider === "" && model === "") return null;
2251
+ return {
2252
+ provider,
2253
+ model
2254
+ };
2255
+ }
2256
+ /** A non-empty string id, or null. */
2257
+ function asId(value) {
2258
+ return typeof value === "string" && value !== "" ? value : null;
2259
+ }
2260
+ /**
2261
+ * The id of the session the main view is showing.
2262
+ *
2263
+ * The model is read per session, so this hop has to answer before any of the
2264
+ * others can. 0.1.7 moved the answer: `sessions.list` stopped publishing a single
2265
+ * `current` id in favour of a row table (`ids` / `byId` / `phase`), and the main
2266
+ * view's session is now whatever holds a `mainView` retention — the same row the
2267
+ * host's own `uiSession.current` binding source is built from. Reading only
2268
+ * `list.current` therefore finds nothing on 0.1.7, and a plugin with no session
2269
+ * id can only ever fall back to rule 1: the background stops following the model.
2270
+ *
2271
+ * The hops are tried in order of authority and every one is optional, so this
2272
+ * answers on both host generations — and while `uiSession` is still mounting.
2273
+ */
2274
+ function resolveMainSessionId(uiCurrent, snapshot) {
2275
+ const fromUiSession = asId(uiCurrent?.key);
2276
+ if (fromUiSession !== null) return fromUiSession;
2277
+ const legacy = asId(snapshot?.current);
2278
+ if (legacy !== null) return legacy;
2279
+ const byId = snapshot?.byId;
2280
+ if (byId !== void 0 && byId !== null) {
2281
+ for (const row of Object.values(byId)) if ((row?.retainedBy?.mainView ?? 0) > 0) {
2282
+ const id = asId(row?.id);
2283
+ if (id !== null) return id;
2284
+ }
2285
+ }
2286
+ return null;
2287
+ }
2288
+ /** Read `uiSession.current` (if that service is on this host) and resolve. */
2289
+ function mainSessionId(ctx, list) {
2290
+ let uiCurrent;
2291
+ try {
2292
+ uiCurrent = ctx.get("uiSession")?.current?.getSnapshot?.();
2293
+ } catch {}
2294
+ return resolveMainSessionId(uiCurrent, list?.getSnapshot());
2295
+ }
2296
+ /**
2297
+ * Watch the current session's model selection.
2298
+ *
2299
+ * @param ctx - client root context.
2300
+ * @param onChange - called with the match text (provider + id + display name),
2301
+ * the best available label, which source answered, and — when nothing could be
2302
+ * read — the hop that failed.
2303
+ * @returns disposer.
2304
+ */
2305
+ function watchModel(ctx, onChange) {
2306
+ let service = null;
2307
+ let list = null;
2308
+ let offList = null;
2309
+ let sessionId = null;
2310
+ let directory = null;
2311
+ let offDirectory = null;
2312
+ /** The session's own `modelSelection` projection face. */
2313
+ let projected = null;
2314
+ let offProjected = null;
2315
+ let lastKey = "\0";
2316
+ let poll = null;
2317
+ let retry = null;
2318
+ let retries = 0;
2319
+ const release = () => {
2320
+ if (offDirectory !== null) {
2321
+ offDirectory();
2322
+ offDirectory = null;
2323
+ }
2324
+ if (offProjected !== null) {
2325
+ offProjected();
2326
+ offProjected = null;
2327
+ }
2328
+ directory = null;
2329
+ projected = null;
2330
+ };
2331
+ /** Bind the session's own durable model selection (the authoritative source). */
2332
+ const bindProjection = (id) => {
2333
+ const svc = service;
2334
+ if (svc === null) return;
2335
+ try {
2336
+ let binding = typeof svc.binding === "function" ? svc.binding(id) : void 0;
2337
+ if (binding === void 0 && typeof svc.scope === "function") {
2338
+ svc.scope(id);
2339
+ binding = typeof svc.binding === "function" ? svc.binding(id) : void 0;
2340
+ }
2341
+ const face = binding?.session?.projections?.faceOf?.("modelSelection");
2342
+ if (face === void 0 || typeof face.subscribe !== "function" || typeof face.getSnapshot !== "function") return;
2343
+ projected = face;
2344
+ offProjected = face.subscribe(() => {
2345
+ emit(false);
2346
+ });
2347
+ } catch {
2348
+ projected = null;
2349
+ offProjected = null;
2350
+ }
2351
+ };
2352
+ const note = () => {
2353
+ if (service === null) return "no-service";
2354
+ if (sessionId === null) return "no-session";
2355
+ if (projected === null && directory === null) return "no-projection";
2356
+ return "empty-selection";
2357
+ };
2358
+ const emit = (force) => {
2359
+ const state = directory?.store.getSnapshot();
2360
+ const fromDirectory = state?.current ?? null;
2361
+ const sel = fromDirectory !== null ? {
2362
+ provider: fromDirectory.provider ?? "",
2363
+ model: fromDirectory.model ?? ""
2364
+ } : (() => {
2365
+ try {
2366
+ return selectionOf(projected?.getSnapshot());
2367
+ } catch {
2368
+ return null;
2369
+ }
2370
+ })();
2371
+ const provider = sel?.provider ?? "";
2372
+ const model = sel?.model ?? "";
2373
+ const name = displayNameOf(state, provider, model);
2374
+ const text = [
2375
+ provider,
2376
+ model,
2377
+ name
2378
+ ].filter((p) => p !== "").join(" ").trim();
2379
+ const noteValue = text === "" ? note() : "";
2380
+ const key = `${text}\u0001${name}\u0001${noteValue}`;
2381
+ if (!force && key === lastKey) return;
2382
+ lastKey = key;
2383
+ onChange(text, name !== "" ? name : model !== "" ? model : provider, "session", noteValue);
2384
+ };
2385
+ const bind = () => {
2386
+ const id = mainSessionId(ctx, list);
2387
+ if (id === sessionId && (directory !== null || projected !== null)) {
2388
+ emit(false);
2389
+ return;
2390
+ }
2391
+ release();
2392
+ sessionId = id;
2393
+ if (id !== null) {
2394
+ bindProjection(id);
2395
+ if (directory === null) {
2396
+ const directories = ctx.get("modelDirectories");
2397
+ if (directories !== void 0 && typeof directories.directoryFor === "function") try {
2398
+ directory = directories.directoryFor(id);
2399
+ offDirectory = directory.store.subscribe(() => {
2400
+ emit(false);
2401
+ });
2402
+ const pending = directory.load?.();
2403
+ if (pending !== void 0 && pending !== null && typeof pending.catch === "function") pending.catch(() => void 0);
2404
+ } catch {
2405
+ directory = null;
2406
+ offDirectory = null;
2407
+ }
2408
+ }
2409
+ }
2410
+ emit(true);
2411
+ };
2412
+ const stopRetry = () => {
2413
+ if (retry === null) return;
2414
+ window.clearInterval(retry);
2415
+ retry = null;
2416
+ };
2417
+ const tick = () => {
2418
+ if (list === null) return;
2419
+ if (mainSessionId(ctx, list) !== sessionId || directory === null && projected === null) bind();
2420
+ else emit(false);
2421
+ };
2422
+ /**
2423
+ * Look the sessions service up. It arrives after this plugin's bundle on this
2424
+ * deployment, so the first attempt usually fails — which is exactly why this
2425
+ * is a retry loop rather than a one-shot lookup.
2426
+ */
2427
+ const attach = () => {
2428
+ const svc = ctx.get("sessions");
2429
+ if (svc === void 0 || svc === null) return false;
2430
+ const face = svc.list;
2431
+ if (face === void 0 || typeof face.subscribe !== "function" || typeof face.getSnapshot !== "function") return false;
2432
+ service = svc;
2433
+ list = face;
2434
+ offList = face.subscribe(bind);
2435
+ if (poll === null) poll = window.setInterval(tick, POLL_MS);
2436
+ bind();
2437
+ return true;
2438
+ };
2439
+ if (!attach()) retry = window.setInterval(() => {
2440
+ retries++;
2441
+ if (attach() || retries >= ATTACH_RETRY_LIMIT) stopRetry();
2442
+ }, ATTACH_RETRY_MS);
2443
+ return () => {
2444
+ stopRetry();
2445
+ if (poll !== null) {
2446
+ window.clearInterval(poll);
2447
+ poll = null;
2448
+ }
2449
+ if (offList !== null) {
2450
+ offList();
2451
+ offList = null;
2452
+ }
2453
+ service = null;
2454
+ list = null;
2455
+ release();
2456
+ };
2457
+ }
2458
+ //#endregion
2459
+ //#region src/host-contracts.ts
2460
+ const SIDEBAR_RIGHT = "dsh-client-ui-sidebar-right";
2461
+ /**
2462
+ * The contract table.
2463
+ *
2464
+ * Ordered by group and then by how visible a break is: the model hop first (the
2465
+ * plugin's whole purpose), then the surfaces it paints, then the tokens it
2466
+ * re-emits, then the regions it decorates.
2467
+ */
2468
+ const HOST_CONTRACTS = [
2469
+ {
2470
+ id: "sessions.list",
2471
+ group: "model",
2472
+ kind: "host-service",
2473
+ criticality: "core",
2474
+ since: "0.1.7",
2475
+ label: {
2476
+ en: "Session list service",
2477
+ zh: "会话列表服务"
2478
+ },
2479
+ symptom: {
2480
+ en: "no session id can be read, so the background is stuck on rule 1 and never follows the model",
2481
+ zh: "读不到会话 id,背景会一直停在规则 1,不再跟着模型切换"
2482
+ },
2483
+ target: "sessions.list",
2484
+ checks: [
2485
+ {
2486
+ kind: "service",
2487
+ id: "sessions"
2488
+ },
2489
+ {
2490
+ kind: "path",
2491
+ service: "sessions",
2492
+ path: "list",
2493
+ expect: "observable"
2494
+ },
2495
+ {
2496
+ kind: "path",
2497
+ service: "sessions",
2498
+ path: "list.getSnapshot",
2499
+ expect: "function"
2500
+ }
2501
+ ],
2502
+ sources: [{
2503
+ path: "dsh-api-session-controller/lib/client.js",
2504
+ literal: "retainedBy"
2505
+ }],
2506
+ usedBy: "src/client/modelbg.ts"
2507
+ },
2508
+ {
2509
+ id: "sessions.list.current",
2510
+ group: "model",
2511
+ kind: "host-service",
2512
+ criticality: "label",
2513
+ since: "<=0.1.6",
2514
+ label: {
2515
+ en: "Session list: legacy current id",
2516
+ zh: "会话列表:旧版 current 字段"
2517
+ },
2518
+ symptom: {
2519
+ en: "harmless on its own — the 0.1.7 hops below take over",
2520
+ zh: "本身无害 —— 0.1.7 上由下面的兜底路径接管"
2521
+ },
2522
+ target: "list.current",
2523
+ checks: [],
2524
+ sources: [{
2525
+ path: "dsh-api-session-controller/lib/client.js",
2526
+ literal: "retainedBy"
2527
+ }],
2528
+ usedBy: "src/client/modelbg.ts"
2529
+ },
2530
+ {
2531
+ id: "sessions.retainedBy.mainView",
2532
+ group: "model",
2533
+ kind: "host-service",
2534
+ criticality: "label",
2535
+ since: "0.1.7",
2536
+ label: {
2537
+ en: "Main-view retention on the session row",
2538
+ zh: "会话行的主视图占用标记"
2539
+ },
2540
+ symptom: {
2541
+ en: "while uiSession is not mounted yet, no session id can be derived",
2542
+ zh: "uiSession 尚未挂载时推导不出会话 id"
2543
+ },
2544
+ target: "byId[].retainedBy.mainView",
2545
+ checks: [],
2546
+ sources: [{
2547
+ path: "dsh-api-session-controller/lib/client.js",
2548
+ literal: "retainedBy"
2549
+ }],
2550
+ usedBy: "src/client/modelbg.ts"
2551
+ },
2552
+ {
2553
+ id: "uiSession.current",
2554
+ group: "model",
2555
+ kind: "host-service",
2556
+ criticality: "core",
2557
+ since: "0.1.7",
2558
+ label: {
2559
+ en: "uiSession service (main view session)",
2560
+ zh: "uiSession 服务(主视图会话)"
2561
+ },
2562
+ symptom: {
2563
+ en: "the session id can only be guessed from the list retention",
2564
+ zh: "会话 id 只能退回用列表占用标记去猜"
2565
+ },
2566
+ target: "uiSession.current",
2567
+ checks: [{
2568
+ kind: "path",
2569
+ service: "uiSession",
2570
+ path: "current",
2571
+ expect: "observable"
2572
+ }],
2573
+ sources: [{
2574
+ path: "dsh-client-ui-session/lib/client.js",
2575
+ literal: "uiSession"
2576
+ }],
2577
+ usedBy: "src/client/modelbg.ts"
2578
+ },
2579
+ {
2580
+ id: "sessions.binding.projections",
2581
+ group: "model",
2582
+ kind: "host-service",
2583
+ criticality: "core",
2584
+ since: "<=0.1.6",
2585
+ label: {
2586
+ en: "Session binding + projections",
2587
+ zh: "会话 binding 与 projections"
2588
+ },
2589
+ symptom: {
2590
+ en: "this session's own model selection is unreadable — only the host default is left",
2591
+ zh: "读不到本会话自己的模型选择,只剩宿主默认模型可用"
2592
+ },
2593
+ target: "binding(id).session.projections.faceOf(\"modelSelection\")",
2594
+ checks: [{
2595
+ kind: "path",
2596
+ service: "sessions",
2597
+ path: "binding",
2598
+ expect: "function"
2599
+ }],
2600
+ sources: [{
2601
+ path: "dsh-api-session-controller/lib/client.js",
2602
+ literal: "faceOf"
2603
+ }],
2604
+ usedBy: "src/client/modelbg.ts"
2605
+ },
2606
+ {
2607
+ id: "modelDirectories.directoryFor",
2608
+ group: "model",
2609
+ kind: "host-service",
2610
+ criticality: "label",
2611
+ since: "<=0.1.6",
2612
+ label: {
2613
+ en: "Model directory (display names)",
2614
+ zh: "模型目录(显示名)"
2615
+ },
2616
+ symptom: {
2617
+ en: "the model is shown as provider/model id instead of its display name",
2618
+ zh: "模型只会显示 provider/模型 id,读不到显示名"
2619
+ },
2620
+ target: "modelDirectories.directoryFor(id)",
2621
+ checks: [{
2622
+ kind: "path",
2623
+ service: "modelDirectories",
2624
+ path: "directoryFor",
2625
+ expect: "function"
2626
+ }],
2627
+ sources: [{
2628
+ path: "dsh-client-ui-model-selection/lib/client.js",
2629
+ literal: "modelSelection"
2630
+ }],
2631
+ usedBy: "src/client/modelbg.ts"
2632
+ },
2633
+ {
2634
+ id: "shell.overlay",
2635
+ group: "surface",
2636
+ kind: "host-attr",
2637
+ criticality: "looks",
2638
+ since: "<=0.1.6",
2639
+ label: {
2640
+ en: "AppFrame overlay + its three columns",
2641
+ zh: "主框架浮层与三列"
2642
+ },
2643
+ symptom: {
2644
+ en: "per-part blur and the main-background opacity cannot find the columns at all",
2645
+ zh: "分区域模糊与主背景透明度完全找不到那几列"
2646
+ },
2647
+ target: "[data-shell-overlay]",
2648
+ checks: [{
2649
+ kind: "attr",
2650
+ attr: "data-shell-overlay"
2651
+ }],
2652
+ sources: [{
2653
+ path: "dsh-client-ui-layout/lib/client.js",
2654
+ literal: "data-shell-overlay"
2655
+ }],
2656
+ usedBy: "src/client/wallpaper.ts"
2657
+ },
2658
+ {
2659
+ id: "settings.dialog",
2660
+ group: "surface",
2661
+ kind: "dom-selector",
2662
+ criticality: "looks",
2663
+ since: "<=0.1.6",
2664
+ label: {
2665
+ en: "Settings dialog (aria-modal)",
2666
+ zh: "设置弹窗(aria-modal)"
2667
+ },
2668
+ symptom: {
2669
+ en: "the settings panel opacity and blur sliders stop having any effect",
2670
+ zh: "设置界面透明度与模糊失效"
2671
+ },
2672
+ target: "[role=\"dialog\"][aria-modal=\"true\"][aria-labelledby]",
2673
+ checks: [{
2674
+ kind: "selector",
2675
+ selector: "div[role=\"dialog\"][aria-modal=\"true\"][aria-labelledby]",
2676
+ optionalWhen: "settingsClosed"
2677
+ }],
2678
+ sources: [{
2679
+ path: "dsh-client-ui-primitives/lib/index.js",
2680
+ literal: "aria-modal"
2681
+ }, {
2682
+ path: "src/client/components/ui.css.ts",
2683
+ literal: ".dab-card"
2684
+ }],
2685
+ usedBy: "src/client/wallpaper.ts"
2686
+ },
2687
+ {
2688
+ id: "settings.card",
2689
+ group: "surface",
2690
+ kind: "host-style",
2691
+ criticality: "looks",
2692
+ since: "<=0.1.6",
2693
+ label: {
2694
+ en: "Option card inside the dialog (.dab-card)",
2695
+ zh: "弹窗内的选项卡(.dab-card)"
2696
+ },
2697
+ symptom: {
2698
+ en: "the dialog option-panel blur has nothing to attach to",
2699
+ zh: "弹窗内选项卡的模糊没有附着对象"
2700
+ },
2701
+ target: ".dab-card",
2702
+ checks: [{
2703
+ kind: "rule",
2704
+ match: ".dab-card",
2705
+ side: "own",
2706
+ hint: "settings dialog card class"
2707
+ }],
2708
+ sources: [{
2709
+ path: "src/client/components/ui.css.ts",
2710
+ literal: ".dab-card"
2711
+ }],
2712
+ usedBy: "src/client/wallpaper.ts"
2713
+ },
2714
+ {
2715
+ id: "composer.card",
2716
+ group: "surface",
2717
+ kind: "host-attr",
2718
+ criticality: "looks",
2719
+ since: "<=0.1.6",
2720
+ label: {
2721
+ en: "Composer card",
2722
+ zh: "输入框卡片"
2723
+ },
2724
+ symptom: {
2725
+ en: "the composer loses its backdrop — the input blur slider does nothing",
2726
+ zh: "输入框失去毛玻璃背板,输入区模糊滑块失效"
2727
+ },
2728
+ target: "[data-composer-card]",
2729
+ checks: [{
2730
+ kind: "attr",
2731
+ attr: "data-composer-card",
2732
+ optionalWhen: "chatNotMounted"
2733
+ }],
2734
+ sources: [{
2735
+ path: "dsh-client-ui-conversation/lib/client.js",
2736
+ literal: "data-composer-card"
2737
+ }],
2738
+ usedBy: "src/client/wallpaper.ts"
2739
+ },
2740
+ {
2741
+ id: "cordis.panel",
2742
+ group: "surface",
2743
+ kind: "host-attr",
2744
+ criticality: "label",
2745
+ since: "<=0.1.6",
2746
+ label: {
2747
+ en: "Cordis panel",
2748
+ zh: "Cordis 面板"
2749
+ },
2750
+ symptom: {
2751
+ en: "the Cordis panel keeps the host surface instead of the input slider's alpha",
2752
+ zh: "Cordis 面板拿不到输入区滑块的透明度,仍是宿主表面"
2753
+ },
2754
+ target: "[data-cordis-panel]",
2755
+ checks: [{
2756
+ kind: "attr",
2757
+ attr: "data-cordis-panel",
2758
+ optionalWhen: "cordisClosed"
2759
+ }],
2760
+ sources: [{
2761
+ path: "dsh-client-ui-cordis/lib/client.js",
2762
+ literal: "data-cordis-panel"
2763
+ }],
2764
+ usedBy: "src/client/wallpaper.ts"
2765
+ },
2766
+ {
2767
+ id: "rightbar.panel",
2768
+ group: "surface",
2769
+ kind: "host-attr",
2770
+ criticality: "looks",
2771
+ since: "0.1.7",
2772
+ label: {
2773
+ en: "File-preview panel host",
2774
+ zh: "文件预览面板宿主"
2775
+ },
2776
+ symptom: {
2777
+ en: "the right panel loses its own opacity/blur card and falls back to the main background",
2778
+ zh: "右侧栏失去自己的透明度/模糊控制,退回跟随主背景"
2779
+ },
2780
+ target: "[data-sidebar-right-panel]",
2781
+ checks: [{
2782
+ kind: "selector",
2783
+ selector: "div[data-sidebar-right-panel]",
2784
+ optionalWhen: "noRightPanel"
2785
+ }],
2786
+ sources: [{
2787
+ path: `${SIDEBAR_RIGHT}/lib/client.js`,
2788
+ literal: "data-sidebar-right-panel"
2789
+ }],
2790
+ usedBy: "src/client/wallpaper.ts"
2791
+ },
2792
+ {
2793
+ id: "rightbar.openFlag",
2794
+ group: "surface",
2795
+ kind: "host-attr",
2796
+ criticality: "looks",
2797
+ since: "0.1.7",
2798
+ label: {
2799
+ en: "Right panel open/closed flag",
2800
+ zh: "右侧栏开合标记"
2801
+ },
2802
+ symptom: {
2803
+ en: "the panel's backdrop cannot tell open from closed — a frosted plate covers the empty half",
2804
+ zh: "分不清右侧栏开着还是关着,空白的右半屏会出现磨砂板"
2805
+ },
2806
+ target: "[data-sidebar-right-open]",
2807
+ checks: [{
2808
+ kind: "rule",
2809
+ match: "[data-sidebar-right-open]",
2810
+ hint: "right panel open flag"
2811
+ }],
2812
+ sources: [{
2813
+ path: `${SIDEBAR_RIGHT}/lib/client.js`,
2814
+ literal: "data-sidebar-right-open"
2815
+ }],
2816
+ usedBy: "src/client/wallpaper.ts"
2817
+ },
2818
+ {
2819
+ id: "dockkit.content",
2820
+ group: "surface",
2821
+ kind: "host-attr",
2822
+ criticality: "core",
2823
+ since: "0.1.7",
2824
+ label: {
2825
+ en: "Dock tab host (the panel's painted surface)",
2826
+ zh: "停靠页宿主(面板真正的表面)"
2827
+ },
2828
+ symptom: {
2829
+ en: "nothing carries the panel surface any more — the right-panel card is painted on a container that is always full width",
2830
+ zh: "面板表面不再落在任何元素上,右面板卡片会被画在一个恒定满宽的容器上"
2831
+ },
2832
+ target: "[data-dockkit-content]",
2833
+ checks: [{
2834
+ kind: "selector",
2835
+ selector: "[data-dockkit-content]",
2836
+ optionalWhen: "noDockTab"
2837
+ }],
2838
+ sources: [{
2839
+ path: ".",
2840
+ literal: "dockkit-content",
2841
+ root: "frontend"
2842
+ }],
2843
+ usedBy: "src/client/wallpaper.ts"
2844
+ },
2845
+ {
2846
+ id: "dockkit.empty",
2847
+ group: "surface",
2848
+ kind: "host-attr",
2849
+ criticality: "looks",
2850
+ since: "0.1.7",
2851
+ label: {
2852
+ en: "Dock empty seat",
2853
+ zh: "停靠空位"
2854
+ },
2855
+ symptom: {
2856
+ en: "an empty preview pane paints the host surface over the wallpaper",
2857
+ zh: "空的预览格会用宿主表面盖住壁纸"
2858
+ },
2859
+ target: "[data-dockkit-empty]",
2860
+ checks: [{
2861
+ kind: "selector",
2862
+ selector: "[data-dockkit-empty]",
2863
+ optionalWhen: "noDockTab"
2864
+ }],
2865
+ sources: [{
2866
+ path: `${SIDEBAR_RIGHT}/lib/client.js`,
2867
+ literal: "data-dockkit-empty"
2868
+ }],
2869
+ usedBy: "src/client/wallpaper.ts"
2870
+ },
2871
+ {
2872
+ id: "dockkit.float",
2873
+ group: "surface",
2874
+ kind: "host-attr",
2875
+ criticality: "label",
2876
+ since: "0.1.7",
2877
+ label: {
2878
+ en: "Floating dock pane flag",
2879
+ zh: "浮动停靠面板标记"
2880
+ },
2881
+ symptom: {
2882
+ en: "floating panes would be painted like docked ones, frosting a panel that should stay the host's",
2883
+ zh: "浮动面板会被当作停靠面板上色,本该保持宿主样式的面板被磨砂"
2884
+ },
2885
+ target: "[data-dockkit-float]",
2886
+ checks: [{
2887
+ kind: "selector",
2888
+ selector: "[data-dockkit-float]",
2889
+ optionalWhen: "noFloatPane"
2890
+ }],
2891
+ sources: [{
2892
+ path: `${SIDEBAR_RIGHT}/lib/client.js`,
2893
+ literal: "data-dockkit-float"
2894
+ }],
2895
+ usedBy: "src/client/wallpaper.ts"
2896
+ },
2897
+ {
2898
+ id: "chat.flow",
2899
+ group: "region",
2900
+ kind: "dom-selector",
2901
+ criticality: "looks",
2902
+ since: "<=0.1.6",
2903
+ label: {
2904
+ en: "Chat message column",
2905
+ zh: "对话消息列"
2906
+ },
2907
+ symptom: {
2908
+ en: "the conversation card (and its opacity/blur) cannot find the message column",
2909
+ zh: "对话卡片(及其透明度/模糊)找不到消息列"
2910
+ },
2911
+ target: "[data-chat-flow]",
2912
+ checks: [{
2913
+ kind: "selector",
2914
+ selector: "[data-chat-flow]",
2915
+ optionalWhen: "chatNotMounted"
2916
+ }],
2917
+ sources: [{
2918
+ path: "dsh-client-ui-chat/lib/client.js",
2919
+ literal: "data-chat-flow"
2920
+ }],
2921
+ usedBy: "src/client/wallpaper.ts"
2922
+ },
2923
+ {
2924
+ id: "conversation.scroll",
2925
+ group: "region",
2926
+ kind: "dom-selector",
2927
+ criticality: "looks",
2928
+ since: "<=0.1.6",
2929
+ label: {
2930
+ en: "Conversation scrollport",
2931
+ zh: "对话滚动区"
2932
+ },
2933
+ symptom: {
2934
+ en: "the marker-less fallback would wrap the whole scrollport in the conversation card",
2935
+ zh: "标记缺失时的兜底会把整个滚动区套上对话卡片"
2936
+ },
2937
+ target: "[data-conversation-scroll]",
2938
+ checks: [{
2939
+ kind: "selector",
2940
+ selector: "[data-conversation-scroll]",
2941
+ optionalWhen: "chatNotMounted"
2942
+ }],
2943
+ sources: [{
2944
+ path: "dsh-client-ui-conversation/lib/client.js",
2945
+ literal: "data-conversation-scroll"
2946
+ }],
2947
+ usedBy: "src/client/wallpaper.ts"
2948
+ },
2949
+ {
2950
+ id: "trajectory.root",
2951
+ group: "region",
2952
+ kind: "dom-selector",
2953
+ criticality: "looks",
2954
+ since: "<=0.1.6",
2955
+ label: {
2956
+ en: "Trajectory view root",
2957
+ zh: "轨迹页根节点"
2958
+ },
2959
+ symptom: {
2960
+ en: "the trajectory opacity/blur sliders stop reaching the trajectory page",
2961
+ zh: "轨迹页的透明度与模糊滑块失效"
2962
+ },
2963
+ target: "[data-conversation-composer-overlay]",
2964
+ checks: [{
2965
+ kind: "selector",
2966
+ selector: "[data-conversation-composer-overlay]",
2967
+ optionalWhen: "trajectoryNotMounted"
2968
+ }],
2969
+ sources: [{
2970
+ path: "dsh-client-ui-trajectory/lib/client.js",
2971
+ literal: "data-conversation-composer-overlay"
2972
+ }],
2973
+ usedBy: "src/client/wallpaper.ts"
2974
+ },
2975
+ {
2976
+ id: "markdown.wideTable",
2977
+ group: "region",
2978
+ kind: "host-style",
2979
+ criticality: "label",
2980
+ since: "<=0.1.6",
2981
+ label: {
2982
+ en: "Wide markdown table clamp",
2983
+ zh: "宽表格收拢样式"
2984
+ },
2985
+ symptom: {
2986
+ en: "a wide table bleeds outside the conversation card instead of scrolling inside it",
2987
+ zh: "宽表格会溢出到对话卡片外,而不是在卡片内横向滚动"
2988
+ },
2989
+ target: ".md-table-wide",
2990
+ checks: [{
2991
+ kind: "rule",
2992
+ match: ".md-table-wide",
2993
+ hint: "markdown table class"
2994
+ }],
2995
+ sources: [{
2996
+ path: "dsh-client-ui-chat/lib/client.js",
2997
+ literal: "md-table-wide"
2998
+ }],
2999
+ usedBy: "src/client/wallpaper.ts"
3000
+ },
3001
+ {
3002
+ id: "token.bgBase",
3003
+ group: "token",
3004
+ kind: "host-token",
3005
+ criticality: "looks",
3006
+ since: "<=0.1.6",
3007
+ label: {
3008
+ en: "Main background surface",
3009
+ zh: "主背景表面"
3010
+ },
3011
+ symptom: {
3012
+ en: "the main-background slider and the right-panel card lose their surface",
3013
+ zh: "主背景透明度与右侧栏卡片失去表面色"
3014
+ },
3015
+ target: "--dsw-alias-bg-base",
3016
+ checks: [{
3017
+ kind: "rule",
3018
+ match: "--dsw-alias-bg-base",
3019
+ hint: "platform palette"
3020
+ }, {
3021
+ kind: "computed",
3022
+ token: "--dsw-alias-bg-base"
3023
+ }],
3024
+ sources: [{
3025
+ path: "dsh-client-ui-theme/lib/client.js",
3026
+ literal: "--dsw-alias-bg-base"
3027
+ }],
3028
+ usedBy: "src/client/wallpaper.ts"
3029
+ },
3030
+ {
3031
+ id: "token.layers",
3032
+ group: "token",
3033
+ kind: "host-token",
3034
+ criticality: "looks",
3035
+ since: "<=0.1.6",
3036
+ label: {
3037
+ en: "Layered surfaces (cards)",
3038
+ zh: "层次表面(卡片)"
3039
+ },
3040
+ symptom: {
3041
+ en: "the card slider, the conversation card and the trajectory slider go blank",
3042
+ zh: "卡片滑块、对话卡片与轨迹滑块失去颜色"
3043
+ },
3044
+ target: "--dsw-alias-bg-layer-1/2/3",
3045
+ checks: [
3046
+ {
3047
+ kind: "rule",
3048
+ match: "--dsw-alias-bg-layer-1",
3049
+ hint: "platform palette"
3050
+ },
3051
+ {
3052
+ kind: "computed",
3053
+ token: "--dsw-alias-bg-layer-1"
3054
+ },
3055
+ {
3056
+ kind: "computed",
3057
+ token: "--dsw-alias-bg-layer-2"
3058
+ },
3059
+ {
3060
+ kind: "computed",
3061
+ token: "--dsw-alias-bg-layer-3"
3062
+ }
3063
+ ],
3064
+ sources: [{
3065
+ path: "dsh-client-ui-theme/lib/client.js",
3066
+ literal: "--dsw-alias-bg-layer-1"
3067
+ }],
3068
+ usedBy: "src/client/wallpaper.ts"
3069
+ },
3070
+ {
3071
+ id: "token.menuSurface",
3072
+ group: "token",
3073
+ kind: "host-token",
3074
+ criticality: "core",
3075
+ since: "0.1.7",
3076
+ label: {
3077
+ en: "Menu material fill (0.1.7 split)",
3078
+ zh: "菜单材质填充(0.1.7 拆分)"
3079
+ },
3080
+ symptom: {
3081
+ en: "the menu panel and its sticky group headings take two different fills — the solid band across the model picker",
3082
+ zh: "菜单面板与吸顶分组标题用了两种填充 —— 模型选择器上那道白道"
3083
+ },
3084
+ target: "--dsw-menu-surface-fill",
3085
+ checks: [{
3086
+ kind: "rule",
3087
+ match: "--dsw-menu-surface-fill",
3088
+ hint: "MenuSurface material fill"
3089
+ }, {
3090
+ kind: "computed",
3091
+ token: "--dsw-menu-surface-fill",
3092
+ on: ".dab-root"
3093
+ }],
3094
+ sources: [{
3095
+ path: "dsh-client-ui-theme/lib/client.js",
3096
+ literal: "--dsw-menu-surface-fill"
3097
+ }, {
3098
+ path: "dsh-client-ui-primitives/lib/MenuSurface.module.css",
3099
+ literal: "var(--dsw-menu-surface-fill)"
3100
+ }],
3101
+ usedBy: "src/client/utils/color.ts, src/client/wallpaper.ts"
3102
+ },
3103
+ {
3104
+ id: "token.menuAlias",
3105
+ group: "token",
3106
+ kind: "host-token",
3107
+ criticality: "looks",
3108
+ since: "<=0.1.6",
3109
+ label: {
3110
+ en: "Menu overlay fill",
3111
+ zh: "菜单浮层填充"
3112
+ },
3113
+ symptom: {
3114
+ en: "dropdowns, the slash menu and popovers keep the host fill while the panel follows the plugin",
3115
+ zh: "下拉、斜杠菜单与浮层保持宿主填充,而面板跟随插件,两边不一致"
3116
+ },
3117
+ target: "--dsw-specific-menu",
3118
+ checks: [{
3119
+ kind: "rule",
3120
+ match: "--dsw-specific-menu",
3121
+ hint: "menu overlay fill"
3122
+ }, {
3123
+ kind: "computed",
3124
+ token: "--dsw-specific-menu",
3125
+ on: ".dab-root"
3126
+ }],
3127
+ sources: [{
3128
+ path: "dsh-client-ui-theme/lib/client.js",
3129
+ literal: "--dsw-specific-menu"
3130
+ }, {
3131
+ path: "dsh-client-ui-chat/lib/client.js",
3132
+ literal: "--dsw-specific-menu"
3133
+ }],
3134
+ usedBy: "src/client/utils/color.ts, src/client/wallpaper.ts"
3135
+ },
3136
+ {
3137
+ id: "token.sidebarFill",
3138
+ group: "token",
3139
+ kind: "host-token",
3140
+ criticality: "looks",
3141
+ since: "<=0.1.6",
3142
+ label: {
3143
+ en: "Sidebar fill",
3144
+ zh: "左侧栏填充"
3145
+ },
3146
+ symptom: {
3147
+ en: "the sidebar opacity slider stops tinting the sidebar",
3148
+ zh: "左侧栏透明度滑块失效"
3149
+ },
3150
+ target: "--dsw-specific-sidebar-fill",
3151
+ checks: [{
3152
+ kind: "rule",
3153
+ match: "--dsw-specific-sidebar-fill",
3154
+ hint: "sidebar fill"
3155
+ }, {
3156
+ kind: "computed",
3157
+ token: "--dsw-specific-sidebar-fill"
3158
+ }],
3159
+ sources: [{
3160
+ path: "dsh-client-ui-theme/lib/client.js",
3161
+ literal: "--dsw-specific-sidebar-fill"
3162
+ }],
3163
+ usedBy: "src/client/utils/color.ts, src/client/wallpaper.ts"
3164
+ },
3165
+ {
3166
+ id: "token.inputFill",
3167
+ group: "token",
3168
+ kind: "host-token",
3169
+ criticality: "looks",
3170
+ since: "<=0.1.6",
3171
+ label: {
3172
+ en: "Input/control surface",
3173
+ zh: "输入与控件表面"
3174
+ },
3175
+ symptom: {
3176
+ en: "the input slider stops tinting the composer and the controls",
3177
+ zh: "输入区透明度滑块失效"
3178
+ },
3179
+ target: "--dsw-specific-input-major",
3180
+ checks: [{
3181
+ kind: "rule",
3182
+ match: "--dsw-specific-input-major",
3183
+ hint: "input/control fill"
3184
+ }, {
3185
+ kind: "computed",
3186
+ token: "--dsw-specific-input-major"
3187
+ }],
3188
+ sources: [{
3189
+ path: "dsh-client-ui-theme/lib/client.js",
3190
+ literal: "--dsw-specific-input-major"
3191
+ }],
3192
+ usedBy: "src/client/utils/color.ts, src/client/wallpaper.ts"
3193
+ },
3194
+ {
3195
+ id: "token.schemeFlag",
3196
+ group: "token",
3197
+ kind: "host-attr",
3198
+ criticality: "core",
3199
+ since: "<=0.1.6",
3200
+ label: {
3201
+ en: "Dark-scheme flag on <body>",
3202
+ zh: "<body> 上的深色标记"
3203
+ },
3204
+ symptom: {
3205
+ en: "a dark rule can no longer force the dark palette — light surfaces flash through",
3206
+ zh: "深色规则无法强制深色调色板,会闪出浅色表面"
3207
+ },
3208
+ target: "body[data-ds-dark-theme]",
3209
+ checks: [{
3210
+ kind: "rule",
3211
+ match: "data-ds-dark-theme",
3212
+ hint: "dark scheme attribute"
3213
+ }],
3214
+ sources: [{
3215
+ path: "dsh-client-ui-theme/lib/client.js",
3216
+ literal: "data-ds-dark-theme"
3217
+ }, {
3218
+ path: ".",
3219
+ literal: "data-ds-dark-theme",
3220
+ root: "frontend"
3221
+ }],
3222
+ usedBy: "src/client/wallpaper.ts"
3223
+ },
3224
+ {
3225
+ id: "token.labelCaption",
3226
+ group: "token",
3227
+ kind: "host-token",
3228
+ criticality: "label",
3229
+ since: "<=0.1.6",
3230
+ label: {
3231
+ en: "Caption label token",
3232
+ zh: "弱化文字令牌"
3233
+ },
3234
+ symptom: {
3235
+ en: "placeholder text falls back to a hard-coded grey instead of the theme's caption colour",
3236
+ zh: "占位文字退回固定灰色,不再跟随主题的弱化色"
3237
+ },
3238
+ target: "--dsw-alias-label-caption",
3239
+ checks: [{
3240
+ kind: "rule",
3241
+ match: "--dsw-alias-label-caption",
3242
+ hint: "label palette"
3243
+ }, {
3244
+ kind: "computed",
3245
+ token: "--dsw-alias-label-caption"
3246
+ }],
3247
+ sources: [{
3248
+ path: "dsh-client-ui-theme/lib/client.js",
3249
+ literal: "--dsw-alias-label-caption"
3250
+ }],
3251
+ usedBy: "src/client/wallpaper.ts"
3252
+ }
3253
+ ];
3254
+ /** Localized label lookup for a contract. */
3255
+ function labelOf(c, lang) {
3256
+ return lang === "zh" ? c.label.zh : c.label.en;
3257
+ }
3258
+ /** Localized symptom lookup for a contract. */
3259
+ function symptomOf(c, lang) {
3260
+ return lang === "zh" ? c.symptom.zh : c.symptom.en;
3261
+ }
3262
+ /** Groups the panel renders, in order. */
3263
+ const CONTRACT_GROUPS = [
3264
+ {
3265
+ id: "model",
3266
+ label: {
3267
+ en: "Model → rule resolution",
3268
+ zh: "模型 → 规则匹配"
3269
+ }
3270
+ },
3271
+ {
3272
+ id: "surface",
3273
+ label: {
3274
+ en: "Surfaces the plugin paints",
3275
+ zh: "插件着色的界面区域"
3276
+ }
3277
+ },
3278
+ {
3279
+ id: "token",
3280
+ label: {
3281
+ en: "Design tokens re-emitted",
3282
+ zh: "被重新发出的设计令牌"
3283
+ }
3284
+ },
3285
+ {
3286
+ id: "region",
3287
+ label: {
3288
+ en: "Conversation views",
3289
+ zh: "对话视图"
3290
+ }
3291
+ }
3292
+ ];
3293
+ /** Overall verdict of a report, for the panel's headline. */
3294
+ function verdictOf(report) {
3295
+ if (report.results.length === 0) return "unknown";
3296
+ if (report.summary.fail > 0) return report.summary.pass > 0 ? "partial" : "broken";
3297
+ return report.summary.pass > 0 ? "ok" : "unknown";
3298
+ }
3299
+ /** Markdown rendering of a report — the thing a user pastes into an issue. */
3300
+ function reportToMarkdown(report, lang) {
3301
+ const zh = lang === "zh";
3302
+ const lines = [];
3303
+ lines.push(zh ? "## dsh-background-by-model 宿主自检" : "## dsh-background-by-model host check");
3304
+ lines.push("");
3305
+ lines.push(`- ${zh ? "阶段" : "phase"}: ${report.phase}`);
3306
+ if (report.plugin.version !== "") lines.push(`- ${zh ? "插件" : "plugin"}: ${report.plugin.version} (dsh >=${report.plugin.floor})`);
3307
+ lines.push(`- dsh: ${report.host.version !== "" ? report.host.version : zh ? "未读到" : "unreadable"}${report.host.compatible === false ? zh ? "(低于插件下限)" : " (below the plugin floor)" : ""}`);
3308
+ if (report.host.node !== "") lines.push(`- node: ${report.host.node}`);
3309
+ if (report.host.root !== "") lines.push(`- host root: ${report.host.root}`);
3310
+ if (report.host.note !== "") lines.push(`- note: ${report.host.note}`);
3311
+ if (report.host.model !== void 0) {
3312
+ const m = report.host.model;
3313
+ lines.push(`- ${zh ? "模型" : "model"}: \`${m.text !== "" ? m.text : "(none)"}\` · ${m.source}${m.note !== "" ? ` · ${m.note}` : ""} · ${zh ? "规则" : "rule"} ${m.rule !== "" ? m.rule : "-"}${m.matched ? "" : zh ? "(兜底)" : " (fallback)"}`);
3314
+ }
3315
+ lines.push("");
3316
+ lines.push(`### ${zh ? "结论" : "Summary"}: ${report.summary.pass} pass / ${report.summary.fail} fail / ${report.summary.skip} n/a`);
3317
+ lines.push("");
3318
+ for (const r of report.results) {
3319
+ const mark = r.status === "pass" ? "PASS" : r.status === "fail" ? "**FAIL**" : r.status === "skip" ? "n/a" : "info";
3320
+ lines.push(`- ${mark} \`${r.id}\` — ${r.label} · \`${r.target}\``);
3321
+ if (r.status === "fail") {
3322
+ lines.push(` - ${zh ? "现象" : "symptom"}: ${r.symptom}`);
3323
+ lines.push(` - ${zh ? "原因" : "reason"}: ${r.reason ?? r.detail}`);
3324
+ lines.push(` - ${zh ? "期望位置" : "expected in"}: ${r.sources.map((s) => s.path).join(", ")}`);
3325
+ } else if (r.detail !== "") lines.push(` - ${r.detail}`);
3326
+ }
3327
+ return lines.join("\n");
3328
+ }
3329
+ //#endregion
3330
+ //#region src/client/judge.ts
3331
+ /** The real browser environment. */
3332
+ function browserEnv(ctx) {
3333
+ const tokenRoot = () => {
3334
+ if (typeof document === "undefined") return null;
3335
+ return document.body ?? document.documentElement ?? null;
3336
+ };
3337
+ return {
3338
+ querySelector: (selector) => document.querySelector(selector),
3339
+ tokenRoot,
3340
+ computedValue: (token, on) => {
3341
+ if (typeof document === "undefined") return "";
3342
+ const host = on ?? tokenRoot();
3343
+ if (host === null) return "";
3344
+ return getComputedStyle(host).getPropertyValue(token).trim();
3345
+ },
3346
+ styleSheets: () => typeof document === "undefined" ? [] : Array.from(document.styleSheets),
3347
+ ctx
3348
+ };
3349
+ }
3350
+ const ANCHORS = {
3351
+ settingsClosed: "div[role=\"dialog\"][aria-modal=\"true\"][aria-labelledby]",
3352
+ chatNotMounted: "[data-chat-flow],[data-conversation-scroll]",
3353
+ trajectoryNotMounted: "[data-conversation-composer-overlay]",
3354
+ noRightPanel: "div[data-sidebar-right-panel],[data-dockkit-strip],[data-dockkit-pane]",
3355
+ cordisClosed: "[data-cordis-panel]",
3356
+ noDockTab: "[data-dockkit-tab]",
3357
+ noFloatPane: "[data-dockkit-float],[data-sidebar-right-mode=\"float\"]"
3358
+ };
3359
+ function mounted(env, anchorId) {
3360
+ const sel = ANCHORS[anchorId];
3361
+ if (sel === void 0) return true;
3362
+ try {
3363
+ return env.querySelector(sel) !== null;
3364
+ } catch {
3365
+ return false;
3366
+ }
3367
+ }
3368
+ /** Walk a dotted path off a root object, tolerating a missing first hop. */
3369
+ function walkPath(root, path) {
3370
+ let cur = root;
3371
+ for (const key of path.split(".")) {
3372
+ if (cur === null || cur === void 0) return void 0;
3373
+ cur = cur[key];
3374
+ }
3375
+ return cur;
3376
+ }
3377
+ function looksObservable(v) {
3378
+ return typeof v === "object" && v !== null && typeof v.subscribe === "function" && typeof v.getSnapshot === "function";
3379
+ }
3380
+ /**
3381
+ * Whether a stylesheet belongs to THIS plugin.
3382
+ *
3383
+ * `data-plugin` cannot answer this: the host's own CSS modules set it on every
3384
+ * `<style>` they inject (`@deepseek-ai/dsh-client-ui-theme` and its peers). Only
3385
+ * the marker written by this plugin's `markOwnSheet` can, and getting this wrong
3386
+ * in the "skip our own sheets" direction skips the host's whole theme.
3387
+ *
3388
+ * Read via `getAttribute` (the browser's own spelling) and fall back to the
3389
+ * camel-cased `dataset` key, which is what a plain-object test fixture exposes.
3390
+ */
3391
+ function isOwnSheet(sheet) {
3392
+ const owner = sheet.ownerNode;
3393
+ if (owner === null || owner === void 0) return false;
3394
+ if (typeof owner.getAttribute === "function") return owner.getAttribute(OWN_SHEET_ATTR) !== null;
3395
+ const ds = owner.dataset;
3396
+ return ds !== void 0 && (ds["data-dab-side"] !== void 0 || ds["dabSide"] !== void 0);
3397
+ }
3398
+ /**
3399
+ * A stylesheet mention of a literal, on the requested side.
3400
+ *
3401
+ * The host's rules are the only place a renamed token still answers honestly:
3402
+ * a token can be *declared* by this plugin while the host's rule that gave it a
3403
+ * value is gone. Cross-origin sheets (none on a local dsh, but the probe must
3404
+ * not throw if one ever appears) are skipped.
3405
+ */
3406
+ function ruleMentioned(env, literal, side) {
3407
+ for (const sheet of env.styleSheets()) {
3408
+ if (isOwnSheet(sheet) !== (side === "own")) continue;
3409
+ let rules = null;
3410
+ try {
3411
+ rules = sheet.cssRules;
3412
+ } catch {
3413
+ continue;
3414
+ }
3415
+ if (rules === null) continue;
3416
+ for (const rule of Array.from(rules)) {
3417
+ const text = rule.cssText;
3418
+ if (typeof text === "string" && text.includes(literal)) return true;
3419
+ const inner = rule.cssRules;
3420
+ if (inner === void 0 || inner === null) continue;
3421
+ for (const child of Array.from(inner)) {
3422
+ const childText = child.cssText;
3423
+ if (typeof childText === "string" && childText.includes(literal)) return true;
3424
+ }
3425
+ }
3426
+ }
3427
+ return false;
3428
+ }
3429
+ /** Which sides of stylesheet the probe can actually read (a blind check must not fail). */
3430
+ function readability(env) {
3431
+ const out = {
3432
+ host: false,
3433
+ own: false
3434
+ };
3435
+ for (const sheet of env.styleSheets()) try {
3436
+ if (sheet.cssRules !== null) {
3437
+ if (isOwnSheet(sheet)) out.own = true;
3438
+ else out.host = true;
3439
+ }
3440
+ } catch {}
3441
+ return out;
3442
+ }
3443
+ function evaluate(env, check) {
3444
+ switch (check.kind) {
3445
+ case "service": {
3446
+ let svc;
3447
+ try {
3448
+ svc = env.ctx.get(check.id);
3449
+ } catch {
3450
+ svc = void 0;
3451
+ }
3452
+ return svc === void 0 || svc === null ? {
3453
+ status: "fail",
3454
+ name: `ctx.get('${check.id}')`,
3455
+ detail: "service not published",
3456
+ reason: `the host does not publish the "${check.id}" service`
3457
+ } : {
3458
+ status: "pass",
3459
+ name: `ctx.get('${check.id}')`,
3460
+ detail: "service present"
3461
+ };
3462
+ }
3463
+ case "path": {
3464
+ let svc;
3465
+ try {
3466
+ svc = env.ctx.get(check.service);
3467
+ } catch {
3468
+ svc = void 0;
3469
+ }
3470
+ if (svc === void 0 || svc === null) return {
3471
+ status: "skip",
3472
+ name: `${check.service}.${check.path}`,
3473
+ detail: "service not published yet"
3474
+ };
3475
+ const value = walkPath(svc, check.path);
3476
+ const name = `${check.service}.${check.path}`;
3477
+ if (check.expect === "function") return typeof value === "function" ? {
3478
+ status: "pass",
3479
+ name,
3480
+ detail: "is a function"
3481
+ } : {
3482
+ status: "fail",
3483
+ name,
3484
+ detail: `expected a function, got ${typeof value}`,
3485
+ reason: `"${name}" is not a function on this host`
3486
+ };
3487
+ if (check.expect === "observable") return looksObservable(value) ? {
3488
+ status: "pass",
3489
+ name,
3490
+ detail: "is an observable face"
3491
+ } : {
3492
+ status: "fail",
3493
+ name,
3494
+ detail: "not an observable face",
3495
+ reason: `"${name}" no longer exposes subscribe()/getSnapshot()`
3496
+ };
3497
+ return value === void 0 ? {
3498
+ status: "fail",
3499
+ name,
3500
+ detail: "undefined",
3501
+ reason: `"${name}" is gone`
3502
+ } : {
3503
+ status: "pass",
3504
+ name,
3505
+ detail: typeof value
3506
+ };
3507
+ }
3508
+ case "selector": {
3509
+ const present = env.querySelector(check.selector) !== null;
3510
+ const name = `querySelector(${JSON.stringify(check.selector)})`;
3511
+ if (present) return {
3512
+ status: "pass",
3513
+ name,
3514
+ detail: "element found"
3515
+ };
3516
+ if (check.optionalWhen !== void 0 && !mounted(env, check.optionalWhen)) return {
3517
+ status: "skip",
3518
+ name,
3519
+ detail: "not on screen right now"
3520
+ };
3521
+ return {
3522
+ status: "fail",
3523
+ name,
3524
+ detail: "no element matches",
3525
+ reason: `no element matches ${check.selector} while its surface is on screen`
3526
+ };
3527
+ }
3528
+ case "attr": {
3529
+ const present = env.querySelector(`[${check.attr}]`) !== null;
3530
+ const name = `querySelector([${check.attr}])`;
3531
+ if (present) return {
3532
+ status: "pass",
3533
+ name,
3534
+ detail: "attribute found"
3535
+ };
3536
+ if (check.optionalWhen !== void 0 && !mounted(env, check.optionalWhen)) return {
3537
+ status: "skip",
3538
+ name,
3539
+ detail: "not on screen right now"
3540
+ };
3541
+ return {
3542
+ status: "fail",
3543
+ name,
3544
+ detail: "no element carries it",
3545
+ reason: `nothing in the DOM carries ${check.attr} while its surface is on screen`
3546
+ };
3547
+ }
3548
+ case "computed": {
3549
+ const host = check.on !== void 0 ? env.querySelector(check.on) : null;
3550
+ const name = `computed(${check.token}${check.on !== void 0 ? ` on ${check.on}` : ""})`;
3551
+ if (check.on !== void 0 && host === null) return {
3552
+ status: "skip",
3553
+ name,
3554
+ detail: `${check.on} not mounted`
3555
+ };
3556
+ const value = env.computedValue(check.token, host);
3557
+ return value !== "" ? {
3558
+ status: "pass",
3559
+ name,
3560
+ detail: value
3561
+ } : {
3562
+ status: "fail",
3563
+ name,
3564
+ detail: "resolves to nothing",
3565
+ reason: `${check.token} resolves to an empty value`
3566
+ };
3567
+ }
3568
+ case "rule": {
3569
+ const side = check.side ?? "host";
3570
+ const name = `${side === "host" ? "host" : "plugin"} stylesheet includes ${JSON.stringify(check.match)}`;
3571
+ if (!readability(env)[side]) return {
3572
+ status: "skip",
3573
+ name,
3574
+ detail: side === "host" ? "no host stylesheet is readable" : "this plugin's sheet is not mounted"
3575
+ };
3576
+ return ruleMentioned(env, check.match, side) ? {
3577
+ status: "pass",
3578
+ name,
3579
+ detail: side === "host" ? "declared by a host rule" : "declared by this plugin's own sheet"
3580
+ } : {
3581
+ status: "fail",
3582
+ name,
3583
+ detail: "no rule mentions it",
3584
+ reason: side === "host" ? `no host stylesheet declares ${check.match}` : `this plugin's own stylesheet does not declare ${check.match} — it was not injected`
3585
+ };
3586
+ }
3587
+ default: return {
3588
+ status: "fail",
3589
+ name: "unknown check",
3590
+ detail: String(check)
3591
+ };
3592
+ }
3593
+ }
3594
+ /** A contract with no checks is documentation only (a legacy alias we still accept). */
3595
+ function noChecks(c, lang) {
3596
+ return {
3597
+ id: c.id,
3598
+ label: labelOf(c, lang),
3599
+ symptom: symptomOf(c, lang),
3600
+ status: "info",
3601
+ detail: lang === "zh" ? "无需运行时检查 —— 仅记录兼容用法" : "no runtime check — accepted for compatibility",
3602
+ target: c.target,
3603
+ sources: c.sources,
3604
+ usedBy: c.usedBy,
3605
+ checks: []
3606
+ };
3607
+ }
3608
+ /**
3609
+ * Run every contract against a probe environment.
3610
+ *
3611
+ * Only the FIRST failing check of a contract is reported: the later ones usually
3612
+ * depend on it (no service ⇒ no methods on it), so listing them all would turn
3613
+ * one rename into five red lines.
3614
+ */
3615
+ function probeContracts(env, lang) {
3616
+ return HOST_CONTRACTS.map((c) => {
3617
+ const result = {
3618
+ id: c.id,
3619
+ label: labelOf(c, lang),
3620
+ symptom: symptomOf(c, lang),
3621
+ status: "pass",
3622
+ detail: "",
3623
+ target: c.target,
3624
+ sources: c.sources,
3625
+ usedBy: c.usedBy,
3626
+ checks: []
3627
+ };
3628
+ if (c.checks.length === 0) return noChecks(c, lang);
3629
+ const outcomes = [];
3630
+ let firstFail;
3631
+ for (const check of c.checks) {
3632
+ let outcome;
3633
+ try {
3634
+ outcome = evaluate(env, check);
3635
+ } catch (e) {
3636
+ outcome = {
3637
+ status: "fail",
3638
+ name: check.kind,
3639
+ detail: String(e),
3640
+ reason: `${check.kind} check threw`
3641
+ };
3642
+ }
3643
+ outcomes.push(outcome);
3644
+ if (outcome.status === "fail" && firstFail === void 0) firstFail = outcome;
3645
+ }
3646
+ result.checks = outcomes.map((o) => `${o.status} · ${o.name} — ${o.detail}`);
3647
+ if (firstFail !== void 0) {
3648
+ result.status = "fail";
3649
+ result.detail = `${firstFail.name} — ${firstFail.detail}`;
3650
+ result.reason = firstFail.reason;
3651
+ return result;
3652
+ }
3653
+ const ran = outcomes.filter((o) => o.status !== "skip");
3654
+ if (ran.length === 0) {
3655
+ result.status = "skip";
3656
+ result.detail = outcomes.map((o) => `${o.name} — ${o.detail}`).join("; ");
3657
+ return result;
3658
+ }
3659
+ result.status = ran.some((o) => o.status === "info") && ran.every((o) => o.status === "info") ? "info" : "pass";
3660
+ const last = ran[ran.length - 1];
3661
+ result.detail = `${last.name} — ${last.detail}`;
3662
+ return result;
3663
+ });
3664
+ }
3665
+ /** Assemble the panel's report around a set of probe results. */
3666
+ function buildClientReport(input, host) {
3667
+ const results = probeContracts(browserEnv(input.ctx), input.lang);
3668
+ const pass = results.filter((r) => r.status === "pass").length;
3669
+ const fail = results.filter((r) => r.status === "fail").length;
3670
+ const skip = results.filter((r) => r.status === "skip" || r.status === "info").length;
3671
+ return {
3672
+ phase: "client",
3673
+ at: (/* @__PURE__ */ new Date()).toISOString(),
3674
+ plugin: {
3675
+ version: input.pluginVersion,
3676
+ floor: input.floor
3677
+ },
3678
+ host: {
3679
+ version: host.version,
3680
+ compatible: host.compatible,
3681
+ node: "",
3682
+ root: "",
3683
+ note: host.note,
3684
+ model: input.model
3685
+ },
3686
+ results,
3687
+ summary: {
3688
+ pass,
3689
+ fail,
3690
+ skip,
3691
+ total: results.length
3692
+ }
3693
+ };
3624
3694
  }
3625
3695
  //#endregion
3696
+ //#region src/client/build-info.ts
3697
+ /** This plugin's version at build time ('0.0.0' when built unbundled). */
3698
+ const PLUGIN_VERSION = "0.4.4";
3699
+ /** The dsh floor this build declares, straight from `engines.dsh`. */
3700
+ const DSH_FLOOR = "0.1.7-rc.2";
3701
+ //#endregion
3626
3702
  //#region src/client/components/icons.tsx
3627
3703
  /** Sun glyph, migrated from @deepseek-ai/dsh-client-ui-primitives IconLightOutline16. */
3628
3704
  const SUN_PATHS = "<path d=\"M11.3496 8C11.3496 6.14985 9.85015 4.65039 8 4.65039C6.14985 4.65039 4.65039 6.14985 4.65039 8C4.65039 9.85015 6.14985 11.3496 8 11.3496C9.85015 11.3496 11.3496 9.85015 11.3496 8ZM12.6504 8C12.6504 10.5681 10.5681 12.6504 8 12.6504C5.43188 12.6504 3.34961 10.5681 3.34961 8C3.34961 5.43188 5.43188 3.34961 8 3.34961C10.5681 3.34961 12.6504 5.43188 12.6504 8Z\" fill=\"currentColor\"/><path d=\"M8.65039 0.5V2.5H7.34961V0.5H8.65039Z\" fill=\"currentColor\"/><path d=\"M8.65039 13.5V15.5H7.34961V13.5H8.65039Z\" fill=\"currentColor\"/><path d=\"M3.15808 2.24035L4.57229 3.65456L3.6525 4.57435L2.23829 3.16014L3.15808 2.24035Z\" fill=\"currentColor\"/><path d=\"M12.3505 11.4327L13.7647 12.8469L12.8449 13.7667L11.4307 12.3525L12.3505 11.4327Z\" fill=\"currentColor\"/><path d=\"M2.24537 12.8469L3.65958 11.4327L4.57937 12.3525L3.16516 13.7667L2.24537 12.8469Z\" fill=\"currentColor\"/><path d=\"M11.4377 3.65455L12.852 2.24033L13.7718 3.16012L12.3575 4.57434L11.4377 3.65455Z\" fill=\"currentColor\"/><path d=\"M0.5 7.35461H2.5V8.6554H0.5L0.5 7.35461Z\" fill=\"currentColor\"/><path d=\"M13.5 7.35461H15.5V8.6554H13.5V8.6554Z\" fill=\"currentColor\"/>";
@@ -6065,6 +6141,7 @@ window.__ModuleLoader__.load({
6065
6141
  }, "dsh-background-by-model: skin dispose");
6066
6142
  const styleEl = document.createElement("style");
6067
6143
  styleEl.dataset.plugin = "dsh-background-by-model";
6144
+ markOwnSheet(styleEl);
6068
6145
  styleEl.textContent = `body[data-ds-dark-theme="dsh-background-by-model"]::before{content:'';position:fixed;inset:0;z-index:-1;pointer-events:none;background:radial-gradient(ellipse 80% 60% at 50% 0%,rgba(255,255,255,0.03) 0%,transparent 60%)}${SETTINGS_STYLE_RULE}${TRAJECTORY_STYLE_RULE}${RIGHTBAR_STYLE_RULE}${INPUT_BLUR_RULE}[data-composer-card] textarea::placeholder,[data-composer-card] input::placeholder,[data-composer-card] [contenteditable]::placeholder,[data-cordis-panel] input::placeholder,[data-cordis-panel] textarea::placeholder,.dab-input::placeholder,.dab-input textarea::placeholder,.dab-input input::placeholder{color:var(--dsh-any-placeholder,var(--dsw-alias-label-caption,#8a8f98))!important;font-style:italic;opacity:.85}`;
6069
6146
  document.head.appendChild(styleEl);
6070
6147
  ctx.effect(() => () => {