dsh-ui-tweaks 0.16.2 → 0.16.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # dsh-ui-tweaks
2
2
 
3
- > **Dependency**: currently targets **DSH v0.1.5-alpha.2**.
3
+ > **Dependency**: currently targets **DSH v0.1.5-rc.1**.
4
4
 
5
5
  A [DeepSeek Harness](https://deepseek-harness.github.io/deepseek-harness/) (DSH) web plugin that live-tunes the conversation UI from the Settings panel.
6
6
 
@@ -18,7 +18,7 @@ A [DeepSeek Harness](https://deepseek-harness.github.io/deepseek-harness/) (DSH)
18
18
  ## Features
19
19
 
20
20
  - **Code font size (px)** — absolute 8–32px, default 13 (DSH's stock code-block size at a 16px body); applies to code blocks, with inline code following proportionally. The legacy percentage (`codeFontScale`) stays compatible and is overridden once a px value is set. Message text keeps DSH's stock sizing.
21
- - **Theme (single choice in Layout, stock look by default)** — choose `Default` or **Neon poster**: a two-scheme poster skin — paper-white with ink-black hairlines in light mode, near-black with light hairlines in dark mode, both with lime highlights and an Anthropic-red action accent (buttons, links and selections follow the theme tokens automatically; code blocks and the composer card get hard offset shadows), applied live. Future skins will be added as further options the same way.
21
+ - **Theme (single choice in Layout, stock look by default)** — choose `Default` or **Neon poster**: a two-scheme poster skin — paper-white with ink-black hairlines in light mode, near-black with light hairlines in dark mode, both with lime highlights and an Anthropic-red action accent (buttons, links and selections follow the theme tokens automatically; code blocks and the composer card get hard offset shadows), and the composer's reasoning-effort label is tinted by intensity (green / amber / blue / violet, with a same-hue wash sweeping option rows on hover), applied live. Future skins will be added as further options the same way.
22
22
  - **Timeline (single choice in Features)** — one switch, two options:
23
23
  - **Native (default)** — DSH's built-in turn rail (the row of small dots beside the messages), the stock behavior.
24
24
  - **Web (classic)** — the v0.11 classic right-side navigation rail, restored: vertically centered on the message area's right edge, a thin line strip when collapsed, a 240px panel on hover (message previews + current-position highlight), a per-item detail bubble with timestamp, and **click to jump** (deep history pages in automatically before landing, with a landing self-check); wheel over the rail scrubs clipped items into reach. Data comes from the server-side `dshChatTimeline` session projection (every user message, independent of the browser's loaded window); sessions with fewer than two user messages hide it. On the web option the native turn rail is hidden with one theme-independent CSS rule (matching its `--turn-natural-height` inline variable), so the two never appear together.
@@ -61,7 +61,7 @@ Settings entry: **Settings → UI Tweaks**.
61
61
  # from npm (recommended, prebuilt)
62
62
  npx -y @deepseek-ai/dsh plugin --profile web add dsh-ui-tweaks
63
63
 
64
- # from GitHub (source; runs the self-contained prepare build)
64
+ # from GitHub (source with prebuilt artifacts; no local build needed)
65
65
  npx -y @deepseek-ai/dsh plugin --profile web add github:wlj521/dsh-ui-tweaks
66
66
  ```
67
67
 
@@ -73,15 +73,7 @@ npx -y @deepseek-ai/dsh plugin --profile web add dsh-ui-tweaks@0.12.0
73
73
  npx -y @deepseek-ai/dsh plugin --profile web add github:wlj521/dsh-ui-tweaks#v0.12.0 # pin a git tag
74
74
  ```
75
75
 
76
- For GitHub installs, pnpm may ask you to approve the package's build script —
77
- add the exact key it prints to the profile's `pnpm-workspace.yaml`:
78
-
79
- ```yaml
80
- allowBuilds:
81
- dsh-ui-tweaks: true
82
- ```
83
-
84
- …then run `add` again. Restart DSH web once after installing (bundle plugins
76
+ Restart DSH web once after installing (bundle plugins
85
77
  are scanned at process start).
86
78
 
87
79
  > If pnpm reports symlink/hoist errors, set `nodeLinker: hoisted` in the
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # dsh-ui-tweaks
2
2
 
3
- > **依赖版本**:当前依赖 **DSH v0.1.5-alpha.2**。
3
+ > **依赖版本**:当前依赖 **DSH v0.1.5-rc.1**。
4
4
 
5
5
  [DeepSeek Harness](https://deepseek-harness.github.io/deepseek-harness/)(DSH)Web UI 插件:在设置面板中实时调整对话界面——代码字号、**主题皮肤**(荧光海报风)、**时间线切换**(原生回合导航轨 / 经典网页时间线)、**GitBar**(会话头部的分支胶囊 + 右侧边栏里的终端 / 代码差异标签页),可开关的**归档管理**(设置中的「归档」页面:查看、恢复或彻底删除已归档会话),可开关的**任务提醒**(会话完成或需要交互时,通过标签页标题闪烁 / 系统通知 / 提示音把你唤回来),以及**缓存命中率两位小数**(把输入框下方统计条的缓存命中百分比改写为精确值)。
6
6
 
@@ -18,7 +18,7 @@
18
18
  ## 功能
19
19
 
20
20
  - **代码字号(px)**:绝对值 8–32,默认 13(正文 16 时的 DSH 原生代码块字号);作用于代码块,行内代码按比例跟随——旧版的百分比配置(`codeFontScale`)仍然兼容,一旦设置新的 px 值即以其为准。消息正文保持 DSH 原生字号。
21
- - **主题(布局区单选,默认即原生外观)**:可选 `默认` 或 **荧光海报**——海报风双方案皮肤:浅色模式纸白底 + 黑粗线条,深色模式近黑底 + 浅色粗线条,都配荧光黄高亮 + Anthropic 红点缀(按钮、链接、选中态跟随主题变量自动变色,代码块与输入框卡片带硬阴影贴纸效果),切换即时生效。后续皮肤会以同样方式追加为新选项。
21
+ - **主题(布局区单选,默认即原生外观)**:可选 `默认` 或 **荧光海报**——海报风双方案皮肤:浅色模式纸白底 + 黑粗线条,深色模式近黑底 + 浅色粗线条,都配荧光黄高亮 + Anthropic 红点缀(按钮、链接、选中态跟随主题变量自动变色,代码块与输入框卡片带硬阴影贴纸效果),输入框模型旁的**推理等级**按强度着色(绿 / 琥珀 / 蓝 / 紫,悬停选项行时同色底从左扫过),切换即时生效。后续皮肤会以同样方式追加为新选项。
22
22
  - **时间线(功能区内单选)**:一个开关两档,选一个:
23
23
  - **原生(默认)**:DSH 自带的回合导航轨(消息右侧的小圆点条),即官方默认行为。
24
24
  - **网页(经典)**:找回 v0.11 的经典右侧导航轨——消息区右缘垂直居中,收起时是一列细线,悬停展开为 240px 面板(消息预览 + 高亮当前位),悬停单项弹出带时间戳的详情气泡,**点击跳转**到对应消息(深历史会自动连续翻页加载后落点,带落点自校验);滚轮可在轨上直接翻阅被裁剪的条目。数据来自服务端 `dshChatTimeline` 会话投影(全量用户消息,与浏览器已加载窗口无关),少于两条用户消息的会话自动隐藏;切换到网页档时,原生回合导航轨由插件用一条主题无关的 CSS 规则(命中其 `--turn-natural-height` 内联变量)隐藏,两个不会同时出现。
@@ -64,7 +64,7 @@ ui-tweaks:
64
64
  # 方式一:从 npm 安装(推荐,预构建产物,一条命令装好)
65
65
  npx -y @deepseek-ai/dsh plugin --profile web add dsh-ui-tweaks
66
66
 
67
- # 方式二:从 GitHub 仓库安装(源码,会运行自包含的 prepare 构建)
67
+ # 方式二:从 GitHub 仓库安装(源码 + 预构建产物,无需本地构建)
68
68
  npx -y @deepseek-ai/dsh plugin --profile web add github:wlj521/dsh-ui-tweaks
69
69
  ```
70
70
 
@@ -75,14 +75,7 @@ npx -y @deepseek-ai/dsh plugin --profile web add dsh-ui-tweaks@0.12.0
75
75
  npx -y @deepseek-ai/dsh plugin --profile web add github:wlj521/dsh-ui-tweaks#v0.12.0 # 锁定 git tag
76
76
  ```
77
77
 
78
- GitHub 安装时,pnpm 可能要求批准该包的构建脚本——把提示的包键加进该 profile 的 `pnpm-workspace.yaml`:
79
-
80
- ```yaml
81
- allowBuilds:
82
- dsh-ui-tweaks: true
83
- ```
84
-
85
- 然后重新执行 `add`。安装完成后**重启一次 `dsh web`**(bundle 插件在进程启动时扫描)。
78
+ 安装完成后**重启一次 `dsh web`**(bundle 插件在进程启动时扫描)。
86
79
 
87
80
  > 若 pnpm 报符号链接/hoist 相关错误,可在 profile 的 `pnpm-workspace.yaml` 中设置 `nodeLinker: hoisted`。
88
81
 
package/lib/client.js CHANGED
@@ -417,6 +417,197 @@ function PreciseCacheHitEntry({ useProjection }) {
417
417
  return null;
418
418
  }
419
419
  };
420
+ __modules["./effort.js"] = function(module, exports, require, __load_) {
421
+ "use strict";
422
+ /**
423
+ * dsh-ui-tweaks — thinking-effort tint for the composer model seat (browser half).
424
+ *
425
+ * Tags every rendered thinking-effort label with its intensity band, so the
426
+ * poster skin can paint it in a solid band color (Low green, Medium amber,
427
+ * High blue, Max Codex-violet), and wash the hovered option row with a
428
+ * left-to-right gradient in the same hue. Three surfaces are covered — the
429
+ * `model · effort` trigger in `conversation.input.model`, the Effort cell
430
+ * value in its dropdown menu root, and every option row in the menu's
431
+ * effort pane (label span plus row button, the button carrying the band for
432
+ * the background wash). Off / provider-Default / unrecognized names stay
433
+ * the stock caption gray — a level we cannot place gets no color rather
434
+ * than a wrong one; extend BAND_WORDS when a new adapter ships new names.
435
+ *
436
+ * - Effort levels are adapter-owned: DeepSeek advertises Off/Low/High/Max,
437
+ * pi-ai-backed providers offer off/minimal/low/medium/high/xhigh/max
438
+ * (shown capitalized), others e.g. Standard. Classification reads the
439
+ * rendered label text, never a fixed id vocabulary.
440
+ * - The trigger renders model-name and effort spans in order (the chevron is
441
+ * an svg), so the trailing span is the effort label when present. The menu
442
+ * is portaled to document.body, outside the seat: each trigger's
443
+ * `aria-controls` names its menu element, which is how the watcher reaches
444
+ * it. Inside the menu, radios beside group sections are the model list
445
+ * (left alone); radios without sections are effort options.
446
+ * - One MutationObserver re-tags on React repaints (model switch, effort
447
+ * change, session switch, menu open); writes are idempotent — an attribute
448
+ * is only touched when it actually changes — so the loop settles after one
449
+ * pass. Removed nodes take their tags with them; cleanup clears the rest.
450
+ * - Mounted only while the neon-poster theme is active (see the effect in
451
+ * index.tsx): other themes have no rules for the tag and stay stock.
452
+ * @module dsh-ui-tweaks/client/effort
453
+ */
454
+ Object.defineProperty(exports, "__esModule", { value: true });
455
+ exports.classifyEffort = classifyEffort;
456
+ exports.installEffortTag = installEffortTag;
457
+ /**
458
+ * Rendered effort names per band, matched case-insensitively after trimming.
459
+ * DeepSeek advertises Off/Low/High/Max; pi-ai-backed providers offer
460
+ * off/minimal/low/medium/high/xhigh/max (shown capitalized); others e.g.
461
+ * Standard. Adapter names are English in practice; the Chinese entries cover
462
+ * adapters that localize their level names.
463
+ */
464
+ const BAND_WORDS = {
465
+ low: ['low', 'minimal', 'mini', 'eco', 'fast', '低', '快速'],
466
+ medium: ['medium', 'standard', 'balanced', 'moderate', '中', '标准'],
467
+ high: ['high', 'deep', 'advanced', '高', '深度', '强'],
468
+ max: ['max', 'maximum', 'ultra', 'extreme', 'highest', 'xhigh', '最高', '最强'],
469
+ };
470
+ /**
471
+ * Names that state no intensity: the provider default (whose real level is
472
+ * unknown to the viewer), an automatic choice, or thinking turned off.
473
+ */
474
+ const NEUTRAL_WORDS = [
475
+ 'default',
476
+ 'provider default',
477
+ 'auto',
478
+ 'off',
479
+ 'none',
480
+ 'disabled',
481
+ '默认',
482
+ '自动',
483
+ '关闭',
484
+ '无',
485
+ ];
486
+ /**
487
+ * Place a rendered effort label on the intensity scale.
488
+ * @param name - the effort label as shown (e.g. 'High').
489
+ * @returns its band, or null to leave the stock caption untouched.
490
+ */
491
+ function classifyEffort(name) {
492
+ const normalized = name.trim().toLowerCase();
493
+ if (normalized === '')
494
+ return null;
495
+ for (const word of NEUTRAL_WORDS) {
496
+ if (normalized === word)
497
+ return null;
498
+ }
499
+ for (const [band, words] of Object.entries(BAND_WORDS)) {
500
+ if (words.includes(normalized))
501
+ return band;
502
+ }
503
+ return null;
504
+ }
505
+ /** Attribute the tagger sets on effort label spans; the skin reads it. */
506
+ const EFFORT_ATTR = 'data-dut-effort';
507
+ /** Model triggers live in the input's model seat, wherever it is mounted. */
508
+ const MODEL_TRIGGER_SELECTOR = 'div[data-slot="conversation.input.model"] button';
509
+ /** Root-cell headings that name the effort row, in either shipped locale. */
510
+ const EFFORT_CELL_LABELS = new Set(['推理等级', 'Effort']);
511
+ /**
512
+ * Tag (or untag) one label span; idempotent — untouched when already correct.
513
+ * @param el - the label span, if the structure offered one.
514
+ * @param present - whether this span is an effort label.
515
+ */
516
+ function tagLabel(el, band) {
517
+ if (!(el instanceof HTMLElement))
518
+ return;
519
+ if (band === null) {
520
+ if (el.hasAttribute(EFFORT_ATTR))
521
+ el.removeAttribute(EFFORT_ATTR);
522
+ return;
523
+ }
524
+ if (el.getAttribute(EFFORT_ATTR) !== band)
525
+ el.setAttribute(EFFORT_ATTR, band);
526
+ }
527
+ /**
528
+ * Tag a model trigger's effort span: the trailing direct-child span, present
529
+ * only while the model advertises reasoning levels.
530
+ */
531
+ function applyToTrigger(button) {
532
+ const spans = button.querySelectorAll(':scope > span');
533
+ for (const span of spans) {
534
+ const isEffort = spans.length >= 2 && span === spans[spans.length - 1];
535
+ tagLabel(span, isEffort ? classifyEffort(span.textContent?.trim() ?? '') : null);
536
+ }
537
+ }
538
+ /**
539
+ * Tag one open model menu, reached through its trigger's aria-controls.
540
+ * Radios beside group sections are the model list (left alone); radios
541
+ * without sections are effort options; the root pane's Effort cell value
542
+ * takes the current effort's band.
543
+ */
544
+ function applyToMenu(menu) {
545
+ const radios = menu.querySelectorAll('button[role="menuitemradio"]');
546
+ const hasGroups = menu.querySelector('section[role="group"]') !== null;
547
+ for (const radio of radios) {
548
+ if (!(radio instanceof HTMLButtonElement))
549
+ continue;
550
+ if (hasGroups) {
551
+ radio.removeAttribute(EFFORT_ATTR);
552
+ for (const tagged of radio.querySelectorAll(`span[${EFFORT_ATTR}]`)) {
553
+ tagged.removeAttribute(EFFORT_ATTR);
554
+ }
555
+ continue;
556
+ }
557
+ const copy = radio.querySelector(':scope > span');
558
+ const band = classifyEffort(copy?.textContent?.trim() ?? '');
559
+ tagLabel(copy, band);
560
+ // The row button carries the same band so the skin can wash its
561
+ // background on hover without :has selectors.
562
+ if (band === null) {
563
+ radio.removeAttribute(EFFORT_ATTR);
564
+ }
565
+ else if (radio.getAttribute(EFFORT_ATTR) !== band) {
566
+ radio.setAttribute(EFFORT_ATTR, band);
567
+ }
568
+ }
569
+ for (const cell of menu.querySelectorAll('button[role="menuitem"]')) {
570
+ if (!(cell instanceof HTMLButtonElement))
571
+ continue;
572
+ const spans = cell.querySelectorAll(':scope > span');
573
+ if (spans.length < 2)
574
+ continue;
575
+ const head = spans[0];
576
+ const value = spans[spans.length - 1] ?? null;
577
+ const isEffortCell = EFFORT_CELL_LABELS.has(head?.textContent?.trim() ?? '');
578
+ tagLabel(value, isEffortCell ? classifyEffort(value?.textContent?.trim() ?? '') : null);
579
+ }
580
+ }
581
+ /**
582
+ * Watch every composer model trigger (and its open dropdown menu) and keep
583
+ * the effort band tags current.
584
+ * @returns cleanup: stops the observer and removes all tags set here.
585
+ */
586
+ function installEffortTag() {
587
+ const apply = () => {
588
+ for (const button of document.querySelectorAll(MODEL_TRIGGER_SELECTOR)) {
589
+ applyToTrigger(button);
590
+ // The menu is portaled to document.body, outside the seat: the
591
+ // trigger's aria-controls names its element while open.
592
+ const menuId = button.getAttribute('aria-controls');
593
+ const menu = menuId === null ? null : document.getElementById(menuId);
594
+ if (menu !== null)
595
+ applyToMenu(menu);
596
+ }
597
+ };
598
+ apply();
599
+ // React owns the trigger: every repaint re-triggers us, and the idempotent
600
+ // writes settle after a single extra pass.
601
+ const observer = new MutationObserver(apply);
602
+ observer.observe(document.body, { childList: true, characterData: true, subtree: true });
603
+ return () => {
604
+ observer.disconnect();
605
+ for (const tagged of document.querySelectorAll(`[${EFFORT_ATTR}]`)) {
606
+ tagged.removeAttribute(EFFORT_ATTR);
607
+ }
608
+ };
609
+ }
610
+ };
420
611
  __modules["./gitbar.js"] = function(module, exports, require, __load_) {
421
612
  "use strict";
422
613
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -2418,6 +2609,7 @@ const mcp_tsx_1 = __load_("./mcp.js");
2418
2609
  const search_tsx_1 = __load_("./search.js");
2419
2610
  const timeline_tsx_1 = __load_("./timeline.js");
2420
2611
  const cachehit_tsx_1 = __load_("./cachehit.js");
2612
+ const effort_tsx_1 = __load_("./effort.js");
2421
2613
  const notifier_ts_1 = __load_("./notifier.js");
2422
2614
  const NS = 'ui-tweaks';
2423
2615
  const SETTINGS_ROUTE = '/_dsh/ui-tweaks/settings';
@@ -2649,6 +2841,8 @@ const en = {
2649
2841
  includeFile: 'Include in commit',
2650
2842
  excludeFile: 'Exclude from commit',
2651
2843
  terminal: 'Terminal',
2844
+ terminalGuide: 'A persistent shell in the session working directory.',
2845
+ diffGuide: 'Review working-tree changes and commit.',
2652
2846
  notRepo: 'Not a git repository.',
2653
2847
  termConnecting: 'connecting…',
2654
2848
  termExited: 'shell exited',
@@ -2883,6 +3077,8 @@ const zh = {
2883
3077
  includeFile: '提交包含此文件',
2884
3078
  excludeFile: '提交排除此文件',
2885
3079
  terminal: '终端',
3080
+ terminalGuide: '在会话工作目录中打开常驻 shell。',
3081
+ diffGuide: '查看工作区改动并提交。',
2886
3082
  notRepo: '当前目录不是 git 仓库。',
2887
3083
  termConnecting: '连接中…',
2888
3084
  termExited: 'shell 已退出',
@@ -3205,6 +3401,68 @@ div[data-composer-card]{
3205
3401
  (scoped to the composer's svg so the class-substring match stays safe) */
3206
3402
  div[data-composer-card] svg [class*="track"]{stroke:var(--dut-scroll-1)}
3207
3403
  div[data-composer-card] svg [class*="fill"]{stroke:var(--dut-accent)}
3404
+ /* composer model seat and its dropdown menu: thinking-effort labels in solid
3405
+ band colors (Low green, Medium amber, High blue, Max Codex-violet).
3406
+ The effort watcher tags the label spans data-dut-effort — the trigger
3407
+ effort span, the menu root Effort cell value, and every effort-option
3408
+ label — while option row buttons carry the same band for the hover wash;
3409
+ Off/Default stay untagged in the stock caption. One plain span selector
3410
+ covers all three label surfaces — the attribute only exists where the
3411
+ watcher set it, so no hashed module class is touched. */
3412
+ [data-slot="conversation.input.model"] span[data-dut-effort],
3413
+ div[role="menu"] span[data-dut-effort]{font-weight:600}
3414
+ [data-slot="conversation.input.model"] span[data-dut-effort="low"],
3415
+ div[role="menu"] span[data-dut-effort="low"]{color:#15803d}
3416
+ [data-slot="conversation.input.model"] span[data-dut-effort="medium"],
3417
+ div[role="menu"] span[data-dut-effort="medium"]{color:#b45309}
3418
+ [data-slot="conversation.input.model"] span[data-dut-effort="high"],
3419
+ div[role="menu"] span[data-dut-effort="high"]{color:#1d4ed8}
3420
+ [data-slot="conversation.input.model"] span[data-dut-effort="max"],
3421
+ div[role="menu"] span[data-dut-effort="max"]{color:#7c3aed}
3422
+ body:not(#dsh-ui-tweaks-theme-scope)[data-ds-dark-theme] [data-slot="conversation.input.model"] span[data-dut-effort="low"],
3423
+ body:not(#dsh-ui-tweaks-theme-scope)[data-ds-dark-theme] div[role="menu"] span[data-dut-effort="low"]{color:#4ade80}
3424
+ body:not(#dsh-ui-tweaks-theme-scope)[data-ds-dark-theme] [data-slot="conversation.input.model"] span[data-dut-effort="medium"],
3425
+ body:not(#dsh-ui-tweaks-theme-scope)[data-ds-dark-theme] div[role="menu"] span[data-dut-effort="medium"]{color:#fbbf24}
3426
+ body:not(#dsh-ui-tweaks-theme-scope)[data-ds-dark-theme] [data-slot="conversation.input.model"] span[data-dut-effort="high"],
3427
+ body:not(#dsh-ui-tweaks-theme-scope)[data-ds-dark-theme] div[role="menu"] span[data-dut-effort="high"]{color:#60a5fa}
3428
+ body:not(#dsh-ui-tweaks-theme-scope)[data-ds-dark-theme] [data-slot="conversation.input.model"] span[data-dut-effort="max"],
3429
+ body:not(#dsh-ui-tweaks-theme-scope)[data-ds-dark-theme] div[role="menu"] span[data-dut-effort="max"]{color:#a78bfa}
3430
+ /* Trigger chip: the input-box effort label additionally rides a translucent
3431
+ pill in its band hue (menu labels stay plain text). Padding/radius work
3432
+ as-is — the trigger is flex, so its spans are already blockified. */
3433
+ [data-slot="conversation.input.model"] span[data-dut-effort]{
3434
+ padding:1px 7px;
3435
+ border-radius:999px;
3436
+ }
3437
+ [data-slot="conversation.input.model"] span[data-dut-effort="low"]{background:rgba(21,128,61,.12)}
3438
+ [data-slot="conversation.input.model"] span[data-dut-effort="medium"]{background:rgba(180,83,9,.12)}
3439
+ [data-slot="conversation.input.model"] span[data-dut-effort="high"]{background:rgba(29,78,216,.10)}
3440
+ [data-slot="conversation.input.model"] span[data-dut-effort="max"]{background:rgba(124,58,237,.12)}
3441
+ body:not(#dsh-ui-tweaks-theme-scope)[data-ds-dark-theme] [data-slot="conversation.input.model"] span[data-dut-effort="low"]{background:rgba(74,222,128,.18)}
3442
+ body:not(#dsh-ui-tweaks-theme-scope)[data-ds-dark-theme] [data-slot="conversation.input.model"] span[data-dut-effort="medium"]{background:rgba(251,191,36,.18)}
3443
+ body:not(#dsh-ui-tweaks-theme-scope)[data-ds-dark-theme] [data-slot="conversation.input.model"] span[data-dut-effort="high"]{background:rgba(96,165,250,.18)}
3444
+ body:not(#dsh-ui-tweaks-theme-scope)[data-ds-dark-theme] [data-slot="conversation.input.model"] span[data-dut-effort="max"]{background:rgba(167,139,250,.20)}
3445
+ /* Effort option rows: on hover the row background wipes in from the left in
3446
+ the band hue (translucent wash into transparency), overriding the stock
3447
+ row hover fill — same specificity contest the skin always wins by naming
3448
+ the attribute. background-size animates reliably (no background-clip text
3449
+ involved); honoring prefers-reduced-motion. */
3450
+ div[role="menu"] button[data-dut-effort]{
3451
+ background-repeat:no-repeat;
3452
+ background-size:0% 100%;
3453
+ transition:background-size .6s ease;
3454
+ }
3455
+ div[role="menu"] button[data-dut-effort="low"]:hover{background-image:linear-gradient(90deg,rgba(21,128,61,.16),rgba(21,128,61,0));background-size:100% 100%}
3456
+ div[role="menu"] button[data-dut-effort="medium"]:hover{background-image:linear-gradient(90deg,rgba(180,83,9,.16),rgba(180,83,9,0));background-size:100% 100%}
3457
+ div[role="menu"] button[data-dut-effort="high"]:hover{background-image:linear-gradient(90deg,rgba(29,78,216,.14),rgba(29,78,216,0));background-size:100% 100%}
3458
+ div[role="menu"] button[data-dut-effort="max"]:hover{background-image:linear-gradient(90deg,rgba(124,58,237,.16),rgba(124,58,237,0));background-size:100% 100%}
3459
+ body:not(#dsh-ui-tweaks-theme-scope)[data-ds-dark-theme] div[role="menu"] button[data-dut-effort="low"]:hover{background-image:linear-gradient(90deg,rgba(74,222,128,.18),rgba(74,222,128,0));background-size:100% 100%}
3460
+ body:not(#dsh-ui-tweaks-theme-scope)[data-ds-dark-theme] div[role="menu"] button[data-dut-effort="medium"]:hover{background-image:linear-gradient(90deg,rgba(251,191,36,.18),rgba(251,191,36,0));background-size:100% 100%}
3461
+ body:not(#dsh-ui-tweaks-theme-scope)[data-ds-dark-theme] div[role="menu"] button[data-dut-effort="high"]:hover{background-image:linear-gradient(90deg,rgba(96,165,250,.20),rgba(96,165,250,0));background-size:100% 100%}
3462
+ body:not(#dsh-ui-tweaks-theme-scope)[data-ds-dark-theme] div[role="menu"] button[data-dut-effort="max"]:hover{background-image:linear-gradient(90deg,rgba(167,139,250,.20),rgba(167,139,250,0));background-size:100% 100%}
3463
+ @media (prefers-reduced-motion:reduce){
3464
+ div[role="menu"] button[data-dut-effort]{transition:none}
3465
+ }
3208
3466
  /* sidebar new-session button: lime sticker. Matched by its CSS-module class,
3209
3467
  NOT the aria-label — the brand row button shares the same "新建会话"
3210
3468
  label (it doubles as the new-session shortcut) and must stay plain. */
@@ -3275,7 +3533,7 @@ function runtimeStyleElement() {
3275
3533
  const BASE_CSS = `
3276
3534
  .dut-settings{display:grid;gap:8px;max-width:680px;padding:4px 2px 24px;color:var(--dsw-alias-label-primary)}
3277
3535
  .dut-settings-header{display:flex;align-items:flex-start;gap:10px;padding:2px 2px 0}
3278
- .dut-logo{flex:none;display:grid;place-items:center;width:30px;height:30px;border-radius:9px;border:1px solid var(--dsw-alias-border-l1);background:linear-gradient(135deg,color-mix(in srgb,var(--dsw-alias-state-business-primary) 16%,transparent),transparent);font-size:15px;line-height:1}
3536
+ .dut-logo{flex:none;display:grid;place-items:center;width:30px;height:30px;border-radius:9px;border:1px solid var(--dsw-alias-border-l1);background-image:linear-gradient(135deg,color-mix(in srgb,var(--dsw-alias-state-business-primary) 16%,transparent),transparent);font-size:15px;line-height:1}
3279
3537
  .dut-settings-header h2{font-size:16px;letter-spacing:-.01em;margin:0 0 2px}
3280
3538
  .dut-settings-header p{max-width:600px;margin:0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.45}
3281
3539
  .dut-panel{display:grid;gap:0;border:1px solid var(--dsw-alias-border-l1);border-radius:14px;background:var(--dsw-alias-bg-layer-1);box-shadow:var(--dsw-shadow-lv1);overflow:hidden}
@@ -3732,7 +3990,8 @@ function apply(ctx) {
3732
3990
  // picking one opens the tab in the sidebar), registered only while the
3733
3991
  // gitBarEnabled toggle in the UI Tweaks section is on. Each tab is a page
3734
3992
  // type (no address patterns): stage one registers the type with its guide
3735
- // capsule, stage two the body under the type's id. The terminal reattaches to
3993
+ // capsule (description shows while the guide lists ≤4 entries), stage two
3994
+ // the body under the type's id. The terminal reattaches to
3736
3995
  // its persistent host shell on every visit, and the diff tab keeps its
3737
3996
  // commit band. The tab registry is resolved lazily: hosts predating the
3738
3997
  // right sidebar simply skip these tabs while everything else keeps working.
@@ -3754,7 +4013,7 @@ function apply(ctx) {
3754
4013
  id: 'dsh-ui-tweaks/terminal',
3755
4014
  kind: 'ui-tweaks-terminal',
3756
4015
  title: () => t('terminal'),
3757
- guide: [{ order: 20, title: () => t('terminal'), icon: icons_tsx_1.TerminalIcon }],
4016
+ guide: [{ order: 20, title: () => t('terminal'), description: () => t('terminalGuide'), icon: icons_tsx_1.TerminalIcon }],
3758
4017
  }));
3759
4018
  disposers.push(ctx.slots.inject('sidebar.right.pane.tab', () => ctx.slots.register({
3760
4019
  name: 'sidebar.right.pane.tab',
@@ -3766,7 +4025,7 @@ function apply(ctx) {
3766
4025
  id: 'dsh-ui-tweaks/diff',
3767
4026
  kind: 'ui-tweaks-diff',
3768
4027
  title: () => t('diffView'),
3769
- guide: [{ order: 30, title: () => t('diffView'), icon: icons_tsx_1.DiffIcon }],
4028
+ guide: [{ order: 30, title: () => t('diffView'), description: () => t('diffGuide'), icon: icons_tsx_1.DiffIcon }],
3770
4029
  }));
3771
4030
  disposers.push(ctx.slots.inject('sidebar.right.pane.tab', () => ctx.slots.register({
3772
4031
  name: 'sidebar.right.pane.tab',
@@ -3927,6 +4186,31 @@ function apply(ctx) {
3927
4186
  disposeEntry = undefined;
3928
4187
  };
3929
4188
  }, 'dsh-ui-tweaks: precise cache hit');
4189
+ // Thinking-effort tint: tags thinking-effort labels — the composer
4190
+ // trigger, the menu root Effort cell and the menu effort options — so the
4191
+ // poster skin can paint solid band colors plus a hover background wash.
4192
+ // Mounted only while the neon-poster theme is active — other themes keep
4193
+ // the stock caption, and leaving the theme removes every tag.
4194
+ ctx.effect(() => {
4195
+ let disposeTag;
4196
+ const sync = () => {
4197
+ const poster = controller.getSnapshot().value?.themeStyle === 'neon-lime';
4198
+ if (poster && disposeTag === undefined) {
4199
+ disposeTag = (0, effort_tsx_1.installEffortTag)();
4200
+ }
4201
+ else if (!poster && disposeTag !== undefined) {
4202
+ disposeTag();
4203
+ disposeTag = undefined;
4204
+ }
4205
+ };
4206
+ sync();
4207
+ const unsubscribe = controller.subscribe(sync);
4208
+ return () => {
4209
+ unsubscribe();
4210
+ disposeTag?.();
4211
+ disposeTag = undefined;
4212
+ };
4213
+ }, 'dsh-ui-tweaks: effort tint');
3930
4214
  // Task notifications: watch every session on the list feed and raise
3931
4215
  // tab-title / system-notification / chime alerts when one finishes its turn
3932
4216
  // or starts blocking on the user. Registered only while the master toggle