@aragon-agent/cli 0.6.3 → 0.6.7
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 +142 -0
- package/README.md +203 -13
- package/dist/agent/controller.d.ts +158 -2
- package/dist/agent/controller.js +400 -1
- package/dist/agent/reducer.d.ts +102 -19
- package/dist/agent/reducer.js +162 -71
- package/dist/agent/system-prompt.d.ts +14 -0
- package/dist/agent/system-prompt.js +2 -0
- package/dist/agent/usage.d.ts +34 -0
- package/dist/agent/usage.js +43 -0
- package/dist/cli.js +132 -23
- package/dist/commands/builtins.js +101 -1
- package/dist/compaction/child.js +5 -0
- package/dist/compaction/command.js +14 -1
- package/dist/compaction/compactor.d.ts +94 -24
- package/dist/compaction/compactor.js +247 -98
- 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 +77 -0
- package/dist/compaction/wiring.d.ts +56 -0
- package/dist/compaction/wiring.js +107 -25
- package/dist/config/cli-commands.js +5 -0
- package/dist/config/env.d.ts +1 -1
- package/dist/config/env.js +63 -2
- package/dist/config/load.d.ts +28 -0
- package/dist/config/load.js +83 -1
- package/dist/config/schema.d.ts +129 -0
- package/dist/config/schema.js +69 -0
- package/dist/config/store.js +47 -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/limits.d.ts +72 -0
- package/dist/input/limits.js +63 -0
- package/dist/input/paste-parse.d.ts +71 -0
- package/dist/input/paste-parse.js +95 -0
- package/dist/input/stdin-filter.d.ts +81 -0
- package/dist/input/stdin-filter.js +379 -0
- package/dist/logging/install.d.ts +2 -0
- package/dist/logging/install.js +53 -1
- package/dist/proc/classify.d.ts +52 -0
- package/dist/proc/classify.js +143 -0
- package/dist/proc/kill-tree.d.ts +42 -0
- package/dist/proc/kill-tree.js +119 -0
- package/dist/proc/limits.d.ts +84 -0
- package/dist/proc/limits.js +84 -0
- package/dist/proc/log-ring.d.ts +68 -0
- package/dist/proc/log-ring.js +102 -0
- package/dist/proc/prompt.d.ts +24 -0
- package/dist/proc/prompt.js +49 -0
- package/dist/proc/readiness.d.ts +102 -0
- package/dist/proc/readiness.js +219 -0
- package/dist/proc/supervisor.d.ts +103 -0
- package/dist/proc/supervisor.js +465 -0
- package/dist/proc/types.d.ts +175 -0
- package/dist/proc/types.js +15 -0
- package/dist/session/persist.js +36 -1
- 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/tools/bash-tool.d.ts +51 -2
- package/dist/tools/bash-tool.js +321 -51
- package/dist/tools/fs-tools.d.ts +17 -0
- package/dist/tools/index.d.ts +55 -2
- package/dist/tools/index.js +39 -1
- package/dist/tools/proc-tools.d.ts +25 -0
- package/dist/tools/proc-tools.js +102 -0
- package/dist/ui/App.d.ts +15 -2
- package/dist/ui/App.js +330 -69
- 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 +56 -1
- package/dist/ui/Composer.js +50 -6
- package/dist/ui/PromptInput.d.ts +52 -3
- package/dist/ui/PromptInput.js +201 -55
- package/dist/ui/StatusBar.d.ts +40 -15
- package/dist/ui/StatusBar.js +51 -9
- 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 +29 -1
- package/dist/ui/UpdateLine.d.ts +1 -1
- package/dist/ui/capabilities.js +5 -5
- package/dist/ui/composer-limits.d.ts +31 -0
- package/dist/ui/composer-limits.js +50 -0
- package/dist/ui/composer-rows.d.ts +73 -0
- package/dist/ui/composer-rows.js +201 -0
- package/dist/ui/editor-reducer.d.ts +37 -0
- package/dist/ui/editor-reducer.js +88 -12
- package/dist/ui/entries/ServiceCard.d.ts +66 -0
- package/dist/ui/entries/ServiceCard.js +116 -0
- package/dist/ui/entries/UserEntry.d.ts +17 -3
- package/dist/ui/entries/UserEntry.js +34 -5
- package/dist/ui/exit-snapshot.d.ts +9 -0
- package/dist/ui/layout/ScrollViewport.d.ts +1 -1
- package/dist/ui/layout/autocomplete.d.ts +18 -0
- package/dist/ui/layout/autocomplete.js +26 -0
- package/dist/ui/layout/budget.d.ts +34 -6
- package/dist/ui/layout/budget.js +32 -7
- package/dist/ui/layout/rail.d.ts +4 -53
- package/dist/ui/layout/rail.js +10 -62
- 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 +24 -0
- package/dist/ui/layout/todo-layout.js +27 -0
- package/dist/ui/layout/virtual-window.js +51 -1
- package/dist/ui/overlays/HelpOverlay.js +3 -0
- package/dist/ui/overlays/PlanReviewOverlay.js +10 -2
- package/dist/ui/overlays/QuestionOverlay.js +5 -1
- package/dist/ui/overlays/SettingsScreen.js +14 -1
- package/dist/ui/paste-frames.d.ts +87 -0
- package/dist/ui/paste-frames.js +112 -0
- package/dist/ui/paste-tokens.d.ts +90 -0
- package/dist/ui/paste-tokens.js +147 -0
- package/dist/ui/screen.d.ts +16 -0
- package/dist/ui/screen.js +45 -2
- package/dist/ui/selection/selection-controller.d.ts +1 -1
- package/dist/ui/transcript-text.js +55 -2
- package/dist/ui/use-wheel-routing.d.ts +1 -1
- package/package.json +2 -2
- package/dist/input/stdin-mouse-filter.d.ts +0 -47
- package/dist/input/stdin-mouse-filter.js +0 -153
|
@@ -3,15 +3,29 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The role glyph, the gutter rail and the inter-entry spacing all belong to
|
|
5
5
|
* `EntryFrame` now (§4.3), so this component renders content and nothing else.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
*
|
|
7
|
+
* IT DOES TAKE `caps` AS OF tui-paste-handling (G7): the row cap below needs one
|
|
8
|
+
* glyph, and a hardcoded ellipsis is the exact failure `glyphs.ts` exists to
|
|
9
|
+
* prevent. That is the only reason the earlier "it has no glyph of its own left
|
|
10
|
+
* to degrade" note no longer holds.
|
|
8
11
|
*/
|
|
9
12
|
import React from 'react';
|
|
10
13
|
import type { Theme } from '../theme.js';
|
|
11
|
-
|
|
14
|
+
import type { TermCapabilities } from '../capabilities.js';
|
|
15
|
+
declare function UserEntryImpl({ text, theme, caps, }: {
|
|
12
16
|
text: string;
|
|
13
17
|
theme: Theme;
|
|
18
|
+
caps: TermCapabilities;
|
|
14
19
|
}): React.ReactElement;
|
|
20
|
+
/**
|
|
21
|
+
* Rows this component renders for a given text.
|
|
22
|
+
*
|
|
23
|
+
* EXPORTED SO `virtual-window.ts` AND THIS FILE CANNOT DISAGREE (I-13). The
|
|
24
|
+
* render cap and the height estimate are ONE number; an over-estimated entry is
|
|
25
|
+
* never mounted, so it is never measured, so the estimate never self-corrects --
|
|
26
|
+
* which makes that particular disagreement permanent and silent.
|
|
27
|
+
*/
|
|
28
|
+
export declare function userEntryRenderedRows(lineCount: number): number;
|
|
15
29
|
/**
|
|
16
30
|
* `React.memo` with the DEFAULT comparator (tui-render-performance L2 / R3).
|
|
17
31
|
* `mapEntry` already preserves object identity for untouched entries, so the
|
|
@@ -1,16 +1,45 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* Render a user message entry.
|
|
4
4
|
*
|
|
5
5
|
* The role glyph, the gutter rail and the inter-entry spacing all belong to
|
|
6
6
|
* `EntryFrame` now (§4.3), so this component renders content and nothing else.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
*
|
|
8
|
+
* IT DOES TAKE `caps` AS OF tui-paste-handling (G7): the row cap below needs one
|
|
9
|
+
* glyph, and a hardcoded ellipsis is the exact failure `glyphs.ts` exists to
|
|
10
|
+
* prevent. That is the only reason the earlier "it has no glyph of its own left
|
|
11
|
+
* to degrade" note no longer holds.
|
|
9
12
|
*/
|
|
10
13
|
import React from 'react';
|
|
11
14
|
import { Box, Text } from 'ink';
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
import { pickGlyphs } from '../glyphs.js';
|
|
16
|
+
import { USER_ENTRY_MAX_ROWS } from '../composer-limits.js';
|
|
17
|
+
function UserEntryImpl({ text, theme, caps, }) {
|
|
18
|
+
const glyphs = pickGlyphs(caps);
|
|
19
|
+
const all = text.split('\n');
|
|
20
|
+
// A submitted 5 000-line paste otherwise asks Yoga to lay out 5 000 nodes
|
|
21
|
+
// inside a measured entry -- the hazard `render-cache.ts:37` already records
|
|
22
|
+
// in the same words ("a multi-megabyte 'code block' is almost always a pasted
|
|
23
|
+
// log"). The full text stays in `ViewState` and in the exit transcript, which
|
|
24
|
+
// is deliberately NOT capped: a replay a user pipes into an issue must be
|
|
25
|
+
// complete.
|
|
26
|
+
const hidden = Math.max(0, all.length - USER_ENTRY_MAX_ROWS);
|
|
27
|
+
const shown = hidden > 0 ? all.slice(0, USER_ENTRY_MAX_ROWS) : all;
|
|
28
|
+
return (_jsxs(Box, { flexDirection: "column", children: [shown.map((line, i) => (_jsx(Text, { color: theme.user, bold: i === 0, children: line }, i))), hidden > 0 && (
|
|
29
|
+
// The tail NAMES the number of hidden rows rather than hiding them
|
|
30
|
+
// silently, which is what makes the cap honest (D-9 / R-10).
|
|
31
|
+
_jsx(Text, { color: theme.muted, children: `${glyphs.ellipsis} +${hidden} more lines` }))] }));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Rows this component renders for a given text.
|
|
35
|
+
*
|
|
36
|
+
* EXPORTED SO `virtual-window.ts` AND THIS FILE CANNOT DISAGREE (I-13). The
|
|
37
|
+
* render cap and the height estimate are ONE number; an over-estimated entry is
|
|
38
|
+
* never mounted, so it is never measured, so the estimate never self-corrects --
|
|
39
|
+
* which makes that particular disagreement permanent and silent.
|
|
40
|
+
*/
|
|
41
|
+
export function userEntryRenderedRows(lineCount) {
|
|
42
|
+
return lineCount > USER_ENTRY_MAX_ROWS ? USER_ENTRY_MAX_ROWS + 1 : lineCount;
|
|
14
43
|
}
|
|
15
44
|
/**
|
|
16
45
|
* `React.memo` with the DEFAULT comparator (tui-render-performance L2 / R3).
|
|
@@ -14,6 +14,15 @@
|
|
|
14
14
|
import type { Entry, UsageTotal } from '../agent/reducer.js';
|
|
15
15
|
export interface ExitSnapshot {
|
|
16
16
|
entries: Entry[];
|
|
17
|
+
/**
|
|
18
|
+
* The session total, FORWARDED not copied.
|
|
19
|
+
*
|
|
20
|
+
* It gained `cacheReadTokens` / `cacheWriteTokens`
|
|
21
|
+
* (context-usage-gauge-accuracy §3.6) and this file needed no change for it,
|
|
22
|
+
* which is the point of referencing the reducer's type rather than restating
|
|
23
|
+
* its fields: `renderTranscriptText` reads the total through `promptTokensOf`,
|
|
24
|
+
* so the exit summary and the status bar cannot drift apart.
|
|
25
|
+
*/
|
|
17
26
|
usageTotal: UsageTotal;
|
|
18
27
|
provider: string;
|
|
19
28
|
model: string;
|
|
@@ -76,7 +76,7 @@ export interface ScrollViewportProps {
|
|
|
76
76
|
* This component is the ONLY one inside the viewport subtree that reads
|
|
77
77
|
* `stdout.columns` directly, and the place it uses the number — the
|
|
78
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
|
|
79
|
+
* wide the terminal is. Once the todo rail takes its responsive share of the frame those two
|
|
80
80
|
* stop being the same number. Optional so every existing caller (and every
|
|
81
81
|
* test) is unchanged.
|
|
82
82
|
*/
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface AutocompleteLayoutInput {
|
|
2
|
+
itemCount: number;
|
|
3
|
+
selected: number;
|
|
4
|
+
maxRows?: number;
|
|
5
|
+
maxHeight?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface AutocompleteLayout {
|
|
8
|
+
start: number;
|
|
9
|
+
count: number;
|
|
10
|
+
moreBelow: number;
|
|
11
|
+
showMore: boolean;
|
|
12
|
+
rowCount: number;
|
|
13
|
+
}
|
|
14
|
+
/** Window suggestions within a total row budget including borders and footer.
|
|
15
|
+
* Only omitted maxHeight is unlimited. Explicit invalid numbers become zero.
|
|
16
|
+
* Selection is clamped to the available items; no input is mutated or rejected.
|
|
17
|
+
*/
|
|
18
|
+
export declare function buildAutocompleteLayout(input: AutocompleteLayoutInput): AutocompleteLayout;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Window suggestions within a total row budget including borders and footer.
|
|
2
|
+
* Only omitted maxHeight is unlimited. Explicit invalid numbers become zero.
|
|
3
|
+
* Selection is clamped to the available items; no input is mutated or rejected.
|
|
4
|
+
*/
|
|
5
|
+
export function buildAutocompleteLayout(input) {
|
|
6
|
+
const total = nonNegativeInt(input.itemCount);
|
|
7
|
+
const maxRows = input.maxRows === undefined ? 6 : nonNegativeInt(input.maxRows);
|
|
8
|
+
const height = input.maxHeight === undefined ? Infinity : nonNegativeInt(input.maxHeight);
|
|
9
|
+
if (total === 0 || maxRows === 0 || height < 3) {
|
|
10
|
+
return { start: 0, count: 0, moreBelow: 0, showMore: false, rowCount: 0 };
|
|
11
|
+
}
|
|
12
|
+
const selected = Math.min(total - 1, nonNegativeInt(input.selected));
|
|
13
|
+
let count = Math.min(total, maxRows, height - 2);
|
|
14
|
+
const windowStart = () => Math.max(0, Math.min(selected - count + 1, total - count));
|
|
15
|
+
let start = windowStart();
|
|
16
|
+
if (count >= 2 && start + count < total && count + 3 > height) {
|
|
17
|
+
count--;
|
|
18
|
+
start = windowStart();
|
|
19
|
+
}
|
|
20
|
+
const moreBelow = total - start - count;
|
|
21
|
+
const showMore = moreBelow > 0 && count + 3 <= height;
|
|
22
|
+
return { start, count, moreBelow, showMore, rowCount: 2 + count + Number(showMore) };
|
|
23
|
+
}
|
|
24
|
+
function nonNegativeInt(value) {
|
|
25
|
+
return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
|
|
26
|
+
}
|
|
@@ -30,11 +30,26 @@ export interface ChromeBudget {
|
|
|
30
30
|
/** Brand bar. Constant 1 in full-screen — this is what makes A-3 hold. */
|
|
31
31
|
header: number;
|
|
32
32
|
toast: number;
|
|
33
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Round border (2) + the DRAFT's own rows, plus the hint row when it fits.
|
|
35
|
+
*
|
|
36
|
+
* The draft term used to be the constant 1 that the `3` / `4` below encode.
|
|
37
|
+
* That constant is what made a pasted 200-line draft draw the transcript 197
|
|
38
|
+
* rows shorter than every consumer of `viewportRows` believed -- the trap
|
|
39
|
+
* `BottomStatusRow.tsx:5-27` already documents at 1/200th the magnitude.
|
|
40
|
+
*/
|
|
34
41
|
composer: number;
|
|
35
42
|
status: number;
|
|
36
43
|
}
|
|
37
|
-
|
|
44
|
+
/**
|
|
45
|
+
* The composer's draft rows, clamped to what this terminal height allows.
|
|
46
|
+
*
|
|
47
|
+
* Exported so the reporter and the budget clamp with ONE function: I-8 requires
|
|
48
|
+
* the rendered row count and the number handed to `viewportRows` to be the same
|
|
49
|
+
* number, and two clamps are how they stop being.
|
|
50
|
+
*/
|
|
51
|
+
export declare function clampDraftRows(rows: number, draftRows: number): number;
|
|
52
|
+
export declare function chromeBudget(rows: number, draftRows?: number): ChromeBudget;
|
|
38
53
|
/**
|
|
39
54
|
* Rows available to the viewport at a given terminal height.
|
|
40
55
|
*
|
|
@@ -42,8 +57,21 @@ export declare function chromeBudget(rows: number): ChromeBudget;
|
|
|
42
57
|
* that argument is the guarantee that the first submitted message cannot make
|
|
43
58
|
* the layout jump (A-4). Do not add it back.
|
|
44
59
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
60
|
+
* `draftRows` IS NOT THAT ARGUMENT, and the distinction is the whole of D-12.
|
|
61
|
+
* A-4 forbids making the budget depend on TRANSCRIPT content, so that the first
|
|
62
|
+
* submitted message cannot move the layout. `draftRows` depends only on what the
|
|
63
|
+
* user is typing right now, and its whole purpose is that the jump is ALREADY
|
|
64
|
+
* HAPPENING -- Yoga takes the rows out of the transcript whether or not this
|
|
65
|
+
* function was told. `App` adds the hysteresis (grow now, shrink one tick later)
|
|
66
|
+
* that keeps it from becoming per-keystroke jitter.
|
|
67
|
+
*
|
|
68
|
+
* It DEFAULTS TO 1, i.e. to the constant this function used to assume, so every
|
|
69
|
+
* pre-existing caller and every pre-existing assertion is byte-identical -- and
|
|
70
|
+
* `viewportRows.length` stays 1, because a parameter with a default does not
|
|
71
|
+
* count toward it.
|
|
72
|
+
*
|
|
73
|
+
* Non-decreasing in `rows` at a fixed `draftRows = 1`: the one step where chrome
|
|
74
|
+
* grows (the hint row at `HINT_MIN_ROWS`) coincides with `rows` growing by one,
|
|
75
|
+
* so the net change is 0 rather than -1.
|
|
48
76
|
*/
|
|
49
|
-
export declare function viewportRows(rows: number): number;
|
|
77
|
+
export declare function viewportRows(rows: number, draftRows?: number): number;
|
package/dist/ui/layout/budget.js
CHANGED
|
@@ -24,14 +24,26 @@
|
|
|
24
24
|
* make this comment a true-sounding statement about a row that is gone (AC-12,
|
|
25
25
|
* and the `wheel-scrolls-transcript-only` RV-3 lesson).
|
|
26
26
|
*/
|
|
27
|
+
import { draftMaxRows } from '../composer-limits.js';
|
|
27
28
|
import { frameHeight, MIN_FULLSCREEN_ROWS } from './frame.js';
|
|
28
29
|
/** Below this many rows the composer's hint line is dropped to buy back a row. */
|
|
29
30
|
export const HINT_MIN_ROWS = 20;
|
|
30
|
-
|
|
31
|
+
/**
|
|
32
|
+
* The composer's draft rows, clamped to what this terminal height allows.
|
|
33
|
+
*
|
|
34
|
+
* Exported so the reporter and the budget clamp with ONE function: I-8 requires
|
|
35
|
+
* the rendered row count and the number handed to `viewportRows` to be the same
|
|
36
|
+
* number, and two clamps are how they stop being.
|
|
37
|
+
*/
|
|
38
|
+
export function clampDraftRows(rows, draftRows) {
|
|
39
|
+
const wanted = Number.isFinite(draftRows) ? Math.floor(draftRows) : 1;
|
|
40
|
+
return Math.max(1, Math.min(wanted, draftMaxRows(rows)));
|
|
41
|
+
}
|
|
42
|
+
export function chromeBudget(rows, draftRows = 1) {
|
|
31
43
|
return {
|
|
32
44
|
header: 1,
|
|
33
45
|
toast: 1,
|
|
34
|
-
composer: rows >= HINT_MIN_ROWS ?
|
|
46
|
+
composer: 2 + clampDraftRows(rows, draftRows) + (rows >= HINT_MIN_ROWS ? 1 : 0),
|
|
35
47
|
status: 1,
|
|
36
48
|
};
|
|
37
49
|
}
|
|
@@ -42,13 +54,26 @@ export function chromeBudget(rows) {
|
|
|
42
54
|
* that argument is the guarantee that the first submitted message cannot make
|
|
43
55
|
* the layout jump (A-4). Do not add it back.
|
|
44
56
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
57
|
+
* `draftRows` IS NOT THAT ARGUMENT, and the distinction is the whole of D-12.
|
|
58
|
+
* A-4 forbids making the budget depend on TRANSCRIPT content, so that the first
|
|
59
|
+
* submitted message cannot move the layout. `draftRows` depends only on what the
|
|
60
|
+
* user is typing right now, and its whole purpose is that the jump is ALREADY
|
|
61
|
+
* HAPPENING -- Yoga takes the rows out of the transcript whether or not this
|
|
62
|
+
* function was told. `App` adds the hysteresis (grow now, shrink one tick later)
|
|
63
|
+
* that keeps it from becoming per-keystroke jitter.
|
|
64
|
+
*
|
|
65
|
+
* It DEFAULTS TO 1, i.e. to the constant this function used to assume, so every
|
|
66
|
+
* pre-existing caller and every pre-existing assertion is byte-identical -- and
|
|
67
|
+
* `viewportRows.length` stays 1, because a parameter with a default does not
|
|
68
|
+
* count toward it.
|
|
69
|
+
*
|
|
70
|
+
* Non-decreasing in `rows` at a fixed `draftRows = 1`: the one step where chrome
|
|
71
|
+
* grows (the hint row at `HINT_MIN_ROWS`) coincides with `rows` growing by one,
|
|
72
|
+
* so the net change is 0 rather than -1.
|
|
48
73
|
*/
|
|
49
|
-
export function viewportRows(rows) {
|
|
74
|
+
export function viewportRows(rows, draftRows = 1) {
|
|
50
75
|
if (!Number.isFinite(rows) || rows < MIN_FULLSCREEN_ROWS)
|
|
51
76
|
return 0;
|
|
52
|
-
const c = chromeBudget(rows);
|
|
77
|
+
const c = chromeBudget(rows, draftRows);
|
|
53
78
|
return Math.max(0, frameHeight(rows) - c.header - c.toast - c.composer - c.status);
|
|
54
79
|
}
|
package/dist/ui/layout/rail.d.ts
CHANGED
|
@@ -1,56 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
* Rail geometry (todo-plan-execution §3.9) — the ONE place that says how wide
|
|
3
|
-
* and how tall the todo rail is allowed to be.
|
|
4
|
-
*
|
|
5
|
-
* Pure and React-free, mirroring `budget.ts`, so `todo-rail.test.ts` can pin the
|
|
6
|
-
* arithmetic without mounting anything. Deliberately NOT part of `budget.ts`:
|
|
7
|
-
* that module answers "how many ROWS does the viewport get" and stays a function
|
|
8
|
-
* of `rows` alone; the rail spends COLUMNS.
|
|
9
|
-
*
|
|
10
|
-
* NAMED FOR ITS POSITION, NOT ITS CONTENT, like `AppShellProps.rail` (D-18):
|
|
11
|
-
* this is a layout primitive and `viewportRows` is its neighbour, not
|
|
12
|
-
* `TodoPanel`.
|
|
13
|
-
*/
|
|
14
|
-
/** Below this the marker + a two-word title do not fit and the column is noise. */
|
|
15
|
-
export declare const TODO_RAIL_MIN_COLS = 18;
|
|
16
|
-
/**
|
|
17
|
-
* Above this, 20% is being spent on whitespace: at 36 columns a step title
|
|
18
|
-
* already fits on one line, and the transcript is the surface that benefits from
|
|
19
|
-
* a 200-column terminal. A deliberate, revertible deviation from a literal
|
|
20
|
-
* reading of R-e; it only binds above 180 columns.
|
|
21
|
-
*/
|
|
1
|
+
export declare const TODO_RAIL_MIN_COLS = 14;
|
|
22
2
|
export declare const TODO_RAIL_MAX_COLS = 36;
|
|
23
|
-
|
|
24
|
-
export declare const TODO_RAIL_MIN_TOTAL_COLS
|
|
25
|
-
/** Below this rail width the 2-column index prefix is dropped for content. */
|
|
3
|
+
export declare const TODO_TRANSCRIPT_MIN_COLS = 62;
|
|
4
|
+
export declare const TODO_RAIL_MIN_TOTAL_COLS: number;
|
|
26
5
|
export declare const TODO_RAIL_INDEX_MIN_COLS = 22;
|
|
27
|
-
/**
|
|
28
|
-
* Two properties are pinned by tests, for the reason `budget.ts` records about
|
|
29
|
-
* the viewport being non-monotonic before anyone measured it:
|
|
30
|
-
*
|
|
31
|
-
* - NON-DECREASING in `cols` across [40, 300]. A user dragging a window wider
|
|
32
|
-
* must never see the rail get narrower.
|
|
33
|
-
* - `cols - todoRailWidth(cols) >= 62` for every `cols >= 80`. Every existing
|
|
34
|
-
* width heuristic inside the viewport (`TERSE_HINT_COLS` 42,
|
|
35
|
-
* `MIN_INDICATOR_COLS` 50, the status bar's 60/72 steps) sits below that
|
|
36
|
-
* floor, so no existing behaviour changes because a rail appeared (R-3).
|
|
37
|
-
*/
|
|
6
|
+
/** Return a non-decreasing rail width; invalid or narrow terminals have no rail. */
|
|
38
7
|
export declare function todoRailWidth(cols: number): number;
|
|
39
|
-
/**
|
|
40
|
-
* How many rows the rail may actually draw.
|
|
41
|
-
*
|
|
42
|
-
* IT IS NOT `viewportBudget`, AND THE DIFFERENCE IS NOT COSMETIC (P1-3 / D-23).
|
|
43
|
-
* `viewportRows()` subtracts the STATIC chrome only, and says so; the live team
|
|
44
|
-
* roster is mounted inside `AppShell`'s measured bottom box, where it costs a
|
|
45
|
-
* header row, up to `TEAM_LIMITS.panelMaxRows` child rows, a `+N more` row and a
|
|
46
|
-
* mail row that `viewportBudget` knows nothing about. `ScrollViewport` is immune
|
|
47
|
-
* because it MEASURES itself; the rail does not measure - a second
|
|
48
|
-
* measure -> setState -> measure loop is exactly what §1.2 refuses - so it has
|
|
49
|
-
* to subtract.
|
|
50
|
-
*
|
|
51
|
-
* Ink clips overflow from the BOTTOM, so an over-request does not error: it
|
|
52
|
-
* silently eats the last row, which is the `+N below` marker - the one row whose
|
|
53
|
-
* absence is indistinguishable from "the list is short". That is why this is
|
|
54
|
-
* arithmetic rather than a shrug at `overflow: hidden`.
|
|
55
|
-
*/
|
|
56
|
-
export declare function todoRailRows(viewportBudget: number, teamActive: boolean): number;
|
package/dist/ui/layout/rail.js
CHANGED
|
@@ -1,68 +1,16 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
* Pure and React-free, mirroring `budget.ts`, so `todo-rail.test.ts` can pin the
|
|
6
|
-
* arithmetic without mounting anything. Deliberately NOT part of `budget.ts`:
|
|
7
|
-
* that module answers "how many ROWS does the viewport get" and stays a function
|
|
8
|
-
* of `rows` alone; the rail spends COLUMNS.
|
|
9
|
-
*
|
|
10
|
-
* NAMED FOR ITS POSITION, NOT ITS CONTENT, like `AppShellProps.rail` (D-18):
|
|
11
|
-
* this is a layout primitive and `viewportRows` is its neighbour, not
|
|
12
|
-
* `TodoPanel`.
|
|
13
|
-
*/
|
|
14
|
-
import { TODO_LIMITS } from '../../todo/limits.js';
|
|
15
|
-
/** R-e, literally: the rail asks for a fifth of the terminal. */
|
|
16
|
-
const RAIL_FRACTION = 0.2;
|
|
17
|
-
/** Below this the marker + a two-word title do not fit and the column is noise. */
|
|
18
|
-
export const TODO_RAIL_MIN_COLS = 18;
|
|
19
|
-
/**
|
|
20
|
-
* Above this, 20% is being spent on whitespace: at 36 columns a step title
|
|
21
|
-
* already fits on one line, and the transcript is the surface that benefits from
|
|
22
|
-
* a 200-column terminal. A deliberate, revertible deviation from a literal
|
|
23
|
-
* reading of R-e; it only binds above 180 columns.
|
|
24
|
-
*/
|
|
1
|
+
/** Responsive TODO rail widths, measured in terminal columns including chrome. */
|
|
2
|
+
const RAIL_FRACTION = 0.15;
|
|
3
|
+
export const TODO_RAIL_MIN_COLS = 14;
|
|
25
4
|
export const TODO_RAIL_MAX_COLS = 36;
|
|
26
|
-
|
|
27
|
-
export const TODO_RAIL_MIN_TOTAL_COLS =
|
|
28
|
-
/** Below this rail width the 2-column index prefix is dropped for content. */
|
|
5
|
+
export const TODO_TRANSCRIPT_MIN_COLS = 62;
|
|
6
|
+
export const TODO_RAIL_MIN_TOTAL_COLS = TODO_TRANSCRIPT_MIN_COLS + TODO_RAIL_MIN_COLS;
|
|
29
7
|
export const TODO_RAIL_INDEX_MIN_COLS = 22;
|
|
30
|
-
/**
|
|
31
|
-
* Two properties are pinned by tests, for the reason `budget.ts` records about
|
|
32
|
-
* the viewport being non-monotonic before anyone measured it:
|
|
33
|
-
*
|
|
34
|
-
* - NON-DECREASING in `cols` across [40, 300]. A user dragging a window wider
|
|
35
|
-
* must never see the rail get narrower.
|
|
36
|
-
* - `cols - todoRailWidth(cols) >= 62` for every `cols >= 80`. Every existing
|
|
37
|
-
* width heuristic inside the viewport (`TERSE_HINT_COLS` 42,
|
|
38
|
-
* `MIN_INDICATOR_COLS` 50, the status bar's 60/72 steps) sits below that
|
|
39
|
-
* floor, so no existing behaviour changes because a rail appeared (R-3).
|
|
40
|
-
*/
|
|
8
|
+
/** Return a non-decreasing rail width; invalid or narrow terminals have no rail. */
|
|
41
9
|
export function todoRailWidth(cols) {
|
|
42
|
-
if (!Number.isFinite(cols)
|
|
10
|
+
if (!Number.isFinite(cols))
|
|
43
11
|
return 0;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* How many rows the rail may actually draw.
|
|
48
|
-
*
|
|
49
|
-
* IT IS NOT `viewportBudget`, AND THE DIFFERENCE IS NOT COSMETIC (P1-3 / D-23).
|
|
50
|
-
* `viewportRows()` subtracts the STATIC chrome only, and says so; the live team
|
|
51
|
-
* roster is mounted inside `AppShell`'s measured bottom box, where it costs a
|
|
52
|
-
* header row, up to `TEAM_LIMITS.panelMaxRows` child rows, a `+N more` row and a
|
|
53
|
-
* mail row that `viewportBudget` knows nothing about. `ScrollViewport` is immune
|
|
54
|
-
* because it MEASURES itself; the rail does not measure - a second
|
|
55
|
-
* measure -> setState -> measure loop is exactly what §1.2 refuses - so it has
|
|
56
|
-
* to subtract.
|
|
57
|
-
*
|
|
58
|
-
* Ink clips overflow from the BOTTOM, so an over-request does not error: it
|
|
59
|
-
* silently eats the last row, which is the `+N below` marker - the one row whose
|
|
60
|
-
* absence is indistinguishable from "the list is short". That is why this is
|
|
61
|
-
* arithmetic rather than a shrug at `overflow: hidden`.
|
|
62
|
-
*/
|
|
63
|
-
export function todoRailRows(viewportBudget, teamActive) {
|
|
64
|
-
if (!Number.isFinite(viewportBudget))
|
|
12
|
+
const columns = Math.floor(cols);
|
|
13
|
+
if (columns < TODO_RAIL_MIN_TOTAL_COLS)
|
|
65
14
|
return 0;
|
|
66
|
-
|
|
67
|
-
return Math.max(0, viewportBudget - reserved);
|
|
15
|
+
return Math.min(TODO_RAIL_MAX_COLS, Math.max(TODO_RAIL_MIN_COLS, Math.round(columns * RAIL_FRACTION)));
|
|
68
16
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SubagentRun, TeamSnapshot } from '../../team/types.js';
|
|
2
|
+
export interface TeamPanelLayout {
|
|
3
|
+
collapsed: boolean;
|
|
4
|
+
rowCount: number;
|
|
5
|
+
visible: SubagentRun[];
|
|
6
|
+
hiddenTotal: number;
|
|
7
|
+
hiddenRunning: number;
|
|
8
|
+
}
|
|
9
|
+
export interface TeamPanelLayoutInput {
|
|
10
|
+
snapshot: TeamSnapshot | null;
|
|
11
|
+
terminalRows: number;
|
|
12
|
+
availableRows: number;
|
|
13
|
+
}
|
|
14
|
+
/** Project actual roster rows, preserving three middle rows when expanded.
|
|
15
|
+
* Invalid geometry becomes zero; legacy callers may pass positive Infinity
|
|
16
|
+
* for an unlimited available-row budget. Does not throw or mutate the snapshot.
|
|
17
|
+
*/
|
|
18
|
+
export declare function buildTeamPanelLayout(input: TeamPanelLayoutInput): TeamPanelLayout;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { TEAM_LIMITS } from '../../team/limits.js';
|
|
2
|
+
import { selectPanelRows } from '../../team/panel-rows.js';
|
|
3
|
+
import { TODO_LIMITS } from '../../todo/limits.js';
|
|
4
|
+
/** Project actual roster rows, preserving three middle rows when expanded.
|
|
5
|
+
* Invalid geometry becomes zero; legacy callers may pass positive Infinity
|
|
6
|
+
* for an unlimited available-row budget. Does not throw or mutate the snapshot.
|
|
7
|
+
*/
|
|
8
|
+
export function buildTeamPanelLayout(input) {
|
|
9
|
+
const available = input.availableRows === Infinity ? Infinity : nonNegativeInt(input.availableRows);
|
|
10
|
+
if (!input.snapshot || available < 1) {
|
|
11
|
+
return { collapsed: true, rowCount: 0, visible: [], hiddenTotal: 0, hiddenRunning: 0 };
|
|
12
|
+
}
|
|
13
|
+
const selection = selectPanelRows(input.snapshot.runs, TEAM_LIMITS.panelMaxRows);
|
|
14
|
+
const natural = 1 + selection.visible.length + Number(selection.hiddenTotal > 0)
|
|
15
|
+
+ Number(Boolean(input.snapshot.lastMessage));
|
|
16
|
+
const collapsed = nonNegativeInt(input.terminalRows) < TEAM_LIMITS.panelCollapseRows
|
|
17
|
+
|| available - natural < TODO_LIMITS.panelMinRows;
|
|
18
|
+
return { ...selection, collapsed, rowCount: collapsed ? 1 : natural };
|
|
19
|
+
}
|
|
20
|
+
function nonNegativeInt(value) {
|
|
21
|
+
return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
|
|
22
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { RenderMode } from './frame.js';
|
|
2
|
+
export interface TodoRailLayoutInput {
|
|
3
|
+
mode: RenderMode;
|
|
4
|
+
cols: number;
|
|
5
|
+
panelEnabled: boolean;
|
|
6
|
+
overlayOpen: boolean;
|
|
7
|
+
itemCount: number;
|
|
8
|
+
viewportBudget: number;
|
|
9
|
+
teamRows: number;
|
|
10
|
+
popupRows: number;
|
|
11
|
+
}
|
|
12
|
+
export interface TodoRailLayout {
|
|
13
|
+
visible: boolean;
|
|
14
|
+
reason: 'visible' | 'inline' | 'disabled' | 'overlay' | 'empty' | 'narrow' | 'short';
|
|
15
|
+
width: number;
|
|
16
|
+
rows: number;
|
|
17
|
+
contentCols: number;
|
|
18
|
+
popupMaxHeight: number;
|
|
19
|
+
}
|
|
20
|
+
/** Allocate columns and rows from the same team/menu projections as the renderers.
|
|
21
|
+
* Invalid geometry becomes zero, excess team/menu rows are clamped. Menu capacity
|
|
22
|
+
* never depends on menu occupancy or rail visibility, preventing feedback loops.
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildTodoRailLayout(input: TodoRailLayoutInput): TodoRailLayout;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { TODO_LIMITS } from '../../todo/limits.js';
|
|
2
|
+
import { todoRailWidth } from './rail.js';
|
|
3
|
+
/** Allocate columns and rows from the same team/menu projections as the renderers.
|
|
4
|
+
* Invalid geometry becomes zero, excess team/menu rows are clamped. Menu capacity
|
|
5
|
+
* never depends on menu occupancy or rail visibility, preventing feedback loops.
|
|
6
|
+
*/
|
|
7
|
+
export function buildTodoRailLayout(input) {
|
|
8
|
+
const cols = nonNegativeInt(input.cols);
|
|
9
|
+
const budget = nonNegativeInt(input.viewportBudget);
|
|
10
|
+
const teamRows = Math.min(budget, nonNegativeInt(input.teamRows));
|
|
11
|
+
const popupMaxHeight = Math.max(0, budget - teamRows - TODO_LIMITS.panelMinRows);
|
|
12
|
+
const popupRows = popupMaxHeight < 3 ? 0 : Math.min(nonNegativeInt(input.popupRows), popupMaxHeight);
|
|
13
|
+
const rows = budget - teamRows - popupRows;
|
|
14
|
+
const width = todoRailWidth(cols);
|
|
15
|
+
const reason = input.mode !== 'fullscreen' ? 'inline'
|
|
16
|
+
: !input.panelEnabled ? 'disabled'
|
|
17
|
+
: input.overlayOpen ? 'overlay'
|
|
18
|
+
: nonNegativeInt(input.itemCount) === 0 ? 'empty'
|
|
19
|
+
: width === 0 ? 'narrow'
|
|
20
|
+
: rows < TODO_LIMITS.panelMinRows ? 'short'
|
|
21
|
+
: 'visible';
|
|
22
|
+
const visible = reason === 'visible';
|
|
23
|
+
return { visible, reason, width, rows, contentCols: cols - (visible ? width : 0), popupMaxHeight };
|
|
24
|
+
}
|
|
25
|
+
function nonNegativeInt(value) {
|
|
26
|
+
return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
|
|
27
|
+
}
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
import { FAST_LIMITS } from '../../fast/limits.js';
|
|
36
36
|
import { COMPACTION_LIMITS } from '../../compaction/limits.js';
|
|
37
37
|
import { GUTTER_WIDTH } from './Gutter.js';
|
|
38
|
+
import { USER_ENTRY_MAX_ROWS } from '../composer-limits.js';
|
|
38
39
|
export const VIRTUAL_LIMITS = {
|
|
39
40
|
/**
|
|
40
41
|
* Entries kept mounted on each side of the visible band.
|
|
@@ -123,6 +124,22 @@ export function entryRevision(entry) {
|
|
|
123
124
|
*/
|
|
124
125
|
case 'compaction':
|
|
125
126
|
return `c${entry.live ? 1 : 0}.${entry.summary?.length ?? 0}.${entry.mode}.${entry.applied ? 1 : 0}`;
|
|
127
|
+
/**
|
|
128
|
+
* `rowsSeen`, AND NEVER `rows.length` (background-service-supervision P0-3).
|
|
129
|
+
*
|
|
130
|
+
* The service tail is a FIXED-SIZE RING: it evicts its oldest row while
|
|
131
|
+
* appending a new one, so the joined length is unchanged while the content is
|
|
132
|
+
* not. That is verbatim the NON-APPEND mutation I-L3-1 above says must extend
|
|
133
|
+
* the revision, and it is the same trap — and the same fix — as the `tool`
|
|
134
|
+
* branch's `liveSeq` two cases up. A length term would go on matching while
|
|
135
|
+
* the card changed underneath it, and the card would freeze at a stale height
|
|
136
|
+
* AND a stale rendered subtree with nothing anywhere reporting it.
|
|
137
|
+
*
|
|
138
|
+
* `status` and the presence of a URL are in because both change the rows the
|
|
139
|
+
* card draws; `exitCode` is in because the terminal row names it.
|
|
140
|
+
*/
|
|
141
|
+
case 'service':
|
|
142
|
+
return `v${entry.status}.${entry.rowsSeen}.${entry.url ? 1 : 0}.${entry.exitCode ?? -1}.${entry.terminal ? 1 : 0}`;
|
|
126
143
|
default:
|
|
127
144
|
return 'x';
|
|
128
145
|
}
|
|
@@ -164,6 +181,10 @@ const TOOL_COLLAPSED_LINES = 8;
|
|
|
164
181
|
const DIFF_COLLAPSED_LINES = 12;
|
|
165
182
|
/** Live tail rows in `ToolCard`; duplicated as a NUMBER, for the same reason. */
|
|
166
183
|
const TOOL_LIVE_TAIL_ROWS = 8;
|
|
184
|
+
/** Tail rows a COLLAPSED `ServiceCard` draws; duplicated for the same reason. */
|
|
185
|
+
const SERVICE_COLLAPSED_TAIL_ROWS = 2;
|
|
186
|
+
/** Tail rows an EXPANDED `ServiceCard` draws; the snapshot cap (8). */
|
|
187
|
+
const SERVICE_EXPANDED_TAIL_ROWS = 8;
|
|
167
188
|
/**
|
|
168
189
|
* Pure UPPER-BOUND estimate of an entry's height, used until a real measurement
|
|
169
190
|
* exists.
|
|
@@ -181,7 +202,14 @@ export function estimateEntryRows(entry, cols, density, expanded, thinkingVisibl
|
|
|
181
202
|
const separation = density === 'comfortable' ? 1 : 0;
|
|
182
203
|
switch (entry.kind) {
|
|
183
204
|
case 'user':
|
|
184
|
-
|
|
205
|
+
// MIRRORS `UserEntry`'s RENDER CAP (+1 for the `... +N more lines` tail).
|
|
206
|
+
// THESE TWO NUMBERS ARE ONE NUMBER (I-13). Capping the renderer alone
|
|
207
|
+
// would leave this function believing a 5 000-line message is 5 000 rows
|
|
208
|
+
// tall -- and unlike an ordinary over-estimate that failure does NOT
|
|
209
|
+
// self-correct: this file's own note above records that "an entry
|
|
210
|
+
// estimated at 400 rows is exactly the one that never gets mounted", and
|
|
211
|
+
// an unmounted entry is never measured.
|
|
212
|
+
return (separation + Math.min(wrappedRows(entry.text, usable), USER_ENTRY_MAX_ROWS + 1));
|
|
185
213
|
case 'notice':
|
|
186
214
|
return separation + wrappedRows(entry.text, usable);
|
|
187
215
|
case 'assistant': {
|
|
@@ -277,6 +305,28 @@ export function estimateEntryRows(entry, cols, density, expanded, thinkingVisibl
|
|
|
277
305
|
const footer = lines > body || (expanded && lines > COMPACTION_LIMITS.cardTextRows) ? 1 : 0;
|
|
278
306
|
return separation + 1 + 1 + body + footer;
|
|
279
307
|
}
|
|
308
|
+
/**
|
|
309
|
+
* THE THIRD SWITCH, and adding the case to only one of this file's two
|
|
310
|
+
* yields a transcript whose scroll arithmetic disagrees with what it drew —
|
|
311
|
+
* drift rather than an error (P0-3 / RV-13). Both `default` branches swallow
|
|
312
|
+
* an unknown kind silently, and `virtual-window.test.ts` hand-enumerates
|
|
313
|
+
* kinds, so neither omission turns anything red on its own.
|
|
314
|
+
*
|
|
315
|
+
* A terminal record is exactly one row. A live card is the status row, the
|
|
316
|
+
* URL row when there is one, and the tail — counted, not wrapped, which is
|
|
317
|
+
* legitimate only because every row is `wrap="truncate"` AND clipped at
|
|
318
|
+
* `LIVE_ROW_MAX_CHARS` on the way into the ring, the same coupling the
|
|
319
|
+
* `tool` branch above records for itself.
|
|
320
|
+
*/
|
|
321
|
+
case 'service': {
|
|
322
|
+
if (entry.terminal)
|
|
323
|
+
return separation + 1;
|
|
324
|
+
const url = entry.url ? 1 : 0;
|
|
325
|
+
const tail = expanded
|
|
326
|
+
? Math.min(entry.rows.length, SERVICE_EXPANDED_TAIL_ROWS)
|
|
327
|
+
: Math.min(entry.rows.length, SERVICE_COLLAPSED_TAIL_ROWS);
|
|
328
|
+
return separation + 1 + url + tail;
|
|
329
|
+
}
|
|
280
330
|
default:
|
|
281
331
|
return separation + 1;
|
|
282
332
|
}
|
|
@@ -26,6 +26,9 @@ function keyRows(times) {
|
|
|
26
26
|
// startup notice is the other place, and it scrolls away.
|
|
27
27
|
['Drag (left button)', 'Select text on screen; releasing copies it'],
|
|
28
28
|
['Up / Down', 'Prompt history (empty input)'],
|
|
29
|
+
// Pasting is the one affordance here with no key of its own, so the only
|
|
30
|
+
// place a user can learn what happened to their 218 lines is this row.
|
|
31
|
+
['Paste', 'Inserted as-is up to 6 lines; larger becomes [Pasted text #N] (--no-paste)'],
|
|
29
32
|
];
|
|
30
33
|
}
|
|
31
34
|
const COMMANDS = [
|
|
@@ -27,6 +27,7 @@ import { Text, useInput } from 'ink';
|
|
|
27
27
|
import { pickGlyphs } from '../glyphs.js';
|
|
28
28
|
import { OverlayFrame } from '../layout/OverlayFrame.js';
|
|
29
29
|
import { wrapToRows } from '../layout/wrap-rows.js';
|
|
30
|
+
import { stripPasteFrames } from '../paste-frames.js';
|
|
30
31
|
/** The frame's border plus its one-column padding on each side. */
|
|
31
32
|
const FRAME_CHROME_COLS = 4;
|
|
32
33
|
export function PlanReviewOverlay({ plan, maxRows, cols, scrollOffset, onScrollClamp, theme, caps, onVerdict, }) {
|
|
@@ -55,8 +56,15 @@ export function PlanReviewOverlay({ plan, maxRows, cols, scrollOffset, onScrollC
|
|
|
55
56
|
setFeedback(feedback.slice(0, -1));
|
|
56
57
|
return;
|
|
57
58
|
}
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
// `stripPasteFrames`, NOT `input` (I-12 / P0-2). This is the THIRD consumer
|
|
60
|
+
// that appends `input` to a string; the design's table listed it as
|
|
61
|
+
// "key-driven only, inert", which was true of the version that had no
|
|
62
|
+
// free-text field. Revision feedback is exactly the kind of thing a user
|
|
63
|
+
// pastes — a failing test, a stack trace — and a framed paste appended
|
|
64
|
+
// verbatim would put NULs into text the model then reads.
|
|
65
|
+
if (input && !key.ctrl && !key.meta && !key.tab) {
|
|
66
|
+
setFeedback(feedback + stripPasteFrames(input));
|
|
67
|
+
}
|
|
60
68
|
return;
|
|
61
69
|
}
|
|
62
70
|
if (input === 'a' || input === 'A')
|