@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.
Files changed (229) hide show
  1. package/README.md +29 -10
  2. package/bin/dsh-tui.js +1 -1
  3. package/lib/types/commands.d.ts.map +1 -1
  4. package/lib/types/commands.js +4 -2
  5. package/lib/types/components/ActivityPicker.d.ts +4 -1
  6. package/lib/types/components/ActivityPicker.d.ts.map +1 -1
  7. package/lib/types/components/ActivityPicker.js +2 -2
  8. package/lib/types/components/BtwPanel.d.ts.map +1 -1
  9. package/lib/types/components/BtwPanel.js +1 -1
  10. package/lib/types/components/Chat/SubagentMessage.d.ts +2 -1
  11. package/lib/types/components/Chat/SubagentMessage.d.ts.map +1 -1
  12. package/lib/types/components/Chat/SubagentMessage.js +11 -3
  13. package/lib/types/components/CommandSuggestions.d.ts +21 -4
  14. package/lib/types/components/CommandSuggestions.d.ts.map +1 -1
  15. package/lib/types/components/CommandSuggestions.js +41 -14
  16. package/lib/types/components/EffortSlider.d.ts +4 -1
  17. package/lib/types/components/EffortSlider.d.ts.map +1 -1
  18. package/lib/types/components/EffortSlider.js +4 -2
  19. package/lib/types/components/ExtensionDialog.js +6 -2
  20. package/lib/types/components/FileSuggestions.d.ts +26 -5
  21. package/lib/types/components/FileSuggestions.d.ts.map +1 -1
  22. package/lib/types/components/FileSuggestions.js +53 -16
  23. package/lib/types/components/GoalTodoPanel.d.ts +26 -2
  24. package/lib/types/components/GoalTodoPanel.d.ts.map +1 -1
  25. package/lib/types/components/GoalTodoPanel.js +86 -17
  26. package/lib/types/components/HelpMenu.d.ts +7 -1
  27. package/lib/types/components/HelpMenu.d.ts.map +1 -1
  28. package/lib/types/components/HelpMenu.js +12 -4
  29. package/lib/types/components/HistorySearchDialog.d.ts +4 -1
  30. package/lib/types/components/HistorySearchDialog.d.ts.map +1 -1
  31. package/lib/types/components/HistorySearchDialog.js +2 -2
  32. package/lib/types/components/LangPicker.d.ts +14 -0
  33. package/lib/types/components/LangPicker.d.ts.map +1 -0
  34. package/lib/types/components/LangPicker.js +17 -0
  35. package/lib/types/components/LoadedContextPanel.d.ts.map +1 -1
  36. package/lib/types/components/LoadedContextPanel.js +3 -1
  37. package/lib/types/components/LogoV2.d.ts +5 -1
  38. package/lib/types/components/LogoV2.d.ts.map +1 -1
  39. package/lib/types/components/LogoV2.js +12 -3
  40. package/lib/types/components/Markdown.d.ts.map +1 -1
  41. package/lib/types/components/Markdown.js +12 -3
  42. package/lib/types/components/MessageList.d.ts +43 -2
  43. package/lib/types/components/MessageList.d.ts.map +1 -1
  44. package/lib/types/components/MessageList.js +553 -41
  45. package/lib/types/components/ModelPicker.d.ts +4 -1
  46. package/lib/types/components/ModelPicker.d.ts.map +1 -1
  47. package/lib/types/components/ModelPicker.js +2 -2
  48. package/lib/types/components/PermissionsPicker.d.ts +21 -0
  49. package/lib/types/components/PermissionsPicker.d.ts.map +1 -0
  50. package/lib/types/components/PermissionsPicker.js +27 -0
  51. package/lib/types/components/PlanPicker.d.ts +15 -0
  52. package/lib/types/components/PlanPicker.d.ts.map +1 -0
  53. package/lib/types/components/PlanPicker.js +18 -0
  54. package/lib/types/components/PresetPicker.d.ts +4 -1
  55. package/lib/types/components/PresetPicker.d.ts.map +1 -1
  56. package/lib/types/components/PresetPicker.js +2 -2
  57. package/lib/types/components/PromptInput.d.ts +4 -3
  58. package/lib/types/components/PromptInput.d.ts.map +1 -1
  59. package/lib/types/components/PromptInput.js +273 -50
  60. package/lib/types/components/RewindPicker.d.ts +12 -1
  61. package/lib/types/components/RewindPicker.d.ts.map +1 -1
  62. package/lib/types/components/RewindPicker.js +6 -4
  63. package/lib/types/components/ScrollbarGutter.d.ts +28 -0
  64. package/lib/types/components/ScrollbarGutter.d.ts.map +1 -0
  65. package/lib/types/components/ScrollbarGutter.js +68 -0
  66. package/lib/types/components/Select.d.ts +8 -1
  67. package/lib/types/components/Select.d.ts.map +1 -1
  68. package/lib/types/components/Select.js +4 -2
  69. package/lib/types/components/SkillsPicker.d.ts +4 -1
  70. package/lib/types/components/SkillsPicker.d.ts.map +1 -1
  71. package/lib/types/components/SkillsPicker.js +2 -2
  72. package/lib/types/components/SubagentCard.d.ts +2 -1
  73. package/lib/types/components/SubagentCard.d.ts.map +1 -1
  74. package/lib/types/components/SubagentCard.js +6 -1
  75. package/lib/types/components/SubagentDashboard.d.ts +4 -0
  76. package/lib/types/components/SubagentDashboard.d.ts.map +1 -1
  77. package/lib/types/components/SubagentDashboard.js +9 -1
  78. package/lib/types/components/SubagentDetailScene.d.ts.map +1 -1
  79. package/lib/types/components/SubagentDetailScene.js +5 -4
  80. package/lib/types/components/SuggestionCard.d.ts +63 -0
  81. package/lib/types/components/SuggestionCard.d.ts.map +1 -0
  82. package/lib/types/components/SuggestionCard.js +77 -0
  83. package/lib/types/components/ThemePicker.d.ts +4 -1
  84. package/lib/types/components/ThemePicker.d.ts.map +1 -1
  85. package/lib/types/components/ThemePicker.js +2 -2
  86. package/lib/types/components/ThinkingToggle.d.ts +4 -1
  87. package/lib/types/components/ThinkingToggle.d.ts.map +1 -1
  88. package/lib/types/components/ThinkingToggle.js +2 -2
  89. package/lib/types/components/TimelineRail.d.ts +53 -0
  90. package/lib/types/components/TimelineRail.d.ts.map +1 -0
  91. package/lib/types/components/TimelineRail.js +190 -0
  92. package/lib/types/components/WorkspaceFlowPicker.d.ts +6 -1
  93. package/lib/types/components/WorkspaceFlowPicker.d.ts.map +1 -1
  94. package/lib/types/components/WorkspaceFlowPicker.js +4 -2
  95. package/lib/types/components/WorkspaceMenuPicker.d.ts +19 -0
  96. package/lib/types/components/WorkspaceMenuPicker.d.ts.map +1 -0
  97. package/lib/types/components/WorkspaceMenuPicker.js +19 -0
  98. package/lib/types/components/WorkspacePicker.d.ts +4 -1
  99. package/lib/types/components/WorkspacePicker.d.ts.map +1 -1
  100. package/lib/types/components/WorkspacePicker.js +2 -2
  101. package/lib/types/components/approvals/ApprovalPanel.d.ts.map +1 -1
  102. package/lib/types/components/approvals/ApprovalPanel.js +5 -1
  103. package/lib/types/components/design-system/ListItem.d.ts +8 -1
  104. package/lib/types/components/design-system/ListItem.d.ts.map +1 -1
  105. package/lib/types/components/design-system/ListItem.js +8 -3
  106. package/lib/types/components/messages/AssistantTextMessage.d.ts +5 -2
  107. package/lib/types/components/messages/AssistantTextMessage.d.ts.map +1 -1
  108. package/lib/types/components/messages/AssistantTextMessage.js +6 -2
  109. package/lib/types/components/messages/AssistantThinkingMessage.d.ts +2 -1
  110. package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
  111. package/lib/types/components/messages/AssistantThinkingMessage.js +10 -4
  112. package/lib/types/components/messages/AssistantToolUseMessage.d.ts +8 -1
  113. package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
  114. package/lib/types/components/messages/AssistantToolUseMessage.js +4 -2
  115. package/lib/types/components/messages/UserPromptMessage.d.ts +2 -1
  116. package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
  117. package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
  118. package/lib/types/components/questions/AskUserQuestionPanel.js +34 -2
  119. package/lib/types/components/questions/PlanReviewPanel.d.ts.map +1 -1
  120. package/lib/types/components/questions/PlanReviewPanel.js +13 -2
  121. package/lib/types/components/sessions/SessionListRow.d.ts +4 -1
  122. package/lib/types/components/sessions/SessionListRow.d.ts.map +1 -1
  123. package/lib/types/components/sessions/SessionListRow.js +4 -2
  124. package/lib/types/components/trajectory/HotspotView.d.ts +7 -1
  125. package/lib/types/components/trajectory/HotspotView.d.ts.map +1 -1
  126. package/lib/types/components/trajectory/HotspotView.js +8 -2
  127. package/lib/types/components/trajectory/Ledger.d.ts +9 -1
  128. package/lib/types/components/trajectory/Ledger.d.ts.map +1 -1
  129. package/lib/types/components/trajectory/Ledger.js +23 -5
  130. package/lib/types/components/trajectory/WaveBand.d.ts +10 -1
  131. package/lib/types/components/trajectory/WaveBand.d.ts.map +1 -1
  132. package/lib/types/components/trajectory/WaveBand.js +7 -2
  133. package/lib/types/dsh-adapter/channel.d.ts +27 -6
  134. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  135. package/lib/types/dsh-adapter/channel.js +432 -66
  136. package/lib/types/dsh-adapter/contract.d.ts +58 -18
  137. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  138. package/lib/types/dsh-adapter/contract.js +124 -36
  139. package/lib/types/dsh-adapter/index.d.ts +10 -2
  140. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  141. package/lib/types/dsh-adapter/index.js +10 -13
  142. package/lib/types/dsh-adapter/plugin.d.ts +0 -9
  143. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  144. package/lib/types/dsh-adapter/plugin.js +136 -6
  145. package/lib/types/i18n.d.ts +194 -16
  146. package/lib/types/i18n.d.ts.map +1 -1
  147. package/lib/types/i18n.js +73 -10
  148. package/lib/types/ink/components/AlternateScreen.d.ts.map +1 -1
  149. package/lib/types/ink/components/AlternateScreen.js +13 -1
  150. package/lib/types/ink/components/App.d.ts +12 -1
  151. package/lib/types/ink/components/App.d.ts.map +1 -1
  152. package/lib/types/ink/components/App.js +101 -4
  153. package/lib/types/ink/components/ClockContext.d.ts.map +1 -1
  154. package/lib/types/ink/components/ClockContext.js +2 -0
  155. package/lib/types/ink/components/ScrollBox.d.ts.map +1 -1
  156. package/lib/types/ink/components/ScrollBox.js +170 -91
  157. package/lib/types/ink/constants.d.ts +9 -0
  158. package/lib/types/ink/constants.d.ts.map +1 -1
  159. package/lib/types/ink/constants.js +9 -0
  160. package/lib/types/ink/events/click-event.d.ts +7 -16
  161. package/lib/types/ink/events/click-event.d.ts.map +1 -1
  162. package/lib/types/ink/events/click-event.js +16 -19
  163. package/lib/types/ink/events/event-handlers.d.ts +11 -1
  164. package/lib/types/ink/events/event-handlers.d.ts.map +1 -1
  165. package/lib/types/ink/events/event-handlers.js +2 -0
  166. package/lib/types/ink/events/input-event.d.ts +9 -0
  167. package/lib/types/ink/events/input-event.d.ts.map +1 -1
  168. package/lib/types/ink/events/input-event.js +4 -0
  169. package/lib/types/ink/events/pointer-event.d.ts +61 -0
  170. package/lib/types/ink/events/pointer-event.d.ts.map +1 -0
  171. package/lib/types/ink/events/pointer-event.js +61 -0
  172. package/lib/types/ink/events/wheel-event.d.ts +17 -0
  173. package/lib/types/ink/events/wheel-event.d.ts.map +1 -0
  174. package/lib/types/ink/events/wheel-event.js +20 -0
  175. package/lib/types/ink/geometry-trace.d.ts +51 -0
  176. package/lib/types/ink/geometry-trace.d.ts.map +1 -0
  177. package/lib/types/ink/geometry-trace.js +83 -0
  178. package/lib/types/ink/hit-test.d.ts +56 -1
  179. package/lib/types/ink/hit-test.d.ts.map +1 -1
  180. package/lib/types/ink/hit-test.js +146 -8
  181. package/lib/types/ink/ink.d.ts +58 -1
  182. package/lib/types/ink/ink.d.ts.map +1 -1
  183. package/lib/types/ink/ink.js +268 -52
  184. package/lib/types/ink/node-cache.d.ts +5 -0
  185. package/lib/types/ink/node-cache.d.ts.map +1 -1
  186. package/lib/types/ink/parse-keypress.d.ts +8 -0
  187. package/lib/types/ink/parse-keypress.d.ts.map +1 -1
  188. package/lib/types/ink/parse-keypress.js +101 -12
  189. package/lib/types/ink/render-node-to-output.d.ts +16 -3
  190. package/lib/types/ink/render-node-to-output.d.ts.map +1 -1
  191. package/lib/types/ink/render-node-to-output.js +144 -25
  192. package/lib/types/ink/selection.d.ts +29 -4
  193. package/lib/types/ink/selection.d.ts.map +1 -1
  194. package/lib/types/ink/selection.js +41 -5
  195. package/lib/types/ink/timeline-rail.d.ts +138 -0
  196. package/lib/types/ink/timeline-rail.d.ts.map +1 -0
  197. package/lib/types/ink/timeline-rail.js +179 -0
  198. package/lib/types/ink/wrap-text.d.ts.map +1 -1
  199. package/lib/types/ink/wrap-text.js +47 -5
  200. package/lib/types/screens/Chat.d.ts.map +1 -1
  201. package/lib/types/screens/Chat.js +819 -337
  202. package/lib/types/screens/SessionBrowser.d.ts.map +1 -1
  203. package/lib/types/screens/SessionBrowser.js +56 -29
  204. package/lib/types/screens/Settings.d.ts.map +1 -1
  205. package/lib/types/screens/Settings.js +64 -22
  206. package/lib/types/screens/StatusLine.d.ts +2 -1
  207. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  208. package/lib/types/screens/StatusLine.js +17 -1
  209. package/lib/types/screens/TrajectoryScene.d.ts.map +1 -1
  210. package/lib/types/screens/TrajectoryScene.js +81 -14
  211. package/lib/types/screens/chatOverlay.d.ts +221 -0
  212. package/lib/types/screens/chatOverlay.d.ts.map +1 -0
  213. package/lib/types/screens/chatOverlay.js +114 -0
  214. package/lib/types/tips.d.ts.map +1 -1
  215. package/lib/types/tips.js +64 -10
  216. package/lib/types/tuiDisplayPrefs.d.ts +15 -0
  217. package/lib/types/tuiDisplayPrefs.d.ts.map +1 -1
  218. package/lib/types/tuiDisplayPrefs.js +9 -0
  219. package/lib/types/utils/debug.d.ts +10 -0
  220. package/lib/types/utils/debug.d.ts.map +1 -1
  221. package/lib/types/utils/debug.js +24 -0
  222. package/lib/types/utils/fileSuggestions.d.ts +19 -0
  223. package/lib/types/utils/fileSuggestions.d.ts.map +1 -0
  224. package/lib/types/utils/fileSuggestions.js +40 -0
  225. package/lib/types/utils/mentions.d.ts.map +1 -1
  226. package/lib/types/utils/mentions.js +3 -1
  227. package/lib/types/utils/paths.d.ts.map +1 -1
  228. package/lib/types/utils/paths.js +1 -0
  229. package/package.json +69 -66
@@ -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
- if (this.altScreenMouseTracking) {
337
- this.options.stdout.write(ENABLE_MOUSE_TRACKING);
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.options.stdout.columns || 80;
499
- const terminalRows = this.options.stdout.rows || 24;
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 scrolled the ScrollBox this frame. Translate the
511
- // selection by the same delta so the highlight stays anchored to the
512
- // TEXT (native terminal behavior — the selection walks up the screen
513
- // as content scrolls, eventually clipping at the top). frontFrame
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
- if (follow && this.selection.anchor &&
523
- // Only translate if the selection is ON scrollbox content. Selections
524
- // in the footer/prompt/StickyPromptHeader are on static text — the
525
- // scroll doesn't move what's under them. Without this guard, a
526
- // footer selection would be shifted by -delta then clamped to
527
- // viewportBottom, teleporting it into the scrollbox. Mirror the
528
- // bounds check the deleted check() in ScrollKeybindingHandler had.
529
- this.selection.anchor.row >= follow.viewportTop && this.selection.anchor.row <= follow.viewportBottom) {
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, viewportTop, viewportTop + delta - 1, 'above');
638
+ captureScrolledRows(this.selection, this.frontFrame.screen, firstRow, lastRow, side);
541
639
  }
542
- shiftAnchor(this.selection, -delta, viewportTop, viewportBottom);
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, viewportTop, viewportTop + delta - 1, 'above');
657
+ captureScrolledRows(this.selection, this.frontFrame.screen, firstRow, lastRow, side);
560
658
  }
561
- const cleared = shiftSelectionForFollow(this.selection, -delta, viewportTop, viewportBottom);
562
- // Auto-clear (both ends overshot minRow) must notify React-land
563
- // so useHasSelection re-renders and the footer copy/escape hint
564
- // disappears. notifySelectionChange() would recurse into onRender;
565
- // fire the listeners directly — they schedule a React update for
566
- // LATER, they don't re-enter this frame.
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. MUST NOT call this.scheduleRender() here: we're inside a
813
- // trailing-edge throttle invocation, timerId is undefined, and lodash's
814
- // debounce sees timeSinceLastCall >= wait (last call was at the start
815
- // of this window) → leadingEdge fires IMMEDIATELY → double render ~0.1ms
816
- // apart → jank. Use a plain timeout. If a wheel event or immediate
817
- // render arrives first, renderNow cancels this timer — no double.
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
- this.drainTimer = setTimeout(this.renderNow, FRAME_INTERVAL_MS >> 2);
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 — idempotent, safe to re-assert on every stdin gap.
1042
- if (this.altScreenMouseTracking) {
1043
- this.options.stdout.write(ENABLE_MOUSE_TRACKING);
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
- if (!this.altScreenActive)
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
- return dispatchClick(this.rootNode, col, row, blank);
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 (this.altScreenActive && !this.isUnmounted && !this.isPaused) {
1795
- this.prevFrameContaminated = true;
1796
- this.scheduleRender();
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;CACb,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"}
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":"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"}
1
+ {"version":3,"file":"parse-keypress.d.ts","sourceRoot":"","sources":["../../../src/ink/parse-keypress.ts"],"names":[],"mappings":"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"}