@deepseek-harness-tui/dsh-tui 0.10.2 → 0.11.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 (277) hide show
  1. package/README.md +171 -124
  2. package/bin/dsh-tui.js +748 -22
  3. package/cordis.patch.yml +38 -10
  4. package/cordis.yml +2 -0
  5. package/lib/types/adapter/channel/session-projection.d.ts.map +1 -1
  6. package/lib/types/adapter/channel/session-projection.js +2 -0
  7. package/lib/types/adapter/channel/ui-policy.d.ts +4 -1
  8. package/lib/types/adapter/channel/ui-policy.d.ts.map +1 -1
  9. package/lib/types/adapter/channel/ui-policy.js +5 -1
  10. package/lib/types/adapter/kernel/host-facade.d.ts.map +1 -1
  11. package/lib/types/adapter/kernel/host-facade.js +1 -0
  12. package/lib/types/adapter/kernel/replay.d.ts.map +1 -1
  13. package/lib/types/adapter/kernel/replay.js +3 -0
  14. package/lib/types/adapter/kernel/runtime.d.ts.map +1 -1
  15. package/lib/types/adapter/kernel/runtime.js +2 -0
  16. package/lib/types/adapter/kernel/slices/workspace.d.ts.map +1 -1
  17. package/lib/types/adapter/kernel/slices/workspace.js +1 -0
  18. package/lib/types/adapter/ports/channel-ui.d.ts +22 -2
  19. package/lib/types/adapter/ports/channel-ui.d.ts.map +1 -1
  20. package/lib/types/adapter/ports/channel-view.d.ts +66 -0
  21. package/lib/types/adapter/ports/channel-view.d.ts.map +1 -1
  22. package/lib/types/adapter/ports/channel-workspace.d.ts +23 -0
  23. package/lib/types/adapter/ports/channel-workspace.d.ts.map +1 -1
  24. package/lib/types/adapter/ports/workspace.d.ts +2 -0
  25. package/lib/types/adapter/ports/workspace.d.ts.map +1 -1
  26. package/lib/types/adapter/upstream/workspace-driver.d.ts.map +1 -1
  27. package/lib/types/adapter/upstream/workspace-driver.js +10 -2
  28. package/lib/types/commands.d.ts.map +1 -1
  29. package/lib/types/commands.js +1 -0
  30. package/lib/types/components/ContextBarView.d.ts +15 -7
  31. package/lib/types/components/ContextBarView.d.ts.map +1 -1
  32. package/lib/types/components/ContextBarView.js +26 -22
  33. package/lib/types/components/ImagePreviewOverlay.d.ts.map +1 -1
  34. package/lib/types/components/ImagePreviewOverlay.js +11 -5
  35. package/lib/types/components/Markdown.d.ts +11 -1
  36. package/lib/types/components/Markdown.d.ts.map +1 -1
  37. package/lib/types/components/Markdown.js +20 -2
  38. package/lib/types/components/MermaidDiagram.d.ts +13 -0
  39. package/lib/types/components/MermaidDiagram.d.ts.map +1 -0
  40. package/lib/types/components/MermaidDiagram.js +61 -0
  41. package/lib/types/components/MessageList.d.ts +26 -1
  42. package/lib/types/components/MessageList.d.ts.map +1 -1
  43. package/lib/types/components/MessageList.js +72 -7
  44. package/lib/types/components/PageMargin.d.ts +1 -1
  45. package/lib/types/components/PromptInput.d.ts +37 -1
  46. package/lib/types/components/PromptInput.d.ts.map +1 -1
  47. package/lib/types/components/PromptInput.js +243 -28
  48. package/lib/types/components/ScrollbarGutter.d.ts +20 -5
  49. package/lib/types/components/ScrollbarGutter.d.ts.map +1 -1
  50. package/lib/types/components/ScrollbarGutter.js +88 -10
  51. package/lib/types/components/StreamingMarkdown.d.ts.map +1 -1
  52. package/lib/types/components/StreamingMarkdown.js +30 -28
  53. package/lib/types/components/Tooltip.d.ts.map +1 -1
  54. package/lib/types/components/Tooltip.js +45 -7
  55. package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
  56. package/lib/types/components/messages/UserPromptMessage.js +5 -7
  57. package/lib/types/components/messages/useImageInspection.d.ts.map +1 -1
  58. package/lib/types/components/messages/useImageInspection.js +19 -9
  59. package/lib/types/components/promptDraftCache.d.ts +102 -0
  60. package/lib/types/components/promptDraftCache.d.ts.map +1 -0
  61. package/lib/types/components/promptDraftCache.js +76 -0
  62. package/lib/types/components/questions/AskUserQuestionPanel.d.ts +17 -1
  63. package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
  64. package/lib/types/components/questions/AskUserQuestionPanel.js +40 -7
  65. package/lib/types/components/questions/QuestionMinimizedBar.d.ts +12 -0
  66. package/lib/types/components/questions/QuestionMinimizedBar.d.ts.map +1 -0
  67. package/lib/types/components/questions/QuestionMinimizedBar.js +43 -0
  68. package/lib/types/components/sessions/SessionListRow.d.ts +23 -1
  69. package/lib/types/components/sessions/SessionListRow.d.ts.map +1 -1
  70. package/lib/types/components/sessions/SessionListRow.js +52 -5
  71. package/lib/types/components/workspaces/HomeWorkspaceRow.d.ts +49 -0
  72. package/lib/types/components/workspaces/HomeWorkspaceRow.d.ts.map +1 -0
  73. package/lib/types/components/workspaces/HomeWorkspaceRow.js +50 -0
  74. package/lib/types/dsh-adapter/bundled-presets.d.ts +10 -0
  75. package/lib/types/dsh-adapter/bundled-presets.d.ts.map +1 -0
  76. package/lib/types/dsh-adapter/bundled-presets.js +80 -0
  77. package/lib/types/dsh-adapter/channel/action-readiness.d.ts +1 -1
  78. package/lib/types/dsh-adapter/channel/action-readiness.d.ts.map +1 -1
  79. package/lib/types/dsh-adapter/channel/action-readiness.js +3 -0
  80. package/lib/types/dsh-adapter/channel/agent-view-projection.d.ts +1 -1
  81. package/lib/types/dsh-adapter/channel/agent-view-projection.d.ts.map +1 -1
  82. package/lib/types/dsh-adapter/channel/agent-view-projection.js +19 -2
  83. package/lib/types/dsh-adapter/channel/background-action.d.ts.map +1 -1
  84. package/lib/types/dsh-adapter/channel/background-action.js +52 -29
  85. package/lib/types/dsh-adapter/channel/binding.d.ts +19 -2
  86. package/lib/types/dsh-adapter/channel/binding.d.ts.map +1 -1
  87. package/lib/types/dsh-adapter/channel/binding.js +78 -18
  88. package/lib/types/dsh-adapter/channel/composer-images.d.ts +5 -0
  89. package/lib/types/dsh-adapter/channel/composer-images.d.ts.map +1 -1
  90. package/lib/types/dsh-adapter/channel/composer-images.js +105 -6
  91. package/lib/types/dsh-adapter/channel/context-bookkeeping.d.ts +8 -0
  92. package/lib/types/dsh-adapter/channel/context-bookkeeping.d.ts.map +1 -1
  93. package/lib/types/dsh-adapter/channel/context-bookkeeping.js +3 -2
  94. package/lib/types/dsh-adapter/channel/ide-selection.d.ts +58 -0
  95. package/lib/types/dsh-adapter/channel/ide-selection.d.ts.map +1 -0
  96. package/lib/types/dsh-adapter/channel/ide-selection.js +226 -0
  97. package/lib/types/dsh-adapter/channel/input-delivery.d.ts +6 -1
  98. package/lib/types/dsh-adapter/channel/input-delivery.d.ts.map +1 -1
  99. package/lib/types/dsh-adapter/channel/input-delivery.js +9 -1
  100. package/lib/types/dsh-adapter/channel/local-actions.d.ts +3 -19
  101. package/lib/types/dsh-adapter/channel/local-actions.d.ts.map +1 -1
  102. package/lib/types/dsh-adapter/channel/local-actions.js +2 -1
  103. package/lib/types/dsh-adapter/channel/model-actions.d.ts +4 -1
  104. package/lib/types/dsh-adapter/channel/model-actions.d.ts.map +1 -1
  105. package/lib/types/dsh-adapter/channel/model-actions.js +82 -6
  106. package/lib/types/dsh-adapter/channel/model-switch.d.ts.map +1 -1
  107. package/lib/types/dsh-adapter/channel/model-switch.js +49 -31
  108. package/lib/types/dsh-adapter/channel/projection.d.ts +4 -0
  109. package/lib/types/dsh-adapter/channel/projection.d.ts.map +1 -1
  110. package/lib/types/dsh-adapter/channel/projection.js +29 -19
  111. package/lib/types/dsh-adapter/channel/reports.d.ts.map +1 -1
  112. package/lib/types/dsh-adapter/channel/reports.js +4 -12
  113. package/lib/types/dsh-adapter/channel/session-fork.d.ts.map +1 -1
  114. package/lib/types/dsh-adapter/channel/session-fork.js +26 -2
  115. package/lib/types/dsh-adapter/channel/session-live-adoption.d.ts +3 -0
  116. package/lib/types/dsh-adapter/channel/session-live-adoption.d.ts.map +1 -1
  117. package/lib/types/dsh-adapter/channel/session-live-adoption.js +1 -0
  118. package/lib/types/dsh-adapter/channel/session-metadata.d.ts +10 -0
  119. package/lib/types/dsh-adapter/channel/session-metadata.d.ts.map +1 -1
  120. package/lib/types/dsh-adapter/channel/session-metadata.js +2 -2
  121. package/lib/types/dsh-adapter/channel/session-resume.d.ts +11 -2
  122. package/lib/types/dsh-adapter/channel/session-resume.d.ts.map +1 -1
  123. package/lib/types/dsh-adapter/channel/session-resume.js +291 -94
  124. package/lib/types/dsh-adapter/channel/session-rewind.d.ts.map +1 -1
  125. package/lib/types/dsh-adapter/channel/session-rewind.js +34 -16
  126. package/lib/types/dsh-adapter/channel/session-tree-actions.d.ts.map +1 -1
  127. package/lib/types/dsh-adapter/channel/session-tree-actions.js +19 -3
  128. package/lib/types/dsh-adapter/channel/settings-host.d.ts.map +1 -1
  129. package/lib/types/dsh-adapter/channel/settings-host.js +4 -3
  130. package/lib/types/dsh-adapter/channel/state.d.ts +1 -1
  131. package/lib/types/dsh-adapter/channel/state.d.ts.map +1 -1
  132. package/lib/types/dsh-adapter/channel/state.js +1 -1
  133. package/lib/types/dsh-adapter/channel/transcript.d.ts.map +1 -1
  134. package/lib/types/dsh-adapter/channel/transcript.js +6 -12
  135. package/lib/types/dsh-adapter/channel/types.d.ts +6 -1
  136. package/lib/types/dsh-adapter/channel/types.d.ts.map +1 -1
  137. package/lib/types/dsh-adapter/channel/workspace-actions.d.ts +4 -1
  138. package/lib/types/dsh-adapter/channel/workspace-actions.d.ts.map +1 -1
  139. package/lib/types/dsh-adapter/channel/workspace-actions.js +41 -1
  140. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  141. package/lib/types/dsh-adapter/channel.js +67 -6
  142. package/lib/types/dsh-adapter/compat/messages.d.ts +15 -0
  143. package/lib/types/dsh-adapter/compat/messages.d.ts.map +1 -0
  144. package/lib/types/dsh-adapter/compat/messages.js +15 -0
  145. package/lib/types/dsh-adapter/compat/settings.d.ts +33 -0
  146. package/lib/types/dsh-adapter/compat/settings.d.ts.map +1 -0
  147. package/lib/types/dsh-adapter/compat/settings.js +43 -0
  148. package/lib/types/dsh-adapter/compat/shell.d.ts +25 -0
  149. package/lib/types/dsh-adapter/compat/shell.d.ts.map +1 -0
  150. package/lib/types/dsh-adapter/compat/shell.js +11 -0
  151. package/lib/types/dsh-adapter/contract.d.ts +4 -4
  152. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  153. package/lib/types/dsh-adapter/contract.js +15 -6
  154. package/lib/types/dsh-adapter/ide-channel.d.ts +224 -0
  155. package/lib/types/dsh-adapter/ide-channel.d.ts.map +1 -0
  156. package/lib/types/dsh-adapter/ide-channel.js +556 -0
  157. package/lib/types/dsh-adapter/index.d.ts +18 -3
  158. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  159. package/lib/types/dsh-adapter/index.js +32 -5
  160. package/lib/types/dsh-adapter/plugin.d.ts +5 -2
  161. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  162. package/lib/types/dsh-adapter/plugin.js +173 -42
  163. package/lib/types/dsh-adapter/preset-resolution.d.ts +8 -0
  164. package/lib/types/dsh-adapter/preset-resolution.d.ts.map +1 -1
  165. package/lib/types/dsh-adapter/preset-resolution.js +10 -0
  166. package/lib/types/dsh-adapter/session-mount-heartbeat.d.ts +53 -0
  167. package/lib/types/dsh-adapter/session-mount-heartbeat.d.ts.map +1 -0
  168. package/lib/types/dsh-adapter/session-mount-heartbeat.js +100 -0
  169. package/lib/types/dsh-adapter/sessionTree.d.ts.map +1 -1
  170. package/lib/types/dsh-adapter/sessionTree.js +3 -2
  171. package/lib/types/dsh-adapter/subagents.d.ts.map +1 -1
  172. package/lib/types/dsh-adapter/subagents.js +6 -5
  173. package/lib/types/dsh-adapter/workspaces.d.ts +46 -3
  174. package/lib/types/dsh-adapter/workspaces.d.ts.map +1 -1
  175. package/lib/types/dsh-adapter/workspaces.js +116 -0
  176. package/lib/types/effortPrefs.d.ts +15 -3
  177. package/lib/types/effortPrefs.d.ts.map +1 -1
  178. package/lib/types/effortPrefs.js +37 -3
  179. package/lib/types/homePrefs.d.ts +15 -0
  180. package/lib/types/homePrefs.d.ts.map +1 -0
  181. package/lib/types/homePrefs.js +53 -0
  182. package/lib/types/i18n.d.ts +257 -296
  183. package/lib/types/i18n.d.ts.map +1 -1
  184. package/lib/types/i18n.js +78 -84
  185. package/lib/types/ink/colorize.d.ts +10 -0
  186. package/lib/types/ink/colorize.d.ts.map +1 -1
  187. package/lib/types/ink/colorize.js +21 -0
  188. package/lib/types/ink/components/ScrollBox.d.ts.map +1 -1
  189. package/lib/types/ink/components/ScrollBox.js +12 -1
  190. package/lib/types/ink/dom.d.ts +1 -0
  191. package/lib/types/ink/dom.d.ts.map +1 -1
  192. package/lib/types/ink/hooks/use-copy-on-select.d.ts +5 -1
  193. package/lib/types/ink/hooks/use-copy-on-select.d.ts.map +1 -1
  194. package/lib/types/ink/hooks/use-copy-on-select.js +15 -1
  195. package/lib/types/ink/ink.d.ts.map +1 -1
  196. package/lib/types/ink/ink.js +36 -1
  197. package/lib/types/ink/output.d.ts +10 -1
  198. package/lib/types/ink/output.d.ts.map +1 -1
  199. package/lib/types/ink/output.js +109 -17
  200. package/lib/types/ink/render-border.d.ts +6 -1
  201. package/lib/types/ink/render-border.d.ts.map +1 -1
  202. package/lib/types/ink/render-border.js +25 -9
  203. package/lib/types/ink/render-node-to-output.d.ts.map +1 -1
  204. package/lib/types/ink/render-node-to-output.js +48 -6
  205. package/lib/types/ink/renderer.d.ts +6 -0
  206. package/lib/types/ink/renderer.d.ts.map +1 -1
  207. package/lib/types/ink/renderer.js +2 -1
  208. package/lib/types/ink/selection.d.ts +60 -0
  209. package/lib/types/ink/selection.d.ts.map +1 -1
  210. package/lib/types/ink/selection.js +129 -0
  211. package/lib/types/ink/sixel-graphics.d.ts +19 -1
  212. package/lib/types/ink/sixel-graphics.d.ts.map +1 -1
  213. package/lib/types/ink/sixel-graphics.js +124 -12
  214. package/lib/types/ink/terminal-image.d.ts +18 -1
  215. package/lib/types/ink/terminal-image.d.ts.map +1 -1
  216. package/lib/types/ink/terminal.d.ts +17 -0
  217. package/lib/types/ink/terminal.d.ts.map +1 -1
  218. package/lib/types/ink/terminal.js +20 -1
  219. package/lib/types/screens/Chat.d.ts +17 -1
  220. package/lib/types/screens/Chat.d.ts.map +1 -1
  221. package/lib/types/screens/Chat.js +284 -72
  222. package/lib/types/screens/SessionSupervisor.d.ts +84 -0
  223. package/lib/types/screens/SessionSupervisor.d.ts.map +1 -0
  224. package/lib/types/screens/SessionSupervisor.js +350 -0
  225. package/lib/types/screens/SessionTree.js +2 -2
  226. package/lib/types/screens/Settings.d.ts.map +1 -1
  227. package/lib/types/screens/Settings.js +32 -9
  228. package/lib/types/screens/StatusLine.d.ts +13 -0
  229. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  230. package/lib/types/screens/StatusLine.js +36 -17
  231. package/lib/types/screens/StatusMetrics.d.ts +79 -22
  232. package/lib/types/screens/StatusMetrics.d.ts.map +1 -1
  233. package/lib/types/screens/StatusMetrics.js +153 -88
  234. package/lib/types/screens/sessionSupervisor/model.d.ts +83 -0
  235. package/lib/types/screens/sessionSupervisor/model.d.ts.map +1 -0
  236. package/lib/types/screens/sessionSupervisor/model.js +69 -0
  237. package/lib/types/screens/sessionSupervisor/useSessionSupervisor.d.ts +120 -0
  238. package/lib/types/screens/sessionSupervisor/useSessionSupervisor.d.ts.map +1 -0
  239. package/lib/types/screens/sessionSupervisor/useSessionSupervisor.js +593 -0
  240. package/lib/types/sessionMounts.d.ts +272 -0
  241. package/lib/types/sessionMounts.d.ts.map +1 -0
  242. package/lib/types/sessionMounts.js +706 -0
  243. package/lib/types/sessions/format.d.ts.map +1 -1
  244. package/lib/types/sessions/format.js +4 -1
  245. package/lib/types/sessions/resumeFailure.d.ts +38 -0
  246. package/lib/types/sessions/resumeFailure.d.ts.map +1 -0
  247. package/lib/types/sessions/resumeFailure.js +58 -0
  248. package/lib/types/terminal-utils/mermaid.d.ts +44 -0
  249. package/lib/types/terminal-utils/mermaid.d.ts.map +1 -0
  250. package/lib/types/terminal-utils/mermaid.js +83 -0
  251. package/lib/types/tips.d.ts.map +1 -1
  252. package/lib/types/tips.js +175 -19
  253. package/lib/types/tuiDisplayPrefs.d.ts +7 -9
  254. package/lib/types/tuiDisplayPrefs.d.ts.map +1 -1
  255. package/lib/types/tuiDisplayPrefs.js +31 -28
  256. package/lib/types/update.d.ts +61 -0
  257. package/lib/types/update.d.ts.map +1 -1
  258. package/lib/types/update.js +194 -0
  259. package/lib/types/utils/imageResize.d.ts +90 -0
  260. package/lib/types/utils/imageResize.d.ts.map +1 -0
  261. package/lib/types/utils/imageResize.js +272 -0
  262. package/lib/types/utils/keymap.d.ts +5 -1
  263. package/lib/types/utils/keymap.d.ts.map +1 -1
  264. package/lib/types/utils/keymap.js +23 -0
  265. package/lib/types/workspaces.d.ts +2 -1
  266. package/lib/types/workspaces.d.ts.map +1 -1
  267. package/lib/types/workspaces.js +1 -1
  268. package/node_modules/@deepseek-harness-tui/dsh-auth/package.json +10 -10
  269. package/package.json +120 -103
  270. package/presets/liangshen/.dsh-tui-managed.json +1 -1
  271. package/presets/liangshen/agent.cordis.yml +15 -0
  272. package/lib/types/screens/AgentView.d.ts +0 -23
  273. package/lib/types/screens/AgentView.d.ts.map +0 -1
  274. package/lib/types/screens/AgentView.js +0 -546
  275. package/lib/types/screens/SessionBrowser.d.ts +0 -42
  276. package/lib/types/screens/SessionBrowser.d.ts.map +0 -1
  277. package/lib/types/screens/SessionBrowser.js +0 -911
@@ -76,6 +76,25 @@ export type SelectionState = {
76
76
  * were on, xterm.js would have consumed the event for native selection
77
77
  * and we'd never receive it. Used by the footer to show the right hint. */
78
78
  lastPressHadAlt: boolean;
79
+ /** Rolling fingerprint (hash) of the rows under the highlight. Ink's
80
+ * render loop refreshes it every frame; a change between frames
81
+ * WITHOUT a paired follow-shift means screen content was replaced in
82
+ * place under a stationary selection (a streaming transcript
83
+ * overwriting the rows the highlight covers), and a copy from these
84
+ * coordinates would read whatever text now sits there — not what the
85
+ * user highlighted. Null until the first frame observes the
86
+ * selection. */
87
+ coveredFingerprint: number | null;
88
+ /** Geometry key (start/end row:col) the fingerprint was taken at. Any
89
+ * user-driven geometry change (drag motion, word/line extension,
90
+ * keyboard pan, multi-click) re-baselines instead of judging — the
91
+ * guard only ever indicts a STATIONARY highlight whose text was
92
+ * swapped underneath. Owned by refreshSelectionFingerprint. */
93
+ coveredGeometry: string | null;
94
+ /** Sticky once the covered rows changed without follow coordination.
95
+ * Commit-time copy (copySelectionNoClear) refuses and clears instead
96
+ * of shipping the replaced text. Cleared on start/clear. */
97
+ stale: boolean;
79
98
  };
80
99
  /**
81
100
  * Create a new selection state with no active selection.
@@ -379,6 +398,47 @@ export declare function selectionBounds(s: SelectionState): {
379
398
  * @returns true when the cell lies inside the selection range.
380
399
  */
381
400
  export declare function isCellSelected(s: SelectionState, col: number, row: number): boolean;
401
+ /**
402
+ * Rehash the rows under the highlight and latch `stale` when they changed
403
+ * without a coordinated shift this frame.
404
+ *
405
+ * Copy reads whatever text occupies the highlight's screen coordinates at
406
+ * commit time. When the transcript REPLACES those rows in place while the
407
+ * highlight sits still (streaming output overwriting folded rows, a card
408
+ * collapsing under the anchor), the copied text is whatever moved in —
409
+ * visibly wrong text, not mojibake from a width bug. The follow/resize
410
+ * shifts keep the highlight anchored to text that MOVES; this guard catches
411
+ * the complementary case: stationary coordinates, moving content.
412
+ *
413
+ * Called once per rendered frame (post-render, pre-swap) on the frame the
414
+ * copy would read. The hash covers every visible cell of every covered row
415
+ * (same visibility rules as getSelectedText: noSelect and spacer cells
416
+ * skipped) via the cell's TEXT — `charPool.get(charId)` — not its pool
417
+ * index, plus the two soft-wrap inputs that decide how those cells are laid
418
+ * out into lines (the row's own `softWrap[row]`, and the `softWrap[row + 1]`
419
+ * extractRowText reads as this row's content end). styleId is excluded so
420
+ * the selection overlay and syntax highlighting themselves cannot trip the
421
+ * guard.
422
+ *
423
+ * Why content and not charId: a charId is an index into a generational
424
+ * CharPool, not a stable identity. Ink.resetPools() (ink.tsx) swaps in a
425
+ * fresh CharPool every ~5 minutes and re-interns the front frame through
426
+ * migrateScreenPools, so the SAME glyph comes back under a different
427
+ * number. Hashing ids would read that renumbering as "the covered rows
428
+ * changed" and refuse a perfectly legitimate copy with the stale-content
429
+ * notice — a false positive on a screen where nothing was replaced. The
430
+ * pool lookup is an array index plus a 1-2 code-unit hash loop, measured
431
+ * at ~0.05ms for a full 200x50 selection (~0.14ms at 200x200), i.e. no
432
+ * worse than hashing the ids themselves.
433
+ *
434
+ * @param s - the selection state to fingerprint.
435
+ * @param screen - the frame's screen buffer.
436
+ * @param coordinated - true when this frame translated the selection
437
+ * endpoints (follow-shift or viewport resize); a fingerprint change in
438
+ * such a frame is the expected content scroll, not an overwrite.
439
+ * @returns true when an uncoordinated change latched `stale` this call.
440
+ */
441
+ export declare function refreshSelectionFingerprint(s: SelectionState, screen: Screen, coordinated: boolean): boolean;
382
442
  /**
383
443
  * Extract text from the screen buffer within the selection range.
384
444
  * Rows are joined with newlines unless the screen's softWrap bitmap
@@ -1 +1 @@
1
- {"version":3,"file":"selection.d.ts","sourceRoot":"","sources":["../../../src/ink/selection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAGpD,KAAK,KAAK,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAA;AAEzC;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,6DAA6D;IAC7D,MAAM,EAAE,KAAK,GAAG,IAAI,CAAA;IACpB,oEAAoE;IACpE,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;IACnB,4CAA4C;IAC5C,UAAU,EAAE,OAAO,CAAA;IACnB;;;;4EAIwE;IACxE,UAAU,EAAE;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,EAAE,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IAClE;;;;0EAIsE;IACtE,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B,qEAAqE;IACrE,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B;;;;;iBAKa;IACb,kBAAkB,EAAE,OAAO,EAAE,CAAA;IAC7B,oCAAoC;IACpC,kBAAkB,EAAE,OAAO,EAAE,CAAA;IAC7B;;;;uEAImE;IACnE,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,sBAAsB;IACtB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB;;;;;0DAKsD;IACtD,UAAU,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5C;;;;gFAI4E;IAC5E,eAAe,EAAE,OAAO,CAAA;CACzB,CAAA;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,cAAc,CAYrD;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,CAAC,EAAE,cAAc,EACjB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,IAAI,CAgBN;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,CAAC,EAAE,cAAc,EACjB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,IAAI,CAeN;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,cAAc,GAAG,IAAI,CAWvD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,cAAc,GAAG,IAAI,CAatD;AAmGD;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAC1B,CAAC,EAAE,cAAc,EACjB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,IAAI,CASN;AAYD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,MAAM,GAAG,SAAS,CAmFpB;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,CAAC,EAAE,cAAc,EACjB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,GACV,IAAI,CAQN;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,CAAC,EAAE,cAAc,EACjB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,IAAI,CA4BN;AAED;gDACgD;AAChD,MAAM,MAAM,SAAS,GACjB,MAAM,GACN,OAAO,GACP,IAAI,GACJ,MAAM,GACN,WAAW,GACX,SAAS,CAAA;AAEb;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAQ3E;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,cAAc,CAC5B,CAAC,EAAE,cAAc,EACjB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,IAAI,CAyFN;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CACzB,CAAC,EAAE,cAAc,EACjB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb,IAAI,CAwBN;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,CAAC,EAAE,cAAc,EACjB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb,OAAO,CAQT;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,uBAAuB,CACrC,CAAC,EAAE,cAAc,EACjB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,UAAU,UAAO,GAChB,OAAO,CAsDT;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oCAAoC,CAClD,CAAC,EAAE,cAAc,EACjB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,OAAO,CA2DT;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAgB,+BAA+B,CAC7C,CAAC,EAAE,cAAc,EACjB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,IAAI,CA8CN;AAED;;0DAE0D;AAC1D,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,cAAc,EAAE,MAAM,CAAA;IACtB,wEAAwE;IACxE,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB,CAAA;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,WAAW,EAC1D,MAAM,EAAE,CAAC,EAAE,EACX,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,CAAC,GAAG,IAAI,CAaV;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,cAAc,GAAG,OAAO,CAEvD;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,cAAc,GAAG;IAClD,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;IACnC,GAAG,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CAClC,GAAG,IAAI,CAKP;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,CAAC,EAAE,cAAc,EACjB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,OAAO,CAQT;AAoDD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAsBzE;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,mBAAmB,CACjC,CAAC,EAAE,cAAc,EACjB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,OAAO,GAAG,OAAO,EACvB,eAAe,SAAI,GAClB,IAAI,CAyDN;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,cAAc,EACzB,SAAS,EAAE,SAAS,GACnB,IAAI,CAoBN"}
1
+ {"version":3,"file":"selection.d.ts","sourceRoot":"","sources":["../../../src/ink/selection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAGpD,KAAK,KAAK,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAA;AAEzC;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,6DAA6D;IAC7D,MAAM,EAAE,KAAK,GAAG,IAAI,CAAA;IACpB,oEAAoE;IACpE,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;IACnB,4CAA4C;IAC5C,UAAU,EAAE,OAAO,CAAA;IACnB;;;;4EAIwE;IACxE,UAAU,EAAE;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,EAAE,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IAClE;;;;0EAIsE;IACtE,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B,qEAAqE;IACrE,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B;;;;;iBAKa;IACb,kBAAkB,EAAE,OAAO,EAAE,CAAA;IAC7B,oCAAoC;IACpC,kBAAkB,EAAE,OAAO,EAAE,CAAA;IAC7B;;;;uEAImE;IACnE,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,sBAAsB;IACtB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB;;;;;0DAKsD;IACtD,UAAU,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5C;;;;gFAI4E;IAC5E,eAAe,EAAE,OAAO,CAAA;IACxB;;;;;;;qBAOiB;IACjB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC;;;;oEAIgE;IAChE,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B;;iEAE6D;IAC7D,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,cAAc,CAerD;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,CAAC,EAAE,cAAc,EACjB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,IAAI,CAmBN;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,CAAC,EAAE,cAAc,EACjB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,IAAI,CAeN;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,cAAc,GAAG,IAAI,CAWvD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,cAAc,GAAG,IAAI,CAgBtD;AAmGD;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAC1B,CAAC,EAAE,cAAc,EACjB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,IAAI,CASN;AAYD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,MAAM,GAAG,SAAS,CAmFpB;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,CAAC,EAAE,cAAc,EACjB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,GACV,IAAI,CAQN;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,CAAC,EAAE,cAAc,EACjB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,IAAI,CA4BN;AAED;gDACgD;AAChD,MAAM,MAAM,SAAS,GACjB,MAAM,GACN,OAAO,GACP,IAAI,GACJ,MAAM,GACN,WAAW,GACX,SAAS,CAAA;AAEb;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAQ3E;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,cAAc,CAC5B,CAAC,EAAE,cAAc,EACjB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,IAAI,CAyFN;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CACzB,CAAC,EAAE,cAAc,EACjB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb,IAAI,CAwBN;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,CAAC,EAAE,cAAc,EACjB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb,OAAO,CAQT;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,uBAAuB,CACrC,CAAC,EAAE,cAAc,EACjB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,UAAU,UAAO,GAChB,OAAO,CAsDT;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oCAAoC,CAClD,CAAC,EAAE,cAAc,EACjB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,OAAO,CA2DT;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAgB,+BAA+B,CAC7C,CAAC,EAAE,cAAc,EACjB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,IAAI,CA8CN;AAED;;0DAE0D;AAC1D,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,cAAc,EAAE,MAAM,CAAA;IACtB,wEAAwE;IACxE,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB,CAAA;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,WAAW,EAC1D,MAAM,EAAE,CAAC,EAAE,EACX,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,CAAC,GAAG,IAAI,CAaV;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,cAAc,GAAG,OAAO,CAEvD;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,cAAc,GAAG;IAClD,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;IACnC,GAAG,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CAClC,GAAG,IAAI,CAKP;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,CAAC,EAAE,cAAc,EACjB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV,OAAO,CAQT;AAoDD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAgB,2BAA2B,CACzC,CAAC,EAAE,cAAc,EACjB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,OAAO,GACnB,OAAO,CAyET;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAsBzE;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,mBAAmB,CACjC,CAAC,EAAE,cAAc,EACjB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,OAAO,GAAG,OAAO,EACvB,eAAe,SAAI,GAClB,IAAI,CAyDN;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,cAAc,EACzB,SAAS,EAAE,SAAS,GACnB,IAAI,CAoBN"}
@@ -26,6 +26,9 @@ export function createSelectionState() {
26
26
  scrolledOffAboveSW: [],
27
27
  scrolledOffBelowSW: [],
28
28
  lastPressHadAlt: false,
29
+ coveredFingerprint: null,
30
+ coveredGeometry: null,
31
+ stale: false,
29
32
  };
30
33
  }
31
34
  /**
@@ -52,6 +55,9 @@ export function startSelection(s, col, row) {
52
55
  s.virtualFocusRow = undefined;
53
56
  s.dragBounds = undefined;
54
57
  s.lastPressHadAlt = false;
58
+ s.coveredFingerprint = null;
59
+ s.coveredGeometry = null;
60
+ s.stale = false;
55
61
  }
56
62
  /**
57
63
  * Track the drag position: update focus to (col, row). No-op while not
@@ -120,6 +126,9 @@ export function clearSelection(s) {
120
126
  s.virtualFocusRow = undefined;
121
127
  s.dragBounds = undefined;
122
128
  s.lastPressHadAlt = false;
129
+ s.coveredFingerprint = null;
130
+ s.coveredGeometry = null;
131
+ s.stale = false;
123
132
  }
124
133
  // Unicode-aware word character matcher: letters (any script), digits,
125
134
  // and the punctuation set iTerm2 treats as word-part by default.
@@ -969,6 +978,126 @@ function joinRows(lines, text, sw) {
969
978
  lines.push(text);
970
979
  }
971
980
  }
981
+ /**
982
+ * Rehash the rows under the highlight and latch `stale` when they changed
983
+ * without a coordinated shift this frame.
984
+ *
985
+ * Copy reads whatever text occupies the highlight's screen coordinates at
986
+ * commit time. When the transcript REPLACES those rows in place while the
987
+ * highlight sits still (streaming output overwriting folded rows, a card
988
+ * collapsing under the anchor), the copied text is whatever moved in —
989
+ * visibly wrong text, not mojibake from a width bug. The follow/resize
990
+ * shifts keep the highlight anchored to text that MOVES; this guard catches
991
+ * the complementary case: stationary coordinates, moving content.
992
+ *
993
+ * Called once per rendered frame (post-render, pre-swap) on the frame the
994
+ * copy would read. The hash covers every visible cell of every covered row
995
+ * (same visibility rules as getSelectedText: noSelect and spacer cells
996
+ * skipped) via the cell's TEXT — `charPool.get(charId)` — not its pool
997
+ * index, plus the two soft-wrap inputs that decide how those cells are laid
998
+ * out into lines (the row's own `softWrap[row]`, and the `softWrap[row + 1]`
999
+ * extractRowText reads as this row's content end). styleId is excluded so
1000
+ * the selection overlay and syntax highlighting themselves cannot trip the
1001
+ * guard.
1002
+ *
1003
+ * Why content and not charId: a charId is an index into a generational
1004
+ * CharPool, not a stable identity. Ink.resetPools() (ink.tsx) swaps in a
1005
+ * fresh CharPool every ~5 minutes and re-interns the front frame through
1006
+ * migrateScreenPools, so the SAME glyph comes back under a different
1007
+ * number. Hashing ids would read that renumbering as "the covered rows
1008
+ * changed" and refuse a perfectly legitimate copy with the stale-content
1009
+ * notice — a false positive on a screen where nothing was replaced. The
1010
+ * pool lookup is an array index plus a 1-2 code-unit hash loop, measured
1011
+ * at ~0.05ms for a full 200x50 selection (~0.14ms at 200x200), i.e. no
1012
+ * worse than hashing the ids themselves.
1013
+ *
1014
+ * @param s - the selection state to fingerprint.
1015
+ * @param screen - the frame's screen buffer.
1016
+ * @param coordinated - true when this frame translated the selection
1017
+ * endpoints (follow-shift or viewport resize); a fingerprint change in
1018
+ * such a frame is the expected content scroll, not an overwrite.
1019
+ * @returns true when an uncoordinated change latched `stale` this call.
1020
+ */
1021
+ export function refreshSelectionFingerprint(s, screen, coordinated) {
1022
+ if (s.stale)
1023
+ return false;
1024
+ const b = selectionBounds(s);
1025
+ if (!b) {
1026
+ s.coveredFingerprint = null;
1027
+ s.coveredGeometry = null;
1028
+ return false;
1029
+ }
1030
+ // Any geometry change re-baselines: drag motion, word/line extension,
1031
+ // keyboard pan, multi-click — the user redefined what is highlighted, so
1032
+ // the next copy legitimately reads the new band's CURRENT text. Only a
1033
+ // stationary highlight can go stale.
1034
+ const geometry = `${b.start.row}:${b.start.col}-${b.end.row}:${b.end.col}`;
1035
+ if (geometry !== s.coveredGeometry) {
1036
+ s.coveredGeometry = geometry;
1037
+ s.coveredFingerprint = null;
1038
+ }
1039
+ const { cells, noSelect, width, height, charPool, softWrap } = screen;
1040
+ let h = 0x811c9dc5;
1041
+ for (let row = b.start.row; row <= b.end.row; row++) {
1042
+ if (row < 0 || row >= height)
1043
+ continue;
1044
+ const rowOff = row * width;
1045
+ // Column bounds mirror getSelectedText exactly: the boundary rows hash
1046
+ // only from start.col / through end.col. Streaming text appended to a
1047
+ // covered row OUTSIDE the selected column range (stable head selected,
1048
+ // live tail still writing) must not latch stale — the copy would not
1049
+ // read those columns anyway.
1050
+ const colStart = row === b.start.row ? b.start.col : 0;
1051
+ const colEnd = row === b.end.row ? b.end.col : width - 1;
1052
+ for (let col = colStart; col <= colEnd; col++) {
1053
+ const ci = (rowOff + col) * 2;
1054
+ // word1's low 2 bits are the cell width; SpacerTail/SpacerHead carry
1055
+ // no text of their own.
1056
+ if ((cells[ci + 1] & 3) >= 2 /* CellWidth.SpacerTail */)
1057
+ continue;
1058
+ if (noSelect[rowOff + col] === 1)
1059
+ continue;
1060
+ // Resolve the id through the pool and hash the actual characters —
1061
+ // the exact string getSelectedText would emit for this cell. Two
1062
+ // pools holding the same glyph hash identically, so a generational
1063
+ // pool swap is invisible here; a different glyph is not.
1064
+ const ch = charPool.get(cells[ci]);
1065
+ for (let k = 0; k < ch.length; k++) {
1066
+ h = Math.imul(h ^ ch.charCodeAt(k), 0x01000193);
1067
+ }
1068
+ }
1069
+ // Row separator + the row's soft-wrap bit: getSelectedText joins a
1070
+ // wrapped row onto the previous line with NO newline (softWrap[row]>0)
1071
+ // but emits a real newline otherwise — identical cells with a flipped
1072
+ // wrap bit produce a different copy, so the fingerprint must see it.
1073
+ h = Math.imul(h ^ 0x9e3779b9 ^ (softWrap[row] > 0 ? 0x51ed270b : 0), 0x85ebca6b);
1074
+ // The row BELOW is an input to THIS row's copy. extractRowText reads
1075
+ // softWrap[row + 1] as this row's content end: > 0 means the row wraps
1076
+ // into the next one, which both clamps the last column to
1077
+ // min(colEnd, contentEnd - 1) and suppresses the trailing-blank trim.
1078
+ // Flipping only the next row's wrap bit therefore rewrites the last
1079
+ // covered line's trailing columns ("A" → "A ") with every
1080
+ // covered CELL unchanged — the guard has to see the wrap, not just the
1081
+ // cells. Hash exactly what extractRowText consumes (0 = not wrapped) so
1082
+ // a contentEnd change that does not move the clamp stays invisible
1083
+ // instead of becoming a false positive.
1084
+ const contentEnd = row + 1 < height ? softWrap[row + 1] : 0;
1085
+ const wrapClamp = contentEnd > 0 ? Math.min(colEnd, contentEnd - 1) + 1 : 0;
1086
+ h = Math.imul(h ^ 0x27d4eb2f ^ wrapClamp, 0x165667b1);
1087
+ }
1088
+ if (s.coveredFingerprint === null) {
1089
+ // First frame observing this selection: baseline, no verdict.
1090
+ s.coveredFingerprint = h;
1091
+ return false;
1092
+ }
1093
+ if (h === s.coveredFingerprint)
1094
+ return false;
1095
+ s.coveredFingerprint = h;
1096
+ if (coordinated)
1097
+ return false;
1098
+ s.stale = true;
1099
+ return true;
1100
+ }
972
1101
  /**
973
1102
  * Extract text from the screen buffer within the selection range.
974
1103
  * Rows are joined with newlines unless the screen's softWrap bitmap
@@ -19,6 +19,8 @@ export declare class SixelGraphicsManager {
19
19
  private desired;
20
20
  private displayed;
21
21
  private nextDisplay;
22
+ /** Rects kept on screen this frame (replacement pending or covered). */
23
+ private readonly held;
22
24
  private readonly repaint;
23
25
  private worker;
24
26
  private workerKeys;
@@ -35,7 +37,23 @@ export declare class SixelGraphicsManager {
35
37
  beginFrame(columns: number, rows: number): void;
36
38
  readonly prepare: (placement: TerminalImagePlacement) => boolean;
37
39
  private variant;
38
- /** Reconcile ALL old rectangles before diffing, with only one baseline copy. */
40
+ /**
41
+ * Reconcile ALL old rectangles before diffing, with only one baseline copy.
42
+ *
43
+ * A rect is erased only when this frame replaces it (a ready raster at a new
44
+ * key/geometry, erased and drawn in the same write) or drops it entirely.
45
+ * Rects whose replacement is still being encoded, or which a later paint
46
+ * covers, are held: erasing them without drawing a replacement in the same
47
+ * frame is exactly the black flash seen while scrolling and the vanishing
48
+ * image under an overlay. {@link paint} folds the held rects back into
49
+ * `displayed`, and the frame that finally replaces one forces a repaint
50
+ * through the `held` marker.
51
+ *
52
+ * A held rect that an overlay PARTLY covers still has to give up the covered
53
+ * cells: its pixels outlive cell writes (a background-only space cell diffs
54
+ * as unchanged, so the frame never rewrites it) and would show through the
55
+ * overlay. Those sub-rects are erased with the placement's surface color.
56
+ */
39
57
  reconcile(screen: Screen, previous: Screen, placements?: readonly TerminalImagePlacement[]): {
40
58
  erase: string;
41
59
  baseline: Screen;
@@ -1 +1 @@
1
- {"version":3,"file":"sixel-graphics.d.ts","sourceRoot":"","sources":["../../../src/ink/sixel-graphics.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAA2C,MAAM,kBAAkB,CAAA;AAChH,OAAO,EAIL,KAAK,gBAAgB,EAAE,KAAK,sBAAsB,EACnD,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAuB,KAAK,MAAM,EAAE,MAAM,aAAa,CAAA;AAC9D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAatC,KAAK,OAAO,GAAG,CAAC,OAAO,EAAE,kBAAkB,KAAK,OAAO,CAAC,WAAW,CAAC,CAAA;AAOpE,oFAAoF;AACpF,qBAAa,oBAAoB;IAuBnB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAAc,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;IAtB3E,OAAO,CAAC,IAAI,CAA+C;IAC3D,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAoC;IACxD,OAAO,CAAC,MAAM,CAAI;IAClB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwC;IAC9D,OAAO,CAAC,WAAW,CAAI;IACvB,OAAO,CAAC,UAAU,CAAI;IACtB,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiC;IACvD,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,SAAS,CAAmC;IACpD,OAAO,CAAC,WAAW,CAAmC;IACtD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;IAChD,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,UAAU,CAAoB;IACtC,OAAO,CAAC,YAAY,CAAuC;IAC3D,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,OAAO,CAAI;IACnB,OAAO,CAAC,IAAI,CAAI;IAChB,OAAO,CAAC,cAAc,CAAQ;gBAED,OAAO,EAAE,MAAM,IAAI,EAAmB,OAAO,CAAC,EAAE,OAAO,YAAA;IAEpF,IAAI,QAAQ,IAAI,OAAO,CAAgE;IAEvF,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAKtC,WAAW,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO;IAQ5C,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAO/C,QAAQ,CAAC,OAAO,GAAI,WAAW,sBAAsB,KAAG,OAAO,CAc9D;IAED,OAAO,CAAC,OAAO;IAgDf,gFAAgF;IAChF,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,SAAS,sBAAsB,EAAE,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE;IA6ChI,iFAAiF;IACjF,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM;IAgBzB,aAAa,IAAI,IAAI;IAErB,KAAK,IAAI,MAAM;IAUf,OAAO,IAAI,MAAM;IAajB,OAAO,CAAC,KAAK;IASb,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,UAAU;IAwBlB,OAAO,CAAC,KAAK;IAWb,OAAO,CAAC,QAAQ;IAchB,OAAO,CAAC,cAAc;CAkEvB"}
1
+ {"version":3,"file":"sixel-graphics.d.ts","sourceRoot":"","sources":["../../../src/ink/sixel-graphics.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAA2C,MAAM,kBAAkB,CAAA;AAChH,OAAO,EAIL,KAAK,gBAAgB,EAAE,KAAK,sBAAsB,EACnD,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAuB,KAAK,MAAM,EAAE,MAAM,aAAa,CAAA;AAC9D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAwBtC,KAAK,OAAO,GAAG,CAAC,OAAO,EAAE,kBAAkB,KAAK,OAAO,CAAC,WAAW,CAAC,CAAA;AAiBpE,oFAAoF;AACpF,qBAAa,oBAAoB;IAyBnB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAAc,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;IAxB3E,OAAO,CAAC,IAAI,CAA+C;IAC3D,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAoC;IACxD,OAAO,CAAC,MAAM,CAAI;IAClB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwC;IAC9D,OAAO,CAAC,WAAW,CAAI;IACvB,OAAO,CAAC,UAAU,CAAI;IACtB,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiC;IACvD,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,SAAS,CAAmC;IACpD,OAAO,CAAC,WAAW,CAAmC;IACtD,wEAAwE;IACxE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAmC;IACxD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;IAChD,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,UAAU,CAAoB;IACtC,OAAO,CAAC,YAAY,CAAuC;IAC3D,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,OAAO,CAAI;IACnB,OAAO,CAAC,IAAI,CAAI;IAChB,OAAO,CAAC,cAAc,CAAQ;gBAED,OAAO,EAAE,MAAM,IAAI,EAAmB,OAAO,CAAC,EAAE,OAAO,YAAA;IAEpF,IAAI,QAAQ,IAAI,OAAO,CAAgE;IAEvF,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAKtC,WAAW,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO;IAQ5C,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAO/C,QAAQ,CAAC,OAAO,GAAI,WAAW,sBAAsB,KAAG,OAAO,CAc9D;IAED,OAAO,CAAC,OAAO;IAgDf;;;;;;;;;;;;;;;;OAgBG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,SAAS,sBAAsB,EAAE,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE;IAwGhI,iFAAiF;IACjF,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM;IAmBzB,aAAa,IAAI,IAAI;IAErB,KAAK,IAAI,MAAM;IAWf,OAAO,IAAI,MAAM;IAajB,OAAO,CAAC,KAAK;IAiBb,OAAO,CAAC,WAAW;IAcnB,OAAO,CAAC,UAAU;IAwBlB,OAAO,CAAC,KAAK;IAWb,OAAO,CAAC,QAAQ;IAchB,OAAO,CAAC,cAAc;CAkEvB"}
@@ -2,10 +2,21 @@ import { Worker } from 'node:worker_threads';
2
2
  import { DEFAULT_TERMINAL_CELL_SIZE, normalizeTerminalCellSize, TERMINAL_IMAGE_MAX_EDGE, TERMINAL_IMAGE_MAX_BYTES, TERMINAL_IMAGE_PREVIEW_MAX_EDGE, TERMINAL_IMAGE_PREVIEW_MAX_BYTES, SIXEL_MAX_ENCODED_BYTES, SIXEL_THUMBNAIL_FRAME_BYTES, SIXEL_CACHE_BYTES, SIXEL_CACHE_ENTRIES, } from './terminal-image.js';
3
3
  import { cellAt, clearRegion } from './screen.js';
4
4
  import { stringWidth } from './stringWidth.js';
5
+ import { backgroundOpenCode } from './colorize.js';
5
6
  function overlaps(a, b) {
6
7
  return a.x < b.x + b.columns && a.x + a.columns > b.x &&
7
8
  a.y < b.y + b.rows && a.y + a.rows > b.y;
8
9
  }
10
+ /** Intersection of two cell rects, or undefined when they do not overlap. */
11
+ function intersectRects(a, b) {
12
+ const x = Math.max(a.x, b.x);
13
+ const y = Math.max(a.y, b.y);
14
+ const right = Math.min(a.x + a.columns, b.x + b.columns);
15
+ const bottom = Math.min(a.y + a.rows, b.y + b.rows);
16
+ if (right <= x || bottom <= y)
17
+ return undefined;
18
+ return { x, y, columns: right - x, rows: bottom - y };
19
+ }
9
20
  /** Viewport-owned pixels. Encoding is shared; placement/erasure is per DOM node. */
10
21
  export class SixelGraphicsManager {
11
22
  onReady;
@@ -22,6 +33,8 @@ export class SixelGraphicsManager {
22
33
  desired = new Set();
23
34
  displayed = new Map();
24
35
  nextDisplay = new Map();
36
+ /** Rects kept on screen this frame (replacement pending or covered). */
37
+ held = new Map();
25
38
  repaint = new Set();
26
39
  worker;
27
40
  workerKeys = new Set();
@@ -119,22 +132,59 @@ export class SixelGraphicsManager {
119
132
  crop: { left, top, width: cropWidth, height: cropHeight } },
120
133
  };
121
134
  }
122
- /** Reconcile ALL old rectangles before diffing, with only one baseline copy. */
135
+ /**
136
+ * Reconcile ALL old rectangles before diffing, with only one baseline copy.
137
+ *
138
+ * A rect is erased only when this frame replaces it (a ready raster at a new
139
+ * key/geometry, erased and drawn in the same write) or drops it entirely.
140
+ * Rects whose replacement is still being encoded, or which a later paint
141
+ * covers, are held: erasing them without drawing a replacement in the same
142
+ * frame is exactly the black flash seen while scrolling and the vanishing
143
+ * image under an overlay. {@link paint} folds the held rects back into
144
+ * `displayed`, and the frame that finally replaces one forces a repaint
145
+ * through the `held` marker.
146
+ *
147
+ * A held rect that an overlay PARTLY covers still has to give up the covered
148
+ * cells: its pixels outlive cell writes (a background-only space cell diffs
149
+ * as unchanged, so the frame never rewrites it) and would show through the
150
+ * overlay. Those sub-rects are erased with the placement's surface color.
151
+ */
123
152
  reconcile(screen, previous, placements) {
124
153
  const actual = placements ? new Map(placements.map(p => [p.node, p])) : undefined;
125
154
  const visible = [];
126
155
  this.desired = new Set();
127
156
  this.nextDisplay.clear();
157
+ this.held.clear();
128
158
  this.repaint.clear();
159
+ // Nodes whose raster this frame drops (the paint no longer registers an
160
+ // image, or a later paint covers every visible cell): their pixels must be
161
+ // erased, unlike the ones merely held below.
162
+ const dropped = new Set();
129
163
  for (const [node, variant] of this.frame) {
130
164
  const placement = actual?.get(node);
131
- if ((actual && !placement) || placement?.occluded)
165
+ if (actual && !placement) {
166
+ dropped.add(node);
167
+ continue;
168
+ }
169
+ if (placement?.occludedFully) {
170
+ dropped.add(node);
132
171
  continue;
172
+ }
133
173
  visible.push(variant);
134
174
  this.desired.add(variant.key);
175
+ // Partially covered: drawing would pierce the overlay that covers it, so
176
+ // keep the pixels already on screen and wait for the cover to clear.
177
+ if (placement?.occluded)
178
+ continue;
135
179
  const raster = this.cache.get(variant.key);
136
180
  if (variant.ready && raster && placement?.graphicsReady !== false && this.isUncovered(variant.rect, screen)) {
137
- this.nextDisplay.set(node, { ...variant.rect, key: variant.key, raster });
181
+ const background = variant.placement.background;
182
+ this.nextDisplay.set(node, {
183
+ ...variant.rect,
184
+ key: variant.key,
185
+ raster,
186
+ ...(background === undefined ? {} : { background }),
187
+ });
138
188
  }
139
189
  }
140
190
  const unique = new Map();
@@ -145,27 +195,71 @@ export class SixelGraphicsManager {
145
195
  this.pending = [...unique.values()].sort((a, b) => Number(b.placement.presentation === 'preview') - Number(a.placement.presentation === 'preview'));
146
196
  this.drain();
147
197
  const erased = [];
198
+ let coverErase = '';
148
199
  for (const [node, old] of this.displayed) {
149
200
  const next = this.nextDisplay.get(node);
150
- if (!next || this.dirty || old.key !== next.key || old.x !== next.x || old.y !== next.y)
151
- erased.push(old);
201
+ if (next !== undefined) {
202
+ if (this.dirty || old.key !== next.key || old.x !== next.x || old.y !== next.y)
203
+ erased.push(old);
204
+ continue;
205
+ }
206
+ // Still requested this frame, but its replacement raster is not ready
207
+ // (scrolling re-crops per row and the encoder is async) or a later paint
208
+ // covers it. Erasing here would leave a hole with no draw in the same
209
+ // frame — the black flash while scrolling and the vanishing image under
210
+ // a tooltip. Keep the pixels already on screen until a replacement can
211
+ // be drawn in the same write.
212
+ if (!this.dirty && this.frame.has(node) && !dropped.has(node)) {
213
+ const placement = actual?.get(node);
214
+ const coveredRects = placement?.coveredRects;
215
+ const coveredKey = coveredRects === undefined || coveredRects.length === 0
216
+ ? undefined
217
+ : coveredRects.map(rect => `${rect.x},${rect.y},${rect.width},${rect.height}`).join(';');
218
+ if (coveredKey !== undefined && coveredKey !== old.covered) {
219
+ // The overlay paints these cells, but only the ones whose style
220
+ // changes get a write: an unchanged background-only space keeps the
221
+ // raster's pixels. Erase the covered cells (with the surface color,
222
+ // so they match the cell model) and leave the rest held.
223
+ const visibleRect = { x: old.x, y: old.y, columns: old.columns, rows: old.rows };
224
+ for (const rect of coveredRects) {
225
+ const clipped = intersectRects(visibleRect, { x: rect.x, y: rect.y, columns: rect.width, rows: rect.height });
226
+ if (clipped !== undefined)
227
+ coverErase += this.erase(clipped, old.background);
228
+ }
229
+ }
230
+ this.held.set(node, coveredKey === undefined
231
+ ? { ...old, held: true }
232
+ : { ...old, held: true, covered: coveredKey });
233
+ continue;
234
+ }
235
+ erased.push(old);
152
236
  }
153
237
  for (const [node, next] of this.nextDisplay) {
154
238
  const old = this.displayed.get(node);
155
- if (!old || this.dirty || old.key !== next.key || old.x !== next.x || old.y !== next.y ||
239
+ if (!old || this.dirty || old.held === true ||
240
+ old.key !== next.key || old.x !== next.x || old.y !== next.y ||
156
241
  erased.some(rect => overlaps(rect, next)))
157
242
  this.repaint.add(node);
158
243
  }
159
- if (erased.length === 0)
160
- return { erase: '', baseline: previous };
244
+ if (erased.length === 0) {
245
+ return coverErase === ''
246
+ ? { erase: '', baseline: previous }
247
+ : { erase: coverErase + '\x1b[H', baseline: previous };
248
+ }
161
249
  const cells = previous.cells.slice();
162
250
  const baseline = { ...previous, cells, cells64: new BigInt64Array(cells.buffer), noSelect: previous.noSelect.slice() };
163
251
  let erase = '';
164
252
  for (const rect of erased) {
165
253
  clearRegion(baseline, rect.x, rect.y, rect.columns, rect.rows);
254
+ // Erase at the terminal default, never at the placement's surface color:
255
+ // clearRegion just reset these cells to a default blank in the model, so
256
+ // the frame writes back everything it has to show and leaves the rest
257
+ // alone — an erase filled with the surface color would keep that color
258
+ // on screen with no write to correct it (the white card of a closed
259
+ // light-theme preview, cleared only when unrelated text scrolls past).
166
260
  erase += this.erase(rect);
167
261
  }
168
- return { erase: erase + '\x1b[H', baseline };
262
+ return { erase: coverErase + erase + '\x1b[H', baseline };
169
263
  }
170
264
  /** Unchanged images have zero transport cost, including unrelated text ticks. */
171
265
  paint(diff) {
@@ -181,6 +275,10 @@ export class SixelGraphicsManager {
181
275
  draw += `\x1b[${next.y + 1};${next.x + 1}H${next.raster.data}`;
182
276
  }
183
277
  this.displayed = new Map(this.nextDisplay);
278
+ // Rects held this frame stay on screen: they were neither erased nor
279
+ // redrawn, so the terminal still shows them.
280
+ for (const [node, entry] of this.held)
281
+ this.displayed.set(node, entry);
184
282
  this.dirty = false;
185
283
  if (draw === '')
186
284
  return '';
@@ -191,6 +289,7 @@ export class SixelGraphicsManager {
191
289
  const output = [...this.displayed.values()].map(rect => this.erase(rect)).join('');
192
290
  this.displayed.clear();
193
291
  this.nextDisplay.clear();
292
+ this.held.clear();
194
293
  this.frame.clear();
195
294
  this.desired.clear();
196
295
  this.pending = [];
@@ -208,21 +307,34 @@ export class SixelGraphicsManager {
208
307
  this.workerKeys.clear();
209
308
  return output;
210
309
  }
211
- erase(rect) {
310
+ erase(rect, background) {
212
311
  const columns = Math.min(rect.columns, this.columns - rect.x);
213
312
  const rows = Math.min(rect.rows, this.rows - rect.y);
214
313
  if (columns <= 0 || rows <= 0)
215
314
  return '';
216
- let data = '\x1b[0m';
315
+ // Erase-Character fills with the cell's current background and is the only
316
+ // way to drop pixels a frame never rewrites. The surface color is for the
317
+ // one rect the frame cannot correct afterwards — cells a later paint
318
+ // covers while keeping their style (see reconcile); every caller that
319
+ // hands those cells back to the diff passes no color.
320
+ // The placement carries the raw color string; Color is its renderer form.
321
+ const open = background === undefined ? '' : backgroundOpenCode(background);
322
+ let data = `\x1b[0m${open}`;
217
323
  for (let y = rect.y; y < rect.y + rows; y++)
218
324
  data += `\x1b[${y + 1};${rect.x + 1}H\x1b[${columns}X`;
325
+ if (open !== '')
326
+ data += '\x1b[0m';
219
327
  return data;
220
328
  }
221
329
  isUncovered(rect, screen) {
330
+ // Any visible glyph under the raster blocks it; background-only cells do
331
+ // not. Image-owned cells carry the surface background in Sixel mode (an
332
+ // opaque backing is required there), and comparing against the empty
333
+ // style would reject the raster's own backing.
222
334
  for (let y = rect.y; y < rect.y + rect.rows; y++) {
223
335
  for (let x = rect.x; x < rect.x + rect.columns; x++) {
224
336
  const cell = cellAt(screen, x, y);
225
- if (cell && (cell.char !== ' ' || cell.styleId !== screen.emptyStyleId))
337
+ if (cell && cell.char !== ' ' && cell.char !== '')
226
338
  return false;
227
339
  }
228
340
  }
@@ -1,4 +1,5 @@
1
1
  import type { DOMElement } from './dom.js';
2
+ import type { Rectangle } from './layout/geometry.js';
2
3
  /** Hard bounds for one decoded image admitted to the terminal renderer. */
3
4
  export declare const TERMINAL_IMAGE_MAX_EDGE = 1024;
4
5
  export declare const TERMINAL_IMAGE_MAX_BYTES: number;
@@ -60,8 +61,24 @@ export interface TerminalImagePlacement {
60
61
  readonly background?: string;
61
62
  /** False while a protocol-specific raster is pending or unavailable. */
62
63
  readonly graphicsReady?: boolean;
63
- /** Later paint operations cover this backing, including blank overlays. */
64
+ /** Later paint operations cover part of this raster's visible cells. */
64
65
  readonly occluded?: boolean;
66
+ /**
67
+ * Later paint operations cover every visible cell of this raster, so it is
68
+ * unreachable and must be erased rather than kept (a blank `opaque` overlay
69
+ * would otherwise be pierced by the pixels beneath it). Partial coverage
70
+ * sets {@link occluded} only.
71
+ */
72
+ readonly occludedFully?: boolean;
73
+ /**
74
+ * The later paint operations that cover PART of this raster, in absolute
75
+ * screen cells. A protocol whose pixels outlive cell writes must erase these
76
+ * rects itself: with Sixel, a background-only space cell over the raster has
77
+ * the same style as the image's own backing cell, so the frame diff emits no
78
+ * write for it and the old pixels keep showing through the overlay (the white
79
+ * block between "100%" and "原像素" in a preview tooltip).
80
+ */
81
+ readonly coveredRects?: readonly Rectangle[];
65
82
  }
66
83
  /**
67
84
  * Normalize terminal-reported cell pixels before they reach allocation math.
@@ -1 +1 @@
1
- {"version":3,"file":"terminal-image.d.ts","sourceRoot":"","sources":["../../../src/ink/terminal-image.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"terminal-image.d.ts","sourceRoot":"","sources":["../../../src/ink/terminal-image.ts"],"names":[],"mappings":"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"}
@@ -32,6 +32,14 @@ export declare function isProgressReportingAvailable(): boolean;
32
32
  * @returns true when the process runs in a JetBrains IDE terminal.
33
33
  */
34
34
  export declare function isJetBrainsIdeTerminal(): boolean;
35
+ /**
36
+ * Detects zellij, which multiplexes the outer terminal instead of emulating
37
+ * it. Capability probes therefore describe the *outer* terminal — zellij even
38
+ * forwards KITTY_WINDOW_ID into the pane, so kitty detection matches — while
39
+ * zellij's own grid decides how control sequences actually apply.
40
+ * @returns true when the process runs inside a zellij pane.
41
+ */
42
+ export declare function isZellij(): boolean;
35
43
  /**
36
44
  * Checks if the terminal supports DEC mode 2026 (synchronized output).
37
45
  * When supported, BSU/ESU sequences prevent visible flicker during redraws.
@@ -136,6 +144,15 @@ export declare const SYNC_OUTPUT_SUPPORTED: boolean;
136
144
  * as content scrolls (the "JetBrains terminal slowly garbles" bug class).
137
145
  * The diff engine falls back to repainting the shifted rows cell-by-cell,
138
146
  * which every terminal renders identically. Disable DECSTBM on JetBrains terminals.
147
+ *
148
+ * zellij is excluded for the same class of reason. Its `CSI T`
149
+ * (grid.rs rotate_scroll_region_up) shifts rows only while the cursor sits
150
+ * inside the scroll region, and its `CSI r` does not home the cursor outside
151
+ * origin mode — but the renderer parks the cursor on the last screen row,
152
+ * below every ScrollBox. The shift is silently dropped while the diff assumes
153
+ * it happened, so stale rows survive every scroll-up frame (tearing, leftover
154
+ * transcript lines). Only DECSTBM is withdrawn: zellij does implement DEC
155
+ * 2026, so BSU/ESU stay on and frames remain atomic.
139
156
  * @returns true when DECSTBM scroll optimization is safe on this terminal.
140
157
  */
141
158
  export declare function isDecstbmSafe(): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../../../src/ink/terminal.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAA;AAKtC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAYtC;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG;IACrB,KAAK,EAAE,SAAS,GAAG,WAAW,GAAG,OAAO,GAAG,eAAe,CAAA;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAED;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,IAAI,OAAO,CAuCtD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,IAAI,OAAO,CAwDvD;AAcD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAEnD;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,IAAI,OAAO,CAGnC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,mBAAmB,IAAI,OAAO,CAE7C;AAyBD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAG9C;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,WAAW,GAAE,MAAM,GAAG,SAAoC,EAC1D,kBAAkB,GAAE,MAAM,GAAG,SAA4C,GACxE,OAAO,CAOT;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,IAAI,OAAO,CAEpD;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,SAAkC,CAAA;AAEpE;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAuBD;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG;IACrB,MAAM,EAAE,QAAQ,CAAA;IAChB,MAAM,EAAE,QAAQ,CAAA;CACjB,CAAA;AAED;;;;;;;GAOG;AACH;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,IAAI,EACV,eAAe,UAAQ,GACtB,MAAM,CAwFR;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,IAAI,EACV,eAAe,UAAQ,GACtB,IAAI,CAMN"}
1
+ {"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../../../src/ink/terminal.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAA;AAKtC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAYtC;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG;IACrB,KAAK,EAAE,SAAS,GAAG,WAAW,GAAG,OAAO,GAAG,eAAe,CAAA;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAED;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,IAAI,OAAO,CAuCtD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,IAAI,OAAO,CAElC;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,IAAI,OAAO,CAwDvD;AAcD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAEnD;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,IAAI,OAAO,CAGnC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,mBAAmB,IAAI,OAAO,CAE7C;AAyBD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAG9C;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,WAAW,GAAE,MAAM,GAAG,SAAoC,EAC1D,kBAAkB,GAAE,MAAM,GAAG,SAA4C,GACxE,OAAO,CAOT;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,IAAI,OAAO,CAEpD;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,SAAkC,CAAA;AAEpE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAuBD;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG;IACrB,MAAM,EAAE,QAAQ,CAAA;IAChB,MAAM,EAAE,QAAQ,CAAA;CACjB,CAAA;AAED;;;;;;;GAOG;AACH;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,IAAI,EACV,eAAe,UAAQ,GACtB,MAAM,CAwFR;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,IAAI,EACV,eAAe,UAAQ,GACtB,IAAI,CAMN"}