@deepseek-harness-tui/dsh-tui 0.8.7 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -10
- package/bin/dsh-tui.js +1 -1
- package/lib/types/commands.d.ts.map +1 -1
- package/lib/types/commands.js +4 -2
- package/lib/types/components/ActivityPicker.d.ts +4 -1
- package/lib/types/components/ActivityPicker.d.ts.map +1 -1
- package/lib/types/components/ActivityPicker.js +2 -2
- package/lib/types/components/BtwPanel.d.ts.map +1 -1
- package/lib/types/components/BtwPanel.js +1 -1
- package/lib/types/components/Chat/SubagentMessage.d.ts +2 -1
- package/lib/types/components/Chat/SubagentMessage.d.ts.map +1 -1
- package/lib/types/components/Chat/SubagentMessage.js +11 -3
- package/lib/types/components/CommandSuggestions.d.ts +21 -4
- package/lib/types/components/CommandSuggestions.d.ts.map +1 -1
- package/lib/types/components/CommandSuggestions.js +41 -14
- package/lib/types/components/EffortSlider.d.ts +4 -1
- package/lib/types/components/EffortSlider.d.ts.map +1 -1
- package/lib/types/components/EffortSlider.js +4 -2
- package/lib/types/components/ExtensionDialog.js +6 -2
- package/lib/types/components/FileSuggestions.d.ts +26 -5
- package/lib/types/components/FileSuggestions.d.ts.map +1 -1
- package/lib/types/components/FileSuggestions.js +53 -16
- package/lib/types/components/GoalTodoPanel.d.ts +26 -2
- package/lib/types/components/GoalTodoPanel.d.ts.map +1 -1
- package/lib/types/components/GoalTodoPanel.js +86 -17
- package/lib/types/components/HelpMenu.d.ts +7 -1
- package/lib/types/components/HelpMenu.d.ts.map +1 -1
- package/lib/types/components/HelpMenu.js +12 -4
- package/lib/types/components/HistorySearchDialog.d.ts +4 -1
- package/lib/types/components/HistorySearchDialog.d.ts.map +1 -1
- package/lib/types/components/HistorySearchDialog.js +2 -2
- package/lib/types/components/LangPicker.d.ts +14 -0
- package/lib/types/components/LangPicker.d.ts.map +1 -0
- package/lib/types/components/LangPicker.js +17 -0
- package/lib/types/components/LoadedContextPanel.d.ts.map +1 -1
- package/lib/types/components/LoadedContextPanel.js +3 -1
- package/lib/types/components/LogoV2.d.ts +5 -1
- package/lib/types/components/LogoV2.d.ts.map +1 -1
- package/lib/types/components/LogoV2.js +12 -3
- package/lib/types/components/Markdown.d.ts.map +1 -1
- package/lib/types/components/Markdown.js +12 -3
- package/lib/types/components/MessageList.d.ts +43 -2
- package/lib/types/components/MessageList.d.ts.map +1 -1
- package/lib/types/components/MessageList.js +553 -41
- package/lib/types/components/ModelPicker.d.ts +4 -1
- package/lib/types/components/ModelPicker.d.ts.map +1 -1
- package/lib/types/components/ModelPicker.js +2 -2
- package/lib/types/components/PermissionsPicker.d.ts +21 -0
- package/lib/types/components/PermissionsPicker.d.ts.map +1 -0
- package/lib/types/components/PermissionsPicker.js +27 -0
- package/lib/types/components/PlanPicker.d.ts +15 -0
- package/lib/types/components/PlanPicker.d.ts.map +1 -0
- package/lib/types/components/PlanPicker.js +18 -0
- package/lib/types/components/PresetPicker.d.ts +4 -1
- package/lib/types/components/PresetPicker.d.ts.map +1 -1
- package/lib/types/components/PresetPicker.js +2 -2
- package/lib/types/components/PromptInput.d.ts +4 -3
- package/lib/types/components/PromptInput.d.ts.map +1 -1
- package/lib/types/components/PromptInput.js +273 -50
- package/lib/types/components/RewindPicker.d.ts +12 -1
- package/lib/types/components/RewindPicker.d.ts.map +1 -1
- package/lib/types/components/RewindPicker.js +6 -4
- package/lib/types/components/ScrollbarGutter.d.ts +28 -0
- package/lib/types/components/ScrollbarGutter.d.ts.map +1 -0
- package/lib/types/components/ScrollbarGutter.js +68 -0
- package/lib/types/components/Select.d.ts +8 -1
- package/lib/types/components/Select.d.ts.map +1 -1
- package/lib/types/components/Select.js +4 -2
- package/lib/types/components/SkillsPicker.d.ts +4 -1
- package/lib/types/components/SkillsPicker.d.ts.map +1 -1
- package/lib/types/components/SkillsPicker.js +2 -2
- package/lib/types/components/SubagentCard.d.ts +2 -1
- package/lib/types/components/SubagentCard.d.ts.map +1 -1
- package/lib/types/components/SubagentCard.js +6 -1
- package/lib/types/components/SubagentDashboard.d.ts +4 -0
- package/lib/types/components/SubagentDashboard.d.ts.map +1 -1
- package/lib/types/components/SubagentDashboard.js +9 -1
- package/lib/types/components/SubagentDetailScene.d.ts.map +1 -1
- package/lib/types/components/SubagentDetailScene.js +5 -4
- package/lib/types/components/SuggestionCard.d.ts +63 -0
- package/lib/types/components/SuggestionCard.d.ts.map +1 -0
- package/lib/types/components/SuggestionCard.js +77 -0
- package/lib/types/components/ThemePicker.d.ts +4 -1
- package/lib/types/components/ThemePicker.d.ts.map +1 -1
- package/lib/types/components/ThemePicker.js +2 -2
- package/lib/types/components/ThinkingToggle.d.ts +4 -1
- package/lib/types/components/ThinkingToggle.d.ts.map +1 -1
- package/lib/types/components/ThinkingToggle.js +2 -2
- package/lib/types/components/TimelineRail.d.ts +53 -0
- package/lib/types/components/TimelineRail.d.ts.map +1 -0
- package/lib/types/components/TimelineRail.js +190 -0
- package/lib/types/components/WorkspaceFlowPicker.d.ts +6 -1
- package/lib/types/components/WorkspaceFlowPicker.d.ts.map +1 -1
- package/lib/types/components/WorkspaceFlowPicker.js +4 -2
- package/lib/types/components/WorkspaceMenuPicker.d.ts +19 -0
- package/lib/types/components/WorkspaceMenuPicker.d.ts.map +1 -0
- package/lib/types/components/WorkspaceMenuPicker.js +19 -0
- package/lib/types/components/WorkspacePicker.d.ts +4 -1
- package/lib/types/components/WorkspacePicker.d.ts.map +1 -1
- package/lib/types/components/WorkspacePicker.js +2 -2
- package/lib/types/components/approvals/ApprovalPanel.d.ts.map +1 -1
- package/lib/types/components/approvals/ApprovalPanel.js +5 -1
- package/lib/types/components/design-system/ListItem.d.ts +8 -1
- package/lib/types/components/design-system/ListItem.d.ts.map +1 -1
- package/lib/types/components/design-system/ListItem.js +8 -3
- package/lib/types/components/messages/AssistantTextMessage.d.ts +5 -2
- package/lib/types/components/messages/AssistantTextMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantTextMessage.js +6 -2
- package/lib/types/components/messages/AssistantThinkingMessage.d.ts +2 -1
- package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantThinkingMessage.js +10 -4
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts +8 -1
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantToolUseMessage.js +4 -2
- package/lib/types/components/messages/UserPromptMessage.d.ts +2 -1
- package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
- package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
- package/lib/types/components/questions/AskUserQuestionPanel.js +34 -2
- package/lib/types/components/questions/PlanReviewPanel.d.ts.map +1 -1
- package/lib/types/components/questions/PlanReviewPanel.js +13 -2
- package/lib/types/components/sessions/SessionListRow.d.ts +4 -1
- package/lib/types/components/sessions/SessionListRow.d.ts.map +1 -1
- package/lib/types/components/sessions/SessionListRow.js +4 -2
- package/lib/types/components/trajectory/HotspotView.d.ts +7 -1
- package/lib/types/components/trajectory/HotspotView.d.ts.map +1 -1
- package/lib/types/components/trajectory/HotspotView.js +8 -2
- package/lib/types/components/trajectory/Ledger.d.ts +9 -1
- package/lib/types/components/trajectory/Ledger.d.ts.map +1 -1
- package/lib/types/components/trajectory/Ledger.js +23 -5
- package/lib/types/components/trajectory/WaveBand.d.ts +10 -1
- package/lib/types/components/trajectory/WaveBand.d.ts.map +1 -1
- package/lib/types/components/trajectory/WaveBand.js +7 -2
- package/lib/types/dsh-adapter/channel.d.ts +27 -6
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel.js +432 -66
- package/lib/types/dsh-adapter/contract.d.ts +58 -18
- package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
- package/lib/types/dsh-adapter/contract.js +124 -36
- package/lib/types/dsh-adapter/index.d.ts +10 -2
- package/lib/types/dsh-adapter/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/index.js +10 -13
- package/lib/types/dsh-adapter/plugin.d.ts +0 -9
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin.js +136 -6
- package/lib/types/i18n.d.ts +194 -16
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +73 -10
- package/lib/types/ink/components/AlternateScreen.d.ts.map +1 -1
- package/lib/types/ink/components/AlternateScreen.js +13 -1
- package/lib/types/ink/components/App.d.ts +12 -1
- package/lib/types/ink/components/App.d.ts.map +1 -1
- package/lib/types/ink/components/App.js +101 -4
- package/lib/types/ink/components/ClockContext.d.ts.map +1 -1
- package/lib/types/ink/components/ClockContext.js +2 -0
- package/lib/types/ink/components/ScrollBox.d.ts.map +1 -1
- package/lib/types/ink/components/ScrollBox.js +170 -91
- package/lib/types/ink/constants.d.ts +9 -0
- package/lib/types/ink/constants.d.ts.map +1 -1
- package/lib/types/ink/constants.js +9 -0
- package/lib/types/ink/events/click-event.d.ts +7 -16
- package/lib/types/ink/events/click-event.d.ts.map +1 -1
- package/lib/types/ink/events/click-event.js +16 -19
- package/lib/types/ink/events/event-handlers.d.ts +11 -1
- package/lib/types/ink/events/event-handlers.d.ts.map +1 -1
- package/lib/types/ink/events/event-handlers.js +2 -0
- package/lib/types/ink/events/input-event.d.ts +9 -0
- package/lib/types/ink/events/input-event.d.ts.map +1 -1
- package/lib/types/ink/events/input-event.js +4 -0
- package/lib/types/ink/events/pointer-event.d.ts +61 -0
- package/lib/types/ink/events/pointer-event.d.ts.map +1 -0
- package/lib/types/ink/events/pointer-event.js +61 -0
- package/lib/types/ink/events/wheel-event.d.ts +17 -0
- package/lib/types/ink/events/wheel-event.d.ts.map +1 -0
- package/lib/types/ink/events/wheel-event.js +20 -0
- package/lib/types/ink/geometry-trace.d.ts +51 -0
- package/lib/types/ink/geometry-trace.d.ts.map +1 -0
- package/lib/types/ink/geometry-trace.js +83 -0
- package/lib/types/ink/hit-test.d.ts +56 -1
- package/lib/types/ink/hit-test.d.ts.map +1 -1
- package/lib/types/ink/hit-test.js +146 -8
- package/lib/types/ink/ink.d.ts +58 -1
- package/lib/types/ink/ink.d.ts.map +1 -1
- package/lib/types/ink/ink.js +268 -52
- package/lib/types/ink/node-cache.d.ts +5 -0
- package/lib/types/ink/node-cache.d.ts.map +1 -1
- package/lib/types/ink/parse-keypress.d.ts +8 -0
- package/lib/types/ink/parse-keypress.d.ts.map +1 -1
- package/lib/types/ink/parse-keypress.js +101 -12
- package/lib/types/ink/render-node-to-output.d.ts +16 -3
- package/lib/types/ink/render-node-to-output.d.ts.map +1 -1
- package/lib/types/ink/render-node-to-output.js +144 -25
- package/lib/types/ink/selection.d.ts +29 -4
- package/lib/types/ink/selection.d.ts.map +1 -1
- package/lib/types/ink/selection.js +41 -5
- package/lib/types/ink/timeline-rail.d.ts +138 -0
- package/lib/types/ink/timeline-rail.d.ts.map +1 -0
- package/lib/types/ink/timeline-rail.js +179 -0
- package/lib/types/ink/wrap-text.d.ts.map +1 -1
- package/lib/types/ink/wrap-text.js +47 -5
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +819 -337
- package/lib/types/screens/SessionBrowser.d.ts.map +1 -1
- package/lib/types/screens/SessionBrowser.js +56 -29
- package/lib/types/screens/Settings.d.ts.map +1 -1
- package/lib/types/screens/Settings.js +64 -22
- package/lib/types/screens/StatusLine.d.ts +2 -1
- package/lib/types/screens/StatusLine.d.ts.map +1 -1
- package/lib/types/screens/StatusLine.js +17 -1
- package/lib/types/screens/TrajectoryScene.d.ts.map +1 -1
- package/lib/types/screens/TrajectoryScene.js +81 -14
- package/lib/types/screens/chatOverlay.d.ts +221 -0
- package/lib/types/screens/chatOverlay.d.ts.map +1 -0
- package/lib/types/screens/chatOverlay.js +114 -0
- package/lib/types/tips.d.ts.map +1 -1
- package/lib/types/tips.js +64 -10
- package/lib/types/tuiDisplayPrefs.d.ts +15 -0
- package/lib/types/tuiDisplayPrefs.d.ts.map +1 -1
- package/lib/types/tuiDisplayPrefs.js +9 -0
- package/lib/types/utils/debug.d.ts +10 -0
- package/lib/types/utils/debug.d.ts.map +1 -1
- package/lib/types/utils/debug.js +24 -0
- package/lib/types/utils/fileSuggestions.d.ts +19 -0
- package/lib/types/utils/fileSuggestions.d.ts.map +1 -0
- package/lib/types/utils/fileSuggestions.js +40 -0
- package/lib/types/utils/mentions.d.ts.map +1 -1
- package/lib/types/utils/mentions.js +3 -1
- package/lib/types/utils/paths.d.ts.map +1 -1
- package/lib/types/utils/paths.js +1 -0
- package/package.json +69 -66
package/lib/types/ink/ink.js
CHANGED
|
@@ -12,12 +12,14 @@ import { logError } from '../utils/log.js';
|
|
|
12
12
|
import { format } from 'util';
|
|
13
13
|
import { colorize } from './colorize.js';
|
|
14
14
|
import App from './components/App.js';
|
|
15
|
-
import { FRAME_INTERVAL_MS } from './constants.js';
|
|
15
|
+
import { FRAME_INTERVAL_MS, PTY_BACKLOG_BYTES } from './constants.js';
|
|
16
16
|
import * as dom from './dom.js';
|
|
17
|
+
import { beginGeometryFrame, endGeometryFrame, GEOMETRY_TRACE_ENABLED, noteFrameCause } from './geometry-trace.js';
|
|
17
18
|
import { KeyboardEvent } from './events/keyboard-event.js';
|
|
18
19
|
import { FocusManager } from './focus.js';
|
|
19
20
|
import { emptyFrame } from './frame.js';
|
|
20
|
-
import { dispatchClick, dispatchHover } from './hit-test.js';
|
|
21
|
+
import { dispatchClick, dispatchHover, dispatchWheel, clearHovered } from './hit-test.js';
|
|
22
|
+
import { logMouseDebug } from '../utils/debug.js';
|
|
21
23
|
import instances from './instances.js';
|
|
22
24
|
import { suppressInputFor } from './input-suppression.js';
|
|
23
25
|
import { LogUpdate } from './log-update.js';
|
|
@@ -30,11 +32,12 @@ import { applyPositionedHighlight, scanPositions } from './render-to-screen.js';
|
|
|
30
32
|
import createRenderer from './renderer.js';
|
|
31
33
|
import { CharPool, cellAt, createScreen, HyperlinkPool, isEmptyCellAt, migrateScreenPools, StylePool } from './screen.js';
|
|
32
34
|
import { applySearchHighlight } from './searchHighlight.js';
|
|
33
|
-
import { applySelectionOverlay, captureScrolledRows, clearSelection, createSelectionState, extendSelection, findPlainTextUrlAt, getSelectedText, hasSelection, moveFocus, selectLineAt, selectWordAt, shiftAnchor, shiftSelection, shiftSelectionForFollow, startSelection, updateSelection } from './selection.js';
|
|
35
|
+
import { applySelectionOverlay, captureScrolledRows, clearSelection, createSelectionState, extendSelection, findPlainTextUrlAt, getSelectedText, hasSelection, moveFocus, pickFollowForSelection, selectLineAt, selectWordAt, shiftAnchor, shiftSelection, shiftSelectionForFollow, startSelection, updateSelection } from './selection.js';
|
|
34
36
|
import { isDecstbmSafe, SYNC_OUTPUT_SUPPORTED, supportsExtendedKeys, supportsWin32InputMode, writeDiffToTerminal } from './terminal.js';
|
|
35
37
|
import { CURSOR_HOME, cursorMove, cursorPosition, DISABLE_KITTY_KEYBOARD, DISABLE_MODIFY_OTHER_KEYS, DISABLE_WIN32_INPUT_MODE, ENABLE_KITTY_KEYBOARD, ENABLE_MODIFY_OTHER_KEYS, ENABLE_WIN32_INPUT_MODE, ERASE_SCREEN, ERASE_SCROLLBACK, SGR_RESET } from './termio/csi.js';
|
|
36
38
|
import { DBP, DFE, DISABLE_MOUSE_TRACKING, ENABLE_MOUSE_TRACKING, ENTER_ALT_SCREEN, EXIT_ALT_SCREEN, SHOW_CURSOR } from './termio/dec.js';
|
|
37
39
|
import { CLEAR_ITERM2_PROGRESS, CLEAR_TAB_STATUS, setClipboard, supportsTabStatus, wrapForMultiplexer } from './termio/osc.js';
|
|
40
|
+
import { decrqm } from './terminal-querier.js';
|
|
38
41
|
import { TerminalWriteProvider } from './useTerminalNotification.js';
|
|
39
42
|
// Alt-screen: renderer.ts sets cursor.visible = !isTTY || screen.height===0,
|
|
40
43
|
// which is always false in alt-screen (TTY + content fills screen).
|
|
@@ -310,9 +313,31 @@ export default class Ink {
|
|
|
310
313
|
// frame resets and renders.
|
|
311
314
|
if (cols === this.terminalColumns && rows === this.terminalRows)
|
|
312
315
|
return;
|
|
316
|
+
noteFrameCause('resize');
|
|
313
317
|
this.terminalColumns = cols;
|
|
314
318
|
this.terminalRows = rows;
|
|
315
319
|
this.altScreenParkPatch = makeAltScreenParkPatch(this.terminalRows);
|
|
320
|
+
// Reflow moved every rect the pointer state was tracking: hover sets
|
|
321
|
+
// and the multi-click chain reference pre-resize geometry. Fire the
|
|
322
|
+
// leave handlers FIRST — a bare clear() strands the crossed rows'
|
|
323
|
+
// hovered=true React state forever (stuck highlights) — then drop the
|
|
324
|
+
// set so a post-resize click is a fresh single click and hover re-fires
|
|
325
|
+
// from scratch. (Coordinates in in-flight events are clamped at the
|
|
326
|
+
// App boundary against the new dimensions.)
|
|
327
|
+
clearHovered(this.hoveredNodes);
|
|
328
|
+
this.app?.resetPointerState();
|
|
329
|
+
// Invalidate every render that was scheduled against the OLD size: a
|
|
330
|
+
// queued microtask generation or a scroll-drain timer would otherwise
|
|
331
|
+
// fire after this resize completes and paint a frame computed for the
|
|
332
|
+
// pre-resize layout (mixed-width rows, off-by-reflow writes). The
|
|
333
|
+
// re-render below schedules fresh work at the new dimensions.
|
|
334
|
+
this.renderGeneration++;
|
|
335
|
+
this.pendingRenderGeneration = null;
|
|
336
|
+
this.scheduleRender.cancel?.();
|
|
337
|
+
if (this.drainTimer !== null) {
|
|
338
|
+
clearTimeout(this.drainTimer);
|
|
339
|
+
this.drainTimer = null;
|
|
340
|
+
}
|
|
316
341
|
// Every cached measurement in the tree was taken against a width that no
|
|
317
342
|
// longer exists. Nothing here is "dirty" in the reconciler's sense — no
|
|
318
343
|
// props changed — so without an explicit sweep the text nodes keep
|
|
@@ -333,9 +358,12 @@ export default class Ink {
|
|
|
333
358
|
// doesn't exit alt-screen. Do NOT write ERASE_SCREEN: render() below
|
|
334
359
|
// can take ~80ms; erasing first leaves the screen blank that whole time.
|
|
335
360
|
if (this.altScreenActive && !this.isPaused && this.options.stdout.isTTY) {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
361
|
+
// Blind mouse re-assert + 1049 probe: conpty resets modes on resize
|
|
362
|
+
// too, and a dropped 1049 means every subsequent frame paints onto
|
|
363
|
+
// the MAIN screen (looks like the app spontaneously exited
|
|
364
|
+
// fullscreen). The probe's re-entry is gated on a positive DECRPM
|
|
365
|
+
// "reset" answer, so this stays inert on healthy terminals.
|
|
366
|
+
this.probeAltScreenHealth();
|
|
339
367
|
this.resetFramesForAltScreen();
|
|
340
368
|
this.needsEraseBeforePaint = true;
|
|
341
369
|
}
|
|
@@ -465,10 +493,12 @@ export default class Ink {
|
|
|
465
493
|
reanchorViewport() {
|
|
466
494
|
if (this.altScreenActive)
|
|
467
495
|
return;
|
|
496
|
+
noteFrameCause('reanchor');
|
|
468
497
|
this.log.requestViewportReanchor();
|
|
469
498
|
}
|
|
470
499
|
/** Render synchronously and invalidate older trailing/drain callbacks. */
|
|
471
500
|
renderNow() {
|
|
501
|
+
noteFrameCause('immediate');
|
|
472
502
|
this.renderGeneration++;
|
|
473
503
|
this.pendingRenderGeneration = null;
|
|
474
504
|
this.scheduleRender.cancel?.();
|
|
@@ -478,10 +508,49 @@ export default class Ink {
|
|
|
478
508
|
}
|
|
479
509
|
this.onRender();
|
|
480
510
|
}
|
|
511
|
+
/**
|
|
512
|
+
* Schedule the next scroll-drain frame — Grok Build's Presenter, ported.
|
|
513
|
+
* Grok keeps TWO cadence knobs (min_draw_ms for renders, scroll_ms for
|
|
514
|
+
* scroll); collapsing both to FRAME_INTERVAL_MS made big flicks steppy
|
|
515
|
+
* (the proportional step, 75%-of-remaining, × 4× the interval = chunky
|
|
516
|
+
* ramp and ~4× longer settle), so the drain keeps its own fast cadence:
|
|
517
|
+
*
|
|
518
|
+
* - CADENCE: quarter interval (~250fps). Drain frames are cheap
|
|
519
|
+
* (DECSTBM + ~10 patches); scroll throughput is unchanged.
|
|
520
|
+
* - IN-FLIGHT GATE: while stdout still holds unflushed bytes above
|
|
521
|
+
* PTY_BACKLOG_BYTES (slow ConPTY round trip, ssh link), queue nothing
|
|
522
|
+
* further — re-probe at quarter interval instead. Grok's equivalent
|
|
523
|
+
* (in_flight_target + writer ack) exists to keep latency bounded
|
|
524
|
+
* under exactly this backpressure; without it each stacked frame adds
|
|
525
|
+
* its full render+write to the input→paint latency, which reads as
|
|
526
|
+
* sticky, laggy scrolling on Windows terminals.
|
|
527
|
+
*
|
|
528
|
+
* The gate holds only DRAIN frames; React-driven renders (keystrokes,
|
|
529
|
+
* streaming) still render via the normal throttle — user-visible updates
|
|
530
|
+
* must never wait behind scroll output.
|
|
531
|
+
*/
|
|
532
|
+
scheduleDrain() {
|
|
533
|
+
if (this.drainTimer !== null)
|
|
534
|
+
return;
|
|
535
|
+
const stdout = this.options.stdout;
|
|
536
|
+
const backlog = typeof stdout.writableLength === 'number'
|
|
537
|
+
? stdout.writableLength
|
|
538
|
+
: 0;
|
|
539
|
+
if (backlog > PTY_BACKLOG_BYTES) {
|
|
540
|
+
this.drainTimer = setTimeout(() => {
|
|
541
|
+
this.drainTimer = null;
|
|
542
|
+
this.scheduleDrain();
|
|
543
|
+
}, FRAME_INTERVAL_MS >> 2);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
this.drainTimer = setTimeout(this.renderNow, FRAME_INTERVAL_MS >> 2);
|
|
547
|
+
}
|
|
481
548
|
onRender() {
|
|
482
549
|
if (this.isUnmounted || this.isPaused) {
|
|
483
550
|
return;
|
|
484
551
|
}
|
|
552
|
+
if (GEOMETRY_TRACE_ENABLED)
|
|
553
|
+
beginGeometryFrame(this.renderGeneration);
|
|
485
554
|
// Entering a render cancels any pending drain tick — this render will
|
|
486
555
|
// handle the drain (and re-schedule below if needed). Prevents a
|
|
487
556
|
// wheel-event-triggered render AND a drain-timer render both firing.
|
|
@@ -494,9 +563,25 @@ export default class Ink {
|
|
|
494
563
|
// Done before the render to avoid dirtying state that would trigger
|
|
495
564
|
// an extra React re-render cycle.
|
|
496
565
|
flushInteractionTime();
|
|
566
|
+
// Dimension consistency: onComputeLayout laid the tree out against
|
|
567
|
+
// this.terminalColumns/Rows (the cached values handleResize owns), so
|
|
568
|
+
// the renderer and the diff engine MUST paint that same size. Reading
|
|
569
|
+
// the live stdout size here mixed the two when a resize event had not
|
|
570
|
+
// fired yet (Windows Terminal emits 'resize' after columns already
|
|
571
|
+
// changed): Yoga laid out for the old width while log-update wrapped
|
|
572
|
+
// and diffed at the new one — rows painted off by the reflow delta.
|
|
573
|
+
// On drift, route through the resize path (cache sync + markTreeDirty
|
|
574
|
+
// + re-render) and bail: the render handleResize schedules paints the
|
|
575
|
+
// correctly-laid-out frame.
|
|
576
|
+
const liveColumns = this.options.stdout.columns || 80;
|
|
577
|
+
const liveRows = this.options.stdout.rows || 24;
|
|
578
|
+
if (this.options.stdout.isTTY && (liveColumns !== this.terminalColumns || liveRows !== this.terminalRows)) {
|
|
579
|
+
this.handleResize();
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
497
582
|
const renderStart = performance.now();
|
|
498
|
-
const terminalWidth = this.
|
|
499
|
-
const terminalRows = this.
|
|
583
|
+
const terminalWidth = this.terminalColumns;
|
|
584
|
+
const terminalRows = this.terminalRows;
|
|
500
585
|
const frame = this.renderer({
|
|
501
586
|
frontFrame: this.frontFrame,
|
|
502
587
|
backFrame: this.backFrame,
|
|
@@ -507,10 +592,11 @@ export default class Ink {
|
|
|
507
592
|
prevFrameContaminated: this.prevFrameContaminated
|
|
508
593
|
});
|
|
509
594
|
const rendererMs = performance.now() - renderStart;
|
|
510
|
-
// Sticky/auto-follow
|
|
511
|
-
// selection by the same delta so the highlight
|
|
512
|
-
// TEXT (native terminal behavior — the
|
|
513
|
-
// as content scrolls, eventually
|
|
595
|
+
// Sticky/auto-follow or wheel-drain scrolled one or more ScrollBoxes
|
|
596
|
+
// this frame. Translate the selection by the same delta so the highlight
|
|
597
|
+
// stays anchored to the TEXT (native terminal behavior — the
|
|
598
|
+
// selection walks up the screen as content scrolls, eventually
|
|
599
|
+
// clipping at the top). frontFrame
|
|
514
600
|
// still holds the PREVIOUS frame's screen (swap is at ~500 below), so
|
|
515
601
|
// captureScrolledRows reads the rows that are about to scroll out
|
|
516
602
|
// before they're overwritten — the text stays copyable until the
|
|
@@ -518,16 +604,28 @@ export default class Ink {
|
|
|
518
604
|
// (screen-local) so only anchor shifts — selection grows toward the
|
|
519
605
|
// mouse as the anchor walks up. After release, both ends are text-
|
|
520
606
|
// anchored and move as a block.
|
|
521
|
-
const follow = consumeFollowScroll();
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
607
|
+
const follow = pickFollowForSelection(consumeFollowScroll(), this.selection.anchor?.row ?? null);
|
|
608
|
+
// pickFollowForSelection already checked anchor-in-viewport (that IS
|
|
609
|
+
// the "selection is on scrollbox content" guard — footer/prompt
|
|
610
|
+
// selections on static text match no viewport and follow nothing).
|
|
611
|
+
// Innermost-viewport wins attributes correctly when several boxes
|
|
612
|
+
// scrolled this frame (transcript draining while an overlay panel's
|
|
613
|
+
// box scrolls): panels render on top, so overlap-row selections
|
|
614
|
+
// belong to the panel, not the covered transcript.
|
|
615
|
+
if (follow && this.selection.anchor) {
|
|
530
616
|
const { delta, viewportTop, viewportBottom } = follow;
|
|
617
|
+
// Signed delta: >0 = content moved up (at-bottom follow or
|
|
618
|
+
// wheel-down drain); <0 = content moved down (wheel-up drain, #438).
|
|
619
|
+
// The capture window is the viewport-edge rows about to scroll out
|
|
620
|
+
// (top edge when content moves up, bottom edge when it moves down),
|
|
621
|
+
// and the shift re-anchors the endpoints by the same amount in the
|
|
622
|
+
// OPPOSITE direction so they track the text, not the screen.
|
|
623
|
+
const rows = Math.abs(delta);
|
|
624
|
+
const up = delta > 0;
|
|
625
|
+
const firstRow = up ? viewportTop : viewportBottom - rows + 1;
|
|
626
|
+
const lastRow = up ? viewportTop + rows - 1 : viewportBottom;
|
|
627
|
+
const side = up ? 'above' : 'below';
|
|
628
|
+
const shift = up ? -rows : rows;
|
|
531
629
|
// captureScrolledRows and shift* are a pair: capture grabs rows about
|
|
532
630
|
// to scroll off, shift moves the selection endpoint so the same rows
|
|
533
631
|
// won't intersect again next frame. Capturing without shifting leaves
|
|
@@ -537,9 +635,9 @@ export default class Ink {
|
|
|
537
635
|
// each shift branch so the pairing can't be broken by a new guard.
|
|
538
636
|
if (this.selection.isDragging) {
|
|
539
637
|
if (hasSelection(this.selection)) {
|
|
540
|
-
captureScrolledRows(this.selection, this.frontFrame.screen,
|
|
638
|
+
captureScrolledRows(this.selection, this.frontFrame.screen, firstRow, lastRow, side);
|
|
541
639
|
}
|
|
542
|
-
shiftAnchor(this.selection,
|
|
640
|
+
shiftAnchor(this.selection, shift, viewportTop, viewportBottom);
|
|
543
641
|
}
|
|
544
642
|
else if (
|
|
545
643
|
// Flag-3 guard: the anchor check above only proves ONE endpoint is
|
|
@@ -556,14 +654,16 @@ export default class Ink {
|
|
|
556
654
|
// is correct there even when focus is in the footer).
|
|
557
655
|
!this.selection.focus || this.selection.focus.row >= viewportTop && this.selection.focus.row <= viewportBottom) {
|
|
558
656
|
if (hasSelection(this.selection)) {
|
|
559
|
-
captureScrolledRows(this.selection, this.frontFrame.screen,
|
|
657
|
+
captureScrolledRows(this.selection, this.frontFrame.screen, firstRow, lastRow, side);
|
|
560
658
|
}
|
|
561
|
-
const cleared = shiftSelectionForFollow(this.selection,
|
|
562
|
-
// Auto-clear (both ends overshot
|
|
563
|
-
//
|
|
564
|
-
//
|
|
565
|
-
//
|
|
566
|
-
//
|
|
659
|
+
const cleared = shiftSelectionForFollow(this.selection, shift, viewportTop, viewportBottom);
|
|
660
|
+
// Auto-clear (both ends overshot an edge — off the top via
|
|
661
|
+
// follow/wheel-down, off the bottom via wheel-up) must notify
|
|
662
|
+
// React-land so useHasSelection re-renders and the footer
|
|
663
|
+
// copy/escape hint disappears. notifySelectionChange() would
|
|
664
|
+
// recurse into onRender; fire the listeners directly — they
|
|
665
|
+
// schedule a React update for LATER, they don't re-enter this
|
|
666
|
+
// frame.
|
|
567
667
|
if (cleared)
|
|
568
668
|
for (const cb of this.selectionListeners)
|
|
569
669
|
cb();
|
|
@@ -809,18 +909,16 @@ export default class Ink {
|
|
|
809
909
|
// are only ever true in alt-screen; in main-screen this is false→false.
|
|
810
910
|
this.prevFrameContaminated = selActive || hlActive;
|
|
811
911
|
// A ScrollBox has pendingScrollDelta left to drain — schedule the next
|
|
812
|
-
// frame
|
|
813
|
-
//
|
|
814
|
-
//
|
|
815
|
-
//
|
|
816
|
-
//
|
|
817
|
-
// render arrives first, renderNow cancels
|
|
818
|
-
//
|
|
819
|
-
// Drain frames are cheap (DECSTBM + ~10 patches, ~200 bytes) so run at
|
|
820
|
-
// quarter interval (~250fps, setTimeout practical floor) for max scroll
|
|
821
|
-
// speed. Regular renders stay at FRAME_INTERVAL_MS via the throttle.
|
|
912
|
+
// frame via scheduleDrain (cadence + pty backpressure gate, see there).
|
|
913
|
+
// MUST NOT call this.scheduleRender() here: we're inside a trailing-edge
|
|
914
|
+
// throttle invocation, timerId is undefined, and lodash's debounce sees
|
|
915
|
+
// timeSinceLastCall >= wait (last call was at the start of this window)
|
|
916
|
+
// → leadingEdge fires IMMEDIATELY → double render ~0.1ms apart → jank.
|
|
917
|
+
// If a wheel event or immediate render arrives first, renderNow cancels
|
|
918
|
+
// this timer — no double.
|
|
822
919
|
if (frame.scrollDrainPending) {
|
|
823
|
-
|
|
920
|
+
noteFrameCause('scroll-drain');
|
|
921
|
+
this.scheduleDrain();
|
|
824
922
|
}
|
|
825
923
|
const yogaMs = getLastYogaMs();
|
|
826
924
|
const commitMs = getLastCommitMs();
|
|
@@ -834,6 +932,7 @@ export default class Ink {
|
|
|
834
932
|
cacheHits: 0,
|
|
835
933
|
live: 0
|
|
836
934
|
};
|
|
935
|
+
endGeometryFrame(performance.now() - renderStart);
|
|
837
936
|
this.options.onFrame?.({
|
|
838
937
|
durationMs: performance.now() - renderStart,
|
|
839
938
|
phases: {
|
|
@@ -953,6 +1052,14 @@ export default class Ink {
|
|
|
953
1052
|
return;
|
|
954
1053
|
this.altScreenActive = active;
|
|
955
1054
|
this.altScreenMouseTracking = active && mouseTracking;
|
|
1055
|
+
// Screen geometry/context just changed wholesale: hover sets, the
|
|
1056
|
+
// multi-click chain, and any pending hyperlink open belong to the old
|
|
1057
|
+
// screen. Fire leave handlers before dropping the set — a bare clear()
|
|
1058
|
+
// strands the old screen's rows with hovered=true forever (stuck
|
|
1059
|
+
// highlights). Stale clickCount would turn the first click into a
|
|
1060
|
+
// double-click; stale hovered nodes would suppress real onMouseEnter.
|
|
1061
|
+
clearHovered(this.hoveredNodes);
|
|
1062
|
+
this.app?.resetPointerState();
|
|
956
1063
|
if (active) {
|
|
957
1064
|
this.mainScreenFrameState = {
|
|
958
1065
|
frontFrame: this.frontFrame,
|
|
@@ -1038,10 +1145,10 @@ export default class Ink {
|
|
|
1038
1145
|
this.renderNow();
|
|
1039
1146
|
return;
|
|
1040
1147
|
}
|
|
1041
|
-
// Mouse tracking —
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1148
|
+
// Mouse tracking + alt-screen health — the probe re-asserts mouse
|
|
1149
|
+
// blindly (idempotent) and re-enters alt only if the terminal answers
|
|
1150
|
+
// DECRPM with "1049 reset".
|
|
1151
|
+
this.probeAltScreenHealth();
|
|
1045
1152
|
// Alt-screen re-entry — destructive (ERASE_SCREEN). Only for callers that
|
|
1046
1153
|
// have a strong signal the terminal actually dropped mode 1049.
|
|
1047
1154
|
if (includeAltScreen) {
|
|
@@ -1064,6 +1171,10 @@ export default class Ink {
|
|
|
1064
1171
|
// Cancel any pending throttled render so it doesn't fire between
|
|
1065
1172
|
// cleanupTerminalModes() and process.exit() and write to main screen.
|
|
1066
1173
|
this.scheduleRender.cancel?.();
|
|
1174
|
+
if (this.drainTimer !== null) {
|
|
1175
|
+
clearTimeout(this.drainTimer);
|
|
1176
|
+
this.drainTimer = null;
|
|
1177
|
+
}
|
|
1067
1178
|
this.app?.detachForShutdown();
|
|
1068
1179
|
// Shutdown bypasses the normal unmount path, so release the process and
|
|
1069
1180
|
// stdout listeners here as well. Otherwise a SIGCONT or resize arriving
|
|
@@ -1071,6 +1182,11 @@ export default class Ink {
|
|
|
1071
1182
|
// through this detached instance after terminal cleanup has completed.
|
|
1072
1183
|
this.unsubscribeTTYHandlers?.();
|
|
1073
1184
|
this.unsubscribeExit();
|
|
1185
|
+
// unmount() early-returns on isUnmounted above, so its instances.delete
|
|
1186
|
+
// never runs — a detached instance would stay in the map and a later
|
|
1187
|
+
// instances.get(stdout) lookup (Chat's reanchorViewport plumbing) could
|
|
1188
|
+
// hand out a dead renderer. Remove the mapping here too.
|
|
1189
|
+
instances.delete(this.options.stdout);
|
|
1074
1190
|
// `detachForShutdown()` deliberately makes later `unmount()` calls a
|
|
1075
1191
|
// no-op, so release process-level output patches here rather than relying
|
|
1076
1192
|
// on unmount() to do it. The shutdown continuation may run an updater
|
|
@@ -1136,6 +1252,57 @@ export default class Ink {
|
|
|
1136
1252
|
drainStdin() {
|
|
1137
1253
|
drainStdin(this.options.stdin);
|
|
1138
1254
|
}
|
|
1255
|
+
/**
|
|
1256
|
+
* Self-heal after a terminal-side mode reset. Windows conpty drops DEC
|
|
1257
|
+
* private modes on DPI changes, window moves between monitors and renderer
|
|
1258
|
+
* restarts — the user sees the app "exit fullscreen" with a dead mouse
|
|
1259
|
+
* while altScreenActive still claims we are in alt. Two layers:
|
|
1260
|
+
*
|
|
1261
|
+
* 1. Blind, idempotent mouse-tracking re-assert (covers the mode reset
|
|
1262
|
+
* without a round trip; ~30 bytes).
|
|
1263
|
+
* 2. DECRQM probe of mode 1049. Re-entry (destructive: ERASE) happens
|
|
1264
|
+
* ONLY on a positive "reset" answer, so iTerm2's
|
|
1265
|
+
* enter-clears-when-already-in-alt quirk can never fire on a healthy
|
|
1266
|
+
* screen, and terminals that ignore DECRQM stay inert.
|
|
1267
|
+
*
|
|
1268
|
+
* Called from every interaction dispatch (click/hover/wheel/key) plus the
|
|
1269
|
+
* focus/resize/stdin-gap triggers. The 250ms throttle keeps the round
|
|
1270
|
+
* trip bounded while active use is going on — a dropped 1049 heals on
|
|
1271
|
+
* the FIRST interaction after the drop instead of waiting for a focus
|
|
1272
|
+
* event or a >5s idle gap (mouse motion keeps lastStdinTime fresh, so
|
|
1273
|
+
* the gap path never fires during active use — the exact pattern that
|
|
1274
|
+
* left the app broken until the user gave up).
|
|
1275
|
+
*/
|
|
1276
|
+
lastHealthProbeAt = 0;
|
|
1277
|
+
probeAltScreenHealth = () => {
|
|
1278
|
+
const now = Date.now();
|
|
1279
|
+
if (now - this.lastHealthProbeAt < 250)
|
|
1280
|
+
return;
|
|
1281
|
+
this.lastHealthProbeAt = now;
|
|
1282
|
+
if (!this.options.stdout.isTTY || this.isPaused || !this.altScreenActive)
|
|
1283
|
+
return;
|
|
1284
|
+
if (this.altScreenMouseTracking) {
|
|
1285
|
+
this.options.stdout.write(ENABLE_MOUSE_TRACKING);
|
|
1286
|
+
}
|
|
1287
|
+
const querier = this.app?.querier;
|
|
1288
|
+
if (querier === undefined)
|
|
1289
|
+
return;
|
|
1290
|
+
void Promise.all([querier.send(decrqm(1049)), querier.flush()]).then(([reply]) => {
|
|
1291
|
+
// DECRPM status: 1/3 = set, 2/4 = reset, 0/undefined = unknown.
|
|
1292
|
+
// Heal only on a POSITIVE reset — an unanswered probe must not
|
|
1293
|
+
// trigger the destructive re-entry.
|
|
1294
|
+
if (reply !== undefined && (reply.status === 2 || reply.status === 4)) {
|
|
1295
|
+
this.reenterAltScreen();
|
|
1296
|
+
}
|
|
1297
|
+
}).catch(() => {
|
|
1298
|
+
/* probe is best-effort; the next trigger retries */
|
|
1299
|
+
});
|
|
1300
|
+
};
|
|
1301
|
+
/** Refocus = first observable moment after a conpty-side mode reset. */
|
|
1302
|
+
handleTerminalFocusProbe = (focused) => {
|
|
1303
|
+
if (focused)
|
|
1304
|
+
this.probeAltScreenHealth();
|
|
1305
|
+
};
|
|
1139
1306
|
/**
|
|
1140
1307
|
* Re-enter alt-screen, clear, home, re-enable mouse tracking, and reset
|
|
1141
1308
|
* frame buffers so the next render repaints from scratch. Self-heal for
|
|
@@ -1429,19 +1596,49 @@ export default class Ink {
|
|
|
1429
1596
|
* Returns true if a DOM handler consumed the click. Gated on
|
|
1430
1597
|
* altScreenActive — clicks only make sense with a fixed viewport where
|
|
1431
1598
|
* nodeCache rects map 1:1 to terminal cells (no scrollback offset).
|
|
1599
|
+
* The button byte is the raw SGR release code; its modifier bits land
|
|
1600
|
+
* on ClickEvent.shift/alt/ctrl.
|
|
1432
1601
|
*/
|
|
1433
|
-
dispatchClick(col, row) {
|
|
1434
|
-
|
|
1602
|
+
dispatchClick(col, row, button = 0) {
|
|
1603
|
+
// Interaction-level health probe: the first click after a conpty-side
|
|
1604
|
+
// mode drop triggers the heal (probe is throttled + no-op on healthy
|
|
1605
|
+
// screens). Runs before the gate so a dropped 1049 is re-entered
|
|
1606
|
+
// within a round trip instead of the click silently missing.
|
|
1607
|
+
this.probeAltScreenHealth();
|
|
1608
|
+
if (!this.altScreenActive) {
|
|
1609
|
+
logMouseDebug('dispatchClick skipped — alt screen inactive', { col, row });
|
|
1435
1610
|
return false;
|
|
1611
|
+
}
|
|
1436
1612
|
const blank = isEmptyCellAt(this.frontFrame.screen, col, row);
|
|
1437
|
-
|
|
1613
|
+
const handled = dispatchClick(this.rootNode, col, row, blank, button);
|
|
1614
|
+
logMouseDebug('dispatchClick', { col, row, handled });
|
|
1615
|
+
return handled;
|
|
1616
|
+
}
|
|
1617
|
+
/**
|
|
1618
|
+
* Route a wheel event to the ScrollBox (any onWheel handler) under the
|
|
1619
|
+
* pointer. Returns true when a handler consumed it, so App can skip the
|
|
1620
|
+
* legacy global wheel-key path and exactly one layer scrolls. Gated on
|
|
1621
|
+
* altScreenActive like dispatchClick — without mouse tracking there are
|
|
1622
|
+
* no wheel coordinates to route by.
|
|
1623
|
+
*/
|
|
1624
|
+
dispatchWheelAt(col, row, deltaY, deltaX = 0, button = 0) {
|
|
1625
|
+
this.probeAltScreenHealth();
|
|
1626
|
+
if (!this.altScreenActive)
|
|
1627
|
+
return false;
|
|
1628
|
+
const handled = dispatchWheel(this.rootNode, col, row, deltaY, deltaX, button);
|
|
1629
|
+
if (handled) {
|
|
1630
|
+
logMouseDebug('dispatchWheelAt consumed', { col, row, deltaY, deltaX });
|
|
1631
|
+
}
|
|
1632
|
+
return handled;
|
|
1438
1633
|
}
|
|
1439
1634
|
dispatchHover(col, row) {
|
|
1635
|
+
this.probeAltScreenHealth();
|
|
1440
1636
|
if (!this.altScreenActive)
|
|
1441
1637
|
return;
|
|
1442
1638
|
dispatchHover(this.rootNode, col, row, this.hoveredNodes);
|
|
1443
1639
|
}
|
|
1444
1640
|
dispatchKeyboardEvent(parsedKey) {
|
|
1641
|
+
this.probeAltScreenHealth();
|
|
1445
1642
|
const target = this.focusManager.activeElement ?? this.rootNode;
|
|
1446
1643
|
const event = new KeyboardEvent(parsedKey);
|
|
1447
1644
|
dispatcher.dispatchDiscrete(target, event);
|
|
@@ -1604,6 +1801,9 @@ export default class Ink {
|
|
|
1604
1801
|
// cascades through useContext → <AlternateScreen>'s useLayoutEffect dep
|
|
1605
1802
|
// array → spurious exit+re-enter of the alt screen on every SIGWINCH.
|
|
1606
1803
|
writeRaw(data) {
|
|
1804
|
+
if (data.includes('\x1b[?1049')) {
|
|
1805
|
+
logMouseDebug('stdout:1049', { len: data.length, head: data.slice(0, 60) });
|
|
1806
|
+
}
|
|
1607
1807
|
this.options.stdout.write(data);
|
|
1608
1808
|
}
|
|
1609
1809
|
setCursorDeclaration = (decl, clearIfNode) => {
|
|
@@ -1617,7 +1817,7 @@ export default class Ink {
|
|
|
1617
1817
|
}
|
|
1618
1818
|
render(node) {
|
|
1619
1819
|
this.currentNode = node;
|
|
1620
|
-
const tree = _jsx(App, { ref: this.setAppRef, stdin: this.options.stdin, stdout: this.options.stdout, stderr: this.options.stderr, exitOnCtrlC: this.options.exitOnCtrlC, onExit: this.unmount, terminalColumns: this.terminalColumns, terminalRows: this.terminalRows, selection: this.selection, onSelectionChange: this.notifySelectionChange, onClickAt: this.dispatchClick, onHoverAt: this.dispatchHover, getHyperlinkAt: this.getHyperlinkAt, onOpenHyperlink: this.openHyperlink, onMultiClick: this.handleMultiClick, onSelectionDrag: this.handleSelectionDrag, onStdinResume: this.reassertTerminalModes, onCursorDeclaration: this.setCursorDeclaration, dispatchKeyboardEvent: this.dispatchKeyboardEvent, children: _jsx(TerminalWriteProvider, { value: this.writeRaw, children: node }) });
|
|
1820
|
+
const tree = _jsx(App, { ref: this.setAppRef, stdin: this.options.stdin, stdout: this.options.stdout, stderr: this.options.stderr, exitOnCtrlC: this.options.exitOnCtrlC, onExit: this.unmount, terminalColumns: this.terminalColumns, terminalRows: this.terminalRows, selection: this.selection, onSelectionChange: this.notifySelectionChange, onClickAt: this.dispatchClick, onHoverAt: this.dispatchHover, onWheelAt: this.dispatchWheelAt, getHyperlinkAt: this.getHyperlinkAt, onOpenHyperlink: this.openHyperlink, onMultiClick: this.handleMultiClick, onSelectionDrag: this.handleSelectionDrag, onStdinResume: this.reassertTerminalModes, onTerminalFocus: this.handleTerminalFocusProbe, onCursorDeclaration: this.setCursorDeclaration, dispatchKeyboardEvent: this.dispatchKeyboardEvent, children: _jsx(TerminalWriteProvider, { value: this.writeRaw, children: node }) });
|
|
1621
1821
|
// @ts-ignore -- ported CC build; type drift tolerated updateContainerSync exists in react-reconciler but not in @types/react-reconciler
|
|
1622
1822
|
reconciler.updateContainerSync(tree, this.container, null, noop);
|
|
1623
1823
|
// @ts-ignore -- ported CC build; type drift tolerated flushSyncWork exists in react-reconciler but not in @types/react-reconciler
|
|
@@ -1791,9 +1991,25 @@ export default class Ink {
|
|
|
1791
1991
|
logForDebugging(`[stderr] ${text}`, {
|
|
1792
1992
|
level: 'warn'
|
|
1793
1993
|
});
|
|
1794
|
-
if (
|
|
1795
|
-
this.
|
|
1796
|
-
|
|
1994
|
+
if (!this.isUnmounted && !this.isPaused) {
|
|
1995
|
+
if (this.altScreenActive) {
|
|
1996
|
+
this.prevFrameContaminated = true;
|
|
1997
|
+
this.scheduleRender();
|
|
1998
|
+
}
|
|
1999
|
+
else {
|
|
2000
|
+
// Main-screen (inline): the diff engine's moves are purely
|
|
2001
|
+
// relative to the physical cursor, so ANY unobserved tty write
|
|
2002
|
+
// (one that slipped through before this patch existed, or via
|
|
2003
|
+
// a path it can't intercept — a snapshotted ESM writer) shifts
|
|
2004
|
+
// every later write by N rows with nothing to detect it after
|
|
2005
|
+
// the fact. The stdin-gap reassert covers slow leaks; this
|
|
2006
|
+
// per-write defensive net closes the fast ones: blind
|
|
2007
|
+
// idempotent viewport repaint from the physical cursor. When
|
|
2008
|
+
// nothing drifted (the usual case — the write was swallowed)
|
|
2009
|
+
// it paints the same pixels again at O(viewport) bytes.
|
|
2010
|
+
this.log.requestViewportReanchor();
|
|
2011
|
+
this.scheduleRender();
|
|
2012
|
+
}
|
|
1797
2013
|
}
|
|
1798
2014
|
}
|
|
1799
2015
|
finally {
|
|
@@ -12,6 +12,11 @@ export type CachedLayout = {
|
|
|
12
12
|
width: number;
|
|
13
13
|
height: number;
|
|
14
14
|
top?: number;
|
|
15
|
+
/** Effective background color (own ?? inherited) at the last render.
|
|
16
|
+
* Renderer compares it per frame so a background change refuses the
|
|
17
|
+
* prevScreen blit — children would otherwise resurrect the stale color
|
|
18
|
+
* (stuck hover highlight). */
|
|
19
|
+
bg?: string;
|
|
15
20
|
};
|
|
16
21
|
/** Layout bounds cached per rendered node, used for blitting and clearing. */
|
|
17
22
|
export declare const nodeCache: WeakMap<DOMElement, CachedLayout>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-cache.d.ts","sourceRoot":"","sources":["../../../src/ink/node-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAC1C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAErD;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"node-cache.d.ts","sourceRoot":"","sources":["../../../src/ink/node-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAC1C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAErD;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ;;;mCAG+B;IAC/B,EAAE,CAAC,EAAE,MAAM,CAAA;CACZ,CAAA;AAED,8EAA8E;AAC9E,eAAO,MAAM,SAAS,mCAA0C,CAAA;AAEhE,kEAAkE;AAClE,eAAO,MAAM,aAAa,kCAAyC,CAAA;AAanE;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,SAAS,EACf,UAAU,EAAE,OAAO,GAClB,IAAI,CAUN;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,IAAI,OAAO,CAIpD"}
|
|
@@ -131,6 +131,14 @@ export type ParsedKey = {
|
|
|
131
131
|
raw: string | undefined;
|
|
132
132
|
code?: string;
|
|
133
133
|
isPasted: boolean;
|
|
134
|
+
/**
|
|
135
|
+
* Pointer column (0-indexed) for wheel keys — SGR/X10 wheel sequences
|
|
136
|
+
* carry the position the wheel event occurred at. Undefined for
|
|
137
|
+
* non-wheel keys.
|
|
138
|
+
*/
|
|
139
|
+
mouseCol?: number;
|
|
140
|
+
/** Pointer row (0-indexed) for wheel keys. See mouseCol. */
|
|
141
|
+
mouseRow?: number;
|
|
134
142
|
};
|
|
135
143
|
/** A terminal response sequence (DECRPM, DA1, OSC reply, etc.) parsed
|
|
136
144
|
* out of the input stream. Not user input — consumers should dispatch
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse-keypress.d.ts","sourceRoot":"","sources":["../../../src/ink/parse-keypress.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAE/B,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAoJtE,gDAAgD;AAChD,eAAO,MAAM,aAAa;;;;;;CAMhB,CAAA;AAEV;;;GAGG;AACH,MAAM,MAAM,gBAAgB;AAC1B,iEAAiE;AAC/D;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AAClD,oEAAoE;GAClE;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE;AACnC,+DAA+D;GAC7D;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE;AACnC,iEAAiE;GAC/D;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AAC1C,sDAAsD;GACpD;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE;AACtD,kFAAkF;GAChF;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AAC7C;wEACwE;GACtE;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAyTvC,iEAAiE;AACjE,MAAM,MAAM,eAAe,GAAG;IAC5B,wDAAwD;IACxD,MAAM,EAAE,OAAO,CAAA;IACf,+DAA+D;IAC/D,OAAO,EAAE,MAAM,CAAA;IACf,yDAAyD;IACzD,IAAI,EAAE,SAAS,EAAE,CAAA;IACjB,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAsFD;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAA;IAC3B,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B;;;;;OAKG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,eAAe,CAAA;IAE5B,UAAU,CAAC,EAAE,SAAS,CAAA;CACvB,CAAA;AAED,kDAAkD;AAClD,eAAO,MAAM,aAAa,EAAE,aAI3B,CAAA;AAmBD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,aAAa,EACxB,KAAK,GAAE,MAAM,GAAG,MAAM,GAAG,IAAS,GACjC,CAAC,WAAW,EAAE,EAAE,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"parse-keypress.d.ts","sourceRoot":"","sources":["../../../src/ink/parse-keypress.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAE/B,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAoJtE,gDAAgD;AAChD,eAAO,MAAM,aAAa;;;;;;CAMhB,CAAA;AAEV;;;GAGG;AACH,MAAM,MAAM,gBAAgB;AAC1B,iEAAiE;AAC/D;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AAClD,oEAAoE;GAClE;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE;AACnC,+DAA+D;GAC7D;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE;AACnC,iEAAiE;GAC/D;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AAC1C,sDAAsD;GACpD;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE;AACtD,kFAAkF;GAChF;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AAC7C;wEACwE;GACtE;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAyTvC,iEAAiE;AACjE,MAAM,MAAM,eAAe,GAAG;IAC5B,wDAAwD;IACxD,MAAM,EAAE,OAAO,CAAA;IACf,+DAA+D;IAC/D,OAAO,EAAE,MAAM,CAAA;IACf,yDAAyD;IACzD,IAAI,EAAE,SAAS,EAAE,CAAA;IACjB,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAsFD;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAA;IAC3B,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B;;;;;OAKG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,eAAe,CAAA;IAE5B,UAAU,CAAC,EAAE,SAAS,CAAA;CACvB,CAAA;AAED,kDAAkD;AAClD,eAAO,MAAM,aAAa,EAAE,aAI3B,CAAA;AAmBD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,aAAa,EACxB,KAAK,GAAE,MAAM,GAAG,MAAM,GAAG,IAAS,GACjC,CAAC,WAAW,EAAE,EAAE,aAAa,CAAC,CAuKhC;AA0GD;;;GAGG;AACH,eAAO,MAAM,mBAAmB,UAe/B,CAAA;AA0HD;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,KAAK,CAAA;IACX,EAAE,EAAE,OAAO,CAAA;IACX,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;IACxB,IAAI,EAAE,OAAO,CAAA;IACb,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,EAAE,OAAO,CAAA;IACd,MAAM,EAAE,OAAO,CAAA;IACf,KAAK,EAAE,OAAO,CAAA;IACd,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;IAC5B,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;IACvB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,OAAO,CAAA;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA;AAED;;6BAE6B;AAC7B,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,UAAU,CAAA;IAChB,uDAAuD;IACvD,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,gBAAgB,CAAA;CAC3B,CAAA;AAED;;0DAE0D;AAC1D,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,OAAO,CAAA;IACb;4DACwD;IACxD,MAAM,EAAE,MAAM,CAAA;IACd,2DAA2D;IAC3D,MAAM,EAAE,OAAO,GAAG,SAAS,CAAA;IAC3B,uCAAuC;IACvC,GAAG,EAAE,MAAM,CAAA;IACX,oCAAoC;IACpC,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED;2EAC2E;AAC3E,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,WAAW,GAAG,cAAc,CAAA"}
|