dsh-claude-desktop-theme 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -51
- package/lib/client.js +2342 -270
- package/lib/index.js +387 -20
- package/lib/types/client/compat/adapter.d.ts +22 -1
- package/lib/types/client/compat/composer-menus.d.ts +6 -3
- package/lib/types/client/compat/context-panel.d.ts +47 -0
- package/lib/types/client/compat/empty-groups.d.ts +46 -0
- package/lib/types/client/compat/host-dom.d.ts +64 -0
- package/lib/types/client/compat/permission-menu.d.ts +88 -0
- package/lib/types/client/compat/sidebar-navigation.d.ts +11 -0
- package/lib/types/client/compat/stats-values.d.ts +18 -1
- package/lib/types/client/compat/view-options.d.ts +58 -0
- package/lib/types/client/contracts/ports.d.ts +147 -0
- package/lib/types/client/features/context-panel/ContextPanel.d.ts +12 -0
- package/lib/types/client/features/context-panel/mount.d.ts +20 -0
- package/lib/types/client/features/settings/locales.d.ts +7 -0
- package/lib/types/client/features/sidebar/navigation-icons.d.ts +6 -0
- package/lib/types/client/features/view-options/ViewOptionsCard.d.ts +30 -0
- package/lib/types/client/features/view-options/mount.d.ts +25 -0
- package/lib/types/host/context/compaction.d.ts +26 -0
- package/lib/types/host/context/pricing.d.ts +99 -0
- package/lib/types/host/context/service.d.ts +24 -0
- package/lib/types/host/context/unit.d.ts +136 -0
- package/lib/types/host/index.d.ts +18 -0
- package/lib/types/shared/config.d.ts +26 -1
- package/lib/types/shared/context.d.ts +243 -0
- package/package.json +6 -3
package/lib/client.js
CHANGED
|
@@ -38,10 +38,13 @@ var FEATURE_IDS = [
|
|
|
38
38
|
"conversation",
|
|
39
39
|
"tool-calls",
|
|
40
40
|
"statistics",
|
|
41
|
-
"composer-pet"
|
|
41
|
+
"composer-pet",
|
|
42
|
+
"composer-stats",
|
|
43
|
+
"context-panel"
|
|
42
44
|
];
|
|
43
45
|
var APPEARANCE_FIELDS = ["canvas", "sidebar"];
|
|
44
46
|
var FONT_FIELDS = ["uiLatin", "uiCjk", "code"];
|
|
47
|
+
var VIEW_FIELDS = ["showEmptyGroups"];
|
|
45
48
|
var DEFAULT_FEATURES = Object.freeze({
|
|
46
49
|
shell: true,
|
|
47
50
|
sidebar: true,
|
|
@@ -49,7 +52,9 @@ var DEFAULT_FEATURES = Object.freeze({
|
|
|
49
52
|
conversation: true,
|
|
50
53
|
"tool-calls": false,
|
|
51
54
|
statistics: false,
|
|
52
|
-
"composer-pet": true
|
|
55
|
+
"composer-pet": true,
|
|
56
|
+
"composer-stats": false,
|
|
57
|
+
"context-panel": true
|
|
53
58
|
});
|
|
54
59
|
var DEFAULT_APPEARANCE = Object.freeze({
|
|
55
60
|
canvas: "",
|
|
@@ -60,6 +65,9 @@ var DEFAULT_FONTS = Object.freeze({
|
|
|
60
65
|
uiCjk: "",
|
|
61
66
|
code: ""
|
|
62
67
|
});
|
|
68
|
+
var DEFAULT_VIEW = Object.freeze({
|
|
69
|
+
showEmptyGroups: false
|
|
70
|
+
});
|
|
63
71
|
var HEX_COLOUR = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i;
|
|
64
72
|
var FONT_NAME = /^[\p{L}\p{N} ._-]+$/u;
|
|
65
73
|
var FONT_NAME_MAX_LENGTH = 64;
|
|
@@ -115,12 +123,21 @@ function resolveConfig(input = {}) {
|
|
|
115
123
|
throw new TypeError(`${key} must be boolean`);
|
|
116
124
|
}
|
|
117
125
|
}
|
|
126
|
+
const view = { ...DEFAULT_VIEW };
|
|
127
|
+
for (const field of VIEW_FIELDS) {
|
|
128
|
+
const value = input.view?.[field];
|
|
129
|
+
if (value !== void 0) {
|
|
130
|
+
if (typeof value !== "boolean") throw new TypeError(`view.${field} must be boolean`);
|
|
131
|
+
view[field] = value;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
118
134
|
return Object.freeze({
|
|
119
135
|
enabled: input.enabled ?? false,
|
|
120
136
|
debug: input.debug ?? false,
|
|
121
137
|
features: Object.freeze(features),
|
|
122
138
|
appearance: resolveSection("appearance", APPEARANCE_FIELDS, DEFAULT_APPEARANCE, normalizeColour, input.appearance, true),
|
|
123
|
-
fonts: resolveSection("fonts", FONT_FIELDS, DEFAULT_FONTS, normalizeFontName, input.fonts, true)
|
|
139
|
+
fonts: resolveSection("fonts", FONT_FIELDS, DEFAULT_FONTS, normalizeFontName, input.fonts, true),
|
|
140
|
+
view: Object.freeze(view)
|
|
124
141
|
});
|
|
125
142
|
}
|
|
126
143
|
function section(value) {
|
|
@@ -136,12 +153,20 @@ function adoptConfig(value) {
|
|
|
136
153
|
if (typeof rawFeatures[id] === "boolean") features[id] = rawFeatures[id];
|
|
137
154
|
}
|
|
138
155
|
}
|
|
156
|
+
const view = { ...DEFAULT_VIEW };
|
|
157
|
+
const rawView = section(input.view);
|
|
158
|
+
if (rawView !== void 0) {
|
|
159
|
+
for (const field of VIEW_FIELDS) {
|
|
160
|
+
if (typeof rawView[field] === "boolean") view[field] = rawView[field];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
139
163
|
return resolveConfig({
|
|
140
164
|
...typeof input.enabled === "boolean" ? { enabled: input.enabled } : {},
|
|
141
165
|
...typeof input.debug === "boolean" ? { debug: input.debug } : {},
|
|
142
166
|
features,
|
|
143
167
|
appearance: pickFields(APPEARANCE_FIELDS, section(input.appearance), normalizeColour),
|
|
144
|
-
fonts: pickFields(FONT_FIELDS, section(input.fonts), normalizeFontName)
|
|
168
|
+
fonts: pickFields(FONT_FIELDS, section(input.fonts), normalizeFontName),
|
|
169
|
+
view
|
|
145
170
|
});
|
|
146
171
|
}
|
|
147
172
|
function pickFields(fields, input, normalize) {
|
|
@@ -161,6 +186,133 @@ var ENTRY_ID = "ui-skin-ccd-style";
|
|
|
161
186
|
var TARGET_DSH_VERSION = "0.2.0-rc.2";
|
|
162
187
|
var ROOT_ATTRIBUTE = "data-dsh-ccd-style";
|
|
163
188
|
|
|
189
|
+
// src/shared/context.ts
|
|
190
|
+
var MCP_PREFIX = "mcp__";
|
|
191
|
+
var MIN_SEGMENT_PX = 2;
|
|
192
|
+
function count(value) {
|
|
193
|
+
return Number.isFinite(value) && value > 0 ? value : 0;
|
|
194
|
+
}
|
|
195
|
+
function readCount(value) {
|
|
196
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : null;
|
|
197
|
+
}
|
|
198
|
+
function readEntries(value) {
|
|
199
|
+
if (!Array.isArray(value)) return [];
|
|
200
|
+
const entries = [];
|
|
201
|
+
for (const entry of value) {
|
|
202
|
+
if (!Array.isArray(entry) || entry.length < 2) continue;
|
|
203
|
+
const [name, tokens] = entry;
|
|
204
|
+
if (typeof name !== "string" || name === "") continue;
|
|
205
|
+
const priced = typeof tokens === "number" && Number.isFinite(tokens) && tokens > 0 ? tokens : 0;
|
|
206
|
+
if (priced === 0) continue;
|
|
207
|
+
entries.push([name, priced]);
|
|
208
|
+
}
|
|
209
|
+
return entries;
|
|
210
|
+
}
|
|
211
|
+
function readContextView(value) {
|
|
212
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
213
|
+
const raw = value;
|
|
214
|
+
return {
|
|
215
|
+
tools: readEntries(raw["tools"]),
|
|
216
|
+
files: readEntries(raw["files"]),
|
|
217
|
+
skills: readEntries(raw["skills"]),
|
|
218
|
+
skillTokens: count(typeof raw["skillTokens"] === "number" ? raw["skillTokens"] : 0),
|
|
219
|
+
fileTokens: count(typeof raw["fileTokens"] === "number" ? raw["fileTokens"] : 0),
|
|
220
|
+
compaction: readCount(raw["compaction"])
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function slice(key, tokens) {
|
|
224
|
+
const priced = count(tokens);
|
|
225
|
+
return priced > 0 ? { key, tokens: priced } : null;
|
|
226
|
+
}
|
|
227
|
+
function splitTools(tools, total) {
|
|
228
|
+
const priced = count(total);
|
|
229
|
+
if (priced === 0) return { mcp: 0, tools: 0 };
|
|
230
|
+
let mcp = 0;
|
|
231
|
+
let builtin = 0;
|
|
232
|
+
for (const [name, tokens] of tools) {
|
|
233
|
+
if (name.startsWith(MCP_PREFIX)) mcp += count(tokens);
|
|
234
|
+
else builtin += count(tokens);
|
|
235
|
+
}
|
|
236
|
+
const pricedTotal = mcp + builtin;
|
|
237
|
+
if (pricedTotal === 0) return { mcp: 0, tools: priced };
|
|
238
|
+
const share = Math.round(priced * mcp / pricedTotal);
|
|
239
|
+
return { mcp: share, tools: priced - share };
|
|
240
|
+
}
|
|
241
|
+
function contextBreakdown(source) {
|
|
242
|
+
const window2 = count(source.window);
|
|
243
|
+
const used = count(source.used);
|
|
244
|
+
const skills = count(source.skillTokens);
|
|
245
|
+
const memory = count(source.fileTokens);
|
|
246
|
+
const tools = count(source.toolsTokens);
|
|
247
|
+
const split = splitTools(source.tools ?? [], tools);
|
|
248
|
+
const messages = Math.max(0, count(source.messageTokens) - skills - memory);
|
|
249
|
+
const autocompact = autocompactTokens(window2, source.compaction, used);
|
|
250
|
+
const slices = [];
|
|
251
|
+
for (const entry of [
|
|
252
|
+
slice("mcp", split.mcp),
|
|
253
|
+
slice("tools", split.tools),
|
|
254
|
+
slice("system", count(source.systemTokens)),
|
|
255
|
+
slice("skills", skills),
|
|
256
|
+
slice("memory", memory),
|
|
257
|
+
slice("messages", messages),
|
|
258
|
+
slice("autocompact", autocompact),
|
|
259
|
+
/* The buffer is reserved out of the window's spare room, not added on top
|
|
260
|
+
of the reading: the reference's own rows come to exactly 100% with it. */
|
|
261
|
+
slice("free", window2 - used - autocompact)
|
|
262
|
+
]) if (entry !== null) slices.push(entry);
|
|
263
|
+
return {
|
|
264
|
+
window: window2,
|
|
265
|
+
used,
|
|
266
|
+
percent: window2 === 0 ? 0 : Math.min(100, Math.round(used / window2 * 100)),
|
|
267
|
+
slices,
|
|
268
|
+
segments: slices.filter((entry) => entry.key !== "free" && entry.key !== "autocompact"),
|
|
269
|
+
residual: used - (count(source.systemTokens) + tools + count(source.messageTokens))
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function autocompactTokens(window2, compaction, used) {
|
|
273
|
+
if (compaction === null || window2 === 0) return 0;
|
|
274
|
+
return Math.max(0, Math.min(window2 - count(compaction), window2 - count(used)));
|
|
275
|
+
}
|
|
276
|
+
function barSegments(breakdown, barWidth) {
|
|
277
|
+
const drawn = [];
|
|
278
|
+
for (const entry of breakdown.slices) {
|
|
279
|
+
if (entry.key === "free") continue;
|
|
280
|
+
if (barWidth > 0 && breakdown.window > 0 && entry.tokens / breakdown.window * barWidth < MIN_SEGMENT_PX) continue;
|
|
281
|
+
drawn.push(entry);
|
|
282
|
+
}
|
|
283
|
+
const placed = drawn.reduce((sum, entry) => sum + entry.tokens, 0);
|
|
284
|
+
return { drawn, free: Math.max(0, breakdown.window - placed) };
|
|
285
|
+
}
|
|
286
|
+
function formatTokens(value) {
|
|
287
|
+
const tokens = Math.max(0, Math.round(value));
|
|
288
|
+
const scaled = (candidate) => candidate.toFixed(1).replace(/\.0$/u, "");
|
|
289
|
+
if (tokens < 1e3) return String(tokens);
|
|
290
|
+
if (tokens < 1e6) return `${scaled(tokens / 1e3)}k`;
|
|
291
|
+
return `${scaled(tokens / 1e6)}M`;
|
|
292
|
+
}
|
|
293
|
+
function formatPercent(tokens, window2) {
|
|
294
|
+
if (window2 <= 0) return "0.0%";
|
|
295
|
+
return `${(Math.max(0, tokens) / window2 * 100).toFixed(1)}%`;
|
|
296
|
+
}
|
|
297
|
+
function toolLabel(name) {
|
|
298
|
+
if (!name.startsWith(MCP_PREFIX)) return name;
|
|
299
|
+
const rest = name.slice(MCP_PREFIX.length);
|
|
300
|
+
const at = rest.indexOf("__");
|
|
301
|
+
if (at <= 0) return rest;
|
|
302
|
+
return `${rest.slice(0, at)} \xB7 ${rest.slice(at + 2)}`;
|
|
303
|
+
}
|
|
304
|
+
function mcpServer(name) {
|
|
305
|
+
if (!name.startsWith(MCP_PREFIX)) return null;
|
|
306
|
+
const rest = name.slice(MCP_PREFIX.length);
|
|
307
|
+
const at = rest.indexOf("__");
|
|
308
|
+
return at > 0 ? rest.slice(0, at) : rest;
|
|
309
|
+
}
|
|
310
|
+
function shortenPath(path, budget = 32) {
|
|
311
|
+
if (path.length <= budget || budget < 8) return path;
|
|
312
|
+
const tail = Math.max(4, Math.floor((budget - 1) * 0.4));
|
|
313
|
+
return `${path.slice(0, budget - 1 - tail)}\u2026${path.slice(-tail)}`;
|
|
314
|
+
}
|
|
315
|
+
|
|
164
316
|
// src/client/compat/host-builds.ts
|
|
165
317
|
var PINNED_BUILD = Object.freeze({
|
|
166
318
|
id: "dsh-0.2.0-rc.2-5e9e301d",
|
|
@@ -273,6 +425,7 @@ var HOST = Object.freeze({
|
|
|
273
425
|
/** SidebarRoot.module.css: expanded macOS window-chrome toggle. */
|
|
274
426
|
sidebarToggle: "._3WPZCG_toggle",
|
|
275
427
|
sidebarNewSession: "._3WPZCG_newSession",
|
|
428
|
+
sidebarNewSessionContent: "._3WPZCG_newSessionContent",
|
|
276
429
|
sidebarNewSessionLabel: "._3WPZCG_newSessionLabel",
|
|
277
430
|
sidebarNewSessionShortcut: "._3WPZCG_newSessionShortcut",
|
|
278
431
|
sidebarPanelList: "._3WPZCG_panelList",
|
|
@@ -287,6 +440,22 @@ var HOST = Object.freeze({
|
|
|
287
440
|
browserSectionHeader: "._7514NG_sectionHeader",
|
|
288
441
|
browserSectionLabel: "._7514NG_sectionLabel",
|
|
289
442
|
browserListArea: "._7514NG_listArea",
|
|
443
|
+
/**
|
|
444
|
+
* WorkspaceBrowser.module.css: the section header's view-options trigger —
|
|
445
|
+
* the one control there that is an `iconButton` *and* carries `wide` (its
|
|
446
|
+
* neighbours are `searchButton` and a plain `iconButton`), and the list class
|
|
447
|
+
* its card puts on the `Menu` surface. `compat/view-options.ts` reads the
|
|
448
|
+
* values out of that card and clicks its rows; the card itself is kept off
|
|
449
|
+
* screen while it does.
|
|
450
|
+
*/
|
|
451
|
+
viewOptionsTrigger: "._7514NG_sectionHeader ._7514NG_iconButton._7514NG_wide",
|
|
452
|
+
viewOptionsMenu: "._7514NG_viewOptionsMenu",
|
|
453
|
+
/**
|
|
454
|
+
* WorkspaceBrowser.module.css: one Workspace's row container — the box that
|
|
455
|
+
* holds its Workspace row, its nested groups and its Session rows, and the
|
|
456
|
+
* box `compat/empty-groups.ts` hides when the group has nothing to show.
|
|
457
|
+
*/
|
|
458
|
+
workspaceGroup: "._7514NG_groupSection",
|
|
290
459
|
settingsTrigger: ".Dws9Sa_trigger",
|
|
291
460
|
settingsTriggerRow: ".Dws9Sa_triggerRow",
|
|
292
461
|
accountTrigger: ".ZogL4G_trigger",
|
|
@@ -321,6 +490,12 @@ var HOST = Object.freeze({
|
|
|
321
490
|
assistantMarkdown: ".gKv1-q_root",
|
|
322
491
|
markdownBody: '._markdown_1ypvv_5:not([data-markdown-variant="compact"])',
|
|
323
492
|
markdownFileMention: "._fileMention_1ypvv_85",
|
|
493
|
+
/**
|
|
494
|
+
* MarkdownText.module.css: the category glyph the host seats inside a URL
|
|
495
|
+
* anchor (and inside file mentions and URL-promoted inline code). `conversation.css`
|
|
496
|
+
* hides the anchor's copy; the reference draws link text alone.
|
|
497
|
+
*/
|
|
498
|
+
markdownLinkIcon: "._linkIcon_1ypvv_94",
|
|
324
499
|
conversationScroll: ".ST7X_W_scrollBody",
|
|
325
500
|
/** ChatView.module.css: the transcript's own scroll column, inside the page's
|
|
326
501
|
scroll viewport, and the two shells of the floating jump-to-bottom control
|
|
@@ -373,6 +548,17 @@ var HOST = Object.freeze({
|
|
|
373
548
|
modelSelectEffort: ".cl2Rlq_triggerEffort",
|
|
374
549
|
/** PermissionSelect.module.css: compact permission text, keeping its button. */
|
|
375
550
|
permissionSelectLabel: ".wXeviG_triggerLabel",
|
|
551
|
+
/** PermissionSelect.module.css: the control itself, in the status bar and in
|
|
552
|
+
the new-session card alike — `compat/permission-menu.ts` reads the mode's
|
|
553
|
+
name off it, and writes the card's own wording back. */
|
|
554
|
+
permissionSelectTrigger: ".wXeviG_trigger",
|
|
555
|
+
/** PermissionSelect.module.css: the one preset badge DSH marks (`EXP`), on
|
|
556
|
+
the trigger as well as on the option row it belongs to. */
|
|
557
|
+
permissionSelectBadge: ".wXeviG_badge",
|
|
558
|
+
/** PermissionSelect.module.css: the badge row's own name, told apart from the
|
|
559
|
+
badge beside it — the row label carries both, and only this one is the
|
|
560
|
+
preset's name. */
|
|
561
|
+
permissionSelectOptionLabelText: ".wXeviG_optionLabelText",
|
|
376
562
|
/** TeamAction.module.css: native team dialog entry, retaining its label at every width. */
|
|
377
563
|
teamActionTrigger: "._2tNPVa_trigger",
|
|
378
564
|
teamActionLabel: "._2tNPVa_triggerLabel",
|
|
@@ -386,6 +572,15 @@ var HOST = Object.freeze({
|
|
|
386
572
|
statsSeparator: ".OpZ85W_sep",
|
|
387
573
|
statsPill: ".OpZ85W_pill",
|
|
388
574
|
contextMeterTrigger: ".y0jqnG_trigger",
|
|
575
|
+
/**
|
|
576
|
+
* ContextMeter.module.css: the host's own click-open breakdown panel. It is a
|
|
577
|
+
* `position: fixed` portal on `document.body` at `z-index: 1100` rather than a
|
|
578
|
+
* native popover, so this plugin's panel — a top-layer popover — paints over
|
|
579
|
+
* it; the marker the driver writes is the belt to that braces, and it keeps
|
|
580
|
+
* the host's panel out of the paint if a click ever reaches DSH's own
|
|
581
|
+
* handler. `compat/context-panel.ts` is the only reader.
|
|
582
|
+
*/
|
|
583
|
+
contextMeterPanel: ".y0jqnG_panel",
|
|
389
584
|
heroRoot: ".bocITq_root",
|
|
390
585
|
heroHeadline: ".bocITq_headline",
|
|
391
586
|
heroWorkspaceChip: ".bocITq_workspace",
|
|
@@ -396,7 +591,21 @@ var HOST = Object.freeze({
|
|
|
396
591
|
heroComposerStack: ".ST7X_W_composerStack",
|
|
397
592
|
/** Every menu surface the `Menu` primitive renders, portal or in place. */
|
|
398
593
|
menu: '[role="menu"]',
|
|
399
|
-
menuItem: '[role="menuitem"]'
|
|
594
|
+
menuItem: '[role="menuitem"]',
|
|
595
|
+
/** Menu.module.css: the row's own label seat, the one box this plugin puts a
|
|
596
|
+
second line after (`compat/permission-menu.ts`). The `_4ub78_` prefix is
|
|
597
|
+
the static web frontend's bundle, identical in both installers, so it needs
|
|
598
|
+
no entry in `host-builds.ts` — the same fact `tokens.css` records for the
|
|
599
|
+
primitive's check mark. */
|
|
600
|
+
menuItemLabel: "._itemLabel_4ub78_190",
|
|
601
|
+
/** Menu.module.css: the trailing mark a *selected* row draws — `Menu` renders
|
|
602
|
+
it only for the row whose id is in `selectedIds`, so its presence is how
|
|
603
|
+
`compat/view-options.ts` reads which option is current. */
|
|
604
|
+
menuItemCheck: "._check_4ub78_179",
|
|
605
|
+
/** Menu.module.css: the scrolling row group inside the card — where the
|
|
606
|
+
permission card's heading is written, above the rows and outside the
|
|
607
|
+
keyboard's walk (that walk collects `button`s only). */
|
|
608
|
+
menuViewport: "._viewport_4ub78_19"
|
|
400
609
|
});
|
|
401
610
|
var ANCHOR = Object.freeze({
|
|
402
611
|
frame: '[data-slot="root"] > div',
|
|
@@ -431,7 +640,14 @@ var ANCHOR = Object.freeze({
|
|
|
431
640
|
* editor's own `data-phase` input. `conversation.input.overlay` renders only
|
|
432
641
|
* with a Session, so this is the seat the no-session pet is measured from.
|
|
433
642
|
*/
|
|
434
|
-
composerCardHero: `${HOST.conversationRoot}[data-phase="hero"] [data-composer-card]
|
|
643
|
+
composerCardHero: `${HOST.conversationRoot}[data-phase="hero"] [data-composer-card]`,
|
|
644
|
+
/**
|
|
645
|
+
* ConversationRoot.tsx: the conversation body, carrying the Session identity
|
|
646
|
+
* the context panel reads its projections by. The Composer seat — and so the
|
|
647
|
+
* context ring — is a descendant, which is what makes `closest()` from the
|
|
648
|
+
* trigger enough. The host's own stable hook rather than the hashed class.
|
|
649
|
+
*/
|
|
650
|
+
conversationSession: "[data-conversation-session]"
|
|
435
651
|
});
|
|
436
652
|
var selectorTables = /* @__PURE__ */ new Map();
|
|
437
653
|
var anchorTables = /* @__PURE__ */ new Map();
|
|
@@ -627,6 +843,88 @@ function resolveBlankSessions(ctx) {
|
|
|
627
843
|
subscribe: (listener) => list.subscribe(listener)
|
|
628
844
|
};
|
|
629
845
|
}
|
|
846
|
+
function createSidebarSessionsPort(ctx) {
|
|
847
|
+
const sessions = ctx.sessions?.list;
|
|
848
|
+
const workspaces = ctx.workspaces?.list;
|
|
849
|
+
if (typeof sessions?.getSnapshot !== "function" || typeof sessions.subscribe !== "function" || typeof workspaces?.getSnapshot !== "function" || typeof workspaces.subscribe !== "function") return null;
|
|
850
|
+
return {
|
|
851
|
+
hasHistory() {
|
|
852
|
+
const catalog = sessions.getSnapshot();
|
|
853
|
+
if (catalog.phase !== "ready") return null;
|
|
854
|
+
return catalog.ids.some((id) => {
|
|
855
|
+
const row = catalog.byId[id];
|
|
856
|
+
return row !== void 0 && row.origin !== "subagent" && !(row.blank && row.parentId === void 0);
|
|
857
|
+
});
|
|
858
|
+
},
|
|
859
|
+
populatedGroups(filter, nested) {
|
|
860
|
+
const catalog = sessions.getSnapshot();
|
|
861
|
+
const registry = workspaces.getSnapshot();
|
|
862
|
+
if (catalog.phase !== "ready" || registry.phase !== "ready") return null;
|
|
863
|
+
const archived = new Set(registry.archivedSessionIds);
|
|
864
|
+
const populated = /* @__PURE__ */ new Set();
|
|
865
|
+
const matches = (id) => {
|
|
866
|
+
const row = catalog.byId[id];
|
|
867
|
+
if (row === void 0 || row.origin === "subagent" || row.blank && row.parentId === void 0) return false;
|
|
868
|
+
return filter === 0 ? !archived.has(id) : filter === 2 ? archived.has(id) : true;
|
|
869
|
+
};
|
|
870
|
+
const assigned = new Set(registry.items.flatMap((workspace) => workspace.sessionIds));
|
|
871
|
+
if (catalog.ids.some((id) => !assigned.has(id) && matches(id))) populated.add("");
|
|
872
|
+
for (const workspace of registry.items) {
|
|
873
|
+
if (workspace.sessionIds.some(matches)) populated.add(workspace.workspaceId);
|
|
874
|
+
}
|
|
875
|
+
if (nested) {
|
|
876
|
+
const path = (value) => value.replace(/\\/gu, "/").replace(/\/+$/u, "") + "/";
|
|
877
|
+
const occupiedPaths = registry.items.filter((row) => populated.has(row.workspaceId)).map((row) => path(row.path));
|
|
878
|
+
for (const row of registry.items) {
|
|
879
|
+
if (occupiedPaths.some((child) => child.startsWith(path(row.path)))) populated.add(row.workspaceId);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
return populated;
|
|
883
|
+
},
|
|
884
|
+
subscribe(listener) {
|
|
885
|
+
const offSessions = sessions.subscribe(listener);
|
|
886
|
+
let offWorkspaces;
|
|
887
|
+
try {
|
|
888
|
+
offWorkspaces = workspaces.subscribe(listener);
|
|
889
|
+
} catch (error) {
|
|
890
|
+
offSessions();
|
|
891
|
+
throw error;
|
|
892
|
+
}
|
|
893
|
+
return () => {
|
|
894
|
+
offWorkspaces();
|
|
895
|
+
offSessions();
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
function counted(value) {
|
|
901
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
902
|
+
}
|
|
903
|
+
function createContextProjections(ctx) {
|
|
904
|
+
const list = ctx.sessions?.list;
|
|
905
|
+
if (typeof list?.getSnapshot !== "function" || typeof list.subscribe !== "function") return null;
|
|
906
|
+
return {
|
|
907
|
+
read(sessionId) {
|
|
908
|
+
const bySession = list.getSnapshot().projectionsBySession;
|
|
909
|
+
const values = bySession?.[sessionId]?.values ?? {};
|
|
910
|
+
const pressure = values.contextPressure;
|
|
911
|
+
const window2 = counted(pressure?.contextWindow);
|
|
912
|
+
if (window2 === 0) return null;
|
|
913
|
+
const breakdown = values.contextBreakdown;
|
|
914
|
+
return {
|
|
915
|
+
window: window2,
|
|
916
|
+
/* The ring's own occupancy: a route that projects a shadow price wins
|
|
917
|
+
over the raw pressure, exactly as `contextOccupancy` decides it. */
|
|
918
|
+
used: counted(pressure?.projectedTokens ?? pressure?.pressureTokens),
|
|
919
|
+
systemTokens: counted(breakdown?.systemTokens),
|
|
920
|
+
toolsTokens: counted(breakdown?.toolsTokens),
|
|
921
|
+
messageTokens: counted(breakdown?.messageTokens),
|
|
922
|
+
breakdown: readContextView(values.ccdContext)
|
|
923
|
+
};
|
|
924
|
+
},
|
|
925
|
+
subscribe: (listener) => list.subscribe(listener)
|
|
926
|
+
};
|
|
927
|
+
}
|
|
630
928
|
function createHostServices(ctx) {
|
|
631
929
|
const slots = ctx.slots;
|
|
632
930
|
const theme = ctx.theme;
|
|
@@ -639,6 +937,8 @@ function createHostServices(ctx) {
|
|
|
639
937
|
theme,
|
|
640
938
|
themePreference: createThemePreferencePort(ctx),
|
|
641
939
|
blankSessions: resolveBlankSessions(ctx),
|
|
940
|
+
sidebarSessions: createSidebarSessionsPort(ctx),
|
|
941
|
+
contextProjections: createContextProjections(ctx),
|
|
642
942
|
configForms: resolveConfigForms(ctx),
|
|
643
943
|
workspace: {
|
|
644
944
|
openSession: (target) => workspace.openSession(target),
|
|
@@ -701,7 +1001,7 @@ function mountComposerMenuPlacement(document2, report) {
|
|
|
701
1001
|
if (height === 0) return;
|
|
702
1002
|
const below = viewport - MARGIN - (anchor.bottom + GAP);
|
|
703
1003
|
const above = anchor.top - GAP - topMargin();
|
|
704
|
-
if (
|
|
1004
|
+
if (above <= below) {
|
|
705
1005
|
restore(menu);
|
|
706
1006
|
return;
|
|
707
1007
|
}
|
|
@@ -773,25 +1073,165 @@ function mountComposerMenuPlacement(document2, report) {
|
|
|
773
1073
|
};
|
|
774
1074
|
}
|
|
775
1075
|
|
|
1076
|
+
// src/client/compat/empty-groups.ts
|
|
1077
|
+
function mountEmptyGroups(document2, hideEmpty, report, options = {}) {
|
|
1078
|
+
const host = hostSelectors(document2);
|
|
1079
|
+
let scheduled = 0;
|
|
1080
|
+
let observer;
|
|
1081
|
+
const marked = /* @__PURE__ */ new Set();
|
|
1082
|
+
let emptyState = null;
|
|
1083
|
+
let emptyArea = null;
|
|
1084
|
+
const unsubscribes = [];
|
|
1085
|
+
let disposed = false;
|
|
1086
|
+
const keepsGroup = (group, populated) => {
|
|
1087
|
+
for (const row of group.querySelectorAll("[data-row-key]")) {
|
|
1088
|
+
const key = row.getAttribute("data-row-key") ?? "";
|
|
1089
|
+
if (key.startsWith("workspace:") && row.getAttribute("aria-expanded") === "false") {
|
|
1090
|
+
if (populated === null || populated.has(key.slice("workspace:".length))) return true;
|
|
1091
|
+
}
|
|
1092
|
+
if (key.startsWith("overflow:")) return true;
|
|
1093
|
+
if (!key.startsWith("session:")) continue;
|
|
1094
|
+
if (row.hasAttribute("data-ccd-blank-session")) continue;
|
|
1095
|
+
return true;
|
|
1096
|
+
}
|
|
1097
|
+
return false;
|
|
1098
|
+
};
|
|
1099
|
+
const clearEmptyState = () => {
|
|
1100
|
+
emptyState?.remove();
|
|
1101
|
+
emptyState = null;
|
|
1102
|
+
emptyArea?.removeAttribute("data-ccd-list-empty");
|
|
1103
|
+
emptyArea = null;
|
|
1104
|
+
};
|
|
1105
|
+
const renderEmptyState = (area, empty, noHistory) => {
|
|
1106
|
+
if (!empty || area === null) {
|
|
1107
|
+
clearEmptyState();
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
const kind = noHistory ? "new" : "filtered";
|
|
1111
|
+
if (emptyArea !== area || !emptyState?.isConnected || emptyState.getAttribute("data-ccd-empty-state") !== kind) {
|
|
1112
|
+
clearEmptyState();
|
|
1113
|
+
const zh2 = (document2.documentElement.lang || globalThis.navigator?.language || "").toLowerCase().startsWith("zh");
|
|
1114
|
+
emptyState = document2.createElement("div");
|
|
1115
|
+
emptyState.className = "ccd-sidebar-empty";
|
|
1116
|
+
emptyState.setAttribute("data-ccd-empty-state", kind);
|
|
1117
|
+
emptyState.setAttribute("role", "status");
|
|
1118
|
+
const message = document2.createElement("div");
|
|
1119
|
+
message.textContent = noHistory ? zh2 ? "\u4F60\u53D1\u8D77\u7684\u4F1A\u8BDD\u4F1A\u663E\u793A\u5728\u8FD9\u91CC" : "Sessions you start will show up here" : zh2 ? "\u6CA1\u6709\u7B26\u5408\u5F53\u524D\u7B5B\u9009\u6761\u4EF6\u7684\u4F1A\u8BDD" : "No sessions match the current filters";
|
|
1120
|
+
emptyState.append(message);
|
|
1121
|
+
if (noHistory) {
|
|
1122
|
+
const pixels = document2.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
1123
|
+
pixels.classList.add("ccd-sidebar-empty-pixels");
|
|
1124
|
+
pixels.setAttribute("viewBox", "0 0 88 80");
|
|
1125
|
+
pixels.setAttribute("aria-hidden", "true");
|
|
1126
|
+
pixels.innerHTML = '<path opacity=".24" d="M50 0h10v10H50zM40 10h10v10H40zM60 10h10v10H60zM50 20h10v10H50z"/><path opacity=".15" d="M15 20h10v10H15zM5 30h10v10H5zM15 30h10v10H15zM25 30h10v10H25zM15 40h10v10H15zM60 30h5v10h-5zM75 20h5v20h-5zM65 40h10v10H65z"/><path opacity=".1" d="M25 50h5v10h-5zM30 60h5v10h-5zM35 70h5v10h-5zM60 50h5v10h-5zM55 60h5v10h-5zM50 70h5v10h-5z"/>';
|
|
1127
|
+
emptyState.append(pixels);
|
|
1128
|
+
}
|
|
1129
|
+
if (!noHistory && options.showAll !== void 0) {
|
|
1130
|
+
const button = document2.createElement("button");
|
|
1131
|
+
button.type = "button";
|
|
1132
|
+
button.textContent = zh2 ? "\u663E\u793A\u6240\u6709\u4F1A\u8BDD" : "Show all sessions";
|
|
1133
|
+
button.addEventListener("click", options.showAll);
|
|
1134
|
+
emptyState.append(button);
|
|
1135
|
+
}
|
|
1136
|
+
emptyArea = area;
|
|
1137
|
+
area.setAttribute("data-ccd-list-empty", "");
|
|
1138
|
+
area.prepend(emptyState);
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
const clear2 = () => {
|
|
1142
|
+
for (const node of marked) node.removeAttribute("data-ccd-empty-group");
|
|
1143
|
+
marked.clear();
|
|
1144
|
+
clearEmptyState();
|
|
1145
|
+
};
|
|
1146
|
+
const sync = () => {
|
|
1147
|
+
scheduled = 0;
|
|
1148
|
+
if (disposed) return;
|
|
1149
|
+
const noHistory = options.source?.hasHistory() === false;
|
|
1150
|
+
const wanted = /* @__PURE__ */ new Set();
|
|
1151
|
+
const preferences = options.settings?.getSnapshot();
|
|
1152
|
+
const populated = options.source?.populatedGroups(
|
|
1153
|
+
preferences?.groups[2]?.selected ?? null,
|
|
1154
|
+
preferences?.groups[0]?.selected === 1
|
|
1155
|
+
) ?? null;
|
|
1156
|
+
const groups = document2.querySelectorAll(host.workspaceGroup);
|
|
1157
|
+
for (const group of groups) {
|
|
1158
|
+
if (noHistory || hideEmpty && !keepsGroup(group, populated)) wanted.add(group);
|
|
1159
|
+
}
|
|
1160
|
+
for (const node of marked) {
|
|
1161
|
+
if (wanted.has(node)) continue;
|
|
1162
|
+
node.removeAttribute("data-ccd-empty-group");
|
|
1163
|
+
marked.delete(node);
|
|
1164
|
+
}
|
|
1165
|
+
for (const node of wanted) {
|
|
1166
|
+
if (marked.has(node)) continue;
|
|
1167
|
+
node.setAttribute("data-ccd-empty-group", "");
|
|
1168
|
+
marked.add(node);
|
|
1169
|
+
}
|
|
1170
|
+
const area = document2.querySelector(host.browserListArea);
|
|
1171
|
+
const flatRows = area?.querySelectorAll('[data-row-key^="session:"], [data-row-key^="overflow:"]');
|
|
1172
|
+
const flatEmpty = flatRows !== void 0 && flatRows.length > 0 && [...flatRows].every((row) => row.hasAttribute("data-ccd-blank-session"));
|
|
1173
|
+
const empty = groups.length > 0 ? wanted.size === groups.length : area !== null && (area.querySelector('[data-row-key="empty"]') !== null || flatEmpty);
|
|
1174
|
+
renderEmptyState(area, empty, noHistory);
|
|
1175
|
+
};
|
|
1176
|
+
const schedule = () => {
|
|
1177
|
+
if (scheduled !== 0) return;
|
|
1178
|
+
scheduled = 1;
|
|
1179
|
+
try {
|
|
1180
|
+
sync();
|
|
1181
|
+
} finally {
|
|
1182
|
+
scheduled = 0;
|
|
1183
|
+
}
|
|
1184
|
+
};
|
|
1185
|
+
try {
|
|
1186
|
+
observer = new MutationObserver(schedule);
|
|
1187
|
+
observer.observe(document2.body, {
|
|
1188
|
+
childList: true,
|
|
1189
|
+
subtree: true,
|
|
1190
|
+
attributes: true,
|
|
1191
|
+
// Host expansion/current-row changes and the blank-session adapter can
|
|
1192
|
+
// change membership without inserting a row. Our own marker is excluded.
|
|
1193
|
+
attributeFilter: ["data-row-key", "aria-expanded", "aria-selected", "data-ccd-blank-session"]
|
|
1194
|
+
});
|
|
1195
|
+
if (options.source) unsubscribes.push(options.source.subscribe(schedule));
|
|
1196
|
+
if (options.settings) unsubscribes.push(options.settings.subscribe(schedule));
|
|
1197
|
+
sync();
|
|
1198
|
+
} catch (error) {
|
|
1199
|
+
observer?.disconnect();
|
|
1200
|
+
for (const off of unsubscribes.splice(0)) off();
|
|
1201
|
+
clear2();
|
|
1202
|
+
report(error);
|
|
1203
|
+
}
|
|
1204
|
+
return () => {
|
|
1205
|
+
disposed = true;
|
|
1206
|
+
scheduled = 0;
|
|
1207
|
+
observer?.disconnect();
|
|
1208
|
+
for (const off of unsubscribes.splice(0)) off();
|
|
1209
|
+
clear2();
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
|
|
776
1213
|
// src/client/compat/stats-values.ts
|
|
777
1214
|
var STAT_VALUE_PROPERTY = "--ccd-stat-value";
|
|
778
1215
|
var TRAILING_WIDTH_PROPERTY = "--ccd-trailing-width";
|
|
779
1216
|
var MODEL_MAX_WIDTH_PROPERTY = "--ccd-model-max-width";
|
|
780
1217
|
var CLUSTER_GAP_PROPERTY = "--ccd-cluster-gap";
|
|
781
|
-
|
|
1218
|
+
var STATS_ATTRIBUTE = "data-ccd-composer-stats";
|
|
1219
|
+
function mountComposerStats(document2, report, readouts) {
|
|
782
1220
|
const host = hostSelectors(document2);
|
|
783
1221
|
let scheduled = 0;
|
|
784
1222
|
let trailing = null;
|
|
785
1223
|
let root = null;
|
|
786
1224
|
const sync = () => {
|
|
787
1225
|
scheduled = 0;
|
|
788
|
-
|
|
789
|
-
const
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
if (
|
|
794
|
-
|
|
1226
|
+
if (readouts) {
|
|
1227
|
+
for (const pill of document2.querySelectorAll(host.statsPill)) {
|
|
1228
|
+
const parts = (pill.querySelector(host.statsLabel)?.textContent ?? "").split("\xB7").map((part) => part.trim()).filter(Boolean);
|
|
1229
|
+
const value = parts.find((part) => part.includes("/")) ?? parts.find((part) => part.includes("%")) ?? parts[0] ?? "";
|
|
1230
|
+
const quoted = value === "" ? "" : JSON.stringify(value);
|
|
1231
|
+
if (pill.style.getPropertyValue(STAT_VALUE_PROPERTY) !== quoted) {
|
|
1232
|
+
if (quoted === "") pill.style.removeProperty(STAT_VALUE_PROPERTY);
|
|
1233
|
+
else pill.style.setProperty(STAT_VALUE_PROPERTY, quoted);
|
|
1234
|
+
}
|
|
795
1235
|
}
|
|
796
1236
|
}
|
|
797
1237
|
const nextRoot = document2.querySelector(host.composerRoot);
|
|
@@ -831,6 +1271,9 @@ function mountComposerStats(document2, report) {
|
|
|
831
1271
|
}
|
|
832
1272
|
};
|
|
833
1273
|
let observer;
|
|
1274
|
+
const html = document2.documentElement;
|
|
1275
|
+
const state = readouts ? "on" : "off";
|
|
1276
|
+
const previous = html.getAttribute(STATS_ATTRIBUTE);
|
|
834
1277
|
try {
|
|
835
1278
|
observer = new MutationObserver(schedule);
|
|
836
1279
|
observer.observe(document2.body, {
|
|
@@ -841,6 +1284,7 @@ function mountComposerStats(document2, report) {
|
|
|
841
1284
|
attributeFilter: ["style", "class", "data-model-compact"]
|
|
842
1285
|
});
|
|
843
1286
|
window.addEventListener("resize", schedule);
|
|
1287
|
+
html.setAttribute(STATS_ATTRIBUTE, state);
|
|
844
1288
|
sync();
|
|
845
1289
|
} catch (error) {
|
|
846
1290
|
observer?.disconnect();
|
|
@@ -858,6 +1302,10 @@ function mountComposerStats(document2, report) {
|
|
|
858
1302
|
for (const pill of document2.querySelectorAll(host.statsPill)) {
|
|
859
1303
|
pill.style.removeProperty(STAT_VALUE_PROPERTY);
|
|
860
1304
|
}
|
|
1305
|
+
if (html.getAttribute(STATS_ATTRIBUTE) === state) {
|
|
1306
|
+
if (previous === null) html.removeAttribute(STATS_ATTRIBUTE);
|
|
1307
|
+
else html.setAttribute(STATS_ATTRIBUTE, previous);
|
|
1308
|
+
}
|
|
861
1309
|
root = null;
|
|
862
1310
|
trailing = null;
|
|
863
1311
|
};
|
|
@@ -1017,6 +1465,200 @@ function mountComposerStatusOrder(document2, report) {
|
|
|
1017
1465
|
};
|
|
1018
1466
|
}
|
|
1019
1467
|
|
|
1468
|
+
// src/client/compat/permission-menu.ts
|
|
1469
|
+
var PERMISSION_MODE_ATTRIBUTE = "data-ccd-permission-mode";
|
|
1470
|
+
var PERMISSION_MENU_ATTRIBUTE = "data-ccd-permission-menu";
|
|
1471
|
+
var DESCRIPTION_ATTRIBUTE = "data-ccd-permission-desc";
|
|
1472
|
+
var HEADING_ATTRIBUTE = "data-ccd-permission-heading";
|
|
1473
|
+
var ZH = {
|
|
1474
|
+
heading: "\u6A21\u5F0F",
|
|
1475
|
+
presets: [
|
|
1476
|
+
{ value: "read-only", name: "\u4EC5\u53EF\u67E5\u770B", label: "\u4EC5\u53EF\u67E5\u770B", description: "\u6587\u4EF6\u53EA\u8BFB\uFF0C\u9700\u8981\u6539\u52A8\u65F6\u4F1A\u5148\u5F81\u6C42\u4F60\u7684\u540C\u610F" },
|
|
1477
|
+
{ value: "workspace-write", name: "\u5DE5\u4F5C\u533A\u5185\u4FEE\u6539", label: "\u5DE5\u4F5C\u533A\u5185\u4FEE\u6539", description: "\u5DE5\u4F5C\u533A\u5185\u7684\u6587\u4EF6\u53EF\u76F4\u63A5\u6539\uFF0C\u5DE5\u4F5C\u533A\u4E4B\u5916\u8981\u5148\u95EE\u4F60" },
|
|
1478
|
+
{ value: "danger-full-access", name: "\u5B8C\u5168\u6743\u9650", label: "\u5B8C\u5168\u6743\u9650", description: "\u4E0D\u518D\u9650\u5236\u6587\u4EF6\u6539\u52A8\uFF0C\u786E\u8BA4\u6B65\u9AA4\u66F4\u5C11" },
|
|
1479
|
+
{
|
|
1480
|
+
value: "auto",
|
|
1481
|
+
name: "Auto review",
|
|
1482
|
+
label: "\u81EA\u52A8\u5BA1\u67E5",
|
|
1483
|
+
badge: { host: "EXP", text: "\u5B9E\u9A8C" },
|
|
1484
|
+
description: "\u65E0\u6C99\u7BB1\u8FD0\u884C\uFF0C\u6BCF\u6B21\u5DE5\u5177\u8C03\u7528\u524D\u7531\u6A21\u578B\u5148\u5BA1\u4E00\u904D"
|
|
1485
|
+
}
|
|
1486
|
+
]
|
|
1487
|
+
};
|
|
1488
|
+
var EN = {
|
|
1489
|
+
heading: "Mode",
|
|
1490
|
+
presets: [
|
|
1491
|
+
{ value: "read-only", name: "Read Only", label: "Read Only", description: "Files stay read-only; changes ask for your approval" },
|
|
1492
|
+
{ value: "workspace-write", name: "Workspace Write", label: "Workspace Write", description: "Edits inside the workspace go through; anything beyond it asks" },
|
|
1493
|
+
{ value: "danger-full-access", name: "Full access", label: "Full access", description: "No file restrictions, and fewer confirmations" },
|
|
1494
|
+
{
|
|
1495
|
+
value: "auto",
|
|
1496
|
+
name: "Auto review",
|
|
1497
|
+
label: "Auto review",
|
|
1498
|
+
badge: { host: "EXP", text: "EXP" },
|
|
1499
|
+
description: "No sandbox; the model reviews every tool call first"
|
|
1500
|
+
}
|
|
1501
|
+
]
|
|
1502
|
+
};
|
|
1503
|
+
function permissionCopy(document2) {
|
|
1504
|
+
const language = (document2.documentElement.lang || globalThis.navigator?.language || "").toLowerCase();
|
|
1505
|
+
return language.startsWith("zh") ? ZH : EN;
|
|
1506
|
+
}
|
|
1507
|
+
function mountPermissionMenu(document2, report) {
|
|
1508
|
+
const host = hostSelectors(document2);
|
|
1509
|
+
const copy = permissionCopy(document2);
|
|
1510
|
+
const drawn = [];
|
|
1511
|
+
const added = [];
|
|
1512
|
+
const marked = [];
|
|
1513
|
+
let trigger = null;
|
|
1514
|
+
let attached = null;
|
|
1515
|
+
let scheduled = 0;
|
|
1516
|
+
const prune = () => {
|
|
1517
|
+
for (let index = drawn.length - 1; index >= 0; index -= 1) {
|
|
1518
|
+
if (!drawn[index].node.isConnected) drawn.splice(index, 1);
|
|
1519
|
+
}
|
|
1520
|
+
for (let index = added.length - 1; index >= 0; index -= 1) {
|
|
1521
|
+
if (!added[index].isConnected) added.splice(index, 1);
|
|
1522
|
+
}
|
|
1523
|
+
for (let index = marked.length - 1; index >= 0; index -= 1) {
|
|
1524
|
+
if (!marked[index].isConnected) marked.splice(index, 1);
|
|
1525
|
+
}
|
|
1526
|
+
};
|
|
1527
|
+
const restore = () => {
|
|
1528
|
+
for (const record of drawn) {
|
|
1529
|
+
if (record.node.isConnected && record.node.data === record.drawn) {
|
|
1530
|
+
record.node.data = record.original;
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
drawn.length = 0;
|
|
1534
|
+
for (const node of added) node.remove();
|
|
1535
|
+
added.length = 0;
|
|
1536
|
+
for (const node of marked) {
|
|
1537
|
+
node.removeAttribute(PERMISSION_MODE_ATTRIBUTE);
|
|
1538
|
+
node.removeAttribute(PERMISSION_MENU_ATTRIBUTE);
|
|
1539
|
+
}
|
|
1540
|
+
marked.length = 0;
|
|
1541
|
+
};
|
|
1542
|
+
const replace = (root, from, to) => {
|
|
1543
|
+
if (from === to) return;
|
|
1544
|
+
const walker = document2.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
1545
|
+
for (let found = walker.nextNode(); found !== null; found = walker.nextNode()) {
|
|
1546
|
+
const node = found;
|
|
1547
|
+
if (node.data.trim() !== from) continue;
|
|
1548
|
+
const original = node.data;
|
|
1549
|
+
node.data = original.replace(from, to);
|
|
1550
|
+
drawn.push({ node, original, drawn: node.data });
|
|
1551
|
+
}
|
|
1552
|
+
};
|
|
1553
|
+
const nameOf = (label) => (label.querySelector(host.permissionSelectOptionLabelText)?.textContent ?? label.textContent ?? "").trim();
|
|
1554
|
+
const presetOf = (label) => {
|
|
1555
|
+
const name = nameOf(label);
|
|
1556
|
+
return copy.presets.find((preset) => preset.name === name || preset.label === name);
|
|
1557
|
+
};
|
|
1558
|
+
const drawName = (label, badge, preset) => {
|
|
1559
|
+
replace(label, preset.name, preset.label);
|
|
1560
|
+
if (badge !== null && preset.badge !== void 0) {
|
|
1561
|
+
replace(badge, preset.badge.host, preset.badge.text);
|
|
1562
|
+
}
|
|
1563
|
+
};
|
|
1564
|
+
const drawCard = (row) => {
|
|
1565
|
+
const card = row.closest(host.menu);
|
|
1566
|
+
if (card === null) return;
|
|
1567
|
+
card.setAttribute(PERMISSION_MENU_ATTRIBUTE, "");
|
|
1568
|
+
if (!marked.includes(card)) marked.push(card);
|
|
1569
|
+
const viewport = card.querySelector(host.menuViewport);
|
|
1570
|
+
if (viewport === null) return;
|
|
1571
|
+
if (viewport.querySelector(`:scope > [${HEADING_ATTRIBUTE}]`) !== null) return;
|
|
1572
|
+
const heading = document2.createElement("div");
|
|
1573
|
+
heading.setAttribute(HEADING_ATTRIBUTE, "");
|
|
1574
|
+
heading.setAttribute("role", "presentation");
|
|
1575
|
+
heading.textContent = copy.heading;
|
|
1576
|
+
viewport.prepend(heading);
|
|
1577
|
+
added.push(heading);
|
|
1578
|
+
};
|
|
1579
|
+
const drawRow = (row) => {
|
|
1580
|
+
const label = row.querySelector(host.menuItemLabel);
|
|
1581
|
+
if (label === null) return;
|
|
1582
|
+
const badge = row.querySelector(host.permissionSelectBadge);
|
|
1583
|
+
const preset = presetOf(label);
|
|
1584
|
+
if (preset === void 0) return;
|
|
1585
|
+
drawName(label, badge, preset);
|
|
1586
|
+
row.setAttribute(PERMISSION_MODE_ATTRIBUTE, preset.value);
|
|
1587
|
+
if (!marked.includes(row)) marked.push(row);
|
|
1588
|
+
if (row.querySelector(`:scope > [${DESCRIPTION_ATTRIBUTE}]`) === null) {
|
|
1589
|
+
const description = document2.createElement("span");
|
|
1590
|
+
description.setAttribute(DESCRIPTION_ATTRIBUTE, "");
|
|
1591
|
+
description.textContent = preset.description;
|
|
1592
|
+
label.after(description);
|
|
1593
|
+
added.push(description);
|
|
1594
|
+
}
|
|
1595
|
+
drawCard(row);
|
|
1596
|
+
};
|
|
1597
|
+
const drawTrigger = (element) => {
|
|
1598
|
+
const label = element.querySelector(host.permissionSelectLabel);
|
|
1599
|
+
if (label === null) return;
|
|
1600
|
+
const badge = element.querySelector(host.permissionSelectBadge);
|
|
1601
|
+
const preset = presetOf(label);
|
|
1602
|
+
if (preset === void 0) return;
|
|
1603
|
+
drawName(label, badge, preset);
|
|
1604
|
+
};
|
|
1605
|
+
const sync = () => {
|
|
1606
|
+
scheduled = 0;
|
|
1607
|
+
prune();
|
|
1608
|
+
if (trigger !== null) drawTrigger(trigger);
|
|
1609
|
+
if (document2.querySelector(host.menu) === null) return;
|
|
1610
|
+
for (const row of document2.querySelectorAll(host.menuItem)) drawRow(row);
|
|
1611
|
+
};
|
|
1612
|
+
const schedule = () => {
|
|
1613
|
+
if (scheduled !== 0) return;
|
|
1614
|
+
scheduled = 1;
|
|
1615
|
+
try {
|
|
1616
|
+
sync();
|
|
1617
|
+
} finally {
|
|
1618
|
+
scheduled = 0;
|
|
1619
|
+
}
|
|
1620
|
+
};
|
|
1621
|
+
let tree;
|
|
1622
|
+
let contents;
|
|
1623
|
+
try {
|
|
1624
|
+
tree = new MutationObserver(() => {
|
|
1625
|
+
const found = document2.querySelector(host.permissionSelectTrigger);
|
|
1626
|
+
if (found !== attached) {
|
|
1627
|
+
contents?.disconnect();
|
|
1628
|
+
attached = found;
|
|
1629
|
+
trigger = found;
|
|
1630
|
+
if (found !== null) {
|
|
1631
|
+
contents = new MutationObserver(schedule);
|
|
1632
|
+
contents.observe(found, { childList: true, subtree: true, characterData: true });
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
schedule();
|
|
1636
|
+
});
|
|
1637
|
+
tree.observe(document2.body, { childList: true, subtree: true });
|
|
1638
|
+
const initial = document2.querySelector(host.permissionSelectTrigger);
|
|
1639
|
+
if (initial !== null) {
|
|
1640
|
+
attached = initial;
|
|
1641
|
+
trigger = initial;
|
|
1642
|
+
contents = new MutationObserver(schedule);
|
|
1643
|
+
contents.observe(initial, { childList: true, subtree: true, characterData: true });
|
|
1644
|
+
}
|
|
1645
|
+
sync();
|
|
1646
|
+
} catch (error) {
|
|
1647
|
+
tree?.disconnect();
|
|
1648
|
+
contents?.disconnect();
|
|
1649
|
+
restore();
|
|
1650
|
+
report(error);
|
|
1651
|
+
return () => {
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
return () => {
|
|
1655
|
+
scheduled = 0;
|
|
1656
|
+
tree?.disconnect();
|
|
1657
|
+
contents?.disconnect();
|
|
1658
|
+
restore();
|
|
1659
|
+
};
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1020
1662
|
// src/client/compat/workspace-menu.ts
|
|
1021
1663
|
var ELLIPSIS = "\u2026";
|
|
1022
1664
|
var SPACED_DOTS = ". . .";
|
|
@@ -1481,13 +2123,13 @@ function resolveThemeTokens(config) {
|
|
|
1481
2123
|
}
|
|
1482
2124
|
|
|
1483
2125
|
// src/client/theme/composer.css
|
|
1484
|
-
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 gap: 0;\n padding-top: 0;\n border: 0;\n border-radius: 0;\n background: transparent;\n box-shadow: none;\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 0;\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 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. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_row {\n position: relative;\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/* The command button is a bare glyph in the reference, not a filled circle \u2014\n and it is a chip like every other control in the row: 20px tall, its 14px\n glyph plus one chip padding on each side, so the box is the same 28px the\n host drew and only its height and corner move. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_add {\n width: calc(var(--ccd-row-icon-size) + 2 * var(--ccd-chip-padding-inline));\n height: var(--ccd-chip-height);\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\nhtml[data-dsh-ccd-style="true"] .yhfFVG_tools {\n align-items: center;\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/* 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/* Hero keeps the workspace row above the composer, as the reference does. */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_composerHero {\n gap: 6px;\n padding-bottom: 0;\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 justify-content: flex-start;\n height: 26px;\n padding: 0 var(--dsh-composer-side-clearance, 16px);\n}\n\n/* Both chips in that row are menu controls \u2014 the workspace picker and the\n agent-preset picker \u2014 and the reference draws each of them as a bordered\n card on the page rather than as a bare label: 1px `--ccd-border`, the row\n radius, a hairline shadow, and no fill until hover. The input surface below\n keeps the stronger border, because it is the editable surface. */\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: 26px;\n min-height: 26px;\n padding: 0 8px;\n border: 1px solid var(--ccd-border);\n border-radius: var(--ccd-radius-row);\n background: transparent;\n box-shadow: var(--ccd-shadow-card);\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 8px\n leading padding keep measuring from a 16px box, so the label\'s x does not\n move. `flex: none` is the host\'s own 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 cards 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';
|
|
2126
|
+
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 gap: 0;\n padding-top: 0;\n border: 0;\n border-radius: 0;\n background: transparent;\n box-shadow: none;\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 0;\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 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. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_row {\n position: relative;\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/* The command button is a bare glyph in the reference, not a filled circle \u2014\n and it is a chip like every other control in the row: 20px tall, its 14px\n glyph plus one chip padding on each side, so the box is the same 28px the\n host drew and only its height and corner move. */\nhtml[data-dsh-ccd-style="true"] .yhfFVG_add {\n width: calc(var(--ccd-row-icon-size) + 2 * var(--ccd-chip-padding-inline));\n height: var(--ccd-chip-height);\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\nhtml[data-dsh-ccd-style="true"] .yhfFVG_tools {\n align-items: center;\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/* Hero keeps the workspace row above the composer, as the reference does. */\nhtml[data-dsh-ccd-style="true"] .ST7X_W_composerHero {\n gap: 6px;\n padding-bottom: 0;\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 justify-content: flex-start;\n height: 26px;\n padding: 0 var(--dsh-composer-side-clearance, 16px);\n}\n\n/* Both chips in that row are menu controls \u2014 the workspace picker and the\n agent-preset picker \u2014 and the reference draws each of them as a bordered\n card on the page rather than as a bare label: 1px `--ccd-border`, the row\n radius, a hairline shadow, and no fill until hover. The input surface below\n keeps the stronger border, because it is the editable surface. */\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: 26px;\n min-height: 26px;\n padding: 0 8px;\n border: 1px solid var(--ccd-border);\n border-radius: var(--ccd-radius-row);\n background: transparent;\n box-shadow: var(--ccd-shadow-card);\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 8px\n leading padding keep measuring from a 16px box, so the label\'s x does not\n move. `flex: none` is the host\'s own 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 cards 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';
|
|
1485
2127
|
|
|
1486
2128
|
// src/client/theme/menus.css
|
|
1487
|
-
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 *\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. */\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/* 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/* 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 keeps its red ink (the host\'s own semantic colour, not\n * this palette\'s accent) and takes the same fill: one row, one fill. Selection\n * is not a fill in this document \u2014 it is the accent check the trailing seat\n * draws (`theme/tokens.css`) and that the host\'s `.selected` class already\n * 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/* 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';
|
|
2129
|
+
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. */\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/* 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/* 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 keeps its red ink (the host\'s own semantic colour, not\n * this palette\'s accent) and takes the same fill: one row, one fill. Selection\n * is not a fill in this document \u2014 it is the accent check the trailing seat\n * draws (`theme/tokens.css`) and that the host\'s `.selected` class already\n * 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/* 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';
|
|
1488
2130
|
|
|
1489
2131
|
// src/client/theme/tokens.css
|
|
1490
|
-
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 /* 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 /* 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 /* 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). */\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-trailing-inset-compact: 8px;\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\n /* Surfaces */\n --ccd-canvas: #fcfcfb;\n --ccd-sidebar: #fbfbfa;\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 --ccd-selected: #ecebe8;\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\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 /* 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\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 /* 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 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}\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 /* Surfaces. The track is the one surface that steps *down* in both schemes:\n it is the groove a raised thumb or selected segment sits in, so on the dark\n canvas it takes the sidebar's darker step rather than the card's brighter\n one (otherwise the raised piece would be invisible). */\n --ccd-canvas: #262624;\n --ccd-sidebar: #1f1e1d;\n --ccd-card: #30302e;\n --ccd-track: #1f1e1d;\n --ccd-raised: #2f2f2d;\n --ccd-hover: #313130;\n --ccd-selected: #3a3a37;\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\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 /* 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}\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";
|
|
2132
|
+
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 /* 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). */\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-trailing-inset-compact: 8px;\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\n /* Surfaces */\n --ccd-canvas: #fcfcfb;\n --ccd-sidebar: #fbfbfa;\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\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\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\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 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}\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 /* Surfaces. The track is the one surface that steps *down* in both schemes:\n it is the groove a raised thumb or selected segment sits in, so on the dark\n canvas it takes the sidebar's darker step rather than the card's brighter\n one (otherwise the raised piece would be invisible). */\n --ccd-canvas: #262624;\n --ccd-sidebar: #1f1e1d;\n --ccd-card: #30302e;\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\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\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}\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";
|
|
1491
2133
|
|
|
1492
2134
|
// src/client/theme/mount.ts
|
|
1493
2135
|
function mountTheme(environment, scope) {
|
|
@@ -2158,7 +2800,6 @@ function ModelPanel({ anchor, state, disabled, load, t, onClose, onSelect }) {
|
|
|
2158
2800
|
"data-autofocus": true,
|
|
2159
2801
|
className: "ccd-model-search",
|
|
2160
2802
|
"aria-label": t("search.placeholder"),
|
|
2161
|
-
placeholder: t("search.placeholder"),
|
|
2162
2803
|
value: query,
|
|
2163
2804
|
onChange: (e) => setQuery(e.currentTarget.value),
|
|
2164
2805
|
onKeyDown: (event) => {
|
|
@@ -2187,69 +2828,1315 @@ function ModelPanel({ anchor, state, disabled, load, t, onClose, onSelect }) {
|
|
|
2187
2828
|
const next = event.key === "ArrowDown" ? (index + 1) % targets.length : event.key === "ArrowUp" ? (index - 1 + targets.length) % targets.length : event.key === "Home" ? 0 : event.key === "End" ? targets.length - 1 : void 0;
|
|
2188
2829
|
if (next === void 0) return;
|
|
2189
2830
|
event.preventDefault();
|
|
2190
|
-
targets[next]?.focus();
|
|
2191
|
-
}, children: groups.map((group) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
2192
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ccd-model-provider", children: group.name }),
|
|
2193
|
-
group.models.map((model) => {
|
|
2194
|
-
const chosen = state.current?.provider === group.id && state.current.model === model.id;
|
|
2195
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
2831
|
+
targets[next]?.focus();
|
|
2832
|
+
}, children: groups.map((group) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
2833
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ccd-model-provider", children: group.name }),
|
|
2834
|
+
group.models.map((model) => {
|
|
2835
|
+
const chosen = state.current?.provider === group.id && state.current.model === model.id;
|
|
2836
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
2837
|
+
"button",
|
|
2838
|
+
{
|
|
2839
|
+
type: "button",
|
|
2840
|
+
"data-model-option": true,
|
|
2841
|
+
"aria-pressed": chosen,
|
|
2842
|
+
disabled,
|
|
2843
|
+
onClick: () => onSelect(modelSelection(group.id, model, state.current)),
|
|
2844
|
+
children: [
|
|
2845
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "ccd-model-name", children: model.name }),
|
|
2846
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { "aria-hidden": "true", children: chosen ? "\u2713" : "" })
|
|
2847
|
+
]
|
|
2848
|
+
},
|
|
2849
|
+
model.id
|
|
2850
|
+
);
|
|
2851
|
+
})
|
|
2852
|
+
] }, group.id)) })
|
|
2853
|
+
] });
|
|
2854
|
+
}
|
|
2855
|
+
|
|
2856
|
+
// src/client/features/model-controls/controls.css
|
|
2857
|
+
var controls_default = "/* Separate model/effort controls, sharing the official Composer's selection seat. */\nhtml[data-dsh-ccd-style=\"true\"] .ccd-model-controls { display: flex; align-items: center; gap: var(--ccd-cluster-gap, 4px); min-width: 0; max-width: var(--ccd-model-max-width, 45cqw); }\n/*\n * Both controls are the status bar's value chips, and a chip's fill is one\n * label line tall rather than one control row: `--ccd-chip-height` (20px)\n * inside the row's own 28px, so the model name and the effort beside it sit\n * 5.5px from the chip's edge instead of the 10px a full-height box leaves. The\n * row keeps `--ccd-control-row-height` \u2014 the dock, the ring's seat and the\n * trailing cluster are all measured from it \u2014 and this flex line centres the\n * chip in it. The corner is the height's own (`--ccd-chip-radius`, the\n * reference's 12 device px at @2x) rather than the row's 8px, which on a 20px\n * box would round the fill towards a pill, and the inline padding is the row's\n * one chip padding (`--ccd-chip-padding-inline`), which is what the command\n * plus, the permission control and the readouts beside them also take. The 12px\n * label, its chosen-value ink and the hover and expanded fills are unchanged;\n * only the box those fills paint is. All three tokens keep a fallback so a sheet\n * mounted without tokens.css still draws a chip rather than collapsing the\n * control to its text.\n */\n.ccd-model-trigger, .ccd-effort-trigger { display: inline-flex; align-items: center; justify-content: center; gap: 5px; min-width: 0; height: var(--ccd-chip-height, 20px); padding: 0 var(--ccd-chip-padding-inline, 7px); border: 0; border-radius: var(--ccd-chip-radius, 6px); background: transparent; color: var(--ccd-text-strong); font: inherit; font-size: 12px; cursor: pointer; white-space: nowrap; }\n.ccd-model-trigger { flex: 1 1 auto; overflow: hidden; }\n.ccd-model-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }\n.ccd-effort-trigger { flex: 0 0 auto; }\n.ccd-model-trigger:hover, .ccd-effort-trigger:hover, .ccd-model-trigger[aria-expanded=\"true\"], .ccd-effort-trigger[aria-expanded=\"true\"] { background: var(--ccd-hover); }\n.ccd-model-trigger:disabled, .ccd-effort-trigger:disabled { opacity: .55; cursor: default; }\n.ccd-model-trigger:focus-visible, .ccd-effort-trigger:focus-visible { outline: 2px solid var(--ccd-effort-color-1); outline-offset: 2px; }\n/*\n * The chosen value is marked by ink, not by weight: full black for the model\n * name and the effort, the row's own 400 for both, and the panel's option rows\n * the same. That only holds if each control names its colour here \u2014 the\n * Composer's tool row paints every button it contains with its own secondary\n * grey (`html[data-dsh-ccd-style=\"true\"] .yhfFVG_row button`, theme/composer.css,\n * specificity 0-2-2), and both triggers and the menu sit inside that row (the\n * panel is a fixed-position child of `.ccd-model-controls`). At their own\n * single-class weight they lose the cascade and the name renders grey whatever\n * the font weight. Only the model name is the chosen value here; the provider\n * heading above it stays `--ccd-text-secondary`.\n */\nhtml[data-dsh-ccd-style=\"true\"] .ccd-model-controls .ccd-model-trigger,\nhtml[data-dsh-ccd-style=\"true\"] .ccd-model-controls .ccd-effort-trigger,\nhtml[data-dsh-ccd-style=\"true\"] .ccd-model-controls .ccd-model-options button { color: var(--ccd-text-strong); }\n/*\n * The model card is a popup of the plugin's own making, so it answers to the\n * popup section of the design document rather than to the Composer it hangs\n * from: 12px corner, 1px outline, three-layer hairline shadow, card fill\n * (DESIGN.md \xA76.7). Its model names keep the chosen-value ink the rule above\n * gives them; everything the card decides for itself \u2014 how tall a row is, what\n * a heading is worth, where an editable face's line steps \u2014 is below.\n */\n.ccd-model-panel, .ccd-model-error { position: fixed; z-index: 1000; box-sizing: border-box; width: min(240px, calc(100vw - 24px)); max-height: calc(100vh - 24px); overflow: auto; padding: 6px; border: 1px solid var(--ccd-border); border-radius: 12px; background: var(--ccd-card); color: var(--ccd-text); box-shadow: var(--ccd-effort-shadow); font: 13px/1.4 var(--ccd-font-ui); }\n/* Native top-layer placement keeps the existing anchor coordinates. The\n backdrop stays click-through so usePopup still owns outside dismissal. */\n.ccd-model-panel[popover], .ccd-effort[popover] { inset: auto; margin: 0; }\n.ccd-model-panel::backdrop, .ccd-effort::backdrop { background: transparent; pointer-events: none; }\n/*\n * The card's one editable face is a bare line, not a box.\n *\n * The reference draws that cell chrome-free: no outline, no fill, no corner\n * and no hint text inside it, with one hairline under the cell where the list\n * begins \u2014 the caret and that rule are the whole of the affordance. The user\n * asked for exactly those two properties, because the panel focuses this cell\n * as it opens: the accent ring \xA7\u4E03 gives a focused control turned the card's\n * first row into a blue frame, and the placeholder put a hint on a field the\n * reader had not asked to fill in, which the reference does not draw either\n * (`ModelControls.tsx` writes no `placeholder` attribute).\n *\n * The cell keeps the card's 24px rhythm \u2014 the same box the rows and the group\n * headings below it occupy \u2014 and bleeds to the card's edges (`margin: 0 -6px`\n * with `width: calc(100% + 12px)`, the card's own 6px padding cancelled on\n * both sides) so the rule under it runs the card's full width like the\n * reference's own divider. The bleed moves the whole cell 6px left of the\n * rows, so the inline padding has to give that 6 back on top of the 8 the rows\n * take: 14px, and typed text \u2014 and the caret that opens with the card \u2014 lands\n * on the row labels' x instead of 6px ahead of it.\n */\n.ccd-model-search { box-sizing: border-box; width: calc(100% + 12px); height: 24px; margin: 0 -6px 4px; padding: 0 14px; border: 0; border-bottom: 1px solid var(--ccd-border-soft); border-radius: 0; background: transparent; color: inherit; font: inherit; }\n/* Focus is the caret. Both spellings are named because the panel focuses the\n field itself rather than a pointer, and a UA stylesheet may paint either\n one \u2014 neither may reach the card. */\n.ccd-model-search:focus, .ccd-model-search:focus-visible { outline: none; }\n/*\n * The provider heading is a cell of the same 24px rhythm as the rows it opens,\n * not a block with a size of its own: one 20px line with 2px above and below,\n * in the control-label size (12px \u2014 the document's scale has no 11px step) and\n * the secondary ink. It never takes a fill, because it is not a choice.\n */\n.ccd-model-provider { padding: 2px 8px; color: var(--ccd-text-secondary); font-size: 12px; line-height: 20px; }\n/*\n * One row is 24px (DESIGN.md \xA76.7): a 13px label on a 20px line with 2px above\n * and below \u2014 the reference's 48 device px at @2x, against the 34px this card\n * used to draw (13px/1.4 plus 8px of block padding). The corner follows the\n * height: 6px, because the host's 12px row radius is a pill at half of 24px.\n * Height is pinned against the box model rather than inherited from it: the\n * button leaves `box-sizing` to the page, so the same declarations would build\n * a 28px row under the content-box default.\n */\n.ccd-model-options button { display: flex; align-items: center; justify-content: space-between; gap: 8px; box-sizing: border-box; width: 100%; min-height: 24px; padding: 2px 8px; border: 0; border-radius: 6px; background: transparent; color: inherit; text-align: left; font: inherit; line-height: 20px; cursor: pointer; }\n/*\n * The row you are on versus the row you are pointing at: the mark says which\n * model is in use, the fill says where the pointer (or the keyboard) is.\n *\n * That is the family's own answer \u2014 the host menus this plugin repaints state\n * selection with the accent check their trailing seat draws and leave the row\n * itself transparent (theme/menus.css, \"Selection is not a fill in this\n * document\"). The card was the one member that spent a second grey on its\n * current option: the selected step softened six tenths toward the card, so\n * \"the model in use\" and \"the row under the pointer\" read as two steps, as the\n * sidebar's current row reads beside its hovered neighbour. The user took that\n * fill out, so the card now states the chosen model the way the rest of the\n * family does \u2014 the accent `\u2713` in the row's trailing seat (theme/tokens.css)\n * and nothing else, with one fill for the pointer and the keyboard.\n *\n * The one step is `--ccd-hover`, which is what the row already took; the\n * chosen row takes it like any other, so pointing at the model in use fills\n * that row rather than resting on a grey it had all along.\n */\n.ccd-model-options button:hover, .ccd-model-options button:focus-visible { background: var(--ccd-hover); }\n/*\n * Keyboard focus is more than the fill the walk shares with the pointer: the\n * row the keyboard is on and the row the pointer is on look the same, so the\n * card states the keyboard's seat twice \u2014 the fill for \"this row is live\" and\n * the document's focus ring (\xA7\u4E03: 2px solid accent, 2px offset, the shape\n * following the control's own 6px corner) for \"the keyboard is here\". That is\n * the pair this plugin's settings popup already draws on its own rows. The row\n * is centred in the card's 6px padding and 2px of offset\n * stay inside it, so the ring never reaches the card's edge.\n */\n.ccd-model-options button:focus-visible { outline: 2px solid var(--ccd-accent); outline-offset: 2px; }\n.ccd-model-options button:disabled { opacity: .55; cursor: default; }\n/*\n * The card's cells that are not choices: loading, empty, a failed provider and\n * the inline error. They take the rows' geometry and ink so the card reads as\n * one column whatever state it is in \u2014 the row is a flex line here for the same\n * reason it is one above, so the message and the retry after it keep the card's\n * 8px gap \u2014 and the retry is a bare accent label: the card has no room for the\n * host's filled button, and flat ink is the document's default control (\xA7\u4E8C.1).\n */\n.ccd-model-note { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px; margin: 0; padding: 2px 8px; color: var(--ccd-text-secondary); line-height: 20px; }\n.ccd-model-retry { padding: 0; border: 0; border-radius: 6px; background: none; color: var(--ccd-accent); font: inherit; cursor: pointer; }\n.ccd-model-retry:hover { text-decoration: underline; }\n.ccd-model-retry:focus-visible { outline: 2px solid var(--ccd-accent); outline-offset: 2px; }\n.ccd-model-error { bottom: 60px; right: 16px; }\n@container ccd-composer (max-width: 420px) { .ccd-model-trigger { padding-inline: 4px; } .ccd-effort-trigger { padding-inline: 4px; } }\n";
|
|
2858
|
+
|
|
2859
|
+
// src/client/features/model-controls/effort.css
|
|
2860
|
+
var effort_default = '/* Adapted from zanwei/claude-model-selector (MIT). See LICENSE. */\n\n.ccd-effort {\n --effort-accent: var(--ccd-effort-color-1);\n --effort-text: var(--ccd-effort-color-3);\n --effort-text-strong: var(--ccd-effort-color-4);\n --effort-muted: var(--ccd-effort-color-5);\n --effort-track: var(--ccd-effort-color-6);\n --effort-track-fill: var(--ccd-effort-color-7);\n --effort-progress: 0;\n --effort-thumb-w: 16px;\n --effort-thumb-h: 20px;\n --effort-track-pad: 1px;\n --effort-track-radius: 10px;\n --effort-surface: var(--ccd-effort-color-8);\n --effort-outline: var(--ccd-effort-color-9);\n --ease-decay: cubic-bezier(0.2, 0, 0, 1);\n display: block;\n width: min(220px, calc(100vw - 24px));\n max-width: 100%;\n color: var(--effort-text);\n font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;\n font-size: 12px;\n line-height: 1.35;\n font-synthesis: none;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n.ccd-effort *, .ccd-effort *::before, .ccd-effort *::after {\n box-sizing: border-box;\n}\n\n.ccd-effort button, .ccd-effort input {\n font: inherit;\n}\n\n.ccd-effort .panel {\n position: absolute;\n z-index: 4;\n right: 0;\n bottom: 3.25rem;\n width: 100%;\n padding: 12px;\n border: 1px solid var(--effort-outline);\n border-radius: 12px;\n background: var(--effort-surface);\n box-shadow:\n 0 1px 2px var(--ccd-effort-color-11),\n 0 4px 10px var(--ccd-effort-color-12),\n 0 12px 28px var(--ccd-effort-color-13);\n opacity: 1;\n transform: translateY(0);\n transition-property: opacity, transform;\n transition-duration: 120ms;\n transition-timing-function: ease-in;\n}\n\n.ccd-effort .header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n min-height: 20px;\n gap: 8px;\n}\n\n.ccd-effort .title {\n display: flex;\n align-items: baseline;\n min-width: 0;\n color: var(--effort-text);\n font-size: 13px;\n font-weight: 500;\n line-height: 1.3;\n letter-spacing: -0.01em;\n text-wrap: balance;\n}\n\n.ccd-effort .level-stage {\n position: relative;\n display: inline-block;\n height: 1.3em;\n margin-left: 4px;\n color: var(--effort-text-strong);\n line-height: inherit;\n vertical-align: baseline;\n}\n\n.ccd-effort .level-stage > span {\n position: absolute;\n top: 0;\n left: 0;\n line-height: inherit;\n white-space: nowrap;\n transform-origin: left center;\n}\n\n.ccd-effort .level-current {\n opacity: 1;\n transform: translateY(0);\n filter: blur(0);\n transition-property: opacity, transform, filter, color;\n transition-duration: 180ms;\n transition-timing-function: cubic-bezier(0.2, 0, 0, 1);\n}\n\n.ccd-effort .level-current.is-preparing {\n opacity: 0;\n transform: translateY(var(--label-enter-y, 3px));\n filter: blur(2px);\n transition-duration: 0ms;\n transition-delay: 0ms;\n}\n\n.ccd-effort .level-outgoing {\n opacity: 1;\n transform: translateY(0);\n filter: blur(0);\n pointer-events: none;\n transition-property: opacity, transform, filter;\n transition-duration: 180ms;\n transition-timing-function: cubic-bezier(0.2, 0, 0, 1);\n}\n\n.ccd-effort .level-outgoing.is-exiting {\n opacity: 0;\n transform: translateY(var(--label-exit-y, -3px));\n filter: blur(2px);\n}\n\n.ccd-effort[data-ultra] .level-current {\n color: var(--effort-accent);\n}\n\n.ccd-effort .help-wrap {\n position: relative;\n flex: 0 0 auto;\n}\n\n.ccd-effort .help-button {\n position: relative;\n display: grid;\n width: 20px;\n height: 20px;\n margin: 0;\n padding: 2px;\n place-items: center;\n border: 0;\n border-radius: 6px;\n background: transparent;\n color: var(--effort-muted);\n cursor: help;\n transition-property: color, background-color, scale;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n}\n\n.ccd-effort .help-button:hover {\n color: var(--ccd-effort-color-14);\n background: var(--ccd-effort-color-15);\n}\n\n.ccd-effort .help-button:active {\n scale: 0.96;\n}\n\n.ccd-effort .help-button:focus-visible {\n outline: 2px solid color-mix(in srgb, var(--effort-accent) 35%, transparent);\n outline-offset: 2px;\n}\n\n.ccd-effort .help-button svg {\n width: 14px;\n height: 14px;\n}\n\n.ccd-effort .tooltip {\n position: absolute;\n z-index: 8;\n top: auto;\n bottom: calc(100% + 6px);\n right: 0;\n width: min(220px, calc(100vw - 24px));\n padding: 10px 12px;\n border: 1px solid var(--ccd-effort-color-16);\n border-radius: 0.5rem;\n background: var(--ccd-effort-color-17);\n box-shadow:\n 0 2px 5px var(--ccd-effort-color-18),\n 0 8px 18px var(--ccd-effort-color-18);\n color: var(--ccd-effort-color-19);\n font-size: 12px;\n font-weight: 450;\n line-height: 1.35;\n text-wrap: pretty;\n opacity: 0;\n visibility: hidden;\n transform: translateY(-2px);\n transition-property: opacity, transform, visibility;\n transition-duration: 120ms;\n transition-timing-function: ease-in;\n}\n\n.ccd-effort .help-wrap:hover .tooltip,\n.ccd-effort .help-button:focus-visible + .tooltip,\n.ccd-effort .help-wrap[data-tip-open] .tooltip {\n opacity: 1;\n visibility: visible;\n transform: translateY(0);\n transition-timing-function: ease-out;\n}\n\n.ccd-effort .axis {\n display: flex;\n justify-content: space-between;\n margin-top: 16px;\n color: var(--effort-muted);\n font-size: 12px;\n font-weight: 450;\n line-height: 1.3;\n letter-spacing: -0.01em;\n}\n\n.ccd-effort .track-shell {\n position: relative;\n height: 32px;\n margin-top: 2px;\n}\n\n.ccd-effort .track {\n position: absolute;\n inset: 6px 0;\n overflow: hidden;\n border-radius: var(--effort-track-radius);\n background-color: var(--effort-track);\n box-shadow: inset 0 1px 1px var(--ccd-effort-color-20);\n}\n\n.ccd-effort .track-fill {\n position: absolute;\n z-index: 0;\n top: 0;\n bottom: 0;\n left: var(--effort-track-pad);\n width: calc(\n (100% - (var(--effort-track-pad) * 2) - var(--effort-thumb-w))\n * var(--effort-progress, 0)\n + (var(--effort-thumb-w) * 0.5)\n );\n border-radius: calc(var(--effort-track-radius) - 1px) 0 0 calc(var(--effort-track-radius) - 1px);\n background: var(--effort-track-fill);\n pointer-events: none;\n transition-property: opacity;\n transition-duration: 200ms;\n transition-timing-function: var(--ease-decay);\n}\n\n.ccd-effort[data-ultra] .track-fill {\n opacity: 0;\n}\n\n.ccd-effort .track::before {\n content: "";\n position: absolute;\n z-index: 0;\n inset: 0;\n border-radius: inherit;\n background: linear-gradient(\n 90deg,\n var(--ccd-effort-color-21) 0%,\n var(--ccd-effort-color-22) 18%,\n var(--ccd-effort-color-23) 32%,\n var(--ccd-effort-color-24) 48%,\n var(--ccd-effort-color-25) 68%,\n var(--ccd-effort-color-26) 82%,\n var(--ccd-effort-color-27) 100%\n );\n opacity: 0;\n transition-property: opacity;\n transition-duration: 340ms;\n transition-timing-function: ease-in;\n}\n\n.ccd-effort[data-ultra] .track::before {\n opacity: 1;\n}\n\n.ccd-effort .ultra-fallback,\n.ccd-effort .pixel-field {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n opacity: 0;\n transition-property: opacity;\n transition-duration: 200ms;\n transition-timing-function: var(--ease-decay);\n}\n\n.ccd-effort .ultra-fallback {\n background: linear-gradient(\n 90deg,\n var(--ccd-effort-color-21) 0%,\n var(--ccd-effort-color-22) 18%,\n var(--ccd-effort-color-23) 32%,\n var(--ccd-effort-color-28) 48%,\n var(--ccd-effort-color-29) 68%,\n var(--ccd-effort-color-30) 82%,\n var(--ccd-effort-color-31) 100%\n );\n}\n\n.ccd-effort[data-ultra] .pixel-field {\n opacity: 1;\n}\n\n.ccd-effort .ticks {\n position: absolute;\n z-index: 1;\n inset: 0 calc(var(--effort-track-pad) + var(--effort-thumb-w) * 0.5);\n display: flex;\n align-items: center;\n justify-content: space-between;\n pointer-events: none;\n}\n\n.ccd-effort .tick {\n width: 3px;\n height: 3px;\n border-radius: 999px;\n background: var(--ccd-effort-color-32);\n opacity: 0.82;\n transition-property: opacity;\n transition-duration: 180ms;\n transition-timing-function: var(--ease-decay);\n}\n\n.ccd-effort .tick:last-child {\n background: var(--effort-accent);\n opacity: 1;\n}\n\n.ccd-effort[data-ultra] .tick {\n opacity: 0;\n}\n\n.ccd-effort .range {\n position: absolute;\n z-index: 3;\n inset: 0 var(--effort-track-pad);\n width: calc(100% - var(--effort-track-pad) * 2);\n height: 100%;\n margin: 0;\n appearance: none;\n -webkit-appearance: none;\n border: 0;\n outline: 0;\n background: transparent;\n cursor: ew-resize;\n touch-action: none;\n}\n\n.ccd-effort .range::-webkit-slider-runnable-track {\n height: var(--effort-thumb-h);\n border: 0;\n background: transparent;\n}\n\n.ccd-effort .range::-webkit-slider-thumb {\n width: var(--effort-thumb-w);\n height: var(--effort-thumb-h);\n margin-top: 0;\n appearance: none;\n -webkit-appearance: none;\n border: 1px solid var(--ccd-effort-color-33);\n border-radius: 0.5rem;\n background: var(--ccd-effort-color-19);\n box-shadow:\n 0 1px 2px var(--ccd-effort-color-34),\n 0 4px 10px var(--ccd-effort-color-35);\n cursor: ew-resize;\n transition-property: transform;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n}\n\n.ccd-effort .range::-moz-range-track {\n height: var(--effort-thumb-h);\n border: 0;\n background: transparent;\n}\n\n.ccd-effort .range::-moz-range-progress {\n background: transparent;\n}\n\n.ccd-effort .range::-moz-range-thumb {\n width: var(--effort-thumb-w);\n height: var(--effort-thumb-h);\n border: 1px solid var(--ccd-effort-color-33);\n border-radius: 0.5rem;\n background: var(--ccd-effort-color-19);\n box-shadow:\n 0 1px 2px var(--ccd-effort-color-34),\n 0 4px 10px var(--ccd-effort-color-35);\n cursor: ew-resize;\n transition-property: transform;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n}\n\n.ccd-effort .range:active::-webkit-slider-thumb {\n transform: scale(0.96);\n}\n\n.ccd-effort .range:active::-moz-range-thumb {\n transform: scale(0.96);\n}\n\n.ccd-effort .range:focus-visible::-webkit-slider-thumb {\n box-shadow:\n 0 0 0 3px color-mix(in srgb, var(--effort-accent) 30%, transparent),\n 0 1px 2px var(--ccd-effort-color-34),\n 0 4px 10px var(--ccd-effort-color-35);\n}\n\n.ccd-effort .range:focus-visible::-moz-range-thumb {\n box-shadow:\n 0 0 0 3px color-mix(in srgb, var(--effort-accent) 30%, transparent),\n 0 1px 2px var(--ccd-effort-color-34),\n 0 4px 10px var(--ccd-effort-color-35);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .ccd-effort .level-current,\n .ccd-effort .level-outgoing,\n .ccd-effort .panel,\n .ccd-effort .track::before,\n .ccd-effort .track-fill,\n .ccd-effort .range::-webkit-slider-thumb,\n .ccd-effort .range::-moz-range-thumb,\n .ccd-effort .help-button,\n .ccd-effort .tooltip {\n transition-duration: 0.001ms;\n }\n\n .ccd-effort[data-ultra] .ultra-fallback {\n opacity: 1;\n }\n\n .ccd-effort .pixel-field {\n display: none;\n }\n}\n\n.ccd-effort .panel { position: static; width: 100%; }\n.ccd-effort { position: fixed; z-index: 1000; margin: 0; padding: 0; border: 0; background: transparent; overflow: visible; }\n.ccd-effort .level-stage::after {\n content: attr(data-longest);\n visibility: hidden;\n pointer-events: none;\n}\n.ccd-effort .selection-error { margin-top: 8px; color: var(--ccd-text-secondary); font-size: 12px; overflow-wrap: anywhere; }\n';
|
|
2861
|
+
|
|
2862
|
+
// src/client/features/model-controls/mount.ts
|
|
2863
|
+
function mountModelControls(ctx, environment, cleanup) {
|
|
2864
|
+
cleanup.add(environment.dom.mountStyles(controls_default));
|
|
2865
|
+
cleanup.add(environment.dom.mountStyles(effort_default));
|
|
2866
|
+
const registration = ctx.inject(["modelDirectories", "sessions", "slots", "remote", "remote.session"], (scope) => {
|
|
2867
|
+
const sessions = scope.sessions;
|
|
2868
|
+
return scope.slots.inject("conversation.input.model", () => scope.slots.register({
|
|
2869
|
+
name: "conversation.input.model",
|
|
2870
|
+
priority: -10,
|
|
2871
|
+
locale: "model",
|
|
2872
|
+
inject: (slotSessionId) => {
|
|
2873
|
+
const sessionId = slotSessionId;
|
|
2874
|
+
const directory = scope.modelDirectories.directoryFor(sessionId);
|
|
2875
|
+
const available = sessions.subagentAddress(sessionId) === void 0;
|
|
2876
|
+
return {
|
|
2877
|
+
available,
|
|
2878
|
+
useDirectory: () => (0, import_react4.useSyncExternalStore)(directory.store.subscribe, directory.store.getSnapshot),
|
|
2879
|
+
load: () => {
|
|
2880
|
+
if (available) void directory.load().catch(() => {
|
|
2881
|
+
});
|
|
2882
|
+
},
|
|
2883
|
+
select: (selection) => available ? directory.select(selection) : Promise.resolve(void 0),
|
|
2884
|
+
selectEffort: (route, effort) => {
|
|
2885
|
+
if (!available) return;
|
|
2886
|
+
const selection = effortSelection(directory.store.getSnapshot(), route, effort);
|
|
2887
|
+
if (selection) void directory.select(selection).catch(() => {
|
|
2888
|
+
});
|
|
2889
|
+
}
|
|
2890
|
+
};
|
|
2891
|
+
}
|
|
2892
|
+
}, ModelControls));
|
|
2893
|
+
});
|
|
2894
|
+
cleanup.add(() => {
|
|
2895
|
+
void registration.dispose();
|
|
2896
|
+
});
|
|
2897
|
+
}
|
|
2898
|
+
|
|
2899
|
+
// src/client/compat/slots.ts
|
|
2900
|
+
var DSH_SLOTS = Object.freeze({
|
|
2901
|
+
sidebar: "sidebar",
|
|
2902
|
+
workspaces: "sidebar.workspaces",
|
|
2903
|
+
settings: "sidebar.settings",
|
|
2904
|
+
main: "main",
|
|
2905
|
+
conversation: "main.conversation",
|
|
2906
|
+
header: "conversation.header",
|
|
2907
|
+
sessionHeader: "conversation.session.header",
|
|
2908
|
+
/** Trailing header seats: `utilities` is a list the native actions occupy too. */
|
|
2909
|
+
sessionHeaderUtilities: "conversation.session.header.utilities",
|
|
2910
|
+
sessionHeaderCorner: "conversation.session.header.corner",
|
|
2911
|
+
composerBar: "conversation.composer.bar",
|
|
2912
|
+
inputDock: "conversation.input.dock",
|
|
2913
|
+
composerDock: "conversation.composer.dock",
|
|
2914
|
+
toolView: "tool.call.toolview",
|
|
2915
|
+
leading: "shell.leading",
|
|
2916
|
+
/** Frame-wide floating layer, click-through until an entry opts back in. */
|
|
2917
|
+
shellOverlay: "shell.overlay"
|
|
2918
|
+
});
|
|
2919
|
+
var OVERRIDE_PRIORITY = -10;
|
|
2920
|
+
|
|
2921
|
+
// src/client/compat/view-options.ts
|
|
2922
|
+
var DRIVING_ATTRIBUTE = "data-ccd-view-options-driving";
|
|
2923
|
+
var OPEN_ATTRIBUTE = "data-ccd-view-options-open";
|
|
2924
|
+
function viewportOf(list) {
|
|
2925
|
+
for (const child of list.children) {
|
|
2926
|
+
if (child.getAttribute("role") === "presentation") return child;
|
|
2927
|
+
}
|
|
2928
|
+
return null;
|
|
2929
|
+
}
|
|
2930
|
+
function labelOf(document2, row) {
|
|
2931
|
+
const host = hostSelectors(document2);
|
|
2932
|
+
const seat = row.querySelector(host.menuItemLabel);
|
|
2933
|
+
return (seat?.textContent ?? row.textContent ?? "").trim();
|
|
2934
|
+
}
|
|
2935
|
+
function isChecked(document2, row) {
|
|
2936
|
+
return row.querySelector(hostSelectors(document2).menuItemCheck) !== null;
|
|
2937
|
+
}
|
|
2938
|
+
function readCard(document2, list) {
|
|
2939
|
+
const host = hostSelectors(document2);
|
|
2940
|
+
const viewport = viewportOf(list);
|
|
2941
|
+
if (viewport === null) return null;
|
|
2942
|
+
const groups = [];
|
|
2943
|
+
for (const child of viewport.children) {
|
|
2944
|
+
const element = child;
|
|
2945
|
+
if (element.getAttribute("role") === "presentation") {
|
|
2946
|
+
groups.push({ label: (element.textContent ?? "").trim(), rows: [] });
|
|
2947
|
+
continue;
|
|
2948
|
+
}
|
|
2949
|
+
const row = element.matches(host.menuItem) ? element : element.querySelector(host.menuItem);
|
|
2950
|
+
if (row === null) continue;
|
|
2951
|
+
if (groups.length === 0) return null;
|
|
2952
|
+
groups[groups.length - 1].rows.push(row);
|
|
2953
|
+
}
|
|
2954
|
+
if (groups.length !== 3) return null;
|
|
2955
|
+
for (const group of groups) {
|
|
2956
|
+
if (group.rows.length < 2) return null;
|
|
2957
|
+
if (group.rows.filter((row) => isChecked(document2, row)).length !== 1) return null;
|
|
2958
|
+
}
|
|
2959
|
+
return {
|
|
2960
|
+
groups: groups.map((group) => ({
|
|
2961
|
+
label: group.label,
|
|
2962
|
+
labels: group.rows.map((row) => labelOf(document2, row)),
|
|
2963
|
+
selected: group.rows.findIndex((row) => isChecked(document2, row))
|
|
2964
|
+
})),
|
|
2965
|
+
rows: groups.map((group) => group.rows)
|
|
2966
|
+
};
|
|
2967
|
+
}
|
|
2968
|
+
function createViewOptionsDriver(document2, report) {
|
|
2969
|
+
const host = hostSelectors(document2);
|
|
2970
|
+
let trigger = null;
|
|
2971
|
+
let attached = null;
|
|
2972
|
+
let open = false;
|
|
2973
|
+
let values = null;
|
|
2974
|
+
let snapshot = { open: false, anchor: null, values: null };
|
|
2975
|
+
let listener = null;
|
|
2976
|
+
let scheduled = 0;
|
|
2977
|
+
let driving = false;
|
|
2978
|
+
let tree;
|
|
2979
|
+
let attributes;
|
|
2980
|
+
let preferences = null;
|
|
2981
|
+
const preferenceListeners = /* @__PURE__ */ new Set();
|
|
2982
|
+
let reading = null;
|
|
2983
|
+
let disposed = false;
|
|
2984
|
+
let inspectWhenAttached = false;
|
|
2985
|
+
const publishPreferences = (next) => {
|
|
2986
|
+
if (disposed || JSON.stringify(next) === JSON.stringify(preferences)) return;
|
|
2987
|
+
preferences = next;
|
|
2988
|
+
for (const notify of preferenceListeners) notify();
|
|
2989
|
+
};
|
|
2990
|
+
const card = () => document2.querySelector(host.viewOptionsMenu);
|
|
2991
|
+
const mark = () => {
|
|
2992
|
+
const node = trigger;
|
|
2993
|
+
if (node === null) return;
|
|
2994
|
+
if (open) {
|
|
2995
|
+
if (!node.hasAttribute(OPEN_ATTRIBUTE)) node.setAttribute(OPEN_ATTRIBUTE, "");
|
|
2996
|
+
if (node.getAttribute("aria-haspopup") !== "menu") node.setAttribute("aria-haspopup", "menu");
|
|
2997
|
+
} else {
|
|
2998
|
+
node.removeAttribute(OPEN_ATTRIBUTE);
|
|
2999
|
+
node.removeAttribute("aria-haspopup");
|
|
3000
|
+
}
|
|
3001
|
+
const expanded = open ? "true" : "false";
|
|
3002
|
+
if (node.getAttribute("aria-expanded") !== expanded) node.setAttribute("aria-expanded", expanded);
|
|
3003
|
+
};
|
|
3004
|
+
const refresh = () => {
|
|
3005
|
+
snapshot = { open, anchor: trigger, values };
|
|
3006
|
+
if (card() === null) mark();
|
|
3007
|
+
listener?.();
|
|
3008
|
+
};
|
|
3009
|
+
const settle = async () => {
|
|
3010
|
+
await Promise.resolve();
|
|
3011
|
+
for (let attempt = 0; attempt < 20 && card() === null; attempt += 1) {
|
|
3012
|
+
await new Promise((resolve) => {
|
|
3013
|
+
setTimeout(resolve, 10);
|
|
3014
|
+
});
|
|
3015
|
+
}
|
|
3016
|
+
};
|
|
3017
|
+
const drive = async () => {
|
|
3018
|
+
const node = trigger;
|
|
3019
|
+
if (node === null) return null;
|
|
3020
|
+
const opened = card() === null;
|
|
3021
|
+
document2.documentElement.setAttribute(DRIVING_ATTRIBUTE, "");
|
|
3022
|
+
if (opened) {
|
|
3023
|
+
driving = true;
|
|
3024
|
+
try {
|
|
3025
|
+
node.click();
|
|
3026
|
+
} finally {
|
|
3027
|
+
driving = false;
|
|
3028
|
+
}
|
|
3029
|
+
await settle();
|
|
3030
|
+
}
|
|
3031
|
+
const list = card();
|
|
3032
|
+
if (list === null) {
|
|
3033
|
+
document2.documentElement.removeAttribute(DRIVING_ATTRIBUTE);
|
|
3034
|
+
return null;
|
|
3035
|
+
}
|
|
3036
|
+
return { list, opened };
|
|
3037
|
+
};
|
|
3038
|
+
const settleClosed = async () => {
|
|
3039
|
+
for (let attempt = 0; attempt < 20 && card() !== null; attempt += 1) {
|
|
3040
|
+
await new Promise((resolve) => {
|
|
3041
|
+
setTimeout(resolve, 10);
|
|
3042
|
+
});
|
|
3043
|
+
}
|
|
3044
|
+
};
|
|
3045
|
+
const endDrive = async (driven) => {
|
|
3046
|
+
if (driven.opened && trigger !== null && card() !== null) {
|
|
3047
|
+
driving = true;
|
|
3048
|
+
try {
|
|
3049
|
+
trigger.click();
|
|
3050
|
+
} finally {
|
|
3051
|
+
driving = false;
|
|
3052
|
+
}
|
|
3053
|
+
await settleClosed();
|
|
3054
|
+
}
|
|
3055
|
+
document2.documentElement.removeAttribute(DRIVING_ATTRIBUTE);
|
|
3056
|
+
};
|
|
3057
|
+
const readHostOnce = async () => {
|
|
3058
|
+
const driven = await drive();
|
|
3059
|
+
if (driven === null) return null;
|
|
3060
|
+
try {
|
|
3061
|
+
const card2 = readCard(document2, driven.list);
|
|
3062
|
+
return card2 === null ? null : { groups: card2.groups };
|
|
3063
|
+
} finally {
|
|
3064
|
+
await endDrive(driven);
|
|
3065
|
+
}
|
|
3066
|
+
};
|
|
3067
|
+
const readHost = () => {
|
|
3068
|
+
if (reading !== null) return reading;
|
|
3069
|
+
reading = readHostOnce().then((next) => {
|
|
3070
|
+
if (next !== null) publishPreferences(next);
|
|
3071
|
+
return next;
|
|
3072
|
+
}).finally(() => {
|
|
3073
|
+
reading = null;
|
|
3074
|
+
});
|
|
3075
|
+
return reading;
|
|
3076
|
+
};
|
|
3077
|
+
const inspect = async () => {
|
|
3078
|
+
inspectWhenAttached = true;
|
|
3079
|
+
if (trigger === null || disposed) return;
|
|
3080
|
+
const focus = document2.activeElement;
|
|
3081
|
+
await readHost();
|
|
3082
|
+
if (!open && typeof HTMLElement !== "undefined" && focus instanceof HTMLElement && focus.isConnected) focus.focus();
|
|
3083
|
+
mark();
|
|
3084
|
+
};
|
|
3085
|
+
const openWithValues = async () => {
|
|
3086
|
+
const read = await readHost();
|
|
3087
|
+
if (read === null) {
|
|
3088
|
+
driving = true;
|
|
3089
|
+
try {
|
|
3090
|
+
trigger?.click();
|
|
3091
|
+
} finally {
|
|
3092
|
+
driving = false;
|
|
3093
|
+
}
|
|
3094
|
+
return;
|
|
3095
|
+
}
|
|
3096
|
+
values = read;
|
|
3097
|
+
open = true;
|
|
3098
|
+
refresh();
|
|
3099
|
+
};
|
|
3100
|
+
const onClick = (event) => {
|
|
3101
|
+
if (driving) return;
|
|
3102
|
+
event.stopPropagation();
|
|
3103
|
+
if (open) {
|
|
3104
|
+
open = false;
|
|
3105
|
+
values = null;
|
|
3106
|
+
refresh();
|
|
3107
|
+
return;
|
|
3108
|
+
}
|
|
3109
|
+
void openWithValues();
|
|
3110
|
+
};
|
|
3111
|
+
const sync = () => {
|
|
3112
|
+
scheduled = 0;
|
|
3113
|
+
const found = document2.querySelector(host.viewOptionsTrigger);
|
|
3114
|
+
if (found === attached) {
|
|
3115
|
+
if (card() === null) mark();
|
|
3116
|
+
return;
|
|
3117
|
+
}
|
|
3118
|
+
attributes?.disconnect();
|
|
3119
|
+
attributes = void 0;
|
|
3120
|
+
attached?.removeEventListener("click", onClick);
|
|
3121
|
+
attached = found;
|
|
3122
|
+
trigger = found;
|
|
3123
|
+
if (found !== null) {
|
|
3124
|
+
found.addEventListener("click", onClick);
|
|
3125
|
+
attributes = new MutationObserver(schedule);
|
|
3126
|
+
attributes.observe(found, { attributes: true, attributeFilter: ["aria-expanded"] });
|
|
3127
|
+
}
|
|
3128
|
+
open = false;
|
|
3129
|
+
values = null;
|
|
3130
|
+
refresh();
|
|
3131
|
+
if (found !== null && inspectWhenAttached) {
|
|
3132
|
+
void Promise.resolve().then(inspect).catch(report);
|
|
3133
|
+
}
|
|
3134
|
+
};
|
|
3135
|
+
const schedule = () => {
|
|
3136
|
+
if (scheduled !== 0) return;
|
|
3137
|
+
scheduled = 1;
|
|
3138
|
+
try {
|
|
3139
|
+
sync();
|
|
3140
|
+
} finally {
|
|
3141
|
+
scheduled = 0;
|
|
3142
|
+
}
|
|
3143
|
+
};
|
|
3144
|
+
try {
|
|
3145
|
+
tree = new MutationObserver(schedule);
|
|
3146
|
+
tree.observe(document2.body, { childList: true, subtree: true });
|
|
3147
|
+
sync();
|
|
3148
|
+
} catch (error) {
|
|
3149
|
+
tree?.disconnect();
|
|
3150
|
+
attributes?.disconnect();
|
|
3151
|
+
report(error);
|
|
3152
|
+
}
|
|
3153
|
+
const port = {
|
|
3154
|
+
getSnapshot: () => snapshot,
|
|
3155
|
+
subscribe(next) {
|
|
3156
|
+
listener = next;
|
|
3157
|
+
return () => {
|
|
3158
|
+
if (listener === next) listener = null;
|
|
3159
|
+
};
|
|
3160
|
+
},
|
|
3161
|
+
close() {
|
|
3162
|
+
open = false;
|
|
3163
|
+
values = null;
|
|
3164
|
+
if (card() !== null && trigger !== null) {
|
|
3165
|
+
driving = true;
|
|
3166
|
+
try {
|
|
3167
|
+
trigger.click();
|
|
3168
|
+
} finally {
|
|
3169
|
+
driving = false;
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
document2.documentElement.removeAttribute(DRIVING_ATTRIBUTE);
|
|
3173
|
+
refresh();
|
|
3174
|
+
},
|
|
3175
|
+
async choose(group, index) {
|
|
3176
|
+
await reading;
|
|
3177
|
+
const driven = await drive();
|
|
3178
|
+
if (driven === null) return false;
|
|
3179
|
+
try {
|
|
3180
|
+
const card2 = readCard(document2, driven.list);
|
|
3181
|
+
const row = card2?.rows[group]?.[index];
|
|
3182
|
+
if (row === void 0) return false;
|
|
3183
|
+
row.click();
|
|
3184
|
+
await settleClosed();
|
|
3185
|
+
publishPreferences({ groups: card2.groups.map((value, position) => position === group ? { ...value, selected: index } : value) });
|
|
3186
|
+
return true;
|
|
3187
|
+
} finally {
|
|
3188
|
+
await endDrive(driven);
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3191
|
+
};
|
|
3192
|
+
return {
|
|
3193
|
+
port,
|
|
3194
|
+
settings: {
|
|
3195
|
+
getSnapshot: () => preferences,
|
|
3196
|
+
subscribe(notify) {
|
|
3197
|
+
preferenceListeners.add(notify);
|
|
3198
|
+
return () => {
|
|
3199
|
+
preferenceListeners.delete(notify);
|
|
3200
|
+
};
|
|
3201
|
+
}
|
|
3202
|
+
},
|
|
3203
|
+
inspect,
|
|
3204
|
+
dispose() {
|
|
3205
|
+
disposed = true;
|
|
3206
|
+
preferenceListeners.clear();
|
|
3207
|
+
scheduled = 0;
|
|
3208
|
+
tree?.disconnect();
|
|
3209
|
+
attributes?.disconnect();
|
|
3210
|
+
attached?.removeEventListener("click", onClick);
|
|
3211
|
+
attached?.removeAttribute(OPEN_ATTRIBUTE);
|
|
3212
|
+
attached?.removeAttribute("aria-haspopup");
|
|
3213
|
+
attached?.removeAttribute("aria-expanded");
|
|
3214
|
+
document2.documentElement.removeAttribute(DRIVING_ATTRIBUTE);
|
|
3215
|
+
listener = null;
|
|
3216
|
+
trigger = null;
|
|
3217
|
+
attached = null;
|
|
3218
|
+
open = false;
|
|
3219
|
+
values = null;
|
|
3220
|
+
}
|
|
3221
|
+
};
|
|
3222
|
+
}
|
|
3223
|
+
|
|
3224
|
+
// src/client/features/view-options/ViewOptionsCard.tsx
|
|
3225
|
+
var import_react5 = require("react");
|
|
3226
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
3227
|
+
var COPY = {
|
|
3228
|
+
zh: {
|
|
3229
|
+
viewOptions: "\u89C6\u56FE\u9009\u9879",
|
|
3230
|
+
status: "\u72B6\u6001",
|
|
3231
|
+
showEmptyGroups: "\u663E\u793A\u7A7A\u5206\u7EC4",
|
|
3232
|
+
clearFilters: "\u6E05\u9664\u7B5B\u9009",
|
|
3233
|
+
filters: ["\u6D3B\u8DC3", "\u5168\u90E8", "\u5DF2\u5F52\u6863"]
|
|
3234
|
+
},
|
|
3235
|
+
en: {
|
|
3236
|
+
viewOptions: "View options",
|
|
3237
|
+
status: "Status",
|
|
3238
|
+
showEmptyGroups: "Show empty groups",
|
|
3239
|
+
clearFilters: "Clear filters",
|
|
3240
|
+
filters: ["Active", "All", "Archived"]
|
|
3241
|
+
}
|
|
3242
|
+
};
|
|
3243
|
+
var FILTER_ORDER = [0, 2, 1];
|
|
3244
|
+
var FILTER_GROUP = 2;
|
|
3245
|
+
var ROW_ORDER = [2, 0, 1];
|
|
3246
|
+
var SUBMENU_GAP = 1;
|
|
3247
|
+
function copyFor(document2) {
|
|
3248
|
+
const language = (document2.documentElement.lang || globalThis.navigator?.language || "").toLowerCase();
|
|
3249
|
+
return language.startsWith("zh") ? COPY.zh : COPY.en;
|
|
3250
|
+
}
|
|
3251
|
+
function optionsOf(group, index, copy) {
|
|
3252
|
+
const order = index === FILTER_GROUP ? FILTER_ORDER : group.labels.map((_, host) => host);
|
|
3253
|
+
return order.map((host) => ({
|
|
3254
|
+
host,
|
|
3255
|
+
label: index === FILTER_GROUP ? copy.filters[host] : group.labels[host]
|
|
3256
|
+
}));
|
|
3257
|
+
}
|
|
3258
|
+
function valueOf(group, index, copy) {
|
|
3259
|
+
if (index === FILTER_GROUP) return copy.filters[group.selected] ?? "";
|
|
3260
|
+
return group.labels[group.selected] ?? "";
|
|
3261
|
+
}
|
|
3262
|
+
function Chevron() {
|
|
3263
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
3264
|
+
"svg",
|
|
3265
|
+
{
|
|
3266
|
+
className: "ccd-view-chevron",
|
|
3267
|
+
viewBox: "0 0 16 16",
|
|
3268
|
+
fill: "none",
|
|
3269
|
+
stroke: "currentColor",
|
|
3270
|
+
strokeWidth: "1",
|
|
3271
|
+
strokeLinecap: "round",
|
|
3272
|
+
strokeLinejoin: "round",
|
|
3273
|
+
"aria-hidden": "true",
|
|
3274
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M6.25 3.5L10.75 8L6.25 12.5" })
|
|
3275
|
+
}
|
|
3276
|
+
);
|
|
3277
|
+
}
|
|
3278
|
+
function Check() {
|
|
3279
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
3280
|
+
"svg",
|
|
3281
|
+
{
|
|
3282
|
+
className: "ccd-view-check",
|
|
3283
|
+
viewBox: "0 0 16 16",
|
|
3284
|
+
fill: "none",
|
|
3285
|
+
stroke: "currentColor",
|
|
3286
|
+
strokeWidth: "1.2",
|
|
3287
|
+
strokeLinecap: "round",
|
|
3288
|
+
strokeLinejoin: "round",
|
|
3289
|
+
"aria-hidden": "true",
|
|
3290
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M3 8.5L6.4 11.9L13 4.6" })
|
|
3291
|
+
}
|
|
3292
|
+
);
|
|
3293
|
+
}
|
|
3294
|
+
function ViewOptionsCard({
|
|
3295
|
+
port,
|
|
3296
|
+
showEmptyGroups,
|
|
3297
|
+
setShowEmptyGroups
|
|
3298
|
+
}) {
|
|
3299
|
+
const state = (0, import_react5.useSyncExternalStore)(port.subscribe, port.getSnapshot);
|
|
3300
|
+
const panel = (0, import_react5.useRef)(null);
|
|
3301
|
+
const submenu = (0, import_react5.useRef)(null);
|
|
3302
|
+
const [openRow, setOpenRow] = (0, import_react5.useState)(null);
|
|
3303
|
+
const [optimistic, setOptimistic] = (0, import_react5.useState)(null);
|
|
3304
|
+
const copy = copyFor(document);
|
|
3305
|
+
const { open, anchor, values } = state;
|
|
3306
|
+
const on = optimistic ?? showEmptyGroups;
|
|
3307
|
+
(0, import_react5.useLayoutEffect)(() => {
|
|
3308
|
+
const surface = panel.current;
|
|
3309
|
+
if (!open || surface === null) return;
|
|
3310
|
+
surface.setAttribute("popover", "manual");
|
|
3311
|
+
surface.showPopover();
|
|
3312
|
+
const place = () => {
|
|
3313
|
+
const node = anchor;
|
|
3314
|
+
if (node === null) return;
|
|
3315
|
+
const a = node.getBoundingClientRect();
|
|
3316
|
+
const p = surface.getBoundingClientRect();
|
|
3317
|
+
const left = Math.max(12, Math.min(a.right - p.width, window.innerWidth - p.width - 12));
|
|
3318
|
+
const top = Math.max(12, Math.min(a.bottom + 4, window.innerHeight - p.height - 12));
|
|
3319
|
+
const nextLeft = `${left}px`;
|
|
3320
|
+
const nextTop = `${top}px`;
|
|
3321
|
+
if (surface.style.left !== nextLeft) surface.style.left = nextLeft;
|
|
3322
|
+
if (surface.style.top !== nextTop) surface.style.top = nextTop;
|
|
3323
|
+
};
|
|
3324
|
+
place();
|
|
3325
|
+
const observer = new MutationObserver(place);
|
|
3326
|
+
observer.observe(document.body, { subtree: true, attributes: true, attributeFilter: ["style"] });
|
|
3327
|
+
const resize = new ResizeObserver(place);
|
|
3328
|
+
resize.observe(surface);
|
|
3329
|
+
window.addEventListener("resize", place);
|
|
3330
|
+
const autofocus = requestAnimationFrame(() => {
|
|
3331
|
+
surface.querySelector(`[data-ccd-view-row="${ROW_ORDER[0]}"]`)?.focus();
|
|
3332
|
+
});
|
|
3333
|
+
return () => {
|
|
3334
|
+
cancelAnimationFrame(autofocus);
|
|
3335
|
+
observer.disconnect();
|
|
3336
|
+
resize.disconnect();
|
|
3337
|
+
window.removeEventListener("resize", place);
|
|
3338
|
+
if (surface.matches(":popover-open")) surface.hidePopover();
|
|
3339
|
+
};
|
|
3340
|
+
}, [open, anchor, values]);
|
|
3341
|
+
(0, import_react5.useEffect)(() => {
|
|
3342
|
+
if (!open) return;
|
|
3343
|
+
const outside = (target) => panel.current?.contains(target) !== true && anchor?.contains(target) !== true;
|
|
3344
|
+
const onPointer = (event) => {
|
|
3345
|
+
if (outside(event.target)) port.close();
|
|
3346
|
+
};
|
|
3347
|
+
const onFocus = (event) => {
|
|
3348
|
+
if (outside(event.target)) port.close();
|
|
3349
|
+
};
|
|
3350
|
+
const onKey = (event) => {
|
|
3351
|
+
if (event.key !== "Escape") return;
|
|
3352
|
+
event.preventDefault();
|
|
3353
|
+
event.stopPropagation();
|
|
3354
|
+
if (openRow !== null) {
|
|
3355
|
+
setOpenRow(null);
|
|
3356
|
+
panel.current?.querySelector(`[data-ccd-view-row="${openRow}"]`)?.focus();
|
|
3357
|
+
return;
|
|
3358
|
+
}
|
|
3359
|
+
if (anchor instanceof HTMLElement) anchor.focus();
|
|
3360
|
+
port.close();
|
|
3361
|
+
};
|
|
3362
|
+
document.addEventListener("pointerdown", onPointer, true);
|
|
3363
|
+
document.addEventListener("focusin", onFocus);
|
|
3364
|
+
document.addEventListener("keydown", onKey, true);
|
|
3365
|
+
return () => {
|
|
3366
|
+
document.removeEventListener("pointerdown", onPointer, true);
|
|
3367
|
+
document.removeEventListener("focusin", onFocus);
|
|
3368
|
+
document.removeEventListener("keydown", onKey, true);
|
|
3369
|
+
};
|
|
3370
|
+
}, [open, openRow, anchor, port]);
|
|
3371
|
+
(0, import_react5.useLayoutEffect)(() => {
|
|
3372
|
+
const surface = submenu.current;
|
|
3373
|
+
const card = panel.current;
|
|
3374
|
+
if (openRow === null || surface === null || card === null) return;
|
|
3375
|
+
const row = card.querySelector(`[data-ccd-view-row="${openRow}"]`);
|
|
3376
|
+
if (row === null) return;
|
|
3377
|
+
const rowRect = row.getBoundingClientRect();
|
|
3378
|
+
const cardRect = card.getBoundingClientRect();
|
|
3379
|
+
const size = surface.getBoundingClientRect();
|
|
3380
|
+
const flip = cardRect.right + SUBMENU_GAP + size.width > window.innerWidth - 12;
|
|
3381
|
+
const top = Math.max(12, Math.min(rowRect.top, window.innerHeight - size.height - 12)) - cardRect.top;
|
|
3382
|
+
surface.style.top = `${top}px`;
|
|
3383
|
+
surface.style.left = flip ? "auto" : `calc(100% + ${SUBMENU_GAP}px)`;
|
|
3384
|
+
surface.style.right = flip ? `calc(100% + ${SUBMENU_GAP}px)` : "auto";
|
|
3385
|
+
surface.setAttribute("data-ccd-flip", flip ? "true" : "false");
|
|
3386
|
+
}, [openRow, values]);
|
|
3387
|
+
if (!open || values === null) return null;
|
|
3388
|
+
const groups = values.groups;
|
|
3389
|
+
const filtersOff = groups[FILTER_GROUP]?.selected !== 0 || showEmptyGroups;
|
|
3390
|
+
const choose = (group, host) => {
|
|
3391
|
+
setOpenRow(null);
|
|
3392
|
+
void port.choose(group, host).then((applied) => {
|
|
3393
|
+
if (applied) port.close();
|
|
3394
|
+
});
|
|
3395
|
+
};
|
|
3396
|
+
const rows = () => {
|
|
3397
|
+
const card = panel.current;
|
|
3398
|
+
if (card === null) return [];
|
|
3399
|
+
const own = card.querySelectorAll(
|
|
3400
|
+
"[data-ccd-view-row], [data-ccd-view-switch], [data-ccd-view-action]"
|
|
3401
|
+
);
|
|
3402
|
+
const flyout = submenu.current?.querySelectorAll("[data-ccd-view-option]");
|
|
3403
|
+
return [...own, ...flyout ?? []];
|
|
3404
|
+
};
|
|
3405
|
+
const move = (event, step) => {
|
|
3406
|
+
const list = rows();
|
|
3407
|
+
if (list.length === 0) return;
|
|
3408
|
+
const index = list.indexOf(document.activeElement);
|
|
3409
|
+
const next = index === -1 ? 0 : (index + step + list.length) % list.length;
|
|
3410
|
+
event.preventDefault();
|
|
3411
|
+
list[next]?.focus();
|
|
3412
|
+
};
|
|
3413
|
+
const onCardKey = (event) => {
|
|
3414
|
+
if (event.key === "ArrowDown") move(event, 1);
|
|
3415
|
+
else if (event.key === "ArrowUp") move(event, -1);
|
|
3416
|
+
else if (event.key === "Tab") {
|
|
3417
|
+
const list = rows();
|
|
3418
|
+
if (list.length === 0) return;
|
|
3419
|
+
event.preventDefault();
|
|
3420
|
+
move(event, event.shiftKey ? -1 : 1);
|
|
3421
|
+
}
|
|
3422
|
+
};
|
|
3423
|
+
const openFlyout = (index) => {
|
|
3424
|
+
setOpenRow(index);
|
|
3425
|
+
requestAnimationFrame(() => {
|
|
3426
|
+
submenu.current?.querySelector('[aria-checked="true"]')?.focus();
|
|
3427
|
+
});
|
|
3428
|
+
};
|
|
3429
|
+
const closeFlyout = (index) => {
|
|
3430
|
+
setOpenRow(null);
|
|
3431
|
+
panel.current?.querySelector(`[data-ccd-view-row="${index}"]`)?.focus();
|
|
3432
|
+
};
|
|
3433
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
3434
|
+
"div",
|
|
3435
|
+
{
|
|
3436
|
+
className: "ccd-view-options",
|
|
3437
|
+
"data-menu-material": "",
|
|
3438
|
+
"data-ccd-view-options": "",
|
|
3439
|
+
role: "menu",
|
|
3440
|
+
"aria-label": anchor?.getAttribute("aria-label") ?? copy.viewOptions,
|
|
3441
|
+
ref: panel,
|
|
3442
|
+
onKeyDown: onCardKey,
|
|
3443
|
+
onPointerLeave: () => {
|
|
3444
|
+
setOpenRow(null);
|
|
3445
|
+
},
|
|
3446
|
+
children: [
|
|
3447
|
+
ROW_ORDER.map((index, position) => {
|
|
3448
|
+
const group = groups[index];
|
|
3449
|
+
if (group === void 0) return null;
|
|
3450
|
+
const options = optionsOf(group, index, copy);
|
|
3451
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react5.Fragment, { children: [
|
|
3452
|
+
position === 1 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "ccd-view-separator", role: "separator" }),
|
|
3453
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ccd-view-row-wrap", role: "presentation", children: [
|
|
3454
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
3455
|
+
"button",
|
|
3456
|
+
{
|
|
3457
|
+
type: "button",
|
|
3458
|
+
role: "menuitem",
|
|
3459
|
+
className: "ccd-view-row",
|
|
3460
|
+
"data-ccd-view-row": index,
|
|
3461
|
+
"aria-haspopup": "menu",
|
|
3462
|
+
"aria-expanded": openRow === index,
|
|
3463
|
+
onPointerEnter: () => {
|
|
3464
|
+
setOpenRow(index);
|
|
3465
|
+
},
|
|
3466
|
+
onClick: () => {
|
|
3467
|
+
setOpenRow(index);
|
|
3468
|
+
},
|
|
3469
|
+
onKeyDown: (event) => {
|
|
3470
|
+
if (event.key === "ArrowRight") {
|
|
3471
|
+
event.preventDefault();
|
|
3472
|
+
openFlyout(index);
|
|
3473
|
+
return;
|
|
3474
|
+
}
|
|
3475
|
+
if (event.key === "ArrowLeft" && openRow === index) {
|
|
3476
|
+
event.preventDefault();
|
|
3477
|
+
setOpenRow(null);
|
|
3478
|
+
return;
|
|
3479
|
+
}
|
|
3480
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
3481
|
+
event.preventDefault();
|
|
3482
|
+
openFlyout(index);
|
|
3483
|
+
return;
|
|
3484
|
+
}
|
|
3485
|
+
},
|
|
3486
|
+
children: [
|
|
3487
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "ccd-view-row-label", children: index === FILTER_GROUP ? copy.status : group.label }),
|
|
3488
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "ccd-view-row-value", children: valueOf(group, index, copy) }),
|
|
3489
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Chevron, {})
|
|
3490
|
+
]
|
|
3491
|
+
}
|
|
3492
|
+
),
|
|
3493
|
+
openRow === index && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
3494
|
+
"div",
|
|
3495
|
+
{
|
|
3496
|
+
className: "ccd-view-submenu",
|
|
3497
|
+
"data-menu-material": "",
|
|
3498
|
+
"data-ccd-flip": "false",
|
|
3499
|
+
role: "menu",
|
|
3500
|
+
"aria-label": index === FILTER_GROUP ? copy.status : group.label,
|
|
3501
|
+
ref: submenu,
|
|
3502
|
+
onPointerLeave: () => {
|
|
3503
|
+
setOpenRow(null);
|
|
3504
|
+
},
|
|
3505
|
+
children: options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
3506
|
+
"button",
|
|
3507
|
+
{
|
|
3508
|
+
type: "button",
|
|
3509
|
+
role: "menuitemradio",
|
|
3510
|
+
"aria-checked": option.host === group.selected,
|
|
3511
|
+
className: "ccd-view-row ccd-view-option",
|
|
3512
|
+
"data-ccd-view-option": `${index}-${option.host}`,
|
|
3513
|
+
onClick: () => {
|
|
3514
|
+
choose(index, option.host);
|
|
3515
|
+
},
|
|
3516
|
+
onKeyDown: (event) => {
|
|
3517
|
+
if (event.key === "ArrowLeft") {
|
|
3518
|
+
event.preventDefault();
|
|
3519
|
+
closeFlyout(index);
|
|
3520
|
+
return;
|
|
3521
|
+
}
|
|
3522
|
+
},
|
|
3523
|
+
children: [
|
|
3524
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "ccd-view-row-label", children: option.label }),
|
|
3525
|
+
option.host === group.selected && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Check, {})
|
|
3526
|
+
]
|
|
3527
|
+
},
|
|
3528
|
+
option.host
|
|
3529
|
+
))
|
|
3530
|
+
}
|
|
3531
|
+
)
|
|
3532
|
+
] })
|
|
3533
|
+
] }, index);
|
|
3534
|
+
}),
|
|
3535
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "ccd-view-separator", role: "separator" }),
|
|
3536
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
3537
|
+
"button",
|
|
3538
|
+
{
|
|
3539
|
+
type: "button",
|
|
3540
|
+
role: "menuitemcheckbox",
|
|
3541
|
+
"aria-checked": on,
|
|
3542
|
+
className: "ccd-view-row",
|
|
3543
|
+
"data-ccd-view-switch": "",
|
|
3544
|
+
onPointerEnter: () => {
|
|
3545
|
+
setOpenRow(null);
|
|
3546
|
+
},
|
|
3547
|
+
onFocus: () => {
|
|
3548
|
+
setOpenRow(null);
|
|
3549
|
+
},
|
|
3550
|
+
onClick: () => {
|
|
3551
|
+
const next = !on;
|
|
3552
|
+
setOptimistic(next);
|
|
3553
|
+
setShowEmptyGroups(next);
|
|
3554
|
+
},
|
|
3555
|
+
children: [
|
|
3556
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "ccd-view-row-label", children: copy.showEmptyGroups }),
|
|
3557
|
+
on && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Check, {})
|
|
3558
|
+
]
|
|
3559
|
+
}
|
|
3560
|
+
),
|
|
3561
|
+
filtersOff && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
3562
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "ccd-view-separator", role: "separator" }),
|
|
3563
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
3564
|
+
"button",
|
|
3565
|
+
{
|
|
3566
|
+
type: "button",
|
|
3567
|
+
role: "menuitem",
|
|
3568
|
+
className: "ccd-view-row",
|
|
3569
|
+
"data-ccd-view-action": "",
|
|
3570
|
+
onPointerEnter: () => {
|
|
3571
|
+
setOpenRow(null);
|
|
3572
|
+
},
|
|
3573
|
+
onFocus: () => {
|
|
3574
|
+
setOpenRow(null);
|
|
3575
|
+
},
|
|
3576
|
+
onClick: () => {
|
|
3577
|
+
void (async () => {
|
|
3578
|
+
if (groups[FILTER_GROUP]?.selected !== 0) await port.choose(FILTER_GROUP, 0);
|
|
3579
|
+
if (showEmptyGroups) setShowEmptyGroups(false);
|
|
3580
|
+
port.close();
|
|
3581
|
+
})();
|
|
3582
|
+
},
|
|
3583
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "ccd-view-row-label", children: copy.clearFilters })
|
|
3584
|
+
}
|
|
3585
|
+
)
|
|
3586
|
+
] })
|
|
3587
|
+
]
|
|
3588
|
+
}
|
|
3589
|
+
);
|
|
3590
|
+
}
|
|
3591
|
+
|
|
3592
|
+
// src/client/features/view-options/view-options.css
|
|
3593
|
+
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 \xA73.5 of docs/plan-sidebar-view-options.md:\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";
|
|
3594
|
+
|
|
3595
|
+
// src/client/features/view-options/mount.ts
|
|
3596
|
+
var VIEW_OPTIONS_ORDER = 30;
|
|
3597
|
+
function mountViewOptions(ctx, environment, cleanup) {
|
|
3598
|
+
cleanup.add(environment.dom.mountStyles(view_options_default));
|
|
3599
|
+
const driver = createViewOptionsDriver(
|
|
3600
|
+
document,
|
|
3601
|
+
(error) => environment.logger.error("view options: the sidebar trigger could not be watched", error)
|
|
3602
|
+
);
|
|
3603
|
+
cleanup.add(driver.dispose);
|
|
3604
|
+
const form = environment.host.configForms.get(ENTRY_ID);
|
|
3605
|
+
const registration = ctx.inject(["slots"], (scope) => scope.slots.inject(
|
|
3606
|
+
DSH_SLOTS.shellOverlay,
|
|
3607
|
+
() => scope.slots.register({
|
|
3608
|
+
name: DSH_SLOTS.shellOverlay,
|
|
3609
|
+
id: "ccd-view-options",
|
|
3610
|
+
order: VIEW_OPTIONS_ORDER,
|
|
3611
|
+
inject: () => ({
|
|
3612
|
+
port: driver.port,
|
|
3613
|
+
showEmptyGroups: environment.config.view.showEmptyGroups,
|
|
3614
|
+
setShowEmptyGroups: (value) => {
|
|
3615
|
+
void form.mutate(
|
|
3616
|
+
[{ op: "set", path: ["view", "showEmptyGroups"], value }],
|
|
3617
|
+
form.getSnapshot().revision
|
|
3618
|
+
).then(
|
|
3619
|
+
(accepted) => {
|
|
3620
|
+
if (!accepted) environment.logger.error("view options: the show-empty-groups write was refused");
|
|
3621
|
+
},
|
|
3622
|
+
(error) => environment.logger.error("view options: the show-empty-groups write failed", error)
|
|
3623
|
+
);
|
|
3624
|
+
}
|
|
3625
|
+
})
|
|
3626
|
+
}, ViewOptionsCard)
|
|
3627
|
+
));
|
|
3628
|
+
cleanup.add(() => {
|
|
3629
|
+
void registration.dispose();
|
|
3630
|
+
});
|
|
3631
|
+
void driver.inspect().catch((error) => environment.logger.error("view options: preferences could not be read", error));
|
|
3632
|
+
return {
|
|
3633
|
+
settings: driver.settings,
|
|
3634
|
+
showAllSessions() {
|
|
3635
|
+
void (async () => {
|
|
3636
|
+
await driver.port.choose(2, 1);
|
|
3637
|
+
})().catch((error) => environment.logger.error("sidebar: show-all-sessions failed", error));
|
|
3638
|
+
}
|
|
3639
|
+
};
|
|
3640
|
+
}
|
|
3641
|
+
|
|
3642
|
+
// src/client/compat/context-panel.ts
|
|
3643
|
+
var OPEN_MARKER = "data-ccd-context-open";
|
|
3644
|
+
var OPEN_ATTRIBUTE2 = "data-ccd-context-panel";
|
|
3645
|
+
function sessionOf(document2, trigger) {
|
|
3646
|
+
if (trigger === null) return null;
|
|
3647
|
+
const body = trigger.closest(hostAnchors(document2).conversationSession);
|
|
3648
|
+
const id = body?.getAttribute("data-conversation-session") ?? "";
|
|
3649
|
+
return id === "" ? null : id;
|
|
3650
|
+
}
|
|
3651
|
+
function splitTools2(tools) {
|
|
3652
|
+
const mcp = [];
|
|
3653
|
+
const builtin = [];
|
|
3654
|
+
for (const entry of tools) {
|
|
3655
|
+
if (mcpServer(entry[0]) === null) builtin.push(entry);
|
|
3656
|
+
else mcp.push(entry);
|
|
3657
|
+
}
|
|
3658
|
+
return { mcp, tools: builtin };
|
|
3659
|
+
}
|
|
3660
|
+
function createContextPanelDriver(document2, projections, report) {
|
|
3661
|
+
const host = hostSelectors(document2);
|
|
3662
|
+
let trigger = null;
|
|
3663
|
+
let attached = null;
|
|
3664
|
+
let open = false;
|
|
3665
|
+
let expanded = false;
|
|
3666
|
+
let sessionId = null;
|
|
3667
|
+
let snapshot = {
|
|
3668
|
+
open: false,
|
|
3669
|
+
anchor: null,
|
|
3670
|
+
breakdown: null,
|
|
3671
|
+
mcp: [],
|
|
3672
|
+
tools: [],
|
|
3673
|
+
files: [],
|
|
3674
|
+
skills: [],
|
|
3675
|
+
expanded: false
|
|
3676
|
+
};
|
|
3677
|
+
let listener = null;
|
|
3678
|
+
let scheduled = 0;
|
|
3679
|
+
let tree;
|
|
3680
|
+
let attributes;
|
|
3681
|
+
let projectionsOff = null;
|
|
3682
|
+
const reading = () => {
|
|
3683
|
+
const id = sessionId;
|
|
3684
|
+
const values = id === null ? null : projections.read(id);
|
|
3685
|
+
if (values === null) return null;
|
|
3686
|
+
const rows = values.breakdown;
|
|
3687
|
+
const tools = rows?.tools ?? [];
|
|
3688
|
+
const split = splitTools2(tools);
|
|
3689
|
+
return {
|
|
3690
|
+
breakdown: contextBreakdown({
|
|
3691
|
+
window: values.window,
|
|
3692
|
+
used: values.used,
|
|
3693
|
+
systemTokens: values.systemTokens,
|
|
3694
|
+
toolsTokens: values.toolsTokens,
|
|
3695
|
+
messageTokens: values.messageTokens,
|
|
3696
|
+
tools,
|
|
3697
|
+
skillTokens: rows?.skillTokens ?? 0,
|
|
3698
|
+
fileTokens: rows?.fileTokens ?? 0,
|
|
3699
|
+
compaction: rows?.compaction ?? null
|
|
3700
|
+
}),
|
|
3701
|
+
mcp: split.mcp,
|
|
3702
|
+
tools: split.tools,
|
|
3703
|
+
files: rows?.files ?? [],
|
|
3704
|
+
skills: rows?.skills ?? []
|
|
3705
|
+
};
|
|
3706
|
+
};
|
|
3707
|
+
const mark = () => {
|
|
3708
|
+
const node = trigger;
|
|
3709
|
+
if (node !== null) {
|
|
3710
|
+
const value = open ? "true" : "false";
|
|
3711
|
+
if (node.getAttribute("aria-expanded") !== value) node.setAttribute("aria-expanded", value);
|
|
3712
|
+
if (open) {
|
|
3713
|
+
if (!node.hasAttribute(OPEN_ATTRIBUTE2)) node.setAttribute(OPEN_ATTRIBUTE2, "");
|
|
3714
|
+
} else {
|
|
3715
|
+
node.removeAttribute(OPEN_ATTRIBUTE2);
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
const html = document2.documentElement;
|
|
3719
|
+
if (open) {
|
|
3720
|
+
if (!html.hasAttribute(OPEN_MARKER)) html.setAttribute(OPEN_MARKER, "");
|
|
3721
|
+
} else {
|
|
3722
|
+
html.removeAttribute(OPEN_MARKER);
|
|
3723
|
+
}
|
|
3724
|
+
};
|
|
3725
|
+
const refresh = () => {
|
|
3726
|
+
const found = open ? reading() : null;
|
|
3727
|
+
snapshot = {
|
|
3728
|
+
open,
|
|
3729
|
+
anchor: trigger,
|
|
3730
|
+
breakdown: found?.breakdown ?? null,
|
|
3731
|
+
mcp: found?.mcp ?? [],
|
|
3732
|
+
tools: found?.tools ?? [],
|
|
3733
|
+
files: found?.files ?? [],
|
|
3734
|
+
skills: found?.skills ?? [],
|
|
3735
|
+
expanded
|
|
3736
|
+
};
|
|
3737
|
+
mark();
|
|
3738
|
+
listener?.();
|
|
3739
|
+
};
|
|
3740
|
+
const close = () => {
|
|
3741
|
+
if (!open) return;
|
|
3742
|
+
open = false;
|
|
3743
|
+
expanded = false;
|
|
3744
|
+
refresh();
|
|
3745
|
+
};
|
|
3746
|
+
const onClick = (event) => {
|
|
3747
|
+
event.stopPropagation();
|
|
3748
|
+
if (open) {
|
|
3749
|
+
close();
|
|
3750
|
+
return;
|
|
3751
|
+
}
|
|
3752
|
+
sessionId = sessionOf(document2, trigger);
|
|
3753
|
+
expanded = false;
|
|
3754
|
+
open = true;
|
|
3755
|
+
refresh();
|
|
3756
|
+
};
|
|
3757
|
+
const sync = () => {
|
|
3758
|
+
scheduled = 0;
|
|
3759
|
+
const found = document2.querySelector(host.contextMeterTrigger);
|
|
3760
|
+
if (found === attached) {
|
|
3761
|
+
const current = sessionOf(document2, found);
|
|
3762
|
+
if (open && current !== sessionId) {
|
|
3763
|
+
sessionId = current;
|
|
3764
|
+
close();
|
|
3765
|
+
return;
|
|
3766
|
+
}
|
|
3767
|
+
mark();
|
|
3768
|
+
return;
|
|
3769
|
+
}
|
|
3770
|
+
attributes?.disconnect();
|
|
3771
|
+
attributes = void 0;
|
|
3772
|
+
attached?.removeEventListener("click", onClick, true);
|
|
3773
|
+
attached = found;
|
|
3774
|
+
trigger = found;
|
|
3775
|
+
if (found !== null) {
|
|
3776
|
+
found.addEventListener("click", onClick, true);
|
|
3777
|
+
attributes = new MutationObserver(schedule);
|
|
3778
|
+
attributes.observe(found, { attributes: true, attributeFilter: ["aria-expanded"] });
|
|
3779
|
+
}
|
|
3780
|
+
if (open) {
|
|
3781
|
+
open = false;
|
|
3782
|
+
expanded = false;
|
|
3783
|
+
}
|
|
3784
|
+
sessionId = sessionOf(document2, found);
|
|
3785
|
+
refresh();
|
|
3786
|
+
};
|
|
3787
|
+
const schedule = () => {
|
|
3788
|
+
if (scheduled !== 0) return;
|
|
3789
|
+
scheduled = 1;
|
|
3790
|
+
try {
|
|
3791
|
+
sync();
|
|
3792
|
+
} finally {
|
|
3793
|
+
scheduled = 0;
|
|
3794
|
+
}
|
|
3795
|
+
};
|
|
3796
|
+
try {
|
|
3797
|
+
tree = new MutationObserver(schedule);
|
|
3798
|
+
tree.observe(document2.body, { childList: true, subtree: true });
|
|
3799
|
+
projectionsOff = projections.subscribe(() => {
|
|
3800
|
+
if (open) refresh();
|
|
3801
|
+
});
|
|
3802
|
+
sync();
|
|
3803
|
+
} catch (error) {
|
|
3804
|
+
tree?.disconnect();
|
|
3805
|
+
attributes?.disconnect();
|
|
3806
|
+
projectionsOff?.();
|
|
3807
|
+
report(error);
|
|
3808
|
+
}
|
|
3809
|
+
const port = {
|
|
3810
|
+
getSnapshot: () => snapshot,
|
|
3811
|
+
subscribe(next) {
|
|
3812
|
+
listener = next;
|
|
3813
|
+
return () => {
|
|
3814
|
+
if (listener === next) listener = null;
|
|
3815
|
+
};
|
|
3816
|
+
},
|
|
3817
|
+
close,
|
|
3818
|
+
toggleExpanded() {
|
|
3819
|
+
if (!open) return;
|
|
3820
|
+
expanded = !expanded;
|
|
3821
|
+
refresh();
|
|
3822
|
+
}
|
|
3823
|
+
};
|
|
3824
|
+
return {
|
|
3825
|
+
port,
|
|
3826
|
+
dispose() {
|
|
3827
|
+
scheduled = 0;
|
|
3828
|
+
tree?.disconnect();
|
|
3829
|
+
attributes?.disconnect();
|
|
3830
|
+
projectionsOff?.();
|
|
3831
|
+
attached?.removeEventListener("click", onClick, true);
|
|
3832
|
+
attached?.removeAttribute(OPEN_ATTRIBUTE2);
|
|
3833
|
+
attached?.removeAttribute("aria-expanded");
|
|
3834
|
+
document2.documentElement.removeAttribute(OPEN_MARKER);
|
|
3835
|
+
listener = null;
|
|
3836
|
+
trigger = null;
|
|
3837
|
+
attached = null;
|
|
3838
|
+
open = false;
|
|
3839
|
+
expanded = false;
|
|
3840
|
+
sessionId = null;
|
|
3841
|
+
}
|
|
3842
|
+
};
|
|
3843
|
+
}
|
|
3844
|
+
|
|
3845
|
+
// src/client/features/context-panel/ContextPanel.tsx
|
|
3846
|
+
var import_react6 = require("react");
|
|
3847
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
3848
|
+
var COPY2 = {
|
|
3849
|
+
zh: {
|
|
3850
|
+
title: "\u4E0A\u4E0B\u6587\u7A97\u53E3",
|
|
3851
|
+
waiting: "\u7B49\u5F85\u9996\u4E2A\u8BF7\u6C42",
|
|
3852
|
+
expand: "\u5C55\u5F00\u660E\u7EC6",
|
|
3853
|
+
collapse: "\u6536\u8D77\u660E\u7EC6",
|
|
3854
|
+
rows: {
|
|
3855
|
+
mcp: "MCP \u5DE5\u5177",
|
|
3856
|
+
tools: "\u7CFB\u7EDF\u5DE5\u5177",
|
|
3857
|
+
system: "\u7CFB\u7EDF\u63D0\u793A\u8BCD",
|
|
3858
|
+
skills: "\u6280\u80FD",
|
|
3859
|
+
memory: "\u8BB0\u5FC6\u6587\u4EF6",
|
|
3860
|
+
messages: "\u5BF9\u8BDD\u6D88\u606F",
|
|
3861
|
+
autocompact: "\u81EA\u52A8\u538B\u7F29\u4F59\u91CF",
|
|
3862
|
+
free: "\u7A7A\u95F2\u7A7A\u95F4"
|
|
3863
|
+
},
|
|
3864
|
+
groups: { mcp: "MCP \u5DE5\u5177", tools: "\u7CFB\u7EDF\u5DE5\u5177", skills: "\u6280\u80FD", memory: "\u8BB0\u5FC6\u6587\u4EF6" },
|
|
3865
|
+
count: "\u9879"
|
|
3866
|
+
},
|
|
3867
|
+
en: {
|
|
3868
|
+
title: "Context window",
|
|
3869
|
+
waiting: "Waiting for the first request",
|
|
3870
|
+
expand: "Expand the breakdown",
|
|
3871
|
+
collapse: "Collapse the breakdown",
|
|
3872
|
+
rows: {
|
|
3873
|
+
mcp: "MCP tools",
|
|
3874
|
+
tools: "System tools",
|
|
3875
|
+
system: "System prompt",
|
|
3876
|
+
skills: "Skills",
|
|
3877
|
+
memory: "Memory files",
|
|
3878
|
+
messages: "Messages",
|
|
3879
|
+
autocompact: "Autocompact buffer",
|
|
3880
|
+
free: "Free space"
|
|
3881
|
+
},
|
|
3882
|
+
groups: { mcp: "MCP tools", tools: "System tools", skills: "Skills", memory: "Memory files" },
|
|
3883
|
+
count: "items"
|
|
3884
|
+
}
|
|
3885
|
+
};
|
|
3886
|
+
var GROUP_SOURCES = [
|
|
3887
|
+
{ key: "mcp", rows: "mcp" },
|
|
3888
|
+
{ key: "tools", rows: "tools" },
|
|
3889
|
+
{ key: "skills", rows: "skills" },
|
|
3890
|
+
{ key: "memory", rows: "files" }
|
|
3891
|
+
];
|
|
3892
|
+
var ASSUMED_BAR_WIDTH = 334;
|
|
3893
|
+
function copyFor2(document2) {
|
|
3894
|
+
const language = (document2.documentElement.lang || globalThis.navigator?.language || "").toLowerCase();
|
|
3895
|
+
return language.startsWith("zh") ? COPY2.zh : COPY2.en;
|
|
3896
|
+
}
|
|
3897
|
+
function Chevron2({ open }) {
|
|
3898
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
3899
|
+
"svg",
|
|
3900
|
+
{
|
|
3901
|
+
className: "ccd-context-chevron",
|
|
3902
|
+
"data-ccd-context-chevron": open ? "open" : "closed",
|
|
3903
|
+
viewBox: "0 0 16 16",
|
|
3904
|
+
fill: "none",
|
|
3905
|
+
stroke: "currentColor",
|
|
3906
|
+
strokeWidth: "1.6",
|
|
3907
|
+
strokeLinecap: "round",
|
|
3908
|
+
strokeLinejoin: "round",
|
|
3909
|
+
"aria-hidden": "true",
|
|
3910
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M6.25 3.5L10.75 8L6.25 12.5" })
|
|
3911
|
+
}
|
|
3912
|
+
);
|
|
3913
|
+
}
|
|
3914
|
+
function ContextPanel({ port }) {
|
|
3915
|
+
const state = (0, import_react6.useSyncExternalStore)(port.subscribe, port.getSnapshot);
|
|
3916
|
+
const panel = (0, import_react6.useRef)(null);
|
|
3917
|
+
const bar = (0, import_react6.useRef)(null);
|
|
3918
|
+
const [barWidth, setBarWidth] = (0, import_react6.useState)(ASSUMED_BAR_WIDTH);
|
|
3919
|
+
const [expandedGroups, setExpandedGroups] = (0, import_react6.useState)(() => /* @__PURE__ */ new Set());
|
|
3920
|
+
const copy = copyFor2(document);
|
|
3921
|
+
const { open, anchor, breakdown, expanded } = state;
|
|
3922
|
+
(0, import_react6.useLayoutEffect)(() => {
|
|
3923
|
+
setExpandedGroups(/* @__PURE__ */ new Set());
|
|
3924
|
+
}, [open]);
|
|
3925
|
+
(0, import_react6.useLayoutEffect)(() => {
|
|
3926
|
+
const surface = panel.current;
|
|
3927
|
+
if (!open || surface === null) return;
|
|
3928
|
+
surface.setAttribute("popover", "manual");
|
|
3929
|
+
surface.showPopover();
|
|
3930
|
+
const place = () => {
|
|
3931
|
+
const node = anchor;
|
|
3932
|
+
if (node === null) return;
|
|
3933
|
+
const a = node.getBoundingClientRect();
|
|
3934
|
+
const p = surface.getBoundingClientRect();
|
|
3935
|
+
const left = Math.max(12, Math.min(a.right - p.width, window.innerWidth - p.width - 12));
|
|
3936
|
+
const top = Math.max(12, Math.min(a.top - p.height - 8, window.innerHeight - p.height - 12));
|
|
3937
|
+
const nextLeft = `${left}px`;
|
|
3938
|
+
const nextTop = `${top}px`;
|
|
3939
|
+
if (surface.style.left !== nextLeft) surface.style.left = nextLeft;
|
|
3940
|
+
if (surface.style.top !== nextTop) surface.style.top = nextTop;
|
|
3941
|
+
};
|
|
3942
|
+
place();
|
|
3943
|
+
const observer = new MutationObserver(place);
|
|
3944
|
+
observer.observe(document.body, { subtree: true, attributes: true, attributeFilter: ["style"] });
|
|
3945
|
+
const resize = new ResizeObserver(place);
|
|
3946
|
+
resize.observe(surface);
|
|
3947
|
+
window.addEventListener("resize", place);
|
|
3948
|
+
return () => {
|
|
3949
|
+
observer.disconnect();
|
|
3950
|
+
resize.disconnect();
|
|
3951
|
+
window.removeEventListener("resize", place);
|
|
3952
|
+
if (surface.matches(":popover-open")) surface.hidePopover();
|
|
3953
|
+
};
|
|
3954
|
+
}, [open, anchor, breakdown, expanded]);
|
|
3955
|
+
(0, import_react6.useLayoutEffect)(() => {
|
|
3956
|
+
const surface = bar.current;
|
|
3957
|
+
if (!open || surface === null) return;
|
|
3958
|
+
const measure = () => {
|
|
3959
|
+
const width = Math.round(surface.getBoundingClientRect().width);
|
|
3960
|
+
if (width > 0) setBarWidth((current) => current === width ? current : width);
|
|
3961
|
+
};
|
|
3962
|
+
measure();
|
|
3963
|
+
const resize = new ResizeObserver(measure);
|
|
3964
|
+
resize.observe(surface);
|
|
3965
|
+
return () => {
|
|
3966
|
+
resize.disconnect();
|
|
3967
|
+
};
|
|
3968
|
+
}, [open, expanded, breakdown]);
|
|
3969
|
+
(0, import_react6.useEffect)(() => {
|
|
3970
|
+
if (!open) return;
|
|
3971
|
+
const outside = (target) => panel.current?.contains(target) !== true && anchor?.contains(target) !== true;
|
|
3972
|
+
const onPointer = (event) => {
|
|
3973
|
+
if (outside(event.target)) port.close();
|
|
3974
|
+
};
|
|
3975
|
+
const onFocus = (event) => {
|
|
3976
|
+
if (outside(event.target)) port.close();
|
|
3977
|
+
};
|
|
3978
|
+
const onKey = (event) => {
|
|
3979
|
+
if (event.key !== "Escape") return;
|
|
3980
|
+
event.preventDefault();
|
|
3981
|
+
event.stopPropagation();
|
|
3982
|
+
if (anchor instanceof HTMLElement) anchor.focus();
|
|
3983
|
+
port.close();
|
|
3984
|
+
};
|
|
3985
|
+
document.addEventListener("pointerdown", onPointer, true);
|
|
3986
|
+
document.addEventListener("focusin", onFocus);
|
|
3987
|
+
document.addEventListener("keydown", onKey, true);
|
|
3988
|
+
return () => {
|
|
3989
|
+
document.removeEventListener("pointerdown", onPointer, true);
|
|
3990
|
+
document.removeEventListener("focusin", onFocus);
|
|
3991
|
+
document.removeEventListener("keydown", onKey, true);
|
|
3992
|
+
};
|
|
3993
|
+
}, [open, anchor, port]);
|
|
3994
|
+
if (!open) return null;
|
|
3995
|
+
const focusables = () => {
|
|
3996
|
+
const surface = panel.current;
|
|
3997
|
+
if (surface === null) return [];
|
|
3998
|
+
return [...surface.querySelectorAll("[data-ccd-context-focus]")];
|
|
3999
|
+
};
|
|
4000
|
+
const onCardKey = (event) => {
|
|
4001
|
+
if (event.key !== "Tab") return;
|
|
4002
|
+
const list = focusables();
|
|
4003
|
+
if (list.length === 0) return;
|
|
4004
|
+
event.preventDefault();
|
|
4005
|
+
const index = list.indexOf(document.activeElement);
|
|
4006
|
+
const step = event.shiftKey ? -1 : 1;
|
|
4007
|
+
const next = index === -1 ? 0 : (index + step + list.length) % list.length;
|
|
4008
|
+
list[next]?.focus();
|
|
4009
|
+
};
|
|
4010
|
+
const reading = breakdown === null ? null : `${formatTokens(breakdown.used)} / ${formatTokens(breakdown.window)} (${breakdown.percent}%)`;
|
|
4011
|
+
const segments = breakdown === null ? { drawn: [], free: 0 } : barSegments(breakdown, barWidth);
|
|
4012
|
+
const groups = [];
|
|
4013
|
+
if (breakdown !== null) {
|
|
4014
|
+
for (const source of GROUP_SOURCES) {
|
|
4015
|
+
const rows = state[source.rows];
|
|
4016
|
+
if (rows.length === 0) continue;
|
|
4017
|
+
const tokens = breakdown.slices.find((entry) => entry.key === source.key)?.tokens ?? 0;
|
|
4018
|
+
groups.push({ label: copy.groups[source.key], tokens, rows });
|
|
4019
|
+
}
|
|
4020
|
+
}
|
|
4021
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
4022
|
+
"div",
|
|
4023
|
+
{
|
|
4024
|
+
className: "ccd-context-panel",
|
|
4025
|
+
"data-ccd-context-panel": "",
|
|
4026
|
+
"data-menu-material": "",
|
|
4027
|
+
role: "dialog",
|
|
4028
|
+
"aria-label": copy.title,
|
|
4029
|
+
ref: panel,
|
|
4030
|
+
onKeyDown: onCardKey,
|
|
4031
|
+
children: [
|
|
4032
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
2196
4033
|
"button",
|
|
2197
4034
|
{
|
|
2198
4035
|
type: "button",
|
|
2199
|
-
"
|
|
2200
|
-
"
|
|
2201
|
-
|
|
2202
|
-
|
|
4036
|
+
className: "ccd-context-head",
|
|
4037
|
+
"data-ccd-context-focus": "",
|
|
4038
|
+
"data-ccd-context-head": "",
|
|
4039
|
+
"aria-expanded": expanded,
|
|
4040
|
+
"aria-label": `${copy.title} \u2014 ${expanded ? copy.collapse : copy.expand}`,
|
|
4041
|
+
onClick: () => {
|
|
4042
|
+
port.toggleExpanded();
|
|
4043
|
+
},
|
|
2203
4044
|
children: [
|
|
2204
|
-
/* @__PURE__ */ (0,
|
|
2205
|
-
/* @__PURE__ */ (0,
|
|
4045
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "ccd-context-title", children: copy.title }),
|
|
4046
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "ccd-context-figures", "data-ccd-context-figures": reading === null ? "waiting" : "ready", children: reading ?? copy.waiting }),
|
|
4047
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Chevron2, { open: expanded })
|
|
2206
4048
|
]
|
|
2207
|
-
}
|
|
2208
|
-
|
|
2209
|
-
)
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
4049
|
+
}
|
|
4050
|
+
),
|
|
4051
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "ccd-context-bar", "data-ccd-context-bar": "", ref: bar, "aria-hidden": "true", children: [
|
|
4052
|
+
segments.drawn.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
4053
|
+
"span",
|
|
4054
|
+
{
|
|
4055
|
+
className: `ccd-context-seg ccd-context-seg-${entry.key}`,
|
|
4056
|
+
"data-ccd-context-seg": entry.key,
|
|
4057
|
+
style: { flexGrow: entry.tokens }
|
|
4058
|
+
},
|
|
4059
|
+
entry.key
|
|
4060
|
+
)),
|
|
4061
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "ccd-context-track", "data-ccd-context-track": "", style: { flexGrow: segments.free } })
|
|
4062
|
+
] }),
|
|
4063
|
+
expanded && breakdown !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "ccd-context-rows", children: [
|
|
4064
|
+
breakdown.slices.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "ccd-context-row", "data-ccd-context-row": entry.key, children: [
|
|
4065
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "ccd-context-label", children: [
|
|
4066
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("i", { className: `ccd-context-swatch ccd-context-swatch-${entry.key}`, "aria-hidden": "true" }),
|
|
4067
|
+
copy.rows[entry.key]
|
|
4068
|
+
] }),
|
|
4069
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "ccd-context-tokens", children: formatTokens(entry.tokens) }),
|
|
4070
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "ccd-context-percent", children: formatPercent(entry.tokens, breakdown.window) })
|
|
4071
|
+
] }, entry.key)),
|
|
4072
|
+
groups.map((group) => {
|
|
4073
|
+
const groupOpen = expandedGroups.has(group.label);
|
|
4074
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("section", { className: "ccd-context-group", "data-ccd-context-group": "", children: [
|
|
4075
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
4076
|
+
"button",
|
|
4077
|
+
{
|
|
4078
|
+
type: "button",
|
|
4079
|
+
className: "ccd-context-group-head",
|
|
4080
|
+
"data-ccd-context-focus": "",
|
|
4081
|
+
"aria-expanded": groupOpen,
|
|
4082
|
+
onClick: () => {
|
|
4083
|
+
setExpandedGroups((current) => {
|
|
4084
|
+
const next = new Set(current);
|
|
4085
|
+
if (next.has(group.label)) next.delete(group.label);
|
|
4086
|
+
else next.add(group.label);
|
|
4087
|
+
return next;
|
|
4088
|
+
});
|
|
4089
|
+
},
|
|
4090
|
+
children: [
|
|
4091
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "ccd-context-label", children: [
|
|
4092
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Chevron2, { open: groupOpen }),
|
|
4093
|
+
group.label
|
|
4094
|
+
] }),
|
|
4095
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "ccd-context-tokens", children: formatTokens(group.tokens) }),
|
|
4096
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "ccd-context-percent", title: `${group.rows.length} ${copy.count}`, children: group.rows.length })
|
|
4097
|
+
]
|
|
4098
|
+
}
|
|
4099
|
+
),
|
|
4100
|
+
groupOpen && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "ccd-context-detail", children: group.rows.map(([name, tokens]) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "ccd-context-row ccd-context-detail-row", children: [
|
|
4101
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "ccd-context-label", title: name, children: shortenPath(name.startsWith("mcp__") ? toolLabel(name) : name) }),
|
|
4102
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "ccd-context-tokens", children: formatTokens(tokens) }),
|
|
4103
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "ccd-context-percent" })
|
|
4104
|
+
] }, name)) })
|
|
4105
|
+
] }, group.label);
|
|
4106
|
+
})
|
|
4107
|
+
] })
|
|
4108
|
+
]
|
|
4109
|
+
}
|
|
4110
|
+
);
|
|
2213
4111
|
}
|
|
2214
4112
|
|
|
2215
|
-
// src/client/features/
|
|
2216
|
-
var controls_default = "/* Separate model/effort controls, sharing the official Composer's selection seat. */\nhtml[data-dsh-ccd-style=\"true\"] .ccd-model-controls { display: flex; align-items: center; gap: var(--ccd-cluster-gap, 4px); min-width: 0; max-width: var(--ccd-model-max-width, 45cqw); }\n/*\n * Both controls are the status bar's value chips, and a chip's fill is one\n * label line tall rather than one control row: `--ccd-chip-height` (20px)\n * inside the row's own 28px, so the model name and the effort beside it sit\n * 5.5px from the chip's edge instead of the 10px a full-height box leaves. The\n * row keeps `--ccd-control-row-height` \u2014 the dock, the ring's seat and the\n * trailing cluster are all measured from it \u2014 and this flex line centres the\n * chip in it. The corner is the height's own (`--ccd-chip-radius`, the\n * reference's 12 device px at @2x) rather than the row's 8px, which on a 20px\n * box would round the fill towards a pill, and the inline padding is the row's\n * one chip padding (`--ccd-chip-padding-inline`), which is what the command\n * plus, the permission control and the readouts beside them also take. The 12px\n * label, its chosen-value ink and the hover and expanded fills are unchanged;\n * only the box those fills paint is. All three tokens keep a fallback so a sheet\n * mounted without tokens.css still draws a chip rather than collapsing the\n * control to its text.\n */\n.ccd-model-trigger, .ccd-effort-trigger { display: inline-flex; align-items: center; justify-content: center; gap: 5px; min-width: 0; height: var(--ccd-chip-height, 20px); padding: 0 var(--ccd-chip-padding-inline, 7px); border: 0; border-radius: var(--ccd-chip-radius, 6px); background: transparent; color: var(--ccd-text-strong); font: inherit; font-size: 12px; cursor: pointer; white-space: nowrap; }\n.ccd-model-trigger { flex: 1 1 auto; overflow: hidden; }\n.ccd-model-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }\n.ccd-effort-trigger { flex: 0 0 auto; }\n.ccd-model-trigger:hover, .ccd-effort-trigger:hover, .ccd-model-trigger[aria-expanded=\"true\"], .ccd-effort-trigger[aria-expanded=\"true\"] { background: var(--ccd-hover); }\n.ccd-model-trigger:disabled, .ccd-effort-trigger:disabled { opacity: .55; cursor: default; }\n.ccd-model-trigger:focus-visible, .ccd-effort-trigger:focus-visible { outline: 2px solid var(--ccd-effort-color-1); outline-offset: 2px; }\n/*\n * The chosen value is marked by ink, not by weight: full black for the model\n * name and the effort, the row's own 400 for both, and the panel's option rows\n * the same. That only holds if each control names its colour here \u2014 the\n * Composer's tool row paints every button it contains with its own secondary\n * grey (`html[data-dsh-ccd-style=\"true\"] .yhfFVG_row button`, theme/composer.css,\n * specificity 0-2-2), and both triggers and the menu sit inside that row (the\n * panel is a fixed-position child of `.ccd-model-controls`). At their own\n * single-class weight they lose the cascade and the name renders grey whatever\n * the font weight. Only the model name is the chosen value here; the provider\n * heading above it stays `--ccd-text-secondary`.\n */\nhtml[data-dsh-ccd-style=\"true\"] .ccd-model-controls .ccd-model-trigger,\nhtml[data-dsh-ccd-style=\"true\"] .ccd-model-controls .ccd-effort-trigger,\nhtml[data-dsh-ccd-style=\"true\"] .ccd-model-controls .ccd-model-options button { color: var(--ccd-text-strong); }\n/*\n * The model card is a popup of the plugin's own making, so it answers to the\n * popup section of the design document rather than to the Composer it hangs\n * from: 12px corner, 1px outline, three-layer hairline shadow, card fill\n * (DESIGN.md \xA76.7). Its model names keep the chosen-value ink the rule above\n * gives them; everything the card decides for itself \u2014 how tall a row is, what\n * a heading is worth, where an editable face's line steps \u2014 is below.\n */\n.ccd-model-panel, .ccd-model-error { position: fixed; z-index: 1000; box-sizing: border-box; width: min(240px, calc(100vw - 24px)); max-height: calc(100vh - 24px); overflow: auto; padding: 6px; border: 1px solid var(--ccd-border); border-radius: 12px; background: var(--ccd-card); color: var(--ccd-text); box-shadow: var(--ccd-effort-shadow); font: 13px/1.4 var(--ccd-font-ui); }\n/* Native top-layer placement keeps the existing anchor coordinates. The\n backdrop stays click-through so usePopup still owns outside dismissal. */\n.ccd-model-panel[popover], .ccd-effort[popover] { inset: auto; margin: 0; }\n.ccd-model-panel::backdrop, .ccd-effort::backdrop { background: transparent; pointer-events: none; }\n/*\n * The card's one editable face. A face carries a stronger line than the card\n * outline around it (DESIGN.md \xA7\u4E8C.4: `--ccd-border-strong`, the line the\n * Composer's own input face draws). It is also a cell of the card's own\n * rhythm, not a banner above it: 24px \u2014 the same box the rows and the group\n * headings below it occupy, a 20px line with 2px above and below \u2014 with the\n * corner that height asks for (the rows' 6px, not the popup's 12px). The\n * placeholder is the muted ink, so the empty field reads in the palette\n * instead of in the browser's own grey.\n */\n.ccd-model-search { box-sizing: border-box; width: 100%; height: 24px; margin-bottom: 4px; padding: 0 8px; border: 1px solid var(--ccd-border-strong); border-radius: 6px; background: var(--ccd-card); color: inherit; font: inherit; }\n.ccd-model-search::placeholder { color: var(--ccd-text-muted); }\n.ccd-model-search:focus-visible { outline: 2px solid var(--ccd-accent); outline-offset: 2px; }\n/*\n * The provider heading is a cell of the same 24px rhythm as the rows it opens,\n * not a block with a size of its own: one 20px line with 2px above and below,\n * in the control-label size (12px \u2014 the document's scale has no 11px step) and\n * the secondary ink. It never takes a fill, because it is not a choice.\n */\n.ccd-model-provider { padding: 2px 8px; color: var(--ccd-text-secondary); font-size: 12px; line-height: 20px; }\n/*\n * One row is 24px (DESIGN.md \xA76.7): a 13px label on a 20px line with 2px above\n * and below \u2014 the reference's 48 device px at @2x, against the 34px this card\n * used to draw (13px/1.4 plus 8px of block padding). The corner follows the\n * height: 6px, because the host's 12px row radius is a pill at half of 24px.\n * Height is pinned against the box model rather than inherited from it: the\n * button leaves `box-sizing` to the page, so the same declarations would build\n * a 28px row under the content-box default.\n */\n.ccd-model-options button { display: flex; align-items: center; justify-content: space-between; gap: 8px; box-sizing: border-box; width: 100%; min-height: 24px; padding: 2px 8px; border: 0; border-radius: 6px; background: transparent; color: inherit; text-align: left; font: inherit; line-height: 20px; cursor: pointer; }\n.ccd-model-options button:hover, .ccd-model-options button:focus-visible { background: var(--ccd-hover); }\n/*\n * Keyboard focus is the one state the fill cannot state on its own: the same\n * fill is what the pointer paints, so a filled row says \"the pointer is here\"\n * and not \"the keyboard is here\". The row takes the document's focus ring\n * (\xA7\u4E03: 2px solid accent, 2px offset, the shape following the control's own 6px\n * corner) beside the fill, which is the pair this plugin's settings popup\n * already draws on its own rows. The row is centred in the card's 6px padding\n * and 2px of offset stay inside it, so the ring never reaches the card's edge.\n */\n.ccd-model-options button:focus-visible { outline: 2px solid var(--ccd-accent); outline-offset: 2px; }\n.ccd-model-options button:disabled { opacity: .55; cursor: default; }\n/*\n * The card's cells that are not choices: loading, empty, a failed provider and\n * the inline error. They take the rows' geometry and ink so the card reads as\n * one column whatever state it is in \u2014 the row is a flex line here for the same\n * reason it is one above, so the message and the retry after it keep the card's\n * 8px gap \u2014 and the retry is a bare accent label: the card has no room for the\n * host's filled button, and flat ink is the document's default control (\xA7\u4E8C.1).\n */\n.ccd-model-note { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px; margin: 0; padding: 2px 8px; color: var(--ccd-text-secondary); line-height: 20px; }\n.ccd-model-retry { padding: 0; border: 0; border-radius: 6px; background: none; color: var(--ccd-accent); font: inherit; cursor: pointer; }\n.ccd-model-retry:hover { text-decoration: underline; }\n.ccd-model-retry:focus-visible { outline: 2px solid var(--ccd-accent); outline-offset: 2px; }\n.ccd-model-error { bottom: 60px; right: 16px; }\n@container ccd-composer (max-width: 420px) { .ccd-model-trigger { padding-inline: 4px; } .ccd-effort-trigger { padding-inline: 4px; } }\n";
|
|
2217
|
-
|
|
2218
|
-
// src/client/features/model-controls/effort.css
|
|
2219
|
-
var effort_default = '/* Adapted from zanwei/claude-model-selector (MIT). See LICENSE. */\n\n.ccd-effort {\n --effort-accent: var(--ccd-effort-color-1);\n --effort-text: var(--ccd-effort-color-3);\n --effort-text-strong: var(--ccd-effort-color-4);\n --effort-muted: var(--ccd-effort-color-5);\n --effort-track: var(--ccd-effort-color-6);\n --effort-track-fill: var(--ccd-effort-color-7);\n --effort-progress: 0;\n --effort-thumb-w: 16px;\n --effort-thumb-h: 20px;\n --effort-track-pad: 1px;\n --effort-track-radius: 10px;\n --effort-surface: var(--ccd-effort-color-8);\n --effort-outline: var(--ccd-effort-color-9);\n --ease-decay: cubic-bezier(0.2, 0, 0, 1);\n display: block;\n width: min(220px, calc(100vw - 24px));\n max-width: 100%;\n color: var(--effort-text);\n font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;\n font-size: 12px;\n line-height: 1.35;\n font-synthesis: none;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n.ccd-effort *, .ccd-effort *::before, .ccd-effort *::after {\n box-sizing: border-box;\n}\n\n.ccd-effort button, .ccd-effort input {\n font: inherit;\n}\n\n.ccd-effort .panel {\n position: absolute;\n z-index: 4;\n right: 0;\n bottom: 3.25rem;\n width: 100%;\n padding: 12px;\n border: 1px solid var(--effort-outline);\n border-radius: 12px;\n background: var(--effort-surface);\n box-shadow:\n 0 1px 2px var(--ccd-effort-color-11),\n 0 4px 10px var(--ccd-effort-color-12),\n 0 12px 28px var(--ccd-effort-color-13);\n opacity: 1;\n transform: translateY(0);\n transition-property: opacity, transform;\n transition-duration: 120ms;\n transition-timing-function: ease-in;\n}\n\n.ccd-effort .header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n min-height: 20px;\n gap: 8px;\n}\n\n.ccd-effort .title {\n display: flex;\n align-items: baseline;\n min-width: 0;\n color: var(--effort-text);\n font-size: 13px;\n font-weight: 500;\n line-height: 1.3;\n letter-spacing: -0.01em;\n text-wrap: balance;\n}\n\n.ccd-effort .level-stage {\n position: relative;\n display: inline-block;\n height: 1.3em;\n margin-left: 4px;\n color: var(--effort-text-strong);\n line-height: inherit;\n vertical-align: baseline;\n}\n\n.ccd-effort .level-stage > span {\n position: absolute;\n top: 0;\n left: 0;\n line-height: inherit;\n white-space: nowrap;\n transform-origin: left center;\n}\n\n.ccd-effort .level-current {\n opacity: 1;\n transform: translateY(0);\n filter: blur(0);\n transition-property: opacity, transform, filter, color;\n transition-duration: 180ms;\n transition-timing-function: cubic-bezier(0.2, 0, 0, 1);\n}\n\n.ccd-effort .level-current.is-preparing {\n opacity: 0;\n transform: translateY(var(--label-enter-y, 3px));\n filter: blur(2px);\n transition-duration: 0ms;\n transition-delay: 0ms;\n}\n\n.ccd-effort .level-outgoing {\n opacity: 1;\n transform: translateY(0);\n filter: blur(0);\n pointer-events: none;\n transition-property: opacity, transform, filter;\n transition-duration: 180ms;\n transition-timing-function: cubic-bezier(0.2, 0, 0, 1);\n}\n\n.ccd-effort .level-outgoing.is-exiting {\n opacity: 0;\n transform: translateY(var(--label-exit-y, -3px));\n filter: blur(2px);\n}\n\n.ccd-effort[data-ultra] .level-current {\n color: var(--effort-accent);\n}\n\n.ccd-effort .help-wrap {\n position: relative;\n flex: 0 0 auto;\n}\n\n.ccd-effort .help-button {\n position: relative;\n display: grid;\n width: 20px;\n height: 20px;\n margin: 0;\n padding: 2px;\n place-items: center;\n border: 0;\n border-radius: 6px;\n background: transparent;\n color: var(--effort-muted);\n cursor: help;\n transition-property: color, background-color, scale;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n}\n\n.ccd-effort .help-button:hover {\n color: var(--ccd-effort-color-14);\n background: var(--ccd-effort-color-15);\n}\n\n.ccd-effort .help-button:active {\n scale: 0.96;\n}\n\n.ccd-effort .help-button:focus-visible {\n outline: 2px solid color-mix(in srgb, var(--effort-accent) 35%, transparent);\n outline-offset: 2px;\n}\n\n.ccd-effort .help-button svg {\n width: 14px;\n height: 14px;\n}\n\n.ccd-effort .tooltip {\n position: absolute;\n z-index: 8;\n top: auto;\n bottom: calc(100% + 6px);\n right: 0;\n width: min(220px, calc(100vw - 24px));\n padding: 10px 12px;\n border: 1px solid var(--ccd-effort-color-16);\n border-radius: 0.5rem;\n background: var(--ccd-effort-color-17);\n box-shadow:\n 0 2px 5px var(--ccd-effort-color-18),\n 0 8px 18px var(--ccd-effort-color-18);\n color: var(--ccd-effort-color-19);\n font-size: 12px;\n font-weight: 450;\n line-height: 1.35;\n text-wrap: pretty;\n opacity: 0;\n visibility: hidden;\n transform: translateY(-2px);\n transition-property: opacity, transform, visibility;\n transition-duration: 120ms;\n transition-timing-function: ease-in;\n}\n\n.ccd-effort .help-wrap:hover .tooltip,\n.ccd-effort .help-button:focus-visible + .tooltip,\n.ccd-effort .help-wrap[data-tip-open] .tooltip {\n opacity: 1;\n visibility: visible;\n transform: translateY(0);\n transition-timing-function: ease-out;\n}\n\n.ccd-effort .axis {\n display: flex;\n justify-content: space-between;\n margin-top: 16px;\n color: var(--effort-muted);\n font-size: 12px;\n font-weight: 450;\n line-height: 1.3;\n letter-spacing: -0.01em;\n}\n\n.ccd-effort .track-shell {\n position: relative;\n height: 32px;\n margin-top: 2px;\n}\n\n.ccd-effort .track {\n position: absolute;\n inset: 6px 0;\n overflow: hidden;\n border-radius: var(--effort-track-radius);\n background-color: var(--effort-track);\n box-shadow: inset 0 1px 1px var(--ccd-effort-color-20);\n}\n\n.ccd-effort .track-fill {\n position: absolute;\n z-index: 0;\n top: 0;\n bottom: 0;\n left: var(--effort-track-pad);\n width: calc(\n (100% - (var(--effort-track-pad) * 2) - var(--effort-thumb-w))\n * var(--effort-progress, 0)\n + (var(--effort-thumb-w) * 0.5)\n );\n border-radius: calc(var(--effort-track-radius) - 1px) 0 0 calc(var(--effort-track-radius) - 1px);\n background: var(--effort-track-fill);\n pointer-events: none;\n transition-property: opacity;\n transition-duration: 200ms;\n transition-timing-function: var(--ease-decay);\n}\n\n.ccd-effort[data-ultra] .track-fill {\n opacity: 0;\n}\n\n.ccd-effort .track::before {\n content: "";\n position: absolute;\n z-index: 0;\n inset: 0;\n border-radius: inherit;\n background: linear-gradient(\n 90deg,\n var(--ccd-effort-color-21) 0%,\n var(--ccd-effort-color-22) 18%,\n var(--ccd-effort-color-23) 32%,\n var(--ccd-effort-color-24) 48%,\n var(--ccd-effort-color-25) 68%,\n var(--ccd-effort-color-26) 82%,\n var(--ccd-effort-color-27) 100%\n );\n opacity: 0;\n transition-property: opacity;\n transition-duration: 340ms;\n transition-timing-function: ease-in;\n}\n\n.ccd-effort[data-ultra] .track::before {\n opacity: 1;\n}\n\n.ccd-effort .ultra-fallback,\n.ccd-effort .pixel-field {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n opacity: 0;\n transition-property: opacity;\n transition-duration: 200ms;\n transition-timing-function: var(--ease-decay);\n}\n\n.ccd-effort .ultra-fallback {\n background: linear-gradient(\n 90deg,\n var(--ccd-effort-color-21) 0%,\n var(--ccd-effort-color-22) 18%,\n var(--ccd-effort-color-23) 32%,\n var(--ccd-effort-color-28) 48%,\n var(--ccd-effort-color-29) 68%,\n var(--ccd-effort-color-30) 82%,\n var(--ccd-effort-color-31) 100%\n );\n}\n\n.ccd-effort[data-ultra] .pixel-field {\n opacity: 1;\n}\n\n.ccd-effort .ticks {\n position: absolute;\n z-index: 1;\n inset: 0 calc(var(--effort-track-pad) + var(--effort-thumb-w) * 0.5);\n display: flex;\n align-items: center;\n justify-content: space-between;\n pointer-events: none;\n}\n\n.ccd-effort .tick {\n width: 3px;\n height: 3px;\n border-radius: 999px;\n background: var(--ccd-effort-color-32);\n opacity: 0.82;\n transition-property: opacity;\n transition-duration: 180ms;\n transition-timing-function: var(--ease-decay);\n}\n\n.ccd-effort .tick:last-child {\n background: var(--effort-accent);\n opacity: 1;\n}\n\n.ccd-effort[data-ultra] .tick {\n opacity: 0;\n}\n\n.ccd-effort .range {\n position: absolute;\n z-index: 3;\n inset: 0 var(--effort-track-pad);\n width: calc(100% - var(--effort-track-pad) * 2);\n height: 100%;\n margin: 0;\n appearance: none;\n -webkit-appearance: none;\n border: 0;\n outline: 0;\n background: transparent;\n cursor: ew-resize;\n touch-action: none;\n}\n\n.ccd-effort .range::-webkit-slider-runnable-track {\n height: var(--effort-thumb-h);\n border: 0;\n background: transparent;\n}\n\n.ccd-effort .range::-webkit-slider-thumb {\n width: var(--effort-thumb-w);\n height: var(--effort-thumb-h);\n margin-top: 0;\n appearance: none;\n -webkit-appearance: none;\n border: 1px solid var(--ccd-effort-color-33);\n border-radius: 0.5rem;\n background: var(--ccd-effort-color-19);\n box-shadow:\n 0 1px 2px var(--ccd-effort-color-34),\n 0 4px 10px var(--ccd-effort-color-35);\n cursor: ew-resize;\n transition-property: transform;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n}\n\n.ccd-effort .range::-moz-range-track {\n height: var(--effort-thumb-h);\n border: 0;\n background: transparent;\n}\n\n.ccd-effort .range::-moz-range-progress {\n background: transparent;\n}\n\n.ccd-effort .range::-moz-range-thumb {\n width: var(--effort-thumb-w);\n height: var(--effort-thumb-h);\n border: 1px solid var(--ccd-effort-color-33);\n border-radius: 0.5rem;\n background: var(--ccd-effort-color-19);\n box-shadow:\n 0 1px 2px var(--ccd-effort-color-34),\n 0 4px 10px var(--ccd-effort-color-35);\n cursor: ew-resize;\n transition-property: transform;\n transition-duration: 150ms;\n transition-timing-function: ease-out;\n}\n\n.ccd-effort .range:active::-webkit-slider-thumb {\n transform: scale(0.96);\n}\n\n.ccd-effort .range:active::-moz-range-thumb {\n transform: scale(0.96);\n}\n\n.ccd-effort .range:focus-visible::-webkit-slider-thumb {\n box-shadow:\n 0 0 0 3px color-mix(in srgb, var(--effort-accent) 30%, transparent),\n 0 1px 2px var(--ccd-effort-color-34),\n 0 4px 10px var(--ccd-effort-color-35);\n}\n\n.ccd-effort .range:focus-visible::-moz-range-thumb {\n box-shadow:\n 0 0 0 3px color-mix(in srgb, var(--effort-accent) 30%, transparent),\n 0 1px 2px var(--ccd-effort-color-34),\n 0 4px 10px var(--ccd-effort-color-35);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .ccd-effort .level-current,\n .ccd-effort .level-outgoing,\n .ccd-effort .panel,\n .ccd-effort .track::before,\n .ccd-effort .track-fill,\n .ccd-effort .range::-webkit-slider-thumb,\n .ccd-effort .range::-moz-range-thumb,\n .ccd-effort .help-button,\n .ccd-effort .tooltip {\n transition-duration: 0.001ms;\n }\n\n .ccd-effort[data-ultra] .ultra-fallback {\n opacity: 1;\n }\n\n .ccd-effort .pixel-field {\n display: none;\n }\n}\n\n.ccd-effort .panel { position: static; width: 100%; }\n.ccd-effort { position: fixed; z-index: 1000; margin: 0; padding: 0; border: 0; background: transparent; overflow: visible; }\n.ccd-effort .level-stage::after {\n content: attr(data-longest);\n visibility: hidden;\n pointer-events: none;\n}\n.ccd-effort .selection-error { margin-top: 8px; color: var(--ccd-text-secondary); font-size: 12px; overflow-wrap: anywhere; }\n';
|
|
4113
|
+
// src/client/features/context-panel/context-panel.css
|
|
4114
|
+
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";
|
|
2220
4115
|
|
|
2221
|
-
// src/client/features/
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
return
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
const selection = effortSelection(directory.store.getSnapshot(), route, effort);
|
|
2246
|
-
if (selection) void directory.select(selection).catch(() => {
|
|
2247
|
-
});
|
|
2248
|
-
}
|
|
2249
|
-
};
|
|
2250
|
-
}
|
|
2251
|
-
}, ModelControls));
|
|
2252
|
-
});
|
|
4116
|
+
// src/client/features/context-panel/mount.ts
|
|
4117
|
+
var CONTEXT_PANEL_ORDER = 40;
|
|
4118
|
+
function mountContextPanel(ctx, environment, cleanup) {
|
|
4119
|
+
const projections = environment.host.contextProjections;
|
|
4120
|
+
if (projections === null) {
|
|
4121
|
+
environment.logger.debug("context panel: this build publishes no session list; the ring keeps its own panel");
|
|
4122
|
+
return;
|
|
4123
|
+
}
|
|
4124
|
+
cleanup.add(environment.dom.mountStyles(context_panel_default));
|
|
4125
|
+
const driver = createContextPanelDriver(
|
|
4126
|
+
document,
|
|
4127
|
+
projections,
|
|
4128
|
+
(error) => environment.logger.error("context panel: the ring trigger could not be watched", error)
|
|
4129
|
+
);
|
|
4130
|
+
cleanup.add(driver.dispose);
|
|
4131
|
+
const registration = ctx.inject(["slots"], (scope) => scope.slots.inject(
|
|
4132
|
+
DSH_SLOTS.shellOverlay,
|
|
4133
|
+
() => scope.slots.register({
|
|
4134
|
+
name: DSH_SLOTS.shellOverlay,
|
|
4135
|
+
id: "ccd-context-panel",
|
|
4136
|
+
order: CONTEXT_PANEL_ORDER,
|
|
4137
|
+
inject: () => ({ port: driver.port })
|
|
4138
|
+
}, ContextPanel)
|
|
4139
|
+
));
|
|
2253
4140
|
cleanup.add(() => {
|
|
2254
4141
|
void registration.dispose();
|
|
2255
4142
|
});
|
|
@@ -2266,7 +4153,7 @@ function openSessionMenu(document2) {
|
|
|
2266
4153
|
const list = trigger.parentElement?.querySelector(HOST.menu) ?? null;
|
|
2267
4154
|
return list === null ? null : { list, trigger };
|
|
2268
4155
|
}
|
|
2269
|
-
function
|
|
4156
|
+
function viewportOf2(list) {
|
|
2270
4157
|
for (const child of list.children) {
|
|
2271
4158
|
if (child.getAttribute("role") === "presentation") return child;
|
|
2272
4159
|
}
|
|
@@ -2279,7 +4166,7 @@ function labelSeat(row) {
|
|
|
2279
4166
|
return null;
|
|
2280
4167
|
}
|
|
2281
4168
|
function insertRow(list, trigger, row, report) {
|
|
2282
|
-
const viewport =
|
|
4169
|
+
const viewport = viewportOf2(list);
|
|
2283
4170
|
const source = viewport?.querySelector(HOST.menuItem) ?? null;
|
|
2284
4171
|
const wrapper = source?.parentElement ?? null;
|
|
2285
4172
|
if (viewport === null || wrapper === null) return null;
|
|
@@ -2354,32 +4241,10 @@ function mountSessionMenuRow(document2, row, report) {
|
|
|
2354
4241
|
};
|
|
2355
4242
|
}
|
|
2356
4243
|
|
|
2357
|
-
// src/client/compat/slots.ts
|
|
2358
|
-
var DSH_SLOTS = Object.freeze({
|
|
2359
|
-
sidebar: "sidebar",
|
|
2360
|
-
workspaces: "sidebar.workspaces",
|
|
2361
|
-
settings: "sidebar.settings",
|
|
2362
|
-
main: "main",
|
|
2363
|
-
conversation: "main.conversation",
|
|
2364
|
-
header: "conversation.header",
|
|
2365
|
-
sessionHeader: "conversation.session.header",
|
|
2366
|
-
/** Trailing header seats: `utilities` is a list the native actions occupy too. */
|
|
2367
|
-
sessionHeaderUtilities: "conversation.session.header.utilities",
|
|
2368
|
-
sessionHeaderCorner: "conversation.session.header.corner",
|
|
2369
|
-
composerBar: "conversation.composer.bar",
|
|
2370
|
-
inputDock: "conversation.input.dock",
|
|
2371
|
-
composerDock: "conversation.composer.dock",
|
|
2372
|
-
toolView: "tool.call.toolview",
|
|
2373
|
-
leading: "shell.leading",
|
|
2374
|
-
/** Frame-wide floating layer, click-through until an entry opts back in. */
|
|
2375
|
-
shellOverlay: "shell.overlay"
|
|
2376
|
-
});
|
|
2377
|
-
var OVERRIDE_PRIORITY = -10;
|
|
2378
|
-
|
|
2379
4244
|
// src/client/features/conversation/header-actions/HeaderActionButton.tsx
|
|
2380
|
-
var
|
|
4245
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
2381
4246
|
function HeaderActionButton({ label, kind, open }) {
|
|
2382
|
-
return /* @__PURE__ */ (0,
|
|
4247
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2383
4248
|
"button",
|
|
2384
4249
|
{
|
|
2385
4250
|
type: "button",
|
|
@@ -2536,7 +4401,7 @@ function mountHeaderActions(ctx, environment, cleanup) {
|
|
|
2536
4401
|
}
|
|
2537
4402
|
|
|
2538
4403
|
// src/client/features/composer-pet/mount.ts
|
|
2539
|
-
var
|
|
4404
|
+
var import_react8 = require("react");
|
|
2540
4405
|
|
|
2541
4406
|
// src/client/compat/pet-anchor.ts
|
|
2542
4407
|
var PET_BOX = Object.freeze({ width: 32, height: 24 });
|
|
@@ -2630,23 +4495,23 @@ function inertAnchor() {
|
|
|
2630
4495
|
}
|
|
2631
4496
|
|
|
2632
4497
|
// src/client/features/composer-pet/ComposerPet.tsx
|
|
2633
|
-
var
|
|
4498
|
+
var import_react7 = require("react");
|
|
2634
4499
|
|
|
2635
4500
|
// src/client/features/composer-pet/Whale.tsx
|
|
2636
|
-
var
|
|
4501
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
2637
4502
|
function Whale() {
|
|
2638
|
-
return /* @__PURE__ */ (0,
|
|
2639
|
-
/* @__PURE__ */ (0,
|
|
2640
|
-
/* @__PURE__ */ (0,
|
|
2641
|
-
/* @__PURE__ */ (0,
|
|
2642
|
-
/* @__PURE__ */ (0,
|
|
2643
|
-
/* @__PURE__ */ (0,
|
|
2644
|
-
/* @__PURE__ */ (0,
|
|
2645
|
-
/* @__PURE__ */ (0,
|
|
2646
|
-
/* @__PURE__ */ (0,
|
|
2647
|
-
/* @__PURE__ */ (0,
|
|
2648
|
-
/* @__PURE__ */ (0,
|
|
2649
|
-
/* @__PURE__ */ (0,
|
|
4503
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { className: "ccd-pet-art", viewBox: "0 0 32 24", width: "32", height: "24", "aria-hidden": "true", shapeRendering: "crispEdges", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("g", { className: "ccd-pet-body", children: [
|
|
4504
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { className: "ccd-pet-tail", d: "M23 12V8H22V5H23V2H24V4H26V5H27V7H28V6H30V5H32V8H31V10H29V11H27V14H25V16H22Z" }),
|
|
4505
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M1 11V9H2V7H4V5H7V4H12V5H15V4H18V5H17V7H19V9H21V11H24V15H23V18H21V20H19V22H16V23H10V22H7V21H5V19H3V17H2V14H1Z" }),
|
|
4506
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { className: "ccd-pet-belly", d: "M3 11H6V12H9V13H11V15H13V17H15V19H18V21H15V20H12V18H10V19H11V20H9V19H7V18H6V17H5V15H4V13H3Z" }),
|
|
4507
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { className: "ccd-pet-fin", d: "M17 17H19V18H21V19H24V20H26V21H23V22H20V21H18V20H17Z" }),
|
|
4508
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { className: "ccd-pet-eye", d: "M17 11H19V12H20V14H18V13H17Z" }),
|
|
4509
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { className: "ccd-pet-eye-shut", d: "M17 12H20V13H17Z" }),
|
|
4510
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("g", { className: "ccd-pet-spout-position", transform: "translate(3 0)", children: [
|
|
4511
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("g", { className: "ccd-pet-spout ccd-pet-spout-stem", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M9 4V-3H10V-4H11V4Z M7 -4H9V-2H7Z M11 -4H13V-2H11Z" }) }),
|
|
4512
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("g", { className: "ccd-pet-spout ccd-pet-spout-left", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M6 -5H8V-3H6Z" }) }),
|
|
4513
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("g", { className: "ccd-pet-spout ccd-pet-spout-right", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M12 -5H14V-3H12Z" }) }),
|
|
4514
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("g", { className: "ccd-pet-spout ccd-pet-spout-top", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M9 -7H11V-5H9Z" }) })
|
|
2650
4515
|
] })
|
|
2651
4516
|
] }) });
|
|
2652
4517
|
}
|
|
@@ -2779,13 +4644,13 @@ function playPetReaction(art, reaction, reducedMotion) {
|
|
|
2779
4644
|
}
|
|
2780
4645
|
|
|
2781
4646
|
// src/client/features/composer-pet/ComposerPet.tsx
|
|
2782
|
-
var
|
|
4647
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
2783
4648
|
function ComposerPet() {
|
|
2784
|
-
const [hidden, setHidden] = (0,
|
|
2785
|
-
const [reaction, setReaction] = (0,
|
|
2786
|
-
const button = (0,
|
|
2787
|
-
const active = (0,
|
|
2788
|
-
(0,
|
|
4649
|
+
const [hidden, setHidden] = (0, import_react7.useState)(() => document.hidden);
|
|
4650
|
+
const [reaction, setReaction] = (0, import_react7.useState)(null);
|
|
4651
|
+
const button = (0, import_react7.useRef)(null);
|
|
4652
|
+
const active = (0, import_react7.useRef)(null);
|
|
4653
|
+
(0, import_react7.useEffect)(() => {
|
|
2789
4654
|
const cancel = () => {
|
|
2790
4655
|
const playback = active.current;
|
|
2791
4656
|
active.current = null;
|
|
@@ -2818,7 +4683,7 @@ function ComposerPet() {
|
|
|
2818
4683
|
setReaction(null);
|
|
2819
4684
|
});
|
|
2820
4685
|
};
|
|
2821
|
-
return /* @__PURE__ */ (0,
|
|
4686
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2822
4687
|
"button",
|
|
2823
4688
|
{
|
|
2824
4689
|
ref: button,
|
|
@@ -2832,17 +4697,17 @@ function ComposerPet() {
|
|
|
2832
4697
|
event.stopPropagation();
|
|
2833
4698
|
play();
|
|
2834
4699
|
},
|
|
2835
|
-
children: /* @__PURE__ */ (0,
|
|
4700
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Whale, {})
|
|
2836
4701
|
}
|
|
2837
4702
|
);
|
|
2838
4703
|
}
|
|
2839
4704
|
|
|
2840
4705
|
// src/client/features/composer-pet/BlankPet.tsx
|
|
2841
|
-
var
|
|
4706
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
2842
4707
|
function BlankPet({ useAnchor }) {
|
|
2843
4708
|
const anchor = useAnchor();
|
|
2844
4709
|
if (anchor === null) return null;
|
|
2845
|
-
return /* @__PURE__ */ (0,
|
|
4710
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "ccd-pet-blank-seat", style: { left: anchor.left, top: anchor.top }, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ComposerPet, {}) });
|
|
2846
4711
|
}
|
|
2847
4712
|
|
|
2848
4713
|
// src/client/features/composer-pet/pet.css
|
|
@@ -2970,7 +4835,7 @@ function mountComposerPet(ctx, environment, cleanup) {
|
|
|
2970
4835
|
id: "ccd-blank-pet",
|
|
2971
4836
|
order: PET_ORDER,
|
|
2972
4837
|
inject: () => ({
|
|
2973
|
-
useAnchor: () => (0,
|
|
4838
|
+
useAnchor: () => (0, import_react8.useSyncExternalStore)(anchor.subscribe, anchor.getSnapshot)
|
|
2974
4839
|
})
|
|
2975
4840
|
}, BlankPet)));
|
|
2976
4841
|
}
|
|
@@ -2984,10 +4849,10 @@ function mountComposerPet(ctx, environment, cleanup) {
|
|
|
2984
4849
|
}
|
|
2985
4850
|
|
|
2986
4851
|
// src/client/features/settings/mount.ts
|
|
2987
|
-
var
|
|
4852
|
+
var import_react12 = require("react");
|
|
2988
4853
|
|
|
2989
4854
|
// src/client/features/settings/ConfigPage.tsx
|
|
2990
|
-
var
|
|
4855
|
+
var import_react11 = require("react");
|
|
2991
4856
|
|
|
2992
4857
|
// src/client/features/settings/edits.ts
|
|
2993
4858
|
function setOperation(path, value) {
|
|
@@ -3049,32 +4914,32 @@ function loadSystemFontFamilies() {
|
|
|
3049
4914
|
}
|
|
3050
4915
|
|
|
3051
4916
|
// src/client/features/settings/fields.tsx
|
|
3052
|
-
var
|
|
4917
|
+
var import_react10 = require("react");
|
|
3053
4918
|
|
|
3054
4919
|
// src/client/features/settings/FontMenu.tsx
|
|
3055
|
-
var
|
|
3056
|
-
var
|
|
4920
|
+
var import_react9 = require("react");
|
|
4921
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
3057
4922
|
var MIN_WIDTH = 300;
|
|
3058
4923
|
var HEIGHT = 320;
|
|
3059
4924
|
var MARGIN2 = 12;
|
|
3060
4925
|
function FontMenu(props) {
|
|
3061
4926
|
const { anchor, families, value, labels, onSelect, onUseText, onClose } = props;
|
|
3062
|
-
const [query, setQuery] = (0,
|
|
3063
|
-
const [active, setActive] = (0,
|
|
3064
|
-
const [rect, setRect] = (0,
|
|
3065
|
-
const listRef = (0,
|
|
3066
|
-
const shown = (0,
|
|
4927
|
+
const [query, setQuery] = (0, import_react9.useState)("");
|
|
4928
|
+
const [active, setActive] = (0, import_react9.useState)(0);
|
|
4929
|
+
const [rect, setRect] = (0, import_react9.useState)(() => anchor.getBoundingClientRect());
|
|
4930
|
+
const listRef = (0, import_react9.useRef)(null);
|
|
4931
|
+
const shown = (0, import_react9.useMemo)(() => filterFamilies(families, query), [families, query]);
|
|
3067
4932
|
const typed = query.trim();
|
|
3068
4933
|
const typedOffered = typed !== "" && !shown.includes(typed);
|
|
3069
4934
|
const rows = typedOffered ? shown.length + 1 : shown.length;
|
|
3070
|
-
(0,
|
|
4935
|
+
(0, import_react9.useEffect)(() => {
|
|
3071
4936
|
setActive(0);
|
|
3072
4937
|
}, [query]);
|
|
3073
|
-
(0,
|
|
4938
|
+
(0, import_react9.useEffect)(() => {
|
|
3074
4939
|
const node = listRef.current?.children[active];
|
|
3075
4940
|
if (node instanceof HTMLElement) node.scrollIntoView({ block: "nearest" });
|
|
3076
4941
|
}, [active, shown.length]);
|
|
3077
|
-
(0,
|
|
4942
|
+
(0, import_react9.useEffect)(() => {
|
|
3078
4943
|
const sync = () => setRect(anchor.getBoundingClientRect());
|
|
3079
4944
|
sync();
|
|
3080
4945
|
window.addEventListener("scroll", sync, true);
|
|
@@ -3084,7 +4949,7 @@ function FontMenu(props) {
|
|
|
3084
4949
|
window.removeEventListener("resize", sync);
|
|
3085
4950
|
};
|
|
3086
4951
|
}, [anchor]);
|
|
3087
|
-
(0,
|
|
4952
|
+
(0, import_react9.useEffect)(() => {
|
|
3088
4953
|
const onKey = (event) => {
|
|
3089
4954
|
if (event.key === "Escape") onClose();
|
|
3090
4955
|
};
|
|
@@ -3147,10 +5012,10 @@ function FontMenu(props) {
|
|
|
3147
5012
|
const below = rect.bottom + 6;
|
|
3148
5013
|
const top = below + HEIGHT > window.innerHeight - MARGIN2 ? Math.max(MARGIN2, rect.top - 6 - HEIGHT) : below;
|
|
3149
5014
|
const left = Math.min(Math.max(MARGIN2, rect.left), window.innerWidth - MARGIN2 - width);
|
|
3150
|
-
return /* @__PURE__ */ (0,
|
|
3151
|
-
families.length === 0 ? /* @__PURE__ */ (0,
|
|
3152
|
-
/* @__PURE__ */ (0,
|
|
3153
|
-
/* @__PURE__ */ (0,
|
|
5015
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ccd-settings-menu", role: "dialog", "aria-label": labels.search, style: { top, left, width }, children: [
|
|
5016
|
+
families.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "ccd-settings-menu-empty", children: labels.unavailable }) : null,
|
|
5017
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
|
|
5018
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
3154
5019
|
"input",
|
|
3155
5020
|
{
|
|
3156
5021
|
className: "ccd-settings-menu-search",
|
|
@@ -3165,9 +5030,9 @@ function FontMenu(props) {
|
|
|
3165
5030
|
onKeyDown
|
|
3166
5031
|
}
|
|
3167
5032
|
),
|
|
3168
|
-
/* @__PURE__ */ (0,
|
|
3169
|
-
shown.length === 0 && !typedOffered ? /* @__PURE__ */ (0,
|
|
3170
|
-
typedOffered ? /* @__PURE__ */ (0,
|
|
5033
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "ccd-settings-menu-count", children: labels.count(shown.length) }),
|
|
5034
|
+
shown.length === 0 && !typedOffered ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "ccd-settings-menu-empty", children: labels.empty }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("ul", { className: "ccd-settings-menu-list", ref: listRef, role: "listbox", "aria-label": labels.search, children: [
|
|
5035
|
+
typedOffered ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
3171
5036
|
"button",
|
|
3172
5037
|
{
|
|
3173
5038
|
type: "button",
|
|
@@ -3177,10 +5042,10 @@ function FontMenu(props) {
|
|
|
3177
5042
|
"data-active": active === 0,
|
|
3178
5043
|
onMouseEnter: () => setActive(0),
|
|
3179
5044
|
onClick: useTyped,
|
|
3180
|
-
children: /* @__PURE__ */ (0,
|
|
5045
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ccd-settings-menu-name", children: labels.useTyped(typed) })
|
|
3181
5046
|
}
|
|
3182
5047
|
) }) : null,
|
|
3183
|
-
shown.map((family, index) => /* @__PURE__ */ (0,
|
|
5048
|
+
shown.map((family, index) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
3184
5049
|
"button",
|
|
3185
5050
|
{
|
|
3186
5051
|
type: "button",
|
|
@@ -3192,8 +5057,8 @@ function FontMenu(props) {
|
|
|
3192
5057
|
onMouseEnter: () => setActive(index + (typedOffered ? 1 : 0)),
|
|
3193
5058
|
onClick: () => choose(family),
|
|
3194
5059
|
children: [
|
|
3195
|
-
/* @__PURE__ */ (0,
|
|
3196
|
-
family === value ? /* @__PURE__ */ (0,
|
|
5060
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ccd-settings-menu-name", style: { fontFamily: `"${family}"` }, children: family }),
|
|
5061
|
+
family === value ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ccd-settings-menu-check", "aria-hidden": "true", children: "\u2713" }) : null
|
|
3197
5062
|
]
|
|
3198
5063
|
}
|
|
3199
5064
|
) }, family))
|
|
@@ -3203,20 +5068,20 @@ function FontMenu(props) {
|
|
|
3203
5068
|
}
|
|
3204
5069
|
|
|
3205
5070
|
// src/client/features/settings/fields.tsx
|
|
3206
|
-
var
|
|
3207
|
-
function
|
|
3208
|
-
return /* @__PURE__ */ (0,
|
|
5071
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
5072
|
+
function Chevron3() {
|
|
5073
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("svg", { width: "12", height: "12", viewBox: "0 0 12 12", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M3 4.5 6 7.5 9 4.5", fill: "none", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" }) });
|
|
3209
5074
|
}
|
|
3210
5075
|
function ThemeGlyph({ id }) {
|
|
3211
5076
|
const stroke = { fill: "none", stroke: "currentColor", strokeWidth: 1.1 };
|
|
3212
|
-
return /* @__PURE__ */ (0,
|
|
3213
|
-
id === "system" ? /* @__PURE__ */ (0,
|
|
3214
|
-
/* @__PURE__ */ (0,
|
|
3215
|
-
/* @__PURE__ */ (0,
|
|
5077
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: [
|
|
5078
|
+
id === "system" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
|
|
5079
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("rect", { x: "2.3", y: "3.1", width: "11.4", height: "7.9", rx: "1.5", ...stroke }),
|
|
5080
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M8 11v1.4M6.3 12.9h3.4", ...stroke, strokeLinecap: "round" })
|
|
3216
5081
|
] }) : null,
|
|
3217
|
-
id === "light" ? /* @__PURE__ */ (0,
|
|
3218
|
-
/* @__PURE__ */ (0,
|
|
3219
|
-
/* @__PURE__ */ (0,
|
|
5082
|
+
id === "light" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
|
|
5083
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("circle", { cx: "8", cy: "8", r: "3.2", ...stroke }),
|
|
5084
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
3220
5085
|
"path",
|
|
3221
5086
|
{
|
|
3222
5087
|
d: "M8 1.2v1.6M8 13.2v1.6M1.2 8h1.6M13.2 8h1.6M3.2 3.2l1.1 1.1M11.7 11.7l1.1 1.1M12.8 3.2l-1.1 1.1M4.3 11.7l-1.1 1.1",
|
|
@@ -3225,18 +5090,18 @@ function ThemeGlyph({ id }) {
|
|
|
3225
5090
|
}
|
|
3226
5091
|
)
|
|
3227
5092
|
] }) : null,
|
|
3228
|
-
id === "dark" ? /* @__PURE__ */ (0,
|
|
5093
|
+
id === "dark" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M13.9 8.5A6 6 0 1 1 7.5 2a4.7 4.7 0 0 0 6.4 6.5Z", ...stroke, strokeLinejoin: "round" }) : null
|
|
3229
5094
|
] });
|
|
3230
5095
|
}
|
|
3231
5096
|
function SwitchRow({ id, label, hint, checked, onChange }) {
|
|
3232
5097
|
const labelId = `${id}-label`;
|
|
3233
5098
|
const hintId = `${id}-hint`;
|
|
3234
|
-
return /* @__PURE__ */ (0,
|
|
3235
|
-
/* @__PURE__ */ (0,
|
|
3236
|
-
/* @__PURE__ */ (0,
|
|
3237
|
-
hint === void 0 ? null : /* @__PURE__ */ (0,
|
|
5099
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ccd-settings-row", "data-ccd-row": "switch", children: [
|
|
5100
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "ccd-settings-label", children: [
|
|
5101
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ccd-settings-name", id: labelId, children: label }),
|
|
5102
|
+
hint === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ccd-settings-hint", id: hintId, children: hint })
|
|
3238
5103
|
] }),
|
|
3239
|
-
/* @__PURE__ */ (0,
|
|
5104
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ccd-settings-control", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
3240
5105
|
"button",
|
|
3241
5106
|
{
|
|
3242
5107
|
type: "button",
|
|
@@ -3247,7 +5112,7 @@ function SwitchRow({ id, label, hint, checked, onChange }) {
|
|
|
3247
5112
|
"aria-labelledby": labelId,
|
|
3248
5113
|
...hint === void 0 ? {} : { "aria-describedby": hintId },
|
|
3249
5114
|
onClick: () => onChange(!checked),
|
|
3250
|
-
children: /* @__PURE__ */ (0,
|
|
5115
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ccd-settings-thumb", "aria-hidden": "true" })
|
|
3251
5116
|
}
|
|
3252
5117
|
) })
|
|
3253
5118
|
] });
|
|
@@ -3256,7 +5121,7 @@ function ThemeRow(props) {
|
|
|
3256
5121
|
const { id, label, hint, value, options, onChange } = props;
|
|
3257
5122
|
const labelId = `${id}-label`;
|
|
3258
5123
|
const hintId = `${id}-hint`;
|
|
3259
|
-
const group = (0,
|
|
5124
|
+
const group = (0, import_react10.useRef)(null);
|
|
3260
5125
|
const move = (step) => {
|
|
3261
5126
|
const current = options.findIndex((option) => option.id === value);
|
|
3262
5127
|
const next = options[(current + step + options.length) % options.length];
|
|
@@ -3264,12 +5129,12 @@ function ThemeRow(props) {
|
|
|
3264
5129
|
onChange(next.id);
|
|
3265
5130
|
group.current?.querySelectorAll("button")[options.indexOf(next)]?.focus();
|
|
3266
5131
|
};
|
|
3267
|
-
return /* @__PURE__ */ (0,
|
|
3268
|
-
/* @__PURE__ */ (0,
|
|
3269
|
-
/* @__PURE__ */ (0,
|
|
3270
|
-
hint === void 0 ? null : /* @__PURE__ */ (0,
|
|
5132
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ccd-settings-row", "data-ccd-row": "theme", children: [
|
|
5133
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "ccd-settings-label", children: [
|
|
5134
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ccd-settings-name", id: labelId, children: label }),
|
|
5135
|
+
hint === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ccd-settings-hint", id: hintId, children: hint })
|
|
3271
5136
|
] }),
|
|
3272
|
-
/* @__PURE__ */ (0,
|
|
5137
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ccd-settings-control", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
3273
5138
|
"span",
|
|
3274
5139
|
{
|
|
3275
5140
|
ref: group,
|
|
@@ -3283,7 +5148,7 @@ function ThemeRow(props) {
|
|
|
3283
5148
|
event.preventDefault();
|
|
3284
5149
|
move(step);
|
|
3285
5150
|
},
|
|
3286
|
-
children: options.map((option) => /* @__PURE__ */ (0,
|
|
5151
|
+
children: options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
3287
5152
|
"button",
|
|
3288
5153
|
{
|
|
3289
5154
|
type: "button",
|
|
@@ -3295,7 +5160,7 @@ function ThemeRow(props) {
|
|
|
3295
5160
|
title: option.label,
|
|
3296
5161
|
tabIndex: option.id === value ? 0 : -1,
|
|
3297
5162
|
onClick: () => onChange(option.id),
|
|
3298
|
-
children: /* @__PURE__ */ (0,
|
|
5163
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ThemeGlyph, { id: option.id })
|
|
3299
5164
|
},
|
|
3300
5165
|
option.id
|
|
3301
5166
|
))
|
|
@@ -3306,13 +5171,13 @@ function ThemeRow(props) {
|
|
|
3306
5171
|
function ColourRow(props) {
|
|
3307
5172
|
const { id, label, hint, value, placeholder, resetLabel, error, onChange, onCommit, onReset } = props;
|
|
3308
5173
|
const labelId = `${id}-label`;
|
|
3309
|
-
return /* @__PURE__ */ (0,
|
|
3310
|
-
/* @__PURE__ */ (0,
|
|
3311
|
-
/* @__PURE__ */ (0,
|
|
3312
|
-
hint === void 0 ? null : /* @__PURE__ */ (0,
|
|
5174
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ccd-settings-row", "data-ccd-row": "colour", children: [
|
|
5175
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "ccd-settings-label", children: [
|
|
5176
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ccd-settings-name", id: labelId, children: label }),
|
|
5177
|
+
hint === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ccd-settings-hint", children: hint })
|
|
3313
5178
|
] }),
|
|
3314
|
-
/* @__PURE__ */ (0,
|
|
3315
|
-
/* @__PURE__ */ (0,
|
|
5179
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "ccd-settings-control", children: [
|
|
5180
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
3316
5181
|
"input",
|
|
3317
5182
|
{
|
|
3318
5183
|
id,
|
|
@@ -3334,9 +5199,9 @@ function ColourRow(props) {
|
|
|
3334
5199
|
}
|
|
3335
5200
|
}
|
|
3336
5201
|
),
|
|
3337
|
-
/* @__PURE__ */ (0,
|
|
5202
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "ccd-settings-button-quiet", onClick: onReset, children: resetLabel })
|
|
3338
5203
|
] }),
|
|
3339
|
-
error === void 0 ? null : /* @__PURE__ */ (0,
|
|
5204
|
+
error === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "ccd-settings-error", role: "alert", children: error })
|
|
3340
5205
|
] });
|
|
3341
5206
|
}
|
|
3342
5207
|
function FontRow(props) {
|
|
@@ -3358,13 +5223,13 @@ function FontRow(props) {
|
|
|
3358
5223
|
onReset
|
|
3359
5224
|
} = props;
|
|
3360
5225
|
const labelId = `${id}-label`;
|
|
3361
|
-
return /* @__PURE__ */ (0,
|
|
3362
|
-
/* @__PURE__ */ (0,
|
|
3363
|
-
/* @__PURE__ */ (0,
|
|
3364
|
-
hint === void 0 ? null : /* @__PURE__ */ (0,
|
|
5226
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ccd-settings-row", "data-ccd-row": "font", children: [
|
|
5227
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "ccd-settings-label", children: [
|
|
5228
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ccd-settings-name", id: labelId, children: label }),
|
|
5229
|
+
hint === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ccd-settings-hint", children: hint })
|
|
3365
5230
|
] }),
|
|
3366
|
-
/* @__PURE__ */ (0,
|
|
3367
|
-
/* @__PURE__ */ (0,
|
|
5231
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "ccd-settings-control", children: [
|
|
5232
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
3368
5233
|
"button",
|
|
3369
5234
|
{
|
|
3370
5235
|
type: "button",
|
|
@@ -3376,15 +5241,15 @@ function FontRow(props) {
|
|
|
3376
5241
|
"aria-expanded": anchor !== void 0,
|
|
3377
5242
|
onClick: onToggle,
|
|
3378
5243
|
children: [
|
|
3379
|
-
/* @__PURE__ */ (0,
|
|
3380
|
-
/* @__PURE__ */ (0,
|
|
5244
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ccd-settings-select-value", children: value === "" ? placeholder : value }),
|
|
5245
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Chevron3, {})
|
|
3381
5246
|
]
|
|
3382
5247
|
}
|
|
3383
5248
|
),
|
|
3384
|
-
/* @__PURE__ */ (0,
|
|
5249
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "ccd-settings-button-quiet", onClick: onReset, children: resetLabel })
|
|
3385
5250
|
] }),
|
|
3386
|
-
error === void 0 ? null : /* @__PURE__ */ (0,
|
|
3387
|
-
anchor === void 0 ? null : /* @__PURE__ */ (0,
|
|
5251
|
+
error === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "ccd-settings-error", role: "alert", children: error }),
|
|
5252
|
+
anchor === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
3388
5253
|
FontMenu,
|
|
3389
5254
|
{
|
|
3390
5255
|
anchor,
|
|
@@ -3407,6 +5272,9 @@ var zh = {
|
|
|
3407
5272
|
"section.features": "\u6A21\u5757",
|
|
3408
5273
|
"section.appearance": "\u5916\u89C2",
|
|
3409
5274
|
"section.fonts": "\u5B57\u4F53",
|
|
5275
|
+
"section.view": "\u89C6\u56FE",
|
|
5276
|
+
"field.showEmptyGroups": "\u663E\u793A\u7A7A\u5206\u7EC4",
|
|
5277
|
+
"hint.showEmptyGroups": "\u5173\u6389\u65F6\u6CA1\u6709\u4F1A\u8BDD\u7684\u5DE5\u4F5C\u533A\u4E0D\u518D\u663E\u793A\uFF1B\u89C6\u56FE\u9009\u9879\u83DC\u5355\u91CC\u7684\u540C\u4E00\u9879",
|
|
3410
5278
|
"field.enabled": "\u542F\u7528 CCD \u98CE\u683C\u754C\u9762",
|
|
3411
5279
|
"field.theme": "\u4E3B\u9898",
|
|
3412
5280
|
"theme.system": "\u8DDF\u968F\u7CFB\u7EDF",
|
|
@@ -3441,7 +5309,11 @@ var zh = {
|
|
|
3441
5309
|
"feature.conversation": "\u804A\u5929\u9875",
|
|
3442
5310
|
"feature.tool-calls": "\u5DE5\u5177\u8C03\u7528\u5C55\u793A",
|
|
3443
5311
|
"feature.statistics": "\u7EDF\u8BA1\u5361\u7247",
|
|
3444
|
-
"feature.composer-pet": "\u8F93\u5165\u6846\u5C0F\u9CB8\u9C7C"
|
|
5312
|
+
"feature.composer-pet": "\u8F93\u5165\u6846\u5C0F\u9CB8\u9C7C",
|
|
5313
|
+
"feature.composer-stats": "\u72B6\u6001\u680F\u7EDF\u8BA1\u8BFB\u6570",
|
|
5314
|
+
"hint.composer-stats": "\u8F93\u51FA\u901F\u5EA6\u4E0E\u7F13\u5B58\u547D\u4E2D\u7387\uFF0C\u5173\u6389\u65F6\u8FD9\u4E24\u9879\u4E0D\u663E\u793A\uFF08\u9ED8\u8BA4\uFF09",
|
|
5315
|
+
"feature.context-panel": "\u4E0A\u4E0B\u6587\u5206\u89E3\u9762\u677F",
|
|
5316
|
+
"hint.context-panel": "\u70B9\u73AF\u6253\u5F00\u7684\u5206\u6BB5\u9762\u677F\uFF1B\u5173\u6389\u65F6\u73AF\u70B9\u5F00\u7684\u662F DSH \u539F\u751F\u9762\u677F"
|
|
3445
5317
|
};
|
|
3446
5318
|
var en = {
|
|
3447
5319
|
"summary": "CCD interface \xB7 appearance \xB7 typefaces",
|
|
@@ -3449,6 +5321,9 @@ var en = {
|
|
|
3449
5321
|
"section.features": "Modules",
|
|
3450
5322
|
"section.appearance": "Appearance",
|
|
3451
5323
|
"section.fonts": "Typefaces",
|
|
5324
|
+
"section.view": "View",
|
|
5325
|
+
"field.showEmptyGroups": "Show empty groups",
|
|
5326
|
+
"hint.showEmptyGroups": "Off keeps a Workspace with no sessions out of the sidebar; the same switch the view-options menu carries",
|
|
3452
5327
|
"field.enabled": "Enable the CCD interface",
|
|
3453
5328
|
"field.theme": "Theme",
|
|
3454
5329
|
"theme.system": "System",
|
|
@@ -3483,11 +5358,15 @@ var en = {
|
|
|
3483
5358
|
"feature.conversation": "Conversation",
|
|
3484
5359
|
"feature.tool-calls": "Tool call presentation",
|
|
3485
5360
|
"feature.statistics": "Statistics card",
|
|
3486
|
-
"feature.composer-pet": "Composer whale"
|
|
5361
|
+
"feature.composer-pet": "Composer whale",
|
|
5362
|
+
"feature.composer-stats": "Composer statistics readouts",
|
|
5363
|
+
"hint.composer-stats": "Output speed and cache-hit rate; the two stay out of the row while this is off (the default)",
|
|
5364
|
+
"feature.context-panel": "Context breakdown panel",
|
|
5365
|
+
"hint.context-panel": "The segmented panel the ring opens; while this is off the ring opens DSH\u2019s own panel"
|
|
3487
5366
|
};
|
|
3488
5367
|
|
|
3489
5368
|
// src/client/features/settings/ConfigPage.tsx
|
|
3490
|
-
var
|
|
5369
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
3491
5370
|
var COLOUR_LABELS = {
|
|
3492
5371
|
canvas: "field.canvas",
|
|
3493
5372
|
sidebar: "field.sidebar"
|
|
@@ -3510,7 +5389,9 @@ var OFFERED_FEATURES = [
|
|
|
3510
5389
|
"new-session",
|
|
3511
5390
|
"conversation",
|
|
3512
5391
|
"composer-pet",
|
|
3513
|
-
"statistics"
|
|
5392
|
+
"statistics",
|
|
5393
|
+
"composer-stats",
|
|
5394
|
+
"context-panel"
|
|
3514
5395
|
];
|
|
3515
5396
|
var FEATURE_KEYS = {
|
|
3516
5397
|
shell: "feature.shell",
|
|
@@ -3519,38 +5400,44 @@ var FEATURE_KEYS = {
|
|
|
3519
5400
|
conversation: "feature.conversation",
|
|
3520
5401
|
"tool-calls": "feature.tool-calls",
|
|
3521
5402
|
statistics: "feature.statistics",
|
|
3522
|
-
"composer-pet": "feature.composer-pet"
|
|
5403
|
+
"composer-pet": "feature.composer-pet",
|
|
5404
|
+
"composer-stats": "feature.composer-stats",
|
|
5405
|
+
"context-panel": "feature.context-panel"
|
|
5406
|
+
};
|
|
5407
|
+
var FEATURE_HINTS = {
|
|
5408
|
+
"composer-stats": "hint.composer-stats",
|
|
5409
|
+
"context-panel": "hint.context-panel"
|
|
3523
5410
|
};
|
|
3524
5411
|
function ConfigPage(props) {
|
|
3525
5412
|
const { view, t, form, themePreference: theme } = props;
|
|
3526
|
-
const translate = (0,
|
|
5413
|
+
const translate = (0, import_react11.useMemo)(
|
|
3527
5414
|
() => t ?? ((key) => zh[key] ?? key),
|
|
3528
5415
|
[t]
|
|
3529
5416
|
);
|
|
3530
|
-
const subscribe = (0,
|
|
3531
|
-
const read = (0,
|
|
3532
|
-
const snapshot = (0,
|
|
3533
|
-
const served = (0,
|
|
5417
|
+
const subscribe = (0, import_react11.useCallback)((listener) => form.subscribe(listener), [form]);
|
|
5418
|
+
const read = (0, import_react11.useCallback)(() => form.getSnapshot(), [form]);
|
|
5419
|
+
const snapshot = (0, import_react11.useSyncExternalStore)(subscribe, read);
|
|
5420
|
+
const served = (0, import_react11.useMemo)(
|
|
3534
5421
|
() => snapshot.status === "ready" ? adoptConfig(snapshot.value) : null,
|
|
3535
5422
|
[snapshot.status, snapshot.value]
|
|
3536
5423
|
);
|
|
3537
5424
|
const themePort = theme ?? null;
|
|
3538
|
-
const subscribeTheme = (0,
|
|
5425
|
+
const subscribeTheme = (0, import_react11.useCallback)(
|
|
3539
5426
|
(listener) => themePort === null ? () => {
|
|
3540
5427
|
} : themePort.subscribe(listener),
|
|
3541
5428
|
[themePort]
|
|
3542
5429
|
);
|
|
3543
|
-
const readTheme = (0,
|
|
5430
|
+
const readTheme = (0, import_react11.useCallback)(
|
|
3544
5431
|
() => themePort === null ? null : themePort.preference(),
|
|
3545
5432
|
[themePort]
|
|
3546
5433
|
);
|
|
3547
|
-
const preference = (0,
|
|
3548
|
-
const [status, setStatus] = (0,
|
|
3549
|
-
const [errors, setErrors] = (0,
|
|
3550
|
-
const [editing, setEditing] = (0,
|
|
3551
|
-
const [menu, setMenu] = (0,
|
|
3552
|
-
const [fontFamilies, setFontFamilies] = (0,
|
|
3553
|
-
const write = (0,
|
|
5434
|
+
const preference = (0, import_react11.useSyncExternalStore)(subscribeTheme, readTheme);
|
|
5435
|
+
const [status, setStatus] = (0, import_react11.useState)("idle");
|
|
5436
|
+
const [errors, setErrors] = (0, import_react11.useState)({});
|
|
5437
|
+
const [editing, setEditing] = (0, import_react11.useState)(null);
|
|
5438
|
+
const [menu, setMenu] = (0, import_react11.useState)(null);
|
|
5439
|
+
const [fontFamilies, setFontFamilies] = (0, import_react11.useState)([]);
|
|
5440
|
+
const write = (0, import_react11.useCallback)((ops) => {
|
|
3554
5441
|
if (ops.length === 0) return Promise.resolve(true);
|
|
3555
5442
|
setStatus("saving");
|
|
3556
5443
|
return form.mutate(ops, form.getSnapshot().revision).then(
|
|
@@ -3564,7 +5451,7 @@ function ConfigPage(props) {
|
|
|
3564
5451
|
}
|
|
3565
5452
|
);
|
|
3566
5453
|
}, [form]);
|
|
3567
|
-
const clearError = (0,
|
|
5454
|
+
const clearError = (0, import_react11.useCallback)((field) => {
|
|
3568
5455
|
setErrors((current) => {
|
|
3569
5456
|
if (!(field in current)) return current;
|
|
3570
5457
|
const next = { ...current };
|
|
@@ -3572,14 +5459,14 @@ function ConfigPage(props) {
|
|
|
3572
5459
|
return next;
|
|
3573
5460
|
});
|
|
3574
5461
|
}, []);
|
|
3575
|
-
const textOf = (0,
|
|
3576
|
-
const edit = (0,
|
|
5462
|
+
const textOf = (0, import_react11.useCallback)((key, fallback) => editing?.key === key ? editing.text : fallback, [editing]);
|
|
5463
|
+
const edit = (0, import_react11.useCallback)((key, text) => {
|
|
3577
5464
|
setEditing({ key, text });
|
|
3578
5465
|
}, []);
|
|
3579
|
-
const askFontFamilies = (0,
|
|
5466
|
+
const askFontFamilies = (0, import_react11.useCallback)(() => {
|
|
3580
5467
|
void loadSystemFontFamilies().then((families) => setFontFamilies(families));
|
|
3581
5468
|
}, []);
|
|
3582
|
-
const settleColour = (0,
|
|
5469
|
+
const settleColour = (0, import_react11.useCallback)((field, text) => {
|
|
3583
5470
|
if (served === null) return;
|
|
3584
5471
|
const raw = text ?? textOf(field, served.appearance[field]);
|
|
3585
5472
|
const result = commitColour(["appearance", field], raw, served.appearance[field]);
|
|
@@ -3591,7 +5478,7 @@ function ConfigPage(props) {
|
|
|
3591
5478
|
setEditing(null);
|
|
3592
5479
|
void write(result.op === void 0 ? [] : [result.op]);
|
|
3593
5480
|
}, [served, textOf, clearError, write]);
|
|
3594
|
-
const settleFont = (0,
|
|
5481
|
+
const settleFont = (0, import_react11.useCallback)((field, text) => {
|
|
3595
5482
|
if (served === null) return;
|
|
3596
5483
|
const raw = text ?? textOf(field, served.fonts[field]);
|
|
3597
5484
|
const result = commitFont(["fonts", field], raw, served.fonts[field]);
|
|
@@ -3604,18 +5491,18 @@ function ConfigPage(props) {
|
|
|
3604
5491
|
void write(result.op === void 0 ? [] : [result.op]);
|
|
3605
5492
|
}, [served, textOf, clearError, write]);
|
|
3606
5493
|
if (view === "summary") {
|
|
3607
|
-
return /* @__PURE__ */ (0,
|
|
5494
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "ccd-settings-summary", children: translate("summary") });
|
|
3608
5495
|
}
|
|
3609
5496
|
if (snapshot.status !== "ready" || served === null) {
|
|
3610
5497
|
const key = snapshot.status === "unavailable" ? "state.unavailable" : "state.loading";
|
|
3611
|
-
return /* @__PURE__ */ (0,
|
|
5498
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "ccd-settings-notice", children: translate(key) });
|
|
3612
5499
|
}
|
|
3613
5500
|
const statusKey = status === "saving" ? "state.saving" : status === "saved" ? "state.saved" : status === "failed" ? "state.failed" : null;
|
|
3614
5501
|
const buildState = hostBuildStatus(document).state;
|
|
3615
|
-
return /* @__PURE__ */ (0,
|
|
3616
|
-
/* @__PURE__ */ (0,
|
|
3617
|
-
/* @__PURE__ */ (0,
|
|
3618
|
-
/* @__PURE__ */ (0,
|
|
5502
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "ccd-settings-page", children: [
|
|
5503
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("section", { className: "ccd-settings-section", children: [
|
|
5504
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h4", { className: "ccd-settings-title", children: translate("section.general") }),
|
|
5505
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3619
5506
|
SwitchRow,
|
|
3620
5507
|
{
|
|
3621
5508
|
id: "ccd-settings-enabled",
|
|
@@ -3625,23 +5512,27 @@ function ConfigPage(props) {
|
|
|
3625
5512
|
}
|
|
3626
5513
|
)
|
|
3627
5514
|
] }),
|
|
3628
|
-
buildState === "unknown" ? /* @__PURE__ */ (0,
|
|
3629
|
-
/* @__PURE__ */ (0,
|
|
3630
|
-
/* @__PURE__ */ (0,
|
|
3631
|
-
OFFERED_FEATURES.map((id) =>
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
5515
|
+
buildState === "unknown" ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "ccd-settings-note ccd-settings-warning", role: "status", children: translate("note.unknownBuild") }) : null,
|
|
5516
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("section", { className: "ccd-settings-section", children: [
|
|
5517
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h4", { className: "ccd-settings-title", children: translate("section.features") }),
|
|
5518
|
+
OFFERED_FEATURES.map((id) => {
|
|
5519
|
+
const hint = FEATURE_HINTS[id];
|
|
5520
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
5521
|
+
SwitchRow,
|
|
5522
|
+
{
|
|
5523
|
+
id: `ccd-settings-feature-${id}`,
|
|
5524
|
+
label: translate(FEATURE_KEYS[id]),
|
|
5525
|
+
...hint === void 0 ? {} : { hint: translate(hint) },
|
|
5526
|
+
checked: served.features[id],
|
|
5527
|
+
onChange: (value) => void write([setOperation(["features", id], value)])
|
|
5528
|
+
},
|
|
5529
|
+
id
|
|
5530
|
+
);
|
|
5531
|
+
})
|
|
3641
5532
|
] }),
|
|
3642
|
-
/* @__PURE__ */ (0,
|
|
3643
|
-
/* @__PURE__ */ (0,
|
|
3644
|
-
preference === null || themePort === null ? null : /* @__PURE__ */ (0,
|
|
5533
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("section", { className: "ccd-settings-section", children: [
|
|
5534
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h4", { className: "ccd-settings-title", children: translate("section.appearance") }),
|
|
5535
|
+
preference === null || themePort === null ? null : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3645
5536
|
ThemeRow,
|
|
3646
5537
|
{
|
|
3647
5538
|
id: "ccd-settings-theme",
|
|
@@ -3653,7 +5544,7 @@ function ConfigPage(props) {
|
|
|
3653
5544
|
),
|
|
3654
5545
|
APPEARANCE_FIELDS.map((field) => {
|
|
3655
5546
|
const failure = errors[field];
|
|
3656
|
-
return /* @__PURE__ */ (0,
|
|
5547
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3657
5548
|
ColourRow,
|
|
3658
5549
|
{
|
|
3659
5550
|
id: `ccd-settings-${field}`,
|
|
@@ -3676,13 +5567,26 @@ function ConfigPage(props) {
|
|
|
3676
5567
|
field
|
|
3677
5568
|
);
|
|
3678
5569
|
}),
|
|
3679
|
-
/* @__PURE__ */ (0,
|
|
5570
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "ccd-settings-note", children: translate("note.coloursApplyToLight") })
|
|
3680
5571
|
] }),
|
|
3681
|
-
/* @__PURE__ */ (0,
|
|
3682
|
-
/* @__PURE__ */ (0,
|
|
5572
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("section", { className: "ccd-settings-section", children: [
|
|
5573
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h4", { className: "ccd-settings-title", children: translate("section.view") }),
|
|
5574
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
5575
|
+
SwitchRow,
|
|
5576
|
+
{
|
|
5577
|
+
id: "ccd-settings-show-empty-groups",
|
|
5578
|
+
label: translate("field.showEmptyGroups"),
|
|
5579
|
+
hint: translate("hint.showEmptyGroups"),
|
|
5580
|
+
checked: served.view.showEmptyGroups,
|
|
5581
|
+
onChange: (value) => void write([setOperation(["view", "showEmptyGroups"], value)])
|
|
5582
|
+
}
|
|
5583
|
+
)
|
|
5584
|
+
] }),
|
|
5585
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("section", { className: "ccd-settings-section", children: [
|
|
5586
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h4", { className: "ccd-settings-title", children: translate("section.fonts") }),
|
|
3683
5587
|
OFFERED_FONTS.map((field) => {
|
|
3684
5588
|
const failure = errors[field];
|
|
3685
|
-
return /* @__PURE__ */ (0,
|
|
5589
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
3686
5590
|
FontRow,
|
|
3687
5591
|
{
|
|
3688
5592
|
id: `ccd-settings-font-${field}`,
|
|
@@ -3696,7 +5600,7 @@ function ConfigPage(props) {
|
|
|
3696
5600
|
menuLabels: {
|
|
3697
5601
|
search: translate("picker.search"),
|
|
3698
5602
|
empty: translate("picker.empty"),
|
|
3699
|
-
count: (
|
|
5603
|
+
count: (count2) => translate("picker.count", { count: count2 }),
|
|
3700
5604
|
unavailable: translate("hint.fontListUnavailable"),
|
|
3701
5605
|
useTyped: (name) => translate("picker.useTyped", { name })
|
|
3702
5606
|
},
|
|
@@ -3726,7 +5630,7 @@ function ConfigPage(props) {
|
|
|
3726
5630
|
);
|
|
3727
5631
|
})
|
|
3728
5632
|
] }),
|
|
3729
|
-
/* @__PURE__ */ (0,
|
|
5633
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "ccd-settings-status", role: "status", children: statusKey === null ? "" : translate(statusKey) })
|
|
3730
5634
|
] });
|
|
3731
5635
|
}
|
|
3732
5636
|
|
|
@@ -3761,7 +5665,7 @@ function mountSettingsPage(ctx, dom, form, themePreference, scope) {
|
|
|
3761
5665
|
const registration = registerSettingsPage(
|
|
3762
5666
|
ctx,
|
|
3763
5667
|
`${PLUGIN_ID}#${ENTRY_ID}`,
|
|
3764
|
-
(props) => (0,
|
|
5668
|
+
(props) => (0, import_react12.createElement)(ConfigPage, { ...props, form, themePreference })
|
|
3765
5669
|
);
|
|
3766
5670
|
scope.add(() => {
|
|
3767
5671
|
void registration.dispose();
|
|
@@ -3878,11 +5782,156 @@ function mountAccountMenu(document2, report) {
|
|
|
3878
5782
|
};
|
|
3879
5783
|
}
|
|
3880
5784
|
|
|
5785
|
+
// src/client/compat/sidebar-navigation.ts
|
|
5786
|
+
var SIDEBAR_NAV_ATTRIBUTE = "data-ccd-nav";
|
|
5787
|
+
function panelKind(name) {
|
|
5788
|
+
if (name === "\u63D2\u4EF6" || name === "Plugins") return "plugins";
|
|
5789
|
+
if (name === "\u81EA\u52A8\u5316\u4EFB\u52A1" || name === "Automation tasks" || name === "\u4F8B\u7A0B" || name === "Routines") return "routines";
|
|
5790
|
+
return null;
|
|
5791
|
+
}
|
|
5792
|
+
function mountSidebarNavigation(document2, icons, report) {
|
|
5793
|
+
const added = /* @__PURE__ */ new Set();
|
|
5794
|
+
const texts = /* @__PURE__ */ new Map();
|
|
5795
|
+
const attributes = /* @__PURE__ */ new Map();
|
|
5796
|
+
let root = null;
|
|
5797
|
+
let observer;
|
|
5798
|
+
let language;
|
|
5799
|
+
const copy = () => (document2.documentElement.lang || globalThis.navigator?.language || "").toLowerCase().startsWith("zh") ? { newSession: "\u65B0\u5EFA\u4F1A\u8BDD", routines: "\u4F8B\u7A0B" } : { newSession: "New session", routines: "Routines" };
|
|
5800
|
+
const attribute = (element, name, value) => {
|
|
5801
|
+
const current = element.getAttribute(name);
|
|
5802
|
+
if (current === value) return;
|
|
5803
|
+
let entries = attributes.get(element);
|
|
5804
|
+
if (!entries) {
|
|
5805
|
+
entries = /* @__PURE__ */ new Map();
|
|
5806
|
+
attributes.set(element, entries);
|
|
5807
|
+
}
|
|
5808
|
+
const previous = entries.get(name);
|
|
5809
|
+
const original = previous?.drawn === current ? previous.original : current;
|
|
5810
|
+
entries.set(name, { original, drawn: value });
|
|
5811
|
+
element.setAttribute(name, value);
|
|
5812
|
+
};
|
|
5813
|
+
const label = (element, value) => {
|
|
5814
|
+
if (!element) return;
|
|
5815
|
+
const walker = document2.createTreeWalker(element, NodeFilter.SHOW_TEXT);
|
|
5816
|
+
for (let found = walker.nextNode(); found !== null; found = walker.nextNode()) {
|
|
5817
|
+
const node = found;
|
|
5818
|
+
if (!node.data.trim() || node.data.trim() === value) continue;
|
|
5819
|
+
const previous = texts.get(node);
|
|
5820
|
+
const original = previous?.drawn === node.data ? previous.original : node.data;
|
|
5821
|
+
const drawn = node.data.replace(node.data.trim(), value);
|
|
5822
|
+
texts.set(node, { original, drawn });
|
|
5823
|
+
node.data = drawn;
|
|
5824
|
+
break;
|
|
5825
|
+
}
|
|
5826
|
+
};
|
|
5827
|
+
const decorate = (button, seat, kind) => {
|
|
5828
|
+
if (!seat) return;
|
|
5829
|
+
attribute(button, SIDEBAR_NAV_ATTRIBUTE, kind);
|
|
5830
|
+
if (!seat.querySelector(":scope > [data-ccd-nav-glyph]")) {
|
|
5831
|
+
const glyph = document2.createElement("span");
|
|
5832
|
+
glyph.className = "ccd-nav-glyph";
|
|
5833
|
+
glyph.setAttribute("data-ccd-nav-glyph", "");
|
|
5834
|
+
glyph.setAttribute("aria-hidden", "true");
|
|
5835
|
+
glyph.innerHTML = icons[kind];
|
|
5836
|
+
seat.prepend(glyph);
|
|
5837
|
+
added.add(glyph);
|
|
5838
|
+
}
|
|
5839
|
+
};
|
|
5840
|
+
const sync = () => {
|
|
5841
|
+
const host = hostSelectors(document2);
|
|
5842
|
+
root = document2.querySelector(host.sidebarRoot);
|
|
5843
|
+
const names = copy();
|
|
5844
|
+
const button = root?.querySelector(host.sidebarNewSession);
|
|
5845
|
+
if (button) {
|
|
5846
|
+
decorate(button, button.querySelector(host.sidebarNewSessionContent), "newSession");
|
|
5847
|
+
label(button.querySelector(host.sidebarNewSessionLabel), names.newSession);
|
|
5848
|
+
attribute(button, "aria-label", names.newSession);
|
|
5849
|
+
}
|
|
5850
|
+
for (const row of root?.querySelectorAll(host.sidebarPanelRow) ?? []) {
|
|
5851
|
+
const kind = panelKind(row.getAttribute("aria-label") ?? row.querySelector(host.sidebarPanelTitle)?.textContent?.trim() ?? "");
|
|
5852
|
+
if (!kind) continue;
|
|
5853
|
+
decorate(row, row.querySelector(host.sidebarPanelGlyph), kind);
|
|
5854
|
+
if (kind === "routines") {
|
|
5855
|
+
label(row.querySelector(host.sidebarPanelTitle), names.routines);
|
|
5856
|
+
attribute(row, "aria-label", names.routines);
|
|
5857
|
+
}
|
|
5858
|
+
}
|
|
5859
|
+
const tooltipOwner = root?.querySelector("[data-ccd-nav]:hover, [data-ccd-nav]:focus-visible");
|
|
5860
|
+
const tooltipKind = tooltipOwner?.getAttribute(SIDEBAR_NAV_ATTRIBUTE);
|
|
5861
|
+
for (const tooltip of tooltipOwner ? document2.querySelectorAll('[role="tooltip"]') : []) {
|
|
5862
|
+
const name = tooltip.textContent?.trim();
|
|
5863
|
+
if (tooltipKind === "routines" && (name === "\u81EA\u52A8\u5316\u4EFB\u52A1" || name === "Automation tasks")) label(tooltip, names.routines);
|
|
5864
|
+
if (tooltipKind === "newSession" && (name === "\u65B0\u4F1A\u8BDD" || name === "\u65B0\u5EFA\u4F1A\u8BDD" || name === "New session")) label(tooltip, names.newSession);
|
|
5865
|
+
}
|
|
5866
|
+
for (const node of added) if (!node.isConnected) added.delete(node);
|
|
5867
|
+
for (const node of texts.keys()) if (!node.isConnected) texts.delete(node);
|
|
5868
|
+
for (const node of attributes.keys()) if (!node.isConnected) attributes.delete(node);
|
|
5869
|
+
};
|
|
5870
|
+
const restore = () => {
|
|
5871
|
+
for (const node of added) node.remove();
|
|
5872
|
+
for (const [node, record] of texts) if (node.data === record.drawn) node.data = record.original;
|
|
5873
|
+
for (const [element, entries] of attributes) for (const [name, record] of entries) {
|
|
5874
|
+
if (element.getAttribute(name) !== record.drawn) continue;
|
|
5875
|
+
if (record.original === null) element.removeAttribute(name);
|
|
5876
|
+
else element.setAttribute(name, record.original);
|
|
5877
|
+
}
|
|
5878
|
+
added.clear();
|
|
5879
|
+
texts.clear();
|
|
5880
|
+
attributes.clear();
|
|
5881
|
+
};
|
|
5882
|
+
const safelySync = () => {
|
|
5883
|
+
try {
|
|
5884
|
+
sync();
|
|
5885
|
+
} catch (error) {
|
|
5886
|
+
report(error);
|
|
5887
|
+
}
|
|
5888
|
+
};
|
|
5889
|
+
try {
|
|
5890
|
+
observer = new MutationObserver((records) => {
|
|
5891
|
+
const host = hostSelectors(document2);
|
|
5892
|
+
const current = document2.querySelector(host.sidebarRoot);
|
|
5893
|
+
if (current !== root || records.some((record) => root?.contains(record.target) || record.target instanceof Element && (record.target.closest('[role="tooltip"]') || Array.from(record.addedNodes).some((node) => node instanceof Element && (node.matches('[role="tooltip"]') || node.querySelector('[role="tooltip"]')))))) safelySync();
|
|
5894
|
+
});
|
|
5895
|
+
observer.observe(document2.body, {
|
|
5896
|
+
childList: true,
|
|
5897
|
+
subtree: true,
|
|
5898
|
+
characterData: true,
|
|
5899
|
+
attributes: true,
|
|
5900
|
+
attributeFilter: ["aria-label"]
|
|
5901
|
+
});
|
|
5902
|
+
language = new MutationObserver(safelySync);
|
|
5903
|
+
language.observe(document2.documentElement, { attributes: true, attributeFilter: ["lang"] });
|
|
5904
|
+
sync();
|
|
5905
|
+
} catch (error) {
|
|
5906
|
+
observer?.disconnect();
|
|
5907
|
+
language?.disconnect();
|
|
5908
|
+
restore();
|
|
5909
|
+
report(error);
|
|
5910
|
+
return () => {
|
|
5911
|
+
};
|
|
5912
|
+
}
|
|
5913
|
+
return () => {
|
|
5914
|
+
observer?.disconnect();
|
|
5915
|
+
language?.disconnect();
|
|
5916
|
+
restore();
|
|
5917
|
+
};
|
|
5918
|
+
}
|
|
5919
|
+
|
|
3881
5920
|
// src/client/features/sidebar/account-menu.css
|
|
3882
5921
|
var account_menu_default = '/*\n * Signed-in account menu.\n *\n * The reference draws the card the sidebar\'s account row opens as a wide menu\n * whose first block is the account itself. The compat layer tags the open menu\n * (`compat/account-menu.ts`) and mirrors the signed-in name onto it, so these\n * rules only reshape the host\'s own rows: no row is added, renamed or reordered.\n *\n * Menu surfaces and rows come from ui-primitives `Menu.module.css`; the tag is\n * the plugin\'s own. Sources are pinned in src/client/compat/host-dom.ts.\n */\n/* The foot row is a hit target, not a panel: the reference\'s fill is a slim bar,\n so the row is as short as the avatar allows and stays pinned to the window\'s\n bottom edge. */\nhtml[data-dsh-ccd-style="true"] .ZogL4G_trigger,\nhtml[data-dsh-ccd-style="true"] .Dws9Sa_trigger {\n height: var(--ccd-account-trigger-height);\n padding: 0 6px;\n border-radius: var(--ccd-radius-row);\n}\n\nhtml[data-dsh-ccd-style="true"] .ZogL4G_trigger[data-collapsed="true"] {\n height: var(--ccd-account-trigger-height);\n}\n\nhtml[data-dsh-ccd-style="true"] [data-ccd-account-menu] {\n box-sizing: border-box;\n width: var(--ccd-account-menu-width);\n min-width: var(--ccd-account-menu-width);\n max-width: var(--ccd-account-menu-width);\n padding: var(--ccd-account-menu-padding);\n border-radius: var(--ccd-radius-panel);\n}\n\n/* The account header the reference shows above the rows.\n *\n * The name sits on the row icon\'s own left edge rather than on the rows\' text\n * edge: the card\'s first column is the icon seat, and both the row icons and\n * this line start it. A row starts its content at its own padding, a fixed\n * distance from the card edge (the `padding: 0 10px` on the row rule below,\n * which the card\'s own padding sits outside of), so the header takes that same\n * inline padding and the two left edges stay one line. The vertical padding is\n * deliberately left alone: the name\'s top and bottom whitespace and the card\'s\n * height depend on it, and the alignment is horizontal.\n *\n * The property holds one *quoted* CSS string token (`compat/account-menu.ts`\n * `accountNameToken`), and the fallback is a quoted string for the same reason:\n * `content` takes no bare word, and a value that is not one `<string>` is\n * invalid at computed-value time, which computes to `none` on `::before` and\n * drops this whole line \u2014 padding included. */\nhtml[data-dsh-ccd-style="true"] [data-ccd-account-menu]::before {\n content: var(--ccd-account-name, "");\n display: block;\n padding: 4px 10px 8px;\n color: var(--ccd-text-muted);\n font-size: var(--ccd-font-size-muted);\n font-weight: 400;\n line-height: 18px;\n /* The header is painted content, not an element the host lays text into, so\n the trigger\'s own ellipsis never reaches it: without these the name runs\n out of the card and the window edge cuts it. Keep it on the header\'s one\n line and let the long end ellipsize, as the trigger\'s own label does. */\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n}\n\nhtml[data-dsh-ccd-style="true"] [data-ccd-account-menu] [role="menuitem"] {\n min-height: var(--ccd-account-row-height);\n padding: 0 10px;\n border-radius: var(--ccd-radius-row);\n color: var(--ccd-text);\n font-size: var(--ccd-font-size-muted);\n line-height: 20px;\n}\n\nhtml[data-dsh-ccd-style="true"] [data-ccd-account-menu] [role="menuitem"]:hover,\nhtml[data-dsh-ccd-style="true"] [data-ccd-account-menu] [role="menuitem"]:focus-visible {\n background: var(--ccd-hover);\n}\n\n/* Row cells are wrapped by the primitive; the last group reads as its own\n block, which is where the reference draws its final hairline. */\nhtml[data-dsh-ccd-style="true"] [data-ccd-account-menu] div:has(> [role="menuitem"]):last-child {\n margin-top: 4px;\n padding-top: 4px;\n border-top: 0.5px solid var(--ccd-border-soft);\n}\n\n/* Sign out is the one row whose icon does not come from the product icon set:\n ui-settings-account inlines it from a 14-unit artwork (`LogoutIcon`,\n `viewBox="0 0 14 14"`, fill-only) that fills its whole canvas, while Settings\n and Feedback are 16-unit artworks that the Menu primitive\'s icon seat scales\n down to its 14px box (`ui-primitives/lib/Menu.js` `MenuItemButton`, whose\n `.itemIcon svg` rule wins over the artwork\'s own width and height). Sign out\n is therefore the only glyph drawn at full slot size: in a 2x capture it inks\n 13.5px against 12 and 10 for its neighbours, with a 1.4px wall against their\n 1.14px stroke. Scaling it by the same 14/16 the product icons already get\n returns it to their size; the scale is taken about the svg\'s own box, so the\n glyph keeps the slot\'s centre. */\nhtml[data-dsh-ccd-style="true"] [data-ccd-account-menu] [role="menuitem"] > span:first-child > span > svg {\n transform: scale(0.875);\n}\n';
|
|
3883
5922
|
|
|
3884
5923
|
// src/client/features/sidebar/sidebar.css
|
|
3885
|
-
var sidebar_default = '/*\n * Sidebar: compact navigation column.\n *\n * Host classes are pinned in src/client/compat/host-dom.ts. The CCD reference\n * puts window controls plus two icon buttons at the top, a short stack of\n * navigation rows, then compact project groups. DSH\'s column carries its own\n * brand row and a global panel list instead of CCD\'s Home/Code switch; those\n * real entries are kept and compacted rather than replaced by inert lookalikes.\n */\n/*\n * Navigation overrides below style the *expanded* column. DSH\'s collapsed sidebar is a\n * different layout \u2014 a 36px icon rail the host centres itself (`.collapsed`\n * rules: root padding `18px 10px 6px`, logo row `padding: 0; height: 36px`,\n * 36\xD736 buttons) and slides in from the right. Overriding that geometry leaves\n * two icons off the rail\'s centre axis, so every expanded-layout override is\n * scoped to `:not(._3WPZCG_collapsed)` and the rail keeps the host\'s numbers.\n * The macOS window-chrome toggle is the exception: its two native owners share\n * a window coordinate independently of the navigation column\'s geometry.\n */\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) {\n --dsh-sidebar-inline-padding: var(--ccd-sidebar-inline-padding);\n /* The account row keeps its distance from the window\'s bottom edge. */\n padding: 0 var(--ccd-sidebar-inline-padding) var(--ccd-sidebar-bottom-padding);\n font-size: var(--ccd-font-size-muted);\n}\n\n/* Keep the window-control row above the brand in the expanded layout. */\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed):not(._3WPZCG_fading) ._3WPZCG_topStrip {\n height: 48px;\n margin: 0 calc(-1 * var(--ccd-sidebar-inline-padding));\n padding: 0 0 0 88px;\n justify-content: flex-start;\n}\n\n/* On macOS the expanded sidebar and shell.leading own different native\n buttons. Anchor both to the window, so neither the sidebar width nor its\n clipping during the grid transition moves the click target. The frame\n switches ownership immediately; hide the outgoing sidebar copy throughout\n collapse instead of letting it fade over the shell\'s new button. */\nhtml[data-dsh-ccd-style="true"][data-platform="darwin"] {\n --ccd-sidebar-toggle-left: 88px;\n}\n\nhtml[data-dsh-ccd-style="true"][data-platform="darwin"][data-fullscreen] {\n --ccd-sidebar-toggle-left: 12px;\n}\n\nhtml[data-dsh-ccd-style="true"][data-platform="darwin"] ._3WPZCG_root ._3WPZCG_toggle {\n position: fixed;\n top: 11px;\n left: var(--ccd-sidebar-toggle-left);\n z-index: 15;\n margin: 0;\n width: 28px;\n height: 28px;\n animation: none;\n -webkit-app-region: no-drag;\n}\n\nhtml[data-dsh-ccd-style="true"][data-platform="darwin"] [data-sidebar-collapsed] ._3WPZCG_toggle {\n visibility: hidden;\n}\n\nhtml[data-dsh-ccd-style="true"][data-platform="darwin"] [data-shell-leading] {\n top: 11px;\n left: var(--ccd-sidebar-toggle-left);\n}\n\nhtml[data-dsh-ccd-style="true"][data-platform="darwin"] [data-shell-leading] button {\n border-radius: 8px;\n color: var(--ccd-text-secondary);\n}\n\nhtml[data-dsh-ccd-style="true"][data-platform="darwin"] [data-shell-leading] button:hover {\n background: var(--ccd-hover);\n}\n\n/* The brand sits on the same inset as the navigation rows below it, so the mark\n and the app name line up with New Session and the panel glyphs. */\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_logoRow {\n height: 32px;\n margin-bottom: 6px;\n padding: 0 8px;\n justify-content: flex-start;\n gap: 4px;\n}\n\n/* The host pulls the brand row up under the window-control strip; the CCD\n header keeps them as two stacked rows. */\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_topStrip + ._3WPZCG_logoRow {\n margin-top: 0;\n}\n\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_brand {\n flex: 0 1 auto;\n}\n\n/* Windows has no traffic-light row: the sidebar toggle, the native caption\n menus and the window controls live in the host\'s 40px strip above the frame,\n so the brand row is the first row below it. The host\'s Windows build grows\n that row to 40px and nudges the brand 4px right of its own inset; the CCD\n header keeps the compact rhythm above and the inset New Session and the panel\n glyphs share, so the nudge is dropped. Everything inside the strip (toggle at\n 12px, caption menus at 48px, window controls at the right) belongs to the host\n and is never moved here. */\nhtml[data-dsh-ccd-style="true"][data-windows-titlebar] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_brand {\n padding-left: 0;\n}\n\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_brandIdentity,\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_brandName {\n height: 20px;\n gap: 5px;\n font-size: 14px;\n font-weight: 600;\n letter-spacing: 0.01em;\n line-height: 20px;\n}\n\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_brandMark svg,\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) [data-slot="sidebar.brand.mark"] svg {\n width: 18px;\n height: auto;\n}\n\n/* "+ New": a flat navigation row rather than a raised button. Its trailing gap\n is the panel list\'s own row gap, so New Session, the global panels and the\n automation panel read as one stack instead of two blocks. The row is the\n reference\'s 26px navigation height with an 8px corner \u2014 the same geometry\n the panel rows below it use, so the three rows sit on one pitch. */\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_newSession,\nhtml[data-dsh-ccd-style="true"][data-platform="darwin"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_newSession {\n justify-content: flex-start;\n height: var(--ccd-nav-row-height);\n margin: 0 0 var(--ccd-nav-gap);\n padding: 0 8px;\n border: 0;\n border-radius: var(--ccd-radius-row);\n background: transparent;\n color: var(--ccd-text);\n font-size: var(--ccd-font-size-body);\n font-weight: 400;\n}\n\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_newSession:hover,\nhtml[data-dsh-ccd-style="true"][data-platform="darwin"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_newSession:hover {\n background: var(--ccd-hover);\n}\n\n/* One icon column. The host draws this row\'s glyph 14px wide and the global\n panel glyphs 16px, and gives the panel rows an 8px gap; left as is, the three\n labels start at different x (39 / 42 / 42) and the glyph centres drift with\n them. The plus therefore gets the same 16px slot and the same 8px gap, so\n every row\'s icon is centred on one axis and every label starts at x=42. */\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_newSessionContent {\n justify-content: flex-start;\n gap: 8px;\n}\n\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_newSessionContent > svg {\n margin-inline: 1px;\n}\n\n/* The browsing region opens on the navigation stack\'s own row rhythm. The\n header below is a 30px box carrying 28px buttons \u2014 that box *is* the icon row,\n and 30px is also what the host\'s expanded search pill measures \u2014 so the only\n whitespace the stack can give up is the margin that used to sit here: 10px on\n top of the header\'s own 2px bottom margin left the three icons floating in a\n band about twice the stack\'s own pitch. Measured at @2x on\n docs/issues/img/06.png the last navigation row\'s ink ended 26px above the icon\n glyphs, against the 14.5px between two navigation rows. The margin drops to\n the row gap, which lifts the icon row and the list together by 9px and leaves\n the 28px hit areas, the header\'s 30px box and every row above untouched. The\n host\'s own `margin-top: 2px` on that header never applies: the plugin\'s\n `margin: 0 0 2px` below outranks its `:not(rail)` rule. */\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_panelList {\n gap: var(--ccd-nav-gap);\n margin-bottom: var(--ccd-nav-gap);\n}\n\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_panelRow {\n min-height: var(--ccd-nav-row-height);\n margin: 0;\n padding: 3px 8px;\n border-radius: var(--ccd-radius-row);\n color: var(--ccd-text-nav);\n font-size: var(--ccd-font-size-body);\n line-height: 20px;\n}\n\n/* Current row versus hover. The reference paints the row you are on one step\n darker than the row you are merely pointing at; the host marks the current\n panel with `panelActive`, so the two states take two different steps here\n instead of sharing the hover grey. */\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_panelRow._3WPZCG_panelActive {\n background: var(--ccd-selected);\n color: var(--ccd-text);\n}\n\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_panelRow:hover {\n background: var(--ccd-hover);\n color: var(--ccd-text);\n}\n\n/* One icon column, one optical size. The panel glyph\'s slot is the same 16px\n the New Session plus already occupies (14px of artwork plus its 1px inline\n margins), so all three labels start at the same x and all three glyphs share\n one centre axis. The artwork *inside* that slot is then sized per mark,\n because each owner draws its ink at a different width on the primitives\'\n 16-unit grid: the plus spans 13 units, the plugins pinwheel 13.8 and the\n automation clock 12. Matched to the plus\'s 12.5px of ink, the pinwheel\n renders at 13.5px and the clock at 15.4px (below), which lands all three\n inks at 12.51px \u2014 inside the 0.5px this stack is checked to. */\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_panelGlyph {\n width: 16px;\n height: 16px;\n color: var(--ccd-text-nav);\n}\n\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_panelGlyph svg {\n width: 13.5px;\n height: 13.5px;\n}\n\n/* The automation clock carries more of the grid as padding than the pinwheel \u2014\n its circle spans 12 of the 16 units against the pinwheel\'s 13.8 \u2014 so at the\n shared panel size it reads about 1.5px small. It is matched by its own\n geometry: of the three marks, only its circle starts on the grid\'s bottom\n vertex. Scaling it up inside the same 16px slot leaves the exception free to\n move nothing \u2014 the slot, the label\'s start and the glyph\'s centre all stay\n where the stack puts them. A later build that redraws the clock simply stops\n matching and falls back to the shared 13.5px artwork. */\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed)\n ._3WPZCG_panelGlyph:has(> svg > path[d^="M8 14C11.3137"]) > svg {\n width: 15.4px;\n height: 15.4px;\n}\n\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_panelRow:hover ._3WPZCG_panelGlyph,\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_panelRow._3WPZCG_panelActive ._3WPZCG_panelGlyph {\n color: var(--ccd-text);\n}\n\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_regionArea {\n margin-left: 0;\n margin-right: 0;\n padding-left: 0;\n}\n\n/* Browsing region: no section title, project rows that carry their own create\n button, compact session rows. */\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._7514NG_root {\n --dsh-session-list-edge-inset: 0px;\n padding-right: 0;\n}\n\n/* The rail keeps the host\'s own insets: this header holds the "add workspace"\n icon in the collapsed layout, and the expanded layout\'s 8px inset would push\n that one icon off the rail\'s centre axis. */\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._7514NG_sectionHeader {\n height: 30px;\n margin: 0 0 2px;\n padding-left: 8px;\n color: var(--ccd-text-muted);\n}\n\n/* The reference has no title over the browsing region: the list starts straight\n after the navigation stack. The header row stays for its three icon buttons\n (search, view options, add workspace), each of which carries its own accessible\n name, so dropping the decorative label costs no entry point. */\nhtml[data-dsh-ccd-style="true"] ._7514NG_sectionLabel {\n display: none;\n}\n\n/* Add workspace: the host draws a folder with a plus; the reference\'s rows use a\n bare mark, so the button keeps its own hit box and callback and takes the\n shared plus artwork over a 14px box.\n\n Both declarations key off the trigger being the container\'s last *button*,\n not its last child (05). The host\'s Tooltip runs with `portal` off, so while\n its bubble is up it appends that bubble as a `span[role="tooltip"]` sibling\n directly after the trigger, inside this container: `:last-child` then falls to\n the bubble, these two rules stop matching under the pointer, and the host\'s\n own folder-plus comes back. `:last-of-type` counts elements of the trigger\'s\n own type, which the span bubble never enters, so the mask and the\n `display: none` that suppresses the host\'s svg both hold in every state \u2014\n hover with the bubble up, keyboard focus, and the open menus. */\nhtml[data-dsh-ccd-style="true"] ._7514NG_headerActions > button:last-of-type svg {\n display: none;\n}\n\nhtml[data-dsh-ccd-style="true"] ._7514NG_headerActions > button:last-of-type::before {\n content: "";\n width: 14px;\n height: 14px;\n background-color: currentColor;\n mask: var(--ccd-icon-plus) center / 14px 14px no-repeat;\n}\n\n/* View options: the reference\'s two-knob control stands upright, and the host\'s\n own artwork is that same control lying down, so the glyph is rotated rather\n than redrawn. */\nhtml[data-dsh-ccd-style="true"] ._7514NG_headerActions > button:first-child svg,\nhtml[data-dsh-ccd-style="true"] ._7514NG_headerActions > *:first-child button svg {\n transform: rotate(90deg);\n}\n\nhtml[data-dsh-ccd-style="true"] ._7514NG_listArea {\n margin: 0;\n padding: 0;\n}\n\n/* Project rows. The host already sizes this row at the reference\'s 34px \u2014 pinned\n here so the stack\'s pitch is a plugin value \u2014 and the 4px inline start drops\n the label onto the reference\'s own 14px edge, which is where the folder glyph\n used to sit. Nesting depth still arrives through `--dsh-workspace-indent`, so\n grouped views keep their step. */\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow {\n height: 34px;\n padding-inline-start: calc(4px + var(--dsh-workspace-indent, 0px));\n color: var(--ccd-text-secondary);\n}\n\n/* No leading folder: the reference marks a project by its name alone, and the\n open/close state is carried by the disclosure mark after the label. */\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow .jJkEga_folder {\n display: none;\n}\n\n/* The host reveals this mark on hover and keeps the text flexible; here it is\n the row\'s disclosure control, sitting right after the label. The slot is\n always laid out and only its ink converges (see below), so the label\'s\n measure never depends on where the pointer is. The host\'s own state still\n owns the turn \u2014 `arrowOpen` rotates the mark a quarter turn \u2014 but its artwork\n is a filled triangle where the reference draws a line chevron, so the mark\n slot paints the shared chevron geometry instead. */\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow .jJkEga_projectText {\n flex: 0 1 auto;\n}\n\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow .jJkEga_chevron {\n display: inline-flex;\n order: 1;\n transition: transform 0.15s var(--ds-ease-in-out, ease-in-out);\n}\n\n@media (prefers-reduced-motion: reduce) {\n html[data-dsh-ccd-style="true"] .jJkEga_projectRow .jJkEga_chevron {\n transition: none;\n }\n}\n\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow .jJkEga_chevron svg {\n display: none;\n}\n\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow .jJkEga_chevron::before {\n content: "";\n width: 16px;\n height: 16px;\n background-color: currentColor;\n mask: var(--ccd-icon-chevron-right) center / 16px 16px no-repeat;\n}\n\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow .jJkEga_chevron:has(.jJkEga_arrowOpen) {\n transform: rotate(90deg);\n}\n\n/* Hover convergence (08). A collapsed row keeps its \u203A \u2014 the reference\'s\n discoverability cue \u2014 while an expanded row shows the turned mark only while\n the pointer is on the row, one of its buttons holds keyboard focus, or that\n row\'s menu is open: the host portals that menu out of the row, so the row\n loses `:hover` as the pointer sets off towards it and the mark would blink\n out mid-gesture. Focus is read as `:focus-visible`, not `:focus-within`, so a\n mouse click on \u22EF/+ does not keep the mark up.\n\n `visibility`, not `display`: the slot keeps its 16px box and its place in the\n row\'s gap in both states, so the row never reflows, the label cannot\n re-truncate as the mark appears, and the trailing actions \u2014 pinned by\n `margin-left: auto` \u2014 cannot move. The slot holds no text and nothing\n focusable, so hiding it takes no entry point away, and the row\'s state still\n reaches assistive technology as the row\'s own `aria-expanded`.\n\n `@media (hover: hover)` guards the whole rule: where `:hover` never arrives\n (a touch screen) an expanded row would lose its state mark for good, so the\n mark stays up in both states there \u2014 the state this plugin shipped before. */\n@media (hover: hover) {\n html[data-dsh-ccd-style="true"] .jJkEga_projectRow:has(.jJkEga_arrowOpen):not(:hover):not(:has(:focus-visible)):not(.jJkEga_menuOpen) .jJkEga_chevron {\n visibility: hidden;\n }\n}\n\n/* Project text then disclosure, with the row\'s actions pinned to the trailing\n edge: `order` moves the mark after the label without touching the host\'s DOM.\n Both actions are permanent \u2014 the create button mirrors the reference\'s\n always-visible row action, and the overflow trigger has to stay laid out\n while its own menu is open: the host closes that menu once the pointer leaves\n a trigger that is no longer there, which a hover-only trigger does the moment\n the pointer sets off towards the list. */\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow .jJkEga_rowActions {\n display: inline-flex;\n order: 2;\n margin-left: auto;\n}\n\n/* The create button is the container\'s last *button*, not its last child, for\n the same reason as the header\'s add-workspace trigger (05): its Tooltip\'s\n bubble is appended as a `span[role="tooltip"]` sibling right after the\n trigger, so `:last-child` would land on that bubble the moment the pointer\n arrives, and the host\'s own circle-plus would come back with it. The sibling\n \u22EF trigger sits inside the menu primitive\'s own wrapper, which is why the\n button here is the container\'s only button child; `:last-of-type` keeps the\n plus artwork, and the `display: none` that suppresses the host\'s svg, through\n hover, keyboard focus, and the row\'s menu being open. */\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow .jJkEga_rowActions > button:last-of-type svg {\n display: none;\n}\n\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow .jJkEga_rowActions > button:last-of-type::before {\n content: "";\n width: 14px;\n height: 14px;\n background-color: currentColor;\n mask: var(--ccd-icon-plus) center / 14px 14px no-repeat;\n}\n\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow .jJkEga_title {\n font-size: var(--ccd-font-size-muted);\n color: var(--ccd-text-muted);\n}\n\n/* A project row never fills: the host still paints its generic hover grey, so\n that fill is cleared and the row answers with ink instead \u2014 the label steps\n up to full strength while the pointer is on it. The host\'s open-menu anchor\n is kept: that fill marks a menu this row raised, not a hover. */\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow:hover {\n background: transparent;\n}\n\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow:hover .jJkEga_title {\n color: var(--ccd-text);\n}\n\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow.jJkEga_menuOpen {\n background: var(--dsh-alias-interactive-bg-hover);\n}\n\nhtml[data-dsh-ccd-style="true"] .jJkEga_sessionRow {\n height: 28px;\n}\n\n/* The row\'s leading 16px cell is the reference\'s shape already \u2014 the label\n lands on the same 38px edge as a navigation label. What arrives there is\n either a 6px solid state dot or the 14px activity ring; the ring is scaled to\n 8px so its 1.5px stroke reads as the reference\'s 6px mark instead of a\n full-size spinner. */\nhtml[data-dsh-ccd-style="true"] .jJkEga_sessionRow > .jJkEga_slot > svg[data-state="ongoing"] {\n width: 8px;\n height: 8px;\n}\n\n/* An idle Session keeps that cell empty. The host gives the leading mark only\n to a row that has one \u2014 running keeps the activity ring, completed or\n waiting keeps its state dot \u2014 and every other row gets the plugin\'s own\n `sidebar.session.row.leading` seat instead, which nothing registers into by\n default. The title therefore starts 16px left of the marked rows above and\n below it, and the column loses its left edge wherever a Session is idle.\n\n The seat is filled with the ring\'s own geometry, not a second mark: the same\n circle the activity ring draws (`--ccd-icon-session-ring`, read from\n `ui-primitives`\' StateDot spinner) at the same 8px scale the rule above\n already puts the spinner at, so an idle row and a running row carry one mark\n that differs only by motion. `currentColor` is the cell\'s own\n `--dsw-alias-label-tertiary` \u2014 the ink the spinner paints with \u2014 so the still\n ring needs no colour of its own and follows the host theme.\n\n The seat\'s outlet is what the `:has()` guard reads: `renderSlot` mounts a\n `div[data-slot]` whose children are the registered entries, so an *empty*\n outlet is exactly "idle, and nothing else asked for this cell". A row whose\n seat is taken keeps its own mark and never gets a ring behind it \u2014 the\n shipped `ui-schedule` puts its task mark there. Archived and provisional rows\n render no seat at all, and the archived row\'s own archive mark stays the one\n thing in that cell; the `:not(.jJkEga_archived)` is that guarantee written\n down rather than left to the host\'s current shape. */\nhtml[data-dsh-ccd-style="true"] .jJkEga_sessionRow:not(.jJkEga_archived) > .jJkEga_slot:has(> div[data-slot="sidebar.session.row.leading"]:empty)::before {\n content: "";\n width: 8px;\n height: 8px;\n background-color: currentColor;\n mask: var(--ccd-icon-session-ring) center / 8px 8px no-repeat;\n}\n\nhtml[data-dsh-ccd-style="true"] .jJkEga_sessionRow .jJkEga_title {\n font-size: var(--ccd-font-size-muted);\n line-height: 18px;\n color: var(--ccd-text);\n}\n\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow,\nhtml[data-dsh-ccd-style="true"] .jJkEga_sessionRow {\n border-radius: 8px;\n}\n\nhtml[data-dsh-ccd-style="true"] .jJkEga_sessionRow:hover,\nhtml[data-dsh-ccd-style="true"] .jJkEga_sessionRow.jJkEga_selected {\n background: var(--ccd-selected);\n}\n\nhtml[data-dsh-ccd-style="true"] .jJkEga_time {\n color: var(--ccd-text-muted);\n}\n\n/* Archived rows. The host fades an archived title with its own caption ink, but\n that pair is one class lighter than the title rule above, so the grey never\n reaches the screen. The state is restated here a step heavier than that rule\n and covers the whole line: title, time and the leading mark together, since a\n full-strength time under a faded title would read as the louder half. The host\n keeps the rest of the state \u2014 hover card, restore button and the click that\n clears the class \u2014 so a restored row simply leaves this block behind. */\nhtml[data-dsh-ccd-style="true"] .jJkEga_sessionRow.jJkEga_archived :is(.jJkEga_title, .jJkEga_time, .jJkEga_slot) {\n color: var(--ccd-text-archived);\n}\n\n/* The host leaves an archived row\'s leading cell empty on purpose \u2014 neither a\n status dot nor a decoration renders there \u2014 so the mark is painted here from\n the shared archive mask. That cell is the same 16x20 inline-flex box every\n status dot is centred in, and the row (28px, `align-items: center`) centres\n the cell on its own line, so a mark centred in the cell lands on the row\'s\n centre line and on the dot\'s axis.\n\n What has to land there is the mark\'s ink, not the square it was drawn in. The\n host artwork is a 1px stroke on a 16-unit canvas and its paths span x 1..15\n and y 2..15 \u2014 the lid\'s top edge sits at y 2.5, the body ends at y 14.5 \u2014 so\n the ink is 14 x 13 units with its centre 0.5 unit below the canvas centre (it\n is already symmetric across x 8, so only y needs saying). Painted at 14px one\n unit is 0.875px, so a box-centred mark hangs 0.4375px low: 0.875 device pixels\n at 2x, which is the whole of the bias the eye picked up, the air above the\n mark being 1.75px against 0.875px below it. Half a unit is given back to the\n top, which leaves 1.3125px of air on either side. `center` keeps the image\n centred in the cell for any box and image size and the -0.4375px is the\n artwork\'s own offset read off that geometry, so neither term is fitted by eye.\n\n The correction rides on the mask\'s placement rather than on the box or on the\n artwork: the flex line still centres the same 14px square the status dot\'s\n cell centres, `--ccd-icon-archive` stays the host\'s geometry verbatim (a later\n DSH build can be re-read into that token without this being lost with it), and\n the placement is applied on every paint instead of being baked into a\n rasterised mark. It is a second declaration rather than part of the shorthand\n so artwork, size and repeat stay on one line \u2014 and so an engine that ignored\n it would still paint a half-unit-low mark instead of painting none. */\nhtml[data-dsh-ccd-style="true"] .jJkEga_sessionRow.jJkEga_archived .jJkEga_slot::before {\n content: "";\n width: 14px;\n height: 14px;\n background-color: currentColor;\n mask: var(--ccd-icon-archive) center / 14px 14px no-repeat;\n mask-position: center calc(50% - 0.4375px);\n}\n\n/* Pointer or current row: hover and selection paint a fill a step darker than\n the sidebar the faded ink was chosen against, and an archived Session can be\n the current one, so the ink steps back up while the row is being acted on. It\n stays short of the unarchived row\'s own ink, which is what keeps the archived\n state readable instead of merely faint. */\nhtml[data-dsh-ccd-style="true"] .jJkEga_sessionRow.jJkEga_archived:is(:hover, .jJkEga_selected) :is(.jJkEga_title, .jJkEga_time, .jJkEga_slot) {\n color: var(--ccd-text-archived-hover);\n}\n\n/* Provisional New Session row. DSH creates the Session with the click on New\n Session and keeps the row in the list while the host still calls it `blank`;\n the column shows the new-session page alone and lets the entry arrive with\n the first sent message. `compat/blank-session-rows.ts` writes the marker from\n the host\'s own `blank` fact \u2014 the stylesheet only hides what it tagged, so an\n untagged (native or unsupported) row is never touched. The row keeps its\n place in the host\'s list model; only the list\'s presentation drops it.\n\n This is the marker\'s half of the filter, and its fallback. The module also\n writes the ids into a plugin-owned sheet keyed by the row key, because\n `ui-workspace`\'s AnimatedRows measures every keyed row inside the commit that\n mounts it: a row a marker reaches one microtask later is a row the host has\n already measured, and the height it then drops out of the flow is animated\n as a jump instead of never being laid out (04). Both halves hide exactly the\n ids the host still calls `blank`, and neither touches a fork child. */\nhtml[data-dsh-ccd-style="true"] [data-row-key^="session:"][data-ccd-blank-session] {\n display: none;\n}\n\n/* Foot: the settings entry reads as the reference\'s account row. */\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_footArea {\n padding-top: 6px;\n border-top: 1px solid var(--ccd-border);\n}\n\nhtml[data-dsh-ccd-style="true"] .Dws9Sa_triggerRow {\n margin: 0;\n}\n\nhtml[data-dsh-ccd-style="true"] .Dws9Sa_trigger {\n border-radius: var(--ccd-radius-row);\n color: var(--ccd-text);\n font-size: var(--ccd-font-size-muted);\n}\n\nhtml[data-dsh-ccd-style="true"]:not([data-platform="darwin"]) ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_toggle {\n /* Non-macOS keeps the native brand-row / icon-rail handoff. */\n margin-left: auto;\n}\n\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_iconButton {\n border-radius: 8px;\n color: var(--ccd-text-secondary);\n}\n\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_iconButton:hover {\n background: var(--ccd-hover);\n}\n';
|
|
5924
|
+
var sidebar_default = '/*\n * Sidebar: compact navigation column.\n *\n * Host classes are pinned in src/client/compat/host-dom.ts. The CCD reference\n * puts window controls plus two icon buttons at the top, a short stack of\n * navigation rows, then compact project groups. DSH\'s column carries its own\n * brand row and a global panel list instead of CCD\'s Home/Code switch; those\n * real entries are kept and compacted rather than replaced by inert lookalikes.\n */\n/*\n * Navigation overrides below style the *expanded* column. DSH\'s collapsed sidebar is a\n * different layout \u2014 a 36px icon rail the host centres itself (`.collapsed`\n * rules: root padding `18px 10px 6px`, logo row `padding: 0; height: 36px`,\n * 36\xD736 buttons) and slides in from the right. Overriding that geometry leaves\n * two icons off the rail\'s centre axis, so every expanded-layout override is\n * scoped to `:not(._3WPZCG_collapsed)` and the rail keeps the host\'s numbers.\n * The macOS window-chrome toggle is the exception: its two native owners share\n * a window coordinate independently of the navigation column\'s geometry.\n */\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) {\n --dsh-sidebar-inline-padding: var(--ccd-sidebar-inline-padding);\n /* The account row keeps its distance from the window\'s bottom edge. */\n padding: 0 var(--ccd-sidebar-inline-padding) var(--ccd-sidebar-bottom-padding);\n font-size: var(--ccd-font-size-muted);\n}\n\n/* Keep the window-control row above the brand in the expanded layout. */\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed):not(._3WPZCG_fading) ._3WPZCG_topStrip {\n height: 48px;\n margin: 0 calc(-1 * var(--ccd-sidebar-inline-padding));\n padding: 0 0 0 88px;\n justify-content: flex-start;\n}\n\n/* On macOS the expanded sidebar and shell.leading own different native\n buttons. Anchor both to the window, so neither the sidebar width nor its\n clipping during the grid transition moves the click target. The frame\n switches ownership immediately; hide the outgoing sidebar copy throughout\n collapse instead of letting it fade over the shell\'s new button. */\nhtml[data-dsh-ccd-style="true"][data-platform="darwin"] {\n --ccd-sidebar-toggle-left: 88px;\n}\n\nhtml[data-dsh-ccd-style="true"][data-platform="darwin"][data-fullscreen] {\n --ccd-sidebar-toggle-left: 12px;\n}\n\nhtml[data-dsh-ccd-style="true"][data-platform="darwin"] ._3WPZCG_root ._3WPZCG_toggle {\n position: fixed;\n top: 11px;\n left: var(--ccd-sidebar-toggle-left);\n z-index: 15;\n margin: 0;\n width: 28px;\n height: 28px;\n animation: none;\n -webkit-app-region: no-drag;\n}\n\nhtml[data-dsh-ccd-style="true"][data-platform="darwin"] [data-sidebar-collapsed] ._3WPZCG_toggle {\n visibility: hidden;\n}\n\nhtml[data-dsh-ccd-style="true"][data-platform="darwin"] [data-shell-leading] {\n top: 11px;\n left: var(--ccd-sidebar-toggle-left);\n}\n\nhtml[data-dsh-ccd-style="true"][data-platform="darwin"] [data-shell-leading] button {\n border-radius: 8px;\n color: var(--ccd-text-secondary);\n}\n\nhtml[data-dsh-ccd-style="true"][data-platform="darwin"] [data-shell-leading] button:hover {\n background: var(--ccd-hover);\n}\n\n/* The brand sits on the same inset as the navigation rows below it, so the mark\n and the app name line up with New Session and the panel glyphs. */\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_logoRow {\n height: 32px;\n margin-bottom: 6px;\n padding: 0 8px;\n justify-content: flex-start;\n gap: 4px;\n}\n\n/* The host pulls the brand row up under the window-control strip; the CCD\n header keeps them as two stacked rows. */\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_topStrip + ._3WPZCG_logoRow {\n margin-top: 0;\n}\n\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_brand {\n flex: 0 1 auto;\n}\n\n/* Windows has no traffic-light row: the sidebar toggle, the native caption\n menus and the window controls live in the host\'s 40px strip above the frame,\n so the brand row is the first row below it. The host\'s Windows build grows\n that row to 40px and nudges the brand 4px right of its own inset; the CCD\n header keeps the compact rhythm above and the inset New Session and the panel\n glyphs share, so the nudge is dropped. Everything inside the strip (toggle at\n 12px, caption menus at 48px, window controls at the right) belongs to the host\n and is never moved here. */\nhtml[data-dsh-ccd-style="true"][data-windows-titlebar] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_brand {\n padding-left: 0;\n}\n\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_brandIdentity,\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_brandName {\n height: 20px;\n gap: 5px;\n font-size: 14px;\n font-weight: 600;\n letter-spacing: 0.01em;\n line-height: 20px;\n}\n\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_brandMark svg,\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) [data-slot="sidebar.brand.mark"] svg {\n width: 18px;\n height: auto;\n}\n\n/* "+ New": a flat navigation row rather than a raised button. Its trailing gap\n is the panel list\'s own row gap, so New Session, the global panels and the\n automation panel read as one stack instead of two blocks. The row is the\n reference\'s 26px navigation height with an 8px corner \u2014 the same geometry\n the panel rows below it use, so the three rows sit on one pitch. */\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_newSession,\nhtml[data-dsh-ccd-style="true"][data-platform="darwin"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_newSession {\n justify-content: flex-start;\n height: var(--ccd-nav-row-height);\n margin: 0 0 var(--ccd-nav-gap);\n padding: 0 8px;\n border: 0;\n border-radius: var(--ccd-radius-row);\n background: transparent;\n color: var(--ccd-text);\n font-size: var(--ccd-font-size-body);\n font-weight: 400;\n}\n\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_newSession:hover,\nhtml[data-dsh-ccd-style="true"][data-platform="darwin"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_newSession:hover {\n background: var(--ccd-hover);\n}\n\n/* One icon column. The host draws this row\'s glyph 14px wide and the global\n panel glyphs 16px, and gives the panel rows an 8px gap; left as is, the three\n labels start at different x (39 / 42 / 42) and the glyph centres drift with\n them. The plus therefore gets the same 16px slot and the same 8px gap, so\n every row\'s icon is centred on one axis and every label starts at x=42. */\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_newSessionContent {\n justify-content: flex-start;\n gap: 8px;\n}\n\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_newSessionContent > svg {\n margin-inline: 1px;\n}\n\n/* The browsing region opens on the navigation stack\'s own row rhythm. The\n header below is a 30px box carrying 28px buttons \u2014 that box *is* the icon row,\n and 30px is also what the host\'s expanded search pill measures \u2014 so the only\n whitespace the stack can give up is the margin that used to sit here: 10px on\n top of the header\'s own 2px bottom margin left the three icons floating in a\n band about twice the stack\'s own pitch. Measured at @2x on\n docs/issues/img/06.png the last navigation row\'s ink ended 26px above the icon\n glyphs, against the 14.5px between two navigation rows. The margin drops to\n the row gap, which lifts the icon row and the list together by 9px and leaves\n the 28px hit areas, the header\'s 30px box and every row above untouched. The\n host\'s own `margin-top: 2px` on that header never applies: the plugin\'s\n `margin: 0 0 2px` below outranks its `:not(rail)` rule. */\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_panelList {\n gap: var(--ccd-nav-gap);\n margin-bottom: var(--ccd-nav-gap);\n}\n\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_panelRow {\n min-height: var(--ccd-nav-row-height);\n margin: 0;\n padding: 3px 8px;\n border-radius: var(--ccd-radius-row);\n color: var(--ccd-text-nav);\n font-size: var(--ccd-font-size-body);\n line-height: 20px;\n}\n\n/* Current row versus hover. The reference paints the row you are on one step\n darker than the row you are merely pointing at; the host marks the current\n panel with `panelActive`, so the two states take two different steps here\n instead of sharing the hover grey. */\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_panelRow._3WPZCG_panelActive {\n background: var(--ccd-selected);\n color: var(--ccd-text);\n}\n\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_panelRow:hover {\n background: var(--ccd-hover);\n color: var(--ccd-text);\n}\n\n/* One icon column, one optical size. The panel glyph\'s slot is the same 16px\n the New Session plus already occupies (14px of artwork plus its 1px inline\n margins), so all three labels start at the same x and all three glyphs share\n one centre axis. The artwork *inside* that slot is then sized per mark,\n because each owner draws its ink at a different width on the primitives\'\n 16-unit grid: the plus spans 13 units, the plugins pinwheel 13.8 and the\n automation clock 12. Matched to the plus\'s 12.5px of ink, the pinwheel\n renders at 13.5px and the clock at 15.4px (below), which lands all three\n inks at 12.51px \u2014 inside the 0.5px this stack is checked to. */\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_panelGlyph {\n width: 16px;\n height: 16px;\n color: var(--ccd-text-nav);\n}\n\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_panelGlyph svg {\n width: 13.5px;\n height: 13.5px;\n}\n\n/* The automation clock carries more of the grid as padding than the pinwheel \u2014\n its circle spans 12 of the 16 units against the pinwheel\'s 13.8 \u2014 so at the\n shared panel size it reads about 1.5px small. It is matched by its own\n geometry: of the three marks, only its circle starts on the grid\'s bottom\n vertex. Scaling it up inside the same 16px slot leaves the exception free to\n move nothing \u2014 the slot, the label\'s start and the glyph\'s centre all stay\n where the stack puts them. A later build that redraws the clock simply stops\n matching and falls back to the shared 13.5px artwork. */\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed)\n ._3WPZCG_panelGlyph:has(> svg > path[d^="M8 14C11.3137"]) > svg {\n width: 15.4px;\n height: 15.4px;\n}\n\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_panelRow:hover ._3WPZCG_panelGlyph,\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_panelRow._3WPZCG_panelActive ._3WPZCG_panelGlyph {\n color: var(--ccd-text);\n}\n\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_regionArea {\n margin-left: 0;\n margin-right: 0;\n padding-left: 0;\n}\n\n/* Browsing region: no section title, project rows that carry their own create\n button, compact session rows. */\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._7514NG_root {\n --dsh-session-list-edge-inset: 0px;\n padding-right: 0;\n}\n\n/* The rail keeps the host\'s own insets: this header holds the "add workspace"\n icon in the collapsed layout, and the expanded layout\'s 8px inset would push\n that one icon off the rail\'s centre axis. */\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._7514NG_sectionHeader {\n height: 30px;\n margin: 0 0 2px;\n padding-left: 8px;\n color: var(--ccd-text-muted);\n}\n\n/* The reference has no title over the browsing region: the list starts straight\n after the navigation stack. The header row stays for its three icon buttons\n (search, view options, add workspace), each of which carries its own accessible\n name, so dropping the decorative label costs no entry point. */\nhtml[data-dsh-ccd-style="true"] ._7514NG_sectionLabel {\n display: none;\n}\n\n/* Add workspace: the host draws a folder with a plus; the reference\'s rows use a\n bare mark, so the button keeps its own hit box and callback and takes the\n shared plus artwork over a 14px box.\n\n Both declarations key off the trigger being the container\'s last *button*,\n not its last child (05). The host\'s Tooltip runs with `portal` off, so while\n its bubble is up it appends that bubble as a `span[role="tooltip"]` sibling\n directly after the trigger, inside this container: `:last-child` then falls to\n the bubble, these two rules stop matching under the pointer, and the host\'s\n own folder-plus comes back. `:last-of-type` counts elements of the trigger\'s\n own type, which the span bubble never enters, so the mask and the\n `display: none` that suppresses the host\'s svg both hold in every state \u2014\n hover with the bubble up, keyboard focus, and the open menus. */\nhtml[data-dsh-ccd-style="true"] ._7514NG_headerActions > button:last-of-type svg {\n display: none;\n}\n\nhtml[data-dsh-ccd-style="true"] ._7514NG_headerActions > button:last-of-type::before {\n content: "";\n width: 14px;\n height: 14px;\n background-color: currentColor;\n mask: var(--ccd-icon-plus) center / 14px 14px no-repeat;\n}\n\n/* View options: the reference\'s two-knob control stands upright, and the host\'s\n own artwork is that same control lying down, so the glyph is rotated rather\n than redrawn. */\nhtml[data-dsh-ccd-style="true"] ._7514NG_headerActions > button:first-child svg,\nhtml[data-dsh-ccd-style="true"] ._7514NG_headerActions > *:first-child button svg {\n transform: rotate(90deg);\n}\n\nhtml[data-dsh-ccd-style="true"] ._7514NG_listArea {\n margin: 0;\n padding: 0;\n}\n\n/* Project rows. The host already sizes this row at the reference\'s 34px \u2014 pinned\n here so the stack\'s pitch is a plugin value \u2014 and the 4px inline start drops\n the label onto the reference\'s own 14px edge, which is where the folder glyph\n used to sit. Nesting depth still arrives through `--dsh-workspace-indent`, so\n grouped views keep their step. */\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow {\n height: 34px;\n padding-inline-start: calc(4px + var(--dsh-workspace-indent, 0px));\n color: var(--ccd-text-secondary);\n}\n\n/* No leading folder: the reference marks a project by its name alone, and the\n open/close state is carried by the disclosure mark after the label. */\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow .jJkEga_folder {\n display: none;\n}\n\n/* The host reveals this mark on hover and keeps the text flexible; here it is\n the row\'s disclosure control, sitting right after the label. The slot is\n always laid out and only its ink converges (see below), so the label\'s\n measure never depends on where the pointer is. The host\'s own state still\n owns the turn \u2014 `arrowOpen` rotates the mark a quarter turn \u2014 but its artwork\n is a filled triangle where the reference draws a line chevron, so the mark\n slot paints the shared chevron geometry instead. */\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow .jJkEga_projectText {\n flex: 0 1 auto;\n}\n\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow .jJkEga_chevron {\n display: inline-flex;\n order: 1;\n transition: transform 0.15s var(--ds-ease-in-out, ease-in-out);\n}\n\n@media (prefers-reduced-motion: reduce) {\n html[data-dsh-ccd-style="true"] .jJkEga_projectRow .jJkEga_chevron {\n transition: none;\n }\n}\n\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow .jJkEga_chevron svg {\n display: none;\n}\n\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow .jJkEga_chevron::before {\n content: "";\n width: 16px;\n height: 16px;\n background-color: currentColor;\n mask: var(--ccd-icon-chevron-right) center / 16px 16px no-repeat;\n}\n\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow .jJkEga_chevron:has(.jJkEga_arrowOpen) {\n transform: rotate(90deg);\n}\n\n/* Hover convergence (08). A collapsed row keeps its \u203A \u2014 the reference\'s\n discoverability cue \u2014 while an expanded row shows the turned mark only while\n the pointer is on the row, one of its buttons holds keyboard focus, or that\n row\'s menu is open: the host portals that menu out of the row, so the row\n loses `:hover` as the pointer sets off towards it and the mark would blink\n out mid-gesture. Focus is read as `:focus-visible`, not `:focus-within`, so a\n mouse click on \u22EF/+ does not keep the mark up.\n\n `visibility`, not `display`: the slot keeps its 16px box and its place in the\n row\'s gap in both states, so the row never reflows, the label cannot\n re-truncate as the mark appears, and the trailing actions \u2014 pinned by\n `margin-left: auto` \u2014 cannot move. The slot holds no text and nothing\n focusable, so hiding it takes no entry point away, and the row\'s state still\n reaches assistive technology as the row\'s own `aria-expanded`.\n\n `@media (hover: hover)` guards the whole rule: where `:hover` never arrives\n (a touch screen) an expanded row would lose its state mark for good, so the\n mark stays up in both states there \u2014 the state this plugin shipped before. */\n@media (hover: hover) {\n html[data-dsh-ccd-style="true"] .jJkEga_projectRow:has(.jJkEga_arrowOpen):not(:hover):not(:has(:focus-visible)):not(.jJkEga_menuOpen) .jJkEga_chevron {\n visibility: hidden;\n }\n}\n\n/* Project text then disclosure, with the row\'s actions pinned to the trailing\n edge: `order` moves the mark after the label without touching the host\'s DOM.\n Both actions are permanent \u2014 the create button mirrors the reference\'s\n always-visible row action, and the overflow trigger has to stay laid out\n while its own menu is open: the host closes that menu once the pointer leaves\n a trigger that is no longer there, which a hover-only trigger does the moment\n the pointer sets off towards the list. */\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow .jJkEga_rowActions {\n display: inline-flex;\n order: 2;\n margin-left: auto;\n}\n\n/* The create button is the container\'s last *button*, not its last child, for\n the same reason as the header\'s add-workspace trigger (05): its Tooltip\'s\n bubble is appended as a `span[role="tooltip"]` sibling right after the\n trigger, so `:last-child` would land on that bubble the moment the pointer\n arrives, and the host\'s own circle-plus would come back with it. The sibling\n \u22EF trigger sits inside the menu primitive\'s own wrapper, which is why the\n button here is the container\'s only button child; `:last-of-type` keeps the\n plus artwork, and the `display: none` that suppresses the host\'s svg, through\n hover, keyboard focus, and the row\'s menu being open. */\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow .jJkEga_rowActions > button:last-of-type svg {\n display: none;\n}\n\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow .jJkEga_rowActions > button:last-of-type::before {\n content: "";\n width: 14px;\n height: 14px;\n background-color: currentColor;\n mask: var(--ccd-icon-plus) center / 14px 14px no-repeat;\n}\n\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow .jJkEga_title {\n font-size: var(--ccd-font-size-muted);\n color: var(--ccd-text-muted);\n}\n\n/* A project row never fills: the host still paints its generic hover grey, so\n that fill is cleared and the row answers with ink instead \u2014 the label steps\n up to full strength while the pointer is on it. The host\'s open-menu anchor\n is kept: that fill marks a menu this row raised, not a hover. */\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow:hover {\n background: transparent;\n}\n\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow:hover .jJkEga_title {\n color: var(--ccd-text);\n}\n\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow.jJkEga_menuOpen {\n background: var(--dsh-alias-interactive-bg-hover);\n}\n\nhtml[data-dsh-ccd-style="true"] .jJkEga_sessionRow {\n height: 28px;\n}\n\n/* The row\'s leading 16px cell is the reference\'s shape already \u2014 the label\n lands on the same 38px edge as a navigation label. What arrives there is\n either a 6px solid state dot or the 14px activity ring; the ring is scaled to\n 8px so its 1.5px stroke reads as the reference\'s 6px mark instead of a\n full-size spinner. */\nhtml[data-dsh-ccd-style="true"] .jJkEga_sessionRow > .jJkEga_slot > svg[data-state="ongoing"] {\n width: 8px;\n height: 8px;\n}\n\n/* An idle Session keeps that cell empty. The host gives the leading mark only\n to a row that has one \u2014 running keeps the activity ring, completed or\n waiting keeps its state dot \u2014 and every other row gets the plugin\'s own\n `sidebar.session.row.leading` seat instead, which nothing registers into by\n default. The title therefore starts 16px left of the marked rows above and\n below it, and the column loses its left edge wherever a Session is idle.\n\n The seat is filled with the ring\'s own geometry, not a second mark: the same\n circle the activity ring draws (`--ccd-icon-session-ring`, read from\n `ui-primitives`\' StateDot spinner) at the same 8px scale the rule above\n already puts the spinner at, so an idle row and a running row carry one mark\n that differs only by motion. `currentColor` is the cell\'s own\n `--dsw-alias-label-tertiary` \u2014 the ink the spinner paints with \u2014 so the still\n ring needs no colour of its own and follows the host theme.\n\n The seat\'s outlet is what the `:has()` guard reads: `renderSlot` mounts a\n `div[data-slot]` whose children are the registered entries, so an *empty*\n outlet is exactly "idle, and nothing else asked for this cell". A row whose\n seat is taken keeps its own mark and never gets a ring behind it \u2014 the\n shipped `ui-schedule` puts its task mark there. Archived and provisional rows\n render no seat at all, and the archived row\'s own archive mark stays the one\n thing in that cell; the `:not(.jJkEga_archived)` is that guarantee written\n down rather than left to the host\'s current shape. */\nhtml[data-dsh-ccd-style="true"] .jJkEga_sessionRow:not(.jJkEga_archived) > .jJkEga_slot:has(> div[data-slot="sidebar.session.row.leading"]:empty)::before {\n content: "";\n width: 8px;\n height: 8px;\n background-color: currentColor;\n mask: var(--ccd-icon-session-ring) center / 8px 8px no-repeat;\n}\n\nhtml[data-dsh-ccd-style="true"] .jJkEga_sessionRow .jJkEga_title {\n font-size: var(--ccd-font-size-muted);\n line-height: 18px;\n color: var(--ccd-text);\n}\n\nhtml[data-dsh-ccd-style="true"] .jJkEga_projectRow,\nhtml[data-dsh-ccd-style="true"] .jJkEga_sessionRow {\n border-radius: 8px;\n}\n\nhtml[data-dsh-ccd-style="true"] .jJkEga_sessionRow:hover,\nhtml[data-dsh-ccd-style="true"] .jJkEga_sessionRow.jJkEga_selected {\n background: var(--ccd-selected);\n}\n\nhtml[data-dsh-ccd-style="true"] .jJkEga_time {\n color: var(--ccd-text-muted);\n}\n\n/* Archived rows. The host fades an archived title with its own caption ink, but\n that pair is one class lighter than the title rule above, so the grey never\n reaches the screen. The state is restated here a step heavier than that rule\n and covers the whole line: title, time and the leading mark together, since a\n full-strength time under a faded title would read as the louder half. The host\n keeps the rest of the state \u2014 hover card, restore button and the click that\n clears the class \u2014 so a restored row simply leaves this block behind. */\nhtml[data-dsh-ccd-style="true"] .jJkEga_sessionRow.jJkEga_archived :is(.jJkEga_title, .jJkEga_time, .jJkEga_slot) {\n color: var(--ccd-text-archived);\n}\n\n/* The host leaves an archived row\'s leading cell empty on purpose \u2014 neither a\n status dot nor a decoration renders there \u2014 so the mark is painted here from\n the shared archive mask. That cell is the same 16x20 inline-flex box every\n status dot is centred in, and the row (28px, `align-items: center`) centres\n the cell on its own line, so a mark centred in the cell lands on the row\'s\n centre line and on the dot\'s axis.\n\n What has to land there is the mark\'s ink, not the square it was drawn in. The\n host artwork is a 1px stroke on a 16-unit canvas and its paths span x 1..15\n and y 2..15 \u2014 the lid\'s top edge sits at y 2.5, the body ends at y 14.5 \u2014 so\n the ink is 14 x 13 units with its centre 0.5 unit below the canvas centre (it\n is already symmetric across x 8, so only y needs saying). Painted at 14px one\n unit is 0.875px, so a box-centred mark hangs 0.4375px low: 0.875 device pixels\n at 2x, which is the whole of the bias the eye picked up, the air above the\n mark being 1.75px against 0.875px below it. Half a unit is given back to the\n top, which leaves 1.3125px of air on either side. `center` keeps the image\n centred in the cell for any box and image size and the -0.4375px is the\n artwork\'s own offset read off that geometry, so neither term is fitted by eye.\n\n The correction rides on the mask\'s placement rather than on the box or on the\n artwork: the flex line still centres the same 14px square the status dot\'s\n cell centres, `--ccd-icon-archive` stays the host\'s geometry verbatim (a later\n DSH build can be re-read into that token without this being lost with it), and\n the placement is applied on every paint instead of being baked into a\n rasterised mark. It is a second declaration rather than part of the shorthand\n so artwork, size and repeat stay on one line \u2014 and so an engine that ignored\n it would still paint a half-unit-low mark instead of painting none. */\nhtml[data-dsh-ccd-style="true"] .jJkEga_sessionRow.jJkEga_archived .jJkEga_slot::before {\n content: "";\n width: 14px;\n height: 14px;\n background-color: currentColor;\n mask: var(--ccd-icon-archive) center / 14px 14px no-repeat;\n mask-position: center calc(50% - 0.4375px);\n}\n\n/* Pointer or current row: hover and selection paint a fill a step darker than\n the sidebar the faded ink was chosen against, and an archived Session can be\n the current one, so the ink steps back up while the row is being acted on. It\n stays short of the unarchived row\'s own ink, which is what keeps the archived\n state readable instead of merely faint. */\nhtml[data-dsh-ccd-style="true"] .jJkEga_sessionRow.jJkEga_archived:is(:hover, .jJkEga_selected) :is(.jJkEga_title, .jJkEga_time, .jJkEga_slot) {\n color: var(--ccd-text-archived-hover);\n}\n\n/* Provisional New Session row. DSH creates the Session with the click on New\n Session and keeps the row in the list while the host still calls it `blank`;\n the column shows the new-session page alone and lets the entry arrive with\n the first sent message. `compat/blank-session-rows.ts` writes the marker from\n the host\'s own `blank` fact \u2014 the stylesheet only hides what it tagged, so an\n untagged (native or unsupported) row is never touched. The row keeps its\n place in the host\'s list model; only the list\'s presentation drops it.\n\n This is the marker\'s half of the filter, and its fallback. The module also\n writes the ids into a plugin-owned sheet keyed by the row key, because\n `ui-workspace`\'s AnimatedRows measures every keyed row inside the commit that\n mounts it: a row a marker reaches one microtask later is a row the host has\n already measured, and the height it then drops out of the flow is animated\n as a jump instead of never being laid out (04). Both halves hide exactly the\n ids the host still calls `blank`, and neither touches a fork child. */\nhtml[data-dsh-ccd-style="true"] [data-row-key^="session:"][data-ccd-blank-session] {\n display: none;\n}\n\n/* Foot: the settings entry reads as the reference\'s account row. */\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_footArea {\n padding-top: 6px;\n border-top: 1px solid var(--ccd-border);\n}\n\nhtml[data-dsh-ccd-style="true"] .Dws9Sa_triggerRow {\n margin: 0;\n}\n\nhtml[data-dsh-ccd-style="true"] .Dws9Sa_trigger {\n border-radius: var(--ccd-radius-row);\n color: var(--ccd-text);\n font-size: var(--ccd-font-size-muted);\n}\n\nhtml[data-dsh-ccd-style="true"]:not([data-platform="darwin"]) ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_toggle {\n /* Non-macOS keeps the native brand-row / icon-rail handoff. */\n margin-left: auto;\n}\n\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_iconButton {\n border-radius: 8px;\n color: var(--ccd-text-secondary);\n}\n\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root:not(._3WPZCG_collapsed) ._3WPZCG_iconButton:hover {\n background: var(--ccd-hover);\n}\n\n/* A Workspace with nothing to show.\n *\n * `compat/empty-groups.ts` marks the group container \u2014 the box holding a\n * Workspace\'s row, its nested groups and its Session rows \u2014 and this is the\n * whole of what the plugin\'s `\u663E\u793A\u7A7A\u5206\u7EC4` view option does: off by default, a\n * Workspace whose rows are all filtered away no longer stands as a heading over\n * nothing. The marker is an attribute rather than a style written per element,\n * so one rule answers every re-render the host makes, and clearing the marker is\n * all it takes to bring the group back. */\nhtml[data-dsh-ccd-style="true"] [data-ccd-empty-group] {\n display: none;\n}\n\n/* Center the prompt in the space between navigation and the account footer. */\nhtml[data-dsh-ccd-style="true"] [data-ccd-list-empty] {\n position: relative;\n}\n\nhtml[data-dsh-ccd-style="true"] .ccd-sidebar-empty {\n position: absolute;\n inset: 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 12px;\n color: var(--ccd-text-muted);\n text-align: center;\n font-size: 12px;\n line-height: 20px;\n}\n\nhtml[data-dsh-ccd-style="true"] .ccd-sidebar-empty-pixels {\n flex: none;\n width: 88px;\n height: 80px;\n margin-top: 10px;\n fill: currentColor;\n}\n\nhtml[data-dsh-ccd-style="true"] .ccd-sidebar-empty button {\n border: 0;\n background: transparent;\n padding: 0;\n color: var(--ccd-text-secondary);\n font: inherit;\n text-decoration: none;\n cursor: pointer;\n}\n\nhtml[data-dsh-ccd-style="true"] .ccd-sidebar-empty button:hover {\n color: var(--ccd-text);\n}\n\nhtml[data-dsh-ccd-style="true"] .ccd-sidebar-empty button:focus-visible {\n outline: 1px solid var(--ccd-accent);\n outline-offset: 2px;\n border-radius: 4px;\n}\n\nhtml[data-dsh-ccd-style="true"] [data-ccd-list-empty] [data-row-key="empty"] {\n display: none;\n}\n';
|
|
5925
|
+
|
|
5926
|
+
// src/client/features/sidebar/navigation.css
|
|
5927
|
+
var navigation_default = '/* Replace only the decorated native glyphs; all click/focus/selection remains\n owned by DSH. A 16px seat preserves the existing text column in both layouts. */\nhtml[data-dsh-ccd-style="true"] [data-ccd-nav="newSession"] ._3WPZCG_newSessionContent > svg,\nhtml[data-dsh-ccd-style="true"] [data-ccd-nav] ._3WPZCG_panelGlyph > [data-slot="sidebar.panellist"] svg {\n display: none;\n}\n\nhtml[data-dsh-ccd-style="true"] [data-ccd-nav] .ccd-nav-glyph {\n display: inline-flex;\n flex: none;\n align-items: center;\n justify-content: center;\n width: 16px;\n height: 16px;\n pointer-events: none;\n}\n\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root [data-ccd-nav] .ccd-nav-glyph > svg {\n flex: none;\n width: 16px;\n height: 16px;\n margin: 0;\n overflow: visible;\n}\n\nhtml[data-dsh-ccd-style="true"] ._3WPZCG_root [data-ccd-nav="newSession"] .ccd-nav-glyph > svg {\n width: 18px;\n height: 18px;\n}\n\nhtml[data-dsh-ccd-style="true"] .ccd-nav-glyph :is(.customize-case, .routine-long-hand) {\n transform-box: view-box;\n transform-origin: 12px 12px;\n transition: transform 280ms cubic-bezier(0.2, 0.8, 0.2, 1);\n}\n\nhtml[data-dsh-ccd-style="true"] .ccd-nav-glyph .icon-disc {\n transition: opacity 180ms ease;\n}\n\nhtml[data-dsh-ccd-style="true"] [data-ccd-nav]:is(:hover, :focus-visible, [aria-current="page"]) .customize-case {\n transform: rotate(-6deg);\n}\n\nhtml[data-dsh-ccd-style="true"] [data-ccd-nav]:is(:hover, :focus-visible, [aria-current="page"]) .routine-long-hand {\n transform: rotate(45deg);\n}\n\nhtml[data-dsh-ccd-style="true"] [data-ccd-nav]:is(:hover, :focus-visible) .icon-disc {\n opacity: 0;\n}\n\n@media (prefers-reduced-motion: reduce) {\n html[data-dsh-ccd-style="true"] .ccd-nav-glyph :is(.customize-case, .routine-long-hand, .icon-disc) {\n transition: none;\n }\n}\n';
|
|
5928
|
+
|
|
5929
|
+
// src/client/features/sidebar/navigation-icons.ts
|
|
5930
|
+
var SIDEBAR_NAVIGATION_ICONS = Object.freeze({
|
|
5931
|
+
newSession: '<svg xmlns="http://www.w3.org/2000/svg" class="claude-sidebar-icon" data-icon="new" width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true">\n <circle class="icon-disc" cx="12" cy="12" r="12" fill="currentColor" fill-opacity="0.09"/>\n <path d="M12 7V17M7 12H17" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>\n</svg>',
|
|
5932
|
+
plugins: '<svg xmlns="http://www.w3.org/2000/svg" class="claude-sidebar-icon" data-icon="customize" width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true">\n <g class="customize-case" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round">\n <path d="M8 7V5.2C8 4.54 8.54 4 9.2 4H14.8C15.46 4 16 4.54 16 5.2V7"/>\n <rect x="3" y="7" width="18" height="12.5" rx="1.5"/>\n <path d="M3 13.5H7M9.5 13.5H14.5M17 13.5H21"/>\n </g>\n</svg>',
|
|
5933
|
+
routines: '<svg xmlns="http://www.w3.org/2000/svg" class="claude-sidebar-icon" data-icon="routines" width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true">\n <g stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round">\n <circle cx="12" cy="12" r="9"/>\n <path class="routine-long-hand" d="M12 12V7.4"/>\n <path d="M12 12L14.8 13.75"/>\n </g>\n</svg>'
|
|
5934
|
+
});
|
|
3886
5935
|
|
|
3887
5936
|
// src/client/features/sidebar/index.ts
|
|
3888
5937
|
var sidebarFeature = {
|
|
@@ -3894,6 +5943,12 @@ var sidebarFeature = {
|
|
|
3894
5943
|
if (!probe.sidebar) environment.logger.debug("sidebar: column not mounted yet");
|
|
3895
5944
|
scope.add(environment.dom.mountStyles(sidebar_default));
|
|
3896
5945
|
scope.add(environment.dom.mountStyles(account_menu_default));
|
|
5946
|
+
scope.add(environment.dom.mountStyles(navigation_default));
|
|
5947
|
+
scope.add(mountSidebarNavigation(
|
|
5948
|
+
document,
|
|
5949
|
+
SIDEBAR_NAVIGATION_ICONS,
|
|
5950
|
+
(error) => environment.logger.error("sidebar: navigation observer failed", error)
|
|
5951
|
+
));
|
|
3897
5952
|
scope.add(mountAccountMenu(
|
|
3898
5953
|
document,
|
|
3899
5954
|
(error) => environment.logger.error("sidebar: account menu observer failed", error)
|
|
@@ -4174,7 +6229,7 @@ function round(value) {
|
|
|
4174
6229
|
}
|
|
4175
6230
|
|
|
4176
6231
|
// src/client/features/conversation/conversation.css
|
|
4177
|
-
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\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\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: 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 */\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 2px;\n padding: 0;\n border-radius: var(--ccd-radius-row);\n background: var(--ccd-track);\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: 0;\n left: 0;\n box-sizing: border-box;\n width: var(--ccd-view-w, 0px);\n height: 100%;\n transform: translateX(var(--ccd-view-x, 0px));\n border: 1px solid var(--ccd-border-soft);\n border-radius: var(--ccd-radius-row);\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 flex: 0 0 40px;\n width: 40px;\n height: 22px;\n padding: 0;\n border: 0;\n border-radius: var(--ccd-radius-row);\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\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@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 keeps every control at 24px down to the collapsed\n * macOS shell\'s 400px column; below 420px the three views this plugin adds \u2014\n * the project folder among them \u2014 step aside so the native team entry keeps its\n * full label. All three stay reachable from the right panel\'s own guide tab and\n * from their shortcuts.\n */\n@container ccd-conversation (max-width: 520px) {\n html[data-dsh-ccd-style="true"] .ST7X_W_header {\n padding-right: var(--ccd-header-trailing-inset-compact);\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@container ccd-conversation (max-width: 420px) {\n html[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities [data-ccd-header-view] {\n display: none;\n }\n\n /* At this width the title, the View switcher, the native team entry and the\n cluster cannot share one row. The title is the only piece the sidebar also\n shows, so it yields \u2014 the same trade the collapsed macOS shell already\n makes \u2014 and every native control keeps its full label. */\n html[data-dsh-ccd-style="true"] .ST7X_W_header:not(.ST7X_W_headerBlank) .ST7X_W_crumbs {\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 line-height: inherit;\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/* 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 * 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 * Everything else stays native: syntax colours, the language label and the\n * copy/wrap controls.\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 */\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}\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 * 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. */\n font-size: inherit;\n line-height: inherit;\n padding: 4px 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';
|
|
6232
|
+
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\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\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: 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 */\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 2px;\n padding: 0;\n border-radius: var(--ccd-radius-row);\n background: var(--ccd-track);\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: 0;\n left: 0;\n box-sizing: border-box;\n width: var(--ccd-view-w, 0px);\n height: 100%;\n transform: translateX(var(--ccd-view-x, 0px));\n border: 1px solid var(--ccd-border-soft);\n border-radius: var(--ccd-radius-row);\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 flex: 0 0 40px;\n width: 40px;\n height: 22px;\n padding: 0;\n border: 0;\n border-radius: var(--ccd-radius-row);\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\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@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 keeps every control at 24px down to the collapsed\n * macOS shell\'s 400px column; below 420px the three views this plugin adds \u2014\n * the project folder among them \u2014 step aside so the native team entry keeps its\n * full label. All three stay reachable from the right panel\'s own guide tab and\n * from their shortcuts.\n */\n@container ccd-conversation (max-width: 520px) {\n html[data-dsh-ccd-style="true"] .ST7X_W_header {\n padding-right: var(--ccd-header-trailing-inset-compact);\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@container ccd-conversation (max-width: 420px) {\n html[data-dsh-ccd-style="true"] .ST7X_W_headerUtilities [data-ccd-header-view] {\n display: none;\n }\n\n /* At this width the title, the View switcher, the native team entry and the\n cluster cannot share one row. The title is the only piece the sidebar also\n shows, so it yields \u2014 the same trade the collapsed macOS shell already\n makes \u2014 and every native control keeps its full label. */\n html[data-dsh-ccd-style="true"] .ST7X_W_header:not(.ST7X_W_headerBlank) .ST7X_W_crumbs {\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';
|
|
4178
6233
|
|
|
4179
6234
|
// src/client/features/conversation/index.ts
|
|
4180
6235
|
var conversationFeature = {
|
|
@@ -4618,7 +6673,7 @@ var DAY = new Intl.DateTimeFormat(LOCALE, { month: "short", day: "numeric", time
|
|
|
4618
6673
|
function formatCount(value) {
|
|
4619
6674
|
return COUNT.format(Math.round(value));
|
|
4620
6675
|
}
|
|
4621
|
-
function
|
|
6676
|
+
function formatTokens2(value) {
|
|
4622
6677
|
const total = Math.max(0, value);
|
|
4623
6678
|
const units = [[1e9, "B"], [1e6, "M"], [1e3, "k"]];
|
|
4624
6679
|
for (const [scale, suffix] of units) {
|
|
@@ -4646,8 +6701,8 @@ function heatLevel(value, peak) {
|
|
|
4646
6701
|
if (value <= 0 || peak <= 0) return 0;
|
|
4647
6702
|
return Math.min(4, Math.max(1, Math.ceil(value / peak * 4)));
|
|
4648
6703
|
}
|
|
4649
|
-
function heatTip(date,
|
|
4650
|
-
return `${formatDayLabel(date)} \u2014 ${formatCount(
|
|
6704
|
+
function heatTip(date, count2) {
|
|
6705
|
+
return `${formatDayLabel(date)} \u2014 ${formatCount(count2)}`;
|
|
4651
6706
|
}
|
|
4652
6707
|
function createSegments(document2, label, options, active, onSelect) {
|
|
4653
6708
|
const group = document2.createElement("div");
|
|
@@ -4747,7 +6802,7 @@ function createModels(document2, data, tips) {
|
|
|
4747
6802
|
title: formatDayLabel(day.date),
|
|
4748
6803
|
rows: day.segments.map((segment) => ({
|
|
4749
6804
|
color: segment.color,
|
|
4750
|
-
text: `${segment.model}: ${
|
|
6805
|
+
text: `${segment.model}: ${formatTokens2(segment.tokens)}`
|
|
4751
6806
|
}))
|
|
4752
6807
|
});
|
|
4753
6808
|
plot.append(column);
|
|
@@ -4945,11 +7000,11 @@ function readPairs(value, key) {
|
|
|
4945
7000
|
const pairs = [];
|
|
4946
7001
|
for (const entry of value) {
|
|
4947
7002
|
if (!Array.isArray(entry) || entry.length !== 2) continue;
|
|
4948
|
-
const [rawKey,
|
|
4949
|
-
if (!isCount(
|
|
7003
|
+
const [rawKey, count2] = entry;
|
|
7004
|
+
if (!isCount(count2)) continue;
|
|
4950
7005
|
const parsed = key(rawKey);
|
|
4951
7006
|
if (parsed === null) continue;
|
|
4952
|
-
pairs.push([parsed,
|
|
7007
|
+
pairs.push([parsed, count2]);
|
|
4953
7008
|
}
|
|
4954
7009
|
return pairs;
|
|
4955
7010
|
}
|
|
@@ -5078,16 +7133,16 @@ function heatWindow(days, now = /* @__PURE__ */ new Date()) {
|
|
|
5078
7133
|
date.setDate(start.getDate() + index);
|
|
5079
7134
|
const key = localDayKey(date.getTime());
|
|
5080
7135
|
const future = date > today;
|
|
5081
|
-
const
|
|
7136
|
+
const count2 = future ? 0 : counts.get(key) ?? 0;
|
|
5082
7137
|
if (future && futureFrom === HEAT_DAYS) futureFrom = index;
|
|
5083
7138
|
keys.push(key);
|
|
5084
|
-
raw.push(
|
|
5085
|
-
peak = Math.max(peak,
|
|
7139
|
+
raw.push(count2);
|
|
7140
|
+
peak = Math.max(peak, count2);
|
|
5086
7141
|
}
|
|
5087
|
-
const heat = raw.map((
|
|
7142
|
+
const heat = raw.map((count2, index) => ({
|
|
5088
7143
|
date: keys[index] ?? "",
|
|
5089
|
-
count,
|
|
5090
|
-
level: index >= futureFrom ? null : heatLevel(
|
|
7144
|
+
count: count2,
|
|
7145
|
+
level: index >= futureFrom ? null : heatLevel(count2, peak)
|
|
5091
7146
|
}));
|
|
5092
7147
|
return { heat, futureFrom };
|
|
5093
7148
|
}
|
|
@@ -5115,7 +7170,7 @@ function buildOverview(snapshot, range, random, now = /* @__PURE__ */ new Date()
|
|
|
5115
7170
|
const tiles = [
|
|
5116
7171
|
{ label: "Sessions", value: formatCount(totals.sessions) },
|
|
5117
7172
|
{ label: "Messages", value: formatCount(totals.messages) },
|
|
5118
|
-
{ label: "Total tokens", value:
|
|
7173
|
+
{ label: "Total tokens", value: formatTokens2(totals.tokens) },
|
|
5119
7174
|
{ label: "Active days", value: formatCount(totals.activeDays) },
|
|
5120
7175
|
{ label: "Peak hour", value: snapshot.busyHour === null ? EMPTY : formatHour(snapshot.busyHour) },
|
|
5121
7176
|
{ label: "Favorite model", value: totals.topModel ?? EMPTY, small: true }
|
|
@@ -5159,12 +7214,12 @@ function buildModels(snapshot, range, now = /* @__PURE__ */ new Date()) {
|
|
|
5159
7214
|
return {
|
|
5160
7215
|
model,
|
|
5161
7216
|
color: modelColor(at),
|
|
5162
|
-
usage: `${
|
|
7217
|
+
usage: `${formatTokens2(counters.input)} in \xB7 ${formatTokens2(counters.output)} out`,
|
|
5163
7218
|
percent: `${(tokens / (totals.tokens || 1) * 100).toFixed(1)}%`
|
|
5164
7219
|
};
|
|
5165
7220
|
});
|
|
5166
7221
|
return {
|
|
5167
|
-
ticks: steps.map(
|
|
7222
|
+
ticks: steps.map(formatTokens2),
|
|
5168
7223
|
top: steps.at(-1) ?? 1,
|
|
5169
7224
|
days,
|
|
5170
7225
|
legend,
|
|
@@ -5386,7 +7441,7 @@ var FEATURES = [
|
|
|
5386
7441
|
toolCallsFeature,
|
|
5387
7442
|
statisticsFeature
|
|
5388
7443
|
];
|
|
5389
|
-
var inject = ["slots", "theme", "uiWorkspace", "configForms", "sessions"];
|
|
7444
|
+
var inject = ["slots", "theme", "uiWorkspace", "configForms", "sessions", "workspaces"];
|
|
5390
7445
|
function apply(ctx) {
|
|
5391
7446
|
if (typeof document === "undefined") return;
|
|
5392
7447
|
ctx.effect(() => {
|
|
@@ -5434,7 +7489,8 @@ function apply(ctx) {
|
|
|
5434
7489
|
));
|
|
5435
7490
|
scope.add(mountComposerStats(
|
|
5436
7491
|
document,
|
|
5437
|
-
(error) => logger.error("composer: statistics readout observer failed", error)
|
|
7492
|
+
(error) => logger.error("composer: statistics readout observer failed", error),
|
|
7493
|
+
next.features["composer-stats"]
|
|
5438
7494
|
));
|
|
5439
7495
|
scope.add(mountComposerStatusOrder(
|
|
5440
7496
|
document,
|
|
@@ -5444,6 +7500,10 @@ function apply(ctx) {
|
|
|
5444
7500
|
document,
|
|
5445
7501
|
(error) => logger.error("composer: placeholder observer failed", error)
|
|
5446
7502
|
));
|
|
7503
|
+
scope.add(mountPermissionMenu(
|
|
7504
|
+
document,
|
|
7505
|
+
(error) => logger.error("composer: permission card observer failed", error)
|
|
7506
|
+
));
|
|
5447
7507
|
scope.add(mountWorkspaceMenu(
|
|
5448
7508
|
document,
|
|
5449
7509
|
(error) => logger.error("workspace menu: label rewrite failed", error)
|
|
@@ -5461,6 +7521,18 @@ function apply(ctx) {
|
|
|
5461
7521
|
if (next.features["composer-pet"]) {
|
|
5462
7522
|
mountComposerPet(ctx, environment, scope);
|
|
5463
7523
|
}
|
|
7524
|
+
if (next.features.sidebar) {
|
|
7525
|
+
const view = mountViewOptions(ctx, environment, scope);
|
|
7526
|
+
scope.add(mountEmptyGroups(
|
|
7527
|
+
document,
|
|
7528
|
+
!next.view.showEmptyGroups,
|
|
7529
|
+
(error) => logger.error("sidebar: empty-group observer failed", error),
|
|
7530
|
+
{ source: host.sidebarSessions, settings: view.settings, showAll: view.showAllSessions }
|
|
7531
|
+
));
|
|
7532
|
+
}
|
|
7533
|
+
if (next.features["context-panel"]) {
|
|
7534
|
+
mountContextPanel(ctx, environment, scope);
|
|
7535
|
+
}
|
|
5464
7536
|
mountFeatures(FEATURES, environment, scope);
|
|
5465
7537
|
scope.add(watchHostBuild(document, (status) => {
|
|
5466
7538
|
if (status.state !== "unknown") return;
|