@deepseek-harness-tui/dsh-tui 0.8.8 → 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 (215) hide show
  1. package/README.md +27 -8
  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 +24 -7
  21. package/lib/types/components/FileSuggestions.d.ts.map +1 -1
  22. package/lib/types/components/FileSuggestions.js +39 -12
  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/Markdown.d.ts.map +1 -1
  38. package/lib/types/components/Markdown.js +12 -3
  39. package/lib/types/components/MessageList.d.ts +43 -2
  40. package/lib/types/components/MessageList.d.ts.map +1 -1
  41. package/lib/types/components/MessageList.js +553 -41
  42. package/lib/types/components/ModelPicker.d.ts +4 -1
  43. package/lib/types/components/ModelPicker.d.ts.map +1 -1
  44. package/lib/types/components/ModelPicker.js +2 -2
  45. package/lib/types/components/PermissionsPicker.d.ts +21 -0
  46. package/lib/types/components/PermissionsPicker.d.ts.map +1 -0
  47. package/lib/types/components/PermissionsPicker.js +27 -0
  48. package/lib/types/components/PlanPicker.d.ts +15 -0
  49. package/lib/types/components/PlanPicker.d.ts.map +1 -0
  50. package/lib/types/components/PlanPicker.js +18 -0
  51. package/lib/types/components/PresetPicker.d.ts +4 -1
  52. package/lib/types/components/PresetPicker.d.ts.map +1 -1
  53. package/lib/types/components/PresetPicker.js +2 -2
  54. package/lib/types/components/PromptInput.d.ts +4 -3
  55. package/lib/types/components/PromptInput.d.ts.map +1 -1
  56. package/lib/types/components/PromptInput.js +248 -29
  57. package/lib/types/components/RewindPicker.d.ts +12 -1
  58. package/lib/types/components/RewindPicker.d.ts.map +1 -1
  59. package/lib/types/components/RewindPicker.js +6 -4
  60. package/lib/types/components/ScrollbarGutter.d.ts +28 -0
  61. package/lib/types/components/ScrollbarGutter.d.ts.map +1 -0
  62. package/lib/types/components/ScrollbarGutter.js +68 -0
  63. package/lib/types/components/Select.d.ts +8 -1
  64. package/lib/types/components/Select.d.ts.map +1 -1
  65. package/lib/types/components/Select.js +4 -2
  66. package/lib/types/components/SkillsPicker.d.ts +4 -1
  67. package/lib/types/components/SkillsPicker.d.ts.map +1 -1
  68. package/lib/types/components/SkillsPicker.js +2 -2
  69. package/lib/types/components/SubagentCard.d.ts +2 -1
  70. package/lib/types/components/SubagentCard.d.ts.map +1 -1
  71. package/lib/types/components/SubagentCard.js +6 -1
  72. package/lib/types/components/SubagentDashboard.d.ts +4 -0
  73. package/lib/types/components/SubagentDashboard.d.ts.map +1 -1
  74. package/lib/types/components/SubagentDashboard.js +9 -1
  75. package/lib/types/components/SubagentDetailScene.d.ts.map +1 -1
  76. package/lib/types/components/SubagentDetailScene.js +5 -4
  77. package/lib/types/components/SuggestionCard.d.ts +63 -0
  78. package/lib/types/components/SuggestionCard.d.ts.map +1 -0
  79. package/lib/types/components/SuggestionCard.js +77 -0
  80. package/lib/types/components/ThemePicker.d.ts +4 -1
  81. package/lib/types/components/ThemePicker.d.ts.map +1 -1
  82. package/lib/types/components/ThemePicker.js +2 -2
  83. package/lib/types/components/ThinkingToggle.d.ts +4 -1
  84. package/lib/types/components/ThinkingToggle.d.ts.map +1 -1
  85. package/lib/types/components/ThinkingToggle.js +2 -2
  86. package/lib/types/components/TimelineRail.d.ts +53 -0
  87. package/lib/types/components/TimelineRail.d.ts.map +1 -0
  88. package/lib/types/components/TimelineRail.js +190 -0
  89. package/lib/types/components/WorkspaceFlowPicker.d.ts +6 -1
  90. package/lib/types/components/WorkspaceFlowPicker.d.ts.map +1 -1
  91. package/lib/types/components/WorkspaceFlowPicker.js +4 -2
  92. package/lib/types/components/WorkspaceMenuPicker.d.ts +19 -0
  93. package/lib/types/components/WorkspaceMenuPicker.d.ts.map +1 -0
  94. package/lib/types/components/WorkspaceMenuPicker.js +19 -0
  95. package/lib/types/components/WorkspacePicker.d.ts +4 -1
  96. package/lib/types/components/WorkspacePicker.d.ts.map +1 -1
  97. package/lib/types/components/WorkspacePicker.js +2 -2
  98. package/lib/types/components/approvals/ApprovalPanel.d.ts.map +1 -1
  99. package/lib/types/components/approvals/ApprovalPanel.js +5 -1
  100. package/lib/types/components/design-system/ListItem.d.ts +8 -1
  101. package/lib/types/components/design-system/ListItem.d.ts.map +1 -1
  102. package/lib/types/components/design-system/ListItem.js +8 -3
  103. package/lib/types/components/messages/AssistantTextMessage.d.ts +5 -2
  104. package/lib/types/components/messages/AssistantTextMessage.d.ts.map +1 -1
  105. package/lib/types/components/messages/AssistantTextMessage.js +6 -2
  106. package/lib/types/components/messages/AssistantThinkingMessage.d.ts +2 -1
  107. package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
  108. package/lib/types/components/messages/AssistantThinkingMessage.js +10 -4
  109. package/lib/types/components/messages/AssistantToolUseMessage.d.ts +8 -1
  110. package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
  111. package/lib/types/components/messages/AssistantToolUseMessage.js +4 -2
  112. package/lib/types/components/messages/UserPromptMessage.d.ts +2 -1
  113. package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
  114. package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
  115. package/lib/types/components/questions/AskUserQuestionPanel.js +34 -2
  116. package/lib/types/components/questions/PlanReviewPanel.d.ts.map +1 -1
  117. package/lib/types/components/questions/PlanReviewPanel.js +13 -2
  118. package/lib/types/components/sessions/SessionListRow.d.ts +4 -1
  119. package/lib/types/components/sessions/SessionListRow.d.ts.map +1 -1
  120. package/lib/types/components/sessions/SessionListRow.js +4 -2
  121. package/lib/types/components/trajectory/HotspotView.d.ts +7 -1
  122. package/lib/types/components/trajectory/HotspotView.d.ts.map +1 -1
  123. package/lib/types/components/trajectory/HotspotView.js +8 -2
  124. package/lib/types/components/trajectory/Ledger.d.ts +9 -1
  125. package/lib/types/components/trajectory/Ledger.d.ts.map +1 -1
  126. package/lib/types/components/trajectory/Ledger.js +23 -5
  127. package/lib/types/components/trajectory/WaveBand.d.ts +10 -1
  128. package/lib/types/components/trajectory/WaveBand.d.ts.map +1 -1
  129. package/lib/types/components/trajectory/WaveBand.js +7 -2
  130. package/lib/types/dsh-adapter/channel.d.ts +16 -5
  131. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  132. package/lib/types/dsh-adapter/channel.js +332 -29
  133. package/lib/types/dsh-adapter/index.d.ts +10 -2
  134. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  135. package/lib/types/dsh-adapter/index.js +4 -1
  136. package/lib/types/dsh-adapter/plugin.d.ts +0 -9
  137. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  138. package/lib/types/dsh-adapter/plugin.js +136 -6
  139. package/lib/types/i18n.d.ts +176 -15
  140. package/lib/types/i18n.d.ts.map +1 -1
  141. package/lib/types/i18n.js +51 -8
  142. package/lib/types/ink/components/AlternateScreen.d.ts.map +1 -1
  143. package/lib/types/ink/components/AlternateScreen.js +13 -1
  144. package/lib/types/ink/components/App.d.ts +12 -1
  145. package/lib/types/ink/components/App.d.ts.map +1 -1
  146. package/lib/types/ink/components/App.js +101 -4
  147. package/lib/types/ink/components/ClockContext.d.ts.map +1 -1
  148. package/lib/types/ink/components/ClockContext.js +2 -0
  149. package/lib/types/ink/components/ScrollBox.d.ts.map +1 -1
  150. package/lib/types/ink/components/ScrollBox.js +170 -91
  151. package/lib/types/ink/constants.d.ts +9 -0
  152. package/lib/types/ink/constants.d.ts.map +1 -1
  153. package/lib/types/ink/constants.js +9 -0
  154. package/lib/types/ink/events/click-event.d.ts +7 -16
  155. package/lib/types/ink/events/click-event.d.ts.map +1 -1
  156. package/lib/types/ink/events/click-event.js +16 -19
  157. package/lib/types/ink/events/event-handlers.d.ts +11 -1
  158. package/lib/types/ink/events/event-handlers.d.ts.map +1 -1
  159. package/lib/types/ink/events/event-handlers.js +2 -0
  160. package/lib/types/ink/events/input-event.d.ts +9 -0
  161. package/lib/types/ink/events/input-event.d.ts.map +1 -1
  162. package/lib/types/ink/events/input-event.js +4 -0
  163. package/lib/types/ink/events/pointer-event.d.ts +61 -0
  164. package/lib/types/ink/events/pointer-event.d.ts.map +1 -0
  165. package/lib/types/ink/events/pointer-event.js +61 -0
  166. package/lib/types/ink/events/wheel-event.d.ts +17 -0
  167. package/lib/types/ink/events/wheel-event.d.ts.map +1 -0
  168. package/lib/types/ink/events/wheel-event.js +20 -0
  169. package/lib/types/ink/geometry-trace.d.ts +51 -0
  170. package/lib/types/ink/geometry-trace.d.ts.map +1 -0
  171. package/lib/types/ink/geometry-trace.js +83 -0
  172. package/lib/types/ink/hit-test.d.ts +56 -1
  173. package/lib/types/ink/hit-test.d.ts.map +1 -1
  174. package/lib/types/ink/hit-test.js +146 -8
  175. package/lib/types/ink/ink.d.ts +58 -1
  176. package/lib/types/ink/ink.d.ts.map +1 -1
  177. package/lib/types/ink/ink.js +230 -29
  178. package/lib/types/ink/node-cache.d.ts +5 -0
  179. package/lib/types/ink/node-cache.d.ts.map +1 -1
  180. package/lib/types/ink/parse-keypress.d.ts +8 -0
  181. package/lib/types/ink/parse-keypress.d.ts.map +1 -1
  182. package/lib/types/ink/parse-keypress.js +101 -12
  183. package/lib/types/ink/render-node-to-output.d.ts +10 -0
  184. package/lib/types/ink/render-node-to-output.d.ts.map +1 -1
  185. package/lib/types/ink/render-node-to-output.js +111 -18
  186. package/lib/types/ink/timeline-rail.d.ts +138 -0
  187. package/lib/types/ink/timeline-rail.d.ts.map +1 -0
  188. package/lib/types/ink/timeline-rail.js +179 -0
  189. package/lib/types/ink/wrap-text.d.ts.map +1 -1
  190. package/lib/types/ink/wrap-text.js +47 -5
  191. package/lib/types/screens/Chat.d.ts.map +1 -1
  192. package/lib/types/screens/Chat.js +819 -337
  193. package/lib/types/screens/SessionBrowser.d.ts.map +1 -1
  194. package/lib/types/screens/SessionBrowser.js +56 -29
  195. package/lib/types/screens/Settings.d.ts.map +1 -1
  196. package/lib/types/screens/Settings.js +64 -22
  197. package/lib/types/screens/StatusLine.d.ts +2 -1
  198. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  199. package/lib/types/screens/StatusLine.js +17 -1
  200. package/lib/types/screens/TrajectoryScene.d.ts.map +1 -1
  201. package/lib/types/screens/TrajectoryScene.js +81 -14
  202. package/lib/types/screens/chatOverlay.d.ts +221 -0
  203. package/lib/types/screens/chatOverlay.d.ts.map +1 -0
  204. package/lib/types/screens/chatOverlay.js +114 -0
  205. package/lib/types/tips.d.ts.map +1 -1
  206. package/lib/types/tips.js +42 -6
  207. package/lib/types/tuiDisplayPrefs.d.ts +15 -0
  208. package/lib/types/tuiDisplayPrefs.d.ts.map +1 -1
  209. package/lib/types/tuiDisplayPrefs.js +9 -0
  210. package/lib/types/utils/debug.d.ts +10 -0
  211. package/lib/types/utils/debug.d.ts.map +1 -1
  212. package/lib/types/utils/debug.js +24 -0
  213. package/lib/types/utils/paths.d.ts.map +1 -1
  214. package/lib/types/utils/paths.js +1 -0
  215. package/package.json +4 -2
@@ -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';
@@ -35,6 +37,7 @@ import { isDecstbmSafe, SYNC_OUTPUT_SUPPORTED, supportsExtendedKeys, supportsWin
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,
@@ -824,18 +909,16 @@ export default class Ink {
824
909
  // are only ever true in alt-screen; in main-screen this is false→false.
825
910
  this.prevFrameContaminated = selActive || hlActive;
826
911
  // A ScrollBox has pendingScrollDelta left to drain — schedule the next
827
- // frame. MUST NOT call this.scheduleRender() here: we're inside a
828
- // trailing-edge throttle invocation, timerId is undefined, and lodash's
829
- // debounce sees timeSinceLastCall >= wait (last call was at the start
830
- // of this window) → leadingEdge fires IMMEDIATELY → double render ~0.1ms
831
- // apart → jank. Use a plain timeout. If a wheel event or immediate
832
- // render arrives first, renderNow cancels this timer — no double.
833
- //
834
- // Drain frames are cheap (DECSTBM + ~10 patches, ~200 bytes) so run at
835
- // quarter interval (~250fps, setTimeout practical floor) for max scroll
836
- // 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.
837
919
  if (frame.scrollDrainPending) {
838
- this.drainTimer = setTimeout(this.renderNow, FRAME_INTERVAL_MS >> 2);
920
+ noteFrameCause('scroll-drain');
921
+ this.scheduleDrain();
839
922
  }
840
923
  const yogaMs = getLastYogaMs();
841
924
  const commitMs = getLastCommitMs();
@@ -849,6 +932,7 @@ export default class Ink {
849
932
  cacheHits: 0,
850
933
  live: 0
851
934
  };
935
+ endGeometryFrame(performance.now() - renderStart);
852
936
  this.options.onFrame?.({
853
937
  durationMs: performance.now() - renderStart,
854
938
  phases: {
@@ -968,6 +1052,14 @@ export default class Ink {
968
1052
  return;
969
1053
  this.altScreenActive = active;
970
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();
971
1063
  if (active) {
972
1064
  this.mainScreenFrameState = {
973
1065
  frontFrame: this.frontFrame,
@@ -1053,10 +1145,10 @@ export default class Ink {
1053
1145
  this.renderNow();
1054
1146
  return;
1055
1147
  }
1056
- // Mouse tracking — idempotent, safe to re-assert on every stdin gap.
1057
- if (this.altScreenMouseTracking) {
1058
- this.options.stdout.write(ENABLE_MOUSE_TRACKING);
1059
- }
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();
1060
1152
  // Alt-screen re-entry — destructive (ERASE_SCREEN). Only for callers that
1061
1153
  // have a strong signal the terminal actually dropped mode 1049.
1062
1154
  if (includeAltScreen) {
@@ -1079,6 +1171,10 @@ export default class Ink {
1079
1171
  // Cancel any pending throttled render so it doesn't fire between
1080
1172
  // cleanupTerminalModes() and process.exit() and write to main screen.
1081
1173
  this.scheduleRender.cancel?.();
1174
+ if (this.drainTimer !== null) {
1175
+ clearTimeout(this.drainTimer);
1176
+ this.drainTimer = null;
1177
+ }
1082
1178
  this.app?.detachForShutdown();
1083
1179
  // Shutdown bypasses the normal unmount path, so release the process and
1084
1180
  // stdout listeners here as well. Otherwise a SIGCONT or resize arriving
@@ -1086,6 +1182,11 @@ export default class Ink {
1086
1182
  // through this detached instance after terminal cleanup has completed.
1087
1183
  this.unsubscribeTTYHandlers?.();
1088
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);
1089
1190
  // `detachForShutdown()` deliberately makes later `unmount()` calls a
1090
1191
  // no-op, so release process-level output patches here rather than relying
1091
1192
  // on unmount() to do it. The shutdown continuation may run an updater
@@ -1151,6 +1252,57 @@ export default class Ink {
1151
1252
  drainStdin() {
1152
1253
  drainStdin(this.options.stdin);
1153
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
+ };
1154
1306
  /**
1155
1307
  * Re-enter alt-screen, clear, home, re-enable mouse tracking, and reset
1156
1308
  * frame buffers so the next render repaints from scratch. Self-heal for
@@ -1444,19 +1596,49 @@ export default class Ink {
1444
1596
  * Returns true if a DOM handler consumed the click. Gated on
1445
1597
  * altScreenActive — clicks only make sense with a fixed viewport where
1446
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.
1447
1601
  */
1448
- dispatchClick(col, row) {
1449
- 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 });
1450
1610
  return false;
1611
+ }
1451
1612
  const blank = isEmptyCellAt(this.frontFrame.screen, col, row);
1452
- 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;
1453
1633
  }
1454
1634
  dispatchHover(col, row) {
1635
+ this.probeAltScreenHealth();
1455
1636
  if (!this.altScreenActive)
1456
1637
  return;
1457
1638
  dispatchHover(this.rootNode, col, row, this.hoveredNodes);
1458
1639
  }
1459
1640
  dispatchKeyboardEvent(parsedKey) {
1641
+ this.probeAltScreenHealth();
1460
1642
  const target = this.focusManager.activeElement ?? this.rootNode;
1461
1643
  const event = new KeyboardEvent(parsedKey);
1462
1644
  dispatcher.dispatchDiscrete(target, event);
@@ -1619,6 +1801,9 @@ export default class Ink {
1619
1801
  // cascades through useContext → <AlternateScreen>'s useLayoutEffect dep
1620
1802
  // array → spurious exit+re-enter of the alt screen on every SIGWINCH.
1621
1803
  writeRaw(data) {
1804
+ if (data.includes('\x1b[?1049')) {
1805
+ logMouseDebug('stdout:1049', { len: data.length, head: data.slice(0, 60) });
1806
+ }
1622
1807
  this.options.stdout.write(data);
1623
1808
  }
1624
1809
  setCursorDeclaration = (decl, clearIfNode) => {
@@ -1632,7 +1817,7 @@ export default class Ink {
1632
1817
  }
1633
1818
  render(node) {
1634
1819
  this.currentNode = node;
1635
- 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 }) });
1636
1821
  // @ts-ignore -- ported CC build; type drift tolerated updateContainerSync exists in react-reconciler but not in @types/react-reconciler
1637
1822
  reconciler.updateContainerSync(tree, this.container, null, noop);
1638
1823
  // @ts-ignore -- ported CC build; type drift tolerated flushSyncWork exists in react-reconciler but not in @types/react-reconciler
@@ -1806,9 +1991,25 @@ export default class Ink {
1806
1991
  logForDebugging(`[stderr] ${text}`, {
1807
1992
  level: 'warn'
1808
1993
  });
1809
- if (this.altScreenActive && !this.isUnmounted && !this.isPaused) {
1810
- this.prevFrameContaminated = true;
1811
- 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
+ }
1812
2013
  }
1813
2014
  }
1814
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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"}
@@ -610,7 +610,12 @@ export function parseMultipleKeypresses(prevState, input = '') {
610
610
  keys.push({ kind: 'response', sequence: token.value, response });
611
611
  }
612
612
  else {
613
- const mouse = parseMouseEvent(token.value);
613
+ // SGR first (1006); X10 (legacy 1000/1002 without SGR) as the
614
+ // compatibility fallback for clicks/drags. Wheel falls through
615
+ // to parseKeypress, which turns it into a wheel key WITH the
616
+ // pointer coordinates for position-based routing.
617
+ const mouse = parseMouseEvent(token.value) ??
618
+ parseX10MouseEvent(token.value);
614
619
  if (mouse) {
615
620
  keys.push(mouse);
616
621
  }
@@ -819,6 +824,8 @@ export const nonAlphanumericKeys = [
819
824
  'backspace',
820
825
  'wheelup',
821
826
  'wheeldown',
827
+ 'wheelleft',
828
+ 'wheelright',
822
829
  'mouse',
823
830
  ];
824
831
  const isShiftKey = (code) => {
@@ -955,6 +962,39 @@ function parseMouseEvent(s) {
955
962
  sequence: s,
956
963
  };
957
964
  }
965
+ /**
966
+ * Parse an X10 mouse sequence (CSI M + Cb/Cx/Cy raw bytes, each +32) into
967
+ * a ParsedMouse, or null for anything else. Serves terminals that honor
968
+ * DECSET 1000/1002 but ignore 1006 (SGR): clicks and button-drags become
969
+ * real ParsedMouse events so text selection works there too.
970
+ *
971
+ * Protocol limits (documented, accepted): X10 has no release events — a
972
+ * started selection finishes via App's lost-release recovery (next press /
973
+ * focus-out / no-button motion), and click dispatch (release-based) stays
974
+ * SGR-only. No-button motion (hover) is unrepresentable without the drag
975
+ * bit, so hover stays SGR-only. Wheel returns null (parseKeypress's wheel
976
+ * branch turns it into a key with coordinates).
977
+ */
978
+ function parseX10MouseEvent(s) {
979
+ if (s.length !== 6 || !s.startsWith('\x1b[M'))
980
+ return null;
981
+ const button = s.charCodeAt(3) - 32;
982
+ // Wheel (bit 6) → key path; no-button without drag bit → unsupported
983
+ // hover encoding, swallow upstream.
984
+ if ((button & 0x40) !== 0)
985
+ return null;
986
+ if ((button & 0x03) === 3 && (button & 0x20) === 0)
987
+ return null;
988
+ return {
989
+ kind: 'mouse',
990
+ button,
991
+ action: 'press',
992
+ // X10 coords are 1-indexed like SGR (charCode - 32).
993
+ col: s.charCodeAt(4) - 32,
994
+ row: s.charCodeAt(5) - 32,
995
+ sequence: s,
996
+ };
997
+ }
958
998
  function parseKeypress(s = '') {
959
999
  let parts;
960
1000
  const key = {
@@ -1020,26 +1060,53 @@ function parseKeypress(s = '') {
1020
1060
  // + direction while ignoring modifier bits (Shift=0x04, Meta=0x08,
1021
1061
  // Ctrl=0x10) — modified wheel events (e.g. Ctrl+scroll, button=80)
1022
1062
  // should still be recognized as wheelup/wheeldown.
1063
+ //
1064
+ // The SGR sequence carries the pointer position (CSI < btn;col;row M) —
1065
+ // preserved on the ParsedKey (mouseCol/mouseRow, 0-indexed) so wheel
1066
+ // routing can hit-test the ScrollBox under the pointer instead of
1067
+ // scrolling a hardcoded target. Buttons 66/67 are horizontal wheel
1068
+ // (wheelleft/wheelright); kept as keys with coords for future consumers.
1023
1069
  if ((match = SGR_MOUSE_RE.exec(s))) {
1024
1070
  const button = parseInt(match[1], 10);
1025
- if ((button & 0x43) === 0x40)
1026
- return createNavKey(s, 'wheelup', false);
1027
- if ((button & 0x43) === 0x41)
1028
- return createNavKey(s, 'wheeldown', false);
1071
+ const col = parseInt(match[2], 10) - 1;
1072
+ const row = parseInt(match[3], 10) - 1;
1073
+ const dir = button & 0x43;
1074
+ if (dir === 0x40)
1075
+ return createWheelKey(s, 'wheelup', col, row, button);
1076
+ if (dir === 0x41)
1077
+ return createWheelKey(s, 'wheeldown', col, row, button);
1078
+ if (dir === 0x42)
1079
+ return createWheelKey(s, 'wheelleft', col, row, button);
1080
+ if (dir === 0x43)
1081
+ return createWheelKey(s, 'wheelright', col, row, button);
1029
1082
  // Shouldn't reach here (parseMouseEvent catches non-wheel) but be safe
1030
1083
  return createNavKey(s, 'mouse', false);
1031
1084
  }
1032
1085
  // X10 mouse: CSI M + 3 raw bytes (Cb+32, Cx+32, Cy+32). Terminals that
1033
1086
  // ignore DECSET 1006 (SGR) but honor 1000/1002 emit this legacy encoding.
1034
- // Button bits match SGR: 0x40 = wheel, low bit = direction. Non-wheel
1035
- // X10 events (clicks/drags) are swallowed here — we only enable mouse
1036
- // tracking in alt-screen and only need wheel for ScrollBox.
1087
+ // Button bits match SGR: 0x40 = wheel, low bit = direction, 0x20 = drag.
1088
+ // Wheel events become wheel keys (with coordinates, like SGR); clicks and
1089
+ // drags become ParsedMouse so X10-only terminals get selection support.
1090
+ // X10 cannot report button release — click dispatch (release-based) stays
1091
+ // SGR-only; a started selection finishes via the lost-release recovery
1092
+ // paths (next press / focus-out / no-button motion).
1037
1093
  if (s.length === 6 && s.startsWith('\x1b[M')) {
1038
1094
  const button = s.charCodeAt(3) - 32;
1039
- if ((button & 0x43) === 0x40)
1040
- return createNavKey(s, 'wheelup', false);
1041
- if ((button & 0x43) === 0x41)
1042
- return createNavKey(s, 'wheeldown', false);
1095
+ const col = s.charCodeAt(4) - 32 - 1;
1096
+ const row = s.charCodeAt(5) - 32 - 1;
1097
+ const dir = button & 0x43;
1098
+ if (dir === 0x40)
1099
+ return createWheelKey(s, 'wheelup', col, row, button);
1100
+ if (dir === 0x41)
1101
+ return createWheelKey(s, 'wheeldown', col, row, button);
1102
+ if (dir === 0x42)
1103
+ return createWheelKey(s, 'wheelleft', col, row, button);
1104
+ if (dir === 0x43)
1105
+ return createWheelKey(s, 'wheelright', col, row, button);
1106
+ // Non-wheel X10 clicks/drags are intercepted by parseX10MouseEvent in
1107
+ // parseMultipleKeypresses before this function runs; reaching here means
1108
+ // a flush path bypassed it (no-button motion without the drag bit — X10
1109
+ // hover, unsupported). Swallow as before.
1043
1110
  return createNavKey(s, 'mouse', false);
1044
1111
  }
1045
1112
  if (s === '\r') {
@@ -1157,3 +1224,25 @@ function createNavKey(s, name, ctrl) {
1157
1224
  isPasted: false,
1158
1225
  };
1159
1226
  }
1227
+ /**
1228
+ * Build a wheel ParsedKey preserving the pointer position and modifier
1229
+ * bits from the protocol's button byte (Shift=0x04, Meta/Alt=0x08,
1230
+ * Ctrl=0x10 — same bit layout decodeModifier documents).
1231
+ */
1232
+ function createWheelKey(s, name, col, row, button) {
1233
+ return {
1234
+ kind: 'key',
1235
+ name,
1236
+ ctrl: (button & 0x10) !== 0,
1237
+ meta: (button & 0x08) !== 0,
1238
+ shift: (button & 0x04) !== 0,
1239
+ option: false,
1240
+ super: false,
1241
+ fn: false,
1242
+ sequence: s,
1243
+ raw: s,
1244
+ isPasted: false,
1245
+ mouseCol: col,
1246
+ mouseRow: row,
1247
+ };
1248
+ }
@@ -1,4 +1,5 @@
1
1
  import type { DOMElement } from './dom.js';
2
+ import type { Rectangle } from './layout/geometry.js';
2
3
  import type Output from './output.js';
3
4
  import type { Screen } from './screen.js';
4
5
  import { type StyledSegment } from './squash-text-nodes.js';
@@ -22,6 +23,15 @@ export type ScrollHint = {
22
23
  bottom: number;
23
24
  delta: number;
24
25
  };
26
+ export type AbsoluteHitEntry = {
27
+ node: DOMElement;
28
+ rect: Rectangle;
29
+ };
30
+ /**
31
+ * The current frame's absolute-positioned nodes in paint order.
32
+ * @returns read-only list; reverse-iterate for topmost-first hit-testing.
33
+ */
34
+ export declare function getAbsoluteHitList(): readonly AbsoluteHitEntry[];
25
35
  /** Reset the scroll hint for the next frame and rotate the absolute-rect buffers. */
26
36
  export declare function resetScrollHint(): void;
27
37
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"render-node-to-output.d.ts","sourceRoot":"","sources":["../../../src/ink/render-node-to-output.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAK1C,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAErC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EACL,KAAK,aAAa,EAEnB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAqBxC,6CAA6C;AAC7C,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAOD;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAWvE,qFAAqF;AACrF,wBAAgB,eAAe,IAAI,IAAI,CAItC;AAED;;;GAGG;AACH,wBAAgB,aAAa,IAAI,UAAU,GAAG,IAAI,CAEjD;AASD,8DAA8D;AAC9D,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,UAAU,GAAG,IAAI,CAEtD;AAgBD;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,cAAc,EAAE,MAAM,CAAA;CACvB,CAAA;AAGD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,IAAI,YAAY,EAAE,CAIpD;AAoFD;;;;;GAKG;AACH,iBAAS,qBAAqB,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,MAAM,EAAE,CASlE;AAED;;;;;;;;;;;GAWG;AACH,iBAAS,wBAAwB,CAC/B,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,aAAa,EAAE,EACzB,aAAa,EAAE,MAAM,EAAE,EACvB,aAAa,EAAE,MAAM,EACrB,WAAW,GAAE,OAAe,GAC3B,MAAM,CA0GR;AA+DD;;;;;;;;;GASG;AACH,iBAAS,kBAAkB,CACzB,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE;IACP,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;IAM9B,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,wBAAwB,CAAC,EAAE,KAAK,CAAA;CACjC,GACA,IAAI,CAo4BN;AAyOD,OAAO,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,CAAA;AAE1D,eAAe,kBAAkB,CAAA"}
1
+ {"version":3,"file":"render-node-to-output.d.ts","sourceRoot":"","sources":["../../../src/ink/render-node-to-output.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAG1C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAGrD,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAErC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EACL,KAAK,aAAa,EAEnB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAqBxC,6CAA6C;AAC7C,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAOD;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAkBvE,MAAM,MAAM,gBAAgB,GAAG;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,CAAA;AAGpE;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,SAAS,gBAAgB,EAAE,CAEhE;AAED,qFAAqF;AACrF,wBAAgB,eAAe,IAAI,IAAI,CAKtC;AAED;;;GAGG;AACH,wBAAgB,aAAa,IAAI,UAAU,GAAG,IAAI,CAEjD;AASD,8DAA8D;AAC9D,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,UAAU,GAAG,IAAI,CAEtD;AAgBD;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,cAAc,EAAE,MAAM,CAAA;CACvB,CAAA;AAGD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,IAAI,YAAY,EAAE,CAIpD;AAoFD;;;;;GAKG;AACH,iBAAS,qBAAqB,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,MAAM,EAAE,CASlE;AAED;;;;;;;;;;;GAWG;AACH,iBAAS,wBAAwB,CAC/B,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,aAAa,EAAE,EACzB,aAAa,EAAE,MAAM,EAAE,EACvB,aAAa,EAAE,MAAM,EACrB,WAAW,GAAE,OAAe,GAC3B,MAAM,CA0GR;AA+DD;;;;;;;;;GASG;AACH,iBAAS,kBAAkB,CACzB,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE;IACP,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;IAM9B,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,wBAAwB,CAAC,EAAE,KAAK,CAAA;CACjC,GACA,IAAI,CAq9BN;AA0OD,OAAO,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,CAAA;AAE1D,eAAe,kBAAkB,CAAA"}