@deepseek-harness-tui/dsh-tui 0.10.0-beta.3 → 0.10.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/lib/types/commands.d.ts.map +1 -1
- package/lib/types/commands.js +4 -1
- package/lib/types/components/MessageList.d.ts.map +1 -1
- package/lib/types/components/MessageList.js +53 -28
- package/lib/types/components/PromptInput.d.ts +19 -1
- package/lib/types/components/PromptInput.d.ts.map +1 -1
- package/lib/types/components/PromptInput.js +34 -11
- package/lib/types/components/approvals/ApprovalPanel.d.ts +4 -1
- package/lib/types/components/approvals/ApprovalPanel.d.ts.map +1 -1
- package/lib/types/components/approvals/ApprovalPanel.js +2 -2
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantToolUseMessage.js +6 -4
- package/lib/types/components/questions/AskUserQuestionPanel.d.ts +4 -0
- package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
- package/lib/types/components/questions/AskUserQuestionPanel.js +6 -1
- package/lib/types/components/smoothReveal.d.ts +0 -40
- package/lib/types/components/smoothReveal.d.ts.map +1 -1
- package/lib/types/components/smoothReveal.js +28 -2
- package/lib/types/dsh-adapter/agent-view.d.ts +66 -0
- package/lib/types/dsh-adapter/agent-view.d.ts.map +1 -0
- package/lib/types/dsh-adapter/agent-view.js +153 -0
- package/lib/types/dsh-adapter/approvals.d.ts +24 -1
- package/lib/types/dsh-adapter/approvals.d.ts.map +1 -1
- package/lib/types/dsh-adapter/approvals.js +37 -1
- package/lib/types/dsh-adapter/channel.d.ts +128 -0
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel.js +988 -28
- package/lib/types/dsh-adapter/inject-channel.d.ts +108 -0
- package/lib/types/dsh-adapter/inject-channel.d.ts.map +1 -0
- package/lib/types/dsh-adapter/inject-channel.js +247 -0
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin.js +26 -6
- package/lib/types/dsh-adapter/providerWizard.d.ts +86 -6
- package/lib/types/dsh-adapter/providerWizard.d.ts.map +1 -1
- package/lib/types/dsh-adapter/providerWizard.js +684 -186
- package/lib/types/history.d.ts +3 -1
- package/lib/types/history.d.ts.map +1 -1
- package/lib/types/history.js +80 -44
- package/lib/types/hooks/useExternalVersion.d.ts +32 -0
- package/lib/types/hooks/useExternalVersion.d.ts.map +1 -0
- package/lib/types/hooks/useExternalVersion.js +53 -0
- package/lib/types/hooks/useRevealVersion.d.ts +21 -0
- package/lib/types/hooks/useRevealVersion.d.ts.map +1 -0
- package/lib/types/hooks/useRevealVersion.js +33 -0
- package/lib/types/i18n.d.ts +410 -1
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +117 -1
- package/lib/types/ink/components/ClockContext.d.ts +6 -0
- package/lib/types/ink/components/ClockContext.d.ts.map +1 -1
- package/lib/types/ink/components/ClockContext.js +24 -3
- package/lib/types/ink/components/ScrollBox.d.ts.map +1 -1
- package/lib/types/ink/components/ScrollBox.js +12 -2
- package/lib/types/ink/flush-tick.d.ts +26 -0
- package/lib/types/ink/flush-tick.d.ts.map +1 -0
- package/lib/types/ink/flush-tick.js +31 -0
- package/lib/types/ink/ink.d.ts.map +1 -1
- package/lib/types/ink/ink.js +67 -8
- package/lib/types/ink/output.js +5 -2
- package/lib/types/ink/render-node-to-output.d.ts +46 -0
- package/lib/types/ink/render-node-to-output.d.ts.map +1 -1
- package/lib/types/ink/render-node-to-output.js +62 -1
- package/lib/types/ink/selection.d.ts +65 -2
- package/lib/types/ink/selection.d.ts.map +1 -1
- package/lib/types/ink/selection.js +172 -3
- package/lib/types/ink/update-overflow-guard.d.ts +63 -0
- package/lib/types/ink/update-overflow-guard.d.ts.map +1 -0
- package/lib/types/ink/update-overflow-guard.js +165 -0
- package/lib/types/screens/AgentView.d.ts +23 -0
- package/lib/types/screens/AgentView.d.ts.map +1 -0
- package/lib/types/screens/AgentView.js +546 -0
- package/lib/types/screens/Chat.d.ts +10 -1
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +220 -19
- package/lib/types/screens/StatusLine.d.ts.map +1 -1
- package/lib/types/screens/StatusLine.js +4 -1
- package/lib/types/sessionHistory.d.ts +18 -0
- package/lib/types/sessionHistory.d.ts.map +1 -1
- package/lib/types/sessionHistory.js +65 -0
- package/package.json +8 -6
package/README.md
CHANGED
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
|
|
55
55
|
- **终端交互**:低资源占用,长会话稳定可靠;多种主题切换,样式美观,实时显示工作状态、TPS、缓存命中率等
|
|
56
56
|
推理等级、输入/输出 token 与 Git/会话信息;终端卡多行命令可经 `/settings` 折叠为首行 + 计数提示(Ctrl+O 或点击卡片展开);全屏模式下悬停在截断的工具卡标题、用户消息或会话标题上约 600ms,浮层显示完整内容。
|
|
57
|
-
- **功能全面**:`/resume` 按工作目录分类浏览、搜索与预览历史会话(左键恢复、右键弹出操作菜单;可固定常用会话——「已固定」分组置顶显示,行内 ★ 或 `Ctrl+P` 切换,持久化到 `~/.dsh-tui`),另有 `/new`、`/compact`、`/export`、`/btw`,模型热切换,原生subagent,会话fork,自动更新,输入框 `/vim` vim 编辑模式、鼠标选区编辑(拖选高亮、Shift+click 扩展、双击选词、Ctrl+C 复制选区)与全屏草稿编辑(`Ctrl+Shift+E` 或输入行 `⛶` 按钮:行号 + 当前行高亮、Enter 换行、Ctrl+Enter 发送、滚轮滚动、点击/拖选,长草稿独占整屏;`/settings` 可关);可在vs code中[以vscode插件形式启动](docs/vscode.md),已上架 VS Code Marketplace。
|
|
57
|
+
- **功能全面**:`/resume` 按工作目录分类浏览、搜索与预览历史会话(左键恢复、右键弹出操作菜单;可固定常用会话——「已固定」分组置顶显示,行内 ★ 或 `Ctrl+P` 切换,持久化到 `~/.dsh-tui`),另有 `/agentview` 会话总览(CC 同款 agent view:空输入 `←` 一键后台化,后台会话派发/预览/回复/停止一站式管理)、`/new`、`/compact`、`/export`、`/btw`,模型热切换,原生subagent,会话fork,自动更新,输入框 `/vim` vim 编辑模式、鼠标选区编辑(拖选高亮、Shift+click 扩展、双击选词、Ctrl+C 复制选区)与全屏草稿编辑(`Ctrl+Shift+E` 或输入行 `⛶` 按钮:行号 + 当前行高亮、Enter 换行、Ctrl+Enter 发送、滚轮滚动、点击/拖选,长草稿独占整屏;`/settings` 可关);可在vs code中[以vscode插件形式启动](docs/vscode.md),已上架 VS Code Marketplace。
|
|
58
58
|
- **扩展丰富**:原生浏览器交互,compter use等大量附属功能性扩展
|
|
59
59
|
- **技能归 DSH 管理**:`/skills` 展示当前 profile、用户与项目发现的技能;dsh-TUI 不预装通用技能。
|
|
60
60
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/commands.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAA;AAElF,MAAM,WAAW,YAAY;IAC3B,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAA;IACnB,yEAAyE;IACzE,YAAY,CAAC,EAAE,qBAAqB,CAAA;IACpC,+DAA+D;IAC/D,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,+EAA+E;AAC/E,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC3B,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,qBAAqB,CAAA;IACpC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,sEAAsE;IACtE,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,2EAA2E;AAC3E,MAAM,WAAW,iBAAkB,SAAQ,YAAY;IACrD,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,MAAM,eAAe,GAAG,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,KAAK,SAAS,qBAAqB,EAAE,CAAA;AAEpG;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE/D;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/commands.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAA;AAElF,MAAM,WAAW,YAAY;IAC3B,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAA;IACnB,yEAAyE;IACzE,YAAY,CAAC,EAAE,qBAAqB,CAAA;IACpC,+DAA+D;IAC/D,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,+EAA+E;AAC/E,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC3B,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,qBAAqB,CAAA;IACpC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,sEAAsE;IACtE,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,2EAA2E;AAC3E,MAAM,WAAW,iBAAkB,SAAQ,YAAY;IACrD,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,MAAM,eAAe,GAAG,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,KAAK,SAAS,qBAAqB,EAAE,CAAA;AAEpG;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE/D;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,YAAY,EA+DxC,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,eAAe,EAAE,SAAS,YAAY,EAElD,CAAA;AAED,0DAA0D;AAC1D,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAEpD,CAAA;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAG1D;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,YAAY,GAAG;IAAE,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAIhG;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,GACX;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAIhD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,SAAS,YAAY,EAAmB,GAC7C,OAAO,CAKT;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,SAAS,YAAY,EAAmB,GAC7C,YAAY,EAAE,CAKhB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,SAAS,YAAY,EAAmB,EAC/C,QAAQ,GAAE,eAA0B,GACnC,iBAAiB,EAAE,CAuCrB"}
|
package/lib/types/commands.js
CHANGED
|
@@ -33,6 +33,9 @@ export const LOCAL_COMMANDS = [
|
|
|
33
33
|
{ name: 'export', description: 'Export the conversation to a markdown file' },
|
|
34
34
|
{ name: 'btw', description: 'Ask a quick side question without interrupting the conversation' },
|
|
35
35
|
{ name: 'trace', description: 'Show the session event trace timeline' },
|
|
36
|
+
{ name: 'agentview', description: 'Open the agent view (all sessions)' },
|
|
37
|
+
{ name: 'bg', description: 'Background this session and open agent view' },
|
|
38
|
+
{ name: 'background', description: 'Background this session and open agent view', tag: 'alias of /bg' },
|
|
36
39
|
// Session / environment
|
|
37
40
|
{ name: 'context', description: 'Show loaded context details' },
|
|
38
41
|
{ name: 'status', description: 'Show session status' },
|
|
@@ -56,7 +59,7 @@ export const LOCAL_COMMANDS = [
|
|
|
56
59
|
{ name: 'tokens', description: 'Show session token usage' },
|
|
57
60
|
// Account / policy
|
|
58
61
|
{ name: 'balance', description: 'Show DeepSeek account balance' },
|
|
59
|
-
{ name: 'provider', description: 'Add an LLM provider (catalog or custom API endpoint)' },
|
|
62
|
+
{ name: 'provider', description: 'Add, edit or delete an LLM provider (catalog or custom API endpoint)' },
|
|
60
63
|
{ name: 'login', description: 'Show API credential status' },
|
|
61
64
|
{ name: 'logout', description: 'Clear the API credential' },
|
|
62
65
|
{ name: 'add-dir', description: 'Show the filesystem policy scope' },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/MessageList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAA;AAEvC,OAAO,EAA8B,KAAK,eAAe,EAAE,MAAM,UAAU,CAAA;AAE3E,OAAO,KAAK,EAAE,OAAO,EAA8D,MAAM,2BAA2B,CAAA;AACpH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/MessageList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAA;AAEvC,OAAO,EAA8B,KAAK,eAAe,EAAE,MAAM,UAAU,CAAA;AAE3E,OAAO,KAAK,EAAE,OAAO,EAA8D,MAAM,2BAA2B,CAAA;AACpH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAkB/C,OAAO,EAAe,KAAK,gBAAgB,EAAqB,MAAM,yBAAyB,CAAA;AAC/F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AA6K3D,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,WAAW,EACX,qBAA8C,EAC9C,kBAA4C,EAC5C,KAAK,EACL,UAAmB,EACnB,YAAwB,EACxB,cAAuB,EACvB,mBAA2B,EAC3B,eAAuB,EACvB,cAAc,EACd,OAAO,EACP,WAAW,EACX,WAAW,EACX,eAAsB,EACtB,mBAA0B,EAC1B,cAAc,EACd,YAAY,EACZ,eAAe,EACf,aAAa,EACb,aAAa,EACb,UAAU,EACV,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,UAAU,EACV,UAAU,GACX,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,+CAA+C;IAC/C,qBAAqB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IAC3C,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAC5C,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,mEAAmE;IACnE,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B;;sEAEkE;IAClE,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB;sEACkE;IAClE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,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;;;;;;;OAOG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,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;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAA;IAC9C;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,qCAAqC;IACrC,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1C,0CAA0C;IAC1C,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;IACvB,6BAA6B;IAC7B,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CACpC,qBAo1BA;AA2XD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,MAAM,EACN,GAAG,EACH,KAAY,EACZ,SAAiB,GAClB,EAAE;IACD,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;qEACiE;IACjE,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,GAAG,KAAK,CAAC,SAAS,CASlB"}
|
|
@@ -11,6 +11,7 @@ import { SubagentMessage } from './Chat/SubagentMessage.js';
|
|
|
11
11
|
import { JobCard } from './Chat/JobCard.js';
|
|
12
12
|
import { isMinimalMode } from '../minimalMode.js';
|
|
13
13
|
import { noteFrameCause, noteListGeometry } from '../ink/geometry-trace.js';
|
|
14
|
+
import { getTerminalFlushTick } from '../ink/flush-tick.js';
|
|
14
15
|
import { InterruptedByUser } from './InterruptedByUser.js';
|
|
15
16
|
import { LogoV2 } from './LogoV2.js';
|
|
16
17
|
import { StreamingMarkdown } from './StreamingMarkdown.js';
|
|
@@ -19,7 +20,8 @@ import { stripNarration } from '../utils/narration.js';
|
|
|
19
20
|
import { stringWidth } from '../ink/stringWidth.js';
|
|
20
21
|
import { truncateToWidth } from '../ink/truncateToWidth.js';
|
|
21
22
|
import { clipPreview } from '../ink/timeline-rail.js';
|
|
22
|
-
import { getRevealVersion, revealLengthOf, revealTextOf
|
|
23
|
+
import { getRevealVersion, revealLengthOf, revealTextOf } from './smoothReveal.js';
|
|
24
|
+
import { useRevealVersion } from '../hooks/useRevealVersion.js';
|
|
23
25
|
/**
|
|
24
26
|
* Transcript rows rendered in the Claude Code visual language: user prompts
|
|
25
27
|
* on a grey bubble with a `❯` pointer, assistant text with a `●` bullet and
|
|
@@ -260,13 +262,17 @@ export function MessageList({ rows, expanded, expandedRows, selectedId, onToggle
|
|
|
260
262
|
};
|
|
261
263
|
// --- layout virtualization ---------------------------------------------
|
|
262
264
|
const { columns, rows: termRows } = useTerminalSize();
|
|
263
|
-
// Smooth-reveal wakeups: the scheduler's tick bumps this
|
|
265
|
+
// Smooth-reveal wakeups: the scheduler's tick bumps this hook, re-running
|
|
264
266
|
// MessageList so the revealed `text`/line-count reads below feed fresh
|
|
265
267
|
// values through the same MemoRow-prop pipeline an arriving chunk uses —
|
|
266
268
|
// memo miss → re-render → post-commit height re-measure. Without this
|
|
267
269
|
// subscription a reveal living in child state would change row heights
|
|
268
270
|
// invisibly to the virtualization (stale cached heights → blank bands).
|
|
269
|
-
|
|
271
|
+
// DefaultLane on purpose (useRevealVersion): a useSyncExternalStore wakeup
|
|
272
|
+
// would force a SyncLane render per 33ms tick, and each such commit ending
|
|
273
|
+
// with streaming work still pending feeds React's nested-update counter
|
|
274
|
+
// until error #185 kills the process (beta.3).
|
|
275
|
+
useRevealVersion();
|
|
270
276
|
// Measured row heights, remembered after a row unmounts so virtualization
|
|
271
277
|
// can compute total content height. Bounded: row ids grow monotonically
|
|
272
278
|
// and rows are never removed from the transcript (foldRows keeps the
|
|
@@ -289,15 +295,18 @@ export function MessageList({ rows, expanded, expandedRows, selectedId, onToggle
|
|
|
289
295
|
const paintedOnceRef = React.useRef(new Set());
|
|
290
296
|
const paintedBaseRef = React.useRef(undefined);
|
|
291
297
|
/** Window-expansion hold: after the window WIDENS (new rows mounted),
|
|
292
|
-
* refuse to tighten
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
298
|
+
* refuse to tighten until a frame containing that layout has actually
|
|
299
|
+
* been FLUSHED to the terminal (flush-tick based, issue #574). React
|
|
300
|
+
* commits and terminal writes are decoupled — the throttled deferred
|
|
301
|
+
* leading edge lets a later commit supersede the wide one inside the
|
|
302
|
+
* same task, so a wall-clock hold (the old 120ms timer) expires during
|
|
303
|
+
* long cold-cache layout work (~190ms on the repro) and the measure-tick
|
|
304
|
+
* re-render still drops the rows before a single byte of them was
|
|
305
|
+
* written; never-mounted rows have no scrollback copy and preset history
|
|
306
|
+
* vanishes. Once flushed, tightening is visually free: those rows sit in
|
|
307
|
+
* scrollback and the diff skips them. */
|
|
299
308
|
const lastStartRef = React.useRef(-1);
|
|
300
|
-
const
|
|
309
|
+
const holdFlushTickRef = React.useRef(-1);
|
|
301
310
|
/** True when frame-budgeted history painting still has batches left
|
|
302
311
|
* (main-screen open): the layout effect schedules the next slice. */
|
|
303
312
|
const paintPendingRef = React.useRef(false);
|
|
@@ -566,15 +575,18 @@ export function MessageList({ rows, expanded, expandedRows, selectedId, onToggle
|
|
|
566
575
|
}
|
|
567
576
|
}
|
|
568
577
|
// Expansion hold — AFTER the extension so it tracks the FINAL window:
|
|
569
|
-
// never tighten
|
|
570
|
-
//
|
|
571
|
-
// re-render that drops the row paints only
|
|
572
|
-
// row's painted-once mark (set at the first
|
|
573
|
-
|
|
578
|
+
// never tighten past a widen until a frame with that layout has been
|
|
579
|
+
// flushed (getTerminalFlushTick advanced since the widen). A mount
|
|
580
|
+
// followed by the measure-tick re-render that drops the row paints only
|
|
581
|
+
// the DROPPED layout, and the row's painted-once mark (set at the first
|
|
582
|
+
// commit) is a lie; holding until the flush makes the mark real.
|
|
583
|
+
if (lastStartRef.current >= 0 &&
|
|
584
|
+
start > lastStartRef.current &&
|
|
585
|
+
getTerminalFlushTick() === holdFlushTickRef.current) {
|
|
574
586
|
start = lastStartRef.current;
|
|
575
587
|
}
|
|
576
588
|
if (lastStartRef.current < 0 || start < lastStartRef.current) {
|
|
577
|
-
|
|
589
|
+
holdFlushTickRef.current = getTerminalFlushTick();
|
|
578
590
|
}
|
|
579
591
|
lastStartRef.current = start;
|
|
580
592
|
}
|
|
@@ -763,18 +775,31 @@ export function MessageList({ rows, expanded, expandedRows, selectedId, onToggle
|
|
|
763
775
|
upId: upTurnIndex === null ? null : timelineTurns[upTurnIndex].id,
|
|
764
776
|
downId: downTurnIndex === null ? null : timelineTurns[downTurnIndex].id,
|
|
765
777
|
};
|
|
766
|
-
const lastTimelineReportRef = React.useRef(
|
|
778
|
+
const lastTimelineReportRef = React.useRef(null);
|
|
767
779
|
React.useEffect(() => {
|
|
768
|
-
//
|
|
769
|
-
//
|
|
770
|
-
//
|
|
771
|
-
//
|
|
772
|
-
//
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
780
|
+
// Value-level dedup, not geometry-key-level: the memo key pins to
|
|
781
|
+
// heightsVersion, which a GROWING streamed row bumps on every commit,
|
|
782
|
+
// so a key-pinned signature re-reports an identical snapshot every
|
|
783
|
+
// commit of a long stream. Each report is a setState dispatched into
|
|
784
|
+
// Chat from this passive effect; it lands as pending residue at commit
|
|
785
|
+
// end, keeping the commit dirty and feeding React's nested-update
|
|
786
|
+
// counter (50 consecutive dirty commits → error #185, the beta.3
|
|
787
|
+
// crash). The O(turns) compare below is integer/string-reference
|
|
788
|
+
// compares only — cheap next to the joined-string signature the memo
|
|
789
|
+
// key replaced, and it fires exactly when the snapshot content changes.
|
|
790
|
+
const prev = lastTimelineReportRef.current;
|
|
791
|
+
if (prev !== null &&
|
|
792
|
+
prev.activeId === timeline.activeId &&
|
|
793
|
+
prev.upId === timeline.upId &&
|
|
794
|
+
prev.downId === timeline.downId &&
|
|
795
|
+
prev.turns.length === timeline.turns.length &&
|
|
796
|
+
prev.turns.every((t, i) => {
|
|
797
|
+
const n = timeline.turns[i];
|
|
798
|
+
return t.id === n.id && t.top === n.top && t.folded === n.folded && t.preview === n.preview;
|
|
799
|
+
}))
|
|
800
|
+
return;
|
|
801
|
+
lastTimelineReportRef.current = timeline;
|
|
802
|
+
onTimeline?.(timeline);
|
|
778
803
|
});
|
|
779
804
|
// Post-commit: measure mounted rows, derive the content-space base from
|
|
780
805
|
// the first mounted row's Yoga top, and clamp render-time scrollTop to the
|
|
@@ -9,6 +9,13 @@ import type { Channel } from '../dsh-adapter/channel.js';
|
|
|
9
9
|
export interface PromptController {
|
|
10
10
|
hasText(): boolean;
|
|
11
11
|
clear(): void;
|
|
12
|
+
/**
|
|
13
|
+
* Append `text` at the end of the input (external injection channel; see
|
|
14
|
+
* dsh-adapter/inject-channel.ts). Unlike `fillText`, which replaces the
|
|
15
|
+
* whole value, this accumulates — matching OpenCode's `tui.prompt.append`
|
|
16
|
+
* so repeated editor sends build one prompt. Returns the resulting value.
|
|
17
|
+
*/
|
|
18
|
+
append(text: string): string;
|
|
12
19
|
/**
|
|
13
20
|
* Copy the active mouse selection to the system clipboard (OSC 52 + the
|
|
14
21
|
* native fallback) and KEEP the selection for further editing. Returns
|
|
@@ -43,6 +50,17 @@ export interface PromptInputProps {
|
|
|
43
50
|
onFillConsumed?(): void;
|
|
44
51
|
/** Double-tap Esc with an empty input: open the rewind picker (CC rewind). */
|
|
45
52
|
onRewindRequest?(): void;
|
|
53
|
+
/**
|
|
54
|
+
* CC agent-view parity: ← on an EMPTY prompt backgrounds this session and
|
|
55
|
+
* opens the agent view (with text, ← moves the caret as usual).
|
|
56
|
+
*/
|
|
57
|
+
onBackgroundRequest?(): void;
|
|
58
|
+
/**
|
|
59
|
+
* Background sessions waiting on the user (agent view "needs input" rows
|
|
60
|
+
* excluding this session); the prompt footer shows the CC-style
|
|
61
|
+
* "← N agents" hint when provided (hidden when undefined).
|
|
62
|
+
*/
|
|
63
|
+
backgroundAgentsNeedingInput?: number;
|
|
46
64
|
/** Filled with the live controller each render (see PromptController). */
|
|
47
65
|
controllerRef?: React.RefObject<PromptController | null>;
|
|
48
66
|
}
|
|
@@ -77,5 +95,5 @@ export interface PromptInputProps {
|
|
|
77
95
|
* working) interrupts the turn and delivers them right away; Ctrl+Enter
|
|
78
96
|
* aborts the turn and sends the current input immediately.
|
|
79
97
|
*/
|
|
80
|
-
export declare function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, selectionActive, fillText, onFillConsumed, onRewindRequest, controllerRef, }: PromptInputProps): React.JSX.Element;
|
|
98
|
+
export declare function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, selectionActive, fillText, onFillConsumed, onRewindRequest, onBackgroundRequest, backgroundAgentsNeedingInput, controllerRef, }: PromptInputProps): React.JSX.Element;
|
|
81
99
|
//# sourceMappingURL=PromptInput.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PromptInput.d.ts","sourceRoot":"","sources":["../../../src/components/PromptInput.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAwBzB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AA4OxD;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,IAAI,OAAO,CAAA;IAClB,KAAK,IAAI,IAAI,CAAA;IACb;;;;;OAKG;IACH,oBAAoB,IAAI,OAAO,CAAA;IAC/B,2EAA2E;IAC3E,SAAS,IAAI,OAAO,CAAA;IACpB;yEACqE;IACrE,SAAS,IAAI,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"PromptInput.d.ts","sourceRoot":"","sources":["../../../src/components/PromptInput.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAwBzB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AA4OxD;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,IAAI,OAAO,CAAA;IAClB,KAAK,IAAI,IAAI,CAAA;IACb;;;;;OAKG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;IAC5B;;;;;OAKG;IACH,oBAAoB,IAAI,OAAO,CAAA;IAC/B,2EAA2E;IAC3E,SAAS,IAAI,OAAO,CAAA;IACpB;yEACqE;IACrE,SAAS,IAAI,OAAO,CAAA;CAAC;AAEvB,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;;;OAGG;IACH,mBAAmB,CAAC,IAAI,IAAI,CAAA;IAC5B;;;;OAIG;IACH,4BAA4B,CAAC,EAAE,MAAM,CAAA;IACrC,0EAA0E;IAC1E,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;CACzD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,4BAA4B,EAC5B,aAAa,GACd,EAAE,gBAAgB,qBA+7ElB"}
|
|
@@ -280,7 +280,7 @@ const DOUBLE_CLICK_MS = 500;
|
|
|
280
280
|
* working) interrupts the turn and delivers them right away; Ctrl+Enter
|
|
281
281
|
* aborts the turn and sends the current input immediately.
|
|
282
282
|
*/
|
|
283
|
-
export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, selectionActive, fillText, onFillConsumed, onRewindRequest, controllerRef, }) {
|
|
283
|
+
export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, selectionActive, fillText, onFillConsumed, onRewindRequest, onBackgroundRequest, backgroundAgentsNeedingInput, controllerRef, }) {
|
|
284
284
|
const [themeName] = useTheme();
|
|
285
285
|
// Raw stdout writer for OSC 52 clipboard writes (selection copy) — must
|
|
286
286
|
// bypass the frame pipeline; null outside a mounted Ink App.
|
|
@@ -406,6 +406,14 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
406
406
|
setValue('');
|
|
407
407
|
setCursor(0);
|
|
408
408
|
},
|
|
409
|
+
append: (text) => {
|
|
410
|
+
const next = valueRef.current + text;
|
|
411
|
+
valueRef.current = next;
|
|
412
|
+
cursorRef.current = next.length;
|
|
413
|
+
setValue(next);
|
|
414
|
+
setCursor(next.length);
|
|
415
|
+
return next;
|
|
416
|
+
},
|
|
409
417
|
consumeSelectionCopy: () => {
|
|
410
418
|
const sel = selectionRef.current;
|
|
411
419
|
if (!sel)
|
|
@@ -672,7 +680,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
672
680
|
historyIndex.current = -1;
|
|
673
681
|
setInput('', 0);
|
|
674
682
|
setSelectedCommand(0);
|
|
675
|
-
appendHistory(trimmed);
|
|
683
|
+
void appendHistory(trimmed);
|
|
676
684
|
channel.submit(trimmed);
|
|
677
685
|
if (notice) {
|
|
678
686
|
channel.notify(notice, { timeoutMs: 2500 });
|
|
@@ -698,7 +706,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
698
706
|
historyIndex.current = -1;
|
|
699
707
|
setInput('', 0);
|
|
700
708
|
setSelectedCommand(0);
|
|
701
|
-
appendHistory(trimmed);
|
|
709
|
+
void appendHistory(trimmed);
|
|
702
710
|
channel.steer(trimmed);
|
|
703
711
|
channel.notify(t('input-interrupted-next'), { timeoutMs: 2500 });
|
|
704
712
|
};
|
|
@@ -716,7 +724,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
716
724
|
historyIndex.current = -1;
|
|
717
725
|
setInput('', 0);
|
|
718
726
|
setSelectedCommand(0);
|
|
719
|
-
appendHistory(trimmed);
|
|
727
|
+
void appendHistory(trimmed);
|
|
720
728
|
channel.submit(trimmed);
|
|
721
729
|
channel.notify(t('input-queued-after-turn'), { timeoutMs: 2500 });
|
|
722
730
|
};
|
|
@@ -762,7 +770,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
762
770
|
setInput('', 0);
|
|
763
771
|
setSelectedCommand(0);
|
|
764
772
|
setFileSelected(0);
|
|
765
|
-
appendHistory(trimmed);
|
|
773
|
+
void appendHistory(trimmed);
|
|
766
774
|
channel.notify(t('input-interrupt-immediate'), { timeoutMs: 2500 });
|
|
767
775
|
};
|
|
768
776
|
/**
|
|
@@ -791,7 +799,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
791
799
|
historyIndex.current = -1;
|
|
792
800
|
setInput('', 0);
|
|
793
801
|
setSelectedCommand(0);
|
|
794
|
-
appendHistory(text.trim());
|
|
802
|
+
void appendHistory(text.trim());
|
|
795
803
|
}
|
|
796
804
|
return handled;
|
|
797
805
|
};
|
|
@@ -1293,7 +1301,11 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
1293
1301
|
return;
|
|
1294
1302
|
}
|
|
1295
1303
|
if (key.upArrow) {
|
|
1296
|
-
|
|
1304
|
+
// A history walk owns the arrows until it returns to the draft: a
|
|
1305
|
+
// recalled entry can itself open the @ menu or the slash menu (e.g.
|
|
1306
|
+
// `/model`), and letting the overlay navigate here strands the stashed
|
|
1307
|
+
// draft — Down would cycle menu rows instead of walking back.
|
|
1308
|
+
if (fileOverlayOpen && historyIndex.current < 0) {
|
|
1297
1309
|
setFileSelected(index => index <= 0 ? fileMatches.length - 1 : index - 1);
|
|
1298
1310
|
return;
|
|
1299
1311
|
}
|
|
@@ -1322,7 +1334,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
1322
1334
|
setInput(value, prevLineStart + Math.min(cursorColumn(value, cursor), prevLine.length));
|
|
1323
1335
|
return;
|
|
1324
1336
|
}
|
|
1325
|
-
if (overlayOpen) {
|
|
1337
|
+
if (overlayOpen && historyIndex.current < 0) {
|
|
1326
1338
|
setSelectedCommand(index => index <= 0 ? suggestions.length - 1 : index - 1);
|
|
1327
1339
|
return;
|
|
1328
1340
|
}
|
|
@@ -1341,7 +1353,8 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
1341
1353
|
return;
|
|
1342
1354
|
}
|
|
1343
1355
|
if (key.downArrow) {
|
|
1344
|
-
|
|
1356
|
+
// Same history-walk ownership as ↑ above.
|
|
1357
|
+
if (fileOverlayOpen && historyIndex.current < 0) {
|
|
1345
1358
|
setFileSelected(index => index >= fileMatches.length - 1 ? 0 : index + 1);
|
|
1346
1359
|
return;
|
|
1347
1360
|
}
|
|
@@ -1384,7 +1397,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
1384
1397
|
setInput(value, nextLineStart + Math.min(cursorColumn(value, cursor), nextLine.length));
|
|
1385
1398
|
return;
|
|
1386
1399
|
}
|
|
1387
|
-
if (overlayOpen) {
|
|
1400
|
+
if (overlayOpen && historyIndex.current < 0) {
|
|
1388
1401
|
setSelectedCommand(index => index >= suggestions.length - 1 ? 0 : index + 1);
|
|
1389
1402
|
return;
|
|
1390
1403
|
}
|
|
@@ -1416,6 +1429,14 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
1416
1429
|
return;
|
|
1417
1430
|
}
|
|
1418
1431
|
if (key.leftArrow) {
|
|
1432
|
+
// CC agent-view parity: ← on an EMPTY prompt backgrounds this session
|
|
1433
|
+
// and opens the agent view; with text it moves the caret as usual.
|
|
1434
|
+
// (The command/file overlays both imply non-empty text, so no extra
|
|
1435
|
+
// gate beyond the help menu is needed.)
|
|
1436
|
+
if (value.length === 0 && !helpOpen) {
|
|
1437
|
+
onBackgroundRequest?.();
|
|
1438
|
+
return;
|
|
1439
|
+
}
|
|
1419
1440
|
// Grapheme-step: skip the whole cluster (surrogate pair, ZWJ emoji,
|
|
1420
1441
|
// combining mark) so the caret never sits inside one. With a
|
|
1421
1442
|
// selection, collapse to its start edge instead.
|
|
@@ -2397,7 +2418,9 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
2397
2418
|
setExpandHovered(true);
|
|
2398
2419
|
}, onMouseLeave: () => {
|
|
2399
2420
|
setExpandHovered(false);
|
|
2400
|
-
}, children: _jsx(Text, { dimColor: !expandHovered, bold: expandHovered, color: expandHovered ? promptAccent : undefined, children: "\u26F6" }) }))] }) })
|
|
2421
|
+
}, children: _jsx(Text, { dimColor: !expandHovered, bold: expandHovered, color: expandHovered ? promptAccent : undefined, children: "\u26F6" }) }))] }) }), backgroundAgentsNeedingInput !== undefined && (_jsx(Box, { flexDirection: "row", justifyContent: "flex-end", paddingRight: 2, children: _jsx(Text, { dimColor: true, children: backgroundAgentsNeedingInput > 0
|
|
2422
|
+
? t('input-background-hint-count', { n: backgroundAgentsNeedingInput })
|
|
2423
|
+
: t('input-background-hint-idle') }) }))] }));
|
|
2401
2424
|
}
|
|
2402
2425
|
/**
|
|
2403
2426
|
* Grapheme word-wrap for one logical line: break at the last space when
|
|
@@ -15,7 +15,10 @@ import type { ApprovalSnapshot } from '../../dsh-adapter/approvals.js';
|
|
|
15
15
|
export type ApprovalPanelProps = {
|
|
16
16
|
/** The approval to render (from the ApprovalStore snapshot). */
|
|
17
17
|
readonly approval: ApprovalSnapshot;
|
|
18
|
+
/** True when the asking agent is NOT the attached session — a background
|
|
19
|
+
* (agent view) session's ask, answered from the same single panel. */
|
|
20
|
+
readonly background?: boolean;
|
|
18
21
|
readonly onDecide: (outcome: 'allowed-once' | 'rejected') => void;
|
|
19
22
|
};
|
|
20
|
-
export declare function ApprovalPanel({ approval, onDecide }: ApprovalPanelProps): React.ReactNode;
|
|
23
|
+
export declare function ApprovalPanel({ approval, background, onDecide }: ApprovalPanelProps): React.ReactNode;
|
|
21
24
|
//# sourceMappingURL=ApprovalPanel.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApprovalPanel.d.ts","sourceRoot":"","sources":["../../../../src/components/approvals/ApprovalPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAA;AAMzB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAA;AAEtE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAA;IACnC,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,cAAc,GAAG,UAAU,KAAK,IAAI,CAAA;CAClE,CAAA;AAID,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,kBAAkB,GAAG,KAAK,CAAC,SAAS,
|
|
1
|
+
{"version":3,"file":"ApprovalPanel.d.ts","sourceRoot":"","sources":["../../../../src/components/approvals/ApprovalPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAA;AAMzB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAA;AAEtE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAA;IACnC;2EACuE;IACvE,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAA;IAC7B,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,cAAc,GAAG,UAAU,KAAK,IAAI,CAAA;CAClE,CAAA;AAID,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,UAAkB,EAAE,QAAQ,EAAE,EAAE,kBAAkB,GAAG,KAAK,CAAC,SAAS,CAuF7G"}
|
|
@@ -18,7 +18,7 @@ import { isPlainReturnInput } from '../../utils/modifiers.js';
|
|
|
18
18
|
import { Divider } from '../design-system/Divider.js';
|
|
19
19
|
import { POINTER } from '../../cc/figures.js';
|
|
20
20
|
const OUTCOMES = ['allowed-once', 'rejected'];
|
|
21
|
-
export function ApprovalPanel({ approval, onDecide }) {
|
|
21
|
+
export function ApprovalPanel({ approval, background = false, onDecide }) {
|
|
22
22
|
const [focusIndex, setFocusIndex] = React.useState(0);
|
|
23
23
|
// Hover highlight per decision row (mouse affordance; the click handler
|
|
24
24
|
// below mirrors the keyboard Enter on the focused row).
|
|
@@ -45,7 +45,7 @@ export function ApprovalPanel({ approval, onDecide }) {
|
|
|
45
45
|
}
|
|
46
46
|
}, { isActive: true });
|
|
47
47
|
const optionLabels = [t('approval-yes'), t('approval-no')];
|
|
48
|
-
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, paddingLeft: 2, paddingRight: 2, width: "100%", children: [_jsx(Divider, { color: "permission", title: t('approval-waiting', { tool: approval.toolName }) }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [approval.external === true && (_jsxs(Text, { color: "warning", wrap: "wrap", children: ["[external] ", t('approval-external-hint')] })), approval.command !== undefined && (_jsx(Box, { flexDirection: "column", paddingX: 2, children: _jsx(Text, { dimColor: true, wrap: "wrap", children: approval.command }) })), approval.reason !== undefined && (_jsx(Text, { dimColor: true, wrap: "wrap", children: approval.reason })), _jsx(Text, { dimColor: true, children: t('approval-proceed') })] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: optionLabels.map((label, index) => {
|
|
48
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, paddingLeft: 2, paddingRight: 2, width: "100%", children: [_jsx(Divider, { color: "permission", title: t('approval-waiting', { tool: approval.toolName }) }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [background && (_jsx(Text, { color: "warning", children: t('approval-background-agent', { id: approval.agentId.slice(0, 8) }) })), approval.external === true && (_jsxs(Text, { color: "warning", wrap: "wrap", children: ["[external] ", t('approval-external-hint')] })), approval.command !== undefined && (_jsx(Box, { flexDirection: "column", paddingX: 2, children: _jsx(Text, { dimColor: true, wrap: "wrap", children: approval.command }) })), approval.reason !== undefined && (_jsx(Text, { dimColor: true, wrap: "wrap", children: approval.reason })), _jsx(Text, { dimColor: true, children: t('approval-proceed') })] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: optionLabels.map((label, index) => {
|
|
49
49
|
const focused = index === focusIndex;
|
|
50
50
|
const hovered = index === hoverIndex;
|
|
51
51
|
return (_jsxs(Box, { flexDirection: "row", marginTop: focused ? 1 : 0, onClick: () => onDecide(OUTCOMES[index]), onMouseEnter: () => setHoverIndex(index), onMouseLeave: () => setHoverIndex(current => (current === index ? -1 : current)), backgroundColor: hovered && !focused ? 'userMessageBackgroundHover' : undefined, children: [_jsx(Box, { width: 1, flexShrink: 0, children: _jsx(Text, { color: focused ? 'claude' : undefined, bold: focused, children: focused ? POINTER : ' ' }) }), _jsxs(Text, { bold: focused, color: focused ? 'claude' : undefined, wrap: "wrap", children: [index + 1, ". ", label] })] }, label));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssistantToolUseMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/messages/AssistantToolUseMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAKzB,OAAO,KAAK,EAA8C,OAAO,EAAE,MAAM,8BAA8B,CAAA;AAMvG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;
|
|
1
|
+
{"version":3,"file":"AssistantToolUseMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/messages/AssistantToolUseMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAKzB,OAAO,KAAK,EAA8C,OAAO,EAAE,MAAM,8BAA8B,CAAA;AAMvG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAIjE,KAAK,KAAK,GAAG;IACX,IAAI,EAAE,OAAO,CAAA;IACb,4DAA4D;IAC5D,SAAS,EAAE,OAAO,CAAA;IAClB,iEAAiE;IACjE,OAAO,EAAE,OAAO,CAAA;IAChB,wCAAwC;IACxC,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,sEAAsE;IACtE,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;OAIG;IACH,OAAO,CAAC,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAA;IACjC;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;IACzC,2EAA2E;IAC3E,cAAc,CAAC,EAAE,cAAc,CAAA;IAC/B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB;8CAC0C;IAC1C,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,qFAAqF;IACrF,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAsFD,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,KAAK,CAKtD;AA+OD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,EACtC,IAAI,EACJ,SAAS,EACT,OAAO,EACP,UAAkB,EAClB,UAAkB,EAClB,OAAO,EACP,QAAQ,EACR,UAAmB,EACnB,cAAuB,EACvB,UAAU,EACV,mBAA2B,EAC3B,YAAoB,EACpB,KAAa,EACb,aAAa,GACd,EAAE,KAAK,GAAG,KAAK,CAAC,SAAS,CAoOzB"}
|
|
@@ -9,9 +9,8 @@ import { SplitDiffView } from '../SplitDiffView.js';
|
|
|
9
9
|
import { SyntaxText } from '../SyntaxText.js';
|
|
10
10
|
import { useTooltip } from '../Tooltip.js';
|
|
11
11
|
import { formatDuration } from '../../cc/format.js';
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
const getNoRevealVersion = () => 0;
|
|
12
|
+
import { revealLinesOf, snapReveal } from '../smoothReveal.js';
|
|
13
|
+
import { useRevealVersion } from '../../hooks/useRevealVersion.js';
|
|
15
14
|
/** Tool display names: DSH emits lowercase tool ids (`bash`); Claude Code
|
|
16
15
|
* shows capitalized names (`Bash`). Map the common ones, fall back to the
|
|
17
16
|
* id with its first letter uppercased. */
|
|
@@ -276,7 +275,10 @@ export function AssistantToolUseMessage({ tool, addMargin, verbose, isSelected =
|
|
|
276
275
|
// MessageList owns the single production subscription and passes a version
|
|
277
276
|
// prop only to active reveal rows. Standalone consumers keep the fallback
|
|
278
277
|
// subscription so the component contract remains self-contained.
|
|
279
|
-
|
|
278
|
+
// DefaultLane on purpose (useRevealVersion): a useSyncExternalStore wakeup
|
|
279
|
+
// forces a SyncLane render per tick, and repeated sync commits ending with
|
|
280
|
+
// streaming work pending feed React's nested-update counter (error #185).
|
|
281
|
+
useRevealVersion(revealVersion === undefined);
|
|
280
282
|
const isRunning = tool.status === 'running';
|
|
281
283
|
const isError = tool.status === 'error';
|
|
282
284
|
const displayArgs = verbose ? tool.argsFull ?? tool.argsText : tool.argsText;
|
|
@@ -28,6 +28,10 @@ export type AskUserQuestionPanelProps = {
|
|
|
28
28
|
* (local wizards, e.g. /provider). Ignored when there are no options —
|
|
29
29
|
* a text-only question would otherwise be unanswerable. */
|
|
30
30
|
readonly hideCustomInput?: boolean;
|
|
31
|
+
/** Pre-checked option labels (multi-select) / default-focused option
|
|
32
|
+
* (single-select) shown on first display, before any saved draft — e.g.
|
|
33
|
+
* the models already enabled on a provider being edited. */
|
|
34
|
+
readonly defaultSelected?: readonly string[];
|
|
31
35
|
/** Presentation intent tag (rc.6): 'plan-review' switches to the
|
|
32
36
|
* decision-card layout; an intent never changes the protocol. */
|
|
33
37
|
readonly intent?: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AskUserQuestionPanel.d.ts","sourceRoot":"","sources":["../../../../src/components/questions/AskUserQuestionPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAA;AAMzB,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAStF,MAAM,MAAM,yBAAyB,GAAG;IACtC,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE;QACjB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;QACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;QACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;QACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC;YAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;QAC3F,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAA;QAC9B;;oEAE4D;QAC5D,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAA;QAClC;0EACkE;QAClE,QAAQ,CAAC,MAAM,CAAC,EAAE;YAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;YAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;SAAE,CAAA;KAC7E,CAAA;IACD,2DAA2D;IAC3D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,sDAAsD;IACtD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,iDAAiD;IACjD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,8EAA8E;IAC9E,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAA;IACrC,QAAQ,CAAC,QAAQ,EAAE,CAAC,SAAS,EAAE,iBAAiB,KAAK,IAAI,CAAA;IACzD,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;IAC7B,mEAAmE;IACnE,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAA;CACjD,CAAA;AAED,wBAAgB,oBAAoB,CAAC,EACnC,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,QAAQ,EACR,QAAQ,EACR,MAAM,GACP,EAAE,yBAAyB,GAAG,KAAK,CAAC,SAAS,
|
|
1
|
+
{"version":3,"file":"AskUserQuestionPanel.d.ts","sourceRoot":"","sources":["../../../../src/components/questions/AskUserQuestionPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAA;AAMzB,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAStF,MAAM,MAAM,yBAAyB,GAAG;IACtC,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE;QACjB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;QACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;QACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;QACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC;YAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;QAC3F,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAA;QAC9B;;oEAE4D;QAC5D,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAA;QAClC;;qEAE6D;QAC7D,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;QAC5C;0EACkE;QAClE,QAAQ,CAAC,MAAM,CAAC,EAAE;YAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;YAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;SAAE,CAAA;KAC7E,CAAA;IACD,2DAA2D;IAC3D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,sDAAsD;IACtD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,iDAAiD;IACjD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,8EAA8E;IAC9E,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAA;IACrC,QAAQ,CAAC,QAAQ,EAAE,CAAC,SAAS,EAAE,iBAAiB,KAAK,IAAI,CAAA;IACzD,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;IAC7B,mEAAmE;IACnE,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAA;CACjD,CAAA;AAED,wBAAgB,oBAAoB,CAAC,EACnC,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,QAAQ,EACR,QAAQ,EACR,MAAM,GACP,EAAE,yBAAyB,GAAG,KAAK,CAAC,SAAS,CAub7C"}
|
|
@@ -37,7 +37,12 @@ export function AskUserQuestionPanel({ question, position, total, answered, init
|
|
|
37
37
|
const { rows: terminalRows } = useTerminalSize();
|
|
38
38
|
/** Rows: the real options plus the inline input row at the tail. */
|
|
39
39
|
const rowCount = options.length + (hideCustomInput ? 0 : 1);
|
|
40
|
-
|
|
40
|
+
// A saved draft wins over the wizard's default selection (returning to a
|
|
41
|
+
// question must restore exactly what the user last had, including an
|
|
42
|
+
// explicit empty answer); the defaults only apply on first display.
|
|
43
|
+
const initialSelected = initialDraft !== undefined
|
|
44
|
+
? initialDraft.selected
|
|
45
|
+
: question.defaultSelected ?? [];
|
|
41
46
|
const initialCustom = initialDraft?.custom ?? '';
|
|
42
47
|
const selectedIndices = options
|
|
43
48
|
.map((option, index) => initialSelected.includes(option.label) ? index : -1)
|
|
@@ -1,43 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Smooth streaming reveal — decouples "text the provider has delivered" from
|
|
3
|
-
* "text the screen is showing" (the oh-my-pi `display.smoothStreaming` idea,
|
|
4
|
-
* see their `StreamingRevealController`). A shared ~30fps scheduler advances
|
|
5
|
-
* each reveal cursor by an adaptive catch-up step, so burst-delivered text (a
|
|
6
|
-
* slow provider dumping a large batch, or a non-streaming response arriving
|
|
7
|
-
* whole) paints as a brief even flow instead of one jarring jump, while
|
|
8
|
-
* genuinely streaming text is re-paced to a steady cadence regardless of
|
|
9
|
-
* chunk timing.
|
|
10
|
-
*
|
|
11
|
-
* Step math (per frame): `max(MIN_STEP, ceil(backlog / CATCHUP_FRAMES))` —
|
|
12
|
-
* an EXPONENTIAL decay over the backlog (each frame removes ~1/8 of what is
|
|
13
|
-
* still unrevealed, the oh-my-pi semantics exactly): a large burst lands its
|
|
14
|
-
* first three quarters within a few frames, the remainder tapers, and the
|
|
15
|
-
* MIN_STEP floor turns the tail into a steady linear crawl so the reveal
|
|
16
|
-
* always finishes. A ~2000-character one-shot delivery paints fully in
|
|
17
|
-
* roughly 1.5s; genuinely streaming text (small backlogs) paces at a steady
|
|
18
|
-
* per-frame rate. The reveal never runs ahead of what has arrived.
|
|
19
|
-
*
|
|
20
|
-
* ARCHITECTURE — module-level cursors, not component state. dsh-tui's
|
|
21
|
-
* transcript virtualization feeds on MemoRow prop changes (flattened
|
|
22
|
-
* primitives) and re-measures mounted rows after every MessageList commit;
|
|
23
|
-
* a reveal living in child-component state would change row heights without
|
|
24
|
-
* touching those props, and the memo/signature/height-measure chain would
|
|
25
|
-
* never notice (stale cached heights → blank bands). Instead the cursors
|
|
26
|
-
* live here keyed by row identity, the READS happen during render (they feed
|
|
27
|
-
* the same flattened `text` prop the streaming path already uses), and the
|
|
28
|
-
* WRITES happen on the scheduler tick, which bumps a version that
|
|
29
|
-
* MessageList subscribes to via useSyncExternalStore. A reveal therefore
|
|
30
|
-
* rides the exact same update pipeline as an arriving chunk: prop change →
|
|
31
|
-
* memo miss → re-render → post-commit measurement. Render-phase reads are
|
|
32
|
-
* idempotent and safe to re-run (a discarded concurrent render at worst
|
|
33
|
-
* creates a cursor the tick soon retires).
|
|
34
|
-
*
|
|
35
|
-
* Unit model: reveal positions are UTF-16 code units (Chinese text is one
|
|
36
|
-
* unit per character). Surrogate pairs are never split (see {@link
|
|
37
|
-
* safeSliceEnd}); grapheme clusters longer than a surrogate pair (emoji ZWJ
|
|
38
|
-
* chains) may briefly show a partial cluster mid-reveal — transient frames
|
|
39
|
-
* repaint within 33ms, and settled rendering is always the full text.
|
|
40
|
-
*/
|
|
41
1
|
/** Reveal tick cadence — ~30fps, matching oh-my-pi's controller. */
|
|
42
2
|
export declare const REVEAL_FRAME_MS: number;
|
|
43
3
|
/** Minimum units (code units or lines) advanced per tick while work remains. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"smoothReveal.d.ts","sourceRoot":"","sources":["../../../src/components/smoothReveal.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"smoothReveal.d.ts","sourceRoot":"","sources":["../../../src/components/smoothReveal.ts"],"names":[],"mappings":"AA0CA,oEAAoE;AACpE,eAAO,MAAM,eAAe,QAAY,CAAA;AACxC,gFAAgF;AAChF,eAAO,MAAM,eAAe,IAAI,CAAA;AAChC,8EAA8E;AAC9E,eAAO,MAAM,qBAAqB,IAAI,CAAA;AAEtC,kEAAkE;AAClE,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAElD;AAiID,oEAAoE;AACpE,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAKhE;AAED,wEAAwE;AACxE,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,4EAA4E;AAC5E,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAE5C;AAMD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,6EAA6E;IAC7E,OAAO,EAAE,OAAO,CAAA;IAChB;;;;;;;;;OASG;IACH,MAAM,EAAE,OAAO,CAAA;CAChB,CAAA;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAiC5F;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAI1F;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAqC5F;AAMD,wEAAwE;AACxE,wBAAgB,kBAAkB,IAAI,IAAI,CAUzC;AAED,yEAAyE;AACzE,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C"}
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
* chains) may briefly show a partial cluster mid-reveal — transient frames
|
|
39
39
|
* repaint within 33ms, and settled rendering is always the full text.
|
|
40
40
|
*/
|
|
41
|
+
import { registerOverflowQuench, swallowNestedUpdateOverflow } from '../ink/update-overflow-guard.js';
|
|
41
42
|
/** Reveal tick cadence — ~30fps, matching oh-my-pi's controller. */
|
|
42
43
|
export const REVEAL_FRAME_MS = 1000 / 30;
|
|
43
44
|
/** Minimum units (code units or lines) advanced per tick while work remains. */
|
|
@@ -108,6 +109,22 @@ function stopRevealTimerIfIdle() {
|
|
|
108
109
|
revealTimer = undefined;
|
|
109
110
|
}
|
|
110
111
|
}
|
|
112
|
+
// Circuit-breaker quench: a sustained #185 oscillation surfacing at the
|
|
113
|
+
// reveal tick pauses this scheduler for the backoff window (cursors freeze
|
|
114
|
+
// in place; the resume timer — or a later render reading an active cursor —
|
|
115
|
+
// restarts it). Absorbing forever would leave the process alive but laggy.
|
|
116
|
+
registerOverflowQuench('reveal.tick', ms => {
|
|
117
|
+
if (revealTimer !== undefined) {
|
|
118
|
+
clearInterval(revealTimer);
|
|
119
|
+
revealTimer = undefined;
|
|
120
|
+
const resume = setTimeout(() => {
|
|
121
|
+
if (revealTimer === undefined && (textCursors.size > 0 || countCursors.size > 0)) {
|
|
122
|
+
ensureRevealTimer();
|
|
123
|
+
}
|
|
124
|
+
}, ms);
|
|
125
|
+
resume.unref?.();
|
|
126
|
+
}
|
|
127
|
+
});
|
|
111
128
|
function revealTick() {
|
|
112
129
|
let advanced = false;
|
|
113
130
|
for (const [key, cursor] of textCursors) {
|
|
@@ -131,8 +148,17 @@ function revealTick() {
|
|
|
131
148
|
}
|
|
132
149
|
if (advanced) {
|
|
133
150
|
revealVersionValue += 1;
|
|
134
|
-
|
|
135
|
-
|
|
151
|
+
// #185 self-heal: a forceStoreRerender enqueue can surface the overflow
|
|
152
|
+
// here; React resets the counter on throw, so absorb and drop the tick.
|
|
153
|
+
for (const listener of [...revealListeners]) {
|
|
154
|
+
try {
|
|
155
|
+
listener();
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
if (!swallowNestedUpdateOverflow(error, 'reveal.tick'))
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
136
162
|
}
|
|
137
163
|
else {
|
|
138
164
|
stopRevealTimerIfIdle();
|