@linxiraos/pi-tui 1.2.1 → 1.3.1
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 +245 -224
- package/README.md +36 -0
- package/THIRD-PARTY-NOTICES.txt +55 -5
- package/dist/types/app-keybindings.d.ts +18 -8
- package/dist/types/apps/git/diff-pane.d.ts +5 -0
- package/dist/types/apps/git/git-tui.d.ts +16 -2
- package/dist/types/apps/git/state.d.ts +11 -0
- package/dist/types/autocomplete.d.ts +2 -0
- package/dist/types/charts/chart-svg.d.ts +7 -13
- package/dist/types/chat/advisor-message.d.ts +4 -1
- package/dist/types/chat/assistant-message.d.ts +9 -4
- package/dist/types/chat/figure-markdown.d.ts +7 -2
- package/dist/types/chat/messages.d.ts +3 -1
- package/dist/types/chat/read-tool-group.d.ts +2 -2
- package/dist/types/chat/table-chart.d.ts +11 -15
- package/dist/types/chat/tool-execution.d.ts +32 -20
- package/dist/types/chat/user-message.d.ts +3 -2
- package/dist/types/chrome/countdown-timer.d.ts +3 -2
- package/dist/types/chrome/diff.d.ts +2 -3
- package/dist/types/chrome/status-notice.d.ts +2 -0
- package/dist/types/chrome/transcript-container.d.ts +2 -0
- package/dist/types/components/image.d.ts +2 -0
- package/dist/types/components/markdown.d.ts +49 -3
- package/dist/types/components/text.d.ts +2 -0
- package/dist/types/debug-server.d.ts +2 -0
- package/dist/types/keybinding-matchers.d.ts +6 -0
- package/dist/types/multiplexers/tmux.d.ts +1 -0
- package/dist/types/multiplexers/types.d.ts +6 -0
- package/dist/types/native/backend.d.ts +5 -4
- package/dist/types/native/blobs.d.ts +24 -12
- package/dist/types/overlays/agent-hub-projection.d.ts +10 -5
- package/dist/types/overlays/annotation-overlay.d.ts +19 -0
- package/dist/types/overlays/approval-dialog.d.ts +79 -0
- package/dist/types/overlays/ask-dialog.d.ts +11 -6
- package/dist/types/overlays/btw-history-panel.d.ts +2 -6
- package/dist/types/overlays/hook-editor.d.ts +7 -2
- package/dist/types/overlays/hook-input.d.ts +12 -4
- package/dist/types/overlays/job-tree.d.ts +50 -0
- package/dist/types/overlays/jobs-panel.d.ts +25 -11
- package/dist/types/overlays/login-dialog.d.ts +8 -3
- package/dist/types/overlays/mcp-add-wizard.d.ts +9 -1
- package/dist/types/overlays/model-browser.d.ts +6 -1
- package/dist/types/overlays/model-picker.d.ts +2 -0
- package/dist/types/overlays/pause-screen.d.ts +5 -0
- package/dist/types/overlays/plan-review-overlay.d.ts +4 -0
- package/dist/types/overlays/report-panel.d.ts +5 -1
- package/dist/types/overlays/reset-usage-selector.d.ts +7 -5
- package/dist/types/overlays/session-account-selector.d.ts +4 -0
- package/dist/types/overlays/session-info-overlay.d.ts +7 -3
- package/dist/types/overlays/session-selector.d.ts +3 -1
- package/dist/types/overlays/usage-dashboard.d.ts +6 -6
- package/dist/types/prompt/composer.d.ts +23 -20
- package/dist/types/prompt/custom-editor.d.ts +20 -10
- package/dist/types/prompt/image-references.d.ts +22 -0
- package/dist/types/prompt/internal-url-autocomplete.d.ts +1 -0
- package/dist/types/prompt/macos-spelling.d.ts +2 -1
- package/dist/types/prompt/prose-gate.d.ts +3 -0
- package/dist/types/prompt/queued-messages.d.ts +1 -1
- package/dist/types/prompt/typing-word.d.ts +25 -0
- package/dist/types/prompt/welcome.d.ts +26 -6
- package/dist/types/render/hanging-wrap.d.ts +7 -0
- package/dist/types/render/hyperlink.d.ts +2 -0
- package/dist/types/render/render-utils.d.ts +9 -1
- package/dist/types/render/utils.d.ts +1 -1
- package/dist/types/setup/scenes/splash.d.ts +6 -5
- package/dist/types/status-line/context-usage.d.ts +4 -1
- package/dist/types/status-line/host.d.ts +6 -4
- package/dist/types/status-line/startup.d.ts +6 -6
- package/dist/types/status-line/types.d.ts +3 -0
- package/dist/types/stdin-buffer.d.ts +7 -0
- package/dist/types/terminal-capabilities.d.ts +16 -1
- package/dist/types/terminal-multiplexer.d.ts +7 -0
- package/dist/types/terminal.d.ts +8 -4
- package/dist/types/theme/symbols.d.ts +1 -1
- package/dist/types/theme/theme.d.ts +11 -0
- package/dist/types/thinking.d.ts +5 -0
- package/dist/types/tmux.d.ts +39 -4
- package/dist/types/tools/ast-edit.d.ts +2 -2
- package/dist/types/tools/ast-grep.d.ts +2 -2
- package/dist/types/tools/bash.d.ts +6 -1
- package/dist/types/tools/edit.d.ts +2 -0
- package/dist/types/tools/eval.d.ts +53 -4
- package/dist/types/tools/find.d.ts +14 -4
- package/dist/types/tools/glob.d.ts +3 -3
- package/dist/types/tools/grep.d.ts +2 -2
- package/dist/types/tools/grouped-file-output.d.ts +9 -4
- package/dist/types/tools/native-view.d.ts +1 -1
- package/dist/types/tools/read.d.ts +32 -10
- package/dist/types/tools/renderer.d.ts +2 -0
- package/dist/types/tools/task.d.ts +31 -0
- package/dist/types/tools/web-search-types.d.ts +1 -0
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/tui.d.ts +20 -3
- package/package.json +10 -10
- package/src/app-keybindings.d.ts +18 -8
- package/src/app-keybindings.ts +20 -8
- package/src/apps/autoresearch-dashboard.ts +444 -27
- package/src/apps/debug/raw-sse.ts +2 -2
- package/src/apps/git/diff-pane.ts +39 -41
- package/src/apps/git/git-tui.ts +80 -14
- package/src/apps/git/sidebar.ts +10 -1
- package/src/apps/git/state.ts +12 -0
- package/src/autocomplete.d.ts +2 -0
- package/src/autocomplete.ts +21 -19
- package/src/bracketed-paste.ts +9 -2
- package/src/charts/chart-svg.d.ts +7 -13
- package/src/charts/chart-svg.ts +19 -22
- package/src/charts/table-data.ts +17 -6
- package/src/chat/advisor-message.d.ts +4 -1
- package/src/chat/advisor-message.ts +77 -24
- package/src/chat/assistant-message.d.ts +9 -4
- package/src/chat/assistant-message.ts +127 -31
- package/src/chat/bash-execution.ts +7 -2
- package/src/chat/figure-markdown.d.ts +7 -2
- package/src/chat/figure-markdown.ts +10 -3
- package/src/chat/image-loading.ts +9 -6
- package/src/chat/messages.d.ts +3 -1
- package/src/chat/messages.ts +4 -1
- package/src/chat/read-tool-group.d.ts +2 -2
- package/src/chat/read-tool-group.ts +97 -42
- package/src/chat/svg-figure.ts +3 -1
- package/src/chat/svg-source.ts +6 -3
- package/src/chat/table-chart.d.ts +11 -15
- package/src/chat/table-chart.ts +24 -57
- package/src/chat/tool-execution.d.ts +32 -20
- package/src/chat/tool-execution.ts +103 -36
- package/src/chat/user-message.d.ts +3 -2
- package/src/chat/user-message.ts +54 -34
- package/src/chrome/countdown-timer.ts +3 -3
- package/src/chrome/diff.d.ts +2 -3
- package/src/chrome/diff.ts +4 -9
- package/src/chrome/status-notice.ts +5 -0
- package/src/chrome/transcript-container.d.ts +2 -0
- package/src/chrome/transcript-container.ts +212 -28
- package/src/components/editor.ts +12 -12
- package/src/components/image.d.ts +2 -0
- package/src/components/image.ts +54 -20
- package/src/components/markdown.d.ts +49 -3
- package/src/components/markdown.ts +482 -12
- package/src/components/settings-list.ts +2 -4
- package/src/components/text.d.ts +2 -0
- package/src/components/text.ts +11 -1
- package/src/debug-server.d.ts +2 -0
- package/src/debug-server.ts +51 -69
- package/src/hotkeys-markdown.ts +2 -0
- package/src/keybinding-matchers.d.ts +6 -0
- package/src/keybinding-matchers.ts +19 -0
- package/src/multiplexers/tmux.d.ts +1 -0
- package/src/multiplexers/tmux.ts +3 -0
- package/src/multiplexers/types.d.ts +6 -0
- package/src/multiplexers/types.ts +6 -0
- package/src/native/backend.d.ts +5 -4
- package/src/native/backend.ts +13 -5
- package/src/native/blobs.d.ts +24 -12
- package/src/native/blobs.ts +56 -7
- package/src/native/reconcile.ts +43 -21
- package/src/overlays/advisor-config.ts +259 -8
- package/src/overlays/agent-hub-projection.d.ts +10 -5
- package/src/overlays/agent-hub-projection.ts +63 -12
- package/src/overlays/annotation-overlay.ts +422 -168
- package/src/overlays/approval-dialog.ts +389 -0
- package/src/overlays/ask-dialog.ts +244 -105
- package/src/overlays/btw-history-panel.ts +208 -147
- package/src/overlays/extensions/extension-list.ts +4 -6
- package/src/overlays/hook-editor.ts +90 -32
- package/src/overlays/hook-input.ts +105 -19
- package/src/overlays/job-tree.ts +177 -0
- package/src/overlays/jobs-panel.ts +222 -142
- package/src/overlays/login-dialog.ts +201 -91
- package/src/overlays/mcp-add-wizard.ts +562 -279
- package/src/overlays/model-browser.ts +32 -7
- package/src/overlays/model-hub.ts +4 -10
- package/src/overlays/model-picker.ts +58 -21
- package/src/overlays/pause-screen.ts +81 -23
- package/src/overlays/plan-review-overlay.ts +16 -0
- package/src/overlays/plugin-selector.ts +17 -1
- package/src/overlays/queue-mode-selector.ts +10 -0
- package/src/overlays/report-panel.ts +6 -2
- package/src/overlays/reset-usage-selector.ts +130 -21
- package/src/overlays/session-account-selector.ts +36 -0
- package/src/overlays/session-info-overlay.ts +199 -76
- package/src/overlays/session-selector.ts +15 -10
- package/src/overlays/settings-selector.ts +3 -0
- package/src/overlays/show-images-selector.ts +1 -0
- package/src/overlays/snapcompact-shape-preview.ts +13 -6
- package/src/overlays/theme-selector.ts +6 -1
- package/src/overlays/thinking-selector.ts +8 -4
- package/src/overlays/tree-selector.ts +117 -2
- package/src/overlays/usage-dashboard.ts +498 -144
- package/src/prompt/composer-cache.ts +4 -1
- package/src/prompt/composer.ts +200 -74
- package/src/prompt/custom-editor.ts +168 -65
- package/src/prompt/image-references.d.ts +22 -0
- package/src/prompt/image-references.ts +79 -2
- package/src/prompt/internal-url-autocomplete.ts +1 -0
- package/src/prompt/macos-spelling.ts +28 -5
- package/src/prompt/prose-gate.ts +3 -0
- package/src/prompt/queued-messages.ts +9 -2
- package/src/prompt/tips.txt +1 -2
- package/src/prompt/typing-word.ts +59 -0
- package/src/prompt/welcome.ts +97 -45
- package/src/render/code-cell.ts +18 -5
- package/src/render/hanging-wrap.d.ts +7 -0
- package/src/render/hanging-wrap.ts +58 -0
- package/src/render/hyperlink.d.ts +2 -0
- package/src/render/hyperlink.ts +5 -0
- package/src/render/render-utils.d.ts +9 -1
- package/src/render/render-utils.ts +16 -2
- package/src/render/utils.d.ts +1 -1
- package/src/render/utils.ts +1 -1
- package/src/render/width-aware-text.ts +6 -5
- package/src/setup/scenes/splash.ts +22 -8
- package/src/status-line/component.ts +79 -11
- package/src/status-line/context-usage.ts +176 -73
- package/src/status-line/footer.ts +18 -35
- package/src/status-line/host.ts +6 -1
- package/src/status-line/segments.ts +16 -5
- package/src/status-line/startup.ts +6 -6
- package/src/status-line/types.ts +1 -0
- package/src/stdin-buffer.d.ts +7 -0
- package/src/stdin-buffer.ts +300 -62
- package/src/terminal-capabilities.d.ts +16 -1
- package/src/terminal-capabilities.ts +66 -28
- package/src/terminal-multiplexer.d.ts +7 -0
- package/src/terminal-multiplexer.ts +14 -0
- package/src/terminal.d.ts +8 -4
- package/src/terminal.ts +190 -63
- package/src/theme/symbols.d.ts +1 -1
- package/src/theme/symbols.ts +1 -0
- package/src/theme/theme-class.ts +1 -0
- package/src/theme/theme.d.ts +11 -0
- package/src/theme/theme.ts +46 -14
- package/src/thinking.d.ts +5 -0
- package/src/thinking.ts +9 -1
- package/src/tmux.d.ts +39 -4
- package/src/tmux.ts +110 -16
- package/src/tools/ask.ts +20 -9
- package/src/tools/ast-edit.d.ts +2 -2
- package/src/tools/ast-edit.ts +30 -7
- package/src/tools/ast-grep.d.ts +2 -2
- package/src/tools/ast-grep.ts +39 -35
- package/src/tools/bash.d.ts +6 -1
- package/src/tools/bash.ts +15 -9
- package/src/tools/debug.ts +2 -1
- package/src/tools/default-renderer.ts +3 -2
- package/src/tools/edit.d.ts +2 -0
- package/src/tools/edit.ts +100 -21
- package/src/tools/eval.d.ts +53 -4
- package/src/tools/eval.ts +173 -137
- package/src/tools/find.d.ts +14 -4
- package/src/tools/find.ts +71 -18
- package/src/tools/glob.d.ts +3 -3
- package/src/tools/glob.ts +51 -17
- package/src/tools/grep.d.ts +2 -2
- package/src/tools/grep.ts +99 -22
- package/src/tools/grouped-file-output.d.ts +9 -4
- package/src/tools/grouped-file-output.ts +21 -6
- package/src/tools/lsp.ts +30 -18
- package/src/tools/mcp.ts +2 -2
- package/src/tools/native-view.d.ts +1 -1
- package/src/tools/native-view.ts +2 -2
- package/src/tools/read.d.ts +32 -10
- package/src/tools/read.ts +89 -43
- package/src/tools/renderer.d.ts +2 -0
- package/src/tools/renderer.ts +2 -0
- package/src/tools/task.d.ts +31 -0
- package/src/tools/task.ts +40 -7
- package/src/tools/todo.ts +3 -2
- package/src/tools/wait.ts +16 -30
- package/src/tools/web-search-types.d.ts +1 -0
- package/src/tools/web-search-types.ts +1 -0
- package/src/tools/web-search.ts +2 -1
- package/src/tools/write.d.ts +2 -0
- package/src/tools/write.ts +36 -12
- package/src/tools/yield.ts +2 -2
- package/src/tui.d.ts +20 -3
- package/src/tui.ts +160 -43
- package/src/utils.ts +5 -3
- package/dist/types/tools/eval-format/code-mode-declarations.d.ts +0 -5
- package/src/tools/eval-format/code-mode-declarations.ts +0 -61
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Each row is one JSON payload keyed by project (the resolved cwd) and kind.
|
|
6
6
|
* Settings-derived kinds (theme/composer preferences, status-bar inputs) are
|
|
7
|
-
* also written under the empty project, so a folder that never ran
|
|
7
|
+
* also written under the empty project, so a folder that never ran zetacode still
|
|
8
8
|
* paints with the user's theme and status bar: those are rarely
|
|
9
9
|
* project-specific, and path/branch render live.
|
|
10
10
|
*
|
|
@@ -94,6 +94,7 @@ function parseUiState(
|
|
|
94
94
|
showHardwareCursor,
|
|
95
95
|
maxInlineImages,
|
|
96
96
|
resizeScrollback,
|
|
97
|
+
completeScrollback,
|
|
97
98
|
imeSafeCursor,
|
|
98
99
|
autocompleteMaxVisible,
|
|
99
100
|
spellingTypoDetection,
|
|
@@ -109,6 +110,7 @@ function parseUiState(
|
|
|
109
110
|
resizeScrollback !== "append" &&
|
|
110
111
|
resizeScrollback !== "rebuild" &&
|
|
111
112
|
resizeScrollback !== "preserve") ||
|
|
113
|
+
(completeScrollback !== undefined && typeof completeScrollback !== "boolean") ||
|
|
112
114
|
typeof imeSafeCursor !== "boolean" ||
|
|
113
115
|
typeof autocompleteMaxVisible !== "number" ||
|
|
114
116
|
typeof spellingTypoDetection !== "boolean" ||
|
|
@@ -136,6 +138,7 @@ function parseUiState(
|
|
|
136
138
|
showHardwareCursor,
|
|
137
139
|
maxInlineImages,
|
|
138
140
|
resizeScrollback: resizeScrollback ?? "rebuild",
|
|
141
|
+
completeScrollback: completeScrollback ?? false,
|
|
139
142
|
imeSafeCursor,
|
|
140
143
|
autocompleteMaxVisible,
|
|
141
144
|
spellingTypoDetection,
|
package/src/prompt/composer.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { ProcessTerminal, type Terminal } from "../terminal";
|
|
|
8
8
|
import {
|
|
9
9
|
type Component,
|
|
10
10
|
Container,
|
|
11
|
+
type RenderTimer,
|
|
11
12
|
type ResizeScrollbackMode,
|
|
12
13
|
type TerminalFramePlan,
|
|
13
14
|
type TerminalFrameProvider,
|
|
@@ -26,6 +27,8 @@ import { WelcomeComponent } from "./welcome";
|
|
|
26
27
|
import { ensureThemeSync, getEditorTheme, theme } from "../theme/theme";
|
|
27
28
|
|
|
28
29
|
const DOUBLE_INTERRUPT_MS = 500;
|
|
30
|
+
// How long idle chrome must hold its height after a turn before it is treated as permanent.
|
|
31
|
+
const IDLE_CHROME_SETTLE_MS = 250;
|
|
29
32
|
|
|
30
33
|
/** Live settings that affect the composer before and after session adoption. */
|
|
31
34
|
export interface ComposerPreferences {
|
|
@@ -34,6 +37,8 @@ export interface ComposerPreferences {
|
|
|
34
37
|
readonly showHardwareCursor: boolean;
|
|
35
38
|
readonly maxInlineImages: number;
|
|
36
39
|
readonly resizeScrollback: ResizeScrollbackMode;
|
|
40
|
+
/** Send rows a growing HUD or dialog pushes off screen straight to scrollback instead of holding them back until it shrinks. */
|
|
41
|
+
readonly completeScrollback: boolean;
|
|
37
42
|
readonly imeSafeCursor: boolean;
|
|
38
43
|
readonly autocompleteMaxVisible: number;
|
|
39
44
|
readonly spellingTypoDetection: boolean;
|
|
@@ -48,6 +53,7 @@ export const COMPOSER_DEFAULTS: ComposerPreferences = {
|
|
|
48
53
|
showHardwareCursor: true,
|
|
49
54
|
maxInlineImages: 8,
|
|
50
55
|
resizeScrollback: "rebuild",
|
|
56
|
+
completeScrollback: false,
|
|
51
57
|
imeSafeCursor: false,
|
|
52
58
|
autocompleteMaxVisible: 10,
|
|
53
59
|
spellingTypoDetection: true,
|
|
@@ -86,18 +92,8 @@ export interface ComposerOptions {
|
|
|
86
92
|
readonly now?: () => number;
|
|
87
93
|
}
|
|
88
94
|
|
|
89
|
-
/** How {@link Composer.setRuntimeChildren}
|
|
95
|
+
/** How {@link Composer.setRuntimeChildren} lays out the roots below the transcript. */
|
|
90
96
|
export interface RuntimeChildrenOptions {
|
|
91
|
-
/**
|
|
92
|
-
* Below-transcript roots whose growth is momentary (inline dialogs, a tall
|
|
93
|
-
* multi-line editor). They are billed at their smallest height so expansion
|
|
94
|
-
* clips the live tail instead of retiring rows a later shrink could not
|
|
95
|
-
* reclaim (#11007). Every other root is billed at its current height, so
|
|
96
|
-
* settled rows it displaces retire to native scrollback. Decision panels
|
|
97
|
-
* declaring `retireDisplacedTranscript` opt out of this floor even when
|
|
98
|
-
* mounted as a child of a transient editor container.
|
|
99
|
-
*/
|
|
100
|
-
readonly transient?: readonly Component[];
|
|
101
97
|
/**
|
|
102
98
|
* Dock order on a TSP terminal (no status strip; the composer carries its
|
|
103
99
|
* facts): the native dock stacks HUD pills, the working row and queued messages over
|
|
@@ -105,6 +101,20 @@ export interface RuntimeChildrenOptions {
|
|
|
105
101
|
* to the roots below the transcript in render order.
|
|
106
102
|
*/
|
|
107
103
|
readonly nativeDock?: readonly Component[];
|
|
104
|
+
/**
|
|
105
|
+
* Below-transcript roots the user opens and closes (the editor surface that
|
|
106
|
+
* drafts and dialogs swap into, docked reports). Their rows never become
|
|
107
|
+
* part of the post-run chrome floor, so collapsing them refills the screen.
|
|
108
|
+
*/
|
|
109
|
+
readonly transientChrome?: readonly Component[];
|
|
110
|
+
/**
|
|
111
|
+
* Transient roots that clear themselves (todo and subagent HUDs, progress).
|
|
112
|
+
* Their first paint can be their tallest, e.g. a restored todo list on a
|
|
113
|
+
* resumed session, so unlike {@link transientChrome} it is never treated as
|
|
114
|
+
* their resting height: all of their rows stay out of the floor. Roots listed
|
|
115
|
+
* here are transient and need not also appear in `transientChrome`.
|
|
116
|
+
*/
|
|
117
|
+
readonly selfClearingChrome?: readonly Component[];
|
|
108
118
|
}
|
|
109
119
|
|
|
110
120
|
/** Controls the first terminal paint for a composer that does not already own the terminal. */
|
|
@@ -257,19 +267,44 @@ export class Composer implements TerminalFrameProvider, NativeSurfaceProvider {
|
|
|
257
267
|
#retiredHeaderStart = 0;
|
|
258
268
|
#resizeRetiredHeaderStart: number | undefined;
|
|
259
269
|
#lastNormalRows = 0;
|
|
260
|
-
//
|
|
261
|
-
//
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
+
// Retirement bills below-transcript chrome at this floor, not its current
|
|
271
|
+
// height. It follows contractions immediately and promotes stable eligible
|
|
272
|
+
// growth only while idle. Transient roots count at their minimum painted
|
|
273
|
+
// height: a growing dialog or draft clips the live tail without committing
|
|
274
|
+
// displaced rows, so those rows refill the screen when it shrinks (#11007).
|
|
275
|
+
// Displaced rows reach history when newer output pushes them past the floor
|
|
276
|
+
// or persistent idle chrome settles and raises it.
|
|
277
|
+
// With `completeScrollback`, rows the chrome pushes off screen go straight
|
|
278
|
+
// to scrollback, and when it shrinks the freed rows stay blank.
|
|
279
|
+
// The floor is terminal-height independent — chrome heights do not scale
|
|
280
|
+
// with rows — so it is retained across resizes rather than rediscovered
|
|
281
|
+
// from whatever chrome is expanded when the height changes.
|
|
282
|
+
#chromeFloor: number | undefined;
|
|
283
|
+
// Settled idle chrome has retired rows; contraction must preserve native history.
|
|
284
|
+
#chromeFloorPromoted = false;
|
|
285
|
+
#runActive = false;
|
|
270
286
|
#transientChrome: ReadonlySet<Component> = new Set();
|
|
271
|
-
#
|
|
272
|
-
|
|
287
|
+
#selfClearingChrome: ReadonlySet<Component> = new Set();
|
|
288
|
+
// Fewest rows the user-driven transient roots have painted since mount; only rows above it are excluded from the floor.
|
|
289
|
+
#transientFloor: number | undefined;
|
|
290
|
+
// Editor and shape the transient floor was measured under, plus the last frame's transient rows.
|
|
291
|
+
// A shape swap (band → pi) changes the editor's resting height; a minimum carried over from the
|
|
292
|
+
// shorter shape would exclude the taller shape's fixed rows forever and clip that many transcript rows.
|
|
293
|
+
#transientEditor: Component | undefined;
|
|
294
|
+
#transientEditorStyle: string | undefined;
|
|
295
|
+
#lastTransientRows = 0;
|
|
296
|
+
// Fewest rows the bare editor painted before the runtime mounted, and the shape it was painted in.
|
|
297
|
+
// A draft replayed from the startup keystroke buffer can already be present on the first runtime
|
|
298
|
+
// frame, so that frame is not the editor's resting height; this is, because the bootstrap frame
|
|
299
|
+
// paints before replay. Only valid while the shape is unchanged.
|
|
300
|
+
#bootstrapEditorRows: number | undefined;
|
|
301
|
+
#bootstrapEditorStyle: string | undefined;
|
|
302
|
+
// Idle chrome height awaiting the settle window, and whether it has elapsed.
|
|
303
|
+
// Turn-scoped HUDs can close a few frames after the last block finalizes;
|
|
304
|
+
// billing them first would commit rows they cover and strand a band.
|
|
305
|
+
#idleChrome: number | undefined;
|
|
306
|
+
#idleChromeSettled = false;
|
|
307
|
+
#idleChromeTimer: RenderTimer | undefined;
|
|
273
308
|
/** Rows the chrome below each below-transcript root took in the last frame (see {@link rowsBelow}). */
|
|
274
309
|
#rowsBelow = new Map<Component, number>();
|
|
275
310
|
#lastInterruptAt = 0;
|
|
@@ -339,6 +374,9 @@ export class Composer implements TerminalFrameProvider, NativeSurfaceProvider {
|
|
|
339
374
|
this.ui.addChild(this.editor);
|
|
340
375
|
this.ui.addChild(this.#statusHost);
|
|
341
376
|
this.ui.setFocus(this.editor);
|
|
377
|
+
// A settle timer that fires while the TUI is stopped (external editor, suspend) is dropped;
|
|
378
|
+
// forget it on restart so the next frame re-arms instead of waiting on it forever.
|
|
379
|
+
this.ui.addStartListener(() => this.#resetIdleChrome());
|
|
342
380
|
}
|
|
343
381
|
/**
|
|
344
382
|
* Rows the below-transcript chrome under `root` (editor, status line, …)
|
|
@@ -349,18 +387,6 @@ export class Composer implements TerminalFrameProvider, NativeSurfaceProvider {
|
|
|
349
387
|
return this.#rowsBelow.get(root);
|
|
350
388
|
}
|
|
351
389
|
|
|
352
|
-
/**
|
|
353
|
-
* Keep the input on the bottom row while the live rows cannot fill the
|
|
354
|
-
* screen, as after an inline decision panel closes. A tall block that just
|
|
355
|
-
* left the chrome above the editor (a command report) may have scrolled
|
|
356
|
-
* rows into native history that cannot be pulled back; without the pin the
|
|
357
|
-
* editor would jump up to where the shorter frame now ends. The pin lifts
|
|
358
|
-
* once live rows fill the screen again.
|
|
359
|
-
*/
|
|
360
|
-
pinInputToBottom(): void {
|
|
361
|
-
this.#anchorAfterInlineRetirement = true;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
390
|
/** Compose the bounded mutable viewport and the next ordered history append. */
|
|
365
391
|
renderFrame(viewport: ViewportSize): TerminalFramePlan {
|
|
366
392
|
if (!this.#started || this.#stopped) return { viewport: [] };
|
|
@@ -377,62 +403,57 @@ export class Composer implements TerminalFrameProvider, NativeSurfaceProvider {
|
|
|
377
403
|
const transcriptIndex = roots.findIndex(root => root instanceof TranscriptContainer);
|
|
378
404
|
if (transcriptIndex < 0) {
|
|
379
405
|
this.#lastClickSpans = [];
|
|
380
|
-
|
|
406
|
+
const viewport: string[] = [];
|
|
407
|
+
for (const root of roots) {
|
|
408
|
+
const rendered = root.render(width);
|
|
409
|
+
if (!this.#runtimeMounted && root === this.editor) this.#recordBootstrapEditorRows(rendered.length);
|
|
410
|
+
viewport.push(...rendered);
|
|
411
|
+
}
|
|
412
|
+
return { viewport: viewport.slice(-rows) };
|
|
381
413
|
}
|
|
382
414
|
const transcript = roots[transcriptIndex] as TranscriptContainer;
|
|
383
415
|
const preRoots = this.#renderRoots(roots.slice(0, transcriptIndex), width);
|
|
384
416
|
const afterRoots = roots.slice(transcriptIndex + 1);
|
|
385
417
|
const after: string[] = [];
|
|
386
418
|
const afterSpans: ViewportClickSpan[] = [];
|
|
387
|
-
let transientRows = 0;
|
|
388
|
-
let displacingRows = 0;
|
|
389
|
-
let decisionPanelOpen = false;
|
|
390
419
|
const ends: { root: Component; end: number }[] = [];
|
|
420
|
+
let transientRows = 0;
|
|
421
|
+
let selfClearingRows = 0;
|
|
391
422
|
for (const root of afterRoots) {
|
|
392
|
-
const chrome: Component = root;
|
|
393
423
|
const start = after.length;
|
|
394
424
|
this.#renderBelowRoot(root, width, after, afterSpans);
|
|
425
|
+
if (this.#selfClearingChrome.has(root)) selfClearingRows += after.length - start;
|
|
426
|
+
else if (this.#transientChrome.has(root)) transientRows += after.length - start;
|
|
395
427
|
ends.push({ root, end: after.length });
|
|
396
|
-
if (this.#transientChrome.has(root)) transientRows += after.length - start;
|
|
397
|
-
if (
|
|
398
|
-
chrome.retireDisplacedTranscript ||
|
|
399
|
-
(root instanceof Container && root.children.some(child => child.retireDisplacedTranscript))
|
|
400
|
-
) {
|
|
401
|
-
if (this.#transientChrome.has(root)) displacingRows += after.length - start;
|
|
402
|
-
decisionPanelOpen = true;
|
|
403
|
-
}
|
|
404
428
|
}
|
|
405
429
|
this.#rowsBelow = new Map(ends.map(({ root, end }) => [root, after.length - end]));
|
|
430
|
+
const transientFloor = this.#syncTransientFloor(transientRows);
|
|
431
|
+
// Eligible chrome excludes user-driven transient rows above their resting height and every
|
|
432
|
+
// self-clearing row; contraction, floor and rebaseline all use it.
|
|
433
|
+
const eligibleChrome = after.length - (transientRows - transientFloor) - selfClearingRows;
|
|
434
|
+
const promotedChromeContracted = this.#chromeFloorPromoted && eligibleChrome < (this.#chromeFloor ?? 0);
|
|
435
|
+
this.#chromeFloor = Math.min(this.#chromeFloor ?? eligibleChrome, eligibleChrome);
|
|
436
|
+
this.#rebaselineChromeFloor(this.#runActive || transcript.hasActiveBlock(), eligibleChrome);
|
|
437
|
+
const belowFloor = this.#preferences.completeScrollback ? after.length : this.#chromeFloor;
|
|
406
438
|
// Offer history under capacity pressure only: blocks stay live (and keep
|
|
407
439
|
// reflowing to the current width) while the screen has room. A batch
|
|
408
440
|
// leaves the mutable viewport in the same frame it is appended, so its
|
|
409
441
|
// rows are never painted twice.
|
|
410
|
-
//
|
|
411
|
-
// Retirement bills ordinary transient roots at their floor, not their
|
|
412
|
-
// peak: a confirmation dialog or a tall multi-line editor clips the live tail for
|
|
413
|
-
// its lifetime, but must not permanently commit transcript rows to native
|
|
414
|
-
// history — otherwise a later shrink cannot refill the freed rows and the
|
|
415
|
-
// editor drifts up above a band of blank rows (#11007).
|
|
416
|
-
this.#transientChromeFloor = Math.min(this.#transientChromeFloor ?? transientRows, transientRows);
|
|
417
|
-
// An ask panel remains open while the user reads the preceding response.
|
|
418
|
-
// Its displaced settled rows must be reachable in scrollback now, rather
|
|
419
|
-
// than clipped until the panel closes. Ordinary drafts retain their floor.
|
|
420
|
-
const belowFloor = after.length - transientRows + Math.max(this.#transientChromeFloor, displacingRows);
|
|
421
442
|
const now = performance.now();
|
|
422
443
|
const frame: AnimationFrame = { now, tick: Math.floor(now / 80) };
|
|
423
444
|
// Retirement measures the same live blocks the viewport lays out below;
|
|
424
445
|
// one open frame renders each of them once for both.
|
|
425
446
|
transcript.beginFrame(frame);
|
|
426
447
|
const history = this.#offerHistory(transcript, width, rows, preRoots.length + belowFloor);
|
|
427
|
-
if (decisionPanelOpen && history !== undefined) this.#anchorAfterInlineRetirement = true;
|
|
428
448
|
const headerVisible = !this.#headerRetired && this.#offeredHistory?.source !== "header";
|
|
429
449
|
const headerRows = headerVisible ? this.#header.render(width) : [];
|
|
430
450
|
const before = [...headerRows, ...preRoots];
|
|
431
451
|
// The live tail is laid out against the same baseline retirement is
|
|
432
452
|
// billed against, so its compaction allocator (one row per block, no
|
|
433
453
|
// inter-block blanks) engages only when a block genuinely cannot retire.
|
|
434
|
-
// Rows the
|
|
435
|
-
// the `drop` slice below
|
|
454
|
+
// Rows the chrome above its floor displaces are clipped from the top by
|
|
455
|
+
// the `drop` slice below; they stay live until the chrome shrinks. With
|
|
456
|
+
// `completeScrollback` none are held back.
|
|
436
457
|
const active = transcript.renderViewport(width, Math.max(0, rows - before.length - belowFloor), frame);
|
|
437
458
|
const activeSpans: ViewportClickSpan[] = [];
|
|
438
459
|
for (const span of transcript.getLastViewportSpans()) {
|
|
@@ -442,13 +463,39 @@ export class Composer implements TerminalFrameProvider, NativeSurfaceProvider {
|
|
|
442
463
|
}
|
|
443
464
|
const drop = Math.max(0, before.length + active.length + after.length - rows);
|
|
444
465
|
const mutable = [...before, ...active, ...after].slice(drop);
|
|
445
|
-
// Once
|
|
446
|
-
|
|
447
|
-
//
|
|
448
|
-
//
|
|
449
|
-
//
|
|
450
|
-
|
|
451
|
-
|
|
466
|
+
// Once a paint reaches the bottom row over committed history, rows above
|
|
467
|
+
// it may already be in native scrollback, which no later frame can pull
|
|
468
|
+
// back down. Keep the input on the bottom row whenever the frame shrinks
|
|
469
|
+
// — a closing dialog, report or HUD, or a live block collapsing. The
|
|
470
|
+
// slack that leaves has to sit somewhere: rows above the live area hold
|
|
471
|
+
// committed history still on screen, so the live rows cannot move up into
|
|
472
|
+
// them, and replaying those rows would duplicate them while clearing
|
|
473
|
+
// history would lose them. Replays position themselves. Without native
|
|
474
|
+
// history the live viewport is the whole document and shrinks in place.
|
|
475
|
+
const filledHistoryRows =
|
|
476
|
+
history?.kind === "replay" || !(history !== undefined || this.ui.hasProviderHistory())
|
|
477
|
+
? undefined
|
|
478
|
+
: this.ui.getFilledHistoryRows();
|
|
479
|
+
// Once settled chrome has retired rows, shrinking it cannot pull those
|
|
480
|
+
// rows back out of native scrollback. Let the editor move up instead of
|
|
481
|
+
// inserting a seam band; subsequent frames retain that natural anchor
|
|
482
|
+
// until new output fills the screen again. Shell history stays untouched.
|
|
483
|
+
const slack =
|
|
484
|
+
filledHistoryRows === undefined || promotedChromeContracted
|
|
485
|
+
? 0
|
|
486
|
+
: Math.max(0, rows - Math.min(rows, filledHistoryRows + (history?.rows.length ?? 0)) - mutable.length);
|
|
487
|
+
// Park the slack below the live rows rather than above them. Above, it
|
|
488
|
+
// splits the on-screen history from the live tail and reads as a hole
|
|
489
|
+
// punched into whatever was streaming — a table's rows included — and
|
|
490
|
+
// each slack row is written with no cells, so no repaint, mark or
|
|
491
|
+
// selection can cover it until something scrolls it away. Below, the
|
|
492
|
+
// transcript stays contiguous with the history, the slack reads as the
|
|
493
|
+
// empty tail of a short document, and growth consumes it before any row
|
|
494
|
+
// can scroll.
|
|
495
|
+
if (slack > 0) {
|
|
496
|
+
const at = Math.max(0, Math.min(mutable.length, before.length + active.length - drop));
|
|
497
|
+
mutable.splice(at, 0, ...Array<string>(slack).fill(""));
|
|
498
|
+
}
|
|
452
499
|
const viewportLength = mutable.length;
|
|
453
500
|
const spans: ViewportClickSpan[] = [];
|
|
454
501
|
const shift = (span: ViewportClickSpan, base: number): void => {
|
|
@@ -462,8 +509,8 @@ export class Composer implements TerminalFrameProvider, NativeSurfaceProvider {
|
|
|
462
509
|
spans.push({ start: clamped, end, candidates: (local: number) => span.candidates(local + skew) });
|
|
463
510
|
}
|
|
464
511
|
};
|
|
465
|
-
for (const span of activeSpans) shift(span,
|
|
466
|
-
for (const span of afterSpans) shift(span,
|
|
512
|
+
for (const span of activeSpans) shift(span, before.length - drop);
|
|
513
|
+
for (const span of afterSpans) shift(span, slack + before.length + active.length - drop);
|
|
467
514
|
this.#lastClickSpans = spans;
|
|
468
515
|
if (history !== undefined && this.#offeredHistory?.source === "header") {
|
|
469
516
|
const visibleHeaderRows = Math.max(0, rows - (mutable.length + drop));
|
|
@@ -672,6 +719,71 @@ export class Composer implements TerminalFrameProvider, NativeSurfaceProvider {
|
|
|
672
719
|
}
|
|
673
720
|
}
|
|
674
721
|
|
|
722
|
+
/** Raise the floor to eligible idle chrome that survives the settle window, even if it appeared after the run. */
|
|
723
|
+
#rebaselineChromeFloor(busy: boolean, chrome: number): void {
|
|
724
|
+
if (busy || chrome <= (this.#chromeFloor ?? chrome)) {
|
|
725
|
+
this.#resetIdleChrome();
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
if (this.#idleChromeSettled && this.#idleChrome === chrome) {
|
|
729
|
+
this.#chromeFloorPromoted = true;
|
|
730
|
+
this.#chromeFloor = chrome;
|
|
731
|
+
this.#resetIdleChrome();
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
if (this.#idleChrome === chrome && this.#idleChromeTimer !== undefined) return;
|
|
735
|
+
this.#resetIdleChrome();
|
|
736
|
+
this.#idleChrome = chrome;
|
|
737
|
+
this.#idleChromeTimer = this.ui.scheduleAfter(IDLE_CHROME_SETTLE_MS, () => {
|
|
738
|
+
this.#idleChromeTimer = undefined;
|
|
739
|
+
this.#idleChromeSettled = true;
|
|
740
|
+
this.ui.requestRender();
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
#resetIdleChrome(): void {
|
|
745
|
+
this.#idleChromeTimer?.cancel();
|
|
746
|
+
this.#idleChromeTimer = undefined;
|
|
747
|
+
this.#idleChrome = undefined;
|
|
748
|
+
this.#idleChromeSettled = false;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/** Track the empty editor's pre-mount height per shape; a shape change restarts the measurement. */
|
|
752
|
+
#recordBootstrapEditorRows(rows: number): void {
|
|
753
|
+
const style = this.editor.getBorderStyle();
|
|
754
|
+
this.#bootstrapEditorRows =
|
|
755
|
+
style === this.#bootstrapEditorStyle ? Math.min(this.#bootstrapEditorRows ?? rows, rows) : rows;
|
|
756
|
+
this.#bootstrapEditorStyle = style;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Follow the transient roots' resting height. Normally a running minimum; when the editor or its
|
|
761
|
+
* shape changes, the floor shifts by that frame's height change instead, so draft growth stays
|
|
762
|
+
* excluded while the new shape's fixed rows are billed.
|
|
763
|
+
*/
|
|
764
|
+
#syncTransientFloor(transientRows: number): number {
|
|
765
|
+
const editor = this.editor;
|
|
766
|
+
const style = editor.getBorderStyle();
|
|
767
|
+
let floor: number;
|
|
768
|
+
if (this.#transientFloor === undefined) {
|
|
769
|
+
// The first runtime frame may already carry a replayed startup draft; seed from the empty
|
|
770
|
+
// editor's pre-mount height when it was painted in the same shape.
|
|
771
|
+
const bootstrap = this.#bootstrapEditorStyle === style ? this.#bootstrapEditorRows : undefined;
|
|
772
|
+
floor = bootstrap === undefined ? transientRows : Math.min(transientRows, bootstrap);
|
|
773
|
+
} else if (editor !== this.#transientEditor || style !== this.#transientEditorStyle) {
|
|
774
|
+
floor = Math.max(0, Math.min(transientRows, this.#transientFloor + transientRows - this.#lastTransientRows));
|
|
775
|
+
} else {
|
|
776
|
+
floor = Math.min(this.#transientFloor, transientRows);
|
|
777
|
+
}
|
|
778
|
+
this.#transientFloor = floor;
|
|
779
|
+
this.#bootstrapEditorRows = undefined;
|
|
780
|
+
this.#bootstrapEditorStyle = undefined;
|
|
781
|
+
this.#transientEditor = editor;
|
|
782
|
+
this.#transientEditorStyle = style;
|
|
783
|
+
this.#lastTransientRows = transientRows;
|
|
784
|
+
return floor;
|
|
785
|
+
}
|
|
786
|
+
|
|
675
787
|
/** Header retires first; replay coalesces it with the complete transcript ledger. */
|
|
676
788
|
#offerHistory(
|
|
677
789
|
transcript: TranscriptContainer,
|
|
@@ -888,6 +1000,17 @@ export class Composer implements TerminalFrameProvider, NativeSurfaceProvider {
|
|
|
888
1000
|
this.ui.requestRender();
|
|
889
1001
|
}
|
|
890
1002
|
|
|
1003
|
+
/**
|
|
1004
|
+
* Mark whether a host agent run is in flight. Mid-run gaps with no streaming
|
|
1005
|
+
* block (tool arguments not yet parseable, between tool calls) then do not
|
|
1006
|
+
* count as the run ending when the post-run chrome floor is decided.
|
|
1007
|
+
*/
|
|
1008
|
+
setRunActive(active: boolean): void {
|
|
1009
|
+
if (this.#runActive === active) return;
|
|
1010
|
+
this.#runActive = active;
|
|
1011
|
+
this.ui.requestRender();
|
|
1012
|
+
}
|
|
1013
|
+
|
|
891
1014
|
/** Replace optional header content around the stable welcome scene. */
|
|
892
1015
|
setHeaderExtras(before: readonly Component[], after: readonly Component[]): void {
|
|
893
1016
|
if (this.#stopped) return;
|
|
@@ -924,10 +1047,13 @@ export class Composer implements TerminalFrameProvider, NativeSurfaceProvider {
|
|
|
924
1047
|
/** Mount or replace session-aware root children while preserving the header and status hosts. */
|
|
925
1048
|
setRuntimeChildren(children: readonly Component[], options: RuntimeChildrenOptions = {}): void {
|
|
926
1049
|
if (this.#stopped) return;
|
|
927
|
-
this.#
|
|
928
|
-
this.#
|
|
929
|
-
this.#
|
|
1050
|
+
this.#chromeFloor = undefined;
|
|
1051
|
+
this.#chromeFloorPromoted = false;
|
|
1052
|
+
this.#resetIdleChrome();
|
|
930
1053
|
this.#nativeDock = options.nativeDock;
|
|
1054
|
+
this.#transientChrome = new Set(options.transientChrome);
|
|
1055
|
+
this.#selfClearingChrome = new Set(options.selfClearingChrome);
|
|
1056
|
+
this.#transientFloor = undefined;
|
|
931
1057
|
this.ui.removeChild(this.#statusHost);
|
|
932
1058
|
if (this.#runtimeMounted) {
|
|
933
1059
|
for (const child of this.#runtimeChildren) this.ui.removeChild(child);
|