dsh-claude-style 0.2.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -1,16 +1,39 @@
1
1
  /**
2
2
  * Claude Style — Claude Code Desktop theme for the DeepSeek Harness web GUI.
3
3
  *
4
- * GENERATED FILE — do not edit. Source lives in src/ (JS zones as fragments,
5
- * stylesheets as plain CSS); `node scripts/build.mjs` assembles this bundle.
6
- * - src/constants.js Zone 1: Constants & Tokens
4
+ * GENERATED FILE — do not edit. Source lives in src/ as feature fragments;
5
+ * `node scripts/build.mjs` assembles this bundle.
6
+ *
7
+ * JS fragments (in assembly order):
7
8
  * - src/assets/*.svg Brand marks (inlined as CSS url() data URIs at build time)
8
- * - src/styles/*.css Zone 2: Stylesheets (tokens, typography, chrome,
9
- * composer, sidebar, components)
10
- * - src/context.js Zone 3: DSH Context & Helpers
11
- * - src/overrides.js Zone 4+5: UI Overrides, Scheduler & Teardown
12
- * - src/settings.js Settings Section (Brand)
13
- * - src/entry.js Zone 6: Plugin Entry & Export
9
+ * * - src/constants.js
10
+ * - src/context/host.js
11
+ * - src/context/prefs.js
12
+ * - src/context/model-copy.js
13
+ * - src/context/i18n.js
14
+ * - src/overrides/popover-utils.js
15
+ * - src/overrides/copy.js
16
+ * - src/overrides/permissions.js
17
+ * - src/overrides/model-picker.js
18
+ * - src/overrides/account-footer.js
19
+ * - src/overrides/scheduler.js
20
+ * - src/settings.js
21
+ * - src/entry.js
22
+ *
23
+ * Stylesheets (in assembly order):
24
+ * * - src/styles/tokens.css
25
+ * - src/styles/typography.css
26
+ * - src/styles/chrome.css
27
+ * - src/styles/composer/hero.css
28
+ * - src/styles/composer/card.css
29
+ * - src/styles/composer/inline.css
30
+ * - src/styles/sidebar.css
31
+ * - src/styles/components/permissions.css
32
+ * - src/styles/components/account-footer.css
33
+ * - src/styles/components/model-picker.css
34
+ * - src/styles/components/footer-takeover.css
35
+ * - src/styles/components/third-party.css
36
+ * - src/styles/components/settings.css
14
37
  */
15
38
  window.__ModuleLoader__.load({
16
39
  id: 'dsh-claude-style',
@@ -23,30 +46,39 @@ window.__ModuleLoader__.load({
23
46
  // section can be a real component without a host half.
24
47
  var React = require('react')
25
48
 
49
+
26
50
  // ============================================================================
27
- // Zone 1: 常量与配置定义 (Constants & Tokens)
51
+ // 常量与配置定义 (Constants & Tokens)
28
52
  // ============================================================================
29
53
  var STYLE_ID = 'dsh-claude-style-style'
30
54
 
31
55
  var COMPOSER_HINT = 'How can I help you today?'
32
56
 
33
57
  /**
34
- * Model-picker copy. The host's model catalog only carries English
35
- * descriptions for its two builtin DeepSeek models, so the skin mirrors
36
- * the host dictionary's zh strings for them (raw descriptions otherwise).
37
- * `deepseek-official` is the provider group id of DeepSeek's own service.
58
+ * Model picker copy.
59
+ *
60
+ * The copy itself is NOT here. It ships as `model-descriptions.json` beside
61
+ * the bundle, and the browser half fetches it at runtime (the host half
62
+ * serves it under MODEL_COPY_ROUTE), so the model table grows without a
63
+ * rebuild and no copy enters the bundle. The language comes from the shell's
64
+ * own `locale` service — one line per row, in the language the rest of the
65
+ * UI is in — never two languages stacked.
66
+ *
67
+ * The constants below are the neutral fallbacks painted before that document
68
+ * arrives, and kept if it never does. They are English because a failed
69
+ * fetch has no locale to honour.
38
70
  */
39
71
  var MODEL_OFFICIAL_GROUP = 'deepseek-official'
40
- var MODEL_FALLBACK_LABEL = '选择模型'
41
- var MODEL_LOADING_LABEL = '正在加载模型…'
42
- var MODEL_EMPTY_LABEL = '没有可用的模型。'
43
- var MODEL_EFFORT_LABEL = '推理等级'
72
+ var MODEL_COPY_ROUTE = '/dsh-claude-style/model-descriptions.json'
73
+ var MODEL_COPY_FALLBACK_LOCALE = 'en'
74
+ var MODEL_FALLBACK_LABEL = 'Select model'
75
+ var MODEL_LOADING_LABEL = 'Loading models…'
76
+ var MODEL_EMPTY_LABEL = 'No models available.'
77
+ var MODEL_EFFORT_LABEL = 'Reasoning effort'
44
78
  var MODEL_EFFORT_DEFAULT = 'Default'
45
79
  var MODEL_MORE_LABEL = 'More models'
46
- var MODEL_DESCRIPTIONS = {
47
- 'deepseek-official/deepseek-v4-flash': '快速、高效且经济;适合目标明确、常规或并行任务。',
48
- 'deepseek-official/deepseek-v4-pro': '更强的自主编码、知识与复杂推理能力;适合复杂或质量优先的任务,但成本更高。',
49
- }
80
+ var MODEL_TRIGGER_LABEL = 'Select model, currently {model}'
81
+ var MODEL_NO_EFFORT_LABEL = 'This model offers no reasoning levels.'
50
82
 
51
83
  /** English weekday names, indexed by Date#getDay() (0 = Sunday). */
52
84
  var WEEKDAY_NAMES = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
@@ -154,24 +186,47 @@ window.__ModuleLoader__.load({
154
186
  var SEGMENTS_CLASS = 'dsh-claude-segments'
155
187
  var SEGMENT_CLASS = 'dsh-claude-segment'
156
188
 
189
+ /**
190
+ * Preferences, persisted in the host settings namespace `claude-style`
191
+ * (lib/index.js owns it; src/context/prefs.js reads and writes it). Each value is
192
+ * mirrored onto the document as an attribute so the stylesheet decides what
193
+ * a preference means, and the defaults here are the shipped behaviour.
194
+ */
195
+
157
196
  /** Brand marks selectable from the settings page. `claude` is the default. */
158
197
  var BRAND_CLAUDE = 'claude'
159
198
  var BRAND_ANTHROPIC = 'anthropic'
199
+ /** Leave the brand area entirely to the host: neither brand variant matches. */
200
+ var BRAND_OFF = 'off'
160
201
  var DEFAULT_BRAND = BRAND_CLAUDE
161
- /** localStorage key holding the chosen brand. */
162
- var BRAND_STORAGE_KEY = 'dsh-claude-style:brand'
163
202
  /** The document attribute the stylesheet switches on. */
164
203
  var BRAND_ATTR = 'data-dsh-claude-brand'
165
204
 
205
+ /** Present while the skin takes over the sidebar footer (settings area + account row). */
206
+ var FOOTER_ATTR = 'data-dsh-claude-footer-takeover'
207
+ /** Present while the composer restyle applies to the page currently shown. */
208
+ var COMPOSER_ATTR = 'data-dsh-claude-composer-active'
209
+ /** Composer surfaces the restyle may cover, in settings order. */
210
+ var COMPOSER_SCOPES = ['off', 'hero', 'conversation', 'all']
211
+ /** Route the browser half reads and writes preferences through (lib/index.js). */
212
+ var PREFS_ROUTE = '/dsh-claude-style/prefs'
213
+
166
214
  /** Wordmark aspect ratio; scripts/build.mjs sizes the sidebar word height from it (geometry lives in src/assets/claude-word.svg). */
167
215
  var CLAUDE_WORD_ASPECT = 512.22 / 121.54
168
216
 
169
217
  var SANS = "'Anthropic Sans Web Text','Noto Sans SC','Source Han Sans SC',-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Hiragino Sans GB','Microsoft YaHei','Helvetica Neue',Helvetica,Arial,sans-serif"
170
218
  var SERIF = "'Anthropic Serif Web Text',Georgia,'Times New Roman','Noto Serif SC','Source Han Serif SC','Songti SC','SimSun',serif"
219
+ /**
220
+ * Conversation prose: Claude sets Latin text in the serif face and lets
221
+ * Chinese fall through to a sans CJK — the serif Latin faces carry no CJK
222
+ * glyphs, so the stack leads with serif and names the sans CJK families
223
+ * after it. UI chrome keeps SANS; only markdown prose uses this.
224
+ */
225
+ var PROSE = "'Anthropic Serif Web Text',Georgia,'Times New Roman','Noto Sans SC','Source Han Sans SC','PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif"
171
226
  var MONO = "'Anthropic Mono Variable',ui-monospace,'SF Mono','JetBrains Mono','Fira Code',Consolas,'Liberation Mono',Menlo,Courier,monospace"
172
227
 
173
228
  // ============================================================================
174
- // Zone 2: 样式表(由 src/styles/*.css 内联生成,勿手改) (CSS Stylesheet)
229
+ // 样式表(由 src/styles/*.css 内联生成,勿手改) (CSS Stylesheet)
175
230
  // ============================================================================
176
231
  var CSS = [
177
232
  "/* --- 2.1 Design Tokens (Dark Base & Light Overrides) --- */",
@@ -179,19 +234,16 @@ window.__ModuleLoader__.load({
179
234
  "body[data-dsh-claude-style] {",
180
235
  " --dsw-font-family: 'Anthropic Sans Web Text','Noto Sans SC','Source Han Sans SC',-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Hiragino Sans GB','Microsoft YaHei','Helvetica Neue',Helvetica,Arial,sans-serif;",
181
236
  " --dsw-font-serif: 'Anthropic Serif Web Text',Georgia,'Times New Roman','Noto Serif SC','Source Han Serif SC','Songti SC','SimSun',serif;",
237
+ " --dsw-font-prose: 'Anthropic Serif Web Text',Georgia,'Times New Roman','Noto Sans SC','Source Han Sans SC','PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif;",
182
238
  " --dsw-font-code: 'Anthropic Mono Variable',ui-monospace,'SF Mono','JetBrains Mono','Fira Code',Consolas,'Liberation Mono',Menlo,Courier,monospace;",
183
- " /* Markdown document headings keep the UI sans at 700 — the face the host",
184
- " ships and the one Claude uses. The host's markdown heading rule is a",
185
- " `font:` shorthand (`--dsw-font-markdown-hN`) that names --dsw-font-family",
186
- " directly, so overriding the four `-font-family` sub-tokens never reached",
187
- " the page; these re-declare the shorthand itself. The ladder is steepened",
188
- " from the host's 21/19/18 toward Claude's measured ≈21/17/15; h1 and h4",
189
- " keep the host's values. */",
190
- " --dsw-font-markdown-h2: 700 calc(18px + var(--dsh-content-font-delta)) / calc(26px + var(--dsh-content-font-delta)) var(--dsw-font-family);",
191
- " --dsw-font-markdown-h3: 700 calc(16px + var(--dsh-content-font-delta)) / calc(24px + var(--dsh-content-font-delta)) var(--dsw-font-family);",
192
- " --dsw-font-markdown-base-font-family: var(--dsw-font-family);",
193
- " --dsw-font-markdown-small-font-family: var(--dsw-font-family);",
194
- " --dsw-font-markdown-table-font-family: var(--dsw-font-family);",
239
+ " /* Markdown document headings keep the host's own ladder, steepened from",
240
+ " 21/19/18 toward Claude's measured ≈21/17/15; h1 and h4 keep the host's",
241
+ " values. The host reads these through a `font:` shorthand that names",
242
+ " --dsw-font-family directly, so overriding the `-font-family` sub-tokens",
243
+ " never reached the page — the shorthand itself is what has to be",
244
+ " re-declared. */",
245
+ " --dsw-font-markdown-h2: 700 calc(18px + var(--dsh-content-font-delta)) / calc(26px + var(--dsh-content-font-delta)) var(--dsw-font-prose);",
246
+ " --dsw-font-markdown-h3: 700 calc(16px + var(--dsh-content-font-delta)) / calc(24px + var(--dsh-content-font-delta)) var(--dsw-font-prose);",
195
247
  "}",
196
248
  "",
197
249
  "/* ---------- design tokens: warm-black (dark) palette ---------- */",
@@ -221,6 +273,13 @@ window.__ModuleLoader__.load({
221
273
  " --dsw-alias-button-info-hover: #e08a6d;",
222
274
  " --dsw-alias-interactive-bg-active: rgba(217, 119, 87, 0.30);",
223
275
  " --dsh-claude-hover-bg: rgba(255, 255, 255, 0.08);",
276
+ " --dsh-claude-inline-code-fg: #e8a08a;",
277
+ " --dsh-claude-link: #8ab4f8;",
278
+ " /* The host pins the code-block surface on `pre.shiki` with !important, so it",
279
+ " is only reachable through its own token; #1c1b1a is the value the skin's",
280
+ " (inert) `pre { background }` rule always meant. */",
281
+ " --dsw-alias-markdown-code-block: #1c1b1a;",
282
+ " --dsw-alias-markdown-code-block-banner: #242320;",
224
283
  " --dsw-alias-interactive-bg-hover: var(--dsh-claude-hover-bg);",
225
284
  " --dsw-alias-interactive-bg-hover-solid: #2e2c29;",
226
285
  " --dsw-alias-label-primary: #faf9f5;",
@@ -280,6 +339,13 @@ window.__ModuleLoader__.load({
280
339
  " /* User message bubbles: the host's light bubble token is deepseek-50 (blue);",
281
340
  " use the skin's button-hover gray instead. */",
282
341
  " --dsw-specific-bubble: var(--dsh-claude-hover-bg);",
342
+ " /* Markdown surfaces, measured off Claude: a white code block, a neutral-gray",
343
+ " inline-code chip, and blue links. The chip colour is the same gray the",
344
+ " components hover with, so the two read as one material. */",
345
+ " --dsw-alias-markdown-code-block: #ffffff;",
346
+ " --dsw-alias-markdown-code-block-banner: #ffffff;",
347
+ " --dsh-claude-inline-code-fg: #943333;",
348
+ " --dsh-claude-link: #184f95;",
283
349
  "}",
284
350
  "",
285
351
  "body[data-dsh-claude-style]:not([data-ds-dark-theme]) {",
@@ -330,14 +396,9 @@ window.__ModuleLoader__.load({
330
396
  "}",
331
397
  "",
332
398
  "body[data-dsh-claude-style]:not([data-ds-dark-theme]) pre {",
333
- " background: #f5f3ee;",
334
399
  " border-color: #e8e6dc;",
335
400
  "}",
336
401
  "",
337
- "body[data-dsh-claude-style]:not([data-ds-dark-theme]) code:not(pre code) {",
338
- " background: #e8e6dc;",
339
- "}",
340
- "",
341
402
  "body[data-dsh-claude-style]:not([data-ds-dark-theme]) table th {",
342
403
  " background: #f0eee6;",
343
404
  "}",
@@ -380,15 +441,26 @@ window.__ModuleLoader__.load({
380
441
  " letter-spacing: -0.015em;",
381
442
  "}",
382
443
  "",
383
- "/* ---------- markdown document headings ---------- */",
384
- "/* The serif display face above is for UI headlines. Document headings are body",
385
- " text: re-assert the host's own markdown heading shorthand (sans, 700) at a",
386
- " weight that beats that rule, so the host's size ladder and line-heights",
387
- " apply untouched. */",
388
- "body[data-dsh-claude-style] [class*=\"markdown\"] h1 { font: var(--dsw-font-markdown-h1); }",
389
- "body[data-dsh-claude-style] [class*=\"markdown\"] h2 { font: var(--dsw-font-markdown-h2); }",
390
- "body[data-dsh-claude-style] [class*=\"markdown\"] h3 { font: var(--dsw-font-markdown-h3); }",
391
- "body[data-dsh-claude-style] [class*=\"markdown\"] h4 { font: var(--dsw-font-markdown-h4); }",
444
+ "/* ---------- markdown document prose ---------- */",
445
+ "/* Claude sets conversation prose — body and headings alike — in the serif",
446
+ " face and lets Chinese fall through to a sans CJK; the serif display rule",
447
+ " above stays for UI headlines. */",
448
+ "body[data-dsh-claude-style] [class*=\"markdown\"] {",
449
+ " font-family: var(--dsw-font-prose);",
450
+ "}",
451
+ "",
452
+ "/* The host gives its markdown elements their own `font:` shorthand, so the",
453
+ " family has to be re-stated wherever those shorthands land. The heading",
454
+ " shorthand is re-asserted too, so the host's weight, size and line-height",
455
+ " survive the serif display rule above. */",
456
+ "body[data-dsh-claude-style] [class*=\"markdown\"] h1 { font: var(--dsw-font-markdown-h1); font-family: var(--dsw-font-prose); }",
457
+ "body[data-dsh-claude-style] [class*=\"markdown\"] h2 { font: var(--dsw-font-markdown-h2); font-family: var(--dsw-font-prose); }",
458
+ "body[data-dsh-claude-style] [class*=\"markdown\"] h3 { font: var(--dsw-font-markdown-h3); font-family: var(--dsw-font-prose); }",
459
+ "body[data-dsh-claude-style] [class*=\"markdown\"] h4 { font: var(--dsw-font-markdown-h4); font-family: var(--dsw-font-prose); }",
460
+ "",
461
+ "body[data-dsh-claude-style] [class*=\"markdown\"] :is(th, td) {",
462
+ " font-family: var(--dsw-font-prose);",
463
+ "}",
392
464
  "",
393
465
  "/* Heading rhythm. The host gives every heading a 32px top margin, which stacks",
394
466
  " on the previous heading's 16px bottom margin: two consecutive headings open",
@@ -420,10 +492,10 @@ window.__ModuleLoader__.load({
420
492
  "}",
421
493
  "",
422
494
  "/* ---------- editorial markdown ---------- */",
423
- "/* Claude's quote keeps the serif face but stands upright on a neutral bar and",
424
- " wash; the warm accent stays reserved for links and controls. */",
495
+ "/* Claude's quote keeps the prose face but stands upright on a neutral bar and",
496
+ " wash; the warm accent stays reserved for controls. */",
425
497
  "body[data-dsh-claude-style] blockquote {",
426
- " font-family: var(--dsw-font-serif);",
498
+ " font-family: var(--dsw-font-prose);",
427
499
  " font-style: normal;",
428
500
  " color: #b0aea5;",
429
501
  " border-left: 2px solid var(--dsw-alias-label-caption);",
@@ -432,19 +504,37 @@ window.__ModuleLoader__.load({
432
504
  " padding: 0.6em 1em;",
433
505
  "}",
434
506
  "",
507
+ "/* The fill rides the host's --dsw-alias-markdown-code-block token — the host",
508
+ " pins `pre.shiki` with !important, so a `background` here would never apply.",
509
+ " Only the frame is styled. */",
435
510
  "body[data-dsh-claude-style] pre {",
436
- " background: #1c1b1a;",
437
511
  " border: 1px solid #242320;",
438
512
  " border-radius: 8px;",
439
513
  "}",
440
514
  "",
515
+ "/* Claude's inline-code chip: warm red on the same neutral gray the components",
516
+ " hover with — both resolve per theme from the palette blocks. */",
441
517
  "body[data-dsh-claude-style] code:not(pre code) {",
442
- " background: #2e2c29;",
518
+ " background: var(--dsh-claude-hover-bg);",
519
+ " color: var(--dsh-claude-inline-code-fg);",
443
520
  " border-radius: 4px;",
444
521
  " padding: 0.15em 0.4em;",
445
522
  " font-size: 0.88em;",
446
523
  "}",
447
524
  "",
525
+ "/* Claude's links: blue, underlined at rest, and without the host's",
526
+ " link-category glyph. */",
527
+ "body[data-dsh-claude-style] [class*=\"linkIcon\"] {",
528
+ " display: none;",
529
+ "}",
530
+ "",
531
+ "body[data-dsh-claude-style] [class*=\"markdown\"] :is(a, a:hover, a:focus) {",
532
+ " color: var(--dsh-claude-link);",
533
+ " text-decoration: underline;",
534
+ " text-decoration-thickness: 1px;",
535
+ " text-underline-offset: 2px;",
536
+ "}",
537
+ "",
448
538
  "body[data-dsh-claude-style] hr {",
449
539
  " border: none;",
450
540
  " border-top: 1px solid rgba(20, 20, 19, 0.14);",
@@ -608,15 +698,15 @@ window.__ModuleLoader__.load({
608
698
  " slot wrapper, not always a bare svg — and the mark is painted on the hitbox,",
609
699
  " so React keeps owning its own node. The hero keeps the clay fill in both",
610
700
  " brands, matching the shipped accent treatment. */",
611
- "body[data-dsh-claude-style] [class*=\"fishHitbox\"] > * {",
701
+ "body[data-dsh-claude-style]:is([data-dsh-claude-brand=\"claude\"], [data-dsh-claude-brand=\"anthropic\"]) [class*=\"fishHitbox\"] > * {",
612
702
  " display: none !important;",
613
703
  "}",
614
704
  "",
615
- "body[data-dsh-claude-style] [class*=\"fishHitbox\"] svg {",
705
+ "body[data-dsh-claude-style]:is([data-dsh-claude-brand=\"claude\"], [data-dsh-claude-brand=\"anthropic\"]) [class*=\"fishHitbox\"] svg {",
616
706
  " display: none !important;",
617
707
  "}",
618
708
  "",
619
- "body[data-dsh-claude-style] [class*=\"fishHitbox\"]::before {",
709
+ "body[data-dsh-claude-style]:is([data-dsh-claude-brand=\"claude\"], [data-dsh-claude-brand=\"anthropic\"]) [class*=\"fishHitbox\"]::before {",
620
710
  " content: \"\";",
621
711
  " flex: none;",
622
712
  " width: 42.5px;",
@@ -626,7 +716,7 @@ window.__ModuleLoader__.load({
626
716
  " background-size: contain;",
627
717
  "}",
628
718
  "",
629
- "body[data-dsh-claude-style]:not([data-dsh-claude-brand=\"anthropic\"]) [class*=\"fishHitbox\"]::before {",
719
+ "body[data-dsh-claude-style][data-dsh-claude-brand=\"claude\"] [class*=\"fishHitbox\"]::before {",
630
720
  " background-image: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%20148.09%20148.18'%3E%3Cg%20transform%3D'translate(-75.96%2C-223.53)'%3E%3Cpath%20fill%3D'%23D97757'%20d%3D'm%20105.01%2C322.07%2029.14%2C-16.35%200.49%2C-1.42%20-0.49%2C-0.79%20h%20-1.42%20l%20-4.87%2C-0.3%20-16.65%2C-0.45%20-14.44%2C-0.6%20-13.99%2C-0.75%20-3.52%2C-0.75%20-3.3%2C-4.35%200.34%2C-2.17%202.96%2C-1.99%204.24%2C0.37%209.37%2C0.64%2014.06%2C0.97%2010.2%2C0.6%2015.11%2C1.57%20h%202.4%20l%200.34%2C-0.97%20-0.82%2C-0.6%20-0.64%2C-0.6%20-14.55%2C-9.86%20-15.75%2C-10.42%20-8.25%2C-6%20-4.46%2C-3.04%20-2.25%2C-2.85%20-0.97%2C-6.22%204.05%2C-4.46%205.44%2C0.37%201.39%2C0.37%205.51%2C4.24%2011.77%2C9.11%2015.37%2C11.32%202.25%2C1.87%200.9%2C-0.64%200.11%2C-0.45%20-1.01%2C-1.69%20-8.36%2C-15.11%20-8.92%2C-15.37%20-3.97%2C-6.37%20-1.05%2C-3.82%20c%20-0.37%2C-1.57%20-0.64%2C-2.89%20-0.64%2C-4.5%20l%204.61%2C-6.26%202.55%2C-0.82%206.15%2C0.82%202.59%2C2.25%203.82%2C8.74%206.19%2C13.76%209.6%2C18.71%202.81%2C5.55%201.5%2C5.14%200.56%2C1.57%20h%200.97%20v%20-0.9%20l%200.79%2C-10.54%201.46%2C-12.94%201.42%2C-16.65%200.49%2C-4.69%202.32%2C-5.62%204.61%2C-3.04%203.6%2C1.72%202.96%2C4.24%20-0.41%2C2.74%20-1.76%2C11.44%20-3.45%2C17.92%20-2.25%2C12%20h%201.31%20l%201.5%2C-1.5%206.07%2C-8.06%2010.2%2C-12.75%204.5%2C-5.06%205.25%2C-5.59%203.37%2C-2.66%20h%206.37%20l%204.69%2C6.97%20-2.1%2C7.2%20-6.56%2C8.32%20-5.44%2C7.05%20-7.8%2C10.5%20-4.87%2C8.4%200.45%2C0.67%201.16%2C-0.11%2017.62%2C-3.75%209.52%2C-1.72%2011.36%2C-1.95%205.14%2C2.4%200.56%2C2.44%20-2.02%2C4.99%20-12.15%2C3%20-14.25%2C2.85%20-21.22%2C5.02%20-0.26%2C0.19%200.3%2C0.37%209.56%2C0.9%204.09%2C0.22%20h%2010.01%20l%2018.64%2C1.39%204.87%2C3.22%202.92%2C3.94%20-0.49%2C3%20-7.5%2C3.82%20-10.12%2C-2.4%20-23.62%2C-5.62%20-8.1%2C-2.02%20h%20-1.12%20v%200.67%20l%206.75%2C6.6%2012.37%2C11.17%2015.49%2C14.4%200.79%2C3.56%20-1.99%2C2.81%20-2.1%2C-0.3%20-13.61%2C-10.24%20-5.25%2C-4.61%20-11.89%2C-10.01%20h%20-0.79%20v%201.05%20l%202.74%2C4.01%2014.47%2C21.75%200.75%2C6.67%20-1.05%2C2.17%20-3.75%2C1.31%20-4.12%2C-0.75%20-8.47%2C-11.89%20-8.74%2C-13.39%20-7.05%2C-12%20-0.86%2C0.49%20-4.16%2C44.81%20-1.95%2C2.29%20-4.5%2C1.72%20-3.75%2C-2.85%20-1.99%2C-4.61%201.99%2C-9.11%202.4%2C-11.89%201.95%2C-9.45%201.76%2C-11.74%201.05%2C-3.9%20-0.07%2C-0.26%20-0.86%2C0.11%20-8.85%2C12.15%20-13.46%2C18.19%20-10.65%2C11.4%20-2.55%2C1.01%20-4.42%2C-2.29%200.41%2C-4.09%202.47%2C-3.64%2014.74%2C-18.75%208.89%2C-11.62%205.74%2C-6.71%20-0.04%2C-0.97%20h%20-0.34%20l%20-39.15%2C25.42%20-6.97%2C0.9%20-3%2C-2.81%200.37%2C-4.61%201.42%2C-1.5%2011.77%2C-8.1%20-0.04%2C0.04%20z'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\");",
631
721
  "}",
632
722
  "",
@@ -648,10 +738,17 @@ window.__ModuleLoader__.load({
648
738
  "}",
649
739
  "",
650
740
  "/* ---------- Claude Code layout: composer input + buttons ---------- */",
741
+ "/* @composer-gate */",
742
+ "/* Everything from here down is the \"Composer restyle\" preference's territory:",
743
+ " the build stamps `[data-dsh-claude-composer-active]` onto every rule below",
744
+ " this marker, and the skin sets that attribute on <body> only while the page",
745
+ " on screen is a surface the preference covers. Above the marker sits the hero",
746
+ " brand mark and headline, which belong to the brand preference instead — a",
747
+ " composer scope of \"conversation only\" must not revert them. */",
651
748
  "/* The composer card is the input box: one step up in type size, pure white",
652
749
  " so it separates from the canvas, and an outline 4px tighter than the",
653
750
  " shipped 22px. */",
654
- "body[data-dsh-claude-style] [data-composer-card] {",
751
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card] {",
655
752
  " font-size: 15px;",
656
753
  " border-radius: 18px;",
657
754
  " border: 1px solid var(--dsw-alias-border-l1);",
@@ -671,11 +768,11 @@ window.__ModuleLoader__.load({
671
768
  " --dsh-composer-text-max-height before it starts scrolling. Both hooks are",
672
769
  " ancestors of the field, so :is() also keeps the tall field through the",
673
770
  " settling frame, while an active composer matches neither. */",
674
- "body[data-dsh-claude-style] :is([class*=\"composerHero\"], [data-phase=\"hero\"]) [data-composer-input] {",
771
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] :is([class*=\"composerHero\"], [data-phase=\"hero\"]) [data-composer-input] {",
675
772
  " min-height: 86px !important;",
676
773
  "}",
677
774
  "",
678
- "body[data-dsh-claude-style] [data-composer-input] {",
775
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-input] {",
679
776
  " min-height: 36px !important;",
680
777
  " height: auto !important;",
681
778
  "}",
@@ -684,13 +781,17 @@ window.__ModuleLoader__.load({
684
781
  " be `[class*=\"_row\"]`: a bare `[class*=\"row\"]` substring-matches the",
685
782
  " shipped input growth wrapper `.p_FcLG_grow` (\"grow\" contains \"row\"),",
686
783
  " which pins the composer field to the toolbar-row metrics. */",
687
- "body[data-dsh-claude-style] [data-composer-card] [class*=\"_row\"] {",
784
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card] [class*=\"_row\"] {",
688
785
  " padding-bottom: 2px !important;",
689
786
  "}",
690
787
  "",
691
- "body[data-dsh-claude-style]:not([data-ds-dark-theme]) [data-composer-card] {",
788
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active]:not([data-ds-dark-theme]) [data-composer-card] {",
692
789
  " background: #ffffff;",
693
- " box-shadow: 0 4px 12px rgba(20, 20, 19, 0.04), 0 1px 3px rgba(20, 20, 19, 0.02);",
790
+ " box-shadow: 0 4px 12px rgba(20, 20, 19, 0.04), 0 1px 3px rgba(20, 20, 19, 0.02), 0 0 0 6px var(--dsw-alias-bg-base), 0 0 16px 10px var(--dsw-alias-bg-base), 0 0 48px 16px var(--dsw-alias-bg-base);",
791
+ "}",
792
+ "",
793
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active][data-ds-dark-theme] [data-composer-card] {",
794
+ " box-shadow: 0 4px 12px rgba(0, 0, 0, 0.30), 0 1px 3px rgba(0, 0, 0, 0.15), 0 0 0 6px var(--dsw-alias-bg-base), 0 0 16px 10px var(--dsw-alias-bg-base), 0 0 48px 16px var(--dsw-alias-bg-base);",
694
795
  "}",
695
796
  "",
696
797
  "/* Focus: the shipped field has no focus face at all — only the caret moves —",
@@ -701,27 +802,27 @@ window.__ModuleLoader__.load({
701
802
  " the focus face inverts to a BRIGHT ivory rim and halo, so the field visibly",
702
803
  " lights up instead of deepening. The tray below continues the same hairline,",
703
804
  " so its three edges follow whenever the card above it holds focus. */",
704
- "body[data-dsh-claude-style]:not([data-ds-dark-theme]) [data-composer-card]:focus-within {",
705
- " border-color: rgba(20, 20, 19, 0.36);",
706
- " box-shadow: 0 0 0 1px rgba(20, 20, 19, 0.13), 0 6px 18px rgba(20, 20, 19, 0.06), 0 2px 6px rgba(20, 20, 19, 0.03);",
805
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active]:not([data-ds-dark-theme]) [data-composer-card]:focus-within {",
806
+ " border-color: rgba(20, 20, 19, 0.18);",
807
+ " box-shadow: 0 0 0 1px rgba(20, 20, 19, 0.065), 0 6px 18px rgba(20, 20, 19, 0.03), 0 2px 6px rgba(20, 20, 19, 0.015), 0 0 0 6px var(--dsw-alias-bg-base), 0 0 16px 10px var(--dsw-alias-bg-base), 0 0 48px 16px var(--dsw-alias-bg-base);",
707
808
  "}",
708
809
  "",
709
- "body[data-dsh-claude-style][data-ds-dark-theme] [data-composer-card]:focus-within {",
710
- " border-color: rgba(250, 249, 245, 0.45);",
711
- " box-shadow: 0 0 0 1px rgba(250, 249, 245, 0.18), 0 6px 24px rgba(0, 0, 0, 0.45), 0 2px 8px rgba(0, 0, 0, 0.22);",
810
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active][data-ds-dark-theme] [data-composer-card]:focus-within {",
811
+ " border-color: rgba(250, 249, 245, 0.22);",
812
+ " box-shadow: 0 0 0 1px rgba(250, 249, 245, 0.09), 0 6px 24px rgba(0, 0, 0, 0.22), 0 2px 8px rgba(0, 0, 0, 0.11), 0 0 0 6px var(--dsw-alias-bg-base), 0 0 16px 10px var(--dsw-alias-bg-base), 0 0 48px 16px var(--dsw-alias-bg-base);",
712
813
  "}",
713
814
  "",
714
- "body[data-dsh-claude-style]:not([data-ds-dark-theme]) [class*=\"composerStack\"]:has([data-composer-card]:focus-within) > [class*=\"heroWorkspaceRow\"] {",
715
- " border-color: rgba(20, 20, 19, 0.36);",
815
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active]:not([data-ds-dark-theme]) [class*=\"composerStack\"]:has([data-composer-card]:focus-within) > [class*=\"heroWorkspaceRow\"] {",
816
+ " border-color: rgba(20, 20, 19, 0.18);",
716
817
  "}",
717
818
  "",
718
- "body[data-dsh-claude-style][data-ds-dark-theme] [class*=\"composerStack\"]:has([data-composer-card]:focus-within) > [class*=\"heroWorkspaceRow\"] {",
719
- " border-color: rgba(250, 249, 245, 0.45);",
819
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active][data-ds-dark-theme] [class*=\"composerStack\"]:has([data-composer-card]:focus-within) > [class*=\"heroWorkspaceRow\"] {",
820
+ " border-color: rgba(250, 249, 245, 0.22);",
720
821
  "}",
721
822
  "",
722
823
  "/* The workspace-trigger variant paints its dashed ring through a masked svg",
723
824
  " cut with rx=22; re-cut it so the ring tracks the 18px card. */",
724
- "body[data-dsh-claude-style] [data-composer-card]:after {",
825
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card]:after {",
725
826
  " border-radius: 18px;",
726
827
  " -webkit-mask: url(\"data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27%3E%3Crect width=%27100%25%27 height=%27100%25%27 fill=%27none%27 rx=%2718%27 ry=%2718%27 stroke=%27black%27 stroke-width=%272%27 stroke-dasharray=%274 4%27/%3E%3C/svg%3E\");",
727
828
  " mask: url(\"data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27%3E%3Crect width=%27100%25%27 height=%27100%25%27 fill=%27none%27 rx=%2718%27 ry=%2718%27 stroke=%27black%27 stroke-width=%272%27 stroke-dasharray=%274 4%27/%3E%3C/svg%3E\");",
@@ -729,21 +830,21 @@ window.__ModuleLoader__.load({
729
830
  "",
730
831
  "/* Commands and attach: 7px corners instead of the shipped full circle, no",
731
832
  " resting fill, and a warm plate on hover. */",
732
- "body[data-dsh-claude-style] :is(button[aria-label=\"指令\"], button[aria-label=\"Commands\"], button[aria-label=\"添加附件\"], button[aria-label=\"Add attachment\"]) {",
833
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] :is(button[aria-label=\"指令\"], button[aria-label=\"Commands\"], button[aria-label=\"添加附件\"], button[aria-label=\"Add attachment\"]) {",
733
834
  " border-radius: 7px;",
734
835
  "}",
735
836
  "",
736
837
  "/* Send, and the stop/queue/steer labels that replace it while running: the",
737
838
  " shipped 34px circle becomes a 7px rounded rectangle. */",
738
- "body[data-dsh-claude-style] :is(button[aria-label=\"发送消息\"], button[aria-label=\"Send message\"], button[aria-label=\"排队发送\"], button[aria-label=\"Queue message\"], button[aria-label=\"插话发送\"], button[aria-label=\"Steer message\"], button[aria-label=\"停止生成\"], button[aria-label=\"Stop generating\"]) {",
839
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] :is(button[aria-label=\"发送消息\"], button[aria-label=\"Send message\"], button[aria-label=\"排队发送\"], button[aria-label=\"Queue message\"], button[aria-label=\"插话发送\"], button[aria-label=\"Steer message\"], button[aria-label=\"停止生成\"], button[aria-label=\"Stop generating\"]) {",
739
840
  " border-radius: 7px;",
740
841
  "}",
741
842
  "",
742
- "body[data-dsh-claude-style]:not([data-ds-dark-theme]) :is(button[aria-label=\"指令\"], button[aria-label=\"Commands\"], button[aria-label=\"添加附件\"], button[aria-label=\"Add attachment\"]) {",
843
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active]:not([data-ds-dark-theme]) :is(button[aria-label=\"指令\"], button[aria-label=\"Commands\"], button[aria-label=\"添加附件\"], button[aria-label=\"Add attachment\"]) {",
743
844
  " background: transparent;",
744
845
  "}",
745
846
  "",
746
- "body[data-dsh-claude-style]:not([data-ds-dark-theme]) :is(button[aria-label=\"指令\"], button[aria-label=\"Commands\"], button[aria-label=\"添加附件\"], button[aria-label=\"Add attachment\"]):hover:not(:disabled) {",
847
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active]:not([data-ds-dark-theme]) :is(button[aria-label=\"指令\"], button[aria-label=\"Commands\"], button[aria-label=\"添加附件\"], button[aria-label=\"Add attachment\"]):hover:not(:disabled) {",
747
848
  " background: #f6f6f4;",
748
849
  "}",
749
850
  "",
@@ -751,16 +852,16 @@ window.__ModuleLoader__.load({
751
852
  " same 7px corners; its chevron is dropped while the click target and the",
752
853
  " menu it opens stay exactly as shipped. Its shipped hover token is only a",
753
854
  " ~6% tint, so the hover plate matches the composer tool buttons. */",
754
- "body[data-dsh-claude-style] [data-composer-card] [class*=\"trigger\"] {",
855
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card] [class*=\"trigger\"] {",
755
856
  " font-size: 14px;",
756
857
  " border-radius: 7px;",
757
858
  "}",
758
859
  "",
759
- "body[data-dsh-claude-style]:not([data-ds-dark-theme]) [data-composer-card] [class*=\"trigger\"]:hover:not(:disabled) {",
860
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active]:not([data-ds-dark-theme]) [data-composer-card] [class*=\"trigger\"]:hover:not(:disabled) {",
760
861
  " background: #f6f6f4;",
761
862
  "}",
762
863
  "",
763
- "body[data-dsh-claude-style] [data-composer-card] [class*=\"_chevron\"] {",
864
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card] [class*=\"_chevron\"] {",
764
865
  " display: none !important;",
765
866
  "}",
766
867
  "",
@@ -770,23 +871,23 @@ window.__ModuleLoader__.load({
770
871
  " equal-width tray (z-index: 1) attached directly to the bottom of the card,",
771
872
  " with transparent background (inheriting the canvas tone #fcfcfb),",
772
873
  " left/right/bottom border, and 18px rounded bottom corners. */",
773
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:has([class*=\"heroWorkspaceRow\"]) {",
874
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:has([class*=\"heroWorkspaceRow\"]) {",
774
875
  " --dsh-claude-bar-h: 96px;",
775
876
  "}",
776
877
  "",
777
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:has([class*=\"heroWorkspaceRow\"]) *:has([data-composer-card]) {",
878
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:has([class*=\"heroWorkspaceRow\"]) *:has([data-composer-card]) {",
778
879
  " order: 1;",
779
880
  " padding-bottom: 0 !important;",
780
881
  "}",
781
882
  "",
782
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:has([class*=\"heroWorkspaceRow\"]) [data-composer-card] {",
883
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:has([class*=\"heroWorkspaceRow\"]) [data-composer-card] {",
783
884
  " gap: 8px !important;",
784
885
  " padding-bottom: 2px !important;",
785
886
  " min-height: 0 !important;",
786
887
  " height: auto !important;",
787
888
  "}",
788
889
  "",
789
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:has([class*=\"heroWorkspaceRow\"]) > [class*=\"heroWorkspaceRow\"] {",
890
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:has([class*=\"heroWorkspaceRow\"]) > [class*=\"heroWorkspaceRow\"] {",
790
891
  " order: 2;",
791
892
  " position: relative;",
792
893
  " z-index: 1;",
@@ -809,14 +910,14 @@ window.__ModuleLoader__.load({
809
910
  "}",
810
911
  "",
811
912
  "/* Muted typography and 7px corners for footer controls */",
812
- "body[data-dsh-claude-style] [class*=\"heroWorkspaceRow\"] :is(button, [role=\"button\"]) {",
913
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"heroWorkspaceRow\"] :is(button, [role=\"button\"]) {",
813
914
  " font-size: 13px;",
814
915
  " color: var(--dsw-alias-label-secondary);",
815
916
  " border-radius: 7px;",
816
917
  "}",
817
918
  "",
818
919
  "/* Hide chevrons in the footer tray for a clean, minimal Claude Code look */",
819
- "body[data-dsh-claude-style] [class*=\"heroWorkspaceRow\"] :is([class*=\"chevron\"], [class*=\"Chevron\"]) {",
920
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"heroWorkspaceRow\"] :is([class*=\"chevron\"], [class*=\"Chevron\"]) {",
820
921
  " display: none !important;",
821
922
  "}",
822
923
  "",
@@ -828,7 +929,7 @@ window.__ModuleLoader__.load({
828
929
  " border-box keeps the outer 34px so the row does not shift. The stop/queue/",
829
930
  " steer labels are untouched, so a running turn keeps its control even with an",
830
931
  " empty draft. */",
831
- "body[data-dsh-claude-style] [data-composer-card]:has([data-composer-placeholder]) :is(button[aria-label=\"发送消息\"], button[aria-label=\"Send message\"]) {",
932
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card]:not([data-has-attachments=\"true\"]):has([data-composer-placeholder]) :is(button[aria-label=\"发送消息\"], button[aria-label=\"Send message\"]) {",
832
933
  " box-sizing: border-box;",
833
934
  " background: transparent;",
834
935
  " color: var(--dsw-alias-label-caption);",
@@ -841,10 +942,10 @@ window.__ModuleLoader__.load({
841
942
  "/* In an active conversation, compress composer to a single-line input card,",
842
943
  " render send button as an enter symbol ↵ inside the card on the right,",
843
944
  " and display toolbar controls directly underneath on the canvas. */",
844
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"],",
845
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"]:focus-within,",
846
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card],",
847
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card]:focus-within {",
945
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"],",
946
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"]:focus-within,",
947
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card],",
948
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card]:focus-within {",
848
949
  " background: transparent !important;",
849
950
  " border: none !important;",
850
951
  " box-shadow: none !important;",
@@ -868,69 +969,120 @@ window.__ModuleLoader__.load({
868
969
  " composer seat near the TOP of the scroll content — sticky bottom:0",
869
970
  " cannot pull an element down past its normal position, so the composer",
870
971
  " appeared mid-scroll and never followed message growth. */",
871
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) {",
972
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) {",
872
973
  " flex: 0 0 auto !important;",
873
974
  " overflow: visible !important;",
874
975
  " height: auto !important;",
875
976
  " max-height: none !important;",
876
977
  "}",
978
+ "/* Message body clearance: 40px placeholder space at the end of the",
979
+ " conversation view so the composer's upward shadow does not obscure",
980
+ " the action buttons (copy, retry, etc.) on the last turn. */",
981
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"viewArea\"] {",
982
+ " padding-bottom: 40px !important;",
983
+ "}",
877
984
  "/* The composer is a chat-view affordance. The host keeps the seat mounted on",
878
985
  " every conversation tab (轨迹 / 上下文 even reserve room for it), but it",
879
986
  " should only show on the chat tab — drop the whole bottom area otherwise.",
880
987
  " Hiding the seat also zeroes the host's --dsh-composer-height, so the",
881
988
  " trajectory ledger's bottom clearance collapses with it. */",
882
- "body[data-dsh-claude-style][data-dsh-claude-composer-hidden] [data-composer-seat] {",
989
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active][data-dsh-claude-composer-hidden] [data-composer-seat] {",
883
990
  " display: none !important;",
884
991
  "}",
885
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"]:not([data-has-attachments=\"true\"]) [class*=\"rail\"]:not([class*=\"trailing\"]),",
886
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card]:not([data-has-attachments=\"true\"]) [class*=\"rail\"]:not([class*=\"trailing\"]) {",
992
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"]:not([data-has-attachments=\"true\"]) [class*=\"rail\"]:not([class*=\"trailing\"]),",
993
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card]:not([data-has-attachments=\"true\"]) [class*=\"rail\"]:not([class*=\"trailing\"]) {",
887
994
  " display: none !important;",
888
995
  "}",
889
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"][data-has-attachments=\"true\"] [class*=\"rail\"]:not([class*=\"trailing\"]),",
890
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card][data-has-attachments=\"true\"] [class*=\"rail\"]:not([class*=\"trailing\"]) {",
996
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"][data-has-attachments=\"true\"] [class*=\"rail\"]:not([class*=\"trailing\"]),",
997
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card][data-has-attachments=\"true\"] [class*=\"rail\"]:not([class*=\"trailing\"]) {",
891
998
  " background: var(--dsw-specific-input-major, #ffffff) !important;",
892
999
  " border: 1px solid var(--dsw-alias-border-l1) !important;",
893
1000
  " border-bottom: none !important;",
894
1001
  " border-radius: 14px 14px 0 0 !important;",
895
- " padding: 8px 12px 2px 12px !important;",
1002
+ " padding: 12px 14px 10px 14px !important;",
896
1003
  " margin: 0 !important;",
897
1004
  " width: 100% !important;",
898
1005
  " max-width: 100% !important;",
899
1006
  " box-sizing: border-box !important;",
900
- " box-shadow: 0 1px 3px rgba(20, 20, 19, 0.03) !important;",
1007
+ " min-height: 162px !important;",
1008
+ " height: auto !important;",
1009
+ " overflow: visible !important;",
1010
+ " box-shadow: 0 1px 3px rgba(20, 20, 19, 0.03), 0 0 0 6px var(--dsw-alias-bg-base), 0 0 16px 10px var(--dsw-alias-bg-base), 0 0 48px 16px var(--dsw-alias-bg-base) !important;",
1011
+ " position: relative !important;",
1012
+ " z-index: 4 !important;",
901
1013
  " transition: border-color 0.12s ease, box-shadow 0.12s ease !important;",
902
1014
  "}",
903
- "body[data-dsh-claude-style]:not([data-ds-dark-theme]) [data-composer-card][data-composer-variant=\"inline\"][data-has-attachments=\"true\"] [class*=\"rail\"]:not([class*=\"trailing\"]),",
904
- "body[data-dsh-claude-style]:not([data-ds-dark-theme]) [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card][data-has-attachments=\"true\"] [class*=\"rail\"]:not([class*=\"trailing\"]) {",
1015
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active]:not([data-ds-dark-theme]) [data-composer-card][data-composer-variant=\"inline\"][data-has-attachments=\"true\"] [class*=\"rail\"]:not([class*=\"trailing\"]),",
1016
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active]:not([data-ds-dark-theme]) [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card][data-has-attachments=\"true\"] [class*=\"rail\"]:not([class*=\"trailing\"]) {",
905
1017
  " background: #ffffff !important;",
906
1018
  " border-color: #e8e6dc !important;",
907
1019
  "}",
908
- "body[data-dsh-claude-style]:not([data-ds-dark-theme]) [data-composer-card][data-composer-variant=\"inline\"]:focus-within [class*=\"rail\"]:not([class*=\"trailing\"]),",
909
- "body[data-dsh-claude-style]:not([data-ds-dark-theme]) [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card]:focus-within [class*=\"rail\"]:not([class*=\"trailing\"]) {",
910
- " border-color: rgba(20, 20, 19, 0.36) !important;",
911
- " box-shadow: 0 0 0 1px rgba(20, 20, 19, 0.13), 0 4px 12px rgba(20, 20, 19, 0.05) !important;",
1020
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active]:not([data-ds-dark-theme]) [data-composer-card][data-composer-variant=\"inline\"]:focus-within [class*=\"rail\"]:not([class*=\"trailing\"]),",
1021
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active]:not([data-ds-dark-theme]) [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card]:focus-within [class*=\"rail\"]:not([class*=\"trailing\"]) {",
1022
+ " border-color: rgba(20, 20, 19, 0.18) !important;",
1023
+ " box-shadow: 0 0 0 1px rgba(20, 20, 19, 0.065), 0 4px 12px rgba(20, 20, 19, 0.025), 0 0 0 6px var(--dsw-alias-bg-base), 0 0 16px 10px var(--dsw-alias-bg-base), 0 0 48px 16px var(--dsw-alias-bg-base) !important;",
912
1024
  "}",
913
- "body[data-dsh-claude-style][data-ds-dark-theme] [data-composer-card][data-composer-variant=\"inline\"]:focus-within [class*=\"rail\"]:not([class*=\"trailing\"]),",
914
- "body[data-dsh-claude-style][data-ds-dark-theme] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card]:focus-within [class*=\"rail\"]:not([class*=\"trailing\"]) {",
915
- " border-color: rgba(250, 249, 245, 0.45) !important;",
916
- " box-shadow: 0 0 0 1px rgba(250, 249, 245, 0.18), 0 4px 16px rgba(0, 0, 0, 0.40) !important;",
1025
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active][data-ds-dark-theme] [data-composer-card][data-composer-variant=\"inline\"]:focus-within [class*=\"rail\"]:not([class*=\"trailing\"]),",
1026
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active][data-ds-dark-theme] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card]:focus-within [class*=\"rail\"]:not([class*=\"trailing\"]) {",
1027
+ " border-color: rgba(250, 249, 245, 0.22) !important;",
1028
+ " box-shadow: 0 0 0 1px rgba(250, 249, 245, 0.09), 0 4px 16px rgba(0, 0, 0, 0.20), 0 0 0 6px var(--dsw-alias-bg-base), 0 0 16px 10px var(--dsw-alias-bg-base), 0 0 48px 16px var(--dsw-alias-bg-base) !important;",
917
1029
  "}",
918
1030
  "/* The attachment rail nests two levels: ComposerAttachments' outer rail wraps",
919
1031
  " AttachmentRail's scrolling inner rail, and BOTH substring-match",
920
1032
  " `[class*=\"rail\"]`, so the block above would draw the card frame twice —",
921
1033
  " one box inside the other. Only the outer shell carries the frame; the",
922
1034
  " inner scrolling rail stays frameless. */",
923
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"][data-has-attachments=\"true\"] [class*=\"rail\"]:not([class*=\"trailing\"]) [class*=\"rail\"]:not([class*=\"trailing\"]),",
924
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card][data-has-attachments=\"true\"] [class*=\"rail\"]:not([class*=\"trailing\"]) [class*=\"rail\"]:not([class*=\"trailing\"]) {",
1035
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"][data-has-attachments=\"true\"] [class*=\"rail\"]:not([class*=\"trailing\"]) [class*=\"rail\"]:not([class*=\"trailing\"]),",
1036
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card][data-has-attachments=\"true\"] [class*=\"rail\"]:not([class*=\"trailing\"]) [class*=\"rail\"]:not([class*=\"trailing\"]) {",
925
1037
  " background: transparent !important;",
926
1038
  " border: none !important;",
927
1039
  " box-shadow: none !important;",
928
1040
  " border-radius: 0 !important;",
929
1041
  " padding: 0 !important;",
930
1042
  " margin: 0 !important;",
1043
+ " min-height: 140px !important;",
1044
+ " height: auto !important;",
1045
+ " display: flex !important;",
1046
+ " align-items: center !important;",
1047
+ " gap: 10px !important;",
1048
+ " overflow-x: auto !important;",
1049
+ " overflow-y: hidden !important;",
1050
+ "}",
1051
+ "/* Attachment thumbnail card: enlarged by 75% (~140px), matching non-focus border without shadow */",
1052
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card] [class*=\"rail\"]:not([class*=\"trailing\"]) :is(._54WpYG_imageItem, [class*=\"imageItem\"], [class*=\"thumbnail\"], [class*=\"FileCard\"], [class*=\"item\"]:has(img), [class*=\"card\"]:has(img), [class*=\"attachment\"]:has(img)) {",
1053
+ " width: 140px !important;",
1054
+ " height: 140px !important;",
1055
+ " min-width: 140px !important;",
1056
+ " min-height: 140px !important;",
1057
+ " max-width: 140px !important;",
1058
+ " max-height: 140px !important;",
1059
+ " border-radius: 12px !important;",
1060
+ " border: 1px solid var(--dsw-alias-border-l1) !important;",
1061
+ " box-shadow: none !important;",
1062
+ " overflow: hidden !important;",
1063
+ " position: relative !important;",
1064
+ " z-index: 5 !important;",
1065
+ " flex: 0 0 140px !important;",
1066
+ " box-sizing: border-box !important;",
1067
+ "}",
1068
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active]:not([data-ds-dark-theme]) [data-composer-card] [class*=\"rail\"]:not([class*=\"trailing\"]) :is(._54WpYG_imageItem, [class*=\"imageItem\"], [class*=\"thumbnail\"], [class*=\"FileCard\"], [class*=\"item\"]:has(img), [class*=\"card\"]:has(img), [class*=\"attachment\"]:has(img)) {",
1069
+ " border-color: #e8e6dc !important;",
1070
+ "}",
1071
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card] [class*=\"rail\"]:not([class*=\"trailing\"]) :is(._54WpYG_imageItem, [class*=\"imageItem\"], [class*=\"thumbnail\"], [class*=\"FileCard\"], [class*=\"item\"]:has(img), [class*=\"card\"]:has(img), [class*=\"attachment\"]:has(img)) :is(img, canvas, [class*=\"preview\"], [class*=\"image\"]) {",
1072
+ " width: 100% !important;",
1073
+ " height: 100% !important;",
1074
+ " max-width: 100% !important;",
1075
+ " max-height: 100% !important;",
1076
+ " object-fit: cover !important;",
1077
+ " display: block !important;",
1078
+ " border-radius: 11px !important;",
1079
+ "}",
1080
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card] [class*=\"rail\"]:not([class*=\"trailing\"]) :is(._54WpYG_imageItem, [class*=\"imageItem\"], [class*=\"thumbnail\"], [class*=\"FileCard\"], [class*=\"item\"]:has(img), [class*=\"card\"]:has(img), [class*=\"attachment\"]:has(img)) :is(button, [role=\"button\"], [class*=\"close\"], [class*=\"delete\"], [class*=\"remove\"], [class*=\"badge\"], [class*=\"tag\"]) {",
1081
+ " position: absolute !important;",
1082
+ " z-index: 6 !important;",
931
1083
  "}",
932
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"] [data-input-scroll],",
933
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] [data-input-scroll] {",
1084
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"] [data-input-scroll],",
1085
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] [data-input-scroll] {",
934
1086
  " background: var(--dsw-specific-input-major, #ffffff) !important;",
935
1087
  " border: 1px solid var(--dsw-alias-border-l1) !important;",
936
1088
  " border-radius: 14px !important;",
@@ -946,51 +1098,53 @@ window.__ModuleLoader__.load({
946
1098
  " padding: 7px 36px 7px 14px !important;",
947
1099
  " margin: 0 !important;",
948
1100
  " position: relative !important;",
1101
+ " z-index: 2 !important;",
949
1102
  " cursor: text !important;",
1103
+ " box-shadow: 0 1px 3px rgba(20, 20, 19, 0.03), 0 0 0 6px var(--dsw-alias-bg-base), 0 0 16px 10px var(--dsw-alias-bg-base), 0 0 48px 16px var(--dsw-alias-bg-base) !important;",
950
1104
  " transition: border-color 0.12s ease, box-shadow 0.12s ease !important;",
951
1105
  "}",
952
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"][data-has-attachments=\"true\"] [data-input-scroll],",
953
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card][data-has-attachments=\"true\"] [data-input-scroll] {",
1106
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"][data-has-attachments=\"true\"] [data-input-scroll],",
1107
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card][data-has-attachments=\"true\"] [data-input-scroll] {",
954
1108
  " border-top: none !important;",
955
1109
  " border-radius: 0 0 14px 14px !important;",
956
- " /* The card is a flex column with an 8px gap; with attachments that gap",
957
- " shows the canvas as a seam line between the thumbnails and the text.",
958
- " Close it (negative margin pulls the input up onto the rail) and move the",
959
- " 8px into the top padding, so the text keeps its distance from the",
960
- " thumbnails and the card's total height is unchanged. */",
961
- " margin-top: -8px !important;",
1110
+ " /* Seamless join with attachment rail above: 1px negative margin prevents subpixel seam without clipping thumbnails */",
1111
+ " margin-top: -1px !important;",
962
1112
  " min-height: 32px !important;",
963
- " padding: 10px 36px 6px 14px !important;",
1113
+ " padding: 8px 36px 6px 14px !important;",
964
1114
  "}",
965
- "body[data-dsh-claude-style]:not([data-ds-dark-theme]) [data-composer-card][data-composer-variant=\"inline\"] [data-input-scroll],",
966
- "body[data-dsh-claude-style]:not([data-ds-dark-theme]) [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] [data-input-scroll] {",
1115
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active]:not([data-ds-dark-theme]) [data-composer-card][data-composer-variant=\"inline\"] [data-input-scroll],",
1116
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active]:not([data-ds-dark-theme]) [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] [data-input-scroll] {",
967
1117
  " background: #ffffff !important;",
968
1118
  " border-color: #e8e6dc !important;",
969
- " box-shadow: 0 1px 3px rgba(20, 20, 19, 0.03) !important;",
1119
+ " box-shadow: 0 1px 3px rgba(20, 20, 19, 0.03), 0 0 0 6px var(--dsw-alias-bg-base), 0 0 16px 10px var(--dsw-alias-bg-base), 0 0 48px 16px var(--dsw-alias-bg-base) !important;",
1120
+ "}",
1121
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active][data-ds-dark-theme] [data-composer-card][data-composer-variant=\"inline\"] [data-input-scroll],",
1122
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active][data-ds-dark-theme] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] [data-input-scroll] {",
1123
+ " box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15), 0 0 0 6px var(--dsw-alias-bg-base), 0 0 16px 10px var(--dsw-alias-bg-base), 0 0 48px 16px var(--dsw-alias-bg-base) !important;",
970
1124
  "}",
971
- "body[data-dsh-claude-style]:not([data-ds-dark-theme]) [data-composer-card][data-composer-variant=\"inline\"]:focus-within [data-input-scroll],",
972
- "body[data-dsh-claude-style]:not([data-ds-dark-theme]) [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card]:focus-within [data-input-scroll] {",
973
- " border-color: rgba(20, 20, 19, 0.36) !important;",
974
- " box-shadow: 0 0 0 1px rgba(20, 20, 19, 0.13), 0 4px 12px rgba(20, 20, 19, 0.05) !important;",
1125
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active]:not([data-ds-dark-theme]) [data-composer-card][data-composer-variant=\"inline\"]:focus-within [data-input-scroll],",
1126
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active]:not([data-ds-dark-theme]) [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card]:focus-within [data-input-scroll] {",
1127
+ " border-color: rgba(20, 20, 19, 0.18) !important;",
1128
+ " box-shadow: 0 0 0 1px rgba(20, 20, 19, 0.065), 0 4px 12px rgba(20, 20, 19, 0.025), 0 0 0 6px var(--dsw-alias-bg-base), 0 0 16px 10px var(--dsw-alias-bg-base), 0 0 48px 16px var(--dsw-alias-bg-base) !important;",
975
1129
  "}",
976
- "body[data-dsh-claude-style][data-ds-dark-theme] [data-composer-card][data-composer-variant=\"inline\"]:focus-within [data-input-scroll],",
977
- "body[data-dsh-claude-style][data-ds-dark-theme] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card]:focus-within [data-input-scroll] {",
978
- " border-color: rgba(250, 249, 245, 0.45) !important;",
979
- " box-shadow: 0 0 0 1px rgba(250, 249, 245, 0.18), 0 4px 16px rgba(0, 0, 0, 0.40) !important;",
1130
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active][data-ds-dark-theme] [data-composer-card][data-composer-variant=\"inline\"]:focus-within [data-input-scroll],",
1131
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active][data-ds-dark-theme] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card]:focus-within [data-input-scroll] {",
1132
+ " border-color: rgba(250, 249, 245, 0.22) !important;",
1133
+ " box-shadow: 0 0 0 1px rgba(250, 249, 245, 0.09), 0 4px 16px rgba(0, 0, 0, 0.20), 0 0 0 6px var(--dsw-alias-bg-base), 0 0 16px 10px var(--dsw-alias-bg-base), 0 0 48px 16px var(--dsw-alias-bg-base) !important;",
980
1134
  "}",
981
1135
  "/* With attachments the rail already carries the card's focus ring, so the",
982
1136
  " input keeps only the deepened drop — its own 1px ring would paint a second",
983
1137
  " line across the seam between the thumbnails and the text. */",
984
- "body[data-dsh-claude-style]:not([data-ds-dark-theme]) [data-composer-card][data-composer-variant=\"inline\"][data-has-attachments=\"true\"]:focus-within [data-input-scroll],",
985
- "body[data-dsh-claude-style]:not([data-ds-dark-theme]) [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card][data-has-attachments=\"true\"]:focus-within [data-input-scroll] {",
986
- " box-shadow: 0 4px 12px rgba(20, 20, 19, 0.05) !important;",
1138
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active]:not([data-ds-dark-theme]) [data-composer-card][data-composer-variant=\"inline\"][data-has-attachments=\"true\"]:focus-within [data-input-scroll],",
1139
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active]:not([data-ds-dark-theme]) [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card][data-has-attachments=\"true\"]:focus-within [data-input-scroll] {",
1140
+ " box-shadow: 0 4px 12px rgba(20, 20, 19, 0.025), 0 0 0 6px var(--dsw-alias-bg-base), 0 0 16px 10px var(--dsw-alias-bg-base), 0 0 48px 16px var(--dsw-alias-bg-base) !important;",
987
1141
  "}",
988
- "body[data-dsh-claude-style][data-ds-dark-theme] [data-composer-card][data-composer-variant=\"inline\"][data-has-attachments=\"true\"]:focus-within [data-input-scroll],",
989
- "body[data-dsh-claude-style][data-ds-dark-theme] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card][data-has-attachments=\"true\"]:focus-within [data-input-scroll] {",
990
- " box-shadow: 0 4px 16px rgba(0, 0, 0, 0.40) !important;",
1142
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active][data-ds-dark-theme] [data-composer-card][data-composer-variant=\"inline\"][data-has-attachments=\"true\"]:focus-within [data-input-scroll],",
1143
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active][data-ds-dark-theme] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card][data-has-attachments=\"true\"]:focus-within [data-input-scroll] {",
1144
+ " box-shadow: 0 4px 16px rgba(0, 0, 0, 0.20), 0 0 0 6px var(--dsw-alias-bg-base), 0 0 16px 10px var(--dsw-alias-bg-base), 0 0 48px 16px var(--dsw-alias-bg-base) !important;",
991
1145
  "}",
992
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"] [data-input-scroll] [class*=\"grow\"],",
993
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] [data-input-scroll] [class*=\"grow\"] {",
1146
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"] [data-input-scroll] [class*=\"grow\"],",
1147
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] [data-input-scroll] [class*=\"grow\"] {",
994
1148
  " width: 100% !important;",
995
1149
  " max-width: 100% !important;",
996
1150
  " min-height: 24px !important;",
@@ -999,8 +1153,8 @@ window.__ModuleLoader__.load({
999
1153
  " box-sizing: border-box !important;",
1000
1154
  " overflow-x: hidden !important;",
1001
1155
  "}",
1002
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"] [data-composer-input],",
1003
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] [data-composer-input] {",
1156
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"] [data-composer-input],",
1157
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] [data-composer-input] {",
1004
1158
  " min-height: 24px !important;",
1005
1159
  " height: auto !important;",
1006
1160
  " line-height: 24px !important;",
@@ -1016,8 +1170,8 @@ window.__ModuleLoader__.load({
1016
1170
  " overflow-wrap: anywhere !important;",
1017
1171
  " overflow-x: hidden !important;",
1018
1172
  "}",
1019
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"] [data-composer-placeholder],",
1020
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] [data-composer-placeholder] {",
1173
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"] [data-composer-placeholder],",
1174
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] [data-composer-placeholder] {",
1021
1175
  " line-height: 24px !important;",
1022
1176
  " height: 24px !important;",
1023
1177
  " position: absolute !important;",
@@ -1035,8 +1189,8 @@ window.__ModuleLoader__.load({
1035
1189
  " user-select: none !important;",
1036
1190
  "}",
1037
1191
  "/* Pinned send button inside input box */",
1038
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"] :is(button[aria-label^=\"发送\"], button[aria-label^=\"Send\"], button[aria-label*=\"发送\"], button[aria-label*=\"Send\"], button[aria-label*=\"排队\"], button[aria-label*=\"Queue\"], button[aria-label*=\"插话\"], button[aria-label*=\"Steer\"], [class*=\"sendBtn\"], [class*=\"sendButton\"]),",
1039
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] :is(button[aria-label^=\"发送\"], button[aria-label^=\"Send\"], button[aria-label*=\"发送\"], button[aria-label*=\"Send\"], button[aria-label*=\"排队\"], button[aria-label*=\"Queue\"], button[aria-label*=\"插话\"], button[aria-label*=\"Steer\"], [class*=\"sendBtn\"], [class*=\"sendButton\"]) {",
1192
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"] :is(button[aria-label^=\"发送\"], button[aria-label^=\"Send\"], button[aria-label*=\"发送\"], button[aria-label*=\"Send\"], button[aria-label*=\"排队\"], button[aria-label*=\"Queue\"], button[aria-label*=\"插话\"], button[aria-label*=\"Steer\"], [class*=\"sendBtn\"], [class*=\"sendButton\"]),",
1193
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] :is(button[aria-label^=\"发送\"], button[aria-label^=\"Send\"], button[aria-label*=\"发送\"], button[aria-label*=\"Send\"], button[aria-label*=\"排队\"], button[aria-label*=\"Queue\"], button[aria-label*=\"插话\"], button[aria-label*=\"Steer\"], [class*=\"sendBtn\"], [class*=\"sendButton\"]) {",
1040
1194
  " position: absolute !important;",
1041
1195
  " bottom: 40px !important;",
1042
1196
  " right: 8px !important;",
@@ -1058,11 +1212,11 @@ window.__ModuleLoader__.load({
1058
1212
  " cursor: pointer !important;",
1059
1213
  " transition: color 0.12s ease, background-color 0.12s ease !important;",
1060
1214
  "}",
1061
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"] :is(button[aria-label^=\"发送\"], button[aria-label^=\"Send\"], button[aria-label*=\"发送\"], button[aria-label*=\"Send\"], button[aria-label*=\"排队\"], button[aria-label*=\"Queue\"], button[aria-label*=\"插话\"], button[aria-label*=\"Steer\"], [class*=\"sendBtn\"], [class*=\"sendButton\"]) :is(svg, [class*=\"chevron\"], span) {",
1215
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"] :is(button[aria-label^=\"发送\"], button[aria-label^=\"Send\"], button[aria-label*=\"发送\"], button[aria-label*=\"Send\"], button[aria-label*=\"排队\"], button[aria-label*=\"Queue\"], button[aria-label*=\"插话\"], button[aria-label*=\"Steer\"], [class*=\"sendBtn\"], [class*=\"sendButton\"]) :is(svg, [class*=\"chevron\"], span) {",
1062
1216
  " display: none !important;",
1063
1217
  "}",
1064
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"] :is(button[aria-label^=\"发送\"], button[aria-label^=\"Send\"], button[aria-label*=\"发送\"], button[aria-label*=\"Send\"], button[aria-label*=\"排队\"], button[aria-label*=\"Queue\"], button[aria-label*=\"插话\"], button[aria-label*=\"Steer\"], [class*=\"sendBtn\"], [class*=\"sendButton\"])::after,",
1065
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] :is(button[aria-label^=\"发送\"], button[aria-label^=\"Send\"], button[aria-label*=\"发送\"], button[aria-label*=\"Send\"], button[aria-label*=\"排队\"], button[aria-label*=\"Queue\"], button[aria-label*=\"插话\"], button[aria-label*=\"Steer\"], [class*=\"sendBtn\"], [class*=\"sendButton\"])::after {",
1218
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"] :is(button[aria-label^=\"发送\"], button[aria-label^=\"Send\"], button[aria-label*=\"发送\"], button[aria-label*=\"Send\"], button[aria-label*=\"排队\"], button[aria-label*=\"Queue\"], button[aria-label*=\"插话\"], button[aria-label*=\"Steer\"], [class*=\"sendBtn\"], [class*=\"sendButton\"])::after,",
1219
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] :is(button[aria-label^=\"发送\"], button[aria-label^=\"Send\"], button[aria-label*=\"发送\"], button[aria-label*=\"Send\"], button[aria-label*=\"排队\"], button[aria-label*=\"Queue\"], button[aria-label*=\"插话\"], button[aria-label*=\"Steer\"], [class*=\"sendBtn\"], [class*=\"sendButton\"])::after {",
1066
1220
  " content: \"↵\" !important;",
1067
1221
  " font-family: var(--dsw-font-code, monospace) !important;",
1068
1222
  " font-size: 16px !important;",
@@ -1070,18 +1224,20 @@ window.__ModuleLoader__.load({
1070
1224
  " line-height: 1 !important;",
1071
1225
  " color: currentColor !important;",
1072
1226
  "}",
1073
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"] :is(button[aria-label^=\"发送\"], button[aria-label^=\"Send\"], button[aria-label*=\"发送\"], button[aria-label*=\"Send\"]):not(:disabled):hover,",
1074
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] :is(button[aria-label^=\"发送\"], button[aria-label^=\"Send\"], button[aria-label*=\"发送\"], button[aria-label*=\"Send\"]):not(:disabled):hover {",
1227
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"] :is(button[aria-label^=\"发送\"], button[aria-label^=\"Send\"], button[aria-label*=\"发送\"], button[aria-label*=\"Send\"]):not(:disabled):hover,",
1228
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] :is(button[aria-label^=\"发送\"], button[aria-label^=\"Send\"], button[aria-label*=\"发送\"], button[aria-label*=\"Send\"]):not(:disabled):hover {",
1075
1229
  " background: var(--dsh-claude-hover-bg, rgba(0, 0, 0, 0.08)) !important;",
1076
1230
  " color: var(--dsw-alias-label-primary) !important;",
1077
1231
  "}",
1078
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"]:not(:has([data-composer-placeholder])) :is(button[aria-label^=\"发送\"], button[aria-label^=\"Send\"], button[aria-label*=\"发送\"], button[aria-label*=\"Send\"]),",
1079
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card]:not(:has([data-composer-placeholder])) :is(button[aria-label^=\"发送\"], button[aria-label^=\"Send\"], button[aria-label*=\"发送\"], button[aria-label*=\"Send\"]) {",
1232
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"]:not(:has([data-composer-placeholder])) :is(button[aria-label^=\"发送\"], button[aria-label^=\"Send\"], button[aria-label*=\"发送\"], button[aria-label*=\"Send\"]),",
1233
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"][data-has-attachments=\"true\"] :is(button[aria-label^=\"发送\"], button[aria-label^=\"Send\"], button[aria-label*=\"发送\"], button[aria-label*=\"Send\"]),",
1234
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card]:not(:has([data-composer-placeholder])) :is(button[aria-label^=\"发送\"], button[aria-label^=\"Send\"], button[aria-label*=\"发送\"], button[aria-label*=\"Send\"]),",
1235
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card][data-has-attachments=\"true\"] :is(button[aria-label^=\"发送\"], button[aria-label^=\"Send\"], button[aria-label*=\"发送\"], button[aria-label*=\"Send\"]) {",
1080
1236
  " color: var(--dsw-alias-brand-primary, #d97757) !important;",
1081
1237
  "}",
1082
1238
  "/* Pinned stop button inside input box */",
1083
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"] :is(button[aria-label*=\"停止\"], button[aria-label*=\"Stop\"]),",
1084
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] :is(button[aria-label*=\"停止\"], button[aria-label*=\"Stop\"]) {",
1239
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"] :is(button[aria-label*=\"停止\"], button[aria-label*=\"Stop\"]),",
1240
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] :is(button[aria-label*=\"停止\"], button[aria-label*=\"Stop\"]) {",
1085
1241
  " position: absolute !important;",
1086
1242
  " bottom: 40px !important;",
1087
1243
  " right: 8px !important;",
@@ -1100,22 +1256,22 @@ window.__ModuleLoader__.load({
1100
1256
  " color: var(--dsw-alias-brand-primary, #d97757) !important;",
1101
1257
  " border: none !important;",
1102
1258
  "}",
1103
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"] :is(button[aria-label*=\"停止\"], button[aria-label*=\"Stop\"]) svg,",
1104
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] :is(button[aria-label*=\"停止\"], button[aria-label*=\"Stop\"]) svg {",
1259
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"] :is(button[aria-label*=\"停止\"], button[aria-label*=\"Stop\"]) svg,",
1260
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] :is(button[aria-label*=\"停止\"], button[aria-label*=\"Stop\"]) svg {",
1105
1261
  " display: block !important;",
1106
1262
  " width: 12px !important;",
1107
1263
  " height: 12px !important;",
1108
1264
  "}",
1109
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"] :is(button[aria-label*=\"停止\"], button[aria-label*=\"Stop\"])::after,",
1110
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] :is(button[aria-label*=\"停止\"], button[aria-label*=\"Stop\"])::after {",
1265
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"] :is(button[aria-label*=\"停止\"], button[aria-label*=\"Stop\"])::after,",
1266
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] :is(button[aria-label*=\"停止\"], button[aria-label*=\"Stop\"])::after {",
1111
1267
  " display: none !important;",
1112
1268
  "}",
1113
1269
  "/* Toolbar row below input card. `[class*=\"_row\"]` on purpose: a bare",
1114
1270
  " `[class*=\"row\"]` substring-matches the shipped `.p_FcLG_grow` input",
1115
1271
  " wrapper (\"grow\" contains \"row\"), and the fixed 28px height + flex",
1116
1272
  " display here would pin the composer field to one line forever. */",
1117
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"] [class*=\"_row\"],",
1118
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] [class*=\"_row\"] {",
1273
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"] [class*=\"_row\"],",
1274
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] [class*=\"_row\"] {",
1119
1275
  " display: flex !important;",
1120
1276
  " flex-wrap: nowrap !important;",
1121
1277
  " justify-content: space-between !important;",
@@ -1131,6 +1287,8 @@ window.__ModuleLoader__.load({
1131
1287
  " max-width: 100% !important;",
1132
1288
  " box-sizing: border-box !important;",
1133
1289
  " overflow: visible !important;",
1290
+ " position: relative !important;",
1291
+ " z-index: 3 !important;",
1134
1292
  "}",
1135
1293
  "/* The context-occupancy meter renders its click-open panel INSIDE the inline",
1136
1294
  " composer card, and its legend classes (`…_rows` / `…_row`) substring-match",
@@ -1138,8 +1296,8 @@ window.__ModuleLoader__.load({
1138
1296
  " list into one 28px flex line and wrapped every label/value. Restore the",
1139
1297
  " shipped legend layout: the list stacks vertically, each row keeps its",
1140
1298
  " natural height and space-between distribution. */",
1141
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"] [class*=\"_rows\"],",
1142
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] [class*=\"_rows\"] {",
1299
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"] [class*=\"_rows\"],",
1300
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] [class*=\"_rows\"] {",
1143
1301
  " display: block !important;",
1144
1302
  " height: auto !important;",
1145
1303
  " min-height: 0 !important;",
@@ -1147,8 +1305,8 @@ window.__ModuleLoader__.load({
1147
1305
  " max-width: none !important;",
1148
1306
  " padding: 0 !important;",
1149
1307
  "}",
1150
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"] [class*=\"_rows\"] [class*=\"_row\"],",
1151
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] [class*=\"_rows\"] [class*=\"_row\"] {",
1308
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"] [class*=\"_rows\"] [class*=\"_row\"],",
1309
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] [class*=\"_rows\"] [class*=\"_row\"] {",
1152
1310
  " display: flex !important;",
1153
1311
  " height: auto !important;",
1154
1312
  " min-height: 0 !important;",
@@ -1157,23 +1315,23 @@ window.__ModuleLoader__.load({
1157
1315
  " padding: 2px 0 !important;",
1158
1316
  " margin: 0 !important;",
1159
1317
  "}",
1160
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"] [class*=\"tools\"],",
1161
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] [class*=\"tools\"] {",
1318
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"] [class*=\"tools\"],",
1319
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] [class*=\"tools\"] {",
1162
1320
  " display: flex !important;",
1163
1321
  " align-items: center !important;",
1164
1322
  " gap: 8px !important;",
1165
1323
  " flex: 0 0 auto !important;",
1166
1324
  " overflow: visible !important;",
1167
1325
  "}",
1168
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"] [class*=\"modes\"],",
1169
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] [class*=\"modes\"] {",
1326
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"] [class*=\"modes\"],",
1327
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] [class*=\"modes\"] {",
1170
1328
  " order: 1 !important;",
1171
1329
  " display: inline-flex !important;",
1172
1330
  " align-items: center !important;",
1173
1331
  " overflow: visible !important;",
1174
1332
  "}",
1175
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"] :is(button[aria-label=\"指令\"], button[aria-label=\"Commands\"]),",
1176
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] :is(button[aria-label=\"指令\"], button[aria-label=\"Commands\"]) {",
1333
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"] :is(button[aria-label=\"指令\"], button[aria-label=\"Commands\"]),",
1334
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] :is(button[aria-label=\"指令\"], button[aria-label=\"Commands\"]) {",
1177
1335
  " order: 2 !important;",
1178
1336
  " width: 24px !important;",
1179
1337
  " height: 24px !important;",
@@ -1184,8 +1342,8 @@ window.__ModuleLoader__.load({
1184
1342
  " align-items: center !important;",
1185
1343
  " justify-content: center !important;",
1186
1344
  "}",
1187
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"] :is(button[aria-label=\"添加附件\"], button[aria-label=\"Add attachment\"]),",
1188
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] :is(button[aria-label=\"添加附件\"], button[aria-label=\"Add attachment\"]) {",
1345
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"] :is(button[aria-label=\"添加附件\"], button[aria-label=\"Add attachment\"]),",
1346
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] :is(button[aria-label=\"添加附件\"], button[aria-label=\"Add attachment\"]) {",
1189
1347
  " order: 3 !important;",
1190
1348
  " width: 24px !important;",
1191
1349
  " height: 24px !important;",
@@ -1196,13 +1354,13 @@ window.__ModuleLoader__.load({
1196
1354
  " align-items: center !important;",
1197
1355
  " justify-content: center !important;",
1198
1356
  "}",
1199
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"] :is(button[aria-label=\"指令\"], button[aria-label=\"Commands\"], button[aria-label=\"添加附件\"], button[aria-label=\"Add attachment\"]):hover:not(:disabled),",
1200
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] :is(button[aria-label=\"指令\"], button[aria-label=\"Commands\"], button[aria-label=\"添加附件\"], button[aria-label=\"Add attachment\"]):hover:not(:disabled) {",
1357
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"] :is(button[aria-label=\"指令\"], button[aria-label=\"Commands\"], button[aria-label=\"添加附件\"], button[aria-label=\"Add attachment\"]):hover:not(:disabled),",
1358
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] :is(button[aria-label=\"指令\"], button[aria-label=\"Commands\"], button[aria-label=\"添加附件\"], button[aria-label=\"Add attachment\"]):hover:not(:disabled) {",
1201
1359
  " background: var(--dsh-claude-hover-bg, rgba(0, 0, 0, 0.08)) !important;",
1202
1360
  " color: var(--dsw-alias-label-primary) !important;",
1203
1361
  "}",
1204
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"] [class*=\"trailing\"],",
1205
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] [class*=\"trailing\"] {",
1362
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"] [class*=\"trailing\"],",
1363
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] [class*=\"trailing\"] {",
1206
1364
  " display: flex !important;",
1207
1365
  " align-items: center !important;",
1208
1366
  " gap: 8px !important;",
@@ -1210,8 +1368,8 @@ window.__ModuleLoader__.load({
1210
1368
  " flex: 0 0 auto !important;",
1211
1369
  "}",
1212
1370
  "/* Model trigger in trailing */",
1213
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"] [class*=\"trailing\"] > :first-child,",
1214
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"] [class*=\"trailing\"] :is([class*=\"trigger\" i], [class*=\"model\" i], [class*=\"Model\"]) {",
1371
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"] [class*=\"trailing\"] > :first-child,",
1372
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"] [class*=\"trailing\"] :is([class*=\"trigger\" i], [class*=\"model\" i], [class*=\"Model\"]) {",
1215
1373
  " display: inline-flex !important;",
1216
1374
  " align-items: center !important;",
1217
1375
  " font-size: 13px !important;",
@@ -1221,8 +1379,8 @@ window.__ModuleLoader__.load({
1221
1379
  " visibility: visible !important;",
1222
1380
  " opacity: 1 !important;",
1223
1381
  "}",
1224
- "body[data-dsh-claude-style]:not([data-ds-dark-theme]) [data-composer-card][data-composer-variant=\"inline\"] [class*=\"trailing\"] > :first-child:hover:not(:disabled),",
1225
- "body[data-dsh-claude-style]:not([data-ds-dark-theme]) [data-composer-card][data-composer-variant=\"inline\"] [class*=\"trailing\"] :is([class*=\"trigger\" i], [class*=\"model\" i], [class*=\"Model\"]):hover:not(:disabled) {",
1382
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active]:not([data-ds-dark-theme]) [data-composer-card][data-composer-variant=\"inline\"] [class*=\"trailing\"] > :first-child:hover:not(:disabled),",
1383
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active]:not([data-ds-dark-theme]) [data-composer-card][data-composer-variant=\"inline\"] [class*=\"trailing\"] :is([class*=\"trigger\" i], [class*=\"model\" i], [class*=\"Model\"]):hover:not(:disabled) {",
1226
1384
  " background: var(--dsh-claude-hover-bg, rgba(0, 0, 0, 0.06)) !important;",
1227
1385
  " color: var(--dsw-alias-label-primary) !important;",
1228
1386
  "}",
@@ -1230,10 +1388,10 @@ window.__ModuleLoader__.load({
1230
1388
  " root is marked and hidden by syncModelControl). One button, one hover",
1231
1389
  " background — the shipped seat stacks an icon + label + effort + chevron,",
1232
1390
  " each with its own surface. */",
1233
- "body[data-dsh-claude-style] [data-dsh-claude-model-host] {",
1391
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-dsh-claude-model-host] {",
1234
1392
  " display: none !important;",
1235
1393
  "}",
1236
- "body[data-dsh-claude-style] [class*=\"trailing\"] .dsh-claude-model-btn {",
1394
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"trailing\"] .dsh-claude-model-btn {",
1237
1395
  " display: inline-flex !important;",
1238
1396
  " align-items: center !important;",
1239
1397
  " gap: 2px !important;",
@@ -1251,25 +1409,25 @@ window.__ModuleLoader__.load({
1251
1409
  " box-sizing: border-box !important;",
1252
1410
  " min-width: 0 !important;",
1253
1411
  "}",
1254
- "body[data-dsh-claude-style] [class*=\"trailing\"] .dsh-claude-model-btn-label.dsh-claude-model-btn-loading {",
1412
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"trailing\"] .dsh-claude-model-btn-label.dsh-claude-model-btn-loading {",
1255
1413
  " color: var(--dsw-alias-label-caption, #a6a094) !important;",
1256
1414
  "}",
1257
- "body[data-dsh-claude-style] [class*=\"trailing\"] .dsh-claude-model-btn:hover {",
1415
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"trailing\"] .dsh-claude-model-btn:hover {",
1258
1416
  " background: var(--dsh-claude-hover-bg, rgba(0, 0, 0, 0.08)) !important;",
1259
1417
  " color: var(--dsw-alias-label-primary) !important;",
1260
1418
  "}",
1261
- "body[data-dsh-claude-style] [class*=\"trailing\"] .dsh-claude-model-btn-label {",
1419
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"trailing\"] .dsh-claude-model-btn-label {",
1262
1420
  " text-overflow: ellipsis !important;",
1263
1421
  " white-space: nowrap !important;",
1264
1422
  " min-width: 0 !important;",
1265
1423
  " overflow: hidden !important;",
1266
1424
  "}",
1267
- "body[data-dsh-claude-style] [class*=\"trailing\"] .dsh-claude-model-btn-effort {",
1425
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"trailing\"] .dsh-claude-model-btn-effort {",
1268
1426
  " flex: none !important;",
1269
1427
  " color: var(--dsw-alias-label-caption, #a6a094) !important;",
1270
1428
  " font-weight: 400 !important;",
1271
1429
  "}",
1272
- "body[data-dsh-claude-style] [class*=\"trailing\"] .dsh-claude-model-btn-chevron {",
1430
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"trailing\"] .dsh-claude-model-btn-chevron {",
1273
1431
  " flex: none !important;",
1274
1432
  " display: inline-flex !important;",
1275
1433
  " align-items: center !important;",
@@ -1280,8 +1438,10 @@ window.__ModuleLoader__.load({
1280
1438
  " position (a full-width line under the card); once mergeStatsIntoRow()",
1281
1439
  " has moved the pills into the row, the override after it resets every",
1282
1440
  " geometry property the host's full-width line depends on. */",
1283
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"] ~ [data-composer-stats],",
1284
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-stats] {",
1441
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"] ~ [data-composer-stats],",
1442
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-stats] {",
1443
+ " position: relative !important;",
1444
+ " z-index: 3 !important;",
1285
1445
  " display: flex !important;",
1286
1446
  " align-items: center !important;",
1287
1447
  " justify-content: center !important;",
@@ -1303,8 +1463,10 @@ window.__ModuleLoader__.load({
1303
1463
  " tool cluster and the trailing model/status group (the row's space-between",
1304
1464
  " centers them). Width, padding and margin come from the host's full-width",
1305
1465
  " line, so all of them are reset; the pills keep their own hover/click. */",
1306
- "body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"] [class*=\"_row\"] [data-composer-stats],",
1307
- "body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] [class*=\"_row\"] [data-composer-stats] {",
1466
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"] [class*=\"_row\"] [data-composer-stats],",
1467
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-card] [class*=\"_row\"] [data-composer-stats] {",
1468
+ " position: relative !important;",
1469
+ " z-index: 3 !important;",
1308
1470
  " width: auto !important;",
1309
1471
  " max-width: none !important;",
1310
1472
  " margin: 0 8px !important;",
@@ -1319,20 +1481,20 @@ window.__ModuleLoader__.load({
1319
1481
  " is hovered, so the toolbar stays clean. Space is preserved — no reflow when",
1320
1482
  " they fade in — and because the pills live inside the card, hovering them",
1321
1483
  " keeps them visible long enough to click. */",
1322
- "body[data-dsh-claude-style] [data-composer-card] [class*=\"_row\"] [data-composer-stats] {",
1484
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card] [class*=\"_row\"] [data-composer-stats] {",
1323
1485
  " opacity: 0 !important;",
1324
1486
  " visibility: hidden !important;",
1325
1487
  " pointer-events: none !important;",
1326
1488
  " transition: opacity 0.15s ease, visibility 0.15s ease !important;",
1327
1489
  "}",
1328
- "body[data-dsh-claude-style] [data-composer-card]:hover [class*=\"_row\"] [data-composer-stats] {",
1490
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card]:hover [class*=\"_row\"] [data-composer-stats] {",
1329
1491
  " opacity: 1 !important;",
1330
1492
  " visibility: visible !important;",
1331
1493
  " pointer-events: auto !important;",
1332
1494
  "}",
1333
1495
  "@media (max-width: 820px) {",
1334
- " body[data-dsh-claude-style] [data-composer-card][data-composer-variant=\"inline\"] ~ [data-composer-stats],",
1335
- " body[data-dsh-claude-style] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-stats] {",
1496
+ " body[data-dsh-claude-style][data-dsh-claude-composer-active] [data-composer-card][data-composer-variant=\"inline\"] ~ [data-composer-stats],",
1497
+ " body[data-dsh-claude-style][data-dsh-claude-composer-active] [class*=\"composerStack\"]:not(:has([class*=\"heroWorkspaceRow\"])) [data-composer-stats] {",
1336
1498
  " display: none !important;",
1337
1499
  " }",
1338
1500
  "}",
@@ -1348,11 +1510,11 @@ window.__ModuleLoader__.load({
1348
1510
  " brandName box, and each wordmark takes its natural width from that height",
1349
1511
  " via the artwork aspect ratios (Claude 4.214:1, ANTHROPIC ~8.94:1). The",
1350
1512
  " wordmark keeps a max-width so a longer brand can never clip the row. */",
1351
- "body[data-dsh-claude-style] :is([class*=\"brandMark\"], [class*=\"railMark\"]) > * {",
1513
+ "body[data-dsh-claude-style]:is([data-dsh-claude-brand=\"claude\"], [data-dsh-claude-brand=\"anthropic\"]) :is([class*=\"brandMark\"], [class*=\"railMark\"]) > * {",
1352
1514
  " display: none !important;",
1353
1515
  "}",
1354
1516
  "",
1355
- "body[data-dsh-claude-style] :is([class*=\"brandMark\"], [class*=\"railMark\"])::before {",
1517
+ "body[data-dsh-claude-style]:is([data-dsh-claude-brand=\"claude\"], [data-dsh-claude-brand=\"anthropic\"]) :is([class*=\"brandMark\"], [class*=\"railMark\"])::before {",
1356
1518
  " content: \"\";",
1357
1519
  " display: block;",
1358
1520
  " flex: none;",
@@ -1363,18 +1525,18 @@ window.__ModuleLoader__.load({
1363
1525
  "}",
1364
1526
  "",
1365
1527
  "/* Claude (default): starburst mark + the Claude wordmark. */",
1366
- "body[data-dsh-claude-style]:not([data-dsh-claude-brand=\"anthropic\"]) :is([class*=\"brandMark\"], [class*=\"railMark\"])::before {",
1528
+ "body[data-dsh-claude-style][data-dsh-claude-brand=\"claude\"] :is([class*=\"brandMark\"], [class*=\"railMark\"])::before {",
1367
1529
  " width: 18px;",
1368
1530
  " height: 18px;",
1369
1531
  " -webkit-mask: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%20148.09%20148.18'%3E%3Cg%20transform%3D'translate(-75.96%2C-223.53)'%3E%3Cpath%20d%3D'm%20105.01%2C322.07%2029.14%2C-16.35%200.49%2C-1.42%20-0.49%2C-0.79%20h%20-1.42%20l%20-4.87%2C-0.3%20-16.65%2C-0.45%20-14.44%2C-0.6%20-13.99%2C-0.75%20-3.52%2C-0.75%20-3.3%2C-4.35%200.34%2C-2.17%202.96%2C-1.99%204.24%2C0.37%209.37%2C0.64%2014.06%2C0.97%2010.2%2C0.6%2015.11%2C1.57%20h%202.4%20l%200.34%2C-0.97%20-0.82%2C-0.6%20-0.64%2C-0.6%20-14.55%2C-9.86%20-15.75%2C-10.42%20-8.25%2C-6%20-4.46%2C-3.04%20-2.25%2C-2.85%20-0.97%2C-6.22%204.05%2C-4.46%205.44%2C0.37%201.39%2C0.37%205.51%2C4.24%2011.77%2C9.11%2015.37%2C11.32%202.25%2C1.87%200.9%2C-0.64%200.11%2C-0.45%20-1.01%2C-1.69%20-8.36%2C-15.11%20-8.92%2C-15.37%20-3.97%2C-6.37%20-1.05%2C-3.82%20c%20-0.37%2C-1.57%20-0.64%2C-2.89%20-0.64%2C-4.5%20l%204.61%2C-6.26%202.55%2C-0.82%206.15%2C0.82%202.59%2C2.25%203.82%2C8.74%206.19%2C13.76%209.6%2C18.71%202.81%2C5.55%201.5%2C5.14%200.56%2C1.57%20h%200.97%20v%20-0.9%20l%200.79%2C-10.54%201.46%2C-12.94%201.42%2C-16.65%200.49%2C-4.69%202.32%2C-5.62%204.61%2C-3.04%203.6%2C1.72%202.96%2C4.24%20-0.41%2C2.74%20-1.76%2C11.44%20-3.45%2C17.92%20-2.25%2C12%20h%201.31%20l%201.5%2C-1.5%206.07%2C-8.06%2010.2%2C-12.75%204.5%2C-5.06%205.25%2C-5.59%203.37%2C-2.66%20h%206.37%20l%204.69%2C6.97%20-2.1%2C7.2%20-6.56%2C8.32%20-5.44%2C7.05%20-7.8%2C10.5%20-4.87%2C8.4%200.45%2C0.67%201.16%2C-0.11%2017.62%2C-3.75%209.52%2C-1.72%2011.36%2C-1.95%205.14%2C2.4%200.56%2C2.44%20-2.02%2C4.99%20-12.15%2C3%20-14.25%2C2.85%20-21.22%2C5.02%20-0.26%2C0.19%200.3%2C0.37%209.56%2C0.9%204.09%2C0.22%20h%2010.01%20l%2018.64%2C1.39%204.87%2C3.22%202.92%2C3.94%20-0.49%2C3%20-7.5%2C3.82%20-10.12%2C-2.4%20-23.62%2C-5.62%20-8.1%2C-2.02%20h%20-1.12%20v%200.67%20l%206.75%2C6.6%2012.37%2C11.17%2015.49%2C14.4%200.79%2C3.56%20-1.99%2C2.81%20-2.1%2C-0.3%20-13.61%2C-10.24%20-5.25%2C-4.61%20-11.89%2C-10.01%20h%20-0.79%20v%201.05%20l%202.74%2C4.01%2014.47%2C21.75%200.75%2C6.67%20-1.05%2C2.17%20-3.75%2C1.31%20-4.12%2C-0.75%20-8.47%2C-11.89%20-8.74%2C-13.39%20-7.05%2C-12%20-0.86%2C0.49%20-4.16%2C44.81%20-1.95%2C2.29%20-4.5%2C1.72%20-3.75%2C-2.85%20-1.99%2C-4.61%201.99%2C-9.11%202.4%2C-11.89%201.95%2C-9.45%201.76%2C-11.74%201.05%2C-3.9%20-0.07%2C-0.26%20-0.86%2C0.11%20-8.85%2C12.15%20-13.46%2C18.19%20-10.65%2C11.4%20-2.55%2C1.01%20-4.42%2C-2.29%200.41%2C-4.09%202.47%2C-3.64%2014.74%2C-18.75%208.89%2C-11.62%205.74%2C-6.71%20-0.04%2C-0.97%20h%20-0.34%20l%20-39.15%2C25.42%20-6.97%2C0.9%20-3%2C-2.81%200.37%2C-4.61%201.42%2C-1.5%2011.77%2C-8.1%20-0.04%2C0.04%20z'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\") center / contain no-repeat;",
1370
1532
  " mask: url(\"data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%20148.09%20148.18'%3E%3Cg%20transform%3D'translate(-75.96%2C-223.53)'%3E%3Cpath%20d%3D'm%20105.01%2C322.07%2029.14%2C-16.35%200.49%2C-1.42%20-0.49%2C-0.79%20h%20-1.42%20l%20-4.87%2C-0.3%20-16.65%2C-0.45%20-14.44%2C-0.6%20-13.99%2C-0.75%20-3.52%2C-0.75%20-3.3%2C-4.35%200.34%2C-2.17%202.96%2C-1.99%204.24%2C0.37%209.37%2C0.64%2014.06%2C0.97%2010.2%2C0.6%2015.11%2C1.57%20h%202.4%20l%200.34%2C-0.97%20-0.82%2C-0.6%20-0.64%2C-0.6%20-14.55%2C-9.86%20-15.75%2C-10.42%20-8.25%2C-6%20-4.46%2C-3.04%20-2.25%2C-2.85%20-0.97%2C-6.22%204.05%2C-4.46%205.44%2C0.37%201.39%2C0.37%205.51%2C4.24%2011.77%2C9.11%2015.37%2C11.32%202.25%2C1.87%200.9%2C-0.64%200.11%2C-0.45%20-1.01%2C-1.69%20-8.36%2C-15.11%20-8.92%2C-15.37%20-3.97%2C-6.37%20-1.05%2C-3.82%20c%20-0.37%2C-1.57%20-0.64%2C-2.89%20-0.64%2C-4.5%20l%204.61%2C-6.26%202.55%2C-0.82%206.15%2C0.82%202.59%2C2.25%203.82%2C8.74%206.19%2C13.76%209.6%2C18.71%202.81%2C5.55%201.5%2C5.14%200.56%2C1.57%20h%200.97%20v%20-0.9%20l%200.79%2C-10.54%201.46%2C-12.94%201.42%2C-16.65%200.49%2C-4.69%202.32%2C-5.62%204.61%2C-3.04%203.6%2C1.72%202.96%2C4.24%20-0.41%2C2.74%20-1.76%2C11.44%20-3.45%2C17.92%20-2.25%2C12%20h%201.31%20l%201.5%2C-1.5%206.07%2C-8.06%2010.2%2C-12.75%204.5%2C-5.06%205.25%2C-5.59%203.37%2C-2.66%20h%206.37%20l%204.69%2C6.97%20-2.1%2C7.2%20-6.56%2C8.32%20-5.44%2C7.05%20-7.8%2C10.5%20-4.87%2C8.4%200.45%2C0.67%201.16%2C-0.11%2017.62%2C-3.75%209.52%2C-1.72%2011.36%2C-1.95%205.14%2C2.4%200.56%2C2.44%20-2.02%2C4.99%20-12.15%2C3%20-14.25%2C2.85%20-21.22%2C5.02%20-0.26%2C0.19%200.3%2C0.37%209.56%2C0.9%204.09%2C0.22%20h%2010.01%20l%2018.64%2C1.39%204.87%2C3.22%202.92%2C3.94%20-0.49%2C3%20-7.5%2C3.82%20-10.12%2C-2.4%20-23.62%2C-5.62%20-8.1%2C-2.02%20h%20-1.12%20v%200.67%20l%206.75%2C6.6%2012.37%2C11.17%2015.49%2C14.4%200.79%2C3.56%20-1.99%2C2.81%20-2.1%2C-0.3%20-13.61%2C-10.24%20-5.25%2C-4.61%20-11.89%2C-10.01%20h%20-0.79%20v%201.05%20l%202.74%2C4.01%2014.47%2C21.75%200.75%2C6.67%20-1.05%2C2.17%20-3.75%2C1.31%20-4.12%2C-0.75%20-8.47%2C-11.89%20-8.74%2C-13.39%20-7.05%2C-12%20-0.86%2C0.49%20-4.16%2C44.81%20-1.95%2C2.29%20-4.5%2C1.72%20-3.75%2C-2.85%20-1.99%2C-4.61%201.99%2C-9.11%202.4%2C-11.89%201.95%2C-9.45%201.76%2C-11.74%201.05%2C-3.9%20-0.07%2C-0.26%20-0.86%2C0.11%20-8.85%2C12.15%20-13.46%2C18.19%20-10.65%2C11.4%20-2.55%2C1.01%20-4.42%2C-2.29%200.41%2C-4.09%202.47%2C-3.64%2014.74%2C-18.75%208.89%2C-11.62%205.74%2C-6.71%20-0.04%2C-0.97%20h%20-0.34%20l%20-39.15%2C25.42%20-6.97%2C0.9%20-3%2C-2.81%200.37%2C-4.61%201.42%2C-1.5%2011.77%2C-8.1%20-0.04%2C0.04%20z'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E\") center / contain no-repeat;",
1371
1533
  "}",
1372
1534
  "",
1373
- "body[data-dsh-claude-style] [class*=\"brandIdentity\"] > [class*=\"brandName\"] > * {",
1535
+ "body[data-dsh-claude-style]:is([data-dsh-claude-brand=\"claude\"], [data-dsh-claude-brand=\"anthropic\"]) [class*=\"brandIdentity\"] > [class*=\"brandName\"] > * {",
1374
1536
  " display: none !important;",
1375
1537
  "}",
1376
1538
  "",
1377
- "body[data-dsh-claude-style] [class*=\"brandIdentity\"] > [class*=\"brandName\"]::before {",
1539
+ "body[data-dsh-claude-style]:is([data-dsh-claude-brand=\"claude\"], [data-dsh-claude-brand=\"anthropic\"]) [class*=\"brandIdentity\"] > [class*=\"brandName\"]::before {",
1378
1540
  " content: \"\";",
1379
1541
  " display: block;",
1380
1542
  " flex: none;",
@@ -1384,7 +1546,7 @@ window.__ModuleLoader__.load({
1384
1546
  " background-size: contain;",
1385
1547
  "}",
1386
1548
  "",
1387
- "body[data-dsh-claude-style]:not([data-dsh-claude-brand=\"anthropic\"]) [class*=\"brandIdentity\"] > [class*=\"brandName\"]::before {",
1549
+ "body[data-dsh-claude-style][data-dsh-claude-brand=\"claude\"] [class*=\"brandIdentity\"] > [class*=\"brandName\"]::before {",
1388
1550
  " width: 75.9px;",
1389
1551
  " max-width: 100%;",
1390
1552
  " height: 18px;",
@@ -1681,8 +1843,8 @@ window.__ModuleLoader__.load({
1681
1843
  "/* ---------- Claude Code layout: permission segments ---------- */",
1682
1844
  "/* The shipped access control is one popup-select button; it is replaced by",
1683
1845
  " a Read | Edit | Auto segmented control over the same three presets. */",
1684
- "body[data-dsh-claude-style] button[aria-label^=\"访问模式\"],",
1685
- "body[data-dsh-claude-style] button[aria-label^=\"Access mode\"] {",
1846
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] button[aria-label^=\"访问模式\"],",
1847
+ "body[data-dsh-claude-style][data-dsh-claude-composer-active] button[aria-label^=\"Access mode\"] {",
1686
1848
  " display: none !important;",
1687
1849
  "}",
1688
1850
  "",
@@ -2025,6 +2187,9 @@ window.__ModuleLoader__.load({
2025
2187
  " font-weight: 500 !important;",
2026
2188
  " line-height: 16px !important;",
2027
2189
  "}",
2190
+ "/* The row carries one description line, in the shell's language: the copy",
2191
+ " document is localized rather than stacked, so there is no second-language",
2192
+ " rule to style. */",
2028
2193
  "body[data-dsh-claude-style] .dsh-claude-model-desc {",
2029
2194
  " font-size: 11px !important;",
2030
2195
  " line-height: 14px !important;",
@@ -2175,13 +2340,39 @@ window.__ModuleLoader__.load({
2175
2340
  " display: none !important;",
2176
2341
  "}",
2177
2342
  "",
2343
+ "/* ---------- Footer takeover ----------",
2344
+ " Everything in this block modifies the HOST's own footer (it hides entries and",
2345
+ " zeroes the containers that host them) and exists only because the skin",
2346
+ " replaces that footer with its account row. The \"Collapse the sidebar settings",
2347
+ " area\" preference turns the whole takeover off, so each rule below carries",
2348
+ " data-dsh-claude-footer-takeover — the skin sets it on <body> only while the takeover is on,",
2349
+ " and with it absent the host footer renders exactly as shipped. */",
2178
2350
  "/* Hide original trigger buttons in footArea while keeping containers intact for modals */",
2179
- "body[data-dsh-claude-style] [class*=\"footArea\"] > [class*=\"settingsArea\"] > [class*=\"triggerRow\"],",
2180
- "body[data-dsh-claude-style] [class*=\"footArea\"] > [class*=\"footerActions\"] :is([class*=\"footerButtons\"], > button) {",
2351
+ "body[data-dsh-claude-style][data-dsh-claude-footer-takeover] [class*=\"footArea\"] > [class*=\"settingsArea\"] > [class*=\"triggerRow\"],",
2352
+ "body[data-dsh-claude-style][data-dsh-claude-footer-takeover] [class*=\"footArea\"] > [class*=\"footerActions\"] :is([class*=\"footerButtons\"], > button) {",
2181
2353
  " display: none !important;",
2182
2354
  "}",
2183
- "body[data-dsh-claude-style] [class*=\"footArea\"] > [class*=\"settingsArea\"],",
2184
- "body[data-dsh-claude-style] [class*=\"footArea\"] > [class*=\"footerActions\"] {",
2355
+ "/* Both containers stay mounted at zero size so the overlays they host keep",
2356
+ " painting, but the collapse must stay geometric. font-size and line-height",
2357
+ " inherit, and the settings dialog mounts IN PLACE inside settingsArea (the",
2358
+ " host renders its overlay as a sibling of the trigger row — no portal): the",
2359
+ " dialog inherited font-size: 0 with line-height: 0px, so every text line",
2360
+ " that sets an explicit font-size without its own line-height (the SubAgent",
2361
+ " page's rows, the onboarding overlay) collapsed to a 0px line box and",
2362
+ " vanished. settingsArea hosts nothing besides its display:none'd trigger row",
2363
+ " and those in-place overlays, so it keeps only the geometric collapse and",
2364
+ " hands real inherited metrics down; footerActions still hosts plain plugin",
2365
+ " entries whose stray in-flow text only the zeroing keeps invisible, so the",
2366
+ " full collapse stays there. */",
2367
+ "body[data-dsh-claude-style][data-dsh-claude-footer-takeover] [class*=\"footArea\"] > [class*=\"settingsArea\"] {",
2368
+ " position: static !important;",
2369
+ " width: 0 !important;",
2370
+ " height: 0 !important;",
2371
+ " margin: 0 !important;",
2372
+ " padding: 0 !important;",
2373
+ " overflow: visible !important;",
2374
+ "}",
2375
+ "body[data-dsh-claude-style][data-dsh-claude-footer-takeover] [class*=\"footArea\"] > [class*=\"footerActions\"] {",
2185
2376
  " position: static !important;",
2186
2377
  " width: 0 !important;",
2187
2378
  " height: 0 !important;",
@@ -2200,10 +2391,10 @@ window.__ModuleLoader__.load({
2200
2391
  " keep their subtree visible — the overlay must stay reachable after the",
2201
2392
  " mirrored popover item opens it — but the entry box itself collapses so it",
2202
2393
  " never paints inside the zero-sized footerActions container. */",
2203
- "body[data-dsh-claude-style] [class*=\"footArea\"] [data-dsh-claude-footer-hidden] {",
2394
+ "body[data-dsh-claude-style][data-dsh-claude-footer-takeover] [class*=\"footArea\"] [data-dsh-claude-footer-hidden] {",
2204
2395
  " display: none !important;",
2205
2396
  "}",
2206
- "body[data-dsh-claude-style] [class*=\"footArea\"] [data-dsh-claude-footer-entry] {",
2397
+ "body[data-dsh-claude-style][data-dsh-claude-footer-takeover] [class*=\"footArea\"] [data-dsh-claude-footer-entry] {",
2207
2398
  " width: 0 !important;",
2208
2399
  " height: 0 !important;",
2209
2400
  " min-width: 0 !important;",
@@ -2229,13 +2420,47 @@ window.__ModuleLoader__.load({
2229
2420
  "body[data-dsh-claude-style] [class*=\"root\"][class*=\"collapsed\"] :is(.dsh-claude-account-label, .dsh-claude-account-chevron) {",
2230
2421
  " display: none !important;",
2231
2422
  "}",
2423
+ "/* The rail's popover cannot stay an absolutely positioned child of the footer.",
2424
+ " The sidebar column is 56px wide with `overflow: hidden`, so a panel anchored",
2425
+ " past the rail edge is cut off the moment it crosses it — and the base rule's",
2426
+ " percentage max-width, measured against the 59px footer, squashed it to 43px.",
2427
+ " Fixed positioning lifts it out of that clip (no ancestor is a containing block",
2428
+ " for it), and overrides.js anchors it to the trigger from there. left/right/",
2429
+ " bottom are reset so the base rule's own `!important` anchors cannot fight the",
2430
+ " resolved coordinates. */",
2232
2431
  "body[data-dsh-claude-style] [class*=\"root\"][class*=\"collapsed\"] .dsh-claude-account-popover {",
2233
- " left: calc(100% + 8px) !important;",
2234
- " bottom: 0 !important;",
2432
+ " position: fixed !important;",
2433
+ " left: auto !important;",
2235
2434
  " right: auto !important;",
2435
+ " bottom: auto !important;",
2436
+ " top: auto !important;",
2236
2437
  " width: 220px !important;",
2438
+ " max-width: min(220px, calc(100vw - 16px)) !important;",
2237
2439
  " transform-origin: bottom left !important;",
2238
2440
  "}",
2441
+ "/* The rail opens the panel beside the trigger, not above it, so the hover bridge",
2442
+ " has to span the horizontal gap: the base rule's downward strip would sit under",
2443
+ " the panel and leave the pointer's 8px crossing uncovered. */",
2444
+ "body[data-dsh-claude-style] [class*=\"root\"][class*=\"collapsed\"] .dsh-claude-account-popover::after {",
2445
+ " top: 0 !important;",
2446
+ " bottom: 0 !important;",
2447
+ " left: auto !important;",
2448
+ " right: 100% !important;",
2449
+ " width: 12px !important;",
2450
+ " height: auto !important;",
2451
+ "}",
2452
+ "/* In the rail the account button is the only control; the settings entry lives",
2453
+ " in the popover. The settings container is zero-sized rather than hidden (its",
2454
+ " dialog must stay reachable), which is enough while the sidebar is wide — the",
2455
+ " entry is squeezed to a 4px sliver. A plugin that ships its own rail variant",
2456
+ " escapes that box instead: the settings plugin's `…_rail` trigger keeps its",
2457
+ " full 36px square and lands straight on top of the Claude mark. Hide the entry",
2458
+ " in the rail. Reachability is unaffected — the popover's settings item opens",
2459
+ " the real trigger programmatically (`realTrigger.click()`), which",
2460
+ " display:none does not block. */",
2461
+ "body[data-dsh-claude-style][data-dsh-claude-footer-takeover] [class*=\"root\"][class*=\"collapsed\"] [class*=\"footArea\"] [class*=\"settingsArea\"] :is(button, [class*=\"triggerRow\"]) {",
2462
+ " display: none !important;",
2463
+ "}",
2239
2464
  "",
2240
2465
  "/* Embedded footer widgets: display-only plugin entries (progress bars,",
2241
2466
  " status panels — the cost-meter balance/quota stack is the known case)",
@@ -2297,10 +2522,12 @@ window.__ModuleLoader__.load({
2297
2522
  " overflow-x: auto;",
2298
2523
  "}",
2299
2524
  "",
2300
- "/* ---------- Claude Code layout: settings section (brand) ---------- */",
2301
- "/* The skin's own page in the settings dialog. It borrows the composer's",
2302
- " segment language so the two controls read as one design. */",
2303
- "body[data-dsh-claude-style] .dsh-claude-brand-section {",
2525
+ "/* ---------- Claude Code layout: settings page ---------- */",
2526
+ "/* The skin's own page in the settings dialog. Its segmented controls reuse the",
2527
+ " composer permission picker's classes (`.dsh-claude-segments` /",
2528
+ " `.dsh-claude-segment`), so the two are literally one control rather than two",
2529
+ " lookalikes; only the row shell and the switch are new here. */",
2530
+ "body[data-dsh-claude-style] .dsh-claude-settings {",
2304
2531
  " display: flex;",
2305
2532
  " flex-direction: column;",
2306
2533
  " gap: 16px;",
@@ -2310,13 +2537,13 @@ window.__ModuleLoader__.load({
2310
2537
  " font-family: var(--dsw-font-family);",
2311
2538
  "}",
2312
2539
  "",
2313
- "body[data-dsh-claude-style] .dsh-claude-brand-title {",
2540
+ "body[data-dsh-claude-style] .dsh-claude-settings-title {",
2314
2541
  " font-size: 15px;",
2315
2542
  " font-weight: 600;",
2316
2543
  " color: var(--dsw-alias-label-primary);",
2317
2544
  "}",
2318
2545
  "",
2319
- "body[data-dsh-claude-style] .dsh-claude-brand-card {",
2546
+ "body[data-dsh-claude-style] .dsh-claude-settings-row {",
2320
2547
  " display: flex;",
2321
2548
  " align-items: center;",
2322
2549
  " gap: 12px;",
@@ -2325,72 +2552,73 @@ window.__ModuleLoader__.load({
2325
2552
  " border-radius: 12px;",
2326
2553
  "}",
2327
2554
  "",
2328
- "body[data-dsh-claude-style] .dsh-claude-brand-card-text {",
2555
+ "body[data-dsh-claude-style] .dsh-claude-settings-row-text {",
2329
2556
  " flex: 1;",
2330
2557
  " min-width: 0;",
2331
2558
  "}",
2332
2559
  "",
2333
- "body[data-dsh-claude-style] .dsh-claude-brand-card-title {",
2560
+ "body[data-dsh-claude-style] .dsh-claude-settings-row-title {",
2334
2561
  " font-size: 13px;",
2335
2562
  " font-weight: 600;",
2336
2563
  " line-height: 1.5;",
2337
2564
  " color: var(--dsw-alias-label-primary);",
2338
2565
  "}",
2339
2566
  "",
2340
- "body[data-dsh-claude-style] .dsh-claude-brand-card-desc {",
2567
+ "body[data-dsh-claude-style] .dsh-claude-settings-row-desc {",
2341
2568
  " margin-top: 4px;",
2342
2569
  " font-size: 12px;",
2343
2570
  " line-height: 1.5;",
2344
2571
  " color: var(--dsw-alias-label-tertiary);",
2345
2572
  "}",
2346
2573
  "",
2347
- "body[data-dsh-claude-style] .dsh-claude-brand-segments {",
2348
- " flex: none;",
2349
- " display: inline-flex;",
2350
- " align-items: center;",
2351
- " gap: 2px;",
2352
- " padding: 2px;",
2353
- " border-radius: 7px;",
2354
- " background: var(--dsw-specific-selector);",
2355
- "}",
2356
- "",
2357
- "body[data-dsh-claude-style]:not([data-ds-dark-theme]) .dsh-claude-brand-segments {",
2358
- " background: #f6f6f4;",
2574
+ "body[data-dsh-claude-style] .dsh-claude-settings-error {",
2575
+ " font-size: 12px;",
2576
+ " line-height: 1.5;",
2577
+ " color: var(--dsw-alias-state-error-primary, #c0392b);",
2359
2578
  "}",
2360
2579
  "",
2361
- "body[data-dsh-claude-style] .dsh-claude-brand-option {",
2580
+ "/* The switch borrows the segment track's palette and radius so a page of rows",
2581
+ " reads as one control set rather than two. */",
2582
+ "body[data-dsh-claude-style] .dsh-claude-settings-switch {",
2583
+ " flex: none;",
2362
2584
  " appearance: none;",
2363
2585
  " margin: 0;",
2364
2586
  " border: 0;",
2365
2587
  " cursor: pointer;",
2366
- " background: transparent;",
2367
- " color: var(--dsw-alias-label-secondary);",
2368
- " font: inherit;",
2369
- " font-size: 13px;",
2370
- " font-weight: 500;",
2371
- " line-height: 18px;",
2372
- " padding: 3px 12px;",
2373
- " border-radius: 7px;",
2374
- " transition: background-color .1s, color .1s;",
2588
+ " width: 36px;",
2589
+ " height: 20px;",
2590
+ " padding: 2px;",
2591
+ " box-sizing: border-box;",
2592
+ " border-radius: 10px;",
2593
+ " background: var(--dsw-specific-selector);",
2594
+ " transition: background-color .12s ease;",
2375
2595
  "}",
2376
2596
  "",
2377
- "body[data-dsh-claude-style] .dsh-claude-brand-option:hover:not([data-active=\"true\"]) {",
2378
- " color: var(--dsw-alias-label-primary);",
2597
+ "body[data-dsh-claude-style]:not([data-ds-dark-theme]) .dsh-claude-settings-switch {",
2598
+ " background: #f6f6f4;",
2379
2599
  "}",
2380
2600
  "",
2381
- "body[data-dsh-claude-style] .dsh-claude-brand-option[data-active=\"true\"] {",
2382
- " color: var(--dsw-alias-label-primary);",
2383
- " background: var(--dsw-alias-bg-layer-3);",
2601
+ "body[data-dsh-claude-style] .dsh-claude-settings-switch[data-on] {",
2602
+ " background: var(--dsw-alias-brand-primary, #d97757);",
2384
2603
  "}",
2385
2604
  "",
2386
- "body[data-dsh-claude-style]:not([data-ds-dark-theme]) .dsh-claude-brand-option[data-active=\"true\"] {",
2387
- " background: var(--dsw-alias-bg-overlay);",
2388
- " box-shadow: 0 1px 2px rgba(20, 20, 19, 0.08);",
2605
+ "body[data-dsh-claude-style] .dsh-claude-settings-switch-knob {",
2606
+ " display: block;",
2607
+ " width: 16px;",
2608
+ " height: 16px;",
2609
+ " border-radius: 50%;",
2610
+ " background: var(--dsw-alias-bg-overlay, #ffffff);",
2611
+ " box-shadow: 0 1px 2px rgba(20, 20, 19, 0.18);",
2612
+ " transition: transform .12s ease;",
2613
+ "}",
2614
+ "",
2615
+ "body[data-dsh-claude-style] .dsh-claude-settings-switch[data-on] .dsh-claude-settings-switch-knob {",
2616
+ " transform: translateX(16px);",
2389
2617
  "}",
2390
2618
  ].join('\n')
2391
2619
 
2392
2620
  // ============================================================================
2393
- // Zone 3: 宿主上下文与通用辅助 (DSH Context & Helpers)
2621
+ // 宿主上下文访问器与辅助 (Host Context & Helpers)
2394
2622
  // ============================================================================
2395
2623
  function findAccessTrigger() {
2396
2624
  var prefixes = ['访问模式', 'Access mode']
@@ -2445,50 +2673,6 @@ window.__ModuleLoader__.load({
2445
2673
  }
2446
2674
  }
2447
2675
 
2448
- /**
2449
- * Read the persisted brand choice.
2450
- *
2451
- * Storage is the renderer's own localStorage, which DSH backs with the
2452
- * `persist:dsh-desktop-renderer` partition, so the value survives an app
2453
- * restart. Every access is guarded: a locked-down or full store must not
2454
- * break the theme, so an unreadable value simply falls back to the default.
2455
- *
2456
- * @returns one of BRAND_CLAUDE / BRAND_ANTHROPIC.
2457
- */
2458
- function readStoredBrand() {
2459
- try {
2460
- var raw = window.localStorage.getItem(BRAND_STORAGE_KEY)
2461
- if (raw === BRAND_ANTHROPIC) return BRAND_ANTHROPIC
2462
- if (raw === BRAND_CLAUDE) return BRAND_CLAUDE
2463
- } catch (error) {
2464
- /* storage unavailable — fall through to the default */
2465
- }
2466
- return DEFAULT_BRAND
2467
- }
2468
-
2469
- /** Persist the brand choice, ignoring a store that refuses writes. */
2470
- function writeStoredBrand(brand) {
2471
- try {
2472
- window.localStorage.setItem(BRAND_STORAGE_KEY, brand)
2473
- } catch (error) {
2474
- /* storage unavailable — the session still shows the chosen brand */
2475
- }
2476
- }
2477
-
2478
- /**
2479
- * Apply a brand to the document. The stylesheet keys off the attribute, so
2480
- * this is the single mutation that repaints the sidebar brand; keeping it to
2481
- * one attribute write is what makes the switch cheap and idempotent.
2482
- *
2483
- * @param brand - the brand to show; unknown values fall back to the default.
2484
- * @returns the brand actually applied.
2485
- */
2486
- function applyBrand(brand) {
2487
- var next = brand === BRAND_ANTHROPIC ? BRAND_ANTHROPIC : BRAND_CLAUDE
2488
- document.body.setAttribute(BRAND_ATTR, next)
2489
- return next
2490
- }
2491
-
2492
2676
  /**
2493
2677
  * The client context exposes no user or account service, so the account
2494
2678
  * name is inferred from the home-directory segment of a workspace path or
@@ -2518,21 +2702,388 @@ window.__ModuleLoader__.load({
2518
2702
  return 'User'
2519
2703
  }
2520
2704
 
2705
+ /**
2706
+ * Host context reference for services that need to read host state
2707
+ * (e.g. locale) outside of apply(ctx)'s direct call stack.
2708
+ */
2709
+ var hostCtx = null
2710
+ function setHostContext(ctx) {
2711
+ hostCtx = ctx
2712
+ }
2713
+
2714
+ /**
2715
+ * Skin preferences.
2716
+ *
2717
+ * The authoritative store is the host settings namespace `claude-style`,
2718
+ * reached through this plugin's own route: the configuration client
2719
+ * (`settingsScope`) only reaches namespaces the api-proxy exposes to it, and
2720
+ * a plugin's own namespace is not on that list. `src/../lib/index.js` owns
2721
+ * the namespace and the route; this side only reads and writes it.
2722
+ *
2723
+ * Every value is mirrored onto the document as an attribute, so the
2724
+ * stylesheet — not this module — decides what a preference means visually.
2725
+ * Until the first read settles (and if it fails) the defaults below hold,
2726
+ * which is exactly the shipped behaviour.
2727
+ */
2728
+ var prefs = {
2729
+ brand: DEFAULT_BRAND,
2730
+ collapseFooter: true,
2731
+ autoPopover: true,
2732
+ composerScope: 'all',
2733
+ }
2734
+ var prefsRevision
2735
+ var prefsAvailable = false
2736
+ var prefsListeners = []
2737
+
2738
+ /** The current preferences (live object; treat as read-only). */
2739
+ function readPrefs() {
2740
+ return prefs
2741
+ }
2742
+
2743
+ /** Observe preference changes; returns the unsubscriber. */
2744
+ function subscribePrefs(listener) {
2745
+ prefsListeners.push(listener)
2746
+ return function () {
2747
+ var index = prefsListeners.indexOf(listener)
2748
+ if (index !== -1) prefsListeners.splice(index, 1)
2749
+ }
2750
+ }
2751
+
2752
+ /**
2753
+ * Adopt a preference set: mirror it onto the document, then notify.
2754
+ * @param next - resolved preferences from the host.
2755
+ */
2756
+ function adoptPrefs(next) {
2757
+ prefs = next
2758
+ // The brand is one attribute write; the other preferences gate rules the
2759
+ // stylesheet and the scheduler read directly.
2760
+ document.body.setAttribute(BRAND_ATTR, next.brand)
2761
+ if (next.collapseFooter) document.body.setAttribute(FOOTER_ATTR, '')
2762
+ else document.body.removeAttribute(FOOTER_ATTR)
2763
+ var listeners = prefsListeners.slice()
2764
+ for (var i = 0; i < listeners.length; i++) {
2765
+ try {
2766
+ listeners[i](next)
2767
+ } catch (error) { /* one bad listener must not stop the rest */ }
2768
+ }
2769
+ }
2770
+
2771
+ /**
2772
+ * Read the preferences once. A failure keeps the defaults and leaves the
2773
+ * settings page to report that the store is unavailable.
2774
+ */
2775
+ function loadPrefs() {
2776
+ if (typeof fetch !== 'function') return
2777
+ try {
2778
+ fetch(PREFS_ROUTE, {
2779
+ method: 'POST',
2780
+ headers: { 'Content-Type': 'application/json' },
2781
+ credentials: 'same-origin',
2782
+ body: '{}',
2783
+ })
2784
+ .then(function (response) {
2785
+ return response.json()
2786
+ })
2787
+ .then(function (data) {
2788
+ if (!data || data.ok !== true) return
2789
+ prefsRevision = data.revision
2790
+ prefsAvailable = data.available === true
2791
+ adoptPrefs(normalizePrefs(data.value))
2792
+ })
2793
+ .catch(function () { /* defaults stay */ })
2794
+ } catch (error) { /* no fetch: defaults stay */ }
2795
+ }
2796
+
2797
+ /** Clamp one host value into the preference shape (the host already did this). */
2798
+ function normalizePrefs(value) {
2799
+ var section = value && typeof value === 'object' ? value : {}
2800
+ return {
2801
+ brand: section.brand === BRAND_ANTHROPIC || section.brand === BRAND_OFF ? section.brand : BRAND_CLAUDE,
2802
+ collapseFooter: section.collapseFooter !== false,
2803
+ autoPopover: section.autoPopover !== false,
2804
+ composerScope: COMPOSER_SCOPES.indexOf(section.composerScope) === -1 ? 'all' : section.composerScope,
2805
+ }
2806
+ }
2807
+
2808
+ /**
2809
+ * Write a partial preference change.
2810
+ *
2811
+ * The revision travels with the write so a concurrent move of the namespace
2812
+ * is rejected rather than silently overwritten; on that rejection the
2813
+ * authoritative value is re-read.
2814
+ *
2815
+ * @param patch - preference keys to change.
2816
+ * @returns a promise for the resolved preferences, or null when unavailable.
2817
+ */
2818
+ function savePrefs(patch) {
2819
+ if (typeof fetch !== 'function') return Promise.resolve(null)
2820
+ var body = { revision: prefsRevision }
2821
+ for (var key in patch) body[key] = patch[key]
2822
+ return fetch(PREFS_ROUTE, {
2823
+ method: 'POST',
2824
+ headers: { 'Content-Type': 'application/json' },
2825
+ credentials: 'same-origin',
2826
+ body: JSON.stringify(body),
2827
+ })
2828
+ .then(function (response) {
2829
+ return response.json().then(function (data) {
2830
+ return { status: response.status, data: data }
2831
+ })
2832
+ })
2833
+ .then(function (result) {
2834
+ var data = result.data
2835
+ if (data && data.ok === true) {
2836
+ prefsRevision = data.revision
2837
+ prefsAvailable = data.available === true
2838
+ adoptPrefs(normalizePrefs(data.value))
2839
+ return prefs
2840
+ }
2841
+ // Conflict or refusal: re-read rather than guess.
2842
+ loadPrefs()
2843
+ return null
2844
+ })
2845
+ .catch(function () {
2846
+ return null
2847
+ })
2848
+ }
2849
+
2850
+ /**
2851
+ * The brand to show. `off` leaves the brand area entirely to the host, so
2852
+ * the stylesheet matches neither brand variant for it.
2853
+ *
2854
+ * @param brand - the stored choice.
2855
+ * @returns the brand actually applied.
2856
+ */
2857
+ function applyBrand(brand) {
2858
+ var next = brand === BRAND_ANTHROPIC || brand === BRAND_OFF ? brand : BRAND_CLAUDE
2859
+ document.body.setAttribute(BRAND_ATTR, next)
2860
+ return next
2861
+ }
2862
+
2863
+ /**
2864
+ * Model & settings localized copy.
2865
+ *
2866
+ * The copy document ships as `model-descriptions.json` beside the bundle.
2867
+ * Both the model picker (src/overrides/model-picker.js) and the settings section
2868
+ * (src/settings.js) consume this copy, so the state lives in the shared context.
2869
+ * where both zones can reach it.
2870
+ */
2871
+ var modelCopy = null
2872
+ var modelCopyRequested = false
2873
+ var modelCopyListeners = []
2874
+
2875
+ function onModelCopyLoaded(listener) {
2876
+ modelCopyListeners.push(listener)
2877
+ return function () {
2878
+ var index = modelCopyListeners.indexOf(listener)
2879
+ if (index !== -1) modelCopyListeners.splice(index, 1)
2880
+ }
2881
+ }
2882
+
2883
+ /**
2884
+ * Fetch the model copy document the host half serves.
2885
+ */
2886
+ function loadModelCopy() {
2887
+ if (modelCopyRequested) return
2888
+ modelCopyRequested = true
2889
+ if (typeof fetch !== 'function') return
2890
+ try {
2891
+ fetch(MODEL_COPY_ROUTE, { credentials: 'same-origin' })
2892
+ .then(function (response) {
2893
+ if (!response.ok) throw new Error('HTTP ' + response.status)
2894
+ return response.json()
2895
+ })
2896
+ .then(function (doc) {
2897
+ modelCopy = indexModelCopy(doc)
2898
+ if (modelCopy === null) return
2899
+ var listeners = modelCopyListeners.slice()
2900
+ for (var i = 0; i < listeners.length; i++) {
2901
+ try {
2902
+ listeners[i](modelCopy)
2903
+ } catch (error) { /* listener error */ }
2904
+ }
2905
+ })
2906
+ .catch(function () { /* fallback copy stays */ })
2907
+ } catch (error) { /* no fetch: fallback copy stays */ }
2908
+ }
2909
+
2910
+ /**
2911
+ * Compile a copy document into the shape lookups want: a folded id index,
2912
+ * the alias table, and the rule lists with their regexps built once.
2913
+ * @param doc - parsed document; anything malformed is dropped, not fatal.
2914
+ * @returns the index, or null when the document is unusable.
2915
+ */
2916
+ function indexModelCopy(doc) {
2917
+ if (!doc || typeof doc !== 'object') return null
2918
+ var exact = doc.exact && typeof doc.exact === 'object' ? doc.exact : {}
2919
+ var index = {
2920
+ ui: doc.ui && typeof doc.ui === 'object' ? doc.ui : {},
2921
+ settings: doc.settings && typeof doc.settings === 'object' ? doc.settings : {},
2922
+ exact: exact,
2923
+ aliases: doc.aliases && typeof doc.aliases === 'object' ? doc.aliases : {},
2924
+ fallback: typeof doc.fallback === 'string' && doc.fallback ? doc.fallback : MODEL_COPY_FALLBACK_LOCALE,
2925
+ folded: {},
2926
+ foldedAliases: {},
2927
+ families: [],
2928
+ tiers: [],
2929
+ }
2930
+ for (var id in exact) index.folded[normalizeModelId(id)] = exact[id]
2931
+ for (var a in index.aliases) {
2932
+ index.foldedAliases[normalizeModelId(a)] = index.aliases[a]
2933
+ index.foldedAliases[a.toLowerCase()] = index.aliases[a]
2934
+ }
2935
+ var compile = function (rules) {
2936
+ var out = []
2937
+ for (var i = 0; i < (rules || []).length; i++) {
2938
+ var rule = rules[i]
2939
+ if (!rule || typeof rule.match !== 'string') continue
2940
+ try {
2941
+ out.push({ re: new RegExp(rule.match, 'i'), key: rule.key, text: rule.text })
2942
+ } catch (error) { /* a malformed rule is skipped, not fatal */ }
2943
+ }
2944
+ return out
2945
+ }
2946
+ index.families = compile(doc.families)
2947
+ index.tiers = compile(doc.tiers)
2948
+ return index
2949
+ }
2950
+
2951
+ /** Fold case and separators so `glm-5.3-flash` and `glm-5-3-flash` agree. */
2952
+ function normalizeModelId(id) {
2953
+ return String(id === void 0 || id === null ? '' : id).toLowerCase().replace(/[^a-z0-9]/g, '')
2954
+ }
2955
+
2956
+ /** The shell's active locale id, or the document fallback when it cannot be read. */
2957
+ function activeLocale(ctx) {
2958
+ var c = ctx || hostCtx
2959
+ try {
2960
+ if (c && typeof c.get === 'function') {
2961
+ var locale = c.get('locale')
2962
+ if (locale && typeof locale.getSnapshot === 'function') {
2963
+ var active = locale.getSnapshot().active
2964
+ if (typeof active === 'string' && active) return active
2965
+ }
2966
+ }
2967
+ } catch (error) { /* no locale service: keep the fallback language */ }
2968
+ return modelCopy === null ? MODEL_COPY_FALLBACK_LOCALE : modelCopy.fallback
2969
+ }
2970
+
2971
+ /** One localized string out of a `{ locale: text }` pair, fallback locale last. */
2972
+ function localized(pair, ctx) {
2973
+ if (!pair || typeof pair !== 'object') return ''
2974
+ var loc = activeLocale(ctx)
2975
+ var text = pair[loc]
2976
+ if (typeof text === 'string' && text) return text
2977
+ var prefix = typeof loc === 'string' && loc.indexOf('-') !== -1 ? loc.split('-')[0] : (typeof loc === 'string' && loc.indexOf('_') !== -1 ? loc.split('_')[0] : '')
2978
+ if (prefix && typeof pair[prefix] === 'string' && pair[prefix]) return pair[prefix]
2979
+ var fallback = modelCopy === null ? MODEL_COPY_FALLBACK_LOCALE : modelCopy.fallback
2980
+ var backstop = pair[fallback]
2981
+ if (typeof backstop === 'string' && backstop) return backstop
2982
+ var fallbackPrefix = typeof fallback === 'string' && fallback.indexOf('-') !== -1 ? fallback.split('-')[0] : ''
2983
+ if (fallbackPrefix && typeof pair[fallbackPrefix] === 'string' && pair[fallbackPrefix]) return pair[fallbackPrefix]
2984
+ return ''
2985
+ }
2986
+
2987
+ /**
2988
+ * One picker label: the document's localized string, else the neutral
2989
+ * English constant the bundle carries. `{name}` placeholders are filled
2990
+ * from `params`, so a label with a slot stays translatable.
2991
+ */
2992
+ function copyLabel(key, fallback, params) {
2993
+ var text = modelCopy === null || !modelCopy.ui ? '' : localized(modelCopy.ui[key])
2994
+ if (!text) text = fallback
2995
+ if (!params) return text
2996
+ return text.replace(/\{(\w+)\}/g, function (match, name) {
2997
+ return Object.prototype.hasOwnProperty.call(params, name) ? String(params[name]) : match
2998
+ })
2999
+ }
3000
+
3001
+ /**
3002
+ * One settings-page string. The settings copy rides the same document as
3003
+ * the picker copy, so the page follows the shell language too — and the
3004
+ * English constants stay as the fallback for a failed fetch.
3005
+ */
3006
+ function settingsCopy(key, fallback) {
3007
+ var text = modelCopy === null || !modelCopy.settings ? '' : localized(modelCopy.settings[key])
3008
+ return text || fallback
3009
+ }
3010
+
2521
3011
  // ============================================================================
2522
- // Zone 4: Claude Code UI 重塑与交互增强 (UI Overrides)
3012
+ // 弹层共享辅助 (Shared Popover Helpers)
2523
3013
  // ============================================================================
3014
+ var POPOVER_MARGIN = 8
3015
+
3016
+ /**
3017
+ * Position a fixed-position popover relative to its trigger.
3018
+ *
3019
+ * `side: 'right'` opens to the trigger's right and bottom-aligns it (the
3020
+ * account popover in the rail). The default `side: 'above'` right-aligns
3021
+ * the popover with the trigger and opens above it with `gap` spacing (the
3022
+ * model picker). `important` switches to `style.setProperty(..., 'important')`
3023
+ * and rounds the coordinates, as the account popover requires.
3024
+ *
3025
+ * @param trigger - element the popover is anchored to.
3026
+ * @param pop - the fixed-position popover element.
3027
+ * @param opts - `{ side, gap, important }`.
3028
+ * @returns the chosen `{ x, y }` in viewport coordinates.
3029
+ */
3030
+ function positionAnchoredPopover(trigger, pop, opts) {
3031
+ opts = opts || {}
3032
+ var rect = trigger.getBoundingClientRect()
3033
+ var width = pop.offsetWidth
3034
+ var height = pop.offsetHeight
3035
+ var margin = opts.margin || POPOVER_MARGIN
3036
+ var x
3037
+ var y
3038
+ if (opts.side === 'right') {
3039
+ x = rect.right + margin
3040
+ if (x + width > window.innerWidth - margin) {
3041
+ x = Math.max(margin, rect.left - margin - width)
3042
+ }
3043
+ y = Math.min(Math.max(margin, rect.bottom - height), Math.max(margin, window.innerHeight - height - margin))
3044
+ } else {
3045
+ x = Math.max(margin, Math.min(rect.right - width, window.innerWidth - width - margin))
3046
+ y = rect.top - (opts.gap || 0) - height
3047
+ if (y < margin) y = Math.min(rect.bottom + (opts.gap || 0), Math.max(margin, window.innerHeight - height - margin))
3048
+ }
3049
+ if (opts.important) {
3050
+ pop.style.setProperty('left', Math.round(x) + 'px', 'important')
3051
+ pop.style.setProperty('top', Math.round(y) + 'px', 'important')
3052
+ } else {
3053
+ pop.style.left = x + 'px'
3054
+ pop.style.top = y + 'px'
3055
+ }
3056
+ return { x: x, y: y }
3057
+ }
3058
+
2524
3059
  /**
2525
- * Install the Claude Code surface rewrites: the copy slots (hero headline,
2526
- * composer hint), the permission segmented control, and the account footer.
2527
- * All of them sit in React-rendered trees — the headline and hint re-render
2528
- * on locale switches, the hint unmounts while a draft exists, and the
2529
- * composer re-renders the access trigger on every preset change — so one
2530
- * MutationObserver re-applies them after each render. Every rewrite is
2531
- * idempotent (a node already in the target state is left alone, and the
2532
- * controls are only inserted when absent), so the observer cannot feed
2533
- * itself.
3060
+ * Hover-intent helper shared by the model picker and account popover.
3061
+ *
3062
+ * The `open` callback is part of the shared signature but the original
3063
+ * hover behaviour only schedules the close side (mouseenter calls the
3064
+ * feature's open function directly); keeping the parameter makes the two
3065
+ * call sites symmetrical.
2534
3066
  */
2535
- function installOverrides(ctx) {
3067
+ function createHoverIntent(open, close, delay) {
3068
+ var timer = null
3069
+ return {
3070
+ cancel: function () {
3071
+ if (timer) {
3072
+ clearTimeout(timer)
3073
+ timer = null
3074
+ }
3075
+ },
3076
+ scheduleClose: function () {
3077
+ if (timer) clearTimeout(timer)
3078
+ timer = setTimeout(close, delay)
3079
+ },
3080
+ }
3081
+ }
3082
+
3083
+ // ============================================================================
3084
+ // 4.1: 文案改写 (Copy Rewrites)
3085
+ // ============================================================================
3086
+ function installCopy(ctx, ui) {
2536
3087
  // --- 4.1 Copy Overrides ---
2537
3088
  /** Shipped idle composer hints (zh / en, hero / default) this skin replaces. */
2538
3089
  var HINT_SOURCES = [
@@ -2559,7 +3110,19 @@ window.__ModuleLoader__.load({
2559
3110
  }
2560
3111
  }
2561
3112
 
3113
+ function isHeroView() {
3114
+ var hasTurns = document.querySelector('[class*="turn"], [class*="message"], [data-turn], [data-message-id]') !== null
3115
+ return !hasTurns && (document.querySelector('[class*="composerHero"], [data-phase="hero"]') !== null)
3116
+ }
3117
+
3118
+ function isComposerActive() {
3119
+ var isHero = isHeroView()
3120
+ var scope = readPrefs().composerScope
3121
+ return scope === 'all' || (isHero ? scope === 'hero' : scope === 'conversation')
3122
+ }
3123
+
2562
3124
  function rewriteHint() {
3125
+ if (!isComposerActive()) return
2563
3126
  var isHero = document.querySelector('[class*="heroWorkspaceRow"], [class*="titleGroup"]') !== null
2564
3127
  var targetHint = isHero ? COMPOSER_HINT : 'Type / for commands'
2565
3128
  var hints = document.querySelectorAll('[data-composer-placeholder]')
@@ -2575,6 +3138,54 @@ window.__ModuleLoader__.load({
2575
3138
  }
2576
3139
  if (idle && text !== targetHint) node.textContent = targetHint
2577
3140
  }
3141
+ syncAttachmentPlaceholder()
3142
+ }
3143
+
3144
+ function syncAttachmentPlaceholder() {
3145
+ if (!isComposerActive()) {
3146
+ var synths = document.querySelectorAll('[data-dsh-synthetic-placeholder]')
3147
+ for (var si = 0; si < synths.length; si++) {
3148
+ if (synths[si].parentElement) synths[si].parentElement.removeChild(synths[si])
3149
+ }
3150
+ return
3151
+ }
3152
+ var isHero = document.querySelector('[class*="heroWorkspaceRow"], [class*="titleGroup"]') !== null
3153
+ var targetHint = isHero ? COMPOSER_HINT : 'Type / for commands'
3154
+ var cards = document.querySelectorAll('[data-composer-card]')
3155
+ for (var ci = 0; ci < cards.length; ci++) {
3156
+ var card = cards[ci]
3157
+ var input = card.querySelector('[data-composer-input]')
3158
+ if (!input) continue
3159
+ var text = (input.textContent || '').replace(/[\u200B-\u200D\uFEFF]/g, '').trim()
3160
+ var isEmpty = text.length === 0
3161
+ var placeholder = card.querySelector('[data-composer-placeholder]')
3162
+ var grow = input.closest ? input.closest('[class*="grow"]') : input.parentElement
3163
+
3164
+ if (isEmpty) {
3165
+ if (!placeholder && grow) {
3166
+ placeholder = document.createElement('div')
3167
+ placeholder.setAttribute('data-composer-placeholder', '')
3168
+ placeholder.setAttribute('data-dsh-synthetic-placeholder', 'true')
3169
+ placeholder.textContent = targetHint
3170
+ grow.appendChild(placeholder)
3171
+ } else if (placeholder) {
3172
+ if (placeholder.style.display === 'none') placeholder.style.display = ''
3173
+ var curText = placeholder.textContent || ''
3174
+ var idle = false
3175
+ for (var j = 0; j < HINT_SOURCES.length; j++) {
3176
+ if (curText.indexOf(HINT_SOURCES[j]) === 0) {
3177
+ idle = true
3178
+ break
3179
+ }
3180
+ }
3181
+ if (idle && curText !== targetHint) placeholder.textContent = targetHint
3182
+ }
3183
+ } else {
3184
+ if (placeholder && placeholder.hasAttribute('data-dsh-synthetic-placeholder')) {
3185
+ if (placeholder.parentElement) placeholder.parentElement.removeChild(placeholder)
3186
+ }
3187
+ }
3188
+ }
2578
3189
  }
2579
3190
 
2580
3191
  /**
@@ -2608,6 +3219,28 @@ window.__ModuleLoader__.load({
2608
3219
  }
2609
3220
  }
2610
3221
 
3222
+
3223
+ ui.copy = {
3224
+ sync: function () {
3225
+ rewriteHeadline()
3226
+ rewriteHint()
3227
+ rewriteTurnStatus()
3228
+ },
3229
+ syncGreeting: function () {
3230
+ rewriteHeadline()
3231
+ },
3232
+ syncAttachmentPlaceholder: syncAttachmentPlaceholder,
3233
+ isHeroView: isHeroView,
3234
+ isComposerActive: isComposerActive
3235
+ }
3236
+
3237
+ return function () {}
3238
+ }
3239
+
3240
+ // ============================================================================
3241
+ // 4.2: 权限段控件与会话内弹层 (Permission Segments & Popover)
3242
+ // ============================================================================
3243
+ function installPermissions(ctx, ui) {
2611
3244
  // --- 4.2 Permission Segments & In-Conversation Popover ---
2612
3245
  var segments = null
2613
3246
  var permContainer = null
@@ -2620,6 +3253,7 @@ window.__ModuleLoader__.load({
2620
3253
  group.className = SEGMENTS_CLASS
2621
3254
  group.setAttribute('role', 'radiogroup')
2622
3255
  group.setAttribute('aria-label', 'Permission')
3256
+ group.setAttribute('data-composer-segments', '')
2623
3257
  for (var i = 0; i < PERMISSION_SEGMENTS.length; i++) {
2624
3258
  var spec = PERMISSION_SEGMENTS[i]
2625
3259
  var item = document.createElement('button')
@@ -2793,7 +3427,7 @@ window.__ModuleLoader__.load({
2793
3427
  var session = currentSession(ctx)
2794
3428
  if (session === null) return
2795
3429
  var settled = session.command('/permission ' + preset)
2796
- if (settled !== void 0 && typeof settled.then === 'function') settled.then(schedule, schedule)
3430
+ if (settled !== void 0 && typeof settled.then === 'function') settled.then(ui.schedule, ui.schedule)
2797
3431
  }
2798
3432
 
2799
3433
  /**
@@ -2847,9 +3481,16 @@ window.__ModuleLoader__.load({
2847
3481
  }
2848
3482
 
2849
3483
  function syncAttachmentState() {
3484
+ var active = ui.copy.isComposerActive()
2850
3485
  var cards = document.querySelectorAll('[data-composer-card]')
2851
3486
  for (var ci = 0; ci < cards.length; ci++) {
2852
3487
  var card = cards[ci]
3488
+ if (!active) {
3489
+ if (card.hasAttribute('data-has-attachments')) {
3490
+ card.removeAttribute('data-has-attachments')
3491
+ }
3492
+ continue
3493
+ }
2853
3494
  var hasAtt = card.querySelector('._54WpYG_imageItem, [class*="imageItem"], [class*="thumbnail"], [class*="FileCard"], [class*="rail"]:not([class*="trailing"]) [class*="item"], [class*="rail"]:not([class*="trailing"]) img, [class*="rail"]:not([class*="trailing"]) [class*="card"]') !== null
2854
3495
  if (hasAtt) {
2855
3496
  if (card.getAttribute('data-has-attachments') !== 'true') {
@@ -2861,6 +3502,7 @@ window.__ModuleLoader__.load({
2861
3502
  }
2862
3503
  }
2863
3504
  }
3505
+ if (ui.copy && ui.copy.syncAttachmentPlaceholder) ui.copy.syncAttachmentPlaceholder()
2864
3506
  }
2865
3507
 
2866
3508
  /**
@@ -2875,6 +3517,16 @@ window.__ModuleLoader__.load({
2875
3517
  function mergeStatsIntoRow() {
2876
3518
  var stats = document.querySelector('[data-composer-stats]')
2877
3519
  if (!stats) return
3520
+ var active = ui.copy.isComposerActive()
3521
+ if (!active) {
3522
+ if (stats.parentElement && stats.parentElement.matches && stats.parentElement.matches('[class*="_row"]')) {
3523
+ var card = stats.closest('[data-composer-card]')
3524
+ if (card && card.parentElement) {
3525
+ card.parentElement.insertBefore(stats, card.nextSibling)
3526
+ }
3527
+ }
3528
+ return
3529
+ }
2878
3530
  // `[class*="_row"]`, not `[class*="row"]`: the bare substring also
2879
3531
  // matches the input growth wrapper (`grow` contains `row`).
2880
3532
  var row = null
@@ -2904,13 +3556,38 @@ window.__ModuleLoader__.load({
2904
3556
 
2905
3557
  // Re-insert when a re-render swapped the host row, then mirror the running preset.
2906
3558
  function syncSegments() {
2907
- var hasTurns = document.querySelector('[class*="turn"], [class*="message"], [data-turn], [data-message-id]') !== null
2908
- var isHero = !hasTurns && (document.querySelector('[class*="composerHero"], [data-phase="hero"]') !== null)
3559
+ var isHero = ui.copy.isHeroView()
2909
3560
  var allCards = document.querySelectorAll('[data-composer-card]')
2910
3561
  for (var c = 0; c < allCards.length; c++) {
2911
3562
  allCards[c].setAttribute('data-composer-variant', isHero ? 'hero' : 'inline')
2912
3563
  }
2913
3564
 
3565
+ var composerOn = ui.copy.isComposerActive()
3566
+ if (composerOn) document.body.setAttribute(COMPOSER_ATTR, '')
3567
+ else document.body.removeAttribute(COMPOSER_ATTR)
3568
+
3569
+ var existingPermContainers = document.querySelectorAll('.dsh-claude-perm-container')
3570
+ var existingSegments = document.querySelectorAll('.' + SEGMENTS_CLASS + '[data-composer-segments]')
3571
+
3572
+ if (!composerOn) {
3573
+ for (var ep = 0; ep < existingPermContainers.length; ep++) {
3574
+ existingPermContainers[ep].remove()
3575
+ }
3576
+ if (permPopover && permPopover.parentElement) {
3577
+ permPopover.parentElement.removeChild(permPopover)
3578
+ }
3579
+ permContainer = null
3580
+ permBtn = null
3581
+ permLabel = null
3582
+ permPopover = null
3583
+
3584
+ for (var es0 = 0; es0 < existingSegments.length; es0++) {
3585
+ existingSegments[es0].remove()
3586
+ }
3587
+ segments = null
3588
+ return
3589
+ }
3590
+
2914
3591
  var trigger = findAccessTrigger()
2915
3592
  if (trigger === null) return
2916
3593
  var host = trigger.parentElement
@@ -2919,9 +3596,6 @@ window.__ModuleLoader__.load({
2919
3596
  var session = currentSession(ctx)
2920
3597
  var preset = session === null ? null : currentPreset(session)
2921
3598
 
2922
- var existingPermContainers = document.querySelectorAll('.dsh-claude-perm-container')
2923
- var existingSegments = document.querySelectorAll('.' + SEGMENTS_CLASS)
2924
-
2925
3599
  if (isHero) {
2926
3600
  for (var i = 0; i < existingPermContainers.length; i++) {
2927
3601
  existingPermContainers[i].remove()
@@ -2997,6 +3671,10 @@ window.__ModuleLoader__.load({
2997
3671
  * single view) means the chat surface is all there is.
2998
3672
  */
2999
3673
  function syncChatTabComposer() {
3674
+ if (!ui.copy.isComposerActive()) {
3675
+ document.body.removeAttribute('data-dsh-claude-composer-hidden')
3676
+ return
3677
+ }
3000
3678
  var chatActive = true
3001
3679
  var seat = document.querySelector('[data-composer-seat]')
3002
3680
  var root = seat && seat.closest ? seat.closest('[data-phase]') : null
@@ -3011,6 +3689,41 @@ window.__ModuleLoader__.load({
3011
3689
  else document.body.setAttribute('data-dsh-claude-composer-hidden', '')
3012
3690
  }
3013
3691
 
3692
+
3693
+ ui.permissions = {
3694
+ sync: function () {
3695
+ syncAttachmentState()
3696
+ mergeStatsIntoRow()
3697
+ syncSegments()
3698
+ syncChatTabComposer()
3699
+ },
3700
+ closeMenu: closePermMenu
3701
+ }
3702
+
3703
+ return function () {
3704
+ if (permDocPointerListener) {
3705
+ document.removeEventListener('pointerdown', permDocPointerListener)
3706
+ permDocPointerListener = null
3707
+ }
3708
+ if (permResizeListener) {
3709
+ window.removeEventListener('resize', permResizeListener)
3710
+ window.removeEventListener('scroll', permResizeListener, true)
3711
+ permResizeListener = null
3712
+ }
3713
+ if (segments !== null && segments.parentElement !== null) segments.parentElement.removeChild(segments)
3714
+ segments = null
3715
+ if (permPopover !== null && permPopover.parentElement !== null) permPopover.parentElement.removeChild(permPopover)
3716
+ permPopover = null
3717
+ permBtn = null
3718
+ permLabel = null
3719
+ permContainer = null
3720
+ }
3721
+ }
3722
+
3723
+ // ============================================================================
3724
+ // 4.4: 模型选择器 (Model Picker)
3725
+ // ============================================================================
3726
+ function installModelPicker(ctx, ui) {
3014
3727
  // --- 4.4 Model picker: replaces the host's model seat ---
3015
3728
  /**
3016
3729
  * The host's model seat is a click-triggered two-pane menu (Model /
@@ -3031,7 +3744,7 @@ window.__ModuleLoader__.load({
3031
3744
  var modelSubPop = null
3032
3745
  var modelBody = null
3033
3746
  var modelSubBody = null
3034
- var modelCloseTimer = null
3747
+ var modelHoverIntent = createHoverIntent(openModelPopover, closeModelPopovers, 180)
3035
3748
  var modelDir = null
3036
3749
  var modelSub = null
3037
3750
  var modelSessionId = null
@@ -3039,18 +3752,63 @@ window.__ModuleLoader__.load({
3039
3752
  var modelBodySig = ''
3040
3753
  var modelSubSig = ''
3041
3754
 
3042
- function cancelCloseModel() {
3043
- if (modelCloseTimer) {
3044
- clearTimeout(modelCloseTimer)
3045
- modelCloseTimer = null
3755
+ /** Exact entry: `provider/model`, bare id, folded id, then the alias table. */
3756
+ function exactModelCopy(groupId, modelId) {
3757
+ if (modelCopy === null) return null
3758
+ var gid = String(groupId === void 0 || groupId === null ? '' : groupId).toLowerCase()
3759
+ var mid = String(modelId === void 0 || modelId === null ? '' : modelId)
3760
+ var midLower = mid.toLowerCase()
3761
+ var byProvider = modelCopy.exact[groupId + '/' + mid] || modelCopy.exact[gid + '/' + midLower]
3762
+ if (byProvider) return byProvider
3763
+ if (modelCopy.exact[mid]) return modelCopy.exact[mid]
3764
+ if (modelCopy.exact[midLower]) return modelCopy.exact[midLower]
3765
+ var folded = normalizeModelId(mid)
3766
+ if (modelCopy.folded[folded]) return modelCopy.folded[folded]
3767
+ var alias = modelCopy.aliases[mid] || modelCopy.aliases[midLower] || modelCopy.aliases[folded] || (modelCopy.foldedAliases && modelCopy.foldedAliases[folded])
3768
+ if (alias) {
3769
+ if (modelCopy.exact[alias]) return modelCopy.exact[alias]
3770
+ var foldedAlias = normalizeModelId(alias)
3771
+ if (modelCopy.folded[foldedAlias]) return modelCopy.folded[foldedAlias]
3772
+ }
3773
+ return null
3774
+ }
3775
+
3776
+ /**
3777
+ * Family entry. The model id is tried alone first because it is the
3778
+ * stronger signal, then `provider/id` for ids that carry no brand of their
3779
+ * own (`abab6.5s-chat` under a provider called `minimax`).
3780
+ */
3781
+ function familyModelCopy(groupId, modelId) {
3782
+ if (modelCopy === null) return null
3783
+ var id = String(modelId === void 0 || modelId === null ? '' : modelId).toLowerCase()
3784
+ var haystacks = [id, String(groupId === void 0 || groupId === null ? '' : groupId).toLowerCase() + '/' + id]
3785
+ for (var h = 0; h < haystacks.length; h++) {
3786
+ for (var i = 0; i < modelCopy.families.length; i++) {
3787
+ var rule = modelCopy.families[i]
3788
+ if (!rule.re.test(haystacks[h])) continue
3789
+ if (rule.key) return modelCopy.exact[rule.key] || null
3790
+ return rule.text
3791
+ }
3792
+ }
3793
+ return null
3794
+ }
3795
+
3796
+ /** Last-resort tier rule, read out of the id itself. */
3797
+ function tierModelCopy(modelId) {
3798
+ if (modelCopy === null) return null
3799
+ var id = String(modelId === void 0 || modelId === null ? '' : modelId).toLowerCase()
3800
+ for (var i = 0; i < modelCopy.tiers.length; i++) {
3801
+ if (modelCopy.tiers[i].re.test(id)) return modelCopy.tiers[i].text
3046
3802
  }
3803
+ return null
3804
+ }
3805
+
3806
+ function cancelCloseModel() {
3807
+ modelHoverIntent.cancel()
3047
3808
  }
3048
3809
 
3049
3810
  function scheduleCloseModel() {
3050
- cancelCloseModel()
3051
- modelCloseTimer = setTimeout(function () {
3052
- closeModelPopovers()
3053
- }, 180)
3811
+ modelHoverIntent.scheduleClose()
3054
3812
  }
3055
3813
 
3056
3814
  function closeModelPopovers() {
@@ -3075,6 +3833,7 @@ window.__ModuleLoader__.load({
3075
3833
  }
3076
3834
  modelSubKind = null
3077
3835
  if (modelSubPop) modelSubPop.setAttribute('data-open', 'false')
3836
+ renderModelBody()
3078
3837
  positionModelPopovers()
3079
3838
  if (modelPop) modelPop.setAttribute('data-open', 'true')
3080
3839
  }
@@ -3082,6 +3841,7 @@ window.__ModuleLoader__.load({
3082
3841
  function openModelSub(kind) {
3083
3842
  cancelCloseModel()
3084
3843
  modelSubKind = kind
3844
+ renderModelSub()
3085
3845
  positionModelPopovers()
3086
3846
  if (modelSubPop) modelSubPop.setAttribute('data-open', 'true')
3087
3847
  }
@@ -3141,7 +3901,7 @@ window.__ModuleLoader__.load({
3141
3901
  var store = modelDir.store
3142
3902
  if (store && typeof store.subscribe === 'function') {
3143
3903
  try {
3144
- modelSub = store.subscribe(function () { schedule() })
3904
+ modelSub = store.subscribe(function () { if (ui.schedule) ui.schedule() })
3145
3905
  } catch (error) {
3146
3906
  modelSub = null
3147
3907
  }
@@ -3187,10 +3947,23 @@ window.__ModuleLoader__.load({
3187
3947
  return { reasoning: reasoning, effective: effective, label: label }
3188
3948
  }
3189
3949
 
3950
+ /**
3951
+ * The description line for one catalog model, in the shell's language.
3952
+ *
3953
+ * Resolution descends: exact entry (one model resold by several providers
3954
+ * folds to a single key) → family rule → tier rule → the catalog's own
3955
+ * text. Family rules are ordered and anchored (see
3956
+ * src/model-descriptions.json) so another vendor's flash tier never
3957
+ * borrows DeepSeek's copy. A model this table has never seen and the
3958
+ * catalog does not describe resolves to an empty string on purpose: a
3959
+ * name-only row beats an invented line.
3960
+ */
3190
3961
  function modelDescription(groupId, model) {
3191
- var key = groupId + '/' + model.id
3192
- if (MODEL_DESCRIPTIONS[key]) return MODEL_DESCRIPTIONS[key]
3193
- return model.description || ''
3962
+ var id = typeof model.id === 'string' ? model.id : ''
3963
+ var pair = exactModelCopy(groupId, id) || familyModelCopy(groupId, id) || tierModelCopy(id)
3964
+ var text = localized(pair, ctx)
3965
+ if (text) return text
3966
+ return typeof model.description === 'string' ? model.description : ''
3194
3967
  }
3195
3968
 
3196
3969
  function modelEl(tag, cls, text) {
@@ -3204,7 +3977,7 @@ window.__ModuleLoader__.load({
3204
3977
  var MODEL_CHEVRON_SVG = '<svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 4l4 4-4 4"/></svg>'
3205
3978
  var MODEL_CHEVRON_DOWN_SVG = '<svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"/></svg>'
3206
3979
 
3207
- /** One selectable model row: name (+ description) and a check when current. */
3980
+ /** One selectable model row: name, description line and a check when current. */
3208
3981
  function buildModelOption(group, model, selected) {
3209
3982
  var item = modelEl('button', 'dsh-claude-model-option')
3210
3983
  item.type = 'button'
@@ -3212,6 +3985,8 @@ window.__ModuleLoader__.load({
3212
3985
  item.setAttribute('aria-checked', selected ? 'true' : 'false')
3213
3986
  var copy = modelEl('span', 'dsh-claude-model-copy')
3214
3987
  copy.appendChild(modelEl('span', 'dsh-claude-model-name', model.name))
3988
+ // One line, in the shell's language: the copy document is localized, so
3989
+ // the row never stacks two languages.
3215
3990
  var desc = modelDescription(group.id, model)
3216
3991
  if (desc) copy.appendChild(modelEl('span', 'dsh-claude-model-desc', desc))
3217
3992
  item.appendChild(copy)
@@ -3238,7 +4013,9 @@ window.__ModuleLoader__.load({
3238
4013
  chevron.innerHTML = MODEL_CHEVRON_SVG
3239
4014
  cell.appendChild(chevron)
3240
4015
  cell.addEventListener('mouseenter', (function (k) {
3241
- return function () { openModelSub(k) }
4016
+ return function () {
4017
+ if (readPrefs().autoPopover) openModelSub(k)
4018
+ }
3242
4019
  })(kind))
3243
4020
  cell.addEventListener('click', (function (k) {
3244
4021
  return function (e) {
@@ -3283,14 +4060,14 @@ window.__ModuleLoader__.load({
3283
4060
  var groups = (snap && snap.groups) || []
3284
4061
  var current = modelCurrent(snap)
3285
4062
  var effort = modelEffort(snap)
3286
- var sig = [status, current ? current.group.id + '/' + current.model.id : '', effort ? String(effort.effective) : ''].join('|')
4063
+ var sig = [status, activeLocale(), current ? current.group.id + '/' + current.model.id : '', effort ? String(effort.effective) : ''].join('|')
3287
4064
  for (var g = 0; g < groups.length; g++) sig += ';' + groups[g].id + ':' + groups[g].models.length
3288
4065
  if (sig === modelBodySig) return
3289
4066
  modelBodySig = sig
3290
4067
  while (modelBody.firstChild) modelBody.removeChild(modelBody.firstChild)
3291
4068
 
3292
4069
  if (status === 'idle' || status === 'loading' || status === 'selecting') {
3293
- modelBody.appendChild(modelEl('div', 'dsh-claude-model-status', MODEL_LOADING_LABEL))
4070
+ modelBody.appendChild(modelEl('div', 'dsh-claude-model-status', copyLabel('loading', MODEL_LOADING_LABEL)))
3294
4071
  } else {
3295
4072
  var official = null
3296
4073
  for (var g2 = 0; g2 < groups.length; g2++) {
@@ -3305,7 +4082,7 @@ window.__ModuleLoader__.load({
3305
4082
  }
3306
4083
  }
3307
4084
  if (rows.length === 0) {
3308
- modelBody.appendChild(modelEl('div', 'dsh-claude-model-status', MODEL_EMPTY_LABEL))
4085
+ modelBody.appendChild(modelEl('div', 'dsh-claude-model-status', copyLabel('empty', MODEL_EMPTY_LABEL)))
3309
4086
  } else {
3310
4087
  for (var r = 0; r < rows.length; r++) {
3311
4088
  var selected = current !== null && current.group.id === rows[r].group.id && current.model.id === rows[r].model.id
@@ -3313,8 +4090,8 @@ window.__ModuleLoader__.load({
3313
4090
  }
3314
4091
  }
3315
4092
  modelBody.appendChild(modelEl('div', 'dsh-claude-model-divider'))
3316
- if (effort) modelBody.appendChild(buildModelCell(MODEL_EFFORT_LABEL, effort.label, 'effort'))
3317
- modelBody.appendChild(buildModelCell(MODEL_MORE_LABEL, '', 'more'))
4093
+ if (effort) modelBody.appendChild(buildModelCell(copyLabel('effortLabel', MODEL_EFFORT_LABEL), effort.label, 'effort'))
4094
+ modelBody.appendChild(buildModelCell(copyLabel('moreLabel', MODEL_MORE_LABEL), '', 'more'))
3318
4095
  }
3319
4096
  }
3320
4097
 
@@ -3329,7 +4106,7 @@ window.__ModuleLoader__.load({
3329
4106
  modelSubSig = sig
3330
4107
  while (modelSubBody.firstChild) modelSubBody.removeChild(modelSubBody.firstChild)
3331
4108
  if (effort === null) {
3332
- modelSubBody.appendChild(modelEl('div', 'dsh-claude-model-status', '当前模型未提供推理等级。'))
4109
+ modelSubBody.appendChild(modelEl('div', 'dsh-claude-model-status', copyLabel('noEffort', MODEL_NO_EFFORT_LABEL)))
3333
4110
  return
3334
4111
  }
3335
4112
  var levels = []
@@ -3375,28 +4152,20 @@ window.__ModuleLoader__.load({
3375
4152
  }
3376
4153
  }
3377
4154
  if (modelSubBody.firstChild === null) {
3378
- modelSubBody.appendChild(modelEl('div', 'dsh-claude-model-status', MODEL_EMPTY_LABEL))
4155
+ modelSubBody.appendChild(modelEl('div', 'dsh-claude-model-status', copyLabel('empty', MODEL_EMPTY_LABEL)))
3379
4156
  }
3380
4157
  }
3381
4158
 
3382
4159
  function positionModelPopovers() {
3383
4160
  if (!modelBtn || !modelPop) return
3384
- var rect = modelBtn.getBoundingClientRect()
3385
- var MARGIN = 8
3386
- var w = modelPop.offsetWidth
3387
- var h = modelPop.offsetHeight
3388
- var x = Math.max(MARGIN, Math.min(rect.right - w, window.innerWidth - w - MARGIN))
3389
- var y = rect.top - 6 - h
3390
- if (y < MARGIN) y = Math.min(rect.bottom + 6, Math.max(MARGIN, window.innerHeight - h - MARGIN))
3391
- modelPop.style.left = x + 'px'
3392
- modelPop.style.top = y + 'px'
4161
+ var pos = positionAnchoredPopover(modelBtn, modelPop, { side: 'above', gap: 6 })
3393
4162
  if (modelSubPop && modelSubPop.getAttribute('data-open') === 'true') {
3394
4163
  var w2 = modelSubPop.offsetWidth
3395
4164
  var h2 = modelSubPop.offsetHeight
3396
4165
  // Beside the first level; flip to its left when the viewport is tight.
3397
- var x2 = x + w + 4
3398
- if (x2 + w2 > window.innerWidth - MARGIN) x2 = Math.max(MARGIN, x - 4 - w2)
3399
- var y2 = Math.max(MARGIN, Math.min(y, window.innerHeight - h2 - MARGIN))
4166
+ var x2 = pos.x + modelPop.offsetWidth + 4
4167
+ if (x2 + w2 > window.innerWidth - POPOVER_MARGIN) x2 = Math.max(POPOVER_MARGIN, pos.x - 4 - w2)
4168
+ var y2 = Math.max(POPOVER_MARGIN, Math.min(pos.y, window.innerHeight - h2 - POPOVER_MARGIN))
3400
4169
  modelSubPop.style.left = x2 + 'px'
3401
4170
  modelSubPop.style.top = y2 + 'px'
3402
4171
  }
@@ -3431,6 +4200,37 @@ window.__ModuleLoader__.load({
3431
4200
 
3432
4201
  /** Build/refresh the trigger, its label and the popover rows. */
3433
4202
  function syncModelControl() {
4203
+ if (!ui.copy.isComposerActive()) {
4204
+ var allHosts = document.querySelectorAll('[data-dsh-claude-model-host]')
4205
+ for (var h = 0; h < allHosts.length; h++) {
4206
+ allHosts[h].removeAttribute('data-dsh-claude-model-host')
4207
+ }
4208
+ var allModelBtns = document.querySelectorAll('.dsh-claude-model-btn')
4209
+ for (var mb = 0; mb < allModelBtns.length; mb++) {
4210
+ allModelBtns[mb].remove()
4211
+ }
4212
+ modelBtn = null
4213
+ var allModelPops = document.querySelectorAll('.dsh-claude-model-popover')
4214
+ for (var mp = 0; mp < allModelPops.length; mp++) {
4215
+ allModelPops[mp].remove()
4216
+ }
4217
+ modelPop = null
4218
+ modelSubPop = null
4219
+ modelBody = null
4220
+ modelSubBody = null
4221
+ modelSubKind = null
4222
+ modelBodySig = ''
4223
+ modelSubSig = ''
4224
+ cancelCloseModel()
4225
+ dropModelSubscription()
4226
+ modelDir = null
4227
+ modelSessionId = null
4228
+ return
4229
+ }
4230
+
4231
+ // The copy document is fetched on first paint of the picker rather than
4232
+ // at install, so a session that never opens it never pays for it.
4233
+ loadModelCopy()
3434
4234
  modelDirectory()
3435
4235
  var slot = document.querySelector('[data-slot="conversation.input.model"]')
3436
4236
  if (slot === null) return
@@ -3448,8 +4248,14 @@ window.__ModuleLoader__.load({
3448
4248
  modelBtn.innerHTML =
3449
4249
  '<span class="dsh-claude-model-btn-label"></span>' +
3450
4250
  '<span class="dsh-claude-model-btn-chevron">' + MODEL_CHEVRON_DOWN_SVG + '</span>'
3451
- modelBtn.addEventListener('mouseenter', function () { openModelPopover() })
3452
- modelBtn.addEventListener('mouseleave', scheduleCloseModel)
4251
+ // Same contract as the account trigger: hover unless the preference
4252
+ // says click-only.
4253
+ modelBtn.addEventListener('mouseenter', function () {
4254
+ if (readPrefs().autoPopover) openModelPopover()
4255
+ })
4256
+ modelBtn.addEventListener('mouseleave', function () {
4257
+ if (readPrefs().autoPopover) scheduleCloseModel()
4258
+ })
3453
4259
  modelBtn.addEventListener('click', function (e) {
3454
4260
  e.stopPropagation()
3455
4261
  if (modelPop && modelPop.getAttribute('data-open') === 'true') closeModelPopovers()
@@ -3462,7 +4268,7 @@ window.__ModuleLoader__.load({
3462
4268
  var snap = modelSnapshot()
3463
4269
  var current = modelCurrent(snap)
3464
4270
  var effort = modelEffort(snap)
3465
- var label = current ? current.model.name : MODEL_FALLBACK_LABEL
4271
+ var label = current ? current.model.name : copyLabel('fallbackLabel', MODEL_FALLBACK_LABEL)
3466
4272
  var labelEl = modelBtn.querySelector('.dsh-claude-model-btn-label')
3467
4273
  if (labelEl) {
3468
4274
  labelEl.textContent = label
@@ -3478,7 +4284,7 @@ window.__ModuleLoader__.load({
3478
4284
  } else if (effortEl !== null && effortEl.parentElement) {
3479
4285
  effortEl.parentElement.removeChild(effortEl)
3480
4286
  }
3481
- modelBtn.setAttribute('aria-label', '选择模型,当前 ' + label)
4287
+ modelBtn.setAttribute('aria-label', copyLabel('triggerLabel', MODEL_TRIGGER_LABEL, { model: label }))
3482
4288
  modelBtn.disabled = false
3483
4289
 
3484
4290
  renderModelBody()
@@ -3489,18 +4295,53 @@ window.__ModuleLoader__.load({
3489
4295
  if (modelPop && modelPop.getAttribute('data-open') === 'true') positionModelPopovers()
3490
4296
  }
3491
4297
 
4298
+
4299
+ ui.model = {
4300
+ sync: syncModelControl,
4301
+ close: closeModelPopovers,
4302
+ owns: function (target) {
4303
+ if (!target) return false
4304
+ return (modelBtn !== null && modelBtn.contains(target)) ||
4305
+ (modelPop !== null && modelPop.contains(target)) ||
4306
+ (modelSubPop !== null && modelSubPop.contains(target))
4307
+ },
4308
+ reposition: positionModelPopovers,
4309
+ invalidateCopy: function () {
4310
+ modelBodySig = ''
4311
+ modelSubSig = ''
4312
+ },
4313
+ teardown: function () {
4314
+ cancelCloseModel()
4315
+ dropModelSubscription()
4316
+ modelDir = null
4317
+ modelSessionId = null
4318
+ modelBtn = null
4319
+ modelPop = null
4320
+ modelSubPop = null
4321
+ modelBody = null
4322
+ modelSubBody = null
4323
+ modelSubKind = null
4324
+ modelBodySig = ''
4325
+ modelSubSig = ''
4326
+ }
4327
+ }
4328
+
4329
+ return ui.model.teardown
4330
+ }
4331
+
4332
+ // ============================================================================
4333
+ // 4.3: 账户页脚与弹层 (Account Footer & Popover)
4334
+ // ============================================================================
4335
+ function installAccountFooter(ctx, ui) {
3492
4336
  // --- 4.3 Account Footer & Popover ---
3493
4337
  var accountBtn = null
3494
4338
  var accountPopover = null
3495
4339
  var popoverBody = null
3496
- var popoverTimer = null
4340
+ var popoverHoverIntent = createHoverIntent(openPopover, closePopover, 150)
3497
4341
 
3498
4342
  function openPopover() {
3499
4343
  if (!accountPopover || !accountBtn) return
3500
- if (popoverTimer) {
3501
- clearTimeout(popoverTimer)
3502
- popoverTimer = null
3503
- }
4344
+ cancelClosePopover()
3504
4345
  // Mirrors are frozen while the popover is open (syncPopoverItems
3505
4346
  // bails), so reconcile them here — before the reveal — to show fresh
3506
4347
  // content/order and bind click targets for the upcoming interaction.
@@ -3508,6 +4349,9 @@ window.__ModuleLoader__.load({
3508
4349
  var footArea = document.querySelector('[class*="footArea"]')
3509
4350
  if (footArea) syncPopoverItems(footArea)
3510
4351
  } catch (error) { /* opening must never fail because of a mirror sync */ }
4352
+ // Resolve the rail anchor before the reveal so the panel never paints at
4353
+ // its stale coordinates for a frame.
4354
+ positionAccountPopover()
3511
4355
  accountPopover.setAttribute('data-open', 'true')
3512
4356
  accountBtn.setAttribute('data-open', 'true')
3513
4357
  accountBtn.setAttribute('aria-expanded', 'true')
@@ -3515,10 +4359,7 @@ window.__ModuleLoader__.load({
3515
4359
 
3516
4360
  function closePopover() {
3517
4361
  if (!accountPopover || !accountBtn) return
3518
- if (popoverTimer) {
3519
- clearTimeout(popoverTimer)
3520
- popoverTimer = null
3521
- }
4362
+ cancelClosePopover()
3522
4363
  accountPopover.setAttribute('data-open', 'false')
3523
4364
  accountBtn.setAttribute('data-open', 'false')
3524
4365
  accountBtn.setAttribute('aria-expanded', 'false')
@@ -3534,18 +4375,36 @@ window.__ModuleLoader__.load({
3534
4375
  }
3535
4376
  }
3536
4377
 
4378
+ function cancelClosePopover() {
4379
+ popoverHoverIntent.cancel()
4380
+ }
4381
+
3537
4382
  function scheduleClosePopover() {
3538
- if (popoverTimer) clearTimeout(popoverTimer)
3539
- popoverTimer = setTimeout(function () {
3540
- closePopover()
3541
- }, 150)
4383
+ popoverHoverIntent.scheduleClose()
3542
4384
  }
3543
4385
 
3544
- function cancelClosePopover() {
3545
- if (popoverTimer) {
3546
- clearTimeout(popoverTimer)
3547
- popoverTimer = null
4386
+ /**
4387
+ * Anchor the account popover to its trigger while the sidebar is a rail.
4388
+ *
4389
+ * In the rail the popover is `position: fixed` (components/account-footer.css): the sidebar
4390
+ * column clips its overflow, so an absolutely positioned panel would be cut
4391
+ * off at the 56px rail edge and never seen. Its coordinates therefore have
4392
+ * to be resolved here — the same contract the model picker's popovers use.
4393
+ * The declarations are written `important` because the stylesheet anchors
4394
+ * the wide-sidebar popover with `!important` as well, and an author
4395
+ * `!important` beats a plain inline declaration.
4396
+ *
4397
+ * With the sidebar wide the CSS anchor is the right one, so the inline
4398
+ * overrides are dropped again and the footer rule takes over.
4399
+ */
4400
+ function positionAccountPopover() {
4401
+ if (!accountPopover || !accountBtn) return
4402
+ if (accountBtn.closest('[class*="collapsed"]') === null) {
4403
+ accountPopover.style.removeProperty('left')
4404
+ accountPopover.style.removeProperty('top')
4405
+ return
3548
4406
  }
4407
+ positionAnchoredPopover(accountBtn, accountPopover, { side: 'right', important: true })
3549
4408
  }
3550
4409
 
3551
4410
  var settingsItem = null
@@ -4015,10 +4874,41 @@ window.__ModuleLoader__.load({
4015
4874
  return null
4016
4875
  }
4017
4876
 
4877
+ /**
4878
+ * Hand the sidebar footer back to the host.
4879
+ *
4880
+ * The "Collapse the sidebar settings area" preference turns the whole
4881
+ * takeover off, so the skin's own nodes go and every marker it put on the
4882
+ * host's entries is removed — with the stylesheet's takeover rules gated
4883
+ * on the same attribute, the footer then renders exactly as shipped.
4884
+ */
4885
+ function dropAccountFooter(footArea) {
4886
+ cancelClosePopover()
4887
+ if (accountBtn !== null && accountBtn.parentElement !== null) accountBtn.parentElement.removeChild(accountBtn)
4888
+ if (accountPopover !== null && accountPopover.parentElement !== null) {
4889
+ accountPopover.parentElement.removeChild(accountPopover)
4890
+ }
4891
+ accountBtn = null
4892
+ accountPopover = null
4893
+ popoverBody = null
4894
+ settingsItem = null
4895
+ var marked = footArea.querySelectorAll(
4896
+ '[data-dsh-claude-footer-entry], [data-dsh-claude-footer-hidden], [data-dsh-claude-footer-overlay]',
4897
+ )
4898
+ for (var i = 0; i < marked.length; i++) {
4899
+ marked[i].removeAttribute('data-dsh-claude-footer-entry')
4900
+ marked[i].removeAttribute('data-dsh-claude-footer-hidden')
4901
+ marked[i].removeAttribute('data-dsh-claude-footer-overlay')
4902
+ }
4903
+ }
4018
4904
  function syncAccountFooter() {
4019
4905
  var footArea = document.querySelector('[class*="footArea"]')
4020
4906
  if (footArea === null) return
4021
4907
 
4908
+ if (!readPrefs().collapseFooter) {
4909
+ dropAccountFooter(footArea)
4910
+ return
4911
+ }
4022
4912
  var username = getUsername(ctx)
4023
4913
 
4024
4914
  if (accountBtn === null || !footArea.contains(accountBtn)) {
@@ -4036,11 +4926,15 @@ window.__ModuleLoader__.load({
4036
4926
  '</span>' +
4037
4927
  '<span class="dsh-claude-account-chevron"></span>'
4038
4928
 
4929
+ // Hover is the default way in; the "Open popovers on hover"
4930
+ // preference turns it off, leaving the click handler below as the only
4931
+ // way in (and the only way out, so a click-opened popover does not
4932
+ // vanish when the pointer leaves).
4039
4933
  accountBtn.addEventListener('mouseenter', function () {
4040
- openPopover()
4934
+ if (readPrefs().autoPopover) openPopover()
4041
4935
  })
4042
4936
  accountBtn.addEventListener('mouseleave', function () {
4043
- scheduleClosePopover()
4937
+ if (readPrefs().autoPopover) scheduleClosePopover()
4044
4938
  })
4045
4939
  accountBtn.addEventListener('click', function (e) {
4046
4940
  e.stopPropagation()
@@ -4093,28 +4987,58 @@ window.__ModuleLoader__.load({
4093
4987
  syncPopoverItems(footArea)
4094
4988
  }
4095
4989
 
4096
- // ============================================================================
4097
- // Zone 5: 响应式调度与生命周期清理 (Scheduler & Teardown)
4098
- // ============================================================================
4990
+
4991
+ ui.footer = {
4992
+ sync: syncAccountFooter,
4993
+ close: closePopover,
4994
+ owns: function (target) {
4995
+ if (!target) return false
4996
+ return (accountBtn !== null && accountBtn.contains(target)) ||
4997
+ (accountPopover !== null && accountPopover.contains(target))
4998
+ },
4999
+ isOpen: function () {
5000
+ return !!(accountPopover && accountPopover.getAttribute('data-open') === 'true')
5001
+ },
5002
+ reposition: positionAccountPopover
5003
+ }
5004
+
5005
+ return function () {
5006
+ cancelClosePopover()
5007
+ var footArea = document.querySelector('[class*="footArea"]')
5008
+ if (footArea) {
5009
+ dropAccountFooter(footArea)
5010
+ } else {
5011
+ if (accountBtn !== null && accountBtn.parentElement !== null) accountBtn.parentElement.removeChild(accountBtn)
5012
+ if (accountPopover !== null && accountPopover.parentElement !== null) accountPopover.parentElement.removeChild(accountPopover)
5013
+ accountBtn = null
5014
+ accountPopover = null
5015
+ popoverBody = null
5016
+ settingsItem = null
5017
+ }
5018
+ }
5019
+ }
5020
+
5021
+ // ============================================================================
5022
+ // 响应式调度与生命周期清理 (Scheduler & Teardown)
5023
+ // ============================================================================
5024
+ function installScheduler(ctx, ui) {
4099
5025
  function onGlobalPointerDown(e) {
4100
5026
  var target = e.target
4101
5027
  // The model picker is hover-driven; a press anywhere outside its
4102
5028
  // trigger and both levels closes it (same discipline as the perm menu).
4103
- if (target && (modelBtn === null || !modelBtn.contains(target)) &&
4104
- (modelPop === null || !modelPop.contains(target)) &&
4105
- (modelSubPop === null || !modelSubPop.contains(target))) {
4106
- closeModelPopovers()
5029
+ if (target && ui.model && !ui.model.owns(target)) {
5030
+ ui.model.close()
4107
5031
  }
4108
- if (!accountPopover || !accountBtn) return
4109
- if (target && (accountBtn.contains(target) || accountPopover.contains(target))) return
4110
- closePopover()
5032
+ if (!ui.footer || !ui.footer.isOpen()) return
5033
+ if (target && ui.footer.owns(target)) return
5034
+ ui.footer.close()
4111
5035
  }
4112
5036
 
4113
5037
  function onGlobalKeyDown(e) {
4114
5038
  if (e.key === 'Escape') {
4115
- closePopover()
4116
- closePermMenu()
4117
- closeModelPopovers()
5039
+ if (ui.footer) ui.footer.close()
5040
+ if (ui.permissions) ui.permissions.closeMenu()
5041
+ if (ui.model) ui.model.close()
4118
5042
  }
4119
5043
  if ((e.ctrlKey || e.metaKey) && e.key === ',') {
4120
5044
  e.preventDefault()
@@ -4152,17 +5076,63 @@ window.__ModuleLoader__.load({
4152
5076
  }
4153
5077
  }
4154
5078
 
5079
+ function onComposerInput(e) {
5080
+ var target = e.target
5081
+ if (!target) return
5082
+ if (target.hasAttribute && (target.hasAttribute('data-composer-input') || (target.closest && target.closest('[data-composer-input]')))) {
5083
+ if (ui.copy && ui.copy.syncAttachmentPlaceholder) ui.copy.syncAttachmentPlaceholder()
5084
+ }
5085
+ }
5086
+
4155
5087
  document.addEventListener('pointerdown', onGlobalPointerDown)
4156
5088
  document.addEventListener('pointerdown', onCardPointerDown)
4157
5089
  document.addEventListener('keydown', onGlobalKeyDown, true)
4158
-
4159
- // The picker is position:fixed against the trigger; scroll of the page
4160
- // (not the conversation's own auto-stick) and resizes move the anchor.
4161
- function onModelViewportChange() {
4162
- if (modelPop && modelPop.getAttribute('data-open') === 'true') positionModelPopovers()
5090
+ document.addEventListener('input', onComposerInput, true)
5091
+ document.addEventListener('compositionend', onComposerInput, true)
5092
+
5093
+ // Both fixed popovers are anchored to their trigger; scroll of the page
5094
+ // (not the conversation's own auto-stick) and resizes move the anchor, so
5095
+ // whichever is open must re-resolve it.
5096
+ function onFixedPopoverViewportChange() {
5097
+ if (ui.model) ui.model.reposition()
5098
+ if (ui.footer && ui.footer.isOpen()) ui.footer.reposition()
5099
+ }
5100
+ window.addEventListener('resize', onFixedPopoverViewportChange)
5101
+ window.addEventListener('scroll', onFixedPopoverViewportChange, true)
5102
+
5103
+ // The picker's copy follows the shell language, so a locale switch has to
5104
+ // rebuild the rows it already painted. Subscribing here (rather than
5105
+ // reading the locale at render time only) is what makes the change land
5106
+ // while a popover is open.
5107
+ var localeUnsubscribe = null
5108
+ function onLocaleChange() {
5109
+ if (ui.model) ui.model.invalidateCopy()
5110
+ schedule()
4163
5111
  }
4164
- window.addEventListener('resize', onModelViewportChange)
4165
- window.addEventListener('scroll', onModelViewportChange, true)
5112
+ try {
5113
+ var localeService = ctx.get('locale')
5114
+ if (localeService && typeof localeService.subscribe === 'function') {
5115
+ localeUnsubscribe = localeService.subscribe(onLocaleChange)
5116
+ }
5117
+ } catch (error) { /* no locale service: the picker keeps the fallback language */ }
5118
+
5119
+ // Preferences gate the stylesheet and this scheduler both — the footer
5120
+ // takeover adds or removes the account row, and the composer scope flips
5121
+ // an attribute the stylesheet reads — so a change re-runs the pass. The
5122
+ // first read also arrives through here, which is what replaces the
5123
+ // defaults with the stored values.
5124
+ var prefsUnsubscribe = null
5125
+ prefsUnsubscribe = subscribePrefs(function () {
5126
+ if (ui.model) ui.model.invalidateCopy()
5127
+ schedule()
5128
+ })
5129
+ loadPrefs()
5130
+
5131
+ var modelCopyUnsubscribe = null
5132
+ modelCopyUnsubscribe = onModelCopyLoaded(function () {
5133
+ if (ui.model) ui.model.invalidateCopy()
5134
+ schedule()
5135
+ })
4166
5136
 
4167
5137
  // Chat streaming mutates the tree constantly; coalesce to one pass a frame.
4168
5138
  var scheduled = false
@@ -4185,15 +5155,13 @@ window.__ModuleLoader__.load({
4185
5155
  scheduled = true
4186
5156
  requestAnimationFrame(function () {
4187
5157
  scheduled = false
4188
- rewriteHeadline()
4189
- rewriteHint()
4190
- rewriteTurnStatus()
4191
- syncAttachmentState()
4192
- mergeStatsIntoRow()
4193
- syncSegments()
4194
- syncChatTabComposer()
4195
- syncModelControl()
4196
- syncAccountFooter()
5158
+ if (ui.copy) ui.copy.sync()
5159
+ if (ui.permissions) ui.permissions.sync()
5160
+ if (ui.model) ui.model.sync()
5161
+ if (ui.footer) ui.footer.sync()
5162
+ // Covers the rail toggle (and any reflow) while the popover is open:
5163
+ // its anchor moved without a window resize or a page scroll.
5164
+ if (ui.footer && ui.footer.isOpen()) ui.footer.reposition()
4197
5165
  if (composerCardObserver) {
4198
5166
  var currentCard = document.querySelector('[data-composer-card]')
4199
5167
  if (currentCard !== observedCard) {
@@ -4204,6 +5172,7 @@ window.__ModuleLoader__.load({
4204
5172
  }
4205
5173
  })
4206
5174
  }
5175
+ ui.schedule = schedule
4207
5176
 
4208
5177
  var observer = new MutationObserver(schedule)
4209
5178
  observer.observe(document.body, {
@@ -4221,69 +5190,40 @@ window.__ModuleLoader__.load({
4221
5190
  // line rolls over on the hour while the app stays open. rewriteHeadline
4222
5191
  // skips identical text, so this cannot feed the observer.
4223
5192
  var greetingTimer = setInterval(function () {
4224
- rewriteHeadline()
5193
+ if (ui.copy) ui.copy.syncGreeting()
4225
5194
  }, 60000)
4226
5195
 
4227
5196
  return function () {
4228
5197
  clearInterval(greetingTimer)
4229
5198
  greetingTimer = null
4230
- cancelCloseModel()
4231
- dropModelSubscription()
4232
- modelDir = null
4233
- modelSessionId = null
4234
- window.removeEventListener('resize', onModelViewportChange)
4235
- window.removeEventListener('scroll', onModelViewportChange, true)
4236
- // Null the model chrome too: the sweep below detaches the nodes, and a
4237
- // later re-install must rebuild them rather than reuse dead elements.
4238
- modelBtn = null
4239
- modelPop = null
4240
- modelSubPop = null
4241
- modelBody = null
4242
- modelSubBody = null
4243
- modelSubKind = null
4244
- modelBodySig = ''
4245
- modelSubSig = ''
5199
+ window.removeEventListener('resize', onFixedPopoverViewportChange)
5200
+ window.removeEventListener('scroll', onFixedPopoverViewportChange, true)
5201
+ if (localeUnsubscribe !== null) {
5202
+ try { localeUnsubscribe() } catch (error) { /* already disposed */ }
5203
+ localeUnsubscribe = null
5204
+ }
5205
+ if (prefsUnsubscribe !== null) {
5206
+ try { prefsUnsubscribe() } catch (error) { /* already disposed */ }
5207
+ prefsUnsubscribe = null
5208
+ }
5209
+ if (modelCopyUnsubscribe !== null) {
5210
+ try { modelCopyUnsubscribe() } catch (error) { /* already disposed */ }
5211
+ modelCopyUnsubscribe = null
5212
+ }
4246
5213
  observer.disconnect()
4247
5214
  if (composerCardObserver) {
4248
5215
  composerCardObserver.disconnect()
4249
5216
  composerCardObserver = null
4250
5217
  observedCard = null
4251
5218
  }
4252
- if (popoverTimer) {
4253
- clearTimeout(popoverTimer)
4254
- popoverTimer = null
4255
- }
4256
5219
  document.removeEventListener('pointerdown', onGlobalPointerDown)
4257
5220
  document.removeEventListener('pointerdown', onCardPointerDown)
4258
5221
  document.removeEventListener('keydown', onGlobalKeyDown, true)
4259
- if (permDocPointerListener) {
4260
- document.removeEventListener('pointerdown', permDocPointerListener)
4261
- permDocPointerListener = null
4262
- }
4263
- if (permResizeListener) {
4264
- window.removeEventListener('resize', permResizeListener)
4265
- window.removeEventListener('scroll', permResizeListener, true)
4266
- permResizeListener = null
4267
- }
4268
- if (segments !== null && segments.parentElement !== null) segments.parentElement.removeChild(segments)
4269
- segments = null
4270
- if (permPopover !== null && permPopover.parentElement !== null) {
4271
- permPopover.parentElement.removeChild(permPopover)
4272
- }
4273
- permPopover = null
4274
- permBtn = null
4275
- permLabel = null
4276
- permContainer = null
4277
- if (accountPopover !== null && accountPopover.parentElement !== null) {
4278
- accountPopover.parentElement.removeChild(accountPopover)
4279
- }
4280
- accountPopover = null
4281
- popoverBody = null
4282
- if (accountBtn !== null && accountBtn.parentElement !== null) {
4283
- accountBtn.parentElement.removeChild(accountBtn)
4284
- }
4285
- accountBtn = null
4286
- var leftoverItems = document.querySelectorAll('.dsh-claude-popover-item, .dsh-claude-popover-embed, .dsh-claude-account-popover, .dsh-claude-account-btn, .dsh-claude-perm-container, .dsh-claude-perm-popover, .dsh-claude-segments, .dsh-claude-model-btn, .dsh-claude-model-popover')
5222
+ document.removeEventListener('input', onComposerInput, true)
5223
+ document.removeEventListener('compositionend', onComposerInput, true)
5224
+ // Safety-net DOM sweep. Feature teardowns run after this and tolerate
5225
+ // nodes already being detached.
5226
+ var leftoverItems = document.querySelectorAll('.dsh-claude-popover-item, .dsh-claude-popover-embed, .dsh-claude-account-popover, .dsh-claude-account-btn, .dsh-claude-perm-container, .dsh-claude-perm-popover, .dsh-claude-segments[data-composer-segments], .dsh-claude-model-btn, .dsh-claude-model-popover, [data-dsh-synthetic-placeholder]')
4287
5227
  for (var li = 0; li < leftoverItems.length; li++) {
4288
5228
  if (leftoverItems[li].parentElement) {
4289
5229
  leftoverItems[li].parentElement.removeChild(leftoverItems[li])
@@ -4300,74 +5240,160 @@ window.__ModuleLoader__.load({
4300
5240
  }
4301
5241
 
4302
5242
  // ============================================================================
4303
- // Zone 4.4: 设置页品牌分区 (Settings Section: Brand)
5243
+ // 设置页 (Settings Section)
4304
5244
  // ============================================================================
4305
5245
  /**
4306
5246
  * The settings page section, mounted by the host into the `settings.section`
4307
- * slot. That slot hands a section only `{ close }` plus the standard hooks, so
4308
- * this component owns its state and persistence rather than reading a store.
5247
+ * slot. That slot hands a section only `{ close }` plus the standard hooks,
5248
+ * so this component owns no store of its own: it reads and writes the skin
5249
+ * preferences through src/context/prefs.js, which owns the host round trip, and
5250
+ * follows changes the same way the rest of the skin does.
5251
+ *
5252
+ * Copy comes from the model copy document's `settings` block, so the page
5253
+ * follows the shell language like every other string the skin paints. The
5254
+ * English literals here are the fallback for a failed fetch.
4309
5255
  *
4310
- * It renders a segmented control in the same visual language as the composer's
4311
- * permission control: a single hairline track with the active segment plated.
5256
+ * Both segmented controls reuse the shared `.dsh-claude-segments` /
5257
+ * `.dsh-claude-segment` classes — the same control the composer's permission
5258
+ * picker uses — so the two read as one design instead of two lookalikes.
4312
5259
  */
4313
- function BrandSettingsSection() {
4314
- var state = React.useState(readStoredBrand())
4315
- var brand = state[0]
4316
- var setBrand = state[1]
4317
-
4318
- var choose = function (next) {
4319
- if (next === brand) return
4320
- writeStoredBrand(next)
4321
- applyBrand(next)
4322
- setBrand(next)
4323
- }
5260
+ function ClaudeStyleSettingsSection() {
5261
+ var state = React.useState(readPrefs())
5262
+ var prefs = state[0]
5263
+ var setPrefs = state[1]
5264
+ var errorState = React.useState(null)
5265
+ var error = errorState[0]
5266
+ var setError = errorState[1]
5267
+
5268
+ // The skin's own apply-side writes land here too (a reload, a conflict
5269
+ // re-read), so the page never drifts from what the document says.
5270
+ React.useEffect(function () {
5271
+ var alive = true
5272
+ var unsubscribe = subscribePrefs(function (next) {
5273
+ if (alive) setPrefs(next)
5274
+ })
5275
+ var unsubscribeCopy = onModelCopyLoaded(function () {
5276
+ if (alive) setPrefs(function (p) { return Object.assign({}, p) })
5277
+ })
5278
+ return function () {
5279
+ alive = false
5280
+ unsubscribe()
5281
+ if (unsubscribeCopy) unsubscribeCopy()
5282
+ }
5283
+ }, [])
4324
5284
 
4325
- var options = [
4326
- { value: BRAND_CLAUDE, label: 'Claude' },
4327
- { value: BRAND_ANTHROPIC, label: 'Anthropic' },
4328
- ]
5285
+ /**
5286
+ * Apply one change. The control flips immediately and the host write
5287
+ * follows; a refusal re-reads the authoritative value and says so.
5288
+ */
5289
+ var write = function (patch) {
5290
+ setError(null)
5291
+ setPrefs(Object.assign({}, prefs, patch))
5292
+ savePrefs(patch).then(function (result) {
5293
+ if (result === null) setError(settingsCopy('unavailable', 'The settings store is unavailable, so changes will not be saved.'))
5294
+ })
5295
+ }
4329
5296
 
4330
- var segments = []
4331
- for (var i = 0; i < options.length; i++) {
4332
- segments.push(
4333
- React.createElement(
5297
+ var segment = function (options, active, onPick) {
5298
+ var buttons = []
5299
+ for (var i = 0; i < options.length; i++) {
5300
+ buttons.push(React.createElement(
4334
5301
  'button',
4335
5302
  {
4336
5303
  key: options[i].value,
4337
5304
  type: 'button',
4338
- className: 'dsh-claude-brand-option',
4339
- 'data-active': options[i].value === brand ? 'true' : 'false',
4340
- 'aria-pressed': options[i].value === brand ? 'true' : 'false',
5305
+ className: SEGMENT_CLASS,
5306
+ 'data-active': options[i].value === active ? '' : undefined,
5307
+ 'aria-pressed': options[i].value === active ? 'true' : 'false',
4341
5308
  onClick: (function (value) {
4342
5309
  return function () {
4343
- choose(value)
5310
+ if (value !== active) onPick(value)
4344
5311
  }
4345
5312
  })(options[i].value),
4346
5313
  },
4347
5314
  options[i].label,
4348
- ),
5315
+ ))
5316
+ }
5317
+ return React.createElement('div', { className: SEGMENTS_CLASS, role: 'group' }, buttons)
5318
+ }
5319
+
5320
+ var toggle = function (on, onPick) {
5321
+ return React.createElement(
5322
+ 'button',
5323
+ {
5324
+ type: 'button',
5325
+ className: 'dsh-claude-settings-switch',
5326
+ role: 'switch',
5327
+ 'aria-checked': on ? 'true' : 'false',
5328
+ 'data-on': on ? '' : undefined,
5329
+ onClick: function () { onPick(!on) },
5330
+ },
5331
+ React.createElement('span', { className: 'dsh-claude-settings-switch-knob' }),
4349
5332
  )
4350
5333
  }
4351
5334
 
4352
- return React.createElement(
4353
- 'div',
4354
- { className: 'dsh-claude-brand-section' },
4355
- React.createElement('div', { className: 'dsh-claude-brand-title' }, 'Claude Style'),
4356
- React.createElement(
5335
+ var row = function (key, title, description, control) {
5336
+ return React.createElement(
4357
5337
  'div',
4358
- { className: 'dsh-claude-brand-card' },
5338
+ { className: 'dsh-claude-settings-row', key: key },
4359
5339
  React.createElement(
4360
5340
  'div',
4361
- { className: 'dsh-claude-brand-card-text' },
4362
- React.createElement('div', { className: 'dsh-claude-brand-card-title' }, 'Sidebar brand'),
4363
- React.createElement(
4364
- 'div',
4365
- { className: 'dsh-claude-brand-card-desc' },
4366
- 'Which brand mark the sidebar shows. Claude is the default.',
4367
- ),
5341
+ { className: 'dsh-claude-settings-row-text' },
5342
+ React.createElement('div', { className: 'dsh-claude-settings-row-title' }, title),
5343
+ React.createElement('div', { className: 'dsh-claude-settings-row-desc' }, description),
4368
5344
  ),
4369
- React.createElement('div', { className: 'dsh-claude-brand-segments', role: 'group' }, segments),
5345
+ control,
5346
+ )
5347
+ }
5348
+
5349
+ var brandOptions = [
5350
+ { value: BRAND_OFF, label: settingsCopy('brandOff', 'Off') },
5351
+ { value: BRAND_CLAUDE, label: settingsCopy('brandClaude', 'Claude') },
5352
+ { value: BRAND_ANTHROPIC, label: settingsCopy('brandAnthropic', 'Anthropic') },
5353
+ ]
5354
+ var scopeOptions = [
5355
+ { value: 'off', label: settingsCopy('scopeOff', 'Off') },
5356
+ { value: 'hero', label: settingsCopy('scopeHero', 'Home only') },
5357
+ { value: 'conversation', label: settingsCopy('scopeConversation', 'Conversation only') },
5358
+ { value: 'all', label: settingsCopy('scopeAll', 'All') },
5359
+ ]
5360
+
5361
+ var rows = [
5362
+ row(
5363
+ 'brand',
5364
+ settingsCopy('brandTitle', 'Brand mark'),
5365
+ settingsCopy('brandDesc', 'Which brand mark the sidebar shows. "Off" leaves the host\'s own brand area untouched.'),
5366
+ segment(brandOptions, prefs.brand, function (value) { write({ brand: value }) }),
5367
+ ),
5368
+ row(
5369
+ 'collapseFooter',
5370
+ settingsCopy('collapseTitle', 'Collapse the sidebar settings area'),
5371
+ settingsCopy('collapseDesc', 'Fold the sidebar footer\'s settings entry into the account popover. Off hands the footer back to the host entirely.'),
5372
+ toggle(prefs.collapseFooter, function (value) { write({ collapseFooter: value }) }),
5373
+ ),
5374
+ row(
5375
+ 'autoPopover',
5376
+ settingsCopy('autoPopoverTitle', 'Open popovers on hover'),
5377
+ settingsCopy('autoPopoverDesc', 'Hover opens the account and model popovers. Off switches them to click-to-open.'),
5378
+ toggle(prefs.autoPopover, function (value) { write({ autoPopover: value }) }),
5379
+ ),
5380
+ row(
5381
+ 'composerScope',
5382
+ settingsCopy('composerTitle', 'Composer restyle'),
5383
+ settingsCopy('composerDesc', 'Which input area the skin restyles: the new-conversation page, the conversation, or both.'),
5384
+ segment(scopeOptions, prefs.composerScope, function (value) { write({ composerScope: value }) }),
4370
5385
  ),
5386
+ ]
5387
+
5388
+ if (error !== null) {
5389
+ rows.push(React.createElement('div', { className: 'dsh-claude-settings-error', key: 'error' }, error))
5390
+ }
5391
+
5392
+ return React.createElement(
5393
+ 'div',
5394
+ { className: 'dsh-claude-settings' },
5395
+ React.createElement('div', { className: 'dsh-claude-settings-title' }, settingsCopy('title', 'Claude Style')),
5396
+ rows,
4371
5397
  )
4372
5398
  }
4373
5399
 
@@ -4391,6 +5417,7 @@ window.__ModuleLoader__.load({
4391
5417
  * @returns a disposer that tears the registration down.
4392
5418
  */
4393
5419
  function installSettingsSection(ctx) {
5420
+ loadModelCopy()
4394
5421
  if (typeof ctx.inject !== 'function') return function () {}
4395
5422
  var fiber = ctx.inject(['slots'], function (scope) {
4396
5423
  var slots = scope.get('slots')
@@ -4398,8 +5425,15 @@ window.__ModuleLoader__.load({
4398
5425
  scope.effect(function () {
4399
5426
  return slots.inject('settings.section', function () {
4400
5427
  return slots.register(
4401
- { name: 'settings.section', id: 'claude-style', order: 22, label: function () { return 'Claude Style' } },
4402
- BrandSettingsSection,
5428
+ {
5429
+ name: 'settings.section',
5430
+ id: 'claude-style',
5431
+ order: 22,
5432
+ // A function, so the navigation entry localizes once the copy
5433
+ // document has arrived; the literal is the pre-fetch fallback.
5434
+ label: function () { return settingsCopy('title', 'Claude Style') },
5435
+ },
5436
+ ClaudeStyleSettingsSection,
4403
5437
  )
4404
5438
  })
4405
5439
  }, 'dsh-claude-style: settings section')
@@ -4414,12 +5448,18 @@ window.__ModuleLoader__.load({
4414
5448
  }
4415
5449
 
4416
5450
  // ============================================================================
4417
- // Zone 6: 插件入口与导出 (Plugin Entry & Export)
5451
+ // 插件入口与导出 (Plugin Entry & Export)
4418
5452
  // ============================================================================
4419
5453
  function apply(ctx) {
4420
5454
  var body = document.body
4421
5455
  body.setAttribute('data-dsh-claude-style', '')
4422
- applyBrand(readStoredBrand())
5456
+ setHostContext(ctx)
5457
+ loadModelCopy()
5458
+ // Preferences are read asynchronously from the host settings namespace;
5459
+ // applying the defaults first keeps every gated rule in a defined state
5460
+ // for the frames before that read settles, and is exactly the shipped
5461
+ // behaviour when it never does.
5462
+ adoptPrefs(prefs)
4423
5463
 
4424
5464
  var old = document.getElementById(STYLE_ID)
4425
5465
  if (old && old.parentElement) old.parentElement.removeChild(old)
@@ -4430,19 +5470,30 @@ window.__ModuleLoader__.load({
4430
5470
  style.textContent = CSS
4431
5471
  document.head.appendChild(style)
4432
5472
 
4433
- var stopOverrides = installOverrides(ctx)
5473
+ var ui = {}
5474
+ var teardowns = []
5475
+ teardowns.push(installCopy(ctx, ui))
5476
+ teardowns.push(installPermissions(ctx, ui))
5477
+ teardowns.push(installModelPicker(ctx, ui))
5478
+ teardowns.push(installAccountFooter(ctx, ui))
5479
+ teardowns.push(installScheduler(ctx, ui)) // 最后装,回调中惰性读 ui 句柄
4434
5480
  var stopSettings = installSettingsSection(ctx)
4435
5481
 
4436
5482
  ctx.effect(function () {
4437
5483
  return function () {
4438
- stopOverrides()
5484
+ for (var i = teardowns.length - 1; i >= 0; i--) {
5485
+ try { teardowns[i]() } catch (error) { /* one teardown must not block the rest */ }
5486
+ }
4439
5487
  stopSettings()
5488
+ setHostContext(null)
4440
5489
  body.removeAttribute('data-dsh-claude-style')
4441
5490
  body.removeAttribute(BRAND_ATTR)
5491
+ body.removeAttribute(FOOTER_ATTR)
5492
+ body.removeAttribute(COMPOSER_ATTR)
4442
5493
  var el = document.getElementById(STYLE_ID)
4443
5494
  if (el) el.remove()
4444
5495
  }
4445
- }, 'dsh-claude-style: Claude Code Desktop theme')
5496
+ }, 'dsh-claude-style: Claude Code desktop theme')
4446
5497
  }
4447
5498
 
4448
5499
  exports.apply = apply