@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.
- package/README.md +247 -0
- package/bin/hoox-tui.js +35 -0
- package/dist/main.js +17594 -0
- package/package.json +72 -0
- package/src/app.tsx +696 -0
- package/src/cli-bridge-test-double.ts +212 -0
- package/src/components/layout/layout.test.tsx +205 -0
- package/src/components/layout/sidebar.tsx +69 -0
- package/src/components/layout/statusbar.test.tsx +423 -0
- package/src/components/layout/statusbar.tsx +348 -0
- package/src/components/shared/animated-border.tsx +58 -0
- package/src/components/shared/chrome.test.tsx +29 -0
- package/src/components/shared/command-palette.tsx +361 -0
- package/src/components/shared/cool-brackets.tsx +75 -0
- package/src/components/shared/crash-screen.tsx +167 -0
- package/src/components/shared/error-boundary.tsx +99 -0
- package/src/components/shared/keybinding-hint.tsx +37 -0
- package/src/components/shared/panel.tsx +61 -0
- package/src/components/shared/quit-modal.tsx +65 -0
- package/src/components/shared/shared.test.tsx +538 -0
- package/src/components/shared/spinner.tsx +85 -0
- package/src/components/shared/status-dot.tsx +54 -0
- package/src/components/shared/view-header.tsx +53 -0
- package/src/components/ui/connection-toasts.test.ts +50 -0
- package/src/components/ui/connection-toasts.ts +215 -0
- package/src/components/ui/dialog.tsx +281 -0
- package/src/components/ui/select.tsx +197 -0
- package/src/components/ui/toast.tsx +185 -0
- package/src/components/views/ai-chat.test.tsx +81 -0
- package/src/components/views/ai-chat.tsx +576 -0
- package/src/components/views/config-editor/action-bar.tsx +102 -0
- package/src/components/views/config-editor/code-editor.tsx +597 -0
- package/src/components/views/config-editor/file-tree.tsx +113 -0
- package/src/components/views/config-editor/types.ts +37 -0
- package/src/components/views/config-editor.test.tsx +465 -0
- package/src/components/views/config-editor.tsx +709 -0
- package/src/components/views/dashboard/alerts-panel.tsx +154 -0
- package/src/components/views/dashboard/auto-repair-panel.tsx +229 -0
- package/src/components/views/dashboard/model-health-section.tsx +276 -0
- package/src/components/views/dashboard/pyne-health-section.tsx +112 -0
- package/src/components/views/dashboard.test.tsx +347 -0
- package/src/components/views/dashboard.tsx +647 -0
- package/src/components/views/db-query.test.tsx +115 -0
- package/src/components/views/db-query.tsx +849 -0
- package/src/components/views/edge-topology.test.tsx +198 -0
- package/src/components/views/edge-topology.tsx +572 -0
- package/src/components/views/kv-viewer.test.tsx +241 -0
- package/src/components/views/kv-viewer.tsx +717 -0
- package/src/components/views/logs-viewer.test.tsx +273 -0
- package/src/components/views/logs-viewer.tsx +616 -0
- package/src/components/views/queue-depth.test.tsx +221 -0
- package/src/components/views/queue-depth.tsx +376 -0
- package/src/components/views/secrets-viewer.test.tsx +65 -0
- package/src/components/views/secrets-viewer.tsx +548 -0
- package/src/components/views/service-manager.test.tsx +358 -0
- package/src/components/views/service-manager.tsx +946 -0
- package/src/components/views/settings.test.tsx +648 -0
- package/src/components/views/settings.tsx +1497 -0
- package/src/components/views/setup-wizard.test.tsx +297 -0
- package/src/components/views/setup-wizard.tsx +1321 -0
- package/src/components/views/trade-monitor.test.tsx +592 -0
- package/src/components/views/trade-monitor.tsx +745 -0
- package/src/components/views/worker-detail.test.tsx +289 -0
- package/src/components/views/worker-detail.tsx +676 -0
- package/src/components/views/worker-settings.test.tsx +43 -0
- package/src/components/views/worker-settings.tsx +563 -0
- package/src/components/views/workers-overview.test.tsx +260 -0
- package/src/components/views/workers-overview.tsx +631 -0
- package/src/hooks/hooks.test.ts +72 -0
- package/src/hooks/index.ts +11 -0
- package/src/hooks/renderer-ref.ts +22 -0
- package/src/hooks/use-keyboard.ts +130 -0
- package/src/hooks/use-polling.ts +73 -0
- package/src/hooks/use-service-data.ts +18 -0
- package/src/main.tsx +117 -0
- package/src/network-test-double.ts +133 -0
- package/src/opentui-augment.d.ts +45 -0
- package/src/opentui-jsx.d.ts +70 -0
- package/src/services/cli-bridge/index.ts +1662 -0
- package/src/services/cli-bridge/standalone.ts +315 -0
- package/src/services/cli-bridge/types.ts +307 -0
- package/src/services/cli-bridge.test.ts +406 -0
- package/src/services/clipboard.test.ts +57 -0
- package/src/services/clipboard.ts +180 -0
- package/src/services/dashboard-settings-loader.ts +31 -0
- package/src/services/dev-log.test.ts +168 -0
- package/src/services/dev-log.ts +194 -0
- package/src/services/hoox-path-service.test.ts +183 -0
- package/src/services/hoox-path-service.ts +185 -0
- package/src/services/tui-connection.test.ts +212 -0
- package/src/services/tui-connection.ts +236 -0
- package/src/services/tui-storage.test.ts +92 -0
- package/src/services/tui-storage.ts +86 -0
- package/src/stores/config-store.test.ts +360 -0
- package/src/stores/service-store.test.ts +1026 -0
- package/src/stores/session-restore.test.ts +134 -0
- package/src/stores/ui-store.test.ts +276 -0
- package/src/test-setup.ts +265 -0
- package/src/test-utils.ts +179 -0
- package/src/types.ts +153 -0
- package/src/utils/colors.test.ts +326 -0
- package/src/utils/formatters.test.ts +236 -0
- package/src/view-registry.test.ts +132 -0
- 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
|
+
});
|