@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
@@ -38,7 +38,7 @@ import { applyPositionedHighlight, scanPositions } from './render-to-screen.js';
38
38
  import createRenderer from './renderer.js';
39
39
  import { CharPool, cellAt, createScreen, HyperlinkPool, isEmptyCellAt, migrateScreenPools, StylePool } from './screen.js';
40
40
  import { applySearchHighlight } from './transcript-highlight.js';
41
- import { applySelectionOverlay, captureScrolledRows, clearSelection, createSelectionState, extendSelection, findPlainTextUrlAt, getSelectedText, hasSelection, moveFocus, pickFollowForSelection, selectLineAt, selectWordAt, shiftSelection, shiftSelectionForFollow, shiftSelectionForViewportResize, shiftSelectionForViewportTranslation, startSelection, updateSelection } from './selection.js';
41
+ import { applySelectionOverlay, captureScrolledRows, clearSelection, createSelectionState, extendSelection, findPlainTextUrlAt, getSelectedText, hasSelection, moveFocus, pickFollowForSelection, refreshSelectionFingerprint, selectLineAt, selectWordAt, shiftSelection, shiftSelectionForFollow, shiftSelectionForViewportResize, shiftSelectionForViewportTranslation, startSelection, updateSelection } from './selection.js';
42
42
  import { isDecstbmSafe, SYNC_OUTPUT_SUPPORTED, serializeDiff, supportsDecrqmProbe, supportsExtendedKeys, supportsWin32InputMode, writeDiffToTerminal } from './terminal.js';
43
43
  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';
44
44
  import { DBP, DFE, DISABLE_MOUSE_TRACKING, ENABLE_MOUSE_TRACKING, ENTER_ALT_SCREEN, EXIT_ALT_SCREEN, SHOW_CURSOR } from './termio/dec.js';
@@ -674,9 +674,20 @@ export default class Ink {
674
674
  altScreen: this.altScreenActive,
675
675
  terminalImages: this.altScreenActive && (this.kittyGraphicsSupported || this.sixelGraphicsSupported),
676
676
  imageReady: sixelActive ? this.sixelGraphicsManager.prepare : undefined,
677
+ // Sixel paints rasters over the cells: an image's own cells must then
678
+ // carry the surface background instead of terminal-default blanks.
679
+ opaqueImageBacking: sixelActive,
677
680
  prevFrameContaminated: this.prevFrameContaminated
678
681
  });
679
682
  const rendererMs = performance.now() - renderStart;
683
+ // Whether THIS frame ran a selection-coordinate translation (viewport
684
+ // resize/follow-shift). A covered-rows fingerprint change in such a
685
+ // frame is the coordinated kind — content moved WITH the highlight.
686
+ // An UNcoordinated change means the rows under a stationary highlight
687
+ // were replaced in place (streaming transcript overwrite), and the
688
+ // selection is marked stale so commit-time copy refuses (see
689
+ // refreshSelectionFingerprint below the overlay block).
690
+ let selectionCoordinated = false;
680
691
  this.maybeProbeKittyGraphics(frame.images ?? []);
681
692
  // Viewport-shrink translation (companion to the follow block below):
682
693
  // chrome mounting around a ScrollBox (the new-messages pill, the sticky
@@ -706,12 +717,14 @@ export default class Ink {
706
717
  const hadSelection = hasSelection(this.selection);
707
718
  if (resize.kind === 'translate') {
708
719
  const cleared = shiftSelectionForViewportTranslation(this.selection, resize.rowDelta, resize.prevTop, resize.prevBottom, resize.top, resize.bottom);
720
+ selectionCoordinated = true;
709
721
  if (cleared)
710
722
  for (const cb of this.selectionListeners)
711
723
  cb();
712
724
  }
713
725
  else {
714
726
  shiftSelectionForViewportResize(this.selection, this.frontFrame.screen, resize.prevTop, resize.prevBottom, resize.top, resize.bottom);
727
+ selectionCoordinated = true;
715
728
  // Both-ends-covered clear must notify React-land so useHasSelection
716
729
  // re-renders and the footer copy/escape hint disappears — direct
717
730
  // listener fire (notifySelectionChange would re-enter onRender).
@@ -775,6 +788,7 @@ export default class Ink {
775
788
  // allowClear=false: both ends clamp to the edge; the ghost guard
776
789
  // runs at release via dragBounds above.
777
790
  shiftSelectionForFollow(this.selection, shift, viewportTop, viewportBottom, false);
791
+ selectionCoordinated = true;
778
792
  }
779
793
  else if (
780
794
  // Flag-3 guard: the anchor check above only proves ONE endpoint is
@@ -794,6 +808,7 @@ export default class Ink {
794
808
  captureScrolledRows(this.selection, this.frontFrame.screen, firstRow, lastRow, side, follow.screenRowOffset);
795
809
  }
796
810
  const cleared = shiftSelectionForFollow(this.selection, shift, viewportTop, viewportBottom);
811
+ selectionCoordinated = true;
797
812
  // Auto-clear (both ends overshot an edge — off the top via
798
813
  // follow/wheel-down, off the bottom via wheel-up) must notify
799
814
  // React-land so useHasSelection re-renders and the footer
@@ -832,6 +847,13 @@ export default class Ink {
832
847
  if (selActive) {
833
848
  applySelectionOverlay(frame.screen, this.selection, this.stylePool);
834
849
  }
850
+ // Commit-consistency guard: hash the rows under the highlight on the
851
+ // frame the copy would actually read. An uncoordinated change since
852
+ // the previous frame latches selection.stale; copySelectionNoClear
853
+ // then refuses rather than shipping whatever text now occupies the
854
+ // highlight coordinates. Runs on frame.screen (post-render, pre-swap)
855
+ // with this frame's coordinated selection coordinates.
856
+ refreshSelectionFingerprint(this.selection, frame.screen, selectionCoordinated);
835
857
  // Scan-highlight: inverse on ALL visible matches (less/vim style).
836
858
  // Position-highlight (below) overlays CURRENT (yellow) on top.
837
859
  hlActive = applySearchHighlight(frame.screen, this.searchHighlightQuery, this.stylePool);
@@ -1910,6 +1932,19 @@ export default class Ink {
1910
1932
  copySelectionNoClear() {
1911
1933
  if (!hasSelection(this.selection))
1912
1934
  return '';
1935
+ // Commit-consistency guard: the rows under the highlight changed
1936
+ // without follow coordination during the selection's lifetime, so
1937
+ // these coordinates now hold text the user never highlighted.
1938
+ // Shipping it would copy visibly wrong content (the "mojibake-looking"
1939
+ // paste of another line); refuse, clear the stale highlight, and let
1940
+ // the caller surface it (React callers enter through copySelection /
1941
+ // useCopyOnSelect's onRefused — the direct no-clear entry must not
1942
+ // leave the misleading highlight up either).
1943
+ if (this.selection.stale) {
1944
+ clearSelection(this.selection);
1945
+ this.notifySelectionChange();
1946
+ return '';
1947
+ }
1913
1948
  const text = getSelectedText(this.selection, this.frontFrame.screen);
1914
1949
  if (text) {
1915
1950
  // Raw OSC 52, or DCS-passthrough-wrapped OSC 52 inside tmux (tmux
@@ -73,6 +73,14 @@ type Options = {
73
73
  /** Paint image fallbacks as blank backing cells and collect placements. */
74
74
  terminalImages?: boolean;
75
75
  imageReady?: (placement: TerminalImagePlacement) => boolean;
76
+ /**
77
+ * True when the active protocol paints rasters OVER the cell grid (Sixel)
78
+ * instead of behind it (Kitty). Image-owned cells must then carry the
79
+ * surface background: the raster covers them anyway, and terminal-default
80
+ * blanks would show as black holes wherever a raster fails to cover a cell
81
+ * (aspect rounding, suppressed/occluded placements).
82
+ */
83
+ opaqueImageBacking?: boolean;
76
84
  /** Image requests from the diff baseline, reused by clean subtree blits. */
77
85
  previousImages?: readonly TerminalImagePlacement[];
78
86
  };
@@ -161,6 +169,7 @@ export default class Output {
161
169
  private readonly stylePool;
162
170
  private screen;
163
171
  terminalImagesEnabled: boolean;
172
+ opaqueImageBacking: boolean;
164
173
  private imageReady;
165
174
  private readonly operations;
166
175
  private readonly imagePlacements;
@@ -201,7 +210,7 @@ export default class Output {
201
210
  * @param height - the new screen height in rows.
202
211
  * @param screen - the screen buffer to render into.
203
212
  */
204
- reset(width: number, height: number, screen: Screen, terminalImages?: boolean, previousImages?: readonly TerminalImagePlacement[], imageReady?: (placement: TerminalImagePlacement) => boolean): void;
213
+ reset(width: number, height: number, screen: Screen, terminalImages?: boolean, previousImages?: readonly TerminalImagePlacement[], imageReady?: (placement: TerminalImagePlacement) => boolean, opaqueImageBacking?: boolean): void;
205
214
  /**
206
215
  * Copy cells from a source screen region (blit = block image transfer).
207
216
  * @param src - the source screen.
@@ -1 +1 @@
1
- {"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../../../src/ink/output.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../../../src/ink/output.ts"],"names":[],"mappings":"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"}
@@ -274,6 +274,81 @@ function buildClusteredChars(line, stylePool, slot) {
274
274
  updateStreamingSlot(slot, line, clustered, chars);
275
275
  return clustered;
276
276
  }
277
+ /**
278
+ * The screen rect a paint operation covers, when it can occlude a raster
279
+ * painted earlier in the same frame. `clip`/`unclip`/`noSelect` paint nothing.
280
+ */
281
+ function imageOcclusionRect(op, width) {
282
+ if (op.type === 'write') {
283
+ return { x: op.x, y: op.y, width: widestLine(op.text), height: op.lines?.length ?? op.text.split('\n').length };
284
+ }
285
+ if (op.type === 'blit')
286
+ return op;
287
+ if (op.type === 'clear' || op.type === 'shade')
288
+ return op.region;
289
+ if (op.type === 'shift')
290
+ return { x: 0, y: op.top, width, height: op.bottom - op.top + 1 };
291
+ return undefined;
292
+ }
293
+ /**
294
+ * The union of two rects, but only when that union covers no cell the pair did
295
+ * not already cover between them. Two rects qualify when one contains the
296
+ * other, or when they are bands sharing a full edge span (same x-span and
297
+ * abutting/overlapping rows, or the transpose) — exactly the pairs whose union
298
+ * is itself a rectangle. An L-shaped pair does not qualify: the bounding box of
299
+ * its two arms covers cells neither arm covers, and the caller would erase
300
+ * pixels that are still visible.
301
+ * @param a - the first rectangle.
302
+ * @param b - the second rectangle.
303
+ * @returns their union, or undefined when folding them would add cells.
304
+ */
305
+ function exactUnion(a, b) {
306
+ const contains = (outer, inner) => outer.x <= inner.x && outer.y <= inner.y &&
307
+ outer.x + outer.width >= inner.x + inner.width &&
308
+ outer.y + outer.height >= inner.y + inner.height;
309
+ if (contains(a, b))
310
+ return a;
311
+ if (contains(b, a))
312
+ return b;
313
+ const sameColumns = a.x === b.x && a.width === b.width &&
314
+ a.y <= b.y + b.height && b.y <= a.y + a.height;
315
+ const sameRows = a.y === b.y && a.height === b.height &&
316
+ a.x <= b.x + b.width && b.x <= a.x + a.width;
317
+ return sameColumns || sameRows ? unionRect(a, b) : undefined;
318
+ }
319
+ /**
320
+ * Fold occluder rects that can be folded without covering anything new, so an
321
+ * image protocol that has to erase them (Sixel) does not repeat the same cells.
322
+ * One overlay usually contributes several rects for the same area (interior
323
+ * fill, border row, text row), and an overlay that covers several placements
324
+ * would otherwise erase the shared cells once per placement.
325
+ *
326
+ * Folding stops at exact unions on purpose. The erasure has to match the cells
327
+ * an overlay really covers: merging any two intersecting rects into their
328
+ * bounding box would take down the pixels beside an L-shaped cover, which are
329
+ * still on screen.
330
+ */
331
+ function mergeOcclusionRects(rects) {
332
+ const merged = [];
333
+ for (const rect of rects) {
334
+ let current = rect;
335
+ let joined = true;
336
+ while (joined) {
337
+ joined = false;
338
+ for (let index = merged.length - 1; index >= 0; index--) {
339
+ const other = merged[index];
340
+ const union = exactUnion(current, other);
341
+ if (union === undefined)
342
+ continue;
343
+ current = union;
344
+ merged.splice(index, 1);
345
+ joined = true;
346
+ }
347
+ }
348
+ merged.push(current);
349
+ }
350
+ return merged;
351
+ }
277
352
  /**
278
353
  * Collects write/blit/clear/clip operations from the render tree, then
279
354
  * applies them to a Screen buffer in get(). The Screen is what gets
@@ -287,6 +362,7 @@ export default class Output {
287
362
  stylePool;
288
363
  screen;
289
364
  terminalImagesEnabled;
365
+ opaqueImageBacking;
290
366
  imageReady;
291
367
  operations = [];
292
368
  imagePlacements = [];
@@ -345,6 +421,7 @@ export default class Output {
345
421
  this.stylePool = stylePool;
346
422
  this.screen = screen;
347
423
  this.terminalImagesEnabled = options.terminalImages ?? false;
424
+ this.opaqueImageBacking = options.opaqueImageBacking ?? false;
348
425
  this.imageReady = options.imageReady;
349
426
  this.previousImages = options.previousImages ?? [];
350
427
  resetScreen(screen, width, height);
@@ -359,11 +436,12 @@ export default class Output {
359
436
  * @param height - the new screen height in rows.
360
437
  * @param screen - the screen buffer to render into.
361
438
  */
362
- reset(width, height, screen, terminalImages = false, previousImages = [], imageReady) {
439
+ reset(width, height, screen, terminalImages = false, previousImages = [], imageReady, opaqueImageBacking = false) {
363
440
  this.width = width;
364
441
  this.height = height;
365
442
  this.screen = screen;
366
443
  this.terminalImagesEnabled = terminalImages;
444
+ this.opaqueImageBacking = opaqueImageBacking;
367
445
  this.imageReady = imageReady;
368
446
  this.previousImages = previousImages;
369
447
  this.operations.length = 0;
@@ -535,22 +613,36 @@ export default class Output {
535
613
  if (end === undefined)
536
614
  return placement;
537
615
  const visible = placement.clip ?? placement;
538
- const overlaps = (rect) => rect.x < visible.x + visible.columns &&
539
- rect.x + rect.width > visible.x && rect.y < visible.y + visible.rows &&
540
- rect.y + rect.height > visible.y;
541
- const occluded = this.operations.slice(end).some(op => {
542
- if (op.type === 'write')
543
- return overlaps({ x: op.x, y: op.y,
544
- width: widestLine(op.text), height: op.lines?.length ?? op.text.split('\n').length });
545
- if (op.type === 'blit')
546
- return overlaps(op);
547
- if (op.type === 'clear' || op.type === 'shade')
548
- return overlaps(op.region);
549
- if (op.type === 'shift')
550
- return overlaps({ x: 0, y: op.top, width: this.width, height: op.bottom - op.top + 1 });
551
- return false;
552
- });
553
- return { ...placement, occluded };
616
+ const right = visible.x + visible.columns;
617
+ const bottom = visible.y + visible.rows;
618
+ const overlaps = (rect) => rect.x < right &&
619
+ rect.x + rect.width > visible.x && rect.y < bottom && rect.y + rect.height > visible.y;
620
+ // A later paint that covers the whole visible rect makes the raster
621
+ // unreachable and erasing is the only correct outcome (a blank `opaque`
622
+ // overlay would otherwise be pierced by the pixels beneath it). A paint
623
+ // that covers only part of it must not take the rest of the raster down
624
+ // with it — SixelGraphicsManager keeps those pixels instead.
625
+ const covers = (rect) => rect.x <= visible.x && rect.y <= visible.y &&
626
+ rect.x + rect.width >= right && rect.y + rect.height >= bottom;
627
+ let occluded = false;
628
+ let covered = false;
629
+ const occluders = [];
630
+ for (const op of this.operations.slice(end)) {
631
+ const rect = imageOcclusionRect(op, this.width);
632
+ if (rect === undefined || !overlaps(rect))
633
+ continue;
634
+ occluded = true;
635
+ if (covers(rect)) {
636
+ covered = true;
637
+ break;
638
+ }
639
+ occluders.push(rect);
640
+ }
641
+ if (!occluded)
642
+ return placement;
643
+ if (covered)
644
+ return { ...placement, occluded, occludedFully: true };
645
+ return { ...placement, occluded, coveredRects: mergeOcclusionRects(occluders) };
554
646
  });
555
647
  }
556
648
  /** Pixel ownership is determined by paint order, not just final text values. */
@@ -1,6 +1,7 @@
1
1
  import { type Boxes, type BoxStyle } from 'cli-boxes';
2
2
  import type { DOMNode } from './dom.js';
3
3
  import type Output from './output.js';
4
+ import type { Color } from './styles.js';
4
5
  /** Options for embedding a text label into a border line. */
5
6
  export type BorderTextOptions = {
6
7
  content: string;
@@ -30,7 +31,11 @@ export type BorderStyle = keyof Boxes | keyof typeof CUSTOM_BORDER_STYLES | BoxS
30
31
  * @param y - the border's top row.
31
32
  * @param node - the node whose border style to render.
32
33
  * @param output - the output buffer receiving the border writes.
34
+ * @param background - the surface color the border's cells belong to, when the
35
+ * node paints its own rect (own background, `opaque`, or an occlusion color
36
+ * while a terminal image sits behind it). Transparent bordered overlays pass
37
+ * undefined and keep terminal-transparent border cells.
33
38
  */
34
- declare const renderBorder: (x: number, y: number, node: DOMNode, output: Output) => void;
39
+ declare const renderBorder: (x: number, y: number, node: DOMNode, output: Output, background?: Color) => void;
35
40
  export default renderBorder;
36
41
  //# sourceMappingURL=render-border.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"render-border.d.ts","sourceRoot":"","sources":["../../../src/ink/render-border.ts"],"names":[],"mappings":"AACA,OAAiB,EAAE,KAAK,KAAK,EAAE,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAA;AAE/D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAA;AACvC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAIrC,6DAA6D;AAC7D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,KAAK,GAAG,QAAQ,CAAA;IAC1B,KAAK,EAAE,OAAO,GAAG,KAAK,GAAG,QAAQ,CAAA;IACjC,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,iEAAiE;AACjE,eAAO,MAAM,oBAAoB;;;;;;;;;;;CAYvB,CAAA;AAEV,qFAAqF;AACrF,MAAM,MAAM,WAAW,GACnB,MAAM,KAAK,GACX,MAAM,OAAO,oBAAoB,GACjC,QAAQ,CAAA;AAiDZ;;;;;;;GAOG;AACH,QAAA,MAAM,YAAY,GAChB,GAAG,MAAM,EACT,GAAG,MAAM,EACT,MAAM,OAAO,EACb,QAAQ,MAAM,KACb,IA8IF,CAAA;AAED,eAAe,YAAY,CAAA"}
1
+ {"version":3,"file":"render-border.d.ts","sourceRoot":"","sources":["../../../src/ink/render-border.ts"],"names":[],"mappings":"AACA,OAAiB,EAAE,KAAK,KAAK,EAAE,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAA;AAE/D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAA;AACvC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAErC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAExC,6DAA6D;AAC7D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,KAAK,GAAG,QAAQ,CAAA;IAC1B,KAAK,EAAE,OAAO,GAAG,KAAK,GAAG,QAAQ,CAAA;IACjC,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,iEAAiE;AACjE,eAAO,MAAM,oBAAoB;;;;;;;;;;;CAYvB,CAAA;AAEV,qFAAqF;AACrF,MAAM,MAAM,WAAW,GACnB,MAAM,KAAK,GACX,MAAM,OAAO,oBAAoB,GACjC,QAAQ,CAAA;AA0DZ;;;;;;;;;;;GAWG;AACH,QAAA,MAAM,YAAY,GAChB,GAAG,MAAM,EACT,GAAG,MAAM,EACT,MAAM,OAAO,EACb,QAAQ,MAAM,EACd,aAAa,KAAK,KACjB,IAqJF,CAAA;AAED,eAAe,YAAY,CAAA"}
@@ -1,6 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import cliBoxes from 'cli-boxes';
3
- import { applyColor } from './colorize.js';
3
+ import { applyColor, applyTextStyles } from './colorize.js';
4
4
  import { stringWidth } from './stringWidth.js';
5
5
  /** Built-in border styles beyond those provided by cli-boxes. */
6
6
  export const CUSTOM_BORDER_STYLES = {
@@ -40,11 +40,19 @@ function embedTextInBorder(borderLine, text, align, offset = 0, borderChar) {
40
40
  borderLine.substring(borderLength - 1);
41
41
  return [before, text, after];
42
42
  }
43
- function styleBorderLine(line, color, dim) {
43
+ function styleBorderLine(line, color, dim, background) {
44
44
  let styled = applyColor(line, color);
45
45
  if (dim) {
46
46
  styled = chalk.dim(styled);
47
47
  }
48
+ // The cells a border occupies belong to the surface it frames. Without an
49
+ // explicit background they diff back to the terminal default, which reads
50
+ // as a black frame around any bordered surface sitting on a colored one (a
51
+ // tooltip over the preview card, that card over a tool card) and as a black
52
+ // hole wherever a Sixel raster is exposed by a border row or column.
53
+ if (background !== undefined) {
54
+ styled = applyTextStyles(styled, { backgroundColor: background });
55
+ }
48
56
  return styled;
49
57
  }
50
58
  /**
@@ -54,8 +62,12 @@ function styleBorderLine(line, color, dim) {
54
62
  * @param y - the border's top row.
55
63
  * @param node - the node whose border style to render.
56
64
  * @param output - the output buffer receiving the border writes.
65
+ * @param background - the surface color the border's cells belong to, when the
66
+ * node paints its own rect (own background, `opaque`, or an occlusion color
67
+ * while a terminal image sits behind it). Transparent bordered overlays pass
68
+ * undefined and keep terminal-transparent border cells.
57
69
  */
58
- const renderBorder = (x, y, node, output) => {
70
+ const renderBorder = (x, y, node, output, background) => {
59
71
  if (node.style.borderStyle) {
60
72
  const width = Math.floor(node.yogaNode.getComputedWidth());
61
73
  const height = Math.floor(node.yogaNode.getComputedHeight());
@@ -85,12 +97,12 @@ const renderBorder = (x, y, node, output) => {
85
97
  if (showTopBorder && node.style.borderText?.position === 'top') {
86
98
  const [before, text, after] = embedTextInBorder(topBorderLine, node.style.borderText.content, node.style.borderText.align, node.style.borderText.offset, box.top);
87
99
  topBorder =
88
- styleBorderLine(before, topBorderColor, dimTopBorderColor) +
100
+ styleBorderLine(before, topBorderColor, dimTopBorderColor, background) +
89
101
  text +
90
- styleBorderLine(after, topBorderColor, dimTopBorderColor);
102
+ styleBorderLine(after, topBorderColor, dimTopBorderColor, background);
91
103
  }
92
104
  else if (showTopBorder) {
93
- topBorder = styleBorderLine(topBorderLine, topBorderColor, dimTopBorderColor);
105
+ topBorder = styleBorderLine(topBorderLine, topBorderColor, dimTopBorderColor, background);
94
106
  }
95
107
  let verticalBorderHeight = height;
96
108
  if (showTopBorder) {
@@ -108,6 +120,10 @@ const renderBorder = (x, y, node, output) => {
108
120
  if (dimRightBorderColor) {
109
121
  rightBorder = chalk.dim(rightBorder);
110
122
  }
123
+ if (background !== undefined) {
124
+ leftBorder = applyTextStyles(leftBorder, { backgroundColor: background });
125
+ rightBorder = applyTextStyles(rightBorder, { backgroundColor: background });
126
+ }
111
127
  const bottomBorderLine = showBottomBorder
112
128
  ? (showLeftBorder ? box.bottomLeft : '') +
113
129
  box.bottom.repeat(contentWidth) +
@@ -118,12 +134,12 @@ const renderBorder = (x, y, node, output) => {
118
134
  if (showBottomBorder && node.style.borderText?.position === 'bottom') {
119
135
  const [before, text, after] = embedTextInBorder(bottomBorderLine, node.style.borderText.content, node.style.borderText.align, node.style.borderText.offset, box.bottom);
120
136
  bottomBorder =
121
- styleBorderLine(before, bottomBorderColor, dimBottomBorderColor) +
137
+ styleBorderLine(before, bottomBorderColor, dimBottomBorderColor, background) +
122
138
  text +
123
- styleBorderLine(after, bottomBorderColor, dimBottomBorderColor);
139
+ styleBorderLine(after, bottomBorderColor, dimBottomBorderColor, background);
124
140
  }
125
141
  else if (showBottomBorder) {
126
- bottomBorder = styleBorderLine(bottomBorderLine, bottomBorderColor, dimBottomBorderColor);
142
+ bottomBorder = styleBorderLine(bottomBorderLine, bottomBorderColor, dimBottomBorderColor, background);
127
143
  }
128
144
  const offsetY = showTopBorder ? 1 : 0;
129
145
  if (topBorder) {
@@ -1 +1 @@
1
- {"version":3,"file":"render-node-to-output.d.ts","sourceRoot":"","sources":["../../../src/ink/render-node-to-output.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"render-node-to-output.d.ts","sourceRoot":"","sources":["../../../src/ink/render-node-to-output.ts"],"names":[],"mappings":"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"}
@@ -787,10 +787,23 @@ function renderNodeToOutput(node, output, options) {
787
787
  // rect, though, and those non-default-background cells would also hide
788
788
  // the image itself. Replace only the image-owned cells with default-
789
789
  // background spaces; layout and surrounding inherited color stay intact.
790
+ //
791
+ // Sixel paints the raster OVER the cells instead, so its backing may
792
+ // (and must) carry the surface background: the raster covers those
793
+ // cells anyway, and terminal-default blanks would show as black holes
794
+ // wherever the raster misses a cell — aspect rounding at the right
795
+ // edge, or a suppressed placement whose cells the frame no longer
796
+ // repaints.
790
797
  const imageWidth = Math.floor(width);
791
798
  const imageHeight = Math.floor(height);
799
+ const backingColor = output.opaqueImageBacking
800
+ ? node.style.backgroundColor ?? inheritedBackgroundColor
801
+ : undefined;
792
802
  const imageLine = ' '.repeat(imageWidth);
793
- output.write(Math.floor(x), Math.floor(y), Array(imageHeight).fill(imageLine).join('\n'));
803
+ const fillLine = backingColor === undefined
804
+ ? imageLine
805
+ : applyTextStyles(imageLine, { backgroundColor: backingColor });
806
+ output.write(Math.floor(x), Math.floor(y), Array(imageHeight).fill(fillLine).join('\n'));
794
807
  output.imageBacking(node);
795
808
  for (const child of node.childNodes) {
796
809
  if (child.nodeName !== '#text')
@@ -891,10 +904,25 @@ function renderNodeToOutput(node, output, options) {
891
904
  // Capture previous scroll bounds BEFORE overwriting — the at-bottom
892
905
  // follow check compares against last frame's max.
893
906
  const prevScrollHeight = node.scrollHeight ?? scrollHeight;
894
- const prevInnerHeight = node.scrollViewportHeight ?? innerHeight;
907
+ const prevViewportHeight = node.scrollViewportHeight;
908
+ const prevInnerHeight = prevViewportHeight ?? innerHeight;
895
909
  const prevViewportTop = node.scrollViewportTop;
896
910
  node.scrollHeight = scrollHeight;
897
911
  node.scrollViewportHeight = innerHeight;
912
+ // Viewport-height change is the one geometry move no scroll notify
913
+ // covers: chrome above/below the box mounts or unmounts (pill,
914
+ // pinned header, recap row) or the terminal changes row count —
915
+ // the viewport resizes with no scroll delta and no sticky flip,
916
+ // while subscribers that paint from the handle's bounds still hold
917
+ // the previous pass's geometry. Fire only on a real transition
918
+ // between two passes: a first frame (`undefined`) observed no
919
+ // change, and scrollTop/scrollViewportTop motion is excluded on
920
+ // purpose (see DOMElement.onViewportHeightChange). Called in the
921
+ // render pass like onStickyRestore below.
922
+ if (prevViewportHeight !== undefined &&
923
+ prevViewportHeight !== innerHeight) {
924
+ node.onViewportHeightChange?.();
925
+ }
898
926
  // Absolute screen-buffer row where the scrollable area (inside
899
927
  // padding) begins. Exposed via ScrollBoxHandle.getViewportTop() so
900
928
  // drag-to-scroll can detect when the drag leaves the scroll viewport.
@@ -1447,7 +1475,17 @@ function renderNodeToOutput(node, output, options) {
1447
1475
  output.shade({ x: Math.floor(x), y: Math.floor(y), width: Math.floor(width), height: Math.floor(height) });
1448
1476
  }
1449
1477
  const ownBackgroundColor = node.style.backgroundColor ?? occlusionBackground;
1450
- if (ownBackgroundColor || node.style.opaque) {
1478
+ // A Sixel image node owns cells the raster normally paints over. When
1479
+ // no placement is admitted — the modal suppressed graphics, the decode
1480
+ // is still pending, or it failed — those cells must still read as the
1481
+ // enclosing surface: the admitted branch above painted them with the
1482
+ // same inherited color, so leaving them unwritten would diff them back
1483
+ // to terminal-default blanks and punch black holes in the card.
1484
+ const imageSurfaceColor = output.opaqueImageBacking && node.nodeName === 'ink-image'
1485
+ ? ownBackgroundColor ?? inheritedBackgroundColor
1486
+ : undefined;
1487
+ const fillColor = ownBackgroundColor ?? imageSurfaceColor;
1488
+ if (fillColor || node.style.opaque) {
1451
1489
  const borderLeft = yogaNode.getComputedBorder(LayoutEdge.Left);
1452
1490
  const borderRight = yogaNode.getComputedBorder(LayoutEdge.Right);
1453
1491
  const borderTop = yogaNode.getComputedBorder(LayoutEdge.Top);
@@ -1456,8 +1494,8 @@ function renderNodeToOutput(node, output, options) {
1456
1494
  const innerHeight = Math.floor(height) - borderTop - borderBottom;
1457
1495
  if (innerWidth > 0 && innerHeight > 0) {
1458
1496
  const spaces = ' '.repeat(innerWidth);
1459
- const fillLine = ownBackgroundColor
1460
- ? applyTextStyles(spaces, { backgroundColor: ownBackgroundColor })
1497
+ const fillLine = fillColor
1498
+ ? applyTextStyles(spaces, { backgroundColor: fillColor })
1461
1499
  : spaces;
1462
1500
  const fill = Array(innerHeight).fill(fillLine).join('\n');
1463
1501
  output.write(x + borderLeft, y + borderTop, fill);
@@ -1485,7 +1523,11 @@ function renderNodeToOutput(node, output, options) {
1485
1523
  // Render border AFTER children to ensure it's not overwritten by child
1486
1524
  // clearing operations. When a child shrinks, it clears its old area,
1487
1525
  // which may overlap with where the parent's border now is.
1488
- renderBorder(x, y, node, output);
1526
+ // A border's cells belong to the surface it frames: pass the node's
1527
+ // effective background so they never fall back to the terminal default
1528
+ // (a black frame around a colored surface, and a black hole where a
1529
+ // Sixel raster is otherwise exposed by a border row or column).
1530
+ renderBorder(x, y, node, output, node.style.backgroundColor ?? occlusionBackground ?? inheritedBackgroundColor);
1489
1531
  }
1490
1532
  else if (node.nodeName === 'ink-root') {
1491
1533
  renderChildren(node, output, x, y, hasRemovedChild, prevScreen, inheritedBackgroundColor);
@@ -13,6 +13,12 @@ export type RenderOptions = {
13
13
  /** Whether terminal graphics are active for this paint pass. */
14
14
  terminalImages?: boolean;
15
15
  imageReady?: (placement: TerminalImagePlacement) => boolean;
16
+ /**
17
+ * True when rasters paint over the cell grid (Sixel): image-owned cells
18
+ * carry the surface background instead of terminal-default blanks. See
19
+ * Output's `opaqueImageBacking`.
20
+ */
21
+ opaqueImageBacking?: boolean;
16
22
  prevFrameContaminated: boolean;
17
23
  };
18
24
  /** Renders the DOM tree into a frame for the given options. */
@@ -1 +1 @@
1
- {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../../src/ink/renderer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,UAAU,EAAiC,MAAM,UAAU,CAAA;AACzE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAIvC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AAUjE,OAAO,EAAgB,KAAK,SAAS,EAAE,MAAM,aAAa,CAAA;AAE1D,+DAA+D;AAC/D,MAAM,MAAM,aAAa,GAAG;IAC1B,UAAU,EAAE,KAAK,CAAA;IACjB,SAAS,EAAE,KAAK,CAAA;IAChB,KAAK,EAAE,OAAO,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,OAAO,CAAA;IAClB,gEAAgE;IAChE,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,sBAAsB,KAAK,OAAO,CAAA;IAK3D,qBAAqB,EAAE,OAAO,CAAA;CAC/B,CAAA;AAED,+DAA+D;AAC/D,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,EAAE,aAAa,KAAK,KAAK,CAAA;AAExD;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,UAAU,cAAc,CACpC,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE,SAAS,GACnB,QAAQ,CAmMV"}
1
+ {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../../src/ink/renderer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,UAAU,EAAiC,MAAM,UAAU,CAAA;AACzE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAIvC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AAUjE,OAAO,EAAgB,KAAK,SAAS,EAAE,MAAM,aAAa,CAAA;AAE1D,+DAA+D;AAC/D,MAAM,MAAM,aAAa,GAAG;IAC1B,UAAU,EAAE,KAAK,CAAA;IACjB,SAAS,EAAE,KAAK,CAAA;IAChB,KAAK,EAAE,OAAO,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,OAAO,CAAA;IAClB,gEAAgE;IAChE,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,sBAAsB,KAAK,OAAO,CAAA;IAC3D;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAK5B,qBAAqB,EAAE,OAAO,CAAA;CAC/B,CAAA;AAED,+DAA+D;AAC/D,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,EAAE,aAAa,KAAK,KAAK,CAAA;AAExD;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,UAAU,cAAc,CACpC,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE,SAAS,GACnB,QAAQ,CAqMV"}
@@ -75,7 +75,7 @@ export default function createRenderer(node, stylePool) {
75
75
  const screen = backScreen ??
76
76
  createScreen(width, height, stylePool, charPool, hyperlinkPool);
77
77
  if (output) {
78
- output.reset(width, height, screen, options.terminalImages, frontFrame.images, options.imageReady);
78
+ output.reset(width, height, screen, options.terminalImages, frontFrame.images, options.imageReady, options.opaqueImageBacking ?? false);
79
79
  }
80
80
  else {
81
81
  output = new Output({
@@ -86,6 +86,7 @@ export default function createRenderer(node, stylePool) {
86
86
  terminalImages: options.terminalImages,
87
87
  previousImages: frontFrame.images,
88
88
  imageReady: options.imageReady,
89
+ opaqueImageBacking: options.opaqueImageBacking ?? false,
89
90
  });
90
91
  }
91
92
  resetLayoutShifted();