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,770 @@
1
+ // Best-effort LaTeX → Unicode for inline / display math captured by the
2
+ // markdown renderer. The terminal can't typeset LaTeX, but Unicode covers
3
+ // most of what models actually emit: Greek letters, blackboard / fraktur /
4
+ // calligraphic capitals, set theory + logic operators, common arrows,
5
+ // sub/superscripts, and `\frac{a}{b}` collapsed to `a/b`.
6
+ //
7
+ // Design rules:
8
+ // • Pure regex pipeline. Anything we don't recognise is preserved
9
+ // verbatim (so a `\foo{bar}` we've never heard of still survives).
10
+ // A real LaTeX parser would be more correct but throws on partial
11
+ // input — terminal users would rather see the raw command than a
12
+ // parse-error placeholder.
13
+ // • Longest-match-first ordering on commands so `\le` doesn't shadow
14
+ // `\leq`, `\sub` doesn't shadow `\subseteq`, etc.
15
+ // • Word-boundary lookahead `(?![A-Za-z])` after each command so
16
+ // `\pix` (made-up command) doesn't get partially substituted as `π`.
17
+ // • `\mathbb{X}`, `\mathcal{X}`, `\mathfrak{X}` only handle a single
18
+ // letter argument — multi-letter `\mathbb{NN}` is rare and would
19
+ // need a real parser to do correctly.
20
+ // • Sub/super scripts only convert if EVERY character has a Unicode
21
+ // equivalent. Mixed content like `^{n+1}` falls back to the raw
22
+ // LaTeX so we don't emit `ⁿ+¹` (which has no `+` superscript glyph
23
+ // in some fonts and reads worse than the source).
24
+
25
+ const SYMBOLS: Record<string, string> = {
26
+ // Greek lowercase
27
+ '\\alpha': 'α',
28
+ '\\beta': 'β',
29
+ '\\gamma': 'γ',
30
+ '\\delta': 'δ',
31
+ '\\epsilon': 'ε',
32
+ '\\varepsilon': 'ε',
33
+ '\\zeta': 'ζ',
34
+ '\\eta': 'η',
35
+ '\\theta': 'θ',
36
+ '\\vartheta': 'ϑ',
37
+ '\\iota': 'ι',
38
+ '\\kappa': 'κ',
39
+ '\\lambda': 'λ',
40
+ '\\mu': 'μ',
41
+ '\\nu': 'ν',
42
+ '\\xi': 'ξ',
43
+ '\\pi': 'π',
44
+ '\\varpi': 'ϖ',
45
+ '\\rho': 'ρ',
46
+ '\\varrho': 'ϱ',
47
+ '\\sigma': 'σ',
48
+ '\\varsigma': 'ς',
49
+ '\\tau': 'τ',
50
+ '\\upsilon': 'υ',
51
+ '\\phi': 'φ',
52
+ '\\varphi': 'φ',
53
+ '\\chi': 'χ',
54
+ '\\psi': 'ψ',
55
+ '\\omega': 'ω',
56
+
57
+ // Greek uppercase
58
+ '\\Gamma': 'Γ',
59
+ '\\Delta': 'Δ',
60
+ '\\Theta': 'Θ',
61
+ '\\Lambda': 'Λ',
62
+ '\\Xi': 'Ξ',
63
+ '\\Pi': 'Π',
64
+ '\\Sigma': 'Σ',
65
+ '\\Upsilon': 'Υ',
66
+ '\\Phi': 'Φ',
67
+ '\\Psi': 'Ψ',
68
+ '\\Omega': 'Ω',
69
+
70
+ // Big operators
71
+ '\\sum': '∑',
72
+ '\\prod': '∏',
73
+ '\\coprod': '∐',
74
+ '\\int': '∫',
75
+ '\\iint': '∬',
76
+ '\\iiint': '∭',
77
+ '\\oint': '∮',
78
+ '\\bigcup': '⋃',
79
+ '\\bigcap': '⋂',
80
+ '\\bigvee': '⋁',
81
+ '\\bigwedge': '⋀',
82
+ '\\bigoplus': '⨁',
83
+ '\\bigotimes': '⨂',
84
+
85
+ // Calculus
86
+ '\\partial': '∂',
87
+ '\\nabla': '∇',
88
+ '\\sqrt': '√',
89
+
90
+ // Sets
91
+ '\\emptyset': '∅',
92
+ '\\varnothing': '∅',
93
+ '\\infty': '∞',
94
+ '\\in': '∈',
95
+ '\\notin': '∉',
96
+ '\\ni': '∋',
97
+ '\\subset': '⊂',
98
+ '\\supset': '⊃',
99
+ '\\subseteq': '⊆',
100
+ '\\supseteq': '⊇',
101
+ '\\subsetneq': '⊊',
102
+ '\\supsetneq': '⊋',
103
+ '\\cup': '∪',
104
+ '\\cap': '∩',
105
+ '\\setminus': '∖',
106
+ '\\complement': '∁',
107
+
108
+ // Logic
109
+ '\\forall': '∀',
110
+ '\\exists': '∃',
111
+ '\\nexists': '∄',
112
+ '\\land': '∧',
113
+ '\\lor': '∨',
114
+ '\\lnot': '¬',
115
+ '\\neg': '¬',
116
+ '\\therefore': '∴',
117
+ '\\because': '∵',
118
+
119
+ // Relations
120
+ '\\le': '≤',
121
+ '\\leq': '≤',
122
+ '\\ge': '≥',
123
+ '\\geq': '≥',
124
+ '\\ne': '≠',
125
+ '\\neq': '≠',
126
+ '\\ll': '≪',
127
+ '\\gg': '≫',
128
+ '\\approx': '≈',
129
+ '\\equiv': '≡',
130
+ '\\cong': '≅',
131
+ '\\sim': '∼',
132
+ '\\simeq': '≃',
133
+ '\\propto': '∝',
134
+ '\\perp': '⊥',
135
+ '\\parallel': '∥',
136
+ '\\models': '⊨',
137
+ '\\vdash': '⊢',
138
+ '\\mid': '∣',
139
+ '\\nmid': '∤',
140
+ '\\divides': '∣',
141
+
142
+ // Common standalone glyphs
143
+ '\\blacksquare': '■',
144
+ '\\square': '□',
145
+ '\\Box': '□',
146
+ '\\qed': '∎',
147
+ '\\bigstar': '★',
148
+
149
+ // Modular arithmetic — the `\pmod{p}` form (with arg) is handled below;
150
+ // the bare `\bmod` / `\mod` commands are simple text substitutions.
151
+ '\\bmod': 'mod',
152
+ '\\mod': 'mod',
153
+
154
+ // Brackets / fences (named delimiter commands; the `\left\X` / `\right\X`
155
+ // unwrapping below leaves these behind for the symbol pass to resolve).
156
+ '\\langle': '⟨',
157
+ '\\rangle': '⟩',
158
+ '\\lceil': '⌈',
159
+ '\\rceil': '⌉',
160
+ '\\lfloor': '⌊',
161
+ '\\rfloor': '⌋',
162
+ '\\|': '‖',
163
+
164
+ // Arrows
165
+ '\\to': '→',
166
+ '\\rightarrow': '→',
167
+ '\\leftarrow': '←',
168
+ '\\leftrightarrow': '↔',
169
+ '\\Rightarrow': '⇒',
170
+ '\\Leftarrow': '⇐',
171
+ '\\Leftrightarrow': '⇔',
172
+ '\\implies': '⟹',
173
+ '\\impliedby': '⟸',
174
+ '\\iff': '⟺',
175
+ '\\mapsto': '↦',
176
+ '\\hookrightarrow': '↪',
177
+ '\\hookleftarrow': '↩',
178
+ '\\uparrow': '↑',
179
+ '\\downarrow': '↓',
180
+ '\\updownarrow': '↕',
181
+
182
+ // Binary operators
183
+ '\\cdot': '⋅',
184
+ '\\cdots': '⋯',
185
+ '\\ldots': '…',
186
+ '\\dots': '…',
187
+ '\\dotsb': '…',
188
+ '\\dotsc': '…',
189
+ '\\vdots': '⋮',
190
+ '\\ddots': '⋱',
191
+ '\\times': '×',
192
+ '\\div': '÷',
193
+ '\\pm': '±',
194
+ '\\mp': '∓',
195
+ '\\circ': '∘',
196
+ '\\bullet': '•',
197
+ '\\star': '⋆',
198
+ '\\ast': '∗',
199
+ '\\oplus': '⊕',
200
+ '\\ominus': '⊖',
201
+ '\\otimes': '⊗',
202
+ '\\odot': '⊙',
203
+ '\\diamond': '⋄',
204
+ '\\angle': '∠',
205
+ '\\triangle': '△',
206
+
207
+ // Spacing — collapse to varying widths of regular space
208
+ '\\,': ' ',
209
+ '\\;': ' ',
210
+ '\\:': ' ',
211
+ '\\!': '',
212
+ '\\ ': ' ',
213
+ '\\quad': ' ',
214
+ '\\qquad': ' ',
215
+
216
+ // Functions (LaTeX renders these in roman; we just keep the name)
217
+ '\\sin': 'sin',
218
+ '\\cos': 'cos',
219
+ '\\tan': 'tan',
220
+ '\\cot': 'cot',
221
+ '\\sec': 'sec',
222
+ '\\csc': 'csc',
223
+ '\\arcsin': 'arcsin',
224
+ '\\arccos': 'arccos',
225
+ '\\arctan': 'arctan',
226
+ '\\sinh': 'sinh',
227
+ '\\cosh': 'cosh',
228
+ '\\tanh': 'tanh',
229
+ '\\log': 'log',
230
+ '\\ln': 'ln',
231
+ '\\exp': 'exp',
232
+ '\\det': 'det',
233
+ '\\dim': 'dim',
234
+ '\\ker': 'ker',
235
+ '\\lim': 'lim',
236
+ '\\liminf': 'liminf',
237
+ '\\limsup': 'limsup',
238
+ '\\sup': 'sup',
239
+ '\\inf': 'inf',
240
+ '\\max': 'max',
241
+ '\\min': 'min',
242
+ '\\arg': 'arg',
243
+ '\\gcd': 'gcd',
244
+
245
+ // Escaped literals — model occasionally emits these for display
246
+ '\\&': '&',
247
+ '\\%': '%',
248
+ '\\$': '$',
249
+ '\\#': '#',
250
+ '\\_': '_',
251
+ '\\{': '{',
252
+ '\\}': '}'
253
+ }
254
+
255
+ const BB: Record<string, string> = {
256
+ A: '𝔸',
257
+ B: '𝔹',
258
+ C: 'ℂ',
259
+ D: '𝔻',
260
+ E: '𝔼',
261
+ F: '𝔽',
262
+ G: '𝔾',
263
+ H: 'ℍ',
264
+ I: '𝕀',
265
+ J: '𝕁',
266
+ K: '𝕂',
267
+ L: '𝕃',
268
+ M: '𝕄',
269
+ N: 'ℕ',
270
+ O: '𝕆',
271
+ P: 'ℙ',
272
+ Q: 'ℚ',
273
+ R: 'ℝ',
274
+ S: '𝕊',
275
+ T: '𝕋',
276
+ U: '𝕌',
277
+ V: '𝕍',
278
+ W: '𝕎',
279
+ X: '𝕏',
280
+ Y: '𝕐',
281
+ Z: 'ℤ'
282
+ }
283
+
284
+ const CAL: Record<string, string> = {
285
+ A: '𝒜',
286
+ B: 'ℬ',
287
+ C: '𝒞',
288
+ D: '𝒟',
289
+ E: 'ℰ',
290
+ F: 'ℱ',
291
+ G: '𝒢',
292
+ H: 'ℋ',
293
+ I: 'ℐ',
294
+ J: '𝒥',
295
+ K: '𝒦',
296
+ L: 'ℒ',
297
+ M: 'ℳ',
298
+ N: '𝒩',
299
+ O: '𝒪',
300
+ P: '𝒫',
301
+ Q: '𝒬',
302
+ R: 'ℛ',
303
+ S: '𝒮',
304
+ T: '𝒯',
305
+ U: '𝒰',
306
+ V: '𝒱',
307
+ W: '𝒲',
308
+ X: '𝒳',
309
+ Y: '𝒴',
310
+ Z: '𝒵'
311
+ }
312
+
313
+ const FRAK: Record<string, string> = {
314
+ A: '𝔄',
315
+ B: '𝔅',
316
+ C: 'ℭ',
317
+ D: '𝔇',
318
+ E: '𝔈',
319
+ F: '𝔉',
320
+ G: '𝔊',
321
+ H: 'ℌ',
322
+ I: 'ℑ',
323
+ J: '𝔍',
324
+ K: '𝔎',
325
+ L: '𝔏',
326
+ M: '𝔐',
327
+ N: '𝔑',
328
+ O: '𝔒',
329
+ P: '𝔓',
330
+ Q: '𝔔',
331
+ R: 'ℜ',
332
+ S: '𝔖',
333
+ T: '𝔗',
334
+ U: '𝔘',
335
+ V: '𝔙',
336
+ W: '𝔚',
337
+ X: '𝔛',
338
+ Y: '𝔜',
339
+ Z: 'ℨ'
340
+ }
341
+
342
+ const SUPERSCRIPT: Record<string, string> = {
343
+ '0': '⁰',
344
+ '1': '¹',
345
+ '2': '²',
346
+ '3': '³',
347
+ '4': '⁴',
348
+ '5': '⁵',
349
+ '6': '⁶',
350
+ '7': '⁷',
351
+ '8': '⁸',
352
+ '9': '⁹',
353
+ '+': '⁺',
354
+ '-': '⁻',
355
+ '=': '⁼',
356
+ '(': '⁽',
357
+ ')': '⁾',
358
+ a: 'ᵃ',
359
+ b: 'ᵇ',
360
+ c: 'ᶜ',
361
+ d: 'ᵈ',
362
+ e: 'ᵉ',
363
+ f: 'ᶠ',
364
+ g: 'ᵍ',
365
+ h: 'ʰ',
366
+ i: 'ⁱ',
367
+ j: 'ʲ',
368
+ k: 'ᵏ',
369
+ l: 'ˡ',
370
+ m: 'ᵐ',
371
+ n: 'ⁿ',
372
+ o: 'ᵒ',
373
+ p: 'ᵖ',
374
+ r: 'ʳ',
375
+ s: 'ˢ',
376
+ t: 'ᵗ',
377
+ u: 'ᵘ',
378
+ v: 'ᵛ',
379
+ w: 'ʷ',
380
+ x: 'ˣ',
381
+ y: 'ʸ',
382
+ z: 'ᶻ'
383
+ }
384
+
385
+ const SUBSCRIPT: Record<string, string> = {
386
+ '0': '₀',
387
+ '1': '₁',
388
+ '2': '₂',
389
+ '3': '₃',
390
+ '4': '₄',
391
+ '5': '₅',
392
+ '6': '₆',
393
+ '7': '₇',
394
+ '8': '₈',
395
+ '9': '₉',
396
+ '+': '₊',
397
+ '-': '₋',
398
+ '=': '₌',
399
+ '(': '₍',
400
+ ')': '₎',
401
+ a: 'ₐ',
402
+ e: 'ₑ',
403
+ h: 'ₕ',
404
+ i: 'ᵢ',
405
+ j: 'ⱼ',
406
+ k: 'ₖ',
407
+ l: 'ₗ',
408
+ m: 'ₘ',
409
+ n: 'ₙ',
410
+ o: 'ₒ',
411
+ p: 'ₚ',
412
+ r: 'ᵣ',
413
+ s: 'ₛ',
414
+ t: 'ₜ',
415
+ u: 'ᵤ',
416
+ v: 'ᵥ',
417
+ x: 'ₓ'
418
+ }
419
+
420
+ // Sentinel control characters used to mark `\boxed` / `\fbox` regions in
421
+ // the converted output. The renderer splits on these to apply a highlight
422
+ // style; consumers that don't want highlighting can strip them with the
423
+ // exported `BOX_RE` below.
424
+ export const BOX_OPEN = '\u0001'
425
+ export const BOX_CLOSE = '\u0002'
426
+ export const BOX_RE = /\u0001([^\u0001\u0002]*)\u0002/g
427
+
428
+ const escapeRe = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
429
+
430
+ // Pre-compile two symbol regexes: one for letter-ending commands (`\pi`,
431
+ // `\sum`) which need a `(?![A-Za-z])` lookahead so they don't partially
432
+ // match `\pix` or `\summa`, and one for punctuation-ending commands
433
+ // (`\{`, `\,`, `\|`) which must NOT have the lookahead — otherwise
434
+ // `\{p` would refuse to substitute because `p` is a letter.
435
+ //
436
+ // Longest commands first inside each group so `\leq` beats `\le`.
437
+ const splitByEnding = (keys: string[]) => {
438
+ const letter: string[] = []
439
+ const punct: string[] = []
440
+
441
+ for (const k of keys) {
442
+ if (/[A-Za-z]$/.test(k)) {
443
+ letter.push(k)
444
+ } else {
445
+ punct.push(k)
446
+ }
447
+ }
448
+
449
+ return { letter, punct }
450
+ }
451
+
452
+ const buildAlt = (cmds: string[]) =>
453
+ cmds
454
+ .sort((a, b) => b.length - a.length)
455
+ .map(escapeRe)
456
+ .join('|')
457
+
458
+ const { letter: LETTER_CMDS, punct: PUNCT_CMDS } = splitByEnding(Object.keys(SYMBOLS))
459
+
460
+ const SYMBOL_LETTER_RE = new RegExp('(?:' + buildAlt(LETTER_CMDS) + ')(?![A-Za-z])', 'g')
461
+ const SYMBOL_PUNCT_RE = new RegExp('(?:' + buildAlt(PUNCT_CMDS) + ')', 'g')
462
+
463
+ const convertScript = (input: string, table: Record<string, string>, sigil: '^' | '_'): string => {
464
+ let out = ''
465
+ let allMapped = true
466
+
467
+ for (const ch of input) {
468
+ const mapped = table[ch]
469
+
470
+ if (!mapped) {
471
+ allMapped = false
472
+
473
+ break
474
+ }
475
+
476
+ out += mapped
477
+ }
478
+
479
+ if (allMapped) {
480
+ return out
481
+ }
482
+
483
+ // Fallback: if the body is a single visible character (e.g. `∞` after
484
+ // earlier symbol substitution), render it without braces — `^∞` reads
485
+ // far better than `^{∞}` in a terminal. Multi-char bodies that don't
486
+ // fully convert use parens (`e^(iπ)`) instead of braces (`e^{iπ}`)
487
+ // because parens are normal punctuation while braces look like
488
+ // unrendered LaTeX.
489
+ const trimmed = input.trim()
490
+
491
+ if ([...trimmed].length === 1) {
492
+ return `${sigil}${trimmed}`
493
+ }
494
+
495
+ return `${sigil}(${trimmed})`
496
+ }
497
+
498
+ // Walk the string and parse `{...}` honouring nested braces. Unlike a
499
+ // `\{[^{}]*\}` regex this survives `\frac{|t|^{p-1}|P(t)|^p}{...}` where
500
+ // the numerator contains its own braces from a superscript. Returns the
501
+ // inner content (without the outer braces) and the offset just past the
502
+ // closing `}`. Returns null if there is no balanced brace at `start`.
503
+ const readBraced = (s: string, start: number): { content: string; end: number } | null => {
504
+ if (s[start] !== '{') {
505
+ return null
506
+ }
507
+
508
+ let depth = 1
509
+ let i = start + 1
510
+
511
+ while (i < s.length && depth > 0) {
512
+ const c = s[i]
513
+
514
+ // Skip escapes — `\{` and `\}` inside a body are literal braces and
515
+ // should not change the brace counter.
516
+ if (c === '\\' && i + 1 < s.length) {
517
+ i += 2
518
+ continue
519
+ }
520
+
521
+ if (c === '{') {
522
+ depth++
523
+ } else if (c === '}') {
524
+ depth--
525
+ }
526
+
527
+ if (depth > 0) {
528
+ i++
529
+ }
530
+ }
531
+
532
+ if (depth !== 0) {
533
+ return null
534
+ }
535
+
536
+ return { content: s.slice(start + 1, i), end: i + 1 }
537
+ }
538
+
539
+ // Replace every occurrence of `\command{arg}` using balanced-brace parsing
540
+ // (so `\boxed{x^{n+1}}` works where a `[^{}]*` regex would fail). The
541
+ // `render` callback receives the inner content already recursed-into, so
542
+ // `\boxed{\boxed{x}}` resolves outside-in cleanly. Unmatched `\command`
543
+ // (no following `{...}`) is preserved verbatim.
544
+ const replaceBracedCommand = (input: string, command: string, render: (content: string) => string): string => {
545
+ const cmdLen = command.length
546
+ let out = ''
547
+ let i = 0
548
+
549
+ while (i < input.length) {
550
+ const idx = input.indexOf(command, i)
551
+
552
+ if (idx < 0) {
553
+ out += input.slice(i)
554
+
555
+ return out
556
+ }
557
+
558
+ const after = input[idx + cmdLen]
559
+
560
+ if (after && /[A-Za-z]/.test(after)) {
561
+ out += input.slice(i, idx + cmdLen)
562
+ i = idx + cmdLen
563
+ continue
564
+ }
565
+
566
+ out += input.slice(i, idx)
567
+
568
+ let p = idx + cmdLen
569
+
570
+ while (input[p] === ' ' || input[p] === '\t') p++
571
+
572
+ const arg = readBraced(input, p)
573
+
574
+ if (!arg) {
575
+ out += input.slice(idx, p + 1)
576
+ i = p + 1
577
+ continue
578
+ }
579
+
580
+ out += render(replaceBracedCommand(arg.content, command, render))
581
+ i = arg.end
582
+ }
583
+
584
+ return out
585
+ }
586
+
587
+ // Replace every `\frac{num}{den}` with `num/den` (parens around either
588
+ // side when its precedence demands it). The recursion handles nested
589
+ // fractions naturally: `\frac{1}{\frac{1}{x}}` collapses to `1/(1/x)`
590
+ // because we recurse into `den` before deciding whether to parenthesise.
591
+ const replaceFracs = (input: string): string => {
592
+ let out = ''
593
+ let i = 0
594
+
595
+ while (i < input.length) {
596
+ const idx = input.indexOf('\\frac', i)
597
+
598
+ if (idx < 0) {
599
+ out += input.slice(i)
600
+
601
+ return out
602
+ }
603
+
604
+ const after = input[idx + 5]
605
+
606
+ // `(?![A-Za-z])` — protect hypothetical commands like `\fraction`.
607
+ if (after && /[A-Za-z]/.test(after)) {
608
+ out += input.slice(i, idx + 5)
609
+ i = idx + 5
610
+ continue
611
+ }
612
+
613
+ out += input.slice(i, idx)
614
+
615
+ let p = idx + 5
616
+
617
+ while (input[p] === ' ' || input[p] === '\t') p++
618
+
619
+ const num = readBraced(input, p)
620
+
621
+ if (!num) {
622
+ out += input.slice(idx, p + 1)
623
+ i = p + 1
624
+ continue
625
+ }
626
+
627
+ p = num.end
628
+
629
+ while (input[p] === ' ' || input[p] === '\t') p++
630
+
631
+ const den = readBraced(input, p)
632
+
633
+ if (!den) {
634
+ out += input.slice(idx, p + 1)
635
+ i = p + 1
636
+ continue
637
+ }
638
+
639
+ out += `${wrapForFrac(replaceFracs(num.content))}/${wrapForFrac(replaceFracs(den.content))}`
640
+ i = den.end
641
+ }
642
+
643
+ return out
644
+ }
645
+
646
+ // Wrap multi-token expressions in parens so `\frac{a+b}{c}` becomes
647
+ // `(a+b)/c` rather than `a+b/c`. We wrap whenever inline `/` would
648
+ // change the meaning — that's any binary operator (`+`, `-`, `*`, `/`)
649
+ // or whitespace separating tokens. `*` and `/` matter because nested
650
+ // fractions and products like `\frac{a*b}{c}` and `\frac{1/x}{y}` would
651
+ // otherwise read as `a*b/c` (right-associative ambiguity) and `1/x/y`.
652
+ // Atomic factors like `n!`, `x^2`, `\sin x` don't trigger any of these
653
+ // and stay un-parenthesised — wrapping them just clutters the output.
654
+ const wrapForFrac = (expr: string) => {
655
+ const trimmed = expr.trim()
656
+
657
+ if (!trimmed) {
658
+ return trimmed
659
+ }
660
+
661
+ if (/^\(.*\)$/.test(trimmed)) {
662
+ return trimmed
663
+ }
664
+
665
+ if (/[+\-/*]|\s/.test(trimmed)) {
666
+ return `(${trimmed})`
667
+ }
668
+
669
+ return trimmed
670
+ }
671
+
672
+ export function texToUnicode(input: string): string {
673
+ let s = input
674
+
675
+ s = s.replace(/\\mathbb\s*\{([A-Za-z])\}/g, (raw, c: string) => BB[c] ?? raw)
676
+ s = s.replace(/\\mathcal\s*\{([A-Za-z])\}/g, (raw, c: string) => CAL[c] ?? raw)
677
+ s = s.replace(/\\mathfrak\s*\{([A-Za-z])\}/g, (raw, c: string) => FRAK[c] ?? raw)
678
+ s = s.replace(/\\mathbf\s*\{([^{}]+)\}/g, (_, c: string) => c)
679
+ s = s.replace(/\\mathit\s*\{([^{}]+)\}/g, (_, c: string) => c)
680
+ s = s.replace(/\\mathrm\s*\{([^{}]+)\}/g, (_, c: string) => c)
681
+ s = s.replace(/\\text\s*\{([^{}]+)\}/g, (_, c: string) => c)
682
+ s = s.replace(/\\operatorname\s*\{([^{}]+)\}/g, (_, c: string) => c)
683
+
684
+ s = s.replace(/\\overline\s*\{([^{}]+)\}/g, (_, c: string) => `${c}\u0305`)
685
+ s = s.replace(/\\hat\s*\{([^{}]+)\}/g, (_, c: string) => `${c}\u0302`)
686
+ s = s.replace(/\\bar\s*\{([^{}]+)\}/g, (_, c: string) => `${c}\u0304`)
687
+ s = s.replace(/\\tilde\s*\{([^{}]+)\}/g, (_, c: string) => `${c}\u0303`)
688
+ s = s.replace(/\\vec\s*\{([^{}]+)\}/g, (_, c: string) => `${c}\u20D7`)
689
+ s = s.replace(/\\dot\s*\{([^{}]+)\}/g, (_, c: string) => `${c}\u0307`)
690
+ s = s.replace(/\\ddot\s*\{([^{}]+)\}/g, (_, c: string) => `${c}\u0308`)
691
+
692
+ s = replaceFracs(s)
693
+
694
+ // `\boxed{X}` / `\fbox{X}` highlight a final answer. Terminals can't
695
+ // draw a real box, so we wrap the content in U+0001 / U+0002 control
696
+ // characters — non-printable, never present in real text — and let the
697
+ // markdown renderer split on them and apply a highlight style (inverse
698
+ // video) to the bracketed region. This keeps `texToUnicode` pure-string
699
+ // while letting the React layer do the actual visual emphasis.
700
+ // Argument is parsed with balanced braces so nested `{...}` from
701
+ // superscripts / fractions inside the box survive.
702
+ s = replaceBracedCommand(s, '\\boxed', body => `${BOX_OPEN}${body.trim()}${BOX_CLOSE}`)
703
+ s = replaceBracedCommand(s, '\\fbox', body => `${BOX_OPEN}${body.trim()}${BOX_CLOSE}`)
704
+
705
+ // `\xrightarrow{label}` / `\xleftarrow{label}` collapse to an arrow with
706
+ // the label inline. LaTeX renders the label above the arrow; in monospace
707
+ // we put it adjacent — `─label→` is the closest readable approximation.
708
+ // Run before the symbol pass so the label can still pick up Greek and
709
+ // operator substitutions afterwards.
710
+ s = s.replace(/\\xrightarrow\s*\{([^{}]*)\}/g, (_, label: string) => `─${label.trim()}→`)
711
+ s = s.replace(/\\xleftarrow\s*\{([^{}]*)\}/g, (_, label: string) => `←${label.trim()}─`)
712
+ s = s.replace(/\\Longrightarrow/g, '⟹')
713
+ s = s.replace(/\\Longleftarrow/g, '⟸')
714
+ s = s.replace(/\\Longleftrightarrow/g, '⟺')
715
+
716
+ // `\pmod{p}` → ` (mod p)` (LaTeX adds parens automatically); `\pod{p}`
717
+ // is a paren-less variant; `\tag{n}` is the equation-number annotation
718
+ // shown to the right of an equation. Collapse to a single-space-prefixed
719
+ // bracketed form. The leading `\s*` in the pattern absorbs any whitespace
720
+ // already in the source so we don't end up with `b (mod p)` (double
721
+ // space) when the user wrote `b \pmod{p}`.
722
+ s = s.replace(/\s*\\pmod\s*\{([^{}]*)\}/g, (_, p: string) => ` (mod ${p.trim()})`)
723
+ s = s.replace(/\s*\\pod\s*\{([^{}]*)\}/g, (_, p: string) => ` (${p.trim()})`)
724
+ s = s.replace(/\s*\\tag\s*\{([^{}]*)\}/g, (_, n: string) => ` (${n.trim()})`)
725
+
726
+ // `\big`, `\Big`, `\bigg`, `\Bigg` (with optional `l`/`r`/`m` suffix)
727
+ // are sizing wrappers analogous to `\left`/`\right` but without the
728
+ // automatic-pairing semantics. Strip them and leave whatever delimiter
729
+ // follows. The trailing `(?![A-Za-z])` protects `\bigtriangleup` and
730
+ // any other letter-continuation command from being shaved.
731
+ s = s.replace(/\\(?:Bigg|bigg|Big|big)[lrm]?(?![A-Za-z])/g, '')
732
+
733
+ // Style / size hints that don't typeset any glyph and only affect how
734
+ // things would be sized in a real LaTeX engine. In a terminal every
735
+ // glyph is one monospace cell, so there's nothing to do — drop them
736
+ // (with any trailing whitespace) so they don't leak through as raw
737
+ // `\displaystyle` in the output.
738
+ s = s.replace(/\\(?:scriptscriptstyle|displaystyle|scriptstyle|textstyle|nolimits|limits)(?![A-Za-z])\s*/g, '')
739
+
740
+ // `\left` and `\right` are sizing wrappers around any delimiter — bare
741
+ // (`\left(`), escaped (`\left\{`), or named (`\left\langle`). Strip the
742
+ // wrapper unconditionally and let the rest of the pipeline (or the
743
+ // upcoming symbol pass) handle whatever delimiter follows. The optional
744
+ // `.?` consumes `\left.` / `\right.` which mean "no delimiter".
745
+ // Lookahead `(?![A-Za-z])` keeps `\leftarrow` / `\leftrightarrow` safe.
746
+ s = s.replace(/\\left(?![A-Za-z])\.?/g, '')
747
+ s = s.replace(/\\right(?![A-Za-z])\.?/g, '')
748
+
749
+ // Run symbol substitution BEFORE scripts so a body like `^{\infty}`
750
+ // becomes `^{∞}` first; convertScript can then either map ∞ to a
751
+ // superscript (it can't — Unicode lacks one) or fall back to `^∞`
752
+ // by stripping braces around the now-single-character body.
753
+ //
754
+ // Punctuation pass first — these can be followed by letters (`\{p`
755
+ // is "open-brace then p"), so the letter pass's `(?![A-Za-z])` rule
756
+ // would wrongly block them.
757
+ s = s.replace(SYMBOL_PUNCT_RE, m => SYMBOLS[m] ?? m)
758
+ s = s.replace(SYMBOL_LETTER_RE, m => SYMBOLS[m] ?? m)
759
+
760
+ // Bare `^c` / `_c` handles ONLY alphanumerics and `+`/`-`/`=`. Parens
761
+ // are intentionally excluded because the braced-fallback above can
762
+ // emit `(...)` and we don't want a second pass to greedily convert
763
+ // its opening paren into `⁽` and orphan the closing one.
764
+ s = s.replace(/\^\s*\{([^{}]+)\}/g, (_, body: string) => convertScript(body, SUPERSCRIPT, '^'))
765
+ s = s.replace(/\^([A-Za-z0-9+\-=])/g, (raw, ch: string) => SUPERSCRIPT[ch] ?? raw)
766
+ s = s.replace(/_\s*\{([^{}]+)\}/g, (_, body: string) => convertScript(body, SUBSCRIPT, '_'))
767
+ s = s.replace(/_([A-Za-z0-9+\-=])/g, (raw, ch: string) => SUBSCRIPT[ch] ?? raw)
768
+
769
+ return s
770
+ }