@aragon-agent/cli 0.6.4 → 0.6.8
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 +37 -0
- package/README.md +163 -37
- package/dist/agent/controller.d.ts +49 -8
- package/dist/agent/controller.js +160 -48
- package/dist/agent/headless.d.ts +2 -1
- package/dist/agent/prompt-options.d.ts +15 -0
- package/dist/agent/prompt-options.js +1 -0
- package/dist/agent/reducer.d.ts +33 -19
- package/dist/agent/reducer.js +62 -71
- package/dist/agent/usage.d.ts +34 -0
- package/dist/agent/usage.js +43 -0
- package/dist/cli.js +131 -54
- package/dist/commands/builtins.js +12 -1
- package/dist/commands/registry.d.ts +2 -3
- package/dist/compaction/compactor.d.ts +60 -24
- package/dist/compaction/compactor.js +89 -61
- package/dist/compaction/context-command.d.ts +33 -0
- package/dist/compaction/context-command.js +166 -0
- package/dist/compaction/meter.d.ts +251 -0
- package/dist/compaction/meter.js +428 -0
- package/dist/compaction/pressure.d.ts +6 -0
- package/dist/compaction/pressure.js +4 -0
- package/dist/compaction/types.d.ts +41 -0
- package/dist/compaction/wiring.d.ts +47 -0
- package/dist/compaction/wiring.js +88 -27
- package/dist/config/cli-commands.js +5 -0
- package/dist/config/env.js +15 -1
- package/dist/config/load.d.ts +17 -0
- package/dist/config/load.js +45 -1
- package/dist/config/schema.d.ts +44 -0
- package/dist/config/schema.js +36 -0
- package/dist/config/store.js +27 -1
- package/dist/exec/events.d.ts +138 -1
- package/dist/exec/events.js +42 -0
- package/dist/exec/index.js +43 -6
- package/dist/exec/progress-reporter.d.ts +23 -0
- package/dist/exec/progress-reporter.js +88 -0
- package/dist/exec/runner.d.ts +34 -0
- package/dist/exec/runner.js +137 -25
- package/dist/fast/reviewer.d.ts +19 -1
- package/dist/fast/reviewer.js +22 -0
- package/dist/fast/types.d.ts +15 -0
- package/dist/fast/wiring.d.ts +10 -1
- package/dist/fast/wiring.js +18 -1
- package/dist/input/interrupt-gesture.d.ts +18 -0
- package/dist/input/interrupt-gesture.js +19 -0
- package/dist/input/pointer-router.d.ts +11 -0
- package/dist/input/pointer-router.js +14 -0
- package/dist/todo/limits.d.ts +4 -17
- package/dist/todo/limits.js +4 -17
- package/dist/todo/panel-rows.d.ts +8 -6
- package/dist/todo/panel-rows.js +19 -6
- package/dist/todo/store.d.ts +8 -13
- package/dist/todo/store.js +13 -14
- package/dist/todo/types.d.ts +2 -1
- package/dist/ui/App.d.ts +2 -0
- package/dist/ui/App.js +233 -183
- package/dist/ui/AutocompletePopup.d.ts +3 -1
- package/dist/ui/AutocompletePopup.js +9 -11
- package/dist/ui/BottomStatusRow.d.ts +1 -1
- package/dist/ui/Composer.d.ts +13 -5
- package/dist/ui/Composer.js +7 -11
- package/dist/ui/PromptCaret.d.ts +11 -0
- package/dist/ui/PromptCaret.js +31 -0
- package/dist/ui/PromptInput.d.ts +12 -6
- package/dist/ui/PromptInput.js +50 -16
- package/dist/ui/StatusBar.d.ts +22 -15
- package/dist/ui/StatusBar.js +61 -16
- package/dist/ui/TeamPanel.d.ts +3 -1
- package/dist/ui/TeamPanel.js +13 -9
- package/dist/ui/TodoPanel.d.ts +5 -5
- package/dist/ui/TodoPanel.js +56 -24
- package/dist/ui/Transcript.js +1 -0
- package/dist/ui/UpdateLine.d.ts +1 -1
- package/dist/ui/capabilities.js +5 -5
- package/dist/ui/composer-rows.js +50 -9
- package/dist/ui/exit-snapshot.d.ts +9 -0
- package/dist/ui/frame-differ.js +2 -19
- package/dist/ui/frame-observer.d.ts +17 -0
- package/dist/ui/frame-observer.js +81 -0
- package/dist/ui/frame-parser.d.ts +7 -0
- package/dist/ui/frame-parser.js +18 -0
- package/dist/ui/layout/AppShell.d.ts +7 -52
- package/dist/ui/layout/AppShell.js +2 -2
- package/dist/ui/layout/ScrollIndicator.d.ts +4 -3
- package/dist/ui/layout/ScrollIndicator.js +4 -4
- package/dist/ui/layout/ScrollViewport.d.ts +13 -66
- package/dist/ui/layout/ScrollViewport.js +183 -185
- package/dist/ui/layout/autocomplete.d.ts +18 -0
- package/dist/ui/layout/autocomplete.js +26 -0
- package/dist/ui/layout/budget.d.ts +3 -51
- package/dist/ui/layout/budget.js +4 -53
- package/dist/ui/layout/document-layout.d.ts +13 -0
- package/dist/ui/layout/document-layout.js +11 -0
- package/dist/ui/layout/follow-state.d.ts +1 -0
- package/dist/ui/layout/follow-state.js +1 -3
- package/dist/ui/layout/rail.d.ts +4 -53
- package/dist/ui/layout/rail.js +10 -62
- package/dist/ui/layout/scroll-indicator.d.ts +1 -1
- package/dist/ui/layout/scroll-indicator.js +3 -2
- package/dist/ui/layout/scrollbar-geometry.d.ts +17 -0
- package/dist/ui/layout/scrollbar-geometry.js +13 -0
- package/dist/ui/layout/team-panel.d.ts +18 -0
- package/dist/ui/layout/team-panel.js +22 -0
- package/dist/ui/layout/todo-layout.d.ts +25 -0
- package/dist/ui/layout/todo-layout.js +28 -0
- package/dist/ui/layout/viewport-geometry.d.ts +1 -0
- package/dist/ui/layout/viewport-geometry.js +1 -0
- package/dist/ui/layout/virtual-window.d.ts +1 -0
- package/dist/ui/layout/virtual-window.js +1 -1
- package/dist/ui/overlays/HelpOverlay.js +5 -2
- package/dist/ui/screen.d.ts +1 -1
- package/dist/ui/screen.js +4 -6
- package/dist/ui/scrollbar-controller.d.ts +30 -0
- package/dist/ui/scrollbar-controller.js +124 -0
- package/dist/ui/transcript-text.js +6 -2
- package/dist/ui/use-wheel-routing.d.ts +2 -0
- package/dist/ui/use-wheel-routing.js +6 -0
- package/package.json +2 -2
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import stripAnsi from 'strip-ansi';
|
|
2
|
+
import stringWidth from 'string-width';
|
|
3
|
+
import { parseInkFrame } from './frame-parser.js';
|
|
4
|
+
import { pickGlyphs } from './glyphs.js';
|
|
5
|
+
function summarizeFrame(chunk, rows, cols) {
|
|
6
|
+
const lines = typeof chunk === 'string' ? parseInkFrame(chunk) : null;
|
|
7
|
+
if (!lines || rows < 12 || cols < 40 || lines.length !== rows - 1)
|
|
8
|
+
return null;
|
|
9
|
+
const unicode = pickGlyphs({ unicode: true, colorLevel: 0 });
|
|
10
|
+
const cells = new Set(['|', '#', unicode.scrollTrack, unicode.scrollThumb]);
|
|
11
|
+
const plain = lines.map((line) => stripAnsi(line));
|
|
12
|
+
const track = plain.slice(1, rows - 3);
|
|
13
|
+
if (plain.some((line) => stringWidth(line) > cols) ||
|
|
14
|
+
!track.every((line) => stringWidth(line) === cols && cells.has(line.slice(-1))))
|
|
15
|
+
return null;
|
|
16
|
+
return { columns: cols, rows, trackTop: 2, trackRows: rows - 4 };
|
|
17
|
+
}
|
|
18
|
+
/** Observe before differ transformation, but acknowledge only after downstream write returns. */
|
|
19
|
+
export function createFrameObserver(options) {
|
|
20
|
+
const { stdout, terminal, scrollbar } = options;
|
|
21
|
+
let summary = null;
|
|
22
|
+
let disposed = false;
|
|
23
|
+
let resizeTimer = null;
|
|
24
|
+
const invalidate = () => { summary = null; scrollbar.invalidate(); };
|
|
25
|
+
const onResize = () => {
|
|
26
|
+
invalidate();
|
|
27
|
+
if (resizeTimer)
|
|
28
|
+
clearTimeout(resizeTimer);
|
|
29
|
+
// Ink suppresses identical frames when a resize burst returns to its starting size.
|
|
30
|
+
// Wait past the 50 ms React size debounce, then request a normal redraw if needed.
|
|
31
|
+
resizeTimer = setTimeout(() => {
|
|
32
|
+
resizeTimer = null;
|
|
33
|
+
if (!disposed && !scrollbar.frameReady)
|
|
34
|
+
scrollbar.requestRedraw?.();
|
|
35
|
+
}, 60);
|
|
36
|
+
resizeTimer.unref?.();
|
|
37
|
+
};
|
|
38
|
+
const match = () => {
|
|
39
|
+
const g = scrollbar.geometry;
|
|
40
|
+
scrollbar.frameReady = !!summary && !!g && summary.columns === terminal.columns &&
|
|
41
|
+
summary.rows === terminal.rows && g.trackCol === summary.columns &&
|
|
42
|
+
g.trackTop === summary.trackTop && g.trackRows === summary.trackRows;
|
|
43
|
+
};
|
|
44
|
+
const observe = (chunk) => {
|
|
45
|
+
summary = summarizeFrame(chunk, terminal.rows, terminal.columns);
|
|
46
|
+
if (!summary) {
|
|
47
|
+
invalidate();
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
match();
|
|
51
|
+
};
|
|
52
|
+
scrollbar.onGeometry = match;
|
|
53
|
+
terminal.on('resize', onResize);
|
|
54
|
+
const bound = new Map();
|
|
55
|
+
const proxy = new Proxy(stdout, {
|
|
56
|
+
get(target, prop) {
|
|
57
|
+
if (prop === 'write')
|
|
58
|
+
return (...args) => {
|
|
59
|
+
const result = Reflect.apply(target.write, target, args);
|
|
60
|
+
if (!disposed)
|
|
61
|
+
observe(args[0]);
|
|
62
|
+
return result;
|
|
63
|
+
};
|
|
64
|
+
const value = Reflect.get(target, prop, target);
|
|
65
|
+
if (typeof value !== 'function')
|
|
66
|
+
return value;
|
|
67
|
+
if (!bound.has(prop))
|
|
68
|
+
bound.set(prop, value.bind(target));
|
|
69
|
+
return bound.get(prop);
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
return { stdout: proxy, invalidate, dispose: () => {
|
|
73
|
+
disposed = true;
|
|
74
|
+
if (resizeTimer)
|
|
75
|
+
clearTimeout(resizeTimer);
|
|
76
|
+
resizeTimer = null;
|
|
77
|
+
terminal.off('resize', onResize);
|
|
78
|
+
scrollbar.onGeometry = undefined;
|
|
79
|
+
invalidate();
|
|
80
|
+
} };
|
|
81
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Recognize Ink's erase-lines prefix without accepting arbitrary control writes. */
|
|
2
|
+
export declare function matchInkErasePrefix(chunk: string): {
|
|
3
|
+
prefix: string;
|
|
4
|
+
erased: number;
|
|
5
|
+
} | null;
|
|
6
|
+
/** Parse complete frames, including Ink's prefixless initial frame. */
|
|
7
|
+
export declare function parseInkFrame(chunk: string): string[] | null;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Recognize Ink's erase-lines prefix without accepting arbitrary control writes. */
|
|
2
|
+
export function matchInkErasePrefix(chunk) {
|
|
3
|
+
const match = /^(?:\x1b\[2K(?:\x1b\[1A)?)+\x1b\[G/.exec(chunk);
|
|
4
|
+
return match ? { prefix: match[0], erased: match[0].split('\x1b[2K').length - 1 } : null;
|
|
5
|
+
}
|
|
6
|
+
/** Parse complete frames, including Ink's prefixless initial frame. */
|
|
7
|
+
export function parseInkFrame(chunk) {
|
|
8
|
+
const prefix = matchInkErasePrefix(chunk);
|
|
9
|
+
const body = chunk.slice(prefix?.prefix.length ?? 0);
|
|
10
|
+
if (!body.endsWith('\n'))
|
|
11
|
+
return null;
|
|
12
|
+
// SGR and OSC-8 may decorate text; cursor movement is never frame content.
|
|
13
|
+
const controls = body.replace(/\x1b\[[0-9;:]*m/g, '')
|
|
14
|
+
.replace(/\x1b\]8;[^\x07\x1b]*(?:\x07|\x1b\\)/g, '');
|
|
15
|
+
if (/[\x00-\x08\x0b-\x1f\x7f]/.test(controls))
|
|
16
|
+
return null;
|
|
17
|
+
return body.slice(0, -1).split('\n');
|
|
18
|
+
}
|
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AppShell (spec §4.3) — the frame that makes R2 true.
|
|
3
|
-
*
|
|
4
|
-
* In `fullscreen` the root box has a FIXED height of `frameHeight(rows)` and the
|
|
5
|
-
* middle viewport is the only `flexGrow` child, so the bottom chrome (toast /
|
|
6
|
-
* composer / status bar) is physically the last thing on the screen from the
|
|
7
|
-
* very first frame — empty session, long session, mid-scroll, overlay open.
|
|
8
|
-
*
|
|
9
|
-
* In `inline` the same children are emitted in the same order with no height
|
|
10
|
-
* constraint, i.e. byte-for-byte the v0.2.0 document-flow behavior.
|
|
11
|
-
*/
|
|
1
|
+
/** Stable fullscreen slots preserve the editor during overlays and small resizes. */
|
|
12
2
|
import React from 'react';
|
|
13
3
|
import { type RenderMode } from './frame.js';
|
|
14
4
|
export interface AppShellProps {
|
|
@@ -17,51 +7,16 @@ export interface AppShellProps {
|
|
|
17
7
|
cols: number;
|
|
18
8
|
header: React.ReactNode;
|
|
19
9
|
viewport: React.ReactNode;
|
|
20
|
-
/**
|
|
21
|
-
* The live subagent roster (team-subagents §6.1), or nothing.
|
|
22
|
-
*
|
|
23
|
-
* IT RENDERS IN THE BOTTOM CHROME OF THE FULL-SCREEN BRANCH, above `toast` and
|
|
24
|
-
* below the viewport: the roster is transient status about the run in progress,
|
|
25
|
-
* so it belongs with the other transient rows rather than inside the scrolling
|
|
26
|
-
* transcript, where it would be interleaved with history and scroll away.
|
|
27
|
-
*/
|
|
10
|
+
/** Legacy inline footer; fullscreen assembles these slots inside its viewport. */
|
|
28
11
|
team?: React.ReactNode;
|
|
29
|
-
/**
|
|
30
|
-
* The right-hand region of the MIDDLE band (todo-plan-execution §3.9), or
|
|
31
|
-
* nothing.
|
|
32
|
-
*
|
|
33
|
-
* NAMED FOR ITS POSITION, NOT ITS CONTENT (D-18): this file is a layout
|
|
34
|
-
* primitive and has no business knowing what a todo is. `team` above already
|
|
35
|
-
* leaks content vocabulary in here; there is no reason to repeat it.
|
|
36
|
-
*
|
|
37
|
-
* IT MUST BE RENDERED IN THE MIDDLE BAND, BESIDE THE VIEWPORT (C-6 / I-4) —
|
|
38
|
-
* that is what makes it a rail rather than another row of bottom chrome. Moved
|
|
39
|
-
* down into the bottom box it would stop being a column beside the transcript
|
|
40
|
-
* and start eating viewport height, growing and shrinking the transcript every
|
|
41
|
-
* time the list changed length.
|
|
42
|
-
*
|
|
43
|
-
* The INLINE branch does not include it at all — a structural guarantee rather
|
|
44
|
-
* than a promise that the caller passes `null` (non-goal 4).
|
|
45
|
-
*/
|
|
12
|
+
/** Kept source-compatible; fullscreen rail is owned by ScrollViewport. */
|
|
46
13
|
rail?: React.ReactNode;
|
|
47
|
-
/**
|
|
48
|
-
* The one-row plan strip for INLINE mode (todo-plan-followthrough §3.7), or
|
|
49
|
-
* nothing.
|
|
50
|
-
*
|
|
51
|
-
* THE STRUCTURAL MIRROR OF `rail` ABOVE, in the opposite direction: the
|
|
52
|
-
* FULL-SCREEN branch does not reference it at all. The reason is the same one
|
|
53
|
-
* `rail`'s inline omission records — a prop that is merely SUPPOSED to be null
|
|
54
|
-
* in one mode becomes non-null eventually, and then the rail and the strip are
|
|
55
|
-
* both on screen showing the same list (I-6). Fullscreen has the rail; that is
|
|
56
|
-
* the whole design.
|
|
57
|
-
*
|
|
58
|
-
* It sits between `viewport` and `team`, mirroring where the rail sits in the
|
|
59
|
-
* other branch: last thing above the bottom chrome, so the strip and the
|
|
60
|
-
* roster read top-to-bottom in the same order in both modes.
|
|
61
|
-
*/
|
|
14
|
+
/** Inline-only plan strip. */
|
|
62
15
|
strip?: React.ReactNode;
|
|
63
16
|
toast: React.ReactNode;
|
|
64
17
|
composer: React.ReactNode;
|
|
65
18
|
status: React.ReactNode;
|
|
19
|
+
inactive?: boolean;
|
|
20
|
+
placeholder?: React.ReactNode;
|
|
66
21
|
}
|
|
67
|
-
export declare function AppShell({ mode, rows, cols, header, viewport, team,
|
|
22
|
+
export declare function AppShell({ mode, rows, cols, header, viewport, team, strip, toast, composer, status, inactive, placeholder, }: AppShellProps): React.ReactElement;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Box } from 'ink';
|
|
3
3
|
import { frameHeight } from './frame.js';
|
|
4
|
-
export function AppShell({ mode, rows, cols, header, viewport, team,
|
|
4
|
+
export function AppShell({ mode, rows, cols, header, viewport, team, strip, toast, composer, status, inactive = false, placeholder, }) {
|
|
5
5
|
if (mode === 'inline') {
|
|
6
6
|
return (_jsxs(Box, { flexDirection: "column", children: [header, viewport, strip, team, toast, composer, status] }));
|
|
7
7
|
}
|
|
8
|
-
return (_jsxs(Box, { flexDirection: "column", height: frameHeight(rows), width: cols, overflow: "hidden", children: [
|
|
8
|
+
return (_jsxs(Box, { flexDirection: "column", height: frameHeight(rows), width: cols, overflow: "hidden", children: [_jsxs(Box, { display: inactive ? 'none' : 'flex', flexDirection: "column", height: frameHeight(rows), width: cols, overflow: "hidden", children: [_jsx(Box, { height: 1, flexShrink: 0, overflow: "hidden", children: header }), _jsx(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, overflow: "hidden", children: viewport }), _jsx(Box, { height: 1, flexShrink: 0, overflow: "hidden", children: toast }), _jsx(Box, { height: 1, flexShrink: 0, overflow: "hidden", children: status })] }), _jsx(Box, { display: inactive ? 'flex' : 'none', height: frameHeight(rows), overflow: "hidden", children: placeholder })] }));
|
|
9
9
|
}
|
|
@@ -18,14 +18,15 @@ import React from 'react';
|
|
|
18
18
|
import type { Theme } from '../theme.js';
|
|
19
19
|
import type { TermCapabilities } from '../capabilities.js';
|
|
20
20
|
import type { ThumbRange } from './scroll-indicator.js';
|
|
21
|
-
/**
|
|
22
|
-
export declare const MIN_INDICATOR_COLS =
|
|
21
|
+
/** Smallest supported fullscreen width; every usable chat frame reserves the column. */
|
|
22
|
+
export declare const MIN_INDICATOR_COLS = 40;
|
|
23
23
|
export interface ScrollIndicatorProps {
|
|
24
24
|
/** Viewport height in rows, as measured by `ScrollViewport`. */
|
|
25
25
|
rows: number;
|
|
26
26
|
/** `null` when the content fits — the rail is drawn as bare track. */
|
|
27
27
|
thumb: ThumbRange | null;
|
|
28
|
+
dragging?: boolean;
|
|
28
29
|
theme: Theme;
|
|
29
30
|
caps: TermCapabilities;
|
|
30
31
|
}
|
|
31
|
-
export declare function ScrollIndicator({ rows, thumb, theme, caps, }: ScrollIndicatorProps): React.ReactElement;
|
|
32
|
+
export declare function ScrollIndicator({ rows, thumb, dragging, theme, caps, }: ScrollIndicatorProps): React.ReactElement;
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
import { pickGlyphs } from '../glyphs.js';
|
|
4
|
-
/**
|
|
5
|
-
export const MIN_INDICATOR_COLS =
|
|
6
|
-
export function ScrollIndicator({ rows, thumb, theme, caps, }) {
|
|
4
|
+
/** Smallest supported fullscreen width; every usable chat frame reserves the column. */
|
|
5
|
+
export const MIN_INDICATOR_COLS = 40;
|
|
6
|
+
export function ScrollIndicator({ rows, thumb, dragging = false, theme, caps, }) {
|
|
7
7
|
const glyphs = pickGlyphs(caps);
|
|
8
8
|
const count = Math.max(0, Math.floor(rows) || 0);
|
|
9
9
|
const cells = [];
|
|
10
10
|
for (let i = 0; i < count; i += 1) {
|
|
11
11
|
const onThumb = !!thumb && i >= thumb.start && i < thumb.start + thumb.size;
|
|
12
|
-
cells.push(_jsx(Text, { color: onThumb ? theme.primary : theme.
|
|
12
|
+
cells.push(_jsx(Text, { color: onThumb ? theme.primary : theme.muted, bold: onThumb && dragging, children: onThumb ? glyphs.scrollThumb : glyphs.scrollTrack }, i));
|
|
13
13
|
}
|
|
14
14
|
return (_jsx(Box, { flexDirection: "column", flexShrink: 0, width: 1, children: cells }));
|
|
15
15
|
}
|
|
@@ -1,88 +1,35 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ScrollViewport (spec §4.5) — a self-drawn scrolling window built from Ink's
|
|
3
|
-
* negative `marginTop` + `overflow: hidden` + `measureElement`.
|
|
4
|
-
*
|
|
5
|
-
* Once the frame occupies the whole screen, `<Static>` history is pushed above
|
|
6
|
-
* the frame where only one line is visible, so native scrollback stops being a
|
|
7
|
-
* usable mechanism and the app has to own scrolling. That is why this exists.
|
|
8
|
-
*
|
|
9
|
-
* SINGLE SOURCE OF TRUTH (review P1-7): both the metrics and the scroll offset
|
|
10
|
-
* live here. The parent only pushes an *intent* down and receives one derived
|
|
11
|
-
* *display* number back. Holding the offset in the parent as well would create a
|
|
12
|
-
* measure → setState → re-render → measure loop whose convergence is provable
|
|
13
|
-
* but whose frame count is not — expensive inside a 33 ms streaming window.
|
|
14
|
-
*
|
|
15
|
-
* AS OF tui-selection-and-scroll-follow THIS IS AN ADAPTER, NOT A POLICY OWNER.
|
|
16
|
-
* The two follow rules are pure and live in `follow-state.ts`; what stays here is
|
|
17
|
-
* measure → compute a tail delta → apply → arm a timer. `scroll.ts` is
|
|
18
|
-
* unchanged, so every existing scroll test keeps passing (R-10).
|
|
19
|
-
*
|
|
20
|
-
* The "N new lines · PgDn" hint used to take this viewport's LAST ROW. It now
|
|
21
|
-
* renders as a chip inside the composer's bordered input row (§4.2), which costs
|
|
22
|
-
* zero rows and removes the feedback loop where the hint's own presence shrank
|
|
23
|
-
* the viewport, which changed `overflowLines`, which was the number the hint
|
|
24
|
-
* displayed. The data still leaves through `onScrolledLinesChange`.
|
|
25
|
-
*/
|
|
26
1
|
import React from 'react';
|
|
2
|
+
import { type DOMElement } from 'ink';
|
|
27
3
|
import type { Theme } from '../theme.js';
|
|
28
4
|
import type { TermCapabilities } from '../capabilities.js';
|
|
5
|
+
import { type ScrollbarBridge } from '../scrollbar-controller.js';
|
|
29
6
|
import { type ScrollIntent } from './scroll.js';
|
|
30
7
|
import { type TailSink } from './follow-state.js';
|
|
31
8
|
export interface ScrollViewportProps {
|
|
32
|
-
/**
|
|
33
|
-
* Scroll intent. `nonce` is monotonic so pressing the same key twice re-fires.
|
|
34
|
-
* `repeat` folds a coalesced wheel burst into one state update (defaults 1).
|
|
35
|
-
*/
|
|
36
9
|
intent?: {
|
|
37
10
|
kind: ScrollIntent;
|
|
38
11
|
nonce: number;
|
|
39
12
|
repeat?: number;
|
|
40
13
|
};
|
|
41
|
-
/** Bumped by the parent to force a pin to the newest output (e.g. on submit). */
|
|
42
14
|
pinToBottomNonce?: number;
|
|
43
|
-
|
|
15
|
+
resetKey?: unknown;
|
|
44
16
|
onScrolledLinesChange?: (lines: number) => void;
|
|
45
|
-
|
|
46
|
-
* Rows hidden ABOVE the window, published on every change (§4.3.3).
|
|
47
|
-
*
|
|
48
|
-
* PUBLISHED, NOT LIFTED — the same shape as `onScrolledLinesChange`, so there
|
|
49
|
-
* is still exactly one owner of the offset. `shiftUp` changes exactly when the
|
|
50
|
-
* rows on screen move, which is the property the selection's clear-list was
|
|
51
|
-
* approximating by enumeration (I-9 / P1-4). Must be referentially stable.
|
|
52
|
-
*/
|
|
53
|
-
onViewportShiftChange?: (shiftUp: number) => void;
|
|
54
|
-
/**
|
|
55
|
-
* The transcript's tail row counter (§4.3.1a), written during ITS render and
|
|
56
|
-
* therefore already current when this component's layout effect runs.
|
|
57
|
-
*
|
|
58
|
-
* ABSENT ⇒ NO ANCHORING AT ALL, deliberately: every existing caller and every
|
|
59
|
-
* existing test that does not supply it behaves exactly as it did before this
|
|
60
|
-
* feature, because `tailDelta` is then permanently 0.
|
|
61
|
-
*/
|
|
17
|
+
onViewportShiftChange?: (shift: number) => void;
|
|
62
18
|
tailRowsRef?: TailSink;
|
|
63
|
-
/**
|
|
64
|
-
* True while a selection drag is in progress (§4.4). It anchors the content
|
|
65
|
-
* even from a pinned viewport, and it suspends the resume timer.
|
|
66
|
-
*/
|
|
67
19
|
hold?: boolean;
|
|
68
|
-
/** Idle delay before returning to the newest line, in ms; `0` disables (AC-9). */
|
|
69
20
|
resumeMs?: number;
|
|
70
|
-
/** Reserve the right-edge scroll-position rail (mouse-wheel §4.8). */
|
|
71
21
|
showScrollIndicator?: boolean;
|
|
72
|
-
/**
|
|
73
|
-
* The viewport's own width, when it is narrower than the terminal
|
|
74
|
-
* (todo-plan-execution §3.9 / C-7).
|
|
75
|
-
*
|
|
76
|
-
* This component is the ONLY one inside the viewport subtree that reads
|
|
77
|
-
* `stdout.columns` directly, and the place it uses the number — the
|
|
78
|
-
* `MIN_INDICATOR_COLS` gate — is about how much room THIS BOX has, not how
|
|
79
|
-
* wide the terminal is. Once the todo rail takes a fifth of the frame those two
|
|
80
|
-
* stop being the same number. Optional so every existing caller (and every
|
|
81
|
-
* test) is unchanged.
|
|
82
|
-
*/
|
|
83
22
|
cols?: number;
|
|
23
|
+
footer?: React.ReactNode;
|
|
24
|
+
rail?: React.ReactNode;
|
|
25
|
+
overlay?: React.ReactNode;
|
|
26
|
+
active?: boolean;
|
|
27
|
+
scrollbar?: ScrollbarBridge;
|
|
28
|
+
composerRef?: React.RefObject<DOMElement>;
|
|
29
|
+
onComposerVisibilityChange?: (visible: boolean) => void;
|
|
84
30
|
theme: Theme;
|
|
85
31
|
caps: TermCapabilities;
|
|
86
32
|
children: React.ReactNode;
|
|
87
33
|
}
|
|
88
|
-
|
|
34
|
+
/** One offset for messages, padding and editor; hidden trees keep their local state. */
|
|
35
|
+
export declare function ScrollViewport(props: ScrollViewportProps): React.ReactElement;
|