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 { AlternateScreen, Box, NoSelect, ScrollBox, Text } from '@nastechai/ink'
2
+ import { useStore } from '@nanostores/react'
3
+ import { Fragment, memo, useMemo, useRef } from 'react'
4
+
5
+ import { useGateway } from '../app/gatewayContext.js'
6
+ import type { AppLayoutProps } from '../app/interfaces.js'
7
+ import { $isBlocked, $overlayState, patchOverlayState } from '../app/overlayStore.js'
8
+ import { $uiState } from '../app/uiStore.js'
9
+ import { INLINE_MODE, SHOW_FPS, TERMUX_TUI_MODE } from '../config/env.js'
10
+ import { PLACEHOLDER } from '../content/placeholders.js'
11
+ import {
12
+ COMPOSER_PROMPT_GAP_WIDTH,
13
+ composerPromptWidth,
14
+ inputVisualHeight,
15
+ stableComposerColumns
16
+ } from '../lib/inputMetrics.js'
17
+ import { PerfPane } from '../lib/perfPane.js'
18
+ import { composerPromptText } from '../lib/prompt.js'
19
+
20
+ import { AgentsOverlay } from './agentsOverlay.js'
21
+ import { GoodVibesHeart, StatusRule, StickyPromptTracker, TranscriptScrollbar } from './appChrome.js'
22
+ import { FloatingOverlays, PromptZone } from './appOverlays.js'
23
+ import { Banner, Panel, SessionPanel } from './branding.js'
24
+ import { FpsOverlay } from './fpsOverlay.js'
25
+ import { HelpHint } from './helpHint.js'
26
+ import { MessageLine } from './messageLine.js'
27
+ import { QueuedMessages } from './queuedMessages.js'
28
+ import { LiveTodoPanel, StreamingAssistant } from './streamingAssistant.js'
29
+ import { TextInput, type TextInputMouseApi } from './textInput.js'
30
+
31
+ const PromptPrefix = memo(function PromptPrefix({
32
+ bold = false,
33
+ color,
34
+ promptText,
35
+ width
36
+ }: {
37
+ bold?: boolean
38
+ color: string
39
+ promptText: string
40
+ width: number
41
+ }) {
42
+ const glyphWidth = Math.max(1, width - COMPOSER_PROMPT_GAP_WIDTH)
43
+
44
+ return (
45
+ <Box width={width}>
46
+ <Box width={glyphWidth}>
47
+ <Text bold={bold} color={color}>
48
+ {promptText}
49
+ </Text>
50
+ </Box>
51
+ <Box width={COMPOSER_PROMPT_GAP_WIDTH} />
52
+ </Box>
53
+ )
54
+ })
55
+
56
+ const TranscriptPane = memo(function TranscriptPane({
57
+ actions,
58
+ composer,
59
+ progress,
60
+ transcript
61
+ }: Pick<AppLayoutProps, 'actions' | 'composer' | 'progress' | 'transcript'>) {
62
+ const ui = useStore($uiState)
63
+
64
+ // LiveTodoPanel rides as a child of the latest user-message row so it
65
+ // visually belongs to the prompt and follows it during scroll. -1 when
66
+ // empty → row.index === -1 is always false → no render.
67
+ const lastUserIdx = useMemo(() => {
68
+ const items = transcript.historyItems
69
+
70
+ for (let i = items.length - 1; i >= 0; i--) {
71
+ if (items[i].role === 'user') {
72
+ return i
73
+ }
74
+ }
75
+
76
+ return -1
77
+ }, [transcript.historyItems])
78
+
79
+ // Index of the first user-role message; every later user message gets a
80
+ // small dash above it so multi-turn transcripts visually segment by
81
+ // turn. -1 when no user message has been sent yet → no separator ever
82
+ // renders.
83
+ const firstUserIdx = useMemo(
84
+ () => transcript.historyItems.findIndex(m => m.role === 'user'),
85
+ [transcript.historyItems]
86
+ )
87
+
88
+ return (
89
+ <>
90
+ <ScrollBox
91
+ flexDirection="column"
92
+ flexGrow={1}
93
+ flexShrink={1}
94
+ onClick={(e: { cellIsBlank?: boolean }) => {
95
+ if (e.cellIsBlank) {
96
+ actions.clearSelection()
97
+ }
98
+ }}
99
+ ref={transcript.scrollRef}
100
+ stickyScroll
101
+ >
102
+ <Box flexDirection="column" paddingX={1}>
103
+ {transcript.virtualHistory.topSpacer > 0 ? <Box height={transcript.virtualHistory.topSpacer} /> : null}
104
+
105
+ {transcript.virtualRows.slice(transcript.virtualHistory.start, transcript.virtualHistory.end).map(row => (
106
+ <Box flexDirection="column" key={row.key} ref={transcript.virtualHistory.measureRef(row.key)}>
107
+ {row.msg.role === 'user' && firstUserIdx >= 0 && row.index > firstUserIdx && (
108
+ <Box marginTop={1}>
109
+ <Text color={ui.theme.color.border}>───</Text>
110
+ </Box>
111
+ )}
112
+
113
+ {row.msg.kind === 'intro' ? (
114
+ <Box flexDirection="column" paddingTop={1}>
115
+ <Banner maxWidth={Math.max(1, composer.cols - 2)} t={ui.theme} />
116
+
117
+ {row.msg.info && <SessionPanel info={row.msg.info} maxWidth={Math.max(1, composer.cols - 2)} sid={ui.sid} t={ui.theme} />}
118
+ </Box>
119
+ ) : row.msg.kind === 'panel' && row.msg.panelData ? (
120
+ <Panel sections={row.msg.panelData.sections} t={ui.theme} title={row.msg.panelData.title} />
121
+ ) : (
122
+ <MessageLine
123
+ cols={composer.cols}
124
+ compact={ui.compact}
125
+ detailsMode={ui.detailsMode}
126
+ detailsModeCommandOverride={ui.detailsModeCommandOverride}
127
+ msg={row.msg}
128
+ sections={ui.sections}
129
+ t={ui.theme}
130
+ />
131
+ )}
132
+
133
+ {row.index === lastUserIdx && <LiveTodoPanel />}
134
+ </Box>
135
+ ))}
136
+
137
+ {transcript.virtualHistory.bottomSpacer > 0 ? <Box height={transcript.virtualHistory.bottomSpacer} /> : null}
138
+
139
+ <StreamingAssistant
140
+ cols={composer.cols}
141
+ compact={ui.compact}
142
+ detailsMode={ui.detailsMode}
143
+ detailsModeCommandOverride={ui.detailsModeCommandOverride}
144
+ progress={progress}
145
+ sections={ui.sections}
146
+ />
147
+ </Box>
148
+ </ScrollBox>
149
+
150
+ <NoSelect flexShrink={0} marginLeft={1}>
151
+ <TranscriptScrollbar scrollRef={transcript.scrollRef} t={ui.theme} />
152
+ </NoSelect>
153
+
154
+ <StickyPromptTracker
155
+ messages={transcript.historyItems}
156
+ offsets={transcript.virtualHistory.offsets}
157
+ onChange={actions.setStickyPrompt}
158
+ scrollRef={transcript.scrollRef}
159
+ />
160
+ </>
161
+ )
162
+ })
163
+
164
+ const ComposerPane = memo(function ComposerPane({
165
+ actions,
166
+ composer,
167
+ status
168
+ }: Pick<AppLayoutProps, 'actions' | 'composer' | 'status'>) {
169
+ const ui = useStore($uiState)
170
+ const isBlocked = useStore($isBlocked)
171
+ const sh = (composer.inputBuf[0] ?? composer.input).startsWith('!')
172
+ const promptText = composerPromptText(ui.theme.brand.prompt, ui.info?.profile_name, sh, TERMUX_TUI_MODE, composer.cols)
173
+ const promptWidth = composerPromptWidth(promptText)
174
+ const promptBlank = ' '.repeat(promptWidth)
175
+ const inputColumns = stableComposerColumns(composer.cols, promptWidth, TERMUX_TUI_MODE)
176
+ const inputHeight = inputVisualHeight(composer.input, inputColumns)
177
+ const inputMouseRef = useRef<null | TextInputMouseApi>(null)
178
+
179
+ const captureInputDrag = (e: GutterMouseEvent) => {
180
+ if (e.button !== 0) {
181
+ return
182
+ }
183
+
184
+ e.stopImmediatePropagation?.()
185
+ inputMouseRef.current?.startAtBeginning()
186
+ }
187
+
188
+ // Drag origin matches the input box's top-left, so localRow / localCol
189
+ // map directly into TextInput coords (after backing out the prompt cell).
190
+ const dragFromPromptRow = (e: GutterMouseEvent) => {
191
+ if (e.button !== 0) {
192
+ return
193
+ }
194
+
195
+ e.stopImmediatePropagation?.()
196
+ inputMouseRef.current?.dragAt(e.localRow ?? 0, (e.localCol ?? 0) - promptWidth)
197
+ }
198
+
199
+ // Spacer rows live on a different vertical origin; only the column is
200
+ // parent-aligned with the input. Force row=0 so vertical drags can't
201
+ // jump the cursor to the wrong wrapped line.
202
+ const dragFromSpacer = (e: GutterMouseEvent) => {
203
+ if (e.button !== 0) {
204
+ return
205
+ }
206
+
207
+ e.stopImmediatePropagation?.()
208
+ inputMouseRef.current?.dragAt(0, (e.localCol ?? 0) - promptWidth)
209
+ }
210
+
211
+ const endInputDrag = () => inputMouseRef.current?.end()
212
+
213
+ return (
214
+ <NoSelect
215
+ flexDirection="column"
216
+ flexShrink={0}
217
+ fromLeftEdge
218
+ onClick={(e: { cellIsBlank?: boolean }) => {
219
+ if (e.cellIsBlank) {
220
+ actions.clearSelection()
221
+ }
222
+ }}
223
+ paddingX={1}
224
+ >
225
+ <QueuedMessages
226
+ cols={composer.cols}
227
+ queued={composer.queuedDisplay}
228
+ queueEditIdx={composer.queueEditIdx}
229
+ t={ui.theme}
230
+ />
231
+
232
+ {ui.bgTasks.size > 0 && (
233
+ <Text color={ui.theme.color.muted}>
234
+ {ui.bgTasks.size} background {ui.bgTasks.size === 1 ? 'task' : 'tasks'} running
235
+ </Text>
236
+ )}
237
+
238
+ {status.showStickyPrompt ? (
239
+ <Text color={ui.theme.color.muted} wrap="truncate-end">
240
+ <Text color={ui.theme.color.label}>↳ </Text>
241
+
242
+ {status.stickyPrompt}
243
+ </Text>
244
+ ) : (
245
+ <Box height={1} onMouseDown={captureInputDrag} onMouseDrag={dragFromSpacer} onMouseUp={endInputDrag} />
246
+ )}
247
+
248
+ <StatusRulePane at="top" composer={composer} status={status} />
249
+
250
+ <Box flexDirection="column" marginTop={ui.statusBar === 'top' ? 0 : 1} position="relative">
251
+ <FloatingOverlays
252
+ cols={composer.cols}
253
+ compIdx={composer.compIdx}
254
+ completions={composer.completions}
255
+ onActiveSessionSelect={actions.activateLiveSession}
256
+ onActiveSessionClose={actions.closeLiveSession}
257
+ onModelSelect={actions.onModelSelect}
258
+ onNewLiveSession={actions.newLiveSession}
259
+ onNewPromptSession={actions.newPromptSession}
260
+ onPickerSelect={actions.resumeById}
261
+ pagerPageSize={composer.pagerPageSize}
262
+ />
263
+
264
+ {composer.input === '?' && !composer.inputBuf.length && <HelpHint t={ui.theme} />}
265
+
266
+ {!isBlocked && (
267
+ <>
268
+ {composer.inputBuf.map((line, i) => (
269
+ <Box key={i}>
270
+ <Box width={promptWidth}>
271
+ {i === 0 ? (
272
+ <PromptPrefix color={ui.theme.color.muted} promptText={promptText} width={promptWidth} />
273
+ ) : (
274
+ <Text color={ui.theme.color.muted}>{promptBlank}</Text>
275
+ )}
276
+ </Box>
277
+
278
+ <Text color={ui.theme.color.text}>{line || ' '}</Text>
279
+ </Box>
280
+ ))}
281
+
282
+ <Box
283
+ onMouseDown={captureInputDrag}
284
+ onMouseDrag={dragFromPromptRow}
285
+ onMouseUp={endInputDrag}
286
+ position="relative"
287
+ width={Math.max(1, composer.cols - 2)}
288
+ >
289
+ <Box width={promptWidth}>
290
+ {sh ? (
291
+ <PromptPrefix color={ui.theme.color.shellDollar} promptText={promptText} width={promptWidth} />
292
+ ) : composer.inputBuf.length ? (
293
+ <Text color={ui.theme.color.prompt}>{promptBlank}</Text>
294
+ ) : (
295
+ <PromptPrefix bold color={ui.theme.color.prompt} promptText={promptText} width={promptWidth} />
296
+ )}
297
+ </Box>
298
+
299
+ <Box flexGrow={0} flexShrink={0} height={inputHeight} width={inputColumns}>
300
+ {/* Reserve the transcript scrollbar gutter too so typing never rewraps when the scrollbar column repaints. */}
301
+ <TextInput
302
+ columns={inputColumns}
303
+ mouseApiRef={inputMouseRef}
304
+ onChange={composer.updateInput}
305
+ onPaste={composer.handleTextPaste}
306
+ onSubmit={composer.submit}
307
+ placeholder={composer.empty ? PLACEHOLDER : ui.busy ? 'Ctrl+C to interrupt…' : ''}
308
+ value={composer.input}
309
+ voiceRecordKey={composer.voiceRecordKey}
310
+ />
311
+ </Box>
312
+
313
+ <Box position="absolute" right={0}>
314
+ <GoodVibesHeart t={ui.theme} tick={status.goodVibesTick} />
315
+ </Box>
316
+ </Box>
317
+ </>
318
+ )}
319
+ </Box>
320
+
321
+ {!composer.empty && !ui.sid && <Text color={ui.theme.color.muted}>⚕ {ui.status}</Text>}
322
+
323
+ <StatusRulePane at="bottom" composer={composer} status={status} />
324
+ </NoSelect>
325
+ )
326
+ })
327
+
328
+ const AgentsOverlayPane = memo(function AgentsOverlayPane() {
329
+ const { gw } = useGateway()
330
+ const ui = useStore($uiState)
331
+ const overlay = useStore($overlayState)
332
+
333
+ return (
334
+ <AgentsOverlay
335
+ gw={gw}
336
+ initialHistoryIndex={overlay.agentsInitialHistoryIndex}
337
+ onClose={() => patchOverlayState({ agents: false, agentsInitialHistoryIndex: 0 })}
338
+ t={ui.theme}
339
+ />
340
+ )
341
+ })
342
+
343
+ const StatusRulePane = memo(function StatusRulePane({
344
+ at,
345
+ composer,
346
+ status
347
+ }: Pick<AppLayoutProps, 'composer' | 'status'> & { at: 'bottom' | 'top' }) {
348
+ const ui = useStore($uiState)
349
+
350
+ if (ui.statusBar !== at) {
351
+ return null
352
+ }
353
+
354
+ return (
355
+ <Box marginTop={at === 'top' ? 1 : 0}>
356
+ <StatusRule
357
+ bgCount={ui.bgTasks.size}
358
+ busy={ui.busy}
359
+ cols={composer.cols}
360
+ cwdLabel={status.cwdLabel}
361
+ liveSessionCount={ui.liveSessionCount}
362
+ model={ui.info?.model ?? ''}
363
+ modelFast={ui.info?.fast || ui.info?.service_tier === 'priority'}
364
+ modelReasoningEffort={ui.info?.reasoning_effort}
365
+ onSessionCountClick={() => patchOverlayState({ sessions: true })}
366
+ sessionStartedAt={status.sessionStartedAt}
367
+ showCost={ui.showCost}
368
+ status={ui.status}
369
+ statusColor={status.statusColor}
370
+ t={ui.theme}
371
+ turnStartedAt={status.turnStartedAt}
372
+ usage={ui.usage}
373
+ voiceLabel={status.voiceLabel}
374
+ />
375
+ </Box>
376
+ )
377
+ })
378
+
379
+ export const AppLayout = memo(function AppLayout({
380
+ actions,
381
+ composer,
382
+ mouseTracking,
383
+ progress,
384
+ status,
385
+ transcript
386
+ }: AppLayoutProps) {
387
+ const overlay = useStore($overlayState)
388
+ const ui = useStore($uiState)
389
+
390
+ // Inline mode skips AlternateScreen so the host terminal's native
391
+ // scrollback captures rows scrolled off the top; composer + progress
392
+ // stay anchored via normal flex-column flow.
393
+ const Shell = INLINE_MODE ? Fragment : AlternateScreen
394
+ const shellProps = INLINE_MODE ? {} : { mouseTracking }
395
+
396
+ return (
397
+ <Shell {...shellProps}>
398
+ <Box flexDirection="column" flexGrow={1}>
399
+ <Box flexDirection="row" flexGrow={1}>
400
+ {overlay.agents ? (
401
+ <PerfPane id="agents">
402
+ <AgentsOverlayPane />
403
+ </PerfPane>
404
+ ) : (
405
+ <PerfPane id="transcript">
406
+ <TranscriptPane actions={actions} composer={composer} progress={progress} transcript={transcript} />
407
+ </PerfPane>
408
+ )}
409
+ </Box>
410
+
411
+ {!overlay.agents && (
412
+ <>
413
+ <PerfPane id="prompt">
414
+ <PromptZone
415
+ cols={composer.cols}
416
+ onApprovalChoice={actions.answerApproval}
417
+ onClarifyAnswer={actions.answerClarify}
418
+ onSecretSubmit={actions.answerSecret}
419
+ onSudoSubmit={actions.answerSudo}
420
+ />
421
+ </PerfPane>
422
+
423
+ <PerfPane id="composer">
424
+ <ComposerPane actions={actions} composer={composer} status={status} />
425
+ </PerfPane>
426
+
427
+ {SHOW_FPS && (
428
+ <Box flexShrink={0} justifyContent="flex-end" paddingRight={1}>
429
+ <FpsOverlay t={ui.theme} />
430
+ </Box>
431
+ )}
432
+ </>
433
+ )}
434
+ </Box>
435
+ </Shell>
436
+ )
437
+ })
438
+
439
+ type GutterMouseEvent = {
440
+ button: number
441
+ localCol?: number
442
+ localRow?: number
443
+ stopImmediatePropagation?: () => void
444
+ }
@@ -0,0 +1,254 @@
1
+ import { Box, Text } from '@nastechai/ink'
2
+ import { useStore } from '@nanostores/react'
3
+
4
+ import { useGateway } from '../app/gatewayContext.js'
5
+ import type { AppOverlaysProps } from '../app/interfaces.js'
6
+ import { $overlayState, patchOverlayState } from '../app/overlayStore.js'
7
+ import { $uiSessionId, $uiTheme } from '../app/uiStore.js'
8
+
9
+ import { ActiveSessionSwitcher } from './activeSessionSwitcher.js'
10
+ import { FloatBox } from './appChrome.js'
11
+ import { MaskedPrompt } from './maskedPrompt.js'
12
+ import { ModelPicker } from './modelPicker.js'
13
+ import { OverlayHint } from './overlayControls.js'
14
+ import { ApprovalPrompt, ClarifyPrompt, ConfirmPrompt } from './prompts.js'
15
+ import { SessionPicker } from './sessionPicker.js'
16
+ import { SkillsHub } from './skillsHub.js'
17
+
18
+ const COMPLETION_WINDOW = 16
19
+
20
+ export function PromptZone({
21
+ cols,
22
+ onApprovalChoice,
23
+ onClarifyAnswer,
24
+ onSecretSubmit,
25
+ onSudoSubmit
26
+ }: Pick<AppOverlaysProps, 'cols' | 'onApprovalChoice' | 'onClarifyAnswer' | 'onSecretSubmit' | 'onSudoSubmit'>) {
27
+ const overlay = useStore($overlayState)
28
+ const theme = useStore($uiTheme)
29
+
30
+ if (overlay.approval) {
31
+ return (
32
+ <Box flexDirection="column" flexShrink={0} paddingX={1} paddingY={1}>
33
+ <ApprovalPrompt onChoice={onApprovalChoice} req={overlay.approval} t={theme} />
34
+ </Box>
35
+ )
36
+ }
37
+
38
+ if (overlay.confirm) {
39
+ const req = overlay.confirm
40
+
41
+ const onConfirm = () => {
42
+ patchOverlayState({ confirm: null })
43
+ req.onConfirm()
44
+ }
45
+
46
+ const onCancel = () => patchOverlayState({ confirm: null })
47
+
48
+ return (
49
+ <Box flexDirection="column" flexShrink={0} paddingX={1} paddingY={1}>
50
+ <ConfirmPrompt onCancel={onCancel} onConfirm={onConfirm} req={req} t={theme} />
51
+ </Box>
52
+ )
53
+ }
54
+
55
+ if (overlay.clarify) {
56
+ return (
57
+ <Box flexDirection="column" flexShrink={0} paddingX={1} paddingY={1}>
58
+ <ClarifyPrompt
59
+ cols={cols}
60
+ onAnswer={onClarifyAnswer}
61
+ onCancel={() => onClarifyAnswer('')}
62
+ req={overlay.clarify}
63
+ t={theme}
64
+ />
65
+ </Box>
66
+ )
67
+ }
68
+
69
+ if (overlay.sudo) {
70
+ return (
71
+ <Box flexDirection="column" flexShrink={0} paddingX={1} paddingY={1}>
72
+ <MaskedPrompt cols={cols} icon="🔐" label="sudo password required" onSubmit={onSudoSubmit} t={theme} />
73
+ </Box>
74
+ )
75
+ }
76
+
77
+ if (overlay.secret) {
78
+ return (
79
+ <Box flexDirection="column" flexShrink={0} paddingX={1} paddingY={1}>
80
+ <MaskedPrompt
81
+ cols={cols}
82
+ icon="🔑"
83
+ label={overlay.secret.prompt}
84
+ onSubmit={onSecretSubmit}
85
+ sub={`for ${overlay.secret.envVar}`}
86
+ t={theme}
87
+ />
88
+ </Box>
89
+ )
90
+ }
91
+
92
+ return null
93
+ }
94
+
95
+ export function FloatingOverlays({
96
+ cols,
97
+ compIdx,
98
+ completions,
99
+ onActiveSessionSelect,
100
+ onActiveSessionClose,
101
+ onModelSelect,
102
+ onNewLiveSession,
103
+ onNewPromptSession,
104
+ onPickerSelect,
105
+ pagerPageSize
106
+ }: Pick<
107
+ AppOverlaysProps,
108
+ | 'cols'
109
+ | 'compIdx'
110
+ | 'completions'
111
+ | 'onActiveSessionSelect'
112
+ | 'onActiveSessionClose'
113
+ | 'onModelSelect'
114
+ | 'onNewLiveSession'
115
+ | 'onNewPromptSession'
116
+ | 'onPickerSelect'
117
+ | 'pagerPageSize'
118
+ >) {
119
+ const { gw } = useGateway()
120
+ const overlay = useStore($overlayState)
121
+ const sid = useStore($uiSessionId)
122
+ const theme = useStore($uiTheme)
123
+
124
+ const hasAny =
125
+ overlay.modelPicker ||
126
+ overlay.pager ||
127
+ overlay.picker ||
128
+ overlay.sessions ||
129
+ overlay.skillsHub ||
130
+ completions.length
131
+
132
+ if (!hasAny) {
133
+ return null
134
+ }
135
+
136
+ // Fixed viewport centered on compIdx — previously the slice end was
137
+ // compIdx + 8 so the dropdown grew from 8 rows to 16 as the user scrolled
138
+ // down, bouncing the height on every keystroke.
139
+ const viewportSize = Math.min(COMPLETION_WINDOW, completions.length)
140
+
141
+ const start = Math.max(0, Math.min(compIdx - Math.floor(COMPLETION_WINDOW / 2), completions.length - viewportSize))
142
+
143
+ return (
144
+ <Box alignItems="flex-start" bottom="100%" flexDirection="column" left={0} position="absolute" right={0}>
145
+ {overlay.picker && (
146
+ <FloatBox color={theme.color.border}>
147
+ <SessionPicker
148
+ gw={gw}
149
+ onCancel={() => patchOverlayState({ picker: false })}
150
+ onSelect={onPickerSelect}
151
+ t={theme}
152
+ />
153
+ </FloatBox>
154
+ )}
155
+
156
+ {overlay.sessions && (
157
+ <FloatBox color={theme.color.border}>
158
+ <ActiveSessionSwitcher
159
+ currentSessionId={sid}
160
+ gw={gw}
161
+ onCancel={() => patchOverlayState({ sessions: false })}
162
+ onClose={onActiveSessionClose}
163
+ onNew={onNewLiveSession}
164
+ onNewPrompt={onNewPromptSession}
165
+ onSelect={onActiveSessionSelect}
166
+ t={theme}
167
+ />
168
+ </FloatBox>
169
+ )}
170
+
171
+ {overlay.modelPicker && (
172
+ <FloatBox color={theme.color.border}>
173
+ <ModelPicker
174
+ gw={gw}
175
+ onCancel={() => patchOverlayState({ modelPicker: false })}
176
+ onSelect={onModelSelect}
177
+ sessionId={sid}
178
+ t={theme}
179
+ />
180
+ </FloatBox>
181
+ )}
182
+
183
+ {overlay.skillsHub && (
184
+ <FloatBox color={theme.color.border}>
185
+ <SkillsHub gw={gw} onClose={() => patchOverlayState({ skillsHub: false })} t={theme} />
186
+ </FloatBox>
187
+ )}
188
+
189
+ {overlay.pager && (
190
+ <FloatBox color={theme.color.border}>
191
+ <Box flexDirection="column" paddingX={1} paddingY={1}>
192
+ {overlay.pager.title && (
193
+ <Box justifyContent="center" marginBottom={1}>
194
+ <Text bold color={theme.color.primary}>
195
+ {overlay.pager.title}
196
+ </Text>
197
+ </Box>
198
+ )}
199
+
200
+ {overlay.pager.lines.slice(overlay.pager.offset, overlay.pager.offset + pagerPageSize).map((line, i) => (
201
+ <Text key={i}>{line}</Text>
202
+ ))}
203
+
204
+ <Box marginTop={1}>
205
+ <OverlayHint t={theme}>
206
+ {overlay.pager.offset + pagerPageSize < overlay.pager.lines.length
207
+ ? `↑↓/jk line · Enter/Space/PgDn page · b/PgUp back · g/G top/bottom · Esc/q close (${Math.min(overlay.pager.offset + pagerPageSize, overlay.pager.lines.length)}/${overlay.pager.lines.length})`
208
+ : `end · ↑↓/jk · b/PgUp back · g top · Esc/q close (${overlay.pager.lines.length} lines)`}
209
+ </OverlayHint>
210
+ </Box>
211
+ </Box>
212
+ </FloatBox>
213
+ )}
214
+
215
+ {!!completions.length && (
216
+ <FloatBox color={theme.color.primary}>
217
+ <Box flexDirection="column" width={Math.max(28, cols - 6)}>
218
+ {completions.slice(start, start + viewportSize).map((item, i) => {
219
+ const active = start + i === compIdx
220
+
221
+ return (
222
+ <Box
223
+ backgroundColor={active ? theme.color.completionCurrentBg : theme.color.completionBg}
224
+ flexDirection="row"
225
+ key={`${start + i}:${item.text}:${item.display}:${item.meta ?? ''}`}
226
+ width="100%"
227
+ >
228
+ {/* flexShrink=0 — when meta overflows the row, Ink/Yoga
229
+ otherwise shaves the last char off the display column
230
+ (e.g. /goal renders as /goa). */}
231
+ <Box flexShrink={0}>
232
+ <Text bold color={theme.color.label}>
233
+ {' '}
234
+ {item.display}
235
+ </Text>
236
+ </Box>
237
+ {item.meta ? (
238
+ <Text
239
+ backgroundColor={active ? theme.color.completionMetaCurrentBg : theme.color.completionMetaBg}
240
+ color={theme.color.muted}
241
+ >
242
+ {' '}
243
+ {item.meta}
244
+ </Text>
245
+ ) : null}
246
+ </Box>
247
+ )
248
+ })}
249
+ </Box>
250
+ </FloatBox>
251
+ )}
252
+ </Box>
253
+ )
254
+ }