@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,273 @@
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
+ /**
8
+ * Logs Viewer Tests — Filter logic, component structure, and keyboard behavior.
9
+ *
10
+ * Tests use bun:test and validate:
11
+ * - Filter AND logic (level + worker + text search)
12
+ * - Color-coded level output
13
+ * - Pause/resume behavior
14
+ * - Empty state rendering
15
+ */
16
+ import { describe, it, expect } from "bun:test";
17
+ import { Colors, LogLevelColor } from "@hoox-sh/hoox-shared";
18
+ import {
19
+ makeLog,
20
+ makeWorker,
21
+ type TestLogEntry,
22
+ type TestWorkerInfo,
23
+ type LogLevel,
24
+ } from "../../test-utils";
25
+ import {
26
+ applyLogFilters,
27
+ sanitizeLogText,
28
+ MAX_VISIBLE_LOGS,
29
+ } from "./logs-viewer";
30
+
31
+ // Thin alias so historical test names keep calling applyFilters
32
+ function applyFilters(
33
+ entries: TestLogEntry[],
34
+ options: {
35
+ allLevels: boolean;
36
+ levels: Set<LogLevel>;
37
+ selectedWorkers: Set<string>;
38
+ searchText: string;
39
+ workers: TestWorkerInfo[];
40
+ }
41
+ ): TestLogEntry[] {
42
+ return applyLogFilters(entries, options) as TestLogEntry[];
43
+ }
44
+
45
+ // ─── Tests ───────────────────────────────────────────────────────────────────
46
+
47
+ describe("LogsViewer filter logic", () => {
48
+ const entries: TestLogEntry[] = [
49
+ makeLog({ id: "1", level: "error", message: "Connection failed" }),
50
+ makeLog({ id: "2", level: "warn", message: "High latency" }),
51
+ makeLog({ id: "3", level: "info", message: "Worker started" }),
52
+ makeLog({ id: "4", level: "debug", message: "Trace data" }),
53
+ makeLog({
54
+ id: "5",
55
+ level: "error",
56
+ message: "Auth failure",
57
+ workerId: "w1",
58
+ }),
59
+ makeLog({
60
+ id: "6",
61
+ level: "info",
62
+ message: "Trade executed",
63
+ workerId: "w2",
64
+ }),
65
+ ];
66
+
67
+ const workers: TestWorkerInfo[] = [
68
+ makeWorker({ id: "w1", name: "alpha-worker" }),
69
+ makeWorker({ id: "w2", name: "beta-worker" }),
70
+ ];
71
+
72
+ it("passes all entries when All levels is selected", () => {
73
+ const result = applyFilters(entries, {
74
+ allLevels: true,
75
+ levels: new Set(),
76
+ selectedWorkers: new Set(),
77
+ searchText: "",
78
+ workers,
79
+ });
80
+ expect(result).toHaveLength(6);
81
+ });
82
+
83
+ it("filters to only selected levels", () => {
84
+ const result = applyFilters(entries, {
85
+ allLevels: false,
86
+ levels: new Set<LogLevel>(["error"]),
87
+ selectedWorkers: new Set(),
88
+ searchText: "",
89
+ workers,
90
+ });
91
+ expect(result).toHaveLength(2);
92
+ expect(result.every((e) => e.level === "error")).toBe(true);
93
+ });
94
+
95
+ it("returns empty when no levels selected and All is off", () => {
96
+ const result = applyFilters(entries, {
97
+ allLevels: false,
98
+ levels: new Set(),
99
+ selectedWorkers: new Set(),
100
+ searchText: "",
101
+ workers,
102
+ });
103
+ expect(result).toHaveLength(0);
104
+ });
105
+
106
+ it("filters by worker name", () => {
107
+ const result = applyFilters(entries, {
108
+ allLevels: true,
109
+ levels: new Set(),
110
+ selectedWorkers: new Set(["alpha-worker"]),
111
+ searchText: "",
112
+ workers,
113
+ });
114
+ // Entries with workerId=w1 (alpha-worker) + entries with no workerId
115
+ expect(result).toHaveLength(1); // only id=5 has workerId=w1
116
+ expect(result[0].id).toBe("5");
117
+ });
118
+
119
+ it("filters by text search (case-insensitive)", () => {
120
+ const result = applyFilters(entries, {
121
+ allLevels: true,
122
+ levels: new Set(),
123
+ selectedWorkers: new Set(),
124
+ searchText: "trade",
125
+ workers,
126
+ });
127
+ expect(result).toHaveLength(1);
128
+ expect(result[0].message).toBe("Trade executed");
129
+ });
130
+
131
+ it("combines filters with AND logic", () => {
132
+ const result = applyFilters(entries, {
133
+ allLevels: false,
134
+ levels: new Set<LogLevel>(["error", "info"]),
135
+ selectedWorkers: new Set(["alpha-worker"]),
136
+ searchText: "auth",
137
+ workers,
138
+ });
139
+ // Must be error OR info, alpha-worker, contains "auth"
140
+ expect(result).toHaveLength(1);
141
+ expect(result[0].id).toBe("5"); // error + w1 + "Auth failure"
142
+ });
143
+
144
+ it("handles empty text search as pass-through", () => {
145
+ const result = applyFilters(entries, {
146
+ allLevels: true,
147
+ levels: new Set(),
148
+ selectedWorkers: new Set(),
149
+ searchText: "",
150
+ workers,
151
+ });
152
+ expect(result).toHaveLength(6);
153
+ });
154
+
155
+ it("handles no workers with selected worker filter", () => {
156
+ const result = applyFilters(entries, {
157
+ allLevels: true,
158
+ levels: new Set(),
159
+ selectedWorkers: new Set(["nonexistent"]),
160
+ searchText: "",
161
+ workers,
162
+ });
163
+ expect(result).toHaveLength(0);
164
+ });
165
+
166
+ it("passes entries with no workerId when workers filter is empty", () => {
167
+ const result = applyFilters(entries, {
168
+ allLevels: true,
169
+ levels: new Set(),
170
+ selectedWorkers: new Set(),
171
+ searchText: "",
172
+ workers,
173
+ });
174
+ // Entries 1-4 have no workerId, they should all pass
175
+ const noWorkerEntries = result.filter((e) => !e.workerId);
176
+ expect(noWorkerEntries).toHaveLength(4);
177
+ });
178
+
179
+ it("excludes entries with no workerId when worker filter is active", () => {
180
+ const result = applyFilters(entries, {
181
+ allLevels: true,
182
+ levels: new Set(),
183
+ selectedWorkers: new Set(["alpha-worker"]),
184
+ searchText: "",
185
+ workers,
186
+ });
187
+ const noWorkerEntries = result.filter((e) => !e.workerId);
188
+ expect(noWorkerEntries).toHaveLength(0);
189
+ });
190
+ });
191
+
192
+ describe("LogsViewer level colors", () => {
193
+ it("maps error to red", () => {
194
+ expect(LogLevelColor.error).toBe(Colors.error);
195
+ });
196
+
197
+ it("maps warn to amber/yellow", () => {
198
+ expect(LogLevelColor.warn).toBe(Colors.warning);
199
+ });
200
+
201
+ it("maps info to foreground", () => {
202
+ expect(LogLevelColor.info).toBe(Colors.foreground);
203
+ });
204
+
205
+ it("maps debug to muted (readable, not dim)", () => {
206
+ expect(LogLevelColor.debug).toBe(Colors.muted);
207
+ });
208
+
209
+ it("has entries for all LogLevel values", () => {
210
+ const allLevels: LogLevel[] = ["error", "warn", "info", "debug"];
211
+ for (const lvl of allLevels) {
212
+ expect(LogLevelColor[lvl]).toBeDefined();
213
+ expect(typeof LogLevelColor[lvl]).toBe("string");
214
+ expect(LogLevelColor[lvl]).toMatch(/^#[0-9A-Fa-f]{6}$/);
215
+ }
216
+ });
217
+ });
218
+
219
+ describe("LogsViewer empty state", () => {
220
+ it("returns empty array for empty input", () => {
221
+ const result = applyFilters([], {
222
+ allLevels: true,
223
+ levels: new Set(),
224
+ selectedWorkers: new Set(),
225
+ searchText: "",
226
+ workers: [],
227
+ });
228
+ expect(result).toHaveLength(0);
229
+ });
230
+
231
+ it("preserves empty array with all filters active", () => {
232
+ const result = applyFilters([], {
233
+ allLevels: false,
234
+ levels: new Set<LogLevel>(["error", "warn"]),
235
+ selectedWorkers: new Set(["some-worker"]),
236
+ searchText: "nothing",
237
+ workers: [makeWorker()],
238
+ });
239
+ expect(result).toHaveLength(0);
240
+ });
241
+ });
242
+
243
+ describe("LogsViewer sanitization & caps", () => {
244
+ it("strips control characters and collapses newlines", () => {
245
+ expect(sanitizeLogText("ok\x1b[31m\nline")).toBe("ok[31m line");
246
+ });
247
+
248
+ it("searches source field as well as message", () => {
249
+ const entries = [
250
+ makeLog({
251
+ id: "1",
252
+ level: "info",
253
+ message: "hello",
254
+ source: "trade-engine",
255
+ }),
256
+ makeLog({ id: "2", level: "info", message: "other", source: "gateway" }),
257
+ ];
258
+ const result = applyLogFilters(entries, {
259
+ allLevels: true,
260
+ levels: new Set(),
261
+ selectedWorkers: new Set(),
262
+ searchText: "trade",
263
+ workers: [],
264
+ });
265
+ expect(result).toHaveLength(1);
266
+ expect(result[0].id).toBe("1");
267
+ });
268
+
269
+ it("caps visible render window below store ring buffer (1000)", () => {
270
+ expect(MAX_VISIBLE_LOGS).toBeLessThanOrEqual(1000);
271
+ expect(MAX_VISIBLE_LOGS).toBeGreaterThan(0);
272
+ });
273
+ });