@deepseek-harness-tui/dsh-tui 0.8.4 → 0.8.6

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.
Files changed (80) hide show
  1. package/lib/types/activityPrefs.d.ts +10 -0
  2. package/lib/types/activityPrefs.d.ts.map +1 -1
  3. package/lib/types/activityPrefs.js +17 -0
  4. package/lib/types/cc/figures.d.ts +7 -0
  5. package/lib/types/cc/figures.d.ts.map +1 -1
  6. package/lib/types/cc/figures.js +23 -0
  7. package/lib/types/cc/markdown.d.ts.map +1 -1
  8. package/lib/types/cc/markdown.js +47 -14
  9. package/lib/types/cc/syntaxTheme.d.ts +12 -0
  10. package/lib/types/cc/syntaxTheme.d.ts.map +1 -0
  11. package/lib/types/cc/syntaxTheme.js +76 -0
  12. package/lib/types/components/EffortChargeGlyph.d.ts +26 -0
  13. package/lib/types/components/EffortChargeGlyph.d.ts.map +1 -0
  14. package/lib/types/components/EffortChargeGlyph.js +70 -0
  15. package/lib/types/components/EffortInputBorder.d.ts +36 -0
  16. package/lib/types/components/EffortInputBorder.d.ts.map +1 -0
  17. package/lib/types/components/EffortInputBorder.js +73 -0
  18. package/lib/types/components/EffortTierBadge.d.ts +26 -0
  19. package/lib/types/components/EffortTierBadge.d.ts.map +1 -0
  20. package/lib/types/components/EffortTierBadge.js +97 -0
  21. package/lib/types/components/LoadedContextPanel.js +1 -1
  22. package/lib/types/components/MessageList.d.ts +4 -1
  23. package/lib/types/components/MessageList.d.ts.map +1 -1
  24. package/lib/types/components/MessageList.js +9 -11
  25. package/lib/types/components/PromptInput.d.ts.map +1 -1
  26. package/lib/types/components/PromptInput.js +8 -3
  27. package/lib/types/components/SplitDiffView.d.ts +4 -5
  28. package/lib/types/components/SplitDiffView.d.ts.map +1 -1
  29. package/lib/types/components/SplitDiffView.js +18 -65
  30. package/lib/types/components/StreamingMarkdown.d.ts.map +1 -1
  31. package/lib/types/components/StreamingMarkdown.js +10 -2
  32. package/lib/types/components/activityFrames.d.ts +7 -28
  33. package/lib/types/components/activityFrames.d.ts.map +1 -1
  34. package/lib/types/components/activityFrames.js +7 -94
  35. package/lib/types/components/design-system/ThemedBox.d.ts.map +1 -1
  36. package/lib/types/components/design-system/ThemedBox.js +4 -1
  37. package/lib/types/components/design-system/ThemedText.js +2 -2
  38. package/lib/types/components/messages/AssistantThinkingMessage.d.ts +12 -6
  39. package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
  40. package/lib/types/components/messages/AssistantThinkingMessage.js +25 -9
  41. package/lib/types/components/messages/AssistantToolUseMessage.d.ts +4 -1
  42. package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
  43. package/lib/types/components/messages/AssistantToolUseMessage.js +45 -17
  44. package/lib/types/components/messages/UserPromptMessage.d.ts +4 -6
  45. package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
  46. package/lib/types/components/messages/UserPromptMessage.js +5 -9
  47. package/lib/types/dsh-adapter/channel.d.ts +22 -0
  48. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  49. package/lib/types/dsh-adapter/channel.js +204 -19
  50. package/lib/types/dsh-adapter/index.d.ts +5 -0
  51. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  52. package/lib/types/dsh-adapter/index.js +19 -0
  53. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  54. package/lib/types/dsh-adapter/plugin.js +176 -4
  55. package/lib/types/dsh-adapter/settings-sections.d.ts +13 -0
  56. package/lib/types/dsh-adapter/settings-sections.d.ts.map +1 -1
  57. package/lib/types/dsh-adapter/settings-sections.js +31 -6
  58. package/lib/types/ink/ink.d.ts +9 -0
  59. package/lib/types/ink/ink.d.ts.map +1 -1
  60. package/lib/types/ink/ink.js +24 -1
  61. package/lib/types/screens/Chat.d.ts.map +1 -1
  62. package/lib/types/screens/Chat.js +35 -6
  63. package/lib/types/screens/Settings.d.ts.map +1 -1
  64. package/lib/types/screens/Settings.js +109 -50
  65. package/lib/types/screens/StatusLine.d.ts +8 -0
  66. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  67. package/lib/types/screens/StatusLine.js +62 -39
  68. package/lib/types/settings-sections.d.ts +1 -1
  69. package/lib/types/settings-sections.d.ts.map +1 -1
  70. package/lib/types/theme.d.ts +12 -0
  71. package/lib/types/theme.d.ts.map +1 -1
  72. package/lib/types/theme.js +51 -21
  73. package/lib/types/tips.js +2 -2
  74. package/lib/types/trajectory/effortIgnition.d.ts +66 -0
  75. package/lib/types/trajectory/effortIgnition.d.ts.map +1 -0
  76. package/lib/types/trajectory/effortIgnition.js +142 -0
  77. package/lib/types/tuiDisplayPrefs.d.ts +47 -0
  78. package/lib/types/tuiDisplayPrefs.d.ts.map +1 -0
  79. package/lib/types/tuiDisplayPrefs.js +60 -0
  80. package/package.json +6 -3
@@ -0,0 +1,97 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * EffortTierBadge — 输入行尾的档位字样徽标:三幕点焰的第二幕载体
4
+ * (与 EffortInputBorder 的双框扫光同一时间轴、同一触发),切到最高
5
+ * 思考强度档时光带行至中段,输入行居中浮现档名(当前档名大写,字母间距从
6
+ * 10 个空格减速聚拢到 1 个——连续位置逐字母取整保证帧帧有移动,
7
+ * 明亮蓝加粗,由暗渐亮),随后随图层整体渐隐让位——行数恒定,静
8
+ * 止时完全不渲染;输入行有文字时不显示(绝不遮挡内容)。
9
+ *
10
+ * 触发判定在渲染期做(props-变化-调整模式,与边框/充能组件同模
11
+ * 式);冷启动恢复偏好/单档表/无档位表/无共享时钟均不触发。时钟
12
+ * 复用 Ink core 共享时钟,仅动画窗口订阅(keepAlive),播完归零。
13
+ */
14
+ import { useContext, useEffect, useReducer, useState } from 'react';
15
+ import { Text } from '../ui.js';
16
+ import { ClockContext } from '../ink/components/ClockContext.js';
17
+ import { rgbString } from '../trajectory/motion.js';
18
+ import { IGNITION_TIMELINE, ignitionHues } from '../trajectory/effortIgnition.js';
19
+ /** 字母间距聚拢:从 10 个空格减速收敛到 1 个(ease-out,快收慢停)。 */
20
+ const GAP_START = 10;
21
+ const GAP_END = 1;
22
+ const CONVERGE_MS = 500;
23
+ export function EffortTierBadge({ effort, levels, onLight, columns, leadingColumns, }) {
24
+ const clock = useContext(ClockContext);
25
+ const [overlay, setOverlay] = useState(null);
26
+ const [prevEffort, setPrevEffort] = useState(effort);
27
+ const [, forceRender] = useReducer((tick) => tick + 1, 0);
28
+ // 渲染期触发(与边框/充能同模式):effort 变化的首帧就以新状态渲染。
29
+ if (effort !== prevEffort) {
30
+ setPrevEffort(effort);
31
+ if (clock !== null &&
32
+ effort !== undefined &&
33
+ levels !== undefined &&
34
+ levels.length > 1 &&
35
+ effort === levels[levels.length - 1]) {
36
+ setOverlay({ label: effort.toUpperCase(), startedAtMs: clock.now() });
37
+ }
38
+ }
39
+ const elapsedMs = overlay === null ? Infinity : Math.max(0, (clock?.now() ?? Date.now()) - overlay.startedAtMs);
40
+ useEffect(() => {
41
+ if (overlay === null || clock === null)
42
+ return;
43
+ return clock.subscribe(() => forceRender(), /* keepAlive */ true);
44
+ }, [overlay, clock]);
45
+ useEffect(() => {
46
+ if (overlay !== null && elapsedMs >= IGNITION_TIMELINE.fadeEndMs)
47
+ setOverlay(null);
48
+ }, [overlay, elapsedMs]);
49
+ if (overlay === null || elapsedMs < IGNITION_TIMELINE.labelStartMs)
50
+ return null;
51
+ const brighten = Math.min(1, (elapsedMs - IGNITION_TIMELINE.labelStartMs) / IGNITION_TIMELINE.labelBrightenMs);
52
+ const fade = elapsedMs < IGNITION_TIMELINE.fadeStartMs
53
+ ? 1
54
+ : Math.max(0, 1 - (elapsedMs - IGNITION_TIMELINE.fadeStartMs) / (IGNITION_TIMELINE.fadeEndMs - IGNITION_TIMELINE.fadeStartMs));
55
+ const alpha = brighten * fade;
56
+ if (alpha <= 0)
57
+ return null;
58
+ const band = onLight ? { r: 240, g: 240, b: 242 } : { r: 27, g: 30, b: 40 };
59
+ // 明亮蓝:accent 混白 35% 提亮(用户拍板的高亮观感)。
60
+ const hue = ignitionHues(onLight)[0];
61
+ const whiten = (x) => Math.round(x + (255 - x) * 0.35);
62
+ const bright = { r: whiten(hue.r), g: whiten(hue.g), b: whiten(hue.b) };
63
+ const mix = (x, y) => Math.round(x + (y - x) * alpha);
64
+ const color = rgbString({ r: mix(band.r, bright.r), g: mix(band.g, bright.g), b: mix(band.b, bright.b) });
65
+ // 字母间距聚拢(Codex 的 converge 语义):间距是连续浮点,字母位置
66
+ // 以**行中心为锚**对称收缩——pos_i = center + (i-(n-1)/2)·(1+gap),
67
+ // 左右字母各向中心移动一半(奇数档名的居中字母原位不动),两侧速
68
+ // 度天然均衡;每字母独立按连续位置取整落列,不同字母在不同帧跨
69
+ // 格,每帧至少一个在动(把间距整体取整会让 ease-out 慢末段上百毫
70
+ // 秒才跨一格,看起来就是卡顿)。曲线混入 15% 线性做末段保底速度。
71
+ const progress = Math.min(1, Math.max(0, (elapsedMs - IGNITION_TIMELINE.labelStartMs) / CONVERGE_MS));
72
+ // 跳变间隔均匀化:离散格子上「减速」若靠曲线导数趋零实现,末段会
73
+ // 出现几十帧不动一格的长停顿再突跳(卡感来源)。改为 90% 线性 +
74
+ // 10% easeOutQuad 的轻缓收尾——跳变间隔全程近似恒定(约 55ms/格),
75
+ // 仅末端轻微放慢,终端上的观感是均匀顺滑的聚拢。
76
+ const eased = 1 - progress;
77
+ const easedWithFloor = 0.9 * eased + 0.1 * (1 - progress * progress);
78
+ const gapF = GAP_END + (GAP_START - GAP_END) * easedWithFloor;
79
+ const letterCount = overlay.label.length;
80
+ // 锚点是**终端几何中心**(不是 ❯/光标之后可用区的中心——那会整体
81
+ // 偏右约 1.5 格;可用区起点在左,其"中点"不含左部占位)。左右字母
82
+ // 严格镜像——Math.round 对 .5 恒向上,正负方向舍入不对称会让跨格
83
+ // 时刻错开;左字母的落列由右字母的舍入结果镜像得出(2C − col),
84
+ // M/X 每次同帧反向同跳,全程对称于终端中心。
85
+ const C = Math.round((columns - 1) / 2) - leadingColumns;
86
+ let spaced = '';
87
+ let column = 0;
88
+ for (let i = 0; i < letterCount; i++) {
89
+ const off = (i - (letterCount - 1) / 2) * (1 + gapF);
90
+ const at = off >= 0
91
+ ? Math.round(C + off)
92
+ : 2 * C - Math.round(C - off);
93
+ spaced += ' '.repeat(Math.max(0, at - column)) + overlay.label[i];
94
+ column = at + 1;
95
+ }
96
+ return (_jsx(Text, { bold: true, color: color, children: spaced }));
97
+ }
@@ -26,5 +26,5 @@ export function LoadedContextPanel({ context, open, onToggle, }) {
26
26
  const summary = summarizeLoadedContext(context);
27
27
  if (summary === '')
28
28
  return null;
29
- return (_jsxs(Box, { flexDirection: "column", marginTop: 1, marginBottom: 1, children: [_jsx(Box, { paddingX: 1, backgroundColor: open ? 'userMessageBackground' : undefined, children: _jsxs(Text, { bold: open, wrap: "truncate", children: [open ? '▼' : '▶', " ", _jsxs(Text, { dimColor: true, children: ["\uFF08Ctrl+P", open ? t('context-panel-collapse') : t('context-panel-expand'), "\uFF09"] }), " ", t('context-loaded'), " \u00B7 ", summary] }) }), open && (_jsxs(Box, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [context.sections.length > 0 && (_jsx(Group, { title: t('context-panel-sections', { n: context.sections.length }), children: context.sections.map(section => (_jsx(Entry, { entry: section }, section.name))) })), context.files.length > 0 && (_jsx(Group, { title: t('context-panel-files', { n: context.files.length }), children: context.files.map(file => (_jsx(Text, { dimColor: true, children: file.displayPath }, file.displayPath))) })), context.contexts.length > 0 && (_jsx(Group, { title: t('context-panel-runtime', { n: context.contexts.length }), children: context.contexts.map(entry => (_jsx(Entry, { entry: entry }, entry.name))) })), context.skills.length > 0 && (_jsx(Group, { title: t('context-panel-skills', { n: context.skills.length }), children: context.skills.map(skill => (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, dimColor: true, children: skill.name }), _jsx(Text, { dimColor: true, wrap: "wrap", children: skill.description })] }, skill.name))) })), context.tools.length > 0 && (_jsx(Group, { title: t('context-panel-tools', { n: context.tools.length }), children: context.tools.map(tool => (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, dimColor: true, children: tool.name }), _jsx(Text, { dimColor: true, wrap: "wrap", children: truncateContextText(tool.description, 160) })] }, tool.name))) })), _jsx(Text, { dimColor: true, children: "\u00B7 /context" })] }))] }));
29
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, marginBottom: 1, children: [_jsx(Box, { paddingX: 1, backgroundColor: open ? 'userMessageBackgroundHover' : undefined, children: _jsxs(Text, { bold: open, wrap: "truncate", children: [open ? '▼' : '▶', " ", _jsxs(Text, { dimColor: true, children: ["\uFF08Ctrl+P", open ? t('context-panel-collapse') : t('context-panel-expand'), "\uFF09"] }), " ", t('context-loaded'), " \u00B7 ", summary] }) }), open && (_jsxs(Box, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [context.sections.length > 0 && (_jsx(Group, { title: t('context-panel-sections', { n: context.sections.length }), children: context.sections.map(section => (_jsx(Entry, { entry: section }, section.name))) })), context.files.length > 0 && (_jsx(Group, { title: t('context-panel-files', { n: context.files.length }), children: context.files.map(file => (_jsx(Text, { dimColor: true, children: file.displayPath }, file.displayPath))) })), context.contexts.length > 0 && (_jsx(Group, { title: t('context-panel-runtime', { n: context.contexts.length }), children: context.contexts.map(entry => (_jsx(Entry, { entry: entry }, entry.name))) })), context.skills.length > 0 && (_jsx(Group, { title: t('context-panel-skills', { n: context.skills.length }), children: context.skills.map(skill => (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, dimColor: true, children: skill.name }), _jsx(Text, { dimColor: true, wrap: "wrap", children: skill.description })] }, skill.name))) })), context.tools.length > 0 && (_jsx(Group, { title: t('context-panel-tools', { n: context.tools.length }), children: context.tools.map(tool => (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, dimColor: true, children: tool.name }), _jsx(Text, { dimColor: true, wrap: "wrap", children: truncateContextText(tool.description, 160) })] }, tool.name))) })), _jsx(Text, { dimColor: true, children: "\u00B7 /context" })] }))] }));
30
30
  }
@@ -2,7 +2,8 @@ import React from 'react';
2
2
  import { type ScrollBoxHandle } from '../ui.js';
3
3
  import type { ChatRow } from '../dsh-adapter/channel.js';
4
4
  import type { DOMElement } from '../ink/dom.js';
5
- export declare function MessageList({ rows, expanded, expandedRows, selectedId, onToggleRow, model, diffLayout, thinkingFold, showAll, onToggleAll, onLoadOlder, thinkingVisible, registerRowRef, scrollHandle, forceMountRowId, newSinceRowId, onUnseenCount, failureHintRowId, failureHint, }: {
5
+ import type { ToolBackground } from '../tuiDisplayPrefs.js';
6
+ export declare function MessageList({ rows, expanded, expandedRows, selectedId, onToggleRow, model, diffLayout, thinkingFold, toolBackground, showAll, onToggleAll, onLoadOlder, thinkingVisible, registerRowRef, scrollHandle, forceMountRowId, newSinceRowId, onUnseenCount, failureHintRowId, failureHint, }: {
6
7
  rows: readonly ChatRow[];
7
8
  expanded: boolean;
8
9
  expandedRows: ReadonlySet<number>;
@@ -13,6 +14,8 @@ export declare function MessageList({ rows, expanded, expandedRows, selectedId,
13
14
  diffLayout?: 'auto' | 'split' | 'unified';
14
15
  /** Thinking-block display mode from channel (`preview`/`full`). */
15
16
  thinkingFold?: 'preview' | 'full';
17
+ /** Tool-card background treatment from the live channel settings. */
18
+ toolBackground?: ToolBackground;
16
19
  showAll: boolean;
17
20
  onToggleAll: () => void;
18
21
  /** Restore folded-away older rows from the session log (CC-style "load
@@ -1 +1 @@
1
- {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/MessageList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,EAA8B,KAAK,eAAe,EAAE,MAAM,UAAU,CAAA;AAC3E,OAAO,KAAK,EAAE,OAAO,EAAyC,MAAM,2BAA2B,CAAA;AAC/F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAuC/C,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,WAAW,EACX,KAAK,EACL,UAAmB,EACnB,YAAwB,EACxB,OAAO,EACP,WAAW,EACX,WAAW,EACX,eAAsB,EACtB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,WAAW,GACZ,EAAE;IACD,IAAI,EAAE,SAAS,OAAO,EAAE,CAAA;IACxB,QAAQ,EAAE,OAAO,CAAA;IACjB,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IACjC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACpC,KAAK,EAAE,MAAM,CAAA;IACb,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;IACzC,mEAAmE;IACnE,YAAY,CAAC,EAAE,SAAS,GAAG,MAAM,CAAA;IACjC,OAAO,EAAE,OAAO,CAAA;IAChB,WAAW,EAAE,MAAM,IAAI,CAAA;IACvB;2EACuE;IACvE,WAAW,CAAC,EAAE,MAAM,IAAI,CAAA;IACxB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,8EAA8E;IAC9E,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,GAAG,IAAI,KAAK,IAAI,CAAA;IAC/D,oDAAoD;IACpD,YAAY,CAAC,EAAE,eAAe,GAAG,IAAI,CAAA;IACrC,4EAA4E;IAC5E,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B;8EAC0E;IAC1E,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,0EAA0E;IAC1E,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,qBA6VA;AAmRD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,MAAM,EACN,GAAG,GACJ,EAAE;IACD,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,GAAG,EAAE,MAAM,CAAA;CACZ,GAAG,KAAK,CAAC,SAAS,CAMlB"}
1
+ {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/MessageList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,EAA8B,KAAK,eAAe,EAAE,MAAM,UAAU,CAAA;AAC3E,OAAO,KAAK,EAAE,OAAO,EAAyC,MAAM,2BAA2B,CAAA;AAC/F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAa/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AA2B3D,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,WAAW,EACX,KAAK,EACL,UAAmB,EACnB,YAAwB,EACxB,cAAuB,EACvB,OAAO,EACP,WAAW,EACX,WAAW,EACX,eAAsB,EACtB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,WAAW,GACZ,EAAE;IACD,IAAI,EAAE,SAAS,OAAO,EAAE,CAAA;IACxB,QAAQ,EAAE,OAAO,CAAA;IACjB,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IACjC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACpC,KAAK,EAAE,MAAM,CAAA;IACb,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;IACzC,mEAAmE;IACnE,YAAY,CAAC,EAAE,SAAS,GAAG,MAAM,CAAA;IACjC,qEAAqE;IACrE,cAAc,CAAC,EAAE,cAAc,CAAA;IAC/B,OAAO,EAAE,OAAO,CAAA;IAChB,WAAW,EAAE,MAAM,IAAI,CAAA;IACvB;2EACuE;IACvE,WAAW,CAAC,EAAE,MAAM,IAAI,CAAA;IACxB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,8EAA8E;IAC9E,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,GAAG,IAAI,KAAK,IAAI,CAAA;IAC/D,oDAAoD;IACpD,YAAY,CAAC,EAAE,eAAe,GAAG,IAAI,CAAA;IACrC,4EAA4E;IAC5E,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B;8EAC0E;IAC1E,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,0EAA0E;IAC1E,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,qBA8VA;AAsRD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,MAAM,EACN,GAAG,GACJ,EAAE;IACD,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,GAAG,EAAE,MAAM,CAAA;CACZ,GAAG,KAAK,CAAC,SAAS,CAMlB"}
@@ -17,7 +17,7 @@ import { truncateToWidth } from '../ink/truncateToWidth.js';
17
17
  /**
18
18
  * Transcript rows rendered in the Claude Code visual language: user prompts
19
19
  * on a grey bubble with a `❯` pointer, assistant text with a `●` bullet and
20
- * markdown, thinking folded to `∴ Thinking (ctrl+o to expand)`, tool calls as
20
+ * markdown, thinking folded to `⚓ Thinking (ctrl+o to expand)`, tool calls as
21
21
  * status-dot cards. `expanded` (Ctrl+O) shows full reasoning + full tool
22
22
  * args/results; `expandedRows` (message-selection mode, Enter) expands single
23
23
  * rows; `selectedId` highlights the selected row.
@@ -37,7 +37,7 @@ const DEFAULT_ROW_HEIGHT = 2;
37
37
  /** Cold-start estimate of the header block above the rows; corrected by the
38
38
  * first layout measurement. */
39
39
  const DEFAULT_HEADER_LINES = 14;
40
- export function MessageList({ rows, expanded, expandedRows, selectedId, onToggleRow, model, diffLayout = 'auto', thinkingFold = 'preview', showAll, onToggleAll, onLoadOlder, thinkingVisible = true, registerRowRef, scrollHandle, forceMountRowId, newSinceRowId, onUnseenCount, failureHintRowId, failureHint, }) {
40
+ export function MessageList({ rows, expanded, expandedRows, selectedId, onToggleRow, model, diffLayout = 'auto', thinkingFold = 'preview', toolBackground = 'none', showAll, onToggleAll, onLoadOlder, thinkingVisible = true, registerRowRef, scrollHandle, forceMountRowId, newSinceRowId, onUnseenCount, failureHintRowId, failureHint, }) {
41
41
  const hiddenCount = rows.length - MAX_RENDERED_ROWS;
42
42
  // The thinking filter runs BEFORE virtualization so window indices line up.
43
43
  const visibleRows = (showAll || hiddenCount <= 0
@@ -54,14 +54,12 @@ export function MessageList({ rows, expanded, expandedRows, selectedId, onToggle
54
54
  prev = row.kind;
55
55
  }
56
56
  }
57
- // CC's expanded rows keep a persistent hover-grey background (VirtualItem:
58
- // `expanded ? userMessageBackgroundHover : undefined`).
57
+ // Selection keeps its highlight; expanded rows render with no fill (the
58
+ // diff line tints inside cards are the only backgrounds in the transcript).
59
59
  const rowBackground = (rowId) => {
60
60
  const isSelected = selectedId === rowId;
61
61
  if (isSelected)
62
62
  return 'messageActionsBackground';
63
- if (expandedRows.has(rowId))
64
- return 'userMessageBackgroundHover';
65
63
  return undefined;
66
64
  };
67
65
  // --- layout virtualization ---------------------------------------------
@@ -334,10 +332,10 @@ export function MessageList({ rows, expanded, expandedRows, selectedId, onToggle
334
332
  // spacing; only the very first row of the whole list has none.
335
333
  const addMargin = margins.get(row.id) === true;
336
334
  const tool = row.tool;
337
- return (_jsx(MemoRow, { rowId: row.id, kind: row.kind, text: row.text, executionTarget: row.executionTarget, streaming: row.streaming === true, durationMs: row.durationMs, time: row.time, addMargin: addMargin, isSelected: selectedId === row.id, isExpanded: expandedRows.has(row.id), expanded: expanded, model: model, diffLayout: diffLayout, thinkingFold: thinkingFold, background: rowBackground(row.id), toolCallId: tool?.callId, toolName: tool?.name, toolArgsText: tool?.argsText, toolArgsFull: tool?.argsFull, toolStatus: tool?.status, toolResultText: tool?.resultText, toolResultFull: tool?.resultFull, toolErrorText: tool?.errorText, toolFootnote: failureHintRowId === row.id ? failureHint : undefined, toolCallView: tool?.callView, toolResultView: tool?.resultView, toolStartedAt: tool?.startedAt, toolDurationMs: tool?.durationMs, nowSec: tool?.status === 'running' ? nowSec : undefined, onToggleRow: onToggleRow, setRowRef: setRowRef }, row.id));
335
+ return (_jsx(MemoRow, { rowId: row.id, kind: row.kind, text: row.text, executionTarget: row.executionTarget, streaming: row.streaming === true, durationMs: row.durationMs, time: row.time, addMargin: addMargin, isSelected: selectedId === row.id, isExpanded: expandedRows.has(row.id), expanded: expanded, model: model, diffLayout: diffLayout, thinkingFold: thinkingFold, toolBackground: toolBackground, background: rowBackground(row.id), toolCallId: tool?.callId, toolName: tool?.name, toolArgsText: tool?.argsText, toolArgsFull: tool?.argsFull, toolStatus: tool?.status, toolResultText: tool?.resultText, toolResultFull: tool?.resultFull, toolErrorText: tool?.errorText, toolFootnote: failureHintRowId === row.id ? failureHint : undefined, toolCallView: tool?.callView, toolResultView: tool?.resultView, toolStartedAt: tool?.startedAt, toolDurationMs: tool?.durationMs, nowSec: tool?.status === 'running' ? nowSec : undefined, onToggleRow: onToggleRow, setRowRef: setRowRef }, row.id));
338
336
  }), bottomPad > 0 && _jsx(Box, { height: bottomPad, flexShrink: 0 })] }));
339
337
  }
340
- function TranscriptRow({ rowId, kind, text, executionTarget, streaming, durationMs, time, addMargin, isSelected, isExpanded, expanded, model, diffLayout, thinkingFold, background, toolCallId, toolName, toolArgsText, toolArgsFull, toolStatus, toolResultText, toolResultFull, toolErrorText, toolFootnote, toolCallView, toolResultView, toolStartedAt, toolDurationMs, onToggleRow, setRowRef, }) {
338
+ function TranscriptRow({ rowId, kind, text, executionTarget, streaming, durationMs, time, addMargin, isSelected, isExpanded, expanded, model, diffLayout, thinkingFold, toolBackground, background, toolCallId, toolName, toolArgsText, toolArgsFull, toolStatus, toolResultText, toolResultFull, toolErrorText, toolFootnote, toolCallView, toolResultView, toolStartedAt, toolDurationMs, onToggleRow, setRowRef, }) {
341
339
  const ref = React.useCallback((el) => {
342
340
  setRowRef(rowId, el);
343
341
  }, [setRowRef, rowId]);
@@ -346,11 +344,11 @@ function TranscriptRow({ rowId, kind, text, executionTarget, streaming, duration
346
344
  }, [onToggleRow, rowId]);
347
345
  switch (kind) {
348
346
  case 'user':
349
- return (_jsx(Box, { flexDirection: "column", ref: ref, children: _jsx(UserPromptMessage, { text: text, addMargin: addMargin, isSelected: isSelected, isExpanded: isExpanded, onClick: onClick }) }));
347
+ return (_jsx(Box, { flexDirection: "column", ref: ref, children: _jsx(UserPromptMessage, { text: text, addMargin: addMargin, isSelected: isSelected, onClick: onClick }) }));
350
348
  case 'assistant':
351
349
  return streaming ? (_jsxs(Box, { alignItems: "flex-start", flexDirection: "row", marginTop: addMargin ? 1 : 0, width: "100%", backgroundColor: background, children: [_jsx(Box, { minWidth: 2, children: _jsx(Text, { color: "text", children: "\u25CF" }) }), _jsx(Box, { flexDirection: "column", children: _jsx(StreamingMarkdown, { children: stripNarration(text) }) })] })) : (_jsxs(Box, { width: "100%", flexDirection: "column", backgroundColor: background, ref: ref, children: [expanded && (_jsx(Box, { flexDirection: "row", justifyContent: "flex-end", gap: 1, marginTop: 1, children: _jsx(MessageMetadata, { timestamp: time, model: model }) })), _jsx(AssistantTextMessage, { text: stripNarration(text), addMargin: addMargin, isSelected: isSelected, isExpanded: isExpanded, onClick: onClick })] }));
352
350
  case 'reasoning':
353
- return (_jsx(Box, { flexDirection: "column", ref: ref, children: _jsx(AssistantThinkingMessage, { thinking: text, addMargin: addMargin, preview: streaming &&
351
+ return (_jsx(Box, { flexDirection: "column", ref: ref, children: _jsx(AssistantThinkingMessage, { thinking: text, addMargin: addMargin, streaming: streaming, preview: streaming &&
354
352
  thinkingFold === 'preview' &&
355
353
  !expanded &&
356
354
  !isExpanded,
@@ -382,7 +380,7 @@ function TranscriptRow({ rowId, kind, text, executionTarget, streaming, duration
382
380
  startedAt: toolStartedAt,
383
381
  durationMs: toolDurationMs,
384
382
  };
385
- return (_jsx(Box, { flexDirection: "column", ref: ref, children: _jsx(AssistantToolUseMessage, { tool: tool, addMargin: addMargin, verbose: isExpanded || expanded, isSelected: isSelected, isExpanded: isExpanded, footnote: toolFootnote, diffLayout: diffLayout }) }));
383
+ return (_jsx(Box, { flexDirection: "column", ref: ref, children: _jsx(AssistantToolUseMessage, { tool: tool, addMargin: addMargin, verbose: isExpanded || expanded, isSelected: isSelected, isExpanded: isExpanded, footnote: toolFootnote, diffLayout: diffLayout, toolBackground: toolBackground }) }));
386
384
  }
387
385
  case 'notice':
388
386
  return (_jsx(Box, { marginTop: 1, ref: ref, children: _jsx(Divider, { title: ` ${text} ` }) }));
@@ -1 +1 @@
1
- {"version":3,"file":"PromptInput.d.ts","sourceRoot":"","sources":["../../../src/components/PromptInput.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AASzB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAkDxD;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,IAAI,OAAO,CAAA;IAClB,KAAK,IAAI,IAAI,CAAA;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAA;IAChB,0EAA0E;IAC1E,QAAQ,EAAE,OAAO,CAAA;IACjB,YAAY,IAAI,IAAI,CAAA;IACpB;;;OAGG;IACH,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAA;IACrD,6EAA6E;IAC7E,eAAe,EAAE,OAAO,CAAA;IACxB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,cAAc,CAAC,IAAI,IAAI,CAAA;IACvB,8EAA8E;IAC9E,eAAe,CAAC,IAAI,IAAI,CAAA;IACxB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;CACzD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,cAAc,EACd,eAAe,EACf,aAAa,GACd,EAAE,gBAAgB,qBA25BlB"}
1
+ {"version":3,"file":"PromptInput.d.ts","sourceRoot":"","sources":["../../../src/components/PromptInput.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAazB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAkDxD;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,IAAI,OAAO,CAAA;IAClB,KAAK,IAAI,IAAI,CAAA;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAA;IAChB,0EAA0E;IAC1E,QAAQ,EAAE,OAAO,CAAA;IACjB,YAAY,IAAI,IAAI,CAAA;IACpB;;;OAGG;IACH,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAA;IACrD,6EAA6E;IAC7E,eAAe,EAAE,OAAO,CAAA;IACxB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,cAAc,CAAC,IAAI,IAAI,CAAA;IACvB,8EAA8E;IAC9E,eAAe,CAAC,IAAI,IAAI,CAAA;IACxB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;CACzD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,cAAc,EACd,eAAe,EACf,aAAa,GACd,EAAE,gBAAgB,qBA46BlB"}
@@ -1,9 +1,13 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import React from 'react';
3
3
  import { readFile, unlink } from 'node:fs/promises';
4
4
  import { basename } from 'node:path';
5
5
  import { t } from '../i18n.js';
6
- import { Box, Text, useInput, useTerminalSize } from '../ui.js';
6
+ import { Box, Text, useInput, useTerminalSize, useTheme } from '../ui.js';
7
+ import { EffortChargeGlyph } from './EffortChargeGlyph.js';
8
+ import { EffortInputBorder } from './EffortInputBorder.js';
9
+ import { EffortTierBadge } from './EffortTierBadge.js';
10
+ import { isLightThemeActive } from '../theme.js';
7
11
  import { useDeclaredCursor } from '../ink/hooks/use-declared-cursor.js';
8
12
  import { stringWidth } from '../ink/stringWidth.js';
9
13
  import { formatClipboardInsert, readClipboard } from '../utils/clipboard.js';
@@ -89,6 +93,7 @@ const MAX_VISIBLE_LINES = 5;
89
93
  * aborts the turn and sends the current input immediately.
90
94
  */
91
95
  export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, selectionActive, fillText, onFillConsumed, onRewindRequest, controllerRef, }) {
96
+ const [themeName] = useTheme();
92
97
  const [value, setValue] = React.useState('');
93
98
  const [cursor, setCursor] = React.useState(0);
94
99
  const valueRef = React.useRef(value);
@@ -882,7 +887,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
882
887
  .filter(item => item.placement === 'steer')
883
888
  .map(item => (_jsxs(Text, { dimColor: true, wrap: "truncate", children: [' ', "\u21B3 ", item.text] }, item.id)))] })), channel.pending.some(item => item.placement === 'followup') && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: ["\u23F3 ", t('input-pending-queue-label')] }), channel.pending
884
889
  .filter(item => item.placement === 'followup')
885
- .map(item => (_jsxs(Text, { dimColor: true, wrap: "truncate", children: [' ', "\u21B3 ", item.text] }, item.id)))] })), _jsxs(Text, { dimColor: true, children: ["Alt+\u2191 ", t('input-pending-actions-hint')] })] })), fileOverlayOpen && (_jsx(Box, { paddingLeft: 2, paddingBottom: 1, children: _jsx(FileSuggestions, { files: fileMatches, selectedIndex: fileSelected, columns: columns }) })), overlayOpen && (_jsx(Box, { paddingLeft: 2, paddingBottom: 1, children: _jsx(CommandSuggestions, { commands: suggestions, selectedIndex: selectedCommand, columns: columns }) }))] })), lastNotification && (_jsx(Box, { position: "absolute", marginTop: -1, height: 1, width: "100%", paddingLeft: 2, paddingRight: 1, flexDirection: "column", justifyContent: "flex-end", overflow: "hidden", children: _jsx(Box, { justifyContent: "flex-end", children: _jsx(Text, { color: lastNotification.color, dimColor: !lastNotification.color, wrap: "truncate", children: lastNotification.text }) }) })), _jsx(Box, { flexDirection: "column", alignItems: "flex-start", justifyContent: "flex-start", borderColor: channel.mode.plan === true ? 'planMode' : 'promptBorder', borderStyle: "round", borderLeft: false, borderRight: false, borderBottom: true, width: "100%", children: _jsxs(Box, { flexDirection: "row", alignItems: "flex-start", width: "100%", children: [_jsx(Text, { dimColor: channel.working, children: "\u276F " }), _jsx(Box, { ref: valueBoxRef, flexGrow: 1, flexShrink: 1, children: value.length === 0 ? (_jsx(Text, { inverse: true, children: " " })) : (_jsx(Box, { flexDirection: "column", children: rendered })) })] }) })] }));
890
+ .map(item => (_jsxs(Text, { dimColor: true, wrap: "truncate", children: [' ', "\u21B3 ", item.text] }, item.id)))] })), _jsxs(Text, { dimColor: true, children: ["Alt+\u2191 ", t('input-pending-actions-hint')] })] })), fileOverlayOpen && (_jsx(Box, { paddingLeft: 2, paddingBottom: 1, children: _jsx(FileSuggestions, { files: fileMatches, selectedIndex: fileSelected, columns: columns }) })), overlayOpen && (_jsx(Box, { paddingLeft: 2, paddingBottom: 1, children: _jsx(CommandSuggestions, { commands: suggestions, selectedIndex: selectedCommand, columns: columns }) }))] })), lastNotification && (_jsx(Box, { position: "absolute", marginTop: -1, height: 1, width: "100%", paddingLeft: 2, paddingRight: 1, flexDirection: "column", justifyContent: "flex-end", overflow: "hidden", children: _jsx(Box, { justifyContent: "flex-end", children: _jsx(Text, { color: lastNotification.color, dimColor: !lastNotification.color, wrap: "truncate", children: lastNotification.text }) }) })), _jsx(EffortInputBorder, { effort: channel.reasoningEffort, levels: channel.effortLevels, columns: columns, onLight: isLightThemeActive(themeName), idleColor: channel.mode.plan === true ? 'planMode' : 'promptBorder', children: _jsxs(Box, { flexDirection: "row", alignItems: "flex-start", width: "100%", children: [_jsx(EffortChargeGlyph, { effort: channel.reasoningEffort, levels: channel.effortLevels, working: channel.working }), _jsx(Box, { ref: valueBoxRef, flexGrow: 1, flexShrink: 1, children: value.length === 0 ? (_jsxs(_Fragment, { children: [_jsx(Text, { inverse: true, children: " " }), _jsx(EffortTierBadge, { effort: channel.reasoningEffort, levels: channel.effortLevels, onLight: isLightThemeActive(themeName), columns: columns, leadingColumns: 3 })] })) : (_jsx(Box, { flexDirection: "column", children: rendered })) })] }) })] }));
886
891
  }
887
892
  /**
888
893
  * Hard-wrap text into visual rows of at most `width` columns (CJK-aware via
@@ -1,10 +1,8 @@
1
1
  import React from 'react';
2
2
  import type { ToolFileDiff } from '../dsh-adapter/channel.js';
3
3
  import { type CliHighlight } from '../cc/cliHighlight.js';
4
- /** chalk style for one raw theme value. Accepts every form the theme
5
- * loader documents: #rgb, #rrggbb, #rrggbbaa (alpha stripped), rgb() with
6
- * or without spaces, ansi256(n), ansi:name (issue #250, P2-5). */
7
- export declare function chalkFromToken(token: string): (text: string) => string;
4
+ export { chalkFromToken } from '../cc/syntaxTheme.js';
5
+ import type { ToolBackground } from '../tuiDisplayPrefs.js';
8
6
  /** Parse a cli-highlight ANSI string into colored runs, preserving open
9
7
  * spans across embedded newlines (multi-line strings/comments). Handles
10
8
  * truecolor 38;2, 256-color 38;5 (tmux / FORCE_COLOR=2, issue #250 P1-1),
@@ -17,12 +15,13 @@ export declare function highlightLines(text: string, language: string | undefine
17
15
  text: string;
18
16
  color?: string;
19
17
  }[][] | undefined;
20
- export declare function SplitDiffView({ diffs, width, maxRows, verbose, }: {
18
+ export declare function SplitDiffView({ diffs, width, maxRows, verbose, toolBackground, }: {
21
19
  readonly diffs: readonly ToolFileDiff[];
22
20
  /** Content width available to the whole two-pane block (divider included). */
23
21
  readonly width: number;
24
22
  /** Row budget when not verbose; overflow folds into one hint row. */
25
23
  readonly maxRows: number;
26
24
  readonly verbose: boolean;
25
+ readonly toolBackground?: ToolBackground;
27
26
  }): React.ReactNode;
28
27
  //# sourceMappingURL=SplitDiffView.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SplitDiffView.d.ts","sourceRoot":"","sources":["../../../src/components/SplitDiffView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAKzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAE7D,OAAO,EAA0B,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAwFjF;;mEAEmE;AACnE,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAmBtE;AAUD;;;oCAGoC;AACpC,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,CAuB9E;AAuBD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,EAAE,EAAE,YAAY,GAAG,IAAI,EACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,SAAS,EAC7D,QAAQ,EAAE,MAAM,GACf,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,EAAE,GAAG,SAAS,CAe3D;AA6ND,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,KAAK,EACL,OAAO,EACP,OAAO,GACR,EAAE;IACD,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAA;IACvC,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAC1B,GAAG,KAAK,CAAC,SAAS,CAiGlB"}
1
+ {"version":3,"file":"SplitDiffView.d.ts","sourceRoot":"","sources":["../../../src/components/SplitDiffView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAKzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAE7D,OAAO,EAA0B,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAIjF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAGrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAgE3D;;;oCAGoC;AACpC,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,CAuB9E;AAuBD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,EAAE,EAAE,YAAY,GAAG,IAAI,EACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,SAAS,EAC7D,QAAQ,EAAE,MAAM,GACf,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,EAAE,GAAG,SAAS,CAe3D;AAsOD,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,KAAK,EACL,OAAO,EACP,OAAO,EACP,cAAuB,GACxB,EAAE;IACD,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAA;IACvC,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAA;CACzC,GAAG,KAAK,CAAC,SAAS,CAiGlB"}
@@ -2,73 +2,19 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React from 'react';
3
3
  import { Box, Text } from '../ui.js';
4
4
  import * as JsDiff from 'diff';
5
- import chalk from 'chalk';
6
5
  import { extname } from 'node:path';
7
6
  import { getCliHighlightPromise } from '../cc/cliHighlight.js';
7
+ import { SYNTAX_CLASS_TO_TOKEN, chalkFromToken } from '../cc/syntaxTheme.js';
8
+ // Backward-compatible re-export: repro scripts import chalkFromToken from
9
+ // this module's old home.
10
+ export { chalkFromToken } from '../cc/syntaxTheme.js';
8
11
  import { getTheme } from '../theme.js';
9
12
  import { useTheme } from './design-system/ThemeProvider.js';
10
13
  /** Replace tabs so width math and alignment hold (pi convention). */
11
14
  const expandTabs = (text) => text.replaceAll('\t', ' ');
12
15
  // --- syntax highlighting ----------------------------------------------------
13
- /** highlight.js token classes → theme syntax tokens. `default` catches
14
- * every unmapped class so cli-highlight's own yellow never leaks through
15
- * (issue #250, P2-6). */
16
- const SYNTAX_CLASS_TO_TOKEN = {
17
- keyword: 'syntaxKeyword',
18
- built_in: 'syntaxKeyword',
19
- literal: 'syntaxKeyword',
20
- string: 'syntaxString',
21
- subst: 'syntaxString',
22
- quote: 'syntaxString',
23
- comment: 'syntaxComment',
24
- number: 'syntaxNumber',
25
- title: 'syntaxFunction',
26
- 'title.function_': 'syntaxFunction',
27
- function: 'syntaxFunction',
28
- 'title.class_': 'syntaxType',
29
- type: 'syntaxType',
30
- class: 'syntaxType',
31
- tag: 'syntaxType',
32
- name: 'syntaxType',
33
- attr: 'syntaxVariable',
34
- attribute: 'syntaxVariable',
35
- variable: 'syntaxVariable',
36
- 'template-variable': 'syntaxVariable',
37
- params: 'syntaxVariable',
38
- operator: 'syntaxOperator',
39
- punctuation: 'syntaxPunctuation',
40
- meta: 'syntaxPunctuation',
41
- symbol: 'syntaxConstant',
42
- regexp: 'syntaxConstant',
43
- default: 'syntaxVariable',
44
- };
45
- /** chalk style for one raw theme value. Accepts every form the theme
46
- * loader documents: #rgb, #rrggbb, #rrggbbaa (alpha stripped), rgb() with
47
- * or without spaces, ansi256(n), ansi:name (issue #250, P2-5). */
48
- export function chalkFromToken(token) {
49
- let match = /^#([0-9a-fA-F]{3})$/.exec(token);
50
- if (match !== null) {
51
- const [r, g, b] = match[1].split('').map(c => parseInt(c + c, 16));
52
- return chalk.rgb(r, g, b);
53
- }
54
- match = /^#([0-9a-fA-F]{6})(?:[0-9a-fA-F]{2})?$/.exec(token);
55
- if (match !== null)
56
- return chalk.hex(`#${match[1]}`);
57
- match = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/.exec(token);
58
- if (match !== null)
59
- return chalk.rgb(Number(match[1]), Number(match[2]), Number(match[3]));
60
- match = /^ansi256\((\d+)\)$/.exec(token);
61
- if (match !== null)
62
- return chalk.ansi256(Number(match[1]));
63
- match = /^ansi:(\w+)$/.exec(token);
64
- if (match !== null) {
65
- const name = match[1] === 'blackBright' ? 'gray' : match[1];
66
- const style = chalk[name];
67
- if (style !== undefined)
68
- return style;
69
- }
70
- return (text) => text;
71
- }
16
+ // The hljs-class → theme-token map and chalkFromToken live in
17
+ // ../cc/syntaxTheme.ts (shared with the markdown code-block renderer).
72
18
  /** ANSI 16-color SGR codes → rgb() strings (the dark-ansi palette path). */
73
19
  const ANSI16 = {
74
20
  30: 'rgb(0,0,0)', 31: 'rgb(128,0,0)', 32: 'rgb(0,128,0)', 33: 'rgb(128,128,0)',
@@ -327,9 +273,16 @@ function alignFileDiff(fileIndex, oldText, newText) {
327
273
  return rows;
328
274
  }
329
275
  // --- rendering --------------------------------------------------------------
330
- function PaneLine({ side, kind, width, tone, padLeft = false, }) {
276
+ function PaneLine({ side, kind, width, tone, toolBackground, padLeft = false, }) {
277
+ const ordinaryBackground = toolBackground === 'subtle'
278
+ ? 'toolCardBackgroundDim'
279
+ : toolBackground === 'strong'
280
+ ? 'toolCardBackground'
281
+ : undefined;
282
+ // Additions/removals keep their semantic tint; unchanged and empty panes
283
+ // inherit the configured ordinary tool-card surface.
331
284
  const backgroundColor = kind === 'context'
332
- ? 'toolCardBackground'
285
+ ? ordinaryBackground
333
286
  : tone === 'old'
334
287
  ? 'diffRemovedDimmed'
335
288
  : 'diffAddedDimmed';
@@ -340,7 +293,7 @@ function PaneLine({ side, kind, width, tone, padLeft = false, }) {
340
293
  const prefix = padLeft ? ` ${marker}` : marker;
341
294
  return (_jsxs(Box, { width: width, flexShrink: 0, backgroundColor: backgroundColor, children: [_jsx(Text, { dimColor: true, backgroundColor: backgroundColor, children: `${prefix} ` }), side === undefined ? (_jsx(Text, { backgroundColor: backgroundColor, children: " " })) : (_jsx(Text, { wrap: "truncate", backgroundColor: backgroundColor, children: side.segments.map((segment, index) => segment.changed ? (_jsx(Text, { color: wordColor, bold: true, backgroundColor: backgroundColor, children: segment.text }, index)) : (_jsx(Text, { color: segment.color, backgroundColor: backgroundColor, children: segment.text }, index))) }))] }));
342
295
  }
343
- export function SplitDiffView({ diffs, width, maxRows, verbose, }) {
296
+ export function SplitDiffView({ diffs, width, maxRows, verbose, toolBackground = 'none', }) {
344
297
  const [hl, setHl] = React.useState(null);
345
298
  React.useEffect(() => {
346
299
  let mounted = true;
@@ -399,7 +352,7 @@ export function SplitDiffView({ diffs, width, maxRows, verbose, }) {
399
352
  next: highlightLines(expandTabs(diff.newText), language, hl, chTheme, themeSig),
400
353
  };
401
354
  });
402
- return (_jsxs(Box, { flexDirection: "column", width: paneWidth * 2 + 1, backgroundColor: "toolCardBackgroundDim", children: [visible.map((row, index) => {
355
+ return (_jsxs(Box, { flexDirection: "column", width: paneWidth * 2 + 1, children: [visible.map((row, index) => {
403
356
  if ('separator' in row) {
404
357
  return (_jsx(Box, { width: paneWidth * 2 + 1, children: _jsx(Text, { dimColor: true, wrap: "truncate", children: row.separator === '⋯' ? '⋯' : ` ${row.separator}` }) }, index));
405
358
  }
@@ -412,6 +365,6 @@ export function SplitDiffView({ diffs, width, maxRows, verbose, }) {
412
365
  const newSide = row.newWords === undefined
413
366
  ? undefined
414
367
  : { segments: newRuns !== undefined ? mergeRuns(newRuns, row.newWords) : row.newWords };
415
- return (_jsxs(Box, { flexDirection: "row", children: [_jsx(PaneLine, { side: oldSide, kind: row.kind === 'add' ? 'context' : row.kind, tone: "old", width: paneWidth }), _jsx(Box, { width: 1, flexShrink: 0, backgroundColor: "toolCardBackgroundDim", children: _jsx(Text, { dimColor: true, children: "\u2502" }) }), _jsx(PaneLine, { side: newSide, kind: row.kind === 'del' ? 'context' : row.kind, tone: "new", width: paneWidth, padLeft: true })] }, index));
368
+ return (_jsxs(Box, { flexDirection: "row", children: [_jsx(PaneLine, { side: oldSide, kind: row.kind === 'add' ? 'context' : row.kind, tone: "old", width: paneWidth, toolBackground: toolBackground }), _jsx(Box, { width: 1, flexShrink: 0, children: _jsx(Text, { dimColor: true, children: "\u2502" }) }), _jsx(PaneLine, { side: newSide, kind: row.kind === 'del' ? 'context' : row.kind, tone: "new", width: paneWidth, toolBackground: toolBackground, padLeft: true })] }, index));
416
369
  }), hidden > 0 && (_jsx(Text, { dimColor: true, children: `… +${hidden} lines (ctrl+o to expand)` }))] }));
417
370
  }
@@ -1 +1 @@
1
- {"version":3,"file":"StreamingMarkdown.d.ts","sourceRoot":"","sources":["../../../src/components/StreamingMarkdown.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAmDzB,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,QAAgB,GACjB,EAAE;IACD,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,GAAG,KAAK,CAAC,SAAS,CA0ClB"}
1
+ {"version":3,"file":"StreamingMarkdown.d.ts","sourceRoot":"","sources":["../../../src/components/StreamingMarkdown.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAmDzB,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,QAAgB,GACjB,EAAE;IACD,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,GAAG,KAAK,CAAC,SAAS,CAmDlB"}
@@ -76,6 +76,14 @@ export function StreamingMarkdown({ children, dimColor = false, }) {
76
76
  prefixRef.current = stripped.substring(0, boundary + advance);
77
77
  }
78
78
  const stablePrefix = prefixRef.current;
79
- const unstableSuffix = clipSuffixTail(stripped.substring(stablePrefix.length), cutRef);
80
- return (_jsxs(Box, { flexDirection: "column", gap: 1, children: [stablePrefix && _jsx(Markdown, { dimColor: dimColor, children: stablePrefix }), unstableSuffix && _jsx(Markdown, { dimColor: dimColor, cacheTokens: false, children: unstableSuffix })] }));
79
+ const suffixSource = stripped.substring(stablePrefix.length);
80
+ const unstableSuffix = clipSuffixTail(suffixSource, cutRef);
81
+ // A lexer boundary must be strictly outside the stable prefix. If marked
82
+ // reports a raw span that ends at the current cursor (possible around an
83
+ // unfinished fence/table while deltas arrive), rendering both branches can
84
+ // paint the same tail twice. Keep the suffix authoritative and never render
85
+ // an overlapping empty/duplicate boundary.
86
+ const hasDistinctSuffix = unstableSuffix !== '' &&
87
+ (stablePrefix === '' || !unstableSuffix.startsWith(stablePrefix));
88
+ return (_jsxs(Box, { flexDirection: "column", gap: 1, children: [stablePrefix && _jsx(Markdown, { dimColor: dimColor, children: stablePrefix }), hasDistinctSuffix && _jsx(Markdown, { dimColor: dimColor, cacheTokens: false, children: unstableSuffix })] }));
81
89
  }
@@ -1,31 +1,10 @@
1
1
  /**
2
- * Working-activity indicator presets, ported from the pi
3
- * working-activity extension (`FRAME_PRESETS`). The TUI renders the current
4
- * frame next to the live working line, tinted by the activity phase.
5
- * `\uFE0E` forces text rendering so Windows never paints the glyphs as
6
- * color emoji (the green-block problem).
2
+ * Working-activity indicator presets — thin re-export of the single source
3
+ * of truth in the `dsh-working-activity` package (`src/frames.ts`). The TUI
4
+ * keeps this shim so existing importers (`/activity` picker, status line,
5
+ * channel, activity prefs) resolve the same names without moving; all preset
6
+ * data (the pi-extension union, 35 presets) lives upstream.
7
+ * @module dsh-tui/components/activityFrames
7
8
  */
8
- /** One working-activity preset: the frame sequence and the per-frame interval. */
9
- export interface FramePreset {
10
- readonly frames: readonly string[];
11
- readonly intervalMs: number;
12
- }
13
- /** Named working-activity frame presets, keyed by preset name (`claude`, `moon`, `sand`, ...). */
14
- export declare const FRAME_PRESETS: Record<string, FramePreset>;
15
- /** The pi extension's default preset. */
16
- export declare const DEFAULT_PRESET = "moon8";
17
- /** Every selectable preset name, `random` first (the pi selector order). */
18
- export declare const PRESET_NAMES: readonly string[];
19
- /**
20
- * Whether `name` selects a known preset or `random`.
21
- * @param name - Candidate preset name.
22
- * @returns True when the name resolves to a preset.
23
- */
24
- export declare function isPresetName(name: string): boolean;
25
- /**
26
- * Resolve a preset name (`random` picks one per process).
27
- * @param name - Preset name, or undefined for the default.
28
- * @returns The matching preset; unknown or absent names fall back to the default.
29
- */
30
- export declare function resolvePreset(name: string | undefined): FramePreset;
9
+ export * from 'dsh-working-activity/frames';
31
10
  //# sourceMappingURL=activityFrames.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"activityFrames.d.ts","sourceRoot":"","sources":["../../../src/components/activityFrames.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,kFAAkF;AAClF,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;IAClC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAC5B;AAED,kGAAkG;AAClG,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CA0DrD,CAAA;AAED,yCAAyC;AACzC,eAAO,MAAM,cAAc,UAAU,CAAA;AAErC,4EAA4E;AAC5E,eAAO,MAAM,YAAY,EAAE,SAAS,MAAM,EAA8C,CAAA;AAExF;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,WAAW,CASnE"}
1
+ {"version":3,"file":"activityFrames.d.ts","sourceRoot":"","sources":["../../../src/components/activityFrames.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,6BAA6B,CAAA"}
@@ -1,96 +1,9 @@
1
1
  /**
2
- * Working-activity indicator presets, ported from the pi
3
- * working-activity extension (`FRAME_PRESETS`). The TUI renders the current
4
- * frame next to the live working line, tinted by the activity phase.
5
- * `\uFE0E` forces text rendering so Windows never paints the glyphs as
6
- * color emoji (the green-block problem).
2
+ * Working-activity indicator presets — thin re-export of the single source
3
+ * of truth in the `dsh-working-activity` package (`src/frames.ts`). The TUI
4
+ * keeps this shim so existing importers (`/activity` picker, status line,
5
+ * channel, activity prefs) resolve the same names without moving; all preset
6
+ * data (the pi-extension union, 35 presets) lives upstream.
7
+ * @module dsh-tui/components/activityFrames
7
8
  */
8
- /** Text-variant selector: keep symbols monochrome on Windows. */
9
- const TE = '\uFE0E';
10
- /** Named working-activity frame presets, keyed by preset name (`claude`, `moon`, `sand`, ...). */
11
- export const FRAME_PRESETS = {
12
- // Claude Code's real sequence: · ✢ * ✶ ✻ ✽ forward + backward.
13
- claude: {
14
- frames: ['·', `✢${TE}`, '*', `✶${TE}`, `✻${TE}`, `✽${TE}`, `✻${TE}`, `✶${TE}`, '*', `✢${TE}`],
15
- intervalMs: 150,
16
- },
17
- star2: { frames: [`✶${TE}`, `✸${TE}`, `✹${TE}`, `✺${TE}`, `✹${TE}`, `✷${TE}`], intervalMs: 140 },
18
- sand: {
19
- frames: ['⠁', '⠂', '⠄', '⡀', '⡈', '⡐', '⡠', '⣀', '⣁', '⣂', '⣄', '⣌', '⣔', '⣤', '⣥', '⣦', '⣮', '⣶', '⣷', '⣿', '⡿', '⠿', '⢟', '⠟', '⡛', '⠛', '⠫', '⢋', '⠋', '⠍', '⡉', '⠉', '⠑', '⠡', '⢁'],
20
- intervalMs: 120,
21
- },
22
- triangle: { frames: ['◢', '◣', '◤', '◥'], intervalMs: 180 },
23
- box: { frames: ['▖', '▘', '▝', '▗'], intervalMs: 180 },
24
- box2: { frames: ['▌', '▀', '▐', '▄'], intervalMs: 180 },
25
- corners: { frames: ['◰', '◳', '◲', '◱'], intervalMs: 190 },
26
- point: { frames: ['∙∙∙', '●∙∙', '∙●∙', '∙∙●', '∙∙∙'], intervalMs: 190 },
27
- layer: { frames: ['-', '=', '≡'], intervalMs: 220 },
28
- flip: { frames: ['_', '_', '_', '-', '`', '`', "'", '´', '-', '_', '_', '_'], intervalMs: 140 },
29
- aesthetic: {
30
- frames: ['▰▱▱▱▱▱▱', '▰▰▱▱▱▱▱', '▰▰▰▱▱▱▱', '▰▰▰▰▱▱▱', '▰▰▰▰▰▱▱', '▰▰▰▰▰▰▱', '▰▰▰▰▰▰▰', '▰▱▱▱▱▱▱'],
31
- intervalMs: 140,
32
- },
33
- hamburger: { frames: ['☱', '☲', '☴'], intervalMs: 220 },
34
- moon: { frames: ['◐', '◓', '◑', '◒'], intervalMs: 240 },
35
- // kimi-code MoonLoader 同款:8 帧 emoji 月相,120ms 一帧,比半圆版更丝滑。
36
- // 不带 \uFE0E:保留彩色 emoji 渲染(Windows Terminal 等现代终端效果最佳)。
37
- moon8: { frames: ['🌑', '🌒', '🌓', '🌔', '🌕', '🌖', '🌗', '🌘'], intervalMs: 120 },
38
- // 鲸鱼喷水:🐳 固定,水柱升起(· → | → ║)再回落,顶珠 ° 模拟水花(对齐 pi 版)。
39
- 'whale-spout': {
40
- frames: ['🐳 ', '🐳° ', '🐳|°', '🐳║°', '🐳|°', '🐳° ', '🐳 '],
41
- intervalMs: 160,
42
- },
43
- // 鲸鱼转圈:🐳 + 环绕方向指示(逆时针转圈语义,对齐 pi 版)。
44
- 'whale-spin': {
45
- frames: ['🐳→', '🐳↘', '🐳↓', '🐳↙', '🐳←', '🐳↖', '🐳↑', '🐳↗'],
46
- intervalMs: 150,
47
- },
48
- comet: {
49
- frames: ['● ', ' ● ', ' ● ', ' ● ', ' ●', ' ● ', ' ● ', ' ● '],
50
- intervalMs: 160,
51
- },
52
- breathe: { frames: ['▁', '▃', '▅', '▇', '▅', '▃'], intervalMs: 210 },
53
- dots: { frames: ['⣾', '⣷', '⣯', '⣟', '⡿', '⢿', '⣻', '⣽'], intervalMs: 140 },
54
- arrow: { frames: ['←', '↖', '↑', '↗', '→', '↘', '↓', '↙'], intervalMs: 160 },
55
- spark: { frames: ['·', '∘', '°', '✧', '°', '∘'], intervalMs: 240 },
56
- bar: { frames: ['▏', '▎', '▍', '▌', '▋', '▊', '▉', '█', '▉', '▊', '▋', '▌', '▍', '▎'], intervalMs: 120 },
57
- braille: { frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'], intervalMs: 120 },
58
- arc: { frames: ['◜', '◠', '◝', '◞', '◡', '◟'], intervalMs: 160 },
59
- circle: { frames: ['◴', '◷', '◶', '◵'], intervalMs: 190 },
60
- grow: { frames: ['.', 'o', 'O', '0', 'O', 'o'], intervalMs: 210 },
61
- noise: { frames: ['▓', '▒', '░', '▒'], intervalMs: 160 },
62
- bounce: { frames: ['⠁', '⠂', '⠄', '⡀', '⢀', '⠠', '⠐', '⠈'], intervalMs: 140 },
63
- rainbow: {
64
- frames: ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█', '▇', '▆', '▅', '▄', '▃', '▂'],
65
- intervalMs: 120,
66
- },
67
- dqpb: { frames: ['d', 'q', 'p', 'b'], intervalMs: 210 },
68
- toggle: { frames: ['⊶', '⊷'], intervalMs: 300 },
69
- };
70
- /** The pi extension's default preset. */
71
- export const DEFAULT_PRESET = 'moon8';
72
- /** Every selectable preset name, `random` first (the pi selector order). */
73
- export const PRESET_NAMES = ['random', ...Object.keys(FRAME_PRESETS)];
74
- /**
75
- * Whether `name` selects a known preset or `random`.
76
- * @param name - Candidate preset name.
77
- * @returns True when the name resolves to a preset.
78
- */
79
- export function isPresetName(name) {
80
- return name === 'random' || Object.hasOwn(FRAME_PRESETS, name);
81
- }
82
- /**
83
- * Resolve a preset name (`random` picks one per process).
84
- * @param name - Preset name, or undefined for the default.
85
- * @returns The matching preset; unknown or absent names fall back to the default.
86
- */
87
- export function resolvePreset(name) {
88
- if (name === 'random') {
89
- const names = Object.keys(FRAME_PRESETS);
90
- // FRAME_PRESETS is non-empty by construction, so any random index is in
91
- // range and the lookup always lands on a preset.
92
- const pick = names[Math.floor(Math.random() * names.length)];
93
- return FRAME_PRESETS[pick];
94
- }
95
- return FRAME_PRESETS[name ?? ''] ?? FRAME_PRESETS[DEFAULT_PRESET];
96
- }
9
+ export * from 'dsh-working-activity/frames';