@linxiraos/pi-tui 1.1.26 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -491
- package/README.md +13 -7
- package/dist/types/active-terminal.d.ts +30 -0
- package/dist/types/app-keybindings.d.ts +7 -2
- package/dist/types/apps/cleanse-board.d.ts +1 -1
- package/dist/types/apps/debug/log-viewer.d.ts +4 -0
- package/dist/types/apps/debug/raw-sse-buffer.d.ts +2 -0
- package/dist/types/apps/git/git-tui.d.ts +5 -4
- package/dist/types/apps/git/help.d.ts +3 -1
- package/dist/types/apps/if-bench-board.d.ts +2 -2
- package/dist/types/apps/setup-model-picker.d.ts +1 -1
- package/dist/types/autocomplete.d.ts +5 -2
- package/dist/types/charts/chart-plan.d.ts +674 -0
- package/dist/types/charts/chart-svg.d.ts +59 -0
- package/dist/types/charts/table-data.d.ts +175 -0
- package/dist/types/chat/assistant-message.d.ts +11 -1
- package/dist/types/chat/chat-transcript-builder.d.ts +3 -0
- package/dist/types/chat/compaction-summary-message.d.ts +1 -0
- package/dist/types/chat/execution-shared.d.ts +6 -5
- package/dist/types/chat/fence-figure.d.ts +42 -0
- package/dist/types/chat/figure-markdown.d.ts +42 -0
- package/dist/types/chat/messages.d.ts +1 -1
- package/dist/types/chat/svg-figure.d.ts +33 -0
- package/dist/types/chat/svg-source.d.ts +49 -0
- package/dist/types/chat/table-chart.d.ts +89 -0
- package/dist/types/chat/tool-execution.d.ts +2 -0
- package/dist/types/chat/user-message.d.ts +17 -4
- package/dist/types/chrome/shared.d.ts +9 -0
- package/dist/types/chrome/status-notice.d.ts +15 -3
- package/dist/types/chrome/transcript-container.d.ts +6 -0
- package/dist/types/components/box.d.ts +1 -0
- package/dist/types/components/disclosure.d.ts +1 -0
- package/dist/types/components/editor.d.ts +23 -5
- package/dist/types/components/form.d.ts +1 -0
- package/dist/types/components/image.d.ts +26 -44
- package/dist/types/components/input.d.ts +2 -1
- package/dist/types/components/key-value-list.d.ts +1 -0
- package/dist/types/components/layout/row.d.ts +1 -0
- package/dist/types/components/layout/stack.d.ts +1 -0
- package/dist/types/components/loader.d.ts +5 -6
- package/dist/types/components/markdown.d.ts +47 -5
- package/dist/types/components/scroll-view.d.ts +1 -0
- package/dist/types/components/section.d.ts +1 -0
- package/dist/types/components/table.d.ts +1 -0
- package/dist/types/components/text.d.ts +1 -0
- package/dist/types/components/tree-view.d.ts +7 -0
- package/dist/types/components/truncated-text.d.ts +1 -0
- package/dist/types/fuzzy.d.ts +29 -1
- package/dist/types/key-hint-format.d.ts +9 -1
- package/dist/types/keybinding-matchers.d.ts +3 -0
- package/dist/types/keybindings.d.ts +1 -1
- package/dist/types/kitty-graphics.d.ts +2 -2
- package/dist/types/loop-watchdog.d.ts +8 -7
- package/dist/types/multiplexers/cmux.d.ts +16 -0
- package/dist/types/multiplexers/herdr.d.ts +26 -0
- package/dist/types/multiplexers/notify.d.ts +13 -0
- package/dist/types/multiplexers/orca.d.ts +12 -0
- package/dist/types/multiplexers/screen.d.ts +9 -0
- package/dist/types/multiplexers/tmux.d.ts +29 -0
- package/dist/types/multiplexers/types.d.ts +100 -0
- package/dist/types/multiplexers/wmux.d.ts +12 -0
- package/dist/types/multiplexers/zellij.d.ts +12 -0
- package/dist/types/native/apply.d.ts +4 -3
- package/dist/types/native/backend.d.ts +5 -4
- package/dist/types/native/blobs.d.ts +7 -3
- package/dist/types/native/encode.d.ts +8 -6
- package/dist/types/native/node.d.ts +36 -5
- package/dist/types/native/overlay.d.ts +8 -3
- package/dist/types/native/settle.d.ts +4 -3
- package/dist/types/overlays/agent-hub-projection.d.ts +6 -5
- package/dist/types/overlays/agent-hub-types.d.ts +9 -0
- package/dist/types/overlays/agent-hub.d.ts +3 -0
- package/dist/types/overlays/agent-transcript-viewer.d.ts +12 -2
- package/dist/types/overlays/agents-hub.d.ts +9 -0
- package/dist/types/overlays/annotation-overlay.d.ts +19 -2
- package/dist/types/overlays/annotation-types.d.ts +2 -0
- package/dist/types/overlays/ask-dialog.d.ts +5 -2
- package/dist/types/overlays/btw-history-panel.d.ts +13 -1
- package/dist/types/overlays/copy-selector.d.ts +1 -0
- package/dist/types/overlays/history-search.d.ts +10 -0
- package/dist/types/overlays/hook-selector.d.ts +9 -4
- package/dist/types/overlays/hub-frame.d.ts +4 -1
- package/dist/types/overlays/jobs-panel.d.ts +1 -7
- package/dist/types/overlays/model-browser.d.ts +54 -7
- package/dist/types/overlays/model-hub.d.ts +37 -0
- package/dist/types/overlays/oauth-selector.d.ts +0 -1
- package/dist/types/overlays/plan-review-overlay.d.ts +3 -2
- package/dist/types/overlays/report-panel.d.ts +68 -0
- package/dist/types/overlays/rewind-selector.d.ts +1 -0
- package/dist/types/overlays/session-observer-registry.d.ts +2 -0
- package/dist/types/overlays/session-selector.d.ts +6 -2
- package/dist/types/overlays/settings-defs.d.ts +1 -0
- package/dist/types/overlays/thinking-selector.d.ts +2 -2
- package/dist/types/overlays/usage-dashboard.d.ts +12 -8
- package/dist/types/overlays/usage-display.d.ts +3 -1
- package/dist/types/prompt/composer-cache.d.ts +0 -16
- package/dist/types/prompt/composer.d.ts +18 -3
- package/dist/types/prompt/custom-editor.d.ts +25 -13
- package/dist/types/prompt/image-source.d.ts +2 -1
- package/dist/types/prompt/interactive-context-helpers.d.ts +5 -1
- package/dist/types/prompt/model-mention-autocomplete.d.ts +1 -1
- package/dist/types/prompt/welcome.d.ts +7 -2
- package/dist/types/render/composite.d.ts +2 -0
- package/dist/types/render/output-pane.d.ts +1 -0
- package/dist/types/render/render-utils.d.ts +1 -0
- package/dist/types/render/tool-card.d.ts +6 -0
- package/dist/types/render/width-aware-text.d.ts +2 -0
- package/dist/types/setup/scenes/types.d.ts +1 -0
- package/dist/types/space-hold.d.ts +40 -25
- package/dist/types/status-line/component.d.ts +3 -0
- package/dist/types/status-line/context-usage.d.ts +7 -5
- package/dist/types/status-line/host.d.ts +4 -1
- package/dist/types/status-line/segments.d.ts +2 -0
- package/dist/types/status-line/types.d.ts +14 -3
- package/dist/types/terminal-capabilities.d.ts +41 -36
- package/dist/types/terminal-multiplexer.d.ts +127 -14
- package/dist/types/terminal.d.ts +23 -16
- package/dist/types/theme/active-symbols.d.ts +1 -1
- package/dist/types/theme/schema.d.ts +11 -2
- package/dist/types/theme/symbols.d.ts +1 -1
- package/dist/types/theme/theme-class.d.ts +7 -0
- package/dist/types/theme/theme.d.ts +1 -1
- package/dist/types/theme/tui-adapters.d.ts +3 -0
- package/dist/types/thinking.d.ts +1 -1
- package/dist/types/tmux.d.ts +11 -4
- package/dist/types/tools/bash.d.ts +7 -2
- package/dist/types/tools/daemon.d.ts +1 -1
- package/dist/types/tools/eval.d.ts +22 -3
- package/dist/types/tools/glob.d.ts +4 -0
- package/dist/types/tools/native-view.d.ts +47 -0
- package/dist/types/tools/output-meta.d.ts +11 -1
- package/dist/types/tools/renderer.d.ts +37 -2
- package/dist/types/tools/streaming-output.d.ts +20 -1
- package/dist/types/tools/task.d.ts +2 -3
- package/dist/types/tools/write.d.ts +9 -2
- package/dist/types/tools/yield.d.ts +22 -0
- package/dist/types/tui.d.ts +36 -0
- package/dist/types/utils.d.ts +6 -0
- package/dist/types/vim.d.ts +19 -5
- package/dist/types/windows-input-mode.d.ts +33 -1
- package/package.json +13 -9
- package/src/active-terminal.d.ts +30 -0
- package/src/active-terminal.ts +80 -0
- package/src/app-keybindings.d.ts +7 -2
- package/src/app-keybindings.ts +7 -2
- package/src/apps/autoresearch-dashboard.ts +7 -3
- package/src/apps/cleanse-board.ts +2 -2
- package/src/apps/cleanse-picker.ts +1 -1
- package/src/apps/debug/log-viewer.ts +188 -72
- package/src/apps/debug/raw-sse-buffer.ts +8 -0
- package/src/apps/debug/raw-sse.ts +39 -5
- package/src/apps/git/diff-pane.ts +96 -23
- package/src/apps/git/git-tui.ts +23 -16
- package/src/apps/git/help.ts +29 -20
- package/src/apps/git/sidebar.ts +8 -3
- package/src/apps/if-bench-board.ts +2 -2
- package/src/apps/ps-top.ts +2 -2
- package/src/apps/setup-model-picker.ts +1 -1
- package/src/autocomplete.d.ts +5 -2
- package/src/autocomplete.ts +67 -43
- package/src/bracketed-paste.ts +10 -3
- package/src/charts/chart-plan.d.ts +674 -0
- package/src/charts/chart-plan.ts +5081 -0
- package/src/charts/chart-svg.d.ts +59 -0
- package/src/charts/chart-svg.ts +4657 -0
- package/src/charts/table-data.d.ts +175 -0
- package/src/charts/table-data.ts +1478 -0
- package/src/chat/advisor-message.ts +11 -0
- package/src/chat/assistant-message.d.ts +11 -1
- package/src/chat/assistant-message.ts +245 -54
- package/src/chat/chat-transcript-builder.d.ts +3 -0
- package/src/chat/chat-transcript-builder.ts +9 -3
- package/src/chat/compaction-summary-message.d.ts +1 -0
- package/src/chat/compaction-summary-message.ts +8 -0
- package/src/chat/execution-shared.d.ts +6 -5
- package/src/chat/execution-shared.ts +42 -25
- package/src/chat/fence-figure.d.ts +42 -0
- package/src/chat/fence-figure.ts +80 -0
- package/src/chat/figure-markdown.d.ts +42 -0
- package/src/chat/figure-markdown.ts +174 -0
- package/src/chat/messages.d.ts +1 -1
- package/src/chat/messages.ts +1 -1
- package/src/chat/read-tool-group.ts +6 -4
- package/src/chat/skill-message.ts +6 -0
- package/src/chat/svg-figure.d.ts +33 -0
- package/src/chat/svg-figure.ts +399 -0
- package/src/chat/svg-source.d.ts +49 -0
- package/src/chat/svg-source.ts +188 -0
- package/src/chat/table-chart.d.ts +89 -0
- package/src/chat/table-chart.ts +294 -0
- package/src/chat/tool-execution.d.ts +2 -0
- package/src/chat/tool-execution.ts +86 -7
- package/src/chat/user-message.d.ts +17 -4
- package/src/chat/user-message.ts +71 -8
- package/src/chrome/shared.ts +53 -0
- package/src/chrome/status-notice.ts +25 -7
- package/src/chrome/transcript-container.d.ts +6 -0
- package/src/chrome/transcript-container.ts +162 -19
- package/src/components/box.d.ts +1 -0
- package/src/components/box.ts +5 -0
- package/src/components/disclosure.d.ts +1 -0
- package/src/components/disclosure.ts +8 -0
- package/src/components/editor.d.ts +23 -5
- package/src/components/editor.ts +316 -47
- package/src/components/form.d.ts +1 -0
- package/src/components/form.ts +8 -0
- package/src/components/image.d.ts +26 -44
- package/src/components/image.ts +115 -152
- package/src/components/input.d.ts +2 -1
- package/src/components/input.ts +17 -7
- package/src/components/key-value-list.d.ts +1 -0
- package/src/components/key-value-list.ts +4 -0
- package/src/components/layout/row.d.ts +1 -0
- package/src/components/layout/row.ts +7 -0
- package/src/components/layout/stack.d.ts +1 -0
- package/src/components/layout/stack.ts +7 -0
- package/src/components/loader.d.ts +5 -6
- package/src/components/loader.ts +10 -17
- package/src/components/markdown.d.ts +47 -5
- package/src/components/markdown.ts +786 -303
- package/src/components/scroll-view.d.ts +1 -0
- package/src/components/scroll-view.ts +5 -0
- package/src/components/section.d.ts +1 -0
- package/src/components/section.ts +5 -0
- package/src/components/settings-list.ts +2 -0
- package/src/components/table.d.ts +1 -0
- package/src/components/table.ts +4 -0
- package/src/components/text.d.ts +1 -0
- package/src/components/text.ts +7 -0
- package/src/components/tree-view.d.ts +7 -0
- package/src/components/tree-view.ts +38 -8
- package/src/components/truncated-text.d.ts +1 -0
- package/src/components/truncated-text.ts +5 -0
- package/src/desktop-notify.ts +3 -2
- package/src/fuzzy.d.ts +29 -1
- package/src/fuzzy.ts +115 -30
- package/src/hotkeys-markdown.ts +9 -2
- package/src/key-hint-format.d.ts +9 -1
- package/src/key-hint-format.ts +26 -11
- package/src/keybinding-matchers.d.ts +3 -0
- package/src/keybinding-matchers.ts +6 -2
- package/src/keybindings.d.ts +1 -1
- package/src/keybindings.ts +1 -1
- package/src/kitty-graphics.d.ts +2 -2
- package/src/kitty-graphics.ts +11 -5
- package/src/loop-watchdog.d.ts +8 -7
- package/src/loop-watchdog.ts +18 -13
- package/src/multiplexers/cmux.d.ts +16 -0
- package/src/multiplexers/cmux.ts +29 -0
- package/src/multiplexers/herdr.d.ts +26 -0
- package/src/multiplexers/herdr.ts +64 -0
- package/src/multiplexers/notify.d.ts +13 -0
- package/src/multiplexers/notify.ts +24 -0
- package/src/multiplexers/orca.d.ts +12 -0
- package/src/multiplexers/orca.ts +16 -0
- package/src/multiplexers/screen.d.ts +9 -0
- package/src/multiplexers/screen.ts +15 -0
- package/src/multiplexers/tmux.d.ts +29 -0
- package/src/multiplexers/tmux.ts +40 -0
- package/src/multiplexers/types.d.ts +100 -0
- package/src/multiplexers/types.ts +110 -0
- package/src/multiplexers/wmux.d.ts +12 -0
- package/src/multiplexers/wmux.ts +16 -0
- package/src/multiplexers/zellij.d.ts +12 -0
- package/src/multiplexers/zellij.ts +24 -0
- package/src/native/apply.d.ts +4 -3
- package/src/native/apply.ts +4 -3
- package/src/native/backend.d.ts +5 -4
- package/src/native/backend.ts +199 -22
- package/src/native/blobs.d.ts +7 -3
- package/src/native/blobs.ts +47 -12
- package/src/native/encode.d.ts +8 -6
- package/src/native/encode.ts +76 -34
- package/src/native/node.d.ts +36 -5
- package/src/native/node.ts +31 -5
- package/src/native/overlay.d.ts +8 -3
- package/src/native/overlay.ts +17 -4
- package/src/native/reconcile.ts +16 -4
- package/src/native/settle.d.ts +4 -3
- package/src/native/settle.ts +4 -3
- package/src/overlays/agent-hub-projection.d.ts +6 -5
- package/src/overlays/agent-hub-projection.ts +124 -13
- package/src/overlays/agent-hub-types.d.ts +9 -0
- package/src/overlays/agent-hub-types.ts +6 -0
- package/src/overlays/agent-hub.ts +48 -29
- package/src/overlays/agent-transcript-viewer.ts +93 -23
- package/src/overlays/agents-hub.ts +28 -8
- package/src/overlays/annotation-overlay.ts +293 -90
- package/src/overlays/annotation-types.ts +2 -0
- package/src/overlays/ask-dialog.ts +10 -17
- package/src/overlays/btw-history-panel.ts +136 -39
- package/src/overlays/copy-selector.d.ts +1 -0
- package/src/overlays/copy-selector.ts +32 -8
- package/src/overlays/extensions/extension-dashboard.ts +4 -4
- package/src/overlays/extensions/state-manager.ts +52 -12
- package/src/overlays/history-search.ts +100 -7
- package/src/overlays/hook-selector.ts +12 -5
- package/src/overlays/hub-frame.ts +19 -1
- package/src/overlays/jobs-panel.ts +33 -52
- package/src/overlays/model-browser.ts +367 -122
- package/src/overlays/model-hub.ts +435 -59
- package/src/overlays/move-overlay.ts +28 -14
- package/src/overlays/oauth-selector.ts +54 -70
- package/src/overlays/pause-screen.ts +2 -2
- package/src/overlays/plan-review-overlay.ts +208 -70
- package/src/overlays/plugin-settings.ts +3 -3
- package/src/overlays/report-panel.ts +191 -0
- package/src/overlays/rewind-selector.ts +88 -17
- package/src/overlays/session-observer-registry.d.ts +2 -0
- package/src/overlays/session-observer-registry.ts +10 -2
- package/src/overlays/session-selector.ts +64 -26
- package/src/overlays/settings-defs.ts +5 -4
- package/src/overlays/settings-selector.ts +33 -8
- package/src/overlays/snapcompact-shape-preview.ts +2 -3
- package/src/overlays/stats-notice.ts +1 -1
- package/src/overlays/thinking-selector.ts +11 -10
- package/src/overlays/tree-selector.ts +41 -20
- package/src/overlays/usage-dashboard.ts +117 -58
- package/src/overlays/usage-display.ts +10 -4
- package/src/overlays/usage-row.ts +22 -8
- package/src/prompt/attachment-chips.ts +15 -11
- package/src/prompt/composer-attachments.ts +6 -4
- package/src/prompt/composer-cache.ts +7 -86
- package/src/prompt/composer.ts +37 -26
- package/src/prompt/custom-editor.ts +282 -105
- package/src/prompt/image-source.ts +2 -1
- package/src/prompt/interactive-context-helpers.ts +32 -18
- package/src/prompt/macos-spelling.ts +7 -0
- package/src/prompt/magic-keywords.ts +11 -2
- package/src/prompt/model-mention-autocomplete.ts +14 -14
- package/src/prompt/queued-messages.ts +6 -4
- package/src/prompt/tips.txt +13 -9
- package/src/prompt/welcome.ts +64 -46
- package/src/prompt/word-completion.ts +38 -5
- package/src/render/composite.d.ts +2 -0
- package/src/render/composite.ts +64 -0
- package/src/render/output-pane.d.ts +1 -0
- package/src/render/output-pane.ts +6 -0
- package/src/render/render-utils.d.ts +1 -0
- package/src/render/render-utils.ts +16 -1
- package/src/render/tool-card.d.ts +6 -0
- package/src/render/tool-card.ts +15 -0
- package/src/render/width-aware-text.d.ts +2 -0
- package/src/render/width-aware-text.ts +5 -0
- package/src/setup/scenes/model.ts +2 -1
- package/src/setup/scenes/types.ts +1 -0
- package/src/space-hold.d.ts +40 -25
- package/src/space-hold.ts +156 -52
- package/src/status-line/component.ts +292 -78
- package/src/status-line/context-usage.ts +21 -46
- package/src/status-line/host.ts +4 -1
- package/src/status-line/segments.ts +40 -22
- package/src/status-line/types.ts +15 -3
- package/src/terminal-capabilities.d.ts +41 -36
- package/src/terminal-capabilities.ts +137 -213
- package/src/terminal-multiplexer.d.ts +127 -14
- package/src/terminal-multiplexer.ts +112 -33
- package/src/terminal.d.ts +23 -16
- package/src/terminal.ts +203 -99
- package/src/theme/active-symbols.d.ts +1 -1
- package/src/theme/active-symbols.ts +1 -1
- package/src/theme/schema-validation.ts +1 -0
- package/src/theme/schema.d.ts +11 -2
- package/src/theme/schema.ts +14 -1
- package/src/theme/session-color.ts +1 -7
- package/src/theme/symbols.d.ts +1 -1
- package/src/theme/symbols.ts +11 -4
- package/src/theme/theme-class.d.ts +7 -0
- package/src/theme/theme-class.ts +26 -1
- package/src/theme/theme-schema.json +5 -1
- package/src/theme/theme.d.ts +1 -1
- package/src/theme/theme.ts +8 -4
- package/src/theme/tui-adapters.d.ts +3 -0
- package/src/theme/tui-adapters.ts +68 -0
- package/src/thinking.d.ts +1 -1
- package/src/thinking.ts +1 -1
- package/src/tmux.d.ts +11 -4
- package/src/tmux.ts +47 -11
- package/src/tools/ast-edit.ts +59 -35
- package/src/tools/ast-grep.ts +35 -16
- package/src/tools/bash.d.ts +7 -2
- package/src/tools/bash.ts +116 -47
- package/src/tools/daemon.d.ts +1 -1
- package/src/tools/daemon.ts +1 -1
- package/src/tools/default-renderer.ts +35 -19
- package/src/tools/edit.ts +142 -120
- package/src/tools/eval-format/index.ts +28 -2
- package/src/tools/eval-format/javascript.ts +7 -5
- package/src/tools/eval.d.ts +22 -3
- package/src/tools/eval.ts +364 -208
- package/src/tools/fetch.ts +5 -6
- package/src/tools/glob.d.ts +4 -0
- package/src/tools/glob.ts +33 -65
- package/src/tools/grep.ts +81 -48
- package/src/tools/index.ts +2 -0
- package/src/tools/lsp.ts +81 -59
- package/src/tools/mcp.ts +111 -68
- package/src/tools/native-view.d.ts +47 -0
- package/src/tools/native-view.ts +102 -1
- package/src/tools/output-meta.d.ts +11 -1
- package/src/tools/output-meta.ts +23 -3
- package/src/tools/read.ts +7 -5
- package/src/tools/renderer.d.ts +37 -2
- package/src/tools/renderer.ts +42 -2
- package/src/tools/streaming-output.d.ts +20 -1
- package/src/tools/streaming-output.ts +154 -50
- package/src/tools/task.d.ts +2 -3
- package/src/tools/task.ts +115 -62
- package/src/tools/todo.ts +119 -30
- package/src/tools/wait.ts +34 -24
- package/src/tools/web-search.ts +1 -1
- package/src/tools/write.d.ts +9 -2
- package/src/tools/write.ts +147 -95
- package/src/tools/yield.d.ts +22 -0
- package/src/tools/yield.ts +225 -0
- package/src/tui.d.ts +36 -0
- package/src/tui.ts +666 -217
- package/src/utils.d.ts +6 -0
- package/src/utils.ts +19 -0
- package/src/vim.d.ts +19 -5
- package/src/vim.ts +333 -18
- package/src/windows-input-mode.d.ts +33 -1
- package/src/windows-input-mode.ts +212 -6
package/src/chat/user-message.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { applyBackgroundToLine, padding, visibleWidth } from "../utils";
|
|
1
|
+
import { applyBackgroundToLine, padding, stripTerminalSequences, visibleWidth } from "../utils";
|
|
2
|
+
import type { AnimationFrame, TranscriptPresentationTarget } from "../chrome/transcript-container";
|
|
2
3
|
import { type Component, Container } from "../tui";
|
|
3
4
|
import { Disclosure } from "../components/disclosure";
|
|
4
5
|
import { Markdown } from "../components/markdown";
|
|
@@ -24,7 +25,13 @@ import type { ReactionTarget } from "./reaction";
|
|
|
24
25
|
import { card, md, node, row, span, text } from "../native/describe";
|
|
25
26
|
import { base64ImageNode } from "../native/blobs";
|
|
26
27
|
import { hasTranscriptActions, runTranscriptAction } from "./transcript-actions";
|
|
27
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
type DescribeContext,
|
|
30
|
+
type NativeChild,
|
|
31
|
+
type NativeNode,
|
|
32
|
+
type NativeUiEvent,
|
|
33
|
+
rootToggleExpanded,
|
|
34
|
+
} from "../native/node";
|
|
28
35
|
import { Memo } from "../native/memo";
|
|
29
36
|
|
|
30
37
|
// OSC 133 shell integration: marks prompt zones for terminal multiplexers.
|
|
@@ -51,7 +58,7 @@ const OSC133_ZONE_CLOSE = OSC133_ZONE_END + OSC133_COMMAND_START + OSC133_COMMAN
|
|
|
51
58
|
|
|
52
59
|
/** How a user bubble styles its prose and chips (see {@link userBubbleColor}). */
|
|
53
60
|
export interface UserBubbleOptions {
|
|
54
|
-
/**
|
|
61
|
+
/** Filesystem paths for attached image chips, indexed by chip number. */
|
|
55
62
|
imageLinks?: readonly (string | undefined)[];
|
|
56
63
|
/** The message's attached images in chip order (`#1` first); a native bubble shows them. */
|
|
57
64
|
images?: readonly ImageContent[];
|
|
@@ -118,7 +125,8 @@ export function userBubbleColor(
|
|
|
118
125
|
* (see {@link ReactionTarget}) drawn right-aligned in the bubble's top padding row;
|
|
119
126
|
* a live-steered message carries a `*` marker left-aligned in the same row.
|
|
120
127
|
*/
|
|
121
|
-
export class UserMessageComponent extends Container implements ReactionTarget {
|
|
128
|
+
export class UserMessageComponent extends Container implements ReactionTarget, TranscriptPresentationTarget {
|
|
129
|
+
#allocation = Number.POSITIVE_INFINITY;
|
|
122
130
|
// Memoized OSC 133 zone wrapping keyed on the underlying container render
|
|
123
131
|
// (same source ref ⇒ identical rows ⇒ reuse the wrapped copy). Keeps this
|
|
124
132
|
// component reference-stable for the transcript's incremental assembly and
|
|
@@ -137,6 +145,8 @@ export class UserMessageComponent extends Container implements ReactionTarget {
|
|
|
137
145
|
readonly #tokens: RegExp;
|
|
138
146
|
#reaction: string | undefined;
|
|
139
147
|
#native: NativeNode | undefined;
|
|
148
|
+
/** The terminal's clock {@link #native} was described with. */
|
|
149
|
+
#nativeHour12: boolean | undefined;
|
|
140
150
|
|
|
141
151
|
constructor(text: string, options: UserBubbleOptions = {}) {
|
|
142
152
|
super();
|
|
@@ -169,6 +179,12 @@ export class UserMessageComponent extends Container implements ReactionTarget {
|
|
|
169
179
|
this.addChild(markdown);
|
|
170
180
|
}
|
|
171
181
|
|
|
182
|
+
override releaseRenderCaches(): void {
|
|
183
|
+
this.#zoneSource = undefined;
|
|
184
|
+
this.#zoneLines = undefined;
|
|
185
|
+
super.releaseRenderCaches();
|
|
186
|
+
}
|
|
187
|
+
|
|
172
188
|
setReaction(emoji: string): void {
|
|
173
189
|
if (this.#reaction === emoji) return;
|
|
174
190
|
this.#reaction = emoji;
|
|
@@ -184,17 +200,20 @@ export class UserMessageComponent extends Container implements ReactionTarget {
|
|
|
184
200
|
* agent's reaction are chips at the bottom-right, so a reaction landing
|
|
185
201
|
* later updates the frame in place, even deep in scrollback.
|
|
186
202
|
*/
|
|
187
|
-
override describe(): NativeNode {
|
|
188
|
-
|
|
203
|
+
override describe(cx?: DescribeContext): NativeNode {
|
|
204
|
+
// The terminal's clock: Bun's own default locale ignores the user's.
|
|
205
|
+
const hour12 = cx?.hour12;
|
|
206
|
+
if (this.#native && this.#nativeHour12 === hour12) return this.#native;
|
|
207
|
+
this.#nativeHour12 = hour12;
|
|
189
208
|
const children: NativeChild[] = [];
|
|
190
209
|
if (!this.#synthetic && hasTranscriptActions()) {
|
|
191
210
|
const tools: NativeChild[] = [];
|
|
192
211
|
if (this.#timestamp !== undefined) {
|
|
193
212
|
const at = new Date(this.#timestamp);
|
|
194
213
|
tools.push(
|
|
195
|
-
text([span(at.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }), "dim mono")], {
|
|
214
|
+
text([span(at.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", hour12 }), "dim mono")], {
|
|
196
215
|
role: "zeta.user.time",
|
|
197
|
-
title: at.toLocaleString(),
|
|
216
|
+
title: at.toLocaleString([], { hour12 }),
|
|
198
217
|
}),
|
|
199
218
|
);
|
|
200
219
|
}
|
|
@@ -270,11 +289,47 @@ export class UserMessageComponent extends Container implements ReactionTarget {
|
|
|
270
289
|
return applyBackgroundToLine(` ${marker}${padding(gap)}${emoji}`, width, this.#bgColor);
|
|
271
290
|
}
|
|
272
291
|
|
|
292
|
+
/** Apply the transcript allocator's current viewport reservation. */
|
|
293
|
+
setTranscriptAllocation(rows: number, _frame?: AnimationFrame): void {
|
|
294
|
+
this.#allocation = Math.max(0, Math.trunc(rows));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
isTranscriptBlockFinalized(): boolean {
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Single-row emergency representation for the overflow transcript layout:
|
|
303
|
+
* renders the first content line instead of the blank top padding row, and
|
|
304
|
+
* omits OSC 133 shell integration markers so moving live blocks across the
|
|
305
|
+
* viewport does not leave stale prompt marks on lines it no longer occupies.
|
|
306
|
+
*/
|
|
307
|
+
renderTranscriptBlockEmergencyRow(width: number): string | undefined {
|
|
308
|
+
return this.#selectEmergencyRow(super.render(width));
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
#selectEmergencyRow(lines: readonly string[]): string | undefined {
|
|
312
|
+
if (lines.length === 0) return undefined;
|
|
313
|
+
if (lines.length >= 3) {
|
|
314
|
+
for (let i = 1; i < lines.length - 1; i++) {
|
|
315
|
+
if (stripTerminalSequences(lines[i]!).trim().length > 0) {
|
|
316
|
+
return lines[i];
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return lines[1];
|
|
320
|
+
}
|
|
321
|
+
return lines[0];
|
|
322
|
+
}
|
|
323
|
+
|
|
273
324
|
override render(width: number): readonly string[] {
|
|
274
325
|
const lines = super.render(width);
|
|
275
326
|
if (lines.length === 0) {
|
|
276
327
|
return lines;
|
|
277
328
|
}
|
|
329
|
+
if (this.#allocation === 1) {
|
|
330
|
+
const emergency = this.#selectEmergencyRow(lines);
|
|
331
|
+
if (emergency !== undefined) return [emergency];
|
|
332
|
+
}
|
|
278
333
|
if (this.#zoneSource === lines && this.#zoneLines !== undefined) {
|
|
279
334
|
return this.#zoneLines;
|
|
280
335
|
}
|
|
@@ -326,6 +381,10 @@ class SyntheticSummary implements Component {
|
|
|
326
381
|
this.#summary = summary;
|
|
327
382
|
}
|
|
328
383
|
|
|
384
|
+
releaseRenderCaches(): void {
|
|
385
|
+
this.#cache = undefined;
|
|
386
|
+
}
|
|
387
|
+
|
|
329
388
|
invalidate(): void {
|
|
330
389
|
this.#cache = undefined;
|
|
331
390
|
}
|
|
@@ -414,6 +473,10 @@ export class CollapsedSyntheticMessageComponent implements Component {
|
|
|
414
473
|
this.#disclosure.invalidate();
|
|
415
474
|
}
|
|
416
475
|
|
|
476
|
+
releaseRenderCaches(): void {
|
|
477
|
+
this.#disclosure.releaseRenderCaches();
|
|
478
|
+
}
|
|
479
|
+
|
|
417
480
|
dispose(): void {
|
|
418
481
|
this.#disclosure.dispose();
|
|
419
482
|
}
|
package/src/chrome/shared.ts
CHANGED
|
@@ -10,6 +10,59 @@ export function sanitizeStatusText(text: string): string {
|
|
|
10
10
|
return sanitizeDisplaySingleLine(text).replace(/ +/g, " ").trim();
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/** 7-bit SGR (`ESC [ <params> m`) — the one escape family a hook status may keep. */
|
|
14
|
+
const SGR_RE = /\x1b\[[0-9;:]*m/g;
|
|
15
|
+
const SGR_RESET = "\x1b[0m";
|
|
16
|
+
const WHITESPACE_RE = /\s/;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Like {@link sanitizeStatusText}, but keeps 7-bit SGR so extensions can colour
|
|
20
|
+
* `ctx.ui.setStatus` text. Every other escape (OSC links/titles, DCS/PM/APC,
|
|
21
|
+
* 7- and 8-bit CSI), C0/C1, lone surrogates, newlines and tabs are sanitized at
|
|
22
|
+
* least as strictly as in `sanitizeStatusText`; space runs collapse and edges
|
|
23
|
+
* trim (`String#trim` whitespace) by visible text, regardless of SGR placement.
|
|
24
|
+
* A styled result always ends with `\x1b[0m`.
|
|
25
|
+
*/
|
|
26
|
+
export function sanitizeHookStatusText(input: string): string {
|
|
27
|
+
// Drop lone surrogates before splitting, as `sanitizeText` does, so stripped escapes
|
|
28
|
+
// cannot fuse surrogate halves from neighbouring chunks into a new character.
|
|
29
|
+
const wellFormed = input.toWellFormed();
|
|
30
|
+
const text = wellFormed === input ? input : wellFormed.replaceAll("\ufffd", "");
|
|
31
|
+
SGR_RE.lastIndex = 0;
|
|
32
|
+
if (!SGR_RE.test(text)) return sanitizeStatusText(text); // byte-identical for unstyled input
|
|
33
|
+
let out = "";
|
|
34
|
+
// SGR codes and whitespace seen since the last visible char, in source order.
|
|
35
|
+
let pending = "";
|
|
36
|
+
let lastWasSpace = false;
|
|
37
|
+
let hasVisible = false;
|
|
38
|
+
let cursor = 0;
|
|
39
|
+
SGR_RE.lastIndex = 0;
|
|
40
|
+
for (;;) {
|
|
41
|
+
const match = SGR_RE.exec(text);
|
|
42
|
+
// `sanitizeText` runs `Bun.stripANSI` only when an ESC is present; a chunk between SGR
|
|
43
|
+
// codes often has none, so strip explicitly to drop 8-bit (C1) sequence payloads.
|
|
44
|
+
const chunk = sanitizeDisplaySingleLine(Bun.stripANSI(text.slice(cursor, match ? match.index : text.length)));
|
|
45
|
+
for (const ch of chunk) {
|
|
46
|
+
if (WHITESPACE_RE.test(ch)) {
|
|
47
|
+
// Leading whitespace drops; ASCII space runs collapse even across SGR codes.
|
|
48
|
+
if (hasVisible && !(ch === " " && lastWasSpace)) pending += ch;
|
|
49
|
+
lastWasSpace = ch === " ";
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
out += pending + ch;
|
|
53
|
+
pending = "";
|
|
54
|
+
lastWasSpace = false;
|
|
55
|
+
hasVisible = true;
|
|
56
|
+
}
|
|
57
|
+
if (!match) break;
|
|
58
|
+
pending += match[0];
|
|
59
|
+
cursor = match.index + match[0].length;
|
|
60
|
+
}
|
|
61
|
+
if (!hasVisible) return "";
|
|
62
|
+
// Trailing whitespace trims; trailing SGR codes stay (they never contain whitespace).
|
|
63
|
+
return out + pending.replace(/\s+/g, "") + SGR_RESET;
|
|
64
|
+
}
|
|
65
|
+
|
|
13
66
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
14
67
|
// Tab Bar Theme
|
|
15
68
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Spacer } from "../components/spacer";
|
|
2
2
|
import { Text } from "../components/text";
|
|
3
|
-
import { node } from "../native/describe";
|
|
3
|
+
import { EMPTY_NODE, node } from "../native/describe";
|
|
4
4
|
import type { NativeNode } from "../native/node";
|
|
5
5
|
import { styledSpans } from "../native/spans";
|
|
6
6
|
import { Container } from "../tui";
|
|
@@ -8,33 +8,51 @@ import { Container } from "../tui";
|
|
|
8
8
|
/** How long a native status toast stays up (ms). */
|
|
9
9
|
const TOAST_TTL = 2400;
|
|
10
10
|
|
|
11
|
+
/** How a {@link StatusNotice} shows. */
|
|
12
|
+
export interface StatusNoticeOptions {
|
|
13
|
+
/** Styles the ANSI line; resolved at render so a theme change re-shapes it. */
|
|
14
|
+
styleFn?: (text: string) => string;
|
|
15
|
+
/**
|
|
16
|
+
* Whether a native terminal toasts it (default). False keeps the notice to
|
|
17
|
+
* the ANSI transcript: background chatter such as MCP connection progress,
|
|
18
|
+
* which would otherwise re-toast on every server that connects or fails.
|
|
19
|
+
*/
|
|
20
|
+
toast?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
11
23
|
/**
|
|
12
24
|
* A transient status notice (`showStatus`: "Thinking blocks: hidden", "Copied
|
|
13
25
|
* to clipboard"). The ANSI render appends a blank row and the dim line to the
|
|
14
26
|
* transcript; a native terminal shows it as a toast instead, so notices never
|
|
15
|
-
* pile up in the history. Updating the text re-shows the toast
|
|
27
|
+
* pile up in the history. Updating the text re-shows the toast; a notice
|
|
28
|
+
* with `toast: false` describes nothing natively.
|
|
16
29
|
*/
|
|
17
30
|
export class StatusNotice extends Container {
|
|
18
31
|
readonly #text: Text;
|
|
19
32
|
#message: string;
|
|
33
|
+
#toast: boolean;
|
|
20
34
|
#native: NativeNode | undefined;
|
|
21
35
|
|
|
22
|
-
constructor(message: string,
|
|
36
|
+
constructor(message: string, options: StatusNoticeOptions = {}) {
|
|
23
37
|
super();
|
|
24
38
|
this.#message = message;
|
|
25
|
-
this.#
|
|
39
|
+
this.#toast = options.toast ?? true;
|
|
40
|
+
this.#text = new Text(message, 1, 0).setStyleFn(options.styleFn);
|
|
26
41
|
this.addChild(new Spacer(1));
|
|
27
42
|
this.addChild(this.#text);
|
|
28
43
|
}
|
|
29
44
|
|
|
30
|
-
setMessage(message: string,
|
|
31
|
-
|
|
45
|
+
setMessage(message: string, options: StatusNoticeOptions = {}): void {
|
|
46
|
+
const toast = options.toast ?? true;
|
|
47
|
+
this.#text.setStyleFn(options.styleFn);
|
|
32
48
|
this.#text.setText(message);
|
|
33
|
-
if (message !== this.#message) this.#native = undefined;
|
|
49
|
+
if (message !== this.#message || toast !== this.#toast) this.#native = undefined;
|
|
34
50
|
this.#message = message;
|
|
51
|
+
this.#toast = toast;
|
|
35
52
|
}
|
|
36
53
|
|
|
37
54
|
override describe(): NativeNode {
|
|
55
|
+
if (!this.#toast) return EMPTY_NODE;
|
|
38
56
|
if (this.#native) return this.#native;
|
|
39
57
|
// Callers may pass pre-styled text; a toast takes plain text.
|
|
40
58
|
const plain = styledSpans(this.#message)
|
|
@@ -63,6 +63,12 @@ export interface TranscriptViewportSpan {
|
|
|
63
63
|
export declare class TranscriptContainer extends Container {
|
|
64
64
|
#private;
|
|
65
65
|
addChild(component: Component): void;
|
|
66
|
+
/**
|
|
67
|
+
* `#entries` mirrors `children`; move both together or the next `#syncEntries`
|
|
68
|
+
* pays a full O(n) rebuild.
|
|
69
|
+
*/
|
|
70
|
+
insertChildAt(component: Component, index: number): void;
|
|
71
|
+
/** Removes `component` if {@link canRemoveBlock} allows it; otherwise does nothing. */
|
|
66
72
|
removeChild(component: Component): void;
|
|
67
73
|
clear(): void;
|
|
68
74
|
setToolActivityVisible(visible: boolean): void;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { type Component, Container, type HistoryBatch } from "../tui";
|
|
2
2
|
import * as logger from "@linxiraos/pi-utils/logger";
|
|
3
3
|
import { popLoopPhase, pushLoopPhase } from "@linxiraos/pi-utils";
|
|
4
|
+
import { renderForScrollback } from "../components/image";
|
|
4
5
|
import { col } from "../native/describe";
|
|
5
6
|
import type { NativeNode } from "../native/node";
|
|
6
7
|
import { isNativeSettled, settleNative } from "../native/settle";
|
|
8
|
+
import { TERMINAL } from "../terminal-capabilities";
|
|
7
9
|
import { isUsageRowBlock } from "../overlays/usage-row";
|
|
8
10
|
import { isToolActivityComponent } from "./tool-activity";
|
|
9
11
|
|
|
@@ -56,6 +58,12 @@ export interface AppendOnlyTranscriptBlock {
|
|
|
56
58
|
|
|
57
59
|
interface FinalizableBlock {
|
|
58
60
|
isTranscriptBlockFinalized?(): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* A finalized block whose rows still wait on async work (an SVG figure's
|
|
63
|
+
* raster). Retirement holds until it lands: committed rows can never be
|
|
64
|
+
* repainted, so retiring early would freeze a placeholder into scrollback.
|
|
65
|
+
*/
|
|
66
|
+
isTranscriptBlockPending?(): boolean;
|
|
59
67
|
/** Render the row that must remain represented under emergency viewport pressure. */
|
|
60
68
|
renderTranscriptBlockEmergencyRow?(width: number): string | undefined;
|
|
61
69
|
}
|
|
@@ -89,6 +97,13 @@ interface TranscriptEntry {
|
|
|
89
97
|
* state for emitted-row slicing but never emits another mid-stream row.
|
|
90
98
|
*/
|
|
91
99
|
stableFrozen: boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Cached "renders at least one image line" result for the settled-image
|
|
102
|
+
* flush, keyed by width and image protocol (the probe upgrade re-encodes
|
|
103
|
+
* text-fallback blocks into graphics). Rows of a settled block are frozen,
|
|
104
|
+
* so a hit is exact.
|
|
105
|
+
*/
|
|
106
|
+
imageScan: { width: number; protocol: string | null; hasImageRows: boolean } | undefined;
|
|
92
107
|
}
|
|
93
108
|
|
|
94
109
|
type RetirementPolicy = "pressure" | "flush";
|
|
@@ -120,7 +135,7 @@ const EMPTY_STABLE_ROWS: readonly TranscriptStableRow[] = [];
|
|
|
120
135
|
|
|
121
136
|
function isFinalized(component: Component): boolean {
|
|
122
137
|
const block = component as Component & FinalizableBlock;
|
|
123
|
-
return block.isTranscriptBlockFinalized?.() ?? true;
|
|
138
|
+
return (block.isTranscriptBlockFinalized?.() ?? true) && block.isTranscriptBlockPending?.() !== true;
|
|
124
139
|
}
|
|
125
140
|
|
|
126
141
|
function blockMode(component: Component): TranscriptBlockMode {
|
|
@@ -199,6 +214,7 @@ export class TranscriptContainer extends Container {
|
|
|
199
214
|
*/
|
|
200
215
|
#frameRows = new Map<TranscriptEntry, readonly string[]>();
|
|
201
216
|
#frameRowsWidth = 0;
|
|
217
|
+
#releaseFailures = new WeakSet<Component>();
|
|
202
218
|
/** The `children` array `#entries` last mirrored; see {@link #syncEntries}. */
|
|
203
219
|
#syncedChildren: Component[] | undefined;
|
|
204
220
|
/** Forces the next {@link #syncEntries} to compare every entry, not just the live tail. */
|
|
@@ -218,16 +234,42 @@ export class TranscriptContainer extends Container {
|
|
|
218
234
|
stableRowCountByWidth: new Map(),
|
|
219
235
|
emitted: 0,
|
|
220
236
|
stableFrozen: false,
|
|
237
|
+
imageScan: undefined,
|
|
221
238
|
});
|
|
222
|
-
//
|
|
223
|
-
//
|
|
239
|
+
// `children` is public and may be edited externally; re-check the whole
|
|
240
|
+
// list once instead of trusting positions. Insert-after-anchor callers
|
|
241
|
+
// use insertChildAt, which keeps both arrays in lockstep.
|
|
224
242
|
this.#entriesUnverified = true;
|
|
225
243
|
}
|
|
226
244
|
|
|
245
|
+
/**
|
|
246
|
+
* `#entries` mirrors `children`; move both together or the next `#syncEntries`
|
|
247
|
+
* pays a full O(n) rebuild.
|
|
248
|
+
*/
|
|
249
|
+
insertChildAt(component: Component, index: number): void {
|
|
250
|
+
this.addChild(component);
|
|
251
|
+
const from = this.children.length - 1;
|
|
252
|
+
if (index >= from) {
|
|
253
|
+
this.#entriesUnverified = false;
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
const [moved] = this.children.splice(from, 1);
|
|
257
|
+
if (moved) this.children.splice(index, 0, moved);
|
|
258
|
+
const [entry] = this.#entries.splice(this.#entries.length - 1, 1);
|
|
259
|
+
if (entry) this.#entries.splice(index, 0, entry);
|
|
260
|
+
// Both arrays moved in lockstep, so the live-tail check applies again.
|
|
261
|
+
this.#entriesUnverified = false;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Removes `component` if {@link canRemoveBlock} allows it; otherwise does nothing. */
|
|
227
265
|
override removeChild(component: Component): void {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
266
|
+
const index = this.#removableIndex(component);
|
|
267
|
+
if (index < 0) return;
|
|
268
|
+
// #removableIndex synced entries, which now mirror children index for
|
|
269
|
+
// index. This class renders through its own entries, never Container's
|
|
270
|
+
// memoized render, so splicing children directly is the whole removal.
|
|
271
|
+
this.children.splice(index, 1);
|
|
272
|
+
this.#entries.splice(index, 1);
|
|
231
273
|
this.#frontier = Math.min(this.#frontier, this.#entries.length);
|
|
232
274
|
this.#childStartRows.delete(component);
|
|
233
275
|
}
|
|
@@ -282,14 +324,24 @@ export class TranscriptContainer extends Container {
|
|
|
282
324
|
|
|
283
325
|
/** Whether a transient block may be discarded without leaving tape history. */
|
|
284
326
|
canRemoveBlock(component: Component): boolean {
|
|
327
|
+
return this.#removableIndex(component) >= 0;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Syncs entries, then returns `component`'s index if it can be discarded
|
|
332
|
+
* without leaving tape history (not committed, not emitted, not in an
|
|
333
|
+
* offered batch), else -1.
|
|
334
|
+
*/
|
|
335
|
+
#removableIndex(component: Component): number {
|
|
285
336
|
this.#syncEntries();
|
|
286
|
-
|
|
287
|
-
|
|
337
|
+
// Removable blocks are transient ones near the live tail, so search from there.
|
|
338
|
+
const index = this.#entries.findLastIndex(entry => entry.component === component);
|
|
339
|
+
if (index < 0) return -1;
|
|
288
340
|
const entry = this.#entries[index]!;
|
|
289
|
-
if (entry.state === "committed" || entry.emitted > 0) return
|
|
290
|
-
if (this.#offered?.kind === "commit" && index < this.#offered.end) return
|
|
291
|
-
if (this.#offered?.kind === "append" && index === this.#offered.entry) return
|
|
292
|
-
return
|
|
341
|
+
if (entry.state === "committed" || entry.emitted > 0) return -1;
|
|
342
|
+
if (this.#offered?.kind === "commit" && index < this.#offered.end) return -1;
|
|
343
|
+
if (this.#offered?.kind === "append" && index === this.#offered.entry) return -1;
|
|
344
|
+
return index;
|
|
293
345
|
}
|
|
294
346
|
|
|
295
347
|
/**
|
|
@@ -576,7 +628,10 @@ export class TranscriptContainer extends Container {
|
|
|
576
628
|
popLoopPhase();
|
|
577
629
|
}
|
|
578
630
|
this.#replayPending = false;
|
|
579
|
-
if (rows.length === 0)
|
|
631
|
+
if (rows.length === 0) {
|
|
632
|
+
this.#releaseCommittedRenderCaches();
|
|
633
|
+
return undefined;
|
|
634
|
+
}
|
|
580
635
|
const batch: HistoryBatch = { id: this.#nextBatchId++, rows, kind: "replay" };
|
|
581
636
|
this.#offered = { batch, kind: "replay" };
|
|
582
637
|
return batch;
|
|
@@ -659,9 +714,20 @@ export class TranscriptContainer extends Container {
|
|
|
659
714
|
if (!fits && appendHead === undefined) break;
|
|
660
715
|
}
|
|
661
716
|
const overflowing = keep > 0 || this.#liveCount() >= MAX_LIVE_BLOCKS;
|
|
717
|
+
// Graphics rows must reach native scrollback as soon as their block is
|
|
718
|
+
// finalized, not when the screen next overflows: live placements are
|
|
719
|
+
// repainted into the reserved-row block every frame, and on terminals
|
|
720
|
+
// whose live-region sixel does not stick (Windows Terminal) the image
|
|
721
|
+
// stays invisible until the exit flush commits the block.
|
|
722
|
+
// Retire the settled prefix through its last image-bearing block; the
|
|
723
|
+
// flush-at-stop path remains the fallback for what is still live then.
|
|
724
|
+
let imageFlushLimit = -1;
|
|
662
725
|
if (policy === "pressure" && !overflowing) {
|
|
663
|
-
|
|
664
|
-
|
|
726
|
+
imageFlushLimit = this.#settledImageFlushLimit(width);
|
|
727
|
+
if (imageFlushLimit < 0) {
|
|
728
|
+
this.#pinnedFrontier = undefined;
|
|
729
|
+
return undefined;
|
|
730
|
+
}
|
|
665
731
|
}
|
|
666
732
|
|
|
667
733
|
if (appendHead !== undefined && keep > 0) {
|
|
@@ -687,8 +753,11 @@ export class TranscriptContainer extends Container {
|
|
|
687
753
|
}
|
|
688
754
|
|
|
689
755
|
// Shutdown retires everything eligible; pressure retires exactly the
|
|
690
|
-
// blocks that no longer fit, plus whatever the live-block cap demands
|
|
756
|
+
// blocks that no longer fit, plus whatever the live-block cap demands
|
|
757
|
+
// — or, without overflow, the settled prefix through its last
|
|
758
|
+
// image-bearing block (graphics must land in scrollback promptly).
|
|
691
759
|
let limit = this.#frontier + keep;
|
|
760
|
+
if (imageFlushLimit >= 0) limit = Math.max(limit, imageFlushLimit);
|
|
692
761
|
if (this.#liveCount() >= MAX_LIVE_BLOCKS) {
|
|
693
762
|
limit = Math.max(limit, this.#frontier + (this.#liveCount() - (MAX_LIVE_BLOCKS - 1)));
|
|
694
763
|
}
|
|
@@ -740,6 +809,8 @@ export class TranscriptContainer extends Container {
|
|
|
740
809
|
this.#retireEntry(this.#entries[index]!);
|
|
741
810
|
}
|
|
742
811
|
this.#frontier = offered.end;
|
|
812
|
+
} else {
|
|
813
|
+
this.#releaseCommittedRenderCaches();
|
|
743
814
|
}
|
|
744
815
|
this.#offered = undefined;
|
|
745
816
|
if (this.#replayRequested) this.#startReplay();
|
|
@@ -759,6 +830,7 @@ export class TranscriptContainer extends Container {
|
|
|
759
830
|
const entry = this.#entries[index]!;
|
|
760
831
|
this.#setAllocation(entry.component, Number.MAX_SAFE_INTEGER, this.#lastFrame);
|
|
761
832
|
const block = trimBlankEdges(entry.component.render(width));
|
|
833
|
+
if (entry.state === "committed") this.#releaseRenderCaches(entry);
|
|
762
834
|
if (block.length === 0) continue;
|
|
763
835
|
if (rows.length > 0) rows.unshift("");
|
|
764
836
|
rows.unshift(...block);
|
|
@@ -806,6 +878,7 @@ export class TranscriptContainer extends Container {
|
|
|
806
878
|
for (const entry of this.#entries) {
|
|
807
879
|
this.#setAllocation(entry.component, Number.MAX_SAFE_INTEGER, this.#lastFrame);
|
|
808
880
|
const block = this.#renderEntry(entry, width);
|
|
881
|
+
if (entry.state === "committed") this.#releaseRenderCaches(entry);
|
|
809
882
|
if (block.length === 0) continue;
|
|
810
883
|
if (rows.length > 0) rows.push("");
|
|
811
884
|
this.#childStartRows.set(entry.component, rows.length);
|
|
@@ -1000,9 +1073,11 @@ export class TranscriptContainer extends Container {
|
|
|
1000
1073
|
// Only the range head is sliced by its emitted stable prefix; every other
|
|
1001
1074
|
// entry renders whole, so the append-only verification pass (a second
|
|
1002
1075
|
// full render of the block's stable prefix) is skipped for them. This
|
|
1003
|
-
// keeps a complete-ledger replay at one render per block.
|
|
1004
|
-
|
|
1005
|
-
|
|
1076
|
+
// keeps a complete-ledger replay at one render per block. These rows go
|
|
1077
|
+
// to native scrollback, where nothing can repaint them.
|
|
1078
|
+
const rendered = renderForScrollback(() =>
|
|
1079
|
+
index === start ? this.#renderEntry(entry, width) : trimBlankEdges(entry.component.render(width)),
|
|
1080
|
+
);
|
|
1006
1081
|
const emittedRows = index === start ? this.#renderStablePrefix(entry, entry.emitted, width).length : 0;
|
|
1007
1082
|
const block = rendered.slice(emittedRows);
|
|
1008
1083
|
reached = index + 1;
|
|
@@ -1045,6 +1120,29 @@ export class TranscriptContainer extends Container {
|
|
|
1045
1120
|
entry.stableRows = EMPTY_STABLE_ROWS;
|
|
1046
1121
|
entry.renderedStableByWidth = new Map();
|
|
1047
1122
|
entry.stableRowCountByWidth = new Map();
|
|
1123
|
+
this.#frameRows.delete(entry);
|
|
1124
|
+
this.#releaseRenderCaches(entry);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
#releaseCommittedRenderCaches(): void {
|
|
1128
|
+
for (let index = 0; index < this.#frontier; index++) {
|
|
1129
|
+
this.#releaseRenderCaches(this.#entries[index]!);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
#releaseRenderCaches(entry: TranscriptEntry): void {
|
|
1134
|
+
const release = entry.component.releaseRenderCaches;
|
|
1135
|
+
if (release === undefined) return;
|
|
1136
|
+
try {
|
|
1137
|
+
release.call(entry.component);
|
|
1138
|
+
} catch (err) {
|
|
1139
|
+
if (this.#releaseFailures.has(entry.component)) return;
|
|
1140
|
+
this.#releaseFailures.add(entry.component);
|
|
1141
|
+
logger.warn("Transcript block failed to release render caches", {
|
|
1142
|
+
component: entry.component.constructor.name,
|
|
1143
|
+
error: String(err),
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
1048
1146
|
}
|
|
1049
1147
|
|
|
1050
1148
|
#startReplay(): void {
|
|
@@ -1146,6 +1244,14 @@ export class TranscriptContainer extends Container {
|
|
|
1146
1244
|
continue;
|
|
1147
1245
|
}
|
|
1148
1246
|
this.#setAllocation(candidate.entry.component, 1, frame);
|
|
1247
|
+
const emergency = (
|
|
1248
|
+
candidate.entry.component as Component & FinalizableBlock
|
|
1249
|
+
).renderTranscriptBlockEmergencyRow?.(width);
|
|
1250
|
+
if (emergency !== undefined) {
|
|
1251
|
+
output.push(emergency);
|
|
1252
|
+
owners.push(candidate.entry.component);
|
|
1253
|
+
continue;
|
|
1254
|
+
}
|
|
1149
1255
|
const rendered = this.#renderEntry(candidate.entry, width).slice(
|
|
1150
1256
|
this.#projectedEmittedRowCount(candidate.entry, candidate.index, width),
|
|
1151
1257
|
);
|
|
@@ -1168,6 +1274,42 @@ export class TranscriptContainer extends Container {
|
|
|
1168
1274
|
}
|
|
1169
1275
|
}
|
|
1170
1276
|
|
|
1277
|
+
/**
|
|
1278
|
+
* End (exclusive) of the settled prefix that must retire for its graphics
|
|
1279
|
+
* rows to land in native scrollback now: one past the last image-bearing
|
|
1280
|
+
* settled entry, or -1 when the prefix holds no image block. Entries after
|
|
1281
|
+
* a non-settled block cannot retire yet, so the walk stops at the first
|
|
1282
|
+
* one — an unfinalized image keeps waiting, exactly like at stop.
|
|
1283
|
+
*/
|
|
1284
|
+
#settledImageFlushLimit(width: number): number {
|
|
1285
|
+
let last = -1;
|
|
1286
|
+
for (let index = this.#frontier; index < this.#entries.length; index++) {
|
|
1287
|
+
const entry = this.#entries[index]!;
|
|
1288
|
+
if (entry.state !== "settled") break;
|
|
1289
|
+
if (this.#hasImageRows(entry, width)) last = index;
|
|
1290
|
+
}
|
|
1291
|
+
return last < 0 ? -1 : last + 1;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
/** Whether the block's measured rows carry an image sequence (cached per width and protocol). */
|
|
1295
|
+
#hasImageRows(entry: TranscriptEntry, width: number): boolean {
|
|
1296
|
+
const protocol = TERMINAL.imageProtocol;
|
|
1297
|
+
const cached = entry.imageScan;
|
|
1298
|
+
if (cached !== undefined && cached.width === width && cached.protocol === protocol) return cached.hasImageRows;
|
|
1299
|
+
let hasImageRows = false;
|
|
1300
|
+
if (protocol !== null) {
|
|
1301
|
+
const rows = this.#measuredRows(entry, width);
|
|
1302
|
+
for (let index = 0; index < rows.length; index++) {
|
|
1303
|
+
if (TERMINAL.isImageLine(rows[index]!)) {
|
|
1304
|
+
hasImageRows = true;
|
|
1305
|
+
break;
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
entry.imageScan = { width, protocol, hasImageRows };
|
|
1310
|
+
return hasImageRows;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1171
1313
|
#liveEntries(): Array<{ entry: TranscriptEntry; index: number }> {
|
|
1172
1314
|
const start = this.#offered?.kind === "commit" ? this.#offered.end : this.#frontier;
|
|
1173
1315
|
const live: Array<{ entry: TranscriptEntry; index: number }> = [];
|
|
@@ -1212,6 +1354,7 @@ export class TranscriptContainer extends Container {
|
|
|
1212
1354
|
stableRowCountByWidth: new Map(),
|
|
1213
1355
|
emitted: 0,
|
|
1214
1356
|
stableFrozen: false,
|
|
1357
|
+
imageScan: undefined,
|
|
1215
1358
|
},
|
|
1216
1359
|
);
|
|
1217
1360
|
this.#frontier = this.#entries.findIndex(entry => entry.state !== "committed");
|
package/src/components/box.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export declare class Box implements Component {
|
|
|
30
30
|
setBgFn(bgFn?: (text: string) => string): void;
|
|
31
31
|
setBorder(border?: BoxBorder): void;
|
|
32
32
|
invalidate(): void;
|
|
33
|
+
releaseRenderCaches(): void;
|
|
33
34
|
/**
|
|
34
35
|
* A `card` when the box has a border or background: the background token
|
|
35
36
|
* picks tone/role (user message, tool state, selection), a border colour
|
package/src/components/box.ts
CHANGED
|
@@ -135,6 +135,11 @@ export class Box implements Component {
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
releaseRenderCaches(): void {
|
|
139
|
+
this.#invalidateCache();
|
|
140
|
+
for (const child of this.children) child.releaseRenderCaches?.();
|
|
141
|
+
}
|
|
142
|
+
|
|
138
143
|
/**
|
|
139
144
|
* A `card` when the box has a border or background: the background token
|
|
140
145
|
* picks tone/role (user message, tool state, selection), a border colour
|
|
@@ -36,6 +36,7 @@ export declare class Disclosure implements Component {
|
|
|
36
36
|
setExpanded(expanded: boolean): void;
|
|
37
37
|
setIgnoreTight(ignore: boolean): this;
|
|
38
38
|
invalidate(): void;
|
|
39
|
+
releaseRenderCaches(): void;
|
|
39
40
|
dispose(): void;
|
|
40
41
|
debugState(): Record<string, unknown>;
|
|
41
42
|
get debugChildren(): readonly Component[];
|
|
@@ -100,6 +100,14 @@ export class Disclosure implements Component {
|
|
|
100
100
|
this.#body?.invalidate?.();
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
releaseRenderCaches(): void {
|
|
104
|
+
this.#cache = undefined;
|
|
105
|
+
this.#boundedCache = undefined;
|
|
106
|
+
this.#summary?.releaseRenderCaches?.();
|
|
107
|
+
this.#collapsedBody?.releaseRenderCaches?.();
|
|
108
|
+
this.#body?.releaseRenderCaches?.();
|
|
109
|
+
}
|
|
110
|
+
|
|
103
111
|
dispose(): void {
|
|
104
112
|
if (this.#disposed) return;
|
|
105
113
|
this.#disposed = true;
|