@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,378 @@
1
+ /**
2
+ * Zustand store for Zones & Federation panel.
3
+ *
4
+ * Manages zone listing, brick health, individual brick detail,
5
+ * drift reconciliation reports, and brick lifecycle operations.
6
+ */
7
+
8
+ import { create } from "zustand";
9
+ import type { FetchClient } from "@nexus-ai-fs/api-client";
10
+ import { createApiAction, categorizeError } from "./create-api-action.js";
11
+ import { useErrorStore } from "./error-store.js";
12
+ import { useUiStore } from "./ui-store.js";
13
+
14
+ // =============================================================================
15
+ // Types (snake_case matching API wire format)
16
+ // =============================================================================
17
+
18
+ export interface BrickStatusResponse {
19
+ readonly name: string;
20
+ readonly state: string;
21
+ readonly protocol_name: string;
22
+ readonly error: string | null;
23
+ readonly started_at: number | null;
24
+ readonly stopped_at: number | null;
25
+ readonly unmounted_at: number | null;
26
+ }
27
+
28
+ export interface BrickTransitionItem {
29
+ readonly timestamp: number;
30
+ readonly event: string;
31
+ readonly from_state: string;
32
+ readonly to_state: string;
33
+ }
34
+
35
+ export interface BrickDetailResponse extends BrickStatusResponse {
36
+ readonly enabled: boolean;
37
+ readonly depends_on: readonly string[];
38
+ readonly depended_by: readonly string[];
39
+ readonly retry_count: number;
40
+ readonly transitions: readonly BrickTransitionItem[];
41
+ }
42
+
43
+ export interface DriftReportItem {
44
+ readonly brick_name: string;
45
+ readonly spec_state: string;
46
+ readonly actual_state: string;
47
+ readonly action: string;
48
+ readonly detail: string;
49
+ }
50
+
51
+ export interface BricksHealthResponse {
52
+ readonly total: number;
53
+ readonly active: number;
54
+ readonly failed: number;
55
+ readonly bricks: readonly BrickStatusResponse[];
56
+ }
57
+
58
+ export interface DriftReportResponse {
59
+ readonly total_bricks: number;
60
+ readonly drifted: number;
61
+ readonly actions_taken: number;
62
+ readonly errors: number;
63
+ readonly drifts: readonly DriftReportItem[];
64
+ readonly last_reconcile_at: number | null;
65
+ readonly reconcile_count: number;
66
+ }
67
+
68
+ export interface ZoneResponse {
69
+ readonly zone_id: string;
70
+ readonly name: string;
71
+ readonly domain: string | null;
72
+ readonly description: string | null;
73
+ readonly phase: string;
74
+ readonly finalizers: readonly string[];
75
+ readonly is_active: boolean;
76
+ readonly created_at: string;
77
+ readonly updated_at: string;
78
+ readonly limits: Record<string, unknown> | null;
79
+ }
80
+
81
+ interface ZonesListResponse {
82
+ readonly zones: readonly ZoneResponse[];
83
+ readonly total: number;
84
+ }
85
+
86
+ // =============================================================================
87
+ // Tab type
88
+ // =============================================================================
89
+
90
+ export type ZoneTab = "zones" | "bricks" | "drift" | "reindex" | "workspaces" | "mcp" | "cache";
91
+
92
+ // =============================================================================
93
+ // Store
94
+ // =============================================================================
95
+
96
+ export interface ZonesState {
97
+ // Zone list
98
+ readonly zones: readonly ZoneResponse[];
99
+ readonly zonesLoading: boolean;
100
+
101
+ // Brick list (from health endpoint)
102
+ readonly bricksHealth: BricksHealthResponse | null;
103
+ readonly bricks: readonly BrickStatusResponse[];
104
+ readonly selectedIndex: number;
105
+ readonly isLoading: boolean;
106
+ readonly error: string | null;
107
+
108
+ // Active tab
109
+ readonly activeTab: ZoneTab;
110
+
111
+ // Brick detail (extended with spec/dependency info)
112
+ readonly brickDetail: BrickDetailResponse | null;
113
+ readonly detailLoading: boolean;
114
+
115
+ // Drift report (global, not per-brick)
116
+ readonly driftReport: DriftReportResponse | null;
117
+ readonly driftLoading: boolean;
118
+
119
+ // Cache management
120
+ readonly cacheStats: unknown | null;
121
+ readonly cacheStatsLoading: boolean;
122
+ readonly hotFiles: readonly unknown[];
123
+ readonly hotFilesLoading: boolean;
124
+
125
+ // Actions
126
+ readonly fetchZones: (client: FetchClient) => Promise<void>;
127
+ readonly fetchBricks: (client: FetchClient) => Promise<void>;
128
+ readonly fetchBrickDetail: (name: string, client: FetchClient) => Promise<void>;
129
+ readonly fetchDrift: (client: FetchClient) => Promise<void>;
130
+ readonly mountBrick: (name: string, client: FetchClient) => Promise<void>;
131
+ readonly unmountBrick: (name: string, client: FetchClient) => Promise<void>;
132
+ readonly unregisterBrick: (name: string, client: FetchClient) => Promise<void>;
133
+ readonly remountBrick: (name: string, client: FetchClient) => Promise<void>;
134
+ readonly resetBrick: (name: string, client: FetchClient) => Promise<void>;
135
+ readonly fetchCacheStats: (client: FetchClient) => Promise<void>;
136
+ readonly fetchHotFiles: (client: FetchClient) => Promise<void>;
137
+ readonly warmupCache: (paths: readonly string[], client: FetchClient) => Promise<void>;
138
+ readonly setSelectedIndex: (index: number) => void;
139
+ readonly setActiveTab: (tab: ZoneTab) => void;
140
+ }
141
+
142
+ const SOURCE = "zones";
143
+
144
+ export const useZonesStore = create<ZonesState>((set, get) => ({
145
+ zones: [],
146
+ zonesLoading: false,
147
+ bricksHealth: null,
148
+ bricks: [],
149
+ selectedIndex: 0,
150
+ isLoading: false,
151
+ error: null,
152
+ activeTab: "zones",
153
+ brickDetail: null,
154
+ detailLoading: false,
155
+ driftReport: null,
156
+ driftLoading: false,
157
+ cacheStats: null,
158
+ cacheStatsLoading: false,
159
+ hotFiles: [],
160
+ hotFilesLoading: false,
161
+
162
+ // =========================================================================
163
+ // Actions migrated to createApiAction
164
+ // =========================================================================
165
+
166
+ fetchZones: async (client) => {
167
+ set({ zonesLoading: true, error: null });
168
+ try {
169
+ // Use rawRequest to avoid the FetchClient's automatic 3× retry on 503.
170
+ // The /api/zones endpoint returns 503 when DatabaseLocalAuth is not
171
+ // configured, which is expected for API-key-only server setups.
172
+ const raw = await client.rawRequest("GET", "/api/zones");
173
+ if (raw.status === 503) {
174
+ // Auth provider not available — treat as "no zones"
175
+ set({ zones: [], zonesLoading: false });
176
+ return;
177
+ }
178
+ if (!raw.ok) {
179
+ const body = await raw.json().catch(() => ({ detail: `HTTP ${raw.status}` })) as { detail?: string };
180
+ throw new Error(body.detail ?? `HTTP ${raw.status}`);
181
+ }
182
+ const data = (await raw.json()) as { zones?: ZoneResponse[] };
183
+ set({ zones: data.zones ?? [], zonesLoading: false });
184
+ useUiStore.getState().markDataUpdated("zones");
185
+ } catch (err) {
186
+ const message = err instanceof Error ? err.message : "Failed to fetch zones";
187
+ set({ zones: [], zonesLoading: false, error: message });
188
+ useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
189
+ }
190
+ },
191
+
192
+ fetchBricks: createApiAction<ZonesState, [FetchClient]>(set, {
193
+ loadingKey: "isLoading",
194
+ source: SOURCE,
195
+ errorMessage: "Failed to fetch bricks",
196
+ action: async (client) => {
197
+ const response = await client.get<BricksHealthResponse>(
198
+ "/api/v2/bricks/health",
199
+ );
200
+ const bricks = response.bricks ?? [];
201
+ return { bricksHealth: response, bricks };
202
+ },
203
+ }),
204
+
205
+ fetchCacheStats: createApiAction<ZonesState, [FetchClient]>(set, {
206
+ loadingKey: "cacheStatsLoading",
207
+ source: SOURCE,
208
+ errorMessage: "Failed to fetch cache stats",
209
+ action: async (client) => {
210
+ const stats = await client.get<unknown>("/api/v2/cache/stats");
211
+ return { cacheStats: stats };
212
+ },
213
+ }),
214
+
215
+ fetchHotFiles: createApiAction<ZonesState, [FetchClient]>(set, {
216
+ loadingKey: "hotFilesLoading",
217
+ source: SOURCE,
218
+ errorMessage: "Failed to fetch hot files",
219
+ action: async (client) => {
220
+ const response = await client.get<{ files: readonly unknown[] }>("/api/v2/cache/hot-files");
221
+ return { hotFiles: response.files ?? [] };
222
+ },
223
+ }),
224
+
225
+ // =========================================================================
226
+ // Actions with special error-path state — inline with error store integration
227
+ // =========================================================================
228
+
229
+ fetchBrickDetail: async (name, client) => {
230
+ set({ detailLoading: true, error: null });
231
+
232
+ try {
233
+ const detail = await client.get<BrickDetailResponse>(
234
+ `/api/v2/bricks/${encodeURIComponent(name)}`,
235
+ );
236
+ set({ brickDetail: detail, detailLoading: false });
237
+ useUiStore.getState().markDataUpdated("zones");
238
+ } catch (err) {
239
+ const message = err instanceof Error ? err.message : "Failed to fetch brick detail";
240
+ set({
241
+ brickDetail: null,
242
+ detailLoading: false,
243
+ error: message,
244
+ });
245
+ useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
246
+ }
247
+ },
248
+
249
+ fetchDrift: async (client) => {
250
+ set({ driftLoading: true, error: null });
251
+
252
+ try {
253
+ const report = await client.get<DriftReportResponse>(
254
+ "/api/v2/bricks/drift",
255
+ );
256
+ set({ driftReport: report, driftLoading: false });
257
+ useUiStore.getState().markDataUpdated("zones");
258
+ } catch (err) {
259
+ const message = err instanceof Error ? err.message : "Failed to fetch drift report";
260
+ set({
261
+ driftReport: null,
262
+ driftLoading: false,
263
+ error: message,
264
+ });
265
+ useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
266
+ }
267
+ },
268
+
269
+ // =========================================================================
270
+ // Actions without loading keys — inline with error store integration
271
+ // =========================================================================
272
+
273
+ mountBrick: async (name, client) => {
274
+ set({ error: null });
275
+
276
+ try {
277
+ await client.post<void>(
278
+ `/api/v2/bricks/${encodeURIComponent(name)}/mount`,
279
+ {},
280
+ );
281
+ await get().fetchBricks(client);
282
+ } catch (err) {
283
+ const message = err instanceof Error ? err.message : "Failed to mount brick";
284
+ set({ error: message });
285
+ useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
286
+ }
287
+ },
288
+
289
+ unmountBrick: async (name, client) => {
290
+ set({ error: null });
291
+
292
+ try {
293
+ await client.post<void>(
294
+ `/api/v2/bricks/${encodeURIComponent(name)}/unmount`,
295
+ {},
296
+ );
297
+ await get().fetchBricks(client);
298
+ } catch (err) {
299
+ const message = err instanceof Error ? err.message : "Failed to unmount brick";
300
+ set({ error: message });
301
+ useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
302
+ }
303
+ },
304
+
305
+ unregisterBrick: async (name, client) => {
306
+ set({ error: null });
307
+
308
+ try {
309
+ await client.post<void>(
310
+ `/api/v2/bricks/${encodeURIComponent(name)}/unregister`,
311
+ {},
312
+ );
313
+ await get().fetchBricks(client);
314
+ // Clamp selectedIndex and clear stale detail after brick removal
315
+ const { bricks, selectedIndex } = get();
316
+ const clamped = Math.min(selectedIndex, Math.max(0, bricks.length - 1));
317
+ set({ selectedIndex: clamped, brickDetail: null });
318
+ } catch (err) {
319
+ const message = err instanceof Error ? err.message : "Failed to unregister brick";
320
+ set({ error: message });
321
+ useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
322
+ }
323
+ },
324
+
325
+ remountBrick: async (name, client) => {
326
+ set({ error: null });
327
+
328
+ try {
329
+ await client.post<void>(
330
+ `/api/v2/bricks/${encodeURIComponent(name)}/remount`,
331
+ {},
332
+ );
333
+ await get().fetchBricks(client);
334
+ } catch (err) {
335
+ const message = err instanceof Error ? err.message : "Failed to remount brick";
336
+ set({ error: message });
337
+ useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
338
+ }
339
+ },
340
+
341
+ resetBrick: async (name, client) => {
342
+ set({ error: null });
343
+
344
+ try {
345
+ await client.post<void>(
346
+ `/api/v2/bricks/${encodeURIComponent(name)}/reset`,
347
+ {},
348
+ );
349
+ await get().fetchBricks(client);
350
+ } catch (err) {
351
+ const message = err instanceof Error ? err.message : "Failed to reset brick";
352
+ set({ error: message });
353
+ useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
354
+ }
355
+ },
356
+
357
+ warmupCache: async (paths, client) => {
358
+ set({ error: null });
359
+ try {
360
+ await client.post("/api/v2/cache/warmup", { paths });
361
+ } catch (err) {
362
+ const message = err instanceof Error ? err.message : "Failed to warmup cache";
363
+ set({ error: message });
364
+ useErrorStore.getState().pushError({ message, category: categorizeError(message), source: SOURCE });
365
+ }
366
+ },
367
+
368
+ setSelectedIndex: (index) => {
369
+ set({
370
+ selectedIndex: index,
371
+ brickDetail: null,
372
+ });
373
+ },
374
+
375
+ setActiveTab: (tab) => {
376
+ set({ activeTab: tab });
377
+ },
378
+ }));