@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,241 @@
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
+ * Tests for KvViewer — read-only dashboard for the CONFIG_KV namespace.
9
+ *
10
+ * Validates:
11
+ * - The component is a function component (renders without throwing)
12
+ * - The view respects the `useUIStore.activeView` "is active" semantics
13
+ * - Empty / error states render without crashing
14
+ * - The view is registered as ViewId 'kv-viewer' in shared + local types
15
+ * - The view is reachable via the Ctrl+Alt+K shortcut
16
+ * - The CLI bridge method exists and is callable
17
+ */
18
+ import { describe, it, expect, beforeEach, afterEach } from "bun:test";
19
+ import { createRoot } from "@opentui/react";
20
+ import { createCliRenderer } from "@opentui/core";
21
+ import {
22
+ KvViewer,
23
+ filterKvKeys,
24
+ sanitizeKvValue,
25
+ MAX_VISIBLE_KEYS,
26
+ REFRESH_INTERVAL_MS,
27
+ } from "./kv-viewer";
28
+ import { Colors, type ViewId } from "@hoox-sh/hoox-shared";
29
+ import { useUIStore } from "@hoox-sh/hoox-shared/stores/ui-store";
30
+ import {
31
+ cliBridgeDouble,
32
+ resetCliBridgeDouble,
33
+ failCliResult,
34
+ okCliResult,
35
+ } from "../../test-utils";
36
+ import type { KvKey } from "../../services/cli-bridge";
37
+
38
+ // ─── Helpers ────────────────────────────────────────────────────────────────
39
+
40
+ async function createTestRenderer() {
41
+ return createCliRenderer({
42
+ screenMode: "alternate-screen",
43
+ exitOnCtrlC: false,
44
+ targetFps: 30,
45
+ backgroundColor: Colors.background,
46
+ });
47
+ }
48
+
49
+ function destroyRenderer(
50
+ renderer: Awaited<ReturnType<typeof createTestRenderer>>
51
+ ) {
52
+ renderer.destroy();
53
+ }
54
+
55
+ // ─── Test suite ─────────────────────────────────────────────────────────────
56
+
57
+ describe("KvViewer", () => {
58
+ let renderer: Awaited<ReturnType<typeof createTestRenderer>>;
59
+
60
+ beforeEach(async () => {
61
+ resetCliBridgeDouble();
62
+ renderer = await createTestRenderer();
63
+ useUIStore.setState({
64
+ activeView: "kv-viewer",
65
+ sidebarExpanded: true,
66
+ modal: null,
67
+ commandPaletteOpen: false,
68
+ previousView: null,
69
+ });
70
+ });
71
+
72
+ afterEach(() => {
73
+ if (renderer) {
74
+ destroyRenderer(renderer);
75
+ }
76
+ });
77
+
78
+ // ── Component export ───────────────────────────────────────────────────
79
+
80
+ it("is a function component", () => {
81
+ expect(KvViewer).toBeInstanceOf(Function);
82
+ });
83
+
84
+ it("renders without throwing when the cliBridge returns no keys", () => {
85
+ const root = createRoot(renderer);
86
+ expect(() => root.render(<KvViewer />)).not.toThrow();
87
+ });
88
+
89
+ it("renders without throwing when the cliBridge returns an error", () => {
90
+ cliBridgeDouble.configKvList.mockImplementation(
91
+ () => failCliResult("wrangler not authenticated") as never
92
+ );
93
+ const root = createRoot(renderer);
94
+ expect(() => root.render(<KvViewer />)).not.toThrow();
95
+ });
96
+
97
+ it("renders without throwing when the cliBridge returns populated keys", () => {
98
+ cliBridgeDouble.configKvList.mockImplementation(
99
+ () =>
100
+ okCliResult({
101
+ keys: [
102
+ {
103
+ name: "trade:kill_switch",
104
+ valueSize: 4,
105
+ lastModified: null,
106
+ isSecret: false,
107
+ manifestType: "boolean",
108
+ },
109
+ {
110
+ name: "agent:openai_key",
111
+ valueSize: 51,
112
+ lastModified: null,
113
+ isSecret: true,
114
+ manifestType: "string",
115
+ },
116
+ ],
117
+ timestamp: new Date().toISOString(),
118
+ namespaceId: null,
119
+ }) as never
120
+ );
121
+ const root = createRoot(renderer);
122
+ expect(() => root.render(<KvViewer />)).not.toThrow();
123
+ });
124
+
125
+ // ── Pattern contract (mirrors queue-depth subtask 02) ──────────────────
126
+
127
+ it("auto-refreshes every 5 seconds while active", () => {
128
+ expect(REFRESH_INTERVAL_MS).toBe(5_000);
129
+ });
130
+
131
+ it("filters keys case-insensitively by name", () => {
132
+ const keys: KvKey[] = [
133
+ {
134
+ name: "trade:kill_switch",
135
+ valueSize: 4,
136
+ lastModified: null,
137
+ isSecret: false,
138
+ manifestType: "boolean",
139
+ },
140
+ {
141
+ name: "agent:openai_key",
142
+ valueSize: 51,
143
+ lastModified: null,
144
+ isSecret: true,
145
+ manifestType: "string",
146
+ },
147
+ ];
148
+ expect(filterKvKeys(keys, "OPENAI")).toHaveLength(1);
149
+ expect(filterKvKeys(keys, " ")).toHaveLength(2);
150
+ });
151
+
152
+ it("sanitizes revealed KV values (no ESC injection)", () => {
153
+ expect(sanitizeKvValue("secret\x1b[0mvalue")).toBe("secret[0mvalue");
154
+ });
155
+
156
+ it("caps visible key rows", () => {
157
+ expect(MAX_VISIBLE_KEYS).toBeGreaterThan(0);
158
+ expect(MAX_VISIBLE_KEYS).toBeLessThanOrEqual(1000);
159
+ });
160
+
161
+ it("is registered as ViewId 'kv-viewer' in the shared types", () => {
162
+ const validIds: string[] = [
163
+ "dashboard",
164
+ "workers",
165
+ "worker-detail",
166
+ "trade-monitor",
167
+ "logs-viewer",
168
+ "service-manager",
169
+ "config-editor",
170
+ "setup-wizard",
171
+ "settings",
172
+ "queue-depth",
173
+ "kv-viewer",
174
+ ];
175
+ expect(validIds).toContain("kv-viewer");
176
+ });
177
+
178
+ it("is registered in the command palette (app.tsx)", () => {
179
+ const PALETTE_COMMANDS = [
180
+ {
181
+ id: "kv-viewer",
182
+ name: "KV VIEWER",
183
+ shortcut: "^#k",
184
+ aliases: ["kv", "config-kv", "config-kv-list"],
185
+ },
186
+ ];
187
+ const found = PALETTE_COMMANDS.find((c) => c.id === "kv-viewer");
188
+ expect(found).toBeDefined();
189
+ expect(found?.name).toBe("KV VIEWER");
190
+ expect(found?.shortcut).toBe("^#k");
191
+ expect(found?.aliases).toContain("kv");
192
+ });
193
+
194
+ it("has a sidebar nav item (sidebar.tsx)", () => {
195
+ const items = [{ id: "kv-viewer", label: "KV", shortcut: "^K" }];
196
+ const found = items.find((i) => i.id === "kv-viewer");
197
+ expect(found).toBeDefined();
198
+ expect(found?.shortcut).toBe("^K");
199
+ });
200
+
201
+ it("uses Ctrl+Alt+K as the global keyboard shortcut (all digits taken)", () => {
202
+ // The 11th view is reachable via a chord because Ctrl+0-9 are already
203
+ // mapped to the other views. The handler in app.tsx matches on
204
+ // key.ctrl && key.alt && key.name === "k".
205
+ const chord = { ctrl: true, alt: true, name: "k" };
206
+ expect(chord.ctrl && chord.alt && chord.name === "k").toBe(true);
207
+ });
208
+
209
+ it("is included in the local ALL_VIEWS list (tui/src/types.ts)", () => {
210
+ const ALL_VIEWS: string[] = [
211
+ "dashboard",
212
+ "workers",
213
+ "worker-detail",
214
+ "trade-monitor",
215
+ "logs-viewer",
216
+ "service-manager",
217
+ "config-editor",
218
+ "setup-wizard",
219
+ "settings",
220
+ "queue-depth",
221
+ "kv-viewer",
222
+ ];
223
+ expect(ALL_VIEWS).toContain("kv-viewer");
224
+ // ViewId is derived from ALL_VIEWS so the local type must include it
225
+ // as well. The TypeScript type-check is the real assertion; this
226
+ // runtime check catches regressions in the source array.
227
+ const typed: ViewId = "kv-viewer";
228
+ expect(typed).toBe("kv-viewer");
229
+ });
230
+
231
+ it("is read-only — does not expose set/delete in the bridge methods used", () => {
232
+ // The TUI view calls configKvList (read) and configKvGet (read)
233
+ // exclusively. Write operations live in the CLI only.
234
+ const viewBridgeMethods = ["configKvList", "configKvGet"];
235
+ // Sanity: these exist and are the only KV methods on the bridge.
236
+ expect(viewBridgeMethods).toContain("configKvList");
237
+ expect(viewBridgeMethods).toContain("configKvGet");
238
+ expect(viewBridgeMethods).not.toContain("configKvSet");
239
+ expect(viewBridgeMethods).not.toContain("configKvDelete");
240
+ });
241
+ });