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,554 @@
1
+ import { Box, type ScrollBoxHandle, stringWidth, Text } from '@nastechai/ink'
2
+ import { useStore } from '@nanostores/react'
3
+ import { type ReactNode, type RefObject, useEffect, useMemo, useRef, useState } from 'react'
4
+ import unicodeSpinners from 'unicode-animations'
5
+
6
+ import { $delegationState } from '../app/delegationStore.js'
7
+ import type { IndicatorStyle } from '../app/interfaces.js'
8
+ import { useTurnSelector } from '../app/turnStore.js'
9
+ import { $uiState } from '../app/uiStore.js'
10
+ import { FACES } from '../content/faces.js'
11
+ import { VERBS } from '../content/verbs.js'
12
+ import { fmtDuration } from '../domain/messages.js'
13
+ import { stickyPromptFromViewport } from '../domain/viewport.js'
14
+ import { buildSubagentTree, treeTotals, widthByDepth } from '../lib/subagentTree.js'
15
+ import { fmtK } from '../lib/text.js'
16
+ import { useScrollbarSnapshot, useViewportSnapshot } from '../lib/viewportStore.js'
17
+ import type { Theme } from '../theme.js'
18
+ import type { Msg, Usage } from '../types.js'
19
+
20
+ const FACE_TICK_MS = 2500
21
+ const HEART_COLORS = ['#ff5fa2', '#ff4d6d']
22
+
23
+ // Keep verb segment width stable so status-bar content to the right doesn't
24
+ // jitter when the ticker rotates between short/long verbs.
25
+ export const VERB_PAD_LEN = VERBS.reduce((max, v) => Math.max(max, v.length), 0) + 1 // + ellipsis
26
+ export const padVerb = (verb: string) => `${verb}…`.padEnd(VERB_PAD_LEN, ' ')
27
+
28
+ // Compact alternates for the `emoji` and `ascii` indicator styles.
29
+ // Each entry is a fixed-width (display-width) glyph.
30
+ const EMOJI_FRAMES = ['⚕ ', '🌀', '🤔', '✨', '🍵', '🔮']
31
+ const ASCII_FRAMES = ['|', '/', '-', '\\']
32
+
33
+ // Faster tick for spinner-style indicators — they read as motion only
34
+ // at frame rates closer to their authored interval.
35
+ const SPINNER_TICK_MS = 100
36
+
37
+ interface IndicatorRender {
38
+ frame: string
39
+ intervalMs: number
40
+ // When false, FaceTicker hides the rotating verb and just shows the
41
+ // glyph + duration. Lets `unicode` stay minimal while the other
42
+ // styles keep the verb-rotation flavour users associate with the
43
+ // running… status.
44
+ showVerb: boolean
45
+ }
46
+
47
+ const renderIndicator = (style: IndicatorStyle, tick: number): IndicatorRender => {
48
+ if (style === 'kaomoji') {
49
+ return { frame: FACES[tick % FACES.length] ?? '', intervalMs: FACE_TICK_MS, showVerb: true }
50
+ }
51
+
52
+ if (style === 'emoji') {
53
+ return {
54
+ frame: EMOJI_FRAMES[tick % EMOJI_FRAMES.length] ?? '⚕ ',
55
+ intervalMs: SPINNER_TICK_MS * 6,
56
+ showVerb: true
57
+ }
58
+ }
59
+
60
+ if (style === 'ascii') {
61
+ return {
62
+ frame: ASCII_FRAMES[tick % ASCII_FRAMES.length] ?? '|',
63
+ intervalMs: SPINNER_TICK_MS,
64
+ showVerb: true
65
+ }
66
+ }
67
+
68
+ // 'unicode' — braille spinner (fixed 1-col). Authored interval is
69
+ // ~80ms; honour it but bound below at a safe minimum so React
70
+ // re-renders stay reasonable. This style is for users who want
71
+ // the cleanest possible status, so no verb rotation either.
72
+ const spinner = unicodeSpinners.braille
73
+ const frame = spinner.frames[tick % spinner.frames.length] ?? '⠋'
74
+
75
+ return { frame, intervalMs: Math.max(SPINNER_TICK_MS, spinner.interval), showVerb: false }
76
+ }
77
+
78
+ function FaceTicker({ color, startedAt }: { color: string; startedAt?: null | number }) {
79
+ const ui = useStore($uiState)
80
+ const style = ui.indicatorStyle
81
+ const [tick, setTick] = useState(() => Math.floor(Math.random() * 1000))
82
+ const [verbTick, setVerbTick] = useState(() => Math.floor(Math.random() * VERBS.length))
83
+ const [now, setNow] = useState(() => Date.now())
84
+
85
+ // Pre-compute cadence + verb-visibility for the active style so an
86
+ // `/indicator` switch re-arms the interval (and skips the verb timer
87
+ // for verb-less styles like `unicode`) without leaving the previous
88
+ // timer dangling.
89
+ const { intervalMs, showVerb } = renderIndicator(style, 0)
90
+
91
+ useEffect(() => {
92
+ const glyph = setInterval(() => setTick(n => n + 1), intervalMs)
93
+ const clock = setInterval(() => setNow(Date.now()), 1000)
94
+ // Verb timer is gated on `showVerb` — `unicode` style hides the verb
95
+ // entirely, so cycling `verbTick` would be an avoidable re-render.
96
+ const verb = showVerb ? setInterval(() => setVerbTick(n => n + 1), FACE_TICK_MS) : null
97
+
98
+ return () => {
99
+ clearInterval(glyph)
100
+ clearInterval(clock)
101
+
102
+ if (verb !== null) {
103
+ clearInterval(verb)
104
+ }
105
+ }
106
+ }, [intervalMs, showVerb])
107
+
108
+ const { frame } = renderIndicator(style, tick)
109
+ const verb = VERBS[verbTick % VERBS.length] ?? ''
110
+ const verbSegment = showVerb ? ` ${padVerb(verb)}` : ''
111
+ // Leading space keeps a gap between the frame and the duration when the
112
+ // verb segment is hidden (e.g. `unicode` spinner style). When the verb
113
+ // IS shown, its trailing padding already provides the gap, so the extra
114
+ // space is harmless.
115
+ const durationSegment = startedAt ? ` · ${fmtDuration(now - startedAt)}` : ''
116
+
117
+ return (
118
+ <Text color={color}>
119
+ {frame}
120
+ {verbSegment}
121
+ {durationSegment}
122
+ </Text>
123
+ )
124
+ }
125
+
126
+ function ctxBarColor(pct: number | undefined, t: Theme) {
127
+ if (pct == null) {
128
+ return t.color.muted
129
+ }
130
+
131
+ if (pct >= 95) {
132
+ return t.color.statusCritical
133
+ }
134
+
135
+ if (pct > 80) {
136
+ return t.color.statusBad
137
+ }
138
+
139
+ if (pct >= 50) {
140
+ return t.color.statusWarn
141
+ }
142
+
143
+ return t.color.statusGood
144
+ }
145
+
146
+ function statusSessionCountLabel(count: number) {
147
+ return `${count} ${count === 1 ? 'session' : 'sessions'}`
148
+ }
149
+
150
+ function ctxBar(pct: number | undefined, w = 10) {
151
+ const p = Math.max(0, Math.min(100, pct ?? 0))
152
+ const filled = Math.round((p / 100) * w)
153
+
154
+ return '█'.repeat(filled) + '░'.repeat(w - filled)
155
+ }
156
+
157
+ export function statusRuleWidths(cols: number, cwdLabel: string) {
158
+ const width = Math.max(1, Math.floor(cols || 1))
159
+ const desiredSeparatorWidth = width >= 24 ? 3 : 1
160
+ const minLeftWidth = width >= 24 ? 8 : 1
161
+ const maxRightWidth = Math.max(0, width - desiredSeparatorWidth - minLeftWidth)
162
+
163
+ if (!cwdLabel || maxRightWidth <= 0) {
164
+ return { leftWidth: width, rightWidth: 0, separatorWidth: 0 }
165
+ }
166
+
167
+ const rightWidth = Math.max(0, Math.min(stringWidth(cwdLabel), maxRightWidth))
168
+ const separatorWidth = rightWidth > 0 ? desiredSeparatorWidth : 0
169
+ const leftWidth = Math.max(1, width - separatorWidth - rightWidth)
170
+
171
+ return { leftWidth, rightWidth, separatorWidth }
172
+ }
173
+
174
+ function SpawnHud({ t }: { t: Theme }) {
175
+ // Tight HUD that only appears when the session is actually fanning out.
176
+ // Colour escalates to warn/error as depth or concurrency approaches the cap.
177
+ const delegation = useStore($delegationState)
178
+ const subagents = useTurnSelector(state => state.subagents)
179
+
180
+ const tree = useMemo(() => buildSubagentTree(subagents), [subagents])
181
+ const totals = useMemo(() => treeTotals(tree), [tree])
182
+
183
+ if (!totals.descendantCount && !delegation.paused) {
184
+ return null
185
+ }
186
+
187
+ const maxDepth = delegation.maxSpawnDepth
188
+ const maxConc = delegation.maxConcurrentChildren
189
+ const depth = Math.max(0, totals.maxDepthFromHere)
190
+ const active = totals.activeCount
191
+
192
+ // `max_concurrent_children` is a per-parent cap, not a global one.
193
+ // `activeCount` sums every running agent across the tree and would
194
+ // over-warn for multi-orchestrator runs. The widest level of the tree
195
+ // is a closer proxy to "most concurrent spawns that could be hitting a
196
+ // single parent's slot budget".
197
+ const widestLevel = widthByDepth(tree).reduce((a, b) => Math.max(a, b), 0)
198
+ const depthRatio = maxDepth ? depth / maxDepth : 0
199
+ const concRatio = maxConc ? widestLevel / maxConc : 0
200
+ const ratio = Math.max(depthRatio, concRatio)
201
+
202
+ const color = delegation.paused || ratio >= 1 ? t.color.error : ratio >= 0.66 ? t.color.warn : t.color.muted
203
+
204
+ const pieces: string[] = []
205
+
206
+ if (delegation.paused) {
207
+ pieces.push('⏸ paused')
208
+ }
209
+
210
+ if (totals.descendantCount > 0) {
211
+ const depthLabel = maxDepth ? `${depth}/${maxDepth}` : `${depth}`
212
+ pieces.push(`d${depthLabel}`)
213
+
214
+ if (active > 0) {
215
+ // Label pairs the widest-level count (drives concRatio above) with
216
+ // the total active count for context. `W/cap` triggers the warn,
217
+ // `+N` is everything else currently running across the tree.
218
+ const extra = Math.max(0, active - widestLevel)
219
+ const widthLabel = maxConc ? `${widestLevel}/${maxConc}` : `${widestLevel}`
220
+ const suffix = extra > 0 ? `+${extra}` : ''
221
+ pieces.push(`⚡${widthLabel}${suffix}`)
222
+ }
223
+ }
224
+
225
+ const atCap = depthRatio >= 1 || concRatio >= 1
226
+
227
+ return (
228
+ <Text color={color}>
229
+ {atCap ? ' │ ⚠ ' : ' │ '}
230
+ {pieces.join(' ')}
231
+ </Text>
232
+ )
233
+ }
234
+
235
+ function SessionDuration({ startedAt }: { startedAt: number }) {
236
+ const [now, setNow] = useState(() => Date.now())
237
+
238
+ useEffect(() => {
239
+ setNow(Date.now())
240
+ const id = setInterval(() => setNow(Date.now()), 1000)
241
+
242
+ return () => clearInterval(id)
243
+ }, [startedAt])
244
+
245
+ return fmtDuration(now - startedAt)
246
+ }
247
+
248
+ const effortLabel = (effort?: string) => {
249
+ const value = String(effort ?? '')
250
+ .trim()
251
+ .toLowerCase()
252
+
253
+ return value && value !== 'medium' && value !== 'normal' && value !== 'default' ? value : ''
254
+ }
255
+
256
+ const shortModelLabel = (model: string) =>
257
+ model
258
+ .split('/')
259
+ .pop()!
260
+ .replace(/^claude[-_]/, '')
261
+ .replace(/^anthropic[-_]/, '')
262
+ .replace(/[-_]/g, ' ')
263
+ .replace(/\b(\d+)\s+(\d+)\b/g, '$1.$2')
264
+ .trim()
265
+
266
+ const modelLabel = (model: string, effort?: string, fast?: boolean) =>
267
+ [shortModelLabel(model), effortLabel(effort), fast ? 'fast' : ''].filter(Boolean).join(' ')
268
+
269
+ export function GoodVibesHeart({ tick, t }: { tick: number; t: Theme }) {
270
+ const [active, setActive] = useState(false)
271
+ const [color, setColor] = useState(t.color.accent)
272
+
273
+ useEffect(() => {
274
+ if (tick <= 0) {
275
+ return
276
+ }
277
+
278
+ const palette = [t.color.error, t.color.warn, t.color.accent]
279
+ setColor(palette[Math.floor(Math.random() * palette.length)]!)
280
+ setActive(true)
281
+
282
+ const id = setTimeout(() => setActive(false), 650)
283
+
284
+ return () => clearTimeout(id)
285
+ }, [t.color.accent, tick])
286
+
287
+ if (!active) {
288
+ return null
289
+ }
290
+
291
+ return <Text color={color}>♥</Text>
292
+ }
293
+
294
+ export function StatusRule({
295
+ cwdLabel,
296
+ cols,
297
+ busy,
298
+ status,
299
+ statusColor,
300
+ model,
301
+ modelFast,
302
+ modelReasoningEffort,
303
+ usage,
304
+ bgCount,
305
+ liveSessionCount,
306
+ sessionStartedAt,
307
+ showCost,
308
+ turnStartedAt,
309
+ voiceLabel,
310
+ onSessionCountClick,
311
+ t
312
+ }: StatusRuleProps) {
313
+ const pct = usage.context_percent
314
+ const barColor = ctxBarColor(pct, t)
315
+
316
+ const ctxLabel = usage.context_max
317
+ ? `${fmtK(usage.context_used ?? 0)}/${fmtK(usage.context_max)}`
318
+ : usage.total > 0
319
+ ? `${fmtK(usage.total)} tok`
320
+ : ''
321
+
322
+ const bar = usage.context_max ? ctxBar(pct) : ''
323
+ const { leftWidth, rightWidth, separatorWidth } = statusRuleWidths(cols, cwdLabel)
324
+ const sessionCountText = liveSessionCount > 0 ? statusSessionCountLabel(liveSessionCount) : ''
325
+ const handleSessionCountClick = (event: { stopImmediatePropagation?: () => void }) => {
326
+ event.stopImmediatePropagation?.()
327
+ onSessionCountClick?.()
328
+ }
329
+
330
+ const sessionCountNode = sessionCountText ? (
331
+ onSessionCountClick ? (
332
+ <Box flexShrink={0} onClick={handleSessionCountClick}>
333
+ <Text color={t.color.accent}> │ {sessionCountText}</Text>
334
+ </Box>
335
+ ) : (
336
+ <Text color={t.color.muted}> │ {sessionCountText}</Text>
337
+ )
338
+ ) : null
339
+
340
+ return (
341
+ <Box height={1}>
342
+ <Box flexDirection="row" flexShrink={1} overflow="hidden" width={leftWidth}>
343
+ <Text color={t.color.border} wrap="truncate-end">
344
+ {'─ '}
345
+ </Text>
346
+ {busy ? (
347
+ <FaceTicker color={statusColor} startedAt={turnStartedAt} />
348
+ ) : (
349
+ <Text color={statusColor} wrap="truncate-end">
350
+ {status}
351
+ </Text>
352
+ )}
353
+ <Text color={t.color.muted} wrap="truncate-end">
354
+ {' │ '}
355
+ {modelLabel(model, modelReasoningEffort, modelFast)}
356
+ </Text>
357
+ {ctxLabel ? (
358
+ <Text color={t.color.muted} wrap="truncate-end">
359
+ {' │ '}
360
+ {ctxLabel}
361
+ </Text>
362
+ ) : null}
363
+ {bar ? (
364
+ <Text color={t.color.muted} wrap="truncate-end">
365
+ {' │ '}
366
+ <Text color={barColor}>[{bar}]</Text> <Text color={barColor}>{pct != null ? `${pct}%` : ''}</Text>
367
+ </Text>
368
+ ) : null}
369
+ {sessionStartedAt ? (
370
+ <Text color={t.color.muted} wrap="truncate-end">
371
+ {' │ '}
372
+ <SessionDuration startedAt={sessionStartedAt} />
373
+ </Text>
374
+ ) : null}
375
+ {typeof usage.compressions === 'number' && usage.compressions > 0 ? (
376
+ <Text color={t.color.muted} wrap="truncate-end">
377
+ {' │ '}
378
+ <Text
379
+ color={usage.compressions >= 10 ? t.color.error : usage.compressions >= 5 ? t.color.warn : t.color.muted}
380
+ >
381
+ cmp {usage.compressions}
382
+ </Text>
383
+ </Text>
384
+ ) : null}
385
+ <SpawnHud t={t} />
386
+ {voiceLabel ? (
387
+ <Text
388
+ color={
389
+ voiceLabel.startsWith('●') ? t.color.error : voiceLabel.startsWith('◉') ? t.color.warn : t.color.muted
390
+ }
391
+ wrap="truncate-end"
392
+ >
393
+ {' │ '}
394
+ {voiceLabel}
395
+ </Text>
396
+ ) : null}
397
+ {sessionCountNode}
398
+ {bgCount > 0 ? (
399
+ <Text color={t.color.muted} wrap="truncate-end">
400
+ {' │ '}
401
+ {bgCount} bg
402
+ </Text>
403
+ ) : null}
404
+ {showCost && typeof usage.cost_usd === 'number' ? (
405
+ <Text color={t.color.muted} wrap="truncate-end">
406
+ {' │ $'}
407
+ {usage.cost_usd.toFixed(4)}
408
+ </Text>
409
+ ) : null}
410
+ </Box>
411
+
412
+ {rightWidth > 0 ? (
413
+ <>
414
+ <Text color={t.color.border}>{separatorWidth >= 3 ? ' ─ ' : ' '}</Text>
415
+ <Box flexShrink={0} width={rightWidth}>
416
+ <Text color={t.color.label} wrap="truncate-end">
417
+ {cwdLabel}
418
+ </Text>
419
+ </Box>
420
+ </>
421
+ ) : null}
422
+ </Box>
423
+ )
424
+ }
425
+
426
+ export function FloatBox({ children, color }: { children: ReactNode; color: string }) {
427
+ return (
428
+ <Box
429
+ alignSelf="flex-start"
430
+ borderColor={color}
431
+ borderStyle="double"
432
+ flexDirection="column"
433
+ marginTop={1}
434
+ opaque
435
+ paddingX={1}
436
+ >
437
+ {children}
438
+ </Box>
439
+ )
440
+ }
441
+
442
+ export function StickyPromptTracker({ messages, offsets, scrollRef, onChange }: StickyPromptTrackerProps) {
443
+ const { atBottom, bottom, top } = useViewportSnapshot(scrollRef)
444
+ const text = stickyPromptFromViewport(messages, offsets, top, bottom, atBottom)
445
+
446
+ useEffect(() => onChange(text), [onChange, text])
447
+
448
+ return null
449
+ }
450
+
451
+ export function TranscriptScrollbar({ scrollRef, t }: TranscriptScrollbarProps) {
452
+ const [hover, setHover] = useState(false)
453
+ const [grab, setGrab] = useState<number | null>(null)
454
+ const grabRef = useRef<number | null>(null)
455
+ const { scrollHeight: total, top: pos, viewportHeight: vp } = useScrollbarSnapshot(scrollRef)
456
+
457
+ if (!vp) {
458
+ return <Box width={1} />
459
+ }
460
+
461
+ const s = scrollRef.current
462
+ const scrollable = total > vp
463
+ const thumb = scrollable ? Math.max(1, Math.round((vp * vp) / total)) : vp
464
+ const travel = Math.max(1, vp - thumb)
465
+ const thumbTop = scrollable ? Math.round((pos / Math.max(1, total - vp)) * travel) : 0
466
+ const thumbColor = grab !== null ? t.color.primary : hover ? t.color.accent : t.color.border
467
+ const trackColor = hover ? t.color.border : t.color.muted
468
+
469
+ const jump = (row: number, offset: number) => {
470
+ if (!s || !scrollable) {
471
+ return
472
+ }
473
+
474
+ s.scrollTo(Math.round((Math.max(0, Math.min(travel, row - offset)) / travel) * Math.max(0, total - vp)))
475
+ }
476
+
477
+ return (
478
+ <Box
479
+ flexDirection="column"
480
+ onMouseDown={(e: { localRow?: number }) => {
481
+ const row = Math.max(0, Math.min(vp - 1, e.localRow ?? 0))
482
+ const off = row >= thumbTop && row < thumbTop + thumb ? row - thumbTop : Math.floor(thumb / 2)
483
+
484
+ grabRef.current = off
485
+ setGrab(off)
486
+ jump(row, off)
487
+ }}
488
+ onMouseDrag={(e: { localRow?: number }) =>
489
+ jump(Math.max(0, Math.min(vp - 1, e.localRow ?? 0)), grabRef.current ?? Math.floor(thumb / 2))
490
+ }
491
+ onMouseEnter={() => setHover(true)}
492
+ onMouseLeave={() => setHover(false)}
493
+ onMouseUp={() => {
494
+ grabRef.current = null
495
+ setGrab(null)
496
+ }}
497
+ width={1}
498
+ >
499
+ {!scrollable ? (
500
+ <Text color={trackColor} dim>
501
+ {' \n'.repeat(Math.max(0, vp - 1))}{' '}
502
+ </Text>
503
+ ) : (
504
+ <>
505
+ {thumbTop > 0 ? (
506
+ <Text color={trackColor} dim={!hover}>
507
+ {`${'│\n'.repeat(Math.max(0, thumbTop - 1))}${thumbTop > 0 ? '│' : ''}`}
508
+ </Text>
509
+ ) : null}
510
+ {thumb > 0 ? (
511
+ <Text color={thumbColor}>{`${'┃\n'.repeat(Math.max(0, thumb - 1))}${thumb > 0 ? '┃' : ''}`}</Text>
512
+ ) : null}
513
+ {vp - thumbTop - thumb > 0 ? (
514
+ <Text color={trackColor} dim={!hover}>
515
+ {`${'│\n'.repeat(Math.max(0, vp - thumbTop - thumb - 1))}${vp - thumbTop - thumb > 0 ? '│' : ''}`}
516
+ </Text>
517
+ ) : null}
518
+ </>
519
+ )}
520
+ </Box>
521
+ )
522
+ }
523
+
524
+ interface StatusRuleProps {
525
+ bgCount: number
526
+ liveSessionCount: number
527
+ busy: boolean
528
+ cols: number
529
+ cwdLabel: string
530
+ model: string
531
+ modelFast?: boolean
532
+ modelReasoningEffort?: string
533
+ sessionStartedAt?: null | number
534
+ showCost: boolean
535
+ status: string
536
+ statusColor: string
537
+ t: Theme
538
+ turnStartedAt?: null | number
539
+ usage: Usage
540
+ voiceLabel?: string
541
+ onSessionCountClick?: () => void
542
+ }
543
+
544
+ interface StickyPromptTrackerProps {
545
+ messages: readonly Msg[]
546
+ offsets: ArrayLike<number>
547
+ onChange: (text: string) => void
548
+ scrollRef: RefObject<ScrollBoxHandle | null>
549
+ }
550
+
551
+ interface TranscriptScrollbarProps {
552
+ scrollRef: RefObject<ScrollBoxHandle | null>
553
+ t: Theme
554
+ }