@hoox-sh/hoox-tui 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +247 -0
  2. package/bin/hoox-tui.js +35 -0
  3. package/dist/main.js +17594 -0
  4. package/package.json +72 -0
  5. package/src/app.tsx +696 -0
  6. package/src/cli-bridge-test-double.ts +212 -0
  7. package/src/components/layout/layout.test.tsx +205 -0
  8. package/src/components/layout/sidebar.tsx +69 -0
  9. package/src/components/layout/statusbar.test.tsx +423 -0
  10. package/src/components/layout/statusbar.tsx +348 -0
  11. package/src/components/shared/animated-border.tsx +58 -0
  12. package/src/components/shared/chrome.test.tsx +29 -0
  13. package/src/components/shared/command-palette.tsx +361 -0
  14. package/src/components/shared/cool-brackets.tsx +75 -0
  15. package/src/components/shared/crash-screen.tsx +167 -0
  16. package/src/components/shared/error-boundary.tsx +99 -0
  17. package/src/components/shared/keybinding-hint.tsx +37 -0
  18. package/src/components/shared/panel.tsx +61 -0
  19. package/src/components/shared/quit-modal.tsx +65 -0
  20. package/src/components/shared/shared.test.tsx +538 -0
  21. package/src/components/shared/spinner.tsx +85 -0
  22. package/src/components/shared/status-dot.tsx +54 -0
  23. package/src/components/shared/view-header.tsx +53 -0
  24. package/src/components/ui/connection-toasts.test.ts +50 -0
  25. package/src/components/ui/connection-toasts.ts +215 -0
  26. package/src/components/ui/dialog.tsx +281 -0
  27. package/src/components/ui/select.tsx +197 -0
  28. package/src/components/ui/toast.tsx +185 -0
  29. package/src/components/views/ai-chat.test.tsx +81 -0
  30. package/src/components/views/ai-chat.tsx +576 -0
  31. package/src/components/views/config-editor/action-bar.tsx +102 -0
  32. package/src/components/views/config-editor/code-editor.tsx +597 -0
  33. package/src/components/views/config-editor/file-tree.tsx +113 -0
  34. package/src/components/views/config-editor/types.ts +37 -0
  35. package/src/components/views/config-editor.test.tsx +465 -0
  36. package/src/components/views/config-editor.tsx +709 -0
  37. package/src/components/views/dashboard/alerts-panel.tsx +154 -0
  38. package/src/components/views/dashboard/auto-repair-panel.tsx +229 -0
  39. package/src/components/views/dashboard/model-health-section.tsx +276 -0
  40. package/src/components/views/dashboard/pyne-health-section.tsx +112 -0
  41. package/src/components/views/dashboard.test.tsx +347 -0
  42. package/src/components/views/dashboard.tsx +647 -0
  43. package/src/components/views/db-query.test.tsx +115 -0
  44. package/src/components/views/db-query.tsx +849 -0
  45. package/src/components/views/edge-topology.test.tsx +198 -0
  46. package/src/components/views/edge-topology.tsx +572 -0
  47. package/src/components/views/kv-viewer.test.tsx +241 -0
  48. package/src/components/views/kv-viewer.tsx +717 -0
  49. package/src/components/views/logs-viewer.test.tsx +273 -0
  50. package/src/components/views/logs-viewer.tsx +616 -0
  51. package/src/components/views/queue-depth.test.tsx +221 -0
  52. package/src/components/views/queue-depth.tsx +376 -0
  53. package/src/components/views/secrets-viewer.test.tsx +65 -0
  54. package/src/components/views/secrets-viewer.tsx +548 -0
  55. package/src/components/views/service-manager.test.tsx +358 -0
  56. package/src/components/views/service-manager.tsx +946 -0
  57. package/src/components/views/settings.test.tsx +648 -0
  58. package/src/components/views/settings.tsx +1497 -0
  59. package/src/components/views/setup-wizard.test.tsx +297 -0
  60. package/src/components/views/setup-wizard.tsx +1321 -0
  61. package/src/components/views/trade-monitor.test.tsx +592 -0
  62. package/src/components/views/trade-monitor.tsx +745 -0
  63. package/src/components/views/worker-detail.test.tsx +289 -0
  64. package/src/components/views/worker-detail.tsx +676 -0
  65. package/src/components/views/worker-settings.test.tsx +43 -0
  66. package/src/components/views/worker-settings.tsx +563 -0
  67. package/src/components/views/workers-overview.test.tsx +260 -0
  68. package/src/components/views/workers-overview.tsx +631 -0
  69. package/src/hooks/hooks.test.ts +72 -0
  70. package/src/hooks/index.ts +11 -0
  71. package/src/hooks/renderer-ref.ts +22 -0
  72. package/src/hooks/use-keyboard.ts +130 -0
  73. package/src/hooks/use-polling.ts +73 -0
  74. package/src/hooks/use-service-data.ts +18 -0
  75. package/src/main.tsx +117 -0
  76. package/src/network-test-double.ts +133 -0
  77. package/src/opentui-augment.d.ts +45 -0
  78. package/src/opentui-jsx.d.ts +70 -0
  79. package/src/services/cli-bridge/index.ts +1662 -0
  80. package/src/services/cli-bridge/standalone.ts +315 -0
  81. package/src/services/cli-bridge/types.ts +307 -0
  82. package/src/services/cli-bridge.test.ts +406 -0
  83. package/src/services/clipboard.test.ts +57 -0
  84. package/src/services/clipboard.ts +180 -0
  85. package/src/services/dashboard-settings-loader.ts +31 -0
  86. package/src/services/dev-log.test.ts +168 -0
  87. package/src/services/dev-log.ts +194 -0
  88. package/src/services/hoox-path-service.test.ts +183 -0
  89. package/src/services/hoox-path-service.ts +185 -0
  90. package/src/services/tui-connection.test.ts +212 -0
  91. package/src/services/tui-connection.ts +236 -0
  92. package/src/services/tui-storage.test.ts +92 -0
  93. package/src/services/tui-storage.ts +86 -0
  94. package/src/stores/config-store.test.ts +360 -0
  95. package/src/stores/service-store.test.ts +1026 -0
  96. package/src/stores/session-restore.test.ts +134 -0
  97. package/src/stores/ui-store.test.ts +276 -0
  98. package/src/test-setup.ts +265 -0
  99. package/src/test-utils.ts +179 -0
  100. package/src/types.ts +153 -0
  101. package/src/utils/colors.test.ts +326 -0
  102. package/src/utils/formatters.test.ts +236 -0
  103. package/src/view-registry.test.ts +132 -0
  104. package/src/view-registry.tsx +368 -0
@@ -0,0 +1,260 @@
1
+ /**
2
+ * Copyright (c) 2026 HOOX · HOOX · jango-blockchained (hoox-sh)
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ /** @jsxImportSource @opentui/react */
7
+ import { describe, it, expect, beforeEach } from "bun:test";
8
+ import { testRender } from "@opentui/react/test-utils";
9
+ import { WorkersOverview } from "./workers-overview";
10
+ import {
11
+ makeWorker,
12
+ resetCliBridgeDouble,
13
+ type TestWorkerInfo,
14
+ } from "../../test-utils";
15
+ import { useServiceStore } from "@hoox-sh/hoox-shared/stores/service-store";
16
+ import { useUIStore } from "@hoox-sh/hoox-shared/stores/ui-store";
17
+
18
+ function collectText(output: unknown): string[] {
19
+ const texts: string[] = [];
20
+ const walk = (node: any): void => {
21
+ if (!node || typeof node !== "object") return;
22
+ if (typeof node === "string") {
23
+ texts.push(node);
24
+ return;
25
+ }
26
+ if (node.children && Array.isArray(node.children)) {
27
+ for (const child of node.children) walk(child);
28
+ }
29
+ if (node.props?.children) {
30
+ const c = node.props.children;
31
+ if (Array.isArray(c)) c.forEach(walk);
32
+ else walk(c);
33
+ }
34
+ };
35
+ walk(output);
36
+ return texts;
37
+ }
38
+
39
+ async function renderWorkersOverview(): Promise<string> {
40
+ const { captureCharFrame, renderOnce } = await testRender(
41
+ <WorkersOverview />,
42
+ { width: 80, height: 24, exitOnCtrlC: false }
43
+ );
44
+ await renderOnce();
45
+ return captureCharFrame();
46
+ }
47
+
48
+ function outputContains(output: string, ...substrings: string[]): boolean {
49
+ return substrings.every((s) => output.includes(s));
50
+ }
51
+
52
+ function resetStores() {
53
+ useServiceStore.setState({
54
+ workers: [],
55
+ logs: [],
56
+ metrics: null,
57
+ connectionStatus: "connected",
58
+ selectedWorkerId: null,
59
+ lastUpdated: 0,
60
+ alerts: [],
61
+ tradeStream: [],
62
+ // Avoid real network during mount effects under partial mock wiring
63
+ fetchWorkers: async () => {},
64
+ streamTrades: async () => {},
65
+ streamLogs: async () => {},
66
+ });
67
+ useUIStore.setState({
68
+ activeView: "workers",
69
+ sidebarExpanded: true,
70
+ modal: null,
71
+ commandPaletteOpen: false,
72
+ previousView: null,
73
+ });
74
+ }
75
+
76
+ describe("WorkersOverview", () => {
77
+ beforeEach(() => {
78
+ resetCliBridgeDouble();
79
+ resetStores();
80
+ });
81
+
82
+ describe("2-column grid layout", () => {
83
+ it("renders all workers", async () => {
84
+ useServiceStore.setState({
85
+ workers: [
86
+ makeWorker({ id: "w1", name: "alpha" }),
87
+ makeWorker({ id: "w2", name: "beta" }),
88
+ makeWorker({ id: "w3", name: "gamma" }),
89
+ ],
90
+ });
91
+
92
+ const text = await renderWorkersOverview();
93
+
94
+ expect(outputContains(text, "ALPHA", "BETA", "GAMMA")).toBe(true);
95
+ expect(outputContains(text, "3 total")).toBe(true);
96
+ });
97
+
98
+ it("shows empty state when no workers", async () => {
99
+ const text = await renderWorkersOverview();
100
+
101
+ expect(outputContains(text, "No workers connected")).toBe(true);
102
+ });
103
+
104
+ it("numbers workers with No.XX format", async () => {
105
+ useServiceStore.setState({
106
+ workers: [
107
+ makeWorker({ id: "w1", name: "alpha" }),
108
+ makeWorker({ id: "w2", name: "beta" }),
109
+ ],
110
+ });
111
+
112
+ const text = await renderWorkersOverview();
113
+
114
+ expect(outputContains(text, "No.01", "No.02")).toBe(true);
115
+ });
116
+ });
117
+
118
+ describe("metric formatting", () => {
119
+ it("formats uptime in hours when under 24h", async () => {
120
+ useServiceStore.setState({
121
+ workers: [makeWorker({ id: "w1", uptime: 7200 })],
122
+ });
123
+
124
+ const text = await renderWorkersOverview();
125
+
126
+ expect(outputContains(text, "2h")).toBe(true);
127
+ });
128
+
129
+ it("formats uptime in days+hours when >= 24h", async () => {
130
+ useServiceStore.setState({
131
+ workers: [makeWorker({ id: "w1", uptime: 259200 })],
132
+ });
133
+
134
+ const text = await renderWorkersOverview();
135
+
136
+ expect(outputContains(text, "3d0h")).toBe(true);
137
+ });
138
+
139
+ it("formats requests with K/M suffixes", async () => {
140
+ useServiceStore.setState({
141
+ workers: [
142
+ makeWorker({ id: "w1", requests: 1_200 }),
143
+ makeWorker({ id: "w2", requests: 1_200_000 }),
144
+ ],
145
+ });
146
+
147
+ const text = await renderWorkersOverview();
148
+
149
+ expect(outputContains(text, "1.2K", "1.2M")).toBe(true);
150
+ });
151
+
152
+ it("shows memory as used/128 MB", async () => {
153
+ useServiceStore.setState({
154
+ workers: [makeWorker({ id: "w1", memory: 64 })],
155
+ });
156
+
157
+ const text = await renderWorkersOverview();
158
+
159
+ expect(outputContains(text, "64/128 MB")).toBe(true);
160
+ });
161
+
162
+ it("shows CPU as percentage", async () => {
163
+ useServiceStore.setState({
164
+ workers: [makeWorker({ id: "w1", cpu: 45.5 })],
165
+ });
166
+
167
+ const text = await renderWorkersOverview();
168
+
169
+ expect(outputContains(text, "45.5%")).toBe(true);
170
+ });
171
+ });
172
+
173
+ describe("status dots", () => {
174
+ it("renders status dot for each worker", async () => {
175
+ useServiceStore.setState({
176
+ workers: [
177
+ makeWorker({ id: "w1", status: "operational" }),
178
+ makeWorker({ id: "w2", status: "degraded" }),
179
+ makeWorker({ id: "w3", status: "down" }),
180
+ ],
181
+ });
182
+
183
+ const text = await renderWorkersOverview();
184
+
185
+ expect(outputContains(text, "█", "▌", "░")).toBe(true);
186
+ });
187
+ });
188
+
189
+ describe("action buttons", () => {
190
+ it("renders [View Details] and [Logs] per card", async () => {
191
+ useServiceStore.setState({
192
+ workers: [makeWorker({ id: "w1" })],
193
+ });
194
+
195
+ const text = await renderWorkersOverview();
196
+
197
+ expect(outputContains(text, "[VIEW DETAILS]", "[LOGS]")).toBe(true);
198
+ });
199
+
200
+ it("renders [DEPLOY] and [RESTART] per card", async () => {
201
+ useServiceStore.setState({
202
+ workers: [makeWorker({ id: "w1" })],
203
+ });
204
+
205
+ const text = await renderWorkersOverview();
206
+
207
+ expect(outputContains(text, "[DEPLOY]", "[RESTART]")).toBe(true);
208
+ });
209
+ });
210
+
211
+ describe("empty state chrome", () => {
212
+ it("uses ViewHeader title and total count when empty", async () => {
213
+ useServiceStore.setState({
214
+ workers: [],
215
+ connectionStatus: "connected",
216
+ });
217
+ const text = await renderWorkersOverview();
218
+ // Header title is uppercase via ViewHeader styling in some themes;
219
+ // bare "Workers" / total meta must still appear.
220
+ expect(text).toContain("Workers");
221
+ expect(text).toContain("0 total");
222
+ });
223
+ });
224
+
225
+ describe("scroll behavior", () => {
226
+ it(
227
+ "wraps content in scrollbox for overflow",
228
+ async () => {
229
+ const workers = [];
230
+ for (let i = 0; i < 20; i++) {
231
+ workers.push(makeWorker({ id: `w${i}`, name: `worker-${i}` }));
232
+ }
233
+ useServiceStore.setState({ workers });
234
+
235
+ const text = await renderWorkersOverview();
236
+
237
+ expect(outputContains(text, "20 total")).toBe(true);
238
+ },
239
+ { timeout: 15_000 }
240
+ );
241
+ });
242
+
243
+ describe("keyboard navigation", () => {
244
+ it("renders without errors (keyboard wired via useKeyboard)", async () => {
245
+ useServiceStore.setState({
246
+ workers: [
247
+ makeWorker({ id: "w1" }),
248
+ makeWorker({ id: "w2" }),
249
+ makeWorker({ id: "w3" }),
250
+ makeWorker({ id: "w4" }),
251
+ makeWorker({ id: "w5" }),
252
+ ],
253
+ });
254
+
255
+ const text = await renderWorkersOverview();
256
+ expect(text).toBeDefined();
257
+ expect(typeof text).toBe("string");
258
+ });
259
+ });
260
+ });