@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,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 HOOX · HOOX · jango-blockchained (hoox-sh)
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, afterEach } from "bun:test";
|
|
7
|
+
import { testRender } from "@opentui/react/test-utils";
|
|
8
|
+
import * as path from "path";
|
|
9
|
+
import * as fs from "fs";
|
|
10
|
+
import * as os from "os";
|
|
11
|
+
import { useUIStore } from "@hoox-sh/hoox-shared/stores/ui-store";
|
|
12
|
+
import { EdgeTopology, MAX_FLOWS_PREVIEW } from "./edge-topology";
|
|
13
|
+
|
|
14
|
+
const mockMetadata = {
|
|
15
|
+
workers: {
|
|
16
|
+
"workers/hoox-worker": {
|
|
17
|
+
description: "Gateway worker",
|
|
18
|
+
category: "gateway",
|
|
19
|
+
tags: ["webhook", "auth"],
|
|
20
|
+
isPublic: true,
|
|
21
|
+
smartPlacement: true,
|
|
22
|
+
},
|
|
23
|
+
"workers/trade-worker": {
|
|
24
|
+
description: "Execution worker",
|
|
25
|
+
category: "execution",
|
|
26
|
+
tags: ["trading"],
|
|
27
|
+
isPublic: false,
|
|
28
|
+
smartPlacement: true,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
infrastructure: {
|
|
32
|
+
"d1:trade-data-db": {
|
|
33
|
+
label: "trade-data-db (D1)",
|
|
34
|
+
category: "database",
|
|
35
|
+
description: "Cloudflare D1 SQLite database.",
|
|
36
|
+
tags: ["d1", "sqlite"],
|
|
37
|
+
bindingName: "DB",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
dataFlows: [
|
|
41
|
+
{
|
|
42
|
+
source: "workers/hoox-worker",
|
|
43
|
+
target: "workers/trade-worker",
|
|
44
|
+
description: "Trading signal",
|
|
45
|
+
flowType: "signal-ingestion",
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
communities: [
|
|
49
|
+
{
|
|
50
|
+
id: "signal-pipeline",
|
|
51
|
+
label: "Signal Pipeline",
|
|
52
|
+
description: "The core trading signal flow",
|
|
53
|
+
nodeIds: [
|
|
54
|
+
"workspace:workers/hoox-worker",
|
|
55
|
+
"workspace:workers/trade-worker",
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/** Write fixture metadata and point HOOX_GRAPH_METADATA_PATH at it (no fs mock). */
|
|
62
|
+
function withGraphFixture(content: string | null): {
|
|
63
|
+
cleanup: () => void;
|
|
64
|
+
path: string | null;
|
|
65
|
+
} {
|
|
66
|
+
const prev = process.env.HOOX_GRAPH_METADATA_PATH;
|
|
67
|
+
if (content === null) {
|
|
68
|
+
// Point at a path that does not exist so load fails
|
|
69
|
+
const missing = path.join(
|
|
70
|
+
os.tmpdir(),
|
|
71
|
+
`hoox-graph-missing-${Date.now()}.json`
|
|
72
|
+
);
|
|
73
|
+
process.env.HOOX_GRAPH_METADATA_PATH = missing;
|
|
74
|
+
return {
|
|
75
|
+
path: missing,
|
|
76
|
+
cleanup: () => {
|
|
77
|
+
if (prev === undefined) delete process.env.HOOX_GRAPH_METADATA_PATH;
|
|
78
|
+
else process.env.HOOX_GRAPH_METADATA_PATH = prev;
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const fixturePath = path.join(
|
|
84
|
+
os.tmpdir(),
|
|
85
|
+
`hoox-graph-meta-${Date.now()}-${Math.random().toString(36).slice(2)}.json`
|
|
86
|
+
);
|
|
87
|
+
fs.writeFileSync(fixturePath, content, "utf-8");
|
|
88
|
+
process.env.HOOX_GRAPH_METADATA_PATH = fixturePath;
|
|
89
|
+
return {
|
|
90
|
+
path: fixturePath,
|
|
91
|
+
cleanup: () => {
|
|
92
|
+
try {
|
|
93
|
+
fs.unlinkSync(fixturePath);
|
|
94
|
+
} catch {
|
|
95
|
+
// ignore
|
|
96
|
+
}
|
|
97
|
+
if (prev === undefined) delete process.env.HOOX_GRAPH_METADATA_PATH;
|
|
98
|
+
else process.env.HOOX_GRAPH_METADATA_PATH = prev;
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
describe("EdgeTopology View", () => {
|
|
104
|
+
let cleanup: (() => void) | null = null;
|
|
105
|
+
|
|
106
|
+
afterEach(() => {
|
|
107
|
+
cleanup?.();
|
|
108
|
+
cleanup = null;
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("caps flow preview for large graphs", () => {
|
|
112
|
+
expect(MAX_FLOWS_PREVIEW).toBe(15);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("renders the topology view with workers and infrastructure", async () => {
|
|
116
|
+
useUIStore.setState({ activeView: "edge-topology" });
|
|
117
|
+
const fixture = withGraphFixture(JSON.stringify(mockMetadata));
|
|
118
|
+
cleanup = fixture.cleanup;
|
|
119
|
+
|
|
120
|
+
const { captureCharFrame, renderOnce } = await testRender(
|
|
121
|
+
<EdgeTopology />,
|
|
122
|
+
{
|
|
123
|
+
width: 80,
|
|
124
|
+
height: 24,
|
|
125
|
+
exitOnCtrlC: false,
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
await renderOnce();
|
|
129
|
+
const output = captureCharFrame();
|
|
130
|
+
|
|
131
|
+
expect(output).toContain("EDGE TOPOLOGY");
|
|
132
|
+
expect(output).toContain("hoox");
|
|
133
|
+
expect(output).toContain("trade-worker");
|
|
134
|
+
expect(output).toContain("trade-");
|
|
135
|
+
expect(output).toContain("data-db");
|
|
136
|
+
expect(output).toContain("SYSTEM FLOWS");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("handles file read errors gracefully", async () => {
|
|
140
|
+
// Point at a path that exists as a directory so existsSync is true... no,
|
|
141
|
+
// resolveGraphMetadataPath returns null if file missing, which shows a
|
|
142
|
+
// different message. Force read throw via invalid JSON file permissions
|
|
143
|
+
// or a path that exists but is unreadable — use a file with valid path
|
|
144
|
+
// that we delete after setting env, then override with a path that exists
|
|
145
|
+
// but read fails: write then chmod 0, or use invalid path after exists
|
|
146
|
+
// check by writing then unlinking and... better: env path to existing
|
|
147
|
+
// file that is not valid JSON? That's parse error. "File not found" needs
|
|
148
|
+
// throw from readFileSync.
|
|
149
|
+
//
|
|
150
|
+
// Use a path that exists for existsSync via a directory named like the
|
|
151
|
+
// file is wrong. Simplest: fixture path that exists, then delete before
|
|
152
|
+
// render so existsSync fails → "graph-metadata.json not found".
|
|
153
|
+
// For "File not found" string from previous test, update assertion to the
|
|
154
|
+
// real error branch message.
|
|
155
|
+
const fixture = withGraphFixture(null);
|
|
156
|
+
cleanup = fixture.cleanup;
|
|
157
|
+
|
|
158
|
+
const { captureCharFrame, renderOnce } = await testRender(
|
|
159
|
+
<EdgeTopology />,
|
|
160
|
+
{
|
|
161
|
+
width: 80,
|
|
162
|
+
height: 24,
|
|
163
|
+
exitOnCtrlC: false,
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
await renderOnce();
|
|
167
|
+
const output = captureCharFrame();
|
|
168
|
+
|
|
169
|
+
expect(output).toContain("graph-metadata.json not found");
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("resolves graph-metadata via path helpers without throwing", () => {
|
|
173
|
+
const candidates = [
|
|
174
|
+
path.resolve(process.cwd(), "graph-metadata.json"),
|
|
175
|
+
path.resolve(process.cwd(), "../../graph-metadata.json"),
|
|
176
|
+
];
|
|
177
|
+
expect(candidates.some((c) => typeof c === "string")).toBe(true);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("surfaces parse/read errors when the override file is invalid", async () => {
|
|
181
|
+
// Empty path that exists but is not valid JSON → catch branch
|
|
182
|
+
const fixture = withGraphFixture("{ not valid json");
|
|
183
|
+
cleanup = fixture.cleanup;
|
|
184
|
+
|
|
185
|
+
const { captureCharFrame, renderOnce } = await testRender(
|
|
186
|
+
<EdgeTopology />,
|
|
187
|
+
{
|
|
188
|
+
width: 80,
|
|
189
|
+
height: 24,
|
|
190
|
+
exitOnCtrlC: false,
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
await renderOnce();
|
|
194
|
+
const output = captureCharFrame();
|
|
195
|
+
|
|
196
|
+
expect(output).toContain("Error loading topology data:");
|
|
197
|
+
});
|
|
198
|
+
});
|