@nexus-ai-fs/tui 0.9.18

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 (193) hide show
  1. package/README.md +30 -0
  2. package/package.json +48 -0
  3. package/src/app.tsx +349 -0
  4. package/src/index.tsx +137 -0
  5. package/src/opentui-env.d.ts +61 -0
  6. package/src/panels/access/access-panel.tsx +597 -0
  7. package/src/panels/access/alert-list.tsx +77 -0
  8. package/src/panels/access/constraint-creator.tsx +128 -0
  9. package/src/panels/access/constraint-list.tsx +72 -0
  10. package/src/panels/access/credential-list.tsx +68 -0
  11. package/src/panels/access/delegation-chain-view.tsx +110 -0
  12. package/src/panels/access/delegation-completer.tsx +120 -0
  13. package/src/panels/access/delegation-creator.tsx +237 -0
  14. package/src/panels/access/delegation-list.tsx +74 -0
  15. package/src/panels/access/fraud-score-view.tsx +94 -0
  16. package/src/panels/access/manifest-creator.tsx +167 -0
  17. package/src/panels/access/manifest-list.tsx +105 -0
  18. package/src/panels/access/namespace-config-view.tsx +525 -0
  19. package/src/panels/access/permission-checker.tsx +231 -0
  20. package/src/panels/agents/agent-status-view.tsx +196 -0
  21. package/src/panels/agents/agents-panel.tsx +493 -0
  22. package/src/panels/agents/delegation-list.tsx +154 -0
  23. package/src/panels/agents/inbox-view.tsx +96 -0
  24. package/src/panels/agents/trajectories-tab.tsx +40 -0
  25. package/src/panels/api-console/api-console-panel.tsx +189 -0
  26. package/src/panels/api-console/codegen-viewer.tsx +36 -0
  27. package/src/panels/api-console/codegen.ts +112 -0
  28. package/src/panels/api-console/endpoint-list.tsx +57 -0
  29. package/src/panels/api-console/request-builder.tsx +69 -0
  30. package/src/panels/api-console/response-viewer.tsx +54 -0
  31. package/src/panels/connectors/available-tab.tsx +357 -0
  32. package/src/panels/connectors/connector-row.tsx +121 -0
  33. package/src/panels/connectors/connectors-panel.tsx +88 -0
  34. package/src/panels/connectors/error-parser.ts +116 -0
  35. package/src/panels/connectors/mounted-tab.tsx +179 -0
  36. package/src/panels/connectors/skills-tab.tsx +235 -0
  37. package/src/panels/connectors/template-generator.ts +211 -0
  38. package/src/panels/connectors/write-tab.tsx +514 -0
  39. package/src/panels/events/audit-tab.tsx +69 -0
  40. package/src/panels/events/audit-trail.tsx +75 -0
  41. package/src/panels/events/connector-detail.tsx +49 -0
  42. package/src/panels/events/connector-list.tsx +73 -0
  43. package/src/panels/events/connectors-tab.tsx +92 -0
  44. package/src/panels/events/event-replay.tsx +80 -0
  45. package/src/panels/events/events-panel.tsx +414 -0
  46. package/src/panels/events/events-tab.tsx +212 -0
  47. package/src/panels/events/lock-list.tsx +54 -0
  48. package/src/panels/events/locks-tab.tsx +103 -0
  49. package/src/panels/events/mcl-replay.tsx +77 -0
  50. package/src/panels/events/mcl-tab.tsx +83 -0
  51. package/src/panels/events/operations-tab-wrapper.tsx +62 -0
  52. package/src/panels/events/operations-tab.tsx +41 -0
  53. package/src/panels/events/replay-tab.tsx +76 -0
  54. package/src/panels/events/secrets-audit.tsx +64 -0
  55. package/src/panels/events/secrets-tab.tsx +75 -0
  56. package/src/panels/events/subscription-list.tsx +54 -0
  57. package/src/panels/events/subscriptions-tab.tsx +82 -0
  58. package/src/panels/files/file-aspects.tsx +93 -0
  59. package/src/panels/files/file-editor.tsx +160 -0
  60. package/src/panels/files/file-explorer-keybindings.ts +468 -0
  61. package/src/panels/files/file-explorer-panel.tsx +545 -0
  62. package/src/panels/files/file-lineage.tsx +163 -0
  63. package/src/panels/files/file-list-item.tsx +28 -0
  64. package/src/panels/files/file-metadata.tsx +62 -0
  65. package/src/panels/files/file-preview.tsx +108 -0
  66. package/src/panels/files/file-schema.tsx +89 -0
  67. package/src/panels/files/file-tree-node.tsx +44 -0
  68. package/src/panels/files/file-tree.tsx +169 -0
  69. package/src/panels/files/share-links-tab.tsx +33 -0
  70. package/src/panels/files/uploads-tab.tsx +45 -0
  71. package/src/panels/payments/approval-list.tsx +83 -0
  72. package/src/panels/payments/balance-card.tsx +43 -0
  73. package/src/panels/payments/budget-card.tsx +70 -0
  74. package/src/panels/payments/payments-panel.tsx +451 -0
  75. package/src/panels/payments/policy-list.tsx +64 -0
  76. package/src/panels/payments/reservation-list.tsx +78 -0
  77. package/src/panels/payments/transaction-list.tsx +103 -0
  78. package/src/panels/payments/transfer-form.tsx +109 -0
  79. package/src/panels/search/column-search.tsx +79 -0
  80. package/src/panels/search/knowledge-view.tsx +100 -0
  81. package/src/panels/search/memory-list.tsx +197 -0
  82. package/src/panels/search/playbook-list.tsx +77 -0
  83. package/src/panels/search/rlm-answer-view.tsx +105 -0
  84. package/src/panels/search/search-panel.tsx +405 -0
  85. package/src/panels/search/search-results.tsx +116 -0
  86. package/src/panels/stack/stack-panel.tsx +474 -0
  87. package/src/panels/versions/conflicts-tab.tsx +59 -0
  88. package/src/panels/versions/entry-detail.tsx +89 -0
  89. package/src/panels/versions/transaction-actions.tsx +34 -0
  90. package/src/panels/versions/transaction-list.tsx +90 -0
  91. package/src/panels/versions/versions-panel.tsx +276 -0
  92. package/src/panels/workflows/execution-list.tsx +102 -0
  93. package/src/panels/workflows/scheduler-view.tsx +135 -0
  94. package/src/panels/workflows/workflow-list.tsx +88 -0
  95. package/src/panels/workflows/workflows-panel.tsx +295 -0
  96. package/src/panels/zones/brick-detail.tsx +136 -0
  97. package/src/panels/zones/brick-list.tsx +56 -0
  98. package/src/panels/zones/cache-tab.tsx +118 -0
  99. package/src/panels/zones/drift-view.tsx +97 -0
  100. package/src/panels/zones/mcp-mounts-tab.tsx +38 -0
  101. package/src/panels/zones/memories-tab.tsx +37 -0
  102. package/src/panels/zones/reindex-status.tsx +84 -0
  103. package/src/panels/zones/workspaces-tab.tsx +37 -0
  104. package/src/panels/zones/zone-list.tsx +73 -0
  105. package/src/panels/zones/zones-panel.tsx +559 -0
  106. package/src/services/command-runner.ts +303 -0
  107. package/src/shared/accessibility-announcements.ts +44 -0
  108. package/src/shared/action-registry.ts +466 -0
  109. package/src/shared/brick-states.ts +91 -0
  110. package/src/shared/command-palette.ts +35 -0
  111. package/src/shared/components/announcement-bar.tsx +30 -0
  112. package/src/shared/components/app-confirm-dialog.tsx +29 -0
  113. package/src/shared/components/breadcrumb.tsx +21 -0
  114. package/src/shared/components/brick-gate.tsx +60 -0
  115. package/src/shared/components/command-output.tsx +95 -0
  116. package/src/shared/components/command-palette.tsx +97 -0
  117. package/src/shared/components/confirm-dialog.tsx +61 -0
  118. package/src/shared/components/diff-viewer.tsx +219 -0
  119. package/src/shared/components/empty-state.tsx +36 -0
  120. package/src/shared/components/error-bar.tsx +60 -0
  121. package/src/shared/components/error-boundary.tsx +53 -0
  122. package/src/shared/components/help-overlay.tsx +99 -0
  123. package/src/shared/components/identity-switcher.tsx +168 -0
  124. package/src/shared/components/loading-indicator.tsx +40 -0
  125. package/src/shared/components/pagination-bar.tsx +68 -0
  126. package/src/shared/components/pre-connection-screen.tsx +398 -0
  127. package/src/shared/components/scroll-indicator.tsx +46 -0
  128. package/src/shared/components/side-nav-utils.ts +68 -0
  129. package/src/shared/components/side-nav.tsx +287 -0
  130. package/src/shared/components/spinner.tsx +26 -0
  131. package/src/shared/components/status-bar.tsx +117 -0
  132. package/src/shared/components/styled-text.tsx +72 -0
  133. package/src/shared/components/sub-tab-bar-utils.ts +100 -0
  134. package/src/shared/components/sub-tab-bar.tsx +40 -0
  135. package/src/shared/components/tab-bar-utils.ts +36 -0
  136. package/src/shared/components/tab-bar.tsx +50 -0
  137. package/src/shared/components/text-input.tsx +73 -0
  138. package/src/shared/components/tooltip.tsx +53 -0
  139. package/src/shared/components/virtual-list.tsx +93 -0
  140. package/src/shared/components/welcome-screen.tsx +111 -0
  141. package/src/shared/hooks/use-api.ts +10 -0
  142. package/src/shared/hooks/use-brick-available.ts +42 -0
  143. package/src/shared/hooks/use-confirm.ts +66 -0
  144. package/src/shared/hooks/use-connection-state.ts +67 -0
  145. package/src/shared/hooks/use-copy.ts +31 -0
  146. package/src/shared/hooks/use-fresh-server.ts +62 -0
  147. package/src/shared/hooks/use-keyboard.ts +58 -0
  148. package/src/shared/hooks/use-list-navigation.ts +106 -0
  149. package/src/shared/hooks/use-swr.ts +117 -0
  150. package/src/shared/hooks/use-tab-fallback.ts +32 -0
  151. package/src/shared/hooks/use-text-input.ts +113 -0
  152. package/src/shared/hooks/use-visible-tabs.ts +61 -0
  153. package/src/shared/lib/circular-buffer.ts +82 -0
  154. package/src/shared/lib/clipboard.ts +14 -0
  155. package/src/shared/nav-items.ts +73 -0
  156. package/src/shared/navigation.ts +110 -0
  157. package/src/shared/status-breadcrumb.ts +74 -0
  158. package/src/shared/syntax-style.ts +3 -0
  159. package/src/shared/tab-visibility.ts +15 -0
  160. package/src/shared/text-style.ts +23 -0
  161. package/src/shared/theme.ts +179 -0
  162. package/src/shared/utils/format-size.ts +20 -0
  163. package/src/shared/utils/format-text.ts +10 -0
  164. package/src/shared/utils/format-time.ts +72 -0
  165. package/src/shared/utils/lru-cache.ts +75 -0
  166. package/src/stores/access-store-types.ts +154 -0
  167. package/src/stores/access-store.ts +674 -0
  168. package/src/stores/agents-store.ts +404 -0
  169. package/src/stores/announcement-store.ts +46 -0
  170. package/src/stores/api-console-store.ts +476 -0
  171. package/src/stores/connectors-store.ts +434 -0
  172. package/src/stores/create-api-action.ts +140 -0
  173. package/src/stores/delegation-store.ts +300 -0
  174. package/src/stores/error-store.ts +102 -0
  175. package/src/stores/events-store.ts +163 -0
  176. package/src/stores/files-store.ts +630 -0
  177. package/src/stores/first-run-store.ts +34 -0
  178. package/src/stores/global-store.ts +255 -0
  179. package/src/stores/infra-store.ts +461 -0
  180. package/src/stores/knowledge-store.ts +358 -0
  181. package/src/stores/lineage-store.ts +126 -0
  182. package/src/stores/mcp-store.ts +147 -0
  183. package/src/stores/payments-store.ts +545 -0
  184. package/src/stores/search-store-types.ts +155 -0
  185. package/src/stores/search-store.ts +656 -0
  186. package/src/stores/share-link-store.ts +151 -0
  187. package/src/stores/stack-store.ts +352 -0
  188. package/src/stores/ui-store.ts +161 -0
  189. package/src/stores/upload-store.ts +131 -0
  190. package/src/stores/versions-store.ts +355 -0
  191. package/src/stores/workflows-store.ts +402 -0
  192. package/src/stores/workspace-store.ts +185 -0
  193. package/src/stores/zones-store.ts +378 -0
@@ -0,0 +1,466 @@
1
+ import type { PanelId } from "../stores/global-store.js";
2
+
3
+ export interface KeyBinding {
4
+ readonly key: string;
5
+ readonly action: string;
6
+ }
7
+
8
+ export function formatActionHints(bindings: readonly KeyBinding[]): string {
9
+ return bindings
10
+ .map((binding) => binding.action ? `${binding.key}:${binding.action}` : binding.key)
11
+ .join(" ");
12
+ }
13
+
14
+ export const GLOBAL_BINDINGS: readonly KeyBinding[] = [
15
+ { key: "Ctrl+P", action: "Command palette" },
16
+ { key: "1-9,0", action: "Switch panel" },
17
+ { key: "Ctrl+I", action: "Identity switcher" },
18
+ { key: "Ctrl+D", action: "Disconnect" },
19
+ { key: "z", action: "Toggle zoom" },
20
+ { key: "?", action: "Help overlay" },
21
+ { key: "q", action: "Quit" },
22
+ ];
23
+
24
+ export const NAV_BINDINGS: readonly KeyBinding[] = [
25
+ { key: "j/↓", action: "Move down" },
26
+ { key: "k/↑", action: "Move up" },
27
+ { key: "g", action: "Jump to top" },
28
+ { key: "G", action: "Jump to bottom" },
29
+ { key: "Enter", action: "Select/expand" },
30
+ { key: "Tab", action: "Switch pane/tab" },
31
+ { key: "Esc", action: "Cancel/back" },
32
+ ];
33
+
34
+ export const PANEL_BINDINGS: Record<PanelId, readonly KeyBinding[]> = {
35
+ files: [
36
+ { key: "l/→", action: "Expand folder" },
37
+ { key: "h/←", action: "Collapse folder" },
38
+ { key: "d", action: "Delete file" },
39
+ { key: "Shift+N", action: "New directory" },
40
+ { key: "Shift+R", action: "Rename" },
41
+ { key: "e", action: "Edit file" },
42
+ { key: "Shift+E", action: "Create new file" },
43
+ { key: "/", action: "Quick filter" },
44
+ { key: "Ctrl+F", action: "Power search" },
45
+ { key: "v", action: "Toggle visual mode" },
46
+ { key: "Space", action: "Toggle select file" },
47
+ { key: "c", action: "Copy selected" },
48
+ { key: "x", action: "Cut selected" },
49
+ { key: "p", action: "Paste clipboard here" },
50
+ { key: "Shift+P", action: "Paste to path" },
51
+ { key: "Esc", action: "Clear selection / exit mode" },
52
+ ],
53
+ versions: [
54
+ { key: "n", action: "New transaction" },
55
+ { key: "Enter", action: "Commit transaction" },
56
+ { key: "Backspace", action: "Rollback" },
57
+ { key: "v", action: "View diff" },
58
+ { key: "c", action: "Toggle conflicts" },
59
+ { key: "f", action: "Cycle status filter" },
60
+ ],
61
+ agents: [
62
+ { key: "d", action: "Revoke delegation" },
63
+ { key: "r", action: "Refresh" },
64
+ { key: "Shift+W", action: "Warmup agent" },
65
+ { key: "Shift+E", action: "Evict agent" },
66
+ { key: "Shift+V", action: "Verify agent" },
67
+ ],
68
+ zones: [
69
+ { key: "n", action: "Register new" },
70
+ { key: "d", action: "Unregister" },
71
+ { key: "m", action: "Mount brick" },
72
+ { key: "u", action: "Unmount brick" },
73
+ { key: "x", action: "Reset brick" },
74
+ { key: "r", action: "Remount" },
75
+ ],
76
+ access: [
77
+ { key: "n", action: "New delegation" },
78
+ { key: "Shift+X", action: "Revoke manifest" },
79
+ { key: "x", action: "Revoke credential" },
80
+ { key: "s", action: "Suspend agent" },
81
+ { key: "o", action: "Complete delegation" },
82
+ { key: "v", action: "View chain" },
83
+ { key: "p", action: "Permission check" },
84
+ { key: "f", action: "Cycle filter" },
85
+ ],
86
+ payments: [
87
+ { key: "n", action: "New policy" },
88
+ { key: "d", action: "Delete policy" },
89
+ { key: "t", action: "Transfer funds" },
90
+ { key: "c", action: "Commit reservation" },
91
+ { key: "x", action: "Release reservation" },
92
+ { key: "a", action: "Affordability check" },
93
+ { key: "i", action: "Integrity check" },
94
+ { key: "]", action: "Next page" },
95
+ { key: "[", action: "Previous page" },
96
+ ],
97
+ search: [
98
+ { key: "/", action: "Search" },
99
+ { key: "m", action: "Cycle mode" },
100
+ { key: "n", action: "Create memory" },
101
+ { key: "u", action: "Update memory" },
102
+ { key: "d", action: "Delete" },
103
+ { key: "v", action: "View diff" },
104
+ ],
105
+ workflows: [
106
+ { key: "e", action: "Execute workflow" },
107
+ { key: "d", action: "Delete workflow" },
108
+ { key: "p", action: "Toggle enabled" },
109
+ ],
110
+ infrastructure: [
111
+ { key: "d", action: "Delete subscription" },
112
+ { key: "t", action: "Test subscription" },
113
+ { key: "r", action: "Reconnect SSE" },
114
+ { key: "c", action: "Clear events" },
115
+ { key: "f", action: "Filter by type" },
116
+ { key: "s", action: "Search filter" },
117
+ ],
118
+ console: [
119
+ { key: ":", action: "Command mode" },
120
+ { key: "Enter", action: "Execute request" },
121
+ { key: "/", action: "Filter endpoints" },
122
+ ],
123
+ };
124
+
125
+ interface ClipboardSummary {
126
+ readonly paths: readonly string[];
127
+ readonly operation: "copy" | "cut";
128
+ }
129
+
130
+ export function getFilesFooterBindings({
131
+ inputMode,
132
+ activeTab,
133
+ catalogAvailable,
134
+ visualMode,
135
+ selectionCount,
136
+ clipboard,
137
+ }: {
138
+ readonly inputMode: "none" | "mkdir" | "rename" | "filter" | "search" | "paste-dest" | "create";
139
+ readonly activeTab: "explorer" | "shareLinks" | "uploads";
140
+ readonly catalogAvailable: boolean;
141
+ readonly visualMode: boolean;
142
+ readonly selectionCount: number;
143
+ readonly clipboard: ClipboardSummary | null;
144
+ }): readonly KeyBinding[] {
145
+ if (inputMode === "filter") {
146
+ return [
147
+ { key: "Type", action: "filter" },
148
+ { key: "Enter", action: "keep filter" },
149
+ { key: "Escape", action: "clear" },
150
+ ];
151
+ }
152
+ if (inputMode === "search") {
153
+ return [
154
+ { key: "g", action: "pattern=glob" },
155
+ { key: "r", action: "pattern=grep" },
156
+ { key: "plain", action: "deep search" },
157
+ { key: "Enter", action: "search" },
158
+ { key: "Esc", action: "cancel" },
159
+ ];
160
+ }
161
+ if (inputMode === "paste-dest") {
162
+ return [
163
+ { key: "Enter path", action: "" },
164
+ { key: "Enter", action: "paste" },
165
+ { key: "Escape", action: "cancel" },
166
+ ];
167
+ }
168
+ if (inputMode !== "none") {
169
+ return [
170
+ { key: "Type name", action: "" },
171
+ { key: "Enter", action: "confirm" },
172
+ { key: "Escape", action: "cancel" },
173
+ { key: "Backspace", action: "delete" },
174
+ ];
175
+ }
176
+
177
+ if (activeTab === "explorer") {
178
+ const bindings: KeyBinding[] = [
179
+ { key: "j/k", action: "nav" },
180
+ { key: "l/Enter", action: "expand" },
181
+ { key: "h", action: "collapse" },
182
+ ];
183
+
184
+ if (visualMode) {
185
+ bindings.push(
186
+ { key: "v", action: "exit visual" },
187
+ { key: "c", action: "copy" },
188
+ { key: "x", action: "cut" },
189
+ );
190
+ } else if (selectionCount > 0) {
191
+ bindings.push(
192
+ { key: `${selectionCount} selected`, action: "" },
193
+ { key: "c", action: "copy" },
194
+ { key: "x", action: "cut" },
195
+ { key: "Esc", action: "clear" },
196
+ );
197
+ } else {
198
+ bindings.push(
199
+ { key: "/", action: "filter" },
200
+ { key: "Ctrl+F", action: "search" },
201
+ { key: "v", action: "visual" },
202
+ { key: "Space", action: "select" },
203
+ );
204
+ }
205
+
206
+ if (clipboard) {
207
+ bindings.push(
208
+ {
209
+ key: "p",
210
+ action: `paste ${clipboard.paths.length} ${clipboard.operation === "cut" ? "cut" : "copied"}`,
211
+ },
212
+ { key: "P", action: "paste to path" },
213
+ );
214
+ }
215
+
216
+ bindings.push(
217
+ { key: "d", action: "del" },
218
+ { key: "N", action: "mkdir" },
219
+ { key: "R", action: "rename" },
220
+ { key: "e", action: "edit" },
221
+ { key: "E", action: "new file" },
222
+ );
223
+
224
+ if (catalogAvailable) {
225
+ bindings.push({ key: "m/a/s", action: "meta" });
226
+ }
227
+
228
+ bindings.push({ key: "?", action: "help" });
229
+ return bindings;
230
+ }
231
+
232
+ if (activeTab === "shareLinks") {
233
+ return [
234
+ { key: "j/k", action: "navigate" },
235
+ { key: "x", action: "revoke" },
236
+ { key: "r", action: "refresh" },
237
+ { key: "Tab", action: "switch tab" },
238
+ { key: "q", action: "quit" },
239
+ ];
240
+ }
241
+
242
+ return [
243
+ { key: "j/k", action: "navigate" },
244
+ { key: "Tab", action: "switch tab" },
245
+ { key: "q", action: "quit" },
246
+ ];
247
+ }
248
+
249
+ export function getEventsFooterBindings({
250
+ filterMode,
251
+ activeTab,
252
+ connectorDetailView,
253
+ }: {
254
+ readonly filterMode: "none" | "type" | "search" | "mcl_urn" | "mcl_aspect" | "acquire_path" | "secrets_filter" | "replay_filter";
255
+ readonly activeTab: "events" | "mcl" | "replay" | "operations" | "audit" | "connectors" | "subscriptions" | "locks" | "secrets";
256
+ readonly connectorDetailView: boolean;
257
+ }): readonly KeyBinding[] {
258
+ if (filterMode !== "none") {
259
+ return [
260
+ { key: "Type value", action: "" },
261
+ { key: "Enter", action: "apply" },
262
+ { key: "Escape", action: "cancel" },
263
+ { key: "Backspace", action: "delete" },
264
+ ];
265
+ }
266
+
267
+ switch (activeTab) {
268
+ case "events":
269
+ return [
270
+ { key: "j/k", action: "navigate" },
271
+ { key: "Enter", action: "expand" },
272
+ { key: "f", action: "filter type" },
273
+ { key: "s", action: "search" },
274
+ { key: "c", action: "clear" },
275
+ { key: "r", action: "reconnect" },
276
+ { key: "y", action: "copy" },
277
+ { key: "Tab", action: "switch" },
278
+ ];
279
+ case "mcl":
280
+ return [
281
+ { key: "u", action: "filter URN" },
282
+ { key: "n", action: "filter aspect" },
283
+ { key: "r", action: "refresh" },
284
+ { key: "Tab", action: "switch tab" },
285
+ ];
286
+ case "replay":
287
+ return [
288
+ { key: "f", action: "filter event type" },
289
+ { key: "r", action: "refresh" },
290
+ { key: "Tab", action: "switch tab" },
291
+ ];
292
+ case "connectors":
293
+ return connectorDetailView
294
+ ? [
295
+ { key: "Escape", action: "back" },
296
+ { key: "r", action: "refresh" },
297
+ { key: "Tab", action: "switch tab" },
298
+ ]
299
+ : [
300
+ { key: "j/k", action: "navigate" },
301
+ { key: "Enter", action: "capabilities" },
302
+ { key: "r", action: "refresh" },
303
+ { key: "Tab", action: "switch tab" },
304
+ ];
305
+ case "subscriptions":
306
+ return [
307
+ { key: "j/k", action: "navigate" },
308
+ { key: "d", action: "delete" },
309
+ { key: "t", action: "test" },
310
+ { key: "r", action: "refresh" },
311
+ { key: "Tab", action: "switch tab" },
312
+ ];
313
+ case "locks":
314
+ return [
315
+ { key: "j/k", action: "navigate" },
316
+ { key: "n", action: "acquire" },
317
+ { key: "d", action: "release" },
318
+ { key: "e", action: "extend" },
319
+ { key: "r", action: "refresh" },
320
+ { key: "Tab", action: "switch tab" },
321
+ ];
322
+ case "secrets":
323
+ return [
324
+ { key: "/", action: "filter" },
325
+ { key: "r", action: "refresh" },
326
+ { key: "Tab", action: "switch tab" },
327
+ ];
328
+ case "audit":
329
+ return [
330
+ { key: "j/k", action: "navigate" },
331
+ { key: "m", action: "load more" },
332
+ { key: "r", action: "refresh" },
333
+ { key: "Tab", action: "switch tab" },
334
+ ];
335
+ default:
336
+ return [
337
+ { key: "j/k", action: "navigate" },
338
+ { key: "r", action: "refresh" },
339
+ { key: "Tab", action: "switch tab" },
340
+ ];
341
+ }
342
+ }
343
+
344
+ export function getPaymentsFooterBindings({
345
+ showTransfer,
346
+ activeTab,
347
+ }: {
348
+ readonly showTransfer: boolean;
349
+ readonly activeTab: "balance" | "reservations" | "transactions" | "policies" | "approvals";
350
+ }): readonly KeyBinding[] {
351
+ if (showTransfer) {
352
+ return [
353
+ { key: "Tab", action: "next field" },
354
+ { key: "Enter", action: "submit" },
355
+ { key: "Escape", action: "cancel" },
356
+ ];
357
+ }
358
+
359
+ switch (activeTab) {
360
+ case "transactions":
361
+ return [
362
+ { key: "j/k", action: "navigate" },
363
+ { key: "]", action: "next" },
364
+ { key: "[", action: "prev" },
365
+ { key: "i", action: "verify integrity" },
366
+ { key: "y", action: "copy" },
367
+ { key: "Tab", action: "switch tab" },
368
+ { key: "r", action: "refresh" },
369
+ ];
370
+ case "policies":
371
+ return [
372
+ { key: "j/k", action: "navigate" },
373
+ { key: "Tab", action: "switch tab" },
374
+ { key: "Shift+N", action: "new" },
375
+ { key: "d", action: "delete" },
376
+ { key: "b", action: "budget" },
377
+ { key: "r", action: "refresh" },
378
+ { key: "q", action: "quit" },
379
+ ];
380
+ case "balance":
381
+ return [
382
+ { key: "Tab", action: "switch tab" },
383
+ { key: "t", action: "transfer" },
384
+ { key: "a", action: "afford check" },
385
+ { key: "r", action: "refresh" },
386
+ { key: "q", action: "quit" },
387
+ ];
388
+ case "approvals":
389
+ return [
390
+ { key: "j/k", action: "navigate" },
391
+ { key: "n", action: "new request" },
392
+ { key: "a", action: "approve" },
393
+ { key: "x", action: "reject" },
394
+ { key: "Tab", action: "switch tab" },
395
+ { key: "r", action: "refresh" },
396
+ { key: "q", action: "quit" },
397
+ ];
398
+ default:
399
+ return [
400
+ { key: "j/k", action: "navigate" },
401
+ { key: "Tab", action: "switch tab" },
402
+ { key: "t", action: "transfer" },
403
+ { key: "r", action: "refresh" },
404
+ { key: "c", action: "commit" },
405
+ { key: "x", action: "release" },
406
+ { key: "q", action: "quit" },
407
+ ];
408
+ }
409
+ }
410
+
411
+ export function getVersionsFooterBindings({
412
+ txnFilterMode,
413
+ }: {
414
+ readonly txnFilterMode: boolean;
415
+ }): readonly KeyBinding[] {
416
+ if (txnFilterMode) {
417
+ return [
418
+ { key: "Type", action: "filter" },
419
+ { key: "Enter", action: "apply" },
420
+ { key: "Escape", action: "clear" },
421
+ ];
422
+ }
423
+
424
+ return [
425
+ { key: "j/k", action: "navigate" },
426
+ { key: "n", action: "new txn" },
427
+ { key: "Enter", action: "commit" },
428
+ { key: "Backspace", action: "rollback" },
429
+ { key: "f", action: "filter" },
430
+ { key: "/", action: "search" },
431
+ { key: "v", action: "diff" },
432
+ { key: "c", action: "conflicts" },
433
+ { key: "y", action: "copy" },
434
+ { key: "q", action: "quit" },
435
+ ];
436
+ }
437
+
438
+ export function getWorkflowsFooterBindings({
439
+ activeTab,
440
+ selectedExecution,
441
+ }: {
442
+ readonly activeTab: "workflows" | "executions" | "scheduler";
443
+ readonly selectedExecution: boolean;
444
+ }): readonly KeyBinding[] {
445
+ if (activeTab === "executions" && selectedExecution) {
446
+ return [
447
+ { key: "j/k", action: "navigate" },
448
+ { key: "Tab", action: "switch tab" },
449
+ { key: "Enter", action: "detail" },
450
+ { key: "Esc", action: "close" },
451
+ { key: "r", action: "refresh" },
452
+ { key: "q", action: "quit" },
453
+ ];
454
+ }
455
+
456
+ return [
457
+ { key: "j/k", action: "navigate" },
458
+ { key: "Tab", action: "switch tab" },
459
+ { key: "e", action: "execute" },
460
+ { key: "d", action: "delete" },
461
+ { key: "p", action: "enable/disable" },
462
+ { key: "r", action: "refresh" },
463
+ { key: "Enter", action: "detail" },
464
+ { key: "q", action: "quit" },
465
+ ];
466
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Brick FSM state constants and state-aware action logic.
3
+ *
4
+ * Single source of truth for brick lifecycle states — matches the backend
5
+ * BrickState enum values exactly (see brick_lifecycle.py).
6
+ */
7
+
8
+ import { brickStateColor } from "./theme.js";
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // State constants (match backend BrickState enum values)
12
+ // ---------------------------------------------------------------------------
13
+
14
+ export const BRICK_STATE = {
15
+ REGISTERED: "registered",
16
+ STARTING: "starting",
17
+ ACTIVE: "active",
18
+ STOPPING: "stopping",
19
+ UNMOUNTED: "unmounted",
20
+ UNREGISTERED: "unregistered",
21
+ FAILED: "failed",
22
+ } as const;
23
+
24
+ export type BrickStateValue = (typeof BRICK_STATE)[keyof typeof BRICK_STATE];
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Actions the TUI can trigger
28
+ // ---------------------------------------------------------------------------
29
+
30
+ export type BrickAction = "mount" | "unmount" | "remount" | "reset" | "unregister";
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // State → allowed actions mapping
34
+ // ---------------------------------------------------------------------------
35
+
36
+ const STATE_ACTIONS: Readonly<Record<string, readonly BrickAction[]>> = {
37
+ [BRICK_STATE.REGISTERED]: ["mount"],
38
+ [BRICK_STATE.STARTING]: [],
39
+ [BRICK_STATE.ACTIVE]: ["unmount"],
40
+ [BRICK_STATE.STOPPING]: [],
41
+ [BRICK_STATE.UNMOUNTED]: ["mount", "remount", "unregister"],
42
+ [BRICK_STATE.UNREGISTERED]: [],
43
+ [BRICK_STATE.FAILED]: ["reset"],
44
+ };
45
+
46
+ /**
47
+ * Returns the set of lifecycle actions valid for a given brick state.
48
+ *
49
+ * Pure function — safe to call from render and easy to test exhaustively.
50
+ */
51
+ export function allowedActionsForState(state: string): ReadonlySet<BrickAction> {
52
+ const actions = STATE_ACTIONS[state];
53
+ return new Set(actions ?? []);
54
+ }
55
+
56
+ // ---------------------------------------------------------------------------
57
+ // State → display indicator
58
+ // ---------------------------------------------------------------------------
59
+
60
+ /**
61
+ * Short state indicator for the brick list sidebar.
62
+ * Matches all 7 backend FSM states.
63
+ */
64
+ export function stateIndicator(state: string): string {
65
+ switch (state) {
66
+ case BRICK_STATE.REGISTERED:
67
+ return "[RG]";
68
+ case BRICK_STATE.STARTING:
69
+ return "[..]";
70
+ case BRICK_STATE.ACTIVE:
71
+ return "[ON]";
72
+ case BRICK_STATE.STOPPING:
73
+ return "[..]";
74
+ case BRICK_STATE.UNMOUNTED:
75
+ return "[UM]";
76
+ case BRICK_STATE.UNREGISTERED:
77
+ return "[--]";
78
+ case BRICK_STATE.FAILED:
79
+ return "[!!]";
80
+ default:
81
+ return "[??]";
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Semantic color for a brick state indicator.
87
+ * Returns a color string from the theme for use with foregroundColor prop.
88
+ */
89
+ export function stateColor(state: string): string {
90
+ return brickStateColor[state] ?? "gray";
91
+ }
@@ -0,0 +1,35 @@
1
+ export interface CommandPaletteItem {
2
+ readonly id: string;
3
+ readonly title: string;
4
+ readonly section: string;
5
+ readonly hint?: string;
6
+ readonly keywords?: readonly string[];
7
+ readonly run: () => void;
8
+ }
9
+
10
+ export function filterCommandPaletteItems(
11
+ items: readonly CommandPaletteItem[],
12
+ query: string,
13
+ ): readonly CommandPaletteItem[] {
14
+ const needle = query.trim().toLowerCase();
15
+ if (!needle) return items;
16
+
17
+ return items
18
+ .map((item) => ({
19
+ item,
20
+ haystack: [
21
+ item.title,
22
+ item.section,
23
+ item.hint ?? "",
24
+ ...(item.keywords ?? []),
25
+ ].join(" ").toLowerCase(),
26
+ }))
27
+ .filter(({ haystack }) => haystack.includes(needle))
28
+ .sort((a, b) => {
29
+ const aStarts = a.item.title.toLowerCase().startsWith(needle) ? 0 : 1;
30
+ const bStarts = b.item.title.toLowerCase().startsWith(needle) ? 0 : 1;
31
+ if (aStarts !== bStarts) return aStarts - bStarts;
32
+ return a.item.title.localeCompare(b.item.title);
33
+ })
34
+ .map(({ item }) => item);
35
+ }
@@ -0,0 +1,30 @@
1
+ import React, { useEffect } from "react";
2
+ import { useAnnouncementStore } from "../../stores/announcement-store.js";
3
+ import { palette, statusColor } from "../theme.js";
4
+
5
+ const ANNOUNCEMENT_COLORS = {
6
+ info: palette.faint,
7
+ success: statusColor.healthy,
8
+ error: palette.error,
9
+ } as const;
10
+
11
+ export function AnnouncementBar(): React.ReactNode {
12
+ const message = useAnnouncementStore((s) => s.message);
13
+ const level = useAnnouncementStore((s) => s.level);
14
+ const sequence = useAnnouncementStore((s) => s.sequence);
15
+ const clear = useAnnouncementStore((s) => s.clear);
16
+
17
+ useEffect(() => {
18
+ if (!message) return;
19
+ const timer = setTimeout(() => clear(), 4000);
20
+ return () => clearTimeout(timer);
21
+ }, [message, sequence, clear]);
22
+
23
+ return (
24
+ <box height={1} width="100%">
25
+ {message
26
+ ? <text foregroundColor={ANNOUNCEMENT_COLORS[level]}>{message}</text>
27
+ : <text> </text>}
28
+ </box>
29
+ );
30
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * App-level ConfirmDialog that reads from the imperative useConfirmStore.
3
+ *
4
+ * Mounted once in App — panels call `useConfirmStore.getState().confirm()`
5
+ * to show the dialog imperatively.
6
+ *
7
+ * @see Issue #3066 Architecture Decision 3A
8
+ */
9
+
10
+ import React from "react";
11
+ import { useConfirmStore } from "../hooks/use-confirm.js";
12
+ import { ConfirmDialog } from "./confirm-dialog.js";
13
+
14
+ export function AppConfirmDialog(): React.ReactNode {
15
+ const visible = useConfirmStore((s) => s.visible);
16
+ const title = useConfirmStore((s) => s.title);
17
+ const message = useConfirmStore((s) => s.message);
18
+ const resolve = useConfirmStore((s) => s.resolve);
19
+
20
+ return (
21
+ <ConfirmDialog
22
+ visible={visible}
23
+ title={title}
24
+ message={message}
25
+ onConfirm={() => resolve?.(true)}
26
+ onCancel={() => resolve?.(false)}
27
+ />
28
+ );
29
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Path breadcrumb navigation bar.
3
+ */
4
+
5
+ import React from "react";
6
+
7
+ interface BreadcrumbProps {
8
+ readonly path: string;
9
+ readonly onNavigate: (path: string) => void;
10
+ }
11
+
12
+ export function Breadcrumb({ path }: BreadcrumbProps): React.ReactNode {
13
+ const segments = path.split("/").filter(Boolean);
14
+ const display = segments.length === 0 ? "/" : `/ ${segments.join(" / ")}`;
15
+
16
+ return (
17
+ <box height={1} width="100%">
18
+ <text>{display}</text>
19
+ </box>
20
+ );
21
+ }