4runr-os 2.1.4 → 2.1.6

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 (566) hide show
  1. package/dist/index.js +16 -1
  2. package/dist/index.js.map +1 -1
  3. package/dist/tui_mk1/kernel.d.ts.map +1 -1
  4. package/dist/tui_mk1/kernel.js +107 -4
  5. package/dist/tui_mk1/kernel.js.map +1 -1
  6. package/dist/tui_mk1/layout/layoutEngine.d.ts +38 -19
  7. package/dist/tui_mk1/layout/layoutEngine.d.ts.map +1 -1
  8. package/dist/tui_mk1/layout/layoutEngine.js +521 -228
  9. package/dist/tui_mk1/layout/layoutEngine.js.map +1 -1
  10. package/dist/tui_mk1/log.d.ts +23 -0
  11. package/dist/tui_mk1/log.d.ts.map +1 -0
  12. package/dist/tui_mk1/log.js +125 -0
  13. package/dist/tui_mk1/log.js.map +1 -0
  14. package/dist/tui_mk1/logger.d.ts.map +1 -1
  15. package/dist/tui_mk1/logger.js +20 -10
  16. package/dist/tui_mk1/logger.js.map +1 -1
  17. package/dist/tui_mk1/mk1App.d.ts +13 -18
  18. package/dist/tui_mk1/mk1App.d.ts.map +1 -1
  19. package/dist/tui_mk1/mk1App.js +334 -157
  20. package/dist/tui_mk1/mk1App.js.map +1 -1
  21. package/dist/tui_mk1/resizeController.d.ts.map +1 -1
  22. package/dist/tui_mk1/resizeController.js +15 -1
  23. package/dist/tui_mk1/resizeController.js.map +1 -1
  24. package/dist/tui_mk1/stdoutGuard.d.ts.map +1 -1
  25. package/dist/tui_mk1/stdoutGuard.js +7 -0
  26. package/dist/tui_mk1/stdoutGuard.js.map +1 -1
  27. package/dist/tui_mk1/terminalRestore.d.ts.map +1 -1
  28. package/dist/tui_mk1/terminalRestore.js +19 -1
  29. package/dist/tui_mk1/terminalRestore.js.map +1 -1
  30. package/dist/tui_mk1/ui/offenderScanner.d.ts +31 -0
  31. package/dist/tui_mk1/ui/offenderScanner.d.ts.map +1 -0
  32. package/dist/tui_mk1/ui/offenderScanner.js +80 -0
  33. package/dist/tui_mk1/ui/offenderScanner.js.map +1 -0
  34. package/dist/tui_mk1/ui/safe.d.ts +9 -0
  35. package/dist/tui_mk1/ui/safe.d.ts.map +1 -0
  36. package/dist/tui_mk1/ui/safe.js +29 -0
  37. package/dist/tui_mk1/ui/safe.js.map +1 -0
  38. package/dist/tui_mk1/ui/safeText.d.ts +22 -0
  39. package/dist/tui_mk1/ui/safeText.d.ts.map +1 -0
  40. package/dist/tui_mk1/ui/safeText.js +50 -0
  41. package/dist/tui_mk1/ui/safeText.js.map +1 -0
  42. package/dist/tui_mk1/ui/uiBuilder.d.ts +44 -0
  43. package/dist/tui_mk1/ui/uiBuilder.d.ts.map +1 -0
  44. package/dist/tui_mk1/ui/uiBuilder.js +467 -0
  45. package/dist/tui_mk1/ui/uiBuilder.js.map +1 -0
  46. package/dist/tui_mk1/viewport/safeViewport.d.ts +10 -1
  47. package/dist/tui_mk1/viewport/safeViewport.d.ts.map +1 -1
  48. package/dist/tui_mk1/viewport/safeViewport.js +47 -15
  49. package/dist/tui_mk1/viewport/safeViewport.js.map +1 -1
  50. package/dist/tui_mk2/kernel.d.ts +28 -0
  51. package/dist/tui_mk2/kernel.d.ts.map +1 -0
  52. package/dist/tui_mk2/kernel.js +138 -0
  53. package/dist/tui_mk2/kernel.js.map +1 -0
  54. package/dist/tui_mk2/mk2App.d.ts +33 -0
  55. package/dist/tui_mk2/mk2App.d.ts.map +1 -0
  56. package/dist/tui_mk2/mk2App.js +135 -0
  57. package/dist/tui_mk2/mk2App.js.map +1 -0
  58. package/package.json +8 -7
  59. package/dist/tui_mk1/ui/panels/createSection.d.ts +0 -18
  60. package/dist/tui_mk1/ui/panels/createSection.d.ts.map +0 -1
  61. package/dist/tui_mk1/ui/panels/createSection.js +0 -42
  62. package/dist/tui_mk1/ui/panels/createSection.js.map +0 -1
  63. package/dist/tui_mk1/ui/panels.d.ts +0 -23
  64. package/dist/tui_mk1/ui/panels.d.ts.map +0 -1
  65. package/dist/tui_mk1/ui/panels.js +0 -108
  66. package/dist/tui_mk1/ui/panels.js.map +0 -1
  67. package/dist/tui_mk1/ui/render/sectionRenderer.d.ts +0 -21
  68. package/dist/tui_mk1/ui/render/sectionRenderer.d.ts.map +0 -1
  69. package/dist/tui_mk1/ui/render/sectionRenderer.js +0 -32
  70. package/dist/tui_mk1/ui/render/sectionRenderer.js.map +0 -1
  71. package/dist/tui_mk1/ui/widgetManager.d.ts +0 -58
  72. package/dist/tui_mk1/ui/widgetManager.d.ts.map +0 -1
  73. package/dist/tui_mk1/ui/widgetManager.js +0 -197
  74. package/dist/tui_mk1/ui/widgetManager.js.map +0 -1
  75. package/dist/ui/boot/sequence.d.ts +0 -10
  76. package/dist/ui/boot/sequence.d.ts.map +0 -1
  77. package/dist/ui/boot/sequence.js +0 -171
  78. package/dist/ui/boot/sequence.js.map +0 -1
  79. package/dist/ui/constraints/layoutSpec.d.ts +0 -47
  80. package/dist/ui/constraints/layoutSpec.d.ts.map +0 -1
  81. package/dist/ui/constraints/layoutSpec.js +0 -60
  82. package/dist/ui/constraints/layoutSpec.js.map +0 -1
  83. package/dist/ui/constraints/unknownHandling.d.ts +0 -29
  84. package/dist/ui/constraints/unknownHandling.d.ts.map +0 -1
  85. package/dist/ui/constraints/unknownHandling.js +0 -60
  86. package/dist/ui/constraints/unknownHandling.js.map +0 -1
  87. package/dist/ui/drilldowns/feed.d.ts +0 -11
  88. package/dist/ui/drilldowns/feed.d.ts.map +0 -1
  89. package/dist/ui/drilldowns/feed.js +0 -68
  90. package/dist/ui/drilldowns/feed.js.map +0 -1
  91. package/dist/ui/drilldowns/index.d.ts +0 -7
  92. package/dist/ui/drilldowns/index.d.ts.map +0 -1
  93. package/dist/ui/drilldowns/index.js +0 -8
  94. package/dist/ui/drilldowns/index.js.map +0 -1
  95. package/dist/ui/drilldowns/posture.d.ts +0 -11
  96. package/dist/ui/drilldowns/posture.d.ts.map +0 -1
  97. package/dist/ui/drilldowns/posture.js +0 -74
  98. package/dist/ui/drilldowns/posture.js.map +0 -1
  99. package/dist/ui/intelligence-posture-view.d.ts +0 -22
  100. package/dist/ui/intelligence-posture-view.d.ts.map +0 -1
  101. package/dist/ui/intelligence-posture-view.js +0 -169
  102. package/dist/ui/intelligence-posture-view.js.map +0 -1
  103. package/dist/ui/navigation/keymaps.d.ts +0 -26
  104. package/dist/ui/navigation/keymaps.d.ts.map +0 -1
  105. package/dist/ui/navigation/keymaps.js +0 -135
  106. package/dist/ui/navigation/keymaps.js.map +0 -1
  107. package/dist/ui/navigation/palette.d.ts +0 -10
  108. package/dist/ui/navigation/palette.d.ts.map +0 -1
  109. package/dist/ui/navigation/palette.js +0 -133
  110. package/dist/ui/navigation/palette.js.map +0 -1
  111. package/dist/ui/navigation/state.d.ts +0 -47
  112. package/dist/ui/navigation/state.d.ts.map +0 -1
  113. package/dist/ui/navigation/state.js +0 -84
  114. package/dist/ui/navigation/state.js.map +0 -1
  115. package/dist/ui/navigation/types.d.ts +0 -38
  116. package/dist/ui/navigation/types.d.ts.map +0 -1
  117. package/dist/ui/navigation/types.js +0 -36
  118. package/dist/ui/navigation/types.js.map +0 -1
  119. package/dist/ui/panels/active-assets.d.ts +0 -12
  120. package/dist/ui/panels/active-assets.d.ts.map +0 -1
  121. package/dist/ui/panels/active-assets.js +0 -83
  122. package/dist/ui/panels/active-assets.js.map +0 -1
  123. package/dist/ui/panels/capability-flags.d.ts +0 -12
  124. package/dist/ui/panels/capability-flags.d.ts.map +0 -1
  125. package/dist/ui/panels/capability-flags.js +0 -59
  126. package/dist/ui/panels/capability-flags.js.map +0 -1
  127. package/dist/ui/panels/command-surface.d.ts +0 -12
  128. package/dist/ui/panels/command-surface.d.ts.map +0 -1
  129. package/dist/ui/panels/command-surface.js +0 -55
  130. package/dist/ui/panels/command-surface.js.map +0 -1
  131. package/dist/ui/panels/network-origin.d.ts +0 -12
  132. package/dist/ui/panels/network-origin.d.ts.map +0 -1
  133. package/dist/ui/panels/network-origin.js +0 -79
  134. package/dist/ui/panels/network-origin.js.map +0 -1
  135. package/dist/ui/panels/operations-feed.d.ts +0 -12
  136. package/dist/ui/panels/operations-feed.d.ts.map +0 -1
  137. package/dist/ui/panels/operations-feed.js +0 -90
  138. package/dist/ui/panels/operations-feed.js.map +0 -1
  139. package/dist/ui/panels/posture.d.ts +0 -12
  140. package/dist/ui/panels/posture.d.ts.map +0 -1
  141. package/dist/ui/panels/posture.js +0 -84
  142. package/dist/ui/panels/posture.js.map +0 -1
  143. package/dist/ui/panels/resources.d.ts +0 -11
  144. package/dist/ui/panels/resources.d.ts.map +0 -1
  145. package/dist/ui/panels/resources.js +0 -88
  146. package/dist/ui/panels/resources.js.map +0 -1
  147. package/dist/ui/primitives/Panel.d.ts +0 -25
  148. package/dist/ui/primitives/Panel.d.ts.map +0 -1
  149. package/dist/ui/primitives/Panel.js +0 -59
  150. package/dist/ui/primitives/Panel.js.map +0 -1
  151. package/dist/ui/rendering/metricRenderer.d.ts +0 -24
  152. package/dist/ui/rendering/metricRenderer.d.ts.map +0 -1
  153. package/dist/ui/rendering/metricRenderer.js +0 -86
  154. package/dist/ui/rendering/metricRenderer.js.map +0 -1
  155. package/dist/ui/runtime/hub.d.ts +0 -12
  156. package/dist/ui/runtime/hub.d.ts.map +0 -1
  157. package/dist/ui/runtime/hub.js +0 -486
  158. package/dist/ui/runtime/hub.js.map +0 -1
  159. package/dist/ui/runtime/hubValidation.d.ts +0 -23
  160. package/dist/ui/runtime/hubValidation.d.ts.map +0 -1
  161. package/dist/ui/runtime/hubValidation.js +0 -90
  162. package/dist/ui/runtime/hubValidation.js.map +0 -1
  163. package/dist/ui/runtime/index.d.ts +0 -29
  164. package/dist/ui/runtime/index.d.ts.map +0 -1
  165. package/dist/ui/runtime/index.js +0 -297
  166. package/dist/ui/runtime/index.js.map +0 -1
  167. package/dist/ui/runtime/no-tui.d.ts +0 -12
  168. package/dist/ui/runtime/no-tui.d.ts.map +0 -1
  169. package/dist/ui/runtime/no-tui.js +0 -77
  170. package/dist/ui/runtime/no-tui.js.map +0 -1
  171. package/dist/ui/runtime/state-builder.d.ts +0 -13
  172. package/dist/ui/runtime/state-builder.d.ts.map +0 -1
  173. package/dist/ui/runtime/state-builder.js +0 -114
  174. package/dist/ui/runtime/state-builder.js.map +0 -1
  175. package/dist/ui/runtime/terminalSizeCheck.d.ts +0 -10
  176. package/dist/ui/runtime/terminalSizeCheck.d.ts.map +0 -1
  177. package/dist/ui/runtime/terminalSizeCheck.js +0 -51
  178. package/dist/ui/runtime/terminalSizeCheck.js.map +0 -1
  179. package/dist/ui/runtime/tuiLogGate.d.ts +0 -22
  180. package/dist/ui/runtime/tuiLogGate.d.ts.map +0 -1
  181. package/dist/ui/runtime/tuiLogGate.js +0 -68
  182. package/dist/ui/runtime/tuiLogGate.js.map +0 -1
  183. package/dist/ui/state/types.d.ts +0 -72
  184. package/dist/ui/state/types.d.ts.map +0 -1
  185. package/dist/ui/state/types.js +0 -6
  186. package/dist/ui/state/types.js.map +0 -1
  187. package/dist/ui/theme/borders.d.ts +0 -20
  188. package/dist/ui/theme/borders.d.ts.map +0 -1
  189. package/dist/ui/theme/borders.js +0 -55
  190. package/dist/ui/theme/borders.js.map +0 -1
  191. package/dist/ui/theme/tokens.d.ts +0 -28
  192. package/dist/ui/theme/tokens.d.ts.map +0 -1
  193. package/dist/ui/theme/tokens.js +0 -50
  194. package/dist/ui/theme/tokens.js.map +0 -1
  195. package/dist/ui/theme/typography.d.ts +0 -14
  196. package/dist/ui/theme/typography.d.ts.map +0 -1
  197. package/dist/ui/theme/typography.js +0 -30
  198. package/dist/ui/theme/typography.js.map +0 -1
  199. package/dist/ui/v3/collectors/assets.collector.d.ts +0 -20
  200. package/dist/ui/v3/collectors/assets.collector.d.ts.map +0 -1
  201. package/dist/ui/v3/collectors/assets.collector.js +0 -80
  202. package/dist/ui/v3/collectors/assets.collector.js.map +0 -1
  203. package/dist/ui/v3/collectors/capabilities.collector.d.ts +0 -18
  204. package/dist/ui/v3/collectors/capabilities.collector.d.ts.map +0 -1
  205. package/dist/ui/v3/collectors/capabilities.collector.js +0 -113
  206. package/dist/ui/v3/collectors/capabilities.collector.js.map +0 -1
  207. package/dist/ui/v3/collectors/network.collector.d.ts +0 -18
  208. package/dist/ui/v3/collectors/network.collector.d.ts.map +0 -1
  209. package/dist/ui/v3/collectors/network.collector.js +0 -37
  210. package/dist/ui/v3/collectors/network.collector.js.map +0 -1
  211. package/dist/ui/v3/collectors/posture.derive.d.ts +0 -24
  212. package/dist/ui/v3/collectors/posture.derive.d.ts.map +0 -1
  213. package/dist/ui/v3/collectors/posture.derive.js +0 -57
  214. package/dist/ui/v3/collectors/posture.derive.js.map +0 -1
  215. package/dist/ui/v3/collectors/resources.d.ts +0 -23
  216. package/dist/ui/v3/collectors/resources.d.ts.map +0 -1
  217. package/dist/ui/v3/collectors/resources.js +0 -136
  218. package/dist/ui/v3/collectors/resources.js.map +0 -1
  219. package/dist/ui/v3/commands/commandEngine.d.ts +0 -77
  220. package/dist/ui/v3/commands/commandEngine.d.ts.map +0 -1
  221. package/dist/ui/v3/commands/commandEngine.js +0 -3289
  222. package/dist/ui/v3/commands/commandEngine.js.map +0 -1
  223. package/dist/ui/v3/commands/commandResult.d.ts +0 -25
  224. package/dist/ui/v3/commands/commandResult.d.ts.map +0 -1
  225. package/dist/ui/v3/commands/commandResult.js +0 -19
  226. package/dist/ui/v3/commands/commandResult.js.map +0 -1
  227. package/dist/ui/v3/commands/diagnose.d.ts +0 -17
  228. package/dist/ui/v3/commands/diagnose.d.ts.map +0 -1
  229. package/dist/ui/v3/commands/diagnose.js +0 -62
  230. package/dist/ui/v3/commands/diagnose.js.map +0 -1
  231. package/dist/ui/v3/commands/errorClassifier.d.ts +0 -23
  232. package/dist/ui/v3/commands/errorClassifier.d.ts.map +0 -1
  233. package/dist/ui/v3/commands/errorClassifier.js +0 -63
  234. package/dist/ui/v3/commands/errorClassifier.js.map +0 -1
  235. package/dist/ui/v3/commands/parser.d.ts +0 -14
  236. package/dist/ui/v3/commands/parser.d.ts.map +0 -1
  237. package/dist/ui/v3/commands/parser.js +0 -29
  238. package/dist/ui/v3/commands/parser.js.map +0 -1
  239. package/dist/ui/v3/commands/router.d.ts +0 -13
  240. package/dist/ui/v3/commands/router.d.ts.map +0 -1
  241. package/dist/ui/v3/commands/router.js +0 -150
  242. package/dist/ui/v3/commands/router.js.map +0 -1
  243. package/dist/ui/v3/config/gateway.d.ts +0 -40
  244. package/dist/ui/v3/config/gateway.d.ts.map +0 -1
  245. package/dist/ui/v3/config/gateway.js +0 -113
  246. package/dist/ui/v3/config/gateway.js.map +0 -1
  247. package/dist/ui/v3/core/event.d.ts +0 -19
  248. package/dist/ui/v3/core/event.d.ts.map +0 -1
  249. package/dist/ui/v3/core/event.js +0 -7
  250. package/dist/ui/v3/core/event.js.map +0 -1
  251. package/dist/ui/v3/core/eventBus.d.ts +0 -39
  252. package/dist/ui/v3/core/eventBus.d.ts.map +0 -1
  253. package/dist/ui/v3/core/eventBus.js +0 -79
  254. package/dist/ui/v3/core/eventBus.js.map +0 -1
  255. package/dist/ui/v3/core/feedStore.d.ts +0 -34
  256. package/dist/ui/v3/core/feedStore.d.ts.map +0 -1
  257. package/dist/ui/v3/core/feedStore.js +0 -46
  258. package/dist/ui/v3/core/feedStore.js.map +0 -1
  259. package/dist/ui/v3/core/logger.d.ts +0 -40
  260. package/dist/ui/v3/core/logger.d.ts.map +0 -1
  261. package/dist/ui/v3/core/logger.js +0 -191
  262. package/dist/ui/v3/core/logger.js.map +0 -1
  263. package/dist/ui/v3/core/opEvent.d.ts +0 -15
  264. package/dist/ui/v3/core/opEvent.d.ts.map +0 -1
  265. package/dist/ui/v3/core/opEvent.js +0 -7
  266. package/dist/ui/v3/core/opEvent.js.map +0 -1
  267. package/dist/ui/v3/index.d.ts +0 -8
  268. package/dist/ui/v3/index.d.ts.map +0 -1
  269. package/dist/ui/v3/index.js +0 -51
  270. package/dist/ui/v3/index.js.map +0 -1
  271. package/dist/ui/v3/runtime/moduleConfig.d.ts +0 -21
  272. package/dist/ui/v3/runtime/moduleConfig.d.ts.map +0 -1
  273. package/dist/ui/v3/runtime/moduleConfig.js +0 -41
  274. package/dist/ui/v3/runtime/moduleConfig.js.map +0 -1
  275. package/dist/ui/v3/section0/index.d.ts +0 -22
  276. package/dist/ui/v3/section0/index.d.ts.map +0 -1
  277. package/dist/ui/v3/section0/index.js +0 -88
  278. package/dist/ui/v3/section0/index.js.map +0 -1
  279. package/dist/ui/v3/section0/runtime/createScreen.d.ts +0 -27
  280. package/dist/ui/v3/section0/runtime/createScreen.d.ts.map +0 -1
  281. package/dist/ui/v3/section0/runtime/createScreen.js +0 -55
  282. package/dist/ui/v3/section0/runtime/createScreen.js.map +0 -1
  283. package/dist/ui/v3/section0/runtime/lifecycle.d.ts +0 -53
  284. package/dist/ui/v3/section0/runtime/lifecycle.d.ts.map +0 -1
  285. package/dist/ui/v3/section0/runtime/lifecycle.js +0 -172
  286. package/dist/ui/v3/section0/runtime/lifecycle.js.map +0 -1
  287. package/dist/ui/v3/section1/index.d.ts +0 -19
  288. package/dist/ui/v3/section1/index.d.ts.map +0 -1
  289. package/dist/ui/v3/section1/index.js +0 -413
  290. package/dist/ui/v3/section1/index.js.map +0 -1
  291. package/dist/ui/v3/section1/runtime/commandLine.d.ts +0 -49
  292. package/dist/ui/v3/section1/runtime/commandLine.d.ts.map +0 -1
  293. package/dist/ui/v3/section1/runtime/commandLine.js +0 -183
  294. package/dist/ui/v3/section1/runtime/commandLine.js.map +0 -1
  295. package/dist/ui/v3/section1/runtime/focusLock.d.ts +0 -24
  296. package/dist/ui/v3/section1/runtime/focusLock.d.ts.map +0 -1
  297. package/dist/ui/v3/section1/runtime/focusLock.js +0 -44
  298. package/dist/ui/v3/section1/runtime/focusLock.js.map +0 -1
  299. package/dist/ui/v3/state/assertUiState.d.ts +0 -27
  300. package/dist/ui/v3/state/assertUiState.d.ts.map +0 -1
  301. package/dist/ui/v3/state/assertUiState.js +0 -89
  302. package/dist/ui/v3/state/assertUiState.js.map +0 -1
  303. package/dist/ui/v3/state/capabilitiesStore.d.ts +0 -54
  304. package/dist/ui/v3/state/capabilitiesStore.d.ts.map +0 -1
  305. package/dist/ui/v3/state/capabilitiesStore.js +0 -76
  306. package/dist/ui/v3/state/capabilitiesStore.js.map +0 -1
  307. package/dist/ui/v3/state/defaultState.d.ts +0 -19
  308. package/dist/ui/v3/state/defaultState.d.ts.map +0 -1
  309. package/dist/ui/v3/state/defaultState.js +0 -28
  310. package/dist/ui/v3/state/defaultState.js.map +0 -1
  311. package/dist/ui/v3/state/gatewayConnectionStore.d.ts +0 -72
  312. package/dist/ui/v3/state/gatewayConnectionStore.d.ts.map +0 -1
  313. package/dist/ui/v3/state/gatewayConnectionStore.js +0 -108
  314. package/dist/ui/v3/state/gatewayConnectionStore.js.map +0 -1
  315. package/dist/ui/v3/state/initializePostureState.d.ts +0 -23
  316. package/dist/ui/v3/state/initializePostureState.d.ts.map +0 -1
  317. package/dist/ui/v3/state/initializePostureState.js +0 -41
  318. package/dist/ui/v3/state/initializePostureState.js.map +0 -1
  319. package/dist/ui/v3/state/panelStore.d.ts +0 -80
  320. package/dist/ui/v3/state/panelStore.d.ts.map +0 -1
  321. package/dist/ui/v3/state/panelStore.js +0 -131
  322. package/dist/ui/v3/state/panelStore.js.map +0 -1
  323. package/dist/ui/v3/state/resourcesData.d.ts +0 -15
  324. package/dist/ui/v3/state/resourcesData.d.ts.map +0 -1
  325. package/dist/ui/v3/state/resourcesData.js +0 -7
  326. package/dist/ui/v3/state/resourcesData.js.map +0 -1
  327. package/dist/ui/v3/state/uiState.d.ts +0 -22
  328. package/dist/ui/v3/state/uiState.d.ts.map +0 -1
  329. package/dist/ui/v3/state/uiState.js +0 -13
  330. package/dist/ui/v3/state/uiState.js.map +0 -1
  331. package/dist/ui/v3/state/uiStateBuilder.d.ts +0 -32
  332. package/dist/ui/v3/state/uiStateBuilder.d.ts.map +0 -1
  333. package/dist/ui/v3/state/uiStateBuilder.js +0 -73
  334. package/dist/ui/v3/state/uiStateBuilder.js.map +0 -1
  335. package/dist/ui/v3/state/uiStateTypes.d.ts +0 -59
  336. package/dist/ui/v3/state/uiStateTypes.d.ts.map +0 -1
  337. package/dist/ui/v3/state/uiStateTypes.js +0 -8
  338. package/dist/ui/v3/state/uiStateTypes.js.map +0 -1
  339. package/dist/ui/v3/state/value.d.ts +0 -80
  340. package/dist/ui/v3/state/value.d.ts.map +0 -1
  341. package/dist/ui/v3/state/value.js +0 -96
  342. package/dist/ui/v3/state/value.js.map +0 -1
  343. package/dist/ui/v3/tui/geometry.d.ts +0 -83
  344. package/dist/ui/v3/tui/geometry.d.ts.map +0 -1
  345. package/dist/ui/v3/tui/geometry.js +0 -201
  346. package/dist/ui/v3/tui/geometry.js.map +0 -1
  347. package/dist/ui/v3/tui/startTui.d.ts +0 -37
  348. package/dist/ui/v3/tui/startTui.d.ts.map +0 -1
  349. package/dist/ui/v3/tui/startTui.js +0 -61
  350. package/dist/ui/v3/tui/startTui.js.map +0 -1
  351. package/dist/ui/v3/tui/terminalMode.d.ts +0 -31
  352. package/dist/ui/v3/tui/terminalMode.d.ts.map +0 -1
  353. package/dist/ui/v3/tui/terminalMode.js +0 -76
  354. package/dist/ui/v3/tui/terminalMode.js.map +0 -1
  355. package/dist/ui/v3/ui/debugUtils.d.ts +0 -67
  356. package/dist/ui/v3/ui/debugUtils.d.ts.map +0 -1
  357. package/dist/ui/v3/ui/debugUtils.js +0 -238
  358. package/dist/ui/v3/ui/debugUtils.js.map +0 -1
  359. package/dist/ui/v3/ui/focus.d.ts +0 -28
  360. package/dist/ui/v3/ui/focus.d.ts.map +0 -1
  361. package/dist/ui/v3/ui/focus.js +0 -38
  362. package/dist/ui/v3/ui/focus.js.map +0 -1
  363. package/dist/ui/v3/ui/layout/hubLayout.d.ts +0 -43
  364. package/dist/ui/v3/ui/layout/hubLayout.d.ts.map +0 -1
  365. package/dist/ui/v3/ui/layout/hubLayout.js +0 -170
  366. package/dist/ui/v3/ui/layout/hubLayout.js.map +0 -1
  367. package/dist/ui/v3/ui/layout/phase1Layout.d.ts +0 -63
  368. package/dist/ui/v3/ui/layout/phase1Layout.d.ts.map +0 -1
  369. package/dist/ui/v3/ui/layout/phase1Layout.js +0 -274
  370. package/dist/ui/v3/ui/layout/phase1Layout.js.map +0 -1
  371. package/dist/ui/v3/ui/layout/phase1Layout.test.d.ts +0 -5
  372. package/dist/ui/v3/ui/layout/phase1Layout.test.d.ts.map +0 -1
  373. package/dist/ui/v3/ui/layout/phase1Layout.test.js +0 -120
  374. package/dist/ui/v3/ui/layout/phase1Layout.test.js.map +0 -1
  375. package/dist/ui/v3/ui/minimalRuntime.d.ts +0 -14
  376. package/dist/ui/v3/ui/minimalRuntime.d.ts.map +0 -1
  377. package/dist/ui/v3/ui/minimalRuntime.js +0 -111
  378. package/dist/ui/v3/ui/minimalRuntime.js.map +0 -1
  379. package/dist/ui/v3/ui/panels/AssetsPanel.d.ts +0 -17
  380. package/dist/ui/v3/ui/panels/AssetsPanel.d.ts.map +0 -1
  381. package/dist/ui/v3/ui/panels/AssetsPanel.js +0 -53
  382. package/dist/ui/v3/ui/panels/AssetsPanel.js.map +0 -1
  383. package/dist/ui/v3/ui/panels/CapabilitiesPanel.d.ts +0 -20
  384. package/dist/ui/v3/ui/panels/CapabilitiesPanel.d.ts.map +0 -1
  385. package/dist/ui/v3/ui/panels/CapabilitiesPanel.js +0 -67
  386. package/dist/ui/v3/ui/panels/CapabilitiesPanel.js.map +0 -1
  387. package/dist/ui/v3/ui/panels/NetworkPanel.d.ts +0 -31
  388. package/dist/ui/v3/ui/panels/NetworkPanel.d.ts.map +0 -1
  389. package/dist/ui/v3/ui/panels/NetworkPanel.js +0 -153
  390. package/dist/ui/v3/ui/panels/NetworkPanel.js.map +0 -1
  391. package/dist/ui/v3/ui/panels/PosturePanel.d.ts +0 -16
  392. package/dist/ui/v3/ui/panels/PosturePanel.d.ts.map +0 -1
  393. package/dist/ui/v3/ui/panels/PosturePanel.js +0 -60
  394. package/dist/ui/v3/ui/panels/PosturePanel.js.map +0 -1
  395. package/dist/ui/v3/ui/panels/ResourcesPanel.d.ts +0 -20
  396. package/dist/ui/v3/ui/panels/ResourcesPanel.d.ts.map +0 -1
  397. package/dist/ui/v3/ui/panels/ResourcesPanel.js +0 -66
  398. package/dist/ui/v3/ui/panels/ResourcesPanel.js.map +0 -1
  399. package/dist/ui/v3/ui/phase1Runtime.d.ts +0 -29
  400. package/dist/ui/v3/ui/phase1Runtime.d.ts.map +0 -1
  401. package/dist/ui/v3/ui/phase1Runtime.js +0 -1648
  402. package/dist/ui/v3/ui/phase1Runtime.js.map +0 -1
  403. package/dist/ui/v3/ui/phase1RuntimeClean.d.ts +0 -34
  404. package/dist/ui/v3/ui/phase1RuntimeClean.d.ts.map +0 -1
  405. package/dist/ui/v3/ui/phase1RuntimeClean.js +0 -1841
  406. package/dist/ui/v3/ui/phase1RuntimeClean.js.map +0 -1
  407. package/dist/ui/v3/ui/primitives/Panel.d.ts +0 -39
  408. package/dist/ui/v3/ui/primitives/Panel.d.ts.map +0 -1
  409. package/dist/ui/v3/ui/primitives/Panel.js +0 -105
  410. package/dist/ui/v3/ui/primitives/Panel.js.map +0 -1
  411. package/dist/ui/v3/ui/theme.d.ts +0 -37
  412. package/dist/ui/v3/ui/theme.d.ts.map +0 -1
  413. package/dist/ui/v3/ui/theme.js +0 -40
  414. package/dist/ui/v3/ui/theme.js.map +0 -1
  415. package/dist/ui/v3/ui/uiRuntime.d.ts +0 -40
  416. package/dist/ui/v3/ui/uiRuntime.d.ts.map +0 -1
  417. package/dist/ui/v3/ui/uiRuntime.js +0 -60
  418. package/dist/ui/v3/ui/uiRuntime.js.map +0 -1
  419. package/dist/ui/v3/ui/widgets/CommandLine.d.ts +0 -26
  420. package/dist/ui/v3/ui/widgets/CommandLine.d.ts.map +0 -1
  421. package/dist/ui/v3/ui/widgets/CommandLine.js +0 -67
  422. package/dist/ui/v3/ui/widgets/CommandLine.js.map +0 -1
  423. package/dist/ui/v3/v1Adapters/agents.d.ts +0 -72
  424. package/dist/ui/v3/v1Adapters/agents.d.ts.map +0 -1
  425. package/dist/ui/v3/v1Adapters/agents.js +0 -182
  426. package/dist/ui/v3/v1Adapters/agents.js.map +0 -1
  427. package/dist/ui/v3/v1Adapters/config.d.ts +0 -67
  428. package/dist/ui/v3/v1Adapters/config.d.ts.map +0 -1
  429. package/dist/ui/v3/v1Adapters/config.js +0 -78
  430. package/dist/ui/v3/v1Adapters/config.js.map +0 -1
  431. package/dist/ui/v3/v1Adapters/connect.d.ts +0 -77
  432. package/dist/ui/v3/v1Adapters/connect.d.ts.map +0 -1
  433. package/dist/ui/v3/v1Adapters/connect.js +0 -576
  434. package/dist/ui/v3/v1Adapters/connect.js.map +0 -1
  435. package/dist/ui/v3/v1Adapters/httpDebug.d.ts +0 -19
  436. package/dist/ui/v3/v1Adapters/httpDebug.d.ts.map +0 -1
  437. package/dist/ui/v3/v1Adapters/httpDebug.js +0 -60
  438. package/dist/ui/v3/v1Adapters/httpDebug.js.map +0 -1
  439. package/dist/ui/v3/v1Adapters/runs.d.ts +0 -77
  440. package/dist/ui/v3/v1Adapters/runs.d.ts.map +0 -1
  441. package/dist/ui/v3/v1Adapters/runs.js +0 -339
  442. package/dist/ui/v3/v1Adapters/runs.js.map +0 -1
  443. package/dist/ui/v4/engine/renderFrame.d.ts +0 -47
  444. package/dist/ui/v4/engine/renderFrame.d.ts.map +0 -1
  445. package/dist/ui/v4/engine/renderFrame.js +0 -653
  446. package/dist/ui/v4/engine/renderFrame.js.map +0 -1
  447. package/dist/ui/v4/engine/resizeController.d.ts +0 -48
  448. package/dist/ui/v4/engine/resizeController.d.ts.map +0 -1
  449. package/dist/ui/v4/engine/resizeController.js +0 -285
  450. package/dist/ui/v4/engine/resizeController.js.map +0 -1
  451. package/dist/ui/v4/engine/safeViewport.d.ts +0 -47
  452. package/dist/ui/v4/engine/safeViewport.d.ts.map +0 -1
  453. package/dist/ui/v4/engine/safeViewport.js +0 -123
  454. package/dist/ui/v4/engine/safeViewport.js.map +0 -1
  455. package/dist/ui/v4/engine/terminalProfile.d.ts +0 -56
  456. package/dist/ui/v4/engine/terminalProfile.d.ts.map +0 -1
  457. package/dist/ui/v4/engine/terminalProfile.js +0 -115
  458. package/dist/ui/v4/engine/terminalProfile.js.map +0 -1
  459. package/dist/ui/v4/index.d.ts +0 -28
  460. package/dist/ui/v4/index.d.ts.map +0 -1
  461. package/dist/ui/v4/index.js +0 -993
  462. package/dist/ui/v4/index.js.map +0 -1
  463. package/dist/ui/v4/layout/layoutEngine.d.ts +0 -62
  464. package/dist/ui/v4/layout/layoutEngine.d.ts.map +0 -1
  465. package/dist/ui/v4/layout/layoutEngine.js +0 -294
  466. package/dist/ui/v4/layout/layoutEngine.js.map +0 -1
  467. package/dist/ui/v4/runtime/keepAlive.d.ts +0 -21
  468. package/dist/ui/v4/runtime/keepAlive.d.ts.map +0 -1
  469. package/dist/ui/v4/runtime/keepAlive.js +0 -149
  470. package/dist/ui/v4/runtime/keepAlive.js.map +0 -1
  471. package/dist/ui/v4/runtime/logger.d.ts +0 -35
  472. package/dist/ui/v4/runtime/logger.d.ts.map +0 -1
  473. package/dist/ui/v4/runtime/logger.js +0 -109
  474. package/dist/ui/v4/runtime/logger.js.map +0 -1
  475. package/dist/ui/v5/debug/assertNoOverflow.d.ts +0 -28
  476. package/dist/ui/v5/debug/assertNoOverflow.d.ts.map +0 -1
  477. package/dist/ui/v5/debug/assertNoOverflow.js +0 -63
  478. package/dist/ui/v5/debug/assertNoOverflow.js.map +0 -1
  479. package/dist/ui/v5/debug/debugCommands.d.ts +0 -20
  480. package/dist/ui/v5/debug/debugCommands.d.ts.map +0 -1
  481. package/dist/ui/v5/debug/debugCommands.js +0 -461
  482. package/dist/ui/v5/debug/debugCommands.js.map +0 -1
  483. package/dist/ui/v5/debugCommands.d.ts +0 -20
  484. package/dist/ui/v5/debugCommands.d.ts.map +0 -1
  485. package/dist/ui/v5/debugCommands.js +0 -81
  486. package/dist/ui/v5/debugCommands.js.map +0 -1
  487. package/dist/ui/v5/guardrails/stdoutGuard.d.ts +0 -23
  488. package/dist/ui/v5/guardrails/stdoutGuard.d.ts.map +0 -1
  489. package/dist/ui/v5/guardrails/stdoutGuard.js +0 -94
  490. package/dist/ui/v5/guardrails/stdoutGuard.js.map +0 -1
  491. package/dist/ui/v5/guardrails/terminalRestore.d.ts +0 -17
  492. package/dist/ui/v5/guardrails/terminalRestore.d.ts.map +0 -1
  493. package/dist/ui/v5/guardrails/terminalRestore.js +0 -47
  494. package/dist/ui/v5/guardrails/terminalRestore.js.map +0 -1
  495. package/dist/ui/v5/index.d.ts +0 -30
  496. package/dist/ui/v5/index.d.ts.map +0 -1
  497. package/dist/ui/v5/index.js +0 -243
  498. package/dist/ui/v5/index.js.map +0 -1
  499. package/dist/ui/v5/kernel/kernel.d.ts +0 -81
  500. package/dist/ui/v5/kernel/kernel.d.ts.map +0 -1
  501. package/dist/ui/v5/kernel/kernel.js +0 -339
  502. package/dist/ui/v5/kernel/kernel.js.map +0 -1
  503. package/dist/ui/v5/kernel.d.ts +0 -75
  504. package/dist/ui/v5/kernel.d.ts.map +0 -1
  505. package/dist/ui/v5/kernel.js +0 -289
  506. package/dist/ui/v5/kernel.js.map +0 -1
  507. package/dist/ui/v5/layout/clampRect.d.ts +0 -28
  508. package/dist/ui/v5/layout/clampRect.d.ts.map +0 -1
  509. package/dist/ui/v5/layout/clampRect.js +0 -45
  510. package/dist/ui/v5/layout/clampRect.js.map +0 -1
  511. package/dist/ui/v5/layout/layoutEngine.d.ts +0 -16
  512. package/dist/ui/v5/layout/layoutEngine.d.ts.map +0 -1
  513. package/dist/ui/v5/layout/layoutEngine.js +0 -99
  514. package/dist/ui/v5/layout/layoutEngine.js.map +0 -1
  515. package/dist/ui/v5/renderGate.d.ts +0 -19
  516. package/dist/ui/v5/renderGate.d.ts.map +0 -1
  517. package/dist/ui/v5/renderGate.js +0 -36
  518. package/dist/ui/v5/renderGate.js.map +0 -1
  519. package/dist/ui/v5/resize/resizeController.d.ts +0 -62
  520. package/dist/ui/v5/resize/resizeController.d.ts.map +0 -1
  521. package/dist/ui/v5/resize/resizeController.js +0 -141
  522. package/dist/ui/v5/resize/resizeController.js.map +0 -1
  523. package/dist/ui/v5/resizeController.d.ts +0 -55
  524. package/dist/ui/v5/resizeController.d.ts.map +0 -1
  525. package/dist/ui/v5/resizeController.js +0 -124
  526. package/dist/ui/v5/resizeController.js.map +0 -1
  527. package/dist/ui/v5/runtime/keepAlive.d.ts +0 -37
  528. package/dist/ui/v5/runtime/keepAlive.d.ts.map +0 -1
  529. package/dist/ui/v5/runtime/keepAlive.js +0 -122
  530. package/dist/ui/v5/runtime/keepAlive.js.map +0 -1
  531. package/dist/ui/v5/runtime/restoreTerminal.d.ts +0 -34
  532. package/dist/ui/v5/runtime/restoreTerminal.d.ts.map +0 -1
  533. package/dist/ui/v5/runtime/restoreTerminal.js +0 -100
  534. package/dist/ui/v5/runtime/restoreTerminal.js.map +0 -1
  535. package/dist/ui/v5/runtime/stdoutGuard.d.ts +0 -42
  536. package/dist/ui/v5/runtime/stdoutGuard.d.ts.map +0 -1
  537. package/dist/ui/v5/runtime/stdoutGuard.js +0 -156
  538. package/dist/ui/v5/runtime/stdoutGuard.js.map +0 -1
  539. package/dist/ui/v5/viewport/getViewport.d.ts +0 -23
  540. package/dist/ui/v5/viewport/getViewport.d.ts.map +0 -1
  541. package/dist/ui/v5/viewport/getViewport.js +0 -117
  542. package/dist/ui/v5/viewport/getViewport.js.map +0 -1
  543. package/dist/ui/v5/viewport.d.ts +0 -41
  544. package/dist/ui/v5/viewport.d.ts.map +0 -1
  545. package/dist/ui/v5/viewport.js +0 -90
  546. package/dist/ui/v5/viewport.js.map +0 -1
  547. package/dist/ui/widgets/flagRow.d.ts +0 -25
  548. package/dist/ui/widgets/flagRow.d.ts.map +0 -1
  549. package/dist/ui/widgets/flagRow.js +0 -57
  550. package/dist/ui/widgets/flagRow.js.map +0 -1
  551. package/dist/ui/widgets/index.d.ts +0 -9
  552. package/dist/ui/widgets/index.d.ts.map +0 -1
  553. package/dist/ui/widgets/index.js +0 -9
  554. package/dist/ui/widgets/index.js.map +0 -1
  555. package/dist/ui/widgets/meter.d.ts +0 -18
  556. package/dist/ui/widgets/meter.d.ts.map +0 -1
  557. package/dist/ui/widgets/meter.js +0 -38
  558. package/dist/ui/widgets/meter.js.map +0 -1
  559. package/dist/ui/widgets/miniMap.d.ts +0 -26
  560. package/dist/ui/widgets/miniMap.d.ts.map +0 -1
  561. package/dist/ui/widgets/miniMap.js +0 -94
  562. package/dist/ui/widgets/miniMap.js.map +0 -1
  563. package/dist/ui/widgets/sparkline.d.ts +0 -17
  564. package/dist/ui/widgets/sparkline.d.ts.map +0 -1
  565. package/dist/ui/widgets/sparkline.js +0 -65
  566. package/dist/ui/widgets/sparkline.js.map +0 -1
@@ -1,1648 +0,0 @@
1
- /**
2
- * Phase 1 Runtime
3
- *
4
- * Layout skeleton + dedicated command line
5
- * - 6 read-only panels
6
- * - 1 command line (only writable area)
7
- * - Focus locked to command line
8
- *
9
- * Phase 2: Command Router
10
- * - Command parsing and routing
11
- * - Output to OPERATIONS FEED panel
12
- *
13
- * Phase 3: Operations Feed as Only Output Channel
14
- * - EventBus system
15
- * - All output goes through EventBus → feedStore
16
- * - Long line truncation
17
- */
18
- import blessed from 'neo-blessed';
19
- // Type assertion for blessed (textbox not in types)
20
- const blessedLib = blessed;
21
- import { computePhase1Layout } from './layout/phase1Layout.js';
22
- import { feedStore } from '../core/feedStore.js';
23
- import { eventBus } from '../core/eventBus.js';
24
- import { logger, enableTuiMode, disableBootPhase } from '../core/logger.js';
25
- import { parse, execute, UiAction } from '../commands/commandEngine.js';
26
- import { buildUiState } from '../state/uiStateBuilder.js';
27
- import { defaultUiState } from '../state/defaultState.js';
28
- import { initializePostureState } from '../state/initializePostureState.js';
29
- import { renderResourcesPanel } from './panels/ResourcesPanel.js';
30
- import { renderPosturePanel } from './panels/PosturePanel.js';
31
- import { renderAssetsPanel } from './panels/AssetsPanel.js';
32
- import { renderNetworkPanel } from './panels/NetworkPanel.js';
33
- import { renderCapabilitiesPanel } from './panels/CapabilitiesPanel.js';
34
- import { isAvailable, isUnavailable, isLoading, available, loading } from '../state/value.js';
35
- import { assertUiState } from '../state/assertUiState.js';
36
- // Step 1: Boot ID and render count tracking
37
- // Phase A: UI Layout Fingerprint
38
- const UI_ROOT_ID = `ui-root-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
39
- const BOOT_ID = `boot-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
40
- const UI_INSTANCE_ID = `ui-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`; // Debug: Track UI instance
41
- let renderCount = 0;
42
- let uiInstance = null;
43
- const DEBUG_LAYOUT = process.env.TUI_DEBUG_LAYOUT === '1';
44
- // CRITICAL: Hard singleton guard - screen can only be created ONCE
45
- let screenCreated = false;
46
- let screenCreationCount = 0; // Debug: Track screen creation count
47
- let screen = null;
48
- let panels = [];
49
- let commandLine = null;
50
- let statusStrip = null;
51
- let isCleaningUp = false;
52
- // CRITICAL: Guard to prevent widgets from being appended multiple times
53
- let widgetsAppended = false;
54
- // CRITICAL: Guard to prevent panels from being created multiple times
55
- let panelsCreated = false;
56
- const PROMPT = '4runr> '; // Non-editable prompt prefix
57
- let currentCommandValue = PROMPT; // Track current command value
58
- // STEP 2: Initialize posture state before first render (prevents flicker)
59
- const initialPostureState = initializePostureState();
60
- const initializedDefaultState = {
61
- ...defaultUiState,
62
- posture: initialPostureState
63
- };
64
- // Section 4: Current UiState (initialized with defaultUiState - all UNAVAILABLE)
65
- let currentUiState = initializedDefaultState;
66
- let lastResourcesAvailable = null; // Phase 4: Track state transitions
67
- // CRITICAL: Guard to prevent duplicate handler binding
68
- let inputHandlersBound = false;
69
- // Part A: Resize tracking (no longer handling resize, just tracking for warning)
70
- let lastResizeWidth = 0;
71
- let lastResizeHeight = 0;
72
- let uiModeState = 'HUB';
73
- // S0.4: Command history
74
- const commandHistory = [];
75
- let historyIndex = -1; // -1 means not browsing history
76
- /**
77
- * S0.1 & S0.4: Reassert focus on command line
78
- * Called after every render and command execution
79
- */
80
- function reassertFocus() {
81
- if (commandLine && screen) {
82
- commandLine.focus();
83
- screen.focused = commandLine;
84
- }
85
- }
86
- /**
87
- * Cleanup and exit
88
- */
89
- /**
90
- * Panel-safe line formatter (used ONLY at render time)
91
- *
92
- * HARDENED: Explicit width truncation with validation
93
- *
94
- * Truncates lines based on panel's inner width.
95
- * Never truncates when storing events - only at render time.
96
- *
97
- * Rules:
98
- * - If line fits → render as-is
99
- * - If line too long → truncate to fit and append ...
100
- * - Never overflow outside the border
101
- * - Explicit validation of width constraints
102
- */
103
- function formatLineForPanel(time, tag, msg, availableWidth) {
104
- // HARDENED: Validate availableWidth (must be positive)
105
- if (availableWidth <= 0) {
106
- return '[ERR]'; // Fallback for invalid width
107
- }
108
- // Calculate prefix width: "HH:MM:SS [TAG] " = time + spaces + brackets + tag + spaces
109
- const prefix = `${time} [${tag}] `;
110
- const prefixWidth = prefix.length;
111
- // HARDENED: Ensure prefix doesn't exceed available width
112
- if (prefixWidth >= availableWidth) {
113
- // Prefix itself is too long - truncate prefix
114
- const truncatedPrefix = prefix.substring(0, Math.max(0, availableWidth - 3)) + '...';
115
- return truncatedPrefix;
116
- }
117
- // Available width for message portion
118
- const msgWidth = availableWidth - prefixWidth;
119
- // HARDENED: Ensure msgWidth is positive
120
- if (msgWidth <= 0) {
121
- return prefix; // No room for message
122
- }
123
- // If message fits, return as-is
124
- if (msg.length <= msgWidth) {
125
- return prefix + msg;
126
- }
127
- // HARDENED: Explicit truncation with validation
128
- // Reserve 3 chars for "..."
129
- const truncateAt = Math.max(0, msgWidth - 3);
130
- const truncatedMsg = msg.substring(0, truncateAt) + '...';
131
- // HARDENED: Final validation - ensure result doesn't exceed availableWidth
132
- const result = prefix + truncatedMsg;
133
- if (result.length > availableWidth) {
134
- // Emergency truncation (should never happen, but safety check)
135
- return result.substring(0, availableWidth);
136
- }
137
- return result;
138
- }
139
- /**
140
- * Section 2: Update operations panel with latest feed entries
141
- *
142
- * HARDENED: Explicit max-line enforcement with validation
143
- *
144
- * Rules:
145
- * - Line-budget enforcement: only render last N visible lines (N = box.height - 2)
146
- * - Panel-safe line formatter: truncate at render time only
147
- * - Never wrap, never overflow
148
- * - Explicit validation of line count
149
- *
150
- * Empty State Behavior (INTENTIONAL):
151
- * - When feed is empty (e.g., after CLEAR_FEED action), displays "No operations yet"
152
- * - This is the Operations renderer's fallback message, NOT an event from clear command
153
- * - clear command emits no events (returns empty events array)
154
- * - Message appears once per render, does not duplicate, does not reappear on resize
155
- * - This is stable and intentional behavior
156
- */
157
- function updateOperationsPanel() {
158
- const operationsPanel = panels[3]; // OPERATIONS is 4th panel (index 3)
159
- if (!operationsPanel)
160
- return;
161
- // HARDENED: Calculate available space with validation
162
- const panelHeight = operationsPanel.height;
163
- const panelWidth = operationsPanel.width;
164
- // Validate panel dimensions
165
- if (panelHeight < 3 || panelWidth < 5) {
166
- // Panel too small - set minimal content
167
- operationsPanel.setContent('\n [ERR: Panel too small]');
168
- return;
169
- }
170
- // Calculate available space (account for border only)
171
- const innerHeight = panelHeight - 2; // Top and bottom border
172
- const innerWidth = panelWidth - 2; // Left and right border only (no padding)
173
- // HARDENED: Validate inner dimensions
174
- if (innerHeight <= 0 || innerWidth <= 0) {
175
- operationsPanel.setContent('\n [ERR: Invalid dimensions]');
176
- return;
177
- }
178
- // HARDENED: Explicit max-line enforcement with validation
179
- // Line-budget enforcement: only render last N visible lines
180
- const maxLines = Math.max(1, Math.floor(innerHeight)); // At least 1 line, floor to ensure integer
181
- // HARDENED: Cap maxLines to prevent excessive rendering
182
- const MAX_ALLOWED_LINES = 100; // Safety cap
183
- const enforcedMaxLines = Math.min(maxLines, MAX_ALLOWED_LINES);
184
- // Get events (get slightly more than needed, but enforce limit in rendering)
185
- const events = feedStore.getLatest(enforcedMaxLines * 2); // Get more than needed for safety
186
- // Format lines with panel-safe formatter
187
- const lines = [];
188
- // INTENTIONAL: Display fallback message when feed is empty
189
- // This appears after CLEAR_FEED action (clear command emits no events)
190
- // This is the Operations renderer's fallback - not an event from clear command
191
- // Behavior is stable: message appears once, does not duplicate on render, does not reappear on resize
192
- if (events.length === 0) {
193
- lines.push('No operations yet');
194
- }
195
- else {
196
- // HARDENED: Explicitly enforce max lines - only render the last enforcedMaxLines events
197
- const eventsToRender = events.slice(-enforcedMaxLines);
198
- // HARDENED: Final validation - ensure we don't exceed enforcedMaxLines
199
- if (eventsToRender.length > enforcedMaxLines) {
200
- // Safety check - should never happen, but enforce limit
201
- eventsToRender.splice(0, eventsToRender.length - enforcedMaxLines);
202
- }
203
- for (const event of eventsToRender) {
204
- const time = formatTimestamp(event.ts);
205
- const line = formatLineForPanel(time, event.tag, event.msg, innerWidth);
206
- lines.push(line);
207
- }
208
- }
209
- // HARDENED: Final line count validation
210
- if (lines.length > enforcedMaxLines) {
211
- // Emergency truncation (should never happen)
212
- lines.splice(0, lines.length - enforcedMaxLines);
213
- }
214
- // Join with newlines, add minimal padding
215
- const paddingLeft = ' ';
216
- const content = lines
217
- .map(line => paddingLeft + line)
218
- .join('\n');
219
- operationsPanel.setContent('\n' + content);
220
- }
221
- /**
222
- * Section 4: Update panel content helper
223
- *
224
- * Generic function to update any panel with Value<T> content
225
- * Handles truncation and padding consistently
226
- */
227
- function updatePanelContent(panel, contentLines) {
228
- if (!panel)
229
- return;
230
- const paddingTop = '\n';
231
- const paddingLeft = ' ';
232
- // CRITICAL: Validate and filter contentLines
233
- // Ensure all lines are strings and filter out empty/null/undefined
234
- const validLines = contentLines
235
- .filter((line) => typeof line === 'string' && line !== null && line !== undefined)
236
- .map(line => String(line)); // Ensure string type
237
- // Phase C: Explicit grid math - account for borders
238
- // Blessed box: 1 char border on each side (left/right/top/bottom)
239
- // Inner content area = width - 2 (left + right border) - 2 (left padding only, we add it back)
240
- const panelWidth = panel.width;
241
- const panelHeight = panel.height;
242
- const innerHeight = Math.max(1, panelHeight - 2); // Top and bottom border, min 1
243
- const innerWidth = Math.max(1, panelWidth - 2 - 2); // Border (2) + left padding space (2 for safety), min 1
244
- // Truncate to fit panel height
245
- const displayLines = validLines.slice(0, Math.min(validLines.length, innerHeight));
246
- // Truncate each line to fit width
247
- const truncatedLines = displayLines.map(line => {
248
- return safeLine(line, innerWidth);
249
- });
250
- // Pad to fill available height if needed
251
- while (truncatedLines.length < innerHeight) {
252
- truncatedLines.push('');
253
- }
254
- // Phase E: Layout Debug Overlay - append debug info if enabled
255
- if (DEBUG_LAYOUT && panel._debugInfo) {
256
- const debug = panel._debugInfo;
257
- truncatedLines.push(''); // Empty line separator
258
- truncatedLines.push(`w=${debug.w} h=${debug.h}`);
259
- truncatedLines.push(`x=${debug.x} y=${debug.y}`);
260
- truncatedLines.push(`term=${debug.terminalCols}x${debug.terminalRows}`);
261
- }
262
- // CRITICAL: Clear old content first, then set new content
263
- // This prevents appending/concatenation issues
264
- const content = truncatedLines
265
- .map(line => paddingLeft + line)
266
- .join('\n');
267
- // Clear and set content atomically
268
- panel.setContent(paddingTop + content);
269
- }
270
- /**
271
- * Section 4: Update posture panel content
272
- *
273
- * Updates text in-place without recreating widget (no flicker)
274
- * No blank panels - always shows AVAILABLE or UNAVAILABLE/UNKNOWN with reason
275
- */
276
- function updatePosturePanel() {
277
- const posturePanel = panels[0]; // POSTURE is 1st panel (index 0)
278
- if (!currentUiState)
279
- return;
280
- const contentLines = renderPosturePanel(currentUiState.posture);
281
- updatePanelContent(posturePanel, contentLines);
282
- }
283
- /**
284
- * Section 4: Update resources panel content
285
- *
286
- * Updates text in-place without recreating widget (no flicker)
287
- * No blank panels - always shows AVAILABLE or UNAVAILABLE/UNKNOWN with reason
288
- */
289
- function updateResourcesPanel() {
290
- const resourcesPanel = panels[1]; // RESOURCES is 2nd panel (index 1)
291
- if (!currentUiState)
292
- return;
293
- const contentLines = renderResourcesPanel(currentUiState.resources);
294
- updatePanelContent(resourcesPanel, contentLines);
295
- }
296
- /**
297
- * Section 4: Update assets panel content
298
- *
299
- * Updates text in-place without recreating widget (no flicker)
300
- * No blank panels - always shows AVAILABLE or UNAVAILABLE/UNKNOWN with reason
301
- */
302
- function updateAssetsPanel() {
303
- const assetsPanel = panels[2]; // ASSETS is 3rd panel (index 2)
304
- if (!currentUiState)
305
- return;
306
- const contentLines = renderAssetsPanel(currentUiState.assets);
307
- updatePanelContent(assetsPanel, contentLines);
308
- }
309
- /**
310
- * Section 4: Update network panel content
311
- *
312
- * Updates text in-place without recreating widget (no flicker)
313
- * No blank panels - always shows AVAILABLE or UNAVAILABLE/UNKNOWN with reason
314
- */
315
- function updateNetworkPanel() {
316
- const networkPanel = panels[4]; // NETWORK is 5th panel (index 4)
317
- if (!currentUiState)
318
- return;
319
- const contentLines = renderNetworkPanel(currentUiState.network);
320
- updatePanelContent(networkPanel, contentLines);
321
- }
322
- /**
323
- * Section 4: Update capabilities panel content
324
- *
325
- * Updates text in-place without recreating widget (no flicker)
326
- * No blank panels - always shows AVAILABLE or UNAVAILABLE/UNKNOWN with reason
327
- */
328
- function updateCapabilitiesPanel() {
329
- const capabilitiesPanel = panels[5]; // CAPABILITIES is 6th panel (index 5)
330
- if (!currentUiState)
331
- return;
332
- const contentLines = renderCapabilitiesPanel(currentUiState.capabilities);
333
- updatePanelContent(capabilitiesPanel, contentLines);
334
- }
335
- /**
336
- * Section 4: Update status strip content
337
- *
338
- * Updates status strip with Value<StatusStripState>
339
- * No blank status - always shows AVAILABLE or UNAVAILABLE/UNKNOWN with reason
340
- */
341
- function updateStatusStrip() {
342
- if (!statusStrip || !currentUiState)
343
- return;
344
- const statusValue = currentUiState.statusStrip;
345
- if (isAvailable(statusValue)) {
346
- const data = statusValue.value;
347
- const content = ` ${data.left} | ${data.right} `;
348
- statusStrip.setContent(content);
349
- }
350
- else {
351
- // UNAVAILABLE or UNKNOWN - show reason
352
- const reason = statusValue.reason || 'Not initialized';
353
- const nextAction = ('nextAction' in statusValue ? statusValue.nextAction : undefined) || 'Run "help"';
354
- const content = ` ${statusValue.availability}: ${reason} | Next: ${nextAction} `;
355
- statusStrip.setContent(content);
356
- }
357
- }
358
- /**
359
- * Step 6: Cleanup and exit with proper unmount
360
- */
361
- function cleanupAndExit(code = 0) {
362
- // Step 6: Call unmount if UI instance exists
363
- if (uiInstance) {
364
- uiInstance.unmount();
365
- }
366
- // Fallback cleanup if unmount didn't run
367
- // Clean up prompt protection interval if it exists
368
- if (commandLine && commandLine._promptProtectionInterval) {
369
- clearInterval(commandLine._promptProtectionInterval);
370
- }
371
- if (isCleaningUp)
372
- return;
373
- isCleaningUp = true;
374
- // Clear render interval
375
- if (screen && screen.renderInterval) {
376
- clearInterval(screen.renderInterval);
377
- }
378
- if (screen) {
379
- try {
380
- screen.destroy();
381
- }
382
- catch (e) {
383
- // Ignore destroy errors
384
- }
385
- screen = null;
386
- }
387
- // Restore terminal state
388
- if (process.stdout.isTTY) {
389
- process.stdout.write('\x1b[?25h'); // Show cursor
390
- process.stdout.write('\x1b[?1049l'); // Exit alternate screen
391
- }
392
- process.exit(code);
393
- }
394
- /**
395
- * Create a read-only panel
396
- *
397
- * Phase B: Single PanelFrame - exactly one border per panel
398
- * Phase C: Explicit width/height - no flex guessing
399
- * Phase E: Debug overlay support
400
- */
401
- function createPanel(screen, boxRect, title, getContent) {
402
- const isOperations = title === 'OPERATIONS';
403
- // Phase C: Explicit grid math - ensure width accounts for borders
404
- // Blessed box borders take 2 chars (1 left + 1 right), so inner width = width - 2
405
- // But we pass the full width to blessed, it handles borders internally
406
- const explicitWidth = Math.max(3, boxRect.width); // Min 3 to fit border + 1 char
407
- const explicitHeight = Math.max(3, boxRect.height); // Min 3 to fit border + 1 char
408
- const panel = blessedLib.box({
409
- top: boxRect.top,
410
- left: boxRect.left,
411
- width: explicitWidth,
412
- height: explicitHeight,
413
- // Phase B: Single border - only the box itself draws borders, no parent containers
414
- border: {
415
- type: 'line',
416
- },
417
- tags: true,
418
- content: '', // Will be updated if getContent is provided
419
- style: {
420
- border: {
421
- fg: 'cyan',
422
- },
423
- },
424
- // Force no-wrap overflow OFF (or wrap ON) on Operations widget
425
- // For "intel console" vibe: truncate on hub (no wrap)
426
- wrap: false, // No wrapping - we manually truncate
427
- scrollable: false, // No scrolling - we enforce line-budget
428
- alwaysScroll: false,
429
- // Make panel read-only and non-focusable - CRITICAL
430
- clickable: false,
431
- keys: false,
432
- mouse: false,
433
- input: false,
434
- keyable: false,
435
- focusable: false,
436
- // Prevent any interaction
437
- interactive: false,
438
- });
439
- // Phase B: Set title in top border (only once, no parent containers add titles)
440
- panel.setLabel(` {cyan-fg}${title.toUpperCase()}{/}`);
441
- // Phase E: Layout Debug Overlay
442
- if (DEBUG_LAYOUT) {
443
- // Store debug info on panel
444
- panel._debugInfo = {
445
- title,
446
- w: explicitWidth,
447
- h: explicitHeight,
448
- x: boxRect.left,
449
- y: boxRect.top,
450
- terminalCols: screen.width,
451
- terminalRows: screen.height,
452
- };
453
- }
454
- // Store content getter for updates
455
- if (getContent) {
456
- panel.getContent = getContent;
457
- }
458
- return panel;
459
- }
460
- /**
461
- * Truncate line to fit width (Phase 3)
462
- *
463
- * Rules:
464
- * - If text.length <= width, return as-is
465
- * - Else return text.slice(0, width - 1) + "…"
466
- */
467
- // Old truncateLine removed - using safeLine instead (defined above)
468
- /**
469
- * Section 2: Format feed entries for display in OPERATIONS panel
470
- *
471
- * Line format: HH:MM:SS [TAG] message...
472
- */
473
- function formatTimestamp(ts) {
474
- try {
475
- const date = new Date(ts);
476
- const hours = date.getHours().toString().padStart(2, '0');
477
- const minutes = date.getMinutes().toString().padStart(2, '0');
478
- const seconds = date.getSeconds().toString().padStart(2, '0');
479
- return `${hours}:${minutes}:${seconds}`;
480
- }
481
- catch {
482
- return '00:00:00';
483
- }
484
- }
485
- /**
486
- * Safe line truncation helper
487
- * Strips newlines/tabs, truncates to max width, appends ...
488
- * FIXED: Prevents text corruption by ensuring valid width and proper truncation
489
- */
490
- function safeLine(line, max) {
491
- // Validate max width
492
- if (max <= 0) {
493
- return '';
494
- }
495
- // Ensure line is a string
496
- if (typeof line !== 'string') {
497
- line = String(line);
498
- }
499
- // Strip newlines and tabs, replace with spaces
500
- // Also collapse multiple spaces into one
501
- let cleaned = line.replace(/[\n\r\t]/g, ' ').replace(/\s+/g, ' ').trim();
502
- // Truncate if exceeds max width
503
- if (cleaned.length > max) {
504
- // Reserve 3 chars for "..."
505
- const truncateAt = Math.max(0, max - 3);
506
- if (truncateAt > 0) {
507
- cleaned = cleaned.substring(0, truncateAt) + '...';
508
- }
509
- else {
510
- // If max is too small, just return "..."
511
- cleaned = '...';
512
- }
513
- }
514
- return cleaned;
515
- }
516
- /**
517
- * Format feed entries for display in OPERATIONS panel
518
- * NOTE: This function is deprecated - use updateOperationsPanel() directly
519
- * which uses formatLineForPanel() for proper truncation
520
- */
521
- function formatFeedEntries(maxLines = 15) {
522
- const events = feedStore.getLatest(maxLines);
523
- const lines = [];
524
- if (events.length === 0) {
525
- lines.push('No operations yet');
526
- }
527
- else {
528
- for (const event of events) {
529
- const time = formatTimestamp(event.ts);
530
- // Don't truncate here - truncation happens in updateOperationsPanel()
531
- const line = `${time} [${event.tag}] ${event.msg}`;
532
- lines.push(line);
533
- }
534
- }
535
- return lines;
536
- }
537
- // CRITICAL: Guard to prevent multiple runtime instances
538
- let runtimeStarted = false;
539
- // Section 2: Render queue system (prevents render storms)
540
- let renderQueued = false;
541
- let renderScheduled = false;
542
- /**
543
- * Section 2: Request render (batches multiple render requests)
544
- * If render already queued, do nothing.
545
- * Uses setImmediate to batch renders.
546
- */
547
- function requestRender() {
548
- if (renderQueued) {
549
- return; // Already queued, skip
550
- }
551
- renderQueued = true;
552
- if (!renderScheduled) {
553
- renderScheduled = true;
554
- setImmediate(() => {
555
- renderQueued = false;
556
- renderScheduled = false;
557
- if (screen) {
558
- // Step 1: Track render count (only log every 1000 renders for debugging)
559
- renderCount++;
560
- if (renderCount % 1000 === 0) {
561
- logger.debug(`render() call #${renderCount} (bootId=${BOOT_ID})`);
562
- }
563
- updateOperationsPanel();
564
- screen.render();
565
- }
566
- });
567
- }
568
- }
569
- /**
570
- * Start Phase 1 runtime
571
- */
572
- /**
573
- * Step 3: Hard guard to prevent double mounting
574
- */
575
- /**
576
- * Step 6: Export unmount function for external use
577
- */
578
- export function unmountUI() {
579
- if (uiInstance) {
580
- uiInstance.unmount();
581
- }
582
- }
583
- export async function startPhase1Runtime() {
584
- // Structural Fix B: Hard guard - if UI already mounted, hard stop
585
- if (uiInstance !== null) {
586
- // Already mounted - hard stop (don't emit to feed, just return)
587
- return;
588
- }
589
- // CRITICAL: Prevent multiple instances from starting
590
- if (runtimeStarted) {
591
- // Already started - hard stop
592
- return;
593
- }
594
- runtimeStarted = true;
595
- // Phase A: UI Layout Fingerprint - Only log in debug mode
596
- if (DEBUG_LAYOUT) {
597
- eventBus.emit({
598
- tag: 'SYS',
599
- msg: `UI_ROOT_ID=${UI_ROOT_ID} created (ROOT_RENDER_COUNT will increment)`,
600
- level: 'INFO'
601
- });
602
- }
603
- // Structural Fix A: Enable TUI mode (monkeypatches stdout/console, routes to eventBus)
604
- enableTuiMode();
605
- // CRITICAL: Set up raw input handling FIRST (before blessed can interfere)
606
- // This ensures we always receive input, even when blessed breaks on resize
607
- if (process.stdin.isTTY) {
608
- process.stdin.setRawMode(true);
609
- process.stdin.resume();
610
- process.stdin.setEncoding('utf8');
611
- }
612
- try {
613
- // CRITICAL: Hard singleton guard - screen can only be created ONCE
614
- if (screenCreated) {
615
- throw new Error(`Screen already created! screenCreationCount=${screenCreationCount}, uiInstanceId=${UI_INSTANCE_ID}`);
616
- }
617
- screenCreated = true;
618
- screenCreationCount++;
619
- // Debug: Log screen creation
620
- if (DEBUG_LAYOUT) {
621
- eventBus.emit({
622
- tag: 'SYS',
623
- msg: `Screen created #${screenCreationCount} (uiInstanceId=${UI_INSTANCE_ID})`,
624
- level: 'INFO'
625
- });
626
- }
627
- // CRITICAL: Block SIGWINCH (resize signal) BEFORE creating screen
628
- // This prevents blessed from ever detecting resize events
629
- // User will need to restart TUI after resizing terminal
630
- if (process.stdout.isTTY) {
631
- // Remove all existing SIGWINCH listeners
632
- process.removeAllListeners('SIGWINCH');
633
- // Add a no-op handler to block the signal
634
- process.on('SIGWINCH', () => {
635
- // Do nothing - block blessed from seeing this signal
636
- });
637
- }
638
- // Create screen
639
- screen = blessedLib.screen({
640
- smartCSR: false, // CRITICAL: Disable smartCSR - it can cause duplicate rendering on resize
641
- title: '4Runr OS',
642
- fullUnicode: true,
643
- // CRITICAL: Disable screen-level input handling to prevent double-typing
644
- sendFocus: false,
645
- warnings: false,
646
- // CRITICAL: Disable blessed's automatic resize handling to prevent duplication
647
- // We handle resize ourselves (or just ignore it)
648
- autoPadding: false,
649
- fastCSR: false,
650
- // CRITICAL: Disable dockBorders - can cause duplicate borders
651
- dockBorders: false,
652
- // CRITICAL: Disable resizeTimeout - prevents blessed from doing its own resize handling
653
- resizeTimeout: false,
654
- });
655
- // CRITICAL: Blessed's resize handling causes widget duplication
656
- // We can't easily prevent blessed from detecting resize (it listens to process.stdout directly)
657
- // So we just don't add any resize handler - let blessed handle it, but don't interfere
658
- // If resize causes issues, user needs to restart TUI
659
- // CRITICAL: Hide terminal cursor completely - we'll use only visual cursor character
660
- // This must be done persistently, as blessed may try to show it during renders
661
- const hideCursor = () => {
662
- if (screen.program && screen.program.hideCursor) {
663
- screen.program.hideCursor();
664
- }
665
- // Also use ANSI escape code as backup
666
- if (process.stdout.isTTY) {
667
- process.stdout.write('\x1b[?25l'); // Hide cursor
668
- }
669
- };
670
- // Hide cursor immediately
671
- hideCursor();
672
- // CRITICAL: Hook into render to keep cursor hidden after every render
673
- // Blessed may try to show cursor during render, so we force hide it after
674
- const originalRender = screen.render.bind(screen);
675
- screen.render = () => {
676
- const result = originalRender();
677
- // Force hide cursor after every render
678
- hideCursor();
679
- return result;
680
- };
681
- if (!screen) {
682
- throw new Error('Failed to create screen');
683
- }
684
- // CRITICAL: Remove ALL screen-level key handlers
685
- // They interfere with the textbox's input handling and cause double-typing
686
- // The textbox will handle ALL input directly
687
- // Only handle Ctrl+C at the process level (already done below with SIGINT)
688
- // Handle signals
689
- // Step 5: Store process listeners for cleanup
690
- const sigintHandler = () => cleanupAndExit(0);
691
- const sigtermHandler = () => cleanupAndExit(0);
692
- const uncaughtExceptionHandler = (error) => {
693
- // Structural Fix A: Use logger, not console
694
- logger.error(`Uncaught exception: ${error.message}`);
695
- if (error.stack) {
696
- logger.error(`Stack: ${error.stack}`);
697
- }
698
- cleanupAndExit(1);
699
- };
700
- process.on('SIGINT', sigintHandler);
701
- process.on('SIGTERM', sigtermHandler);
702
- process.on('uncaughtException', uncaughtExceptionHandler);
703
- // Compute layout
704
- const width = screen.width;
705
- const height = screen.height;
706
- const layoutResult = computePhase1Layout(width, height);
707
- if (!layoutResult.ok || !layoutResult.layout) {
708
- // Terminal too small - show error
709
- const errorBox = blessedLib.box({
710
- top: Math.floor((height - 5) / 2),
711
- left: Math.floor((width - 60) / 2),
712
- width: 60,
713
- height: 5,
714
- border: {
715
- type: 'line',
716
- },
717
- tags: true,
718
- content: `\n ${layoutResult.errorMessage || 'Terminal too small'}\n\n Press q or Ctrl+C to exit`,
719
- style: {
720
- border: {
721
- fg: 'red',
722
- },
723
- fg: 'red',
724
- },
725
- });
726
- screen.append(errorBox);
727
- screen.render();
728
- return;
729
- }
730
- const layout = layoutResult.layout;
731
- // CRITICAL: Create panels ONLY ONCE - prevent duplication on resize
732
- if (!panelsCreated) {
733
- panelsCreated = true;
734
- // Create all panels (read-only)
735
- if (!screen)
736
- throw new Error('Screen not initialized');
737
- // Clear panels array first (safety)
738
- panels = [];
739
- panels.push(createPanel(screen, layout.posture, 'POSTURE'));
740
- // RESOURCES panel (Phase 4: real metrics)
741
- const resourcesPanel = createPanel(screen, layout.resources, 'RESOURCES');
742
- panels.push(resourcesPanel);
743
- panels.push(createPanel(screen, layout.assets, 'ASSETS'));
744
- // OPERATIONS panel with feed content (Phase 3: EventBus)
745
- const operationsPanel = createPanel(screen, layout.operations, 'OPERATIONS', formatFeedEntries);
746
- panels.push(operationsPanel);
747
- panels.push(createPanel(screen, layout.network, 'NETWORK'));
748
- panels.push(createPanel(screen, layout.capabilities, 'CAPABILITIES'));
749
- }
750
- // Section 2: Wire eventBus -> feedStore -> OperationsPanel
751
- // Subscribe once during app startup
752
- eventBus.subscribe((e) => {
753
- feedStore.push(e);
754
- requestRender();
755
- });
756
- // CRITICAL: Create status strip and command line ONLY ONCE
757
- if (!statusStrip) {
758
- // Create status strip (left side of bottom bar)
759
- // Make command line bigger - reduce status to 20%
760
- const statusWidth = Math.floor(layout.commandLine.width * 0.2); // 20% for status (was 60%)
761
- statusStrip = blessedLib.box({
762
- top: layout.commandLine.top,
763
- left: layout.commandLine.left,
764
- width: statusWidth,
765
- height: layout.commandLine.height,
766
- border: {
767
- type: 'line',
768
- },
769
- tags: true,
770
- content: ' MODE: OFFLINE | CONNECT: NO | POSTURE: UNKNOWN ',
771
- style: {
772
- border: {
773
- fg: 'grey',
774
- },
775
- fg: 'grey',
776
- },
777
- clickable: false,
778
- keys: false,
779
- mouse: false,
780
- input: false,
781
- });
782
- statusStrip.setLabel(' {grey-fg}STATUS{/}');
783
- }
784
- // CRITICAL: Create command line ONLY ONCE
785
- // Track input value manually (must be outside if block for scope)
786
- let commandInputValue = PROMPT;
787
- if (!commandLine) {
788
- // Create command line (right side of bottom bar)
789
- // CRITICAL: Use a plain box instead of textbox to avoid blessed's input duplication bugs
790
- const statusWidth = Math.floor(layout.commandLine.width * 0.2);
791
- const commandWidth = layout.commandLine.width - statusWidth - 1; // Remaining width
792
- commandLine = blessedLib.box({
793
- top: layout.commandLine.top,
794
- left: layout.commandLine.left + statusWidth + 1,
795
- width: commandWidth,
796
- height: layout.commandLine.height,
797
- border: {
798
- type: 'line',
799
- },
800
- tags: true,
801
- content: PROMPT,
802
- style: {
803
- border: {
804
- fg: 'cyan',
805
- },
806
- fg: 'white',
807
- focus: {
808
- border: {
809
- fg: 'green',
810
- },
811
- fg: 'white',
812
- },
813
- },
814
- // Make it focusable but NOT use blessed's input handling
815
- focusable: true,
816
- keyable: true,
817
- input: false, // CRITICAL: Disable blessed's input handling
818
- keys: false, // We'll handle keys manually
819
- mouse: false,
820
- // CRITICAL: Position cursor at 0,0 relative to the box (inside the border)
821
- scrollable: false,
822
- alwaysScroll: false,
823
- });
824
- if (!commandLine)
825
- throw new Error('Command line not initialized');
826
- commandLine.setLabel(' {cyan-fg}COMMAND LINE{/}');
827
- }
828
- // Manual input handling - bypass blessed textbox completely
829
- let isProcessingKey = false;
830
- let cursorPosition = PROMPT.length; // Track cursor position (after prompt)
831
- let scrollOffset = 0; // Horizontal scroll offset for long input
832
- // Make updateCommandLineDisplay a variable so we can update it on resize
833
- // Add horizontal scrolling for long input
834
- let updateCommandLineDisplay = () => {
835
- if (commandLine && screen) {
836
- // Calculate available width (account for border and padding)
837
- const availableWidth = commandLine.width - 4; // Left/right border + padding
838
- // Calculate cursor position in the full string
839
- const fullText = commandInputValue;
840
- const cursorIndex = cursorPosition;
841
- // If cursor is beyond visible area, adjust scroll
842
- if (cursorIndex - scrollOffset >= availableWidth - 1) {
843
- scrollOffset = cursorIndex - availableWidth + 2; // Keep cursor visible
844
- }
845
- if (cursorIndex < scrollOffset) {
846
- scrollOffset = Math.max(0, cursorIndex - 5); // Show some context before cursor
847
- }
848
- // Extract visible portion
849
- const visibleStart = scrollOffset;
850
- const visibleEnd = Math.min(fullText.length, visibleStart + availableWidth - 1);
851
- let visibleText = fullText.substring(visibleStart, visibleEnd);
852
- // Insert cursor at correct position
853
- const cursorPosInVisible = cursorIndex - visibleStart;
854
- const beforeCursor = visibleText.substring(0, cursorPosInVisible);
855
- const afterCursor = visibleText.substring(cursorPosInVisible);
856
- const cursorChar = '{inverse} {/}'; // Inverted space (block cursor)
857
- const displayValue = beforeCursor + cursorChar + afterCursor;
858
- commandLine.setContent(displayValue);
859
- // Render will automatically hide cursor via our hook above
860
- screen.render();
861
- }
862
- };
863
- // CRITICAL: Define handleSubmit BEFORE stdin handler so it can be called
864
- // Define submit handler as a function so we can call it directly from stdin handler
865
- const handleSubmit = async (value) => {
866
- // Reset history index
867
- historyIndex = -1;
868
- // 0) Debug: Emit raw input if TUI_DEBUG=1
869
- const isDebug = process.env.TUI_DEBUG === '1';
870
- if (isDebug) {
871
- const rawCapped = value.length > 50 ? value.substring(0, 50) + '...' : value;
872
- eventBus.emit({
873
- id: `dbg-raw-${Date.now()}`,
874
- ts: Date.now(),
875
- tag: 'DBG',
876
- level: 'INFO',
877
- msg: `raw="${rawCapped}"`,
878
- });
879
- }
880
- // Step 1: Debug trace - submit pipeline
881
- // Extract command part (remove prompt if present)
882
- const raw = value.startsWith(PROMPT) ? value.slice(PROMPT.length) : value;
883
- const commandPart = raw.trim();
884
- if (isDebug) {
885
- eventBus.emit({
886
- id: `dbg-submit-${Date.now()}`,
887
- ts: Date.now(),
888
- tag: 'DBG',
889
- level: 'INFO',
890
- msg: `submit raw="${raw}" norm="${commandPart}" len=${commandPart.length}`,
891
- });
892
- }
893
- // Step 2: Normalization is done in execute() - just pass raw trimmed input
894
- // Do NOT normalize here (single place only)
895
- if (commandPart.length === 0) {
896
- // NOOP - no events, just return
897
- reassertFocus();
898
- return;
899
- }
900
- // CRITICAL: Parse command BEFORE clearing input (need to know if it's valid)
901
- // Section 3: Parse command using UI-agnostic engine (pass raw trimmed input)
902
- const parsed = parse(commandPart);
903
- // CRITICAL FIX #1: Emit [CMD] event IMMEDIATELY (before any async work)
904
- // This makes the command visible instantly in the Operations feed
905
- const cmdEvent = {
906
- id: `cmd-immediate-${Date.now()}`,
907
- ts: Date.now(),
908
- tag: 'CMD',
909
- level: 'INFO',
910
- msg: parsed.name || commandPart,
911
- };
912
- eventBus.emit(cmdEvent);
913
- updateOperationsPanel();
914
- screen?.render(); // Show [CMD] event immediately
915
- // CRITICAL FIX #2: Clear input IMMEDIATELY (after emitting [CMD], before async work)
916
- // This ensures the command line clears instantly when Enter is pressed
917
- currentCommandValue = PROMPT;
918
- commandInputValue = PROMPT;
919
- cursorPosition = PROMPT.length;
920
- scrollOffset = 0;
921
- // Force update command line display IMMEDIATELY and SYNCHRONOUSLY
922
- if (commandLine && screen) {
923
- const cursorChar = '{inverse} {/}';
924
- commandLine.setContent(PROMPT + cursorChar);
925
- updateCommandLineDisplay();
926
- screen.render(); // Render immediately to show cleared input
927
- }
928
- // Add command to history (S0.4)
929
- if (commandPart) {
930
- commandHistory.push(commandPart);
931
- // Keep last 100 commands
932
- if (commandHistory.length > 100) {
933
- commandHistory.shift();
934
- }
935
- }
936
- // Empty command - do nothing, keep focus
937
- if (!parsed.name) {
938
- reassertFocus();
939
- return;
940
- }
941
- // CRITICAL FIX #2: [CMD] event already emitted above for immediate feedback
942
- // Commands may emit additional [CMD] events, but we filter duplicates in the feed
943
- // Section 3: Execute command using UI-agnostic engine (now async)
944
- const ctx = {}; // Lightweight context (empty for now)
945
- // Note: connect command now uses LOADING state with attemptId
946
- // The command handler returns LOADING state immediately in uiStateUpdate
947
- // No need to set CONNECTING state here - command handler does it
948
- // CRITICAL: For connect command, set LOADING state IMMEDIATELY
949
- // The command handler will emit all events (including probes)
950
- // We just set the UI state here for instant feedback
951
- if (parsed.name === 'connect') {
952
- const { resolveGatewayUrl } = await import('../config/gateway.js');
953
- const target = resolveGatewayUrl();
954
- // Set LOADING state immediately (command handler will emit events)
955
- currentUiState = {
956
- ...currentUiState,
957
- network: loading('Connecting to Gateway...', {
958
- attemptId: Date.now(),
959
- target,
960
- timeout: 5,
961
- }),
962
- statusStrip: available({
963
- left: 'NET: CONNECTING...',
964
- right: 'GATEWAY: CONNECTING',
965
- }),
966
- };
967
- // Update panels immediately
968
- updateNetworkPanel();
969
- updateStatusStrip();
970
- screen?.render();
971
- }
972
- // CRITICAL: Start command execution but don't await yet
973
- // This allows us to emit immediate events before async work completes
974
- const executePromise = execute(parsed, ctx);
975
- // For commands that return events immediately (like connect), emit them right away
976
- // We'll handle the full result after await
977
- let result;
978
- try {
979
- result = await executePromise;
980
- }
981
- catch (error) {
982
- // Command execution failed - emit error event
983
- const errorMsg = error?.message || 'Command execution failed';
984
- eventBus.emit({
985
- id: `err-exec-${Date.now()}`,
986
- ts: Date.now(),
987
- tag: 'ERR',
988
- level: 'ERROR',
989
- msg: errorMsg,
990
- });
991
- // Update operations panel to show error
992
- updateOperationsPanel();
993
- screen?.render();
994
- return; // Exit early on error
995
- }
996
- // Section 3: Append CommandResult.events to Operations Feed
997
- // Emit ALL events from command handler (including probes for connect)
998
- for (const event of result.events) {
999
- eventBus.emit(event);
1000
- }
1001
- // Update operations panel immediately after emitting all events
1002
- updateOperationsPanel();
1003
- screen?.render();
1004
- // Section 6: Handle UiState update if provided
1005
- // A) This updates UI immediately (within 50ms) even before async work completes
1006
- // CRITICAL: Always apply uiStateUpdate if provided (overrides any temporary CONNECTING state)
1007
- if (result.uiStateUpdate) {
1008
- // CRITICAL: Apply state update - this will override CONNECTING with UNAVAILABLE on failure
1009
- try {
1010
- const newState = result.uiStateUpdate(currentUiState);
1011
- currentUiState = newState;
1012
- assertUiState(currentUiState);
1013
- }
1014
- catch (stateError) {
1015
- // State validation failed - log but don't crash
1016
- eventBus.emit({
1017
- id: `err-state-${Date.now()}`,
1018
- ts: Date.now(),
1019
- tag: 'ERR',
1020
- level: 'ERROR',
1021
- msg: `State validation error: ${stateError.message}`,
1022
- });
1023
- // Don't update state if validation fails
1024
- }
1025
- // CRITICAL: Force update Network panel FIRST (before other panels)
1026
- // This ensures CONNECTING state is immediately replaced with UNAVAILABLE on error
1027
- updateNetworkPanel();
1028
- updateStatusStrip();
1029
- // Update other panels
1030
- updatePosturePanel();
1031
- updateResourcesPanel();
1032
- updateAssetsPanel();
1033
- updateCapabilitiesPanel();
1034
- // CRITICAL: Force render immediately to show the updated Network panel
1035
- screen?.render();
1036
- // Double-check: Verify Network panel was updated (debug only)
1037
- if (process.env.TUI_DEBUG === '1') {
1038
- const networkValue = currentUiState.network;
1039
- const isConnecting = isLoading(networkValue);
1040
- if (isConnecting && parsed.name === 'connect') {
1041
- // This shouldn't happen after error - log it (LOADING should be cleared by command)
1042
- eventBus.emit({
1043
- id: `dbg-state-${Date.now()}`,
1044
- ts: Date.now(),
1045
- tag: 'DBG',
1046
- level: 'INFO',
1047
- msg: `WARN: Network still LOADING after uiStateUpdate`,
1048
- });
1049
- }
1050
- }
1051
- }
1052
- else {
1053
- // If no uiStateUpdate provided, still update panels to reflect current state
1054
- // This ensures panels don't get stuck in intermediate states
1055
- updateNetworkPanel();
1056
- updateStatusStrip();
1057
- screen?.render();
1058
- }
1059
- // CRITICAL: Always update operations panel after command execution
1060
- // This ensures all events are visible even if no state update occurred
1061
- updateOperationsPanel();
1062
- screen?.render();
1063
- // Section 3: Handle CommandResult.action explicitly
1064
- if (result.action === UiAction.EXIT) {
1065
- // Step 6: Call unmount before exit
1066
- if (uiInstance) {
1067
- uiInstance.unmount();
1068
- }
1069
- // Exit the application
1070
- cleanupAndExit(0);
1071
- return;
1072
- }
1073
- if (result.action === UiAction.CLEAR_FEED) {
1074
- // Clear Operations Feed only (panels remain intact)
1075
- feedStore.clear();
1076
- // No output event - clear is silent
1077
- }
1078
- if (result.action === UiAction.HOME) {
1079
- // S0.3: State machine - handle state transitions
1080
- uiModeState = 'HUB';
1081
- }
1082
- // Input already cleared above (before async work)
1083
- // Just ensure focus and render
1084
- reassertFocus();
1085
- // Operations panel already updated above after command execution
1086
- // Just ensure final render
1087
- screen?.render();
1088
- };
1089
- // Step 5: Store cleanup functions for collectors/timers (must be defined before stdin listener)
1090
- const cleanupFunctions = [];
1091
- // Step 5: Store process listener cleanup
1092
- cleanupFunctions.push(() => {
1093
- process.removeListener('SIGINT', sigintHandler);
1094
- process.removeListener('SIGTERM', sigtermHandler);
1095
- process.removeListener('uncaughtException', uncaughtExceptionHandler);
1096
- });
1097
- // CRITICAL: Use RAW stdin handler (blessed breaks on resize)
1098
- // Read directly from process.stdin - bypasses blessed completely
1099
- // NOTE: This handler is bound ONCE and never re-bound, so it always uses current variables
1100
- // Step 5: Store stdin listener cleanup
1101
- let stdinListener = null;
1102
- stdinListener = (data) => {
1103
- // Don't check commandLine here - it might be recreated, but we'll call handleSubmit directly
1104
- if (!screen)
1105
- return;
1106
- const input = data.toString();
1107
- // Handle Ctrl+C (ASCII 3) - ALWAYS works
1108
- if (input === '\x03') {
1109
- cleanupAndExit(0);
1110
- return;
1111
- }
1112
- // Handle Enter (ASCII 13 or 10)
1113
- if (input === '\r' || input === '\n') {
1114
- // Extract command part (without prompt)
1115
- const commandPart = commandInputValue.startsWith(PROMPT)
1116
- ? commandInputValue.slice(PROMPT.length).trim()
1117
- : commandInputValue.trim();
1118
- if (commandPart) {
1119
- // Call handleSubmit with the full value (it will extract the command part internally)
1120
- // This ensures it works even after resize when widget is recreated
1121
- // CRITICAL: Don't clear commandInputValue here - handleSubmit does it
1122
- handleSubmit(commandInputValue).catch((err) => {
1123
- // Emit error to operations feed (visible to user)
1124
- eventBus.emit({
1125
- id: `err-handler-${Date.now()}`,
1126
- ts: Date.now(),
1127
- tag: 'ERR',
1128
- level: 'ERROR',
1129
- msg: `Command handler error: ${err?.message || String(err)}`,
1130
- });
1131
- updateOperationsPanel();
1132
- screen?.render();
1133
- });
1134
- }
1135
- else {
1136
- // Empty command - just reset
1137
- commandInputValue = PROMPT;
1138
- cursorPosition = PROMPT.length;
1139
- scrollOffset = 0;
1140
- updateCommandLineDisplay();
1141
- }
1142
- return;
1143
- }
1144
- // Handle Backspace (ASCII 127 or 8)
1145
- if (input === '\x7f' || input === '\x08') {
1146
- if (cursorPosition > PROMPT.length) {
1147
- // Delete character before cursor
1148
- const before = commandInputValue.substring(0, cursorPosition - 1);
1149
- const after = commandInputValue.substring(cursorPosition);
1150
- commandInputValue = before + after;
1151
- cursorPosition--;
1152
- updateCommandLineDisplay();
1153
- }
1154
- return;
1155
- }
1156
- // Handle Delete (not standard in raw mode, but handle if received)
1157
- if (input === '\x1b[3~') {
1158
- if (cursorPosition < commandInputValue.length) {
1159
- const before = commandInputValue.substring(0, cursorPosition);
1160
- const after = commandInputValue.substring(cursorPosition + 1);
1161
- commandInputValue = before + after;
1162
- updateCommandLineDisplay();
1163
- }
1164
- return;
1165
- }
1166
- // Handle Left Arrow (cursor movement)
1167
- if (input === '\x1b[D' || input === '\x1b[1;2D') {
1168
- if (cursorPosition > PROMPT.length) {
1169
- cursorPosition--;
1170
- updateCommandLineDisplay();
1171
- }
1172
- return;
1173
- }
1174
- // Handle Right Arrow (cursor movement)
1175
- if (input === '\x1b[C' || input === '\x1b[1;2C') {
1176
- if (cursorPosition < commandInputValue.length) {
1177
- cursorPosition++;
1178
- updateCommandLineDisplay();
1179
- }
1180
- return;
1181
- }
1182
- // Handle Up Arrow (history navigation)
1183
- if (input === '\x1b[A') {
1184
- if (commandHistory.length === 0)
1185
- return;
1186
- if (historyIndex === -1) {
1187
- historyIndex = commandHistory.length - 1;
1188
- }
1189
- else if (historyIndex > 0) {
1190
- historyIndex--;
1191
- }
1192
- if (historyIndex >= 0 && historyIndex < commandHistory.length) {
1193
- const historyCmd = commandHistory[historyIndex];
1194
- const fullValue = `${PROMPT}${historyCmd}`;
1195
- commandInputValue = fullValue;
1196
- cursorPosition = fullValue.length;
1197
- scrollOffset = 0;
1198
- updateCommandLineDisplay();
1199
- }
1200
- return;
1201
- }
1202
- // Handle Down Arrow (history navigation)
1203
- if (input === '\x1b[B') {
1204
- if (historyIndex === -1)
1205
- return;
1206
- if (historyIndex < commandHistory.length - 1) {
1207
- historyIndex++;
1208
- const historyCmd = commandHistory[historyIndex];
1209
- const fullValue = `${PROMPT}${historyCmd}`;
1210
- commandInputValue = fullValue;
1211
- cursorPosition = fullValue.length;
1212
- scrollOffset = 0;
1213
- updateCommandLineDisplay();
1214
- }
1215
- else {
1216
- historyIndex = -1;
1217
- commandInputValue = PROMPT;
1218
- cursorPosition = PROMPT.length;
1219
- scrollOffset = 0;
1220
- updateCommandLineDisplay();
1221
- }
1222
- return;
1223
- }
1224
- // Handle Home (move to start of input, after prompt)
1225
- if (input === '\x1b[H' || input === '\x1b[1~') {
1226
- cursorPosition = PROMPT.length;
1227
- updateCommandLineDisplay();
1228
- return;
1229
- }
1230
- // Handle End (move to end of input)
1231
- if (input === '\x1b[F' || input === '\x1b[4~') {
1232
- cursorPosition = commandInputValue.length;
1233
- updateCommandLineDisplay();
1234
- return;
1235
- }
1236
- // Handle Ctrl+V (paste) - ASCII 22
1237
- if (input === '\x16') {
1238
- // Paste from clipboard (if available)
1239
- // Note: In raw mode, we can't easily access clipboard
1240
- // But we can handle multi-character paste sequences
1241
- // For now, we'll handle it in the printable characters section
1242
- // by detecting rapid character sequences
1243
- return; // Will be handled by detecting rapid input
1244
- }
1245
- // Handle Escape (ASCII 27) - clear input
1246
- if (input === '\x1b') {
1247
- commandInputValue = PROMPT;
1248
- cursorPosition = PROMPT.length;
1249
- scrollOffset = 0;
1250
- updateCommandLineDisplay();
1251
- return;
1252
- }
1253
- // Handle printable characters (including paste sequences)
1254
- // Check for multi-character input (paste)
1255
- if (input.length > 1) {
1256
- // This is likely a paste - insert all characters at cursor
1257
- for (let i = 0; i < input.length; i++) {
1258
- const ch = input[i];
1259
- if (ch >= ' ' && ch <= '~') {
1260
- const before = commandInputValue.substring(0, cursorPosition);
1261
- const after = commandInputValue.substring(cursorPosition);
1262
- commandInputValue = before + ch + after;
1263
- cursorPosition++;
1264
- }
1265
- }
1266
- updateCommandLineDisplay();
1267
- return;
1268
- }
1269
- // Handle single printable character
1270
- if (input.length === 1 && input >= ' ' && input <= '~') {
1271
- // Insert at cursor position
1272
- const before = commandInputValue.substring(0, cursorPosition);
1273
- const after = commandInputValue.substring(cursorPosition);
1274
- commandInputValue = before + input + after;
1275
- cursorPosition++;
1276
- updateCommandLineDisplay();
1277
- }
1278
- };
1279
- // Step 5: Register stdin listener once
1280
- process.stdin.on('data', stdinListener);
1281
- cleanupFunctions.push(() => {
1282
- if (stdinListener) {
1283
- process.stdin.removeListener('data', stdinListener);
1284
- }
1285
- });
1286
- // Prompt protection is now handled in the manual keypress handler above
1287
- // No need for separate protection since we control all input
1288
- // Handle command submission (Phase 3: EventBus)
1289
- // CRITICAL: Bind handlers exactly ONCE - never in render loops or intervals
1290
- if (!commandLine)
1291
- throw new Error('Command line not initialized');
1292
- // Structural Fix 3: Hard guard - bind listeners exactly once
1293
- if (inputHandlersBound) {
1294
- // Already bound - hard stop (don't log, don't bind again)
1295
- return;
1296
- }
1297
- // Mark as bound BEFORE binding (prevents race conditions)
1298
- inputHandlersBound = true;
1299
- // Removed debug log - not needed in production
1300
- // Attach submit handler to initial commandLine (handleSubmit is defined above)
1301
- commandLine.on('submit', handleSubmit);
1302
- // S0.4: Command history navigation (Up/Down)
1303
- // These are bound once at startup, never in intervals
1304
- // Note: Up/Down are handled in the manual keypress handler, but we keep these for compatibility
1305
- screen.key('up', () => {
1306
- if (screen?.focused !== commandLine)
1307
- return;
1308
- if (commandHistory.length === 0)
1309
- return;
1310
- if (historyIndex === -1) {
1311
- // Start browsing from most recent
1312
- historyIndex = commandHistory.length - 1;
1313
- }
1314
- else if (historyIndex > 0) {
1315
- historyIndex--;
1316
- }
1317
- if (historyIndex >= 0 && historyIndex < commandHistory.length) {
1318
- const historyCmd = commandHistory[historyIndex];
1319
- const fullValue = `${PROMPT}${historyCmd}`;
1320
- commandInputValue = fullValue;
1321
- currentCommandValue = fullValue;
1322
- cursorPosition = fullValue.length;
1323
- scrollOffset = 0;
1324
- updateCommandLineDisplay();
1325
- }
1326
- });
1327
- commandLine.key('down', () => {
1328
- if (historyIndex === -1)
1329
- return;
1330
- if (historyIndex < commandHistory.length - 1) {
1331
- historyIndex++;
1332
- const historyCmd = commandHistory[historyIndex];
1333
- const fullValue = `${PROMPT}${historyCmd}`;
1334
- commandInputValue = fullValue;
1335
- currentCommandValue = fullValue;
1336
- cursorPosition = fullValue.length;
1337
- scrollOffset = 0;
1338
- updateCommandLineDisplay();
1339
- }
1340
- else {
1341
- // Reached end of history - clear input (but keep prompt)
1342
- historyIndex = -1;
1343
- commandInputValue = PROMPT;
1344
- currentCommandValue = PROMPT;
1345
- cursorPosition = PROMPT.length;
1346
- scrollOffset = 0;
1347
- updateCommandLineDisplay();
1348
- }
1349
- });
1350
- // S0.4: Esc clears input (handled in manual keypress handler above)
1351
- // Keep this for compatibility but it should be handled by screen-level handler
1352
- // Mark handlers as bound (all handlers above are now attached)
1353
- // This prevents any accidental duplicate binding
1354
- // Track command value changes (only reset history, don't interfere with input)
1355
- // Note: We don't use 'keypress' here as it can cause double-typing issues
1356
- // The textbox handles input automatically, we just need to track when user types
1357
- // We'll reset history index on submit instead
1358
- // CRITICAL: NO RESIZE HANDLING
1359
- // We've blocked SIGWINCH above, so blessed will never detect resize
1360
- // User must restart TUI after resizing terminal
1361
- // This is the ONLY way to prevent blessed's duplication bugs
1362
- // Prevent Tab from moving focus to panels (single handler only, bound once)
1363
- // Note: Tab handler is already bound above with other keys, so we don't need this
1364
- // But keeping it as a safety guard (only if somehow handlers weren't bound)
1365
- if (commandLine && !inputHandlersBound) {
1366
- logger.warn('Tab handler being bound separately - this should not happen');
1367
- commandLine.key('tab', () => {
1368
- // Tab does nothing - stay in command line
1369
- commandLine?.focus();
1370
- screen?.render();
1371
- });
1372
- }
1373
- // CRITICAL: Append widgets ONLY ONCE - prevent blessed resize from causing duplication
1374
- // Guard ensures widgets are never appended multiple times, even if resize triggers re-execution
1375
- if (!widgetsAppended) {
1376
- widgetsAppended = true;
1377
- // CRITICAL: Append command line FIRST so it gets focus by default
1378
- if (!screen)
1379
- throw new Error('Screen not initialized');
1380
- if (commandLine) {
1381
- screen.append(commandLine);
1382
- // ONLY the command line should be focusable and receive input
1383
- commandLine.focusable = true;
1384
- commandLine.keyable = true;
1385
- commandLine.input = true;
1386
- commandLine.keys = true;
1387
- // Focus immediately after appending
1388
- commandLine.focus();
1389
- screen.focused = commandLine;
1390
- }
1391
- // Append status strip (not focusable)
1392
- if (statusStrip) {
1393
- screen.append(statusStrip);
1394
- statusStrip.focusable = false;
1395
- statusStrip.keyable = false;
1396
- statusStrip.input = false;
1397
- }
1398
- // Append all panels AFTER command line (not focusable)
1399
- panels.forEach(panel => {
1400
- if (screen) {
1401
- screen.append(panel);
1402
- // CRITICAL: Ensure panels CANNOT receive focus or input
1403
- panel.focusable = false;
1404
- panel.keyable = false;
1405
- panel.clickable = false;
1406
- panel.input = false;
1407
- }
1408
- });
1409
- }
1410
- // S0.1 & S0.4: Force focus to command line again after all widgets appended
1411
- if (commandLine && screen) {
1412
- commandLine.focus();
1413
- screen.focused = commandLine;
1414
- }
1415
- // S0.4: Prevent focus from entering panels - hard lock
1416
- // Set screen to only allow focus on command line
1417
- const focusScreen = screen;
1418
- if (focusScreen) {
1419
- focusScreen.on('focus', (widget) => {
1420
- // If focus tries to go to a panel, redirect to command line
1421
- if (widget && typeof widget !== 'string' && panels.includes(widget)) {
1422
- reassertFocus();
1423
- }
1424
- });
1425
- // S0.4: Mouse clicks do nothing except refocus input
1426
- focusScreen.on('click', () => {
1427
- reassertFocus();
1428
- });
1429
- }
1430
- // Section 2: Startup events (must emit on boot)
1431
- // Emit once the screen is ready
1432
- // Removed verbose "UI booted" message
1433
- // STEP 5: Posture configuration is visible in POSTURE panel - no need to log to Operations
1434
- // Runtime fingerprint (for debugging network topology)
1435
- const os = await import('os');
1436
- const platform = process.platform;
1437
- const hostname = os.hostname();
1438
- const envHostname = process.env.HOSTNAME || 'unknown';
1439
- const cwd = process.cwd();
1440
- const hasSshConnection = !!process.env.SSH_CONNECTION;
1441
- // Get non-internal IPs
1442
- const interfaces = os.networkInterfaces();
1443
- const ips = [];
1444
- for (const ifaceName in interfaces) {
1445
- const iface = interfaces[ifaceName];
1446
- if (iface) {
1447
- for (const addr of iface) {
1448
- if (!addr.internal && addr.family === 'IPv4') {
1449
- ips.push(addr.address);
1450
- }
1451
- }
1452
- }
1453
- }
1454
- // Removed verbose runtime info (only log in debug mode if needed)
1455
- // Subscribe to connection store updates and update Network panel
1456
- const { gatewayConnectionStore } = await import('../state/gatewayConnectionStore.js');
1457
- const { capabilitiesStore } = await import('../state/capabilitiesStore.js');
1458
- gatewayConnectionStore.subscribe((connectionState) => {
1459
- // Update Network panel whenever store changes
1460
- updateNetworkPanel();
1461
- // Also update Capabilities panel (depends on connection)
1462
- updateCapabilitiesPanel();
1463
- screen?.render();
1464
- // Only log significant state changes (connected/disconnected), not every update
1465
- if (connectionState.status === 'connected' || connectionState.status === 'disconnected') {
1466
- eventBus.emit({
1467
- tag: 'SYS',
1468
- msg: `Network: ${connectionState.status}`,
1469
- level: 'INFO'
1470
- });
1471
- }
1472
- });
1473
- // Subscribe to capabilities store updates
1474
- capabilitiesStore.subscribe(() => {
1475
- updateCapabilitiesPanel();
1476
- screen?.render();
1477
- });
1478
- // Removed verbose "Type help" message
1479
- // Update operations panel with feed content
1480
- updateOperationsPanel();
1481
- // Section 4: Initial UiState build
1482
- // CRITICAL: Use initializedDefaultState (has correct posture state) not defaultUiState
1483
- currentUiState = initializedDefaultState;
1484
- // Section 4: Validate defaultUiState contract
1485
- assertUiState(currentUiState);
1486
- // Update all panels with default state (no blank panels)
1487
- // FIXED: Update panels immediately with default state to prevent showing stale data
1488
- updatePosturePanel();
1489
- updateResourcesPanel();
1490
- updateAssetsPanel();
1491
- updateNetworkPanel();
1492
- updateCapabilitiesPanel();
1493
- updateStatusStrip();
1494
- // Part A: Initialize resize dimensions (prevents false no-op detection on first resize)
1495
- if (screen) {
1496
- lastResizeWidth = screen.width;
1497
- lastResizeHeight = screen.height;
1498
- }
1499
- // Initial render with default state
1500
- renderCount++;
1501
- if (DEBUG_LAYOUT) {
1502
- eventBus.emit({
1503
- tag: 'SYS',
1504
- msg: `ROOT_RENDER_COUNT=${renderCount} (UI_ROOT_ID=${UI_ROOT_ID})`,
1505
- level: 'INFO'
1506
- });
1507
- }
1508
- // Structural Fix 4: Render FIRST with default state, then do async init
1509
- // Initial render (focus is already set above)
1510
- screen?.render();
1511
- // Structural Fix A: Disable boot phase AFTER first render
1512
- // This enables strict stdout blocking now that blessed is initialized
1513
- disableBootPhase();
1514
- // Reassert focus after first render to ensure it sticks
1515
- reassertFocus();
1516
- // Structural Fix 4: Move async init AFTER mount (don't block render)
1517
- // Kick off async state building in background - it will update UI when ready
1518
- buildUiState(currentUiState).then((newState) => {
1519
- // Section 4: Validate state contract
1520
- assertUiState(newState);
1521
- lastResourcesAvailable = isAvailable(newState.resources);
1522
- currentUiState = newState;
1523
- // Update all panels with real state
1524
- updatePosturePanel();
1525
- updateResourcesPanel();
1526
- updateAssetsPanel();
1527
- updateNetworkPanel();
1528
- updateCapabilitiesPanel();
1529
- updateStatusStrip();
1530
- screen?.render();
1531
- }).catch((error) => {
1532
- // If state building fails, keep default state (all UNAVAILABLE)
1533
- // Error is already logged if DEBUG_NO_TUI=1
1534
- // Panels already show UNAVAILABLE with reason, so UI is still meaningful
1535
- logger.error(`State building failed: ${error instanceof Error ? error.message : String(error)}`);
1536
- });
1537
- // Render loop to update operations feed and resources (every 1500ms)
1538
- const renderInterval = setInterval(async () => {
1539
- // Update operations feed
1540
- updateOperationsPanel();
1541
- // Section 4: Update all panels with latest UiState
1542
- // CRITICAL: Pass currentUiState to preserve command-updated values
1543
- try {
1544
- const newState = await buildUiState(currentUiState);
1545
- // Section 4: Validate state contract
1546
- assertUiState(newState);
1547
- // Check for state transition (AVAILABLE → UNAVAILABLE)
1548
- const wasAvailable = lastResourcesAvailable;
1549
- const isNowAvailable = isAvailable(newState.resources);
1550
- if (wasAvailable && !isNowAvailable && isUnavailable(newState.resources)) {
1551
- // Transition to UNAVAILABLE - emit event
1552
- eventBus.emitTag('SYS', `RESOURCES UNAVAILABLE: ${newState.resources.reason || 'Unknown reason'}`, 'WARN');
1553
- }
1554
- lastResourcesAvailable = isNowAvailable;
1555
- currentUiState = newState;
1556
- // Section 4: Update all panels (no blank panels)
1557
- updatePosturePanel();
1558
- updateResourcesPanel();
1559
- updateAssetsPanel();
1560
- updateNetworkPanel();
1561
- updateCapabilitiesPanel();
1562
- updateStatusStrip();
1563
- }
1564
- catch (error) {
1565
- // If state building fails, don't crash - just skip this update
1566
- // Panels already show UNAVAILABLE with reason, so UI is still meaningful
1567
- // Error is already logged if DEBUG_NO_TUI=1
1568
- }
1569
- // Reassert focus periodically to keep cursor in command line
1570
- // But only if user isn't actively typing (check if we're processing a key)
1571
- if (!isProcessingKey) {
1572
- reassertFocus();
1573
- }
1574
- screen?.render();
1575
- }, 1500);
1576
- // Step 5: Store interval for cleanup
1577
- screen.renderInterval = renderInterval;
1578
- cleanupFunctions.push(() => {
1579
- clearInterval(renderInterval);
1580
- });
1581
- // Structural Fix B: Store unmount function in global singleton
1582
- const unmountFn = () => {
1583
- logger.debug(`Unmounting UI (bootId=${BOOT_ID}, renderCount=${renderCount})`);
1584
- isCleaningUp = true;
1585
- // Step 5: Stop all collectors/timers
1586
- cleanupFunctions.forEach(cleanup => {
1587
- try {
1588
- cleanup();
1589
- }
1590
- catch (error) {
1591
- logger.error(`Cleanup function failed: ${error instanceof Error ? error.message : String(error)}`);
1592
- }
1593
- });
1594
- // Cleanup resize debounce timer
1595
- if (screen?._resizeDebounceTimer) {
1596
- clearTimeout(screen._resizeDebounceTimer);
1597
- screen._resizeDebounceTimer = null;
1598
- }
1599
- // Step 6: Remove listeners
1600
- process.removeListener('SIGINT', sigintHandler);
1601
- process.removeListener('SIGTERM', sigtermHandler);
1602
- process.removeListener('uncaughtException', uncaughtExceptionHandler);
1603
- // Step 7: Restore terminal state
1604
- if (process.stdout.isTTY) {
1605
- process.stdout.write('\x1b[?25h'); // Show cursor
1606
- process.stdout.write('\x1b[?1049l'); // Exit alternate screen
1607
- }
1608
- // Step 8: Destroy screen
1609
- if (screen) {
1610
- try {
1611
- screen.destroy();
1612
- }
1613
- catch (error) {
1614
- // Ignore destroy errors
1615
- }
1616
- screen = null;
1617
- }
1618
- // Step 9: Reset state
1619
- panels = [];
1620
- commandLine = null;
1621
- statusStrip = null;
1622
- uiInstance = null;
1623
- runtimeStarted = false;
1624
- renderCount = 0;
1625
- widgetsAppended = false; // Reset append guard
1626
- screenCreated = false; // Reset screen creation guard
1627
- screenCreationCount = 0; // Reset creation count
1628
- // Step 10: Reset global singleton guard
1629
- import('../tui/startTui.js').then(({ resetTuiGuard }) => {
1630
- resetTuiGuard();
1631
- }).catch(() => {
1632
- // Ignore import errors
1633
- });
1634
- };
1635
- uiInstance = {
1636
- unmount: unmountFn
1637
- };
1638
- // Store unmount in global singleton
1639
- const { setTuiUnmount } = await import('../tui/startTui.js');
1640
- setTuiUnmount(unmountFn);
1641
- // Removed verbose "UI mounted" message
1642
- }
1643
- catch (error) {
1644
- // Structural Fix A: TUI mode remains enabled even on error
1645
- throw error;
1646
- }
1647
- }
1648
- //# sourceMappingURL=phase1Runtime.js.map