dsh-claude-desktop-theme 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/README.md +78 -140
- package/lib/client.js +1931 -400
- package/lib/index.js +38 -17
- package/lib/types/client/compat/conversation-process.d.ts +8 -0
- package/lib/types/client/compat/frame.d.ts +29 -0
- package/lib/types/client/compat/hero-chips.d.ts +9 -0
- package/lib/types/client/compat/host-dom.d.ts +6 -0
- package/lib/types/client/compat/mutation-scope.d.ts +43 -0
- package/lib/types/client/compat/pane-chrome.d.ts +2 -0
- package/lib/types/client/compat/reply-signature.d.ts +6 -0
- package/lib/types/client/compat/view-switch.d.ts +1 -1
- package/lib/types/client/contracts/ports.d.ts +2 -0
- package/lib/types/client/features/conversation/header-actions/HeaderActionButton.d.ts +2 -2
- package/lib/types/client/features/conversation/header-actions/entries.d.ts +11 -15
- package/lib/types/client/features/conversation/view-switch-icons.d.ts +2 -2
- package/lib/types/client/features/model-controls/popup.d.ts +20 -0
- package/lib/types/client/features/new-session/ripples.d.ts +2 -2
- package/lib/types/client/features/statistics/source.d.ts +2 -1
- package/package.json +1 -2
package/lib/client.js
CHANGED
|
@@ -67,7 +67,7 @@ var DEFAULT_FONTS = Object.freeze({
|
|
|
67
67
|
});
|
|
68
68
|
var DEFAULT_VIEW = Object.freeze({
|
|
69
69
|
showEmptyGroups: false,
|
|
70
|
-
groupByStatus:
|
|
70
|
+
groupByStatus: true,
|
|
71
71
|
showHeaderSubagents: false,
|
|
72
72
|
showHeaderTeam: false
|
|
73
73
|
});
|
|
@@ -526,6 +526,9 @@ var HOST = Object.freeze({
|
|
|
526
526
|
browserList: "._7514NG_list",
|
|
527
527
|
/** FlatList is the only native body the status presentation reorders. */
|
|
528
528
|
browserFlatList: "._7514NG_flatList",
|
|
529
|
+
/** StateDot's stable state attributes, used when root sessionStatus is absent. */
|
|
530
|
+
sessionNeedsInput: '[aria-hidden="true"][data-state="warning"]',
|
|
531
|
+
sessionWorking: '[aria-hidden="true"][data-state="ongoing"]',
|
|
529
532
|
/** Rows.module.css: one Workspace's row, the line the merged header shares,
|
|
530
533
|
and its trailing action group (`sidebar.css` parks both). */
|
|
531
534
|
workspaceRow: ".jJkEga_projectRow",
|
|
@@ -705,6 +708,9 @@ var HOST = Object.freeze({
|
|
|
705
708
|
menuViewport: "._viewport_4ub78_19"
|
|
706
709
|
});
|
|
707
710
|
var ANCHOR = Object.freeze({
|
|
711
|
+
/** ui-chat TurnProcessNodeView / ChatGroupSeat: process containers, not leaf details. */
|
|
712
|
+
turnProcess: "button[data-turn-process]",
|
|
713
|
+
stepProcessToggle: "[data-step-process] > div > button[data-process-activity]",
|
|
708
714
|
frame: '[data-slot="root"] > div',
|
|
709
715
|
sidebar: '[data-slot="sidebar"]',
|
|
710
716
|
workspaces: '[data-slot="sidebar.workspaces"]',
|
|
@@ -985,10 +991,19 @@ function createSidebarSessionsPort(ctx) {
|
|
|
985
991
|
if (workspace.sessionIds.some(matches)) populated.add(workspace.workspaceId);
|
|
986
992
|
}
|
|
987
993
|
if (nested) {
|
|
988
|
-
const
|
|
989
|
-
const
|
|
994
|
+
const paths = /* @__PURE__ */ new Map();
|
|
995
|
+
const path = (workspaceId, value) => {
|
|
996
|
+
let normalized = paths.get(workspaceId);
|
|
997
|
+
if (normalized === void 0) {
|
|
998
|
+
normalized = value.replace(/\\/gu, "/").replace(/\/+$/u, "") + "/";
|
|
999
|
+
paths.set(workspaceId, normalized);
|
|
1000
|
+
}
|
|
1001
|
+
return normalized;
|
|
1002
|
+
};
|
|
1003
|
+
const occupied = registry.items.filter((row) => populated.has(row.workspaceId)).map((row) => path(row.workspaceId, row.path));
|
|
990
1004
|
for (const row of registry.items) {
|
|
991
|
-
|
|
1005
|
+
const own = path(row.workspaceId, row.path);
|
|
1006
|
+
if (occupied.some((child) => child.startsWith(own))) populated.add(row.workspaceId);
|
|
992
1007
|
}
|
|
993
1008
|
}
|
|
994
1009
|
return populated;
|
|
@@ -1099,6 +1114,7 @@ function mountBrowserHeader(document2, report) {
|
|
|
1099
1114
|
const sync = () => {
|
|
1100
1115
|
scheduled = 0;
|
|
1101
1116
|
if (disposed) return;
|
|
1117
|
+
for (const node of rewritten.keys()) if (!node.isConnected) rewritten.delete(node);
|
|
1102
1118
|
name(
|
|
1103
1119
|
document2.querySelector(host.browserSectionLabel),
|
|
1104
1120
|
document2.querySelector('[data-ccd-empty-state="new"]') !== null
|
|
@@ -1144,6 +1160,42 @@ function mountBrowserHeader(document2, report) {
|
|
|
1144
1160
|
};
|
|
1145
1161
|
}
|
|
1146
1162
|
|
|
1163
|
+
// src/client/compat/mutation-scope.ts
|
|
1164
|
+
function overlaps(node, region) {
|
|
1165
|
+
if (node === region) return true;
|
|
1166
|
+
const candidate = node;
|
|
1167
|
+
if (typeof candidate.contains === "function" && candidate.contains(region)) return true;
|
|
1168
|
+
return typeof region.contains === "function" && region.contains(node);
|
|
1169
|
+
}
|
|
1170
|
+
function touches(record, region) {
|
|
1171
|
+
if (record.type === "attributes" || record.type === "characterData") {
|
|
1172
|
+
return overlaps(record.target, region);
|
|
1173
|
+
}
|
|
1174
|
+
if (record.target === region || region.contains(record.target)) return true;
|
|
1175
|
+
for (const node of record.addedNodes) if (overlaps(node, region)) return true;
|
|
1176
|
+
for (const node of record.removedNodes) if (overlaps(node, region)) return true;
|
|
1177
|
+
return false;
|
|
1178
|
+
}
|
|
1179
|
+
function recordsTouch(records, region) {
|
|
1180
|
+
if (region === null || region.isConnected === false) return true;
|
|
1181
|
+
if (typeof region.contains !== "function") return true;
|
|
1182
|
+
for (const record of records) if (touches(record, region)) return true;
|
|
1183
|
+
return false;
|
|
1184
|
+
}
|
|
1185
|
+
function recordsIntroduce(records, selector) {
|
|
1186
|
+
for (const record of records) {
|
|
1187
|
+
if (record.type !== "childList") continue;
|
|
1188
|
+
for (const node of record.addedNodes) {
|
|
1189
|
+
if (node.nodeType !== 1) continue;
|
|
1190
|
+
const element = node;
|
|
1191
|
+
if (typeof element.matches !== "function" || typeof element.querySelector !== "function") return true;
|
|
1192
|
+
if (element.matches(selector)) return true;
|
|
1193
|
+
if (element.querySelector(selector) !== null) return true;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
return false;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1147
1199
|
// src/client/compat/composer-menus.ts
|
|
1148
1200
|
var FLIPPED_MENU_ATTRIBUTE = "data-ccd-menu-flipped";
|
|
1149
1201
|
var GAP = 4;
|
|
@@ -1156,6 +1208,7 @@ function mountComposerMenuPlacement(document2, report) {
|
|
|
1156
1208
|
const placed = /* @__PURE__ */ new Map();
|
|
1157
1209
|
const measured = /* @__PURE__ */ new Map();
|
|
1158
1210
|
let scheduled = 0;
|
|
1211
|
+
let anchorRegion = null;
|
|
1159
1212
|
const anchorOf = () => {
|
|
1160
1213
|
const expanded = '[aria-expanded="true"]';
|
|
1161
1214
|
return document2.querySelector(`${host.heroWorkspaceRow} ${expanded}`) ?? document2.querySelector(`${host.heroWorkspaceChip}${expanded}`) ?? document2.querySelector(`${host.composerCard} ${expanded}`) ?? document2.querySelector(`${host.accountTrigger}${expanded}`);
|
|
@@ -1209,7 +1262,7 @@ function mountComposerMenuPlacement(document2, report) {
|
|
|
1209
1262
|
const previous = placed.get(menu)?.previous ?? menu.style.maxHeight;
|
|
1210
1263
|
const cap = fitsAbove ? previous : `${Math.round(above)}px`;
|
|
1211
1264
|
placed.set(menu, { previous, cap });
|
|
1212
|
-
menu.setAttribute(FLIPPED_MENU_ATTRIBUTE, "");
|
|
1265
|
+
if (menu.getAttribute(FLIPPED_MENU_ATTRIBUTE) !== "") menu.setAttribute(FLIPPED_MENU_ATTRIBUTE, "");
|
|
1213
1266
|
const top = `${Math.round(anchor.top - GAP - Math.min(height, fitsAbove ? height : above))}px`;
|
|
1214
1267
|
if (menu.style.top !== top) menu.style.top = top;
|
|
1215
1268
|
if (menu.style.maxHeight !== cap) menu.style.maxHeight = cap;
|
|
@@ -1217,6 +1270,9 @@ function mountComposerMenuPlacement(document2, report) {
|
|
|
1217
1270
|
const sync = () => {
|
|
1218
1271
|
scheduled = 0;
|
|
1219
1272
|
const anchor = anchorOf();
|
|
1273
|
+
anchorRegion = anchor?.closest?.(
|
|
1274
|
+
`${host.heroWorkspaceRow}, ${host.heroWorkspaceChip}, ${host.composerCard}, ${host.accountTrigger}`
|
|
1275
|
+
) ?? anchor;
|
|
1220
1276
|
const visited = /* @__PURE__ */ new Set();
|
|
1221
1277
|
if (anchor !== null) {
|
|
1222
1278
|
const anchorRect = anchor.getBoundingClientRect();
|
|
@@ -1244,12 +1300,15 @@ function mountComposerMenuPlacement(document2, report) {
|
|
|
1244
1300
|
};
|
|
1245
1301
|
let observer;
|
|
1246
1302
|
try {
|
|
1247
|
-
observer = new MutationObserver(
|
|
1303
|
+
observer = new MutationObserver((records) => {
|
|
1304
|
+
if (records.some((record) => record.type === "attributes" && record.attributeName === "aria-expanded") || anchorRegion !== null && recordsTouch(records, anchorRegion) || [...measured.keys()].some((menu) => recordsTouch(records, menu)) || recordsIntroduce(records, `${host.menu}, ${host.heroWorkspaceRow}, ${host.heroWorkspaceChip}, ${host.composerCard}, ${host.accountTrigger}`)) schedule();
|
|
1305
|
+
});
|
|
1248
1306
|
observer.observe(document2.body, {
|
|
1249
1307
|
childList: true,
|
|
1250
1308
|
subtree: true,
|
|
1309
|
+
characterData: true,
|
|
1251
1310
|
attributes: true,
|
|
1252
|
-
attributeFilter: ["style", "aria-expanded"]
|
|
1311
|
+
attributeFilter: ["style", "aria-expanded", "role"]
|
|
1253
1312
|
});
|
|
1254
1313
|
window.addEventListener("resize", onResize);
|
|
1255
1314
|
sync();
|
|
@@ -1266,6 +1325,7 @@ function mountComposerMenuPlacement(document2, report) {
|
|
|
1266
1325
|
window.removeEventListener("resize", onResize);
|
|
1267
1326
|
for (const menu of [...placed.keys()]) restore(menu);
|
|
1268
1327
|
measured.clear();
|
|
1328
|
+
anchorRegion = null;
|
|
1269
1329
|
};
|
|
1270
1330
|
}
|
|
1271
1331
|
|
|
@@ -1279,6 +1339,9 @@ function mountEmptyGroups(document2, hideEmpty, report, options = {}) {
|
|
|
1279
1339
|
let emptyArea = null;
|
|
1280
1340
|
const unsubscribes = [];
|
|
1281
1341
|
let disposed = false;
|
|
1342
|
+
let area = null;
|
|
1343
|
+
let catalogDirty = true;
|
|
1344
|
+
let catalog = null;
|
|
1282
1345
|
const keepsGroup = (group, populated) => {
|
|
1283
1346
|
for (const row of group.querySelectorAll("[data-row-key]")) {
|
|
1284
1347
|
const key = row.getAttribute("data-row-key") ?? "";
|
|
@@ -1298,13 +1361,13 @@ function mountEmptyGroups(document2, hideEmpty, report, options = {}) {
|
|
|
1298
1361
|
emptyArea?.removeAttribute("data-ccd-list-empty");
|
|
1299
1362
|
emptyArea = null;
|
|
1300
1363
|
};
|
|
1301
|
-
const renderEmptyState = (
|
|
1302
|
-
if (!empty ||
|
|
1364
|
+
const renderEmptyState = (area2, empty, noHistory) => {
|
|
1365
|
+
if (!empty || area2 === null) {
|
|
1303
1366
|
clearEmptyState();
|
|
1304
1367
|
return;
|
|
1305
1368
|
}
|
|
1306
1369
|
const kind = noHistory ? "new" : "filtered";
|
|
1307
|
-
if (emptyArea !==
|
|
1370
|
+
if (emptyArea !== area2 || !emptyState?.isConnected || emptyState.getAttribute("data-ccd-empty-state") !== kind) {
|
|
1308
1371
|
clearEmptyState();
|
|
1309
1372
|
const zh2 = (document2.documentElement.lang || globalThis.navigator?.language || "").toLowerCase().startsWith("zh");
|
|
1310
1373
|
emptyState = document2.createElement("div");
|
|
@@ -1329,9 +1392,9 @@ function mountEmptyGroups(document2, hideEmpty, report, options = {}) {
|
|
|
1329
1392
|
button.addEventListener("click", options.showAll);
|
|
1330
1393
|
emptyState.append(button);
|
|
1331
1394
|
}
|
|
1332
|
-
emptyArea =
|
|
1333
|
-
|
|
1334
|
-
|
|
1395
|
+
emptyArea = area2;
|
|
1396
|
+
area2.setAttribute("data-ccd-list-empty", "");
|
|
1397
|
+
area2.prepend(emptyState);
|
|
1335
1398
|
}
|
|
1336
1399
|
};
|
|
1337
1400
|
const clear2 = () => {
|
|
@@ -1339,17 +1402,28 @@ function mountEmptyGroups(document2, hideEmpty, report, options = {}) {
|
|
|
1339
1402
|
marked.clear();
|
|
1340
1403
|
clearEmptyState();
|
|
1341
1404
|
};
|
|
1405
|
+
const readCatalog = () => {
|
|
1406
|
+
const preferences = options.settings?.getSnapshot();
|
|
1407
|
+
const filter = preferences?.groups[2]?.selected ?? null;
|
|
1408
|
+
const nested = preferences?.groups[0]?.selected === 1;
|
|
1409
|
+
if (!catalogDirty && catalog !== null && catalog.filter === filter && catalog.nested === nested) return catalog;
|
|
1410
|
+
catalog = {
|
|
1411
|
+
filter,
|
|
1412
|
+
nested,
|
|
1413
|
+
history: options.source?.hasHistory() ?? null,
|
|
1414
|
+
populated: options.source?.populatedGroups(filter, nested) ?? null
|
|
1415
|
+
};
|
|
1416
|
+
catalogDirty = false;
|
|
1417
|
+
return catalog;
|
|
1418
|
+
};
|
|
1342
1419
|
const sync = () => {
|
|
1343
1420
|
scheduled = 0;
|
|
1344
1421
|
if (disposed) return;
|
|
1345
|
-
const
|
|
1422
|
+
const { history, populated } = readCatalog();
|
|
1423
|
+
const noHistory = history === false;
|
|
1346
1424
|
const wanted = /* @__PURE__ */ new Set();
|
|
1347
|
-
|
|
1348
|
-
const
|
|
1349
|
-
preferences?.groups[2]?.selected ?? null,
|
|
1350
|
-
preferences?.groups[0]?.selected === 1
|
|
1351
|
-
) ?? null;
|
|
1352
|
-
const groups = document2.querySelectorAll(host.workspaceGroup);
|
|
1425
|
+
if (area === null || !area.isConnected) area = document2.querySelector(host.browserListArea);
|
|
1426
|
+
const groups = area !== null && area.isConnected ? area.querySelectorAll(host.workspaceGroup) : document2.querySelectorAll(host.workspaceGroup);
|
|
1353
1427
|
for (const group of groups) {
|
|
1354
1428
|
if (noHistory || hideEmpty && !keepsGroup(group, populated)) wanted.add(group);
|
|
1355
1429
|
}
|
|
@@ -1363,7 +1437,6 @@ function mountEmptyGroups(document2, hideEmpty, report, options = {}) {
|
|
|
1363
1437
|
node.setAttribute("data-ccd-empty-group", "");
|
|
1364
1438
|
marked.add(node);
|
|
1365
1439
|
}
|
|
1366
|
-
const area = document2.querySelector(host.browserListArea);
|
|
1367
1440
|
const flatRows = area?.querySelectorAll('[data-row-key^="session:"], [data-row-key^="overflow:"]');
|
|
1368
1441
|
const flatEmpty = flatRows !== void 0 && flatRows.length > 0 && [...flatRows].every((row) => row.hasAttribute("data-ccd-blank-session"));
|
|
1369
1442
|
const empty = groups.length > 0 ? wanted.size === groups.length : area !== null && (area.querySelector('[data-row-key="empty"]') !== null || flatEmpty);
|
|
@@ -1378,8 +1451,19 @@ function mountEmptyGroups(document2, hideEmpty, report, options = {}) {
|
|
|
1378
1451
|
scheduled = 0;
|
|
1379
1452
|
}
|
|
1380
1453
|
};
|
|
1454
|
+
const relevant = (records) => {
|
|
1455
|
+
if (area === null || !area.isConnected) return true;
|
|
1456
|
+
if (recordsTouch(records, area)) return true;
|
|
1457
|
+
return recordsIntroduce(records, host.browserListArea);
|
|
1458
|
+
};
|
|
1459
|
+
const onCatalog = () => {
|
|
1460
|
+
catalogDirty = true;
|
|
1461
|
+
schedule();
|
|
1462
|
+
};
|
|
1381
1463
|
try {
|
|
1382
|
-
observer = new MutationObserver(
|
|
1464
|
+
observer = new MutationObserver((records) => {
|
|
1465
|
+
if (relevant(records)) schedule();
|
|
1466
|
+
});
|
|
1383
1467
|
observer.observe(document2.body, {
|
|
1384
1468
|
childList: true,
|
|
1385
1469
|
subtree: true,
|
|
@@ -1388,8 +1472,8 @@ function mountEmptyGroups(document2, hideEmpty, report, options = {}) {
|
|
|
1388
1472
|
// change membership without inserting a row. Our own marker is excluded.
|
|
1389
1473
|
attributeFilter: ["data-row-key", "aria-expanded", "aria-selected", "data-ccd-blank-session"]
|
|
1390
1474
|
});
|
|
1391
|
-
if (options.source) unsubscribes.push(options.source.subscribe(
|
|
1392
|
-
if (options.settings) unsubscribes.push(options.settings.subscribe(
|
|
1475
|
+
if (options.source) unsubscribes.push(options.source.subscribe(onCatalog));
|
|
1476
|
+
if (options.settings) unsubscribes.push(options.settings.subscribe(onCatalog));
|
|
1393
1477
|
sync();
|
|
1394
1478
|
} catch (error) {
|
|
1395
1479
|
observer?.disconnect();
|
|
@@ -1406,6 +1490,39 @@ function mountEmptyGroups(document2, hideEmpty, report, options = {}) {
|
|
|
1406
1490
|
};
|
|
1407
1491
|
}
|
|
1408
1492
|
|
|
1493
|
+
// src/client/compat/frame.ts
|
|
1494
|
+
function frameScheduler(task, win = globalThis.window ?? null) {
|
|
1495
|
+
let frame = 0;
|
|
1496
|
+
let timer;
|
|
1497
|
+
const clear2 = () => {
|
|
1498
|
+
if (frame !== 0) {
|
|
1499
|
+
(win?.cancelAnimationFrame ?? globalThis.cancelAnimationFrame)(frame);
|
|
1500
|
+
frame = 0;
|
|
1501
|
+
}
|
|
1502
|
+
if (timer !== void 0) {
|
|
1503
|
+
clearTimeout(timer);
|
|
1504
|
+
timer = void 0;
|
|
1505
|
+
}
|
|
1506
|
+
};
|
|
1507
|
+
const run = () => {
|
|
1508
|
+
frame = 0;
|
|
1509
|
+
timer = void 0;
|
|
1510
|
+
task();
|
|
1511
|
+
};
|
|
1512
|
+
return {
|
|
1513
|
+
schedule() {
|
|
1514
|
+
if (frame !== 0 || timer !== void 0) return;
|
|
1515
|
+
const request = win?.requestAnimationFrame ?? globalThis.requestAnimationFrame;
|
|
1516
|
+
if (typeof request === "function") {
|
|
1517
|
+
frame = request.call(win ?? void 0, run);
|
|
1518
|
+
return;
|
|
1519
|
+
}
|
|
1520
|
+
timer = setTimeout(run, 16);
|
|
1521
|
+
},
|
|
1522
|
+
cancel: clear2
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1409
1526
|
// src/client/compat/stats-values.ts
|
|
1410
1527
|
var STAT_VALUE_PROPERTY = "--ccd-stat-value";
|
|
1411
1528
|
var TRAILING_WIDTH_PROPERTY = "--ccd-trailing-width";
|
|
@@ -1414,13 +1531,15 @@ var CLUSTER_GAP_PROPERTY = "--ccd-cluster-gap";
|
|
|
1414
1531
|
var STATS_ATTRIBUTE = "data-ccd-composer-stats";
|
|
1415
1532
|
function mountComposerStats(document2, report, readouts) {
|
|
1416
1533
|
const host = hostSelectors(document2);
|
|
1417
|
-
let scheduled = 0;
|
|
1418
1534
|
let trailing = null;
|
|
1419
1535
|
let root = null;
|
|
1536
|
+
let row = null;
|
|
1537
|
+
let pills = [];
|
|
1420
1538
|
const sync = () => {
|
|
1421
|
-
|
|
1539
|
+
pills = readouts ? [...document2.querySelectorAll(host.statsPill)] : [];
|
|
1540
|
+
row = document2.querySelector(host.composerRow);
|
|
1422
1541
|
if (readouts) {
|
|
1423
|
-
for (const pill of
|
|
1542
|
+
for (const pill of pills) {
|
|
1424
1543
|
const parts = (pill.querySelector(host.statsLabel)?.textContent ?? "").split("\xB7").map((part) => part.trim()).filter(Boolean);
|
|
1425
1544
|
const value = parts.find((part) => part.includes("/")) ?? parts.find((part) => part.includes("%")) ?? parts[0] ?? "";
|
|
1426
1545
|
const quoted = value === "" ? "" : JSON.stringify(value);
|
|
@@ -1439,7 +1558,6 @@ function mountComposerStats(document2, report, readouts) {
|
|
|
1439
1558
|
if (root.style.getPropertyValue(TRAILING_WIDTH_PROPERTY) !== width) {
|
|
1440
1559
|
root.style.setProperty(TRAILING_WIDTH_PROPERTY, width);
|
|
1441
1560
|
}
|
|
1442
|
-
const row = document2.querySelector(host.composerRow);
|
|
1443
1561
|
const dock = root.querySelector(host.composerDock);
|
|
1444
1562
|
const model = trailing.querySelector(host.modelSelectTrigger);
|
|
1445
1563
|
if (row !== null && dock !== null && model !== null) {
|
|
@@ -1457,21 +1575,21 @@ function mountComposerStats(document2, report, readouts) {
|
|
|
1457
1575
|
}
|
|
1458
1576
|
}
|
|
1459
1577
|
};
|
|
1460
|
-
const
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
scheduled = 0;
|
|
1467
|
-
}
|
|
1578
|
+
const frames = frameScheduler(sync, document2.defaultView);
|
|
1579
|
+
const schedule = () => frames.schedule();
|
|
1580
|
+
const relevant = (records) => {
|
|
1581
|
+
if (recordsTouch(records, row)) return true;
|
|
1582
|
+
for (const pill of pills) if (recordsTouch(records, pill)) return true;
|
|
1583
|
+
return false;
|
|
1468
1584
|
};
|
|
1469
1585
|
let observer;
|
|
1470
1586
|
const html = document2.documentElement;
|
|
1471
1587
|
const state = readouts ? "on" : "off";
|
|
1472
1588
|
const previous = html.getAttribute(STATS_ATTRIBUTE);
|
|
1473
1589
|
try {
|
|
1474
|
-
observer = new MutationObserver(
|
|
1590
|
+
observer = new MutationObserver((records) => {
|
|
1591
|
+
if (relevant(records)) schedule();
|
|
1592
|
+
});
|
|
1475
1593
|
observer.observe(document2.body, {
|
|
1476
1594
|
childList: true,
|
|
1477
1595
|
subtree: true,
|
|
@@ -1483,6 +1601,7 @@ function mountComposerStats(document2, report, readouts) {
|
|
|
1483
1601
|
html.setAttribute(STATS_ATTRIBUTE, state);
|
|
1484
1602
|
sync();
|
|
1485
1603
|
} catch (error) {
|
|
1604
|
+
frames.cancel();
|
|
1486
1605
|
observer?.disconnect();
|
|
1487
1606
|
window.removeEventListener("resize", schedule);
|
|
1488
1607
|
report(error);
|
|
@@ -1490,7 +1609,7 @@ function mountComposerStats(document2, report, readouts) {
|
|
|
1490
1609
|
};
|
|
1491
1610
|
}
|
|
1492
1611
|
return () => {
|
|
1493
|
-
|
|
1612
|
+
frames.cancel();
|
|
1494
1613
|
observer?.disconnect();
|
|
1495
1614
|
window.removeEventListener("resize", schedule);
|
|
1496
1615
|
root?.style.removeProperty(TRAILING_WIDTH_PROPERTY);
|
|
@@ -1507,6 +1626,67 @@ function mountComposerStats(document2, report, readouts) {
|
|
|
1507
1626
|
};
|
|
1508
1627
|
}
|
|
1509
1628
|
|
|
1629
|
+
// src/client/compat/hero-chips.ts
|
|
1630
|
+
var HERO_CHIP_WIDTH_PROPERTY = "--ccd-workspace-seat";
|
|
1631
|
+
function mountHeroChips(document2, report) {
|
|
1632
|
+
const host = hostSelectors(document2);
|
|
1633
|
+
let stack = null;
|
|
1634
|
+
let seat = null;
|
|
1635
|
+
let sizes;
|
|
1636
|
+
let mutations;
|
|
1637
|
+
const measure = () => {
|
|
1638
|
+
if (stack === null || seat === null || !seat.isConnected) return;
|
|
1639
|
+
const width = seat.getBoundingClientRect().width;
|
|
1640
|
+
if (width <= 0) return;
|
|
1641
|
+
const value = `${Math.round(width)}px`;
|
|
1642
|
+
if (stack.style.getPropertyValue(HERO_CHIP_WIDTH_PROPERTY) !== value) {
|
|
1643
|
+
stack.style.setProperty(HERO_CHIP_WIDTH_PROPERTY, value);
|
|
1644
|
+
}
|
|
1645
|
+
};
|
|
1646
|
+
const sync = () => {
|
|
1647
|
+
const row = document2.querySelector(host.heroWorkspaceRow);
|
|
1648
|
+
const nextStack = row?.closest(host.composerStack) ?? null;
|
|
1649
|
+
if (nextStack !== stack) {
|
|
1650
|
+
stack?.style.removeProperty(HERO_CHIP_WIDTH_PROPERTY);
|
|
1651
|
+
stack = nextStack;
|
|
1652
|
+
}
|
|
1653
|
+
const nextSeat = row;
|
|
1654
|
+
if (nextSeat !== seat) {
|
|
1655
|
+
seat = nextSeat;
|
|
1656
|
+
sizes?.disconnect();
|
|
1657
|
+
if (seat !== null) sizes?.observe(seat);
|
|
1658
|
+
}
|
|
1659
|
+
measure();
|
|
1660
|
+
};
|
|
1661
|
+
const release = () => {
|
|
1662
|
+
stack?.style.removeProperty(HERO_CHIP_WIDTH_PROPERTY);
|
|
1663
|
+
};
|
|
1664
|
+
try {
|
|
1665
|
+
sizes = new ResizeObserver(() => {
|
|
1666
|
+
measure();
|
|
1667
|
+
});
|
|
1668
|
+
mutations = new MutationObserver(() => {
|
|
1669
|
+
sync();
|
|
1670
|
+
});
|
|
1671
|
+
mutations.observe(document2.documentElement, { childList: true, subtree: true, characterData: true });
|
|
1672
|
+
sync();
|
|
1673
|
+
} catch (error) {
|
|
1674
|
+
sizes?.disconnect();
|
|
1675
|
+
mutations?.disconnect();
|
|
1676
|
+
release();
|
|
1677
|
+
report(error);
|
|
1678
|
+
return () => {
|
|
1679
|
+
};
|
|
1680
|
+
}
|
|
1681
|
+
return () => {
|
|
1682
|
+
sizes?.disconnect();
|
|
1683
|
+
mutations?.disconnect();
|
|
1684
|
+
release();
|
|
1685
|
+
stack = null;
|
|
1686
|
+
seat = null;
|
|
1687
|
+
};
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1510
1690
|
// src/client/compat/composer-placeholder.ts
|
|
1511
1691
|
var DEFAULT_COPY = /* @__PURE__ */ new Map([
|
|
1512
1692
|
["\u63CF\u8FF0\u4F60\u60F3\u8981\u6784\u5EFA\u7684\u5185\u5BB9, / \u8C03\u7528\u6307\u4EE4, @ \u6587\u4EF6\u6216\u5BF9\u8BDD", "\u53D1\u6D88\u606F\u6216\u521B\u5EFA\u4EFB\u52A1, / \u8C03\u7528\u6307\u4EE4, @ \u6587\u4EF6\u6216\u5BF9\u8BDD"],
|
|
@@ -2050,17 +2230,19 @@ function mountScrollPills(document2, report) {
|
|
|
2050
2230
|
const pill = {
|
|
2051
2231
|
surface,
|
|
2052
2232
|
element,
|
|
2233
|
+
onScroll: () => {
|
|
2234
|
+
place(pill);
|
|
2235
|
+
awaken(pill);
|
|
2236
|
+
},
|
|
2053
2237
|
timer: 0
|
|
2054
2238
|
};
|
|
2055
2239
|
surface.setAttribute(PILL_SURFACE_ATTRIBUTE, "");
|
|
2056
|
-
surface.addEventListener("scroll",
|
|
2057
|
-
place(pill);
|
|
2058
|
-
awaken(pill);
|
|
2059
|
-
}, { passive: true });
|
|
2240
|
+
surface.addEventListener("scroll", pill.onScroll, { passive: true });
|
|
2060
2241
|
pills.set(surface, pill);
|
|
2061
2242
|
place(pill);
|
|
2062
2243
|
};
|
|
2063
2244
|
const release = (pill) => {
|
|
2245
|
+
pill.surface.removeEventListener("scroll", pill.onScroll);
|
|
2064
2246
|
if (pill.timer !== 0) window.clearTimeout(pill.timer);
|
|
2065
2247
|
pill.surface.removeAttribute(PILL_SURFACE_ATTRIBUTE);
|
|
2066
2248
|
pill.element.remove();
|
|
@@ -2324,9 +2506,14 @@ function mountFilesPane(document2, report) {
|
|
|
2324
2506
|
const applyFilter = (pane) => {
|
|
2325
2507
|
const query = pane.query.trim().toLowerCase();
|
|
2326
2508
|
const rows = [...pane.body.querySelectorAll("[data-files-entry]")];
|
|
2509
|
+
const mark = (row, hidden) => {
|
|
2510
|
+
if (row.hasAttribute(ROW_ATTRIBUTE) === hidden) return;
|
|
2511
|
+
if (hidden) row.setAttribute(ROW_ATTRIBUTE, "");
|
|
2512
|
+
else row.removeAttribute(ROW_ATTRIBUTE);
|
|
2513
|
+
};
|
|
2327
2514
|
if (query === "") {
|
|
2328
|
-
for (const row of rows) row
|
|
2329
|
-
pane.noMatch.hidden = true;
|
|
2515
|
+
for (const row of rows) mark(row, false);
|
|
2516
|
+
if (!pane.noMatch.hidden) pane.noMatch.hidden = true;
|
|
2330
2517
|
return;
|
|
2331
2518
|
}
|
|
2332
2519
|
const visible = /* @__PURE__ */ new Set();
|
|
@@ -2337,10 +2524,7 @@ function mountFilesPane(document2, report) {
|
|
|
2337
2524
|
hits++;
|
|
2338
2525
|
for (let node = row; node !== null; node = enclosingRow(node)) visible.add(node);
|
|
2339
2526
|
}
|
|
2340
|
-
for (const row of rows)
|
|
2341
|
-
if (visible.has(row)) row.removeAttribute(ROW_ATTRIBUTE);
|
|
2342
|
-
else row.setAttribute(ROW_ATTRIBUTE, "");
|
|
2343
|
-
}
|
|
2527
|
+
for (const row of rows) mark(row, !visible.has(row));
|
|
2344
2528
|
pane.noMatch.hidden = hits > 0;
|
|
2345
2529
|
};
|
|
2346
2530
|
const setQuery = (pane, query) => {
|
|
@@ -2445,7 +2629,24 @@ function mountFilesPane(document2, report) {
|
|
|
2445
2629
|
if (block.parentElement !== body) body.append(block);
|
|
2446
2630
|
writeEmpty(block, paneLabels(paneLanguage(document2)).files);
|
|
2447
2631
|
};
|
|
2632
|
+
const REGION_SELECTOR = [host.filesRoot, host.rightbarPanel].join(", ");
|
|
2633
|
+
const ARRIVAL_SELECTOR = [host.rightbarPanel, host.filesRoot, host.dockEmpty].join(", ");
|
|
2634
|
+
const EMPTY_SELECTOR = `${host.rightbarPanel} ${host.dockEmpty} ${host.dockPane}`;
|
|
2635
|
+
let regions = [];
|
|
2636
|
+
const collectRegions = () => {
|
|
2637
|
+
const found = /* @__PURE__ */ new Set();
|
|
2638
|
+
for (const pane of panes.values()) found.add(pane.root);
|
|
2639
|
+
for (const root of document2.querySelectorAll(REGION_SELECTOR)) found.add(root);
|
|
2640
|
+
for (const root of document2.querySelectorAll(ARRIVAL_SELECTOR)) found.add(root);
|
|
2641
|
+
regions = [...found];
|
|
2642
|
+
};
|
|
2643
|
+
const relevant = (records) => {
|
|
2644
|
+
if (regions.length === 0) return true;
|
|
2645
|
+
for (const region of regions) if (recordsTouch(records, region)) return true;
|
|
2646
|
+
return recordsIntroduce(records, ARRIVAL_SELECTOR);
|
|
2647
|
+
};
|
|
2448
2648
|
const sync = () => {
|
|
2649
|
+
collectRegions();
|
|
2449
2650
|
for (const pane of [...panes.values()]) {
|
|
2450
2651
|
if (!pane.root.isConnected || pane.body !== pane.root.querySelector(host.filesBody) || pane.field.parentElement !== headerOf(pane.root)) release(pane);
|
|
2451
2652
|
}
|
|
@@ -2475,12 +2676,13 @@ function mountFilesPane(document2, report) {
|
|
|
2475
2676
|
}
|
|
2476
2677
|
if (!tab.hasAttribute(TAB_ATTRIBUTE)) tab.setAttribute(TAB_ATTRIBUTE, "");
|
|
2477
2678
|
}
|
|
2478
|
-
const
|
|
2479
|
-
for (const pane of document2.querySelectorAll(panel)) adoptEmpty(pane);
|
|
2679
|
+
for (const pane of document2.querySelectorAll(EMPTY_SELECTOR)) adoptEmpty(pane);
|
|
2480
2680
|
};
|
|
2481
2681
|
let observer;
|
|
2482
2682
|
try {
|
|
2483
|
-
observer = new MutationObserver(
|
|
2683
|
+
observer = new MutationObserver((records) => {
|
|
2684
|
+
if (relevant(records)) sync();
|
|
2685
|
+
});
|
|
2484
2686
|
observer.observe(document2.body, { childList: true, subtree: true });
|
|
2485
2687
|
observer.observe(document2.documentElement, { attributes: true, attributeFilter: ["lang"] });
|
|
2486
2688
|
sync();
|
|
@@ -2644,6 +2846,10 @@ function mountPaneChrome(node) {
|
|
|
2644
2846
|
if (!options.filesRoot && (options.kind !== "terminal" || options.terminals.length < 2)) return;
|
|
2645
2847
|
event.preventDefault();
|
|
2646
2848
|
event.stopImmediatePropagation();
|
|
2849
|
+
if (options.onCloseGroup) {
|
|
2850
|
+
options.onCloseGroup();
|
|
2851
|
+
return;
|
|
2852
|
+
}
|
|
2647
2853
|
const { closeTabs, onClose } = options;
|
|
2648
2854
|
const pending = [...closeTabs];
|
|
2649
2855
|
const step = () => {
|
|
@@ -2794,6 +3000,36 @@ function mountPaneChrome(node) {
|
|
|
2794
3000
|
if (label.textContent !== value) label.textContent = value;
|
|
2795
3001
|
if (label.title !== value) label.title = value;
|
|
2796
3002
|
if (label.previousElementSibling !== source) source.after(label);
|
|
3003
|
+
const markdown = /\.(?:md|markdown)$/iu.test(path);
|
|
3004
|
+
let toggle2 = source.parentElement?.querySelector("[data-ccd-markdown-toggle]");
|
|
3005
|
+
const viewer = node.querySelector("[data-document-viewer-menu]");
|
|
3006
|
+
if (!markdown || !viewer) {
|
|
3007
|
+
toggle2?.remove();
|
|
3008
|
+
continue;
|
|
3009
|
+
}
|
|
3010
|
+
if (!toggle2) {
|
|
3011
|
+
toggle2 = document2.createElement("button");
|
|
3012
|
+
toggle2.type = "button";
|
|
3013
|
+
toggle2.setAttribute("data-ccd-markdown-toggle", "");
|
|
3014
|
+
toggle2.addEventListener("click", () => {
|
|
3015
|
+
const rendered2 = node.querySelector("[data-document-markdown]") !== null;
|
|
3016
|
+
const before = new Set(document2.querySelectorAll('[role="menu"]'));
|
|
3017
|
+
node.querySelector("[data-document-viewer-menu]")?.click();
|
|
3018
|
+
requestAnimationFrame(() => {
|
|
3019
|
+
const menu2 = [...document2.querySelectorAll('[role="menu"]')].find((item) => !before.has(item));
|
|
3020
|
+
const items = [...menu2?.querySelectorAll('[role^="menuitem"]') ?? []];
|
|
3021
|
+
const target = items.find((item) => rendered2 ? /^(?:code|source|代码|源码)$/iu.test(item.textContent?.trim() ?? "") : /^markdown$/iu.test(item.textContent?.trim() ?? ""));
|
|
3022
|
+
target?.click();
|
|
3023
|
+
});
|
|
3024
|
+
});
|
|
3025
|
+
source.parentElement?.append(toggle2);
|
|
3026
|
+
}
|
|
3027
|
+
const rendered = node.querySelector("[data-document-markdown]") !== null;
|
|
3028
|
+
const title2 = rendered ? "Source" : "Preview";
|
|
3029
|
+
if (toggle2.getAttribute("aria-label") === title2) continue;
|
|
3030
|
+
toggle2.title = title2;
|
|
3031
|
+
toggle2.setAttribute("aria-label", title2);
|
|
3032
|
+
toggle2.innerHTML = rendered ? '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="m8 6-6 6 6 6m8-12 6 6-6 6M14 3l-4 18"/></svg>' : '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M2 12s3.5-8 10-8 10 8 10 8-3.5 8-10 8S2 12 2 12Z"/><circle cx="12" cy="12" r="3.5"/></svg>';
|
|
2797
3033
|
}
|
|
2798
3034
|
const reload = controls.querySelector("[data-ccd-preview-reload]");
|
|
2799
3035
|
const original = node.querySelector('[data-textpreview-tool="reload"]');
|
|
@@ -2854,12 +3090,94 @@ function mountPaneChrome(node) {
|
|
|
2854
3090
|
element.append(workspace);
|
|
2855
3091
|
}
|
|
2856
3092
|
};
|
|
3093
|
+
const tabCells = /* @__PURE__ */ new Map();
|
|
3094
|
+
let tabList = null;
|
|
3095
|
+
let nameSpan = null;
|
|
3096
|
+
let nameText = "";
|
|
3097
|
+
const writeTab = (held, tab) => {
|
|
3098
|
+
if (held.title !== tab.title) {
|
|
3099
|
+
if (options.kind === "terminal") terminalText(held.select, tab.title);
|
|
3100
|
+
else held.select.textContent = tab.title;
|
|
3101
|
+
held.title = tab.title;
|
|
3102
|
+
}
|
|
3103
|
+
if (held.select.title !== (tab.path ?? tab.title)) held.select.title = tab.path ?? tab.title;
|
|
3104
|
+
const closeLabel = `${labels.chrome.closeTab} ${tab.title}`;
|
|
3105
|
+
if (held.remove.getAttribute("aria-label") !== closeLabel) {
|
|
3106
|
+
held.remove.setAttribute("aria-label", closeLabel);
|
|
3107
|
+
held.remove.title = closeLabel;
|
|
3108
|
+
}
|
|
3109
|
+
if (held.select.getAttribute("aria-selected") !== String(tab.selected)) {
|
|
3110
|
+
held.select.setAttribute("aria-selected", String(tab.selected));
|
|
3111
|
+
held.select.tabIndex = tab.selected ? 0 : -1;
|
|
3112
|
+
if (tab.selected) requestAnimationFrame(() => {
|
|
3113
|
+
if (held.cell.isConnected) held.cell.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
3114
|
+
});
|
|
3115
|
+
}
|
|
3116
|
+
if (tab.selected) held.cell.setAttribute("data-selected", "");
|
|
3117
|
+
else held.cell.removeAttribute("data-selected");
|
|
3118
|
+
};
|
|
3119
|
+
const tabCell = (tab) => {
|
|
3120
|
+
const kept = tabCells.get(tab.id);
|
|
3121
|
+
if (kept !== void 0) return kept;
|
|
3122
|
+
const cell = document2.createElement("span");
|
|
3123
|
+
cell.setAttribute(options.kind === "terminal" ? "data-ccd-terminal-tab" : "data-ccd-file-tab", "");
|
|
3124
|
+
const select = document2.createElement("button");
|
|
3125
|
+
select.type = "button";
|
|
3126
|
+
select.setAttribute("role", "tab");
|
|
3127
|
+
select.addEventListener("pointerdown", (event) => event.stopPropagation());
|
|
3128
|
+
select.addEventListener("click", () => options.onSelect(tab.id));
|
|
3129
|
+
select.addEventListener("keydown", (event) => {
|
|
3130
|
+
const live = options.kind === "terminal" ? options.terminals : options.files;
|
|
3131
|
+
const index = live.findIndex((item) => item.id === tab.id);
|
|
3132
|
+
if (event.key === "Delete") {
|
|
3133
|
+
event.preventDefault();
|
|
3134
|
+
options.onClose(tab.id);
|
|
3135
|
+
return;
|
|
3136
|
+
}
|
|
3137
|
+
const target = event.key === "ArrowRight" ? (index + 1) % live.length : event.key === "ArrowLeft" ? (index + live.length - 1) % live.length : event.key === "Home" ? 0 : event.key === "End" ? live.length - 1 : -1;
|
|
3138
|
+
if (target < 0) return;
|
|
3139
|
+
event.preventDefault();
|
|
3140
|
+
options.onSelect(live[target].id);
|
|
3141
|
+
requestAnimationFrame(() => {
|
|
3142
|
+
document2.querySelector(`[data-dockkit-content="${live[target].id}"] [role="tab"][aria-selected="true"]`)?.focus();
|
|
3143
|
+
});
|
|
3144
|
+
});
|
|
3145
|
+
cell.append(select);
|
|
3146
|
+
const remove = button(cell, labels.chrome.closeTab, options.kind === "terminal" ? "data-ccd-terminal-close" : "data-ccd-file-close", "X", () => options.onClose(tab.id));
|
|
3147
|
+
remove.tabIndex = -1;
|
|
3148
|
+
const created = { cell, select, remove, title: "" };
|
|
3149
|
+
tabCells.set(tab.id, created);
|
|
3150
|
+
return created;
|
|
3151
|
+
};
|
|
3152
|
+
const updateTabs = (terminals) => {
|
|
3153
|
+
const list = tabList;
|
|
3154
|
+
if (list === null) return;
|
|
3155
|
+
const live = new Set(terminals.map((tab) => tab.id));
|
|
3156
|
+
for (const [id, held] of [...tabCells]) {
|
|
3157
|
+
if (live.has(id)) continue;
|
|
3158
|
+
held.cell.remove();
|
|
3159
|
+
tabCells.delete(id);
|
|
3160
|
+
}
|
|
3161
|
+
terminals.forEach((tab, index) => {
|
|
3162
|
+
const held = tabCell(tab);
|
|
3163
|
+
writeTab(held, tab);
|
|
3164
|
+
if (list.children[index] !== held.cell) list.insertBefore(held.cell, list.children[index] ?? null);
|
|
3165
|
+
});
|
|
3166
|
+
};
|
|
3167
|
+
const updateName = (next) => {
|
|
3168
|
+
if (nameSpan === null || nameText === `${next.kind}|${next.title}`) return;
|
|
3169
|
+
nameText = `${next.kind}|${next.title}`;
|
|
3170
|
+
if (next.kind === "terminal") terminalText(nameSpan, next.title);
|
|
3171
|
+
else nameSpan.textContent = next.title;
|
|
3172
|
+
};
|
|
2857
3173
|
const update = (next) => {
|
|
2858
3174
|
options = next;
|
|
2859
3175
|
labels = paneLabels(paneLanguage(document2));
|
|
2860
3176
|
const hidden = next.filesRoot?.hasAttribute("data-ccd-files-tree") ?? false;
|
|
2861
|
-
const value = `${paneLanguage(document2)}|${next.kind}|${next.
|
|
3177
|
+
const value = `${paneLanguage(document2)}|${next.kind}|${next.zoomed}|${hidden}|${next.filesRoot === null}|${next.terminals.length > 1}|${next.terminals.map((tab) => tab.id).join(",")}|${next.files.map((file) => file.id).join(",")}`;
|
|
2862
3178
|
if (value === signature) {
|
|
3179
|
+
updateTabs(next.kind === "terminal" ? next.terminals : next.files);
|
|
3180
|
+
updateName(next);
|
|
2863
3181
|
refreshNative();
|
|
2864
3182
|
return;
|
|
2865
3183
|
}
|
|
@@ -2869,6 +3187,10 @@ function mountPaneChrome(node) {
|
|
|
2869
3187
|
addressSlot = null;
|
|
2870
3188
|
caption.replaceChildren();
|
|
2871
3189
|
controls.replaceChildren();
|
|
3190
|
+
tabCells.clear();
|
|
3191
|
+
tabList = null;
|
|
3192
|
+
nameSpan = null;
|
|
3193
|
+
nameText = "";
|
|
2872
3194
|
node.setAttribute("data-ccd-pane-kind", next.kind);
|
|
2873
3195
|
if (next.kind === "browser") {
|
|
2874
3196
|
const globe = button(caption, labels.chrome.address, "data-ccd-browser-address", "Globe", address);
|
|
@@ -2892,42 +3214,20 @@ function mountPaneChrome(node) {
|
|
|
2892
3214
|
toggle2.setAttribute("data-ccd-files-mark", "");
|
|
2893
3215
|
toggle2.setAttribute("aria-expanded", String(!hidden));
|
|
2894
3216
|
}
|
|
2895
|
-
if (next.kind === "terminal" && next.terminals.length > 1) {
|
|
3217
|
+
if (next.kind === "terminal" && next.terminals.length > 1 || next.files.length > 0) {
|
|
3218
|
+
const terminal = next.kind === "terminal";
|
|
2896
3219
|
const tabs = document2.createElement("span");
|
|
2897
|
-
tabs.setAttribute("data-ccd-terminal-tabs", "");
|
|
3220
|
+
tabs.setAttribute(terminal ? "data-ccd-terminal-tabs" : "data-ccd-file-tabs", "");
|
|
2898
3221
|
tabs.setAttribute("role", "tablist");
|
|
2899
|
-
tabs.setAttribute("aria-label", labels.chrome.terminals);
|
|
3222
|
+
tabs.setAttribute("aria-label", terminal ? labels.chrome.terminals : labels.files.title);
|
|
2900
3223
|
caption.append(tabs);
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
cell.setAttribute("data-ccd-terminal-tab", "");
|
|
2904
|
-
if (tab.selected) cell.setAttribute("data-selected", "");
|
|
2905
|
-
const select = document2.createElement("button");
|
|
2906
|
-
select.type = "button";
|
|
2907
|
-
terminalText(select, tab.title);
|
|
2908
|
-
select.setAttribute("role", "tab");
|
|
2909
|
-
select.setAttribute("aria-selected", String(tab.selected));
|
|
2910
|
-
select.tabIndex = tab.selected ? 0 : -1;
|
|
2911
|
-
select.addEventListener("pointerdown", (event) => event.stopPropagation());
|
|
2912
|
-
select.addEventListener("click", () => next.onSelect(tab.id));
|
|
2913
|
-
select.addEventListener("keydown", (event) => {
|
|
2914
|
-
const index = next.terminals.indexOf(tab);
|
|
2915
|
-
const target = event.key === "ArrowRight" ? (index + 1) % next.terminals.length : event.key === "ArrowLeft" ? (index + next.terminals.length - 1) % next.terminals.length : event.key === "Home" ? 0 : event.key === "End" ? next.terminals.length - 1 : -1;
|
|
2916
|
-
if (target < 0) return;
|
|
2917
|
-
event.preventDefault();
|
|
2918
|
-
next.onSelect(next.terminals[target].id);
|
|
2919
|
-
document2.querySelector(`[data-dockkit-content="${next.terminals[target].id}"] [role="tab"][aria-selected="true"]`)?.focus();
|
|
2920
|
-
});
|
|
2921
|
-
cell.append(select);
|
|
2922
|
-
const remove = button(cell, `${labels.chrome.closeTab} ${tab.title}`, "data-ccd-terminal-close", "X", () => next.onClose(tab.id));
|
|
2923
|
-
remove.tabIndex = -1;
|
|
2924
|
-
tabs.append(cell);
|
|
2925
|
-
}
|
|
3224
|
+
tabList = tabs;
|
|
3225
|
+
updateTabs(terminal ? next.terminals : next.files);
|
|
2926
3226
|
} else {
|
|
2927
3227
|
const name = document2.createElement("span");
|
|
2928
|
-
if (next.kind === "terminal") terminalText(name, next.title);
|
|
2929
|
-
else name.textContent = next.title;
|
|
2930
3228
|
caption.append(name);
|
|
3229
|
+
nameSpan = name;
|
|
3230
|
+
updateName(next);
|
|
2931
3231
|
}
|
|
2932
3232
|
if (next.kind === "terminal") button(caption, labels.chrome.newTerminal, "data-ccd-terminal-add", "Add", next.onNew);
|
|
2933
3233
|
else if (next.filesRoot || next.kind === "text") button(controls, labels.chrome.search, "data-ccd-pane-search", "Search", () => {
|
|
@@ -2958,6 +3258,7 @@ function mountPaneChrome(node) {
|
|
|
2958
3258
|
for (const attribute of ["data-ccd-pane-chrome", "data-ccd-pane-kind"]) node.removeAttribute(attribute);
|
|
2959
3259
|
clearAddress(form);
|
|
2960
3260
|
for (const [source, label] of paths) {
|
|
3261
|
+
source.parentElement?.querySelector("[data-ccd-markdown-toggle]")?.remove();
|
|
2961
3262
|
source.removeAttribute("data-ccd-preview-path-source");
|
|
2962
3263
|
label.remove();
|
|
2963
3264
|
}
|
|
@@ -3080,6 +3381,7 @@ var TREE_WIDTH = "--ccd-files-tree-width";
|
|
|
3080
3381
|
var FILE_GROUP = "files";
|
|
3081
3382
|
var TERMINAL_GROUP = "terminals";
|
|
3082
3383
|
var EXPANDED = "data-ccd-pane-expanded";
|
|
3384
|
+
var CLOSING = "data-ccd-panes-closing";
|
|
3083
3385
|
function mountRightPanes(ctx, document2, report) {
|
|
3084
3386
|
const registration = ctx.inject(["sidebarRight"], (scope) => {
|
|
3085
3387
|
const sidebar = scope.sidebarRight;
|
|
@@ -3220,6 +3522,17 @@ function createRightPanes(document2, sidebar, report) {
|
|
|
3220
3522
|
}
|
|
3221
3523
|
const record = records.find((record2) => record2.tabId === node.getAttribute("data-dockkit-content"));
|
|
3222
3524
|
const filesRoot = group.tree === void 0 ? null : root.querySelector(`[data-dockkit-content="${group.tree}"] [data-files-root]`);
|
|
3525
|
+
const filepath = (id) => {
|
|
3526
|
+
const source = root.querySelector(`[data-dockkit-content="${id}"] [data-textpreview-path]`);
|
|
3527
|
+
let path = source?.title || records.find((record2) => record2.tabId === id)?.contentId || id;
|
|
3528
|
+
if (path.startsWith("dsh-resource://file/session/")) path = path.replace(/^dsh-resource:\/\/file\/session\/[^/]+/u, "");
|
|
3529
|
+
try {
|
|
3530
|
+
path = decodeURIComponent(path);
|
|
3531
|
+
} catch {
|
|
3532
|
+
}
|
|
3533
|
+
const cwd = filesRoot?.getAttribute("data-files-root");
|
|
3534
|
+
return cwd ? relativeFilePath(path, cwd) : path;
|
|
3535
|
+
};
|
|
3223
3536
|
const filename = (id) => {
|
|
3224
3537
|
let value = records.find((record2) => record2.tabId === id)?.contentId?.split("/").at(-1) ?? id;
|
|
3225
3538
|
try {
|
|
@@ -3248,6 +3561,11 @@ function createRightPanes(document2, sidebar, report) {
|
|
|
3248
3561
|
},
|
|
3249
3562
|
onNew: () => sidebar.openTab("terminal"),
|
|
3250
3563
|
closeTabs: group.tabs,
|
|
3564
|
+
...sidebar.close ? { onCloseGroup: () => {
|
|
3565
|
+
state.requestedFiles = true;
|
|
3566
|
+
for (const id of group.tabs) sidebar.close(id);
|
|
3567
|
+
schedule();
|
|
3568
|
+
} } : {},
|
|
3251
3569
|
onSelect: (id) => {
|
|
3252
3570
|
if (group.id === TERMINAL_GROUP) state.terminal = id;
|
|
3253
3571
|
else state.preview = id;
|
|
@@ -3255,6 +3573,12 @@ function createRightPanes(document2, sidebar, report) {
|
|
|
3255
3573
|
schedule();
|
|
3256
3574
|
},
|
|
3257
3575
|
onClose: (id) => {
|
|
3576
|
+
if (state.preview === id) {
|
|
3577
|
+
const files = group.tabs.filter((tab) => records.some((record2) => record2.tabId === tab && isFile(record2)));
|
|
3578
|
+
const index = files.indexOf(id);
|
|
3579
|
+
state.preview = files[index + 1] ?? files[index - 1] ?? null;
|
|
3580
|
+
if (state.preview !== null) sidebar.focus(state.preview);
|
|
3581
|
+
}
|
|
3258
3582
|
state.requestedFiles = true;
|
|
3259
3583
|
const close = root.querySelector(`[data-dockkit-content="${id}"] [data-dockkit-float-close]`);
|
|
3260
3584
|
if (close) {
|
|
@@ -3264,7 +3588,7 @@ function createRightPanes(document2, sidebar, report) {
|
|
|
3264
3588
|
}
|
|
3265
3589
|
},
|
|
3266
3590
|
terminals: group.id === TERMINAL_GROUP ? group.tabs.map((id, index) => ({ id, title: group.tabs.length > 1 ? `${labels.chrome.terminal} ${index + 1}${workspace ? ` ${workspace}` : ""}` : title, selected: id === group.terminal })) : [],
|
|
3267
|
-
files: group.tabs.filter((id) => id !== group.tree && records.find((record2) => record2.tabId === id)?.kind === "text").map((id) => ({ id, title: filename(id), selected: id === group.preview }))
|
|
3591
|
+
files: group.tabs.filter((id) => id !== group.tree && records.find((record2) => record2.tabId === id)?.kind === "text").map((id) => ({ id, title: filename(id), path: filepath(id), selected: id === group.preview }))
|
|
3268
3592
|
});
|
|
3269
3593
|
};
|
|
3270
3594
|
const placeSeams = (root, boundaries) => {
|
|
@@ -3301,6 +3625,13 @@ function createRightPanes(document2, sidebar, report) {
|
|
|
3301
3625
|
const panel = panelOnScreen();
|
|
3302
3626
|
const root = panel?.querySelector(host.dockSurface) ?? null;
|
|
3303
3627
|
const session = panel === null ? null : sessionOf2(panel);
|
|
3628
|
+
const records = (sidebar.openTabs?.getSnapshot?.() ?? []).filter((record) => record.sessionId === session);
|
|
3629
|
+
for (const button of document2.querySelectorAll("[data-ccd-header-view]")) {
|
|
3630
|
+
const kind = button.getAttribute("data-ccd-header-view");
|
|
3631
|
+
const headerSession = button.closest(host.conversationRoot)?.querySelector("[data-conversation-session]")?.getAttribute("data-conversation-session");
|
|
3632
|
+
const shown = panel?.hasAttribute("data-sidebar-right-open") === true && panel.closest("[data-rightbar-collapsed]") === null && headerSession === session && records.some((record) => record.kind === kind);
|
|
3633
|
+
setAttribute(button, "aria-pressed", String(shown));
|
|
3634
|
+
}
|
|
3304
3635
|
if (root !== observed) {
|
|
3305
3636
|
clear2();
|
|
3306
3637
|
resize?.disconnect();
|
|
@@ -3315,8 +3646,8 @@ function createRightPanes(document2, sidebar, report) {
|
|
|
3315
3646
|
}
|
|
3316
3647
|
}
|
|
3317
3648
|
if (root === null || panel === null || session === null) return;
|
|
3649
|
+
if (!panel.hasAttribute("data-sidebar-right-open")) panel.removeAttribute(CLOSING);
|
|
3318
3650
|
const state = stateOf(session);
|
|
3319
|
-
const records = (sidebar.openTabs?.getSnapshot?.() ?? []).filter((record) => record.sessionId === session);
|
|
3320
3651
|
const active = root.querySelector("[data-dockkit-float-active]")?.getAttribute("data-dockkit-content") ?? null;
|
|
3321
3652
|
const previews = records.filter(isFile).map((record) => record.tabId);
|
|
3322
3653
|
const opened = state.seeded && records.length >= state.records && previews.some((id) => !state.previews.has(id));
|
|
@@ -3340,6 +3671,7 @@ function createRightPanes(document2, sidebar, report) {
|
|
|
3340
3671
|
if (groups.length === 0) {
|
|
3341
3672
|
clear2();
|
|
3342
3673
|
if (state.hadViews && panel.hasAttribute("data-sidebar-right-open")) {
|
|
3674
|
+
setAttribute(panel, CLOSING, "");
|
|
3343
3675
|
window.setTimeout(() => {
|
|
3344
3676
|
if (disposed || !panel.isConnected || !panel.hasAttribute("data-sidebar-right-open")) return;
|
|
3345
3677
|
const live = (sidebar.openTabs?.getSnapshot?.() ?? []).filter((record) => record.sessionId === session);
|
|
@@ -3355,6 +3687,7 @@ function createRightPanes(document2, sidebar, report) {
|
|
|
3355
3687
|
state.requestedFiles = false;
|
|
3356
3688
|
return;
|
|
3357
3689
|
}
|
|
3690
|
+
panel.removeAttribute(CLOSING);
|
|
3358
3691
|
state.hadViews = true;
|
|
3359
3692
|
pairPreviewWithTree(session, panel, state, records, previews, opened);
|
|
3360
3693
|
const box = root.getBoundingClientRect();
|
|
@@ -3437,7 +3770,7 @@ function createRightPanes(document2, sidebar, report) {
|
|
|
3437
3770
|
setProperty(filesRoot, TREE_WIDTH, `${width}px`);
|
|
3438
3771
|
owned.add(filesRoot);
|
|
3439
3772
|
}
|
|
3440
|
-
if (!treeNode) decorate(node, group, root, state, records);
|
|
3773
|
+
if (!treeNode && !inactive) decorate(node, group, root, state, records);
|
|
3441
3774
|
else {
|
|
3442
3775
|
controls.get(node)?.release();
|
|
3443
3776
|
controls.delete(node);
|
|
@@ -3507,6 +3840,19 @@ function createRightPanes(document2, sidebar, report) {
|
|
|
3507
3840
|
const finishPointer = (event) => {
|
|
3508
3841
|
if (dragging?.pointer === event.pointerId) endDrag();
|
|
3509
3842
|
};
|
|
3843
|
+
const onHeaderClick = (event) => {
|
|
3844
|
+
const button = event.target instanceof Element ? event.target.closest('[data-ccd-header-view][aria-pressed="true"]') : null;
|
|
3845
|
+
if (button === null) return;
|
|
3846
|
+
const panel = panelOnScreen();
|
|
3847
|
+
const kind = button.getAttribute("data-ccd-header-view");
|
|
3848
|
+
const nodes = panel?.querySelectorAll("[data-ccd-pane-chrome]:not([data-ccd-pane-inactive])") ?? [];
|
|
3849
|
+
const pane = [...nodes].find((node) => node.getAttribute("data-ccd-pane-kind") === kind || kind === "files" && node.querySelector("[data-ccd-files-switch]") !== null);
|
|
3850
|
+
const close = pane?.querySelector("[data-dockkit-float-close]");
|
|
3851
|
+
if (!close) return;
|
|
3852
|
+
event.preventDefault();
|
|
3853
|
+
event.stopImmediatePropagation();
|
|
3854
|
+
close.click();
|
|
3855
|
+
};
|
|
3510
3856
|
const onPointerDown = (event) => {
|
|
3511
3857
|
if (event.button !== 0) return;
|
|
3512
3858
|
if (event.target instanceof Element && event.target.closest("[data-ccd-files-switch]")) return;
|
|
@@ -3571,11 +3917,14 @@ function createRightPanes(document2, sidebar, report) {
|
|
|
3571
3917
|
resize?.disconnect();
|
|
3572
3918
|
unsubscribe();
|
|
3573
3919
|
document2.removeEventListener("pointerdown", onPointerDown, true);
|
|
3920
|
+
document2.removeEventListener("click", onHeaderClick, true);
|
|
3574
3921
|
document2.removeEventListener("keydown", onKeyDown, true);
|
|
3575
3922
|
window.removeEventListener("resize", schedule);
|
|
3576
3923
|
const panel = panelOnScreen();
|
|
3577
3924
|
const session = panel === null ? null : sessionOf2(panel);
|
|
3578
3925
|
clear2();
|
|
3926
|
+
for (const button of document2.querySelectorAll("[data-ccd-header-view]")) button.removeAttribute("aria-pressed");
|
|
3927
|
+
for (const node of document2.querySelectorAll(`[${CLOSING}]`)) node.removeAttribute(CLOSING);
|
|
3579
3928
|
if (panel !== null && session !== null) {
|
|
3580
3929
|
for (const tab of floated.get(session) ?? []) {
|
|
3581
3930
|
const node = [...panel.querySelectorAll(host.dockFloat)].find((node2) => node2.getAttribute("data-dockkit-content") === tab);
|
|
@@ -3596,6 +3945,8 @@ function createRightPanes(document2, sidebar, report) {
|
|
|
3596
3945
|
if (mutation.type === "attributes" && mutation.attributeName === "lang" && mutation.target === document2.documentElement) return true;
|
|
3597
3946
|
const target = mutation.target instanceof Element ? mutation.target : mutation.target.parentElement;
|
|
3598
3947
|
if (target === null) return false;
|
|
3948
|
+
if (mutation.type === "attributes" && (mutation.attributeName === "data-rightbar-collapsed" || mutation.attributeName === "data-conversation-session")) return true;
|
|
3949
|
+
if (mutation.type === "childList" && [...mutation.addedNodes].some((node) => node instanceof Element && (node.matches("[data-ccd-header-view]") || node.querySelector("[data-ccd-header-view]") !== null))) return true;
|
|
3599
3950
|
if (mutation.type === "attributes" && mutation.attributeName === "hidden") return target.matches(host.rightbarSession);
|
|
3600
3951
|
if (target.closest(host.rightbarPanel) !== null) {
|
|
3601
3952
|
if (target.closest(host.dockFloatGrip) !== null) return true;
|
|
@@ -3613,6 +3964,8 @@ function createRightPanes(document2, sidebar, report) {
|
|
|
3613
3964
|
attributes: true,
|
|
3614
3965
|
attributeFilter: [
|
|
3615
3966
|
"hidden",
|
|
3967
|
+
"data-conversation-session",
|
|
3968
|
+
"data-rightbar-collapsed",
|
|
3616
3969
|
"data-sidebar-right-open",
|
|
3617
3970
|
"data-sidebar-right-panel",
|
|
3618
3971
|
"data-sidebar-right-session",
|
|
@@ -3629,6 +3982,7 @@ function createRightPanes(document2, sidebar, report) {
|
|
|
3629
3982
|
unsubscribe = sidebar.openTabs?.subscribe?.(schedule) ?? (() => {
|
|
3630
3983
|
});
|
|
3631
3984
|
document2.addEventListener("pointerdown", onPointerDown, true);
|
|
3985
|
+
document2.addEventListener("click", onHeaderClick, true);
|
|
3632
3986
|
document2.addEventListener("keydown", onKeyDown, true);
|
|
3633
3987
|
window.addEventListener("resize", schedule);
|
|
3634
3988
|
schedule();
|
|
@@ -3685,27 +4039,34 @@ function mountOpenTargetMode(document2, report) {
|
|
|
3685
4039
|
};
|
|
3686
4040
|
const sync = () => {
|
|
3687
4041
|
const copy = headerLabels(document2);
|
|
4042
|
+
const live = /* @__PURE__ */ new Set();
|
|
3688
4043
|
for (const anchor of document2.querySelectorAll(
|
|
3689
4044
|
`${host.conversationHeader} ${host.openTargetAnchor}`
|
|
3690
4045
|
)) {
|
|
4046
|
+
live.add(anchor);
|
|
3691
4047
|
const chevron = anchor.querySelector(host.openTargetChevron);
|
|
3692
|
-
const
|
|
4048
|
+
const main = anchor.querySelector(host.openTargetMain);
|
|
4049
|
+
let previous = annotations.get(anchor);
|
|
4050
|
+
if (previous !== void 0 && (previous.chevron !== chevron || previous.main !== main)) {
|
|
4051
|
+
restore(previous);
|
|
4052
|
+
annotations.delete(anchor);
|
|
4053
|
+
previous = void 0;
|
|
4054
|
+
}
|
|
3693
4055
|
marked.add(anchor);
|
|
3694
4056
|
if (chevron === null) {
|
|
3695
|
-
anchor.setAttribute(MODE_ATTRIBUTE, "direct");
|
|
4057
|
+
if (anchor.getAttribute(MODE_ATTRIBUTE) !== "direct") anchor.setAttribute(MODE_ATTRIBUTE, "direct");
|
|
3696
4058
|
if (previous !== void 0) {
|
|
3697
4059
|
annotations.delete(anchor);
|
|
3698
4060
|
restore(previous);
|
|
3699
4061
|
}
|
|
3700
4062
|
continue;
|
|
3701
4063
|
}
|
|
3702
|
-
anchor.setAttribute(MODE_ATTRIBUTE, "menu");
|
|
4064
|
+
if (anchor.getAttribute(MODE_ATTRIBUTE) !== "menu") anchor.setAttribute(MODE_ATTRIBUTE, "menu");
|
|
3703
4065
|
if (previous !== void 0) {
|
|
3704
4066
|
if (chevron.getAttribute("aria-label") !== copy.open) chevron.setAttribute("aria-label", copy.open);
|
|
3705
4067
|
if (chevron.getAttribute("title") !== copy.open) chevron.setAttribute("title", copy.open);
|
|
3706
4068
|
continue;
|
|
3707
4069
|
}
|
|
3708
|
-
const main = anchor.querySelector(host.openTargetMain);
|
|
3709
4070
|
annotations.set(anchor, {
|
|
3710
4071
|
chevron,
|
|
3711
4072
|
main,
|
|
@@ -3719,11 +4080,15 @@ function mountOpenTargetMode(document2, report) {
|
|
|
3719
4080
|
main?.setAttribute("aria-hidden", "true");
|
|
3720
4081
|
main?.setAttribute("tabindex", "-1");
|
|
3721
4082
|
}
|
|
3722
|
-
for (const
|
|
3723
|
-
if (anchor
|
|
3724
|
-
|
|
4083
|
+
for (const anchor of marked) {
|
|
4084
|
+
if (live.has(anchor)) continue;
|
|
4085
|
+
anchor.removeAttribute(MODE_ATTRIBUTE);
|
|
4086
|
+
const entry = annotations.get(anchor);
|
|
4087
|
+
if (entry !== void 0) {
|
|
4088
|
+
restore(entry);
|
|
4089
|
+
annotations.delete(anchor);
|
|
4090
|
+
}
|
|
3725
4091
|
marked.delete(anchor);
|
|
3726
|
-
restore(entry);
|
|
3727
4092
|
}
|
|
3728
4093
|
};
|
|
3729
4094
|
const observer = new MutationObserver(() => {
|
|
@@ -3787,13 +4152,8 @@ function headerMenuActions(ctx) {
|
|
|
3787
4152
|
|
|
3788
4153
|
// src/client/compat/header-menus.ts
|
|
3789
4154
|
var CHEVRON = '<svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m3.5 6 4.5 4.5L12.5 6"/></svg>';
|
|
3790
|
-
var LAPTOP = '<svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.
|
|
4155
|
+
var LAPTOP = '<svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 10.5v-6A1.5 1.5 0 0 1 4.5 3h7A1.5 1.5 0 0 1 13 4.5v6"/><rect x="1.5" y="10.5" width="13" height="2.5" rx=".65"/></svg>';
|
|
3791
4156
|
var HEADER_MENU_ICONS = {
|
|
3792
|
-
"\u5728\u8BBF\u8FBE\u4E2D\u663E\u793A": '<path d="M1.5 5V3.5h5l1.5 2h6.5v8h-13V5Z"/>',
|
|
3793
|
-
"\u590D\u5236": '<rect x="5.5" y="1.5" width="8" height="10" rx="1"/><path d="M10.5 11.5v3h-8v-10h3"/>',
|
|
3794
|
-
"\u5728\u7EC8\u7AEF\u4E2D\u6253\u5F00": '<rect x="1.5" y="2.5" width="13" height="11" rx="1.5"/><path d="m4 6 2 2-2 2M8.5 10h3"/>',
|
|
3795
|
-
"\u6587\u4EF6\u5939\u8DEF\u5F84": '<path d="m6.5 5 1-1a3 3 0 0 1 4.25 4.25l-1 1M9.5 11l-1 1A3 3 0 0 1 4.25 7.75l1-1M6 10l4-4"/>',
|
|
3796
|
-
"\u6587\u4EF6\u5939\u540D\u79F0": '<path d="M2 4.5V3h4l1.5 2H14v8H2V4.5ZM5 8h6M5 10.5h4"/>',
|
|
3797
4157
|
"\u6253\u5F00\u65B9\u5F0F": '<rect x="5" y="2" width="9" height="9" rx="1"/><path d="M11 11v3H2V5h3"/>',
|
|
3798
4158
|
"\u91CD\u547D\u540D": '<path d="M3 10.5 2.5 14l3.5-.5L14 5.5 10.5 2 3 10.5ZM9 3.5l3.5 3.5"/>',
|
|
3799
4159
|
"\u521B\u5EFA\u5206\u652F": '<circle cx="5" cy="3" r="1.5"/><circle cx="11" cy="4" r="1.5"/><circle cx="5" cy="13" r="1.5"/><path d="M5 4.5v7M11 5.5V7a3 3 0 0 1-3 3H5"/>',
|
|
@@ -3814,6 +4174,7 @@ function mountHeaderMenus(document2, actions, report) {
|
|
|
3814
4174
|
let dialog = null;
|
|
3815
4175
|
let editing = null;
|
|
3816
4176
|
const seats = /* @__PURE__ */ new Map();
|
|
4177
|
+
let headers = [];
|
|
3817
4178
|
const button = (label) => {
|
|
3818
4179
|
const node = document2.createElement("button");
|
|
3819
4180
|
node.type = "button";
|
|
@@ -4038,18 +4399,17 @@ function mountHeaderMenus(document2, actions, report) {
|
|
|
4038
4399
|
const deletion = unavailable(menu, "\u5220\u9664", "DSH \u6682\u672A\u63D0\u4F9B\u5220\u9664\u4F1A\u8BDD\u7684\u516C\u5F00\u63A5\u53E3\uFF1B\u53EF\u4F7F\u7528\u5F52\u6863\uFF0C\u6B64\u9879\u4EC5\u8FD8\u539F\u5916\u89C2\u3002", "D");
|
|
4039
4400
|
deletion.setAttribute("data-ccd-menu-destructive", "");
|
|
4040
4401
|
} else {
|
|
4041
|
-
unavailable(menu, "\u5728\u8BBF\u8FBE\u4E2D\u663E\u793A", "DSH \u6682\u672A\u63D0\u4F9B\u5728\u8BBF\u8FBE\uFF0F\u8D44\u6E90\u7BA1\u7406\u5668\u4E2D\u663E\u793A\u76EE\u5F55\u7684\u516C\u5F00\u63A5\u53E3\uFF0C\u6B64\u9879\u4EC5\u8FD8\u539F\u5916\u89C2\u3002");
|
|
4042
|
-
separator(menu);
|
|
4043
4402
|
sub(menu, "\u590D\u5236", (content) => {
|
|
4044
4403
|
const copy = (value) => run(() => window2.navigator.clipboard.writeText(value));
|
|
4045
4404
|
const current = actions.read(id);
|
|
4046
4405
|
row(content, "\u6587\u4EF6\u5939\u8DEF\u5F84", () => copy(current?.path ?? ""));
|
|
4047
4406
|
row(content, "\u6587\u4EF6\u5939\u540D\u79F0", () => copy(current?.folder ?? ""));
|
|
4048
4407
|
});
|
|
4049
|
-
separator(menu);
|
|
4050
4408
|
const terminal = seat.header.querySelector('[data-ccd-header-view="terminal"]');
|
|
4051
|
-
if (terminal !== null
|
|
4052
|
-
|
|
4409
|
+
if (terminal !== null && !terminal.disabled) {
|
|
4410
|
+
separator(menu);
|
|
4411
|
+
row(menu, "\u5728\u7EC8\u7AEF\u4E2D\u6253\u5F00", () => run(() => terminal.click()));
|
|
4412
|
+
}
|
|
4053
4413
|
}
|
|
4054
4414
|
for (const item of menu.querySelectorAll("button:not([aria-haspopup])")) item.addEventListener("pointerenter", clearSubmenu);
|
|
4055
4415
|
position();
|
|
@@ -4058,6 +4418,7 @@ function mountHeaderMenus(document2, actions, report) {
|
|
|
4058
4418
|
const releaseSeat = (seat) => {
|
|
4059
4419
|
if (editing?.seat === seat) editing.finish();
|
|
4060
4420
|
if (owner === seat) close();
|
|
4421
|
+
seat.releaseTitle();
|
|
4061
4422
|
seat.icon.remove();
|
|
4062
4423
|
seat.trigger.remove();
|
|
4063
4424
|
seat.project.remove();
|
|
@@ -4068,7 +4429,8 @@ function mountHeaderMenus(document2, actions, report) {
|
|
|
4068
4429
|
releaseSeat(seat);
|
|
4069
4430
|
seats.delete(header);
|
|
4070
4431
|
}
|
|
4071
|
-
|
|
4432
|
+
headers = [...document2.querySelectorAll(host.conversationHeader)];
|
|
4433
|
+
for (const header of headers) {
|
|
4072
4434
|
const root = header.closest(host.conversationRoot);
|
|
4073
4435
|
const id = root?.querySelector("[data-conversation-session]")?.getAttribute("data-conversation-session");
|
|
4074
4436
|
const data = id ? actions.read(id) : null;
|
|
@@ -4096,8 +4458,36 @@ function mountHeaderMenus(document2, actions, report) {
|
|
|
4096
4458
|
project.setAttribute("data-ccd-project-trigger", "");
|
|
4097
4459
|
project.setAttribute("aria-haspopup", "menu");
|
|
4098
4460
|
project.setAttribute("aria-expanded", "false");
|
|
4099
|
-
seat = { header, nav, icon, trigger, project, id
|
|
4461
|
+
seat = { header, nav, icon, trigger, project, id, releaseTitle: () => {
|
|
4462
|
+
} };
|
|
4100
4463
|
const current = seat;
|
|
4464
|
+
const title = [...nav.querySelectorAll(host.conversationCrumb)].at(-1);
|
|
4465
|
+
if (title) {
|
|
4466
|
+
const attributes = ["role", "tabindex", "title"];
|
|
4467
|
+
const original = attributes.map((name) => title.getAttribute(name));
|
|
4468
|
+
title.setAttribute("role", "button");
|
|
4469
|
+
title.setAttribute("tabindex", "0");
|
|
4470
|
+
title.setAttribute("title", "\u91CD\u547D\u540D\u4F1A\u8BDD");
|
|
4471
|
+
const activate = (event) => {
|
|
4472
|
+
event.preventDefault();
|
|
4473
|
+
event.stopPropagation();
|
|
4474
|
+
if (editing?.seat !== current) rename(current, current.id);
|
|
4475
|
+
};
|
|
4476
|
+
const key = (event) => {
|
|
4477
|
+
if (event.key === "Enter" || event.key === " ") activate(event);
|
|
4478
|
+
};
|
|
4479
|
+
title.addEventListener("click", activate, true);
|
|
4480
|
+
title.addEventListener("keydown", key, true);
|
|
4481
|
+
current.releaseTitle = () => {
|
|
4482
|
+
title.removeEventListener("click", activate, true);
|
|
4483
|
+
title.removeEventListener("keydown", key, true);
|
|
4484
|
+
attributes.forEach((name, index) => {
|
|
4485
|
+
const value = original[index];
|
|
4486
|
+
if (value == null) title.removeAttribute(name);
|
|
4487
|
+
else title.setAttribute(name, value);
|
|
4488
|
+
});
|
|
4489
|
+
};
|
|
4490
|
+
}
|
|
4101
4491
|
trigger.addEventListener("click", (event) => show(current, trigger, "session", event.detail === 0));
|
|
4102
4492
|
project.addEventListener("click", (event) => show(current, project, "project", event.detail === 0));
|
|
4103
4493
|
nav.setAttribute("data-ccd-session-title", "");
|
|
@@ -4114,7 +4504,7 @@ function mountHeaderMenus(document2, actions, report) {
|
|
|
4114
4504
|
if (editing?.seat === seat && !editing.input.isConnected) editing.finish();
|
|
4115
4505
|
if (seat.project.textContent !== data.folder) seat.project.textContent = data.folder;
|
|
4116
4506
|
if (seat.project.title !== data.path) seat.project.title = data.path;
|
|
4117
|
-
seat.project.hidden = data.path === "";
|
|
4507
|
+
if (seat.project.hidden !== (data.path === "")) seat.project.hidden = data.path === "";
|
|
4118
4508
|
}
|
|
4119
4509
|
};
|
|
4120
4510
|
const safelySync = () => {
|
|
@@ -4173,7 +4563,9 @@ function mountHeaderMenus(document2, actions, report) {
|
|
|
4173
4563
|
shortcut.click();
|
|
4174
4564
|
}
|
|
4175
4565
|
};
|
|
4176
|
-
const observer = new MutationObserver(
|
|
4566
|
+
const observer = new MutationObserver((records) => {
|
|
4567
|
+
if (records.some((record) => record.type === "attributes" && record.attributeName === "data-conversation-session") || headers.some((header) => recordsTouch(records, header)) || recordsIntroduce(records, host.conversationHeader)) safelySync();
|
|
4568
|
+
});
|
|
4177
4569
|
observer.observe(document2.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ["data-conversation-session"] });
|
|
4178
4570
|
const unsubscribe = actions.subscribe(safelySync);
|
|
4179
4571
|
document2.addEventListener("pointerdown", pointer, true);
|
|
@@ -4190,6 +4582,7 @@ function mountHeaderMenus(document2, actions, report) {
|
|
|
4190
4582
|
dialog = null;
|
|
4191
4583
|
for (const seat of seats.values()) releaseSeat(seat);
|
|
4192
4584
|
seats.clear();
|
|
4585
|
+
headers = [];
|
|
4193
4586
|
document2.removeEventListener("pointerdown", pointer, true);
|
|
4194
4587
|
document2.removeEventListener("keydown", keydown, true);
|
|
4195
4588
|
window2.removeEventListener("resize", position);
|
|
@@ -4434,7 +4827,7 @@ function resolveThemeTokens(config) {
|
|
|
4434
4827
|
}
|
|
4435
4828
|
|
|
4436
4829
|
// src/client/theme/composer.css
|
|
4437
|
-
var composer_default = '/*\n * Shared composer geometry.\n *\n * Both the blank-session page and the conversation page must place the official\n * Composer at the same width and shape, so the geometry lives here instead of\n * being duplicated in two features (siblings never import each other). The\n * upstream structure is kept: the resident Composer card stays the single\n * editable root, and only where its parts sit changes.\n *\n * Reference geometry (@2x reference screenshots, logical px at 1374x871):\n * - input surface: 770x42, 1px border, radius 10, text line 24px at 8/14 padding\n * - the send control is the return arrow *inside* that surface, 24px, 14px in\n * from the right border and anchored to the bottom text line\n * - control row: outside the surface, 4px below it, ~28px tall\n * - window edge: 8px below the row\n *\n * Host classes are pinned in src/client/compat/host-dom.ts.\n */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_body {\n --dsh-chat-content-width: var(--ccd-composer-width);\n --dsh-composer-card-max-width: var(--ccd-composer-width);\n --dsh-composer-side-clearance: 16px;\n}\n\n/* The card becomes a transparent column: the input surface draws the border. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_card {\n --ccd-composer-add-reserve: calc(var(--ccd-send-size) + var(--ccd-cluster-gap));\n gap: 0;\n padding-top: 0;\n border: 0;\n border-radius: 0;\n background: transparent;\n box-shadow: none;\n}\n\n/* Capture mode hides the native tools group, including the plus. Let its text\n reclaim the leading inset without changing the voice seat or its layout. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_card:has(.yhfFVG_tools[hidden]) {\n --ccd-composer-add-reserve: 0px;\n}\n\n/* Without a workspace the host makes the whole card a workspace picker and\n draws a dashed ::after around it. Move that state cue to the input surface\n that owns our border, while keeping the host\'s picker behavior. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_cardWorkspaceTrigger::after {\n content: none;\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_scroll {\n box-sizing: border-box;\n min-height: var(--ccd-composer-min-height);\n margin-right: 0;\n padding: var(--ccd-composer-padding);\n border: 1px solid var(--ccd-border-strong);\n border-radius: var(--ccd-composer-radius);\n background: var(--ccd-card);\n box-shadow: var(--ccd-shadow-card);\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_cardWorkspaceTrigger .yhfFVG_scroll {\n border-style: dashed;\n}\n\n/* The editor owns the focus cue. Model/effort menus live in this card too,\n so :focus-within would leave the ring on while a menu has keyboard focus.\n Darken the existing 1px border without changing the surface geometry. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_card:has(.yhfFVG_input:focus) .yhfFVG_scroll {\n border-color: var(--ccd-composer-focus-border);\n}\n\n/* Attachments: the `ui-attachment` plugin renders the draft-image rail as a\n sibling of the input panel inside the card (`conversation.input.attachments`\n slot, class pinned in src/client/compat/host-dom.ts \u2014 ComposerAttachments\n .module.css, `dVdiKa_`). With the card transparent the previews would float\n on the page above the surface, and the send control, pinned to the card\'s\n top-right corner, would leave the surface with them. When the rail is\n present the two merge back into one surface: the rail draws the top cap and\n the panel drops its own, while the card\'s zero gap keeps the 1px side\n borders touching so no seam shows. The host\'s unstyled card pulled the rail\n 6px into the panel (`margin-bottom: -6px`); that belongs to the old card and\n is dropped. The send control stays beside the bottom text line, so the rail\n needs only the same side padding as the input. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_card:has(.dVdiKa_rail) .dVdiKa_rail {\n box-sizing: border-box;\n margin-bottom: 0;\n padding: 10px var(--ccd-composer-padding-inline) 0;\n border: 1px solid var(--ccd-border-strong);\n border-bottom: 0;\n border-radius: var(--ccd-composer-radius) var(--ccd-composer-radius) 0 0;\n background: var(--ccd-card);\n box-shadow: var(--ccd-shadow-card);\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_card:has(.dVdiKa_rail) .yhfFVG_scroll {\n border-top: 0;\n border-radius: 0 0 var(--ccd-composer-radius) var(--ccd-composer-radius);\n}\n\n/* The attachment cap shares the editor\'s focused border colour. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_card:has(.yhfFVG_input:focus) .dVdiKa_rail {\n border-color: var(--ccd-composer-focus-border);\n}\n\n/* Match the image tile to the outer editable surface, including its clipping. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_card .dVdiKa_thumbnail {\n border-radius: var(--ccd-composer-radius);\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_input,\nhtml[data-dsh-ccd-style="true"] .yhfFVG_hero .yhfFVG_input {\n box-sizing: border-box;\n min-height: 24px;\n /* Keep text clear of the controls, including when the editor scrolls. */\n padding: 0 var(--ccd-send-reserve) 0 var(--ccd-composer-add-reserve);\n font-family: var(--ccd-font-ui);\n font-size: var(--dsh-content-font-size, 14px);\n line-height: 24px;\n color: var(--ccd-text);\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_placeholder {\n /* Hero copy is longer than chat copy. Limit only the placeholder, leaving\n the actual editor\'s wrapping and multiline growth untouched. */\n inset: 0 0 auto 0;\n box-sizing: border-box;\n padding-right: var(--ccd-send-reserve);\n padding-left: var(--ccd-composer-add-reserve);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: var(--ccd-text-placeholder);\n font-size: var(--dsh-content-font-size, 14px);\n line-height: 24px;\n}\n\n/* Send / stop stay beside the input\'s bottom line. The row is their containing\n block: its top follows the input\'s bottom even if the row itself wraps.\n The host still owns both buttons, their state, labels and click handlers. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_primary {\n /* The user agent\'s `1px 6px` button padding is asymmetric against a 24px box\n only in the sense that it steals the content box; drop it and centre. */\n padding: 0;\n position: absolute;\n top: auto;\n bottom: calc(100% + var(--ccd-composer-row-gap) + var(--ccd-composer-padding-block) + 1px);\n right: var(--ccd-composer-padding-inline);\n width: var(--ccd-send-size);\n height: var(--ccd-send-size);\n border-radius: var(--ccd-send-radius);\n background: transparent;\n color: var(--ccd-text-nav);\n transform: none;\n transition: background-color 0.1s, color 0.1s;\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_primary:hover:not(:disabled) {\n background: var(--ccd-hover);\n color: var(--ccd-text);\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_primary:disabled {\n background: transparent;\n opacity: 0.4;\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_primary svg {\n display: none;\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_primary::before {\n content: "";\n width: 16px;\n height: 16px;\n background-color: currentColor;\n mask: var(--ccd-icon-send) center / 16px 16px no-repeat;\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_primary:has(rect)::before {\n mask-image: var(--ccd-icon-stop);\n}\n\n/* DSH exposes both stop and send when a new draft is entered while running. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_row .yhfFVG_primary:has(+ .yhfFVG_primary) {\n right: calc(var(--ccd-composer-padding-inline) + var(--ccd-send-size) + 6px);\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_card:has(.yhfFVG_primary + .yhfFVG_primary) .yhfFVG_input {\n padding-right: calc(var(--ccd-send-reserve) + var(--ccd-send-size) + 6px);\n}\n\n/* Tool row: outside the surface, directly below it, and one rhythm end to end.\n The row\'s own gap is the host\'s 12px, which the leading groups kept while the\n trailing cluster was already on `--ccd-cluster-gap`; the rule below hands the\n same 6px to every container in the bar, so the leading pair, the trailing\n cluster, the statistics dock and the ring read as one row.\n\n One line, at every width. The host\'s row wraps\n (`InputBar.module.css`: `.yhfFVG_row { flex-wrap: wrap }`) and breaks before\n it shrinks anything: a flex container decides its lines from the children\'s\n *base* sizes, so a column too narrow for the leading pair and the whole\n trailing cluster put \u2014 and the cluster lands on a second line of its own, a\n row lower, while the model chip that could have given up the space is still\n drawn at its cap. The reference draws the bar as one line\n at every window width (its `DeepSeek V4.1 Fl\u2026` is the model name clipped by\n the chip\'s own ellipsis, not a wrapped row), and the Composer\'s bottom inset\n is measured for that one line: two lines push the row over the window edge.\n So the wrap goes and the squeeze lands where the reference puts it \u2014 the\n model name, whose chip is the one item here that can shorten without\n losing its reading (`min-width: 0` and the host\'s own ellipsis on\n `.cl2Rlq_triggerLabel`), while the leading controls keep their boxes\n (`.yhfFVG_tools` below), the effort keeps its `flex-shrink: 0` and the ring\n keeps its reserved seat. Standing in the way of that squeeze is the trailing\n group\'s own `flex: none` (the host\'s, under `.yhfFVG_trailing`): the group is\n sized from the model chip\'s budget, and the budget\n (`compat/stats-values.ts` publishes the row\'s measured remainder as\n `--ccd-model-max-width`) is what the row has *left over* \u2014 not what the chip\n may take from a line that also carries the leading pair. Left unshrinkable\n the cluster would simply run past the Composer\'s right edge with the chip at\n its cap; allowed to shrink, the chip settles on what is actually there and\n ellipsizes, which is the one reading the reference is willing to shorten. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_row {\n position: relative;\n flex-wrap: nowrap;\n gap: var(--ccd-cluster-gap);\n min-height: var(--ccd-control-row-height);\n margin-top: var(--ccd-composer-row-gap);\n padding: 0 var(--ccd-composer-row-inset);\n color: var(--ccd-text-secondary);\n font-size: var(--ccd-font-size-muted);\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_row button {\n color: var(--ccd-text-secondary);\n}\n\n/* The model selector\'s two texts \u2014 the model name and the reasoning effort \u2014\n are the row\'s chosen values, not chrome, so they carry the chosen-value ink\n instead of the row\'s label grey; the host\'s own `font-weight: 400` stays.\n Each text is its own span, so the colour lands on the spans rather than on\n the button the rule above paints, and this plugin\'s own model/effort pair\n (features/model-controls) follows the same token. Host classes are pinned in\n src/client/compat/host-dom.ts (`ui-model-selection`\'s\n ModelSelect.module.css, `cl2Rlq_`). */\nhtml[data-dsh-ccd-style="true"] .cl2Rlq_triggerLabel,\nhtml[data-dsh-ccd-style="true"] .cl2Rlq_triggerEffort {\n color: var(--ccd-text-strong);\n}\n\n/* Budget the complete row: the native model-collapse check cannot see the\n sibling statistics dock. Keep the effort readable and give the name the\n remaining space, instead of hiding either at a fixed column breakpoint. */\nhtml[data-dsh-ccd-style="true"] .cl2Rlq_trigger {\n box-sizing: border-box;\n max-width: min(360px, var(--ccd-model-max-width, 45cqw));\n}\n\nhtml[data-dsh-ccd-style="true"] .cl2Rlq_triggerEffort {\n flex-shrink: 0;\n}\n\n/* Move only the native command button\'s painted box into the input surface.\n Like send/stop, it follows the last visible text line from the row\'s top, so\n multiline growth, editor scrolling and attachment caps cannot displace it.\n It remains inside the tools group: the host\'s hidden attribute still removes\n it during voice capture, and its focus/selection-aware launcher stays intact. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_add {\n box-sizing: border-box;\n position: absolute;\n bottom: calc(100% + var(--ccd-composer-row-gap) + var(--ccd-composer-padding-block) + 1px);\n left: var(--ccd-composer-padding-inline);\n width: var(--ccd-send-size);\n height: var(--ccd-send-size);\n padding: 0;\n border-radius: var(--ccd-chip-radius);\n background: transparent;\n color: var(--ccd-text-secondary);\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_add:hover:not(:disabled) {\n background: var(--ccd-hover);\n color: var(--ccd-text);\n}\n\n/* The leading pair keeps its boxes: the row no longer wraps, so the only item\n it may take the slack from is the model chip beside it. The permission chip\'s\n label is the whole control (`composer.css`, above), so a shrunk tools group\n would be a control with its own reading cut off rather than a shorter one. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_tools {\n flex: none;\n align-items: center;\n}\n\n/* The trailing group\'s half of the same trade: it stays at its own size where\n there is room (the host\'s `flex: none`), and gives the model chip up to the\n line\'s real remainder where there is not. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_trailing {\n min-width: 0;\n flex-shrink: 1;\n}\n\n/* Statistics cluster and context ring: the dock is a sibling of the card inside\n the Composer root, so it is laid over the tool row instead of taking a line of\n its own. Every readout keeps its own icon and its click-open detail panel;\n only the narrow-column labels go. The root becomes the containing block, and\n the offsets below walk back in from the column edge to the card\'s own right\n edge, which the dock cannot see from where it sits.\n\n The dock now owns the row\'s *last* seat rather than the one before the model:\n the statistics cluster still stops one cluster gap short of the trailing\n group, and the context ring hangs past it, on the row\'s right content edge.\n Both offsets are built from `--ccd-ring-offset` \u2014 the trailing cluster\'s live\n width, one cluster gap and the ring\'s own seat \u2014 so the ring lands on that\n edge whatever the cluster measures, and the trailing group\'s `margin-right`\n below keeps the model/effort pair clear of it. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_root {\n position: relative;\n container: ccd-composer / inline-size;\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_dock {\n position: absolute;\n --ccd-ring-offset: calc(var(--ccd-trailing-width, 120px) + var(--ccd-cluster-gap)\n + var(--ccd-context-ring-seat));\n right: calc((100% - min(100% - 32px, var(--ccd-composer-width))) / 2\n + var(--ccd-composer-row-inset) + var(--ccd-ring-offset));\n bottom: var(--ccd-composer-bottom-gap);\n height: var(--ccd-control-row-height);\n /* Never measure narrower than the ring\'s own trigger box: compat/stats-values.ts\n subtracts this dock from the model\'s budget, and an empty dock would\n otherwise hand the model the ring\'s seat as well. */\n min-width: var(--ccd-context-ring-trigger);\n padding: 0;\n gap: var(--ccd-cluster-gap);\n align-items: center;\n justify-content: flex-end;\n /* The box is an overlay: only its occupants take pointer input, so an empty\n dock never shadows the row underneath. */\n pointer-events: none;\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_dock > * {\n pointer-events: auto;\n}\n\n/* The context ring\'s seat: the status bar\'s last one, where the voice control\n used to sit. The ring is the host\'s own element \u2014 `ContextMeter` renders\n `span.y0jqnG_root` as the dock\'s last child \u2014 and is placed rather than\n re-parented: the host unmounts that ring whenever the voice control expands,\n and React would then remove it from a parent it no longer belongs to. The\n plugin\'s empty-state ring below shares this box exactly, so a session gaining\n its first tokens hands the seat over without moving or resizing anything. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_dock > .y0jqnG_root,\nhtml[data-dsh-ccd-style="true"] .yhfFVG_dock::after {\n position: absolute;\n right: calc(-1 * var(--ccd-ring-offset));\n top: 0;\n width: var(--ccd-context-ring-trigger);\n height: var(--ccd-control-row-height);\n flex: none;\n}\n\n/* The seat is the row\'s own 28px line and the ring\'s trigger inside it is a\n chip like every other control, so the artwork has to be centred in the seat\n rather than laid out from its top edge: `align-items: stretch` does not\n stretch an item that states its own height, it parks it at the line\'s start. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_dock > .y0jqnG_root {\n align-items: center;\n justify-content: center;\n}\n\n/* A session with no usage renders no `ContextMeter` at all: the component\n returns null until the token meter knows both a numerator and the context\n window, so the empty state is not something the host hides \u2014 there is nothing\n to show. This draws the ring\'s own track in the seat above, at the host\'s own\n geometry (`ContextMeter.module.css`: 14px box, r 5.5, 2px stroke), and\n withdraws the moment the host\'s ring exists or the voice control\'s capture row\n takes the bar \u2014 exactly the states in which the host unmounts its own ring. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_dock::after {\n content: "";\n background-color: var(--ccd-context-ring-track);\n mask: var(--ccd-icon-context-ring) center / var(--ccd-context-ring-size) var(--ccd-context-ring-size) no-repeat;\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_dock:has(.y0jqnG_root)::after,\nhtml[data-dsh-ccd-style="true"] .yhfFVG_root:has(.yhfFVG_trailingActive) .yhfFVG_dock::after {\n content: none;\n}\n\n/* The trailing cluster stops one ring seat short of the row\'s right content\n edge, so the model/effort pair never lands under the ring. While the voice\n control\'s capture row owns the bar the ring is unmounted and the seat is given\n back, leaving the host\'s own capture layout whole. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_trailing {\n margin-right: var(--ccd-context-ring-seat);\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_root:has(.yhfFVG_trailingActive) .yhfFVG_trailing {\n margin-right: 0;\n}\n\n/* The host hides a hero-phase dock that has nothing in it; the ring\'s seat is\n drawn through a pseudo-element, which `:empty` does not see. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_hero .yhfFVG_dock:empty {\n display: flex;\n}\n\n/*\n * The whole status row reads on one pitch \u2014 not just the trailing cluster. The\n * host separates its groups with 12px (`InputBar.module.css`: the row itself,\n * `.yhfFVG_tools` and `.yhfFVG_modes`) while the plugin\'s trailing cluster\n * already ran on 6px, which put the leading pair, the model/effort pair and the\n * ring on two different rhythms; every container in the bar now takes its gap\n * from `--ccd-cluster-gap` (tokens.css).\n *\n * The group\'s last seat is the native activity control (speech to text). While\n * no occupant is mounted, its wrapper is still a box \u2014 the renderer seats every\n * entry in a `display: contents` wrapper, so the host\'s own `:empty` rule cannot\n * see that it is empty \u2014 and the group\'s gap then held the model/effort pair\n * 12px off the row\'s right content edge. Hiding the empty wrapper moves the pair\n * (and, through `--ccd-trailing-width`, the whole cluster) back onto that edge.\n * An expanded activity control carries the other class and keeps its seat.\n *\n * The control itself no longer lives in this group: flex `order` cannot cross\n * the two containers, so compat/composer-status-order.ts moves the seat into the\n * leading group\'s modes cluster, immediately after the permission control. The\n * same empty-seat rule is repeated for that parent below.\n */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_tools,\nhtml[data-dsh-ccd-style="true"] .yhfFVG_modes,\nhtml[data-dsh-ccd-style="true"] .yhfFVG_trailing,\nhtml[data-dsh-ccd-style="true"] .yhfFVG_standardControls {\n gap: var(--ccd-cluster-gap);\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_trailing > .yhfFVG_activity:not(:has(> :not([data-slot]))):not(:has([data-slot] > *)),\nhtml[data-dsh-ccd-style="true"] .yhfFVG_modes > .yhfFVG_activity:not(:has(> :not([data-slot]))):not(:has([data-slot] > *)) {\n display: none;\n}\n\n/* Voice input. The glyph is addressed through the control\'s own slot outlet \u2014\n the one stable name the seat has \u2014 so its size holds whether the seat still\n sits in the trailing group or already beside the permission control, which is\n where compat/composer-status-order.ts moves it; the `data-ccd-voice-seat` mark\n that module leaves on a relocated seat is what the placement rule reads. The\n host draws the microphone at 18px, while every other glyph in the status bar\n (command plus, statistics readouts, context ring) is 14px artwork, so the\n mic takes that size and stays centred in its box. Only `button > svg` is\n resized: the recording row\'s waveform is a direct child of its own row and\n keeps the host\'s sizing. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_modes > [data-ccd-voice-seat] {\n align-items: center;\n}\n\nhtml[data-dsh-ccd-style="true"] [data-slot="conversation.input.activity"] button > svg {\n width: var(--ccd-row-icon-size);\n height: var(--ccd-row-icon-size);\n}\n\n/* The mic\'s own hit box is the row\'s glyph box, not the host\'s 28\xD714 strip: a\n chip of the row\'s height around the 14px mark, the same box the command plus\n beside it carries. The capture layout is left alone \u2014 the host hands that row\n its own round buttons and its own geometry. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_root:not(:has(.yhfFVG_trailingActive))\n [data-slot="conversation.input.activity"] button {\n box-sizing: border-box;\n display: inline-flex;\n width: calc(var(--ccd-row-icon-size) + 2 * var(--ccd-chip-padding-inline));\n height: var(--ccd-chip-height);\n align-items: center;\n justify-content: center;\n}\n\nhtml[data-dsh-ccd-style="true"] .OpZ85W_root {\n gap: var(--ccd-cluster-gap);\n align-items: center;\n}\n\n/* The readouts are opted into (`features.composer-stats`, off by default);\n `compat/stats-values.ts` publishes that state on the root. Off, the host\'s\n whole cluster leaves the line \u2014 each pill and the counts it prints \u2014 and the\n mirroring rules below have nothing to draw on. Nothing moves when it goes:\n the cluster and the context ring are both occupants of the dock, which is\n right-aligned, so the ring keeps the edge it already had, and the model\n button\'s budget is re-measured from the dock\'s own width by that same module.\n `[data-composer-stats]` is the host\'s stable marker for the cluster, so this\n rule outlives a build whose class hashes move. */\nhtml[data-dsh-ccd-style="true"][data-ccd-composer-stats="off"] [data-composer-stats] {\n display: none;\n}\n\n/* Same control shape as the model and permission selectors next to them: the\n host draws these readouts as full pills (`border-radius: 999px`) on its own\n 28px line, which reads as a different family of control in the same row. They\n take the row\'s chip box \u2014 height, corner and the readouts\' own 6px inline\n padding \u2014 and the row\'s own hover step, because the host paints its pill\n hover from `--dsw-alias-interactive-bg-hover` (a 6% bluish grey) while every\n other control in the bar sits in this palette\'s warm `--ccd-hover`. */\nhtml[data-dsh-ccd-style="true"] .OpZ85W_pill,\nhtml[data-dsh-ccd-style="true"] .y0jqnG_trigger {\n gap: 4px;\n height: var(--ccd-chip-height);\n padding: 0 6px;\n border-radius: var(--ccd-chip-radius);\n color: var(--ccd-text-secondary);\n font-size: var(--ccd-font-size-muted);\n}\n\nhtml[data-dsh-ccd-style="true"] .OpZ85W_pill:hover,\nhtml[data-dsh-ccd-style="true"] button.OpZ85W_pill[aria-expanded="true"],\nhtml[data-dsh-ccd-style="true"] .y0jqnG_trigger:hover,\nhtml[data-dsh-ccd-style="true"] .y0jqnG_trigger[aria-expanded="true"] {\n background: var(--ccd-hover);\n}\n\n/* Wide columns show one short readout per icon, derived from host text.\n Original aria-labels and click handlers stay on the host buttons. */\nhtml[data-dsh-ccd-style="true"] .OpZ85W_label,\nhtml[data-dsh-ccd-style="true"] .OpZ85W_sep {\n display: none;\n}\n\nhtml[data-dsh-ccd-style="true"] .OpZ85W_pill::after {\n content: var(--ccd-stat-value, "");\n font-variant-numeric: tabular-nums;\n}\n\n@container ccd-composer (max-width: 700px) {\n html[data-dsh-ccd-style="true"] .OpZ85W_pill::after {\n content: none;\n }\n}\n\n/* The ring carries no reading: the arc is the reading. The host\'s own text\n (`span` inside the trigger) goes, while the trigger keeps its box so the\n seat\'s geometry is unchanged. The hover panel keeps its numbers, and the\n accessible name keeps its wording \u2014 compat/composer-status-order.ts strips\n only the percentage from that. */\nhtml[data-dsh-ccd-style="true"] .y0jqnG_trigger > span {\n display: none;\n}\n\n/* The consumed arc is the one reading this control exists to show, so it takes\n the interface\'s accent blue instead of the host\'s tertiary label grey, which\n painted it the same ink as the percentage beside it. Only the colour moves:\n the host\'s 14px box, 2px stroke and round cap are untouched, and the track\n underneath keeps `--dsw-alias-border-l3` (already a theme override), so the\n two arcs stay clearly apart. The blue holds at every percentage \u2014 the\n reference draws no warning step, and a hue that changed near the limit would\n leave the ring\'s own reading ambiguous. */\nhtml[data-dsh-ccd-style="true"] .y0jqnG_fill {\n stroke: var(--ccd-context-ring-fill);\n}\n\n/* The permission control is the row\'s other text chip: the host draws it on the\n row\'s full 28px with a 4px/8px inline padding, so beside the model chip it kept\n a taller fill and a different ink rhythm. It takes the chip box and the chip\'s\n own 7px padding on both sides \u2014 the host\'s asymmetry was the two marks it\n reserved room for, and neither of them is drawn (below), so the label sits on\n the chip\'s even padding. Its hover fill is the row\'s warm step for the same\n reason the readouts above are. Host class pinned in\n src/client/compat/host-dom.ts (`ui-permission-presets`\'s\n PermissionSelect.module.css, `wXeviG_`). */\nhtml[data-dsh-ccd-style="true"] .wXeviG_trigger {\n box-sizing: border-box;\n height: var(--ccd-chip-height);\n padding: 0 var(--ccd-chip-padding-inline);\n border-radius: var(--ccd-chip-radius);\n}\n\nhtml[data-dsh-ccd-style="true"] .wXeviG_trigger:hover:not(:disabled) {\n background: var(--ccd-hover);\n}\n\n/* The chip is its text, and nothing else. The host leads the control with the\n mode\'s own glyph \u2014 `PermissionIconReadOnlyRegular` / `...WorkspaceWrite` /\n `...FullAccess`, the same 14px artwork the command plus beside it draws \u2014 and\n closes it with `IconChevronDownOutlineRegular`. Neither says anything the chip\n does not: the glyph repeats the mode the label already spells out, and this\n row\'s other menu controls (the two hero chips) draw no disclosure mark either.\n Both come out of the line, and with them the host\'s two 4px gaps and the\n 4px/8px asymmetry they reserved. The control keeps the host\'s `aria-label`\n ("\u6A21\u5F0F \u2026") and its `title`, so a screen reader still hears the mode in full,\n and the button keeps its own name \u2014 it is the same control, drawn shorter.\n Its open state has no mark left to turn; the row\'s one fill already answers\n the pointer that opened it. */\nhtml[data-dsh-ccd-style="true"] .wXeviG_triggerIcon,\nhtml[data-dsh-ccd-style="true"] .wXeviG_chevron {\n display: none;\n}\n\n/* The label is what is left, so it is drawn at every width. The host\'s own\n container query drops it below 460px\n (`.wXeviG_trigger:has(.wXeviG_triggerIcon) .wXeviG_triggerLabel`), reading the\n glyph as the reading that survives the narrow column; with that glyph\n withdrawn the rule would leave a chip that still answers a click and shows\n nothing. `:has()` plus the child combinator is what outranks the host\'s rule\n \u2014 the sheet order does not decide it. */\nhtml[data-dsh-ccd-style="true"] .wXeviG_trigger:has(.wXeviG_triggerIcon) > .wXeviG_triggerLabel {\n display: inline;\n}\n\n/* Narrow columns tighten the readouts\' side padding and the ring\'s seat; the\n permission chip keeps its label, because the label is the whole control. */\n@container ccd-composer (max-width: 560px) {\n html[data-dsh-ccd-style="true"] .OpZ85W_pill,\n html[data-dsh-ccd-style="true"] .y0jqnG_trigger {\n padding-inline: 4px;\n }\n\n /* The ring\'s trigger keeps the chip\'s height but drops 7px of side padding to\n 4px, so the seat it occupies is 22px wide; the empty-state ring follows it\n (the seat the row reserves stays the authored 32px, a hair generous). */\n html[data-dsh-ccd-style="true"] .yhfFVG_dock {\n --ccd-context-ring-trigger: 22px;\n }\n}\n\nhtml[data-dsh-ccd-style="true"] .OpZ85W_pill,\nhtml[data-dsh-ccd-style="true"] .y0jqnG_trigger {\n flex-shrink: 0;\n white-space: nowrap;\n}\n\n/* The native workspace row is conditional on the hero phase: React removes it\n when a conversation starts. Reorder the paired workspace/preset row with CSS\n instead of moving its DOM node out of the parent React will remove it from.\n Overlay its native row on the status line and reserve its width in tools.\n Keep this stack unpositioned: positioning it changes the containing block\n of the absolute hero greeting and can place that content over the editor.\n The workspace row anchors to the host\'s positioned composer seat instead. */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_composerHero {\n --ccd-workspace-seat: clamp(140px, 25vw, 260px);\n gap: 0;\n padding-bottom: 0;\n reading-flow: flex-visual;\n}\n\n/* Bottom inset: row \u2192 window edge. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_root,\nhtml[data-dsh-ccd-style="true"] .yhfFVG_hero {\n padding-bottom: var(--ccd-composer-bottom-gap);\n}\n\n/* The chips row starts on the input surface\'s own left edge: the Composer root\n insets that surface by the side clearance, so the row carries exactly that\n inset and nothing more. The reference measures the chip\'s border within 1px\n of the input\'s border, so a chip that sat any further right would read as a\n second, misaligned column. */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_heroWorkspaceRow {\n position: absolute;\n /* The later, positioned InputBar spans this row, including the empty tools\n padding reserved for these chips. Keep native buttons above that hit box. */\n z-index: 2;\n bottom: var(--ccd-composer-bottom-gap);\n left: calc((100% - min(100%, calc(var(--ccd-composer-width) + 32px))) / 2\n + var(--dsh-composer-side-clearance, 16px) + var(--ccd-composer-row-inset));\n box-sizing: border-box;\n width: var(--ccd-workspace-seat);\n min-width: 0;\n flex-wrap: nowrap;\n gap: var(--ccd-cluster-gap);\n justify-content: flex-start;\n height: var(--ccd-control-row-height);\n margin-top: 0;\n padding: 0;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_composerHero:has(.ST7X_W_heroWorkspaceRow) .yhfFVG_tools {\n padding-left: calc(var(--ccd-workspace-seat) + var(--ccd-cluster-gap));\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_heroWorkspaceRow .bocITq_workspace {\n flex: 0 1 auto;\n min-width: 0;\n overflow: hidden;\n white-space: nowrap;\n}\n\nhtml[data-dsh-ccd-style="true"] .bocITq_workspaceLabel {\n min-width: 0;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_heroWorkspaceRow button[aria-haspopup="menu"]:not(.bocITq_workspace) {\n flex: none;\n white-space: nowrap;\n}\n\n/* Keep the workspace picker and the existing preset seat as the same native\n menu controls, with the lightweight chip geometry of the lower tool row. */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_heroWorkspaceRow button[aria-haspopup="menu"],\nhtml[data-dsh-ccd-style="true"] .bocITq_workspace {\n box-sizing: border-box;\n height: var(--ccd-chip-height);\n min-height: var(--ccd-chip-height);\n padding: 0 var(--ccd-chip-padding-inline);\n border: 0;\n border-radius: var(--ccd-chip-radius);\n background: transparent;\n box-shadow: none;\n color: var(--ccd-text-secondary);\n font-size: var(--ccd-font-size-muted);\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_heroWorkspaceRow button[aria-haspopup="menu"]:hover:not(:disabled),\nhtml[data-dsh-ccd-style="true"] .ST7X_W_heroWorkspaceRow button[aria-haspopup="menu"][aria-expanded="true"],\nhtml[data-dsh-ccd-style="true"] .bocITq_workspace:hover:not(:disabled),\nhtml[data-dsh-ccd-style="true"] .bocITq_workspace[aria-expanded="true"] {\n background: var(--ccd-hover);\n color: var(--ccd-text);\n}\n\n/* The chip\'s leading mark. The host picks that glyph from the chip\'s own state:\n `IconFolderOpenRegular` \u2014 a slanted front panel over a translucent back panel\n \u2014 as soon as a workspace is chosen, and a closed folder only while the chip\n carries its placeholder. The reference leads with one closed folder either\n way, so the host\'s svg is taken out of the line and the mark is painted in\n its place from `--ccd-icon-folder`, on the same 16px box, with the chip\'s own\n `currentColor` \u2014 which is the ink the label beside it uses, and the ink the\n chip already steps up on hover, so the mark follows both for free.\n\n `::before` on the button is the first flex item of its own `inline-flex`\n line, exactly where the host\'s svg sat: the button\'s 4px gap and its chip\n padding keep measuring from a 16px box. `flex: none` is the host\'s own\n declaration on that svg, restated\n because the seat is now a pseudo-element: left shrinkable, a long workspace\n name would take its width out of the mark\'s 16px box as well as its own. */\nhtml[data-dsh-ccd-style="true"] .bocITq_workspace .bocITq_folder {\n display: none;\n}\n\nhtml[data-dsh-ccd-style="true"] .bocITq_workspace::before {\n content: "";\n flex: none;\n width: 16px;\n height: 16px;\n background-color: currentColor;\n mask: var(--ccd-icon-folder) center / 16px 16px no-repeat;\n}\n\n/* Neither chip in the row closes on a disclosure mark. The reference draws the\n two controls as a mark, a label and nothing else, where both host controls end\n their line with `IconChevronDownOutlineRegular`: the workspace chip through\n `HeroShell.module.css`, the mode chip through `AgentPresetSeat.module.css`.\n The first selector names the workspace chip\'s own mark; the second is what\n reaches the seat, whose module prefix is not in `host-builds.ts` and so\n cannot be written down here at all \u2014 its chevron is matched on the artwork\n itself, the way this stylesheet already matches the panel glyphs. Both are\n the same 12px `M4 6L7.29289\u2026` path; the seat\'s other mark is the preset\n glyph and the workspace chip\'s is the folder, so neither can be caught here.\n\n `display: none` is the whole change: the 12px box and the 4px gap leave the\n line with it, and each label ends on the chip\'s own 8px trailing padding.\n Nothing is taken from the controls themselves \u2014 both keep `aria-haspopup`\n and `aria-expanded`, so a menu is still announced as a menu. */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_heroWorkspaceRow button[aria-haspopup="menu"] > .bocITq_chevron,\nhtml[data-dsh-ccd-style="true"] .ST7X_W_heroWorkspaceRow button[aria-haspopup="menu"] > svg:has(> path[d^="M4 6L7.29289"]) {\n display: none;\n}\n\n/* Both chips in the hero row open a card from the host\'s `Menu` primitive, and\n every other host popup \u2014 the workspace `\u22EF` and session `\u22EF` menus, view\n options, the permission picker, the account card, the command palette \u2014 is\n built from the same two pieces. Their card chrome and their 24px flat cells\n are drawn once for the whole interface in [menus.css](menus.css) (DESIGN.md\n \xA76.7), so this sheet keeps only what belongs to the Composer it lays out.\n\n What is genuinely the mode card\'s own is the second line of its rows: */\n\n/* The mode card\'s second line, and the reason its rows were not flat cells in\n the first place: `ui-agent-preset` renders each row\'s label as a stack of a\n name and a description (`AgentPresetSeat.module.css`: `.item` is a column\n flex with a 2px gap, `.itemName` 13px/20px, `.itemDesc` a 12px/16px caption),\n which makes one row 66px tall and stretches the card to its longest\n description\'s measure \u2014 324px for the four shipped presets, where the flat\n cell needs a fraction of that. The design document\'s popup row is "text plus\n a trailing mark" (DESIGN.md \xA76.7) and its height is one 20px line, so the\n description is not drawn: the row then draws its name alone, and the card\n falls back to the primitive\'s own 144px minimum width.\n\n The line is reached structurally, not by class name: those two spans are the\n label wrapper\'s only children and that wrapper is the row label\'s only child,\n so "everything after the first span of the label\'s own span" is the\n description on every row of this card. The classes in that pane (`m0vb0q_*`)\n are a per-build hash of a desktop package \u2014 the same fact `host-builds.ts`\n records for `_3li69W_` from the same package \u2014 so naming one would leave the\n Windows build\'s rows two lines tall, silently, because that build has no\n counterpart to alias it to. The guard is the preset seat\'s own expanded\n state: the workspace chip beside it is the row\'s other `aria-haspopup`\n button, and `:not(.bocITq_workspace)` keeps this rule on the mode card (the\n workspace chip\'s class is pinned in `compat/host-dom.ts`). */\nhtml[data-dsh-ccd-style="true"]:has(.ST7X_W_heroWorkspaceRow button[aria-haspopup="menu"]:not(.bocITq_workspace)[aria-expanded="true"])\n [role="menu"] > ._viewport_4ub78_19 > ._itemWrap_4ub78_90 > ._item_4ub78_90 > ._itemLabel_4ub78_190 > span > :not(:first-child) {\n display: none;\n}\n';
|
|
4830
|
+
var composer_default = '/*\n * Shared composer geometry.\n *\n * Both the blank-session page and the conversation page must place the official\n * Composer at the same width and shape, so the geometry lives here instead of\n * being duplicated in two features (siblings never import each other). The\n * upstream structure is kept: the resident Composer card stays the single\n * editable root, and only where its parts sit changes.\n *\n * Reference geometry (@2x reference screenshots, logical px at 1374x871):\n * - input surface: 770x42, 1px border, radius 10, text line 24px at 8/14 padding\n * - the send control is the return arrow *inside* that surface, 24px, 14px in\n * from the right border and anchored to the bottom text line\n * - control row: outside the surface, 4px below it, ~28px tall\n * - window edge: 8px below the row\n *\n * Host classes are pinned in src/client/compat/host-dom.ts.\n */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_body {\n --dsh-chat-content-width: var(--ccd-composer-width);\n --dsh-composer-card-max-width: var(--ccd-composer-width);\n --dsh-composer-side-clearance: 16px;\n}\n\n/* The card becomes a transparent column: the input surface draws the border. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_card {\n --ccd-composer-add-reserve: calc(var(--ccd-send-size) + var(--ccd-cluster-gap));\n gap: 0;\n padding-top: 0;\n border: 0;\n border-radius: 0;\n background: transparent;\n box-shadow: none;\n}\n\n/* Capture mode hides the native tools group, including the plus. Let its text\n reclaim the leading inset without changing the voice seat or its layout. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_card:has(.yhfFVG_tools[hidden]) {\n --ccd-composer-add-reserve: 0px;\n}\n\n/* Without a workspace the host makes the whole card a workspace picker and\n draws a dashed ::after around it. Move that state cue to the input surface\n that owns our border, while keeping the host\'s picker behavior. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_cardWorkspaceTrigger::after {\n content: none;\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_scroll {\n box-sizing: border-box;\n min-height: var(--ccd-composer-min-height);\n margin-right: 0;\n padding: var(--ccd-composer-padding);\n border: 1px solid var(--ccd-border-strong);\n border-radius: var(--ccd-composer-radius);\n background: var(--ccd-card);\n box-shadow: var(--ccd-shadow-card);\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_cardWorkspaceTrigger .yhfFVG_scroll {\n border-style: dashed;\n}\n\n/* The editor owns the focus cue. Model/effort menus live in this card too,\n so :focus-within would leave the ring on while a menu has keyboard focus.\n Darken the existing 1px border without changing the surface geometry. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_card:has(.yhfFVG_input:focus) .yhfFVG_scroll {\n border-color: var(--ccd-composer-focus-border);\n}\n\n/* Attachments: the `ui-attachment` plugin renders the draft-image rail as a\n sibling of the input panel inside the card (`conversation.input.attachments`\n slot, class pinned in src/client/compat/host-dom.ts \u2014 ComposerAttachments\n .module.css, `dVdiKa_`). With the card transparent the previews would float\n on the page above the surface, and the send control, pinned to the card\'s\n top-right corner, would leave the surface with them. When the rail is\n present the two merge back into one surface: the rail draws the top cap and\n the panel drops its own, while the card\'s zero gap keeps the 1px side\n borders touching so no seam shows. The host\'s unstyled card pulled the rail\n 6px into the panel (`margin-bottom: -6px`); that belongs to the old card and\n is dropped. The send control stays beside the bottom text line, so the rail\n needs only the same side padding as the input. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_card:has(.dVdiKa_rail) .dVdiKa_rail {\n box-sizing: border-box;\n margin-bottom: 0;\n padding: 10px var(--ccd-composer-padding-inline) 0;\n border: 1px solid var(--ccd-border-strong);\n border-bottom: 0;\n border-radius: var(--ccd-composer-radius) var(--ccd-composer-radius) 0 0;\n background: var(--ccd-card);\n box-shadow: var(--ccd-shadow-card);\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_card:has(.dVdiKa_rail) .yhfFVG_scroll {\n border-top: 0;\n border-radius: 0 0 var(--ccd-composer-radius) var(--ccd-composer-radius);\n}\n\n/* The attachment cap shares the editor\'s focused border colour. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_card:has(.yhfFVG_input:focus) .dVdiKa_rail {\n border-color: var(--ccd-composer-focus-border);\n}\n\n/* Match the image tile to the outer editable surface, including its clipping. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_card .dVdiKa_thumbnail {\n border-radius: var(--ccd-composer-radius);\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_input,\nhtml[data-dsh-ccd-style="true"] .yhfFVG_hero .yhfFVG_input {\n box-sizing: border-box;\n min-height: 24px;\n /* Keep text clear of the controls, including when the editor scrolls. */\n padding: 0 var(--ccd-send-reserve) 0 var(--ccd-composer-add-reserve);\n font-family: var(--ccd-font-ui);\n font-size: var(--dsh-content-font-size, 14px);\n line-height: 24px;\n color: var(--ccd-text);\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_placeholder {\n /* Hero copy is longer than chat copy. Limit only the placeholder, leaving\n the actual editor\'s wrapping and multiline growth untouched. */\n inset: 0 0 auto 0;\n box-sizing: border-box;\n padding-right: var(--ccd-send-reserve);\n padding-left: var(--ccd-composer-add-reserve);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: var(--ccd-text-placeholder);\n font-size: var(--dsh-content-font-size, 14px);\n line-height: 24px;\n}\n\n/* Send / stop stay beside the input\'s bottom line. The row is their containing\n block: its top follows the input\'s bottom even if the row itself wraps.\n The host still owns both buttons, their state, labels and click handlers. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_primary {\n /* The user agent\'s `1px 6px` button padding is asymmetric against a 24px box\n only in the sense that it steals the content box; drop it and centre. */\n padding: 0;\n position: absolute;\n top: auto;\n bottom: calc(100% + var(--ccd-composer-row-gap) + var(--ccd-composer-padding-block) + 1px);\n right: var(--ccd-composer-padding-inline);\n width: var(--ccd-send-size);\n height: var(--ccd-send-size);\n border-radius: var(--ccd-send-radius);\n background: transparent;\n color: var(--ccd-text-nav);\n transform: none;\n transition: background-color 0.1s, color 0.1s;\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_primary:hover:not(:disabled) {\n background: var(--ccd-hover);\n color: var(--ccd-text);\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_primary:disabled {\n background: transparent;\n opacity: 0.4;\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_primary svg {\n display: none;\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_primary::before {\n content: "";\n width: 16px;\n height: 16px;\n background-color: currentColor;\n mask: var(--ccd-icon-send) center / 16px 16px no-repeat;\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_primary:has(rect)::before {\n mask-image: var(--ccd-icon-stop);\n}\n\n/* DSH exposes both stop and send when a new draft is entered while running. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_row .yhfFVG_primary:has(+ .yhfFVG_primary) {\n right: calc(var(--ccd-composer-padding-inline) + var(--ccd-send-size) + 6px);\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_card:has(.yhfFVG_primary + .yhfFVG_primary) .yhfFVG_input {\n padding-right: calc(var(--ccd-send-reserve) + var(--ccd-send-size) + 6px);\n}\n\n/* Tool row: outside the surface, directly below it, and one rhythm end to end.\n The row\'s own gap is the host\'s 12px, which the leading groups kept while the\n trailing cluster was already on `--ccd-cluster-gap`; the rule below hands the\n same 6px to every container in the bar, so the leading pair, the trailing\n cluster, the statistics dock and the ring read as one row.\n\n One line, at every width. The host\'s row wraps\n (`InputBar.module.css`: `.yhfFVG_row { flex-wrap: wrap }`) and breaks before\n it shrinks anything: a flex container decides its lines from the children\'s\n *base* sizes, so a column too narrow for the leading pair and the whole\n trailing cluster put \u2014 and the cluster lands on a second line of its own, a\n row lower, while the model chip that could have given up the space is still\n drawn at its cap. The reference draws the bar as one line\n at every window width (its `DeepSeek V4.1 Fl\u2026` is the model name clipped by\n the chip\'s own ellipsis, not a wrapped row), and the Composer\'s bottom inset\n is measured for that one line: two lines push the row over the window edge.\n So the wrap goes and the squeeze lands where the reference puts it \u2014 the\n model name, whose chip is the one item here that can shorten without\n losing its reading (`min-width: 0` and the host\'s own ellipsis on\n `.cl2Rlq_triggerLabel`), while the leading controls keep their boxes\n (`.yhfFVG_tools` below), the effort keeps its `flex-shrink: 0` and the ring\n keeps its reserved seat. Standing in the way of that squeeze is the trailing\n group\'s own `flex: none` (the host\'s, under `.yhfFVG_trailing`): the group is\n sized from the model chip\'s budget, and the budget\n (`compat/stats-values.ts` publishes the row\'s measured remainder as\n `--ccd-model-max-width`) is what the row has *left over* \u2014 not what the chip\n may take from a line that also carries the leading pair. Left unshrinkable\n the cluster would simply run past the Composer\'s right edge with the chip at\n its cap; allowed to shrink, the chip settles on what is actually there and\n ellipsizes, which is the one reading the reference is willing to shorten. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_row {\n position: relative;\n flex-wrap: nowrap;\n gap: var(--ccd-cluster-gap);\n min-height: var(--ccd-control-row-height);\n margin-top: var(--ccd-composer-row-gap);\n padding: 0 var(--ccd-composer-row-inset);\n color: var(--ccd-text-secondary);\n font-size: var(--ccd-font-size-muted);\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_row button {\n color: var(--ccd-text-secondary);\n}\n\n/* The model selector\'s two texts \u2014 the model name and the reasoning effort \u2014\n are the row\'s chosen values, not chrome, so they carry the chosen-value ink\n instead of the row\'s label grey; the host\'s own `font-weight: 400` stays.\n Each text is its own span, so the colour lands on the spans rather than on\n the button the rule above paints, and this plugin\'s own model/effort pair\n (features/model-controls) follows the same token. Host classes are pinned in\n src/client/compat/host-dom.ts (`ui-model-selection`\'s\n ModelSelect.module.css, `cl2Rlq_`). */\nhtml[data-dsh-ccd-style="true"] .cl2Rlq_triggerLabel,\nhtml[data-dsh-ccd-style="true"] .cl2Rlq_triggerEffort {\n color: var(--ccd-text-strong);\n}\n\n/* Budget the complete row: the native model-collapse check cannot see the\n sibling statistics dock. Keep the effort readable and give the name the\n remaining space, instead of hiding either at a fixed column breakpoint. */\nhtml[data-dsh-ccd-style="true"] .cl2Rlq_trigger {\n box-sizing: border-box;\n max-width: min(360px, var(--ccd-model-max-width, 45cqw));\n}\n\nhtml[data-dsh-ccd-style="true"] .cl2Rlq_triggerEffort {\n flex-shrink: 0;\n}\n\n/* Move only the native command button\'s painted box into the input surface.\n Like send/stop, it follows the last visible text line from the row\'s top, so\n multiline growth, editor scrolling and attachment caps cannot displace it.\n It remains inside the tools group: the host\'s hidden attribute still removes\n it during voice capture, and its focus/selection-aware launcher stays intact. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_add {\n box-sizing: border-box;\n position: absolute;\n bottom: calc(100% + var(--ccd-composer-row-gap) + var(--ccd-composer-padding-block) + 1px);\n left: var(--ccd-composer-padding-inline);\n width: var(--ccd-send-size);\n height: var(--ccd-send-size);\n padding: 0;\n border-radius: var(--ccd-chip-radius);\n background: transparent;\n color: var(--ccd-text-secondary);\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_add:hover:not(:disabled) {\n background: var(--ccd-hover);\n color: var(--ccd-text);\n}\n\n/* The leading pair keeps its boxes: the row no longer wraps, so the only item\n it may take the slack from is the model chip beside it. The permission chip\'s\n label is the whole control (`composer.css`, above), so a shrunk tools group\n would be a control with its own reading cut off rather than a shorter one. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_tools {\n flex: none;\n align-items: center;\n}\n\n/* The trailing group\'s half of the same trade: it stays at its own size where\n there is room (the host\'s `flex: none`), and gives the model chip up to the\n line\'s real remainder where there is not. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_trailing {\n min-width: 0;\n flex-shrink: 1;\n}\n\n/* Statistics cluster and context ring: the dock is a sibling of the card inside\n the Composer root, so it is laid over the tool row instead of taking a line of\n its own. Every readout keeps its own icon and its click-open detail panel;\n only the narrow-column labels go. The root becomes the containing block, and\n the offsets below walk back in from the column edge to the card\'s own right\n edge, which the dock cannot see from where it sits.\n\n The dock now owns the row\'s *last* seat rather than the one before the model:\n the statistics cluster still stops one cluster gap short of the trailing\n group, and the context ring hangs past it, on the row\'s right content edge.\n Both offsets are built from `--ccd-ring-offset` \u2014 the trailing cluster\'s live\n width, one cluster gap and the ring\'s own seat \u2014 so the ring lands on that\n edge whatever the cluster measures, and the trailing group\'s `margin-right`\n below keeps the model/effort pair clear of it. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_root {\n position: relative;\n container: ccd-composer / inline-size;\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_dock {\n position: absolute;\n --ccd-ring-offset: calc(var(--ccd-trailing-width, 120px) + var(--ccd-cluster-gap)\n + var(--ccd-context-ring-seat));\n right: calc((100% - min(100% - 32px, var(--ccd-composer-width))) / 2\n + var(--ccd-composer-row-inset) + var(--ccd-ring-offset));\n bottom: var(--ccd-composer-bottom-gap);\n height: var(--ccd-control-row-height);\n /* Never measure narrower than the ring\'s own trigger box: compat/stats-values.ts\n subtracts this dock from the model\'s budget, and an empty dock would\n otherwise hand the model the ring\'s seat as well. */\n min-width: var(--ccd-context-ring-trigger);\n padding: 0;\n gap: var(--ccd-cluster-gap);\n align-items: center;\n justify-content: flex-end;\n /* The box is an overlay: only its occupants take pointer input, so an empty\n dock never shadows the row underneath. */\n pointer-events: none;\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_dock > * {\n pointer-events: auto;\n}\n\n/* The context ring\'s seat: the status bar\'s last one, where the voice control\n used to sit. The ring is the host\'s own element \u2014 `ContextMeter` renders\n `span.y0jqnG_root` as the dock\'s last child \u2014 and is placed rather than\n re-parented: the host unmounts that ring whenever the voice control expands,\n and React would then remove it from a parent it no longer belongs to. The\n plugin\'s empty-state ring below shares this box exactly, so a session gaining\n its first tokens hands the seat over without moving or resizing anything. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_dock > .y0jqnG_root,\nhtml[data-dsh-ccd-style="true"] .yhfFVG_dock::after {\n position: absolute;\n right: calc(-1 * var(--ccd-ring-offset));\n top: 0;\n width: var(--ccd-context-ring-trigger);\n height: var(--ccd-control-row-height);\n flex: none;\n}\n\n/* The seat is the row\'s own 28px line and the ring\'s trigger inside it is a\n chip like every other control, so the artwork has to be centred in the seat\n rather than laid out from its top edge: `align-items: stretch` does not\n stretch an item that states its own height, it parks it at the line\'s start. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_dock > .y0jqnG_root {\n align-items: center;\n justify-content: center;\n}\n\n/* A session with no usage renders no `ContextMeter` at all: the component\n returns null until the token meter knows both a numerator and the context\n window, so the empty state is not something the host hides \u2014 there is nothing\n to show. This draws the ring\'s own track in the seat above, at the host\'s own\n geometry (`ContextMeter.module.css`: 14px box, r 5.5, 2px stroke), and\n withdraws the moment the host\'s ring exists or the voice control\'s capture row\n takes the bar \u2014 exactly the states in which the host unmounts its own ring. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_dock::after {\n content: "";\n background-color: var(--ccd-context-ring-track);\n mask: var(--ccd-icon-context-ring) center / var(--ccd-context-ring-size) var(--ccd-context-ring-size) no-repeat;\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_dock:has(.y0jqnG_root)::after,\nhtml[data-dsh-ccd-style="true"] .yhfFVG_root:has(.yhfFVG_trailingActive) .yhfFVG_dock::after {\n content: none;\n}\n\n/* The trailing cluster stops one ring seat short of the row\'s right content\n edge, so the model/effort pair never lands under the ring. While the voice\n control\'s capture row owns the bar the ring is unmounted and the seat is given\n back, leaving the host\'s own capture layout whole. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_trailing {\n margin-right: var(--ccd-context-ring-seat);\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_root:has(.yhfFVG_trailingActive) .yhfFVG_trailing {\n margin-right: 0;\n}\n\n/* The host hides a hero-phase dock that has nothing in it; the ring\'s seat is\n drawn through a pseudo-element, which `:empty` does not see. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_hero .yhfFVG_dock:empty {\n display: flex;\n}\n\n/*\n * The whole status row reads on one pitch \u2014 not just the trailing cluster. The\n * host separates its groups with 12px (`InputBar.module.css`: the row itself,\n * `.yhfFVG_tools` and `.yhfFVG_modes`) while the plugin\'s trailing cluster\n * already ran on 6px, which put the leading pair, the model/effort pair and the\n * ring on two different rhythms; every container in the bar now takes its gap\n * from `--ccd-cluster-gap` (tokens.css).\n *\n * The group\'s last seat is the native activity control (speech to text). While\n * no occupant is mounted, its wrapper is still a box \u2014 the renderer seats every\n * entry in a `display: contents` wrapper, so the host\'s own `:empty` rule cannot\n * see that it is empty \u2014 and the group\'s gap then held the model/effort pair\n * 12px off the row\'s right content edge. Hiding the empty wrapper moves the pair\n * (and, through `--ccd-trailing-width`, the whole cluster) back onto that edge.\n * An expanded activity control carries the other class and keeps its seat.\n *\n * The control itself no longer lives in this group: flex `order` cannot cross\n * the two containers, so compat/composer-status-order.ts moves the seat into the\n * leading group\'s modes cluster, immediately after the permission control. The\n * same empty-seat rule is repeated for that parent below.\n */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_tools,\nhtml[data-dsh-ccd-style="true"] .yhfFVG_modes,\nhtml[data-dsh-ccd-style="true"] .yhfFVG_trailing,\nhtml[data-dsh-ccd-style="true"] .yhfFVG_standardControls {\n gap: var(--ccd-cluster-gap);\n}\n\nhtml[data-dsh-ccd-style="true"] .yhfFVG_trailing > .yhfFVG_activity:not(:has(> :not([data-slot]))):not(:has([data-slot] > *)),\nhtml[data-dsh-ccd-style="true"] .yhfFVG_modes > .yhfFVG_activity:not(:has(> :not([data-slot]))):not(:has([data-slot] > *)) {\n display: none;\n}\n\n/* Voice input. The glyph is addressed through the control\'s own slot outlet \u2014\n the one stable name the seat has \u2014 so its size holds whether the seat still\n sits in the trailing group or already beside the permission control, which is\n where compat/composer-status-order.ts moves it; the `data-ccd-voice-seat` mark\n that module leaves on a relocated seat is what the placement rule reads. The\n host draws the microphone at 18px, while every other glyph in the status bar\n (command plus, statistics readouts, context ring) is 14px artwork, so the\n mic takes that size and stays centred in its box. Only `button > svg` is\n resized: the recording row\'s waveform is a direct child of its own row and\n keeps the host\'s sizing. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_modes > [data-ccd-voice-seat] {\n align-items: center;\n}\n\nhtml[data-dsh-ccd-style="true"] [data-slot="conversation.input.activity"] button > svg {\n width: var(--ccd-row-icon-size);\n height: var(--ccd-row-icon-size);\n}\n\n/* The mic\'s own hit box is the row\'s glyph box, not the host\'s 28\xD714 strip: a\n chip of the row\'s height around the 14px mark, the same box the command plus\n beside it carries. The capture layout is left alone \u2014 the host hands that row\n its own round buttons and its own geometry. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_root:not(:has(.yhfFVG_trailingActive))\n [data-slot="conversation.input.activity"] button {\n box-sizing: border-box;\n display: inline-flex;\n width: calc(var(--ccd-row-icon-size) + 2 * var(--ccd-chip-padding-inline));\n height: var(--ccd-chip-height);\n align-items: center;\n justify-content: center;\n}\n\nhtml[data-dsh-ccd-style="true"] .OpZ85W_root {\n gap: var(--ccd-cluster-gap);\n align-items: center;\n}\n\n/* The readouts are opted into (`features.composer-stats`, off by default);\n `compat/stats-values.ts` publishes that state on the root. Off, the host\'s\n whole cluster leaves the line \u2014 each pill and the counts it prints \u2014 and the\n mirroring rules below have nothing to draw on. Nothing moves when it goes:\n the cluster and the context ring are both occupants of the dock, which is\n right-aligned, so the ring keeps the edge it already had, and the model\n button\'s budget is re-measured from the dock\'s own width by that same module.\n Older hosts mark the whole cluster with `[data-composer-stats]`; current\n hosts mark each of its two anchors with `[data-composer-stat]`. Keep both\n stable hooks so the setting survives that DOM split without depending on a\n CSS-module hash. */\nhtml[data-dsh-ccd-style="true"][data-ccd-composer-stats="off"] [data-composer-stats],\nhtml[data-dsh-ccd-style="true"][data-ccd-composer-stats="off"] [data-composer-stat] {\n display: none;\n}\n\n/* Same control shape as the model and permission selectors next to them: the\n host draws these readouts as full pills (`border-radius: 999px`) on its own\n 28px line, which reads as a different family of control in the same row. They\n take the row\'s chip box \u2014 height, corner and the readouts\' own 6px inline\n padding \u2014 and the row\'s own hover step, because the host paints its pill\n hover from `--dsw-alias-interactive-bg-hover` (a 6% bluish grey) while every\n other control in the bar sits in this palette\'s warm `--ccd-hover`. */\nhtml[data-dsh-ccd-style="true"] .OpZ85W_pill,\nhtml[data-dsh-ccd-style="true"] .y0jqnG_trigger {\n gap: 4px;\n height: var(--ccd-chip-height);\n padding: 0 6px;\n border-radius: var(--ccd-chip-radius);\n color: var(--ccd-text-secondary);\n font-size: var(--ccd-font-size-muted);\n}\n\nhtml[data-dsh-ccd-style="true"] .OpZ85W_pill:hover,\nhtml[data-dsh-ccd-style="true"] button.OpZ85W_pill[aria-expanded="true"],\nhtml[data-dsh-ccd-style="true"] .y0jqnG_trigger:hover,\nhtml[data-dsh-ccd-style="true"] .y0jqnG_trigger[aria-expanded="true"] {\n background: var(--ccd-hover);\n}\n\n/* Wide columns show one short readout per icon, derived from host text.\n Original aria-labels and click handlers stay on the host buttons. */\nhtml[data-dsh-ccd-style="true"] .OpZ85W_label,\nhtml[data-dsh-ccd-style="true"] .OpZ85W_sep {\n display: none;\n}\n\nhtml[data-dsh-ccd-style="true"] .OpZ85W_pill::after {\n content: var(--ccd-stat-value, "");\n font-variant-numeric: tabular-nums;\n}\n\n@container ccd-composer (max-width: 700px) {\n html[data-dsh-ccd-style="true"] .OpZ85W_pill::after {\n content: none;\n }\n}\n\n/* The ring carries no reading: the arc is the reading. The host\'s own text\n (`span` inside the trigger) goes, while the trigger keeps its box so the\n seat\'s geometry is unchanged. The hover panel keeps its numbers, and the\n accessible name keeps its wording \u2014 compat/composer-status-order.ts strips\n only the percentage from that. */\nhtml[data-dsh-ccd-style="true"] .y0jqnG_trigger > span {\n display: none;\n}\n\n/* The consumed arc is the one reading this control exists to show, so it takes\n the interface\'s accent blue instead of the host\'s tertiary label grey, which\n painted it the same ink as the percentage beside it. Only the colour moves:\n the host\'s 14px box, 2px stroke and round cap are untouched, and the track\n underneath keeps `--dsw-alias-border-l3` (already a theme override), so the\n two arcs stay clearly apart. The blue holds at every percentage \u2014 the\n reference draws no warning step, and a hue that changed near the limit would\n leave the ring\'s own reading ambiguous. */\nhtml[data-dsh-ccd-style="true"] .y0jqnG_fill {\n stroke: var(--ccd-context-ring-fill);\n}\n\n/* The permission control is the row\'s other text chip: the host draws it on the\n row\'s full 28px with a 4px/8px inline padding, so beside the model chip it kept\n a taller fill and a different ink rhythm. It takes the chip box and the chip\'s\n own 7px padding on both sides \u2014 the host\'s asymmetry was the two marks it\n reserved room for, and neither of them is drawn (below), so the label sits on\n the chip\'s even padding. Its hover fill is the row\'s warm step for the same\n reason the readouts above are. Host class pinned in\n src/client/compat/host-dom.ts (`ui-permission-presets`\'s\n PermissionSelect.module.css, `wXeviG_`). */\nhtml[data-dsh-ccd-style="true"] .wXeviG_trigger {\n box-sizing: border-box;\n height: var(--ccd-chip-height);\n padding: 0 var(--ccd-chip-padding-inline);\n border-radius: var(--ccd-chip-radius);\n}\n\nhtml[data-dsh-ccd-style="true"] .wXeviG_trigger:hover:not(:disabled) {\n background: var(--ccd-hover);\n}\n\n/* The chip is its text, and nothing else. The host leads the control with the\n mode\'s own glyph \u2014 `PermissionIconReadOnlyRegular` / `...WorkspaceWrite` /\n `...FullAccess`, the same 14px artwork the command plus beside it draws \u2014 and\n closes it with `IconChevronDownOutlineRegular`. Neither says anything the chip\n does not: the glyph repeats the mode the label already spells out, and this\n row\'s other menu controls (the two hero chips) draw no disclosure mark either.\n Both come out of the line, and with them the host\'s two 4px gaps and the\n 4px/8px asymmetry they reserved. The control keeps the host\'s `aria-label`\n ("\u6A21\u5F0F \u2026") and its `title`, so a screen reader still hears the mode in full,\n and the button keeps its own name \u2014 it is the same control, drawn shorter.\n Its open state has no mark left to turn; the row\'s one fill already answers\n the pointer that opened it. */\nhtml[data-dsh-ccd-style="true"] .wXeviG_triggerIcon,\nhtml[data-dsh-ccd-style="true"] .wXeviG_chevron {\n display: none;\n}\n\n/* The label is what is left, so it is drawn at every width. The host\'s own\n container query drops it below 460px\n (`.wXeviG_trigger:has(.wXeviG_triggerIcon) .wXeviG_triggerLabel`), reading the\n glyph as the reading that survives the narrow column; with that glyph\n withdrawn the rule would leave a chip that still answers a click and shows\n nothing. `:has()` plus the child combinator is what outranks the host\'s rule\n \u2014 the sheet order does not decide it. */\nhtml[data-dsh-ccd-style="true"] .wXeviG_trigger:has(.wXeviG_triggerIcon) > .wXeviG_triggerLabel {\n display: inline;\n}\n\n/* Narrow columns tighten the readouts\' side padding and the ring\'s seat; the\n permission chip keeps its label, because the label is the whole control. */\n@container ccd-composer (max-width: 560px) {\n html[data-dsh-ccd-style="true"] .OpZ85W_pill,\n html[data-dsh-ccd-style="true"] .y0jqnG_trigger {\n padding-inline: 4px;\n }\n\n /* The ring\'s trigger keeps the chip\'s height but drops 7px of side padding to\n 4px, so the seat it occupies is 22px wide; the empty-state ring follows it\n (the seat the row reserves stays the authored 32px, a hair generous). */\n html[data-dsh-ccd-style="true"] .yhfFVG_dock {\n --ccd-context-ring-trigger: 22px;\n }\n}\n\nhtml[data-dsh-ccd-style="true"] .OpZ85W_pill,\nhtml[data-dsh-ccd-style="true"] .y0jqnG_trigger {\n flex-shrink: 0;\n white-space: nowrap;\n}\n\n/* The native workspace row is conditional on the hero phase: React removes it\n when a conversation starts. Reorder the paired workspace/preset row with CSS\n instead of moving its DOM node out of the parent React will remove it from.\n Overlay its native row on the status line and reserve its width in tools.\n Keep this stack unpositioned: positioning it changes the containing block\n of the absolute hero greeting and can place that content over the editor.\n The workspace row anchors to the host\'s positioned composer seat instead. */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_composerHero {\n /* The row is measured as a whole; each chip keeps its own content width. */\n --ccd-workspace-seat: 280px;\n --ccd-hero-control-gap: 2px;\n --ccd-hero-control-padding: 6px;\n gap: 0;\n padding-bottom: 0;\n reading-flow: flex-visual;\n}\n\n/* Bottom inset: row \u2192 window edge. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_root,\nhtml[data-dsh-ccd-style="true"] .yhfFVG_hero {\n padding-bottom: var(--ccd-composer-bottom-gap);\n}\n\n/* The chips row starts on the input surface\'s own left edge: the Composer root\n insets that surface by the side clearance, so the row carries exactly that\n inset and nothing more. The reference measures the chip\'s border within 1px\n of the input\'s border, so a chip that sat any further right would read as a\n second, misaligned column. */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_heroWorkspaceRow {\n position: absolute;\n /* The later, positioned InputBar spans this row, including the empty tools\n padding reserved for these chips. Keep native buttons above that hit box. */\n z-index: 2;\n bottom: var(--ccd-composer-bottom-gap);\n left: calc((100% - min(100%, calc(var(--ccd-composer-width) + 32px))) / 2\n + var(--dsh-composer-side-clearance, 16px) + var(--ccd-composer-row-inset));\n box-sizing: border-box;\n width: max-content;\n min-width: 0;\n flex-wrap: nowrap;\n gap: var(--ccd-hero-control-gap);\n justify-content: flex-start;\n height: var(--ccd-control-row-height);\n margin-top: 0;\n padding: 0;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_composerHero:has(.ST7X_W_heroWorkspaceRow) .yhfFVG_tools {\n padding-left: calc(var(--ccd-workspace-seat) + var(--ccd-hero-control-gap));\n}\n\n/* Equal visible spacing: 6px button padding + 2px gap + 6px padding.\n The permission control belongs to another native container, so apply the\n same geometry across that boundary rather than stacking group spacing. */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_composerHero .yhfFVG_tools,\nhtml[data-dsh-ccd-style="true"] .ST7X_W_composerHero .yhfFVG_modes {\n gap: var(--ccd-hero-control-gap);\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_heroWorkspaceRow button[aria-haspopup="menu"],\nhtml[data-dsh-ccd-style="true"] .ST7X_W_composerHero .wXeviG_trigger {\n padding-inline: var(--ccd-hero-control-padding);\n border: 0;\n font-family: var(--ccd-font-ui);\n font-weight: 400;\n line-height: 20px;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_heroWorkspaceRow button[aria-haspopup="menu"] > svg {\n color: inherit;\n}\n\n/* Permission is a separate control group. Give it a visible 8px boundary\n after the preset (the row reserve contributes 2px, this adds 6px). */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_composerHero .wXeviG_trigger {\n margin-inline-start: 6px;\n}\n\n/* Allow useful project names before truncating. Cap only long names, keeping\n room for the preset, permission and model controls on narrower columns. */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_heroWorkspaceRow .bocITq_workspace {\n flex: none;\n width: max-content;\n max-width: clamp(100px, 24cqw, 240px);\n min-width: 0;\n overflow: hidden;\n white-space: nowrap;\n}\n\nhtml[data-dsh-ccd-style="true"] .bocITq_workspaceLabel {\n min-width: 0;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n/* Nothing in the mode chip gives way. The row\'s flex item is the `Menu` anchor\n the seat draws around its button, not the button itself: left shrinkable, that\n anchor took its share of the row\'s overflow and cut the mode\'s label, which is\n the reading this row exists to show whole \u2014 and with the anchor held, the\n button inside has to be held as well, or its host `max-width: 100%` hands the\n row\'s overflow straight back to the label. The renderer seats the seat in a\n `display: contents` outlet, so the anchor is that outlet\'s element child: the\n stable name for a seat whose module class prefix is not a pinned one, and the\n row is measured by `compat/hero-chips.ts`. */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_heroWorkspaceRow [data-slot="conversation.hero.agentPreset"] > :first-child,\nhtml[data-dsh-ccd-style="true"] .ST7X_W_heroWorkspaceRow button[aria-haspopup="menu"]:not(.bocITq_workspace) {\n flex: none;\n white-space: nowrap;\n}\n\n/* Keep the workspace picker and the existing preset seat as the same native\n menu controls, with the lightweight chip geometry of the lower tool row. */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_heroWorkspaceRow button[aria-haspopup="menu"],\nhtml[data-dsh-ccd-style="true"] .bocITq_workspace {\n box-sizing: border-box;\n height: var(--ccd-chip-height);\n min-height: var(--ccd-chip-height);\n padding: 0 var(--ccd-hero-control-padding, var(--ccd-chip-padding-inline));\n border: 0;\n border-radius: var(--ccd-chip-radius);\n background: transparent;\n box-shadow: none;\n color: var(--ccd-text-secondary);\n font-size: var(--ccd-font-size-muted);\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_heroWorkspaceRow button[aria-haspopup="menu"]:hover:not(:disabled),\nhtml[data-dsh-ccd-style="true"] .ST7X_W_heroWorkspaceRow button[aria-haspopup="menu"][aria-expanded="true"],\nhtml[data-dsh-ccd-style="true"] .bocITq_workspace:hover:not(:disabled),\nhtml[data-dsh-ccd-style="true"] .bocITq_workspace[aria-expanded="true"] {\n background: var(--ccd-hover);\n color: var(--ccd-text);\n}\n\n/* The chip\'s leading mark. The host picks that glyph from the chip\'s own state:\n `IconFolderOpenRegular` \u2014 a slanted front panel over a translucent back panel\n \u2014 as soon as a workspace is chosen, and a closed folder only while the chip\n carries its placeholder. The reference leads with one closed folder either\n way, so the host\'s svg is taken out of the line and the mark is painted in\n its place from `--ccd-icon-folder`, on the same 16px box, with the chip\'s own\n `currentColor` \u2014 which is the ink the label beside it uses, and the ink the\n chip already steps up on hover, so the mark follows both for free.\n\n `::before` on the button is the first flex item of its own `inline-flex`\n line, exactly where the host\'s svg sat: the button\'s 4px gap and its chip\n padding keep measuring from a 16px box. `flex: none` is the host\'s own\n declaration on that svg, restated\n because the seat is now a pseudo-element: left shrinkable, a long workspace\n name would take its width out of the mark\'s 16px box as well as its own. */\nhtml[data-dsh-ccd-style="true"] .bocITq_workspace .bocITq_folder {\n display: none;\n}\n\nhtml[data-dsh-ccd-style="true"] .bocITq_workspace::before {\n content: "";\n flex: none;\n width: 16px;\n height: 16px;\n background-color: currentColor;\n mask: var(--ccd-icon-folder) center / 16px 16px no-repeat;\n}\n\n/* Neither chip in the row closes on a disclosure mark. The reference draws the\n two controls as a mark, a label and nothing else, where both host controls end\n their line with `IconChevronDownOutlineRegular`: the workspace chip through\n `HeroShell.module.css`, the mode chip through `AgentPresetSeat.module.css`.\n The first selector names the workspace chip\'s own mark; the second is what\n reaches the seat, whose module prefix is not in `host-builds.ts` and so\n cannot be written down here at all \u2014 its chevron is matched on the artwork\n itself, the way this stylesheet already matches the panel glyphs. Both are\n the same 12px `M4 6L7.29289\u2026` path; the seat\'s other mark is the preset\n glyph and the workspace chip\'s is the folder, so neither can be caught here.\n\n `display: none` is the whole change: the 12px box and the 4px gap leave the\n line with it, and each label ends on the chip\'s own 8px trailing padding.\n Nothing is taken from the controls themselves \u2014 both keep `aria-haspopup`\n and `aria-expanded`, so a menu is still announced as a menu. */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_heroWorkspaceRow button[aria-haspopup="menu"] > .bocITq_chevron,\nhtml[data-dsh-ccd-style="true"] .ST7X_W_heroWorkspaceRow button[aria-haspopup="menu"] > svg:has(> path[d^="M4 6L7.29289"]) {\n display: none;\n}\n\n/* Both chips in the hero row open a card from the host\'s `Menu` primitive, and\n every other host popup \u2014 the workspace `\u22EF` and session `\u22EF` menus, view\n options, the permission picker, the account card, the command palette \u2014 is\n built from the same two pieces. Their card chrome and their 24px flat cells\n are drawn once for the whole interface in [menus.css](menus.css) (DESIGN.md\n \xA76.7), so this sheet keeps only what belongs to the Composer it lays out.\n\n What is genuinely the mode card\'s own is the second line of its rows: */\n\n/* The mode card\'s second line, and the reason its rows were not flat cells in\n the first place: `ui-agent-preset` renders each row\'s label as a stack of a\n name and a description (`AgentPresetSeat.module.css`: `.item` is a column\n flex with a 2px gap, `.itemName` 13px/20px, `.itemDesc` a 12px/16px caption),\n which makes one row 66px tall and stretches the card to its longest\n description\'s measure \u2014 324px for the four shipped presets, where the flat\n cell needs a fraction of that. The design document\'s popup row is "text plus\n a trailing mark" (DESIGN.md \xA76.7) and its height is one 20px line, so the\n description is not drawn: the row then draws its name alone, and the card\n falls back to the primitive\'s own 144px minimum width.\n\n The line is reached structurally, not by class name: those two spans are the\n label wrapper\'s only children and that wrapper is the row label\'s only child,\n so "everything after the first span of the label\'s own span" is the\n description on every row of this card. The classes in that pane (`m0vb0q_*`)\n are a per-build hash of a desktop package \u2014 the same fact `host-builds.ts`\n records for `_3li69W_` from the same package \u2014 so naming one would leave the\n Windows build\'s rows two lines tall, silently, because that build has no\n counterpart to alias it to. The guard is the preset seat\'s own expanded\n state: the workspace chip beside it is the row\'s other `aria-haspopup`\n button, and `:not(.bocITq_workspace)` keeps this rule on the mode card (the\n workspace chip\'s class is pinned in `compat/host-dom.ts`). */\nhtml[data-dsh-ccd-style="true"]:has(.ST7X_W_heroWorkspaceRow button[aria-haspopup="menu"]:not(.bocITq_workspace)[aria-expanded="true"])\n [role="menu"] > ._viewport_4ub78_19 > ._itemWrap_4ub78_90 > ._item_4ub78_90 > ._itemLabel_4ub78_190 > span > :not(:first-child) {\n display: none;\n}\n';
|
|
4438
4831
|
|
|
4439
4832
|
// src/client/theme/menus.css
|
|
4440
4833
|
var menus_default = '/*\n * Host popup surfaces \u2014 menus and panels (DESIGN.md \xA76.7).\n *\n * The document draws one popup for the whole interface: a 12px card with a 1px\n * outline, the three-layer hairline shadow and the card fill, and one flat 24px\n * cell per row \u2014 the host\'s own 13px label on a 20px line with 2px above and\n * below, a 6px corner, and no leading icon seat, because a row is text plus a\n * trailing mark.\n *\n * Every host popup is built from two shared pieces, so this sheet is two\n * families of rules rather than one per menu:\n *\n * - **The card** is `MenuSurface` (`ui-primitives`), which tags itself\n * `data-menu-material`; that node is the same whichever primitive renders\n * inside it \u2014 a `Menu` list, a submenu, the signed-in account card, or the\n * Composer\'s command palette. Its own skin is a translucent material: a 16px\n * `--dsw-radius-lg` corner, a 0.5px elevation stroke and the\n * `--dsw-elevation-prominent` shadow on the card, plus a `.material` child\n * covering its padding box with `--dsw-menu-surface-fill` (a 58%-alpha\n * near-white) under a `blur(40px) saturate(150%)` backdrop.\n *\n * - **The rows** belong to whichever primitive renders them. `ui-primitives`\'\n * `Menu` renders `button[role="menuitem"]` \u2014 `Menu.js` `MenuItemButton`: an\n * optional `.itemIcon` seat, the label and an optional trailing shortcut \u2014\n * and the Composer\'s command palette renders `button[role="option"]` inside a\n * `[role="listbox"]` viewport whose classes are a frontend asset hash\n * `compat/host-dom.ts` does not pin, so those rows are reached through ARIA\n * and structure instead of a class name.\n *\n * Until this sheet existed the document\'s chrome was written per menu: the two\n * hero chips\' pickers in `composer.css`, the account card in\n * `features/sidebar/account-menu.css`. The workspace `\u22EF`, session `\u22EF`,\n * view-options and permission menus \u2014 which open from the sidebar and from the\n * Composer\'s own status bar \u2014 therefore still drew the host\'s translucent 16px\n * material with 34px cells and a leading glyph, next to hero pickers that drew\n * this one. The rules below are now the single place that draws a host popup;\n * the sheets that used to carry a copy keep only what is genuinely theirs.\n *\n * Deliberately outside those rules:\n *\n * - the **account menu** (`compat/account-menu.ts` tags the card\n * `data-ccd-account-menu`) keeps its own row rhythm and its leading glyphs:\n * the reference for that card draws them\n * (`features/sidebar/account-menu.css`), and it is a wide account card rather\n * than a list of choices. Its *card* is still the document\'s popup and takes\n * the rule below; only the row rules step around it.\n * - the plugin\'s own **model and effort cards** (`features/model-controls`) are\n * not `MenuSurface` nodes at all \u2014 they already draw this chrome from their\n * own sheets and answer to their own tests.\n * - the **permission card** (`compat/permission-menu.ts` tags the card\n * `data-ccd-permission-menu` and each row it drew a second line for) keeps the\n * card rule and steps out of the row rule: its rows are the family\'s one\n * two-line cell, and the block at the end of this sheet is that cell. It is\n * here rather than in a sheet of its own because the card, the row group and\n * the trailing check are all this family\'s, and only the line heights differ.\n *\n * Host classes are pinned in src/client/compat/host-dom.ts.\n */\n\n/* The card.\n *\n * Three of the material\'s four values move here: the corner, the outline and\n * the shadow. The fourth, the fill, moves through the token the `.material`\n * layer reads rather than by naming that layer\'s class \u2014 one less hash to pin,\n * and the layer keeps its own `inset: 0`, so its arc still follows the card.\n *\n * The card states `--ccd-card` beside the token for the one pixel the material\n * cannot cover: the material is inset by the card\'s border, so without a\n * background of its own the corner arc would leave a sliver of the page showing\n * through. `--dsw-menu-backdrop-filter` is withdrawn with the fill: the palette\n * fill is opaque, so the 40px blur would only cost compositing.\n *\n * Everything else the surface decides for itself \u2014 its 4px padding, the\n * scrollable viewport, the footer\'s pinned row, the card\'s own\n * `--dsh-scrollbar-*` rebinding \u2014 stays the host\'s. (That rebinding still\n * stands; `theme/scrollbars.css` is the one sheet that moves the two thumb\n * colours it resolves to.) */\nhtml[data-dsh-ccd-style="true"] [data-menu-material] {\n box-sizing: border-box;\n border: 1px solid var(--ccd-border);\n border-radius: var(--ccd-radius-panel);\n background: var(--ccd-card);\n box-shadow: var(--ccd-effort-shadow);\n --dsw-menu-surface-fill: var(--ccd-card);\n --dsw-menu-backdrop-filter: none;\n}\n\n/* Input-trigger menus keep their native anchor and keyboard selection. The\n * description is a separate span in MenuView; labels and aliases stay intact. */\nhtml[data-dsh-ccd-style="true"] [data-menu-material][data-trigger-menu] {\n width: min(280px, 100%);\n right: auto;\n}\n\nhtml[data-dsh-ccd-style="true"] [data-menu-material][data-trigger-menu] [role="option"] > [class$="_itemDescription"] {\n display: none;\n}\n\n/* A compact menu gives the name and alias the space descriptions used to own. */\nhtml[data-dsh-ccd-style="true"] [data-menu-material][data-trigger-menu] [role="option"] > [class$="_itemName"],\nhtml[data-dsh-ccd-style="true"] [data-menu-material][data-trigger-menu] [role="option"] > [class$="_itemAlias"] {\n flex-shrink: 1;\n min-width: 0;\n max-width: none;\n}\n\n/* One row, one cell.\n *\n * The host\'s cell is 34px \u2014 a 13px label on a 20px line under a 34px minimum\n * with 6px of block padding \u2014 and its dense variant is 30px; the reference\n * measures one row as 48 device px at @2x, the same line in a 24px box. Cutting\n * the minimum and the block padding to that row\'s own 2px is the whole change:\n * the 8px inline padding, the 6px gap, the 13px/20px label and the 14px\n * trailing mark are still the host\'s, so the label\'s x and measure do not move.\n *\n * `min-height` and `padding-block` are one decision \u2014 2px around that 20px line\n * is exactly the 24px the reference draws \u2014 and they only add up under\n * `border-box`, which the renderer\'s own button default supplies and nothing the\n * host\'s cell declares. Stating it keeps the row at 24px whatever the page does\n * with padding instead of resting the arithmetic on a default.\n *\n * The corner follows the height: the reference\'s highlight fits a 12-device-px\n * curve at @2x, the same 6px the transcript\'s table and code cards carry. The\n * host\'s row radius is 12px, exactly half of a 24px row, so every hover and\n * focus fill would round into a pill instead of the reference\'s soft rectangle.\n *\n * The command palette\'s rows are `[role="option"]`, not `[role="menuitem"]`,\n * and they answer to the same cell: its row is the same 34px box with the same\n * 12px corner, and one row per command is what the reference draws there too.\n *\n * The account card is named out: its rows are its reference\'s own\n * (`features/sidebar/account-menu.css`, 28px rows with their leading glyphs), and\n * that sheet mounts after this one, so an exclusion is a decision rather than a\n * race. */\nhtml[data-dsh-ccd-style="true"] [data-menu-material]:not([data-ccd-account-menu]) [role="menuitem"],\nhtml[data-dsh-ccd-style="true"] [data-menu-material]:not([data-ccd-account-menu]) [role="option"] {\n box-sizing: border-box;\n min-height: 24px;\n padding-block: 2px;\n border-radius: var(--ccd-table-radius);\n}\n\n/* No leading icon seat.\n *\n * A row is a label and a trailing mark, so the seat is removed rather than\n * reserved: out of the flex line it leaves neither itself nor the row\'s 6px gap\n * behind, and the label falls back to the row\'s own 8px start padding inside the\n * card\'s 4px \u2014 12px from the card\'s left edge, the same x on every row of every\n * popup, and the same x the hero pickers\' rows already had.\n *\n * Both seats are named by the fact that makes them seats rather than by a class\n * this sheet cannot pin: `ui-primitives`\' `Menu.js` gives its icon span the\n * `.itemIcon` class (`_4ub78_` is in `compat/host-dom.ts`), while the command\n * palette renders its icon as the row\'s first child span with\n * `aria-hidden="true"` \u2014 a decorative seat, and the palette\'s own class prefix\n * is not pinned anywhere. */\nhtml[data-dsh-ccd-style="true"] [data-menu-material]:not([data-ccd-account-menu]) [role="menuitem"] > ._itemIcon_4ub78_148,\nhtml[data-dsh-ccd-style="true"] [data-menu-material]:not([data-ccd-account-menu]) [role="option"] > span[aria-hidden="true"]:first-child {\n display: none;\n}\n\n/* The add/command menu uses the host\'s semantic outline icons. Keep their\n * compact 14px seats and let the ink follow the row in both themes. */\nhtml[data-dsh-ccd-style="true"] [data-menu-material][data-trigger-menu] [role="option"] > span[aria-hidden="true"]:first-child {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n color: inherit;\n}\n\nhtml[data-dsh-ccd-style="true"] [data-menu-material][data-trigger-menu] [role="option"] > span[aria-hidden="true"]:first-child svg {\n width: 14px;\n height: 14px;\n}\n\n/* One fill for the row\'s states.\n *\n * The pointer gets the fill every other control in this palette gets \u2014 one\n * hover step for the whole interface rather than the host\'s 6%-bluish\n * `--dsw-alias-interactive-bg-hover` inside a popup \u2014 and the keyboard walk\n * takes the same fill, because the host already reads focus that way and the\n * ring that would have told the two apart is not drawn in this family (below).\n * The destructive row is the one exception: it takes a red fill with white ink\n * (below), in every state, so the danger action reads apart from the rest of\n * the card. Selection is not a fill in this document \u2014 it is the accent check\n * the trailing seat draws (`theme/tokens.css`) and that the host\'s `.selected`\n * class already leaves transparent.\n *\n * The command palette\'s active row is its keyboard cursor (the listbox keeps DOM\n * focus and publishes `aria-activedescendant`), so it takes the fill by\n * `aria-selected` rather than by a pseudo-class. */\nhtml[data-dsh-ccd-style="true"] [data-menu-material]:not([data-ccd-account-menu]) [role="menuitem"]:hover:not(:disabled),\nhtml[data-dsh-ccd-style="true"] [data-menu-material]:not([data-ccd-account-menu]) [role="menuitem"]:focus-visible:not(:disabled),\nhtml[data-dsh-ccd-style="true"] [data-menu-material]:not([data-ccd-account-menu]) [role="option"]:hover,\nhtml[data-dsh-ccd-style="true"] [data-menu-material]:not([data-ccd-account-menu]) [role="option"][aria-selected="true"] {\n background: var(--ccd-hover);\n}\n\n/* The host\'s destructive row (`Menu.module.css` `.danger`, the `_4ub78_` build\n * the rest of this sheet names) rests in the host\'s red ink. Hover and the\n * keyboard walk fill it with the red and white ink, label and shortcut included.\n * It sits after the hover rule above and is more specific, so the host\'s own\n * danger hover tint loses to it. The header session menu\'s Delete row takes the\n * same fill in `features/conversation/header-actions/header-menus.css`. */\nhtml[data-dsh-ccd-style="true"] [data-menu-material]:not([data-ccd-account-menu]) [role="menuitem"]._danger_4ub78_226:hover:not(:disabled),\nhtml[data-dsh-ccd-style="true"] [data-menu-material]:not([data-ccd-account-menu]) [role="menuitem"]._danger_4ub78_226:focus-visible:not(:disabled) {\n background: var(--ccd-menu-danger);\n color: var(--ccd-menu-danger-ink);\n}\n\nhtml[data-dsh-ccd-style="true"] [data-menu-material]:not([data-ccd-account-menu]) [role="menuitem"]._danger_4ub78_226:hover:not(:disabled) span,\nhtml[data-dsh-ccd-style="true"] [data-menu-material]:not([data-ccd-account-menu]) [role="menuitem"]._danger_4ub78_226:focus-visible:not(:disabled) span {\n color: inherit;\n}\n\n/* Filling the row is the whole state: no emphasis ring on a popup row.\n *\n * \xA7\u4E03\'s ring was drawn here first and taken out again. It is a box on a 24px\n * cell, so it cannot sit still: at the first and the last row the scrolling\n * viewport cuts it open (the rows sit on its edge \u2014 measured in the running\n * interface, the top stroke of `\u91CD\u547D\u540D` and the bottom stroke of `\u5220\u9664\u5DE5\u4F5C\u533A`\n * both lost their line), and on any row between them it paints 2px into the\n * neighbour\'s cell. The host\'s own keyboard walk answers focus with the fill\n * for the same reason (`Menu.module.css` `.item:focus-visible` states\n * `outline: none`), so the family keeps one indication and the rule below is\n * the statement of that decision rather than a leftover of the host\'s. The\n * account card is left out here as everywhere else in this sheet. */\nhtml[data-dsh-ccd-style="true"] [data-menu-material]:not([data-ccd-account-menu]) [role="menuitem"]:focus-visible,\nhtml[data-dsh-ccd-style="true"] [data-menu-material]:not([data-ccd-account-menu]) [role="option"][aria-selected="true"] {\n outline: none;\n}\n\n/* The card\'s non-option rows \u2014 group headings, and the palette\'s section and\n * group titles \u2014 are cells of the same rhythm, not blocks with a size of their\n * own: one 20px line with 2px above and below, in the control-label size (12px;\n * the document\'s scale has no 11px step) and the secondary ink. The host draws\n * them at 11px/15-16px with 5-6px of block padding and the tertiary ink, which\n * makes a heading the tallest thing in a card of 24px rows.\n *\n * They are reached structurally: both primitives render those titles as direct\n * `role="presentation"` children of the scrolling container \u2014 the `Menu` list\'s\n * `.viewport` and the palette\'s own `[role="listbox"]` viewport \u2014 while rows sit\n * one wrapper deeper and separators carry `role="separator"`. */\nhtml[data-dsh-ccd-style="true"] [data-menu-material]:not([data-ccd-account-menu]) ._viewport_4ub78_19 > [role="presentation"],\nhtml[data-dsh-ccd-style="true"] [data-menu-material]:not([data-ccd-account-menu]) [role="listbox"] > [role="presentation"] {\n box-sizing: border-box;\n padding: 2px 8px;\n color: var(--ccd-text-secondary);\n font-size: 12px;\n line-height: 20px;\n}\n\n/* The card\'s two internal hairlines \u2014 a data separator and the pinned footer\'s\n * top edge \u2014 take the palette\'s soft line rather than the host\'s\n * `--dsw-alias-border-l2`, so a line inside a card is one step under the card\'s\n * own outline. Sizes stay the host\'s: a 0.5px hairline and its margins. */\nhtml[data-dsh-ccd-style="true"] [data-menu-material]:not([data-ccd-account-menu]) [role="separator"] {\n background: var(--ccd-border-soft);\n}\n\nhtml[data-dsh-ccd-style="true"] [data-menu-material]:not([data-ccd-account-menu]) ._footer_4ub78_62 {\n border-top-color: var(--ccd-border-soft);\n}\n\n/* The permission card: the family\'s one two-line cell.\n *\n * Every other card in this family draws a row as text plus a trailing mark, and\n * DSH\'s permission card draws its four presets that way too \u2014 `\u4EC5\u53EF\u67E5\u770B` and its\n * neighbours, one 20px line each. The reference draws the same four rows a line\n * taller: the mode\'s name over a sentence saying what the mode does, with the\n * one flagged preset carrying a badge beside its name. `compat/permission-menu.ts`\n * recognises the rows (by the name each one draws \u2014 the primitive gives a row no\n * identity of its own), writes the second line and tags the row and the card; the\n * rules below draw what it tagged, and nothing else in this sheet has to move.\n *\n * The cell is measured from that reference at @2x, the way \xA7\u4E09 measures everything\n * else: rows repeat every 80.5 device px \u2014 40px \u2014 with the name at 14px on a 20px\n * line and the sentence at 12px on a 16px line flush under it, on the family\'s own\n * 2px of block padding (20 + 16 + 4 = the measured 40). 14px is the document\'s body\n * step, one above the 13px control label a single-line menu row draws: a row that\n * carries a sentence reads as a paragraph, not as a list item.\n *\n * Two things are stated against the family rule rather than left to it. The host\'s\n * flex line becomes the grid below, which is what puts the name and the sentence on\n * two tracks instead of side by side; and `min-height` and `padding-block` are given\n * again, because the family rule sets both \u2014 at a specificity this selector beats\n * (0-5-1 to its 0-4-1), so no sheet order is relied on, and a two-line row that\n * inherited the 24px floor and the 2px padding would be clipped rather than drawn.\n * The host\'s 6px `gap` becomes the grid\'s column gap for the same reason it was a\n * flex gap: it is the distance to the trailing mark. `row-gap` is stated as 0 to\n * keep it off the two lines, which sit flush.\n *\n * The label and the check are placed by hand, because a grid without explicit\n * areas fills row-major: the name would take the first cell, the sentence the\n * second \u2014 beside it \u2014 and the check the third line. The icon seat needs no\n * placement at all: the family rule takes it out of the line with `display: none`,\n * and a `display: none` child generates no box to place. */\nhtml[data-dsh-ccd-style="true"] [data-menu-material][data-ccd-permission-menu] [role="menuitem"][data-ccd-permission-mode] {\n box-sizing: border-box;\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n align-items: center;\n row-gap: 0;\n min-height: 40px;\n padding-block: 2px;\n}\n\nhtml[data-dsh-ccd-style="true"] [data-menu-material][data-ccd-permission-menu] [role="menuitem"][data-ccd-permission-mode] > ._itemLabel_4ub78_190 {\n grid-area: 1 / 1;\n}\n\n/* The second line, in the document\'s \u5F31\u5316\u58A8: the reference measures its sentence at\n * 137/255, which is this palette\'s muted step, a clear one below the name above it.\n * It is one line and it is not narrowed \u2014 `nowrap` is what lets the card size itself\n * to the longest sentence (its own measure lands just inside the primitive\'s 360px\n * ceiling), which is exactly what the reference does and what this family\'s rows,\n * being single lines of a name, never have to decide. */\nhtml[data-dsh-ccd-style="true"] [data-menu-material][data-ccd-permission-menu] [data-ccd-permission-desc] {\n grid-area: 2 / 1;\n overflow: hidden;\n color: var(--ccd-text-muted);\n font-size: 12px;\n line-height: 16px;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* The trailing mark is the host\'s own selected check \u2014 an accent `\u2713` by\n * `theme/tokens.css`, never a fill \u2014 and it centres against the two lines rather\n * than against the name. */\nhtml[data-dsh-ccd-style="true"] [data-menu-material][data-ccd-permission-menu] [role="menuitem"][data-ccd-permission-mode] > ._check_4ub78_179 {\n grid-area: 1 / 2 / span 2;\n align-self: center;\n}\n\n/* The one badge, as the reference draws it: a 16px pill beside the name, not the\n * 8px superscript the host sets. The host\'s own `.badge` is `font-size: 8px` at\n * weight 600 with `letter-spacing: .2px`, baseline-aligned into the name\'s line\n * (`align-self: flex-start` under a -1px pull) \u2014 every one of those is a\n * superscript\'s, and the reference\'s badge is a tag with a fill and a corner.\n *\n * The fill is the palette\'s \u5206\u6BB5\u8F68\u9053 step, which is the reference\'s measured\n * 243/255, and it is also the one fill that survives this row\'s hover: the pointer\n * fills the row `--ccd-hover` (240/255 light, 49/255 dark), and the track step\n * reads lighter than it in the light theme and darker in the dark one, so the pill\n * is never the fill it sits on. The 4px corner is the smallest in the ladder and a\n * shade rounder than the reference\'s measured 3px; 6px \u2014 the chip and card step \u2014\n * would round a 16px box towards a capsule.\n *\n * The gap the host leaves between a name and its badge (4px) is widened to the\n * reference\'s 6px here, and only here: the pair is one control, and 4px reads\n * cramped beside a 16px pill. */\nhtml[data-dsh-ccd-style="true"] [data-menu-material][data-ccd-permission-menu] [role="menuitem"][data-ccd-permission-mode] .wXeviG_badge {\n align-self: center;\n margin-top: 0;\n padding: 0 5px;\n border-radius: 4px;\n background: var(--ccd-track);\n color: var(--ccd-text-secondary);\n font-size: 12px;\n font-weight: 400;\n letter-spacing: normal;\n line-height: 16px;\n}\n\nhtml[data-dsh-ccd-style="true"] [data-menu-material][data-ccd-permission-menu] [role="menuitem"][data-ccd-permission-mode] .wXeviG_optionLabel {\n column-gap: 6px;\n}\n\n/* The group\'s name, above the rows.\n *\n * It is the host\'s own heading row \u2014 a `role="presentation"` div inside the row\n * group, which is what `Menu` renders for a label entry, written there by\n * `compat/permission-menu.ts` \u2014 so the family rule above already gives it the\n * 12px control-label step, the secondary ink and the 8px start padding every other\n * cell has. The one thing it does not have is this card\'s own breathing room: the\n * reference measures ~29px between the card\'s top edge and the first row, against\n * the 24px a cell of the family\'s rhythm occupies, and the 6px above it is that\n * difference. The name is a step lighter than the rows\' names, which is what a\n * heading above a card of choices should be. */\nhtml[data-dsh-ccd-style="true"] [data-menu-material][data-ccd-permission-menu] ._viewport_4ub78_19 > [data-ccd-permission-heading] {\n padding-block: 6px 2px;\n}\n';
|
|
@@ -4443,7 +4836,7 @@ var menus_default = '/*\n * Host popup surfaces \u2014 menus and panels (DESIGN.
|
|
|
4443
4836
|
var scrollbars_default = "/*\n * Scrollbars (DESIGN.md \xA7\u4E09, \xA7\u56DB).\n *\n * DSH paints its own scrollbars. `ui-theme/src/styles/scrollbar.css` declares\n * five tokens on `body` \u2014 the thumb, its hover step, the thumb's transparent\n * border, the track's end margin and the track's width \u2014 and one un-scoped\n * `::-webkit-scrollbar` family paints them, so every scroller in the window\n * takes the same bar.\n *\n * Three of those five are geometry this sheet must not touch. The transcript's\n * `scrollbar-gutter: stable` reserves the track's width; the sidebar tree's\n * rows are padded by `--dsh-session-list-scrollbar-width` (5px, the host's own\n * value); the settings dialog's column and the menu cards widen their track to\n * 9px and 6px and inset the thumb inside it. Every one of those is measured\n * against the host's own width, so the interface keeps the host's track and\n * changes what is painted in it.\n *\n * What is painted is nothing, most of the time. The reference interface's\n * scrollbars are the platform's overlay scrollers: they appear while the reader\n * is moving something and fade again, so a window at rest carries no bar at\n * all \u2014 and the platform's own scroller, which `scrollbar-color` cannot reach,\n * is a grey pill the width of the system's. So the ink is ours and it is\n * published, not permanent: `compat/scroll-pill.ts` marks a surface\n * `data-ccd-scrolling` while it moves, and the two rules at the bottom of this\n * sheet are the whole of the state \u2014 transparent at rest, this interface's ink\n * while it is moving or while the pointer is on the bar itself.\n *\n * The five page-level surfaces the same module tracks go one step further and\n * carry no ink at all: `data-ccd-scroll-pill` takes the painted thumb out of\n * the picture and a floating pill takes its place. The painted thumb's length\n * is the content's share of the track \u2014 a short conversation, a panel with one\n * page of content, paints a bar as tall as its own viewport \u2014 while the pill is\n * a fixed short capsule that only slides. `.ccd-scroll-pill`'s own rules are at\n * the end of this sheet; the module owns its geometry.\n *\n * The reset is the half that is easy to miss. The standard `scrollbar-width`\n * and `scrollbar-color` properties *suppress* the `::-webkit-scrollbar` rules\n * whenever either is set to anything but `auto`, so one document-wide\n * `scrollbar-width: thin` \u2014 which the other skin installed beside this plugin\n * in this window makes, and which any page-global stylesheet may make \u2014 hands\n * every scroller in the window back to the platform. Stating `auto` puts the\n * host's painter back; in a document that declares neither property it is a\n * no-op. It is stated on the scrolling element itself rather than on an\n * ancestor, because the host declares the same properties on `body` and an\n * inherited value would otherwise be the one in force.\n *\n * The scope is the interface's own scroll surfaces rather than `*`. The host\n * hides the scrollbar on several horizontal rails \u2014 the draft-attachment rail,\n * the turn-nav popover, the breadcrumb trail, the dock strip and the tab strips\n * all declare `scrollbar-width: none` \u2014 and a blanket reset would give every\n * one of them a bar it does not want. This sheet adds no scrollbar anywhere, it\n * only repaints the ones the interface already shows. (The ink token does\n * inherit into whatever sits inside a scoped scroller \u2014 a custom property\n * inherits and cannot be scoped away \u2014 but a rail that hides its scrollbar\n * paints nothing to read it, and `scrollbar-width` is not inherited, so a\n * descendant keeps the value it was given.)\n *\n * The reset reaches into five of those surfaces and no more. Two of the host's\n * rails live *inside* the first list's members: `ConversationRoot` renders the\n * Composer's seat inside the page's scrolling body, and `ChatView` renders the\n * turn navigator beside the transcript column rather than inside it. A blanket\n * descendant form would reach both and put a bar on each, so the second list\n * names only the surfaces that hold scrollers of their own: the transcript\n * column (whose fenced code blocks and wide tables scroll inside it), the host's\n * popup cards (whose scrolling viewport is a child of the card) and this\n * plugin's own two. The two lists are written out in each of the three rules\n * below, because the reset, the absent ink and its reveal have to land on the\n * same elements and CSS has no way to name a set once.\n */\n\n/* The surfaces themselves, and the five whose contents scroll on their own. */\nhtml[data-dsh-ccd-style=\"true\"] :is(\n /* The Conversation page's scroller, and the transcript column inside it. */\n [data-conversation-scroll],\n .icaHSq_scroll,\n /* The sidebar's session/workspace tree, and the Files pane's. */\n ._7514NG_list,\n [data-files-body],\n /* The agent-team panel's body, the Composer's textarea, the settings\n dialog's two columns. */\n ._2tNPVa_body,\n .yhfFVG_scroll,\n .Dws9Sa_options,\n .Dws9Sa_navList,\n /* The host's popup cards. */\n [data-menu-material],\n /* This plugin's own panels and cards. */\n .ccd-model-panel,\n .ccd-model-error,\n .ccd-effort,\n .ccd-context-detail,\n .ccd-settings-menu-list\n),\nhtml[data-dsh-ccd-style=\"true\"] :is(\n .icaHSq_scroll,\n [data-menu-material],\n .ccd-model-panel,\n .ccd-model-error,\n .ccd-effort\n) * {\n scrollbar-width: auto;\n scrollbar-color: auto;\n /* The thumb the host's own rule paints, in this interface's ink. Stated on\n the scroller and on everything inside one, so it also wins on the surfaces\n that rebind `--dsh-scrollbar-thumb` for themselves \u2014 a menu card, the\n settings panel and the dockkit panes all point it at another host alias,\n and an element's own declaration is the one that reaches the scroller\n there. */\n --dsh-scrollbar-thumb: var(--ccd-scrollbar-thumb);\n --dsh-scrollbar-thumb-hover: var(--ccd-scrollbar-thumb-hover);\n}\n\n/* A surface at rest carries no ink. */\nhtml[data-dsh-ccd-style=\"true\"] :is(\n [data-conversation-scroll],\n .icaHSq_scroll,\n ._7514NG_list,\n [data-files-body],\n ._2tNPVa_body,\n .yhfFVG_scroll,\n .Dws9Sa_options,\n .Dws9Sa_navList,\n [data-menu-material],\n .ccd-model-panel,\n .ccd-model-error,\n .ccd-effort,\n .ccd-context-detail,\n .ccd-settings-menu-list\n)::-webkit-scrollbar-thumb,\nhtml[data-dsh-ccd-style=\"true\"] :is(\n .icaHSq_scroll,\n [data-menu-material],\n .ccd-model-panel,\n .ccd-model-error,\n .ccd-effort\n) *::-webkit-scrollbar-thumb {\n background: transparent;\n}\n\n/*\n * The two states the ink has: while the surface moves (the module's mark, which\n * it clears again once the surface has been still), and while the pointer is on\n * the bar itself \u2014 the grab a reader makes without moving anything first.\n */\nhtml[data-dsh-ccd-style=\"true\"] :is(\n [data-conversation-scroll],\n .icaHSq_scroll,\n ._7514NG_list,\n [data-files-body],\n ._2tNPVa_body,\n .yhfFVG_scroll,\n .Dws9Sa_options,\n .Dws9Sa_navList,\n [data-menu-material],\n .ccd-model-panel,\n .ccd-model-error,\n .ccd-effort,\n .ccd-context-detail,\n .ccd-settings-menu-list\n)[data-ccd-scrolling]::-webkit-scrollbar-thumb,\nhtml[data-dsh-ccd-style=\"true\"] :is(\n .icaHSq_scroll,\n [data-menu-material],\n .ccd-model-panel,\n .ccd-model-error,\n .ccd-effort\n) *[data-ccd-scrolling]::-webkit-scrollbar-thumb,\nhtml[data-dsh-ccd-style=\"true\"] :is(\n [data-conversation-scroll],\n .icaHSq_scroll,\n ._7514NG_list,\n [data-files-body],\n ._2tNPVa_body,\n .yhfFVG_scroll,\n .Dws9Sa_options,\n .Dws9Sa_navList,\n [data-menu-material],\n .ccd-model-panel,\n .ccd-model-error,\n .ccd-effort,\n .ccd-context-detail,\n .ccd-settings-menu-list\n)::-webkit-scrollbar-thumb:hover,\nhtml[data-dsh-ccd-style=\"true\"] :is(\n .icaHSq_scroll,\n [data-menu-material],\n .ccd-model-panel,\n .ccd-model-error,\n .ccd-effort\n) *::-webkit-scrollbar-thumb:hover {\n background: var(--ccd-scrollbar-thumb);\n}\n\n/*\n * The five pill surfaces keep no ink at all \u2014 the floating capsule speaks for\n * them. They are named by the module's own mark rather than by the two lists,\n * so the sheet never has to know which of the surfaces those are. The module\n * leaves them out of the moving mark, and the two selectors that repeat it here\n * are the belt to that brace: the bar under the pointer and the bar during a\n * drag both stay empty.\n */\nhtml[data-dsh-ccd-style=\"true\"] [data-ccd-scroll-pill]::-webkit-scrollbar-thumb,\nhtml[data-dsh-ccd-style=\"true\"] [data-ccd-scroll-pill][data-ccd-scrolling]::-webkit-scrollbar-thumb,\nhtml[data-dsh-ccd-style=\"true\"] [data-ccd-scroll-pill]::-webkit-scrollbar-thumb:hover {\n background: transparent;\n}\n\n/*\n * The pill: one fixed layer for the whole window, one absolutely positioned\n * capsule per tracked surface, moved with a transform the module computes. It\n * takes no pointer events \u2014 the host's own thumb is still laid out under it, and\n * the capsule is sized and centred to sit inside it, so a drag that starts on\n * the pill grabs that bar.\n */\n[data-ccd-scroll-layer] {\n position: fixed;\n inset: 0;\n z-index: 40;\n pointer-events: none;\n}\n\n/* The macOS host excludes body > :not(#root) from native window dragging.\n This full-window decoration matches that rule even with pointer-events:\n none, so reset its app-region to avoid erasing the header's drag surface.\n Repeat :not(#root) to outrank the host's ID specificity; other portals keep\n their no-drag regions. */\nhtml[data-dsh-ccd-style=\"true\"][data-platform=\"darwin\"] body > [data-ccd-scroll-layer]:not(#root) {\n -webkit-app-region: initial;\n}\n\n[data-ccd-scroll-thumb] {\n position: absolute;\n top: 0;\n left: 0;\n box-sizing: border-box;\n width: 4px;\n border-radius: 999px;\n background: var(--ccd-scrollbar-thumb);\n opacity: 0;\n transition: opacity 140ms ease-out;\n}\n\n[data-ccd-scroll-thumb][data-ccd-scrolling] {\n opacity: 1;\n}\n\n@media (prefers-reduced-motion: reduce) {\n [data-ccd-scroll-thumb] { transition: none; }\n}\n";
|
|
4444
4837
|
|
|
4445
4838
|
// src/client/theme/tokens.css
|
|
4446
|
-
var tokens_default = "/* CCD geometry, type, artwork and light/dark colour variables.\n * Colours are centralized here; the dark palette follows the host's body marker.\n */\nhtml[data-dsh-ccd-style=\"true\"] {\n /* Geometry */\n /* Top of the blank-session greeting block. The reference puts the title a\n short step below the header instead of centring it, so the statistics card\n hanging under it still clears the Composer on a short window. */\n --ccd-hero-top: 16px;\n --ccd-sidebar-width: 264px;\n --ccd-sidebar-inline-padding: 10px;\n --ccd-sidebar-bottom-padding: 8px;\n --ccd-row-height: 28px;\n /* Workspace (group) row. The host's own 34px, pinned because the browsing\n region's merged header band is this same box: the section header parks on\n the row's line and the two have to agree to the pixel. */\n --ccd-workspace-row-height: 34px;\n /* Navigation stack. The reference's three rows measured 26px tall on a 26.5px\n pitch \u2014 contiguous, not separated \u2014 against the host's own 36px/7px row. */\n --ccd-nav-row-height: 26px;\n --ccd-nav-gap: 1px;\n --ccd-radius-row: 8px;\n --ccd-radius-panel: 12px;\n /* View-options reference: 20 device px corners at @2x. */\n --ccd-view-menu-radius: 10px;\n /* Assistant table card. The reference crop's corner curve fits a 12-device-px\n radius at @2x, so the card is rounder than the row radius it borrows its\n scale from. */\n --ccd-table-radius: 6px;\n /* Fenced code block card. The reference code block shows the same 12-device-px\n curve at @2x as the table card, so the transcript's two boxed surfaces stay\n one family. */\n --ccd-code-radius: 6px;\n --ccd-composer-width: 770px;\n --ccd-composer-min-height: 42px;\n --ccd-composer-radius: 10px;\n --ccd-composer-padding: 8px 14px;\n --ccd-composer-padding-block: 8px;\n --ccd-composer-padding-inline: 14px;\n --ccd-composer-row-gap: 4px;\n --ccd-composer-row-inset: 4px;\n --ccd-composer-bottom-gap: 5px;\n --ccd-control-row-height: 28px;\n /* Status-bar chip. The tool row's controls are all this one box: the row's\n 28px is the layout line, and every control inside it is a chip of this\n height rather than a box stretched to the line. The reference draws the\n model chip's fill as one label line \u2014 40 device px at @2x, so the row's\n 12px label sits 5.5px from the chip's top and bottom where the row itself\n would leave it 10px \u2014 and the same box now carries the command plus, the\n permission control, the voice control, the statistics readouts and the\n context ring, so a pointer crossing the row finds one fill height instead\n of three. The row keeps `--ccd-control-row-height`: the dock, the ring's\n seat and the trailing cluster are all measured from it. The corner follows\n the height \u2014 12 device px at @2x, the same curve the transcript's table and\n code cards carry, rather than the row's 8px radius, which is nearly half of\n a 20px chip and would round every fill towards a pill. */\n --ccd-chip-height: 20px;\n --ccd-chip-radius: 6px;\n /* The chip family's inline padding: the model/effort chips, the permission\n control and the two square glyph buttons (plus, voice), whose boxes are\n their 14px artwork plus one of these on each side. Six to seven px is the\n whole range in the reference; the readouts and the ring keep their own\n slightly tighter 6px, which is a 1px difference in ink and nothing in a\n fill's shape. */\n --ccd-chip-padding-inline: 7px;\n --ccd-send-size: 24px;\n --ccd-send-reserve: 30px;\n /* One gap for the whole status row: the row itself, the leading tools group,\n its modes cluster, the trailing cluster (the native readouts, the context\n ring, the model/effort pair) and the statistics dock all space their\n children with this. The host's own row ran 12px in the leading groups and\n the plugin's 6px in the trailing one, which read as two different rows in\n one bar. Each control carries its own 6-7px inner padding, so an even box\n gap also reads as an even visible rhythm. */\n --ccd-cluster-gap: 6px;\n /* Context ring. The host draws it as 14px artwork (`ContextMeter.module.css`:\n viewBox 0 0 14 14, cx=cy=7, r=5.5, 2px stroke, round cap) inside a 28px\n trigger with 6px of side padding, so the trigger's own box is 26px and the\n seat the status bar has to hold for it \u2014 that box plus one cluster gap \u2014 is\n 32px. The host's ring and this plugin's empty-state ring hang in that one\n seat, which is what keeps the ring's position and size the same before and\n after a session has usage. */\n --ccd-context-ring-size: 14px;\n --ccd-context-ring-trigger: 26px;\n --ccd-context-ring-seat: calc(var(--ccd-context-ring-trigger) + var(--ccd-cluster-gap));\n /* Context breakdown panel. Measured off the supplied reference at @2x\n (698\xD7178 for the collapsed state, 828\xD71006 for the expanded one): a 361px\n card with a 1px outline and a 12px corner, 12px inline and 8px top padding,\n a 13px/20px header, a 4px bar 5px under it, 19px value rows, 17px detail\n rows, and 10px swatches. The width is the one number the crop cut off, so\n it is the reference's own rounded value: the bar and the percentage column\n both end exactly at 360 \u2212 2 \u2212 24 = 334px, which is what the crop shows. */\n --ccd-context-panel-width: 360px;\n --ccd-context-panel-radius: 12px;\n --ccd-context-panel-padding-block: 8px;\n --ccd-context-panel-padding-inline: 12px;\n --ccd-context-panel-gap: 8px;\n --ccd-context-row-height: 19px;\n --ccd-context-detail-height: 17px;\n --ccd-context-swatch-size: 10px;\n --ccd-context-swatch-radius: 3px;\n --ccd-context-bar-height: 4px;\n --ccd-context-bar-radius: 2px;\n --ccd-context-bar-gap: 1px;\n /* 5px under the header line, and nothing below: the card's own bottom padding\n is what follows the bar in the collapsed state, and the rows state their\n own 4px above themselves in the expanded one. */\n --ccd-context-bar-margin-block: 5px 0;\n /* The buffer segment's own step away from the used group: the reference\n leaves 4px there where every used segment is 1px apart, because the grey\n block is a reservation rather than another category. */\n --ccd-context-bar-reserve-gap: 4px;\n /* Ten detail rows before a group scrolls \u2014 the reference's own visible run,\n and what keeps a session with 85 tools from drawing a 1500px panel. */\n --ccd-context-detail-max: 170px;\n /* The two value columns: the token column's right edge sits 48px in from the\n content edge (measured 618 of 715 device px), which is a 40px percentage\n column plus this gap. */\n --ccd-context-percent-width: 40px;\n --ccd-context-column-gap: 8px;\n /* The status bar's own glyph size: the command plus, the statistics readouts\n and the ring all draw 14px artwork in the 28px control row. The\n speech-to-text control draws 18px of its own; the composer sheet brings that\n one down to this size. The permission control draws no glyph at all \u2014 it is\n its text (composer.css). */\n --ccd-row-icon-size: 14px;\n --ccd-account-menu-width: 236px;\n --ccd-account-menu-padding: 5px;\n --ccd-account-row-height: 28px;\n --ccd-account-trigger-height: 30px;\n /* Conversation header action cluster. The CCD reference keeps every control\n on one 30px centre pitch with a 12px trailing inset (measured at @2x from\n docs/reference/ccd-conversation.png: icon centres 28.75/30/30px apart, the\n trailing glyph centre 25.75px from the window edge). That inset is what the\n cluster takes while the Composer is narrower than the column and centred in\n it; where the Composer fills the column, `conversation.css` ends the\n cluster on the input surface's own border instead, which is the line the\n reference puts the closing control on and a measurement rather than a\n second token. */\n --ccd-header-action-size: 28px;\n --ccd-header-action-compact: 24px;\n --ccd-header-icon-size: 16px;\n --ccd-header-action-gap: 2px;\n --ccd-header-trailing-inset: 12px;\n --ccd-header-menu-border: #d6d6d6;\n --ccd-header-menu-separator: #e2e2e2;\n --ccd-header-menu-danger: #b4262b;\n --ccd-menu-danger: #c04742;\n --ccd-menu-danger-ink: #ffffff;\n --ccd-header-trigger-hover: #f0f0ef;\n --ccd-header-project-fill: #e5e5e5;\n --ccd-header-project-active: #cfcfcf;\n /* Transcript edge fade. 24px is the band the host's own internal-request\n viewer uses for `fadeTop`\uFF0F`fadeBottom`, so the whole interface softens its\n scroll viewport edges by the same amount. */\n --ccd-edge-fade: 24px;\n /* Jump-to-bottom control. The reference's own affordance measures 40\xD744\n device px at @2x \u2014 a ~21px mark above the Composer \u2014 against the host's\n 34px floating circle, so the box shrinks to this one step while the glyph\n inside it keeps the host's 16px seat. */\n --ccd-to-bottom-size: 22px;\n /* Right panel (the Files, terminal and browser tabs). The reference draws it\n as a floating card: 16 device px in from the column's top, right and bottom\n edges; a 12px corner (measured off the fill: the curve is 20 device px at\n @2x from its first inset pixel to the straight edge, plus the 1px outline);\n a 32px header strip whose 24px tab mark sits 4px under the card's top; and\n an 8px pane gutter (16 device px), which is where the Files pane's own row\n line begins. */\n --ccd-right-panel-inset: 8px;\n --ccd-right-panel-radius: var(--ccd-radius-panel);\n --ccd-right-panel-strip-height: 32px;\n --ccd-right-panel-gutter: 8px;\n --ccd-right-panel-gap: 12px;\n /* The tab's own mark, measured off the reference: a 24px square (5px corner,\n carried by the chip radius) tinted #d3e2f9 behind the 16px glyph. */\n --ccd-right-panel-glyph-size: 24px;\n /* Files tree. The reference's rows run on a 24px pitch (48 device px at @2x)\n where the host's 13px/1.5 row draws 29.5px, and its pane header is the\n 26px rounded field over them. */\n --ccd-files-row-height: 24px;\n --ccd-files-field-height: 26px;\n --ccd-files-glyph-size: 16px;\n\n /* Claude Desktop 2.31226.1's active flat frame, confirmed by window capture:\n gray-10 canvas; sidebar mixes gray-20 and gray-10 equally in sRGB. */\n --ccd-canvas: #fcfcfb;\n --ccd-sidebar: #fbfbf9;\n --ccd-card: #ffffff;\n /* Segmented-control track. The reference measures #f6f6f4 against a white\n page; the same step down from this canvas keeps the same contrast. */\n --ccd-track: #f3f3f2;\n --ccd-raised: #f0f0ef;\n --ccd-hover: #f0f0ee;\n /* Sampled from the supplied view-options reference, separate from the\n warmer surfaces used by Composer and the host's other menus. */\n --ccd-view-menu-hover: #f5f5f5;\n --ccd-view-menu-text: #0b0b0b;\n --ccd-view-menu-muted: #9b9994;\n --ccd-view-menu-border: #d7d7d6;\n --ccd-view-menu-separator: #ebebeb;\n --ccd-view-menu-accent: #2a78d6;\n --ccd-view-menu-shadow: 0 1px 3px rgba(0, 0, 0, .08), 0 8px 20px rgba(0, 0, 0, .12), 0 16px 32px rgba(0, 0, 0, .06);\n --ccd-selected: #ecebe8;\n /* The fill a control takes while it sits on a row that is already filled:\n a Session row paints `--ccd-selected` under the pointer, so its \u22EE takes\n this one to stay a step below what it stands on. Sampled from the supplied\n reference at @2x, where that row measures #edece9 under the control's own\n #e1e0dd \u2014 the same 12/255 step, and the relationship `theme/palette.ts`\n derives for a configured canvas. */\n --ccd-hover-strong: #e1e0dd;\n /* The right panel's tab mark: the reference's own blue-tinted square,\n sampled at #d3e2f9 with the accent for its glyph. It is the one tinted seat\n in this plugin's surfaces, so it is stated rather than derived. */\n --ccd-right-panel-glyph-bg: #d3e2f9;\n /* The Files pane's own ink, sampled off the reference at @2x. Its tree names\n and its empty state's title measure #0b0b0b \u2014 a step below this palette's\n `--ccd-text`, the ink the rest of the interface draws \u2014 and its tab title\n measures #52514f, between `--ccd-text` and `--ccd-text-secondary`. The tab\n mark's glyph is the one blue that is not this palette's accent: the\n reference draws it #304e91 on the #d3e2f9 square. */\n --ccd-files-ink: #0b0b0b;\n --ccd-right-panel-tab-ink: #52514f;\n --ccd-files-chip-ink: #304e91;\n\n /* Lines */\n --ccd-border: #e3e3e1;\n --ccd-border-strong: #e0e0df;\n --ccd-border-soft: #ebebe9;\n /* The rule under a table head reads one step stronger than the card outline\n in the reference (#d9d9d9 against #e3e3e3). */\n --ccd-table-head-line: #d9d9d8;\n /* The right panel's card outline, sampled at #d7d7d6 \u2014 one step stronger than\n the row outline above and the same ink the menu cards' own border carries,\n which is what makes a floating panel and a menu read as one material. */\n --ccd-right-panel-border: #d7d7d6;\n --ccd-browser-canvas: #f7f7f7;\n --ccd-browser-dot: #d1d0ce;\n\n /* Scrollbar ink. The host paints the scrollbars themselves\n (`ui-theme/src/styles/scrollbar.css`: a 5px track and a pill thumb read\n from `--dsh-scrollbar-thumb`), and this interface keeps that geometry \u2014\n the transcript's stable gutter, the sidebar's\n `--dsh-session-list-scrollbar-width` padding and the dockkit panes are all\n measured against the host's own width \u2014 so these two tokens are the ink\n alone, bound in theme/scrollbars.css.\n The ink is near-black by decision rather than by measurement: the bar is\n 5px wide and carries no surface of its own, so none of this palette's\n surface steps \u2014 5% to 14% black \u2014 reads at that size, and the interface\n this one follows draws a dark scrollbar. Hover goes one step further down,\n the direction the host's own hover step takes. */\n --ccd-scrollbar-thumb: #333331;\n --ccd-scrollbar-thumb-hover: #171716;\n\n /* Text */\n --ccd-text: #1f1f1d;\n --ccd-text-secondary: #6f6f6a;\n --ccd-text-muted: #8f8f8a;\n --ccd-text-placeholder: #89877f;\n /* One step below full strength: a row that is not the current one. The\n reference paints its inactive navigation rows at #4a4a47 \u2014 between this\n palette's --ccd-text and --ccd-text-secondary \u2014 and keeps the current row\n at full black, so the stack reads as one current entry plus quieter\n neighbours instead of three equally loud labels. */\n --ccd-text-nav: #4a4a47;\n /* Archived Session row. The host fades that title with its own caption ink,\n but this plugin's row ink outranks the host pair, so the state needs a step\n of its own: one above --ccd-text-muted on the same warm ramp, which is the\n faded row the reference draws while still reading against the sidebar. A row\n that is current or under the pointer takes the second value, because the\n hover and selected fills sit a step below the sidebar the first one was\n measured on. */\n --ccd-text-archived: #a0a09b;\n --ccd-text-archived-hover: #8a8a85;\n\n /* New-session statistics card. The reference panel is a stacked translucent\n grey: the card is 5% black over the canvas and a stat tile (and an empty\n heat cell) is 10% black over the card, so these are the measured opaque\n equivalents. The four heat levels are the reference's own `hsl(217 70% L%)`\n with L = 72\uFF0F64\uFF0F56\uFF0F48, i.e. \"that day's value / the busiest day\" in four\n equal steps. */\n --ccd-stats-panel: #f0f0f0;\n --ccd-stats-tile: #dadada;\n /* The card header's active tab: the reference paints `fill-ghost-hover`, the\n same 5% black as the card itself, over the card \u2014 one step lighter than a\n stat tile. */\n --ccd-stats-tab: #e6e6e6;\n --ccd-stats-label: #898781;\n --ccd-stats-value: #0b0b0b;\n --ccd-stats-heat-1: hsl(217 70% 72%);\n --ccd-stats-heat-2: hsl(217 70% 64%);\n --ccd-stats-heat-3: hsl(217 70% 56%);\n --ccd-stats-heat-4: hsl(217 70% 48%);\n /* The `Models` view colours its bars and legend by rank:\n `hsl(217 70% min(88, 58 + 7n)%)`, which flattens into these six shades. */\n --ccd-stats-model-1: hsl(217 70% 58%);\n --ccd-stats-model-2: hsl(217 70% 65%);\n --ccd-stats-model-3: hsl(217 70% 72%);\n --ccd-stats-model-4: hsl(217 70% 79%);\n --ccd-stats-model-5: hsl(217 70% 86%);\n --ccd-stats-model-6: hsl(217 70% 88%);\n /* Tooltips are the reference's dark popup in both themes; on a dark panel it\n steps up to a mid grey so the box keeps its contrast. */\n --ccd-stats-tip-bg: #0c0c0c;\n --ccd-stats-tip-fg: #f7f7f5;\n\n /* Assistant Markdown palette matched to the CCD transcript reference. */\n --ccd-link: #2b61b5;\n --ccd-link-underline: #a7badd;\n --ccd-inline-code-text: #b63630;\n --ccd-inline-code-fill: #f1f1f0;\n --ccd-inline-code-border: #e2e2e0;\n\n /* Context meter. The host paints the ring's consumed arc with the same\n tertiary label grey as the percentage beside it, so the reading carries no\n weight. The accent blue is this plugin's one emphasis colour: taking it\n through `var()` lets the ring follow `--ccd-accent` the moment that token\n exists, and the fallback is the blue measured off the target arc\n (docs/issues/img/12-2.png), so the ring is never left grey meanwhile. The\n track underneath stays the host's `--dsw-alias-border-l3`, which\n theme/tokens.ts already overrides. */\n --ccd-context-ring-fill: var(--ccd-accent, #4b76ce);\n\n /* Context breakdown categories, sampled from the supplied reference's bar and\n swatches. Seven steps, in the order the panel and the bar draw them: the\n reference's own hues, which are deliberately outside this palette's blue\n ramp \u2014 a breakdown that reused one hue family would make five categories\n read as one. `--ccd-context-free` is both the free-space swatch and the\n bar's track, exactly as the reference draws them. */\n --ccd-context-mcp: #2a78d8;\n --ccd-context-tools: #e96a33;\n --ccd-context-system: #1ead7c;\n --ccd-context-skills: #eda105;\n --ccd-context-memory: #ea7ca4;\n /* The conversation's own row. The reference's crop happens to hold no\n messages at all \u2014 89k of its 9% reading is prompt, tools and skills \u2014 so\n this one is chosen rather than sampled: the same lightness and chroma as\n its neighbours, one hue family over, so it cannot be mistaken for the\n accent blue of MCP tools. */\n --ccd-context-messages: #8b6fd4;\n --ccd-context-buffer: #c2c0b8;\n --ccd-context-free: #eeeeee;\n\n /* The plugin's one emphasis blue: the selected check in every Menu surface,\n the context ring, and whatever else has to read as \"this one is chosen\".\n It is the blue measured off the reference check mark\n (docs/issues/img/01-3.png: the stroke's own pixels are #4b76d0/#507ad2, so\n the token takes the darker mode) and shares the stats heat ramp's hue\n family (hsl(217 \u2026)), which keeps every blue in the interface one voice. */\n --ccd-accent: #4b76ce;\n --ccd-hero-ripple-ink: #2477ac;\n\n /* State colours that DSH already owns semantics for. */\n --ccd-diff-added: #508c48;\n --ccd-diff-removed: #b33156;\n\n /* Type. The two fallback tails are what a configured family is prepended to\n (see theme/tokens.ts); with nothing configured the consuming variables keep\n the built-in stacks. Geist leads the interface tail because it is the\n upstream design Anthropic Sans was cut from; it is resolved from the system\n font list, so the tail degrades to the platform stack when it is not\n installed (assets/fonts/ ships the file for a local install only). */\n --ccd-font-fallback: Geist, -apple-system, BlinkMacSystemFont, \"SF Pro Text\", \"Helvetica Neue\",\n \"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", sans-serif;\n --ccd-code-fallback: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace;\n --ccd-font-ui: var(--ccd-font-fallback);\n --ccd-font-code: var(--ccd-code-fallback);\n --ccd-font-size-body: 14px;\n --ccd-font-size-muted: 13px;\n /* Blank-session title. The reference's own type scale puts the greeting on\n `--cds-font-size-title` (1.25rem, ~20px) with a 25px leading at weight 400,\n which is a good deal quieter than the 26px this used to be. */\n --ccd-font-size-headline: 20px;\n /* Transcript headings. The reference's ladder sits one step under the host's\n (DSH draws h1/h2/h3 at 21/19/18px): its own crop measures a 19px h1 and a\n 16px h2 over the 14px body \u2014 27 and 23 device px of CJK ink against the\n 21-device-px body line at @2x \u2014 and the h2 is confirmed independently by\n the inline-code chip, whose 0.95em/1.2 rule this plugin already matches:\n it lands on the reference's 33 device px exactly at 16px. h3 has no crop of\n its own and takes the next step down. */\n --ccd-font-size-h1: 19px;\n --ccd-font-size-h2: 16px;\n --ccd-font-size-h3: 15px;\n /* Transcript leading. The host sets the assistant body at 14px/24px; the\n reference runs the same 14px on a 20px line. Its own window measures a\n 30.7-device-px pitch between two lines of a paragraph where this plugin's\n 24px draws 36.6, and both shots put their body's CJK ink at 20 device px\n with a 770px Composer at 1176 (1.53 device px per CSS px), so the two are\n read at one scale. The reference's paragraph-to-paragraph gap is 46 device\n px \u2014 this 20px line plus the 10px margin both sides already share. */\n --ccd-transcript-line-height: 20px;\n /* The one step up from the host's own 400: used where a control has to read\n as a chosen value rather than as chrome (model, reasoning effort). */\n --ccd-font-weight-strong: 600;\n\n /* Hand-drawn 16px marks matching the CCD interaction recording: return\n arrow and a small square in a ring. The host keeps its buttons, handlers\n and SVG state markers; these masks replace only their visible artwork. */\n --ccd-send-radius: 6px;\n --ccd-icon-send: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M12 4.5V8A2 2 0 0 1 10 10H3.5M6.5 7L3.5 10L6.5 13' stroke='black' stroke-width='1.25' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");\n --ccd-icon-stop: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='5.5' fill='none' stroke='black' stroke-width='1.25'/%3E%3Crect x='6' y='6' width='4' height='4' rx='.6' fill='black'/%3E%3C/svg%3E\");\n\n /* The header's three painted glyphs, taken from DSH 0.2.0-rc.2 shared\n artwork (MIT) so no host component is copied:\n - IconRightUpOutlineRegular (`ui-primitives/icons`) is the generic \"open\"\n glyph the native open-in-app control falls back to;\n - the terminal prompt is `ui-sidebar-terminal/TerminalIcon`'s own 16px\n geometry, the glyph that view's tab title draws;\n - the globe is `ui-primitives`' IconGlobeOutlineRegular, the glyph\n `ui-sidebar-browser` draws in its tab title.\n Masks let the host keep ownership of its buttons while the plugin supplies\n the artwork. */\n --ccd-icon-open: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M11.7256 2.77441C12.5538 2.77469 13.2256 3.44616 13.2256 4.27441V10.1416H12.2256V4.27441C12.2256 3.99844 12.0015 3.77469 11.7256 3.77441H5.7207V2.77441H11.7256Z' fill='black'/%3E%3Cpath d='M2.77441 13.2255L12.3756 3.62427' stroke='black'/%3E%3C/svg%3E\");\n --ccd-icon-terminal: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M3 4L7 8L3 12' stroke='black'/%3E%3Cpath d='M9 12H13' stroke='black'/%3E%3C/svg%3E\");\n --ccd-icon-browser: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M7.99986 14.0887C11.3626 14.0887 14.0886 11.3627 14.0886 7.99998C14.0886 4.63727 11.3626 1.91125 7.99986 1.91125C4.63715 1.91125 1.91113 4.63727 1.91113 7.99998C1.91113 11.3627 4.63715 14.0887 7.99986 14.0887Z' stroke='black'/%3E%3Cpath d='M2.34619 8H13.6538' stroke='black' stroke-linecap='square'/%3E%3Cpath d='M7.99976 14.0889C9.23509 14.0889 10.1743 11.3629 10.1743 8.00006C10.1743 4.63739 9.23509 1.91138 7.99976 1.91138' stroke='black'/%3E%3Cpath d='M7.99973 14.0889C6.76445 14.0889 5.8252 11.3629 5.8252 8.00006C5.8252 4.63739 6.76445 1.91138 7.99973 1.91138' stroke='black'/%3E%3C/svg%3E\");\n\n /* The session menu's own mark, stacked. The host draws\n `IconEllipsisOutlineRegular` (`ui-primitives`, MIT) as three 2px dots on one\n horizontal line \u2014 circles at (3, 8), (8, 8) and (13, 8) of a 16px box \u2014 and\n the reference stacks them, so this is that same geometry transposed to\n (8, 3), (8, 8) and (8, 13): one dot size, one 5-unit pitch, one 16px box,\n carried as a mask over the button's own `currentColor` so the host keeps its\n button and this interface supplies the mark. */\n --ccd-icon-more: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M8 4C8.55228 4 9 3.55228 9 3C9 2.44772 8.55228 2 8 2C7.44772 2 7 2.44772 7 3C7 3.55228 7.44772 4 8 4Z' fill='black'/%3E%3Cpath d='M8 9C8.55228 9 9 8.55228 9 8C9 7.44772 8.55228 7 8 7C7.44772 7 7 7.44772 7 8C7 8.55228 7.44772 9 8 9Z' fill='black'/%3E%3Cpath d='M8 14C8.55228 14 9 13.55228 9 13C9 12.44772 8.55228 12 8 12C7.44772 12 7 12.44772 7 13C7 13.55228 7.44772 14 8 14Z' fill='black'/%3E%3C/svg%3E\");\n\n /* A bare plus, the one glyph the reference repeats on every actionable row.\n It is DSH's own `IconPlusOutlineArtwork` geometry (ui-primitives, MIT): two\n 1px strokes across a 16px box, so the plugin can put the same mark on the\n Workspace header's add button and on a project row's create button without\n authoring artwork of its own. */\n --ccd-icon-plus: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M8 2V14' stroke='black'/%3E%3Cpath d='M2 8H14' stroke='black'/%3E%3C/svg%3E\");\n\n /* The workspace picker chip's leading mark. DSH switches that glyph with the\n chip's own state \u2014 `IconFolderOpenRegular` once a workspace is chosen, a\n closed folder while the chip only carries a placeholder \u2014 and the open one\n draws a slanted front panel over a translucent back panel, where the\n reference's button leads with one closed folder in either state. The\n geometry below is DSH's own `FolderCloseArtwork` (`ui-primitives`, MIT) on\n its 16-unit grid and 1px stroke, with the artwork's second path \u2014 the rule\n line across the body \u2014 left out: the reference draws the body empty, and\n the tab, the step and the four rounded corners are the whole of the mark. */\n --ccd-icon-folder: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M1.50439 3.11059C1.50439 2.55831 1.95211 2.1106 2.50439 2.1106H5.43389C5.67773 2.1106 5.91318 2.19969 6.09593 2.36113L7.71649 3.79265C7.89924 3.95409 8.1347 4.04319 8.3785 4.04319H13.4958C14.0481 4.04319 14.4958 4.4909 14.4958 5.04319V12.8894C14.4958 13.4417 14.0481 13.8894 13.4958 13.8894H2.50439C1.95211 13.8894 1.50439 13.4417 1.50439 12.8894V4.04319V3.11059Z' stroke='black'/%3E%3C/svg%3E\");\n\n /* The reference's disclosure mark is a 1px line chevron where the host draws a\n filled triangle; this is DSH's own `IconChevronRightOutlineArtwork` geometry\n (ui-primitives, MIT). It is painted over the host's mark slot, so the host's\n own `arrowOpen` state still drives the quarter turn. */\n --ccd-icon-chevron-right: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M6 12L9.29289 8.70711C9.68342 8.31658 9.68342 7.68342 9.29289 7.29289L6 4' stroke='black'/%3E%3C/svg%3E\");\n\n /* The Files tree's two file marks, measured off the reference at @2x: a page\n with its bottom-right corner folded, and the angle-bracket pair, each drawn\n 8\u20139px wide inside the 16px seat the host's own type glyphs occupy (the\n reference's names then start on the same 33px the row's own gap states).\n DSH classifies a file by name and extension too, but its set is a\n different one \u2014 `.gitignore` is a git file to the host and a plain page to\n the reference, `LICENSE` a page to both \u2014 so `right-panel.css` carries the\n reference's own split as an extension list rather than reading the host's. */\n --ccd-icon-file: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M5.5 4.5H10.5C11.0523 4.5 11.5 4.94772 11.5 5.5V8.5L8.5 11.5H5.5C4.94772 11.5 4.5 11.0523 4.5 10.5V5.5C4.5 4.94772 4.94772 4.5 5.5 4.5Z' stroke='black'/%3E%3Cpath d='M8.5 8.5H11.5L8.5 11.5' stroke='black'/%3E%3C/svg%3E\");\n --ccd-icon-file-code: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M6.2 5.2L4.2 8.05L6.2 10.9' stroke='black' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M9.8 5.2L11.8 8.05L9.8 10.9' stroke='black' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M9.4 4.6L6.6 11.4' stroke='black' stroke-linecap='round'/%3E%3C/svg%3E\");\n\n /* The Files tab's mark: the reference draws a three-row list \u2014 a bullet and a\n rule per row \u2014 12px of ink on the tab's own blue square, so the artwork is\n stated on the square's 24-unit grid. The square itself is a seat the tab\n sheet paints; this is only the ink that sits in it. */\n --ccd-icon-files-list: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='black'%3E%3Crect x='6' y='6.5' width='2' height='2' rx='.5'/%3E%3Crect x='9.5' y='6.5' width='8' height='2' rx='.5'/%3E%3Crect x='6' y='11' width='2' height='2' rx='.5'/%3E%3Crect x='9.5' y='11' width='8' height='2' rx='.5'/%3E%3Crect x='6' y='15.5' width='2' height='2' rx='.5'/%3E%3Crect x='9.5' y='15.5' width='8' height='2' rx='.5'/%3E%3C/g%3E%3C/svg%3E\");\n\n /* The Files pane's magnifier, for the filter field's leading seat and for the\n one control the pane's header adds: a lens and a handle, 13px of ink in the\n 16px box the field's own row draws, one 1px stroke like the marks around it. */\n --ccd-icon-search: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Ccircle cx='6.6' cy='6.6' r='4.6' stroke='black'/%3E%3Cpath d='M9.9 9.9L13.9 13.9' stroke='black' stroke-linecap='round'/%3E%3C/svg%3E\");\n\n /* The archived Session row's leading mark. The host deliberately leaves that\n 16px cell empty, so the plugin paints the same geometry itself: DSH's own\n `IconArchiveOutlineRegular` artwork (ui-primitives, MIT) \u2014 a lid, a body and\n a handle drawn with 1px strokes \u2014 carried as a mask like the marks around\n it, so no third-party asset or copied component enters the stylesheet. */\n --ccd-icon-archive: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M13.5 2.5H2.5C1.94772 2.5 1.5 2.94772 1.5 3.5V4.5C1.5 5.05228 1.94772 5.5 2.5 5.5H13.5C14.0523 5.5 14.5 5.05228 14.5 4.5V3.5C14.5 2.94772 14.0523 2.5 13.5 2.5Z' stroke='black'/%3E%3Cpath d='M2.5 5.5V13.5C2.5 13.7652 2.60536 14.0196 2.79289 14.2071C2.98043 14.3946 3.23478 14.5 3.5 14.5H12.5C12.7652 14.5 13.0196 14.3946 13.2071 14.2071C13.3946 14.0196 13.5 13.7652 13.5 13.5V5.5' stroke='black'/%3E%3Cpath d='M6.5 9.5H9.5' stroke='black'/%3E%3C/svg%3E\");\n\n /* The idle Session row's leading ring. The host hands that cell to the\n plugin's `sidebar.session.row.leading` seat as soon as a row has no status\n of its own, and the running row's activity ring is `ui-primitives`'\n StateDot spinner: a 2-unit stroke on a 9.5-unit radius in a 24-unit box.\n The same circle is carried here as a mask, so the still mark and the\n spinning one are one geometry at the one 8px scale the stylesheet already\n gives the spinner \u2014 and the ring takes the cell's own `currentColor`\n (the host's `--dsw-alias-label-tertiary`, the spinner's ink) instead of an\n authored colour. */\n --ccd-icon-session-ring: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none'%3E%3Ccircle cx='12' cy='12' r='9.5' stroke='black' stroke-width='2'/%3E%3C/svg%3E\");\n\n /* The context ring's track, for the seat an empty session has to fill. The\n host's own `ContextMeter` returns nothing at all until the token meter knows\n a numerator and the context window, so the plugin draws that ring's track\n itself \u2014 the host's exact geometry from `ContextMeter.module.css`\n (viewBox 0 0 14 14, cx=cy=7, r=5.5, 2px stroke) carried as a mask, so the\n colour stays the host track token above. The consumed arc is not drawn: at\n 0% the host's own arc has no length either. */\n --ccd-icon-context-ring: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14' fill='none'%3E%3Ccircle cx='7' cy='7' r='5.5' stroke='black' stroke-width='2'/%3E%3C/svg%3E\");\n\n /* The Composer whale. DeepSeek's own mark is a blue whale with a pale belly,\n so the two inks are the brand pair rather than a derived palette step: the\n artwork only ever appears as this one mark. The dark side lifts the blue a\n step so it keeps its contrast on the dark canvas instead of sinking into\n it; the belly stays the light ink in both schemes, because it is what makes\n the silhouette read at 64px. */\n --ccd-pet-blue: #4d6bfe;\n --ccd-pet-light: #dfe6ff;\n\n /* Elevation */\n --ccd-shadow-card: 0 1px 2px rgb(0 0 0 / 4%);\n --ccd-shadow-segment: 0 1px 2px rgb(0 0 0 / 8%);\n /* The right panel's card, read off the reference's own edges: the canvas\n (252/255) sits at 243 half a pixel beside the card, 235 directly under it\n and back to 251 eight pixels out \u2014 a two-layer shadow, the near one for the\n edge and the far one for the falloff. */\n --ccd-right-panel-shadow: 0 1px 3px rgb(0 0 0 / 4%), 0 5px 14px rgb(0 0 0 / 8%);\n}\n\n/*\n * Host-token aliases belong on `body`, exactly where the host writes them.\n *\n * `--dsw-*` values are not in the document stylesheets: DSH's theme presenter\n * writes them, and this plugin's own overrides, as inline styles on `body`\n * (theme/tokens.ts). A custom property is resolved *where it is declared*, so an\n * alias of a host token declared on the root would reference a variable that is\n * not in scope there: it computes to the guaranteed-invalid value and inherits\n * as invalid into every subtree, and the rule consuming it falls back to\n * `transparent` rather than the host's colour. The empty-state context ring\n * (theme/composer.css, `.yhfFVG_dock::after`) vanished exactly that way.\n */\nhtml[data-dsh-ccd-style=\"true\"] body {\n /* Resolve on body so dark mode and configured palettes both participate. */\n --ccd-composer-focus-border: color-mix(in srgb, var(--ccd-card) 60%, var(--ccd-text-secondary));\n /* The empty state's ring draws the host's track alone. It stays the host's\n own track token (`ContextMeter.module.css` strokes `.y0jqnG_track` with\n `--dsw-alias-border-l3`, which theme/tokens.ts already retints), so an\n empty session shows the same hollow ring the host would draw at 0%. */\n --ccd-context-ring-track: var(--dsw-alias-border-l3);\n}\n\n/*\n * Dark palette.\n *\n * DSH's theme presenter writes `body[data-ds-dark-theme]` from the resolved\n * colour scheme (`@deepseek-ai/dsh-client-ui-theme` client styles, DSH\n * 0.2.0-rc.2), so the plugin follows the host's own switch instead of carrying a\n * second one. The block is a descendant of the root activation marker: the\n * variables land on `body`, cover every colour the light block declares, and\n * stay absent while the plugin is off.\n *\n * The warm-neutral family is kept rather than swapping in the host's bluish\n * greys: the same relationship holds in both schemes (canvas a touch lighter\n * than the sidebar, card the brightest surface), while the recessed steps\n * (track, raised, hover, selected) move *up* in lightness where the light\n * palette moves down, because on a dark canvas a surface reads as raised by\n * catching more light. Lines keep one visible step against the surfaces they\n * separate; text keeps the light palette's descending tone order.\n */\nhtml[data-dsh-ccd-style=\"true\"] body[data-ds-dark-theme] {\n --ccd-hero-ripple-ink: #4aabdb;\n /* The same flat frame uses gray-850 for the dark page and mixes it with\n 20% black for the sidebar. The control track stays below its raised thumb\n while both sit above the darker page. */\n --ccd-canvas: #151515;\n --ccd-sidebar: #111111;\n --ccd-card: #30302e;\n --ccd-header-menu-border: var(--ccd-border);\n --ccd-header-menu-separator: var(--ccd-border-soft);\n --ccd-header-menu-danger: #f08080;\n --ccd-menu-danger: #c04742;\n --ccd-menu-danger-ink: #ffffff;\n --ccd-header-trigger-hover: var(--ccd-hover);\n --ccd-header-project-fill: var(--ccd-track);\n --ccd-header-project-active: var(--ccd-selected);\n --ccd-track: #1f1e1d;\n --ccd-raised: #2f2f2d;\n --ccd-hover: #313130;\n --ccd-view-menu-hover: var(--ccd-selected);\n --ccd-view-menu-text: var(--ccd-text);\n --ccd-view-menu-muted: var(--ccd-text-muted);\n --ccd-view-menu-border: var(--ccd-border);\n --ccd-view-menu-separator: var(--ccd-border-soft);\n --ccd-view-menu-accent: var(--ccd-accent);\n --ccd-view-menu-shadow: 0 1px 3px rgba(0, 0, 0, .4), 0 8px 20px rgba(0, 0, 0, .3), 0 16px 32px rgba(0, 0, 0, .2);\n --ccd-selected: #3a3a37;\n /* The same step as the light side, in the direction this canvas recesses:\n the fill goes one step up from `selected` here \u2014 which happens to land on\n the strong-outline step below, a coincidence of the two ramps rather than\n the reason for the value. */\n --ccd-hover-strong: #474743;\n /* The tab mark's tint, dark side: the accent's own hue over this card, which\n is the #d3e2f9 relationship the light side measures, not its value. */\n --ccd-right-panel-glyph-bg: #2b3a55;\n /* The Files pane's inks go back to the palette's own ramp here: the reference\n is a light-mode capture, and its three sampled values are only meaningful\n as relationships (a step below the body ink, a step above the muted one and\n the accent's own hue) that this side already carries. */\n --ccd-files-ink: var(--ccd-text);\n --ccd-right-panel-tab-ink: var(--ccd-text);\n --ccd-files-chip-ink: var(--ccd-accent);\n\n /* Lines */\n --ccd-border: #3e3e3b;\n --ccd-border-strong: #474743;\n --ccd-border-soft: #343431;\n /* The head rule stays the one line drawn stronger than the card outline. */\n --ccd-table-head-line: #4a4a46;\n /* The card outline's dark side: one step above the row outline, as in the\n light palette. */\n --ccd-right-panel-border: #474743;\n --ccd-browser-canvas: #242423;\n --ccd-browser-dot: #454542;\n\n /* Scrollbar ink, dark side: on this canvas the bar is the one piece of the\n chrome that has to read as raised, so it sits above every surface step\n here and lightens on hover \u2014 the mirror of the light palette, whose ink\n sits below every surface and darkens. */\n --ccd-scrollbar-thumb: #5c5b57;\n --ccd-scrollbar-thumb-hover: #75746f;\n\n /* Text */\n --ccd-text: #f0efec;\n --ccd-text-secondary: #b4b1ab;\n --ccd-text-muted: #918f89;\n /* Same step down from full strength as the light palette: the dark ink sits\n the same distance below --ccd-text as #4a4a47 sits below #1f1f1d. */\n --ccd-text-nav: #d3d0ca;\n --ccd-text-placeholder: #8b8983;\n /* Archived rows, dark side: the same two steps below the row ink, kept\n readable against this sidebar and the selected fill that sits above it. */\n --ccd-text-archived: #82807c;\n --ccd-text-archived-hover: #a6a39c;\n\n /* Composer whale, dark side: the same mark, with the blue raised so it reads\n against this canvas; the belly keeps the light ink. */\n --ccd-pet-blue: #7b93ff;\n --ccd-pet-light: #e8ecff;\n\n /* Statistics card, dark side: the same stacked greys over this canvas, and\n the heat ramp raised one step so the steps stay visible on a dark panel. */\n --ccd-stats-panel: #2c2c2a;\n --ccd-stats-tile: #383835;\n --ccd-stats-tab: #3a3a37;\n --ccd-stats-label: #918f89;\n --ccd-stats-value: #f0efec;\n --ccd-stats-heat-1: hsl(217 62% 78%);\n --ccd-stats-heat-2: hsl(217 64% 70%);\n --ccd-stats-heat-3: hsl(217 66% 62%);\n --ccd-stats-heat-4: hsl(217 70% 54%);\n /* Model ramp, raised for the dark panel; six distinct steps, as the light\n theme's clamped ramp produces. */\n --ccd-stats-model-1: hsl(217 62% 70%);\n --ccd-stats-model-2: hsl(217 64% 75%);\n --ccd-stats-model-3: hsl(217 66% 80%);\n --ccd-stats-model-4: hsl(217 68% 84%);\n --ccd-stats-model-5: hsl(217 70% 87%);\n --ccd-stats-model-6: hsl(217 70% 89%);\n --ccd-stats-tip-bg: #454542;\n --ccd-stats-tip-fg: #f7f7f5;\n\n /* Assistant Markdown palette, lightened for a dark transcript. */\n --ccd-link: #86b0e8;\n --ccd-link-underline: #4a5c76;\n --ccd-inline-code-text: #f0918a;\n --ccd-inline-code-fill: #35332f;\n --ccd-inline-code-border: #454239;\n\n /* Context meter ring, dark side: the same hue as the light accent, one step\n up in lightness (55% \u2192 68%) and saturation so the arc keeps its blue on\n this canvas instead of greying into it. Same `var()` order as the light\n block. */\n --ccd-context-ring-fill: var(--ccd-accent, #789be3);\n\n /* Context breakdown categories, dark side. The reference ships one light\n crop, so these are derived the way the rest of this palette is: the same\n hue, raised in lightness by the step the statistics ramp and the accent\n take on this canvas, so five categories stay five colours instead of\n sinking into the card. The two greys are the exception \u2014 they are surfaces\n rather than inks, so the buffer steps down to a mid warm grey and the free\n track steps up off the card, which is what keeps the reserved block and the\n empty room apart at a glance. */\n --ccd-context-mcp: #5b9ae8;\n --ccd-context-tools: #f0885a;\n --ccd-context-system: #43c79b;\n --ccd-context-skills: #f0b93c;\n --ccd-context-memory: #ef93b5;\n --ccd-context-messages: #a58fe6;\n --ccd-context-buffer: #5f5b53;\n --ccd-context-free: #3f3f3b;\n\n /* The accent, dark side: the same hue and the same 55% \u2192 68% lightness step\n the statistics heat ramp takes on this canvas, so the check keeps its blue\n against the menu's surface instead of sinking into it. The hue is the\n reference's measured blue (220) rather than the ramp's 217, but the two sit\n in one family. */\n --ccd-accent: #789be3;\n\n /* State colours that DSH already owns semantics for. */\n --ccd-diff-added: #7cbd72;\n --ccd-diff-removed: #e08fae;\n\n /* Elevation reads as a shadow under the surface, not as a grey wash. */\n --ccd-shadow-card: 0 1px 2px rgb(0 0 0 / 40%);\n --ccd-shadow-segment: 0 1px 2px rgb(0 0 0 / 50%);\n --ccd-right-panel-shadow: 0 1px 2px rgb(0 0 0 / 35%), 0 4px 10px rgb(0 0 0 / 35%);\n}\n\n/* zanwei effort reference palette, MIT; canvas reads these theme values. */\nhtml[data-dsh-ccd-style=\"true\"] {\n --ccd-effort-color-1: #8c73c9;\n --ccd-effort-color-2: #a17ec2;\n --ccd-effort-color-3: #5f5b58;\n --ccd-effort-color-4: #3f3b38;\n --ccd-effort-color-5: #77736f;\n --ccd-effort-color-6: #edeae8;\n --ccd-effort-color-7: #e0dbd6;\n --ccd-effort-color-8: #ffffff;\n --ccd-effort-color-9: rgba(76, 70, 65, 0.12);\n --ccd-effort-color-10: #2788d6;\n --ccd-effort-color-11: rgba(62, 56, 50, 0.05);\n --ccd-effort-color-12: rgba(62, 56, 50, 0.04);\n --ccd-effort-color-13: rgba(62, 56, 50, 0.06);\n --ccd-effort-color-14: #74706d;\n --ccd-effort-color-15: rgba(82, 76, 70, 0.055);\n --ccd-effort-color-16: rgba(76, 70, 65, 0.1);\n --ccd-effort-color-17: #34312f;\n --ccd-effort-color-18: rgba(35, 31, 29, 0.12);\n --ccd-effort-color-19: #fff;\n --ccd-effort-color-20: rgba(70, 64, 59, 0.035);\n --ccd-effort-color-21: #eeebe9;\n --ccd-effort-color-22: #ece9e7;\n --ccd-effort-color-23: #e2dce3;\n --ccd-effort-color-24: #d9d0df;\n --ccd-effort-color-25: #d0c1da;\n --ccd-effort-color-26: #cdbcd9;\n --ccd-effort-color-27: #cbbad8;\n --ccd-effort-color-28: #d5cadc;\n --ccd-effort-color-29: #c8b5d4;\n --ccd-effort-color-30: #bda6cc;\n --ccd-effort-color-31: #b59bc6;\n --ccd-effort-color-32: #b6b2af;\n --ccd-effort-color-33: rgba(76, 70, 65, 0.13);\n --ccd-effort-color-34: rgba(62, 56, 50, 0.08);\n --ccd-effort-color-35: rgba(62, 56, 50, 0.055);\n --ccd-effort-pixel-leftColor: 210, 206, 214;\n --ccd-effort-pixel-deepViolet: 156, 120, 192;\n --ccd-effort-pixel-deepMid: 156, 132, 192;\n --ccd-effort-pixel-midPurple: 168, 144, 204;\n --ccd-effort-pixel-softMid: 168, 156, 204;\n --ccd-effort-pixel-softLilac: 180, 168, 204;\n --ccd-effort-pixel-paleCool: 192, 180, 204;\n --ccd-effort-pixel-highlightColor: 216, 204, 228;\n --ccd-effort-pixel-peakColor: 232, 224, 242;\n}\n\nhtml[data-dsh-ccd-style=\"true\"] {\n --ccd-effort-shadow: 0 1px 2px rgba(62, 56, 50, .05), 0 4px 10px rgba(62, 56, 50, .04), 0 12px 28px rgba(62, 56, 50, .06);\n}\n\n/*\n * The same reference field on the dark canvas: the purple ramp keeps its\n * left-to-right direction (neutral \u2192 violet) inside the dark range, the\n * neutrals flip so the panel is a card above the transcript, and the knob stays\n * the brightest thing on the track \u2014 its fill is also the tooltip's text\n * colour, so the two keep following one value.\n */\nhtml[data-dsh-ccd-style=\"true\"] body[data-ds-dark-theme] {\n --ccd-effort-color-1: #a48ede;\n --ccd-effort-color-2: #b294d3;\n --ccd-effort-color-3: #e7e4de;\n --ccd-effort-color-4: #f6f4f0;\n --ccd-effort-color-5: #a09b94;\n --ccd-effort-color-6: #3a3936;\n --ccd-effort-color-7: #4b4944;\n --ccd-effort-color-8: #2b2a28;\n --ccd-effort-color-9: rgba(255, 255, 255, 0.1);\n --ccd-effort-color-10: #4d93f8;\n --ccd-effort-color-11: rgba(0, 0, 0, 0.45);\n --ccd-effort-color-12: rgba(0, 0, 0, 0.35);\n --ccd-effort-color-13: rgba(0, 0, 0, 0.45);\n --ccd-effort-color-14: #b7b2aa;\n --ccd-effort-color-15: rgba(255, 255, 255, 0.06);\n --ccd-effort-color-16: rgba(255, 255, 255, 0.14);\n --ccd-effort-color-17: #3a3936;\n --ccd-effort-color-18: rgba(0, 0, 0, 0.5);\n --ccd-effort-color-19: #ece9e4;\n --ccd-effort-color-20: rgba(255, 255, 255, 0.04);\n --ccd-effort-color-21: #34332f;\n --ccd-effort-color-22: #373430;\n --ccd-effort-color-23: #3e3943;\n --ccd-effort-color-24: #463e51;\n --ccd-effort-color-25: #4f4560;\n --ccd-effort-color-26: #57496a;\n --ccd-effort-color-27: #5c4d71;\n --ccd-effort-color-28: #524667;\n --ccd-effort-color-29: #63527c;\n --ccd-effort-color-30: #6e5989;\n --ccd-effort-color-31: #786096;\n --ccd-effort-color-32: #6f6c66;\n --ccd-effort-color-33: rgba(255, 255, 255, 0.16);\n --ccd-effort-color-34: rgba(0, 0, 0, 0.4);\n --ccd-effort-color-35: rgba(0, 0, 0, 0.35);\n --ccd-effort-pixel-leftColor: 92, 88, 98;\n --ccd-effort-pixel-deepViolet: 150, 116, 196;\n --ccd-effort-pixel-deepMid: 150, 128, 196;\n --ccd-effort-pixel-midPurple: 162, 140, 204;\n --ccd-effort-pixel-softMid: 162, 152, 204;\n --ccd-effort-pixel-softLilac: 174, 164, 206;\n --ccd-effort-pixel-paleCool: 184, 174, 206;\n --ccd-effort-pixel-highlightColor: 200, 188, 224;\n --ccd-effort-pixel-peakColor: 214, 204, 236;\n --ccd-effort-shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 4px 10px rgba(0, 0, 0, .3), 0 12px 28px rgba(0, 0, 0, .35);\n}\n\n/*\n * Chosen-value ink. The Composer's model name \u2014 in the tool row and in the menu\n * it opens \u2014 is the value that row exists to show, so it takes full black\n * instead of the warm near-black every other primary label uses; the dark\n * scheme keeps the same relationship with its own strongest ink, white. Neither\n * side is derived from a configured surface, so the pair is declared here\n * rather than in the scheme blocks above.\n */\nhtml[data-dsh-ccd-style=\"true\"] {\n --ccd-text-strong: #000000;\n}\n\nhtml[data-dsh-ccd-style=\"true\"] body[data-ds-dark-theme] {\n --ccd-text-strong: #ffffff;\n}\n\n/*\n * Header icon ink.\n *\n * The conversation header's trailing cluster is icon-only \u2014 the native session\n * menu, the two views this plugin adds, the scheduled-tasks entry while it has\n * records, the collapsed right panel's re-entry control \u2014 and the reference\n * draws those marks at full strength where the host uses its secondary label\n * grey. Like the chosen-value ink above, this pair answers to no configured\n * surface, so it is declared here rather than in the scheme blocks: the pure\n * black the reference measured on the light canvas, and its mirror white on the\n * dark one, because a black mark would vanish into that canvas.\n *\n * What each state puts behind the mark stays the host's: a near-black wash at\n * 6-14% in the light scheme, a white one at 8-14% in the dark, and the button's\n * own dimmed opacity while it is disabled \u2014 so the ink never meets a fill of its\n * own lightness (see `header-actions.css`).\n */\nhtml[data-dsh-ccd-style=\"true\"] {\n --ccd-header-icon-color: #000000;\n}\n\nhtml[data-dsh-ccd-style=\"true\"] body[data-ds-dark-theme] {\n --ccd-header-icon-color: #ffffff;\n}\n\n/*\n * The selected check in every Menu surface.\n *\n * The host paints it with `--dsw-alias-label-primary` in the Menu primitive's\n * own `Menu.module.css` (`.check`), so the workspace picker, the permission\n * menu, the view-options menu and the session \u22EF menu all show the same\n * near-black mark \u2014 a selection reads no louder than the label beside it. One\n * rule recolours all of them, because they all come from that one primitive.\n * The model picker is not one of them: it is this plugin's own card, and the\n * block at the end of this file is the rule that reaches its mark.\n *\n * Only the colour changes: the mark keeps the host's 14px seat, its\n * `display: grid` centring, its row height and every hover fill. The class is\n * the one the shipped static web frontend gives that module (`_4ub78_`, whose\n * local names run `_check_4ub78_179`, `_item_4ub78_90`, \u2026), and that asset is\n * platform-independent \u2014 both installers carry the identical\n * `@deepseek-ai/dsh-web-frontend` dist \u2014 so it is the same name on Windows and\n * needs no second spelling in `host-builds.ts` (`host-dom.ts` records the same\n * prefix).\n *\n * The rule covers the seat and its artwork: the svg inherits through\n * `currentColor`, and naming it as well keeps the mark blue even if a future\n * build moves the colour onto the artwork instead of the seat.\n */\nhtml[data-dsh-ccd-style=\"true\"] [role=\"menu\"] ._check_4ub78_179,\nhtml[data-dsh-ccd-style=\"true\"] [role=\"menuitem\"] ._check_4ub78_179,\nhtml[data-dsh-ccd-style=\"true\"] [role=\"menu\"] ._check_4ub78_179 svg,\nhtml[data-dsh-ccd-style=\"true\"] [role=\"menuitem\"] ._check_4ub78_179 svg {\n color: var(--ccd-accent);\n}\n\n/*\n * The selected mark in the model picker.\n *\n * The Composer's model seat is not the host's `ModelSelect`: this plugin's\n * `ModelControls` wins `conversation.input.model` (its registration carries\n * `priority: -10` while the host registers the same seat at the default 0, and\n * the slot chain takes the lowest priority that answers), and the card that\n * seat opens marks the chosen model with the text `\u2713` in the row's trailing\n * `span[aria-hidden=\"true\"]` (`features/model-controls/ModelControls.tsx`).\n * `features/model-controls/controls.css` paints that row `--ccd-text-strong`,\n * so the mark inherits full black \u2014 which is exactly what it did: it is\n * neither `._check_4ub78_179` nor an svg, so the Menu rule above cannot reach\n * it, and no `fill`/`stroke` override would either, because a text glyph is\n * painted by `color` alone.\n *\n * Only the ink changes: the row keeps its height, padding and hover, and the\n * artwork is named beside the seat so a later icon in the same seat stays\n * covered. The seat is reached two ways \u2014 the row's own selected state\n * (`aria-pressed`, which React writes as `\"true\"`/`\"false\"`) and the seat that\n * actually holds a glyph (`:not(:empty)`, because the unchosen rows render an\n * empty span) \u2014 so a later switch to `aria-checked` on a `menuitemradio` row\n * cannot silently return this mark to black.\n *\n * The host's own `ModelSelect` card is covered as well, for the path where\n * that seat is the one on screen: its `.check` seat holds an svg whose path\n * strokes `currentColor` (ui-primitives `IconCheckOutlineArtwork`), so the\n * same `color` reaches the artwork. `cl2Rlq_` is already in the prefix table\n * (`compat/host-dom.ts`), and the Windows build's `wq12jW_` spelling is what\n * `compat/dom.ts` substitutes when this sheet is mounted.\n */\nhtml[data-dsh-ccd-style=\"true\"] .ccd-model-controls .ccd-model-options button[aria-pressed=\"true\"] > span[aria-hidden=\"true\"],\nhtml[data-dsh-ccd-style=\"true\"] .ccd-model-controls .ccd-model-options button[aria-pressed=\"true\"] > span[aria-hidden=\"true\"] svg,\nhtml[data-dsh-ccd-style=\"true\"] .ccd-model-controls .ccd-model-options button > span[aria-hidden=\"true\"]:not(:empty),\nhtml[data-dsh-ccd-style=\"true\"] .ccd-model-controls .ccd-model-options button > span[aria-hidden=\"true\"]:not(:empty) svg,\nhtml[data-dsh-ccd-style=\"true\"] [role=\"menu\"] .cl2Rlq_check,\nhtml[data-dsh-ccd-style=\"true\"] [role=\"menuitemradio\"] .cl2Rlq_check,\nhtml[data-dsh-ccd-style=\"true\"] [role=\"menu\"] .cl2Rlq_check svg,\nhtml[data-dsh-ccd-style=\"true\"] [role=\"menuitemradio\"] .cl2Rlq_check svg {\n color: var(--ccd-accent);\n}\n";
|
|
4839
|
+
var tokens_default = "/* CCD geometry, type, artwork and light/dark colour variables.\n * Colours are centralized here; the dark palette follows the host's body marker.\n */\nhtml[data-dsh-ccd-style=\"true\"] {\n /* Geometry */\n /* Top of the blank-session greeting block. The reference puts the title a\n short step below the header instead of centring it, so the statistics card\n hanging under it still clears the Composer on a short window. */\n --ccd-hero-top: 16px;\n --ccd-sidebar-width: 264px;\n --ccd-sidebar-inline-padding: 10px;\n --ccd-sidebar-bottom-padding: 8px;\n --ccd-row-height: 28px;\n /* Workspace (group) row. The host's own 34px, pinned because the browsing\n region's merged header band is this same box: the section header parks on\n the row's line and the two have to agree to the pixel. */\n --ccd-workspace-row-height: 34px;\n /* Navigation stack. The reference's three rows measured 26px tall on a 26.5px\n pitch \u2014 contiguous, not separated \u2014 against the host's own 36px/7px row. */\n --ccd-nav-row-height: 26px;\n --ccd-nav-gap: 1px;\n --ccd-radius-row: 8px;\n --ccd-radius-panel: 12px;\n /* View-options reference: 20 device px corners at @2x. */\n --ccd-view-menu-radius: 10px;\n /* Assistant table card. The reference crop's corner curve fits a 12-device-px\n radius at @2x, so the card is rounder than the row radius it borrows its\n scale from. */\n --ccd-table-radius: 6px;\n /* Fenced code block card. The reference code block shows the same 12-device-px\n curve at @2x as the table card, so the transcript's two boxed surfaces stay\n one family. */\n --ccd-code-radius: 6px;\n --ccd-composer-width: 770px;\n --ccd-composer-min-height: 42px;\n --ccd-composer-radius: 10px;\n --ccd-composer-padding: 8px 14px;\n --ccd-composer-padding-block: 8px;\n --ccd-composer-padding-inline: 14px;\n --ccd-composer-row-gap: 4px;\n --ccd-composer-row-inset: 4px;\n --ccd-composer-bottom-gap: 5px;\n --ccd-control-row-height: 28px;\n /* Status-bar chip. The tool row's controls are all this one box: the row's\n 28px is the layout line, and every control inside it is a chip of this\n height rather than a box stretched to the line. The reference draws the\n model chip's fill as one label line \u2014 40 device px at @2x, so the row's\n 12px label sits 5.5px from the chip's top and bottom where the row itself\n would leave it 10px \u2014 and the same box now carries the command plus, the\n permission control, the voice control, the statistics readouts and the\n context ring, so a pointer crossing the row finds one fill height instead\n of three. The row keeps `--ccd-control-row-height`: the dock, the ring's\n seat and the trailing cluster are all measured from it. The corner follows\n the height \u2014 12 device px at @2x, the same curve the transcript's table and\n code cards carry, rather than the row's 8px radius, which is nearly half of\n a 20px chip and would round every fill towards a pill. */\n --ccd-chip-height: 20px;\n --ccd-chip-radius: 6px;\n /* The chip family's inline padding: the model/effort chips, the permission\n control and the two square glyph buttons (plus, voice), whose boxes are\n their 14px artwork plus one of these on each side. Six to seven px is the\n whole range in the reference; the readouts and the ring keep their own\n slightly tighter 6px, which is a 1px difference in ink and nothing in a\n fill's shape. */\n --ccd-chip-padding-inline: 7px;\n --ccd-send-size: 24px;\n --ccd-send-reserve: 30px;\n /* One gap for the whole status row: the row itself, the leading tools group,\n its modes cluster, the trailing cluster (the native readouts, the context\n ring, the model/effort pair) and the statistics dock all space their\n children with this. The host's own row ran 12px in the leading groups and\n the plugin's 6px in the trailing one, which read as two different rows in\n one bar. Each control carries its own 6-7px inner padding, so an even box\n gap also reads as an even visible rhythm. */\n --ccd-cluster-gap: 6px;\n /* Context ring. The host draws it as 14px artwork (`ContextMeter.module.css`:\n viewBox 0 0 14 14, cx=cy=7, r=5.5, 2px stroke, round cap) inside a 28px\n trigger with 6px of side padding, so the trigger's own box is 26px and the\n seat the status bar has to hold for it \u2014 that box plus one cluster gap \u2014 is\n 32px. The host's ring and this plugin's empty-state ring hang in that one\n seat, which is what keeps the ring's position and size the same before and\n after a session has usage. */\n --ccd-context-ring-size: 14px;\n --ccd-context-ring-trigger: 26px;\n --ccd-context-ring-seat: calc(var(--ccd-context-ring-trigger) + var(--ccd-cluster-gap));\n /* Context breakdown panel. Measured off the supplied reference at @2x\n (698\xD7178 for the collapsed state, 828\xD71006 for the expanded one): a 361px\n card with a 1px outline and a 12px corner, 12px inline and 8px top padding,\n a 13px/20px header, a 4px bar 5px under it, 19px value rows, 17px detail\n rows, and 10px swatches. The width is the one number the crop cut off, so\n it is the reference's own rounded value: the bar and the percentage column\n both end exactly at 360 \u2212 2 \u2212 24 = 334px, which is what the crop shows. */\n --ccd-context-panel-width: 360px;\n --ccd-context-panel-radius: 12px;\n --ccd-context-panel-padding-block: 8px;\n --ccd-context-panel-padding-inline: 12px;\n --ccd-context-panel-gap: 8px;\n --ccd-context-row-height: 19px;\n --ccd-context-detail-height: 17px;\n --ccd-context-swatch-size: 10px;\n --ccd-context-swatch-radius: 3px;\n --ccd-context-bar-height: 4px;\n --ccd-context-bar-radius: 2px;\n --ccd-context-bar-gap: 1px;\n /* 5px under the header line, and nothing below: the card's own bottom padding\n is what follows the bar in the collapsed state, and the rows state their\n own 4px above themselves in the expanded one. */\n --ccd-context-bar-margin-block: 5px 0;\n /* The buffer segment's own step away from the used group: the reference\n leaves 4px there where every used segment is 1px apart, because the grey\n block is a reservation rather than another category. */\n --ccd-context-bar-reserve-gap: 4px;\n /* Ten detail rows before a group scrolls \u2014 the reference's own visible run,\n and what keeps a session with 85 tools from drawing a 1500px panel. */\n --ccd-context-detail-max: 170px;\n /* The two value columns: the token column's right edge sits 48px in from the\n content edge (measured 618 of 715 device px), which is a 40px percentage\n column plus this gap. */\n --ccd-context-percent-width: 40px;\n --ccd-context-column-gap: 8px;\n /* The status bar's own glyph size: the command plus, the statistics readouts\n and the ring all draw 14px artwork in the 28px control row. The\n speech-to-text control draws 18px of its own; the composer sheet brings that\n one down to this size. The permission control draws no glyph at all \u2014 it is\n its text (composer.css). */\n --ccd-row-icon-size: 14px;\n --ccd-account-menu-width: 236px;\n --ccd-account-menu-padding: 5px;\n --ccd-account-row-height: 28px;\n --ccd-account-trigger-height: 30px;\n /* Conversation header action cluster. The CCD reference keeps every control\n on one 30px centre pitch with a 12px trailing inset (measured at @2x from\n docs/reference/ccd-conversation.png: icon centres 28.75/30/30px apart, the\n trailing glyph centre 25.75px from the window edge). That inset is what the\n cluster takes while the Composer is narrower than the column and centred in\n it; where the Composer fills the column, `conversation.css` ends the\n cluster on the input surface's own border instead, which is the line the\n reference puts the closing control on and a measurement rather than a\n second token. */\n --ccd-header-action-size: 28px;\n --ccd-header-action-compact: 24px;\n --ccd-header-icon-size: 16px;\n --ccd-header-action-gap: 2px;\n --ccd-header-trailing-inset: 12px;\n --ccd-header-menu-border: #d6d6d6;\n --ccd-header-menu-separator: #e2e2e2;\n --ccd-header-menu-danger: #b4262b;\n --ccd-menu-danger: #c04742;\n --ccd-menu-danger-ink: #ffffff;\n --ccd-header-trigger-hover: #f0f0ef;\n --ccd-header-project-fill: #e5e5e5;\n --ccd-header-label-ink: #5a5a58;\n --ccd-header-project-active: #cfcfcf;\n /* Transcript edge fade. 24px is the band the host's own internal-request\n viewer uses for `fadeTop`\uFF0F`fadeBottom`, so the whole interface softens its\n scroll viewport edges by the same amount. */\n --ccd-edge-fade: 24px;\n /* Jump-to-bottom control. The reference's own affordance measures 40\xD744\n device px at @2x \u2014 a ~21px mark above the Composer \u2014 against the host's\n 34px floating circle, so the box shrinks to this one step while the glyph\n inside it keeps the host's 16px seat. */\n --ccd-to-bottom-size: 22px;\n /* Right panel (the Files, terminal and browser tabs). The reference draws it\n as a floating card: 16 device px in from the column's top, right and bottom\n edges; a 12px corner (measured off the fill: the curve is 20 device px at\n @2x from its first inset pixel to the straight edge, plus the 1px outline);\n a 32px header strip whose 24px tab mark sits 4px under the card's top; and\n an 8px pane gutter (16 device px), which is where the Files pane's own row\n line begins. */\n --ccd-right-panel-inset: 8px;\n --ccd-right-panel-radius: var(--ccd-radius-panel);\n --ccd-right-panel-strip-height: 32px;\n --ccd-right-panel-gutter: 8px;\n --ccd-right-panel-gap: 12px;\n /* The tab's own mark, measured off the reference: a 24px square (5px corner,\n carried by the chip radius) tinted #d3e2f9 behind the 16px glyph. */\n --ccd-right-panel-glyph-size: 24px;\n /* Files tree. The reference's rows run on a 24px pitch (48 device px at @2x)\n where the host's 13px/1.5 row draws 29.5px, and its pane header is the\n 26px rounded field over them. */\n --ccd-files-row-height: 24px;\n --ccd-files-field-height: 26px;\n --ccd-files-glyph-size: 16px;\n\n /* Claude Desktop 2.31226.1's active flat frame, confirmed by window capture:\n gray-10 canvas; sidebar mixes gray-20 and gray-10 equally in sRGB. */\n --ccd-canvas: #fcfcfb;\n --ccd-sidebar: #fbfbf9;\n --ccd-card: #ffffff;\n /* Segmented-control track. The reference measures #f6f6f4 against a white\n page; the same step down from this canvas keeps the same contrast. */\n --ccd-track: #f3f3f2;\n --ccd-raised: #f0f0ef;\n --ccd-hover: #f0f0ee;\n /* Sampled from the supplied view-options reference, separate from the\n warmer surfaces used by Composer and the host's other menus. */\n --ccd-view-menu-hover: #f5f5f5;\n --ccd-view-menu-text: #0b0b0b;\n --ccd-view-menu-muted: #9b9994;\n --ccd-view-menu-border: #d7d7d6;\n --ccd-view-menu-separator: #ebebeb;\n --ccd-view-menu-accent: #2a78d6;\n --ccd-view-menu-shadow: 0 1px 3px rgba(0, 0, 0, .08), 0 8px 20px rgba(0, 0, 0, .12), 0 16px 32px rgba(0, 0, 0, .06);\n --ccd-selected: #ecebe8;\n /* The fill a control takes while it sits on a row that is already filled:\n a Session row paints `--ccd-selected` under the pointer, so its \u22EE takes\n this one to stay a step below what it stands on. Sampled from the supplied\n reference at @2x, where that row measures #edece9 under the control's own\n #e1e0dd \u2014 the same 12/255 step, and the relationship `theme/palette.ts`\n derives for a configured canvas. */\n --ccd-hover-strong: #e1e0dd;\n /* The right panel's tab mark: the reference's own blue-tinted square,\n sampled at #d3e2f9 with the accent for its glyph. It is the one tinted seat\n in this plugin's surfaces, so it is stated rather than derived. */\n --ccd-right-panel-glyph-bg: #d3e2f9;\n /* The Files pane's own ink, sampled off the reference at @2x. Its tree names\n and its empty state's title measure #0b0b0b \u2014 a step below this palette's\n `--ccd-text`, the ink the rest of the interface draws \u2014 and its tab title\n measures #52514f, between `--ccd-text` and `--ccd-text-secondary`. The tab\n mark's glyph is the one blue that is not this palette's accent: the\n reference draws it #304e91 on the #d3e2f9 square. */\n --ccd-files-ink: #0b0b0b;\n --ccd-right-panel-tab-ink: #52514f;\n --ccd-files-chip-ink: #304e91;\n /* File header chips use the reference's neutral greys, separate from the\n warmer general-purpose hover surfaces. Geometry is in right-panel.css. */\n --ccd-file-tab-selected: #e6e6e6;\n --ccd-file-tab-hover: #f2f2f2;\n --ccd-file-close-hover: #d9d9d9;\n --ccd-file-close-pressed: #cecece;\n\n /* Lines */\n --ccd-border: #e3e3e1;\n --ccd-border-strong: #e0e0df;\n --ccd-border-soft: #ebebe9;\n /* The rule under a table head reads one step stronger than the card outline\n in the reference (#d9d9d9 against #e3e3e3). */\n --ccd-table-head-line: #d9d9d8;\n /* The right panel's card outline, sampled at #d7d7d6 \u2014 one step stronger than\n the row outline above and the same ink the menu cards' own border carries,\n which is what makes a floating panel and a menu read as one material. */\n --ccd-right-panel-border: #d7d7d6;\n --ccd-browser-canvas: #f7f7f7;\n --ccd-browser-dot: #d1d0ce;\n\n /* Scrollbar ink. The host paints the scrollbars themselves\n (`ui-theme/src/styles/scrollbar.css`: a 5px track and a pill thumb read\n from `--dsh-scrollbar-thumb`), and this interface keeps that geometry \u2014\n the transcript's stable gutter, the sidebar's\n `--dsh-session-list-scrollbar-width` padding and the dockkit panes are all\n measured against the host's own width \u2014 so these two tokens are the ink\n alone, bound in theme/scrollbars.css.\n The ink is near-black by decision rather than by measurement: the bar is\n 5px wide and carries no surface of its own, so none of this palette's\n surface steps \u2014 5% to 14% black \u2014 reads at that size, and the interface\n this one follows draws a dark scrollbar. Hover goes one step further down,\n the direction the host's own hover step takes. */\n --ccd-scrollbar-thumb: #333331;\n --ccd-scrollbar-thumb-hover: #171716;\n\n /* Text */\n --ccd-text: #1f1f1d;\n --ccd-text-secondary: #6f6f6a;\n --ccd-text-muted: #8f8f8a;\n --ccd-text-placeholder: #89877f;\n /* One step below full strength: a row that is not the current one. The\n reference paints its inactive navigation rows at #4a4a47 \u2014 between this\n palette's --ccd-text and --ccd-text-secondary \u2014 and keeps the current row\n at full black, so the stack reads as one current entry plus quieter\n neighbours instead of three equally loud labels. */\n --ccd-text-nav: #4a4a47;\n /* Archived Session row. The host fades that title with its own caption ink,\n but this plugin's row ink outranks the host pair, so the state needs a step\n of its own: one above --ccd-text-muted on the same warm ramp, which is the\n faded row the reference draws while still reading against the sidebar. A row\n that is current or under the pointer takes the second value, because the\n hover and selected fills sit a step below the sidebar the first one was\n measured on. */\n --ccd-text-archived: #a0a09b;\n --ccd-text-archived-hover: #8a8a85;\n\n /* New-session statistics card. The reference panel is a stacked translucent\n grey: the card is 5% black over the canvas and a stat tile (and an empty\n heat cell) is 10% black over the card, so these are the measured opaque\n equivalents. The four heat levels are the reference's own `hsl(217 70% L%)`\n with L = 72\uFF0F64\uFF0F56\uFF0F48, i.e. \"that day's value / the busiest day\" in four\n equal steps. */\n --ccd-stats-panel: #f0f0f0;\n --ccd-stats-tile: #dadada;\n /* The card header's active tab: the reference paints `fill-ghost-hover`, the\n same 5% black as the card itself, over the card \u2014 one step lighter than a\n stat tile. */\n --ccd-stats-tab: #e6e6e6;\n --ccd-stats-label: #898781;\n --ccd-stats-value: #0b0b0b;\n --ccd-stats-heat-1: hsl(217 70% 72%);\n --ccd-stats-heat-2: hsl(217 70% 64%);\n --ccd-stats-heat-3: hsl(217 70% 56%);\n --ccd-stats-heat-4: hsl(217 70% 48%);\n /* The `Models` view colours its bars and legend by rank:\n `hsl(217 70% min(88, 58 + 7n)%)`, which flattens into these six shades. */\n --ccd-stats-model-1: hsl(217 70% 58%);\n --ccd-stats-model-2: hsl(217 70% 65%);\n --ccd-stats-model-3: hsl(217 70% 72%);\n --ccd-stats-model-4: hsl(217 70% 79%);\n --ccd-stats-model-5: hsl(217 70% 86%);\n --ccd-stats-model-6: hsl(217 70% 88%);\n /* Tooltips are the reference's dark popup in both themes; on a dark panel it\n steps up to a mid grey so the box keeps its contrast. */\n --ccd-stats-tip-bg: #0c0c0c;\n --ccd-stats-tip-fg: #f7f7f5;\n\n /* Assistant Markdown palette matched to the CCD transcript reference. */\n --ccd-link: #2b61b5;\n --ccd-link-underline: #a7badd;\n --ccd-inline-code-text: #a8322d;\n --ccd-inline-code-fill: #f1f1f0;\n --ccd-inline-code-border: #e2e2e0;\n\n /* Context meter. The host paints the ring's consumed arc with the same\n tertiary label grey as the percentage beside it, so the reading carries no\n weight. The accent blue is this plugin's one emphasis colour: taking it\n through `var()` lets the ring follow `--ccd-accent` the moment that token\n exists, and the fallback is the blue measured off the target arc in the\n reference at @2x, so the ring is never left grey meanwhile. The\n track underneath stays the host's `--dsw-alias-border-l3`, which\n theme/tokens.ts already overrides. */\n --ccd-context-ring-fill: var(--ccd-accent, #4b76ce);\n\n /* Context breakdown categories, sampled from the supplied reference's bar and\n swatches. Seven steps, in the order the panel and the bar draw them: the\n reference's own hues, which are deliberately outside this palette's blue\n ramp \u2014 a breakdown that reused one hue family would make five categories\n read as one. `--ccd-context-free` is both the free-space swatch and the\n bar's track, exactly as the reference draws them. */\n --ccd-context-mcp: #2a78d8;\n --ccd-context-tools: #e96a33;\n --ccd-context-system: #1ead7c;\n --ccd-context-skills: #eda105;\n --ccd-context-memory: #ea7ca4;\n /* The conversation's own row. The reference's crop happens to hold no\n messages at all \u2014 89k of its 9% reading is prompt, tools and skills \u2014 so\n this one is chosen rather than sampled: the same lightness and chroma as\n its neighbours, one hue family over, so it cannot be mistaken for the\n accent blue of MCP tools. */\n --ccd-context-messages: #8b6fd4;\n --ccd-context-buffer: #c2c0b8;\n --ccd-context-free: #eeeeee;\n\n /* The plugin's one emphasis blue: the selected check in every Menu surface,\n the context ring, and whatever else has to read as \"this one is chosen\".\n It is the blue measured off the reference's check mark at @2x: the\n stroke's own pixels are #4b76d0/#507ad2, so the token takes the darker\n mode, and it shares the stats heat ramp's hue\n family (hsl(217 \u2026)), which keeps every blue in the interface one voice. */\n --ccd-accent: #4b76ce;\n --ccd-hero-ripple-ink: #60bdeb;\n\n /* State colours that DSH already owns semantics for. */\n --ccd-diff-added: #508c48;\n --ccd-diff-removed: #b33156;\n\n /* Type. The two fallback tails are what a configured family is prepended to\n (see theme/tokens.ts); with nothing configured the consuming variables keep\n the built-in stacks. Geist leads the interface tail because it is the\n upstream design Anthropic Sans was cut from; it is resolved from the system\n font list, so the tail degrades to the platform stack when it is not\n installed (assets/fonts/ ships the file for a local install only). */\n --ccd-font-fallback: Geist, -apple-system, BlinkMacSystemFont, \"SF Pro Text\", \"Helvetica Neue\",\n \"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", sans-serif;\n --ccd-code-fallback: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace;\n --ccd-font-ui: var(--ccd-font-fallback);\n --ccd-font-code: var(--ccd-code-fallback);\n --ccd-font-size-body: 14px;\n --ccd-font-size-muted: 13px;\n /* Blank-session title. The reference's own type scale puts the greeting on\n `--cds-font-size-title` (1.25rem, ~20px) with a 25px leading at weight 400,\n which is a good deal quieter than the 26px this used to be. */\n --ccd-font-size-headline: 20px;\n /* Transcript headings. The reference's ladder sits one step under the host's\n (DSH draws h1/h2/h3 at 21/19/18px): its own crop measures a 19px h1 and a\n 16px h2 over the 14px body \u2014 27 and 23 device px of CJK ink against the\n 21-device-px body line at @2x \u2014 and the h2 is confirmed independently by\n the inline-code chip, whose 0.95em/1.2 rule this plugin already matches:\n it lands on the reference's 33 device px exactly at 16px. h3 has no crop of\n its own and takes the next step down. */\n --ccd-font-size-h1: 19px;\n --ccd-font-size-h2: 16px;\n --ccd-font-size-h3: 15px;\n /* Transcript leading. The host sets the assistant body at 14px/24px; the\n reference runs the same 14px on a 20px line. Its own window measures a\n 30.7-device-px pitch between two lines of a paragraph where this plugin's\n 24px draws 36.6, and both shots put their body's CJK ink at 20 device px\n with a 770px Composer at 1176 (1.53 device px per CSS px), so the two are\n read at one scale. The reference's paragraph-to-paragraph gap is 46 device\n px \u2014 this 20px line plus the 10px margin both sides already share. */\n --ccd-transcript-line-height: 20px;\n /* The one step up from the host's own 400: used where a control has to read\n as a chosen value rather than as chrome (model, reasoning effort). */\n --ccd-font-weight-strong: 600;\n\n /* Hand-drawn 16px marks matching the CCD interaction recording: return\n arrow and a small square in a ring. The host keeps its buttons, handlers\n and SVG state markers; these masks replace only their visible artwork. */\n --ccd-send-radius: 6px;\n --ccd-icon-send: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M12 4.5V8A2 2 0 0 1 10 10H3.5M6.5 7L3.5 10L6.5 13' stroke='black' stroke-width='1.25' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");\n --ccd-icon-stop: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='5.5' fill='none' stroke='black' stroke-width='1.25'/%3E%3Crect x='6' y='6' width='4' height='4' rx='.6' fill='black'/%3E%3C/svg%3E\");\n\n /* The header's three painted glyphs, taken from DSH 0.2.0-rc.2 shared\n artwork (MIT) so no host component is copied:\n - IconRightUpOutlineRegular (`ui-primitives/icons`) is the generic \"open\"\n glyph the native open-in-app control falls back to;\n - the terminal prompt is `ui-sidebar-terminal/TerminalIcon`'s own 16px\n geometry, the glyph that view's tab title draws;\n - the globe is `ui-primitives`' IconGlobeOutlineRegular, the glyph\n `ui-sidebar-browser` draws in its tab title.\n Masks let the host keep ownership of its buttons while the plugin supplies\n the artwork. */\n --ccd-icon-open: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M11.7256 2.77441C12.5538 2.77469 13.2256 3.44616 13.2256 4.27441V10.1416H12.2256V4.27441C12.2256 3.99844 12.0015 3.77469 11.7256 3.77441H5.7207V2.77441H11.7256Z' fill='black'/%3E%3Cpath d='M2.77441 13.2255L12.3756 3.62427' stroke='black'/%3E%3C/svg%3E\");\n --ccd-icon-terminal: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M3 4L7 8L3 12' stroke='black'/%3E%3Cpath d='M9 12H13' stroke='black'/%3E%3C/svg%3E\");\n --ccd-icon-browser: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M7.99986 14.0887C11.3626 14.0887 14.0886 11.3627 14.0886 7.99998C14.0886 4.63727 11.3626 1.91125 7.99986 1.91125C4.63715 1.91125 1.91113 4.63727 1.91113 7.99998C1.91113 11.3627 4.63715 14.0887 7.99986 14.0887Z' stroke='black'/%3E%3Cpath d='M2.34619 8H13.6538' stroke='black' stroke-linecap='square'/%3E%3Cpath d='M7.99976 14.0889C9.23509 14.0889 10.1743 11.3629 10.1743 8.00006C10.1743 4.63739 9.23509 1.91138 7.99976 1.91138' stroke='black'/%3E%3Cpath d='M7.99973 14.0889C6.76445 14.0889 5.8252 11.3629 5.8252 8.00006C5.8252 4.63739 6.76445 1.91138 7.99973 1.91138' stroke='black'/%3E%3C/svg%3E\");\n\n /* The session menu's own mark, stacked. The host draws\n `IconEllipsisOutlineRegular` (`ui-primitives`, MIT) as three 2px dots on one\n horizontal line \u2014 circles at (3, 8), (8, 8) and (13, 8) of a 16px box \u2014 and\n the reference stacks them, so this is that same geometry transposed to\n (8, 3), (8, 8) and (8, 13): one dot size, one 5-unit pitch, one 16px box,\n carried as a mask over the button's own `currentColor` so the host keeps its\n button and this interface supplies the mark. */\n --ccd-icon-more: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M8 4C8.55228 4 9 3.55228 9 3C9 2.44772 8.55228 2 8 2C7.44772 2 7 2.44772 7 3C7 3.55228 7.44772 4 8 4Z' fill='black'/%3E%3Cpath d='M8 9C8.55228 9 9 8.55228 9 8C9 7.44772 8.55228 7 8 7C7.44772 7 7 7.44772 7 8C7 8.55228 7.44772 9 8 9Z' fill='black'/%3E%3Cpath d='M8 14C8.55228 14 9 13.55228 9 13C9 12.44772 8.55228 12 8 12C7.44772 12 7 12.44772 7 13C7 13.55228 7.44772 14 8 14Z' fill='black'/%3E%3C/svg%3E\");\n\n /* A bare plus, the one glyph the reference repeats on every actionable row.\n It is DSH's own `IconPlusOutlineArtwork` geometry (ui-primitives, MIT): two\n 1px strokes across a 16px box, so the plugin can put the same mark on the\n Workspace header's add button and on a project row's create button without\n authoring artwork of its own. */\n --ccd-icon-plus: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M8 2V14' stroke='black'/%3E%3Cpath d='M2 8H14' stroke='black'/%3E%3C/svg%3E\");\n\n /* The workspace picker chip's leading mark. DSH switches that glyph with the\n chip's own state \u2014 `IconFolderOpenRegular` once a workspace is chosen, a\n closed folder while the chip only carries a placeholder \u2014 and the open one\n draws a slanted front panel over a translucent back panel, where the\n reference's button leads with one closed folder in either state. The\n geometry below is DSH's own `FolderCloseArtwork` (`ui-primitives`, MIT) on\n its 16-unit grid and 1px stroke, with the artwork's second path \u2014 the rule\n line across the body \u2014 left out: the reference draws the body empty, and\n the tab, the step and the four rounded corners are the whole of the mark. */\n --ccd-icon-folder: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M1.50439 3.11059C1.50439 2.55831 1.95211 2.1106 2.50439 2.1106H5.43389C5.67773 2.1106 5.91318 2.19969 6.09593 2.36113L7.71649 3.79265C7.89924 3.95409 8.1347 4.04319 8.3785 4.04319H13.4958C14.0481 4.04319 14.4958 4.4909 14.4958 5.04319V12.8894C14.4958 13.4417 14.0481 13.8894 13.4958 13.8894H2.50439C1.95211 13.8894 1.50439 13.4417 1.50439 12.8894V4.04319V3.11059Z' stroke='black'/%3E%3C/svg%3E\");\n\n /* The reference's disclosure mark is a 1px line chevron where the host draws a\n filled triangle; this is DSH's own `IconChevronRightOutlineArtwork` geometry\n (ui-primitives, MIT). It is painted over the host's mark slot, so the host's\n own `arrowOpen` state still drives the quarter turn. */\n --ccd-icon-chevron-right: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M6 12L9.29289 8.70711C9.68342 8.31658 9.68342 7.68342 9.29289 7.29289L6 4' stroke='black'/%3E%3C/svg%3E\");\n\n /* The Files tree's two file marks, measured off the reference at @2x: a page\n with its bottom-right corner folded, and the angle-bracket pair, each drawn\n 8\u20139px wide inside the 16px seat the host's own type glyphs occupy (the\n reference's names then start on the same 33px the row's own gap states).\n DSH classifies a file by name and extension too, but its set is a\n different one \u2014 `.gitignore` is a git file to the host and a plain page to\n the reference, `LICENSE` a page to both \u2014 so `right-panel.css` carries the\n reference's own split as an extension list rather than reading the host's. */\n --ccd-icon-file: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M5.5 4.5H10.5C11.0523 4.5 11.5 4.94772 11.5 5.5V8.5L8.5 11.5H5.5C4.94772 11.5 4.5 11.0523 4.5 10.5V5.5C4.5 4.94772 4.94772 4.5 5.5 4.5Z' stroke='black'/%3E%3Cpath d='M8.5 8.5H11.5L8.5 11.5' stroke='black'/%3E%3C/svg%3E\");\n --ccd-icon-file-code: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M6.2 5.2L4.2 8.05L6.2 10.9' stroke='black' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M9.8 5.2L11.8 8.05L9.8 10.9' stroke='black' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M9.4 4.6L6.6 11.4' stroke='black' stroke-linecap='round'/%3E%3C/svg%3E\");\n\n /* The Files tab's mark: the reference draws a three-row list \u2014 a bullet and a\n rule per row \u2014 12px of ink on the tab's own blue square, so the artwork is\n stated on the square's 24-unit grid. The square itself is a seat the tab\n sheet paints; this is only the ink that sits in it. */\n --ccd-icon-files-list: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg fill='black'%3E%3Crect x='6' y='6.5' width='2' height='2' rx='.5'/%3E%3Crect x='9.5' y='6.5' width='8' height='2' rx='.5'/%3E%3Crect x='6' y='11' width='2' height='2' rx='.5'/%3E%3Crect x='9.5' y='11' width='8' height='2' rx='.5'/%3E%3Crect x='6' y='15.5' width='2' height='2' rx='.5'/%3E%3Crect x='9.5' y='15.5' width='8' height='2' rx='.5'/%3E%3C/g%3E%3C/svg%3E\");\n\n /* The Files pane's magnifier, for the filter field's leading seat and for the\n one control the pane's header adds: a lens and a handle, 13px of ink in the\n 16px box the field's own row draws, one 1px stroke like the marks around it. */\n --ccd-icon-search: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Ccircle cx='6.6' cy='6.6' r='4.6' stroke='black'/%3E%3Cpath d='M9.9 9.9L13.9 13.9' stroke='black' stroke-linecap='round'/%3E%3C/svg%3E\");\n\n /* The archived Session row's leading mark. The host deliberately leaves that\n 16px cell empty, so the plugin paints the same geometry itself: DSH's own\n `IconArchiveOutlineRegular` artwork (ui-primitives, MIT) \u2014 a lid, a body and\n a handle drawn with 1px strokes \u2014 carried as a mask like the marks around\n it, so no third-party asset or copied component enters the stylesheet. */\n --ccd-icon-archive: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M13.5 2.5H2.5C1.94772 2.5 1.5 2.94772 1.5 3.5V4.5C1.5 5.05228 1.94772 5.5 2.5 5.5H13.5C14.0523 5.5 14.5 5.05228 14.5 4.5V3.5C14.5 2.94772 14.0523 2.5 13.5 2.5Z' stroke='black'/%3E%3Cpath d='M2.5 5.5V13.5C2.5 13.7652 2.60536 14.0196 2.79289 14.2071C2.98043 14.3946 3.23478 14.5 3.5 14.5H12.5C12.7652 14.5 13.0196 14.3946 13.2071 14.2071C13.3946 14.0196 13.5 13.7652 13.5 13.5V5.5' stroke='black'/%3E%3Cpath d='M6.5 9.5H9.5' stroke='black'/%3E%3C/svg%3E\");\n\n /* The idle Session row's leading ring. The host hands that cell to the\n plugin's `sidebar.session.row.leading` seat as soon as a row has no status\n of its own, and the running row's activity ring is `ui-primitives`'\n StateDot spinner: a 2-unit stroke on a 9.5-unit radius in a 24-unit box.\n The same circle is carried here as a mask, so the still mark and the\n spinning one are one geometry at the one 8px scale the stylesheet already\n gives the spinner \u2014 and the ring takes the cell's own `currentColor`\n (the host's `--dsw-alias-label-tertiary`, the spinner's ink) instead of an\n authored colour. */\n --ccd-icon-session-ring: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none'%3E%3Ccircle cx='12' cy='12' r='9.5' stroke='black' stroke-width='2'/%3E%3C/svg%3E\");\n\n /* The context ring's track, for the seat an empty session has to fill. The\n host's own `ContextMeter` returns nothing at all until the token meter knows\n a numerator and the context window, so the plugin draws that ring's track\n itself \u2014 the host's exact geometry from `ContextMeter.module.css`\n (viewBox 0 0 14 14, cx=cy=7, r=5.5, 2px stroke) carried as a mask, so the\n colour stays the host track token above. The consumed arc is not drawn: at\n 0% the host's own arc has no length either. */\n --ccd-icon-context-ring: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14' fill='none'%3E%3Ccircle cx='7' cy='7' r='5.5' stroke='black' stroke-width='2'/%3E%3C/svg%3E\");\n\n /* The Composer whale. DeepSeek's own mark is a blue whale with a pale belly,\n so the two inks are the brand pair rather than a derived palette step: the\n artwork only ever appears as this one mark. The dark side lifts the blue a\n step so it keeps its contrast on the dark canvas instead of sinking into\n it; the belly stays the light ink in both schemes, because it is what makes\n the silhouette read at 64px. */\n --ccd-pet-blue: #4d6bfe;\n --ccd-pet-light: #dfe6ff;\n\n /* Elevation */\n --ccd-shadow-card: 0 1px 2px rgb(0 0 0 / 4%);\n --ccd-shadow-segment: 0 1px 2px rgb(0 0 0 / 8%);\n /* The right panel's card, read off the reference's own edges: the canvas\n (252/255) sits at 243 half a pixel beside the card, 235 directly under it\n and back to 251 eight pixels out \u2014 a two-layer shadow, the near one for the\n edge and the far one for the falloff. */\n --ccd-right-panel-shadow: 0 1px 3px rgb(0 0 0 / 4%), 0 5px 14px rgb(0 0 0 / 8%);\n}\n\n/*\n * Host-token aliases belong on `body`, exactly where the host writes them.\n *\n * `--dsw-*` values are not in the document stylesheets: DSH's theme presenter\n * writes them, and this plugin's own overrides, as inline styles on `body`\n * (theme/tokens.ts). A custom property is resolved *where it is declared*, so an\n * alias of a host token declared on the root would reference a variable that is\n * not in scope there: it computes to the guaranteed-invalid value and inherits\n * as invalid into every subtree, and the rule consuming it falls back to\n * `transparent` rather than the host's colour. The empty-state context ring\n * (theme/composer.css, `.yhfFVG_dock::after`) vanished exactly that way.\n */\nhtml[data-dsh-ccd-style=\"true\"] body {\n /* Resolve on body so dark mode and configured palettes both participate. */\n --ccd-composer-focus-border: color-mix(in srgb, var(--ccd-card) 60%, var(--ccd-text-secondary));\n /* The empty state's ring draws the host's track alone. It stays the host's\n own track token (`ContextMeter.module.css` strokes `.y0jqnG_track` with\n `--dsw-alias-border-l3`, which theme/tokens.ts already retints), so an\n empty session shows the same hollow ring the host would draw at 0%. */\n --ccd-context-ring-track: var(--dsw-alias-border-l3);\n}\n\n/*\n * Dark palette.\n *\n * DSH's theme presenter writes `body[data-ds-dark-theme]` from the resolved\n * colour scheme (`@deepseek-ai/dsh-client-ui-theme` client styles, DSH\n * 0.2.0-rc.2), so the plugin follows the host's own switch instead of carrying a\n * second one. The block is a descendant of the root activation marker: the\n * variables land on `body`, cover every colour the light block declares, and\n * stay absent while the plugin is off.\n *\n * The warm-neutral family is kept rather than swapping in the host's bluish\n * greys: the same relationship holds in both schemes (canvas a touch lighter\n * than the sidebar, card the brightest surface), while the recessed steps\n * (track, raised, hover, selected) move *up* in lightness where the light\n * palette moves down, because on a dark canvas a surface reads as raised by\n * catching more light. Lines keep one visible step against the surfaces they\n * separate; text keeps the light palette's descending tone order.\n */\nhtml[data-dsh-ccd-style=\"true\"] body[data-ds-dark-theme] {\n --ccd-hero-ripple-ink: #60bdeb;\n /* The same flat frame uses gray-850 for the dark page and mixes it with\n 20% black for the sidebar. The control track stays below its raised thumb\n while both sit above the darker page. */\n --ccd-canvas: #151515;\n --ccd-sidebar: #111111;\n --ccd-card: #30302e;\n --ccd-header-menu-border: var(--ccd-border);\n --ccd-header-menu-separator: var(--ccd-border-soft);\n --ccd-header-menu-danger: #f08080;\n --ccd-menu-danger: #c04742;\n --ccd-menu-danger-ink: #ffffff;\n --ccd-header-trigger-hover: var(--ccd-hover);\n --ccd-header-project-fill: var(--ccd-track);\n --ccd-header-label-ink: var(--ccd-text-secondary);\n --ccd-header-project-active: var(--ccd-selected);\n --ccd-track: #1f1e1d;\n --ccd-raised: #2f2f2d;\n --ccd-hover: #313130;\n --ccd-view-menu-hover: var(--ccd-selected);\n --ccd-view-menu-text: var(--ccd-text);\n --ccd-view-menu-muted: var(--ccd-text-muted);\n --ccd-view-menu-border: var(--ccd-border);\n --ccd-view-menu-separator: var(--ccd-border-soft);\n --ccd-view-menu-accent: var(--ccd-accent);\n --ccd-view-menu-shadow: 0 1px 3px rgba(0, 0, 0, .4), 0 8px 20px rgba(0, 0, 0, .3), 0 16px 32px rgba(0, 0, 0, .2);\n --ccd-selected: #3a3a37;\n /* The same step as the light side, in the direction this canvas recesses:\n the fill goes one step up from `selected` here \u2014 which happens to land on\n the strong-outline step below, a coincidence of the two ramps rather than\n the reason for the value. */\n --ccd-hover-strong: #474743;\n /* The tab mark's tint, dark side: the accent's own hue over this card, which\n is the #d3e2f9 relationship the light side measures, not its value. */\n --ccd-right-panel-glyph-bg: #2b3a55;\n /* The Files pane's inks go back to the palette's own ramp here: the reference\n is a light-mode capture, and its three sampled values are only meaningful\n as relationships (a step below the body ink, a step above the muted one and\n the accent's own hue) that this side already carries. */\n --ccd-files-ink: var(--ccd-text);\n --ccd-right-panel-tab-ink: var(--ccd-text);\n --ccd-files-chip-ink: var(--ccd-accent);\n --ccd-file-tab-selected: var(--ccd-selected);\n --ccd-file-tab-hover: var(--ccd-hover);\n --ccd-file-close-hover: var(--ccd-hover-strong);\n --ccd-file-close-pressed: var(--ccd-border-strong);\n\n /* Lines */\n --ccd-border: #3e3e3b;\n --ccd-border-strong: #474743;\n --ccd-border-soft: #343431;\n /* The head rule stays the one line drawn stronger than the card outline. */\n --ccd-table-head-line: #4a4a46;\n /* The card outline's dark side: one step above the row outline, as in the\n light palette. */\n --ccd-right-panel-border: #474743;\n --ccd-browser-canvas: #242423;\n --ccd-browser-dot: #454542;\n\n /* Scrollbar ink, dark side: on this canvas the bar is the one piece of the\n chrome that has to read as raised, so it sits above every surface step\n here and lightens on hover \u2014 the mirror of the light palette, whose ink\n sits below every surface and darkens. */\n --ccd-scrollbar-thumb: #5c5b57;\n --ccd-scrollbar-thumb-hover: #75746f;\n\n /* Text */\n --ccd-text: #f0efec;\n --ccd-text-secondary: #b4b1ab;\n --ccd-text-muted: #918f89;\n /* Same step down from full strength as the light palette: the dark ink sits\n the same distance below --ccd-text as #4a4a47 sits below #1f1f1d. */\n --ccd-text-nav: #d3d0ca;\n --ccd-text-placeholder: #8b8983;\n /* Archived rows, dark side: the same two steps below the row ink, kept\n readable against this sidebar and the selected fill that sits above it. */\n --ccd-text-archived: #82807c;\n --ccd-text-archived-hover: #a6a39c;\n\n /* Composer whale, dark side: the same mark, with the blue raised so it reads\n against this canvas; the belly keeps the light ink. */\n --ccd-pet-blue: #7b93ff;\n --ccd-pet-light: #e8ecff;\n\n /* Statistics card, dark side: the same stacked greys over this canvas, and\n the heat ramp raised one step so the steps stay visible on a dark panel. */\n --ccd-stats-panel: #2c2c2a;\n --ccd-stats-tile: #383835;\n --ccd-stats-tab: #3a3a37;\n --ccd-stats-label: #918f89;\n --ccd-stats-value: #f0efec;\n --ccd-stats-heat-1: hsl(217 62% 78%);\n --ccd-stats-heat-2: hsl(217 64% 70%);\n --ccd-stats-heat-3: hsl(217 66% 62%);\n --ccd-stats-heat-4: hsl(217 70% 54%);\n /* Model ramp, raised for the dark panel; six distinct steps, as the light\n theme's clamped ramp produces. */\n --ccd-stats-model-1: hsl(217 62% 70%);\n --ccd-stats-model-2: hsl(217 64% 75%);\n --ccd-stats-model-3: hsl(217 66% 80%);\n --ccd-stats-model-4: hsl(217 68% 84%);\n --ccd-stats-model-5: hsl(217 70% 87%);\n --ccd-stats-model-6: hsl(217 70% 89%);\n --ccd-stats-tip-bg: #454542;\n --ccd-stats-tip-fg: #f7f7f5;\n\n /* Assistant Markdown palette, lightened for a dark transcript. */\n --ccd-link: #86b0e8;\n --ccd-link-underline: #4a5c76;\n --ccd-inline-code-text: #f0918a;\n --ccd-inline-code-fill: #35332f;\n --ccd-inline-code-border: #454239;\n\n /* Context meter ring, dark side: the same hue as the light accent, one step\n up in lightness (55% \u2192 68%) and saturation so the arc keeps its blue on\n this canvas instead of greying into it. Same `var()` order as the light\n block. */\n --ccd-context-ring-fill: var(--ccd-accent, #789be3);\n\n /* Context breakdown categories, dark side. The reference ships one light\n crop, so these are derived the way the rest of this palette is: the same\n hue, raised in lightness by the step the statistics ramp and the accent\n take on this canvas, so five categories stay five colours instead of\n sinking into the card. The two greys are the exception \u2014 they are surfaces\n rather than inks, so the buffer steps down to a mid warm grey and the free\n track steps up off the card, which is what keeps the reserved block and the\n empty room apart at a glance. */\n --ccd-context-mcp: #5b9ae8;\n --ccd-context-tools: #f0885a;\n --ccd-context-system: #43c79b;\n --ccd-context-skills: #f0b93c;\n --ccd-context-memory: #ef93b5;\n --ccd-context-messages: #a58fe6;\n --ccd-context-buffer: #5f5b53;\n --ccd-context-free: #3f3f3b;\n\n /* The accent, dark side: the same hue and the same 55% \u2192 68% lightness step\n the statistics heat ramp takes on this canvas, so the check keeps its blue\n against the menu's surface instead of sinking into it. The hue is the\n reference's measured blue (220) rather than the ramp's 217, but the two sit\n in one family. */\n --ccd-accent: #789be3;\n\n /* State colours that DSH already owns semantics for. */\n --ccd-diff-added: #7cbd72;\n --ccd-diff-removed: #e08fae;\n\n /* Elevation reads as a shadow under the surface, not as a grey wash. */\n --ccd-shadow-card: 0 1px 2px rgb(0 0 0 / 40%);\n --ccd-shadow-segment: 0 1px 2px rgb(0 0 0 / 50%);\n --ccd-right-panel-shadow: 0 1px 2px rgb(0 0 0 / 35%), 0 4px 10px rgb(0 0 0 / 35%);\n}\n\n/* zanwei effort reference palette, MIT; canvas reads these theme values. */\nhtml[data-dsh-ccd-style=\"true\"] {\n --ccd-effort-color-1: #8c73c9;\n --ccd-effort-color-2: #a17ec2;\n --ccd-effort-color-3: #5f5b58;\n --ccd-effort-color-4: #3f3b38;\n --ccd-effort-color-5: #77736f;\n --ccd-effort-color-6: #edeae8;\n --ccd-effort-color-7: #e0dbd6;\n --ccd-effort-color-8: #ffffff;\n --ccd-effort-color-9: rgba(76, 70, 65, 0.12);\n --ccd-effort-color-10: #2788d6;\n --ccd-effort-color-11: rgba(62, 56, 50, 0.05);\n --ccd-effort-color-12: rgba(62, 56, 50, 0.04);\n --ccd-effort-color-13: rgba(62, 56, 50, 0.06);\n --ccd-effort-color-14: #74706d;\n --ccd-effort-color-15: rgba(82, 76, 70, 0.055);\n --ccd-effort-color-16: rgba(76, 70, 65, 0.1);\n --ccd-effort-color-17: #34312f;\n --ccd-effort-color-18: rgba(35, 31, 29, 0.12);\n --ccd-effort-color-19: #fff;\n --ccd-effort-color-20: rgba(70, 64, 59, 0.035);\n --ccd-effort-color-21: #eeebe9;\n --ccd-effort-color-22: #ece9e7;\n --ccd-effort-color-23: #e2dce3;\n --ccd-effort-color-24: #d9d0df;\n --ccd-effort-color-25: #d0c1da;\n --ccd-effort-color-26: #cdbcd9;\n --ccd-effort-color-27: #cbbad8;\n --ccd-effort-color-28: #d5cadc;\n --ccd-effort-color-29: #c8b5d4;\n --ccd-effort-color-30: #bda6cc;\n --ccd-effort-color-31: #b59bc6;\n --ccd-effort-color-32: #b6b2af;\n --ccd-effort-color-33: rgba(76, 70, 65, 0.13);\n --ccd-effort-color-34: rgba(62, 56, 50, 0.08);\n --ccd-effort-color-35: rgba(62, 56, 50, 0.055);\n --ccd-effort-pixel-leftColor: 210, 206, 214;\n --ccd-effort-pixel-deepViolet: 156, 120, 192;\n --ccd-effort-pixel-deepMid: 156, 132, 192;\n --ccd-effort-pixel-midPurple: 168, 144, 204;\n --ccd-effort-pixel-softMid: 168, 156, 204;\n --ccd-effort-pixel-softLilac: 180, 168, 204;\n --ccd-effort-pixel-paleCool: 192, 180, 204;\n --ccd-effort-pixel-highlightColor: 216, 204, 228;\n --ccd-effort-pixel-peakColor: 232, 224, 242;\n}\n\nhtml[data-dsh-ccd-style=\"true\"] {\n --ccd-effort-shadow: 0 1px 2px rgba(62, 56, 50, .05), 0 4px 10px rgba(62, 56, 50, .04), 0 12px 28px rgba(62, 56, 50, .06);\n}\n\n/*\n * The same reference field on the dark canvas: the purple ramp keeps its\n * left-to-right direction (neutral \u2192 violet) inside the dark range, the\n * neutrals flip so the panel is a card above the transcript, and the knob stays\n * the brightest thing on the track \u2014 its fill is also the tooltip's text\n * colour, so the two keep following one value.\n */\nhtml[data-dsh-ccd-style=\"true\"] body[data-ds-dark-theme] {\n --ccd-effort-color-1: #a48ede;\n --ccd-effort-color-2: #b294d3;\n --ccd-effort-color-3: #e7e4de;\n --ccd-effort-color-4: #f6f4f0;\n --ccd-effort-color-5: #a09b94;\n --ccd-effort-color-6: #3a3936;\n --ccd-effort-color-7: #4b4944;\n --ccd-effort-color-8: #2b2a28;\n --ccd-effort-color-9: rgba(255, 255, 255, 0.1);\n --ccd-effort-color-10: #4d93f8;\n --ccd-effort-color-11: rgba(0, 0, 0, 0.45);\n --ccd-effort-color-12: rgba(0, 0, 0, 0.35);\n --ccd-effort-color-13: rgba(0, 0, 0, 0.45);\n --ccd-effort-color-14: #b7b2aa;\n --ccd-effort-color-15: rgba(255, 255, 255, 0.06);\n --ccd-effort-color-16: rgba(255, 255, 255, 0.14);\n --ccd-effort-color-17: #3a3936;\n --ccd-effort-color-18: rgba(0, 0, 0, 0.5);\n --ccd-effort-color-19: #ece9e4;\n --ccd-effort-color-20: rgba(255, 255, 255, 0.04);\n --ccd-effort-color-21: #34332f;\n --ccd-effort-color-22: #373430;\n --ccd-effort-color-23: #3e3943;\n --ccd-effort-color-24: #463e51;\n --ccd-effort-color-25: #4f4560;\n --ccd-effort-color-26: #57496a;\n --ccd-effort-color-27: #5c4d71;\n --ccd-effort-color-28: #524667;\n --ccd-effort-color-29: #63527c;\n --ccd-effort-color-30: #6e5989;\n --ccd-effort-color-31: #786096;\n --ccd-effort-color-32: #6f6c66;\n --ccd-effort-color-33: rgba(255, 255, 255, 0.16);\n --ccd-effort-color-34: rgba(0, 0, 0, 0.4);\n --ccd-effort-color-35: rgba(0, 0, 0, 0.35);\n --ccd-effort-pixel-leftColor: 92, 88, 98;\n --ccd-effort-pixel-deepViolet: 150, 116, 196;\n --ccd-effort-pixel-deepMid: 150, 128, 196;\n --ccd-effort-pixel-midPurple: 162, 140, 204;\n --ccd-effort-pixel-softMid: 162, 152, 204;\n --ccd-effort-pixel-softLilac: 174, 164, 206;\n --ccd-effort-pixel-paleCool: 184, 174, 206;\n --ccd-effort-pixel-highlightColor: 200, 188, 224;\n --ccd-effort-pixel-peakColor: 214, 204, 236;\n --ccd-effort-shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 4px 10px rgba(0, 0, 0, .3), 0 12px 28px rgba(0, 0, 0, .35);\n}\n\n/*\n * Chosen-value ink. The Composer's model name \u2014 in the tool row and in the menu\n * it opens \u2014 is the value that row exists to show, so it takes full black\n * instead of the warm near-black every other primary label uses; the dark\n * scheme keeps the same relationship with its own strongest ink, white. Neither\n * side is derived from a configured surface, so the pair is declared here\n * rather than in the scheme blocks above.\n */\nhtml[data-dsh-ccd-style=\"true\"] {\n --ccd-text-strong: #000000;\n}\n\nhtml[data-dsh-ccd-style=\"true\"] body[data-ds-dark-theme] {\n --ccd-text-strong: #ffffff;\n}\n\n/*\n * Header icon ink.\n *\n * The conversation header's trailing cluster is icon-only \u2014 the native session\n * menu, the two views this plugin adds, the scheduled-tasks entry while it has\n * records, the collapsed right panel's re-entry control \u2014 and the reference\n * draws those marks at full strength where the host uses its secondary label\n * grey. Like the chosen-value ink above, this pair answers to no configured\n * surface, so it is declared here rather than in the scheme blocks: the pure\n * black the reference measured on the light canvas, and its mirror white on the\n * dark one, because a black mark would vanish into that canvas.\n *\n * What each state puts behind the mark stays the host's: a near-black wash at\n * 6-14% in the light scheme, a white one at 8-14% in the dark, and the button's\n * own dimmed opacity while it is disabled \u2014 so the ink never meets a fill of its\n * own lightness (see `header-actions.css`).\n */\nhtml[data-dsh-ccd-style=\"true\"] {\n --ccd-header-icon-color: #000000;\n}\n\nhtml[data-dsh-ccd-style=\"true\"] body[data-ds-dark-theme] {\n --ccd-header-icon-color: #ffffff;\n}\n\n/*\n * The selected check in every Menu surface.\n *\n * The host paints it with `--dsw-alias-label-primary` in the Menu primitive's\n * own `Menu.module.css` (`.check`), so the workspace picker, the permission\n * menu, the view-options menu and the session \u22EF menu all show the same\n * near-black mark \u2014 a selection reads no louder than the label beside it. One\n * rule recolours all of them, because they all come from that one primitive.\n * The model picker is not one of them: it is this plugin's own card, and the\n * block at the end of this file is the rule that reaches its mark.\n *\n * Only the colour changes: the mark keeps the host's 14px seat, its\n * `display: grid` centring, its row height and every hover fill. The class is\n * the one the shipped static web frontend gives that module (`_4ub78_`, whose\n * local names run `_check_4ub78_179`, `_item_4ub78_90`, \u2026), and that asset is\n * platform-independent \u2014 both installers carry the identical\n * `@deepseek-ai/dsh-web-frontend` dist \u2014 so it is the same name on Windows and\n * needs no second spelling in `host-builds.ts` (`host-dom.ts` records the same\n * prefix).\n *\n * The rule covers the seat and its artwork: the svg inherits through\n * `currentColor`, and naming it as well keeps the mark blue even if a future\n * build moves the colour onto the artwork instead of the seat.\n */\nhtml[data-dsh-ccd-style=\"true\"] [role=\"menu\"] ._check_4ub78_179,\nhtml[data-dsh-ccd-style=\"true\"] [role=\"menuitem\"] ._check_4ub78_179,\nhtml[data-dsh-ccd-style=\"true\"] [role=\"menu\"] ._check_4ub78_179 svg,\nhtml[data-dsh-ccd-style=\"true\"] [role=\"menuitem\"] ._check_4ub78_179 svg {\n color: var(--ccd-accent);\n}\n\n/*\n * The selected mark in the model picker.\n *\n * The Composer's model seat is not the host's `ModelSelect`: this plugin's\n * `ModelControls` wins `conversation.input.model` (its registration carries\n * `priority: -10` while the host registers the same seat at the default 0, and\n * the slot chain takes the lowest priority that answers), and the card that\n * seat opens marks the chosen model with the text `\u2713` in the row's trailing\n * `span[aria-hidden=\"true\"]` (`features/model-controls/ModelControls.tsx`).\n * `features/model-controls/controls.css` paints that row `--ccd-text-strong`,\n * so the mark inherits full black \u2014 which is exactly what it did: it is\n * neither `._check_4ub78_179` nor an svg, so the Menu rule above cannot reach\n * it, and no `fill`/`stroke` override would either, because a text glyph is\n * painted by `color` alone.\n *\n * Only the ink changes: the row keeps its height, padding and hover, and the\n * artwork is named beside the seat so a later icon in the same seat stays\n * covered. The seat is reached two ways \u2014 the row's own selected state\n * (`aria-pressed`, which React writes as `\"true\"`/`\"false\"`) and the seat that\n * actually holds a glyph (`:not(:empty)`, because the unchosen rows render an\n * empty span) \u2014 so a later switch to `aria-checked` on a `menuitemradio` row\n * cannot silently return this mark to black.\n *\n * The host's own `ModelSelect` card is covered as well, for the path where\n * that seat is the one on screen: its `.check` seat holds an svg whose path\n * strokes `currentColor` (ui-primitives `IconCheckOutlineArtwork`), so the\n * same `color` reaches the artwork. `cl2Rlq_` is already in the prefix table\n * (`compat/host-dom.ts`), and the Windows build's `wq12jW_` spelling is what\n * `compat/dom.ts` substitutes when this sheet is mounted.\n */\nhtml[data-dsh-ccd-style=\"true\"] .ccd-model-controls .ccd-model-options button[aria-pressed=\"true\"] > span[aria-hidden=\"true\"],\nhtml[data-dsh-ccd-style=\"true\"] .ccd-model-controls .ccd-model-options button[aria-pressed=\"true\"] > span[aria-hidden=\"true\"] svg,\nhtml[data-dsh-ccd-style=\"true\"] .ccd-model-controls .ccd-model-options button > span[aria-hidden=\"true\"]:not(:empty),\nhtml[data-dsh-ccd-style=\"true\"] .ccd-model-controls .ccd-model-options button > span[aria-hidden=\"true\"]:not(:empty) svg,\nhtml[data-dsh-ccd-style=\"true\"] [role=\"menu\"] .cl2Rlq_check,\nhtml[data-dsh-ccd-style=\"true\"] [role=\"menuitemradio\"] .cl2Rlq_check,\nhtml[data-dsh-ccd-style=\"true\"] [role=\"menu\"] .cl2Rlq_check svg,\nhtml[data-dsh-ccd-style=\"true\"] [role=\"menuitemradio\"] .cl2Rlq_check svg {\n color: var(--ccd-accent);\n}\n";
|
|
4447
4840
|
|
|
4448
4841
|
// src/client/theme/mount.ts
|
|
4449
4842
|
function mountTheme(environment, scope) {
|
|
@@ -4516,15 +4909,27 @@ function magnet(value) {
|
|
|
4516
4909
|
const t = 1 - distance / 0.5;
|
|
4517
4910
|
return value - delta * (0.68 + 0.42 * t) * t * t;
|
|
4518
4911
|
}
|
|
4519
|
-
function
|
|
4520
|
-
const
|
|
4521
|
-
|
|
4912
|
+
function schemeOf(canvas) {
|
|
4913
|
+
const document2 = canvas.ownerDocument;
|
|
4914
|
+
return `${document2?.documentElement?.getAttribute("data-dsh-ccd-style") ?? ""}|${document2?.body?.hasAttribute("data-ds-dark-theme") ?? false}`;
|
|
4915
|
+
}
|
|
4916
|
+
var palettes = /* @__PURE__ */ new WeakMap();
|
|
4917
|
+
function paletteOf(canvas, scheme) {
|
|
4918
|
+
const cached2 = palettes.get(canvas);
|
|
4919
|
+
if (cached2 !== void 0 && cached2.scheme === scheme) return cached2.palette;
|
|
4522
4920
|
const style = getComputedStyle(canvas);
|
|
4523
4921
|
const color = (key) => {
|
|
4524
4922
|
const channels = style.getPropertyValue("--ccd-effort-pixel-" + key).split(",").map(Number);
|
|
4525
4923
|
return [channels[0] ?? 0, channels[1] ?? 0, channels[2] ?? 0];
|
|
4526
4924
|
};
|
|
4527
4925
|
const palette = { leftColor: color("leftColor"), deepViolet: color("deepViolet"), deepMid: color("deepMid"), midPurple: color("midPurple"), softMid: color("softMid"), softLilac: color("softLilac"), paleCool: color("paleCool"), highlightColor: color("highlightColor"), peakColor: color("peakColor") };
|
|
4926
|
+
palettes.set(canvas, { scheme, palette });
|
|
4927
|
+
return palette;
|
|
4928
|
+
}
|
|
4929
|
+
function drawPixelField(canvas, elapsed, reduced) {
|
|
4930
|
+
const context = canvas.getContext("2d");
|
|
4931
|
+
if (!context || !canvas.width || !canvas.height) return;
|
|
4932
|
+
const palette = paletteOf(canvas, schemeOf(canvas));
|
|
4528
4933
|
const ratio = Math.min(window.devicePixelRatio || 1, 2);
|
|
4529
4934
|
const width = canvas.width / ratio, height = canvas.height / ratio;
|
|
4530
4935
|
context.setTransform(ratio, 0, 0, ratio, 0, 0);
|
|
@@ -4640,10 +5045,10 @@ function drawPixelField(canvas, elapsed, reduced) {
|
|
|
4640
5045
|
mix(leftColor[1], variedPurple[1], purpleAmount),
|
|
4641
5046
|
mix(leftColor[2], variedPurple[2], purpleAmount)
|
|
4642
5047
|
];
|
|
4643
|
-
const
|
|
5048
|
+
const color = hottestHighlight ? mixColor(baseColor, peakColor, 0.95) : mixColor(baseColor, highlightColor, highlightAmount);
|
|
4644
5049
|
const baseOpacity = 0.7 + baseHash * 0.2;
|
|
4645
5050
|
context.globalAlpha = peakHighlight || hottestHighlight ? revealAlpha * fieldIntensity : revealAlpha * fieldIntensity * clamp2(baseOpacity + flowingFlicker * 0.12, 0, 1);
|
|
4646
|
-
context.fillStyle =
|
|
5051
|
+
context.fillStyle = color;
|
|
4647
5052
|
context.fillRect(x + gap * 0.5, y + gap * 0.5, cell - gap, cell - gap);
|
|
4648
5053
|
}
|
|
4649
5054
|
}
|
|
@@ -4652,6 +5057,15 @@ function drawPixelField(canvas, elapsed, reduced) {
|
|
|
4652
5057
|
|
|
4653
5058
|
// src/client/features/model-controls/popup.ts
|
|
4654
5059
|
var import_react = require("react");
|
|
5060
|
+
function cachedSize(surface) {
|
|
5061
|
+
let size = null;
|
|
5062
|
+
const read = () => {
|
|
5063
|
+
const { width, height } = surface.getBoundingClientRect();
|
|
5064
|
+
size = { width, height };
|
|
5065
|
+
return size;
|
|
5066
|
+
};
|
|
5067
|
+
return { current: () => size ?? read(), refresh: read };
|
|
5068
|
+
}
|
|
4655
5069
|
function usePopup(anchor, close) {
|
|
4656
5070
|
const panel = (0, import_react.useRef)(null);
|
|
4657
5071
|
const [position, setPosition] = (0, import_react.useState)({ left: 0, top: 0, visibility: "hidden" });
|
|
@@ -4660,13 +5074,26 @@ function usePopup(anchor, close) {
|
|
|
4660
5074
|
if (!surface || !trigger) return;
|
|
4661
5075
|
surface.setAttribute("popover", "manual");
|
|
4662
5076
|
surface.showPopover();
|
|
5077
|
+
const size = cachedSize(surface);
|
|
4663
5078
|
const place = () => {
|
|
4664
|
-
const a = trigger.getBoundingClientRect(), p =
|
|
5079
|
+
const a = trigger.getBoundingClientRect(), p = size.current();
|
|
4665
5080
|
const left = Math.max(12, Math.min(a.right - p.width, innerWidth - p.width - 12));
|
|
4666
5081
|
const top = Math.max(12, Math.min(a.top - p.height - 8, innerHeight - p.height - 12));
|
|
4667
5082
|
setPosition((previous) => previous.left === left && previous.top === top && previous.visibility === "visible" ? previous : { left, top, visibility: "visible" });
|
|
4668
5083
|
};
|
|
4669
5084
|
place();
|
|
5085
|
+
let placed = 0;
|
|
5086
|
+
let placementTimer;
|
|
5087
|
+
const schedulePlace = () => {
|
|
5088
|
+
if (placed !== 0 || placementTimer !== void 0) return;
|
|
5089
|
+
const run = () => {
|
|
5090
|
+
placed = 0;
|
|
5091
|
+
placementTimer = void 0;
|
|
5092
|
+
place();
|
|
5093
|
+
};
|
|
5094
|
+
if (typeof requestAnimationFrame === "function") placed = requestAnimationFrame(run);
|
|
5095
|
+
else placementTimer = setTimeout(run, 16);
|
|
5096
|
+
};
|
|
4670
5097
|
const onPointer = (event) => {
|
|
4671
5098
|
if (!surface.contains(event.target) && !trigger.contains(event.target)) close();
|
|
4672
5099
|
};
|
|
@@ -4692,20 +5119,29 @@ function usePopup(anchor, close) {
|
|
|
4692
5119
|
first.focus();
|
|
4693
5120
|
}
|
|
4694
5121
|
};
|
|
4695
|
-
const observer = new MutationObserver(
|
|
5122
|
+
const observer = new MutationObserver(schedulePlace);
|
|
4696
5123
|
observer.observe(document.body, { subtree: true, attributes: true, attributeFilter: ["style"] });
|
|
4697
|
-
const resize = new ResizeObserver(
|
|
5124
|
+
const resize = new ResizeObserver(() => {
|
|
5125
|
+
size.refresh();
|
|
5126
|
+
schedulePlace();
|
|
5127
|
+
});
|
|
4698
5128
|
resize.observe(surface);
|
|
4699
|
-
|
|
5129
|
+
const onResize = () => {
|
|
5130
|
+
size.refresh();
|
|
5131
|
+
schedulePlace();
|
|
5132
|
+
};
|
|
5133
|
+
window.addEventListener("resize", onResize);
|
|
4700
5134
|
document.addEventListener("pointerdown", onPointer, true);
|
|
4701
5135
|
document.addEventListener("focusin", onFocus);
|
|
4702
5136
|
surface.addEventListener("keydown", onKey);
|
|
4703
5137
|
const autofocus = requestAnimationFrame(() => surface.querySelector("[data-autofocus]")?.focus());
|
|
4704
5138
|
return () => {
|
|
4705
5139
|
cancelAnimationFrame(autofocus);
|
|
5140
|
+
if (placed !== 0) cancelAnimationFrame(placed);
|
|
5141
|
+
if (placementTimer !== void 0) clearTimeout(placementTimer);
|
|
4706
5142
|
observer.disconnect();
|
|
4707
5143
|
resize.disconnect();
|
|
4708
|
-
window.removeEventListener("resize",
|
|
5144
|
+
window.removeEventListener("resize", onResize);
|
|
4709
5145
|
document.removeEventListener("pointerdown", onPointer, true);
|
|
4710
5146
|
document.removeEventListener("focusin", onFocus);
|
|
4711
5147
|
surface.removeEventListener("keydown", onKey);
|
|
@@ -4757,25 +5193,31 @@ function EffortPanel({ anchor, choices, selected, disabled, busy, error, onCommi
|
|
|
4757
5193
|
const element = canvas.current;
|
|
4758
5194
|
const reduced = matchMedia("(prefers-reduced-motion: reduce)");
|
|
4759
5195
|
let animation = 0, last = 0, started = performance.now();
|
|
4760
|
-
|
|
5196
|
+
let width = 0, height = 0;
|
|
5197
|
+
const size = () => {
|
|
4761
5198
|
const rect = element.getBoundingClientRect(), ratio = Math.min(devicePixelRatio || 1, 2);
|
|
4762
|
-
|
|
5199
|
+
width = Math.round(rect.width * ratio);
|
|
5200
|
+
height = Math.round(rect.height * ratio);
|
|
4763
5201
|
if (element.width !== width || element.height !== height) {
|
|
4764
5202
|
element.width = width;
|
|
4765
5203
|
element.height = height;
|
|
4766
5204
|
}
|
|
5205
|
+
};
|
|
5206
|
+
const draw = (time) => {
|
|
5207
|
+
if (element.width === 0 || element.height === 0) size();
|
|
4767
5208
|
if (time - last >= 33 || reduced.matches) {
|
|
4768
5209
|
last = time;
|
|
4769
5210
|
drawPixelField(element, time - started, reduced.matches);
|
|
4770
5211
|
}
|
|
4771
5212
|
if (!reduced.matches) animation = requestAnimationFrame(draw);
|
|
4772
5213
|
};
|
|
5214
|
+
const resize = new ResizeObserver(() => size());
|
|
4773
5215
|
const restart = () => {
|
|
4774
5216
|
cancelAnimationFrame(animation);
|
|
4775
5217
|
started = performance.now();
|
|
5218
|
+
size();
|
|
4776
5219
|
draw(started);
|
|
4777
5220
|
};
|
|
4778
|
-
const resize = new ResizeObserver(restart);
|
|
4779
5221
|
resize.observe(element);
|
|
4780
5222
|
reduced.addEventListener("change", restart);
|
|
4781
5223
|
restart();
|
|
@@ -5219,7 +5661,7 @@ function sessionGroup(status, running) {
|
|
|
5219
5661
|
var GROUPS = ["input", "working", "ended"];
|
|
5220
5662
|
function mountStatusGroups(document2, ctx, showEmpty, report) {
|
|
5221
5663
|
const status = ctx.uiSession?.sessionStatus;
|
|
5222
|
-
if (!
|
|
5664
|
+
if (!ctx.sessions?.list) return () => {
|
|
5223
5665
|
};
|
|
5224
5666
|
const host = hostSelectors(document2);
|
|
5225
5667
|
const win = document2.defaultView;
|
|
@@ -5263,13 +5705,30 @@ function mountStatusGroups(document2, ctx, showEmpty, report) {
|
|
|
5263
5705
|
saved.clear();
|
|
5264
5706
|
list = null;
|
|
5265
5707
|
};
|
|
5266
|
-
const observer = new MutationObserver(() =>
|
|
5708
|
+
const observer = new MutationObserver((records) => {
|
|
5709
|
+
if (list !== null && list.isConnected && !recordsTouch(records, list) && !recordsIntroduce(records, host.browserFlatList)) return;
|
|
5710
|
+
schedule();
|
|
5711
|
+
});
|
|
5267
5712
|
const observe = () => observer.observe(document2.documentElement, {
|
|
5268
5713
|
childList: true,
|
|
5269
5714
|
subtree: true,
|
|
5270
5715
|
attributes: true,
|
|
5271
|
-
attributeFilter: ["class", "lang", "data-row-key", "data-ccd-blank-session"]
|
|
5716
|
+
attributeFilter: ["class", "lang", "data-row-key", "data-state", "data-ccd-blank-session"]
|
|
5272
5717
|
});
|
|
5718
|
+
let childrenDirty = true;
|
|
5719
|
+
let childrenByParent = /* @__PURE__ */ new Map();
|
|
5720
|
+
const readChildren = (catalog) => {
|
|
5721
|
+
if (!childrenDirty) return childrenByParent;
|
|
5722
|
+
childrenByParent = /* @__PURE__ */ new Map();
|
|
5723
|
+
for (const child of Object.values(catalog.byId)) {
|
|
5724
|
+
if (child.origin !== "subagent" || !child.parentId) continue;
|
|
5725
|
+
const children = childrenByParent.get(child.parentId) ?? [];
|
|
5726
|
+
children.push(child);
|
|
5727
|
+
childrenByParent.set(child.parentId, children);
|
|
5728
|
+
}
|
|
5729
|
+
childrenDirty = false;
|
|
5730
|
+
return childrenByParent;
|
|
5731
|
+
};
|
|
5273
5732
|
const sync = () => {
|
|
5274
5733
|
queued = false;
|
|
5275
5734
|
if (disposed) return;
|
|
@@ -5279,36 +5738,106 @@ function mountStatusGroups(document2, ctx, showEmpty, report) {
|
|
|
5279
5738
|
if (next !== list) restore();
|
|
5280
5739
|
if (!next) return;
|
|
5281
5740
|
list = next;
|
|
5282
|
-
const
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5741
|
+
for (const [node, values] of saved) if (!node.isConnected || !list.contains(node)) {
|
|
5742
|
+
restoreNode(node, values);
|
|
5743
|
+
saved.delete(node);
|
|
5744
|
+
}
|
|
5745
|
+
for (const [node, animation] of animations) if (!node.isConnected || !list.contains(node)) {
|
|
5746
|
+
animation.cancel();
|
|
5747
|
+
animations.delete(node);
|
|
5748
|
+
}
|
|
5749
|
+
for (const node of owned) if (!node.isConnected || !list.contains(node)) {
|
|
5750
|
+
node.remove();
|
|
5751
|
+
owned.delete(node);
|
|
5290
5752
|
}
|
|
5753
|
+
const catalog = ctx.sessions.list.getSnapshot();
|
|
5754
|
+
const statuses = status?.getSnapshot();
|
|
5755
|
+
const childrenOf = readChildren(catalog);
|
|
5291
5756
|
const zh2 = document2.documentElement.lang.toLowerCase().startsWith("zh");
|
|
5292
5757
|
const labels = zh2 ? ["\u9700\u8981\u8F93\u5165", "\u8FDB\u884C\u4E2D", "\u5DF2\u7ED3\u675F"] : ["Needs input", "Working", "Ended"];
|
|
5293
5758
|
const rows = [...list.querySelectorAll('[data-row-key^="session:"]')].filter((row) => !row.hasAttribute("data-ccd-blank-session"));
|
|
5294
|
-
const
|
|
5295
|
-
let layoutChanged = false;
|
|
5296
|
-
remember(list, "data-ccd-status-list", "");
|
|
5297
|
-
const counts = [0, 0, 0];
|
|
5298
|
-
const stride = (rows.length + 1) * 2;
|
|
5299
|
-
const ordered = [];
|
|
5300
|
-
for (const row of rows) {
|
|
5759
|
+
const plan = rows.map((row) => {
|
|
5301
5760
|
const id = row.dataset.rowKey.slice(8);
|
|
5302
5761
|
const summary = catalog.byId[id];
|
|
5303
|
-
const children =
|
|
5304
|
-
const runningChild = children.some((child) => statuses
|
|
5305
|
-
const ownStatus = statuses.get(id)
|
|
5762
|
+
const children = childrenOf.get(id) ?? [];
|
|
5763
|
+
const runningChild = children.some((child) => statuses?.get(child.id)?.running ?? child.running);
|
|
5764
|
+
const ownStatus = statuses ? statuses.get(id) : {
|
|
5765
|
+
...row.querySelector(host.sessionNeedsInput) ? { pendingInteraction: { kind: "native" } } : {},
|
|
5766
|
+
running: !!row.querySelector(host.sessionWorking) || summary?.running
|
|
5767
|
+
};
|
|
5306
5768
|
const group = sessionGroup({
|
|
5307
5769
|
...ownStatus,
|
|
5308
5770
|
running: (ownStatus?.running ?? summary?.running ?? false) || runningChild
|
|
5309
5771
|
}, false);
|
|
5310
|
-
|
|
5311
|
-
|
|
5772
|
+
return { row, id, group, children };
|
|
5773
|
+
});
|
|
5774
|
+
const counts = [0, 0, 0];
|
|
5775
|
+
const stride = (rows.length + 1) * 2;
|
|
5776
|
+
const orderedRows = plan.map((entry) => {
|
|
5777
|
+
const index = GROUPS.indexOf(entry.group);
|
|
5778
|
+
return { ...entry, order: index * stride + ++counts[index] * 2 };
|
|
5779
|
+
});
|
|
5780
|
+
const boxes = /* @__PURE__ */ new Map();
|
|
5781
|
+
for (const node of owned) {
|
|
5782
|
+
const key = node.dataset.ccdStatusOwned;
|
|
5783
|
+
if (key !== void 0) boxes.set(key, node);
|
|
5784
|
+
}
|
|
5785
|
+
const signatures = /* @__PURE__ */ new Map();
|
|
5786
|
+
for (const { id, group, children } of plan) {
|
|
5787
|
+
if (group === "working" && children.length > 0) {
|
|
5788
|
+
signatures.set(`children:${id}`, JSON.stringify(children.map((child) => [child.id, child.displayTitle, child.running])));
|
|
5789
|
+
}
|
|
5790
|
+
}
|
|
5791
|
+
let changed = false;
|
|
5792
|
+
for (const { row, group, order } of orderedRows) {
|
|
5793
|
+
if (row.dataset.ccdStatusOrder !== String(order) || row.dataset.ccdStatusGroup !== group || row.style.getPropertyValue("--ccd-status-order") !== String(order) || row.getAttribute("tabindex") !== "0") {
|
|
5794
|
+
changed = true;
|
|
5795
|
+
break;
|
|
5796
|
+
}
|
|
5797
|
+
for (let parent = row.parentElement; parent && parent !== list; parent = parent.parentElement) {
|
|
5798
|
+
if (!parent.hasAttribute("data-ccd-status-wrapper")) {
|
|
5799
|
+
changed = true;
|
|
5800
|
+
break;
|
|
5801
|
+
}
|
|
5802
|
+
}
|
|
5803
|
+
if (changed) break;
|
|
5804
|
+
}
|
|
5805
|
+
if (!changed) {
|
|
5806
|
+
for (const [key, signature] of signatures) {
|
|
5807
|
+
if (boxes.get(key)?.dataset.signature !== signature) {
|
|
5808
|
+
changed = true;
|
|
5809
|
+
break;
|
|
5810
|
+
}
|
|
5811
|
+
}
|
|
5812
|
+
}
|
|
5813
|
+
if (!changed) {
|
|
5814
|
+
for (const [index, group] of GROUPS.entries()) {
|
|
5815
|
+
const heading = boxes.get(group);
|
|
5816
|
+
if (counts[index] || showEmpty) {
|
|
5817
|
+
if (heading === void 0 || heading.textContent !== labels[index]) {
|
|
5818
|
+
changed = true;
|
|
5819
|
+
break;
|
|
5820
|
+
}
|
|
5821
|
+
} else if (heading !== void 0) {
|
|
5822
|
+
changed = true;
|
|
5823
|
+
break;
|
|
5824
|
+
}
|
|
5825
|
+
}
|
|
5826
|
+
}
|
|
5827
|
+
if (!changed) {
|
|
5828
|
+
const live = /* @__PURE__ */ new Set([...signatures.keys()]);
|
|
5829
|
+
for (const key of boxes.keys()) if (key.startsWith("children:") && !live.has(key)) {
|
|
5830
|
+
changed = true;
|
|
5831
|
+
break;
|
|
5832
|
+
}
|
|
5833
|
+
}
|
|
5834
|
+
if (!changed) return;
|
|
5835
|
+
const animateLayout = !motion.matches && !document2.hidden;
|
|
5836
|
+
const before = animateLayout ? new Map(rows.map((row) => [row, row.getBoundingClientRect()])) : null;
|
|
5837
|
+
let layoutChanged = false;
|
|
5838
|
+
remember(list, "data-ccd-status-list", "");
|
|
5839
|
+
const ordered = [];
|
|
5840
|
+
for (const { row, id, group, order, children } of orderedRows) {
|
|
5312
5841
|
if (row.dataset.ccdStatusOrder !== String(order)) layoutChanged = true;
|
|
5313
5842
|
remember(row, "data-ccd-status-order", String(order));
|
|
5314
5843
|
remember(row, "data-ccd-status-group", group);
|
|
@@ -5319,11 +5848,13 @@ function mountStatusGroups(document2, ctx, showEmpty, report) {
|
|
|
5319
5848
|
value: row.style.getPropertyValue("--ccd-status-order"),
|
|
5320
5849
|
priority: row.style.getPropertyPriority("--ccd-status-order")
|
|
5321
5850
|
});
|
|
5322
|
-
row.style.
|
|
5851
|
+
if (row.style.getPropertyValue("--ccd-status-order") !== String(order)) {
|
|
5852
|
+
row.style.setProperty("--ccd-status-order", String(order));
|
|
5853
|
+
}
|
|
5323
5854
|
remember(row, "tabindex", "0");
|
|
5324
5855
|
ordered.push(row);
|
|
5325
5856
|
const childKey = `children:${id}`;
|
|
5326
|
-
let childBox =
|
|
5857
|
+
let childBox = boxes.get(childKey);
|
|
5327
5858
|
if (group === "working" && children.length) {
|
|
5328
5859
|
if (!childBox) {
|
|
5329
5860
|
layoutChanged = true;
|
|
@@ -5334,9 +5865,10 @@ function mountStatusGroups(document2, ctx, showEmpty, report) {
|
|
|
5334
5865
|
owned.add(childBox);
|
|
5335
5866
|
list.append(childBox);
|
|
5336
5867
|
}
|
|
5337
|
-
childBox.style.order = String(order + 1);
|
|
5338
|
-
|
|
5339
|
-
|
|
5868
|
+
if (childBox.style.order !== String(order + 1)) childBox.style.order = String(order + 1);
|
|
5869
|
+
const childLabel = zh2 ? "\u5B50\u4EFB\u52A1" : "Subagents";
|
|
5870
|
+
if (childBox.getAttribute("aria-label") !== childLabel) childBox.setAttribute("aria-label", childLabel);
|
|
5871
|
+
const signature = signatures.get(childKey);
|
|
5340
5872
|
if (childBox.dataset.signature !== signature) {
|
|
5341
5873
|
layoutChanged = true;
|
|
5342
5874
|
childBox.dataset.signature = signature;
|
|
@@ -5356,7 +5888,7 @@ function mountStatusGroups(document2, ctx, showEmpty, report) {
|
|
|
5356
5888
|
}
|
|
5357
5889
|
}
|
|
5358
5890
|
for (const [index, group] of GROUPS.entries()) {
|
|
5359
|
-
let heading =
|
|
5891
|
+
let heading = boxes.get(group);
|
|
5360
5892
|
if (counts[index] || showEmpty) {
|
|
5361
5893
|
if (!heading) {
|
|
5362
5894
|
layoutChanged = true;
|
|
@@ -5367,7 +5899,7 @@ function mountStatusGroups(document2, ctx, showEmpty, report) {
|
|
|
5367
5899
|
owned.add(heading);
|
|
5368
5900
|
list.append(heading);
|
|
5369
5901
|
}
|
|
5370
|
-
heading.style.order = String(index * stride);
|
|
5902
|
+
if (heading.style.order !== String(index * stride)) heading.style.order = String(index * stride);
|
|
5371
5903
|
if (heading.textContent !== labels[index]) heading.textContent = labels[index];
|
|
5372
5904
|
} else if (heading) {
|
|
5373
5905
|
layoutChanged = true;
|
|
@@ -5384,7 +5916,7 @@ function mountStatusGroups(document2, ctx, showEmpty, report) {
|
|
|
5384
5916
|
if (layoutChanged) {
|
|
5385
5917
|
stopAnimations();
|
|
5386
5918
|
}
|
|
5387
|
-
if (layoutChanged &&
|
|
5919
|
+
if (layoutChanged && before !== null) for (const row of ordered) {
|
|
5388
5920
|
const previous = before.get(row);
|
|
5389
5921
|
const rect = row.getBoundingClientRect();
|
|
5390
5922
|
const dy = previous.top - rect.top;
|
|
@@ -5398,10 +5930,6 @@ function mountStatusGroups(document2, ctx, showEmpty, report) {
|
|
|
5398
5930
|
if (animations.get(row) === animation) animations.delete(row);
|
|
5399
5931
|
};
|
|
5400
5932
|
}
|
|
5401
|
-
for (const [node, values] of saved) if (!node.isConnected) {
|
|
5402
|
-
restoreNode(node, values);
|
|
5403
|
-
saved.delete(node);
|
|
5404
|
-
}
|
|
5405
5933
|
} catch (error) {
|
|
5406
5934
|
restore();
|
|
5407
5935
|
report(error);
|
|
@@ -5439,8 +5967,11 @@ function mountStatusGroups(document2, ctx, showEmpty, report) {
|
|
|
5439
5967
|
event.stopPropagation();
|
|
5440
5968
|
}
|
|
5441
5969
|
};
|
|
5442
|
-
const offStatus = status
|
|
5443
|
-
const offList = ctx.sessions.list.subscribe(
|
|
5970
|
+
const offStatus = status?.subscribe(schedule);
|
|
5971
|
+
const offList = ctx.sessions.list.subscribe(() => {
|
|
5972
|
+
childrenDirty = true;
|
|
5973
|
+
schedule();
|
|
5974
|
+
});
|
|
5444
5975
|
document2.addEventListener("keydown", keydown);
|
|
5445
5976
|
document2.addEventListener("dragstart", drag, true);
|
|
5446
5977
|
document2.addEventListener("visibilitychange", stopAnimations);
|
|
@@ -5451,7 +5982,7 @@ function mountStatusGroups(document2, ctx, showEmpty, report) {
|
|
|
5451
5982
|
disposed = true;
|
|
5452
5983
|
observer.disconnect();
|
|
5453
5984
|
offList();
|
|
5454
|
-
offStatus();
|
|
5985
|
+
offStatus?.();
|
|
5455
5986
|
document2.removeEventListener("keydown", keydown);
|
|
5456
5987
|
document2.removeEventListener("dragstart", drag, true);
|
|
5457
5988
|
document2.removeEventListener("visibilitychange", stopAnimations);
|
|
@@ -6193,7 +6724,7 @@ function ViewOptionsCard({
|
|
|
6193
6724
|
}
|
|
6194
6725
|
|
|
6195
6726
|
// src/client/features/view-options/view-options.css
|
|
6196
|
-
var view_options_default = "/*\n * The sidebar's view-options card (DESIGN.md \xA76.7).\n *\n * This is the plugin's own popup, drawn where the host's eight-row menu used to\n * be: three value rows \u2014 the dimension on the left, the current choice on the\n * right, a disclosure mark at the end \u2014 one switch row, and one action row,\n * with the options of a value row one level down in a card of their own. The\n * numbers are the reference's, measured in
|
|
6727
|
+
var view_options_default = "/*\n * The sidebar's view-options card (DESIGN.md \xA76.7).\n *\n * This is the plugin's own popup, drawn where the host's eight-row menu used to\n * be: three value rows \u2014 the dimension on the left, the current choice on the\n * right, a disclosure mark at the end \u2014 one switch row, and one action row,\n * with the options of a value row one level down in a card of their own. The\n * numbers are the reference's, measured at @2x and recorded in DESIGN.md \xA76.7:\n * a 24px row (13px/20px label, 2px above and below, 8px inline), a 6px row\n * corner, the value column at 12px in the muted ink, 12px between the columns,\n * a 16px disclosure mark pulled 4px toward the edge, and 1px separators inset\n * 8px with 4px above and below.\n *\n * Both surfaces carry `data-menu-material` for the shared card fill, with a\n * scoped 10px corner, hairline outline and broader reference shadow.\n * The host's driving marker (`html[data-ccd-view-options-driving]`)\n * keeps the card this plugin *drives* off screen without touching this one.\n *\n * Two things are stated rather than inherited:\n *\n * - **The row is a grid, not the family's flex line.** A value row has three\n * seats \u2014 label, value, mark \u2014 and the value has to sit against the trailing\n * edge however long the label is, so the label takes the flexible column and\n * the other two are sized by their contents. Rows with only a label and a\n * mark (the switch, and the options inside the flyout) drop the third column,\n * because an empty `auto` column would still take its 12px column gap and\n * push the mark off the edge.\n * - **The flyout is positioned inside the card.** The card is a native popover\n * in the top layer, so an absolutely positioned child paints outside its\n * bounds and above the page without a second top-layer surface. A 1px gap\n * and matching hit bridge keep the cards almost touching, while the submenu\n * top aligns with the active row, as in the supplied reference.\n */\n\n.ccd-view-options {\n position: fixed;\n inset: auto;\n margin: 0;\n box-sizing: border-box;\n width: 200px;\n min-width: 200px;\n max-width: 320px;\n padding: 4px;\n /* Popovers default to overflow:auto, which clips an absolutely positioned\n submenu outside this surface and prevents pointer input from reaching it. */\n overflow: visible;\n color: var(--ccd-view-menu-text);\n font: 13px/20px var(--ccd-font-ui);\n /* The seat it is registered in is click-through; a top-layer surface is hit\n tested on its own, and this states it rather than relying on that. */\n pointer-events: auto;\n}\n\n/* The UA hides a closed popover with `display: none`; the card's own display\n has to be scoped to the open state or it would keep that rule from applying. */\n.ccd-view-options:popover-open {\n display: flex;\n flex-direction: column;\n}\n\n.ccd-view-options::backdrop {\n background: transparent;\n pointer-events: none;\n}\n\n/* The supplied reference has a thinner outline and a 10px corner, with a\n broader shadow. Scope these overrides to the two view-options surfaces. */\nhtml[data-dsh-ccd-style=\"true\"] .ccd-view-options[data-menu-material],\nhtml[data-dsh-ccd-style=\"true\"] .ccd-view-submenu[data-menu-material] {\n /* The shared menu rules also consume these two variables. Rebind them\n locally so their more specific hover/separator selectors agree. */\n --ccd-hover: var(--ccd-view-menu-hover);\n --ccd-border-soft: var(--ccd-view-menu-separator);\n border: .5px solid var(--ccd-view-menu-border);\n border-radius: var(--ccd-view-menu-radius);\n box-shadow: var(--ccd-view-menu-shadow);\n}\n\n.ccd-view-row-wrap {\n display: contents;\n}\n\n.ccd-view-row {\n box-sizing: border-box;\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto auto;\n align-items: center;\n column-gap: 12px;\n width: 100%;\n min-height: 24px;\n padding: 2px 8px;\n border: 0;\n border-radius: var(--ccd-table-radius);\n background: none;\n color: var(--ccd-view-menu-text);\n font: inherit;\n text-align: left;\n cursor: default;\n}\n\n.ccd-view-row[data-ccd-view-switch],\n.ccd-view-submenu .ccd-view-row {\n grid-template-columns: minmax(0, 1fr) auto;\n}\n\n/* One fill for the row's states, the family's own: the pointer, the keyboard\n walk and the row whose flyout is open all read as the same hover step, and\n the selection is the accent check rather than a second fill (DESIGN.md \xA76.7). */\n.ccd-view-row:hover,\n.ccd-view-row:focus-visible,\n.ccd-view-row[aria-expanded='true'] {\n background: var(--ccd-view-menu-hover);\n}\n\n.ccd-view-row:focus-visible {\n outline: none;\n}\n\n.ccd-view-row-label {\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n/* The current value: one step below the label in size and ink, so the row reads\n as \"this dimension, this choice\" rather than two labels side by side. */\n.ccd-view-row-value {\n max-width: 160px;\n overflow: hidden;\n color: var(--ccd-view-menu-muted);\n font-size: 12px;\n line-height: 16px;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.ccd-view-chevron,\n.ccd-view-check {\n width: 16px;\n height: 16px;\n margin-right: -4px;\n}\n\n.ccd-view-chevron {\n color: var(--ccd-view-menu-muted);\n}\n\n.ccd-view-check {\n color: var(--ccd-view-menu-accent);\n}\n\n.ccd-view-separator {\n height: 1px;\n margin: 4px 8px;\n background: var(--ccd-view-menu-separator);\n}\n\n.ccd-view-submenu {\n position: absolute;\n z-index: 1;\n box-sizing: border-box;\n min-width: 128px;\n max-width: 260px;\n padding: 4px;\n display: flex;\n flex-direction: column;\n}\n\n/* The bridge spans the gap between a row and its flyout, so a pointer crossing\n it does not fall out of both surfaces and close the card behind it. */\n.ccd-view-submenu::before {\n content: '';\n position: absolute;\n top: 0;\n bottom: 0;\n left: -1px;\n width: 1px;\n}\n\n.ccd-view-submenu[data-ccd-flip='true']::before {\n left: auto;\n right: -1px;\n}\n\n/* The host card this plugin drives.\n *\n * `compat/view-options.ts` opens DSH's own view-options menu to read the three\n * view settings and to click one of its rows \u2014 that menu is the only handle the\n * host publishes on values it keeps in the browser \u2014 and the reader must never\n * see it. The marker the driver writes on the document element takes the card\n * and the vibrancy backing the primitive portals beside it out of the paint;\n * `visibility` rather than `display` is what keeps the box the host's own\n * positioning measures.\n *\n * The rule lives in this sheet rather than in `theme/menus.css` because it is\n * the driver's own mechanism, not a fact about the popup family: the family\n * sheet draws every `MenuSurface` the document has, and this one only ever\n * applies while this card is driving. The card above carries\n * `data-menu-material` as well \u2014 it *is* that family \u2014 so the family sheet draws\n * its chrome, while this rule names the host's list class and the backing\n * element alone. */\nhtml[data-dsh-ccd-style=\"true\"][data-ccd-view-options-driving] ._7514NG_viewOptionsMenu,\nhtml[data-dsh-ccd-style=\"true\"][data-ccd-view-options-driving] [data-menu-backing] {\n visibility: hidden;\n pointer-events: none;\n}\n";
|
|
6197
6728
|
|
|
6198
6729
|
// src/client/features/view-options/mount.ts
|
|
6199
6730
|
var VIEW_OPTIONS_ORDER = 30;
|
|
@@ -6274,6 +6805,23 @@ function sessionOf(document2, trigger) {
|
|
|
6274
6805
|
const id = body?.getAttribute("data-conversation-session") ?? "";
|
|
6275
6806
|
return id === "" ? null : id;
|
|
6276
6807
|
}
|
|
6808
|
+
function sameReading(left, right) {
|
|
6809
|
+
if (left === right) return true;
|
|
6810
|
+
if (left.window !== right.window || left.used !== right.used || left.systemTokens !== right.systemTokens || left.toolsTokens !== right.toolsTokens || left.messageTokens !== right.messageTokens) return false;
|
|
6811
|
+
const a = left.breakdown, b = right.breakdown;
|
|
6812
|
+
if (a === b) return true;
|
|
6813
|
+
if (a === null || b === null) return false;
|
|
6814
|
+
return a.skillTokens === b.skillTokens && a.fileTokens === b.fileTokens && a.compaction === b.compaction && sameEntries(a.tools, b.tools) && sameEntries(a.files, b.files) && sameEntries(a.skills, b.skills);
|
|
6815
|
+
}
|
|
6816
|
+
function sameEntries(left, right) {
|
|
6817
|
+
if (left === right) return true;
|
|
6818
|
+
if (left === void 0 || right === void 0 || left.length !== right.length) return false;
|
|
6819
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
6820
|
+
const a = left[index], b = right[index];
|
|
6821
|
+
if (a[0] !== b[0] || a[1] !== b[1]) return false;
|
|
6822
|
+
}
|
|
6823
|
+
return true;
|
|
6824
|
+
}
|
|
6277
6825
|
function splitTools2(tools) {
|
|
6278
6826
|
const mcp = [];
|
|
6279
6827
|
const builtin = [];
|
|
@@ -6305,10 +6853,10 @@ function createContextPanelDriver(document2, projections, report) {
|
|
|
6305
6853
|
let tree;
|
|
6306
6854
|
let attributes;
|
|
6307
6855
|
let projectionsOff = null;
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6856
|
+
let cached2 = null;
|
|
6857
|
+
let publishedRaw = null;
|
|
6858
|
+
const rawReading = () => sessionId === null ? null : projections.read(sessionId);
|
|
6859
|
+
function buildReading(values) {
|
|
6312
6860
|
const rows = values.breakdown;
|
|
6313
6861
|
const tools = rows?.tools ?? [];
|
|
6314
6862
|
const split = splitTools2(tools);
|
|
@@ -6329,6 +6877,12 @@ function createContextPanelDriver(document2, projections, report) {
|
|
|
6329
6877
|
files: rows?.files ?? [],
|
|
6330
6878
|
skills: rows?.skills ?? []
|
|
6331
6879
|
};
|
|
6880
|
+
}
|
|
6881
|
+
const reading = (values) => {
|
|
6882
|
+
if (cached2 !== null && sameReading(cached2.raw, values)) return cached2.value;
|
|
6883
|
+
const value = buildReading(values);
|
|
6884
|
+
cached2 = { raw: values, value };
|
|
6885
|
+
return value;
|
|
6332
6886
|
};
|
|
6333
6887
|
const mark = () => {
|
|
6334
6888
|
const node = trigger;
|
|
@@ -6349,7 +6903,9 @@ function createContextPanelDriver(document2, projections, report) {
|
|
|
6349
6903
|
}
|
|
6350
6904
|
};
|
|
6351
6905
|
const refresh = () => {
|
|
6352
|
-
const
|
|
6906
|
+
const raw = open ? rawReading() : null;
|
|
6907
|
+
const found = raw === null ? null : reading(raw);
|
|
6908
|
+
publishedRaw = raw;
|
|
6353
6909
|
snapshot = {
|
|
6354
6910
|
open,
|
|
6355
6911
|
anchor: trigger,
|
|
@@ -6363,10 +6919,15 @@ function createContextPanelDriver(document2, projections, report) {
|
|
|
6363
6919
|
mark();
|
|
6364
6920
|
listener?.();
|
|
6365
6921
|
};
|
|
6922
|
+
const forget = () => {
|
|
6923
|
+
cached2 = null;
|
|
6924
|
+
publishedRaw = null;
|
|
6925
|
+
};
|
|
6366
6926
|
const close = () => {
|
|
6367
6927
|
if (!open) return;
|
|
6368
6928
|
open = false;
|
|
6369
6929
|
expanded = false;
|
|
6930
|
+
forget();
|
|
6370
6931
|
refresh();
|
|
6371
6932
|
};
|
|
6372
6933
|
const onClick = (event) => {
|
|
@@ -6387,6 +6948,7 @@ function createContextPanelDriver(document2, projections, report) {
|
|
|
6387
6948
|
const current = sessionOf(document2, found);
|
|
6388
6949
|
if (open && current !== sessionId) {
|
|
6389
6950
|
sessionId = current;
|
|
6951
|
+
forget();
|
|
6390
6952
|
close();
|
|
6391
6953
|
return;
|
|
6392
6954
|
}
|
|
@@ -6407,6 +6969,7 @@ function createContextPanelDriver(document2, projections, report) {
|
|
|
6407
6969
|
open = false;
|
|
6408
6970
|
expanded = false;
|
|
6409
6971
|
}
|
|
6972
|
+
forget();
|
|
6410
6973
|
sessionId = sessionOf(document2, found);
|
|
6411
6974
|
refresh();
|
|
6412
6975
|
};
|
|
@@ -6423,7 +6986,11 @@ function createContextPanelDriver(document2, projections, report) {
|
|
|
6423
6986
|
tree = new MutationObserver(schedule);
|
|
6424
6987
|
tree.observe(document2.body, { childList: true, subtree: true });
|
|
6425
6988
|
projectionsOff = projections.subscribe(() => {
|
|
6426
|
-
if (open)
|
|
6989
|
+
if (!open) return;
|
|
6990
|
+
const raw = rawReading();
|
|
6991
|
+
if (raw === publishedRaw) return;
|
|
6992
|
+
if (raw !== null && publishedRaw !== null && sameReading(publishedRaw, raw)) return;
|
|
6993
|
+
refresh();
|
|
6427
6994
|
});
|
|
6428
6995
|
sync();
|
|
6429
6996
|
} catch (error) {
|
|
@@ -6464,6 +7031,7 @@ function createContextPanelDriver(document2, projections, report) {
|
|
|
6464
7031
|
open = false;
|
|
6465
7032
|
expanded = false;
|
|
6466
7033
|
sessionId = null;
|
|
7034
|
+
forget();
|
|
6467
7035
|
}
|
|
6468
7036
|
};
|
|
6469
7037
|
}
|
|
@@ -6577,7 +7145,7 @@ function ContextPanel({ port }) {
|
|
|
6577
7145
|
window.removeEventListener("resize", place);
|
|
6578
7146
|
if (surface.matches(":popover-open")) surface.hidePopover();
|
|
6579
7147
|
};
|
|
6580
|
-
}, [open, anchor
|
|
7148
|
+
}, [open, anchor]);
|
|
6581
7149
|
(0, import_react6.useLayoutEffect)(() => {
|
|
6582
7150
|
const surface = bar.current;
|
|
6583
7151
|
if (!open || surface === null) return;
|
|
@@ -6591,7 +7159,7 @@ function ContextPanel({ port }) {
|
|
|
6591
7159
|
return () => {
|
|
6592
7160
|
resize.disconnect();
|
|
6593
7161
|
};
|
|
6594
|
-
}, [open
|
|
7162
|
+
}, [open]);
|
|
6595
7163
|
(0, import_react6.useEffect)(() => {
|
|
6596
7164
|
if (!open) return;
|
|
6597
7165
|
const outside = (target) => panel.current?.contains(target) !== true && anchor?.contains(target) !== true;
|
|
@@ -6737,7 +7305,7 @@ function ContextPanel({ port }) {
|
|
|
6737
7305
|
}
|
|
6738
7306
|
|
|
6739
7307
|
// src/client/features/context-panel/context-panel.css
|
|
6740
|
-
var context_panel_default = "/*\n * The context ring's breakdown panel (DESIGN.md \xA76.8).\n *\n * This is the plugin's own popup, drawn where DSH's 264px three-row panel used\n * to be. The geometry is the supplied reference's, measured at @2x and recorded\n * in `docs/plan-context-breakdown-panel.md` \xA72: a 360px card with a 12px corner\n * and a hairline outline, 8px top and 12px inline padding, a 13px/20px header\n * line, a 4px segmented bar 5px under it, 19px value rows, 17px detail rows,\n * 10px swatches, and a 40px percentage column whose right edge is the content\n * edge \u2014 the token column's own right edge then lands 48px in, which is the\n * 618-of-715 device-pixel line the reference draws.\n *\n * Three things are stated rather than inherited:\n *\n * - **The bar is a flex row of weights, not of percentages.** Every segment's\n * `flex-grow` is its token count and the track's is the room left over, so a\n * segment's share of the bar *is* its share of the window, and the reference's\n * own gaps \u2014 1px between used segments, 4px before the reserved grey \u2014 come\n * out of that share rather than being added to it. A category under 2px wide\n * is dropped by the card before it gets here, exactly as the reference drops\n * the categories that round to nothing.\n * - **The header line is the whole affordance.** There is no button at the\n * bottom; the row is a real `button` carrying `aria-expanded`, so the panel's\n * two states are one control's two states and the chevron is its mark.\n * - **The groups scroll rather than the panel.** Ten detail rows is the\n * reference's own visible run; a session with 85 tools keeps that height and\n * scrolls inside the group, so the card never grows past the viewport.\n */\n\n.ccd-context-panel {\n position: fixed;\n inset: auto;\n margin: 0;\n box-sizing: border-box;\n width: var(--ccd-context-panel-width);\n max-width: calc(100vw - 24px);\n padding: var(--ccd-context-panel-padding-block) var(--ccd-context-panel-padding-inline)\n var(--ccd-context-panel-padding-inline);\n /* Popovers default to overflow:auto, which would put a second scrollbar on\n the card; the groups own their own overflow instead. */\n overflow: visible;\n color: var(--ccd-view-menu-text);\n font: 13px/20px var(--ccd-font-ui);\n font-variant-numeric: tabular-nums;\n /* The seat it is registered in is click-through; a top-layer surface is hit\n tested on its own, and this states it rather than relying on that. */\n pointer-events: auto;\n}\n\n/* The UA hides a closed popover with `display: none`; the card's own display\n has to be scoped to the open state or it would keep that rule from applying. */\n.ccd-context-panel:popover-open {\n display: block;\n}\n\n.ccd-context-panel::backdrop {\n background: transparent;\n pointer-events: none;\n}\n\n/* The reference's own chrome: a hairline outline in the family's own border ink\n and a 12px corner, which is the card fill one step wider than the menu cards'\n 10px. */\nhtml[data-dsh-ccd-style=\"true\"] .ccd-context-panel[data-menu-material] {\n border: 1px solid var(--ccd-view-menu-border);\n border-radius: var(--ccd-context-panel-radius);\n box-shadow: var(--ccd-view-menu-shadow);\n}\n\n/* The header line: the title takes the flexible column, the reading sits against\n it, and the disclosure mark ends the row. */\n.ccd-context-head {\n box-sizing: border-box;\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto auto;\n align-items: center;\n column-gap: var(--ccd-context-column-gap);\n width: 100%;\n min-height: 20px;\n padding: 0;\n border: 0;\n background: none;\n color: var(--ccd-view-menu-muted);\n font: inherit;\n text-align: left;\n cursor: default;\n}\n\n.ccd-context-head:focus-visible {\n outline: none;\n}\n\n.ccd-context-title {\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n/* The reading is the one number on the card, and the reference paints it in the\n same grey as the title beside it: the row reads as one line, not as a label\n and a value. */\n.ccd-context-figures {\n color: var(--ccd-view-menu-muted);\n white-space: nowrap;\n}\n\n.ccd-context-chevron {\n width: 12px;\n height: 12px;\n color: var(--ccd-view-menu-muted);\n transition: transform 120ms ease;\n}\n\n.ccd-context-chevron[data-ccd-context-chevron='open'] {\n transform: rotate(90deg);\n}\n\n/* The bar. Its own background is the free-space track, so the track element the\n card renders carries no fill of its own \u2014 it is there for the flex weights to\n add up to the window, and the reference's own free space is simply what the\n segments leave behind. */\n.ccd-context-bar {\n display: flex;\n gap: var(--ccd-context-bar-gap);\n height: var(--ccd-context-bar-height);\n /* `margin-block` rather than the `margin` shorthand: the token carries the 5px\n above the bar and the 4px below it, and the inline axis takes no margin. */\n margin-block: var(--ccd-context-bar-margin-block);\n margin-inline: 0;\n border-radius: var(--ccd-context-bar-radius);\n background: var(--ccd-context-free);\n overflow: hidden;\n}\n\n.ccd-context-seg,\n.ccd-context-track {\n flex: 0 0 0;\n height: 100%;\n border-radius: 1px;\n}\n\n.ccd-context-track {\n border-radius: 0;\n}\n\n/* The reserved grey steps away from the used group by 4px where every used\n segment is 1px apart \u2014 the reference draws the reservation as its own block\n rather than another category. The row's own gap supplies 1px of it. */\n.ccd-context-seg-autocompact {\n margin-left: calc(var(--ccd-context-bar-reserve-gap) - var(--ccd-context-bar-gap));\n}\n\n.ccd-context-seg-mcp { background: var(--ccd-context-mcp); }\n.ccd-context-seg-tools { background: var(--ccd-context-tools); }\n.ccd-context-seg-system { background: var(--ccd-context-system); }\n.ccd-context-seg-skills { background: var(--ccd-context-skills); }\n.ccd-context-seg-memory { background: var(--ccd-context-memory); }\n.ccd-context-seg-messages { background: var(--ccd-context-messages); }\n.ccd-context-seg-autocompact { background: var(--ccd-context-buffer); }\n\n/* The reference's value rows start 4px under the bar, not 8: the panel's own\n gap token belongs to the header, and this is a tighter step. */\n.ccd-context-rows {\n margin-top: 4px;\n}\n\n/* One row of the panel: swatch and name, tokens, percentage. The three columns\n are the same in every row, the groups included, which is what keeps the\n numbers in one column down the whole card. */\n.ccd-context-row {\n box-sizing: border-box;\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto var(--ccd-context-percent-width);\n align-items: center;\n column-gap: var(--ccd-context-column-gap);\n /* The card's own 20px leading would make every row 20px tall; the reference's\n value rows are on a 19px pitch, so the row states its own. */\n min-height: var(--ccd-context-row-height);\n line-height: var(--ccd-context-row-height);\n}\n\n/* The swatch-to-name step the reference draws: 4px on a value row, and 3px\n under a group's own chevron, which is what puts every name in the card on one\n x \u2014 the swatch is 10px, the chevron 12px, and the two gaps differ by exactly\n the difference in their widths. */\n.ccd-context-label {\n display: flex;\n align-items: center;\n gap: 4px;\n min-width: 0;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.ccd-context-tokens,\n.ccd-context-percent {\n color: var(--ccd-view-menu-text);\n text-align: right;\n white-space: nowrap;\n}\n\n.ccd-context-percent {\n color: var(--ccd-view-menu-text);\n}\n\n.ccd-context-swatch {\n flex: none;\n width: var(--ccd-context-swatch-size);\n height: var(--ccd-context-swatch-size);\n border-radius: var(--ccd-context-swatch-radius);\n}\n\n.ccd-context-swatch-mcp { background: var(--ccd-context-mcp); }\n.ccd-context-swatch-tools { background: var(--ccd-context-tools); }\n.ccd-context-swatch-system { background: var(--ccd-context-system); }\n.ccd-context-swatch-skills { background: var(--ccd-context-skills); }\n.ccd-context-swatch-memory { background: var(--ccd-context-memory); }\n.ccd-context-swatch-messages { background: var(--ccd-context-messages); }\n.ccd-context-swatch-autocompact { background: var(--ccd-context-buffer); }\n.ccd-context-swatch-free { background: var(--ccd-context-free); }\n\n.ccd-context-group {\n margin-top: 4px;\n}\n\n.ccd-context-group-head {\n box-sizing: border-box;\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto var(--ccd-context-percent-width);\n align-items: center;\n column-gap: var(--ccd-context-column-gap);\n width: 100%;\n min-height: var(--ccd-context-row-height);\n padding: 0;\n border: 0;\n background: none;\n color: var(--ccd-view-menu-text);\n /* `font` is a shorthand and resets `line-height` with the rest of the font,\n so the row's own leading has to be stated after it. */\n font: inherit;\n font-size: 13px;\n line-height: var(--ccd-context-row-height);\n text-align: left;\n cursor: default;\n}\n\n.ccd-context-group-head:focus-visible {\n outline: none;\n}\n\n.ccd-context-group-head .ccd-context-label {\n gap: 3px;\n}\n\n/* The reference pulls the group's chevron 1px past the content edge, which is\n what puts its own name \u2014 and every detail row under it \u2014 on the same x as the\n value rows' labels above. */\n.ccd-context-group-head .ccd-context-chevron {\n margin-left: -1px;\n}\n\n/* The detail rows are one step tighter than the value rows above them \u2014 17px\n against 19px, the reference's own pitch \u2014 and their names sit one indent in,\n under the group's own label rather than under its chevron. */\n.ccd-context-detail {\n max-height: var(--ccd-context-detail-max);\n overflow-y: auto;\n overscroll-behavior: contain;\n}\n\n.ccd-context-detail-row {\n min-height: var(--ccd-context-detail-height);\n line-height: var(--ccd-context-detail-height);\n}\n\n.ccd-context-detail-row .ccd-context-label {\n /* Indented by the swatch column and its gap, so a detail row's name sits\n under the group's own name rather than under its chevron. */\n padding-left: calc(var(--ccd-context-swatch-size) + 4px);\n color: var(--ccd-view-menu-muted);\n}\n\n.ccd-context-detail-row .ccd-context-tokens {\n color: var(--ccd-view-menu-muted);\n}\n\n/* The host's own panel, which the driver's marker keeps out of the paint.\n *\n * `compat/context-panel.ts` intercepts the ring's click, so DSH never sets its\n * own open state and this rule is never the mechanism \u2014 it is the belt to that\n * braces, and it exists so that a click that somehow reaches the host's handler\n * still leaves the reader looking at one panel. `visibility` rather than\n * `display` is what keeps the box the host's anchored-position hook measures.\n *\n * The rule lives in this sheet rather than in `theme/composer.css` because it is\n * the driver's own mechanism, not a fact about the ring: this sheet only ever\n * applies while this plugin's panel is the one on screen. */\nhtml[data-dsh-ccd-style=\"true\"][data-ccd-context-open] .y0jqnG_panel {\n visibility: hidden;\n pointer-events: none;\n}\n";
|
|
7308
|
+
var context_panel_default = "/*\n * The context ring's breakdown panel (DESIGN.md \xA76.8).\n *\n * This is the plugin's own popup, drawn where DSH's 264px three-row panel used\n * to be. The geometry is the supplied reference's, measured at @2x and recorded\n * in DESIGN.md \xA76.8: a 360px card with a 12px corner\n * and a hairline outline, 8px top and 12px inline padding, a 13px/20px header\n * line, a 4px segmented bar 5px under it, 19px value rows, 17px detail rows,\n * 10px swatches, and a 40px percentage column whose right edge is the content\n * edge \u2014 the token column's own right edge then lands 48px in, which is the\n * 618-of-715 device-pixel line the reference draws.\n *\n * Three things are stated rather than inherited:\n *\n * - **The bar is a flex row of weights, not of percentages.** Every segment's\n * `flex-grow` is its token count and the track's is the room left over, so a\n * segment's share of the bar *is* its share of the window, and the reference's\n * own gaps \u2014 1px between used segments, 4px before the reserved grey \u2014 come\n * out of that share rather than being added to it. A category under 2px wide\n * is dropped by the card before it gets here, exactly as the reference drops\n * the categories that round to nothing.\n * - **The header line is the whole affordance.** There is no button at the\n * bottom; the row is a real `button` carrying `aria-expanded`, so the panel's\n * two states are one control's two states and the chevron is its mark.\n * - **The groups scroll rather than the panel.** Ten detail rows is the\n * reference's own visible run; a session with 85 tools keeps that height and\n * scrolls inside the group, so the card never grows past the viewport.\n */\n\n.ccd-context-panel {\n position: fixed;\n inset: auto;\n margin: 0;\n box-sizing: border-box;\n width: var(--ccd-context-panel-width);\n max-width: calc(100vw - 24px);\n padding: var(--ccd-context-panel-padding-block) var(--ccd-context-panel-padding-inline)\n var(--ccd-context-panel-padding-inline);\n /* Popovers default to overflow:auto, which would put a second scrollbar on\n the card; the groups own their own overflow instead. */\n overflow: visible;\n color: var(--ccd-view-menu-text);\n font: 13px/20px var(--ccd-font-ui);\n font-variant-numeric: tabular-nums;\n /* The seat it is registered in is click-through; a top-layer surface is hit\n tested on its own, and this states it rather than relying on that. */\n pointer-events: auto;\n}\n\n/* The UA hides a closed popover with `display: none`; the card's own display\n has to be scoped to the open state or it would keep that rule from applying. */\n.ccd-context-panel:popover-open {\n display: block;\n}\n\n.ccd-context-panel::backdrop {\n background: transparent;\n pointer-events: none;\n}\n\n/* The reference's own chrome: a hairline outline in the family's own border ink\n and a 12px corner, which is the card fill one step wider than the menu cards'\n 10px. */\nhtml[data-dsh-ccd-style=\"true\"] .ccd-context-panel[data-menu-material] {\n border: 1px solid var(--ccd-view-menu-border);\n border-radius: var(--ccd-context-panel-radius);\n box-shadow: var(--ccd-view-menu-shadow);\n}\n\n/* The header line: the title takes the flexible column, the reading sits against\n it, and the disclosure mark ends the row. */\n.ccd-context-head {\n box-sizing: border-box;\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto auto;\n align-items: center;\n column-gap: var(--ccd-context-column-gap);\n width: 100%;\n min-height: 20px;\n padding: 0;\n border: 0;\n background: none;\n color: var(--ccd-view-menu-muted);\n font: inherit;\n text-align: left;\n cursor: default;\n}\n\n.ccd-context-head:focus-visible {\n outline: none;\n}\n\n.ccd-context-title {\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n/* The reading is the one number on the card, and the reference paints it in the\n same grey as the title beside it: the row reads as one line, not as a label\n and a value. */\n.ccd-context-figures {\n color: var(--ccd-view-menu-muted);\n white-space: nowrap;\n}\n\n.ccd-context-chevron {\n width: 12px;\n height: 12px;\n color: var(--ccd-view-menu-muted);\n transition: transform 120ms ease;\n}\n\n.ccd-context-chevron[data-ccd-context-chevron='open'] {\n transform: rotate(90deg);\n}\n\n/* The bar. Its own background is the free-space track, so the track element the\n card renders carries no fill of its own \u2014 it is there for the flex weights to\n add up to the window, and the reference's own free space is simply what the\n segments leave behind. */\n.ccd-context-bar {\n display: flex;\n gap: var(--ccd-context-bar-gap);\n height: var(--ccd-context-bar-height);\n /* `margin-block` rather than the `margin` shorthand: the token carries the 5px\n above the bar and the 4px below it, and the inline axis takes no margin. */\n margin-block: var(--ccd-context-bar-margin-block);\n margin-inline: 0;\n border-radius: var(--ccd-context-bar-radius);\n background: var(--ccd-context-free);\n overflow: hidden;\n}\n\n.ccd-context-seg,\n.ccd-context-track {\n flex: 0 0 0;\n height: 100%;\n border-radius: 1px;\n}\n\n.ccd-context-track {\n border-radius: 0;\n}\n\n/* The reserved grey steps away from the used group by 4px where every used\n segment is 1px apart \u2014 the reference draws the reservation as its own block\n rather than another category. The row's own gap supplies 1px of it. */\n.ccd-context-seg-autocompact {\n margin-left: calc(var(--ccd-context-bar-reserve-gap) - var(--ccd-context-bar-gap));\n}\n\n.ccd-context-seg-mcp { background: var(--ccd-context-mcp); }\n.ccd-context-seg-tools { background: var(--ccd-context-tools); }\n.ccd-context-seg-system { background: var(--ccd-context-system); }\n.ccd-context-seg-skills { background: var(--ccd-context-skills); }\n.ccd-context-seg-memory { background: var(--ccd-context-memory); }\n.ccd-context-seg-messages { background: var(--ccd-context-messages); }\n.ccd-context-seg-autocompact { background: var(--ccd-context-buffer); }\n\n/* The reference's value rows start 4px under the bar, not 8: the panel's own\n gap token belongs to the header, and this is a tighter step. */\n.ccd-context-rows {\n margin-top: 4px;\n}\n\n/* One row of the panel: swatch and name, tokens, percentage. The three columns\n are the same in every row, the groups included, which is what keeps the\n numbers in one column down the whole card. */\n.ccd-context-row {\n box-sizing: border-box;\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto var(--ccd-context-percent-width);\n align-items: center;\n column-gap: var(--ccd-context-column-gap);\n /* The card's own 20px leading would make every row 20px tall; the reference's\n value rows are on a 19px pitch, so the row states its own. */\n min-height: var(--ccd-context-row-height);\n line-height: var(--ccd-context-row-height);\n}\n\n/* The swatch-to-name step the reference draws: 4px on a value row, and 3px\n under a group's own chevron, which is what puts every name in the card on one\n x \u2014 the swatch is 10px, the chevron 12px, and the two gaps differ by exactly\n the difference in their widths. */\n.ccd-context-label {\n display: flex;\n align-items: center;\n gap: 4px;\n min-width: 0;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.ccd-context-tokens,\n.ccd-context-percent {\n color: var(--ccd-view-menu-text);\n text-align: right;\n white-space: nowrap;\n}\n\n.ccd-context-percent {\n color: var(--ccd-view-menu-text);\n}\n\n.ccd-context-swatch {\n flex: none;\n width: var(--ccd-context-swatch-size);\n height: var(--ccd-context-swatch-size);\n border-radius: var(--ccd-context-swatch-radius);\n}\n\n.ccd-context-swatch-mcp { background: var(--ccd-context-mcp); }\n.ccd-context-swatch-tools { background: var(--ccd-context-tools); }\n.ccd-context-swatch-system { background: var(--ccd-context-system); }\n.ccd-context-swatch-skills { background: var(--ccd-context-skills); }\n.ccd-context-swatch-memory { background: var(--ccd-context-memory); }\n.ccd-context-swatch-messages { background: var(--ccd-context-messages); }\n.ccd-context-swatch-autocompact { background: var(--ccd-context-buffer); }\n.ccd-context-swatch-free { background: var(--ccd-context-free); }\n\n.ccd-context-group {\n margin-top: 4px;\n}\n\n.ccd-context-group-head {\n box-sizing: border-box;\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto var(--ccd-context-percent-width);\n align-items: center;\n column-gap: var(--ccd-context-column-gap);\n width: 100%;\n min-height: var(--ccd-context-row-height);\n padding: 0;\n border: 0;\n background: none;\n color: var(--ccd-view-menu-text);\n /* `font` is a shorthand and resets `line-height` with the rest of the font,\n so the row's own leading has to be stated after it. */\n font: inherit;\n font-size: 13px;\n line-height: var(--ccd-context-row-height);\n text-align: left;\n cursor: default;\n}\n\n.ccd-context-group-head:focus-visible {\n outline: none;\n}\n\n.ccd-context-group-head .ccd-context-label {\n gap: 3px;\n}\n\n/* The reference pulls the group's chevron 1px past the content edge, which is\n what puts its own name \u2014 and every detail row under it \u2014 on the same x as the\n value rows' labels above. */\n.ccd-context-group-head .ccd-context-chevron {\n margin-left: -1px;\n}\n\n/* The detail rows are one step tighter than the value rows above them \u2014 17px\n against 19px, the reference's own pitch \u2014 and their names sit one indent in,\n under the group's own label rather than under its chevron. */\n.ccd-context-detail {\n max-height: var(--ccd-context-detail-max);\n overflow-y: auto;\n overscroll-behavior: contain;\n}\n\n.ccd-context-detail-row {\n min-height: var(--ccd-context-detail-height);\n line-height: var(--ccd-context-detail-height);\n}\n\n.ccd-context-detail-row .ccd-context-label {\n /* Indented by the swatch column and its gap, so a detail row's name sits\n under the group's own name rather than under its chevron. */\n padding-left: calc(var(--ccd-context-swatch-size) + 4px);\n color: var(--ccd-view-menu-muted);\n}\n\n.ccd-context-detail-row .ccd-context-tokens {\n color: var(--ccd-view-menu-muted);\n}\n\n/* The host's own panel, which the driver's marker keeps out of the paint.\n *\n * `compat/context-panel.ts` intercepts the ring's click, so DSH never sets its\n * own open state and this rule is never the mechanism \u2014 it is the belt to that\n * braces, and it exists so that a click that somehow reaches the host's handler\n * still leaves the reader looking at one panel. `visibility` rather than\n * `display` is what keeps the box the host's anchored-position hook measures.\n *\n * The rule lives in this sheet rather than in `theme/composer.css` because it is\n * the driver's own mechanism, not a fact about the ring: this sheet only ever\n * applies while this plugin's panel is the one on screen. */\nhtml[data-dsh-ccd-style=\"true\"][data-ccd-context-open] .y0jqnG_panel {\n visibility: hidden;\n pointer-events: none;\n}\n";
|
|
6741
7309
|
|
|
6742
7310
|
// src/client/features/context-panel/mount.ts
|
|
6743
7311
|
var CONTEXT_PANEL_ORDER = 40;
|
|
@@ -6885,13 +7453,13 @@ function HeaderActionButton({ label, kind, open }) {
|
|
|
6885
7453
|
|
|
6886
7454
|
// src/client/features/conversation/header-actions/entries.ts
|
|
6887
7455
|
var FILES_KIND = "files";
|
|
6888
|
-
var
|
|
6889
|
-
var
|
|
6890
|
-
var
|
|
7456
|
+
var TERMINAL_ORDER = -3;
|
|
7457
|
+
var BROWSER_ORDER = -2;
|
|
7458
|
+
var FOLDER_ORDER = -1;
|
|
6891
7459
|
var VIEWS = [
|
|
6892
|
-
{ id: "ccd-folder", kind: FILES_KIND, order: FOLDER_ORDER },
|
|
6893
7460
|
{ id: "ccd-terminal", kind: "terminal", order: TERMINAL_ORDER },
|
|
6894
|
-
{ id: "ccd-browser", kind: "browser", order: BROWSER_ORDER }
|
|
7461
|
+
{ id: "ccd-browser", kind: "browser", order: BROWSER_ORDER },
|
|
7462
|
+
{ id: "ccd-folder", kind: FILES_KIND, order: FOLDER_ORDER }
|
|
6895
7463
|
];
|
|
6896
7464
|
var CORNER_ENTRY = "header-corner";
|
|
6897
7465
|
function utilitiesEntry(view, labels, open) {
|
|
@@ -6943,47 +7511,333 @@ function openView(sidebar, kind, sessionId) {
|
|
|
6943
7511
|
}
|
|
6944
7512
|
|
|
6945
7513
|
// src/client/features/conversation/header-actions/header-actions.css
|
|
6946
|
-
var header_actions_default =
|
|
7514
|
+
var header_actions_default = '/*\n * The conversation header\'s trailing cluster: the three right-panel views this\n * plugin adds \u2014 terminal, browser, then the project folder \u2014 plus the\n * reference\'s own adaptations to the host controls of the same row (issue 16).\n * The corner seat past the cluster is retired, so these three are the whole of\n * the row\'s own additions.\n *\n * The views\' 28px box, hover surface, spacing and compact width all come from\n * the cluster rules in `features/conversation/conversation.css`, so these\n * buttons sit in exactly the same rhythm as the native controls beside them.\n * Their artwork is declared here: masks over DSH\'s own 16px glyph geometry,\n * taken from the packages that draw them (see theme/tokens.css).\n *\n * The host classes named below are the pinned ones of `compat/host-dom.ts`:\n * `ST7X_W_` for the header and its two trailing seats, `iq4beG_` for the\n * open-in-app control (`openTargetAnchor`\uFF0F`openTargetSplit`) and `Da3aKq_` for\n * the native session menu (`sessionMoreButton`). `compat/dom.ts` rewrites each\n * prefix for the build actually on screen, through `compat/host-builds.ts`.\n */\n.ccd-header-action {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n border: 0;\n background: transparent;\n color: var(--ccd-header-icon-color);\n cursor: pointer;\n}\n\n/* The hover/focus signal is the host\'s own fill; the ink keeps its full\n strength in every state, exactly as the native ghost buttons beside this one\n behave. */\n.ccd-header-action:hover:not(:disabled),\n.ccd-header-action:focus-visible {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.ccd-header-action:disabled {\n cursor: default;\n opacity: 0.4;\n}\n\n.ccd-header-action::before {\n content: "";\n width: var(--ccd-header-icon-size);\n height: var(--ccd-header-icon-size);\n background-color: currentColor;\n mask: var(--ccd-icon-terminal) center / var(--ccd-header-icon-size) var(--ccd-header-icon-size) no-repeat;\n}\n\n.ccd-header-action[data-ccd-header-view="browser"]::before {\n mask-image: var(--ccd-icon-browser);\n}\n\n/*\n * The project folder follows the browser and opens the right panel\'s file list\n * for the Session\'s own directory. It shares the workspace chip\'s closed-folder\n * glyph (`theme/tokens.css`).\n */\n.ccd-header-action[data-ccd-header-view="files"]::before {\n mask-image: var(--ccd-icon-folder);\n}\n\n/*\n * The header\'s own open-in-app control is retired here (16).\n *\n * `ui-open-in-app` registers `open-in-app` into\n * `conversation.session.header.utilities` at order -10, so its split box is the\n * first control of that group; in the DOM it is the `Menu` primitive\'s anchor\n * wrapper span (`iq4beG_menuAnchor`) wrapped around the `iq4beG_split` box with\n * its `iq4beG_main` and `iq4beG_chevron` halves. This plugin used to repaint\n * that box as one generic "open" glyph; the feature moves elsewhere, so the\n * wrapper goes instead.\n *\n * Scoped to the header\'s utilities seat: the same package\'s entries in the\n * right panel (`sidebar.right.tab.files.actions`, the document viewer\'s own\n * `sidebar.right.tab.document.actions` and its unpreviewable seat) keep their\n * control untouched. The wrapper is the flex item \u2014 the slot seat around it is\n * `display: contents` \u2014 so taking it out of the box tree takes the whole split\n * box with it, leaves the group\'s own `gap` measured between the controls that\n * remain (no empty cell to compensate, no padding to trim) and keeps both halves\n * out of the tab order. The width it used to hold is absorbed by the flexible\n * gap ahead of the group, so the row still ends on the same trailing inset.\n */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities .iq4beG_menuAnchor {\n display: none;\n}\n\n/*\n * One ink for the trailing cluster (16).\n *\n * Every control in the utilities seat is an icon-only ghost button:\n * the native session menu (`Da3aKq_moreButton`, the "More actions" menu), the\n * three views this plugin adds and the scheduled-tasks entry while a Session\n * has records. The host paints each with its own secondary label grey; the\n * reference draws them at full strength,\n * so the cluster takes one token \u2014 pure black on the light canvas, its white\n * mirror on the dark one (theme/tokens.css) \u2014 and every state the host owns\n * stays its own.\n *\n * The seat is named in the selector rather than only the group:\n * `conversation.css` paints this group\'s buttons with the secondary ink as well,\n * and that sheet mounts after this one, so an equal-specificity rule here would\n * lose the cascade. Naming the seat lifts this above it without `!important`,\n * and keeps the reach at controls: the header\'s text, its View switcher and the\n * leading Agent-preset\uFF0Fteam chrome are not in this seat.\n *\n * Naming the button and its `svg` covers both a glyph that inherits\n * `currentColor` (the session menu\'s ellipsis) and one a later build might paint\n * with a fill of its own. Hover, active and keyboard focus keep the host\'s\n * translucent fills, disabled keeps the button\'s own dimmed opacity, so this ink\n * never lands on a fill of its own lightness.\n */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities > [data-slot] button,\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities > [data-slot] button svg {\n color: var(--ccd-header-icon-color);\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities > [data-slot] .ccd-header-action[aria-pressed="true"] {\n color: var(--ccd-files-chip-ink);\n background: var(--ccd-right-panel-glyph-bg);\n}\n\n/*\n * The session menu\'s mark, stacked (16).\n *\n * The host draws `IconEllipsisOutlineRegular` (`ui-primitives`, MIT) as three\n * 2px dots on one horizontal line \u2014 circles at (3, 8), (8, 8) and (13, 8) of\n * its 16px box. The reference stacks them, so the host\'s own glyph steps aside\n * and the same geometry, transposed to (8, 3), (8, 8) and (8, 13), is painted as\n * a mask over the button\'s `currentColor` (theme/tokens.css, `--ccd-icon-more`).\n *\n * Menu wraps the button in an inline-flex span: the cluster sizes that span,\n * while Button\'s native small size remains 28px. Fill the wrapper so the real\n * button also becomes 24px in a narrow column, including its hover surface.\n * The menu and click handler stay the host\'s own. Explicit flex centring keeps\n * the replacement mark off the text baseline; disabled opacity remains native.\n */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities .Da3aKq_moreButton {\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n min-width: 0;\n min-height: 0;\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n line-height: 1;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities .Da3aKq_moreButton svg {\n display: none;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities .Da3aKq_moreButton::before {\n content: "";\n display: block;\n flex: none;\n width: var(--ccd-header-icon-size);\n height: var(--ccd-header-icon-size);\n background-color: currentColor;\n mask: var(--ccd-icon-more) center / var(--ccd-header-icon-size) var(--ccd-header-icon-size) no-repeat;\n}\n';
|
|
6947
7515
|
|
|
6948
7516
|
// src/client/features/conversation/header-actions/header-menus.css
|
|
6949
|
-
var header_menus_default =
|
|
7517
|
+
var header_menus_default = `/* Reference crops are @2x: 136/192px cards, 24px rows and 10px corners. */
|
|
7518
|
+
html[data-dsh-ccd-style="true"] .ST7X_W_header:has([data-ccd-session-title]) {
|
|
7519
|
+
/* Equal spacing between the title controls, project chip and team entry. */
|
|
7520
|
+
--ccd-header-group-gap: 10px;
|
|
7521
|
+
padding-top: 6px;
|
|
7522
|
+
padding-bottom: 6px;
|
|
7523
|
+
padding-left: 18px;
|
|
7524
|
+
}
|
|
6950
7525
|
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
7526
|
+
html[data-dsh-ccd-style="true"] .ST7X_W_header [data-ccd-session-title] {
|
|
7527
|
+
flex: 0 1 auto;
|
|
7528
|
+
/* Use the name's natural width. Only actual pressure from the remaining
|
|
7529
|
+
header controls should truncate it, never a fixed or percentage cap. */
|
|
7530
|
+
max-width: none;
|
|
7531
|
+
min-width: 0;
|
|
7532
|
+
gap: 6px;
|
|
7533
|
+
overflow: hidden;
|
|
7534
|
+
-webkit-app-region: no-drag;
|
|
6954
7535
|
}
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
|
|
6982
|
-
|
|
6983
|
-
|
|
6984
|
-
|
|
6985
|
-
|
|
6986
|
-
|
|
7536
|
+
|
|
7537
|
+
html[data-dsh-ccd-style="true"] [data-ccd-session-title] .ST7X_W_crumb {
|
|
7538
|
+
max-width: none;
|
|
7539
|
+
min-width: 0;
|
|
7540
|
+
padding: 0;
|
|
7541
|
+
color: var(--ccd-header-icon-color);
|
|
7542
|
+
font-size: 13px;
|
|
7543
|
+
font-weight: 500;
|
|
7544
|
+
line-height: 24px;
|
|
7545
|
+
}
|
|
7546
|
+
|
|
7547
|
+
/* Keep the icon, title, chevron and chip on the traffic lights' y=25 axis. */
|
|
7548
|
+
html[data-dsh-ccd-style="true"][data-platform="darwin"] .ST7X_W_header:has([data-ccd-session-title]) {
|
|
7549
|
+
padding-top: 10px;
|
|
7550
|
+
padding-bottom: 0;
|
|
7551
|
+
}
|
|
7552
|
+
|
|
7553
|
+
html[data-dsh-ccd-style="true"] [data-ccd-session-title] .ST7X_W_crumbSeg {
|
|
7554
|
+
flex: 0 1 auto;
|
|
7555
|
+
min-width: 0;
|
|
7556
|
+
}
|
|
7557
|
+
|
|
7558
|
+
html[data-dsh-ccd-style="true"] [data-ccd-session-title] .ST7X_W_crumb[role="button"] {
|
|
7559
|
+
cursor: text;
|
|
7560
|
+
padding: 0 4px;
|
|
7561
|
+
margin: 0 -4px;
|
|
7562
|
+
border-radius: 6px;
|
|
7563
|
+
}
|
|
7564
|
+
|
|
7565
|
+
html[data-dsh-ccd-style="true"] [data-ccd-session-title] .ST7X_W_crumb[role="button"]:hover {
|
|
7566
|
+
background: var(--ccd-header-trigger-hover);
|
|
7567
|
+
}
|
|
7568
|
+
|
|
7569
|
+
html[data-dsh-ccd-style="true"] [data-ccd-session-title] .ST7X_W_crumb[role="button"]:focus-visible {
|
|
7570
|
+
outline: 2px solid Highlight;
|
|
7571
|
+
outline-offset: 1px;
|
|
7572
|
+
}
|
|
7573
|
+
|
|
7574
|
+
html[data-dsh-ccd-style="true"] [data-ccd-session-editing] > :not([data-ccd-session-icon], [data-ccd-session-trigger], [data-ccd-session-rename]) {
|
|
7575
|
+
display: none;
|
|
7576
|
+
}
|
|
7577
|
+
|
|
7578
|
+
html[data-dsh-ccd-style="true"] [data-ccd-session-rename] {
|
|
7579
|
+
box-sizing: border-box;
|
|
7580
|
+
flex: 0 1 auto;
|
|
7581
|
+
min-width: 32px;
|
|
7582
|
+
height: 24px;
|
|
7583
|
+
padding: 0 2px;
|
|
7584
|
+
border: 0;
|
|
7585
|
+
border-radius: 4px;
|
|
7586
|
+
outline: none;
|
|
7587
|
+
background: transparent;
|
|
7588
|
+
color: var(--ccd-header-icon-color);
|
|
7589
|
+
font: 500 13px/24px var(--ccd-font-ui);
|
|
7590
|
+
-webkit-app-region: no-drag;
|
|
7591
|
+
}
|
|
7592
|
+
|
|
7593
|
+
html[data-dsh-ccd-style="true"] [data-ccd-session-title][data-ccd-session-editing] {
|
|
7594
|
+
overflow: visible;
|
|
7595
|
+
}
|
|
7596
|
+
|
|
7597
|
+
html[data-dsh-ccd-style="true"] [data-ccd-session-rename]:focus {
|
|
7598
|
+
outline: 2px solid Highlight;
|
|
7599
|
+
outline-offset: -2px;
|
|
7600
|
+
}
|
|
7601
|
+
|
|
7602
|
+
html[data-dsh-ccd-style="true"] [data-ccd-session-icon] {
|
|
7603
|
+
display: inline-flex;
|
|
7604
|
+
flex: none;
|
|
7605
|
+
align-items: center;
|
|
7606
|
+
width: 16px;
|
|
7607
|
+
height: 24px;
|
|
7608
|
+
color: var(--ccd-header-icon-color);
|
|
7609
|
+
}
|
|
7610
|
+
|
|
7611
|
+
html[data-dsh-ccd-style="true"] [data-ccd-session-icon] svg {
|
|
7612
|
+
width: 16px;
|
|
7613
|
+
height: 16px;
|
|
7614
|
+
flex: none;
|
|
7615
|
+
}
|
|
7616
|
+
|
|
7617
|
+
html[data-dsh-ccd-style="true"] [data-ccd-session-trigger] {
|
|
7618
|
+
display: inline-flex;
|
|
7619
|
+
flex: none;
|
|
7620
|
+
align-items: center;
|
|
7621
|
+
justify-content: center;
|
|
7622
|
+
width: 24px;
|
|
7623
|
+
height: 24px;
|
|
7624
|
+
padding: 0;
|
|
7625
|
+
border: 0;
|
|
7626
|
+
border-radius: 7px;
|
|
7627
|
+
background: transparent;
|
|
7628
|
+
color: var(--ccd-header-label-ink);
|
|
7629
|
+
cursor: pointer;
|
|
7630
|
+
}
|
|
7631
|
+
|
|
7632
|
+
html[data-dsh-ccd-style="true"] [data-ccd-session-trigger]:hover,
|
|
7633
|
+
html[data-dsh-ccd-style="true"] [data-ccd-session-trigger][aria-expanded="true"] {
|
|
7634
|
+
background: var(--ccd-header-trigger-hover);
|
|
7635
|
+
}
|
|
7636
|
+
|
|
7637
|
+
html[data-dsh-ccd-style="true"] [data-ccd-project-trigger] {
|
|
7638
|
+
order: 0;
|
|
7639
|
+
flex: 0 3 auto;
|
|
7640
|
+
min-width: 0;
|
|
7641
|
+
max-width: min(220px, 40%);
|
|
7642
|
+
height: 20px;
|
|
7643
|
+
margin-left: var(--ccd-header-group-gap);
|
|
7644
|
+
padding: 0 5px;
|
|
7645
|
+
border: 0;
|
|
7646
|
+
border-radius: 6px;
|
|
7647
|
+
background: var(--ccd-header-project-fill);
|
|
7648
|
+
color: var(--ccd-header-label-ink);
|
|
7649
|
+
font: inherit;
|
|
7650
|
+
font-size: 13px;
|
|
7651
|
+
font-weight: 400;
|
|
7652
|
+
line-height: 20px;
|
|
7653
|
+
white-space: nowrap;
|
|
7654
|
+
overflow: hidden;
|
|
7655
|
+
text-overflow: ellipsis;
|
|
7656
|
+
cursor: pointer;
|
|
7657
|
+
-webkit-app-region: no-drag;
|
|
7658
|
+
}
|
|
7659
|
+
|
|
7660
|
+
html[data-dsh-ccd-style="true"] [data-ccd-project-trigger][hidden] { display: none; }
|
|
7661
|
+
|
|
7662
|
+
html[data-dsh-ccd-style="true"] [data-ccd-project-trigger]:hover,
|
|
7663
|
+
html[data-dsh-ccd-style="true"] [data-ccd-project-trigger][aria-expanded="true"] {
|
|
7664
|
+
background: var(--ccd-header-project-active);
|
|
7665
|
+
}
|
|
7666
|
+
|
|
7667
|
+
html[data-dsh-ccd-style="true"] [data-ccd-header-menu] {
|
|
7668
|
+
position: fixed;
|
|
7669
|
+
inset: auto;
|
|
7670
|
+
box-sizing: border-box;
|
|
7671
|
+
width: max-content;
|
|
7672
|
+
min-width: 136px;
|
|
7673
|
+
max-width: calc(100vw - 16px);
|
|
7674
|
+
max-height: calc(100vh - 16px);
|
|
7675
|
+
overflow: auto;
|
|
7676
|
+
margin: 0;
|
|
7677
|
+
padding: 4px;
|
|
7678
|
+
border: 1px solid var(--ccd-header-menu-border);
|
|
7679
|
+
border-radius: 10px;
|
|
7680
|
+
background: var(--ccd-card);
|
|
7681
|
+
color: var(--ccd-view-menu-text);
|
|
7682
|
+
box-shadow: var(--ccd-view-menu-shadow);
|
|
7683
|
+
font-family: var(--ccd-font-ui);
|
|
7684
|
+
font-size: 13px;
|
|
7685
|
+
line-height: 20px;
|
|
7686
|
+
-webkit-app-region: no-drag;
|
|
7687
|
+
}
|
|
7688
|
+
|
|
7689
|
+
html[data-dsh-ccd-style="true"] [data-ccd-header-menu="session"] { width: 160px; }
|
|
7690
|
+
html[data-dsh-ccd-style="true"] [data-ccd-header-menu="project"] { width: 176px; }
|
|
7691
|
+
|
|
7692
|
+
html[data-dsh-ccd-style="true"] [data-ccd-header-menu] button {
|
|
7693
|
+
box-sizing: border-box;
|
|
7694
|
+
display: flex;
|
|
7695
|
+
align-items: center;
|
|
7696
|
+
gap: 6px;
|
|
7697
|
+
width: 100%;
|
|
7698
|
+
min-height: 24px;
|
|
7699
|
+
padding: 2px 8px;
|
|
7700
|
+
border: 0;
|
|
7701
|
+
border-radius: 5px;
|
|
7702
|
+
background: transparent;
|
|
7703
|
+
color: inherit;
|
|
7704
|
+
font: inherit;
|
|
7705
|
+
text-align: left;
|
|
7706
|
+
white-space: nowrap;
|
|
7707
|
+
cursor: pointer;
|
|
7708
|
+
}
|
|
7709
|
+
|
|
7710
|
+
html[data-dsh-ccd-style="true"] [data-ccd-header-menu] button > [data-ccd-header-row-label] { flex: 1; }
|
|
7711
|
+
html[data-dsh-ccd-style="true"] [data-ccd-header-menu] button:hover,
|
|
7712
|
+
html[data-dsh-ccd-style="true"] [data-ccd-header-menu] button:focus-visible,
|
|
7713
|
+
html[data-dsh-ccd-style="true"] [data-ccd-header-menu] button[aria-expanded="true"] {
|
|
7714
|
+
outline: none;
|
|
7715
|
+
background: var(--ccd-view-menu-hover);
|
|
7716
|
+
}
|
|
7717
|
+
|
|
7718
|
+
html[data-dsh-ccd-style="true"] [data-ccd-header-menu] [data-ccd-menu-mark] {
|
|
7719
|
+
flex: none;
|
|
7720
|
+
color: var(--ccd-text-muted);
|
|
7721
|
+
font-size: 12px;
|
|
7722
|
+
}
|
|
7723
|
+
|
|
7724
|
+
html[data-dsh-ccd-style="true"] [data-ccd-header-menu] [aria-haspopup] [data-ccd-menu-mark] {
|
|
7725
|
+
display: flex;
|
|
7726
|
+
width: 12px;
|
|
7727
|
+
height: 16px;
|
|
7728
|
+
align-items: center;
|
|
7729
|
+
justify-content: center;
|
|
7730
|
+
}
|
|
7731
|
+
|
|
7732
|
+
html[data-dsh-ccd-style="true"] [data-ccd-header-menu] [aria-haspopup] [data-ccd-menu-mark] svg {
|
|
7733
|
+
flex: none;
|
|
7734
|
+
width: 14px;
|
|
7735
|
+
height: 14px;
|
|
7736
|
+
transform: rotate(-90deg);
|
|
7737
|
+
}
|
|
7738
|
+
|
|
7739
|
+
/* The destructive row rests in the host's red ink. Only the pointer or the
|
|
7740
|
+
* keyboard walk fills it: the red fill with white ink, the shortcut included, the
|
|
7741
|
+
* same as the host's danger rows (\`menus.css\`). */
|
|
7742
|
+
html[data-dsh-ccd-style="true"] [data-ccd-header-menu] [data-ccd-menu-destructive] { color: var(--ccd-header-menu-danger); }
|
|
7743
|
+
|
|
7744
|
+
html[data-dsh-ccd-style="true"] [data-ccd-header-menu] [data-ccd-menu-destructive]:hover,
|
|
7745
|
+
html[data-dsh-ccd-style="true"] [data-ccd-header-menu] [data-ccd-menu-destructive]:focus-visible {
|
|
7746
|
+
background: var(--ccd-menu-danger);
|
|
7747
|
+
color: var(--ccd-menu-danger-ink);
|
|
7748
|
+
}
|
|
7749
|
+
|
|
7750
|
+
html[data-dsh-ccd-style="true"] [data-ccd-header-menu] [data-ccd-menu-destructive]:hover [data-ccd-menu-mark],
|
|
7751
|
+
html[data-dsh-ccd-style="true"] [data-ccd-header-menu] [data-ccd-menu-destructive]:focus-visible [data-ccd-menu-mark] { color: inherit; }
|
|
7752
|
+
html[data-dsh-ccd-style="true"] [data-ccd-header-menu] hr {
|
|
7753
|
+
height: 1px;
|
|
7754
|
+
margin: 4px 8px;
|
|
7755
|
+
border: 0;
|
|
7756
|
+
background: var(--ccd-header-menu-separator);
|
|
7757
|
+
}
|
|
7758
|
+
|
|
7759
|
+
html[data-dsh-ccd-style="true"] [data-ccd-header-dialog] {
|
|
7760
|
+
box-sizing: border-box;
|
|
7761
|
+
width: min(360px, calc(100vw - 32px));
|
|
7762
|
+
padding: 16px;
|
|
7763
|
+
border: 1px solid var(--ccd-header-menu-border);
|
|
7764
|
+
border-radius: 10px;
|
|
7765
|
+
background: var(--ccd-card);
|
|
7766
|
+
color: var(--ccd-text);
|
|
7767
|
+
box-shadow: var(--ccd-view-menu-shadow);
|
|
7768
|
+
font: 13px/20px var(--ccd-font-ui);
|
|
7769
|
+
}
|
|
7770
|
+
|
|
7771
|
+
html[data-dsh-ccd-style="true"] [data-ccd-header-dialog] p { margin: 0 0 12px; }
|
|
7772
|
+
html[data-dsh-ccd-style="true"] [data-ccd-header-dialog] button {
|
|
7773
|
+
margin-right: 6px;
|
|
7774
|
+
padding: 4px 12px;
|
|
7775
|
+
border: 1px solid var(--ccd-border);
|
|
7776
|
+
border-radius: 5px;
|
|
7777
|
+
background: var(--ccd-track);
|
|
7778
|
+
color: inherit;
|
|
7779
|
+
font: inherit;
|
|
7780
|
+
cursor: pointer;
|
|
7781
|
+
}
|
|
7782
|
+
|
|
7783
|
+
/* Only the decorative seat gets icon geometry; labels retain the flexible cell. */
|
|
7784
|
+
html[data-dsh-ccd-style="true"] [data-ccd-header-menu] button {
|
|
7785
|
+
gap: 8px;
|
|
7786
|
+
}
|
|
7787
|
+
|
|
7788
|
+
html[data-dsh-ccd-style="true"] [data-ccd-header-menu] [data-ccd-header-row-icon] {
|
|
7789
|
+
display: inline-flex;
|
|
7790
|
+
flex: 0 0 16px;
|
|
7791
|
+
align-items: center;
|
|
7792
|
+
justify-content: center;
|
|
7793
|
+
width: 16px;
|
|
7794
|
+
height: 16px;
|
|
7795
|
+
color: inherit;
|
|
7796
|
+
}
|
|
7797
|
+
|
|
7798
|
+
html[data-dsh-ccd-style="true"] [data-ccd-header-row-icon] svg {
|
|
7799
|
+
flex: none;
|
|
7800
|
+
width: 16px;
|
|
7801
|
+
height: 16px;
|
|
7802
|
+
}
|
|
7803
|
+
`;
|
|
7804
|
+
|
|
7805
|
+
// src/client/features/conversation/header-actions/mount.ts
|
|
7806
|
+
function renderNothing() {
|
|
7807
|
+
return null;
|
|
7808
|
+
}
|
|
7809
|
+
function mountHeaderActions(ctx, environment, cleanup) {
|
|
7810
|
+
cleanup.add(environment.dom.mountStyles(header_actions_default));
|
|
7811
|
+
cleanup.add(environment.dom.mountStyles(header_menus_default));
|
|
7812
|
+
const registration = ctx.inject(["slots", "sidebarRight", "sidebarRightTabs"], (scope) => {
|
|
7813
|
+
const sidebar = resolveSidebarRight(scope);
|
|
7814
|
+
const tabs = resolveSidebarTabs(scope);
|
|
7815
|
+
if (sidebar === void 0 || tabs === void 0) {
|
|
7816
|
+
environment.logger.debug("header-actions: right-Sidebar services are unavailable; native header retained");
|
|
7817
|
+
return () => {
|
|
7818
|
+
};
|
|
7819
|
+
}
|
|
7820
|
+
const copy = headerLabels(document);
|
|
7821
|
+
const labels = {
|
|
7822
|
+
files: copy.files,
|
|
7823
|
+
terminal: copy.terminal,
|
|
7824
|
+
browser: copy.browser
|
|
7825
|
+
};
|
|
7826
|
+
const live = /* @__PURE__ */ new Map();
|
|
7827
|
+
const register = (key, entry) => {
|
|
7828
|
+
if (live.has(key)) return;
|
|
7829
|
+
live.set(key, entry());
|
|
7830
|
+
};
|
|
7831
|
+
const withdraw = (key) => {
|
|
7832
|
+
live.get(key)?.();
|
|
7833
|
+
live.delete(key);
|
|
7834
|
+
};
|
|
7835
|
+
const openKind = (kind, slotSessionId) => {
|
|
7836
|
+
try {
|
|
7837
|
+
openView(sidebar, kind, String(slotSessionId));
|
|
7838
|
+
} catch (error) {
|
|
7839
|
+
environment.logger.error(`header-actions: the ${kind} view rejected the request`, error);
|
|
7840
|
+
}
|
|
6987
7841
|
};
|
|
6988
7842
|
const takeView = (view) => scope.slots.inject(
|
|
6989
7843
|
DSH_SLOTS.sessionHeaderUtilities,
|
|
@@ -7048,16 +7902,49 @@ function createPetAnchor(document2, report) {
|
|
|
7048
7902
|
value = next;
|
|
7049
7903
|
for (const listener of [...listeners]) listener();
|
|
7050
7904
|
};
|
|
7905
|
+
let watching = false;
|
|
7906
|
+
let observer;
|
|
7907
|
+
let phaseObserver;
|
|
7908
|
+
const attach = () => {
|
|
7909
|
+
if (watching) return;
|
|
7910
|
+
watching = true;
|
|
7911
|
+
observer?.observe(document2.body, {
|
|
7912
|
+
childList: true,
|
|
7913
|
+
subtree: true,
|
|
7914
|
+
attributes: true,
|
|
7915
|
+
attributeFilter: ["style", "class", "data-phase"]
|
|
7916
|
+
});
|
|
7917
|
+
window.addEventListener("resize", sync);
|
|
7918
|
+
document2.addEventListener("scroll", sync, true);
|
|
7919
|
+
document2.addEventListener("visibilitychange", sync);
|
|
7920
|
+
if (card !== null) size?.observe(card);
|
|
7921
|
+
};
|
|
7922
|
+
const detach = () => {
|
|
7923
|
+
if (!watching) return;
|
|
7924
|
+
watching = false;
|
|
7925
|
+
observer?.disconnect();
|
|
7926
|
+
size?.disconnect();
|
|
7927
|
+
window.removeEventListener("resize", sync);
|
|
7928
|
+
document2.removeEventListener("scroll", sync, true);
|
|
7929
|
+
document2.removeEventListener("visibilitychange", sync);
|
|
7930
|
+
};
|
|
7051
7931
|
const sync = () => {
|
|
7052
7932
|
if (released) return;
|
|
7053
7933
|
const next = document2.querySelector(anchors.composerCardHero);
|
|
7934
|
+
if (next === null) {
|
|
7935
|
+
detach();
|
|
7936
|
+
card = null;
|
|
7937
|
+
publish(null);
|
|
7938
|
+
return;
|
|
7939
|
+
}
|
|
7940
|
+
if (!watching) attach();
|
|
7054
7941
|
if (next !== card) {
|
|
7055
7942
|
if (card !== null) size?.disconnect();
|
|
7056
7943
|
card = next;
|
|
7057
7944
|
if (card !== null) size?.observe(card);
|
|
7058
7945
|
}
|
|
7059
|
-
const rect = card
|
|
7060
|
-
if (
|
|
7946
|
+
const rect = card.getBoundingClientRect();
|
|
7947
|
+
if (rect.width === 0 || rect.height === 0 || document2.hidden) {
|
|
7061
7948
|
publish(null);
|
|
7062
7949
|
return;
|
|
7063
7950
|
}
|
|
@@ -7066,27 +7953,28 @@ function createPetAnchor(document2, report) {
|
|
|
7066
7953
|
top: Math.round(rect.top - PET_BOX.height)
|
|
7067
7954
|
});
|
|
7068
7955
|
};
|
|
7069
|
-
let observer;
|
|
7070
7956
|
try {
|
|
7071
7957
|
observer = new MutationObserver(sync);
|
|
7072
|
-
|
|
7958
|
+
phaseObserver = new MutationObserver((records) => {
|
|
7959
|
+
if (watching) return;
|
|
7960
|
+
const arrived = records.some((record) => record.type === "attributes" ? record.attributeName === "data-phase" : [...record.addedNodes].some((node) => node.nodeType === 1 && ((node.matches?.("[data-phase]") ?? false) || (node.querySelector?.("[data-phase]") ?? null) !== null)));
|
|
7961
|
+
if (!arrived) return;
|
|
7962
|
+
sync();
|
|
7963
|
+
});
|
|
7964
|
+
if (typeof ResizeObserver === "function") size = new ResizeObserver(sync);
|
|
7965
|
+
sync();
|
|
7966
|
+
phaseObserver.observe(document2.body, {
|
|
7073
7967
|
childList: true,
|
|
7074
7968
|
subtree: true,
|
|
7075
7969
|
attributes: true,
|
|
7076
|
-
attributeFilter: ["
|
|
7970
|
+
attributeFilter: ["data-phase"]
|
|
7077
7971
|
});
|
|
7078
|
-
if (typeof ResizeObserver === "function") size = new ResizeObserver(sync);
|
|
7079
|
-
window.addEventListener("resize", sync);
|
|
7080
|
-
document2.addEventListener("scroll", sync, true);
|
|
7081
|
-
document2.addEventListener("visibilitychange", sync);
|
|
7082
|
-
sync();
|
|
7083
7972
|
} catch (error) {
|
|
7084
7973
|
observer?.disconnect();
|
|
7974
|
+
phaseObserver?.disconnect();
|
|
7975
|
+
detach();
|
|
7085
7976
|
size?.disconnect();
|
|
7086
7977
|
size = void 0;
|
|
7087
|
-
window.removeEventListener("resize", sync);
|
|
7088
|
-
document2.removeEventListener("scroll", sync, true);
|
|
7089
|
-
document2.removeEventListener("visibilitychange", sync);
|
|
7090
7978
|
listeners.clear();
|
|
7091
7979
|
report(error);
|
|
7092
7980
|
return inertAnchor();
|
|
@@ -7102,12 +7990,11 @@ function createPetAnchor(document2, report) {
|
|
|
7102
7990
|
dispose() {
|
|
7103
7991
|
if (released) return;
|
|
7104
7992
|
released = true;
|
|
7105
|
-
|
|
7106
|
-
|
|
7993
|
+
phaseObserver?.disconnect();
|
|
7994
|
+
const attached = watching;
|
|
7995
|
+
detach();
|
|
7996
|
+
if (!attached) size?.disconnect();
|
|
7107
7997
|
size = void 0;
|
|
7108
|
-
window.removeEventListener("resize", sync);
|
|
7109
|
-
document2.removeEventListener("scroll", sync, true);
|
|
7110
|
-
document2.removeEventListener("visibilitychange", sync);
|
|
7111
7998
|
listeners.clear();
|
|
7112
7999
|
card = null;
|
|
7113
8000
|
value = null;
|
|
@@ -8504,9 +9391,9 @@ var right_panel_default = `/*
|
|
|
8504
9391
|
* which owns the frame's columns; this sheet dresses what the panel draws
|
|
8505
9392
|
* inside it. Everything here speaks the DockKit and Files packages' own stable
|
|
8506
9393
|
* data hooks rather than their hashed class names (\`compat/host-dom.ts\` pins
|
|
8507
|
-
* each one), and every number is measured off the reference at @2x:
|
|
8508
|
-
*
|
|
8509
|
-
*
|
|
9394
|
+
* each one), and every number is measured off the reference at @2x: its Files
|
|
9395
|
+
* crop, with its terminal and browser crops for the shared header row (DESIGN.md
|
|
9396
|
+
* \xA76.9).
|
|
8510
9397
|
*
|
|
8511
9398
|
* The host keeps the structure, the controls, the handlers and their copy. This
|
|
8512
9399
|
* sheet changes what a box looks like: the strip's line, the tab's own mark,
|
|
@@ -8589,6 +9476,14 @@ html[data-dsh-ccd-style="true"] [data-sidebar-right-panel] [data-ccd-files-mark]
|
|
|
8589
9476
|
mask: var(--ccd-icon-files-list) center / var(--ccd-right-panel-glyph-size) var(--ccd-right-panel-glyph-size) no-repeat;
|
|
8590
9477
|
}
|
|
8591
9478
|
|
|
9479
|
+
html[data-dsh-ccd-style="true"] [data-sidebar-right-panel] [data-ccd-files-switch][aria-expanded="false"] {
|
|
9480
|
+
background: transparent;
|
|
9481
|
+
}
|
|
9482
|
+
|
|
9483
|
+
html[data-dsh-ccd-style="true"] [data-sidebar-right-panel] [data-ccd-files-switch][aria-expanded="false"]::before {
|
|
9484
|
+
color: var(--ccd-right-panel-tab-ink);
|
|
9485
|
+
}
|
|
9486
|
+
|
|
8592
9487
|
/* The strip's controls: the reference draws five 26px seats that touch, each
|
|
8593
9488
|
with about 12px of ink in it, so the host's own boxes shrink to that pitch \u2014
|
|
8594
9489
|
the split control, the fullscreen / collapse chrome and the add-tab control
|
|
@@ -9068,7 +9963,9 @@ html[data-dsh-ccd-style="true"] [data-sidebar-right-panel] [data-ccd-pane] [data
|
|
|
9068
9963
|
|
|
9069
9964
|
/* The panel's own column with panes on screen: no tab chips, no add / split
|
|
9070
9965
|
controls, and no body \u2014 the guide's landing page is the panel's, not a
|
|
9071
|
-
pane's
|
|
9966
|
+
pane's. Keep it covered through the deferred collapse and its transition. */
|
|
9967
|
+
html[data-dsh-ccd-style="true"] [data-sidebar-right-panel][data-ccd-panes-closing] [data-dockkit-pane],
|
|
9968
|
+
html[data-dsh-ccd-style="true"] [data-sidebar-right-panel]:not([data-sidebar-right-open]) [data-dockkit-pane],
|
|
9072
9969
|
html[data-dsh-ccd-style="true"] [data-sidebar-right-panel] [data-ccd-panes] [data-dockkit-strip],
|
|
9073
9970
|
html[data-dsh-ccd-style="true"] [data-sidebar-right-panel] [data-ccd-panes] [data-dockkit-tab],
|
|
9074
9971
|
html[data-dsh-ccd-style="true"] [data-sidebar-right-panel] [data-ccd-panes] [data-dockkit-add-tab],
|
|
@@ -9141,17 +10038,6 @@ html[data-dsh-ccd-style="true"] [data-ccd-pane-controls] {
|
|
|
9141
10038
|
gap: 0;
|
|
9142
10039
|
}
|
|
9143
10040
|
|
|
9144
|
-
html[data-dsh-ccd-style="true"] [data-ccd-file-tabs] {
|
|
9145
|
-
min-width: 0;
|
|
9146
|
-
max-width: 100px;
|
|
9147
|
-
height: 26px;
|
|
9148
|
-
border: 0;
|
|
9149
|
-
border-radius: var(--ccd-chip-radius);
|
|
9150
|
-
color: var(--ccd-text-muted);
|
|
9151
|
-
background: var(--ccd-card);
|
|
9152
|
-
font: inherit;
|
|
9153
|
-
font-size: 12px;
|
|
9154
|
-
}
|
|
9155
10041
|
|
|
9156
10042
|
html[data-dsh-ccd-style="true"] [data-ccd-pane-seam]:focus-visible {
|
|
9157
10043
|
outline: 2px solid var(--ccd-accent);
|
|
@@ -9189,15 +10075,28 @@ html[data-dsh-ccd-style="true"] [data-ccd-pane]:not([data-ccd-pane-tree]) [data-
|
|
|
9189
10075
|
grid-template-rows: var(--ccd-right-panel-strip-height) minmax(0, 1fr);
|
|
9190
10076
|
}
|
|
9191
10077
|
|
|
10078
|
+
/* One divider spans both grid rows, independent of the host body's margins. */
|
|
10079
|
+
html[data-dsh-ccd-style="true"] [data-ccd-pane]:not([data-ccd-pane-tree]) [data-files-root]:not([data-ccd-files-combined]):not([data-ccd-files-compact]):not([data-ccd-files-tree="hidden"])::after {
|
|
10080
|
+
content: "";
|
|
10081
|
+
position: absolute;
|
|
10082
|
+
top: 0;
|
|
10083
|
+
bottom: 0;
|
|
10084
|
+
left: var(--ccd-files-tree-width, 34%);
|
|
10085
|
+
width: 1px;
|
|
10086
|
+
background: var(--ccd-border);
|
|
10087
|
+
pointer-events: none;
|
|
10088
|
+
}
|
|
10089
|
+
|
|
9192
10090
|
html[data-dsh-ccd-style="true"] [data-ccd-pane]:not([data-ccd-pane-tree]) [data-files-root] > div:not([data-files-body]):not([data-ccd-files-landing]) {
|
|
9193
10091
|
grid-column: 1;
|
|
9194
10092
|
grid-row: 1;
|
|
10093
|
+
border-right: 0;
|
|
9195
10094
|
}
|
|
9196
10095
|
|
|
9197
10096
|
html[data-dsh-ccd-style="true"] [data-ccd-pane]:not([data-ccd-pane-tree]) [data-files-root] > [data-files-body] {
|
|
9198
10097
|
grid-column: 1;
|
|
9199
10098
|
grid-row: 2;
|
|
9200
|
-
border-right:
|
|
10099
|
+
border-right: 0;
|
|
9201
10100
|
}
|
|
9202
10101
|
|
|
9203
10102
|
html[data-dsh-ccd-style="true"] [data-ccd-pane]:not([data-ccd-pane-tree]) [data-files-root]:not([data-ccd-files-combined]) > [data-ccd-files-landing] {
|
|
@@ -9276,7 +10175,8 @@ html[data-dsh-ccd-style="true"] [data-ccd-pane] [data-files-root][data-ccd-files
|
|
|
9276
10175
|
display: none;
|
|
9277
10176
|
}
|
|
9278
10177
|
|
|
9279
|
-
html[data-dsh-ccd-style="true"] [data-ccd-pane] [data-files-root][data-ccd-files-compact] > [data-files-body]
|
|
10178
|
+
html[data-dsh-ccd-style="true"] [data-ccd-pane] [data-files-root][data-ccd-files-compact] > [data-files-body],
|
|
10179
|
+
html[data-dsh-ccd-style="true"] [data-ccd-pane] [data-files-root][data-ccd-files-compact] > div:not([data-files-body]):not([data-ccd-files-landing]) {
|
|
9280
10180
|
border-right: 0;
|
|
9281
10181
|
}
|
|
9282
10182
|
|
|
@@ -9536,7 +10436,7 @@ html[data-dsh-ccd-style="true"] [data-ccd-pane-caption] > [data-ccd-terminal-add
|
|
|
9536
10436
|
border-radius: var(--ccd-chip-radius);
|
|
9537
10437
|
background: transparent;
|
|
9538
10438
|
}
|
|
9539
|
-
html[data-dsh-ccd-style="true"] [data-ccd-terminal-tabs] {
|
|
10439
|
+
html[data-dsh-ccd-style="true"] :is([data-ccd-terminal-tabs], [data-ccd-file-tabs]) {
|
|
9540
10440
|
display: flex;
|
|
9541
10441
|
align-items: center;
|
|
9542
10442
|
flex: 0 1 auto;
|
|
@@ -9545,22 +10445,22 @@ html[data-dsh-ccd-style="true"] [data-ccd-terminal-tabs] {
|
|
|
9545
10445
|
overflow-x: auto;
|
|
9546
10446
|
scrollbar-width: none;
|
|
9547
10447
|
}
|
|
9548
|
-
html[data-dsh-ccd-style="true"] [data-ccd-terminal-tabs] > [data-ccd-terminal-tab] {
|
|
10448
|
+
html[data-dsh-ccd-style="true"] :is([data-ccd-terminal-tabs], [data-ccd-file-tabs]) > :is([data-ccd-terminal-tab], [data-ccd-file-tab]) {
|
|
9549
10449
|
display: inline-flex;
|
|
9550
10450
|
flex: 0 1 auto;
|
|
9551
10451
|
align-items: center;
|
|
9552
10452
|
border-radius: var(--ccd-chip-radius);
|
|
9553
10453
|
min-width: 96px;
|
|
9554
10454
|
}
|
|
9555
|
-
html[data-dsh-ccd-style="true"] [data-ccd-terminal-tab]:is([data-selected], :hover, :focus-within) {
|
|
10455
|
+
html[data-dsh-ccd-style="true"] :is([data-ccd-terminal-tab], [data-ccd-file-tab]):is([data-selected], :hover, :focus-within) {
|
|
9556
10456
|
background: var(--ccd-hover);
|
|
9557
10457
|
}
|
|
9558
10458
|
/* Name and close are one tab surface, including hover over either button. */
|
|
9559
|
-
html[data-dsh-ccd-style="true"] [data-sidebar-right-panel] [data-ccd-pane] [data-dockkit-float-grip] [data-ccd-terminal-tab] > button:is(:hover, :active, :focus-visible) {
|
|
10459
|
+
html[data-dsh-ccd-style="true"] [data-sidebar-right-panel] [data-ccd-pane] [data-dockkit-float-grip] :is([data-ccd-terminal-tab], [data-ccd-file-tab]) > button:is(:hover, :active, :focus-visible) {
|
|
9560
10460
|
background: transparent;
|
|
9561
10461
|
box-shadow: none;
|
|
9562
10462
|
}
|
|
9563
|
-
html[data-dsh-ccd-style="true"] [data-ccd-terminal-tab] > [role="tab"] {
|
|
10463
|
+
html[data-dsh-ccd-style="true"] :is([data-ccd-terminal-tab], [data-ccd-file-tab]) > [role="tab"] {
|
|
9564
10464
|
width: auto !important;
|
|
9565
10465
|
min-width: 0;
|
|
9566
10466
|
flex: 1;
|
|
@@ -9574,7 +10474,7 @@ html[data-dsh-ccd-style="true"] [data-ccd-terminal-tab] > [role="tab"] {
|
|
|
9574
10474
|
font: inherit;
|
|
9575
10475
|
text-align: left;
|
|
9576
10476
|
}
|
|
9577
|
-
html[data-dsh-ccd-style="true"] [data-ccd-terminal-tab] > [data-ccd-terminal-close] {
|
|
10477
|
+
html[data-dsh-ccd-style="true"] :is([data-ccd-terminal-tab], [data-ccd-file-tab]) > :is([data-ccd-terminal-close], [data-ccd-file-close]) {
|
|
9578
10478
|
width: 20px !important;
|
|
9579
10479
|
height: 24px !important;
|
|
9580
10480
|
display: inline-flex;
|
|
@@ -9585,10 +10485,50 @@ html[data-dsh-ccd-style="true"] [data-ccd-terminal-tab] > [data-ccd-terminal-clo
|
|
|
9585
10485
|
border-radius: var(--ccd-chip-radius);
|
|
9586
10486
|
background: transparent;
|
|
9587
10487
|
}
|
|
9588
|
-
html[data-dsh-ccd-style="true"] [data-ccd-terminal-tab] > [data-ccd-terminal-close] svg {
|
|
10488
|
+
html[data-dsh-ccd-style="true"] :is([data-ccd-terminal-tab], [data-ccd-file-tab]) > :is([data-ccd-terminal-close], [data-ccd-file-close]) svg {
|
|
9589
10489
|
width: 10px !important;
|
|
9590
10490
|
height: 10px !important;
|
|
9591
10491
|
}
|
|
10492
|
+
/* At @2x the reference's 48px chip holds a 40px square close surface:
|
|
10493
|
+
24px / 20px logical, with a 2px inset rather than a full-height capsule. */
|
|
10494
|
+
html[data-dsh-ccd-style="true"] [data-sidebar-right-panel] [data-ccd-pane] [data-dockkit-float-grip] [data-ccd-file-tab] > [data-ccd-file-close] {
|
|
10495
|
+
width: 20px !important;
|
|
10496
|
+
height: 20px !important;
|
|
10497
|
+
border-radius: 5px;
|
|
10498
|
+
}
|
|
10499
|
+
html[data-dsh-ccd-style="true"] [data-sidebar-right-panel] [data-ccd-pane] [data-dockkit-float-grip] [data-ccd-file-tab] > button {
|
|
10500
|
+
height: 20px !important;
|
|
10501
|
+
color: inherit;
|
|
10502
|
+
}
|
|
10503
|
+
html[data-dsh-ccd-style="true"] [data-sidebar-right-panel] [data-ccd-pane] [data-dockkit-float-grip] [data-ccd-file-tab] > [data-ccd-file-close]:is(:hover, :focus-visible) {
|
|
10504
|
+
background: var(--ccd-file-close-hover);
|
|
10505
|
+
box-shadow: var(--ccd-shadow-segment);
|
|
10506
|
+
}
|
|
10507
|
+
html[data-dsh-ccd-style="true"] [data-sidebar-right-panel] [data-ccd-pane] [data-dockkit-float-grip] [data-ccd-file-tab] > [data-ccd-file-close]:active {
|
|
10508
|
+
background: var(--ccd-file-close-pressed);
|
|
10509
|
+
box-shadow: none;
|
|
10510
|
+
}
|
|
10511
|
+
/* Files retain readable names and scroll along the header when space runs out. */
|
|
10512
|
+
html[data-dsh-ccd-style="true"] [data-ccd-pane-caption] > [data-ccd-file-tabs] {
|
|
10513
|
+
overflow-x: auto;
|
|
10514
|
+
overflow-y: hidden;
|
|
10515
|
+
}
|
|
10516
|
+
html[data-dsh-ccd-style="true"] [data-ccd-file-tabs] > [data-ccd-file-tab] {
|
|
10517
|
+
box-sizing: border-box;
|
|
10518
|
+
flex: none;
|
|
10519
|
+
max-width: 200px;
|
|
10520
|
+
height: 24px;
|
|
10521
|
+
padding: 2px 2px 2px 4px;
|
|
10522
|
+
border-radius: 6px;
|
|
10523
|
+
color: var(--ccd-right-panel-tab-ink);
|
|
10524
|
+
}
|
|
10525
|
+
html[data-dsh-ccd-style="true"] [data-ccd-file-tab]:is(:hover, :focus-within) {
|
|
10526
|
+
background: var(--ccd-file-tab-hover);
|
|
10527
|
+
}
|
|
10528
|
+
html[data-dsh-ccd-style="true"] [data-ccd-file-tab][data-selected] {
|
|
10529
|
+
background: var(--ccd-file-tab-selected);
|
|
10530
|
+
color: var(--ccd-files-ink);
|
|
10531
|
+
}
|
|
9592
10532
|
/* Expanded cards cover the conversation but retain the navigation column. */
|
|
9593
10533
|
html[data-dsh-ccd-style="true"] [data-sidebar-right-panel][data-ccd-pane-expanded] [data-ccd-pane-leading] [data-dockkit-float-grip] {
|
|
9594
10534
|
padding-inline: 8px 4px;
|
|
@@ -9674,6 +10614,15 @@ html[data-dsh-ccd-style="true"] [data-ccd-pane-caption] > [data-ccd-browser-addr
|
|
|
9674
10614
|
height: 24px;
|
|
9675
10615
|
}
|
|
9676
10616
|
|
|
10617
|
+
/* Only the source viewer's code gains weight; its counter stays quiet.
|
|
10618
|
+
Markdown prose, code-block banners and the pane toolbar keep host weights. */
|
|
10619
|
+
html[data-dsh-ccd-style="true"] [data-ccd-pane-kind="text"] [data-code-preview] pre code {
|
|
10620
|
+
font-weight: 500;
|
|
10621
|
+
}
|
|
10622
|
+
html[data-dsh-ccd-style="true"] [data-ccd-pane-kind="text"] [data-code-preview] pre code > .line::before {
|
|
10623
|
+
font-weight: 400;
|
|
10624
|
+
}
|
|
10625
|
+
|
|
9677
10626
|
/* File preview: relative path below the title; reload beside header search. */
|
|
9678
10627
|
html[data-dsh-ccd-style="true"] [data-ccd-pane-kind="text"] :is([data-document-viewer-menu], [data-textpreview-tool="reload"], [data-ccd-preview-path-source]),
|
|
9679
10628
|
html[data-dsh-ccd-style="true"] [data-ccd-pane-kind="text"] [data-slot="sidebar.right.tab.document.actions"] {
|
|
@@ -9690,6 +10639,37 @@ html[data-dsh-ccd-style="true"] [data-ccd-preview-path] {
|
|
|
9690
10639
|
font-size: 14px;
|
|
9691
10640
|
line-height: 20px;
|
|
9692
10641
|
}
|
|
10642
|
+
|
|
10643
|
+
/* Match the file preview's wrap and renderer controls to the pane toolbar. */
|
|
10644
|
+
html[data-dsh-ccd-style="true"] [data-ccd-pane-kind="text"] :has(> [data-textpreview-path]) button,
|
|
10645
|
+
html[data-dsh-ccd-style="true"] [data-ccd-markdown-toggle] {
|
|
10646
|
+
box-sizing: border-box;
|
|
10647
|
+
display: inline-flex;
|
|
10648
|
+
align-items: center;
|
|
10649
|
+
justify-content: center;
|
|
10650
|
+
flex: none;
|
|
10651
|
+
width: 26px;
|
|
10652
|
+
height: 26px;
|
|
10653
|
+
min-width: 0;
|
|
10654
|
+
min-height: 0;
|
|
10655
|
+
padding: 0;
|
|
10656
|
+
border: 0;
|
|
10657
|
+
border-radius: var(--ccd-chip-radius);
|
|
10658
|
+
color: var(--ccd-files-ink);
|
|
10659
|
+
background: transparent;
|
|
10660
|
+
cursor: pointer;
|
|
10661
|
+
}
|
|
10662
|
+
|
|
10663
|
+
html[data-dsh-ccd-style="true"] [data-ccd-pane-kind="text"] :has(> [data-textpreview-path]) button:is(:hover, :focus-visible),
|
|
10664
|
+
html[data-dsh-ccd-style="true"] [data-ccd-markdown-toggle]:is(:hover, :focus-visible) {
|
|
10665
|
+
background: var(--ccd-hover);
|
|
10666
|
+
}
|
|
10667
|
+
|
|
10668
|
+
html[data-dsh-ccd-style="true"] [data-ccd-pane-kind="text"] :has(> [data-textpreview-path]) button svg,
|
|
10669
|
+
html[data-dsh-ccd-style="true"] [data-ccd-markdown-toggle] svg {
|
|
10670
|
+
width: 16px;
|
|
10671
|
+
height: 16px;
|
|
10672
|
+
}
|
|
9693
10673
|
`;
|
|
9694
10674
|
|
|
9695
10675
|
// src/client/features/shell/index.ts
|
|
@@ -9711,45 +10691,62 @@ var shellFeature = {
|
|
|
9711
10691
|
function mountSidebarActionAlignment(document2) {
|
|
9712
10692
|
const host = hostSelectors(document2);
|
|
9713
10693
|
const originals = /* @__PURE__ */ new Map();
|
|
9714
|
-
const resize = new ResizeObserver(() =>
|
|
10694
|
+
const resize = new ResizeObserver(() => frames.schedule());
|
|
9715
10695
|
const watched = /* @__PURE__ */ new Set();
|
|
10696
|
+
let sidebars = [];
|
|
9716
10697
|
const sync = () => {
|
|
9717
|
-
|
|
10698
|
+
sidebars = [...document2.querySelectorAll(host.sidebarRoot)];
|
|
10699
|
+
const live = /* @__PURE__ */ new Set();
|
|
10700
|
+
const updates = [];
|
|
10701
|
+
for (const sidebar of sidebars) {
|
|
9718
10702
|
const header = sidebar.querySelector(host.browserSectionHeader);
|
|
9719
10703
|
const row = sidebar.querySelector(host.workspaceRow);
|
|
9720
10704
|
if (header === null || row === null) continue;
|
|
9721
10705
|
if (!originals.has(header)) originals.set(header, header.style.paddingRight);
|
|
9722
10706
|
for (const element of [header, row]) {
|
|
10707
|
+
live.add(element);
|
|
9723
10708
|
if (!watched.has(element)) {
|
|
9724
10709
|
watched.add(element);
|
|
9725
10710
|
resize.observe(element);
|
|
9726
10711
|
}
|
|
9727
10712
|
}
|
|
9728
|
-
|
|
9729
|
-
const
|
|
10713
|
+
const headerBox = header.getBoundingClientRect();
|
|
10714
|
+
const rowBox = row.getBoundingClientRect();
|
|
10715
|
+
if (headerBox.width === 0 || rowBox.width === 0) continue;
|
|
10716
|
+
const inset = Math.max(0, headerBox.right - rowBox.right);
|
|
9730
10717
|
const value = `${inset}px`;
|
|
10718
|
+
updates.push({ header, value });
|
|
10719
|
+
}
|
|
10720
|
+
for (const { header, value } of updates) {
|
|
9731
10721
|
if (header.style.paddingRight !== value) header.style.paddingRight = value;
|
|
9732
10722
|
}
|
|
9733
10723
|
for (const element of watched) {
|
|
9734
|
-
if (!element
|
|
10724
|
+
if (!live.has(element)) {
|
|
9735
10725
|
resize.unobserve(element);
|
|
9736
10726
|
watched.delete(element);
|
|
9737
10727
|
}
|
|
9738
10728
|
}
|
|
9739
10729
|
for (const [header, original] of originals) {
|
|
9740
|
-
if (!header
|
|
10730
|
+
if (!live.has(header)) {
|
|
9741
10731
|
header.style.paddingRight = original;
|
|
9742
10732
|
originals.delete(header);
|
|
9743
10733
|
}
|
|
9744
10734
|
}
|
|
9745
10735
|
};
|
|
9746
|
-
const
|
|
10736
|
+
const frames = frameScheduler(sync, document2.defaultView);
|
|
10737
|
+
const mutations = new MutationObserver((records) => {
|
|
10738
|
+
if (sidebars.some((sidebar) => recordsTouch(records, sidebar)) || recordsIntroduce(records, host.sidebarRoot)) frames.schedule();
|
|
10739
|
+
});
|
|
9747
10740
|
mutations.observe(document2.documentElement, { childList: true, subtree: true });
|
|
9748
10741
|
sync();
|
|
9749
10742
|
return () => {
|
|
10743
|
+
frames.cancel();
|
|
9750
10744
|
mutations.disconnect();
|
|
9751
10745
|
resize.disconnect();
|
|
9752
10746
|
for (const [header, original] of originals) header.style.paddingRight = original;
|
|
10747
|
+
originals.clear();
|
|
10748
|
+
watched.clear();
|
|
10749
|
+
sidebars = [];
|
|
9753
10750
|
};
|
|
9754
10751
|
}
|
|
9755
10752
|
|
|
@@ -9849,6 +10846,7 @@ function mountAccountMore(menu) {
|
|
|
9849
10846
|
for (const [text, url] of [
|
|
9850
10847
|
[zh2 ? "DeepSeek \u7F51\u9875\u804A\u5929" : "DeepSeek Chat", "https://chat.deepseek.com/"],
|
|
9851
10848
|
["API Platform", "https://platform.deepseek.com/"],
|
|
10849
|
+
[zh2 ? "\u63D2\u4EF6 Bug \u53CD\u9988" : "Report a plugin bug", "https://github.com/zkforge/dsh-claude-desktop-theme/issues/new"],
|
|
9852
10850
|
[zh2 ? "\u63D2\u4EF6 GitHub \xB7 \u6C42 Star \u2606" : "Plugin GitHub \xB7 Star us \u2606", "https://github.com/zkforge/dsh-claude-desktop-theme"]
|
|
9853
10851
|
]) {
|
|
9854
10852
|
const button = document2.createElement("button");
|
|
@@ -10518,9 +11516,9 @@ html[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_n
|
|
|
10518
11516
|
and 30px is also what the host's expanded search pill measures \u2014 so the only
|
|
10519
11517
|
whitespace the stack can give up is the margin that used to sit here: 10px on
|
|
10520
11518
|
top of the header's own 2px bottom margin left the three icons floating in a
|
|
10521
|
-
band about twice the stack's own pitch. Measured at @2x on
|
|
10522
|
-
|
|
10523
|
-
|
|
11519
|
+
band about twice the stack's own pitch. Measured at @2x on the reference,
|
|
11520
|
+
the last navigation row's ink ended 26px above the icon glyphs, against the
|
|
11521
|
+
14.5px between two navigation rows. The margin drops to
|
|
10524
11522
|
the row gap, which lifts the icon row and the list together by 9px and leaves
|
|
10525
11523
|
the 28px hit areas, the header's 30px box and every row above untouched. The
|
|
10526
11524
|
host's own \`margin-top: 2px\` on that header never applies: the plugin's
|
|
@@ -10593,35 +11591,91 @@ html[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_p
|
|
|
10593
11591
|
color: var(--ccd-text);
|
|
10594
11592
|
}
|
|
10595
11593
|
|
|
10596
|
-
html[data-dsh-ccd-style="true"] ._3WPZCG_regionArea {
|
|
11594
|
+
html[data-dsh-ccd-style="true"] ._3WPZCG_regionArea {
|
|
11595
|
+
margin-left: 0;
|
|
11596
|
+
margin-right: 0;
|
|
11597
|
+
padding-left: 0;
|
|
11598
|
+
}
|
|
11599
|
+
|
|
11600
|
+
/* Browsing region: a separate toolbar above compact project groups. */
|
|
11601
|
+
html[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._7514NG_root {
|
|
11602
|
+
--dsh-session-list-edge-inset: 0px;
|
|
11603
|
+
padding-right: 0;
|
|
11604
|
+
}
|
|
11605
|
+
|
|
11606
|
+
/* Align the search border with the navigation row's background edge. Its
|
|
11607
|
+
1px border, 4px inset and 22px icon button put the icon centre at 16px,
|
|
11608
|
+
matching the navigation row's 8px inset and 16px icon seat. Leave a small
|
|
11609
|
+
gap below the toolbar before the first group, keeping the compact search
|
|
11610
|
+
field and its controls centred in the same row.
|
|
11611
|
+
The collapsed rail keeps the host's own insets for its add-workspace icon. */
|
|
11612
|
+
html[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._7514NG_sectionHeader {
|
|
11613
|
+
position: relative;
|
|
11614
|
+
height: 26px;
|
|
11615
|
+
margin: 0 0 4px;
|
|
11616
|
+
padding-left: 0;
|
|
11617
|
+
/* Runtime measures the tree edge, including the actual scrollbar gutter. */
|
|
11618
|
+
padding-right: 0;
|
|
11619
|
+
color: var(--ccd-text-muted);
|
|
11620
|
+
/* The field now meets this row's left edge. The host's rounded clipping
|
|
11621
|
+
would cut into its corners and shadow, so only the field clips itself. */
|
|
11622
|
+
overflow: visible;
|
|
11623
|
+
}
|
|
11624
|
+
|
|
11625
|
+
/* Keep the native search field visible in place of the region heading.
|
|
11626
|
+
React still owns querying, clearing, Escape and the search shortcut. */
|
|
11627
|
+
html[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._7514NG_sectionLabel {
|
|
11628
|
+
display: none;
|
|
11629
|
+
}
|
|
11630
|
+
|
|
11631
|
+
html[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._7514NG_searchSlot {
|
|
11632
|
+
flex: 1;
|
|
11633
|
+
max-width: none;
|
|
10597
11634
|
margin-left: 0;
|
|
10598
|
-
margin-right: 0;
|
|
10599
11635
|
padding-left: 0;
|
|
11636
|
+
transition: none;
|
|
10600
11637
|
}
|
|
10601
11638
|
|
|
10602
|
-
|
|
10603
|
-
|
|
10604
|
-
|
|
10605
|
-
|
|
11639
|
+
html[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._7514NG_search {
|
|
11640
|
+
width: 100%;
|
|
11641
|
+
height: 24px;
|
|
11642
|
+
margin: 0;
|
|
11643
|
+
padding: 0 4px;
|
|
11644
|
+
border: 1px solid var(--ccd-border);
|
|
11645
|
+
border-radius: 7px;
|
|
11646
|
+
background: var(--ccd-card);
|
|
11647
|
+
box-shadow: var(--ccd-shadow-card);
|
|
11648
|
+
color: var(--ccd-text-muted);
|
|
11649
|
+
transition: none;
|
|
10606
11650
|
}
|
|
10607
11651
|
|
|
10608
|
-
|
|
10609
|
-
|
|
10610
|
-
|
|
10611
|
-
|
|
10612
|
-
html[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) .
|
|
10613
|
-
|
|
10614
|
-
|
|
10615
|
-
margin:
|
|
10616
|
-
|
|
10617
|
-
|
|
10618
|
-
|
|
11652
|
+
html[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._7514NG_search:focus-within {
|
|
11653
|
+
border-color: var(--ccd-text-muted);
|
|
11654
|
+
}
|
|
11655
|
+
|
|
11656
|
+
html[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._7514NG_searchInput {
|
|
11657
|
+
width: 0;
|
|
11658
|
+
min-width: 0;
|
|
11659
|
+
margin-left: 2px;
|
|
11660
|
+
opacity: 1;
|
|
11661
|
+
pointer-events: auto;
|
|
11662
|
+
color: var(--ccd-text);
|
|
11663
|
+
font-family: inherit;
|
|
11664
|
+
font-size: 13px;
|
|
11665
|
+
}
|
|
11666
|
+
|
|
11667
|
+
html[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._7514NG_searchInput::placeholder {
|
|
10619
11668
|
color: var(--ccd-text-muted);
|
|
10620
11669
|
}
|
|
10621
11670
|
|
|
10622
|
-
/*
|
|
10623
|
-
html[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) .
|
|
10624
|
-
max-width:
|
|
11671
|
+
/* Searching normally hides these native actions; keep View options available. */
|
|
11672
|
+
html[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._7514NG_headerActions {
|
|
11673
|
+
max-width: none;
|
|
11674
|
+
opacity: 1;
|
|
11675
|
+
visibility: visible;
|
|
11676
|
+
pointer-events: auto;
|
|
11677
|
+
transform: none;
|
|
11678
|
+
transition: none;
|
|
10625
11679
|
}
|
|
10626
11680
|
|
|
10627
11681
|
/* The reference gives a Workspace row one standing action \u2014 the plus that starts
|
|
@@ -10845,14 +11899,32 @@ html[data-dsh-ccd-style="true"] :has(> .jJkEga_hoverContent > .jJkEga_hoverStatu
|
|
|
10845
11899
|
display: none;
|
|
10846
11900
|
}
|
|
10847
11901
|
|
|
10848
|
-
/*
|
|
10849
|
-
|
|
10850
|
-
either a 6px solid state dot or the 14px activity ring; the ring is scaled to
|
|
10851
|
-
8px so its 1.5px stroke reads as the reference's 6px mark instead of a
|
|
10852
|
-
full-size spinner. */
|
|
11902
|
+
/* Keep the leading cell and use a compact 6px mark; replace the host spinner with a
|
|
11903
|
+
breathing circle whose centre fades between hollow and solid. */
|
|
10853
11904
|
html[data-dsh-ccd-style="true"] .jJkEga_sessionRow > .jJkEga_slot > svg[data-state="ongoing"] {
|
|
10854
|
-
width:
|
|
10855
|
-
height:
|
|
11905
|
+
width: 6px;
|
|
11906
|
+
height: 6px;
|
|
11907
|
+
box-sizing: border-box;
|
|
11908
|
+
border: 1px solid currentColor;
|
|
11909
|
+
border-radius: 50%;
|
|
11910
|
+
background-color: currentColor;
|
|
11911
|
+
animation: var(--ccd-session-activity-animation, ccd-session-breathe 2s ease-in-out infinite);
|
|
11912
|
+
}
|
|
11913
|
+
|
|
11914
|
+
html[data-dsh-ccd-style="true"] .jJkEga_sessionRow > .jJkEga_slot > [data-state="ongoing"] > * {
|
|
11915
|
+
display: none;
|
|
11916
|
+
animation: none;
|
|
11917
|
+
}
|
|
11918
|
+
|
|
11919
|
+
@keyframes ccd-session-breathe {
|
|
11920
|
+
0%, 100% { background-color: transparent; }
|
|
11921
|
+
50% { background-color: currentColor; }
|
|
11922
|
+
}
|
|
11923
|
+
|
|
11924
|
+
@media (prefers-reduced-motion: reduce) {
|
|
11925
|
+
html[data-dsh-ccd-style="true"] .jJkEga_sessionRow {
|
|
11926
|
+
--ccd-session-activity-animation: none;
|
|
11927
|
+
}
|
|
10856
11928
|
}
|
|
10857
11929
|
|
|
10858
11930
|
/* An idle Session keeps that cell empty. The host gives the leading mark only
|
|
@@ -10864,9 +11936,8 @@ html[data-dsh-ccd-style="true"] .jJkEga_sessionRow > .jJkEga_slot > svg[data-sta
|
|
|
10864
11936
|
|
|
10865
11937
|
The seat is filled with the ring's own geometry, not a second mark: the same
|
|
10866
11938
|
circle the activity ring draws (\`--ccd-icon-session-ring\`, read from
|
|
10867
|
-
\`ui-primitives\`' StateDot spinner) at the same
|
|
10868
|
-
|
|
10869
|
-
that differs only by motion. \`currentColor\` is the cell's own
|
|
11939
|
+
\`ui-primitives\`' StateDot spinner) at the same 6px scale as the running mark above,
|
|
11940
|
+
which breathes between hollow and solid. \`currentColor\` is the cell's own
|
|
10870
11941
|
\`--dsw-alias-label-tertiary\` \u2014 the ink the spinner paints with \u2014 so the still
|
|
10871
11942
|
ring needs no colour of its own and follows the host theme.
|
|
10872
11943
|
|
|
@@ -10880,10 +11951,10 @@ html[data-dsh-ccd-style="true"] .jJkEga_sessionRow > .jJkEga_slot > svg[data-sta
|
|
|
10880
11951
|
down rather than left to the host's current shape. */
|
|
10881
11952
|
html[data-dsh-ccd-style="true"] .jJkEga_sessionRow:not(.jJkEga_archived) > .jJkEga_slot:has(> div[data-slot="sidebar.session.row.leading"]:empty)::before {
|
|
10882
11953
|
content: "";
|
|
10883
|
-
width:
|
|
10884
|
-
height:
|
|
11954
|
+
width: 6px;
|
|
11955
|
+
height: 6px;
|
|
10885
11956
|
background-color: currentColor;
|
|
10886
|
-
mask: var(--ccd-icon-session-ring) center /
|
|
11957
|
+
mask: var(--ccd-icon-session-ring) center / 6px 6px no-repeat;
|
|
10887
11958
|
}
|
|
10888
11959
|
|
|
10889
11960
|
html[data-dsh-ccd-style="true"] .jJkEga_sessionRow .jJkEga_title {
|
|
@@ -11089,6 +12160,9 @@ html[data-dsh-ccd-style="true"] [data-ccd-list-empty] {
|
|
|
11089
12160
|
|
|
11090
12161
|
html[data-dsh-ccd-style="true"] .ccd-sidebar-empty {
|
|
11091
12162
|
position: absolute;
|
|
12163
|
+
/* The native scrolling tree is a positioned sibling painted after this
|
|
12164
|
+
prepended prompt. Keep recovery controls above its empty hit surface. */
|
|
12165
|
+
z-index: 1;
|
|
11092
12166
|
inset: 0;
|
|
11093
12167
|
display: flex;
|
|
11094
12168
|
flex-direction: column;
|
|
@@ -11140,10 +12214,21 @@ html[data-dsh-ccd-style="true"] .jJkEga_sessionRow .jJkEga_rowActions {
|
|
|
11140
12214
|
flex: none;
|
|
11141
12215
|
margin-left: auto;
|
|
11142
12216
|
gap: 0;
|
|
12217
|
+
height: 22px;
|
|
12218
|
+
align-items: center;
|
|
12219
|
+
}
|
|
12220
|
+
|
|
12221
|
+
/* Menu's inline wrapper must not add a text-baseline descent below the button. */
|
|
12222
|
+
html[data-dsh-ccd-style="true"] .jJkEga_sessionRow .jJkEga_rowActions > span {
|
|
12223
|
+
display: inline-flex;
|
|
12224
|
+
align-items: center;
|
|
12225
|
+
height: 22px;
|
|
11143
12226
|
}
|
|
11144
12227
|
|
|
11145
12228
|
html[data-dsh-ccd-style="true"] .jJkEga_sessionRow:has(.jJkEga_rowActions) {
|
|
11146
|
-
|
|
12229
|
+
box-sizing: border-box;
|
|
12230
|
+
min-width: 0;
|
|
12231
|
+
padding-inline-end: 3px;
|
|
11147
12232
|
}
|
|
11148
12233
|
|
|
11149
12234
|
/* The reference keeps the trigger highlighted while its popup is open. */
|
|
@@ -11278,13 +12363,18 @@ html[data-dsh-ccd-style="true"] [data-ccd-status-order]:is(:hover, :focus-within
|
|
|
11278
12363
|
}
|
|
11279
12364
|
html[data-dsh-ccd-style="true"] .ccd-status-heading {
|
|
11280
12365
|
flex: none;
|
|
11281
|
-
/*
|
|
11282
|
-
|
|
11283
|
-
|
|
11284
|
-
|
|
11285
|
-
font-
|
|
12366
|
+
/* Section labels share the Recents toolbar's 4px inset; only session
|
|
12367
|
+
titles are indented past the native status icon. */
|
|
12368
|
+
padding: 12px 8px 4px 4px;
|
|
12369
|
+
color: var(--ccd-text-secondary);
|
|
12370
|
+
font-size: calc(var(--ccd-font-size-muted) - 1px);
|
|
12371
|
+
font-weight: 400;
|
|
11286
12372
|
line-height: 20px;
|
|
11287
12373
|
}
|
|
12374
|
+
html[data-dsh-ccd-style="true"] .ccd-status-heading:nth-child(1 of .ccd-status-heading) {
|
|
12375
|
+
/* The toolbar already provides separation above the first visible group. */
|
|
12376
|
+
padding-top: 4px;
|
|
12377
|
+
}
|
|
11288
12378
|
html[data-dsh-ccd-style="true"] .ccd-status-children {
|
|
11289
12379
|
flex: none;
|
|
11290
12380
|
min-width: 0;
|
|
@@ -11394,6 +12484,7 @@ function mountHeroCanvas(document2, draw, report) {
|
|
|
11394
12484
|
const reduced = win.matchMedia("(prefers-reduced-motion: reduce)");
|
|
11395
12485
|
let root = null;
|
|
11396
12486
|
let canvas = null;
|
|
12487
|
+
let titleCanvas = null;
|
|
11397
12488
|
let frame = 0;
|
|
11398
12489
|
let last = 0;
|
|
11399
12490
|
let elapsed = 0;
|
|
@@ -11408,19 +12499,34 @@ function mountHeroCanvas(document2, draw, report) {
|
|
|
11408
12499
|
if (!canvas || !root) return;
|
|
11409
12500
|
const ratio = Math.min(win.devicePixelRatio || 1, 2);
|
|
11410
12501
|
const rect = root.getBoundingClientRect();
|
|
11411
|
-
|
|
11412
|
-
|
|
12502
|
+
const width = Math.round(rect.width * ratio);
|
|
12503
|
+
const height = Math.round(rect.height * ratio);
|
|
12504
|
+
if (canvas.width !== width) canvas.width = width;
|
|
12505
|
+
if (canvas.height !== height) canvas.height = height;
|
|
12506
|
+
if (titleCanvas) {
|
|
12507
|
+
titleCanvas.style.left = `${rect.left}px`;
|
|
12508
|
+
titleCanvas.style.width = `${rect.width}px`;
|
|
12509
|
+
titleCanvas.style.height = `${Math.max(0, rect.top)}px`;
|
|
12510
|
+
if (titleCanvas.width !== width) titleCanvas.width = width;
|
|
12511
|
+
const titleHeight = Math.round(Math.max(0, rect.top) * ratio);
|
|
12512
|
+
if (titleCanvas.height !== titleHeight) titleCanvas.height = titleHeight;
|
|
12513
|
+
}
|
|
11413
12514
|
sync();
|
|
11414
12515
|
});
|
|
12516
|
+
let busy = false;
|
|
11415
12517
|
const tick = (time) => {
|
|
11416
12518
|
frame = 0;
|
|
11417
12519
|
if (!canvas || disposed) return;
|
|
11418
|
-
if (
|
|
12520
|
+
if (busy) {
|
|
12521
|
+
if (last) elapsed += time - last;
|
|
12522
|
+
last = time;
|
|
12523
|
+
} else if (!last || time - last >= 1e3 / 24) {
|
|
11419
12524
|
if (last) elapsed += time - last;
|
|
11420
12525
|
last = time;
|
|
11421
12526
|
impulses = impulses.filter((impulse) => elapsed - impulse.startedAt < HERO_RIPPLE_DURATION);
|
|
11422
12527
|
try {
|
|
11423
12528
|
draw(canvas, elapsed, impulses);
|
|
12529
|
+
if (titleCanvas && titleCanvas.height) draw(titleCanvas, elapsed, []);
|
|
11424
12530
|
} catch (error) {
|
|
11425
12531
|
stop();
|
|
11426
12532
|
report(error);
|
|
@@ -11429,6 +12535,16 @@ function mountHeroCanvas(document2, draw, report) {
|
|
|
11429
12535
|
}
|
|
11430
12536
|
frame = win.requestAnimationFrame(tick);
|
|
11431
12537
|
};
|
|
12538
|
+
const engage = (event) => {
|
|
12539
|
+
const { key, metaKey, ctrlKey } = event;
|
|
12540
|
+
if (key === "Shift" || key === "Control" || key === "Alt" || key === "Meta" || metaKey || ctrlKey) return;
|
|
12541
|
+
busy = true;
|
|
12542
|
+
};
|
|
12543
|
+
const release = () => {
|
|
12544
|
+
if (!busy) return;
|
|
12545
|
+
busy = false;
|
|
12546
|
+
last = 0;
|
|
12547
|
+
};
|
|
11432
12548
|
function sync() {
|
|
11433
12549
|
if (disposed) return;
|
|
11434
12550
|
const next = document2.querySelector(`${host.conversationRoot}[data-phase="hero"]`);
|
|
@@ -11436,8 +12552,10 @@ function mountHeroCanvas(document2, draw, report) {
|
|
|
11436
12552
|
stop();
|
|
11437
12553
|
resize.disconnect();
|
|
11438
12554
|
canvas?.remove();
|
|
12555
|
+
titleCanvas?.remove();
|
|
11439
12556
|
root = next;
|
|
11440
12557
|
canvas = null;
|
|
12558
|
+
titleCanvas = null;
|
|
11441
12559
|
elapsed = 0;
|
|
11442
12560
|
impulses = [];
|
|
11443
12561
|
if (root) {
|
|
@@ -11445,6 +12563,10 @@ function mountHeroCanvas(document2, draw, report) {
|
|
|
11445
12563
|
canvas.className = "ccd-hero-ripples";
|
|
11446
12564
|
canvas.setAttribute("aria-hidden", "true");
|
|
11447
12565
|
root.prepend(canvas);
|
|
12566
|
+
titleCanvas = document2.createElement("canvas");
|
|
12567
|
+
titleCanvas.className = "ccd-hero-ripples ccd-hero-ripples-titlebar";
|
|
12568
|
+
titleCanvas.setAttribute("aria-hidden", "true");
|
|
12569
|
+
document2.body.prepend(titleCanvas);
|
|
11448
12570
|
resize.observe(root);
|
|
11449
12571
|
}
|
|
11450
12572
|
}
|
|
@@ -11452,9 +12574,11 @@ function mountHeroCanvas(document2, draw, report) {
|
|
|
11452
12574
|
stop();
|
|
11453
12575
|
impulses = [];
|
|
11454
12576
|
if (canvas) canvas.hidden = true;
|
|
12577
|
+
if (titleCanvas) titleCanvas.hidden = true;
|
|
11455
12578
|
return;
|
|
11456
12579
|
}
|
|
11457
12580
|
canvas.hidden = false;
|
|
12581
|
+
if (titleCanvas) titleCanvas.hidden = false;
|
|
11458
12582
|
if (!frame) {
|
|
11459
12583
|
last = 0;
|
|
11460
12584
|
frame = win.requestAnimationFrame(tick);
|
|
@@ -11470,17 +12594,21 @@ function mountHeroCanvas(document2, draw, report) {
|
|
|
11470
12594
|
${host.composerCard}, ${host.conversationHeader}`)) return;
|
|
11471
12595
|
const rect = canvas.getBoundingClientRect();
|
|
11472
12596
|
if (!rect.width || !rect.height || event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom) return;
|
|
11473
|
-
|
|
11474
|
-
impulses = [{
|
|
12597
|
+
impulses.push({
|
|
11475
12598
|
x: (event.clientX - rect.left) / rect.width,
|
|
11476
12599
|
y: (event.clientY - rect.top) / rect.height,
|
|
11477
12600
|
startedAt: elapsed
|
|
11478
|
-
}
|
|
12601
|
+
});
|
|
11479
12602
|
};
|
|
11480
|
-
const observer = new MutationObserver(
|
|
12603
|
+
const observer = new MutationObserver((records) => {
|
|
12604
|
+
if (root !== null && !root.isConnected || records.some((record) => record.type === "attributes" && (record.target === root || record.target.matches(host.conversationRoot))) || recordsIntroduce(records, `${host.conversationRoot}[data-phase="hero"]`)) sync();
|
|
12605
|
+
});
|
|
11481
12606
|
observer.observe(document2.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["data-phase"] });
|
|
11482
12607
|
document2.addEventListener("visibilitychange", sync);
|
|
11483
12608
|
document2.addEventListener("click", click, { passive: true });
|
|
12609
|
+
document2.addEventListener("keydown", engage, { capture: true, passive: true });
|
|
12610
|
+
document2.addEventListener("keyup", release, { capture: true, passive: true });
|
|
12611
|
+
win.addEventListener("blur", release);
|
|
11484
12612
|
reduced.addEventListener("change", sync);
|
|
11485
12613
|
win.addEventListener("resize", sync);
|
|
11486
12614
|
sync();
|
|
@@ -11490,12 +12618,17 @@ function mountHeroCanvas(document2, draw, report) {
|
|
|
11490
12618
|
observer.disconnect();
|
|
11491
12619
|
resize.disconnect();
|
|
11492
12620
|
canvas?.remove();
|
|
12621
|
+
titleCanvas?.remove();
|
|
11493
12622
|
document2.removeEventListener("visibilitychange", sync);
|
|
11494
12623
|
document2.removeEventListener("click", click);
|
|
12624
|
+
document2.removeEventListener("keydown", engage, { capture: true });
|
|
12625
|
+
document2.removeEventListener("keyup", release, { capture: true });
|
|
12626
|
+
win.removeEventListener("blur", release);
|
|
11495
12627
|
reduced.removeEventListener("change", sync);
|
|
11496
12628
|
win.removeEventListener("resize", sync);
|
|
11497
12629
|
root = null;
|
|
11498
12630
|
canvas = null;
|
|
12631
|
+
titleCanvas = null;
|
|
11499
12632
|
impulses = [];
|
|
11500
12633
|
};
|
|
11501
12634
|
}
|
|
@@ -11503,6 +12636,8 @@ function mountHeroCanvas(document2, draw, report) {
|
|
|
11503
12636
|
// src/client/features/new-session/ripples.ts
|
|
11504
12637
|
var GLYPHS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
11505
12638
|
var TAU = Math.PI * 2;
|
|
12639
|
+
var GLYPH_WIDTH = 8;
|
|
12640
|
+
var GLYPH_HEIGHT = 15;
|
|
11506
12641
|
var clamp3 = (value) => Math.max(0, Math.min(1, value));
|
|
11507
12642
|
var smooth = (from, to, value) => {
|
|
11508
12643
|
const t = clamp3((value - from) / (to - from));
|
|
@@ -11526,16 +12661,14 @@ function noise(x, y) {
|
|
|
11526
12661
|
) * 2 - 1;
|
|
11527
12662
|
}
|
|
11528
12663
|
function frameAt(elapsed, width, height) {
|
|
11529
|
-
const
|
|
11530
|
-
const cycle = Math.floor(elapsed / period);
|
|
11531
|
-
const age = elapsed % period / 1e3;
|
|
12664
|
+
const time = elapsed / 1e3;
|
|
11532
12665
|
return {
|
|
11533
|
-
time
|
|
12666
|
+
time,
|
|
11534
12667
|
scale: Math.max(320, Math.min(width, height, 900)),
|
|
11535
|
-
offsetX: hash(
|
|
11536
|
-
offsetY: hash(
|
|
11537
|
-
phase: hash(
|
|
11538
|
-
strength:
|
|
12668
|
+
offsetX: hash(0, 17) * 13,
|
|
12669
|
+
offsetY: hash(0, 43) * 13,
|
|
12670
|
+
phase: hash(0, 71) * TAU,
|
|
12671
|
+
strength: 0.9 + 0.1 * Math.sin(time * TAU / 12.8)
|
|
11539
12672
|
};
|
|
11540
12673
|
}
|
|
11541
12674
|
function fieldAt(x, y, frame) {
|
|
@@ -11558,83 +12691,235 @@ function fieldAt(x, y, frame) {
|
|
|
11558
12691
|
);
|
|
11559
12692
|
return strength * (crest * 0.46 + secondary * 0.14) * patches;
|
|
11560
12693
|
}
|
|
11561
|
-
function
|
|
11562
|
-
let light = 0;
|
|
12694
|
+
function clicksAt(elapsed, width, height, impulses) {
|
|
11563
12695
|
const scale = Math.max(320, Math.min(width, height, 900));
|
|
12696
|
+
const frames = [];
|
|
11564
12697
|
for (const impulse of impulses) {
|
|
11565
12698
|
const age = (elapsed - impulse.startedAt) / HERO_RIPPLE_DURATION;
|
|
11566
12699
|
if (age < 0 || age >= 1) continue;
|
|
11567
|
-
const
|
|
11568
|
-
const
|
|
11569
|
-
const
|
|
11570
|
-
const
|
|
11571
|
-
const
|
|
11572
|
-
|
|
11573
|
-
|
|
11574
|
-
|
|
11575
|
-
const texture = fieldAt(x, y, {
|
|
11576
|
-
time: 3 + age * 3.5,
|
|
12700
|
+
const growth = smooth(0, 0.2, age), radius = age * 1.25, thickness = 0.022 + age * 0.018;
|
|
12701
|
+
const contact = (1 - smooth(0.025, 0.12, age)) * 0.24;
|
|
12702
|
+
const margin = growth * (Math.SQRT2 * 0.12 + 0.053) + thickness * 4;
|
|
12703
|
+
const inner = contact > 0 ? 0 : Math.max(0, radius - 0.19 - margin);
|
|
12704
|
+
const outer = Math.max(radius + margin, contact > 0 ? 0.4 : 0);
|
|
12705
|
+
frames.push({
|
|
12706
|
+
x: impulse.x * width,
|
|
12707
|
+
y: impulse.y * height,
|
|
11577
12708
|
scale,
|
|
11578
|
-
|
|
11579
|
-
|
|
11580
|
-
|
|
11581
|
-
|
|
12709
|
+
age,
|
|
12710
|
+
seed: impulse.x * 7 + impulse.y * 11 + impulse.startedAt / 1e4,
|
|
12711
|
+
growth,
|
|
12712
|
+
radius,
|
|
12713
|
+
thickness,
|
|
12714
|
+
contact,
|
|
12715
|
+
fade: smooth(0, 0.045, age) * (1 - smooth(0.25, 1, age)),
|
|
12716
|
+
innerSquared: inner * inner,
|
|
12717
|
+
outerSquared: outer * outer
|
|
11582
12718
|
});
|
|
11583
|
-
const fade = smooth(0, 0.055, age) * (1 - smooth(0.12, 1, age));
|
|
11584
|
-
const contact = Math.exp(-(u * u + v * v) / 0.018) * (1 - smooth(0.035, 0.16, age)) * 0.32;
|
|
11585
|
-
light += (texture * spread * 1.6 + contact) * fade;
|
|
11586
12719
|
}
|
|
11587
|
-
return
|
|
11588
|
-
}
|
|
11589
|
-
function
|
|
11590
|
-
const
|
|
11591
|
-
|
|
11592
|
-
|
|
11593
|
-
|
|
11594
|
-
|
|
11595
|
-
|
|
12720
|
+
return frames;
|
|
12721
|
+
}
|
|
12722
|
+
function clickLightAt(x, y, frame) {
|
|
12723
|
+
const { scale, age, seed, growth, radius, thickness, fade, contact } = frame;
|
|
12724
|
+
const dx = (x - frame.x) / scale, dy = (y - frame.y) / scale;
|
|
12725
|
+
const squared = dx * dx + dy * dy;
|
|
12726
|
+
if (squared < frame.innerSquared || squared > frame.outerSquared) return 0;
|
|
12727
|
+
const u = dx + growth * 0.12 * noise(dx * 3.2 + seed + age * 0.5, dy * 3.2 - age * 0.4);
|
|
12728
|
+
const v = dy + growth * 0.12 * noise(dx * 3.1 + 23 - age * 0.5, dy * 3.1 + seed);
|
|
12729
|
+
const angle = Math.atan2(v, u);
|
|
12730
|
+
const distance = Math.hypot(u, v) + growth * (0.035 * Math.sin(angle * 3 + seed + age * 2) + 0.018 * Math.sin(angle * 5 - age * 3));
|
|
12731
|
+
let crests = 0;
|
|
12732
|
+
for (let i = 0; i < 3; i++) {
|
|
12733
|
+
const front = (distance - radius + i * 0.095) / thickness;
|
|
12734
|
+
if (Math.abs(front) <= 4) crests += Math.exp(-front * front) * (1 - i * 0.23);
|
|
12735
|
+
}
|
|
12736
|
+
const patches = crests > 0 ? 0.025 + 0.975 * smooth(
|
|
12737
|
+
-0.1,
|
|
12738
|
+
0.65,
|
|
12739
|
+
noise(u * 5 + seed + age * 0.3, v * 5 - age * 0.2)
|
|
12740
|
+
) : 0;
|
|
12741
|
+
const point = contact > 0 ? Math.exp(-(u * u + v * v) / 9e-3) * contact : 0;
|
|
12742
|
+
return (crests * patches * 0.65 + point) * fade;
|
|
12743
|
+
}
|
|
12744
|
+
var surfaces = /* @__PURE__ */ new WeakMap();
|
|
12745
|
+
function schemeOf2(canvas) {
|
|
12746
|
+
const document2 = canvas.ownerDocument;
|
|
12747
|
+
return `${document2?.documentElement?.getAttribute("data-dsh-ccd-style") ?? ""}|${document2?.body?.hasAttribute("data-ds-dark-theme") ?? false}`;
|
|
12748
|
+
}
|
|
12749
|
+
function surfaceOf(canvas, scheme) {
|
|
12750
|
+
const cached2 = surfaces.get(canvas);
|
|
12751
|
+
const ratio = Math.min(canvas.ownerDocument.defaultView.devicePixelRatio || 1, 2);
|
|
12752
|
+
if (cached2 !== void 0 && cached2.bitmapWidth === canvas.width && cached2.bitmapHeight === canvas.height && cached2.ratio === ratio && cached2.scheme === scheme) return cached2;
|
|
12753
|
+
const width = canvas.width / ratio, height = canvas.height / ratio;
|
|
12754
|
+
const step = 18;
|
|
12755
|
+
const columns = Math.max(1, Math.ceil(width / step) + 1), rows = Math.max(1, Math.ceil(height / step) + 1);
|
|
12756
|
+
const cells = [];
|
|
12757
|
+
for (let y = 0; y < height; y += GLYPH_HEIGHT) for (let x = 0; x < width; x += GLYPH_WIDTH) {
|
|
12758
|
+
const edge = Math.min(
|
|
12759
|
+
smooth(0, 55, x),
|
|
12760
|
+
smooth(0, 55, width - x),
|
|
12761
|
+
smooth(0, 16, y),
|
|
12762
|
+
smooth(0, 100, height - y)
|
|
12763
|
+
);
|
|
12764
|
+
if (edge === 0) continue;
|
|
12765
|
+
cells.push({
|
|
12766
|
+
x,
|
|
12767
|
+
y,
|
|
12768
|
+
index: Math.floor(y / step) * columns + Math.floor(x / step),
|
|
12769
|
+
fx: x / step % 1,
|
|
12770
|
+
fy: y / step % 1,
|
|
12771
|
+
grain: hash(Math.floor(x / GLYPH_WIDTH), Math.floor(y / GLYPH_HEIGHT)),
|
|
12772
|
+
edge,
|
|
12773
|
+
tick: -1,
|
|
12774
|
+
glyph: 0
|
|
12775
|
+
});
|
|
12776
|
+
}
|
|
12777
|
+
const surface = {
|
|
12778
|
+
bitmapWidth: canvas.width,
|
|
12779
|
+
bitmapHeight: canvas.height,
|
|
12780
|
+
width,
|
|
12781
|
+
height,
|
|
12782
|
+
ratio,
|
|
12783
|
+
step,
|
|
12784
|
+
columns,
|
|
12785
|
+
rows,
|
|
12786
|
+
cells,
|
|
12787
|
+
light: new Float32Array(columns * rows),
|
|
12788
|
+
scheme,
|
|
12789
|
+
ink: cached2?.scheme === scheme ? cached2.ink : ""
|
|
12790
|
+
};
|
|
12791
|
+
surfaces.set(canvas, surface);
|
|
12792
|
+
return surface;
|
|
11596
12793
|
}
|
|
11597
12794
|
function drawHeroRipples(canvas, elapsed, impulses = []) {
|
|
11598
12795
|
const context = canvas.getContext("2d");
|
|
11599
12796
|
if (!context) return;
|
|
11600
|
-
const
|
|
11601
|
-
const
|
|
12797
|
+
const surface = surfaceOf(canvas, schemeOf2(canvas));
|
|
12798
|
+
const { width, height, ratio, step, columns, rows, light } = surface;
|
|
11602
12799
|
context.setTransform(ratio, 0, 0, ratio, 0, 0);
|
|
11603
12800
|
context.clearRect(0, 0, width, height);
|
|
11604
12801
|
if (!width || !height) return;
|
|
11605
|
-
|
|
11606
|
-
context.
|
|
12802
|
+
if (surface.ink === "") surface.ink = getComputedStyle(canvas).getPropertyValue("--ccd-hero-ripple-ink").trim();
|
|
12803
|
+
context.fillStyle = surface.ink;
|
|
12804
|
+
context.font = "10px monospace";
|
|
11607
12805
|
context.textBaseline = "top";
|
|
12806
|
+
if (!surface.atlas && canvas.ownerDocument.createElement) {
|
|
12807
|
+
const atlas = canvas.ownerDocument.createElement("canvas");
|
|
12808
|
+
atlas.width = GLYPHS.length * GLYPH_WIDTH * ratio;
|
|
12809
|
+
atlas.height = GLYPH_HEIGHT * ratio;
|
|
12810
|
+
const ink = atlas.getContext("2d");
|
|
12811
|
+
if (ink) {
|
|
12812
|
+
ink.setTransform(ratio, 0, 0, ratio, 0, 0);
|
|
12813
|
+
ink.fillStyle = surface.ink;
|
|
12814
|
+
ink.font = context.font;
|
|
12815
|
+
ink.textBaseline = "top";
|
|
12816
|
+
for (let i = 0; i < GLYPHS.length; i++) ink.fillText(GLYPHS[i], i * GLYPH_WIDTH, 0);
|
|
12817
|
+
surface.atlas = atlas;
|
|
12818
|
+
}
|
|
12819
|
+
}
|
|
12820
|
+
if (surface.atlas && !surface.layers) {
|
|
12821
|
+
const glyphs = canvas.ownerDocument.createElement("canvas");
|
|
12822
|
+
glyphs.width = canvas.width;
|
|
12823
|
+
glyphs.height = canvas.height;
|
|
12824
|
+
const mask = canvas.ownerDocument.createElement("canvas");
|
|
12825
|
+
mask.width = Math.ceil(width / GLYPH_WIDTH);
|
|
12826
|
+
mask.height = Math.ceil(height / GLYPH_HEIGHT);
|
|
12827
|
+
const glyphContext = glyphs.getContext("2d"), maskContext = mask.getContext("2d");
|
|
12828
|
+
if (glyphContext && maskContext?.createImageData) {
|
|
12829
|
+
glyphContext.setTransform(ratio, 0, 0, ratio, 0, 0);
|
|
12830
|
+
const pixels = maskContext.createImageData(mask.width, mask.height);
|
|
12831
|
+
pixels.data.fill(255);
|
|
12832
|
+
for (let i = 3; i < pixels.data.length; i += 4) pixels.data[i] = 0;
|
|
12833
|
+
surface.layers = { glyphs, mask, context: glyphContext, maskContext, pixels };
|
|
12834
|
+
}
|
|
12835
|
+
}
|
|
12836
|
+
const layers = surface.layers;
|
|
11608
12837
|
const frame = frameAt(elapsed, width, height);
|
|
11609
12838
|
const fadeIn = smooth(0, 1.4, elapsed / 1e3);
|
|
11610
|
-
const step = 18;
|
|
11611
|
-
const columns = Math.ceil(width / step) + 1, rows = Math.ceil(height / step) + 1;
|
|
11612
|
-
const light = new Float32Array(columns * rows);
|
|
11613
12839
|
for (let row = 0; row < rows; row++) for (let col = 0; col < columns; col++) {
|
|
11614
|
-
light[row * columns + col] = fieldAt(col * step, row * step, frame) * fadeIn
|
|
12840
|
+
light[row * columns + col] = fieldAt(col * step, row * step, frame) * fadeIn;
|
|
11615
12841
|
}
|
|
11616
|
-
for (
|
|
11617
|
-
const
|
|
11618
|
-
|
|
11619
|
-
|
|
11620
|
-
|
|
11621
|
-
|
|
11622
|
-
|
|
11623
|
-
|
|
11624
|
-
|
|
11625
|
-
|
|
11626
|
-
|
|
11627
|
-
|
|
11628
|
-
|
|
11629
|
-
|
|
11630
|
-
|
|
11631
|
-
|
|
11632
|
-
|
|
11633
|
-
|
|
11634
|
-
|
|
12842
|
+
for (const click of clicksAt(elapsed, width, height, impulses)) {
|
|
12843
|
+
const reach = Math.sqrt(click.outerSquared) * click.scale;
|
|
12844
|
+
const left = Math.max(0, Math.ceil((click.x - reach) / step));
|
|
12845
|
+
const right = Math.min(columns - 1, Math.floor((click.x + reach) / step));
|
|
12846
|
+
const top = Math.max(0, Math.ceil((click.y - reach) / step));
|
|
12847
|
+
const bottom = Math.min(rows - 1, Math.floor((click.y + reach) / step));
|
|
12848
|
+
for (let row = top; row <= bottom; row++) for (let col = left; col <= right; col++) {
|
|
12849
|
+
light[row * columns + col] += clickLightAt(col * step, row * step, click);
|
|
12850
|
+
}
|
|
12851
|
+
}
|
|
12852
|
+
for (const cell of surface.cells) {
|
|
12853
|
+
const { x, y, index, fx, fy, grain, edge } = cell;
|
|
12854
|
+
const illumination = mix2(
|
|
12855
|
+
mix2(light[index], light[index + 1], fx),
|
|
12856
|
+
mix2(light[index + columns], light[index + columns + 1], fx),
|
|
12857
|
+
fy
|
|
12858
|
+
);
|
|
12859
|
+
const alpha = Math.min(0.72, (0.012 + grain * 0.013) * fadeIn + illumination * 1.65 * (0.45 + 0.55 * grain)) * edge;
|
|
12860
|
+
if (layers) {
|
|
12861
|
+
const pixel = (Math.floor(y / GLYPH_HEIGHT) * layers.mask.width + Math.floor(x / GLYPH_WIDTH)) * 4 + 3;
|
|
12862
|
+
layers.pixels.data[pixel] = alpha > 6e-3 ? Math.round(alpha * 255) : 0;
|
|
12863
|
+
}
|
|
12864
|
+
if (alpha <= 6e-3) continue;
|
|
12865
|
+
const tick = Math.floor(elapsed / (300 + grain * 400) + grain * 17);
|
|
12866
|
+
const changed = tick !== cell.tick;
|
|
12867
|
+
if (changed) {
|
|
12868
|
+
cell.tick = tick;
|
|
12869
|
+
cell.glyph = Math.floor(hash(
|
|
12870
|
+
Math.floor(x / GLYPH_WIDTH) + tick * 1013,
|
|
12871
|
+
Math.floor(y / GLYPH_HEIGHT)
|
|
12872
|
+
) * GLYPHS.length);
|
|
12873
|
+
}
|
|
12874
|
+
if (layers && surface.atlas) {
|
|
12875
|
+
if (changed) {
|
|
12876
|
+
layers.context.clearRect(x, y, GLYPH_WIDTH, GLYPH_HEIGHT);
|
|
12877
|
+
layers.context.drawImage(
|
|
12878
|
+
surface.atlas,
|
|
12879
|
+
cell.glyph * GLYPH_WIDTH * ratio,
|
|
12880
|
+
0,
|
|
12881
|
+
GLYPH_WIDTH * ratio,
|
|
12882
|
+
GLYPH_HEIGHT * ratio,
|
|
12883
|
+
x,
|
|
12884
|
+
y,
|
|
12885
|
+
GLYPH_WIDTH,
|
|
12886
|
+
GLYPH_HEIGHT
|
|
12887
|
+
);
|
|
12888
|
+
}
|
|
12889
|
+
continue;
|
|
11635
12890
|
}
|
|
12891
|
+
context.globalAlpha = alpha;
|
|
12892
|
+
if (surface.atlas) {
|
|
12893
|
+
context.drawImage(
|
|
12894
|
+
surface.atlas,
|
|
12895
|
+
cell.glyph * GLYPH_WIDTH * ratio,
|
|
12896
|
+
0,
|
|
12897
|
+
GLYPH_WIDTH * ratio,
|
|
12898
|
+
GLYPH_HEIGHT * ratio,
|
|
12899
|
+
x,
|
|
12900
|
+
y,
|
|
12901
|
+
GLYPH_WIDTH,
|
|
12902
|
+
GLYPH_HEIGHT
|
|
12903
|
+
);
|
|
12904
|
+
} else context.fillText(GLYPHS[cell.glyph], x, y);
|
|
11636
12905
|
}
|
|
11637
12906
|
context.globalAlpha = 1;
|
|
12907
|
+
if (layers) {
|
|
12908
|
+
layers.maskContext.putImageData(layers.pixels, 0, 0);
|
|
12909
|
+
context.drawImage(layers.glyphs, 0, 0, width, height);
|
|
12910
|
+
const smoothing = context.imageSmoothingEnabled;
|
|
12911
|
+
context.imageSmoothingEnabled = false;
|
|
12912
|
+
context.globalCompositeOperation = "destination-in";
|
|
12913
|
+
context.drawImage(
|
|
12914
|
+
layers.mask,
|
|
12915
|
+
0,
|
|
12916
|
+
0,
|
|
12917
|
+
layers.mask.width * GLYPH_WIDTH,
|
|
12918
|
+
layers.mask.height * GLYPH_HEIGHT
|
|
12919
|
+
);
|
|
12920
|
+
context.globalCompositeOperation = "source-over";
|
|
12921
|
+
context.imageSmoothingEnabled = smoothing;
|
|
12922
|
+
}
|
|
11638
12923
|
}
|
|
11639
12924
|
|
|
11640
12925
|
// src/client/features/new-session/new-session.css
|
|
@@ -11722,6 +13007,19 @@ html[data-dsh-ccd-style="true"] .ccd-hero-ripples {
|
|
|
11722
13007
|
@media (prefers-reduced-motion: reduce) {
|
|
11723
13008
|
html[data-dsh-ccd-style="true"] .ccd-hero-ripples { display: none; }
|
|
11724
13009
|
}
|
|
13010
|
+
|
|
13011
|
+
/* Let the background show through the conversation's own header. */
|
|
13012
|
+
html[data-dsh-ccd-style="true"] .ST7X_W_root[data-phase="hero"] .ST7X_W_header {
|
|
13013
|
+
background: transparent;
|
|
13014
|
+
}
|
|
13015
|
+
|
|
13016
|
+
/* The caption strip is outside the hero; this decorative layer never receives
|
|
13017
|
+
pointer events, so native caption buttons and window dragging stay native. */
|
|
13018
|
+
html[data-dsh-ccd-style="true"] .ccd-hero-ripples-titlebar {
|
|
13019
|
+
position: fixed;
|
|
13020
|
+
inset: 0 auto auto;
|
|
13021
|
+
z-index: 1;
|
|
13022
|
+
}
|
|
11725
13023
|
`;
|
|
11726
13024
|
|
|
11727
13025
|
// src/client/features/new-session/index.ts
|
|
@@ -11755,7 +13053,6 @@ function mountConversationFade(document2, report) {
|
|
|
11755
13053
|
const host = hostSelectors(document2);
|
|
11756
13054
|
let scroller = null;
|
|
11757
13055
|
let page = null;
|
|
11758
|
-
let scheduled = 0;
|
|
11759
13056
|
const paint = () => {
|
|
11760
13057
|
if (scroller === null || !scroller.isConnected) return;
|
|
11761
13058
|
const state = edgeFadeState(scroller.scrollTop, scroller.scrollHeight, scroller.clientHeight);
|
|
@@ -11769,7 +13066,6 @@ function mountConversationFade(document2, report) {
|
|
|
11769
13066
|
if (page !== null) publishInset(page, composerInset(scroller, document2));
|
|
11770
13067
|
};
|
|
11771
13068
|
const sync = () => {
|
|
11772
|
-
scheduled = 0;
|
|
11773
13069
|
const next = document2.querySelector(ANCHOR.conversationScroll);
|
|
11774
13070
|
if (next !== scroller) {
|
|
11775
13071
|
if (scroller !== null) {
|
|
@@ -11781,18 +13077,14 @@ function mountConversationFade(document2, report) {
|
|
|
11781
13077
|
}
|
|
11782
13078
|
paint();
|
|
11783
13079
|
};
|
|
11784
|
-
const
|
|
11785
|
-
|
|
11786
|
-
|
|
11787
|
-
try {
|
|
11788
|
-
sync();
|
|
11789
|
-
} finally {
|
|
11790
|
-
scheduled = 0;
|
|
11791
|
-
}
|
|
11792
|
-
};
|
|
13080
|
+
const frames = frameScheduler(sync, document2.defaultView);
|
|
13081
|
+
const schedule = () => frames.schedule();
|
|
13082
|
+
const relevant = (records) => recordsTouch(records, scroller) || recordsTouch(records, page);
|
|
11793
13083
|
let observer;
|
|
11794
13084
|
try {
|
|
11795
|
-
observer = new MutationObserver(
|
|
13085
|
+
observer = new MutationObserver((records) => {
|
|
13086
|
+
if (relevant(records)) schedule();
|
|
13087
|
+
});
|
|
11796
13088
|
observer.observe(document2.body, {
|
|
11797
13089
|
childList: true,
|
|
11798
13090
|
subtree: true,
|
|
@@ -11803,6 +13095,7 @@ function mountConversationFade(document2, report) {
|
|
|
11803
13095
|
window.addEventListener("resize", schedule);
|
|
11804
13096
|
sync();
|
|
11805
13097
|
} catch (error) {
|
|
13098
|
+
frames.cancel();
|
|
11806
13099
|
observer?.disconnect();
|
|
11807
13100
|
window.removeEventListener("resize", schedule);
|
|
11808
13101
|
report(error);
|
|
@@ -11810,7 +13103,7 @@ function mountConversationFade(document2, report) {
|
|
|
11810
13103
|
};
|
|
11811
13104
|
}
|
|
11812
13105
|
return () => {
|
|
11813
|
-
|
|
13106
|
+
frames.cancel();
|
|
11814
13107
|
observer?.disconnect();
|
|
11815
13108
|
window.removeEventListener("resize", schedule);
|
|
11816
13109
|
if (scroller !== null) {
|
|
@@ -11845,6 +13138,170 @@ function clear(scroller) {
|
|
|
11845
13138
|
scroller.removeAttribute(FADE_BOTTOM_ATTRIBUTE);
|
|
11846
13139
|
}
|
|
11847
13140
|
|
|
13141
|
+
// src/client/compat/conversation-process.ts
|
|
13142
|
+
function mountConversationProcess(document2, report) {
|
|
13143
|
+
let released = false;
|
|
13144
|
+
let pending = false;
|
|
13145
|
+
const open = (button) => {
|
|
13146
|
+
if (button.disabled || button.getAttribute("aria-expanded") !== "false") return;
|
|
13147
|
+
const focused = document2.activeElement;
|
|
13148
|
+
button.click();
|
|
13149
|
+
if (document2.activeElement === button) {
|
|
13150
|
+
button.blur();
|
|
13151
|
+
if (focused?.isConnected && "focus" in focused) {
|
|
13152
|
+
focused.focus({ preventScroll: true });
|
|
13153
|
+
}
|
|
13154
|
+
}
|
|
13155
|
+
};
|
|
13156
|
+
const relevant = (record) => {
|
|
13157
|
+
if (record.type === "attributes") {
|
|
13158
|
+
const target = record.target;
|
|
13159
|
+
return typeof target.matches === "function" && target.matches(`${ANCHOR.turnProcess}, ${ANCHOR.stepProcessToggle}`) && target.getAttribute("aria-expanded") === "false";
|
|
13160
|
+
}
|
|
13161
|
+
return record.addedNodes.length > 0;
|
|
13162
|
+
};
|
|
13163
|
+
const act = (records) => {
|
|
13164
|
+
if (!records.some(relevant)) return;
|
|
13165
|
+
for (const record of records) {
|
|
13166
|
+
if (!relevant(record)) continue;
|
|
13167
|
+
if (record.type === "attributes") {
|
|
13168
|
+
const target = record.target;
|
|
13169
|
+
if (typeof target.click === "function" && target.getAttribute("aria-expanded") === "false") {
|
|
13170
|
+
open(target);
|
|
13171
|
+
}
|
|
13172
|
+
continue;
|
|
13173
|
+
}
|
|
13174
|
+
for (const node of record.addedNodes) {
|
|
13175
|
+
if (node.nodeType !== 1) continue;
|
|
13176
|
+
const element = node;
|
|
13177
|
+
if (typeof element.matches !== "function") continue;
|
|
13178
|
+
if (element.matches(`${ANCHOR.turnProcess}, ${ANCHOR.stepProcessToggle}`)) {
|
|
13179
|
+
open(element);
|
|
13180
|
+
}
|
|
13181
|
+
for (const button of element.querySelectorAll(
|
|
13182
|
+
`${ANCHOR.turnProcess}, ${ANCHOR.stepProcessToggle}`
|
|
13183
|
+
)) open(button);
|
|
13184
|
+
}
|
|
13185
|
+
}
|
|
13186
|
+
};
|
|
13187
|
+
const sync = (records) => {
|
|
13188
|
+
pending = false;
|
|
13189
|
+
if (released) return;
|
|
13190
|
+
try {
|
|
13191
|
+
if (records === void 0) {
|
|
13192
|
+
for (const button of document2.querySelectorAll(
|
|
13193
|
+
`${ANCHOR.turnProcess}, ${ANCHOR.stepProcessToggle}`
|
|
13194
|
+
)) open(button);
|
|
13195
|
+
return;
|
|
13196
|
+
}
|
|
13197
|
+
act(records);
|
|
13198
|
+
} catch (error) {
|
|
13199
|
+
report(error);
|
|
13200
|
+
}
|
|
13201
|
+
};
|
|
13202
|
+
let observer;
|
|
13203
|
+
try {
|
|
13204
|
+
observer = new MutationObserver((records) => {
|
|
13205
|
+
if (pending || released) {
|
|
13206
|
+
if (!released) {
|
|
13207
|
+
try {
|
|
13208
|
+
act(records);
|
|
13209
|
+
} catch (error) {
|
|
13210
|
+
report(error);
|
|
13211
|
+
}
|
|
13212
|
+
}
|
|
13213
|
+
return;
|
|
13214
|
+
}
|
|
13215
|
+
pending = true;
|
|
13216
|
+
const batch = records.slice();
|
|
13217
|
+
queueMicrotask(() => sync(batch));
|
|
13218
|
+
});
|
|
13219
|
+
observer.observe(document2.body, {
|
|
13220
|
+
subtree: true,
|
|
13221
|
+
childList: true,
|
|
13222
|
+
attributes: true,
|
|
13223
|
+
attributeFilter: ["aria-expanded", "disabled"]
|
|
13224
|
+
});
|
|
13225
|
+
} catch (error) {
|
|
13226
|
+
observer?.disconnect();
|
|
13227
|
+
report(error);
|
|
13228
|
+
return () => {
|
|
13229
|
+
};
|
|
13230
|
+
}
|
|
13231
|
+
sync();
|
|
13232
|
+
return () => {
|
|
13233
|
+
released = true;
|
|
13234
|
+
observer?.disconnect();
|
|
13235
|
+
};
|
|
13236
|
+
}
|
|
13237
|
+
|
|
13238
|
+
// src/client/compat/reply-signature.ts
|
|
13239
|
+
var REPLY_SIGNATURE_SEAT = '[data-turn-tail][data-actions-reveal="always"]:has(> [data-clock="end"])';
|
|
13240
|
+
function mountReplySignature(document2, report) {
|
|
13241
|
+
let button = null;
|
|
13242
|
+
let animation;
|
|
13243
|
+
let released = false;
|
|
13244
|
+
let pending = false;
|
|
13245
|
+
const clear2 = () => {
|
|
13246
|
+
animation?.cancel();
|
|
13247
|
+
animation = void 0;
|
|
13248
|
+
if (button) {
|
|
13249
|
+
button.onclick = null;
|
|
13250
|
+
button.remove();
|
|
13251
|
+
button = null;
|
|
13252
|
+
}
|
|
13253
|
+
};
|
|
13254
|
+
const sync = () => {
|
|
13255
|
+
pending = false;
|
|
13256
|
+
if (released) return;
|
|
13257
|
+
try {
|
|
13258
|
+
const seat = document2.querySelector(REPLY_SIGNATURE_SEAT);
|
|
13259
|
+
if (button?.parentElement !== seat) clear2();
|
|
13260
|
+
if (!seat) return;
|
|
13261
|
+
if (!button) {
|
|
13262
|
+
button = document2.createElement("button");
|
|
13263
|
+
button.type = "button";
|
|
13264
|
+
button.className = "ccd-reply-signature";
|
|
13265
|
+
button.onclick = () => {
|
|
13266
|
+
animation?.cancel();
|
|
13267
|
+
if (document2.defaultView?.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
|
13268
|
+
animation = button?.animate([
|
|
13269
|
+
{ transform: "rotate(0deg)" },
|
|
13270
|
+
{ transform: "rotate(-18deg)" },
|
|
13271
|
+
{ transform: "rotate(16deg)" },
|
|
13272
|
+
{ transform: "rotate(-11deg)" },
|
|
13273
|
+
{ transform: "rotate(7deg)" },
|
|
13274
|
+
{ transform: "rotate(0deg)" }
|
|
13275
|
+
], { duration: 700, easing: "ease-in-out" });
|
|
13276
|
+
};
|
|
13277
|
+
seat.append(button);
|
|
13278
|
+
}
|
|
13279
|
+
const label = document2.documentElement.lang.startsWith("zh") ? "\u6643\u6643\u9CB8\u9C7C\u5C3E\u5DF4" : "Wiggle the whale tail";
|
|
13280
|
+
button.setAttribute("aria-label", label);
|
|
13281
|
+
button.title = label;
|
|
13282
|
+
} catch (error) {
|
|
13283
|
+
report(error);
|
|
13284
|
+
}
|
|
13285
|
+
};
|
|
13286
|
+
const observer = new MutationObserver(() => {
|
|
13287
|
+
if (pending || released) return;
|
|
13288
|
+
pending = true;
|
|
13289
|
+
queueMicrotask(sync);
|
|
13290
|
+
});
|
|
13291
|
+
observer.observe(document2.documentElement, {
|
|
13292
|
+
subtree: true,
|
|
13293
|
+
childList: true,
|
|
13294
|
+
attributes: true,
|
|
13295
|
+
attributeFilter: ["data-actions-reveal", "lang"]
|
|
13296
|
+
});
|
|
13297
|
+
sync();
|
|
13298
|
+
return () => {
|
|
13299
|
+
released = true;
|
|
13300
|
+
observer.disconnect();
|
|
13301
|
+
clear2();
|
|
13302
|
+
};
|
|
13303
|
+
}
|
|
13304
|
+
|
|
11848
13305
|
// src/client/compat/view-switch.ts
|
|
11849
13306
|
var VIEW_OFFSET_PROPERTY = "--ccd-view-x";
|
|
11850
13307
|
var VIEW_WIDTH_PROPERTY = "--ccd-view-w";
|
|
@@ -11883,6 +13340,24 @@ function mountViewSwitch(document2, icons, report) {
|
|
|
11883
13340
|
entries.set(name, { original, drawn: value });
|
|
11884
13341
|
element.setAttribute(name, value);
|
|
11885
13342
|
};
|
|
13343
|
+
const release = (element) => {
|
|
13344
|
+
const entries = attributes.get(element);
|
|
13345
|
+
if (entries !== void 0) {
|
|
13346
|
+
for (const [name, record] of entries) {
|
|
13347
|
+
if (element.getAttribute(name) !== record.drawn) continue;
|
|
13348
|
+
if (record.original === null) element.removeAttribute(name);
|
|
13349
|
+
else element.setAttribute(name, record.original);
|
|
13350
|
+
}
|
|
13351
|
+
attributes.delete(element);
|
|
13352
|
+
}
|
|
13353
|
+
for (const glyph of element.querySelectorAll(`[${VIEW_GLYPH_ATTRIBUTE}]`)) glyphs.delete(glyph);
|
|
13354
|
+
};
|
|
13355
|
+
const prune = () => {
|
|
13356
|
+
for (const glyph of glyphs) if (!glyph.isConnected) glyphs.delete(glyph);
|
|
13357
|
+
for (const element of [...attributes.keys()]) {
|
|
13358
|
+
if (!element.isConnected) release(element);
|
|
13359
|
+
}
|
|
13360
|
+
};
|
|
11886
13361
|
const decorate = (tablist) => {
|
|
11887
13362
|
for (const tab of tablist.querySelectorAll(selectors.conversationTab)) {
|
|
11888
13363
|
const label = tab.textContent?.trim() ?? "";
|
|
@@ -11899,7 +13374,7 @@ function mountViewSwitch(document2, icons, report) {
|
|
|
11899
13374
|
tab.prepend(glyph);
|
|
11900
13375
|
glyphs.add(glyph);
|
|
11901
13376
|
}
|
|
11902
|
-
|
|
13377
|
+
prune();
|
|
11903
13378
|
};
|
|
11904
13379
|
const dock = (tablist) => {
|
|
11905
13380
|
const next = document2.querySelector(selectors.sidebarColumn);
|
|
@@ -11934,9 +13409,11 @@ function mountViewSwitch(document2, icons, report) {
|
|
|
11934
13409
|
scheduled = 0;
|
|
11935
13410
|
const next = document2.querySelector(selectors.conversationTabs);
|
|
11936
13411
|
if (next !== tabs) {
|
|
13412
|
+
if (tabs !== null) release(tabs);
|
|
11937
13413
|
tabs = next;
|
|
11938
13414
|
selected = null;
|
|
11939
13415
|
}
|
|
13416
|
+
prune();
|
|
11940
13417
|
if (tabs === null || !tabs.isConnected) return;
|
|
11941
13418
|
decorate(tabs);
|
|
11942
13419
|
dock(tabs);
|
|
@@ -11974,7 +13451,9 @@ function mountViewSwitch(document2, icons, report) {
|
|
|
11974
13451
|
attributes.clear();
|
|
11975
13452
|
};
|
|
11976
13453
|
try {
|
|
11977
|
-
observer = new MutationObserver(
|
|
13454
|
+
observer = new MutationObserver((records) => {
|
|
13455
|
+
if (tabs !== null && recordsTouch(records, tabs) || column !== null && recordsTouch(records, column) || recordsIntroduce(records, `${selectors.conversationTabs}, ${selectors.sidebarColumn}`)) schedule();
|
|
13456
|
+
});
|
|
11978
13457
|
observer.observe(document2.body, {
|
|
11979
13458
|
childList: true,
|
|
11980
13459
|
subtree: true,
|
|
@@ -12017,7 +13496,7 @@ function round(value) {
|
|
|
12017
13496
|
}
|
|
12018
13497
|
|
|
12019
13498
|
// src/client/features/conversation/conversation.css
|
|
12020
|
-
var conversation_default = '/*\n * Conversation page: header chrome, transcript column and message surfaces.\n *\n * The transcript, its scroll behaviour, tool rendering and approval surfaces\n * stay entirely DSH\'s; only width, type and surface colours change. Host\n * classes are pinned in src/client/compat/host-dom.ts.\n */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_root {\n container: ccd-conversation / inline-size;\n}\n\n/*\n * Transcript width handles: hidden here, because this interface removed what\n * they control (17).\n *\n * `ui-conversation`\'s `ConversationWidthControls` renders one handle per side as\n * the Conversation body\'s last child (ConversationRoot.module.css,\n * `ST7X_W_widthHandle`, stable hook `data-width-handle`; component\n * `skeleton/ConversationWidthControls.tsx`). Each is a transparent full-height\n * strip \u2014 `position: absolute; top: 0; bottom: 0`, `cursor: col-resize`,\n * `width: min(10px, (100% - var(--dsh-chat-content-width)) / 2 - 48px)` \u2014 parked\n * 24px outside the transcript column, whose only ink is the 2px rule its own\n * `::after` reveals on hover or while dragging. Dragging one publishes\n * `--dsh-chat-user-width` on the Conversation root.\n *\n * That property has exactly one consumer in the host, the body rule\n * `--dsh-chat-content-width: var(--dsh-chat-user-width, clamp(680px, \u2026))`, and\n * this interface replaces it: `theme/composer.css` pins\n * `--dsh-chat-content-width: var(--ccd-composer-width)` (770px) on the same\n * element with higher specificity. A drag therefore changes nothing \u2014 not the\n * transcript column, the user stack, the jump-to-bottom slot or the wide-table\n * bleed \u2014 while the handle keeps its pointer capture, its resize cursor and its\n * hover rule. It is a false affordance whose state cannot be reached: hiding it\n * takes away no behaviour, and with the plugin off the host\'s own rule returns\n * and the handles drag again.\n *\n * `display: none`, not `visibility` or a transparent paint, and scoped to the\n * Conversation root so `ui-layout`\'s real column handles (`_6Qf49G_handle`, 8px,\n * on the sidebar and rightbar tracks) are never touched. The handle is an\n * absolutely positioned, empty box outside the body\'s flex flow, so removing it\n * cannot move a sibling: transcript width, the scroll viewport\'s stable\n * scrollbar gutter and the page\'s scroll range keep the values they had.\n */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_root [data-width-handle] {\n display: none;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_header {\n background: var(--ccd-canvas);\n color: var(--ccd-text);\n /*\n * The reference has no rule between the header and the transcript \u2014 the two\n * read as one page. The host draws a hairline here (`border-bottom: .5px\n * solid`), so it is painted out rather than removed: keeping the border\'s\n * width leaves the header\'s own height untouched.\n */\n border-bottom-color: transparent;\n /*\n * One header row. DSH stacks a title row and, below it, its View tablist; the\n * reference keeps every piece of header chrome on the title line. The two\n * wrapper boxes dissolve (`display: contents`) instead of being re-parented,\n * so the host\'s own children stay where React put them and only their order\n * changes: title, View switcher, Agent-preset chip, utilities, corner.\n */\n display: flex;\n align-items: center;\n /* The host reserves the second row only while its tablist is a row of its\n own; that row is gone, so its `min-height: 76px` goes with it. */\n min-height: 0;\n padding: 10px var(--ccd-header-trailing-inset) 10px 12px;\n /*\n * Where the Composer\'s input surface ends, measured from the column\'s right\n * edge in the numbers that draw it, for the narrow-column rules below: the\n * Composer root\'s own side clearance (`theme/composer.css` pins the host\'s\n * `--dsh-composer-side-clearance` at 16px), the stable scrollbar gutter the\n * page\'s scroll viewport reserves (`ST7X_W_scrollBody { scrollbar-gutter:\n * stable }` over the theme\'s 5px `--dsh-scrollbar-width` track) and that\n * viewport\'s own `margin-right: 2px`.\n */\n --ccd-composer-trailing-edge: calc(\n var(--dsh-composer-side-clearance, 16px) + var(--dsh-scrollbar-width, 5px) + 2px);\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerBlank {\n /* Blank Sessions keep the single-row height they had as a grid row. */\n min-height: 40px;\n padding-bottom: 0;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerSessionless {\n min-height: 50px;\n}\n\n/*\n * On macOS the title line shares the window-control row\'s centre line: the\n * traffic lights, the collapse toggle (`top: 11px` + 28 / 2) and the parked\n * switcher (`top: 12px` + 26 / 2) all centre on y = 25. The flex items above\n * centre on the header\'s content box, and with the base padding (10px top and\n * bottom) that box centred on 20 \u2014 the title, the team chip and the right-hand\n * panel buttons sat 5px above the line they are meant to share.\n *\n * Holding the header at 40px \u2014 the height it already has, with 20px items \u2014\n * and dropping the bottom padding leaves a 30px content box from y = 10 to\n * y = 40 whatever the items\' own heights (up to 30px): its centre is 25. A\n * collapsed inline switcher (26px) therefore centres on the same line too.\n * Windows has no such row, so its header keeps the base rule, and the\n * sessionless header already centres on 25 with its own 10px bottom padding.\n */\n/* Only macOS shares the conversation header with the window drag surface.\n The host excludes its controls from dragging only on darwin; Windows has a\n separate caption strip, so making this row draggable swallows button clicks. */\nhtml[data-dsh-ccd-style="true"][data-platform="darwin"] .ST7X_W_header {\n -webkit-app-region: drag;\n}\n\nhtml[data-dsh-ccd-style="true"][data-platform="darwin"] .ST7X_W_header:not(.ST7X_W_headerSessionless) {\n min-height: 40px;\n padding-bottom: 0;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_titleRow,\nhtml[data-dsh-ccd-style="true"] .ST7X_W_titleCluster {\n display: contents;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_crumbs {\n /* Stable natural title width (capped at 112px) keeps the following switcher\n anchored while the rightbar changes column width. */\n flex: none;\n box-sizing: border-box;\n max-width: calc(112px + max(0px, var(--dsh-frame-leading-clearance, 0px) - 12px));\n padding-inline-start: max(0px, calc(var(--dsh-frame-leading-clearance, 0px) - 12px));\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerActions {\n order: 2;\n /* Stable gap between switcher and team entry.\n `margin-right: auto` is the header\'s flexible gap: the title, the View\n switcher and the chip stay grouped at the leading edge, as in the\n reference, and the utilities keep the trailing edge they had before. The\n chip always renders, so the gap survives an empty tools group. */\n /* The preceding title has 8px end padding + the track\'s 2px margin.\n Give the track-to-team boundary the same visible 10px separation. */\n margin-left: var(--ccd-header-group-gap, 10px);\n gap: 4px;\n margin-right: auto;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities {\n order: 3;\n margin-left: 0;\n /* One uniform gap, so every visible space in the cluster is equal. */\n gap: var(--ccd-header-action-gap);\n}\n\n/*\n * The corner seat is retired, and the utilities group owns the trailing edge.\n *\n * That single cell\'s shipped occupant is `ui-sidebar-right`\'s collapsed-panel\n * expand control, which this interface does not draw; the plugin shadows it with\n * an entry that renders nothing (`features/conversation/header-actions/entries.ts`)\n * and keeps the expand action in the Session menu instead. So the seat has no\n * box of its own to place \u2014 its own gap in front of the utilities would be 2px\n * of trailing space after the last control, and the reference measures the\n * cluster\'s inset from the header\'s own padding. A Blank Session renders the\n * same empty seat, which is why the host\'s `margin-left: auto` for that row\n * needs no answer here: there is nothing left in the corner to place either way.\n */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerCorner {\n margin: 0;\n}\n\n/*\n * View switcher: the host\'s own `role="tablist"`, reshaped into the reference\'s\n * segmented control. The selected segment is drawn by one thumb \u2014 the\n * `::before` of the tablist \u2014 moved with the geometry published by\n * `compat/view-switch.ts`; the buttons keep their roles, labels and handlers,\n * and the host\'s own underline is dropped.\n *\n * Geometry is the reference\'s own pill (docs/issues/img/todo-sidebar-sessions-target.webp,\n * @2x): a 66 \xD7 26 track \u2014 131 \xD7 52 device px, so two 33px segments \u2014 6px\n * corner, with the raised thumb inset 1px top and bottom and carrying a 5px\n * corner, a 1px outline and the segment shadow. The two segments are the icon\n * buttons below, not text: `compat/view-switch.ts` swaps each shipped View\'s\n * label for the bundled artwork in `view-switch-icons.ts`.\n */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_tabs {\n order: 1;\n position: relative;\n /* Never squeezed: a narrow header truncates the title instead. */\n flex: none;\n display: inline-flex;\n align-items: stretch;\n gap: 0;\n /* The switcher sits directly after the Session title; later header chrome\n (the Agent-preset chip today, more to come) follows it. */\n margin: 0 0 0 var(--ccd-header-group-gap, 2px);\n padding: 0;\n border-radius: 6px;\n background: var(--ccd-track);\n -webkit-app-region: no-drag;\n}\n\n/* The docked pill sits over the sidebar\'s native window-drag strip. Let its\n empty chrome participate in dragging while keeping the view buttons clickable. */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_tabs[data-ccd-view-dock="sidebar"] {\n -webkit-app-region: drag;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_tabs[data-ccd-view-dock="sidebar"] .ST7X_W_tab {\n -webkit-app-region: no-drag;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_tabs::before {\n content: "";\n position: absolute;\n top: 1px;\n bottom: 1px;\n left: 0;\n box-sizing: border-box;\n width: var(--ccd-view-w, 0px);\n transform: translateX(var(--ccd-view-x, 0px));\n border: 1px solid var(--ccd-border-soft);\n border-radius: 5px;\n background: var(--ccd-card);\n box-shadow: var(--ccd-shadow-segment);\n pointer-events: none;\n transition: transform var(--ccd-view-duration, 0ms) var(--ds-ease-in-out, ease),\n width var(--ccd-view-duration, 0ms) var(--ds-ease-in-out, ease);\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_tab {\n position: relative;\n z-index: 1;\n box-sizing: border-box;\n /* The reference\'s 33px segment is a floor, not a fixed width: a View this\n plugin has no artwork for keeps its own label and its own room. */\n flex: 0 1 auto;\n min-width: 33px;\n height: 26px;\n padding: 0 6px;\n border: 0;\n border-radius: 5px;\n background: transparent;\n color: var(--ccd-text-muted);\n font-size: var(--ccd-font-size-muted);\n font-weight: 500;\n line-height: 20px;\n white-space: nowrap;\n}\n\n/* A decorated tab draws bundled artwork in place of the host\'s label text;\n `compat/view-switch.ts` writes the marker and the button\'s accessible name.\n An unknown View keeps its label and is left alone. */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_tab[data-ccd-view] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n font-size: 0;\n line-height: 0;\n}\n\n/*\n * The artwork seat leaves the button\'s own flow and centres on the button\'s\n * box \u2014 not on a line box beside the host\'s label, and not inside whatever\n * content box the host\'s own rules leave here. The host\'s `.tab` gives this\n * button `padding: 0 0 9px`, its own 16px line and its own alignment, and any\n * of them winning back even one of those declarations moved the icon ~4.5px\n * above the segment; the seat is absolutely centred on the padding box\n * instead, which is the box the sliding thumb is drawn on. Measured in\n * `tests/view-switch-browser.test.mjs`, including a state that hands the\n * button\'s inner layout back to the host\'s rules.\n */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_tab[data-ccd-view] > [data-ccd-view-glyph] {\n position: absolute;\n inset: 0;\n margin: auto;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n line-height: 0;\n pointer-events: none;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_tab[data-ccd-view] > [data-ccd-view-glyph] > svg {\n display: block;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_tab::after {\n display: none;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_tabActive {\n color: var(--ccd-text);\n}\n\n/*\n * Docked: the switcher leaves the header for the sidebar\'s window-control row.\n *\n * The reference keeps it at the top-right of the sidebar column, on the same\n * row as the traffic lights and the collapse toggle \u2014 measured at 8px inside\n * the column\'s right edge (its divider sits 16 device px right of the pill),\n * with the pill\'s 26px centre on the toggle\'s own centre (11px + 28 / 2 = 25).\n * The node stays in the header where React rendered it: `position: fixed` takes\n * it out of that row and the coordinates come from `compat/view-switch.ts`,\n * measured live from the sidebar column \u2014 the same way the macOS sidebar toggle\n * is parked. It is written only where the column has the corner to hold it:\n * macOS with the sidebar expanded, so the host\'s own row (Windows, or the\n * collapsed shell\'s 0px column) keeps the switcher inline.\n */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_tabs[data-ccd-view-dock="sidebar"] {\n position: fixed;\n top: 12px;\n right: var(--ccd-view-dock-right, 8px);\n z-index: 15;\n margin: 0;\n}\n\n/* With the switcher parked, native crumbs supply 8px end padding. The custom\n title has no end padding, so its project and team use the full group gap. */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_header:has(.ST7X_W_tabs[data-ccd-view-dock]) .ST7X_W_headerActions {\n margin-left: var(--ccd-header-group-gap, 2px);\n}\n\n@media (prefers-reduced-motion: reduce) {\n html[data-dsh-ccd-style="true"] .ST7X_W_tabs::before {\n transition: none;\n }\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_crumbs,\nhtml[data-dsh-ccd-style="true"] .ST7X_W_crumb {\n color: var(--ccd-text);\n font-size: var(--ccd-font-size-muted);\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_crumb {\n max-width: 100%;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerActions button,\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities button {\n color: var(--ccd-text-secondary);\n}\n\n/* The collapsed macOS shell consumes 160px for native window controls. Keep\n that reservation stable and omit title crumbs in this shell state, so a\n 400px column still fits the complete team entry and every native button. */\nhtml[data-dsh-ccd-style="true"][data-platform="darwin"] [data-sidebar-collapsed] .ST7X_W_header:not(.ST7X_W_headerBlank) .ST7X_W_crumbs {\n max-width: none;\n}\n\nhtml[data-dsh-ccd-style="true"][data-platform="darwin"] [data-sidebar-collapsed] .ST7X_W_header:not(.ST7X_W_headerBlank) .ST7X_W_crumb {\n display: none;\n}\n\n/* Keep the actionable team entry complete; omit the passive header mode. */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerActions ._2tNPVa_triggerLabel {\n display: inline;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerActions ._3li69W_label {\n display: none;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerActions ._2tNPVa_trigger {\n padding-inline: 0;\n}\n\n/*\n * Trailing action cluster. Every control takes the same square hit box, so the\n * visible gaps between glyphs are equal rather than only the box gaps: the\n * previous build zeroed the host\'s `gap` and shrank single controls, which left\n * ~7px after the split button but ~14px after the 24px menu button. Geometry is\n * declared once in theme/tokens.css. The host\'s own 15px artwork stays native\n * inside these boxes; the plugin only paints the three glyphs below.\n *\n * The renderer wraps every list entry in a `display: contents` seat\n * (`[data-slot="conversation.session.header.utilities"]`), so the boxes are the\n * seat\'s children \u2014 the seat itself has no box to size. (The corner seat is\n * retired and holds nothing: see the rule above.)\n *\n * `[role="tooltip"]` is named out because a seat\'s children are not always\n * controls. `ui-primitives`\' `Tooltip` clones its anchor and renders its bubble\n * as a *sibling* of it, inside the same React fragment \u2014 which lands both nodes\n * in the seat. Sizing that bubble to the control box clipped it to one glyph\n * and a half the moment it appeared; the bubble keeps its own geometry on\n * every hover.\n */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities > [data-slot] > *:not([role="tooltip"]) {\n box-sizing: border-box;\n flex: none;\n width: var(--ccd-header-action-size);\n height: var(--ccd-header-action-size);\n border-radius: var(--ccd-radius-row);\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities .Da3aKq_moreButton {\n padding: 0;\n}\n\n/*\n * The "open in app" control becomes one generic glyph whose whole box opens the\n * native application menu. The chevron half \u2014 the only control that opens that\n * menu \u2014 is stretched over the split box as the hit area, while the application\n * image and the chevron glyph are replaced by shared artwork. The host keeps the\n * menu, its items, its icon route and its callbacks; `compat/open-target.ts`\n * marks the no-menu state, which hides this control instead of direct-opening.\n */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities .iq4beG_split {\n position: relative;\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n border: 0;\n border-radius: inherit;\n overflow: hidden;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities .iq4beG_chevron {\n position: absolute;\n inset: 0;\n z-index: 1;\n width: 100%;\n height: 100%;\n padding: 0;\n border: 0;\n background: transparent;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities .iq4beG_chevron svg {\n display: none;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities .iq4beG_chevron:hover:not(:disabled),\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities .iq4beG_chevron:focus-visible,\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities .iq4beG_main:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities .iq4beG_main {\n width: 100%;\n height: 100%;\n gap: 0;\n padding: 0;\n color: var(--ccd-text-secondary);\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities .iq4beG_main > * {\n display: none;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities .iq4beG_main::before {\n content: "";\n width: var(--ccd-header-icon-size);\n height: var(--ccd-header-icon-size);\n background-color: currentColor;\n mask: var(--ccd-icon-open) center / var(--ccd-header-icon-size) var(--ccd-header-icon-size) no-repeat;\n}\n\n/* No application menu exists (a directory with at most one candidate): a chooser\n glyph would promise what the host cannot show, so the control stays away. */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities [data-ccd-open-mode="direct"] {\n display: none;\n}\n\n/*\n * Narrow columns: the cluster ends on the Composer\'s own trailing edge.\n *\n * The design\'s 12px inset (`--ccd-header-trailing-inset`) is measured in a\n * window whose transcript column is wider than the Composer\'s 770px cap, so the\n * Composer is centred inside the column and the two rows end at different\n * places \u2014 an inset the reference keeps at every width. Where the Composer\n * *fills* the column instead, its input surface\'s right border is a line the\n * cluster sits directly above, and the reference draws the cluster ending on it:\n * the last control\'s centre on that border, so the \u22EE reads as the row\'s closing\n * mark rather than as an icon hanging outside the Composer. That state is the\n * one an expanded right panel leaves, and this is where it is answered.\n *\n * The column that fills its Composer is the Composer\'s own 770px cap plus what\n * surrounds the surface: the root\'s two clearances and the page scroll\n * viewport\'s stable gutter and 2px margin, i.e. 810px. Below it the trailing\n * padding is the surface\'s own edge (`--ccd-composer-trailing-edge`, declared\n * on the header above) less half the closing control, so the box is centred on\n * the border rather than ending on it; `min()` keeps the design\'s inset as the\n * ceiling, for a deployment that hands the Composer a wider clearance.\n */\n@container ccd-conversation (max-width: 810px) {\n html[data-dsh-ccd-style="true"] .ST7X_W_header {\n padding-right: min(\n var(--ccd-header-trailing-inset),\n calc(var(--ccd-composer-trailing-edge) - var(--ccd-header-action-size) / 2));\n }\n}\n\n/* Very narrow columns keep every control on the collapsed macOS shell\'s 24px\n box, so the same edge is taken against the smaller box: half of it is 2px\n less, and the cluster\'s last control stays centred on the input surface\'s\n border. */\n@container ccd-conversation (max-width: 520px) {\n html[data-dsh-ccd-style="true"] .ST7X_W_header {\n padding-right: min(\n var(--ccd-header-trailing-inset),\n calc(var(--ccd-composer-trailing-edge) - var(--ccd-header-action-compact) / 2));\n }\n\n html[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities > [data-slot] > *:not([role="tooltip"]) {\n width: var(--ccd-header-action-compact);\n height: var(--ccd-header-action-compact);\n }\n}\n\n/*\n * The title yields before the cluster does \u2014 and, like the three views below,\n * only to a switcher that is still in the row.\n *\n * With the switcher in it, the row is four pieces \u2014 title, switcher, native team\n * entry, cluster \u2014 and at this width they cannot share one line. The title is\n * the only piece the sidebar also shows, so it goes: the same trade the\n * collapsed macOS shell already makes, and every native control keeps its full\n * label.\n *\n * Parked in the sidebar\'s window-control row (`compat/view-switch.ts` writes\n * `data-ccd-view-dock`; macOS with the sidebar expanded, which is where an\n * expanded right panel leaves the column), the switcher takes its 66px track and\n * the two margins around it out of the row, and the three pieces that remain\n * occupy 377px of the 400px floor the frame protects for this column while the\n * right panel is open (`ui-layout`\'s `CENTER_MIN`) \u2014 so they fit, with the 72px\n * between the chip and the cluster left to `margin-right: auto`. Withdrawing the\n * title there bought nothing and cost the one place a narrow column still names\n * its session.\n */\n@container ccd-conversation (max-width: 420px) {\n html[data-dsh-ccd-style="true"] .ST7X_W_header:not(.ST7X_W_headerBlank):has(.ST7X_W_tabs:not([data-ccd-view-dock]))\n .ST7X_W_crumbs {\n display: none;\n }\n}\n\n/*\n * The three views this plugin adds \u2014 the project folder among them \u2014 step aside\n * only while the row still carries the View switcher.\n *\n * The switcher is normally parked in the sidebar\'s window-control row\n * (`compat/view-switch.ts` writes `data-ccd-view-dock` on the tablist wherever\n * that column has the corner to hold it), and the row is then the title, the\n * team chip and the cluster: at most ~235px of chrome, so all three stay drawn\n * at any width the Composer can still be used at \u2014 including the column an\n * expanded right panel leaves, where they are the only way to switch the\n * panel\'s view back. With the switcher still in the row \u2014 a Windows build, or a\n * collapsed macOS sidebar whose window is narrow \u2014 there are four pieces on the\n * line, and these three are the ones that go: they are the plugin\'s own\n * controls, they have a second entry in the right panel\'s own tab strip, and\n * the native team chip keeps its full label either way.\n */\n@container ccd-conversation (max-width: 420px) {\n html[data-dsh-ccd-style="true"] .ST7X_W_header:has(.ST7X_W_tabs:not([data-ccd-view-dock]))\n .ST7X_W_headerUtilities [data-ccd-header-view] {\n display: none;\n }\n}\n\n/* Transcript column keeps the reference\'s breathing room. */\nhtml[data-dsh-ccd-style="true"] .icaHSq_scroll {\n padding-top: 16px;\n padding-bottom: 8px;\n}\n\n/* Center and size the host\'s jump-to-bottom control.\n * Source: ui-chat/ChatView.module.css, HOST.chatViewToBottomSlot/toBottom.\n * Negative margin follows the button size to retain its gap above Composer.\n */\nhtml[data-dsh-ccd-style="true"] .icaHSq_toBottomSlot {\n justify-content: center;\n padding-right: 0;\n}\n\nhtml[data-dsh-ccd-style="true"] .icaHSq_toBottom {\n width: var(--ccd-to-bottom-size);\n height: var(--ccd-to-bottom-size);\n margin-top: calc(-1 * var(--ccd-to-bottom-size));\n}\n\n/* Paint edge gradients on the page body above the transcript and below\n * sticky Composer. conversation-fade.ts publishes scroll flags and the bottom\n * inset; the lower overlay covers Composer\'s full footprint. Since Composer\n * is inside the scroller, the gradient belongs on this separate layer.\n */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_body::before,\nhtml[data-dsh-ccd-style="true"] .ST7X_W_body::after {\n content: "";\n position: absolute;\n left: 0;\n right: 0;\n height: var(--ccd-edge-fade);\n background: var(--ccd-canvas);\n pointer-events: none;\n /* Above the transcript, below the Composer seat (7) and the jump-to-bottom\n control (8) so neither is veiled. */\n z-index: 1;\n opacity: 0;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_body::before {\n top: 0;\n background: linear-gradient(to bottom, var(--ccd-canvas), transparent);\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_body::after {\n bottom: 0;\n height: calc(var(--ccd-fade-bottom-inset, 0px) + var(--ccd-edge-fade));\n background: linear-gradient(\n to top,\n var(--ccd-canvas) 0,\n var(--ccd-canvas) calc(100% - var(--ccd-edge-fade)),\n transparent 100%\n );\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_body:has([data-conversation-scroll][data-ccd-fade-top])::before {\n opacity: 1;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_body:has([data-conversation-scroll][data-ccd-fade-bottom])::after {\n opacity: 1;\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root {\n font-size: var(--dsh-content-font-size, 14px);\n line-height: 24px;\n color: var(--ccd-text);\n}\n\n/* Official MarkdownText in assistant replies only. Compact reasoning/tool\n Markdown, user references and the Composer retain their own presentation.\n Sources and static-library class names are pinned in compat/host-dom.ts. */\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) {\n font-family: var(--ccd-font-ui);\n font-size: inherit;\n /* The host runs the assistant body at 14px/24px; the reference runs the same\n 14px on a 20px line, which is what its paragraphs and its paragraph gaps\n are measured against. Reasoning and tool Markdown (`[data-markdown-variant\n ="compact"]`) keep the host\'s own leading. */\n line-height: var(--ccd-transcript-line-height);\n color: var(--ccd-text);\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) :is(a, ._fileMention_1ypvv_85) {\n max-width: 100%;\n color: var(--ccd-link);\n font-weight: inherit;\n text-decoration-line: underline;\n text-decoration-style: solid;\n text-decoration-color: var(--ccd-link-underline);\n text-decoration-thickness: 1px;\n text-underline-offset: 3px;\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) :is(a, ._fileMention_1ypvv_85):is(:hover, :focus) {\n text-decoration-color: var(--ccd-link);\n}\n\n/*\n * The host prefixes a URL link with a category glyph \u2014 `MarkdownText.module.css`\n * seats a `.linkIcon` globe inside the anchor (1.1em, -0.25em of optical\n * offset) \u2014 so "\u5916\u94FE" draws as "\u2295 \u5916\u94FE" where the reference draws the link text\n * alone. The glyph is not painted inside an anchor; the anchor keeps its text,\n * its underline and its hit area. The other two shapes that carry the class \u2014\n * a file mention and URL-promoted inline code \u2014 are not anchors but file chips,\n * and keep their own glyph.\n */\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) a ._linkIcon_1ypvv_94 {\n display: none;\n}\n\n/* Limit chips to the text column so long identifiers can wrap inside them.\n The host uses !important for its code-size ratio, so this override must too.\n The fenced block is not a chip: it takes its own rule below. */\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) :not(pre) > code {\n display: inline-block;\n max-width: 100%;\n vertical-align: baseline;\n font-family: var(--ccd-font-code);\n font-size: 0.95em !important;\n font-weight: inherit;\n line-height: 1.2;\n color: var(--ccd-inline-code-text);\n background: var(--ccd-inline-code-fill);\n border: 1px solid var(--ccd-inline-code-border);\n border-radius: 5px;\n padding: 1px 4px;\n overflow-wrap: anywhere;\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) strong {\n font-weight: var(--ccd-font-weight-strong);\n}\n\n/*\n * Headings. The host draws the transcript\'s h1/h2/h3 at 21/19/18px inside a\n * 32px top and 16px bottom margin, a full step larger and a good deal looser\n * than the reference: its crop measures a 19px h1 and a 16px h2 (the tokens in\n * `theme/tokens.css` carry that measurement), and the ink between two stacked\n * headings is 20 device px against this plugin\'s 68. The margin below is what\n * puts that gap back \u2014 the collapsed 8px plus each heading\'s own half-leading\n * lands within a couple of device px of the reference at @2x. h4-h6 keep the\n * host\'s size (body size, weight 600) and take the same rhythm.\n */\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) :is(h1, h2, h3) {\n margin: 8px 0 6px;\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) h1 {\n font-size: var(--ccd-font-size-h1);\n line-height: 26px;\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) h2 {\n font-size: var(--ccd-font-size-h2);\n line-height: 22px;\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) h3 {\n font-size: var(--ccd-font-size-h3);\n line-height: 20px;\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) :is(h4, h5, h6) {\n margin: 10px 0 6px;\n}\n\n/*\n * Block rhythm. The host spaces the transcript\'s blocks at 16px. The reference\n * runs tighter around prose and quotes \u2014 its crop puts a paragraph-to-quote and\n * a quote-to-list run at 18 device px of ink, which is ~7-10px of collapsed\n * margin once each block\'s half-leading is taken off \u2014 while its lists and its\n * table and code cards keep the host\'s step. Only the two that differ move;\n * list items keep the host\'s own 8px rhythm.\n *\n * The list guard is written on the paragraph \u2014 `p:not(li p)` \u2014 and not as a\n * `:not(li) > p` child of the root: a paragraph in this DOM is the root\'s own\n * child, and a descendant combinator there asks for a parent *below* the root,\n * so it matched nothing but the paragraphs nested one level deeper (a quote\'s)\n * and every top-level paragraph kept the host\'s 16px.\n */\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) p:not(li p) {\n margin: 10px 0;\n}\n\n/*\n * Blockquote. The host draws a 2px rule in `--dsw-alias-label-caption` \u2014 its\n * own mid grey \u2014 and leaves the text at full body ink, so a quote reads as an\n * indented paragraph rather than as a quote. The reference draws a 4px rule in\n * the same value as this plugin\'s outline token, with its text one step down in\n * ink (this plugin\'s secondary step, against a body that stays near-black). The\n * rule-to-text distance matches in both (27 and 29 device px), so only the\n * rule\'s weight, its colour and the text\'s ink move.\n */\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) blockquote {\n margin: 10px 0;\n border-left: 4px solid var(--ccd-border);\n padding-left: 15px;\n color: var(--ccd-text-secondary);\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) hr {\n /* The host spaces the rule at 32px on each side; the reference\'s crop puts\n its card-rule-copy run at 71 device px of ink, which is ~20px a side. */\n margin: 20px 0;\n}\n\n/*\n * List rhythm. The host sizes its marker\'s own line at the 24px it used to draw\n * the body at, so the marker box stands a step taller than the transcript\'s\n * leading and stretches every list row to 22px \u2014 the item pitch lands at 28\n * where the reference\'s own rows measure 26 (its 40 device px at 1.53). The\n * marker rides the item\'s line instead, which is what a browser that sets no\n * marker line at all would do.\n */\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) li::marker {\n line-height: inherit;\n}\n\n/*\n * Task lists. The host leaves the list marker in place, so a `- [x]` row draws\n * "\u2022 \u2611 label" where the reference draws the checkbox alone, and its 6px\n * inter-item margin \u2014 which the reference\'s own rows do not carry \u2014 widens the\n * pitch to 46 device px against the reference\'s 35. The marker goes and the\n * margin goes with it; the checkbox itself already matches (the reference\'s\n * fill measures the same 20 device px in the host\'s grey accent).\n *\n * The checkbox sits directly in the item in a tight list and inside the item\'s\n * paragraph in a loose one, so both shapes are named.\n */\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) :is(li:has(> input[type="checkbox"]), li:has(> p:first-child > input[type="checkbox"])) {\n list-style: none;\n margin-top: 0;\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) input[type="checkbox"] {\n /* The host\'s 8px trailing margin leaves 20 device px before the label where\n the reference leaves 10. */\n margin-right: 6px;\n}\n\n/*\n * Fenced code blocks take the reference\'s outlined card: a white fill inside a\n * single hairline, replacing the host\'s cool wash. The host paints three nested\n * surfaces with that wash \u2014 the wrapper, the language banner and `pre` itself \u2014\n * and the banner\'s outer wrapper falls back to the page token, so every one of\n * them is repainted here; leaving any single layer would show a grey band.\n *\n * `data-code-wrap` is a stable host anchor (the class list also carries\n * `md-code-block`); the inner layers are reached through it so no hashed class\n * name is repeated here.\n *\n * The card is also a *row*, because the reference\'s is. Its one-line fence\n * draws as a single 33px-tall card (50 device px at @2x) that hugs its content \u2014\n * code first, the copy control (and the wrap toggle, and the run control on a\n * shell fence) trailing it \u2014 and floors at 320px, which is why its own short\n * `npm run check` card carries a wide gap between the code and its controls.\n * The host instead fills the column and stacks a 36px language banner over a\n * `pre` padded `6px 22px 20px`, which is 135 device px for that same one line.\n *\n * So the block becomes a flex row with the banner reversed behind the code: the\n * banner\'s controls ride the first code line, the `pre` takes the row\'s spare\n * width (pushing those controls to the trailing edge exactly as the reference\'s\n * floored card does) and the column stays the ceiling. `min(320px, 100%)` keeps\n * a narrow transcript from being pushed out by the floor.\n */\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) [data-code-wrap] {\n box-sizing: border-box;\n border: 1px solid var(--ccd-border);\n border-radius: var(--ccd-code-radius);\n background: var(--ccd-card);\n display: flex;\n /* The banner is the DOM\'s first child; the code reads first. */\n flex-direction: row-reverse;\n align-items: flex-start;\n width: fit-content;\n min-width: min(320px, 100%);\n max-width: 100%;\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) [data-code-wrap] > *,\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) [data-code-wrap] [data-code-block-banner],\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) [data-code-wrap] pre {\n /* The card owns the surface now; the layers inside it only carry text. */\n background: transparent;\n border-radius: 0;\n}\n\n/*\n * The banner stops being a row above the code. Its wrapper is the sticky\n * scroller that used to pin the language row to the top of a tall card; in a\n * row it is the card\'s trailing control strip, seated on the first code line\n * (31px = the `pre`\'s 6px padding over the host\'s 19px code line), which is the\n * whole card when the fence is one line. The host\'s own compact variant\n * neutralises the same wrapper the same way.\n */\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) [data-code-wrap] > :has(> [data-code-block-banner]) {\n position: static;\n z-index: auto;\n display: flex;\n align-items: center;\n height: 31px;\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) [data-code-wrap] [data-code-block-banner] {\n width: 100%;\n /* 11px, not 12: the card\'s own 1px outline sits outside this padding and the\n reference measures its 18 device px from the card\'s outer edge. */\n padding: 0 11px 0 0;\n font: inherit;\n}\n\n/*\n * The language label is not painted: the reference\'s card shows the code and its\n * controls alone, and its short card is 320px wide for 90px of code precisely\n * because nothing else sits in that row. The label is the banner\'s leading\n * group in both of the host\'s toolbar shapes, so it is named structurally\n * rather than by its hashed class; the copy and wrap controls, their labels and\n * the run control on a shell fence all stay.\n */\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) [data-code-wrap] [data-code-block-banner] > :first-child {\n display: none;\n}\n\n/*\n * The code keeps the host\'s 19px line and takes the reference\'s size (one step\n * under the body, where the host draws it at 11px) and its padding: the\n * reference holds the line with ~6px above and below and 16px of leading inset\n * (26 device px at @2x), against the host\'s `6px 22px 20px`. `min-width: 0`\n * lets the code shrink below its own content when the wrap toggle is off.\n */\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) [data-code-wrap] pre {\n flex: 1 1 auto;\n min-width: 0;\n padding: 6px 16px;\n font-size: var(--ccd-font-size-muted);\n}\n\n/*\n * Tables: one rounded card, as in the reference. The host collapses borders,\n * takes every cell\'s leading padding away (`padding: 10px 16px 10px 0`) and\n * leaves the row rule to each cell, so a card that can carry a radius needs\n * separate borders, one outline of its own and its own cell padding. The head\n * keeps a filled surface and a rule one step darker than the outline; body rows\n * are separated by hairlines, and the card\'s own edge closes the last one.\n * Column alignment markers stay the host\'s business.\n */\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) table {\n box-sizing: border-box;\n width: 100%;\n max-width: 100%;\n border: 1px solid var(--ccd-border);\n border-radius: var(--ccd-table-radius);\n border-collapse: separate;\n border-spacing: 0;\n /* The head fill and the row rules are painted by the cells, so the card has\n to clip them to its own radius. */\n overflow: hidden;\n font-size: inherit;\n line-height: inherit;\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) :is(th, td) {\n /* The host sizes table text one step below the transcript; the reference\n keeps a table at body size. Its row is 33px \u2014 the transcript\'s 20px line\n with 6px above and below, which is the 51 device px both shots measure \u2014\n so the cells keep the body leading and pay the difference in padding. */\n font-size: inherit;\n line-height: inherit;\n padding: 6px 10px;\n border: 0;\n border-bottom: 1px solid var(--ccd-border);\n vertical-align: middle;\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) thead th {\n background: var(--ccd-raised);\n border-bottom-color: var(--ccd-table-head-line);\n color: var(--ccd-text);\n font-weight: var(--ccd-font-weight-strong);\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) tbody tr:last-child :is(th, td) {\n border-bottom: 0;\n}\n\n/*\n * Wide tables stay inside the content column.\n *\n * The host marks every table of four columns or more `md-table-wide`\n * (AssistantMarkdown.module.css) and bleeds that wrapper out to the scroller\'s\n * full width through `--dsh-table-spare`\uFF0F`--dsh-table-lead`, pinning the table\'s\n * leading edge back to the column with a negative margin and a matching leading\n * pad. In the host\'s own geometry the Composer card is 32px wider than the\n * column, so the borrowed width lands inside its edges; here the card and the\n * column are the same width, and a bleeded table left the card and the Composer\n * on different right edges (measured +86px at the reference window, growing with\n * the view area). Dropping the bleed keeps the table on the column; a table\n * whose rows cannot wrap far enough still uses the host\'s own `md-table-wide`\n * scroll, which needs the wrapper\'s own `overflow-x` and scrollbar gutter, so\n * neither is touched here.\n */\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root .gKv1-q_body .md-table-wide {\n box-sizing: border-box;\n width: 100%;\n max-width: 100%;\n margin-left: 0;\n padding-left: 0;\n}\n\nhtml[data-dsh-ccd-style="true"] .icaHSq_column {\n --dsh-chat-flow-gap: 10px;\n}\n\n/* User message: soft raised bubble, as in the reference. */\nhtml[data-dsh-ccd-style="true"] .IzP3Va_bubble {\n padding: 7px 14px;\n border-radius: var(--ccd-composer-radius);\n background: var(--ccd-raised);\n color: var(--ccd-text);\n font-size: var(--dsh-content-font-size, 14px);\n line-height: 22px;\n}\n\nhtml[data-dsh-ccd-style="true"] .IzP3Va_userStack {\n max-width: min(calc(var(--dsh-chat-content-width) * 0.72), 82%);\n gap: 6px;\n}\n\n/* Tool and reasoning rows: quiet single lines. */\nhtml[data-dsh-ccd-style="true"] .Q2dzhW_root,\nhtml[data-dsh-ccd-style="true"] .HiSsxa_root,\nhtml[data-dsh-ccd-style="true"] .-gRAhq_root {\n color: var(--ccd-text-secondary);\n font-size: var(--ccd-font-size-body);\n}\n';
|
|
13499
|
+
var conversation_default = '/*\n * Conversation page: header chrome, transcript column and message surfaces.\n *\n * The transcript, its scroll behaviour, tool rendering and approval surfaces\n * stay entirely DSH\'s; only width, type and surface colours change. Host\n * classes are pinned in src/client/compat/host-dom.ts.\n */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_root {\n container: ccd-conversation / inline-size;\n}\n\n/* ChangedFiles is the host\'s completed-turn summary, separate from explicitly\n delivered files and the tool calls that performed the edits. */\nhtml[data-dsh-ccd-style="true"] [data-turn-tail] [data-changed-files] {\n display: none;\n}\n\n/* MessageIconActions exposes data-clock="end" on assistant action strips.\n Keep the native actions and feedback slot, with Claude-sized icon seats. */\nhtml[data-dsh-ccd-style="true"] [data-turn-tail] [data-clock="end"] {\n height: 24px;\n gap: 0;\n margin-top: 4px;\n margin-left: -4px;\n}\n\n/* compat/reply-signature.ts seats this button only at the latest settled reply.\n The mask is DSH\'s RunningWhaleTail REST_PATH (16 \xD7 16). */\nhtml[data-dsh-ccd-style="true"] .ccd-reply-signature {\n display: block;\n flex: none;\n align-self: flex-start;\n width: 24px;\n height: 24px;\n margin-top: 8px;\n padding: 0;\n border: 0;\n border-radius: 4px;\n background: transparent;\n cursor: pointer;\n transform-origin: 45% 85%;\n}\n\nhtml[data-dsh-ccd-style="true"] .ccd-reply-signature:focus-visible {\n outline: 2px solid var(--ccd-accent);\n outline-offset: 4px;\n}\n\nhtml[data-dsh-ccd-style="true"] .ccd-reply-signature::before {\n content: "";\n display: block;\n width: 100%;\n height: 100%;\n background: var(--ccd-pet-blue);\n mask: url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 16 16\' fill=\'none\'%3E%3Cpath d=\'M8.844 13.742C8.967 12.328 8.45 10.4 8.45 9.65C8.45 8.94 8.88 8.43 9.6 8.43C11.285 8.43 12.106 8.281 12.685 8.104C13.71 7.791 14.585 6.768 15.055 5.945C15.137 5.803 14.99 5.641 14.829 5.671C13.829 5.86 12.828 5.376 11.827 4.978C10.659 4.514 9.491 4.707 8.935 4.876C8.805 4.915 8.658 4.819 8.636 4.686C8.468 3.643 7.405 2.615 5.498 2.238C4.54 2.048 3.748 1.574 3.347 1.202C3.252 1.113 3.088 1.125 3.03 1.242C2.628 2.059 2.168 3.82 5.248 6.115C5.82 6.494 6.31 6.785 6.574 7.637C6.72 8.104 6.157 9.168 6.061 9.368C5.157 11.27 5.089 12.19 4.926 13.742\' stroke=\'black\' stroke-width=\'1\'/%3E%3C/svg%3E") center / contain no-repeat;\n}\n\nhtml[data-dsh-ccd-style="true"] [data-turn-tail] [data-clock="end"] button:has(> svg:only-child) {\n box-sizing: border-box;\n width: 24px;\n min-width: 24px;\n height: 24px;\n padding: 4px;\n}\n\nhtml[data-dsh-ccd-style="true"] [data-turn-tail] [data-clock="end"] button:has(> svg:only-child) > svg {\n width: 16px;\n height: 16px;\n}\n\n/* MessageIconActions\' final span groups token usage and the completion time. */\nhtml[data-dsh-ccd-style="true"] [data-turn-tail] [data-clock="end"] > span:last-child {\n gap: 6px;\n margin-left: 6px;\n font-size: 12px;\n line-height: 20px;\n}\n\nhtml[data-dsh-ccd-style="true"] [data-turn-tail] [data-clock="end"] button[aria-haspopup="dialog"] {\n box-sizing: border-box;\n width: 24px;\n min-width: 24px;\n height: 24px;\n justify-content: center;\n gap: 0;\n padding: 4px;\n border-radius: 6px;\n}\n\nhtml[data-dsh-ccd-style="true"] [data-turn-tail] [data-clock="end"] button[aria-haspopup="dialog"] > svg {\n width: 16px;\n height: 16px;\n}\n\n/* Preserve the localized usage label as the button\'s accessible name. */\nhtml[data-dsh-ccd-style="true"] [data-turn-tail] [data-clock="end"] button[aria-haspopup="dialog"] > span {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n}\n\n/* TurnUsagePanel portals to body. Its details hook identifies this dialog\n without touching session statistics or the host\'s anchored positioning. */\nhtml[data-dsh-ccd-style="true"] [role="dialog"]:has(> [data-turn-usage-details]) {\n box-sizing: border-box;\n width: min(340px, calc(100vw - 24px));\n min-width: 0;\n max-width: calc(100vw - 24px);\n max-height: calc(100dvh - 24px);\n overflow: auto;\n padding: 14px 16px;\n border: .5px solid var(--ccd-view-menu-border);\n border-radius: 12px;\n background: var(--ccd-card);\n box-shadow: var(--ccd-view-menu-shadow);\n backdrop-filter: none;\n color: var(--ccd-text-secondary);\n font: 400 12px/18px var(--ccd-font-ui);\n}\n\nhtml[data-dsh-ccd-style="true"] [role="dialog"]:has(> [data-turn-usage-details]) > div:first-child {\n align-items: center;\n gap: 12px;\n margin-bottom: 12px;\n color: var(--ccd-text);\n font-size: 13px;\n font-weight: 500;\n line-height: 20px;\n}\n\nhtml[data-dsh-ccd-style="true"] [role="dialog"]:has(> [data-turn-usage-details]) > div[aria-hidden="true"] {\n margin-bottom: 12px;\n border-top-color: var(--ccd-border);\n}\n\nhtml[data-dsh-ccd-style="true"] [data-turn-usage-details] {\n grid-template-columns: minmax(76px, auto) minmax(0, 1fr);\n gap: 8px 16px;\n margin: 0;\n color: var(--ccd-text-secondary);\n}\n\nhtml[data-dsh-ccd-style="true"] [data-turn-usage-details] > dd {\n color: var(--ccd-text);\n overflow-wrap: anywhere;\n font-variant-numeric: tabular-nums;\n}\n\nhtml[data-dsh-ccd-style="true"] [data-turn-usage-details] > dd > span {\n display: block;\n color: var(--ccd-text-muted);\n font-size: 11px;\n white-space: normal;\n}\n\n/* Process containers stay open via compat/conversation-process.ts. Only hide\n their controls after expansion, so a failed adaptation stays operable. */\nhtml[data-dsh-ccd-style="true"] button[data-turn-process]:not([aria-expanded="false"]),\nhtml[data-dsh-ccd-style="true"] [data-step-process] > div:has(> button[data-process-activity][aria-expanded="true"]) {\n display: none;\n}\n\nhtml[data-dsh-ccd-style="true"] [data-step-process-body]:not([hidden]) {\n max-height: none;\n overflow: visible;\n scrollbar-gutter: auto;\n mask-image: none;\n}\n\n/*\n * Transcript width handles: hidden here, because this interface removed what\n * they control (17).\n *\n * `ui-conversation`\'s `ConversationWidthControls` renders one handle per side as\n * the Conversation body\'s last child (ConversationRoot.module.css,\n * `ST7X_W_widthHandle`, stable hook `data-width-handle`; component\n * `skeleton/ConversationWidthControls.tsx`). Each is a transparent full-height\n * strip \u2014 `position: absolute; top: 0; bottom: 0`, `cursor: col-resize`,\n * `width: min(10px, (100% - var(--dsh-chat-content-width)) / 2 - 48px)` \u2014 parked\n * 24px outside the transcript column, whose only ink is the 2px rule its own\n * `::after` reveals on hover or while dragging. Dragging one publishes\n * `--dsh-chat-user-width` on the Conversation root.\n *\n * That property has exactly one consumer in the host, the body rule\n * `--dsh-chat-content-width: var(--dsh-chat-user-width, clamp(680px, \u2026))`, and\n * this interface replaces it: `theme/composer.css` pins\n * `--dsh-chat-content-width: var(--ccd-composer-width)` (770px) on the same\n * element with higher specificity. A drag therefore changes nothing \u2014 not the\n * transcript column, the user stack, the jump-to-bottom slot or the wide-table\n * bleed \u2014 while the handle keeps its pointer capture, its resize cursor and its\n * hover rule. It is a false affordance whose state cannot be reached: hiding it\n * takes away no behaviour, and with the plugin off the host\'s own rule returns\n * and the handles drag again.\n *\n * `display: none`, not `visibility` or a transparent paint, and scoped to the\n * Conversation root so `ui-layout`\'s real column handles (`_6Qf49G_handle`, 8px,\n * on the sidebar and rightbar tracks) are never touched. The handle is an\n * absolutely positioned, empty box outside the body\'s flex flow, so removing it\n * cannot move a sibling: transcript width, the scroll viewport\'s stable\n * scrollbar gutter and the page\'s scroll range keep the values they had.\n */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_root [data-width-handle] {\n display: none;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_header {\n background: var(--ccd-canvas);\n color: var(--ccd-text);\n /*\n * The reference has no rule between the header and the transcript \u2014 the two\n * read as one page. The host draws a hairline here (`border-bottom: .5px\n * solid`), so it is painted out rather than removed: keeping the border\'s\n * width leaves the header\'s own height untouched.\n */\n border-bottom-color: transparent;\n /*\n * One header row. DSH stacks a title row and, below it, its View tablist; the\n * reference keeps every piece of header chrome on the title line. The two\n * wrapper boxes dissolve (`display: contents`) instead of being re-parented,\n * so the host\'s own children stay where React put them and only their order\n * changes: title, View switcher, Agent-preset chip, utilities, corner.\n */\n display: flex;\n align-items: center;\n /* The host reserves the second row only while its tablist is a row of its\n own; that row is gone, so its `min-height: 76px` goes with it. */\n min-height: 0;\n padding: 10px var(--ccd-header-trailing-inset) 10px 12px;\n /*\n * Where the Composer\'s input surface ends, measured from the column\'s right\n * edge in the numbers that draw it, for the narrow-column rules below: the\n * Composer root\'s own side clearance (`theme/composer.css` pins the host\'s\n * `--dsh-composer-side-clearance` at 16px), the stable scrollbar gutter the\n * page\'s scroll viewport reserves (`ST7X_W_scrollBody { scrollbar-gutter:\n * stable }` over the theme\'s 5px `--dsh-scrollbar-width` track) and that\n * viewport\'s own `margin-right: 2px`.\n */\n --ccd-composer-trailing-edge: calc(\n var(--dsh-composer-side-clearance, 16px) + var(--dsh-scrollbar-width, 5px) + 2px);\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerBlank {\n /* Blank Sessions keep the single-row height they had as a grid row. */\n min-height: 40px;\n padding-bottom: 0;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerSessionless {\n min-height: 50px;\n}\n\n/*\n * On macOS the title line shares the window-control row\'s centre line: the\n * traffic lights, the collapse toggle (`top: 11px` + 28 / 2) and the parked\n * switcher (`top: 12px` + 26 / 2) all centre on y = 25. The flex items above\n * centre on the header\'s content box, and with the base padding (10px top and\n * bottom) that box centred on 20 \u2014 the title, the team chip and the right-hand\n * panel buttons sat 5px above the line they are meant to share.\n *\n * Holding the header at 40px \u2014 the height it already has, with 20px items \u2014\n * and dropping the bottom padding leaves a 30px content box from y = 10 to\n * y = 40 whatever the items\' own heights (up to 30px): its centre is 25. A\n * collapsed inline switcher (26px) therefore centres on the same line too.\n * Windows has no such row, so its header keeps the base rule, and the\n * sessionless header already centres on 25 with its own 10px bottom padding.\n */\n/* Only macOS shares the conversation header with the window drag surface.\n The host excludes its controls from dragging only on darwin; Windows has a\n separate caption strip, so making this row draggable swallows button clicks. */\nhtml[data-dsh-ccd-style="true"][data-platform="darwin"] .ST7X_W_header {\n -webkit-app-region: drag;\n}\n\nhtml[data-dsh-ccd-style="true"][data-platform="darwin"] .ST7X_W_header:not(.ST7X_W_headerSessionless) {\n min-height: 40px;\n padding-bottom: 0;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_titleRow,\nhtml[data-dsh-ccd-style="true"] .ST7X_W_titleCluster {\n display: contents;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_crumbs {\n /* Stable natural title width (capped at 112px) keeps the following switcher\n anchored while the rightbar changes column width. */\n flex: none;\n box-sizing: border-box;\n max-width: calc(112px + max(0px, var(--dsh-frame-leading-clearance, 0px) - 12px));\n padding-inline-start: max(0px, calc(var(--dsh-frame-leading-clearance, 0px) - 12px));\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerActions {\n order: 2;\n /* Stable gap between switcher and team entry.\n `margin-right: auto` is the header\'s flexible gap: the title, the View\n switcher and the chip stay grouped at the leading edge, as in the\n reference, and the utilities keep the trailing edge they had before. The\n chip always renders, so the gap survives an empty tools group. */\n /* The preceding title has 8px end padding + the track\'s 2px margin.\n Give the track-to-team boundary the same visible 10px separation. */\n margin-left: var(--ccd-header-group-gap, 10px);\n gap: 4px;\n margin-right: auto;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities {\n order: 3;\n margin-left: 0;\n /* One uniform gap, so every visible space in the cluster is equal. */\n gap: var(--ccd-header-action-gap);\n}\n\n/*\n * The corner seat is retired, and the utilities group owns the trailing edge.\n *\n * That single cell\'s shipped occupant is `ui-sidebar-right`\'s collapsed-panel\n * expand control, which this interface does not draw; the plugin shadows it with\n * an entry that renders nothing (`features/conversation/header-actions/entries.ts`)\n * and keeps the expand action in the Session menu instead. So the seat has no\n * box of its own to place \u2014 its own gap in front of the utilities would be 2px\n * of trailing space after the last control, and the reference measures the\n * cluster\'s inset from the header\'s own padding. A Blank Session renders the\n * same empty seat, which is why the host\'s `margin-left: auto` for that row\n * needs no answer here: there is nothing left in the corner to place either way.\n */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerCorner {\n margin: 0;\n}\n\n/*\n * View switcher: the host\'s own `role="tablist"`, reshaped into the reference\'s\n * segmented control. The selected segment is drawn by one thumb \u2014 the\n * `::before` of the tablist \u2014 moved with the geometry published by\n * `compat/view-switch.ts`; the buttons keep their roles, labels and handlers,\n * and the host\'s own underline is dropped.\n *\n * Geometry is the reference\'s own pill (DESIGN.md \xA76.5, @2x): a 66 \xD7 26 track\n * \u2014 131 \xD7 52 device px, so two 33px segments \u2014 6px\n * corner, with the raised thumb inset 1px top and bottom and carrying a 5px\n * corner, a 1px outline and the segment shadow. The two segments are the icon\n * buttons below, not text: `compat/view-switch.ts` swaps each shipped View\'s\n * label for the bundled artwork in `view-switch-icons.ts`.\n */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_tabs {\n order: 1;\n position: relative;\n /* Never squeezed: a narrow header truncates the title instead. */\n flex: none;\n display: inline-flex;\n align-items: stretch;\n gap: 0;\n /* The switcher sits directly after the Session title; later header chrome\n (the Agent-preset chip today, more to come) follows it. */\n margin: 0 0 0 var(--ccd-header-group-gap, 2px);\n padding: 0;\n border-radius: 6px;\n background: var(--ccd-track);\n -webkit-app-region: no-drag;\n}\n\n/* The docked pill sits over the sidebar\'s native window-drag strip. Let its\n empty chrome participate in dragging while keeping the view buttons clickable. */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_tabs[data-ccd-view-dock="sidebar"] {\n -webkit-app-region: drag;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_tabs[data-ccd-view-dock="sidebar"] .ST7X_W_tab {\n -webkit-app-region: no-drag;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_tabs::before {\n content: "";\n position: absolute;\n top: 1px;\n bottom: 1px;\n left: 0;\n box-sizing: border-box;\n width: var(--ccd-view-w, 0px);\n transform: translateX(var(--ccd-view-x, 0px));\n border: 1px solid var(--ccd-border-soft);\n border-radius: 5px;\n background: var(--ccd-card);\n box-shadow: var(--ccd-shadow-segment);\n pointer-events: none;\n transition: transform var(--ccd-view-duration, 0ms) var(--ds-ease-in-out, ease),\n width var(--ccd-view-duration, 0ms) var(--ds-ease-in-out, ease);\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_tab {\n position: relative;\n z-index: 1;\n box-sizing: border-box;\n /* The reference\'s 33px segment is a floor, not a fixed width: a View this\n plugin has no artwork for keeps its own label and its own room. */\n flex: 0 1 auto;\n min-width: 33px;\n height: 26px;\n padding: 0 6px;\n border: 0;\n border-radius: 5px;\n background: transparent;\n color: var(--ccd-text-muted);\n font-size: var(--ccd-font-size-muted);\n font-weight: 500;\n line-height: 20px;\n white-space: nowrap;\n}\n\n/* A decorated tab draws bundled artwork in place of the host\'s label text;\n `compat/view-switch.ts` writes the marker and the button\'s accessible name.\n An unknown View keeps its label and is left alone. */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_tab[data-ccd-view] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n font-size: 0;\n line-height: 0;\n}\n\n/*\n * The artwork seat leaves the button\'s own flow and centres on the button\'s\n * box \u2014 not on a line box beside the host\'s label, and not inside whatever\n * content box the host\'s own rules leave here. The host\'s `.tab` gives this\n * button `padding: 0 0 9px`, its own 16px line and its own alignment, and any\n * of them winning back even one of those declarations moved the icon ~4.5px\n * above the segment; the seat is absolutely centred on the padding box\n * instead, which is the box the sliding thumb is drawn on. Measured in\n * `tests/view-switch-browser.test.mjs`, including a state that hands the\n * button\'s inner layout back to the host\'s rules.\n */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_tab[data-ccd-view] > [data-ccd-view-glyph] {\n position: absolute;\n inset: 0;\n margin: auto;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n line-height: 0;\n pointer-events: none;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_tab[data-ccd-view] > [data-ccd-view-glyph] > svg {\n display: block;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_tab::after {\n display: none;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_tabActive {\n color: var(--ccd-text);\n}\n\n/*\n * Docked: the switcher leaves the header for the sidebar\'s window-control row.\n *\n * The reference keeps it at the top-right of the sidebar column, on the same\n * row as the traffic lights and the collapse toggle \u2014 measured at 8px inside\n * the column\'s right edge (its divider sits 16 device px right of the pill),\n * with the pill\'s 26px centre on the toggle\'s own centre (11px + 28 / 2 = 25).\n * The node stays in the header where React rendered it: `position: fixed` takes\n * it out of that row and the coordinates come from `compat/view-switch.ts`,\n * measured live from the sidebar column \u2014 the same way the macOS sidebar toggle\n * is parked. It is written only where the column has the corner to hold it:\n * macOS with the sidebar expanded, so the host\'s own row (Windows, or the\n * collapsed shell\'s 0px column) keeps the switcher inline.\n */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_tabs[data-ccd-view-dock="sidebar"] {\n position: fixed;\n top: 12px;\n right: var(--ccd-view-dock-right, 8px);\n z-index: 15;\n margin: 0;\n}\n\n/* With the switcher parked, native crumbs supply 8px end padding. The custom\n title has no end padding, so its project and team use the full group gap. */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_header:has(.ST7X_W_tabs[data-ccd-view-dock]) .ST7X_W_headerActions {\n margin-left: var(--ccd-header-group-gap, 2px);\n}\n\n@media (prefers-reduced-motion: reduce) {\n html[data-dsh-ccd-style="true"] .ST7X_W_tabs::before {\n transition: none;\n }\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_crumbs,\nhtml[data-dsh-ccd-style="true"] .ST7X_W_crumb {\n color: var(--ccd-text);\n font-size: var(--ccd-font-size-muted);\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_crumb {\n max-width: 100%;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerActions button,\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities button {\n color: var(--ccd-text-secondary);\n}\n\n/* The collapsed macOS shell consumes 160px for native window controls. Keep\n that reservation stable and omit title crumbs in this shell state, so a\n 400px column still fits the complete team entry and every native button. */\nhtml[data-dsh-ccd-style="true"][data-platform="darwin"] [data-sidebar-collapsed] .ST7X_W_header:not(.ST7X_W_headerBlank) .ST7X_W_crumbs {\n max-width: none;\n}\n\nhtml[data-dsh-ccd-style="true"][data-platform="darwin"] [data-sidebar-collapsed] .ST7X_W_header:not(.ST7X_W_headerBlank) .ST7X_W_crumb {\n display: none;\n}\n\n/* Keep the actionable team entry complete; omit the passive header mode. */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerActions ._2tNPVa_triggerLabel {\n display: inline;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerActions ._3li69W_label {\n display: none;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerActions ._2tNPVa_trigger {\n padding-inline: 0;\n}\n\n/*\n * Trailing action cluster. Every control takes the same square hit box, so the\n * visible gaps between glyphs are equal rather than only the box gaps: the\n * previous build zeroed the host\'s `gap` and shrank single controls, which left\n * ~7px after the split button but ~14px after the 24px menu button. Geometry is\n * declared once in theme/tokens.css. The host\'s own 15px artwork stays native\n * inside these boxes; the plugin only paints the three glyphs below.\n *\n * The renderer wraps every list entry in a `display: contents` seat\n * (`[data-slot="conversation.session.header.utilities"]`), so the boxes are the\n * seat\'s children \u2014 the seat itself has no box to size. (The corner seat is\n * retired and holds nothing: see the rule above.)\n *\n * `[role="tooltip"]` is named out because a seat\'s children are not always\n * controls. `ui-primitives`\' `Tooltip` clones its anchor and renders its bubble\n * as a *sibling* of it, inside the same React fragment \u2014 which lands both nodes\n * in the seat. Sizing that bubble to the control box clipped it to one glyph\n * and a half the moment it appeared; the bubble keeps its own geometry on\n * every hover.\n */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities > [data-slot] > *:not([role="tooltip"]) {\n box-sizing: border-box;\n flex: none;\n width: var(--ccd-header-action-size);\n height: var(--ccd-header-action-size);\n border-radius: var(--ccd-radius-row);\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities .Da3aKq_moreButton {\n padding: 0;\n}\n\n/*\n * The "open in app" control becomes one generic glyph whose whole box opens the\n * native application menu. The chevron half \u2014 the only control that opens that\n * menu \u2014 is stretched over the split box as the hit area, while the application\n * image and the chevron glyph are replaced by shared artwork. The host keeps the\n * menu, its items, its icon route and its callbacks; `compat/open-target.ts`\n * marks the no-menu state, which hides this control instead of direct-opening.\n */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities .iq4beG_split {\n position: relative;\n box-sizing: border-box;\n width: 100%;\n height: 100%;\n border: 0;\n border-radius: inherit;\n overflow: hidden;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities .iq4beG_chevron {\n position: absolute;\n inset: 0;\n z-index: 1;\n width: 100%;\n height: 100%;\n padding: 0;\n border: 0;\n background: transparent;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities .iq4beG_chevron svg {\n display: none;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities .iq4beG_chevron:hover:not(:disabled),\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities .iq4beG_chevron:focus-visible,\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities .iq4beG_main:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities .iq4beG_main {\n width: 100%;\n height: 100%;\n gap: 0;\n padding: 0;\n color: var(--ccd-text-secondary);\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities .iq4beG_main > * {\n display: none;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities .iq4beG_main::before {\n content: "";\n width: var(--ccd-header-icon-size);\n height: var(--ccd-header-icon-size);\n background-color: currentColor;\n mask: var(--ccd-icon-open) center / var(--ccd-header-icon-size) var(--ccd-header-icon-size) no-repeat;\n}\n\n/* No application menu exists (a directory with at most one candidate): a chooser\n glyph would promise what the host cannot show, so the control stays away. */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities [data-ccd-open-mode="direct"] {\n display: none;\n}\n\n/*\n * Narrow columns: the cluster ends on the Composer\'s own trailing edge.\n *\n * The design\'s 12px inset (`--ccd-header-trailing-inset`) is measured in a\n * window whose transcript column is wider than the Composer\'s 770px cap, so the\n * Composer is centred inside the column and the two rows end at different\n * places \u2014 an inset the reference keeps at every width. Where the Composer\n * *fills* the column instead, its input surface\'s right border is a line the\n * cluster sits directly above, and the reference draws the cluster ending on it:\n * the last control\'s centre on that border, so the \u22EE reads as the row\'s closing\n * mark rather than as an icon hanging outside the Composer. That state is the\n * one an expanded right panel leaves, and this is where it is answered.\n *\n * The column that fills its Composer is the Composer\'s own 770px cap plus what\n * surrounds the surface: the root\'s two clearances and the page scroll\n * viewport\'s stable gutter and 2px margin, i.e. 810px. Below it the trailing\n * padding is the surface\'s own edge (`--ccd-composer-trailing-edge`, declared\n * on the header above) less half the closing control, so the box is centred on\n * the border rather than ending on it; `min()` keeps the design\'s inset as the\n * ceiling, for a deployment that hands the Composer a wider clearance.\n */\n@container ccd-conversation (max-width: 810px) {\n html[data-dsh-ccd-style="true"] .ST7X_W_header {\n padding-right: min(\n var(--ccd-header-trailing-inset),\n calc(var(--ccd-composer-trailing-edge) - var(--ccd-header-action-size) / 2));\n }\n}\n\n/* Very narrow columns keep every control on the collapsed macOS shell\'s 24px\n box, so the same edge is taken against the smaller box: half of it is 2px\n less, and the cluster\'s last control stays centred on the input surface\'s\n border. */\n@container ccd-conversation (max-width: 520px) {\n html[data-dsh-ccd-style="true"] .ST7X_W_header {\n padding-right: min(\n var(--ccd-header-trailing-inset),\n calc(var(--ccd-composer-trailing-edge) - var(--ccd-header-action-compact) / 2));\n }\n\n html[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities > [data-slot] > *:not([role="tooltip"]) {\n width: var(--ccd-header-action-compact);\n height: var(--ccd-header-action-compact);\n }\n}\n\n/*\n * The title yields before the cluster does \u2014 and, like the three views below,\n * only to a switcher that is still in the row.\n *\n * With the switcher in it, the row is four pieces \u2014 title, switcher, native team\n * entry, cluster \u2014 and at this width they cannot share one line. The title is\n * the only piece the sidebar also shows, so it goes: the same trade the\n * collapsed macOS shell already makes, and every native control keeps its full\n * label.\n *\n * Parked in the sidebar\'s window-control row (`compat/view-switch.ts` writes\n * `data-ccd-view-dock`; macOS with the sidebar expanded, which is where an\n * expanded right panel leaves the column), the switcher takes its 66px track and\n * the two margins around it out of the row, and the three pieces that remain\n * occupy 377px of the 400px floor the frame protects for this column while the\n * right panel is open (`ui-layout`\'s `CENTER_MIN`) \u2014 so they fit, with the 72px\n * between the chip and the cluster left to `margin-right: auto`. Withdrawing the\n * title there bought nothing and cost the one place a narrow column still names\n * its session.\n */\n@container ccd-conversation (max-width: 420px) {\n html[data-dsh-ccd-style="true"] .ST7X_W_header:not(.ST7X_W_headerBlank):has(.ST7X_W_tabs:not([data-ccd-view-dock]))\n .ST7X_W_crumbs {\n display: none;\n }\n}\n\n/*\n * The three views this plugin adds \u2014 the project folder among them \u2014 step aside\n * only while the row still carries the View switcher.\n *\n * The switcher is normally parked in the sidebar\'s window-control row\n * (`compat/view-switch.ts` writes `data-ccd-view-dock` on the tablist wherever\n * that column has the corner to hold it), and the row is then the title, the\n * team chip and the cluster: at most ~235px of chrome, so all three stay drawn\n * at any width the Composer can still be used at \u2014 including the column an\n * expanded right panel leaves, where they are the only way to switch the\n * panel\'s view back. With the switcher still in the row \u2014 a Windows build, or a\n * collapsed macOS sidebar whose window is narrow \u2014 there are four pieces on the\n * line, and these three are the ones that go: they are the plugin\'s own\n * controls, they have a second entry in the right panel\'s own tab strip, and\n * the native team chip keeps its full label either way.\n */\n@container ccd-conversation (max-width: 420px) {\n html[data-dsh-ccd-style="true"] .ST7X_W_header:has(.ST7X_W_tabs:not([data-ccd-view-dock]))\n .ST7X_W_headerUtilities [data-ccd-header-view] {\n display: none;\n }\n}\n\n/* Transcript column keeps the reference\'s breathing room. */\nhtml[data-dsh-ccd-style="true"] .icaHSq_scroll {\n padding-top: 16px;\n padding-bottom: 8px;\n}\n\n/* Center and size the host\'s jump-to-bottom control.\n * Source: ui-chat/ChatView.module.css, HOST.chatViewToBottomSlot/toBottom.\n * Negative margin follows the button size to retain its gap above Composer.\n */\nhtml[data-dsh-ccd-style="true"] .icaHSq_toBottomSlot {\n justify-content: center;\n padding-right: 0;\n}\n\nhtml[data-dsh-ccd-style="true"] .icaHSq_toBottom {\n width: var(--ccd-to-bottom-size);\n height: var(--ccd-to-bottom-size);\n margin-top: calc(-1 * var(--ccd-to-bottom-size));\n}\n\n/* Paint edge gradients on the page body above the transcript and below\n * sticky Composer. conversation-fade.ts publishes scroll flags and the bottom\n * inset; the lower overlay covers Composer\'s full footprint. Since Composer\n * is inside the scroller, the gradient belongs on this separate layer.\n */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_body::before,\nhtml[data-dsh-ccd-style="true"] .ST7X_W_body::after {\n content: "";\n position: absolute;\n left: 0;\n right: 0;\n height: var(--ccd-edge-fade);\n background: var(--ccd-canvas);\n pointer-events: none;\n /* Above the transcript, below the Composer seat (7) and the jump-to-bottom\n control (8) so neither is veiled. */\n z-index: 1;\n opacity: 0;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_body::before {\n top: 0;\n background: linear-gradient(to bottom, var(--ccd-canvas), transparent);\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_body::after {\n bottom: 0;\n height: calc(var(--ccd-fade-bottom-inset, 0px) + var(--ccd-edge-fade));\n background: linear-gradient(\n to top,\n var(--ccd-canvas) 0,\n var(--ccd-canvas) calc(100% - var(--ccd-edge-fade)),\n transparent 100%\n );\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_body:has([data-conversation-scroll][data-ccd-fade-top])::before {\n opacity: 1;\n}\n\nhtml[data-dsh-ccd-style="true"] .ST7X_W_body:has([data-conversation-scroll][data-ccd-fade-bottom])::after {\n opacity: 1;\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root {\n font-size: var(--dsh-content-font-size, 14px);\n line-height: 24px;\n color: var(--ccd-text);\n}\n\n/* Official MarkdownText in assistant replies only. Compact reasoning/tool\n Markdown, user references and the Composer retain their own presentation.\n Sources and static-library class names are pinned in compat/host-dom.ts. */\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) {\n font-family: var(--ccd-font-ui);\n font-size: inherit;\n /* The host runs the assistant body at 14px/24px; the reference runs the same\n 14px on a 20px line, which is what its paragraphs and its paragraph gaps\n are measured against. Reasoning and tool Markdown (`[data-markdown-variant\n ="compact"]`) keep the host\'s own leading. */\n line-height: var(--ccd-transcript-line-height);\n color: var(--ccd-text);\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) :is(a, ._fileMention_1ypvv_85) {\n max-width: 100%;\n color: var(--ccd-link);\n font-weight: 500;\n text-decoration-line: underline;\n text-decoration-style: solid;\n text-decoration-color: var(--ccd-link-underline);\n text-decoration-thickness: 1px;\n text-underline-offset: 3px;\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) :is(a, ._fileMention_1ypvv_85):is(:hover, :focus) {\n text-decoration-color: var(--ccd-link);\n}\n\n/*\n * Render URL and file links as text alone. The host uses `.linkIcon` for\n * category glyphs inside anchors and file mentions; hiding it also removes\n * its margin while preserving the link text, underline and click behavior.\n */\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) :is(a, ._fileMention_1ypvv_85) ._linkIcon_1ypvv_94 {\n display: none;\n}\n\n/* Limit chips to the text column so long identifiers can wrap inside them.\n The host uses !important for its code-size ratio, so this override must too.\n The fenced block is not a chip: it takes its own rule below. */\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) :not(pre) > code {\n display: inline-block;\n max-width: 100%;\n vertical-align: baseline;\n font-family: var(--ccd-font-code);\n font-size: 0.95em !important;\n font-weight: 500;\n line-height: 1.2;\n color: var(--ccd-inline-code-text);\n background: var(--ccd-inline-code-fill);\n border: 1px solid var(--ccd-inline-code-border);\n border-radius: 5px;\n padding: 1px 4px;\n overflow-wrap: anywhere;\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) strong {\n font-weight: var(--ccd-font-weight-strong);\n}\n\n/*\n * Headings. The host draws the transcript\'s h1/h2/h3 at 21/19/18px inside a\n * 32px top and 16px bottom margin, a full step larger and a good deal looser\n * than the reference: its crop measures a 19px h1 and a 16px h2 (the tokens in\n * `theme/tokens.css` carry that measurement), and the ink between two stacked\n * headings is 20 device px against this plugin\'s 68. The margin below is what\n * puts that gap back \u2014 the collapsed 8px plus each heading\'s own half-leading\n * lands within a couple of device px of the reference at @2x. h4-h6 keep the\n * host\'s size (body size, weight 600) and take the same rhythm.\n */\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) :is(h1, h2, h3) {\n margin: 8px 0 6px;\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) h1 {\n font-size: var(--ccd-font-size-h1);\n line-height: 26px;\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) h2 {\n font-size: var(--ccd-font-size-h2);\n line-height: 22px;\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) h3 {\n font-size: var(--ccd-font-size-h3);\n line-height: 20px;\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) :is(h4, h5, h6) {\n margin: 10px 0 6px;\n}\n\n/*\n * Block rhythm. The host spaces the transcript\'s blocks at 16px. The reference\n * runs tighter around prose and quotes \u2014 its crop puts a paragraph-to-quote and\n * a quote-to-list run at 18 device px of ink, which is ~7-10px of collapsed\n * margin once each block\'s half-leading is taken off \u2014 while its lists and its\n * table and code cards keep the host\'s step. Only the two that differ move;\n * list items keep the host\'s own 8px rhythm.\n *\n * The list guard is written on the paragraph \u2014 `p:not(li p)` \u2014 and not as a\n * `:not(li) > p` child of the root: a paragraph in this DOM is the root\'s own\n * child, and a descendant combinator there asks for a parent *below* the root,\n * so it matched nothing but the paragraphs nested one level deeper (a quote\'s)\n * and every top-level paragraph kept the host\'s 16px.\n */\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) p:not(li p) {\n margin: 10px 0;\n}\n\n/*\n * Blockquote. The host draws a 2px rule in `--dsw-alias-label-caption` \u2014 its\n * own mid grey \u2014 and leaves the text at full body ink, so a quote reads as an\n * indented paragraph rather than as a quote. The reference draws a 4px rule in\n * the same value as this plugin\'s outline token, with its text one step down in\n * ink (this plugin\'s secondary step, against a body that stays near-black). The\n * rule-to-text distance matches in both (27 and 29 device px), so only the\n * rule\'s weight, its colour and the text\'s ink move.\n */\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) blockquote {\n margin: 10px 0;\n border-left: 4px solid var(--ccd-border);\n padding-left: 15px;\n color: var(--ccd-text-secondary);\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) hr {\n /* The host spaces the rule at 32px on each side; the reference\'s crop puts\n its card-rule-copy run at 71 device px of ink, which is ~20px a side. */\n margin: 20px 0;\n}\n\n/*\n * List rhythm. The host sizes its marker\'s own line at the 24px it used to draw\n * the body at, so the marker box stands a step taller than the transcript\'s\n * leading and stretches every list row to 22px \u2014 the item pitch lands at 28\n * where the reference\'s own rows measure 26 (its 40 device px at 1.53). The\n * marker rides the item\'s line instead, which is what a browser that sets no\n * marker line at all would do.\n */\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) li::marker {\n line-height: inherit;\n}\n\n/*\n * Task lists. The host leaves the list marker in place, so a `- [x]` row draws\n * "\u2022 \u2611 label" where the reference draws the checkbox alone, and its 6px\n * inter-item margin \u2014 which the reference\'s own rows do not carry \u2014 widens the\n * pitch to 46 device px against the reference\'s 35. The marker goes and the\n * margin goes with it; the checkbox itself already matches (the reference\'s\n * fill measures the same 20 device px in the host\'s grey accent).\n *\n * The checkbox sits directly in the item in a tight list and inside the item\'s\n * paragraph in a loose one, so both shapes are named.\n */\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) :is(li:has(> input[type="checkbox"]), li:has(> p:first-child > input[type="checkbox"])) {\n list-style: none;\n margin-top: 0;\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) input[type="checkbox"] {\n /* The host\'s 8px trailing margin leaves 20 device px before the label where\n the reference leaves 10. */\n margin-right: 6px;\n}\n\n/*\n * Fenced code blocks take the reference\'s outlined card: a white fill inside a\n * single hairline, replacing the host\'s cool wash. The host paints three nested\n * surfaces with that wash \u2014 the wrapper, the language banner and `pre` itself \u2014\n * and the banner\'s outer wrapper falls back to the page token, so every one of\n * them is repainted here; leaving any single layer would show a grey band.\n *\n * `data-code-wrap` is a stable host anchor (the class list also carries\n * `md-code-block`); the inner layers are reached through it so no hashed class\n * name is repeated here.\n *\n * The card is also a *row*, because the reference\'s is. Its one-line fence\n * draws as a single 33px-tall card (50 device px at @2x) that hugs its content \u2014\n * code first, the copy control (and the wrap toggle, and the run control on a\n * shell fence) trailing it \u2014 and floors at 320px, which is why its own short\n * `npm run check` card carries a wide gap between the code and its controls.\n * The host instead fills the column and stacks a 36px language banner over a\n * `pre` padded `6px 22px 20px`, which is 135 device px for that same one line.\n *\n * So the block becomes a flex row with the banner reversed behind the code: the\n * banner\'s controls ride the first code line, the `pre` takes the row\'s spare\n * width (pushing those controls to the trailing edge exactly as the reference\'s\n * floored card does) and the column stays the ceiling. `min(320px, 100%)` keeps\n * a narrow transcript from being pushed out by the floor.\n */\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) [data-code-wrap] {\n box-sizing: border-box;\n border: 1px solid var(--ccd-border);\n border-radius: var(--ccd-code-radius);\n background: var(--ccd-card);\n display: flex;\n /* The banner is the DOM\'s first child; the code reads first. */\n flex-direction: row-reverse;\n align-items: flex-start;\n width: fit-content;\n min-width: min(320px, 100%);\n max-width: 100%;\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) [data-code-wrap] > *,\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) [data-code-wrap] [data-code-block-banner],\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) [data-code-wrap] pre {\n /* The card owns the surface now; the layers inside it only carry text. */\n background: transparent;\n border-radius: 0;\n}\n\n/*\n * The banner stops being a row above the code. Its wrapper is the sticky\n * scroller that used to pin the language row to the top of a tall card; in a\n * row it is the card\'s trailing control strip, seated on the first code line\n * (31px = the `pre`\'s 6px padding over the host\'s 19px code line), which is the\n * whole card when the fence is one line. The host\'s own compact variant\n * neutralises the same wrapper the same way.\n */\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) [data-code-wrap] > :has(> [data-code-block-banner]) {\n position: static;\n z-index: auto;\n display: flex;\n align-items: center;\n height: 31px;\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) [data-code-wrap] [data-code-block-banner] {\n width: 100%;\n /* 11px, not 12: the card\'s own 1px outline sits outside this padding and the\n reference measures its 18 device px from the card\'s outer edge. */\n padding: 0 11px 0 0;\n font: inherit;\n}\n\n/*\n * The language label is not painted: the reference\'s card shows the code and its\n * controls alone, and its short card is 320px wide for 90px of code precisely\n * because nothing else sits in that row. The label is the banner\'s leading\n * group in both of the host\'s toolbar shapes, so it is named structurally\n * rather than by its hashed class; the copy and wrap controls, their labels and\n * the run control on a shell fence all stay.\n */\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) [data-code-wrap] [data-code-block-banner] > :first-child {\n display: none;\n}\n\n/*\n * The code keeps the host\'s 19px line and takes the reference\'s size (one step\n * under the body, where the host draws it at 11px) and its padding: the\n * reference holds the line with ~6px above and below and 16px of leading inset\n * (26 device px at @2x), against the host\'s `6px 22px 20px`. `min-width: 0`\n * lets the code shrink below its own content when the wrap toggle is off.\n */\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) [data-code-wrap] pre {\n flex: 1 1 auto;\n min-width: 0;\n padding: 6px 16px;\n font-size: var(--ccd-font-size-muted);\n}\n\n/*\n * Tables: one rounded card, as in the reference. The host collapses borders,\n * takes every cell\'s leading padding away (`padding: 10px 16px 10px 0`) and\n * leaves the row rule to each cell, so a card that can carry a radius needs\n * separate borders, one outline of its own and its own cell padding. The head\n * keeps a filled surface and a rule one step darker than the outline; body rows\n * are separated by hairlines, and the card\'s own edge closes the last one.\n * Column alignment markers stay the host\'s business.\n */\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) table {\n box-sizing: border-box;\n width: 100%;\n max-width: 100%;\n border: 1px solid var(--ccd-border);\n border-radius: var(--ccd-table-radius);\n border-collapse: separate;\n border-spacing: 0;\n /* The head fill and the row rules are painted by the cells, so the card has\n to clip them to its own radius. */\n overflow: hidden;\n font-size: inherit;\n line-height: inherit;\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) :is(th, td) {\n /* The host sizes table text one step below the transcript; the reference\n keeps a table at body size. Its row is 33px \u2014 the transcript\'s 20px line\n with 6px above and below, which is the 51 device px both shots measure \u2014\n so the cells keep the body leading and pay the difference in padding. */\n font-size: inherit;\n line-height: inherit;\n padding: 6px 10px;\n border: 0;\n border-bottom: 1px solid var(--ccd-border);\n vertical-align: middle;\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) thead th {\n background: var(--ccd-raised);\n border-bottom-color: var(--ccd-table-head-line);\n color: var(--ccd-text);\n font-weight: var(--ccd-font-weight-strong);\n}\n\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root ._markdown_1ypvv_5:not([data-markdown-variant="compact"]) tbody tr:last-child :is(th, td) {\n border-bottom: 0;\n}\n\n/*\n * Wide tables stay inside the content column.\n *\n * The host marks every table of four columns or more `md-table-wide`\n * (AssistantMarkdown.module.css) and bleeds that wrapper out to the scroller\'s\n * full width through `--dsh-table-spare`\uFF0F`--dsh-table-lead`, pinning the table\'s\n * leading edge back to the column with a negative margin and a matching leading\n * pad. In the host\'s own geometry the Composer card is 32px wider than the\n * column, so the borrowed width lands inside its edges; here the card and the\n * column are the same width, and a bleeded table left the card and the Composer\n * on different right edges (measured +86px at the reference window, growing with\n * the view area). Dropping the bleed keeps the table on the column; a table\n * whose rows cannot wrap far enough still uses the host\'s own `md-table-wide`\n * scroll, which needs the wrapper\'s own `overflow-x` and scrollbar gutter, so\n * neither is touched here.\n */\nhtml[data-dsh-ccd-style="true"] .gKv1-q_root .gKv1-q_body .md-table-wide {\n box-sizing: border-box;\n width: 100%;\n max-width: 100%;\n margin-left: 0;\n padding-left: 0;\n}\n\nhtml[data-dsh-ccd-style="true"] .icaHSq_column {\n --dsh-chat-flow-gap: 10px;\n}\n\n/* User message: soft raised bubble, as in the reference. */\nhtml[data-dsh-ccd-style="true"] .IzP3Va_bubble {\n padding: 7px 14px;\n border-radius: var(--ccd-composer-radius);\n background: var(--ccd-raised);\n color: var(--ccd-text);\n font-size: var(--dsh-content-font-size, 14px);\n line-height: 22px;\n}\n\nhtml[data-dsh-ccd-style="true"] .IzP3Va_userStack {\n max-width: min(calc(var(--dsh-chat-content-width) * 0.72), 82%);\n gap: 6px;\n}\n\n/* Tool and reasoning rows: quiet single lines. */\nhtml[data-dsh-ccd-style="true"] .Q2dzhW_root,\nhtml[data-dsh-ccd-style="true"] .HiSsxa_root,\nhtml[data-dsh-ccd-style="true"] .-gRAhq_root {\n color: var(--ccd-text-secondary);\n font-size: var(--ccd-font-size-body);\n}\n';
|
|
12021
13500
|
|
|
12022
13501
|
// src/client/features/conversation/view-switch-icons.ts
|
|
12023
13502
|
var VIEW_SWITCH_ICONS = Object.freeze({
|
|
@@ -12051,6 +13530,14 @@ var conversationFeature = {
|
|
|
12051
13530
|
const probe = probeHost(document);
|
|
12052
13531
|
if (!probe.conversation) environment.logger.debug("conversation: body not mounted yet");
|
|
12053
13532
|
scope.add(environment.dom.mountStyles(conversation_default));
|
|
13533
|
+
scope.add(mountReplySignature(
|
|
13534
|
+
document,
|
|
13535
|
+
(error) => environment.logger.error("conversation: reply signature failed", error)
|
|
13536
|
+
));
|
|
13537
|
+
scope.add(mountConversationProcess(
|
|
13538
|
+
document,
|
|
13539
|
+
(error) => environment.logger.error("conversation: process expansion failed", error)
|
|
13540
|
+
));
|
|
12054
13541
|
mountHeaderVisibility(environment.host.slots, environment.config.view, scope);
|
|
12055
13542
|
scope.add(mountViewSwitch(
|
|
12056
13543
|
document,
|
|
@@ -13113,11 +14600,21 @@ function mountStatisticsCard(document2, scope, logger, source) {
|
|
|
13113
14600
|
card?.layout();
|
|
13114
14601
|
};
|
|
13115
14602
|
let observer;
|
|
14603
|
+
const frames = frameScheduler(() => sync(), document2.defaultView);
|
|
14604
|
+
const relevant = (records) => {
|
|
14605
|
+
const hero = document2.querySelector(HERO);
|
|
14606
|
+
if (hero === null) return true;
|
|
14607
|
+
if (recordsTouch(records, hero)) return true;
|
|
14608
|
+
return card !== null && recordsTouch(records, card.element);
|
|
14609
|
+
};
|
|
13116
14610
|
try {
|
|
13117
|
-
observer = new MutationObserver(() =>
|
|
14611
|
+
observer = new MutationObserver((records) => {
|
|
14612
|
+
if (relevant(records)) frames.schedule();
|
|
14613
|
+
});
|
|
13118
14614
|
observer.observe(document2.body, { childList: true, subtree: true });
|
|
13119
14615
|
sync();
|
|
13120
14616
|
} catch (error) {
|
|
14617
|
+
frames.cancel();
|
|
13121
14618
|
observer?.disconnect();
|
|
13122
14619
|
remove();
|
|
13123
14620
|
logger.error("statistics: card mount failed", error);
|
|
@@ -13129,6 +14626,7 @@ function mountStatisticsCard(document2, scope, logger, source) {
|
|
|
13129
14626
|
sync();
|
|
13130
14627
|
});
|
|
13131
14628
|
scope.add(() => {
|
|
14629
|
+
frames.cancel();
|
|
13132
14630
|
window.removeEventListener("resize", relayout);
|
|
13133
14631
|
observer?.disconnect();
|
|
13134
14632
|
unsubscribe();
|
|
@@ -13148,6 +14646,7 @@ function isUsable(snapshot) {
|
|
|
13148
14646
|
}
|
|
13149
14647
|
function createStatsSource(options) {
|
|
13150
14648
|
const url = options.url ?? STATS_PATH;
|
|
14649
|
+
const page = globalThis.document;
|
|
13151
14650
|
const listeners = /* @__PURE__ */ new Set();
|
|
13152
14651
|
let snapshot = null;
|
|
13153
14652
|
let timer;
|
|
@@ -13166,21 +14665,24 @@ function createStatsSource(options) {
|
|
|
13166
14665
|
}
|
|
13167
14666
|
};
|
|
13168
14667
|
const schedule = (delay) => {
|
|
13169
|
-
if (disposed) return;
|
|
14668
|
+
if (disposed || page?.hidden) return;
|
|
13170
14669
|
timer = setTimeout(() => {
|
|
14670
|
+
timer = void 0;
|
|
13171
14671
|
void poll();
|
|
13172
14672
|
}, delay);
|
|
13173
14673
|
};
|
|
13174
14674
|
const poll = async () => {
|
|
13175
|
-
if (disposed) return;
|
|
13176
|
-
|
|
14675
|
+
if (disposed || page?.hidden || controller !== void 0) return;
|
|
14676
|
+
const read = new AbortController();
|
|
14677
|
+
controller = read;
|
|
13177
14678
|
let delay = IDLE_INTERVAL_MS;
|
|
13178
14679
|
try {
|
|
13179
14680
|
const response = await fetch(url, {
|
|
13180
14681
|
headers: { accept: "application/json" },
|
|
13181
14682
|
credentials: "same-origin",
|
|
13182
|
-
signal:
|
|
14683
|
+
signal: read.signal
|
|
13183
14684
|
});
|
|
14685
|
+
if (disposed || controller !== read) return;
|
|
13184
14686
|
if (!response.ok) {
|
|
13185
14687
|
if (!reported) {
|
|
13186
14688
|
reported = true;
|
|
@@ -13190,6 +14692,7 @@ function createStatsSource(options) {
|
|
|
13190
14692
|
delay = RETRY_INTERVAL_MS;
|
|
13191
14693
|
} else {
|
|
13192
14694
|
const body = await response.json();
|
|
14695
|
+
if (disposed || controller !== read) return;
|
|
13193
14696
|
const parsed = readStatsSnapshot(body);
|
|
13194
14697
|
if (parsed === null) {
|
|
13195
14698
|
if (!reported) {
|
|
@@ -13206,28 +14709,52 @@ function createStatsSource(options) {
|
|
|
13206
14709
|
}
|
|
13207
14710
|
}
|
|
13208
14711
|
} catch (error) {
|
|
13209
|
-
if (disposed || controller
|
|
14712
|
+
if (disposed || controller !== read || read.signal.aborted) return;
|
|
13210
14713
|
options.logger.debug(`statistics: snapshot read failed (${String(error)})`);
|
|
13211
14714
|
delay = RETRY_INTERVAL_MS;
|
|
13212
14715
|
} finally {
|
|
13213
|
-
controller
|
|
13214
|
-
|
|
14716
|
+
if (controller === read) {
|
|
14717
|
+
controller = void 0;
|
|
14718
|
+
schedule(delay);
|
|
14719
|
+
}
|
|
14720
|
+
}
|
|
14721
|
+
};
|
|
14722
|
+
const pause = () => {
|
|
14723
|
+
if (timer !== void 0) {
|
|
14724
|
+
clearTimeout(timer);
|
|
14725
|
+
timer = void 0;
|
|
14726
|
+
}
|
|
14727
|
+
const pending = controller;
|
|
14728
|
+
controller = void 0;
|
|
14729
|
+
pending?.abort();
|
|
14730
|
+
};
|
|
14731
|
+
const visibility = () => {
|
|
14732
|
+
if (page.hidden) pause();
|
|
14733
|
+
else {
|
|
14734
|
+
if (timer !== void 0) {
|
|
14735
|
+
clearTimeout(timer);
|
|
14736
|
+
timer = void 0;
|
|
14737
|
+
}
|
|
14738
|
+
void poll();
|
|
13215
14739
|
}
|
|
13216
14740
|
};
|
|
14741
|
+
page?.addEventListener("visibilitychange", visibility);
|
|
13217
14742
|
void poll();
|
|
13218
14743
|
return {
|
|
13219
14744
|
get current() {
|
|
13220
14745
|
return snapshot;
|
|
13221
14746
|
},
|
|
13222
14747
|
subscribe(listener) {
|
|
14748
|
+
if (disposed) return () => {
|
|
14749
|
+
};
|
|
13223
14750
|
listeners.add(listener);
|
|
13224
14751
|
return () => listeners.delete(listener);
|
|
13225
14752
|
},
|
|
13226
14753
|
dispose() {
|
|
13227
14754
|
disposed = true;
|
|
14755
|
+
page?.removeEventListener("visibilitychange", visibility);
|
|
13228
14756
|
listeners.clear();
|
|
13229
|
-
|
|
13230
|
-
controller?.abort();
|
|
14757
|
+
pause();
|
|
13231
14758
|
}
|
|
13232
14759
|
};
|
|
13233
14760
|
}
|
|
@@ -13305,6 +14832,10 @@ function apply(ctx) {
|
|
|
13305
14832
|
(error) => logger.error("composer: statistics readout observer failed", error),
|
|
13306
14833
|
next.features["composer-stats"]
|
|
13307
14834
|
));
|
|
14835
|
+
scope.add(mountHeroChips(
|
|
14836
|
+
document,
|
|
14837
|
+
(error) => logger.error("composer: hero chip width observer failed", error)
|
|
14838
|
+
));
|
|
13308
14839
|
scope.add(mountComposerStatusOrder(
|
|
13309
14840
|
document,
|
|
13310
14841
|
(error) => logger.error("composer: status-bar order observer failed", error)
|