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,236 @@
1
+ import noop from 'lodash-es/noop.js'
2
+ import type { ReactElement } from 'react'
3
+ import { LegacyRoot } from 'react-reconciler/constants.js'
4
+
5
+ import { logForDebugging } from '../utils/debug.js'
6
+
7
+ import { createNode, type DOMElement } from './dom.js'
8
+ import { FocusManager } from './focus.js'
9
+ import Output from './output.js'
10
+ import reconciler from './reconciler.js'
11
+ import renderNodeToOutput, { resetLayoutShifted } from './render-node-to-output.js'
12
+ import {
13
+ cellAtIndex,
14
+ CellWidth,
15
+ CharPool,
16
+ createScreen,
17
+ HyperlinkPool,
18
+ type Screen,
19
+ setCellStyleId,
20
+ StylePool
21
+ } from './screen.js'
22
+
23
+ /** Position of a match within a rendered message, relative to the message's
24
+ * own bounding box (row 0 = message top). Stable across scroll — to
25
+ * highlight on the real screen, add the message's screen-row offset. */
26
+ export type MatchPosition = {
27
+ row: number
28
+ col: number
29
+ /** Number of CELLS the match spans (= query.length for ASCII, more
30
+ * for wide chars in the query). */
31
+ len: number
32
+ }
33
+
34
+ // Shared across calls. Pools accumulate style/char interns — reusing them
35
+ // means later calls hit cache more. Root/container reuse saves the
36
+ // createContainer cost (~1ms). LegacyRoot: all work sync, no scheduling —
37
+ // ConcurrentRoot's scheduler backlog leaks across roots via flushSyncWork.
38
+ let root: DOMElement | undefined
39
+ let container: ReturnType<typeof reconciler.createContainer> | undefined
40
+ let stylePool: StylePool | undefined
41
+ let charPool: CharPool | undefined
42
+ let hyperlinkPool: HyperlinkPool | undefined
43
+ let output: Output | undefined
44
+
45
+ const timing = { reconcile: 0, yoga: 0, paint: 0, scan: 0, calls: 0 }
46
+ const LOG_EVERY = 20
47
+
48
+ /** Render a React element (wrapped in all contexts the component needs —
49
+ * caller's job) to an isolated Screen buffer at the given width. Returns
50
+ * the Screen + natural height (from yoga). Used for search: render ONE
51
+ * message, scan its Screen for the query, get exact (row, col) positions.
52
+ *
53
+ * ~1-3ms per call (yoga alloc + calculateLayout + paint). The
54
+ * flushSyncWork cross-root leak measured ~0.0003ms/call growth — fine
55
+ * for on-demand single-message rendering, pathological for render-all-
56
+ * 8k-upfront. Cache per (msg, query, width) upstream.
57
+ *
58
+ * Unmounts between calls. Root/container/pools persist for reuse. */
59
+ export function renderToScreen(el: ReactElement, width: number): { screen: Screen; height: number } {
60
+ if (!root) {
61
+ root = createNode('ink-root')
62
+ root.focusManager = new FocusManager(() => false)
63
+ stylePool = new StylePool()
64
+ charPool = new CharPool()
65
+ hyperlinkPool = new HyperlinkPool()
66
+ container = reconciler.createContainer(root, LegacyRoot, null, false, null, 'search-render', noop, noop, noop, noop)
67
+ }
68
+
69
+ const t0 = performance.now()
70
+ reconciler.updateContainerSync(el, container, null, noop)
71
+ reconciler.flushSyncWork()
72
+ const t1 = performance.now()
73
+
74
+ // Yoga layout. Root might not have a yogaNode if the tree is empty.
75
+ root.yogaNode?.setWidth(width)
76
+ root.yogaNode?.calculateLayout(width)
77
+ const height = Math.ceil(root.yogaNode?.getComputedHeight() ?? 0)
78
+ const t2 = performance.now()
79
+
80
+ // Paint to a fresh Screen. Width = given, height = yoga's natural.
81
+ // No alt-screen, no prevScreen (every call is fresh).
82
+ const screen = createScreen(
83
+ width,
84
+ Math.max(1, height), // avoid 0-height Screen (createScreen may choke)
85
+ stylePool!,
86
+ charPool!,
87
+ hyperlinkPool!
88
+ )
89
+
90
+ if (!output) {
91
+ output = new Output({ width, height, stylePool: stylePool!, screen })
92
+ } else {
93
+ output.reset(width, height, screen)
94
+ }
95
+
96
+ resetLayoutShifted()
97
+ renderNodeToOutput(root, output, { prevScreen: undefined })
98
+ // renderNodeToOutput queues writes into Output; .get() flushes the
99
+ // queue into the Screen's cell arrays. Without this the screen is
100
+ // blank (constructor-zero).
101
+ const rendered = output.get()
102
+ const t3 = performance.now()
103
+
104
+ // Unmount so next call gets a fresh tree. Leaves root/container/pools.
105
+ reconciler.updateContainerSync(null, container, null, noop)
106
+ reconciler.flushSyncWork()
107
+
108
+ timing.reconcile += t1 - t0
109
+ timing.yoga += t2 - t1
110
+ timing.paint += t3 - t2
111
+
112
+ if (++timing.calls % LOG_EVERY === 0) {
113
+ const total = timing.reconcile + timing.yoga + timing.paint + timing.scan
114
+ logForDebugging(
115
+ `renderToScreen: ${timing.calls} calls · ` +
116
+ `reconcile=${timing.reconcile.toFixed(1)}ms yoga=${timing.yoga.toFixed(1)}ms ` +
117
+ `paint=${timing.paint.toFixed(1)}ms scan=${timing.scan.toFixed(1)}ms · ` +
118
+ `total=${total.toFixed(1)}ms · avg ${(total / timing.calls).toFixed(2)}ms/call`
119
+ )
120
+ }
121
+
122
+ return { screen: rendered, height }
123
+ }
124
+
125
+ /** Scan a Screen buffer for all occurrences of query. Returns positions
126
+ * relative to the buffer (row 0 = buffer top). Same cell-skip logic as
127
+ * applySearchHighlight (SpacerTail/SpacerHead/noSelect) so positions
128
+ * match what the overlay highlight would find. Case-insensitive.
129
+ *
130
+ * For the side-render use: this Screen is the FULL message (natural
131
+ * height, not viewport-clipped). Positions are stable — to highlight
132
+ * on the real screen, add the message's screen offset (lo). */
133
+ export function scanPositions(screen: Screen, query: string): MatchPosition[] {
134
+ const lq = query.toLowerCase()
135
+
136
+ if (!lq) {
137
+ return []
138
+ }
139
+
140
+ const qlen = lq.length
141
+ const w = screen.width
142
+ const h = screen.height
143
+ const noSelect = screen.noSelect
144
+ const positions: MatchPosition[] = []
145
+
146
+ const t0 = performance.now()
147
+
148
+ for (let row = 0; row < h; row++) {
149
+ const rowOff = row * w
150
+ // Same text-build as applySearchHighlight. Keep in sync — or extract
151
+ // to a shared helper (TODO once both are stable). codeUnitToCell
152
+ // maps indexOf positions (code units in the LOWERCASED text) to cell
153
+ // indices in colOf — surrogate pairs (emoji) and multi-unit lowercase
154
+ // (Turkish İ → i + U+0307) make text.length > colOf.length.
155
+ let text = ''
156
+ const colOf: number[] = []
157
+ const codeUnitToCell: number[] = []
158
+
159
+ for (let col = 0; col < w; col++) {
160
+ const idx = rowOff + col
161
+ const cell = cellAtIndex(screen, idx)
162
+
163
+ if (cell.width === CellWidth.SpacerTail || cell.width === CellWidth.SpacerHead || noSelect[idx] === 1) {
164
+ continue
165
+ }
166
+
167
+ const lc = cell.char.toLowerCase()
168
+ const cellIdx = colOf.length
169
+
170
+ for (let i = 0; i < lc.length; i++) {
171
+ codeUnitToCell.push(cellIdx)
172
+ }
173
+
174
+ text += lc
175
+ colOf.push(col)
176
+ }
177
+
178
+ // Non-overlapping — same advance as applySearchHighlight.
179
+ let pos = text.indexOf(lq)
180
+
181
+ while (pos >= 0) {
182
+ const startCi = codeUnitToCell[pos]!
183
+ const endCi = codeUnitToCell[pos + qlen - 1]!
184
+ const col = colOf[startCi]!
185
+ const endCol = colOf[endCi]! + 1
186
+ positions.push({ row, col, len: endCol - col })
187
+ pos = text.indexOf(lq, pos + qlen)
188
+ }
189
+ }
190
+
191
+ timing.scan += performance.now() - t0
192
+
193
+ return positions
194
+ }
195
+
196
+ /** Write CURRENT (yellow+bold+underline) at positions[currentIdx] +
197
+ * rowOffset. OTHER positions are NOT styled here — the scan-highlight
198
+ * (applySearchHighlight with null hint) does inverse for all visible
199
+ * matches, including these. Two-layer: scan = 'you could go here',
200
+ * position = 'you ARE here'. Writing inverse again here would be a
201
+ * no-op (withInverse idempotent) but wasted work.
202
+ *
203
+ * Positions are message-relative (row 0 = message top). rowOffset =
204
+ * message's current screen-top (lo). Clips outside [0, height). */
205
+ export function applyPositionedHighlight(
206
+ screen: Screen,
207
+ stylePool: StylePool,
208
+ positions: MatchPosition[],
209
+ rowOffset: number,
210
+ currentIdx: number
211
+ ): boolean {
212
+ if (currentIdx < 0 || currentIdx >= positions.length) {
213
+ return false
214
+ }
215
+
216
+ const p = positions[currentIdx]!
217
+ const row = p.row + rowOffset
218
+
219
+ if (row < 0 || row >= screen.height) {
220
+ return false
221
+ }
222
+
223
+ const transform = (id: number) => stylePool.withCurrentMatch(id)
224
+ const rowOff = row * screen.width
225
+
226
+ for (let col = p.col; col < p.col + p.len; col++) {
227
+ if (col < 0 || col >= screen.width) {
228
+ continue
229
+ }
230
+
231
+ const cell = cellAtIndex(screen, rowOff + col)
232
+ setCellStyleId(screen, col, row, transform(cell.styleId))
233
+ }
234
+
235
+ return true
236
+ }
@@ -0,0 +1,169 @@
1
+ import { logForDebugging } from '../utils/debug.js'
2
+
3
+ import { type DOMElement, markDirty } from './dom.js'
4
+ import type { Frame } from './frame.js'
5
+ import { consumeAbsoluteRemovedFlag } from './node-cache.js'
6
+ import Output from './output.js'
7
+ import renderNodeToOutput, {
8
+ didAbsoluteOverlayMove,
9
+ getScrollDrainNode,
10
+ getScrollHint,
11
+ resetLayoutShifted,
12
+ resetScrollDrainNode,
13
+ resetScrollHint
14
+ } from './render-node-to-output.js'
15
+ import { createScreen, type StylePool } from './screen.js'
16
+
17
+ export type RenderOptions = {
18
+ frontFrame: Frame
19
+ backFrame: Frame
20
+ isTTY: boolean
21
+ terminalWidth: number
22
+ terminalRows: number
23
+ altScreen: boolean
24
+ // True when the previous frame's screen buffer was mutated post-render
25
+ // (selection overlay), reset to blank (alt-screen enter/resize/SIGCONT),
26
+ // or reset to 0×0 (forceRedraw). Blitting from such a prevScreen would
27
+ // copy stale inverted cells, blanks, or nothing. When false, blit is safe.
28
+ prevFrameContaminated: boolean
29
+ }
30
+
31
+ export type Renderer = (options: RenderOptions) => Frame
32
+
33
+ export default function createRenderer(node: DOMElement, stylePool: StylePool): Renderer {
34
+ // Reuse Output across frames so charCache (tokenize + grapheme clustering)
35
+ // persists — most lines don't change between renders.
36
+ let output: Output | undefined
37
+
38
+ return options => {
39
+ const { frontFrame, backFrame, isTTY, terminalWidth, terminalRows } = options
40
+
41
+ const prevScreen = frontFrame.screen
42
+ const backScreen = backFrame.screen
43
+ // Read pools from the back buffer's screen — pools may be replaced
44
+ // between frames (generational reset), so we can't capture them in the closure
45
+ const charPool = backScreen.charPool
46
+ const hyperlinkPool = backScreen.hyperlinkPool
47
+
48
+ // Return empty frame if yoga node doesn't exist or layout hasn't been computed yet.
49
+ // getComputedHeight() returns NaN before calculateLayout() is called.
50
+ // Also check for invalid dimensions (negative, Infinity) that would cause RangeError
51
+ // when creating arrays.
52
+ const computedHeight = node.yogaNode?.getComputedHeight()
53
+ const computedWidth = node.yogaNode?.getComputedWidth()
54
+
55
+ const hasInvalidHeight = computedHeight === undefined || !Number.isFinite(computedHeight) || computedHeight < 0
56
+
57
+ const hasInvalidWidth = computedWidth === undefined || !Number.isFinite(computedWidth) || computedWidth < 0
58
+
59
+ if (!node.yogaNode || hasInvalidHeight || hasInvalidWidth) {
60
+ // Log to help diagnose root cause (visible with --debug flag)
61
+ if (node.yogaNode && (hasInvalidHeight || hasInvalidWidth)) {
62
+ logForDebugging(
63
+ `Invalid yoga dimensions: width=${computedWidth}, height=${computedHeight}, ` +
64
+ `childNodes=${node.childNodes.length}, terminalWidth=${terminalWidth}, terminalRows=${terminalRows}`
65
+ )
66
+ }
67
+
68
+ return {
69
+ screen: createScreen(terminalWidth, 0, stylePool, charPool, hyperlinkPool),
70
+ viewport: { width: terminalWidth, height: terminalRows },
71
+ cursor: { x: 0, y: 0, visible: true }
72
+ }
73
+ }
74
+
75
+ const width = Math.floor(node.yogaNode.getComputedWidth())
76
+ const yogaHeight = Math.floor(node.yogaNode.getComputedHeight())
77
+ // Alt-screen: the screen buffer IS the alt buffer — always exactly
78
+ // terminalRows tall. <AlternateScreen> wraps children in <Box
79
+ // height={rows} flexShrink={0}>, so yogaHeight should equal
80
+ // terminalRows. But if something renders as a SIBLING of that Box
81
+ // (bug: MessageSelector was outside <FullscreenLayout>), yogaHeight
82
+ // exceeds rows and every assumption below (viewport +1 hack, cursor.y
83
+ // clamp, log-update's heightDelta===0 fast path) breaks, desyncing
84
+ // virtual/physical cursors. Clamping here enforces the invariant:
85
+ // overflow writes land at y >= screen.height and setCellAt drops
86
+ // them. The sibling is invisible (obvious, easy to find) instead of
87
+ // corrupting the whole terminal.
88
+ const height = options.altScreen ? terminalRows : yogaHeight
89
+
90
+ if (options.altScreen && yogaHeight > terminalRows) {
91
+ logForDebugging(
92
+ `alt-screen: yoga height ${yogaHeight} > terminalRows ${terminalRows} — ` +
93
+ `something is rendering outside <AlternateScreen>. Overflow clipped.`,
94
+ { level: 'warn' }
95
+ )
96
+ }
97
+
98
+ const screen = backScreen ?? createScreen(width, height, stylePool, charPool, hyperlinkPool)
99
+
100
+ if (output) {
101
+ output.reset(width, height, screen)
102
+ } else {
103
+ output = new Output({ width, height, stylePool, screen })
104
+ }
105
+
106
+ resetLayoutShifted()
107
+ resetScrollHint()
108
+ resetScrollDrainNode()
109
+
110
+ // prevFrameContaminated: selection overlay mutated the returned screen
111
+ // buffer post-render (in ink.tsx), resetFramesForAltScreen() replaced it
112
+ // with blanks, or forceRedraw() reset it to 0×0. Blit on the NEXT frame
113
+ // would copy stale inverted cells / blanks / nothing. When clean, blit
114
+ // restores the O(unchanged) fast path for steady-state frames (spinner
115
+ // tick, text stream).
116
+ // Removing an absolute-positioned node poisons prevScreen: it may
117
+ // have painted over non-siblings (e.g. an overlay over a ScrollBox
118
+ // earlier in tree order), so their blits would restore the removed
119
+ // node's pixels. hasRemovedChild only shields direct siblings.
120
+ // Normal-flow removals don't paint cross-subtree and are fine.
121
+ const absoluteRemoved = consumeAbsoluteRemovedFlag()
122
+ renderNodeToOutput(node, output, {
123
+ prevScreen: absoluteRemoved || options.prevFrameContaminated ? undefined : prevScreen
124
+ })
125
+
126
+ const renderedScreen = output.get()
127
+
128
+ // Drain continuation: render cleared scrollbox.dirty, so next frame's
129
+ // root blit would skip the subtree. markDirty walks ancestors so the
130
+ // next frame descends. Done AFTER render so the clear-dirty at the end
131
+ // of renderNodeToOutput doesn't overwrite this.
132
+ const drainNode = getScrollDrainNode()
133
+
134
+ if (drainNode) {
135
+ markDirty(drainNode)
136
+ }
137
+
138
+ return {
139
+ absoluteOverlayMoved: didAbsoluteOverlayMove(),
140
+ scrollHint: options.altScreen ? getScrollHint() : null,
141
+ scrollDrainPending: drainNode !== null,
142
+ screen: renderedScreen,
143
+ viewport: {
144
+ width: terminalWidth,
145
+ // Alt screen: fake viewport.height = rows + 1 so that
146
+ // shouldClearScreen()'s `screen.height >= viewport.height` check
147
+ // (which treats exactly-filling content as "overflows" for
148
+ // scrollback purposes) never fires. Alt-screen content is always
149
+ // exactly `rows` tall (via <Box height={rows}>) but never
150
+ // scrolls — the cursor.y clamp below keeps the cursor-restore
151
+ // from emitting an LF. With the standard diff path, every frame
152
+ // is incremental; no fullResetSequence_CAUSES_FLICKER.
153
+ height: options.altScreen ? terminalRows + 1 : terminalRows
154
+ },
155
+ cursor: {
156
+ x: 0,
157
+ // In the alt screen, keep the cursor inside the viewport. When
158
+ // screen.height === terminalRows exactly (content fills the alt
159
+ // screen), cursor.y = screen.height would trigger log-update's
160
+ // cursor-restore LF at the last row, scrolling one row off the top
161
+ // of the alt buffer and desyncing the diff's cursor model. The
162
+ // cursor is hidden so its position only matters for diff coords.
163
+ y: options.altScreen ? Math.max(0, Math.min(screen.height, terminalRows) - 1) : screen.height,
164
+ // Hide cursor when there's dynamic output to render (only in TTY mode)
165
+ visible: !isTTY || screen.height === 0
166
+ }
167
+ }
168
+ }
169
+ }
@@ -0,0 +1,204 @@
1
+ import { Stream } from 'stream'
2
+
3
+ import type { ReactNode } from 'react'
4
+
5
+ import { logForDebugging } from '../utils/debug.js'
6
+
7
+ import type { FrameEvent } from './frame.js'
8
+ import Ink, { type Options as InkOptions } from './ink.js'
9
+ import instances from './instances.js'
10
+
11
+ export type RenderOptions = {
12
+ /**
13
+ * Output stream where app will be rendered.
14
+ *
15
+ * @default process.stdout
16
+ */
17
+ stdout?: NodeJS.WriteStream
18
+ /**
19
+ * Input stream where app will listen for input.
20
+ *
21
+ * @default process.stdin
22
+ */
23
+ stdin?: NodeJS.ReadStream
24
+ /**
25
+ * Error stream.
26
+ * @default process.stderr
27
+ */
28
+ stderr?: NodeJS.WriteStream
29
+ /**
30
+ * Configure whether Ink should listen to Ctrl+C keyboard input and exit the app. This is needed in case `process.stdin` is in raw mode, because then Ctrl+C is ignored by default and process is expected to handle it manually.
31
+ *
32
+ * @default true
33
+ */
34
+ exitOnCtrlC?: boolean
35
+
36
+ /**
37
+ * Patch console methods to ensure console output doesn't mix with Ink output.
38
+ *
39
+ * @default true
40
+ */
41
+ patchConsole?: boolean
42
+
43
+ /**
44
+ * Called after each frame render with timing and flicker information.
45
+ */
46
+ onFrame?: (event: FrameEvent) => void
47
+
48
+ /**
49
+ * Called when a click lands on a cell with an OSC 8 hyperlink (or a
50
+ * plain-text URL the renderer detects on the same row). The host owns
51
+ * the actual open — `child_process.spawn` with an argv array (NOT
52
+ * shell-mode) to the platform's native opener: `open` on macOS,
53
+ * `xdg-open` on Linux/BSD, `explorer.exe` on Windows. Avoid
54
+ * `cmd.exe /c start` — `start` is a cmd builtin that reparses the URL
55
+ * through cmd's tokenizer (`&` / `|` / `^` / `<` / `>` get split or
56
+ * reinterpreted as command syntax), which both breaks plain URLs with
57
+ * `&` in query strings and undermines any protocol allowlist on the
58
+ * caller side. NasTech wires this in `entry.tsx`; library users who
59
+ * don't pass it will see clickable underline styling but no action on
60
+ * click in any terminal where mouse tracking is on.
61
+ */
62
+ onHyperlinkClick?: (url: string) => void
63
+ }
64
+
65
+ export type Instance = {
66
+ /**
67
+ * Replace previous root node with a new one or update props of the current root node.
68
+ */
69
+ rerender: Ink['render']
70
+ /**
71
+ * Manually unmount the whole Ink app.
72
+ */
73
+ unmount: Ink['unmount']
74
+ /**
75
+ * Returns a promise, which resolves when app is unmounted.
76
+ */
77
+ waitUntilExit: Ink['waitUntilExit']
78
+ cleanup: () => void
79
+ }
80
+
81
+ /**
82
+ * A managed Ink root, similar to react-dom's createRoot API.
83
+ * Separates instance creation from rendering so the same root
84
+ * can be reused for multiple sequential screens.
85
+ */
86
+ export type Root = {
87
+ render: (node: ReactNode) => void
88
+ unmount: () => void
89
+ waitUntilExit: () => Promise<void>
90
+ }
91
+
92
+ export const forceRedraw = (stdout: NodeJS.WriteStream = process.stdout): boolean => {
93
+ const instance = instances.get(stdout)
94
+
95
+ if (!instance) {
96
+ return false
97
+ }
98
+
99
+ instance.forceRedraw()
100
+
101
+ return true
102
+ }
103
+
104
+ /**
105
+ * Mount a component and render the output.
106
+ */
107
+ export const renderSync = (node: ReactNode, options?: NodeJS.WriteStream | RenderOptions): Instance => {
108
+ const opts = getOptions(options)
109
+
110
+ const inkOptions: InkOptions = {
111
+ stdout: process.stdout,
112
+ stdin: process.stdin,
113
+ stderr: process.stderr,
114
+ exitOnCtrlC: true,
115
+ patchConsole: true,
116
+ ...opts
117
+ }
118
+
119
+ const instance: Ink = getInstance(inkOptions.stdout, () => new Ink(inkOptions))
120
+
121
+ instance.render(node)
122
+
123
+ return {
124
+ rerender: instance.render,
125
+ unmount() {
126
+ instance.unmount()
127
+ },
128
+ waitUntilExit: instance.waitUntilExit,
129
+ cleanup: () => instances.delete(inkOptions.stdout)
130
+ }
131
+ }
132
+
133
+ const wrappedRender = async (node: ReactNode, options?: NodeJS.WriteStream | RenderOptions): Promise<Instance> => {
134
+ // Preserve the microtask boundary that `await loadYoga()` used to provide.
135
+ // Without it, the first render fires synchronously before async startup work
136
+ // (e.g. useReplBridge notification state) settles, and the subsequent Static
137
+ // write overwrites scrollback instead of appending below the logo.
138
+ await Promise.resolve()
139
+ const instance = renderSync(node, options)
140
+ logForDebugging(`[render] first ink render: ${Math.round(process.uptime() * 1000)}ms since process start`)
141
+
142
+ return instance
143
+ }
144
+
145
+ export default wrappedRender
146
+
147
+ /**
148
+ * Create an Ink root without rendering anything yet.
149
+ * Like react-dom's createRoot — call root.render() to mount a tree.
150
+ */
151
+ export async function createRoot({
152
+ stdout = process.stdout,
153
+ stdin = process.stdin,
154
+ stderr = process.stderr,
155
+ exitOnCtrlC = true,
156
+ patchConsole = true,
157
+ onFrame,
158
+ onHyperlinkClick
159
+ }: RenderOptions = {}): Promise<Root> {
160
+ // See wrappedRender — preserve microtask boundary from the old WASM await.
161
+ await Promise.resolve()
162
+
163
+ const instance = new Ink({
164
+ stdout,
165
+ stdin,
166
+ stderr,
167
+ exitOnCtrlC,
168
+ patchConsole,
169
+ onFrame,
170
+ onHyperlinkClick
171
+ })
172
+
173
+ // Register in the instances map so that code that looks up the Ink
174
+ // instance by stdout (e.g. external editor pause/resume) can find it.
175
+ instances.set(stdout, instance)
176
+
177
+ return {
178
+ render: node => instance.render(node),
179
+ unmount: () => instance.unmount(),
180
+ waitUntilExit: () => instance.waitUntilExit()
181
+ }
182
+ }
183
+
184
+ const getOptions = (stdout: NodeJS.WriteStream | RenderOptions | undefined = {}): RenderOptions => {
185
+ if (stdout instanceof Stream) {
186
+ return {
187
+ stdout,
188
+ stdin: process.stdin
189
+ }
190
+ }
191
+
192
+ return stdout
193
+ }
194
+
195
+ const getInstance = (stdout: NodeJS.WriteStream, createInstance: () => Ink): Ink => {
196
+ let instance = instances.get(stdout)
197
+
198
+ if (!instance) {
199
+ instance = createInstance()
200
+ instances.set(stdout, instance)
201
+ }
202
+
203
+ return instance
204
+ }