nastech-tui 0.0.1

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 (424) hide show
  1. package/.prettierrc +11 -0
  2. package/README.md +346 -0
  3. package/eslint.config.mjs +111 -0
  4. package/package.json +51 -0
  5. package/packages/nastech-ink/ambient.d.ts +83 -0
  6. package/packages/nastech-ink/index.d.ts +40 -0
  7. package/packages/nastech-ink/index.js +1 -0
  8. package/packages/nastech-ink/nastech-ink/ambient.d.ts +83 -0
  9. package/packages/nastech-ink/nastech-ink/index.d.ts +40 -0
  10. package/packages/nastech-ink/nastech-ink/index.js +1 -0
  11. package/packages/nastech-ink/nastech-ink/package.json +54 -0
  12. package/packages/nastech-ink/nastech-ink/src/bootstrap/state.ts +9 -0
  13. package/packages/nastech-ink/nastech-ink/src/entry-exports.ts +32 -0
  14. package/packages/nastech-ink/nastech-ink/src/hooks/use-stderr.ts +15 -0
  15. package/packages/nastech-ink/nastech-ink/src/hooks/use-stdout.ts +15 -0
  16. package/packages/nastech-ink/nastech-ink/src/ink/Ansi.tsx +435 -0
  17. package/packages/nastech-ink/nastech-ink/src/ink/app-mouse.test.ts +123 -0
  18. package/packages/nastech-ink/nastech-ink/src/ink/bidi.ts +145 -0
  19. package/packages/nastech-ink/nastech-ink/src/ink/cache-eviction.ts +45 -0
  20. package/packages/nastech-ink/nastech-ink/src/ink/clearTerminal.ts +68 -0
  21. package/packages/nastech-ink/nastech-ink/src/ink/colorize.test.ts +60 -0
  22. package/packages/nastech-ink/nastech-ink/src/ink/colorize.ts +277 -0
  23. package/packages/nastech-ink/nastech-ink/src/ink/components/AlternateScreen.tsx +133 -0
  24. package/packages/nastech-ink/nastech-ink/src/ink/components/App.tsx +830 -0
  25. package/packages/nastech-ink/nastech-ink/src/ink/components/AppContext.ts +20 -0
  26. package/packages/nastech-ink/nastech-ink/src/ink/components/Box.tsx +294 -0
  27. package/packages/nastech-ink/nastech-ink/src/ink/components/Button.tsx +236 -0
  28. package/packages/nastech-ink/nastech-ink/src/ink/components/ClockContext.tsx +133 -0
  29. package/packages/nastech-ink/nastech-ink/src/ink/components/CursorAdvanceContext.ts +35 -0
  30. package/packages/nastech-ink/nastech-ink/src/ink/components/CursorDeclarationContext.ts +28 -0
  31. package/packages/nastech-ink/nastech-ink/src/ink/components/ErrorOverview.tsx +130 -0
  32. package/packages/nastech-ink/nastech-ink/src/ink/components/Link.tsx +38 -0
  33. package/packages/nastech-ink/nastech-ink/src/ink/components/Newline.tsx +43 -0
  34. package/packages/nastech-ink/nastech-ink/src/ink/components/NoSelect.tsx +73 -0
  35. package/packages/nastech-ink/nastech-ink/src/ink/components/RawAnsi.tsx +61 -0
  36. package/packages/nastech-ink/nastech-ink/src/ink/components/ScrollBox.tsx +290 -0
  37. package/packages/nastech-ink/nastech-ink/src/ink/components/Spacer.tsx +23 -0
  38. package/packages/nastech-ink/nastech-ink/src/ink/components/StdinContext.ts +25 -0
  39. package/packages/nastech-ink/nastech-ink/src/ink/components/TerminalFocusContext.tsx +63 -0
  40. package/packages/nastech-ink/nastech-ink/src/ink/components/TerminalSizeContext.tsx +7 -0
  41. package/packages/nastech-ink/nastech-ink/src/ink/components/Text.test.ts +38 -0
  42. package/packages/nastech-ink/nastech-ink/src/ink/components/Text.tsx +336 -0
  43. package/packages/nastech-ink/nastech-ink/src/ink/constants.ts +6 -0
  44. package/packages/nastech-ink/nastech-ink/src/ink/cursor.ts +5 -0
  45. package/packages/nastech-ink/nastech-ink/src/ink/devtools.ts +2 -0
  46. package/packages/nastech-ink/nastech-ink/src/ink/dom.ts +495 -0
  47. package/packages/nastech-ink/nastech-ink/src/ink/events/click-event.ts +38 -0
  48. package/packages/nastech-ink/nastech-ink/src/ink/events/cmd-shortcuts.test.ts +65 -0
  49. package/packages/nastech-ink/nastech-ink/src/ink/events/dispatcher.ts +242 -0
  50. package/packages/nastech-ink/nastech-ink/src/ink/events/emitter.ts +40 -0
  51. package/packages/nastech-ink/nastech-ink/src/ink/events/event-handlers.ts +84 -0
  52. package/packages/nastech-ink/nastech-ink/src/ink/events/event.ts +11 -0
  53. package/packages/nastech-ink/nastech-ink/src/ink/events/focus-event.ts +18 -0
  54. package/packages/nastech-ink/nastech-ink/src/ink/events/input-event.ts +176 -0
  55. package/packages/nastech-ink/nastech-ink/src/ink/events/keyboard-event.ts +57 -0
  56. package/packages/nastech-ink/nastech-ink/src/ink/events/mouse-event.ts +18 -0
  57. package/packages/nastech-ink/nastech-ink/src/ink/events/paste-event.ts +10 -0
  58. package/packages/nastech-ink/nastech-ink/src/ink/events/resize-event.ts +12 -0
  59. package/packages/nastech-ink/nastech-ink/src/ink/events/terminal-event.ts +107 -0
  60. package/packages/nastech-ink/nastech-ink/src/ink/events/terminal-focus-event.ts +19 -0
  61. package/packages/nastech-ink/nastech-ink/src/ink/focus.ts +219 -0
  62. package/packages/nastech-ink/nastech-ink/src/ink/frame.ts +124 -0
  63. package/packages/nastech-ink/nastech-ink/src/ink/get-max-width.ts +27 -0
  64. package/packages/nastech-ink/nastech-ink/src/ink/global.d.ts +1 -0
  65. package/packages/nastech-ink/nastech-ink/src/ink/hit-test.test.ts +38 -0
  66. package/packages/nastech-ink/nastech-ink/src/ink/hit-test.ts +224 -0
  67. package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-animation-frame.ts +62 -0
  68. package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-app.ts +9 -0
  69. package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-cursor-advance.ts +33 -0
  70. package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-declared-cursor.ts +75 -0
  71. package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-external-process.ts +27 -0
  72. package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-input.ts +95 -0
  73. package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-interval.ts +71 -0
  74. package/packages/nastech-ink/package.json +57 -0
  75. package/packages/nastech-ink/src/bootstrap/state.ts +9 -0
  76. package/packages/nastech-ink/src/entry-exports.ts +32 -0
  77. package/packages/nastech-ink/src/hooks/use-stderr.ts +15 -0
  78. package/packages/nastech-ink/src/hooks/use-stdout.ts +15 -0
  79. package/packages/nastech-ink/src/ink/Ansi.tsx +435 -0
  80. package/packages/nastech-ink/src/ink/app-mouse.test.ts +123 -0
  81. package/packages/nastech-ink/src/ink/app-rawmode-mouse.test.ts +91 -0
  82. package/packages/nastech-ink/src/ink/bidi.ts +145 -0
  83. package/packages/nastech-ink/src/ink/cache-eviction.ts +45 -0
  84. package/packages/nastech-ink/src/ink/clearTerminal.ts +68 -0
  85. package/packages/nastech-ink/src/ink/colorize.test.ts +60 -0
  86. package/packages/nastech-ink/src/ink/colorize.ts +277 -0
  87. package/packages/nastech-ink/src/ink/components/AlternateScreen.tsx +133 -0
  88. package/packages/nastech-ink/src/ink/components/App.tsx +855 -0
  89. package/packages/nastech-ink/src/ink/components/AppContext.ts +20 -0
  90. package/packages/nastech-ink/src/ink/components/Box.tsx +294 -0
  91. package/packages/nastech-ink/src/ink/components/Button.tsx +236 -0
  92. package/packages/nastech-ink/src/ink/components/ClockContext.tsx +133 -0
  93. package/packages/nastech-ink/src/ink/components/CursorAdvanceContext.ts +35 -0
  94. package/packages/nastech-ink/src/ink/components/CursorDeclarationContext.ts +28 -0
  95. package/packages/nastech-ink/src/ink/components/ErrorOverview.tsx +130 -0
  96. package/packages/nastech-ink/src/ink/components/Link.tsx +38 -0
  97. package/packages/nastech-ink/src/ink/components/Newline.tsx +43 -0
  98. package/packages/nastech-ink/src/ink/components/NoSelect.tsx +73 -0
  99. package/packages/nastech-ink/src/ink/components/RawAnsi.tsx +61 -0
  100. package/packages/nastech-ink/src/ink/components/ScrollBox.tsx +290 -0
  101. package/packages/nastech-ink/src/ink/components/Spacer.tsx +23 -0
  102. package/packages/nastech-ink/src/ink/components/StdinContext.ts +25 -0
  103. package/packages/nastech-ink/src/ink/components/TerminalFocusContext.tsx +63 -0
  104. package/packages/nastech-ink/src/ink/components/TerminalSizeContext.tsx +7 -0
  105. package/packages/nastech-ink/src/ink/components/Text.test.ts +38 -0
  106. package/packages/nastech-ink/src/ink/components/Text.tsx +336 -0
  107. package/packages/nastech-ink/src/ink/constants.ts +6 -0
  108. package/packages/nastech-ink/src/ink/cursor.ts +5 -0
  109. package/packages/nastech-ink/src/ink/devtools.ts +2 -0
  110. package/packages/nastech-ink/src/ink/dom.ts +495 -0
  111. package/packages/nastech-ink/src/ink/events/click-event.ts +38 -0
  112. package/packages/nastech-ink/src/ink/events/cmd-shortcuts.test.ts +65 -0
  113. package/packages/nastech-ink/src/ink/events/dispatcher.ts +242 -0
  114. package/packages/nastech-ink/src/ink/events/emitter.ts +40 -0
  115. package/packages/nastech-ink/src/ink/events/event-handlers.ts +84 -0
  116. package/packages/nastech-ink/src/ink/events/event.ts +11 -0
  117. package/packages/nastech-ink/src/ink/events/focus-event.ts +18 -0
  118. package/packages/nastech-ink/src/ink/events/input-event.ts +176 -0
  119. package/packages/nastech-ink/src/ink/events/keyboard-event.ts +57 -0
  120. package/packages/nastech-ink/src/ink/events/mouse-event.ts +18 -0
  121. package/packages/nastech-ink/src/ink/events/paste-event.ts +10 -0
  122. package/packages/nastech-ink/src/ink/events/resize-event.ts +12 -0
  123. package/packages/nastech-ink/src/ink/events/terminal-event.ts +107 -0
  124. package/packages/nastech-ink/src/ink/events/terminal-focus-event.ts +19 -0
  125. package/packages/nastech-ink/src/ink/focus.ts +219 -0
  126. package/packages/nastech-ink/src/ink/frame.ts +124 -0
  127. package/packages/nastech-ink/src/ink/get-max-width.ts +27 -0
  128. package/packages/nastech-ink/src/ink/global.d.ts +1 -0
  129. package/packages/nastech-ink/src/ink/hit-test.test.ts +38 -0
  130. package/packages/nastech-ink/src/ink/hit-test.ts +224 -0
  131. package/packages/nastech-ink/src/ink/hooks/use-animation-frame.ts +62 -0
  132. package/packages/nastech-ink/src/ink/hooks/use-app.ts +9 -0
  133. package/packages/nastech-ink/src/ink/hooks/use-cursor-advance.ts +33 -0
  134. package/packages/nastech-ink/src/ink/hooks/use-declared-cursor.ts +75 -0
  135. package/packages/nastech-ink/src/ink/hooks/use-external-process.ts +27 -0
  136. package/packages/nastech-ink/src/ink/hooks/use-input.ts +95 -0
  137. package/packages/nastech-ink/src/ink/hooks/use-interval.ts +71 -0
  138. package/packages/nastech-ink/src/ink/hooks/use-search-highlight.ts +56 -0
  139. package/packages/nastech-ink/src/ink/hooks/use-selection.ts +101 -0
  140. package/packages/nastech-ink/src/ink/hooks/use-stdin.ts +9 -0
  141. package/packages/nastech-ink/src/ink/hooks/use-tab-status.ts +71 -0
  142. package/packages/nastech-ink/src/ink/hooks/use-terminal-focus.ts +18 -0
  143. package/packages/nastech-ink/src/ink/hooks/use-terminal-title.ts +34 -0
  144. package/packages/nastech-ink/src/ink/hooks/use-terminal-viewport.ts +100 -0
  145. package/packages/nastech-ink/src/ink/hyperlinkHover.ts +52 -0
  146. package/packages/nastech-ink/src/ink/ink-cursor-advance.test.ts +234 -0
  147. package/packages/nastech-ink/src/ink/ink-resize.test.ts +50 -0
  148. package/packages/nastech-ink/src/ink/ink.tsx +2705 -0
  149. package/packages/nastech-ink/src/ink/instances.ts +10 -0
  150. package/packages/nastech-ink/src/ink/layout/engine.ts +6 -0
  151. package/packages/nastech-ink/src/ink/layout/geometry.ts +98 -0
  152. package/packages/nastech-ink/src/ink/layout/node.ts +145 -0
  153. package/packages/nastech-ink/src/ink/layout/yoga.ts +313 -0
  154. package/packages/nastech-ink/src/ink/line-width-cache.ts +38 -0
  155. package/packages/nastech-ink/src/ink/log-update.test.ts +223 -0
  156. package/packages/nastech-ink/src/ink/log-update.ts +752 -0
  157. package/packages/nastech-ink/src/ink/lru.ts +14 -0
  158. package/packages/nastech-ink/src/ink/measure-element.ts +23 -0
  159. package/packages/nastech-ink/src/ink/measure-text.ts +50 -0
  160. package/packages/nastech-ink/src/ink/node-cache.ts +53 -0
  161. package/packages/nastech-ink/src/ink/optimizer.ts +99 -0
  162. package/packages/nastech-ink/src/ink/output.ts +845 -0
  163. package/packages/nastech-ink/src/ink/parse-keypress.test.ts +133 -0
  164. package/packages/nastech-ink/src/ink/parse-keypress.ts +848 -0
  165. package/packages/nastech-ink/src/ink/reconciler.ts +382 -0
  166. package/packages/nastech-ink/src/ink/render-border.ts +206 -0
  167. package/packages/nastech-ink/src/ink/render-node-to-output.ts +1582 -0
  168. package/packages/nastech-ink/src/ink/render-to-screen.ts +236 -0
  169. package/packages/nastech-ink/src/ink/renderer.ts +169 -0
  170. package/packages/nastech-ink/src/ink/root.ts +204 -0
  171. package/packages/nastech-ink/src/ink/screen.ts +1590 -0
  172. package/packages/nastech-ink/src/ink/searchHighlight.ts +91 -0
  173. package/packages/nastech-ink/src/ink/selection.test.ts +82 -0
  174. package/packages/nastech-ink/src/ink/selection.ts +1143 -0
  175. package/packages/nastech-ink/src/ink/squash-text-nodes.ts +74 -0
  176. package/packages/nastech-ink/src/ink/stringWidth.ts +341 -0
  177. package/packages/nastech-ink/src/ink/styles.ts +750 -0
  178. package/packages/nastech-ink/src/ink/supports-hyperlinks.ts +51 -0
  179. package/packages/nastech-ink/src/ink/tabstops.ts +44 -0
  180. package/packages/nastech-ink/src/ink/terminal-focus-state.ts +52 -0
  181. package/packages/nastech-ink/src/ink/terminal-querier.ts +222 -0
  182. package/packages/nastech-ink/src/ink/terminal.test.ts +15 -0
  183. package/packages/nastech-ink/src/ink/terminal.ts +299 -0
  184. package/packages/nastech-ink/src/ink/termio/ansi.ts +75 -0
  185. package/packages/nastech-ink/src/ink/termio/csi.ts +334 -0
  186. package/packages/nastech-ink/src/ink/termio/dec.ts +99 -0
  187. package/packages/nastech-ink/src/ink/termio/esc.ts +69 -0
  188. package/packages/nastech-ink/src/ink/termio/osc.test.ts +191 -0
  189. package/packages/nastech-ink/src/ink/termio/osc.ts +724 -0
  190. package/packages/nastech-ink/src/ink/termio/parser.ts +467 -0
  191. package/packages/nastech-ink/src/ink/termio/sgr.ts +362 -0
  192. package/packages/nastech-ink/src/ink/termio/tokenize.test.ts +185 -0
  193. package/packages/nastech-ink/src/ink/termio/tokenize.ts +350 -0
  194. package/packages/nastech-ink/src/ink/termio/types.ts +230 -0
  195. package/packages/nastech-ink/src/ink/termio.ts +42 -0
  196. package/packages/nastech-ink/src/ink/useTerminalNotification.ts +110 -0
  197. package/packages/nastech-ink/src/ink/warn.ts +15 -0
  198. package/packages/nastech-ink/src/ink/widest-line.ts +22 -0
  199. package/packages/nastech-ink/src/ink/wrap-text.test.ts +17 -0
  200. package/packages/nastech-ink/src/ink/wrap-text.ts +144 -0
  201. package/packages/nastech-ink/src/ink/wrapAnsi.ts +13 -0
  202. package/packages/nastech-ink/src/native-ts/yoga-layout/enums.ts +112 -0
  203. package/packages/nastech-ink/src/native-ts/yoga-layout/index.ts +2326 -0
  204. package/packages/nastech-ink/src/utils/debug.ts +6 -0
  205. package/packages/nastech-ink/src/utils/earlyInput.ts +131 -0
  206. package/packages/nastech-ink/src/utils/env.ts +66 -0
  207. package/packages/nastech-ink/src/utils/envUtils.ts +13 -0
  208. package/packages/nastech-ink/src/utils/execFileNoThrow.test.ts +146 -0
  209. package/packages/nastech-ink/src/utils/execFileNoThrow.ts +115 -0
  210. package/packages/nastech-ink/src/utils/fullscreen.ts +3 -0
  211. package/packages/nastech-ink/src/utils/intl.ts +87 -0
  212. package/packages/nastech-ink/src/utils/log.ts +7 -0
  213. package/packages/nastech-ink/src/utils/semver.ts +57 -0
  214. package/packages/nastech-ink/src/utils/sliceAnsi.ts +106 -0
  215. package/packages/nastech-ink/text-input.d.ts +2 -0
  216. package/packages/nastech-ink/text-input.js +1 -0
  217. package/scripts/build.mjs +61 -0
  218. package/scripts/profile-tui.mjs +121 -0
  219. package/src/__tests__/activeSessionSwitcher.test.ts +157 -0
  220. package/src/__tests__/appChromeStatusRule.test.tsx +84 -0
  221. package/src/__tests__/appChromeStatusRuleDevCredits.test.tsx +73 -0
  222. package/src/__tests__/approvalAction.test.ts +50 -0
  223. package/src/__tests__/asCommandDispatch.test.ts +27 -0
  224. package/src/__tests__/blockLayout.test.ts +122 -0
  225. package/src/__tests__/clipboard.test.ts +369 -0
  226. package/src/__tests__/constants.test.ts +53 -0
  227. package/src/__tests__/createGatewayEventHandler.test.ts +1091 -0
  228. package/src/__tests__/createSlashHandler.test.ts +822 -0
  229. package/src/__tests__/creditsCommand.test.ts +144 -0
  230. package/src/__tests__/cursorDriftRegression.test.ts +114 -0
  231. package/src/__tests__/details.test.ts +115 -0
  232. package/src/__tests__/emoji.test.ts +64 -0
  233. package/src/__tests__/externalLink.test.ts +144 -0
  234. package/src/__tests__/forceTruecolor.test.ts +191 -0
  235. package/src/__tests__/gatewayClient.test.ts +394 -0
  236. package/src/__tests__/gatewayRecovery.test.ts +47 -0
  237. package/src/__tests__/markdown.test.ts +331 -0
  238. package/src/__tests__/mathUnicode.test.ts +293 -0
  239. package/src/__tests__/memoryMonitor.test.ts +102 -0
  240. package/src/__tests__/messageLine.test.ts +19 -0
  241. package/src/__tests__/messages.test.ts +92 -0
  242. package/src/__tests__/orchestratorPromptSession.test.ts +64 -0
  243. package/src/__tests__/osc52.test.ts +67 -0
  244. package/src/__tests__/parentLog.test.ts +75 -0
  245. package/src/__tests__/paths.test.ts +70 -0
  246. package/src/__tests__/platform.test.ts +556 -0
  247. package/src/__tests__/precisionWheel.test.ts +44 -0
  248. package/src/__tests__/prompt.test.ts +31 -0
  249. package/src/__tests__/providers.test.ts +65 -0
  250. package/src/__tests__/reasoning.test.ts +76 -0
  251. package/src/__tests__/rpc.test.ts +27 -0
  252. package/src/__tests__/scroll.test.ts +99 -0
  253. package/src/__tests__/slashParity.test.ts +123 -0
  254. package/src/__tests__/spawnHistoryStore.test.ts +46 -0
  255. package/src/__tests__/stateIsolation.test.ts +46 -0
  256. package/src/__tests__/statusBarTicker.test.ts +18 -0
  257. package/src/__tests__/statusRule.test.ts +32 -0
  258. package/src/__tests__/streamingMarkdown.test.ts +121 -0
  259. package/src/__tests__/subagentTree.test.ts +407 -0
  260. package/src/__tests__/syntax.test.ts +45 -0
  261. package/src/__tests__/terminalModes.test.ts +39 -0
  262. package/src/__tests__/terminalParity.test.ts +77 -0
  263. package/src/__tests__/terminalSetup.test.ts +386 -0
  264. package/src/__tests__/termux.test.ts +35 -0
  265. package/src/__tests__/termuxComposerLayout.test.ts +40 -0
  266. package/src/__tests__/text.test.ts +233 -0
  267. package/src/__tests__/textInputBurstInput.test.ts +40 -0
  268. package/src/__tests__/textInputCursorSourceOfTruth.test.ts +50 -0
  269. package/src/__tests__/textInputFastEcho.test.ts +200 -0
  270. package/src/__tests__/textInputLineNav.test.ts +55 -0
  271. package/src/__tests__/textInputPassThrough.test.ts +59 -0
  272. package/src/__tests__/textInputRightClick.test.ts +48 -0
  273. package/src/__tests__/textInputWrap.test.ts +151 -0
  274. package/src/__tests__/theme.test.ts +311 -0
  275. package/src/__tests__/turnControllerNotice.test.ts +43 -0
  276. package/src/__tests__/turnStore.test.ts +66 -0
  277. package/src/__tests__/useCompletion.test.ts +35 -0
  278. package/src/__tests__/useComposerState.test.ts +59 -0
  279. package/src/__tests__/useConfigSync.test.ts +460 -0
  280. package/src/__tests__/useInputHandlers.test.ts +77 -0
  281. package/src/__tests__/useQueue.test.ts +28 -0
  282. package/src/__tests__/useSessionLifecycle.test.ts +60 -0
  283. package/src/__tests__/useVirtualHistoryHeights.test.ts +39 -0
  284. package/src/__tests__/viewport.test.ts +58 -0
  285. package/src/__tests__/viewportStore.test.ts +85 -0
  286. package/src/__tests__/virtualHeights.test.ts +96 -0
  287. package/src/__tests__/virtualHistoryClamp.test.ts +19 -0
  288. package/src/__tests__/virtualHistoryOffsetCache.test.ts +282 -0
  289. package/src/__tests__/wheelAccel.test.ts +138 -0
  290. package/src/app/createGatewayEventHandler.ts +833 -0
  291. package/src/app/createSlashHandler.ts +130 -0
  292. package/src/app/delegationStore.ts +77 -0
  293. package/src/app/gatewayContext.tsx +19 -0
  294. package/src/app/gatewayRecovery.ts +35 -0
  295. package/src/app/inputSelectionStore.ts +15 -0
  296. package/src/app/interfaces.ts +394 -0
  297. package/src/app/overlayStore.ts +53 -0
  298. package/src/app/scroll.ts +71 -0
  299. package/src/app/setupHandoff.ts +54 -0
  300. package/src/app/slash/commands/core.ts +648 -0
  301. package/src/app/slash/commands/credits.ts +57 -0
  302. package/src/app/slash/commands/debug.ts +48 -0
  303. package/src/app/slash/commands/ops.ts +717 -0
  304. package/src/app/slash/commands/session.ts +554 -0
  305. package/src/app/slash/commands/setup.ts +20 -0
  306. package/src/app/slash/registry.ts +20 -0
  307. package/src/app/slash/types.ts +21 -0
  308. package/src/app/spawnHistoryStore.ts +159 -0
  309. package/src/app/turnController.ts +866 -0
  310. package/src/app/turnStore.ts +85 -0
  311. package/src/app/uiStore.ts +44 -0
  312. package/src/app/useComposerState.ts +367 -0
  313. package/src/app/useConfigSync.ts +288 -0
  314. package/src/app/useInputHandlers.ts +576 -0
  315. package/src/app/useLongRunToolCharms.ts +69 -0
  316. package/src/app/useMainApp.ts +1039 -0
  317. package/src/app/useSessionLifecycle.ts +366 -0
  318. package/src/app/useSubmission.ts +429 -0
  319. package/src/app.tsx +25 -0
  320. package/src/banner.ts +93 -0
  321. package/src/components/activeSessionSwitcher.tsx +635 -0
  322. package/src/components/agentsOverlay.tsx +1073 -0
  323. package/src/components/appChrome.tsx +554 -0
  324. package/src/components/appLayout.tsx +444 -0
  325. package/src/components/appOverlays.tsx +254 -0
  326. package/src/components/branding.tsx +466 -0
  327. package/src/components/fpsOverlay.tsx +30 -0
  328. package/src/components/helpHint.tsx +73 -0
  329. package/src/components/markdown.tsx +1119 -0
  330. package/src/components/maskedPrompt.tsx +34 -0
  331. package/src/components/messageLine.tsx +237 -0
  332. package/src/components/modelPicker.tsx +527 -0
  333. package/src/components/overlayControls.tsx +50 -0
  334. package/src/components/pluginsHub.tsx +238 -0
  335. package/src/components/prompts.tsx +276 -0
  336. package/src/components/queuedMessages.tsx +64 -0
  337. package/src/components/sessionPicker.tsx +227 -0
  338. package/src/components/skillsHub.tsx +308 -0
  339. package/src/components/streamingAssistant.tsx +110 -0
  340. package/src/components/streamingMarkdown.tsx +174 -0
  341. package/src/components/textInput.tsx +1340 -0
  342. package/src/components/themed.tsx +30 -0
  343. package/src/components/thinking.tsx +1224 -0
  344. package/src/components/todoPanel.tsx +93 -0
  345. package/src/config/env.ts +64 -0
  346. package/src/config/limits.ts +13 -0
  347. package/src/config/timing.ts +6 -0
  348. package/src/content/charms.ts +1 -0
  349. package/src/content/faces.ts +17 -0
  350. package/src/content/fortunes.ts +30 -0
  351. package/src/content/hotkeys.ts +37 -0
  352. package/src/content/placeholders.ts +13 -0
  353. package/src/content/setup.ts +17 -0
  354. package/src/content/verbs.ts +38 -0
  355. package/src/domain/blockLayout.ts +146 -0
  356. package/src/domain/details.ts +76 -0
  357. package/src/domain/messages.ts +91 -0
  358. package/src/domain/paths.ts +16 -0
  359. package/src/domain/providers.ts +11 -0
  360. package/src/domain/roles.ts +9 -0
  361. package/src/domain/slash.ts +10 -0
  362. package/src/domain/usage.ts +3 -0
  363. package/src/domain/viewport.ts +51 -0
  364. package/src/entry.tsx +104 -0
  365. package/src/gatewayClient.ts +730 -0
  366. package/src/gatewayTypes.ts +568 -0
  367. package/src/hooks/useCompletion.ts +112 -0
  368. package/src/hooks/useGitBranch.ts +72 -0
  369. package/src/hooks/useInputHistory.ts +11 -0
  370. package/src/hooks/useQueue.ts +76 -0
  371. package/src/hooks/useVirtualHistory.ts +554 -0
  372. package/src/lib/circularBuffer.ts +48 -0
  373. package/src/lib/clipboard.ts +182 -0
  374. package/src/lib/editor.test.ts +74 -0
  375. package/src/lib/editor.ts +47 -0
  376. package/src/lib/emoji.ts +55 -0
  377. package/src/lib/externalCli.ts +16 -0
  378. package/src/lib/externalLink.ts +435 -0
  379. package/src/lib/forceTruecolor.ts +60 -0
  380. package/src/lib/fpsStore.ts +51 -0
  381. package/src/lib/fuzzy.test.ts +109 -0
  382. package/src/lib/fuzzy.ts +177 -0
  383. package/src/lib/gracefulExit.ts +47 -0
  384. package/src/lib/history.ts +82 -0
  385. package/src/lib/inputMetrics.ts +203 -0
  386. package/src/lib/liveProgress.test.ts +116 -0
  387. package/src/lib/liveProgress.ts +79 -0
  388. package/src/lib/mathUnicode.ts +770 -0
  389. package/src/lib/memory.test.ts +155 -0
  390. package/src/lib/memory.ts +188 -0
  391. package/src/lib/memoryMonitor.ts +109 -0
  392. package/src/lib/messages.test.ts +29 -0
  393. package/src/lib/messages.ts +8 -0
  394. package/src/lib/openExternalUrl.test.ts +217 -0
  395. package/src/lib/openExternalUrl.ts +158 -0
  396. package/src/lib/osc52.ts +73 -0
  397. package/src/lib/parentLog.ts +57 -0
  398. package/src/lib/perfPane.tsx +107 -0
  399. package/src/lib/platform.ts +409 -0
  400. package/src/lib/precisionWheel.ts +48 -0
  401. package/src/lib/prompt.ts +35 -0
  402. package/src/lib/reasoning.ts +55 -0
  403. package/src/lib/rpc.ts +41 -0
  404. package/src/lib/subagentTree.ts +355 -0
  405. package/src/lib/syntax.ts +117 -0
  406. package/src/lib/terminalModes.ts +51 -0
  407. package/src/lib/terminalParity.ts +78 -0
  408. package/src/lib/terminalSetup.ts +444 -0
  409. package/src/lib/termux.ts +29 -0
  410. package/src/lib/text.test.ts +18 -0
  411. package/src/lib/text.ts +339 -0
  412. package/src/lib/todo.test.ts +21 -0
  413. package/src/lib/todo.ts +9 -0
  414. package/src/lib/viewportStore.ts +124 -0
  415. package/src/lib/virtualHeights.ts +145 -0
  416. package/src/lib/wheelAccel.ts +190 -0
  417. package/src/protocol/interpolation.ts +3 -0
  418. package/src/protocol/paste.ts +1 -0
  419. package/src/theme.ts +589 -0
  420. package/src/types/nastech-ink.d.ts +176 -0
  421. package/src/types.ts +212 -0
  422. package/tsconfig.build.json +9 -0
  423. package/tsconfig.json +19 -0
  424. package/vitest.config.ts +7 -0
@@ -0,0 +1,495 @@
1
+ import type { FocusManager } from './focus.js'
2
+ import { createLayoutNode } from './layout/engine.js'
3
+ import type { LayoutNode } from './layout/node.js'
4
+ import { LayoutMeasureMode } from './layout/node.js'
5
+ import measureText from './measure-text.js'
6
+ import { addPendingClear, nodeCache } from './node-cache.js'
7
+ import squashTextNodes from './squash-text-nodes.js'
8
+ import type { Styles, TextStyles } from './styles.js'
9
+ import { expandTabs } from './tabstops.js'
10
+ import wrapText from './wrap-text.js'
11
+
12
+ type InkNode = {
13
+ parentNode: DOMElement | undefined
14
+ yogaNode?: LayoutNode
15
+ style: Styles
16
+ }
17
+
18
+ export type TextName = '#text'
19
+ export type ElementNames =
20
+ | 'ink-root'
21
+ | 'ink-box'
22
+ | 'ink-text'
23
+ | 'ink-virtual-text'
24
+ | 'ink-link'
25
+ | 'ink-progress'
26
+ | 'ink-raw-ansi'
27
+
28
+ export type NodeNames = ElementNames | TextName
29
+
30
+ export type DOMElement = {
31
+ nodeName: ElementNames
32
+ attributes: Record<string, DOMNodeAttribute>
33
+ childNodes: DOMNode[]
34
+ textStyles?: TextStyles
35
+
36
+ // Internal properties
37
+ onComputeLayout?: () => void
38
+ onRender?: () => void
39
+ onImmediateRender?: () => void
40
+ // Used to skip empty renders during React 19's effect double-invoke in test mode
41
+ hasRenderedContent?: boolean
42
+
43
+ // When true, this node needs re-rendering
44
+ dirty: boolean
45
+ // Set by the reconciler's hideInstance/unhideInstance; survives style updates.
46
+ isHidden?: boolean
47
+ // Event handlers set by the reconciler for the capture/bubble dispatcher.
48
+ // Stored separately from attributes so handler identity changes don't
49
+ // mark dirty and defeat the blit optimization.
50
+ _eventHandlers?: Record<string, unknown>
51
+
52
+ // Scroll state for overflow: 'scroll' boxes. scrollTop is the number of
53
+ // rows the content is scrolled down by. scrollHeight/scrollViewportHeight
54
+ // are computed at render time and stored for imperative access. stickyScroll
55
+ // auto-pins scrollTop to the bottom when content grows.
56
+ scrollTop?: number
57
+ // Accumulated scroll delta not yet applied to scrollTop. The renderer
58
+ // drains this at SCROLL_MAX_PER_FRAME rows/frame so fast flicks show
59
+ // intermediate frames instead of one big jump. Direction reversal
60
+ // naturally cancels (pure accumulator, no target tracking).
61
+ pendingScrollDelta?: number
62
+ // Render-time clamp bounds for virtual scroll. useVirtualScroll writes
63
+ // the currently-mounted children's coverage span; render-node-to-output
64
+ // clamps scrollTop to stay within it. Prevents blank screen when
65
+ // scrollTo's direct write races past React's async re-render — instead
66
+ // of painting spacer (blank), the renderer holds at the edge of mounted
67
+ // content until React catches up (next commit updates these bounds and
68
+ // the clamp releases). Undefined = no clamp (sticky-scroll, cold start).
69
+ scrollClampMin?: number
70
+ scrollClampMax?: number
71
+ scrollHeight?: number
72
+ scrollViewportHeight?: number
73
+ scrollViewportTop?: number
74
+ stickyScroll?: boolean
75
+ notifyScrollChange?: () => void
76
+ // Set by ScrollBox.scrollToElement; render-node-to-output reads
77
+ // el.yogaNode.getComputedTop() (FRESH — same Yoga pass as scrollHeight)
78
+ // and sets scrollTop = top + offset, then clears this. Unlike an
79
+ // imperative scrollTo(N) which bakes in a number that's stale by the
80
+ // time the throttled render fires, the element ref defers the position
81
+ // read to paint time. One-shot.
82
+ scrollAnchor?: { el: DOMElement; offset: number }
83
+ // Only set on ink-root. The document owns focus — any node can
84
+ // reach it by walking parentNode, like browser getRootNode().
85
+ focusManager?: FocusManager
86
+ // Measurement cache for ink-text nodes: avoids re-squashing and re-wrapping
87
+ // text when yoga calls measureFunc multiple times per frame with different
88
+ // widths during flex re-pass. Keyed by `${width}|${widthMode}`.
89
+ _textMeasureCache?: { gen: number; entries: Map<string, { _gen: number; result: { width: number; height: number } }> }
90
+ } & InkNode
91
+
92
+ export type TextNode = {
93
+ nodeName: TextName
94
+ nodeValue: string
95
+ } & InkNode
96
+
97
+ export type DOMNode<T = { nodeName: NodeNames }> = T extends {
98
+ nodeName: infer U
99
+ }
100
+ ? U extends '#text'
101
+ ? TextNode
102
+ : DOMElement
103
+ : never
104
+
105
+ export type DOMNodeAttribute = boolean | string | number
106
+
107
+ export const createNode = (nodeName: ElementNames): DOMElement => {
108
+ const needsYogaNode = nodeName !== 'ink-virtual-text' && nodeName !== 'ink-link' && nodeName !== 'ink-progress'
109
+
110
+ const node: DOMElement = {
111
+ nodeName,
112
+ style: {},
113
+ attributes: {},
114
+ childNodes: [],
115
+ parentNode: undefined,
116
+ yogaNode: needsYogaNode ? createLayoutNode() : undefined,
117
+ dirty: false
118
+ }
119
+
120
+ if (nodeName === 'ink-text') {
121
+ node.yogaNode?.setMeasureFunc(measureTextNode.bind(null, node))
122
+ } else if (nodeName === 'ink-raw-ansi') {
123
+ node.yogaNode?.setMeasureFunc(measureRawAnsiNode.bind(null, node))
124
+ }
125
+
126
+ return node
127
+ }
128
+
129
+ export const appendChildNode = (node: DOMElement, childNode: DOMElement): void => {
130
+ if (childNode.parentNode) {
131
+ removeChildNode(childNode.parentNode, childNode)
132
+ }
133
+
134
+ childNode.parentNode = node
135
+ node.childNodes.push(childNode)
136
+
137
+ if (childNode.yogaNode) {
138
+ node.yogaNode?.insertChild(childNode.yogaNode, node.yogaNode.getChildCount())
139
+ }
140
+
141
+ markDirty(node)
142
+ }
143
+
144
+ export const insertBeforeNode = (node: DOMElement, newChildNode: DOMNode, beforeChildNode: DOMNode): void => {
145
+ if (newChildNode.parentNode) {
146
+ removeChildNode(newChildNode.parentNode, newChildNode)
147
+ }
148
+
149
+ newChildNode.parentNode = node
150
+
151
+ const index = node.childNodes.indexOf(beforeChildNode)
152
+
153
+ if (index >= 0) {
154
+ // Calculate yoga index BEFORE modifying childNodes.
155
+ // We can't use DOM index directly because some children (like ink-progress,
156
+ // ink-link, ink-virtual-text) don't have yogaNodes, so DOM indices don't
157
+ // match yoga indices.
158
+ let yogaIndex = 0
159
+
160
+ if (newChildNode.yogaNode && node.yogaNode) {
161
+ for (let i = 0; i < index; i++) {
162
+ if (node.childNodes[i]?.yogaNode) {
163
+ yogaIndex++
164
+ }
165
+ }
166
+ }
167
+
168
+ node.childNodes.splice(index, 0, newChildNode)
169
+
170
+ if (newChildNode.yogaNode && node.yogaNode) {
171
+ node.yogaNode.insertChild(newChildNode.yogaNode, yogaIndex)
172
+ }
173
+
174
+ markDirty(node)
175
+
176
+ return
177
+ }
178
+
179
+ node.childNodes.push(newChildNode)
180
+
181
+ if (newChildNode.yogaNode) {
182
+ node.yogaNode?.insertChild(newChildNode.yogaNode, node.yogaNode.getChildCount())
183
+ }
184
+
185
+ markDirty(node)
186
+ }
187
+
188
+ export const removeChildNode = (node: DOMElement, removeNode: DOMNode): void => {
189
+ if (removeNode.yogaNode) {
190
+ removeNode.parentNode?.yogaNode?.removeChild(removeNode.yogaNode)
191
+ }
192
+
193
+ // Collect cached rects from the removed subtree so they can be cleared
194
+ collectRemovedRects(node, removeNode)
195
+
196
+ removeNode.parentNode = undefined
197
+
198
+ const index = node.childNodes.indexOf(removeNode)
199
+
200
+ if (index >= 0) {
201
+ node.childNodes.splice(index, 1)
202
+ }
203
+
204
+ markDirty(node)
205
+ }
206
+
207
+ function collectRemovedRects(parent: DOMElement, removed: DOMNode, underAbsolute = false): void {
208
+ if (removed.nodeName === '#text') {
209
+ return
210
+ }
211
+
212
+ const elem = removed as DOMElement
213
+ // If this node or any ancestor in the removed subtree was absolute,
214
+ // its painted pixels may overlap non-siblings — flag for global blit
215
+ // disable. Normal-flow removals only affect direct siblings, which
216
+ // hasRemovedChild already handles.
217
+ const isAbsolute = underAbsolute || elem.style.position === 'absolute'
218
+ const cached = nodeCache.get(elem)
219
+
220
+ if (cached) {
221
+ addPendingClear(parent, cached, isAbsolute)
222
+ nodeCache.delete(elem)
223
+ }
224
+
225
+ for (const child of elem.childNodes) {
226
+ collectRemovedRects(parent, child, isAbsolute)
227
+ }
228
+ }
229
+
230
+ export const setAttribute = (node: DOMElement, key: string, value: DOMNodeAttribute): void => {
231
+ // Skip 'children' - React handles children via appendChild/removeChild,
232
+ // not attributes. React always passes a new children reference, so
233
+ // tracking it as an attribute would mark everything dirty every render.
234
+ if (key === 'children') {
235
+ return
236
+ }
237
+
238
+ // Skip if unchanged
239
+ if (node.attributes[key] === value) {
240
+ return
241
+ }
242
+
243
+ node.attributes[key] = value
244
+ markDirty(node)
245
+ }
246
+
247
+ export const setStyle = (node: DOMNode, style: Styles): void => {
248
+ // Compare style properties to avoid marking dirty unnecessarily.
249
+ // React creates new style objects on every render even when unchanged.
250
+ if (stylesEqual(node.style, style)) {
251
+ return
252
+ }
253
+
254
+ node.style = style
255
+ markDirty(node)
256
+ }
257
+
258
+ export const setTextStyles = (node: DOMElement, textStyles: TextStyles): void => {
259
+ // Same dirty-check guard as setStyle: React (and buildTextStyles in Text.tsx)
260
+ // allocate a new textStyles object on every render even when values are
261
+ // unchanged, so compare by value to avoid markDirty -> yoga re-measurement
262
+ // on every Text re-render.
263
+ if (shallowEqual(node.textStyles, textStyles)) {
264
+ return
265
+ }
266
+
267
+ node.textStyles = textStyles
268
+ markDirty(node)
269
+ }
270
+
271
+ function stylesEqual(a: Styles, b: Styles): boolean {
272
+ return shallowEqual(a, b)
273
+ }
274
+
275
+ function shallowEqual<T extends object>(a: T | undefined, b: T | undefined): boolean {
276
+ // Fast path: same object reference (or both undefined)
277
+ if (a === b) {
278
+ return true
279
+ }
280
+
281
+ if (a === undefined || b === undefined) {
282
+ return false
283
+ }
284
+
285
+ // Get all keys from both objects
286
+ const aKeys = Object.keys(a) as (keyof T)[]
287
+ const bKeys = Object.keys(b) as (keyof T)[]
288
+
289
+ // Different number of properties
290
+ if (aKeys.length !== bKeys.length) {
291
+ return false
292
+ }
293
+
294
+ // Compare each property
295
+ for (const key of aKeys) {
296
+ if (a[key] !== b[key]) {
297
+ return false
298
+ }
299
+ }
300
+
301
+ return true
302
+ }
303
+
304
+ export const createTextNode = (text: string): TextNode => {
305
+ const node: TextNode = {
306
+ nodeName: '#text',
307
+ nodeValue: text,
308
+ yogaNode: undefined,
309
+ parentNode: undefined,
310
+ style: {}
311
+ }
312
+
313
+ setTextNodeValue(node, text)
314
+
315
+ return node
316
+ }
317
+
318
+ const MEASURE_CACHE_CAP = 16
319
+
320
+ const measureTextNode = function (
321
+ node: DOMNode,
322
+ width: number,
323
+ widthMode: LayoutMeasureMode
324
+ ): { width: number; height: number } {
325
+ const elem = node.nodeName !== '#text' ? (node as DOMElement) : node.parentNode
326
+
327
+ if (elem && elem.nodeName === 'ink-text') {
328
+ let cache = elem._textMeasureCache
329
+
330
+ if (!cache) {
331
+ cache = { gen: 0, entries: new Map() }
332
+ elem._textMeasureCache = cache
333
+ }
334
+
335
+ const key = `${width}|${widthMode}`
336
+ const hit = cache.entries.get(key)
337
+
338
+ if (hit && hit._gen === cache.gen) {
339
+ return hit.result
340
+ }
341
+
342
+ const result = computeTextMeasure(node, width, widthMode)
343
+
344
+ // Enforce cap with FIFO eviction to avoid unbounded growth during
345
+ // pathological frames where yoga probes many widths.
346
+ if (cache.entries.size >= MEASURE_CACHE_CAP) {
347
+ const firstKey = cache.entries.keys().next().value
348
+
349
+ if (firstKey !== undefined) {
350
+ cache.entries.delete(firstKey)
351
+ }
352
+ }
353
+
354
+ cache.entries.set(key, { _gen: cache.gen, result })
355
+
356
+ return result
357
+ }
358
+
359
+ return computeTextMeasure(node, width, widthMode)
360
+ }
361
+
362
+ const computeTextMeasure = function (
363
+ node: DOMNode,
364
+ width: number,
365
+ widthMode: LayoutMeasureMode
366
+ ): { width: number; height: number } {
367
+ const rawText = node.nodeName === '#text' ? node.nodeValue : squashTextNodes(node)
368
+
369
+ // Expand tabs for measurement (worst case: 8 spaces each).
370
+ // Actual tab expansion happens in output.ts based on screen position.
371
+ const text = expandTabs(rawText)
372
+
373
+ const dimensions = measureText(text, width)
374
+
375
+ // Text fits into container, no need to wrap
376
+ if (dimensions.width <= width) {
377
+ return dimensions
378
+ }
379
+
380
+ // This is happening when <Box> is shrinking child nodes and layout asks
381
+ // if we can fit this text node in a <1px space, so we just say "no"
382
+ if (dimensions.width >= 1 && width > 0 && width < 1) {
383
+ return dimensions
384
+ }
385
+
386
+ // For text with embedded newlines (pre-wrapped content), avoid re-wrapping
387
+ // at measurement width when layout is asking for intrinsic size (Undefined mode).
388
+ // This prevents height inflation during min/max size checks.
389
+ //
390
+ // However, when layout provides an actual constraint (Exactly or AtMost mode),
391
+ // we must respect it and measure at that width. Otherwise, if the actual
392
+ // rendering width is smaller than the natural width, the text will wrap to
393
+ // more lines than layout expects, causing content to be truncated.
394
+ if (text.includes('\n') && widthMode === LayoutMeasureMode.Undefined) {
395
+ const effectiveWidth = Math.max(width, dimensions.width)
396
+
397
+ return measureText(text, effectiveWidth)
398
+ }
399
+
400
+ const textWrap = node.style?.textWrap ?? 'wrap'
401
+ const wrappedText = wrapText(text, width, textWrap)
402
+
403
+ return measureText(wrappedText, width)
404
+ }
405
+
406
+ // ink-raw-ansi nodes hold pre-rendered ANSI strings with known dimensions.
407
+ // No stringWidth, no wrapping, no tab expansion — the producer (e.g. ColorDiff)
408
+ // already wrapped to the target width and each line is exactly one terminal row.
409
+ const measureRawAnsiNode = function (node: DOMElement): {
410
+ width: number
411
+ height: number
412
+ } {
413
+ return {
414
+ width: node.attributes['rawWidth'] as number,
415
+ height: node.attributes['rawHeight'] as number
416
+ }
417
+ }
418
+
419
+ /**
420
+ * Mark a node and all its ancestors as dirty for re-rendering.
421
+ * Also marks yoga dirty for text remeasurement if this is a text node.
422
+ */
423
+ export const markDirty = (node?: DOMNode): void => {
424
+ let current: DOMNode | undefined = node
425
+ let markedYoga = false
426
+
427
+ while (current) {
428
+ if (current.nodeName !== '#text') {
429
+ const elem = current as DOMElement
430
+ elem.dirty = true
431
+
432
+ // Only mark yoga dirty on leaf nodes that have measure functions
433
+ if (!markedYoga && (elem.nodeName === 'ink-text' || elem.nodeName === 'ink-raw-ansi') && elem.yogaNode) {
434
+ elem.yogaNode.markDirty()
435
+ markedYoga = true
436
+ }
437
+
438
+ // Invalidate text measurement cache — child text or style changed.
439
+ if (elem._textMeasureCache) {
440
+ elem._textMeasureCache.gen++
441
+ }
442
+ }
443
+
444
+ current = current.parentNode
445
+ }
446
+ }
447
+
448
+ // Walk to root and call its onRender (the throttled scheduleRender). Use for
449
+ // DOM-level mutations (scrollTop changes) that should trigger an Ink frame
450
+ // without going through React's reconciler. Pair with markDirty() so the
451
+ // renderer knows which subtree to re-evaluate.
452
+ export const scheduleRenderFrom = (node?: DOMNode): void => {
453
+ let cur: DOMNode | undefined = node
454
+
455
+ while (cur?.parentNode) {
456
+ cur = cur.parentNode
457
+ }
458
+
459
+ if (cur && cur.nodeName !== '#text') {
460
+ ;(cur as DOMElement).onRender?.()
461
+ }
462
+ }
463
+
464
+ export const setTextNodeValue = (node: TextNode, text: string): void => {
465
+ if (typeof text !== 'string') {
466
+ text = String(text)
467
+ }
468
+
469
+ // Skip if unchanged
470
+ if (node.nodeValue === text) {
471
+ return
472
+ }
473
+
474
+ node.nodeValue = text
475
+ markDirty(node)
476
+ }
477
+
478
+ function isDOMElement(node: DOMElement | TextNode): node is DOMElement {
479
+ return node.nodeName !== '#text'
480
+ }
481
+
482
+ // Clear yogaNode references recursively before freeing.
483
+ // freeRecursive() frees the node and ALL its children, so we must clear
484
+ // all yogaNode references to prevent dangling pointers.
485
+ export const clearYogaNodeReferences = (node: DOMElement | TextNode): void => {
486
+ if ('childNodes' in node) {
487
+ for (const child of node.childNodes) {
488
+ clearYogaNodeReferences(child)
489
+ }
490
+
491
+ node._textMeasureCache = undefined
492
+ }
493
+
494
+ node.yogaNode = undefined
495
+ }
@@ -0,0 +1,38 @@
1
+ import { Event } from './event.js'
2
+
3
+ /**
4
+ * Mouse click event. Fired on left-button release without drag, only when
5
+ * mouse tracking is enabled (i.e. inside <AlternateScreen>).
6
+ *
7
+ * Bubbles from the deepest hit node up through parentNode. Call
8
+ * stopImmediatePropagation() to prevent ancestors' onClick from firing.
9
+ */
10
+ export class ClickEvent extends Event {
11
+ /** 0-indexed screen column of the click */
12
+ readonly col: number
13
+ /** 0-indexed screen row of the click */
14
+ readonly row: number
15
+ /**
16
+ * Click column relative to the current handler's Box (col - box.x).
17
+ * Recomputed by dispatchClick before each handler fires, so an onClick
18
+ * on a container sees coords relative to that container, not to any
19
+ * child the click landed on.
20
+ */
21
+ localCol = 0
22
+ /** Click row relative to the current handler's Box (row - box.y). */
23
+ localRow = 0
24
+ /**
25
+ * True if the clicked cell has no visible content (unwritten in the
26
+ * screen buffer — both packed words are 0). Handlers can check this to
27
+ * ignore clicks on blank space to the right of text, so accidental
28
+ * clicks on empty terminal space don't toggle state.
29
+ */
30
+ readonly cellIsBlank: boolean
31
+
32
+ constructor(col: number, row: number, cellIsBlank: boolean) {
33
+ super()
34
+ this.col = col
35
+ this.row = row
36
+ this.cellIsBlank = cellIsBlank
37
+ }
38
+ }
@@ -0,0 +1,65 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { parseMultipleKeypresses } from '../parse-keypress.js'
4
+
5
+ import { InputEvent } from './input-event.js'
6
+
7
+ function parseOne(sequence: string) {
8
+ const [keys] = parseMultipleKeypresses({ incomplete: '', mode: 'NORMAL' }, sequence)
9
+ expect(keys).toHaveLength(1)
10
+
11
+ return keys[0]!
12
+ }
13
+
14
+ describe('enhanced keyboard modifier parsing', () => {
15
+ it('detects modified Enter sequences for multiline composer shortcuts', () => {
16
+ const shiftEnter = new InputEvent(parseOne('\u001b[13;2u'))
17
+ const ctrlEnter = new InputEvent(parseOne('\u001b[13;5u'))
18
+ const modifyOtherShiftEnter = new InputEvent(parseOne('\u001b[27;2;13~'))
19
+
20
+ expect(shiftEnter.key.return).toBe(true)
21
+ expect(shiftEnter.key.shift).toBe(true)
22
+ expect(shiftEnter.input).toBe('')
23
+
24
+ expect(ctrlEnter.key.return).toBe(true)
25
+ expect(ctrlEnter.key.ctrl).toBe(true)
26
+ expect(ctrlEnter.input).toBe('')
27
+
28
+ expect(modifyOtherShiftEnter.key.return).toBe(true)
29
+ expect(modifyOtherShiftEnter.key.shift).toBe(true)
30
+ expect(modifyOtherShiftEnter.input).toBe('')
31
+ })
32
+
33
+ it('preserves Cmd as super for kitty keyboard CSI-u sequences', () => {
34
+ const parsed = parseOne('\u001b[99;9u')
35
+ const event = new InputEvent(parsed)
36
+
37
+ expect(parsed.name).toBe('c')
38
+ expect(event.key.meta).toBe(false)
39
+ expect(event.key.super).toBe(true)
40
+ })
41
+
42
+ it('preserves forwarded VS Code/Cursor Cmd+C copy sequence as ctrl+super+c', () => {
43
+ const parsed = parseOne('\u001b[99;13u')
44
+ const event = new InputEvent(parsed)
45
+
46
+ expect(parsed.name).toBe('c')
47
+ expect(event.key.ctrl).toBe(true)
48
+ expect(event.key.super).toBe(true)
49
+ })
50
+
51
+ it('preserves Cmd on word-delete and word-navigation sequences', () => {
52
+ const backspace = new InputEvent(parseOne('\u001b[127;9u'))
53
+ const left = new InputEvent(parseOne('\u001b[1;9D'))
54
+ const right = new InputEvent(parseOne('\u001b[1;9C'))
55
+
56
+ expect(backspace.key.backspace).toBe(true)
57
+ expect(backspace.key.super).toBe(true)
58
+
59
+ expect(left.key.leftArrow).toBe(true)
60
+ expect(left.key.super).toBe(true)
61
+
62
+ expect(right.key.rightArrow).toBe(true)
63
+ expect(right.key.super).toBe(true)
64
+ })
65
+ })