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,444 @@
1
+ import { copyFile, mkdir, readFile, writeFile } from 'node:fs/promises'
2
+ import { homedir } from 'node:os'
3
+ import { join } from 'node:path'
4
+
5
+ export type SupportedTerminal = 'cursor' | 'vscode' | 'windsurf'
6
+
7
+ export type FileOps = {
8
+ copyFile: typeof copyFile
9
+ mkdir: typeof mkdir
10
+ readFile: typeof readFile
11
+ writeFile: typeof writeFile
12
+ }
13
+
14
+ type Keybinding = {
15
+ args?: { text?: string }
16
+ command?: string
17
+ key?: string
18
+ when?: string
19
+ }
20
+
21
+ export type TerminalSetupResult = {
22
+ message: string
23
+ requiresRestart?: boolean
24
+ success: boolean
25
+ }
26
+
27
+ const DEFAULT_FILE_OPS: FileOps = { copyFile, mkdir, readFile, writeFile }
28
+ const COPY_SEQUENCE = '\u001b[99;13u'
29
+ const MULTILINE_SEQUENCE = '\\\r\n'
30
+
31
+ const TERMINAL_META: Record<SupportedTerminal, { appName: string; label: string }> = {
32
+ vscode: { appName: 'Code', label: 'VS Code' },
33
+ cursor: { appName: 'Cursor', label: 'Cursor' },
34
+ windsurf: { appName: 'Windsurf', label: 'Windsurf' }
35
+ }
36
+
37
+ const MAC_COPY_BINDING: Keybinding = {
38
+ key: 'cmd+c',
39
+ command: 'workbench.action.terminal.sendSequence',
40
+ when: 'terminalFocus && terminalTextSelected',
41
+ args: { text: COPY_SEQUENCE }
42
+ }
43
+
44
+ const BASE_BINDINGS: Keybinding[] = [
45
+ {
46
+ key: 'shift+enter',
47
+ command: 'workbench.action.terminal.sendSequence',
48
+ when: 'terminalFocus',
49
+ args: { text: MULTILINE_SEQUENCE }
50
+ },
51
+ {
52
+ key: 'ctrl+enter',
53
+ command: 'workbench.action.terminal.sendSequence',
54
+ when: 'terminalFocus',
55
+ args: { text: MULTILINE_SEQUENCE }
56
+ },
57
+ {
58
+ key: 'cmd+enter',
59
+ command: 'workbench.action.terminal.sendSequence',
60
+ when: 'terminalFocus',
61
+ args: { text: MULTILINE_SEQUENCE }
62
+ },
63
+ {
64
+ key: 'cmd+z',
65
+ command: 'workbench.action.terminal.sendSequence',
66
+ when: 'terminalFocus',
67
+ args: { text: '\u001b[122;9u' }
68
+ },
69
+ {
70
+ key: 'shift+cmd+z',
71
+ command: 'workbench.action.terminal.sendSequence',
72
+ when: 'terminalFocus',
73
+ args: { text: '\u001b[122;10u' }
74
+ }
75
+ ]
76
+
77
+ const targetBindings = (platform: NodeJS.Platform): Keybinding[] =>
78
+ platform === 'darwin' ? [MAC_COPY_BINDING, ...BASE_BINDINGS] : BASE_BINDINGS
79
+
80
+ export function detectVSCodeLikeTerminal(env: NodeJS.ProcessEnv = process.env): null | SupportedTerminal {
81
+ const askpass = env['VSCODE_GIT_ASKPASS_MAIN']?.toLowerCase() ?? ''
82
+
83
+ if (env['CURSOR_TRACE_ID'] || askpass.includes('cursor')) {
84
+ return 'cursor'
85
+ }
86
+
87
+ if (askpass.includes('windsurf')) {
88
+ return 'windsurf'
89
+ }
90
+
91
+ if (env['TERM_PROGRAM'] === 'vscode' || env['VSCODE_GIT_IPC_HANDLE']) {
92
+ return 'vscode'
93
+ }
94
+
95
+ return null
96
+ }
97
+
98
+ /**
99
+ * Strip JSONC features (// line comments, /* block comments *\/, trailing commas)
100
+ * so the result is valid JSON parseable by JSON.parse().
101
+ * Handles comments inside strings correctly (preserves them).
102
+ */
103
+ export function stripJsonComments(content: string): string {
104
+ let result = ''
105
+ let i = 0
106
+ const len = content.length
107
+
108
+ while (i < len) {
109
+ const ch = content[i]!
110
+
111
+ // String literal — copy as-is, including any comment-like chars inside
112
+ if (ch === '"') {
113
+ let j = i + 1
114
+
115
+ while (j < len) {
116
+ if (content[j] === '\\') {
117
+ j += 2 // skip escaped char
118
+ } else if (content[j] === '"') {
119
+ j++
120
+
121
+ break
122
+ } else {
123
+ j++
124
+ }
125
+ }
126
+
127
+ result += content.slice(i, j)
128
+ i = j
129
+
130
+ continue
131
+ }
132
+
133
+ // Line comment
134
+ if (ch === '/' && content[i + 1] === '/') {
135
+ const eol = content.indexOf('\n', i)
136
+ i = eol === -1 ? len : eol
137
+
138
+ continue
139
+ }
140
+
141
+ // Block comment
142
+ if (ch === '/' && content[i + 1] === '*') {
143
+ const end = content.indexOf('*/', i + 2)
144
+ i = end === -1 ? len : end + 2
145
+
146
+ continue
147
+ }
148
+
149
+ result += ch
150
+ i++
151
+ }
152
+
153
+ // Remove trailing commas before ] or }
154
+ return result.replace(/,(\s*[}\]])/g, '$1')
155
+ }
156
+
157
+ export function isRemoteShellSession(env: NodeJS.ProcessEnv): boolean {
158
+ return Boolean(env['SSH_CONNECTION'] || env['SSH_TTY'] || env['SSH_CLIENT'])
159
+ }
160
+
161
+ export function getVSCodeStyleConfigDir(
162
+ appName: string,
163
+ platform: NodeJS.Platform = process.platform,
164
+ env: NodeJS.ProcessEnv = process.env,
165
+ homeDir: string = homedir()
166
+ ): null | string {
167
+ if (platform === 'darwin') {
168
+ return join(homeDir, 'Library', 'Application Support', appName, 'User')
169
+ }
170
+
171
+ if (platform === 'win32') {
172
+ return env['APPDATA'] ? join(env['APPDATA'], appName, 'User') : null
173
+ }
174
+
175
+ return join(homeDir, '.config', appName, 'User')
176
+ }
177
+
178
+ function isKeybinding(value: unknown): value is Keybinding {
179
+ return typeof value === 'object' && value !== null
180
+ }
181
+
182
+ function sameBinding(a: Keybinding, b: Keybinding): boolean {
183
+ return a.key === b.key && a.command === b.command && a.when === b.when && a.args?.text === b.args?.text
184
+ }
185
+
186
+ type WhenRequirements = {
187
+ forbidden: Set<string>
188
+ required: Set<string>
189
+ }
190
+
191
+ const WHEN_TOKEN_RE = /!?[A-Za-z_][\w.]*/g
192
+
193
+ function parseWhenRequirements(when: string): WhenRequirements {
194
+ const required = new Set<string>()
195
+ const forbidden = new Set<string>()
196
+
197
+ for (const [token] of when.matchAll(WHEN_TOKEN_RE)) {
198
+ if (token.startsWith('!')) {
199
+ forbidden.add(token.slice(1))
200
+ } else {
201
+ required.add(token)
202
+ }
203
+ }
204
+
205
+ return { forbidden, required }
206
+ }
207
+
208
+ function requirementsContradict(a: WhenRequirements, b: WhenRequirements): boolean {
209
+ for (const token of a.required) {
210
+ if (b.forbidden.has(token)) {
211
+ return true
212
+ }
213
+ }
214
+
215
+ for (const token of b.required) {
216
+ if (a.forbidden.has(token)) {
217
+ return true
218
+ }
219
+ }
220
+
221
+ return false
222
+ }
223
+
224
+ function whensOverlap(a: string, b: string): boolean {
225
+ if (a === b) {
226
+ return true
227
+ }
228
+
229
+ // Empty when = global, overlaps every context.
230
+ if (!a || !b) {
231
+ return true
232
+ }
233
+
234
+ const left = parseWhenRequirements(a)
235
+ const right = parseWhenRequirements(b)
236
+
237
+ if (requirementsContradict(left, right)) {
238
+ return false
239
+ }
240
+
241
+ // This intentionally avoids a full VS Code when-clause parser. If two
242
+ // same-key bindings share a positive context token and don't explicitly
243
+ // contradict each other, they can fire together in that context.
244
+ for (const token of left.required) {
245
+ if (right.required.has(token)) {
246
+ return true
247
+ }
248
+ }
249
+
250
+ return false
251
+ }
252
+
253
+ // VS Code allows multiple bindings on the same key as long as their `when`
254
+ // clauses don't overlap. We flag a conflict when the contexts overlap but
255
+ // the bindings differ — e.g. existing `terminalFocus` cmd+c overlaps with
256
+ // our `terminalFocus && terminalTextSelected`, so the existing binding
257
+ // would shadow ours when text isn't selected.
258
+ function bindingsConflict(existing: Keybinding, target: Keybinding): boolean {
259
+ if (existing.key !== target.key) {
260
+ return false
261
+ }
262
+
263
+ if (!whensOverlap(existing.when ?? '', target.when ?? '')) {
264
+ return false
265
+ }
266
+
267
+ return !sameBinding(existing, target)
268
+ }
269
+
270
+ async function backupFile(filePath: string, ops: FileOps): Promise<void> {
271
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-')
272
+ await ops.copyFile(filePath, `${filePath}.backup.${stamp}`)
273
+ }
274
+
275
+ export async function configureTerminalKeybindings(
276
+ terminal: SupportedTerminal,
277
+ options?: {
278
+ env?: NodeJS.ProcessEnv
279
+ fileOps?: Partial<FileOps>
280
+ homeDir?: string
281
+ platform?: NodeJS.Platform
282
+ }
283
+ ): Promise<TerminalSetupResult> {
284
+ const env = options?.env ?? process.env
285
+ const platform = options?.platform ?? process.platform
286
+ const homeDir = options?.homeDir ?? homedir()
287
+ const ops: FileOps = { ...DEFAULT_FILE_OPS, ...(options?.fileOps ?? {}) }
288
+ const meta = TERMINAL_META[terminal]
289
+
290
+ if (isRemoteShellSession(env)) {
291
+ return {
292
+ success: false,
293
+ message: `${meta.label} terminal setup must be run on the local machine, not inside an SSH session.`
294
+ }
295
+ }
296
+
297
+ const configDir = getVSCodeStyleConfigDir(meta.appName, platform, env, homeDir)
298
+
299
+ if (!configDir) {
300
+ return {
301
+ success: false,
302
+ message: `Could not determine ${meta.label} settings path on this platform.`
303
+ }
304
+ }
305
+
306
+ const keybindingsFile = join(configDir, 'keybindings.json')
307
+
308
+ try {
309
+ await ops.mkdir(configDir, { recursive: true })
310
+
311
+ let keybindings: unknown[] = []
312
+ let hasExistingFile = false
313
+
314
+ try {
315
+ const content = await ops.readFile(keybindingsFile, 'utf8')
316
+ hasExistingFile = true
317
+ const parsed: unknown = JSON.parse(stripJsonComments(content))
318
+
319
+ if (!Array.isArray(parsed)) {
320
+ return {
321
+ success: false,
322
+ message: `${meta.label} keybindings.json is not a JSON array: ${keybindingsFile}`
323
+ }
324
+ }
325
+
326
+ keybindings = parsed
327
+ } catch (error) {
328
+ const code = (error as NodeJS.ErrnoException | undefined)?.code
329
+
330
+ if (code !== 'ENOENT') {
331
+ return {
332
+ success: false,
333
+ message: `Failed to read ${meta.label} keybindings: ${error}`
334
+ }
335
+ }
336
+ }
337
+
338
+ const targets = targetBindings(platform)
339
+
340
+ const conflicts = targets.filter(target =>
341
+ keybindings.some(existing => isKeybinding(existing) && bindingsConflict(existing, target))
342
+ )
343
+
344
+ if (conflicts.length) {
345
+ return {
346
+ success: false,
347
+ message:
348
+ `Existing terminal keybindings would conflict in ${keybindingsFile}: ` + conflicts.map(c => c.key).join(', ')
349
+ }
350
+ }
351
+
352
+ let added = 0
353
+
354
+ for (const target of targets.slice().reverse()) {
355
+ const exists = keybindings.some(existing => isKeybinding(existing) && sameBinding(existing, target))
356
+
357
+ if (!exists) {
358
+ keybindings.unshift(target)
359
+ added += 1
360
+ }
361
+ }
362
+
363
+ if (!added) {
364
+ return {
365
+ success: true,
366
+ message: `${meta.label} terminal keybindings already configured.`
367
+ }
368
+ }
369
+
370
+ if (hasExistingFile) {
371
+ await backupFile(keybindingsFile, ops)
372
+ }
373
+
374
+ await ops.writeFile(keybindingsFile, `${JSON.stringify(keybindings, null, 2)}\n`, 'utf8')
375
+
376
+ return {
377
+ success: true,
378
+ requiresRestart: true,
379
+ message: `Added ${added} ${meta.label} terminal keybinding${added === 1 ? '' : 's'} in ${keybindingsFile}`
380
+ }
381
+ } catch (error) {
382
+ return {
383
+ success: false,
384
+ message: `Failed to configure ${meta.label} terminal shortcuts: ${error}`
385
+ }
386
+ }
387
+ }
388
+
389
+ export async function configureDetectedTerminalKeybindings(options?: {
390
+ env?: NodeJS.ProcessEnv
391
+ fileOps?: Partial<FileOps>
392
+ homeDir?: string
393
+ platform?: NodeJS.Platform
394
+ }): Promise<TerminalSetupResult> {
395
+ const detected = detectVSCodeLikeTerminal(options?.env ?? process.env)
396
+
397
+ if (!detected) {
398
+ return {
399
+ success: false,
400
+ message: 'No supported IDE terminal detected. Supported: VS Code, Cursor, Windsurf.'
401
+ }
402
+ }
403
+
404
+ return configureTerminalKeybindings(detected, options)
405
+ }
406
+
407
+ export async function shouldPromptForTerminalSetup(options?: {
408
+ env?: NodeJS.ProcessEnv
409
+ fileOps?: Partial<FileOps>
410
+ homeDir?: string
411
+ platform?: NodeJS.Platform
412
+ }): Promise<boolean> {
413
+ const env = options?.env ?? process.env
414
+ const detected = detectVSCodeLikeTerminal(env)
415
+
416
+ if (!detected || isRemoteShellSession(env)) {
417
+ return false
418
+ }
419
+
420
+ const platform = options?.platform ?? process.platform
421
+ const homeDir = options?.homeDir ?? homedir()
422
+ const ops: FileOps = { ...DEFAULT_FILE_OPS, ...(options?.fileOps ?? {}) }
423
+ const meta = TERMINAL_META[detected]
424
+ const configDir = getVSCodeStyleConfigDir(meta.appName, platform, env, homeDir)
425
+
426
+ if (!configDir) {
427
+ return false
428
+ }
429
+
430
+ try {
431
+ const content = await ops.readFile(join(configDir, 'keybindings.json'), 'utf8')
432
+ const parsed: unknown = JSON.parse(stripJsonComments(content))
433
+
434
+ if (!Array.isArray(parsed)) {
435
+ return true
436
+ }
437
+
438
+ return targetBindings(platform).some(
439
+ target => !parsed.some(existing => isKeybinding(existing) && sameBinding(existing, target))
440
+ )
441
+ } catch {
442
+ return true
443
+ }
444
+ }
@@ -0,0 +1,29 @@
1
+ const TERMUX_PREFIX = '/data/data/com.termux/files/usr'
2
+
3
+ const truthy = (value?: string) => /^(?:1|true|yes|on)$/i.test(String(value ?? '').trim())
4
+
5
+ export const isTermuxEnv = (env: NodeJS.ProcessEnv = process.env): boolean => {
6
+ const prefix = String(env.PREFIX ?? '')
7
+
8
+ return Boolean(env.TERMUX_VERSION) || prefix.includes(TERMUX_PREFIX)
9
+ }
10
+
11
+ /**
12
+ * Return true when NasTech should enable Termux-focused TUI defaults.
13
+ *
14
+ * Defaults to on in Termux, with an explicit opt-out for debugging:
15
+ * NASTECH_TUI_TERMUX_MODE=0
16
+ */
17
+ export const isTermuxTuiMode = (env: NodeJS.ProcessEnv = process.env): boolean => {
18
+ if (!isTermuxEnv(env)) {
19
+ return false
20
+ }
21
+
22
+ const override = String(env.NASTECH_TUI_TERMUX_MODE ?? '').trim().toLowerCase()
23
+
24
+ if (override) {
25
+ return truthy(override)
26
+ }
27
+
28
+ return true
29
+ }
@@ -0,0 +1,18 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { stripTrailingPasteNewlines } from './text.js'
4
+
5
+ describe('stripTrailingPasteNewlines', () => {
6
+ it('removes trailing newline runs from pasted text', () => {
7
+ expect(stripTrailingPasteNewlines('alpha\n')).toBe('alpha')
8
+ expect(stripTrailingPasteNewlines('alpha\nbeta\n\n')).toBe('alpha\nbeta')
9
+ })
10
+
11
+ it('preserves interior newlines', () => {
12
+ expect(stripTrailingPasteNewlines('alpha\nbeta\ngamma')).toBe('alpha\nbeta\ngamma')
13
+ })
14
+
15
+ it('preserves newline-only pastes', () => {
16
+ expect(stripTrailingPasteNewlines('\n\n')).toBe('\n\n')
17
+ })
18
+ })