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.
- package/dist/index.js +16 -1
- package/dist/index.js.map +1 -1
- package/dist/tui_mk1/kernel.d.ts.map +1 -1
- package/dist/tui_mk1/kernel.js +107 -4
- package/dist/tui_mk1/kernel.js.map +1 -1
- package/dist/tui_mk1/layout/layoutEngine.d.ts +38 -19
- package/dist/tui_mk1/layout/layoutEngine.d.ts.map +1 -1
- package/dist/tui_mk1/layout/layoutEngine.js +521 -228
- package/dist/tui_mk1/layout/layoutEngine.js.map +1 -1
- package/dist/tui_mk1/log.d.ts +23 -0
- package/dist/tui_mk1/log.d.ts.map +1 -0
- package/dist/tui_mk1/log.js +125 -0
- package/dist/tui_mk1/log.js.map +1 -0
- package/dist/tui_mk1/logger.d.ts.map +1 -1
- package/dist/tui_mk1/logger.js +20 -10
- package/dist/tui_mk1/logger.js.map +1 -1
- package/dist/tui_mk1/mk1App.d.ts +13 -18
- package/dist/tui_mk1/mk1App.d.ts.map +1 -1
- package/dist/tui_mk1/mk1App.js +334 -157
- package/dist/tui_mk1/mk1App.js.map +1 -1
- package/dist/tui_mk1/resizeController.d.ts.map +1 -1
- package/dist/tui_mk1/resizeController.js +15 -1
- package/dist/tui_mk1/resizeController.js.map +1 -1
- package/dist/tui_mk1/stdoutGuard.d.ts.map +1 -1
- package/dist/tui_mk1/stdoutGuard.js +7 -0
- package/dist/tui_mk1/stdoutGuard.js.map +1 -1
- package/dist/tui_mk1/terminalRestore.d.ts.map +1 -1
- package/dist/tui_mk1/terminalRestore.js +19 -1
- package/dist/tui_mk1/terminalRestore.js.map +1 -1
- package/dist/tui_mk1/ui/offenderScanner.d.ts +31 -0
- package/dist/tui_mk1/ui/offenderScanner.d.ts.map +1 -0
- package/dist/tui_mk1/ui/offenderScanner.js +80 -0
- package/dist/tui_mk1/ui/offenderScanner.js.map +1 -0
- package/dist/tui_mk1/ui/safe.d.ts +9 -0
- package/dist/tui_mk1/ui/safe.d.ts.map +1 -0
- package/dist/tui_mk1/ui/safe.js +29 -0
- package/dist/tui_mk1/ui/safe.js.map +1 -0
- package/dist/tui_mk1/ui/safeText.d.ts +22 -0
- package/dist/tui_mk1/ui/safeText.d.ts.map +1 -0
- package/dist/tui_mk1/ui/safeText.js +50 -0
- package/dist/tui_mk1/ui/safeText.js.map +1 -0
- package/dist/tui_mk1/ui/uiBuilder.d.ts +44 -0
- package/dist/tui_mk1/ui/uiBuilder.d.ts.map +1 -0
- package/dist/tui_mk1/ui/uiBuilder.js +467 -0
- package/dist/tui_mk1/ui/uiBuilder.js.map +1 -0
- package/dist/tui_mk1/viewport/safeViewport.d.ts +10 -1
- package/dist/tui_mk1/viewport/safeViewport.d.ts.map +1 -1
- package/dist/tui_mk1/viewport/safeViewport.js +47 -15
- package/dist/tui_mk1/viewport/safeViewport.js.map +1 -1
- package/dist/tui_mk2/kernel.d.ts +28 -0
- package/dist/tui_mk2/kernel.d.ts.map +1 -0
- package/dist/tui_mk2/kernel.js +138 -0
- package/dist/tui_mk2/kernel.js.map +1 -0
- package/dist/tui_mk2/mk2App.d.ts +33 -0
- package/dist/tui_mk2/mk2App.d.ts.map +1 -0
- package/dist/tui_mk2/mk2App.js +135 -0
- package/dist/tui_mk2/mk2App.js.map +1 -0
- package/package.json +8 -7
- package/dist/tui_mk1/ui/panels/createSection.d.ts +0 -18
- package/dist/tui_mk1/ui/panels/createSection.d.ts.map +0 -1
- package/dist/tui_mk1/ui/panels/createSection.js +0 -42
- package/dist/tui_mk1/ui/panels/createSection.js.map +0 -1
- package/dist/tui_mk1/ui/panels.d.ts +0 -23
- package/dist/tui_mk1/ui/panels.d.ts.map +0 -1
- package/dist/tui_mk1/ui/panels.js +0 -108
- package/dist/tui_mk1/ui/panels.js.map +0 -1
- package/dist/tui_mk1/ui/render/sectionRenderer.d.ts +0 -21
- package/dist/tui_mk1/ui/render/sectionRenderer.d.ts.map +0 -1
- package/dist/tui_mk1/ui/render/sectionRenderer.js +0 -32
- package/dist/tui_mk1/ui/render/sectionRenderer.js.map +0 -1
- package/dist/tui_mk1/ui/widgetManager.d.ts +0 -58
- package/dist/tui_mk1/ui/widgetManager.d.ts.map +0 -1
- package/dist/tui_mk1/ui/widgetManager.js +0 -197
- package/dist/tui_mk1/ui/widgetManager.js.map +0 -1
- package/dist/ui/boot/sequence.d.ts +0 -10
- package/dist/ui/boot/sequence.d.ts.map +0 -1
- package/dist/ui/boot/sequence.js +0 -171
- package/dist/ui/boot/sequence.js.map +0 -1
- package/dist/ui/constraints/layoutSpec.d.ts +0 -47
- package/dist/ui/constraints/layoutSpec.d.ts.map +0 -1
- package/dist/ui/constraints/layoutSpec.js +0 -60
- package/dist/ui/constraints/layoutSpec.js.map +0 -1
- package/dist/ui/constraints/unknownHandling.d.ts +0 -29
- package/dist/ui/constraints/unknownHandling.d.ts.map +0 -1
- package/dist/ui/constraints/unknownHandling.js +0 -60
- package/dist/ui/constraints/unknownHandling.js.map +0 -1
- package/dist/ui/drilldowns/feed.d.ts +0 -11
- package/dist/ui/drilldowns/feed.d.ts.map +0 -1
- package/dist/ui/drilldowns/feed.js +0 -68
- package/dist/ui/drilldowns/feed.js.map +0 -1
- package/dist/ui/drilldowns/index.d.ts +0 -7
- package/dist/ui/drilldowns/index.d.ts.map +0 -1
- package/dist/ui/drilldowns/index.js +0 -8
- package/dist/ui/drilldowns/index.js.map +0 -1
- package/dist/ui/drilldowns/posture.d.ts +0 -11
- package/dist/ui/drilldowns/posture.d.ts.map +0 -1
- package/dist/ui/drilldowns/posture.js +0 -74
- package/dist/ui/drilldowns/posture.js.map +0 -1
- package/dist/ui/intelligence-posture-view.d.ts +0 -22
- package/dist/ui/intelligence-posture-view.d.ts.map +0 -1
- package/dist/ui/intelligence-posture-view.js +0 -169
- package/dist/ui/intelligence-posture-view.js.map +0 -1
- package/dist/ui/navigation/keymaps.d.ts +0 -26
- package/dist/ui/navigation/keymaps.d.ts.map +0 -1
- package/dist/ui/navigation/keymaps.js +0 -135
- package/dist/ui/navigation/keymaps.js.map +0 -1
- package/dist/ui/navigation/palette.d.ts +0 -10
- package/dist/ui/navigation/palette.d.ts.map +0 -1
- package/dist/ui/navigation/palette.js +0 -133
- package/dist/ui/navigation/palette.js.map +0 -1
- package/dist/ui/navigation/state.d.ts +0 -47
- package/dist/ui/navigation/state.d.ts.map +0 -1
- package/dist/ui/navigation/state.js +0 -84
- package/dist/ui/navigation/state.js.map +0 -1
- package/dist/ui/navigation/types.d.ts +0 -38
- package/dist/ui/navigation/types.d.ts.map +0 -1
- package/dist/ui/navigation/types.js +0 -36
- package/dist/ui/navigation/types.js.map +0 -1
- package/dist/ui/panels/active-assets.d.ts +0 -12
- package/dist/ui/panels/active-assets.d.ts.map +0 -1
- package/dist/ui/panels/active-assets.js +0 -83
- package/dist/ui/panels/active-assets.js.map +0 -1
- package/dist/ui/panels/capability-flags.d.ts +0 -12
- package/dist/ui/panels/capability-flags.d.ts.map +0 -1
- package/dist/ui/panels/capability-flags.js +0 -59
- package/dist/ui/panels/capability-flags.js.map +0 -1
- package/dist/ui/panels/command-surface.d.ts +0 -12
- package/dist/ui/panels/command-surface.d.ts.map +0 -1
- package/dist/ui/panels/command-surface.js +0 -55
- package/dist/ui/panels/command-surface.js.map +0 -1
- package/dist/ui/panels/network-origin.d.ts +0 -12
- package/dist/ui/panels/network-origin.d.ts.map +0 -1
- package/dist/ui/panels/network-origin.js +0 -79
- package/dist/ui/panels/network-origin.js.map +0 -1
- package/dist/ui/panels/operations-feed.d.ts +0 -12
- package/dist/ui/panels/operations-feed.d.ts.map +0 -1
- package/dist/ui/panels/operations-feed.js +0 -90
- package/dist/ui/panels/operations-feed.js.map +0 -1
- package/dist/ui/panels/posture.d.ts +0 -12
- package/dist/ui/panels/posture.d.ts.map +0 -1
- package/dist/ui/panels/posture.js +0 -84
- package/dist/ui/panels/posture.js.map +0 -1
- package/dist/ui/panels/resources.d.ts +0 -11
- package/dist/ui/panels/resources.d.ts.map +0 -1
- package/dist/ui/panels/resources.js +0 -88
- package/dist/ui/panels/resources.js.map +0 -1
- package/dist/ui/primitives/Panel.d.ts +0 -25
- package/dist/ui/primitives/Panel.d.ts.map +0 -1
- package/dist/ui/primitives/Panel.js +0 -59
- package/dist/ui/primitives/Panel.js.map +0 -1
- package/dist/ui/rendering/metricRenderer.d.ts +0 -24
- package/dist/ui/rendering/metricRenderer.d.ts.map +0 -1
- package/dist/ui/rendering/metricRenderer.js +0 -86
- package/dist/ui/rendering/metricRenderer.js.map +0 -1
- package/dist/ui/runtime/hub.d.ts +0 -12
- package/dist/ui/runtime/hub.d.ts.map +0 -1
- package/dist/ui/runtime/hub.js +0 -486
- package/dist/ui/runtime/hub.js.map +0 -1
- package/dist/ui/runtime/hubValidation.d.ts +0 -23
- package/dist/ui/runtime/hubValidation.d.ts.map +0 -1
- package/dist/ui/runtime/hubValidation.js +0 -90
- package/dist/ui/runtime/hubValidation.js.map +0 -1
- package/dist/ui/runtime/index.d.ts +0 -29
- package/dist/ui/runtime/index.d.ts.map +0 -1
- package/dist/ui/runtime/index.js +0 -297
- package/dist/ui/runtime/index.js.map +0 -1
- package/dist/ui/runtime/no-tui.d.ts +0 -12
- package/dist/ui/runtime/no-tui.d.ts.map +0 -1
- package/dist/ui/runtime/no-tui.js +0 -77
- package/dist/ui/runtime/no-tui.js.map +0 -1
- package/dist/ui/runtime/state-builder.d.ts +0 -13
- package/dist/ui/runtime/state-builder.d.ts.map +0 -1
- package/dist/ui/runtime/state-builder.js +0 -114
- package/dist/ui/runtime/state-builder.js.map +0 -1
- package/dist/ui/runtime/terminalSizeCheck.d.ts +0 -10
- package/dist/ui/runtime/terminalSizeCheck.d.ts.map +0 -1
- package/dist/ui/runtime/terminalSizeCheck.js +0 -51
- package/dist/ui/runtime/terminalSizeCheck.js.map +0 -1
- package/dist/ui/runtime/tuiLogGate.d.ts +0 -22
- package/dist/ui/runtime/tuiLogGate.d.ts.map +0 -1
- package/dist/ui/runtime/tuiLogGate.js +0 -68
- package/dist/ui/runtime/tuiLogGate.js.map +0 -1
- package/dist/ui/state/types.d.ts +0 -72
- package/dist/ui/state/types.d.ts.map +0 -1
- package/dist/ui/state/types.js +0 -6
- package/dist/ui/state/types.js.map +0 -1
- package/dist/ui/theme/borders.d.ts +0 -20
- package/dist/ui/theme/borders.d.ts.map +0 -1
- package/dist/ui/theme/borders.js +0 -55
- package/dist/ui/theme/borders.js.map +0 -1
- package/dist/ui/theme/tokens.d.ts +0 -28
- package/dist/ui/theme/tokens.d.ts.map +0 -1
- package/dist/ui/theme/tokens.js +0 -50
- package/dist/ui/theme/tokens.js.map +0 -1
- package/dist/ui/theme/typography.d.ts +0 -14
- package/dist/ui/theme/typography.d.ts.map +0 -1
- package/dist/ui/theme/typography.js +0 -30
- package/dist/ui/theme/typography.js.map +0 -1
- package/dist/ui/v3/collectors/assets.collector.d.ts +0 -20
- package/dist/ui/v3/collectors/assets.collector.d.ts.map +0 -1
- package/dist/ui/v3/collectors/assets.collector.js +0 -80
- package/dist/ui/v3/collectors/assets.collector.js.map +0 -1
- package/dist/ui/v3/collectors/capabilities.collector.d.ts +0 -18
- package/dist/ui/v3/collectors/capabilities.collector.d.ts.map +0 -1
- package/dist/ui/v3/collectors/capabilities.collector.js +0 -113
- package/dist/ui/v3/collectors/capabilities.collector.js.map +0 -1
- package/dist/ui/v3/collectors/network.collector.d.ts +0 -18
- package/dist/ui/v3/collectors/network.collector.d.ts.map +0 -1
- package/dist/ui/v3/collectors/network.collector.js +0 -37
- package/dist/ui/v3/collectors/network.collector.js.map +0 -1
- package/dist/ui/v3/collectors/posture.derive.d.ts +0 -24
- package/dist/ui/v3/collectors/posture.derive.d.ts.map +0 -1
- package/dist/ui/v3/collectors/posture.derive.js +0 -57
- package/dist/ui/v3/collectors/posture.derive.js.map +0 -1
- package/dist/ui/v3/collectors/resources.d.ts +0 -23
- package/dist/ui/v3/collectors/resources.d.ts.map +0 -1
- package/dist/ui/v3/collectors/resources.js +0 -136
- package/dist/ui/v3/collectors/resources.js.map +0 -1
- package/dist/ui/v3/commands/commandEngine.d.ts +0 -77
- package/dist/ui/v3/commands/commandEngine.d.ts.map +0 -1
- package/dist/ui/v3/commands/commandEngine.js +0 -3289
- package/dist/ui/v3/commands/commandEngine.js.map +0 -1
- package/dist/ui/v3/commands/commandResult.d.ts +0 -25
- package/dist/ui/v3/commands/commandResult.d.ts.map +0 -1
- package/dist/ui/v3/commands/commandResult.js +0 -19
- package/dist/ui/v3/commands/commandResult.js.map +0 -1
- package/dist/ui/v3/commands/diagnose.d.ts +0 -17
- package/dist/ui/v3/commands/diagnose.d.ts.map +0 -1
- package/dist/ui/v3/commands/diagnose.js +0 -62
- package/dist/ui/v3/commands/diagnose.js.map +0 -1
- package/dist/ui/v3/commands/errorClassifier.d.ts +0 -23
- package/dist/ui/v3/commands/errorClassifier.d.ts.map +0 -1
- package/dist/ui/v3/commands/errorClassifier.js +0 -63
- package/dist/ui/v3/commands/errorClassifier.js.map +0 -1
- package/dist/ui/v3/commands/parser.d.ts +0 -14
- package/dist/ui/v3/commands/parser.d.ts.map +0 -1
- package/dist/ui/v3/commands/parser.js +0 -29
- package/dist/ui/v3/commands/parser.js.map +0 -1
- package/dist/ui/v3/commands/router.d.ts +0 -13
- package/dist/ui/v3/commands/router.d.ts.map +0 -1
- package/dist/ui/v3/commands/router.js +0 -150
- package/dist/ui/v3/commands/router.js.map +0 -1
- package/dist/ui/v3/config/gateway.d.ts +0 -40
- package/dist/ui/v3/config/gateway.d.ts.map +0 -1
- package/dist/ui/v3/config/gateway.js +0 -113
- package/dist/ui/v3/config/gateway.js.map +0 -1
- package/dist/ui/v3/core/event.d.ts +0 -19
- package/dist/ui/v3/core/event.d.ts.map +0 -1
- package/dist/ui/v3/core/event.js +0 -7
- package/dist/ui/v3/core/event.js.map +0 -1
- package/dist/ui/v3/core/eventBus.d.ts +0 -39
- package/dist/ui/v3/core/eventBus.d.ts.map +0 -1
- package/dist/ui/v3/core/eventBus.js +0 -79
- package/dist/ui/v3/core/eventBus.js.map +0 -1
- package/dist/ui/v3/core/feedStore.d.ts +0 -34
- package/dist/ui/v3/core/feedStore.d.ts.map +0 -1
- package/dist/ui/v3/core/feedStore.js +0 -46
- package/dist/ui/v3/core/feedStore.js.map +0 -1
- package/dist/ui/v3/core/logger.d.ts +0 -40
- package/dist/ui/v3/core/logger.d.ts.map +0 -1
- package/dist/ui/v3/core/logger.js +0 -191
- package/dist/ui/v3/core/logger.js.map +0 -1
- package/dist/ui/v3/core/opEvent.d.ts +0 -15
- package/dist/ui/v3/core/opEvent.d.ts.map +0 -1
- package/dist/ui/v3/core/opEvent.js +0 -7
- package/dist/ui/v3/core/opEvent.js.map +0 -1
- package/dist/ui/v3/index.d.ts +0 -8
- package/dist/ui/v3/index.d.ts.map +0 -1
- package/dist/ui/v3/index.js +0 -51
- package/dist/ui/v3/index.js.map +0 -1
- package/dist/ui/v3/runtime/moduleConfig.d.ts +0 -21
- package/dist/ui/v3/runtime/moduleConfig.d.ts.map +0 -1
- package/dist/ui/v3/runtime/moduleConfig.js +0 -41
- package/dist/ui/v3/runtime/moduleConfig.js.map +0 -1
- package/dist/ui/v3/section0/index.d.ts +0 -22
- package/dist/ui/v3/section0/index.d.ts.map +0 -1
- package/dist/ui/v3/section0/index.js +0 -88
- package/dist/ui/v3/section0/index.js.map +0 -1
- package/dist/ui/v3/section0/runtime/createScreen.d.ts +0 -27
- package/dist/ui/v3/section0/runtime/createScreen.d.ts.map +0 -1
- package/dist/ui/v3/section0/runtime/createScreen.js +0 -55
- package/dist/ui/v3/section0/runtime/createScreen.js.map +0 -1
- package/dist/ui/v3/section0/runtime/lifecycle.d.ts +0 -53
- package/dist/ui/v3/section0/runtime/lifecycle.d.ts.map +0 -1
- package/dist/ui/v3/section0/runtime/lifecycle.js +0 -172
- package/dist/ui/v3/section0/runtime/lifecycle.js.map +0 -1
- package/dist/ui/v3/section1/index.d.ts +0 -19
- package/dist/ui/v3/section1/index.d.ts.map +0 -1
- package/dist/ui/v3/section1/index.js +0 -413
- package/dist/ui/v3/section1/index.js.map +0 -1
- package/dist/ui/v3/section1/runtime/commandLine.d.ts +0 -49
- package/dist/ui/v3/section1/runtime/commandLine.d.ts.map +0 -1
- package/dist/ui/v3/section1/runtime/commandLine.js +0 -183
- package/dist/ui/v3/section1/runtime/commandLine.js.map +0 -1
- package/dist/ui/v3/section1/runtime/focusLock.d.ts +0 -24
- package/dist/ui/v3/section1/runtime/focusLock.d.ts.map +0 -1
- package/dist/ui/v3/section1/runtime/focusLock.js +0 -44
- package/dist/ui/v3/section1/runtime/focusLock.js.map +0 -1
- package/dist/ui/v3/state/assertUiState.d.ts +0 -27
- package/dist/ui/v3/state/assertUiState.d.ts.map +0 -1
- package/dist/ui/v3/state/assertUiState.js +0 -89
- package/dist/ui/v3/state/assertUiState.js.map +0 -1
- package/dist/ui/v3/state/capabilitiesStore.d.ts +0 -54
- package/dist/ui/v3/state/capabilitiesStore.d.ts.map +0 -1
- package/dist/ui/v3/state/capabilitiesStore.js +0 -76
- package/dist/ui/v3/state/capabilitiesStore.js.map +0 -1
- package/dist/ui/v3/state/defaultState.d.ts +0 -19
- package/dist/ui/v3/state/defaultState.d.ts.map +0 -1
- package/dist/ui/v3/state/defaultState.js +0 -28
- package/dist/ui/v3/state/defaultState.js.map +0 -1
- package/dist/ui/v3/state/gatewayConnectionStore.d.ts +0 -72
- package/dist/ui/v3/state/gatewayConnectionStore.d.ts.map +0 -1
- package/dist/ui/v3/state/gatewayConnectionStore.js +0 -108
- package/dist/ui/v3/state/gatewayConnectionStore.js.map +0 -1
- package/dist/ui/v3/state/initializePostureState.d.ts +0 -23
- package/dist/ui/v3/state/initializePostureState.d.ts.map +0 -1
- package/dist/ui/v3/state/initializePostureState.js +0 -41
- package/dist/ui/v3/state/initializePostureState.js.map +0 -1
- package/dist/ui/v3/state/panelStore.d.ts +0 -80
- package/dist/ui/v3/state/panelStore.d.ts.map +0 -1
- package/dist/ui/v3/state/panelStore.js +0 -131
- package/dist/ui/v3/state/panelStore.js.map +0 -1
- package/dist/ui/v3/state/resourcesData.d.ts +0 -15
- package/dist/ui/v3/state/resourcesData.d.ts.map +0 -1
- package/dist/ui/v3/state/resourcesData.js +0 -7
- package/dist/ui/v3/state/resourcesData.js.map +0 -1
- package/dist/ui/v3/state/uiState.d.ts +0 -22
- package/dist/ui/v3/state/uiState.d.ts.map +0 -1
- package/dist/ui/v3/state/uiState.js +0 -13
- package/dist/ui/v3/state/uiState.js.map +0 -1
- package/dist/ui/v3/state/uiStateBuilder.d.ts +0 -32
- package/dist/ui/v3/state/uiStateBuilder.d.ts.map +0 -1
- package/dist/ui/v3/state/uiStateBuilder.js +0 -73
- package/dist/ui/v3/state/uiStateBuilder.js.map +0 -1
- package/dist/ui/v3/state/uiStateTypes.d.ts +0 -59
- package/dist/ui/v3/state/uiStateTypes.d.ts.map +0 -1
- package/dist/ui/v3/state/uiStateTypes.js +0 -8
- package/dist/ui/v3/state/uiStateTypes.js.map +0 -1
- package/dist/ui/v3/state/value.d.ts +0 -80
- package/dist/ui/v3/state/value.d.ts.map +0 -1
- package/dist/ui/v3/state/value.js +0 -96
- package/dist/ui/v3/state/value.js.map +0 -1
- package/dist/ui/v3/tui/geometry.d.ts +0 -83
- package/dist/ui/v3/tui/geometry.d.ts.map +0 -1
- package/dist/ui/v3/tui/geometry.js +0 -201
- package/dist/ui/v3/tui/geometry.js.map +0 -1
- package/dist/ui/v3/tui/startTui.d.ts +0 -37
- package/dist/ui/v3/tui/startTui.d.ts.map +0 -1
- package/dist/ui/v3/tui/startTui.js +0 -61
- package/dist/ui/v3/tui/startTui.js.map +0 -1
- package/dist/ui/v3/tui/terminalMode.d.ts +0 -31
- package/dist/ui/v3/tui/terminalMode.d.ts.map +0 -1
- package/dist/ui/v3/tui/terminalMode.js +0 -76
- package/dist/ui/v3/tui/terminalMode.js.map +0 -1
- package/dist/ui/v3/ui/debugUtils.d.ts +0 -67
- package/dist/ui/v3/ui/debugUtils.d.ts.map +0 -1
- package/dist/ui/v3/ui/debugUtils.js +0 -238
- package/dist/ui/v3/ui/debugUtils.js.map +0 -1
- package/dist/ui/v3/ui/focus.d.ts +0 -28
- package/dist/ui/v3/ui/focus.d.ts.map +0 -1
- package/dist/ui/v3/ui/focus.js +0 -38
- package/dist/ui/v3/ui/focus.js.map +0 -1
- package/dist/ui/v3/ui/layout/hubLayout.d.ts +0 -43
- package/dist/ui/v3/ui/layout/hubLayout.d.ts.map +0 -1
- package/dist/ui/v3/ui/layout/hubLayout.js +0 -170
- package/dist/ui/v3/ui/layout/hubLayout.js.map +0 -1
- package/dist/ui/v3/ui/layout/phase1Layout.d.ts +0 -63
- package/dist/ui/v3/ui/layout/phase1Layout.d.ts.map +0 -1
- package/dist/ui/v3/ui/layout/phase1Layout.js +0 -274
- package/dist/ui/v3/ui/layout/phase1Layout.js.map +0 -1
- package/dist/ui/v3/ui/layout/phase1Layout.test.d.ts +0 -5
- package/dist/ui/v3/ui/layout/phase1Layout.test.d.ts.map +0 -1
- package/dist/ui/v3/ui/layout/phase1Layout.test.js +0 -120
- package/dist/ui/v3/ui/layout/phase1Layout.test.js.map +0 -1
- package/dist/ui/v3/ui/minimalRuntime.d.ts +0 -14
- package/dist/ui/v3/ui/minimalRuntime.d.ts.map +0 -1
- package/dist/ui/v3/ui/minimalRuntime.js +0 -111
- package/dist/ui/v3/ui/minimalRuntime.js.map +0 -1
- package/dist/ui/v3/ui/panels/AssetsPanel.d.ts +0 -17
- package/dist/ui/v3/ui/panels/AssetsPanel.d.ts.map +0 -1
- package/dist/ui/v3/ui/panels/AssetsPanel.js +0 -53
- package/dist/ui/v3/ui/panels/AssetsPanel.js.map +0 -1
- package/dist/ui/v3/ui/panels/CapabilitiesPanel.d.ts +0 -20
- package/dist/ui/v3/ui/panels/CapabilitiesPanel.d.ts.map +0 -1
- package/dist/ui/v3/ui/panels/CapabilitiesPanel.js +0 -67
- package/dist/ui/v3/ui/panels/CapabilitiesPanel.js.map +0 -1
- package/dist/ui/v3/ui/panels/NetworkPanel.d.ts +0 -31
- package/dist/ui/v3/ui/panels/NetworkPanel.d.ts.map +0 -1
- package/dist/ui/v3/ui/panels/NetworkPanel.js +0 -153
- package/dist/ui/v3/ui/panels/NetworkPanel.js.map +0 -1
- package/dist/ui/v3/ui/panels/PosturePanel.d.ts +0 -16
- package/dist/ui/v3/ui/panels/PosturePanel.d.ts.map +0 -1
- package/dist/ui/v3/ui/panels/PosturePanel.js +0 -60
- package/dist/ui/v3/ui/panels/PosturePanel.js.map +0 -1
- package/dist/ui/v3/ui/panels/ResourcesPanel.d.ts +0 -20
- package/dist/ui/v3/ui/panels/ResourcesPanel.d.ts.map +0 -1
- package/dist/ui/v3/ui/panels/ResourcesPanel.js +0 -66
- package/dist/ui/v3/ui/panels/ResourcesPanel.js.map +0 -1
- package/dist/ui/v3/ui/phase1Runtime.d.ts +0 -29
- package/dist/ui/v3/ui/phase1Runtime.d.ts.map +0 -1
- package/dist/ui/v3/ui/phase1Runtime.js +0 -1648
- package/dist/ui/v3/ui/phase1Runtime.js.map +0 -1
- package/dist/ui/v3/ui/phase1RuntimeClean.d.ts +0 -34
- package/dist/ui/v3/ui/phase1RuntimeClean.d.ts.map +0 -1
- package/dist/ui/v3/ui/phase1RuntimeClean.js +0 -1841
- package/dist/ui/v3/ui/phase1RuntimeClean.js.map +0 -1
- package/dist/ui/v3/ui/primitives/Panel.d.ts +0 -39
- package/dist/ui/v3/ui/primitives/Panel.d.ts.map +0 -1
- package/dist/ui/v3/ui/primitives/Panel.js +0 -105
- package/dist/ui/v3/ui/primitives/Panel.js.map +0 -1
- package/dist/ui/v3/ui/theme.d.ts +0 -37
- package/dist/ui/v3/ui/theme.d.ts.map +0 -1
- package/dist/ui/v3/ui/theme.js +0 -40
- package/dist/ui/v3/ui/theme.js.map +0 -1
- package/dist/ui/v3/ui/uiRuntime.d.ts +0 -40
- package/dist/ui/v3/ui/uiRuntime.d.ts.map +0 -1
- package/dist/ui/v3/ui/uiRuntime.js +0 -60
- package/dist/ui/v3/ui/uiRuntime.js.map +0 -1
- package/dist/ui/v3/ui/widgets/CommandLine.d.ts +0 -26
- package/dist/ui/v3/ui/widgets/CommandLine.d.ts.map +0 -1
- package/dist/ui/v3/ui/widgets/CommandLine.js +0 -67
- package/dist/ui/v3/ui/widgets/CommandLine.js.map +0 -1
- package/dist/ui/v3/v1Adapters/agents.d.ts +0 -72
- package/dist/ui/v3/v1Adapters/agents.d.ts.map +0 -1
- package/dist/ui/v3/v1Adapters/agents.js +0 -182
- package/dist/ui/v3/v1Adapters/agents.js.map +0 -1
- package/dist/ui/v3/v1Adapters/config.d.ts +0 -67
- package/dist/ui/v3/v1Adapters/config.d.ts.map +0 -1
- package/dist/ui/v3/v1Adapters/config.js +0 -78
- package/dist/ui/v3/v1Adapters/config.js.map +0 -1
- package/dist/ui/v3/v1Adapters/connect.d.ts +0 -77
- package/dist/ui/v3/v1Adapters/connect.d.ts.map +0 -1
- package/dist/ui/v3/v1Adapters/connect.js +0 -576
- package/dist/ui/v3/v1Adapters/connect.js.map +0 -1
- package/dist/ui/v3/v1Adapters/httpDebug.d.ts +0 -19
- package/dist/ui/v3/v1Adapters/httpDebug.d.ts.map +0 -1
- package/dist/ui/v3/v1Adapters/httpDebug.js +0 -60
- package/dist/ui/v3/v1Adapters/httpDebug.js.map +0 -1
- package/dist/ui/v3/v1Adapters/runs.d.ts +0 -77
- package/dist/ui/v3/v1Adapters/runs.d.ts.map +0 -1
- package/dist/ui/v3/v1Adapters/runs.js +0 -339
- package/dist/ui/v3/v1Adapters/runs.js.map +0 -1
- package/dist/ui/v4/engine/renderFrame.d.ts +0 -47
- package/dist/ui/v4/engine/renderFrame.d.ts.map +0 -1
- package/dist/ui/v4/engine/renderFrame.js +0 -653
- package/dist/ui/v4/engine/renderFrame.js.map +0 -1
- package/dist/ui/v4/engine/resizeController.d.ts +0 -48
- package/dist/ui/v4/engine/resizeController.d.ts.map +0 -1
- package/dist/ui/v4/engine/resizeController.js +0 -285
- package/dist/ui/v4/engine/resizeController.js.map +0 -1
- package/dist/ui/v4/engine/safeViewport.d.ts +0 -47
- package/dist/ui/v4/engine/safeViewport.d.ts.map +0 -1
- package/dist/ui/v4/engine/safeViewport.js +0 -123
- package/dist/ui/v4/engine/safeViewport.js.map +0 -1
- package/dist/ui/v4/engine/terminalProfile.d.ts +0 -56
- package/dist/ui/v4/engine/terminalProfile.d.ts.map +0 -1
- package/dist/ui/v4/engine/terminalProfile.js +0 -115
- package/dist/ui/v4/engine/terminalProfile.js.map +0 -1
- package/dist/ui/v4/index.d.ts +0 -28
- package/dist/ui/v4/index.d.ts.map +0 -1
- package/dist/ui/v4/index.js +0 -993
- package/dist/ui/v4/index.js.map +0 -1
- package/dist/ui/v4/layout/layoutEngine.d.ts +0 -62
- package/dist/ui/v4/layout/layoutEngine.d.ts.map +0 -1
- package/dist/ui/v4/layout/layoutEngine.js +0 -294
- package/dist/ui/v4/layout/layoutEngine.js.map +0 -1
- package/dist/ui/v4/runtime/keepAlive.d.ts +0 -21
- package/dist/ui/v4/runtime/keepAlive.d.ts.map +0 -1
- package/dist/ui/v4/runtime/keepAlive.js +0 -149
- package/dist/ui/v4/runtime/keepAlive.js.map +0 -1
- package/dist/ui/v4/runtime/logger.d.ts +0 -35
- package/dist/ui/v4/runtime/logger.d.ts.map +0 -1
- package/dist/ui/v4/runtime/logger.js +0 -109
- package/dist/ui/v4/runtime/logger.js.map +0 -1
- package/dist/ui/v5/debug/assertNoOverflow.d.ts +0 -28
- package/dist/ui/v5/debug/assertNoOverflow.d.ts.map +0 -1
- package/dist/ui/v5/debug/assertNoOverflow.js +0 -63
- package/dist/ui/v5/debug/assertNoOverflow.js.map +0 -1
- package/dist/ui/v5/debug/debugCommands.d.ts +0 -20
- package/dist/ui/v5/debug/debugCommands.d.ts.map +0 -1
- package/dist/ui/v5/debug/debugCommands.js +0 -461
- package/dist/ui/v5/debug/debugCommands.js.map +0 -1
- package/dist/ui/v5/debugCommands.d.ts +0 -20
- package/dist/ui/v5/debugCommands.d.ts.map +0 -1
- package/dist/ui/v5/debugCommands.js +0 -81
- package/dist/ui/v5/debugCommands.js.map +0 -1
- package/dist/ui/v5/guardrails/stdoutGuard.d.ts +0 -23
- package/dist/ui/v5/guardrails/stdoutGuard.d.ts.map +0 -1
- package/dist/ui/v5/guardrails/stdoutGuard.js +0 -94
- package/dist/ui/v5/guardrails/stdoutGuard.js.map +0 -1
- package/dist/ui/v5/guardrails/terminalRestore.d.ts +0 -17
- package/dist/ui/v5/guardrails/terminalRestore.d.ts.map +0 -1
- package/dist/ui/v5/guardrails/terminalRestore.js +0 -47
- package/dist/ui/v5/guardrails/terminalRestore.js.map +0 -1
- package/dist/ui/v5/index.d.ts +0 -30
- package/dist/ui/v5/index.d.ts.map +0 -1
- package/dist/ui/v5/index.js +0 -243
- package/dist/ui/v5/index.js.map +0 -1
- package/dist/ui/v5/kernel/kernel.d.ts +0 -81
- package/dist/ui/v5/kernel/kernel.d.ts.map +0 -1
- package/dist/ui/v5/kernel/kernel.js +0 -339
- package/dist/ui/v5/kernel/kernel.js.map +0 -1
- package/dist/ui/v5/kernel.d.ts +0 -75
- package/dist/ui/v5/kernel.d.ts.map +0 -1
- package/dist/ui/v5/kernel.js +0 -289
- package/dist/ui/v5/kernel.js.map +0 -1
- package/dist/ui/v5/layout/clampRect.d.ts +0 -28
- package/dist/ui/v5/layout/clampRect.d.ts.map +0 -1
- package/dist/ui/v5/layout/clampRect.js +0 -45
- package/dist/ui/v5/layout/clampRect.js.map +0 -1
- package/dist/ui/v5/layout/layoutEngine.d.ts +0 -16
- package/dist/ui/v5/layout/layoutEngine.d.ts.map +0 -1
- package/dist/ui/v5/layout/layoutEngine.js +0 -99
- package/dist/ui/v5/layout/layoutEngine.js.map +0 -1
- package/dist/ui/v5/renderGate.d.ts +0 -19
- package/dist/ui/v5/renderGate.d.ts.map +0 -1
- package/dist/ui/v5/renderGate.js +0 -36
- package/dist/ui/v5/renderGate.js.map +0 -1
- package/dist/ui/v5/resize/resizeController.d.ts +0 -62
- package/dist/ui/v5/resize/resizeController.d.ts.map +0 -1
- package/dist/ui/v5/resize/resizeController.js +0 -141
- package/dist/ui/v5/resize/resizeController.js.map +0 -1
- package/dist/ui/v5/resizeController.d.ts +0 -55
- package/dist/ui/v5/resizeController.d.ts.map +0 -1
- package/dist/ui/v5/resizeController.js +0 -124
- package/dist/ui/v5/resizeController.js.map +0 -1
- package/dist/ui/v5/runtime/keepAlive.d.ts +0 -37
- package/dist/ui/v5/runtime/keepAlive.d.ts.map +0 -1
- package/dist/ui/v5/runtime/keepAlive.js +0 -122
- package/dist/ui/v5/runtime/keepAlive.js.map +0 -1
- package/dist/ui/v5/runtime/restoreTerminal.d.ts +0 -34
- package/dist/ui/v5/runtime/restoreTerminal.d.ts.map +0 -1
- package/dist/ui/v5/runtime/restoreTerminal.js +0 -100
- package/dist/ui/v5/runtime/restoreTerminal.js.map +0 -1
- package/dist/ui/v5/runtime/stdoutGuard.d.ts +0 -42
- package/dist/ui/v5/runtime/stdoutGuard.d.ts.map +0 -1
- package/dist/ui/v5/runtime/stdoutGuard.js +0 -156
- package/dist/ui/v5/runtime/stdoutGuard.js.map +0 -1
- package/dist/ui/v5/viewport/getViewport.d.ts +0 -23
- package/dist/ui/v5/viewport/getViewport.d.ts.map +0 -1
- package/dist/ui/v5/viewport/getViewport.js +0 -117
- package/dist/ui/v5/viewport/getViewport.js.map +0 -1
- package/dist/ui/v5/viewport.d.ts +0 -41
- package/dist/ui/v5/viewport.d.ts.map +0 -1
- package/dist/ui/v5/viewport.js +0 -90
- package/dist/ui/v5/viewport.js.map +0 -1
- package/dist/ui/widgets/flagRow.d.ts +0 -25
- package/dist/ui/widgets/flagRow.d.ts.map +0 -1
- package/dist/ui/widgets/flagRow.js +0 -57
- package/dist/ui/widgets/flagRow.js.map +0 -1
- package/dist/ui/widgets/index.d.ts +0 -9
- package/dist/ui/widgets/index.d.ts.map +0 -1
- package/dist/ui/widgets/index.js +0 -9
- package/dist/ui/widgets/index.js.map +0 -1
- package/dist/ui/widgets/meter.d.ts +0 -18
- package/dist/ui/widgets/meter.d.ts.map +0 -1
- package/dist/ui/widgets/meter.js +0 -38
- package/dist/ui/widgets/meter.js.map +0 -1
- package/dist/ui/widgets/miniMap.d.ts +0 -26
- package/dist/ui/widgets/miniMap.d.ts.map +0 -1
- package/dist/ui/widgets/miniMap.js +0 -94
- package/dist/ui/widgets/miniMap.js.map +0 -1
- package/dist/ui/widgets/sparkline.d.ts +0 -17
- package/dist/ui/widgets/sparkline.d.ts.map +0 -1
- package/dist/ui/widgets/sparkline.js +0 -65
- 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
|