@pellux/goodvibes-tui 0.18.20 → 0.18.23
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 +120 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/core/conversation-rendering.ts +20 -6
- package/src/input/commands/session.ts +0 -1
- package/src/input/feed-context-factory.ts +236 -0
- package/src/input/handler-feed.ts +44 -6
- package/src/input/handler-shortcuts.ts +138 -125
- package/src/input/handler.ts +121 -119
- package/src/input/keybindings.ts +30 -0
- package/src/panels/approval-panel.ts +54 -82
- package/src/panels/automation-control-panel.ts +119 -161
- package/src/panels/communication-panel.ts +68 -107
- package/src/panels/control-plane-panel.ts +116 -172
- package/src/panels/hooks-panel.ts +101 -138
- package/src/panels/incident-review-panel.ts +55 -107
- package/src/panels/local-auth-panel.ts +76 -93
- package/src/panels/mcp-panel.ts +108 -155
- package/src/panels/ops-control-panel.ts +50 -85
- package/src/panels/panel-manager.ts +22 -2
- package/src/panels/plugins-panel.ts +36 -60
- package/src/panels/routes-panel.ts +89 -141
- package/src/panels/scrollable-list-panel.ts +45 -14
- package/src/panels/security-panel.ts +101 -137
- package/src/panels/services-panel.ts +58 -102
- package/src/panels/settings-sync-panel.ts +76 -122
- package/src/panels/subscription-panel.ts +63 -86
- package/src/panels/tasks-panel.ts +129 -179
- package/src/panels/watchers-panel.ts +88 -137
- package/src/renderer/buffer.ts +11 -0
- package/src/renderer/diff.ts +8 -0
- package/src/renderer/help-overlay.ts +37 -28
- package/src/renderer/markdown.ts +3 -145
- package/src/version.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,126 @@ All notable changes to GoodVibes TUI.
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [0.18.23] — 2026-04-16
|
|
8
|
+
|
|
9
|
+
### Wave 4α+β performance bundle + α review follow-ups + regression fixes
|
|
10
|
+
|
|
11
|
+
Bundles Wave 4α (conversation-rendering double-parse elimination) and Wave 4β
|
|
12
|
+
(feed-context object reuse) into a single release, adds documentation and test
|
|
13
|
+
follow-ups from the 4α review, and fixes two regressions surfaced during that
|
|
14
|
+
review.
|
|
15
|
+
|
|
16
|
+
### Performance (Wave 4α — conversation-rendering)
|
|
17
|
+
|
|
18
|
+
- **`src/core/conversation-rendering.ts`** — eliminates the legacy `renderMarkdown()`
|
|
19
|
+
duplicate call used for `'code'` mode line counting. The 'all' mode retain its
|
|
20
|
+
intentional measurement pass (see inline comment for rationale); the 'code' mode
|
|
21
|
+
now derives its gutter width from the single `renderMarkdownTracked()` call.
|
|
22
|
+
|
|
23
|
+
### Performance (Wave 4β — feed-context object reuse)
|
|
24
|
+
|
|
25
|
+
- **`src/input/handler.ts`** — `InputHandler` now allocates a single `InputFeedContext`
|
|
26
|
+
at startup (`initFeedContext()`) and mutates only the 14 mutable fields before
|
|
27
|
+
each `feedInputTokens()` call via `syncFeedContextMutableFields()`. Stable service
|
|
28
|
+
handles, closures, and callbacks are wired once and never re-assigned. Eliminates
|
|
29
|
+
per-keystroke allocations on the hot input path.
|
|
30
|
+
- **`src/input/feed-context-factory.ts`** (new) — extracts `buildInitialFeedContext()`
|
|
31
|
+
and the `FeedContextMutableInit` / `FeedContextStableRefs` / `FeedContextClosures`
|
|
32
|
+
interfaces out of `handler.ts` to keep `handler.ts` under the 800-line architecture
|
|
33
|
+
cap.
|
|
34
|
+
|
|
35
|
+
### Regression fixes
|
|
36
|
+
|
|
37
|
+
- **R1 — `handler.ts` architecture cap** — `handler.ts` was 830 lines after Wave 4β.
|
|
38
|
+
Extracted factory functions into `src/input/feed-context-factory.ts`; `handler.ts`
|
|
39
|
+
is now exactly 800 lines. `bun run architecture:check` passes.
|
|
40
|
+
- **R2 — `SearchableListPanel.buildFilterInputLine` cursor glyph** — the focused
|
|
41
|
+
filter line (`[Label] query_`) was rendering the trailing `_` as the block cursor
|
|
42
|
+
glyph `█` because `buildSearchInputLine` substitutes `_` → `█` when `active:true`.
|
|
43
|
+
Fixed by passing `active:false` with explicit `inputBg`/`info` colors; the
|
|
44
|
+
`[Label] ` bracket format provides the focused visual affordance without triggering
|
|
45
|
+
the substitution. Fixes the pre-existing test failure from 0.18.22.
|
|
46
|
+
|
|
47
|
+
### Quality bumps (Wave 4α review follow-ups)
|
|
48
|
+
|
|
49
|
+
- **F1** — `src/test/input/feed-context-reuse.test.ts`: added mutable-field assertion
|
|
50
|
+
verifying `ctx.prompt` and `ctx.cursorPos` update between feeds (feeds 'a' → 'b',
|
|
51
|
+
asserts accumulated mutation).
|
|
52
|
+
- **F2** — `src/test/input/keybinding-lookup.test.ts`: replaced no-op reload test
|
|
53
|
+
with a real temp-file config override (remaps `search` → Ctrl+G, verifies Ctrl+F
|
|
54
|
+
returns null); added a second test for conflicting bindings (two actions mapped to
|
|
55
|
+
same combo resolves to one of them, not null).
|
|
56
|
+
- **F3** — `src/input/handler-feed.ts`: added JSDoc to `InputFeedContext` interface
|
|
57
|
+
documenting all mutable-per-feed fields vs. stable service handles and explaining
|
|
58
|
+
the reuse rationale.
|
|
59
|
+
- **F4** — `src/input/feed-context-factory.ts`: `syncFeedContextMutableFields()` has
|
|
60
|
+
full JSDoc listing every synced field and documenting intentional exclusions.
|
|
61
|
+
- **F5** — `src/core/conversation-rendering.ts`: added inline comment block explaining
|
|
62
|
+
why 'all' mode requires a double-call to `renderMarkdownTracked` (Option B) and
|
|
63
|
+
why single-pass is not feasible.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## [0.18.22] — 2026-04-16
|
|
68
|
+
|
|
69
|
+
### Wave 3b / Tier 2 TUI UX Consistency — Panel Migration Batch
|
|
70
|
+
|
|
71
|
+
Migrates 7 more BasePanel-direct panels to `ScrollableListPanel<T>`, restores
|
|
72
|
+
section-title text lost during prior migrations, and fixes 8 pre-existing test regressions.
|
|
73
|
+
|
|
74
|
+
### Migrated panels
|
|
75
|
+
|
|
76
|
+
- **`src/panels/hooks-panel.ts`** — `HooksPanel` → `ScrollableListPanel<HookEntry>`.
|
|
77
|
+
Contracts/chains/managed/file stats in `header`; selected hook detail, activity (with `Recent Activity` label), authoring (with `Authoring` label) in `footer`. Empty state shows extra context via `header` parameter.
|
|
78
|
+
- **`src/panels/mcp-panel.ts`** — `McpPanel` → `ScrollableListPanel<McpServerSecurityEntry>`.
|
|
79
|
+
Derived type via `ReturnType<McpRegistry['listServerSecurity']>[number]` since no named export exists.
|
|
80
|
+
`MCP posture` label + stats + guidance in `header`; selected server detail, repair actions, decision log in `footer`.
|
|
81
|
+
- **`src/panels/approval-panel.ts`** — `ApprovalPanel` → `ScrollableListPanel<ApprovalRow>`.
|
|
82
|
+
`Approval posture` label + approval counts + guidance + `Selected Lane` label + detail in `header`; nav hint in `footer`.
|
|
83
|
+
- **`src/panels/security-panel.ts`** — `SecurityPanel` → `ScrollableListPanel<TokenAuditResult>`.
|
|
84
|
+
Governance + threat lines in `header`; selected detail + attack path findings in `footer`.
|
|
85
|
+
- **`src/panels/services-panel.ts`** — `ServicesPanel` → `ScrollableListPanel<ServicePanelEntry>`.
|
|
86
|
+
`r` (refresh) and `t` (test selected) key overrides preserved; loading state handled via early return.
|
|
87
|
+
- **`src/panels/subscription-panel.ts`** — `SubscriptionPanel` → `ScrollableListPanel<SubscriptionRow>`.
|
|
88
|
+
Fully overrides `handleInput` (uses `ArrowUp`/`ArrowDown`); logout confirm state preserved; empty state uses direct `buildPanelWorkspace` path.
|
|
89
|
+
- **`src/panels/tasks-panel.ts`** — `TasksPanel` → `ScrollableListPanel<RuntimeTask>`.
|
|
90
|
+
`!readModel` early-exit preserved; `buildSummaryBlock` in `header`, `buildDetailBlock` in `footer`.
|
|
91
|
+
- **`src/panels/incident-review-panel.ts`** — `IncidentReviewPanel` → `ScrollableListPanel<FailureReport>`.
|
|
92
|
+
`Action Rail` label added before action guidance lines in `footer`.
|
|
93
|
+
- **`src/panels/communication-panel.ts`** — `CommunicationPanel` → `ScrollableListPanel<CommunicationRecord>`.
|
|
94
|
+
`Communication posture` label added to both posture line arrays (empty + populated states).
|
|
95
|
+
|
|
96
|
+
### Panels kept as BasePanel
|
|
97
|
+
|
|
98
|
+
- `PolicyPanel`, `RemotePanel`, `ProviderHealthPanel`, `PanelListPanel`, `OrchestrationPanel`,
|
|
99
|
+
`MarketplacePanel`, `SchedulePanel`, `MemoryPanel`, `KnowledgePanel`, `SkillsPanel`,
|
|
100
|
+
`SessionBrowserPanel` — all use `resolveScrollablePanelSection`, multi-line-per-item render,
|
|
101
|
+
dual-mode browsing, `setInterval`, or `canRenderNow()` / `reportRenderDuration()` patterns
|
|
102
|
+
incompatible with `ScrollableListPanel<T>`.
|
|
103
|
+
|
|
104
|
+
### Test fixes
|
|
105
|
+
|
|
106
|
+
Restored section-title strings (`'Approval posture'`, `'Communication posture'`, `'MCP posture'`,
|
|
107
|
+
`'Action Rail'`, `'Recent Activity'`, `'Selected Lane'`) dropped during migration, fixing 8
|
|
108
|
+
regressions across `approval-panel`, `communication-panel`, `hooks-panel`, `mcp-panel`, and
|
|
109
|
+
`incident-review-panel` test files.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## [0.18.21] — 2026-04-16
|
|
114
|
+
|
|
115
|
+
### Wave 3a / Tier 2 TUI UX Consistency Infrastructure — I5
|
|
116
|
+
|
|
117
|
+
Final item from Wave 3a: selection gutter and filter input UX consistency across list panels.
|
|
118
|
+
|
|
119
|
+
### I5 — Selection gutter + filter input label conventions
|
|
120
|
+
|
|
121
|
+
- **`src/panels/scrollable-list-panel.ts`** — `ScrollableListPanel`: added opt-in `protected showSelectionGutter = false`. When enabled, `renderList()` post-processes each item line to prepend a 2-column left gutter: `▸ ` (info color, bold) for the selected row, ` ` for all others. Line width is preserved by dropping the last 2 cells. Default off to avoid breaking panels with custom selection indicators.
|
|
122
|
+
- **`src/panels/scrollable-list-panel.ts`** — `SearchableListPanel`: added `protected buildFilterInputLine(width, label, focused)`. Renders the filter line with context-sensitive label formatting: `[Filter] query_` when `focused=true` (active, bold, cursor visible), `Filter: query` when `focused=false` (dim, no cursor). Delegates to `buildSearchInputLine` from `polish.ts`.
|
|
123
|
+
- **New test file `src/test/panels/scrollable-list-panel-i5.test.ts`**: 13 tests covering gutter on/off, column position of `▸`, line-width preservation, and filter label format in both focused/unfocused states.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
7
127
|
## [0.18.20] — 2026-04-16
|
|
8
128
|
|
|
9
129
|
### Wave 3a / Tier 2 TUI UX Consistency Infrastructure
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/mgd34msu/goodvibes-tui/actions/workflows/ci.yml)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[](https://github.com/mgd34msu/goodvibes-tui)
|
|
6
6
|
|
|
7
7
|
A terminal-native AI coding, operations, automation, knowledge, and integration console with a typed runtime, omnichannel surfaces, structured memory/knowledge, and a raw ANSI renderer.
|
|
8
8
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-tui",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.23",
|
|
4
4
|
"description": "Terminal-native GoodVibes product for coding, operations, automation, knowledge, channels, and daemon-backed control-plane workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/main.ts",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { UIFactory } from '../renderer/ui-factory.ts';
|
|
2
|
-
import {
|
|
2
|
+
import { renderMarkdownTracked } from '../renderer/markdown.ts';
|
|
3
3
|
import { renderToolCallBlock } from '../renderer/tool-call.ts';
|
|
4
4
|
import { renderThinkingBlock } from '../renderer/thinking.ts';
|
|
5
5
|
import { renderSystemMessage } from '../renderer/system-message.ts';
|
|
@@ -102,10 +102,24 @@ export function renderConversationAssistantMessage(
|
|
|
102
102
|
if (message.content) {
|
|
103
103
|
const showAllLineNumbers = lineNumberMode === 'all';
|
|
104
104
|
const showCodeBlockLineNumbers = lineNumberMode === 'all' ? false : lineNumberMode === 'code';
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
// First pass: measure totalLines for gutter sizing (only when line-numbers='all').
|
|
106
|
+
// When line numbers are off, skip the measurement pass entirely.
|
|
107
|
+
//
|
|
108
|
+
// NOTE: The 'all' mode intentionally calls renderMarkdownTracked twice:
|
|
109
|
+
// 1. Measure pass: render at full `width` to get the total line count, which
|
|
110
|
+
// determines `numWidth` (digit count) and thus `gutterW` (gutter column width).
|
|
111
|
+
// 2. Render pass: render at `width - gutterW` with the gutter factored in.
|
|
112
|
+
//
|
|
113
|
+
// Single-pass is not feasible here because `numWidth` depends on `totalLines`,
|
|
114
|
+
// which is unknown before rendering. The 4α commit message claim that this
|
|
115
|
+
// "eliminates double-parse when line numbers are enabled" was inaccurate:
|
|
116
|
+
// 4α eliminated the legacy `renderMarkdown()` duplicate that was used for the
|
|
117
|
+
// code-block line-number mode ('code'). The 'all' mode double-call is unavoidable
|
|
118
|
+
// by design and remains unchanged.
|
|
119
|
+
const measureWidth = showAllLineNumbers ? width : 0;
|
|
120
|
+
const totalLines = showAllLineNumbers
|
|
121
|
+
? renderMarkdownTracked(message.content, measureWidth, { codeBlockLineNumbers: false }).lines.length
|
|
122
|
+
: 0;
|
|
109
123
|
const numWidth = Math.max(3, String(totalLines).length);
|
|
110
124
|
const gutterW = numWidth + 3;
|
|
111
125
|
const contentWidth = showAllLineNumbers ? width - gutterW : width;
|
|
@@ -239,7 +253,7 @@ export function renderConversationToolMessage(
|
|
|
239
253
|
// Leave invalid JSON as-is.
|
|
240
254
|
}
|
|
241
255
|
}
|
|
242
|
-
context.history.addLines(
|
|
256
|
+
context.history.addLines(renderMarkdownTracked(contentToRender, width).lines);
|
|
243
257
|
}
|
|
244
258
|
|
|
245
259
|
const renderedLineCount = context.history.getLineCount() - startLine;
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* feed-context-factory.ts — Construction and mutable-field sync for InputFeedContext.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from handler.ts (Wave 4α review, 0.18.23) to keep handler.ts under the
|
|
5
|
+
* 800-line architecture cap.
|
|
6
|
+
*
|
|
7
|
+
* Two exported functions:
|
|
8
|
+
* - `buildInitialFeedContext` — assembles the long-lived InputFeedContext from
|
|
9
|
+
* pre-built field groups. Called once at InputHandler construction time.
|
|
10
|
+
* - `syncFeedContextMutableFields` — copies mutable scalar fields into the
|
|
11
|
+
* already-allocated context before each feed() dispatch.
|
|
12
|
+
*/
|
|
13
|
+
import type { InputFeedContext } from './handler-feed.ts';
|
|
14
|
+
import type { SelectionManager } from './selection.ts';
|
|
15
|
+
import type { InfiniteBuffer } from '../core/history.ts';
|
|
16
|
+
import type { CommandRegistry, CommandContext } from './command-registry.ts';
|
|
17
|
+
import type { AutocompleteEngine } from './autocomplete.ts';
|
|
18
|
+
import type { FilePickerModal } from './file-picker.ts';
|
|
19
|
+
import type { ModelPickerModal } from './model-picker.ts';
|
|
20
|
+
import type { SelectionModal } from './selection-modal.ts';
|
|
21
|
+
import type { SelectionResult } from './selection-modal.ts';
|
|
22
|
+
import type { SearchManager } from './search.ts';
|
|
23
|
+
import type { InputHistory, HistorySearch } from './input-history.ts';
|
|
24
|
+
import type { ConversationManager } from '../core/conversation';
|
|
25
|
+
import type { ProcessModal } from '../renderer/process-modal.ts';
|
|
26
|
+
import type { LiveTailModal } from '../renderer/live-tail-modal.ts';
|
|
27
|
+
import type { BlockActionsMenu } from '../renderer/block-actions.ts';
|
|
28
|
+
import type { AgentDetailModal } from '../renderer/agent-detail-modal.ts';
|
|
29
|
+
import type { ContextInspectorModal } from '../renderer/context-inspector.ts';
|
|
30
|
+
import type { BookmarkModal } from './bookmark-modal.ts';
|
|
31
|
+
import type { SettingsModal } from './settings-modal.ts';
|
|
32
|
+
import type { SessionPickerModal } from './session-picker-modal.ts';
|
|
33
|
+
import type { ProfilePickerModal } from './profile-picker-modal.ts';
|
|
34
|
+
import type { WrappedPromptInfo } from './handler-prompt-buffer.ts';
|
|
35
|
+
import type { Panel } from '../panels/types.ts';
|
|
36
|
+
import type { PanelManager } from '../panels/panel-manager.ts';
|
|
37
|
+
import type { KeybindingsManager } from './keybindings.ts';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Initial mutable scalar values for InputFeedContext.
|
|
41
|
+
*
|
|
42
|
+
* **Mutable fields** (synced per-feed via syncFeedContextMutableFields or inside
|
|
43
|
+
* action closures that call syncFeedContextMutableFields):
|
|
44
|
+
* - `prompt`, `cursorPos` — current text buffer state
|
|
45
|
+
* - `commandMode`, `panelFocused`, `indicatorFocused` — focus-mode flags
|
|
46
|
+
* - `helpOverlayActive`, `helpScrollOffset` — help overlay state
|
|
47
|
+
* - `shortcutsOverlayActive`, `shortcutsScrollOffset` — shortcuts overlay state
|
|
48
|
+
* - `nextPasteId`, `nextImageId` — monotonically increasing ID counters
|
|
49
|
+
* - `mouseDownRow`, `mouseDownCol` — drag-tracking coordinates
|
|
50
|
+
* - `contentWidth` — reflow width, updated by setContentWidth()
|
|
51
|
+
* - `selectionCallback` — current selection modal callback (nullable)
|
|
52
|
+
*/
|
|
53
|
+
export interface FeedContextMutableInit {
|
|
54
|
+
prompt: string;
|
|
55
|
+
cursorPos: number;
|
|
56
|
+
commandMode: boolean;
|
|
57
|
+
panelFocused: boolean;
|
|
58
|
+
indicatorFocused: boolean;
|
|
59
|
+
helpOverlayActive: boolean;
|
|
60
|
+
helpScrollOffset: number;
|
|
61
|
+
shortcutsOverlayActive: boolean;
|
|
62
|
+
shortcutsScrollOffset: number;
|
|
63
|
+
nextPasteId: number;
|
|
64
|
+
nextImageId: number;
|
|
65
|
+
mouseDownRow: number;
|
|
66
|
+
mouseDownCol: number;
|
|
67
|
+
contentWidth: number;
|
|
68
|
+
selectionCallback: ((result: SelectionResult | null) => void) | null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Stable (readonly) service references for InputFeedContext.
|
|
73
|
+
*
|
|
74
|
+
* **Stable references** (set once at construction, never reallocated):
|
|
75
|
+
* - `pasteRegistry`, `imageRegistry` — owned Maps, never replaced
|
|
76
|
+
* - `selectionModal`, `bookmarkModal`, `settingsModal`, `sessionPickerModal`,
|
|
77
|
+
* `profilePickerModal` — modal objects constructed once
|
|
78
|
+
* - `filePicker`, `modelPicker`, `processModal`, `liveTailModal`,
|
|
79
|
+
* `agentDetailModal`, `contextInspectorModal`, `blockActionsMenu`,
|
|
80
|
+
* `searchManager`, `historySearch` — service objects constructed once
|
|
81
|
+
* - `panelManager`, `keybindingsManager` — from uiServices, stable
|
|
82
|
+
* - `modalStack` — reference to the handler's shared array
|
|
83
|
+
* - `getHistory`, `getViewportHeight`, `getScrollTop`, `scroll`, `exitApp` — callbacks
|
|
84
|
+
* - `commandRegistry`, `commandContext`, `autocomplete`, `inputHistory`,
|
|
85
|
+
* `conversationManager` — wired after construction; synced at feed() entry only
|
|
86
|
+
* (not per-action) since no in-feed action changes them
|
|
87
|
+
*
|
|
88
|
+
* **Rationale:** per-feed mutation on a single object avoids per-keystroke GC pressure
|
|
89
|
+
* from ~80-field object allocation. Stable references are service handles that never
|
|
90
|
+
* need to change after construction.
|
|
91
|
+
*/
|
|
92
|
+
export interface FeedContextStableRefs {
|
|
93
|
+
selection: SelectionManager;
|
|
94
|
+
pasteRegistry: Map<string, string>;
|
|
95
|
+
imageRegistry: Map<string, { data: string; mediaType: string }>;
|
|
96
|
+
selectionModal: SelectionModal;
|
|
97
|
+
bookmarkModal: BookmarkModal;
|
|
98
|
+
settingsModal: SettingsModal;
|
|
99
|
+
sessionPickerModal: SessionPickerModal;
|
|
100
|
+
profilePickerModal: ProfilePickerModal;
|
|
101
|
+
historySearch: HistorySearch;
|
|
102
|
+
commandRegistry: CommandRegistry | null;
|
|
103
|
+
commandContext: CommandContext | undefined;
|
|
104
|
+
autocomplete: AutocompleteEngine | null;
|
|
105
|
+
filePicker: FilePickerModal;
|
|
106
|
+
modelPicker: ModelPickerModal;
|
|
107
|
+
processModal: ProcessModal;
|
|
108
|
+
liveTailModal: LiveTailModal;
|
|
109
|
+
agentDetailModal: AgentDetailModal;
|
|
110
|
+
contextInspectorModal: ContextInspectorModal;
|
|
111
|
+
blockActionsMenu: BlockActionsMenu;
|
|
112
|
+
searchManager: SearchManager;
|
|
113
|
+
modalStack: string[];
|
|
114
|
+
inputHistory: InputHistory | null;
|
|
115
|
+
conversationManager: ConversationManager | null;
|
|
116
|
+
panelManager: PanelManager;
|
|
117
|
+
keybindingsManager: KeybindingsManager;
|
|
118
|
+
getHistory: () => InfiniteBuffer;
|
|
119
|
+
getViewportHeight: () => number;
|
|
120
|
+
getScrollTop: () => number;
|
|
121
|
+
scroll: (delta: number) => void;
|
|
122
|
+
exitApp: () => void;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Bound method closures for InputFeedContext. Built in handler.ts where private members are accessible. */
|
|
126
|
+
export interface FeedContextClosures {
|
|
127
|
+
modalOpened: (name: string) => void;
|
|
128
|
+
handleEscape: () => void;
|
|
129
|
+
handleCopy: () => void;
|
|
130
|
+
handleCtrlC: () => void;
|
|
131
|
+
handleBlockCopy: () => void;
|
|
132
|
+
handleBookmark: () => void;
|
|
133
|
+
handleBlockSave: () => void;
|
|
134
|
+
handleDiffApply: () => boolean;
|
|
135
|
+
handleUndo: () => void;
|
|
136
|
+
handleRedo: () => void;
|
|
137
|
+
handlePaste: () => void;
|
|
138
|
+
saveUndoState: () => void;
|
|
139
|
+
ensureInputCursorVisible: (contentWidth?: number) => void;
|
|
140
|
+
registerPaste: (content: string) => string;
|
|
141
|
+
executeBlockAction: (id: string) => void;
|
|
142
|
+
cyclePanelTab: (direction: 'next' | 'prev') => void;
|
|
143
|
+
onPanelInputConsumed: (activePanel: Panel | null, key: string) => void;
|
|
144
|
+
getWrappedPromptInfo: (contentWidth: number) => WrappedPromptInfo;
|
|
145
|
+
moveCursorVertical: (direction: -1 | 1) => boolean;
|
|
146
|
+
handlePathCompletion: () => boolean;
|
|
147
|
+
handleBlockToggle: () => void;
|
|
148
|
+
findMarkerAtPos: (pos: number) => { start: number; end: number } | null;
|
|
149
|
+
cleanupMarkerRegistry: (text: string) => void;
|
|
150
|
+
expandPrompt: (text: string) => string | import('@pellux/goodvibes-sdk/platform/providers/interface').ContentPart[];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* buildInitialFeedContext — Allocate the single InputFeedContext that lives for the
|
|
155
|
+
* lifetime of the InputHandler.
|
|
156
|
+
*
|
|
157
|
+
* Accepts pre-built field groups from handler.ts where private access is available.
|
|
158
|
+
* The resulting context object is mutated in place on every feed() call via
|
|
159
|
+
* syncFeedContextMutableFields — no re-allocation occurs per keystroke.
|
|
160
|
+
*
|
|
161
|
+
* @param mutable Initial mutable scalar values (synced per-feed).
|
|
162
|
+
* @param stable Stable service references (set once, never replaced).
|
|
163
|
+
* @param closures Pre-built bound method closures (built in handler.ts).
|
|
164
|
+
*/
|
|
165
|
+
export function buildInitialFeedContext(
|
|
166
|
+
mutable: FeedContextMutableInit,
|
|
167
|
+
stable: FeedContextStableRefs,
|
|
168
|
+
closures: FeedContextClosures,
|
|
169
|
+
): InputFeedContext {
|
|
170
|
+
const noop = (): void => {};
|
|
171
|
+
return {
|
|
172
|
+
// --- mutable scalars ---
|
|
173
|
+
...mutable,
|
|
174
|
+
// --- requestRender: placeholder, swapped per-feed to buffered version ---
|
|
175
|
+
requestRender: noop,
|
|
176
|
+
// --- stable refs ---
|
|
177
|
+
...stable,
|
|
178
|
+
// --- closures ---
|
|
179
|
+
...closures,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* syncFeedContextMutableFields — Copy mutable scalar fields into the reused
|
|
185
|
+
* InputFeedContext object.
|
|
186
|
+
*
|
|
187
|
+
* **Fields synced (updated on every call):**
|
|
188
|
+
* - `prompt` — current prompt text buffer
|
|
189
|
+
* - `cursorPos` — caret position within prompt
|
|
190
|
+
* - `commandMode` — whether command-mode prefix is active
|
|
191
|
+
* - `panelFocused` — whether the active panel owns keyboard focus
|
|
192
|
+
* - `indicatorFocused` — whether the status indicator owns focus
|
|
193
|
+
* - `helpOverlayActive` / `helpScrollOffset` — help overlay visibility and scroll
|
|
194
|
+
* - `shortcutsOverlayActive` / `shortcutsScrollOffset` — shortcuts overlay state
|
|
195
|
+
* - `selectionCallback` — the current in-flight selection modal callback
|
|
196
|
+
* - `nextPasteId` / `nextImageId` — monotonically increasing allocation counters
|
|
197
|
+
* - `mouseDownRow` / `mouseDownCol` — drag-start coordinates
|
|
198
|
+
*
|
|
199
|
+
* **Fields intentionally excluded (synced only at feed() entry, not here):**
|
|
200
|
+
* - `contentWidth` — semi-stable; only changes on terminal resize via setContentWidth().
|
|
201
|
+
* Synced at feed() entry because it is only relevant for layout, not action-reaction
|
|
202
|
+
* sequences within a single feed.
|
|
203
|
+
* - `commandRegistry`, `commandContext` — wired after construction via
|
|
204
|
+
* setCommandRegistry(); synced at feed() entry since no in-feed action changes them.
|
|
205
|
+
* - `autocomplete` — wired after construction; synced at feed() entry.
|
|
206
|
+
* - `inputHistory`, `conversationManager` — late-wired service handles; stable within
|
|
207
|
+
* a feed. Synced at feed() entry only.
|
|
208
|
+
* - `requestRender` — swapped to a buffered version at feed() entry and restored in
|
|
209
|
+
* the finally block; not managed by this function.
|
|
210
|
+
*
|
|
211
|
+
* Called from within action closures (`handleEscape`, `handleCtrlC`, `handleUndo`,
|
|
212
|
+
* `handleRedo`, `handlePaste`) that mutate handler state mid-feed, so subsequent
|
|
213
|
+
* route handlers see updated values without re-reading handler fields.
|
|
214
|
+
*
|
|
215
|
+
* @param fields Current mutable field values from the handler.
|
|
216
|
+
* @param ctx The InputFeedContext to update in place.
|
|
217
|
+
*/
|
|
218
|
+
export function syncFeedContextMutableFields(
|
|
219
|
+
fields: FeedContextMutableInit,
|
|
220
|
+
ctx: InputFeedContext,
|
|
221
|
+
): void {
|
|
222
|
+
ctx.prompt = fields.prompt;
|
|
223
|
+
ctx.cursorPos = fields.cursorPos;
|
|
224
|
+
ctx.commandMode = fields.commandMode;
|
|
225
|
+
ctx.panelFocused = fields.panelFocused;
|
|
226
|
+
ctx.indicatorFocused = fields.indicatorFocused;
|
|
227
|
+
ctx.helpOverlayActive = fields.helpOverlayActive;
|
|
228
|
+
ctx.helpScrollOffset = fields.helpScrollOffset;
|
|
229
|
+
ctx.shortcutsOverlayActive = fields.shortcutsOverlayActive;
|
|
230
|
+
ctx.shortcutsScrollOffset = fields.shortcutsScrollOffset;
|
|
231
|
+
ctx.selectionCallback = fields.selectionCallback;
|
|
232
|
+
ctx.nextPasteId = fields.nextPasteId;
|
|
233
|
+
ctx.nextImageId = fields.nextImageId;
|
|
234
|
+
ctx.mouseDownRow = fields.mouseDownRow;
|
|
235
|
+
ctx.mouseDownCol = fields.mouseDownCol;
|
|
236
|
+
}
|
|
@@ -40,6 +40,44 @@ import { SelectionManager } from './selection.ts';
|
|
|
40
40
|
import type { PanelManager } from '../panels/panel-manager.ts';
|
|
41
41
|
import type { KeybindingsManager } from './keybindings.ts';
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* InputFeedContext — The single long-lived context object passed to feedInputTokens
|
|
45
|
+
* on every keystroke. Allocated once at InputHandler construction; mutated in place
|
|
46
|
+
* per-feed to avoid per-keystroke GC pressure from ~80-field object allocation.
|
|
47
|
+
*
|
|
48
|
+
* **Mutable per-feed** (synced from handler at the top of every feed() call, and
|
|
49
|
+
* updated inside action closures via syncFeedContextMutableFields):
|
|
50
|
+
* - `prompt`, `cursorPos` — current text buffer state
|
|
51
|
+
* - `commandMode`, `panelFocused`, `indicatorFocused` — focus-mode flags
|
|
52
|
+
* - `helpOverlayActive`, `helpScrollOffset` — help overlay visibility and scroll
|
|
53
|
+
* - `shortcutsOverlayActive`, `shortcutsScrollOffset` — shortcuts overlay state
|
|
54
|
+
* - `nextPasteId`, `nextImageId` — monotonically increasing ID counters
|
|
55
|
+
* - `mouseDownRow`, `mouseDownCol` — drag-tracking coordinates
|
|
56
|
+
* - `contentWidth` — reflow width (semi-stable; synced at feed() entry only)
|
|
57
|
+
* - `selectionCallback` — current in-flight selection modal callback (nullable)
|
|
58
|
+
* - `requestRender` — swapped per-feed to a buffered version, restored after
|
|
59
|
+
*
|
|
60
|
+
* **Stable service handles** (set once at construction, never reallocated):
|
|
61
|
+
* - `commandRegistry`, `commandContext` — wired via setCommandRegistry() after
|
|
62
|
+
* construction; synced at feed() entry (not per-action) since no action changes them
|
|
63
|
+
* - `autocomplete` — wired after construction; synced at feed() entry
|
|
64
|
+
* - `inputHistory`, `conversationManager` — late-wired service handles; synced at
|
|
65
|
+
* feed() entry only since no in-feed action rewires them
|
|
66
|
+
* - `pasteRegistry`, `imageRegistry` — owned Maps, never replaced
|
|
67
|
+
* - `selectionModal`, `bookmarkModal`, `settingsModal`, `sessionPickerModal`,
|
|
68
|
+
* `profilePickerModal` — modal objects constructed once in InputHandler constructor
|
|
69
|
+
* - `filePicker`, `modelPicker`, `processModal`, `liveTailModal`, `agentDetailModal`,
|
|
70
|
+
* `contextInspectorModal`, `blockActionsMenu`, `searchManager`, `historySearch` —
|
|
71
|
+
* service objects constructed once
|
|
72
|
+
* - `panelManager`, `keybindingsManager` — from uiServices, stable for app lifetime
|
|
73
|
+
* - `modalStack` — reference to the handler's shared array (mutated in place)
|
|
74
|
+
* - `getHistory`, `getViewportHeight`, `getScrollTop`, `scroll`, `exitApp` — stable
|
|
75
|
+
* callbacks bound in the InputHandler constructor
|
|
76
|
+
* - All method closures (`modalOpened`, `handleEscape`, etc.) — bound once at init
|
|
77
|
+
*
|
|
78
|
+
* **Rationale:** per-feed mutation avoids per-keystroke allocation cost; stable
|
|
79
|
+
* references are service handles whose identity never changes after construction.
|
|
80
|
+
*/
|
|
43
81
|
export interface InputFeedContext {
|
|
44
82
|
prompt: string;
|
|
45
83
|
cursorPos: number;
|
|
@@ -65,9 +103,9 @@ export interface InputFeedContext {
|
|
|
65
103
|
readonly sessionPickerModal: SessionPickerModal;
|
|
66
104
|
readonly profilePickerModal: ProfilePickerModal;
|
|
67
105
|
readonly historySearch: HistorySearch;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
106
|
+
commandRegistry: CommandRegistry | null;
|
|
107
|
+
commandContext: CommandContext | undefined;
|
|
108
|
+
autocomplete: AutocompleteEngine | null;
|
|
71
109
|
readonly filePicker: FilePickerModal;
|
|
72
110
|
readonly modelPicker: ModelPickerModal;
|
|
73
111
|
readonly processModal: ProcessModal;
|
|
@@ -79,13 +117,13 @@ export interface InputFeedContext {
|
|
|
79
117
|
readonly panelManager: PanelManager;
|
|
80
118
|
readonly keybindingsManager: KeybindingsManager;
|
|
81
119
|
readonly modalStack: string[];
|
|
82
|
-
|
|
83
|
-
|
|
120
|
+
inputHistory: InputHistory | null;
|
|
121
|
+
conversationManager: ConversationManager | null;
|
|
84
122
|
readonly getHistory: () => InfiniteBuffer;
|
|
85
123
|
readonly getViewportHeight: () => number;
|
|
86
124
|
readonly getScrollTop: () => number;
|
|
87
125
|
readonly scroll: (delta: number) => void;
|
|
88
|
-
|
|
126
|
+
requestRender: () => void;
|
|
89
127
|
readonly modalOpened: (name: string) => void;
|
|
90
128
|
readonly handleEscape: () => void;
|
|
91
129
|
readonly handleCopy: () => void;
|