claw-dashboard 2.1.0 → 2.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 (90) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/docs/API.md +1 -2
  3. package/index.js +32 -42
  4. package/package.json +23 -12
  5. package/src/auto-save.js +11 -5
  6. package/src/cli/export-snapshot.js +3 -1
  7. package/src/cli/import-snapshot.js +3 -1
  8. package/src/config.js +9 -15
  9. package/src/errors.js +0 -9
  10. package/src/security.js +6 -2
  11. package/src/snapshot.js +73 -26
  12. package/src/web-server.js +7 -10
  13. package/src/widgets/builtin-widgets.js +0 -94
  14. package/src/widgets/index.js +0 -1
  15. package/.c8rc.json +0 -38
  16. package/.dockerignore +0 -68
  17. package/.github/dependabot.yml +0 -45
  18. package/.github/pull_request_template.md +0 -39
  19. package/.github/workflows/ci.yml +0 -147
  20. package/.github/workflows/release.yml +0 -40
  21. package/.github/workflows/security.yml +0 -84
  22. package/.husky/pre-commit +0 -1
  23. package/.planning/codebase/ARCHITECTURE.md +0 -206
  24. package/.planning/codebase/INTEGRATIONS.md +0 -150
  25. package/.planning/codebase/STACK.md +0 -122
  26. package/.planning/codebase/STRUCTURE.md +0 -201
  27. package/CONTRIBUTING.md +0 -378
  28. package/Dockerfile +0 -54
  29. package/FEATURES.md +0 -307
  30. package/TODO.md +0 -28
  31. package/ai.openclaw.dashboard.plist +0 -35
  32. package/build-cjs.js +0 -127
  33. package/cjs-shim.js +0 -13
  34. package/dist/clawdash +0 -1729
  35. package/dist/clawdash.meta.json +0 -2236
  36. package/dist/widgets.cjs +0 -6511
  37. package/docker-compose.yml +0 -67
  38. package/esbuild.config.js +0 -158
  39. package/eslint.config.js +0 -56
  40. package/examples/plugins/README.md +0 -122
  41. package/examples/plugins/api-status/index.js +0 -294
  42. package/examples/plugins/api-status/plugin.json +0 -19
  43. package/examples/plugins/hello-world/index.js +0 -104
  44. package/examples/plugins/hello-world/plugin.json +0 -15
  45. package/examples/plugins/system-metrics-chart/index.js +0 -339
  46. package/examples/plugins/system-metrics-chart/plugin.json +0 -18
  47. package/examples/plugins/weather-widget/index.js +0 -136
  48. package/examples/plugins/weather-widget/plugin.json +0 -19
  49. package/index.cjs +0 -23005
  50. package/jest.config.js +0 -11
  51. package/scripts/release.js +0 -595
  52. package/src/database.js +0 -734
  53. package/tests/alerts.test.js +0 -437
  54. package/tests/auto-save.test.js +0 -529
  55. package/tests/cache.test.js +0 -317
  56. package/tests/cli.test.js +0 -351
  57. package/tests/command-palette.test.js +0 -320
  58. package/tests/config-processor.test.js +0 -452
  59. package/tests/config-validator.test.js +0 -710
  60. package/tests/config-watcher.test.js +0 -594
  61. package/tests/config.test.js +0 -755
  62. package/tests/database.test.js +0 -438
  63. package/tests/errors.test.js +0 -189
  64. package/tests/example-plugins.test.js +0 -624
  65. package/tests/integration.test.js +0 -1321
  66. package/tests/loading-states.test.js +0 -300
  67. package/tests/manifest-validation-on-load.test.js +0 -671
  68. package/tests/performance-monitor.test.js +0 -190
  69. package/tests/plugin-api-rate-limit.test.js +0 -302
  70. package/tests/plugin-errors.test.js +0 -311
  71. package/tests/plugin-lifecycle-e2e.test.js +0 -1036
  72. package/tests/plugin-reload.test.js +0 -764
  73. package/tests/rate-limiter.test.js +0 -539
  74. package/tests/retry.test.js +0 -308
  75. package/tests/security.test.js +0 -411
  76. package/tests/settings-modal.test.js +0 -226
  77. package/tests/theme-selector.test.js +0 -57
  78. package/tests/utils.js +0 -242
  79. package/tests/utils.test.js +0 -317
  80. package/tests/validate-plugin-cli.test.js +0 -359
  81. package/tests/validation.test.js +0 -837
  82. package/tests/web-server.test.js +0 -646
  83. package/tests/widget-arrange-mode.test.js +0 -183
  84. package/tests/widget-config-hot-reload.test.js +0 -465
  85. package/tests/widget-dependency.test.js +0 -591
  86. package/tests/widget-error-boundary.test.js +0 -749
  87. package/tests/widget-error-isolation.test.js +0 -469
  88. package/tests/widget-integration.test.js +0 -1147
  89. package/tests/widget-refresh-intervals.test.js +0 -284
  90. package/tests/worker-pool.test.js +0 -522
@@ -1,320 +0,0 @@
1
- /**
2
- * Tests for Command Palette Lifecycle
3
- * Verifies the command palette modal behavior and navigation guards
4
- */
5
-
6
- import { jest } from '@jest/globals';
7
-
8
- describe('Command Palette Lifecycle', () => {
9
- describe('_commandPaletteClosing flag behavior', () => {
10
- test('should block navigation when command palette is closing', () => {
11
- // Simulate the state during command palette close transition
12
- const state = {
13
- _commandPaletteClosing: false,
14
- w: {
15
- commandPaletteBox: null,
16
- commandPaletteInput: null,
17
- commandPaletteList: null,
18
- },
19
- isModalActive: false,
20
- };
21
-
22
- // Simulate navigation guard logic
23
- const shouldBlockNavigation = () => {
24
- return !!(state._commandPaletteClosing || (state.w.commandPaletteBox && state.w.commandPaletteInput && state.w.commandPaletteInput.focused));
25
- };
26
-
27
- // Initially, navigation should NOT be blocked
28
- expect(shouldBlockNavigation()).toBe(false);
29
-
30
- // Simulate opening command palette
31
- state.w.commandPaletteBox = { destroy: jest.fn() };
32
- state.w.commandPaletteInput = { focused: true };
33
- state.w.commandPaletteList = { focus: jest.fn() };
34
- state.isModalActive = true;
35
- expect(shouldBlockNavigation()).toBe(true); // Blocked while command palette is open
36
-
37
- // Simulate closeCommandPalette() being called - the critical fix
38
- // _commandPaletteClosing is set synchronously BEFORE the async transition
39
- state._commandPaletteClosing = true;
40
- state.isModalActive = false;
41
- // commandPaletteInput still exists during transition
42
- expect(shouldBlockNavigation()).toBe(true); // Still blocked during transition!
43
-
44
- // After transition completes
45
- state._commandPaletteClosing = false;
46
- state.w.commandPaletteInput = null;
47
- state.w.commandPaletteList = null;
48
- state.w.commandPaletteBox = null;
49
- expect(shouldBlockNavigation()).toBe(false); // Now navigation is allowed
50
- });
51
-
52
- test('should handle rapid open/close cycles', () => {
53
- const state = {
54
- _commandPaletteClosing: false,
55
- w: {
56
- commandPaletteBox: null,
57
- commandPaletteInput: null,
58
- commandPaletteList: null,
59
- },
60
- isModalActive: false,
61
- };
62
-
63
- const shouldBlockNavigation = () => {
64
- return !!(state._commandPaletteClosing || (state.w.commandPaletteBox && state.w.commandPaletteInput && state.w.commandPaletteInput.focused));
65
- };
66
-
67
- // Rapid open/close cycle
68
- // Open
69
- state.w.commandPaletteBox = {};
70
- state.w.commandPaletteInput = { focused: true };
71
- state.isModalActive = true;
72
- expect(shouldBlockNavigation()).toBe(true);
73
-
74
- // Close starts
75
- state._commandPaletteClosing = true;
76
- state.isModalActive = false;
77
- expect(shouldBlockNavigation()).toBe(true);
78
-
79
- // Close completes
80
- state._commandPaletteClosing = false;
81
- state.w.commandPaletteInput = null;
82
- state.w.commandPaletteBox = null;
83
- expect(shouldBlockNavigation()).toBe(false);
84
-
85
- // Rapidly re-open before any navigation
86
- state.w.commandPaletteBox = {};
87
- state.w.commandPaletteInput = { focused: true };
88
- state.isModalActive = true;
89
- expect(shouldBlockNavigation()).toBe(true);
90
-
91
- // Close again
92
- state._commandPaletteClosing = true;
93
- state.isModalActive = false;
94
- expect(shouldBlockNavigation()).toBe(true);
95
-
96
- // Final cleanup
97
- state._commandPaletteClosing = false;
98
- state.w.commandPaletteInput = null;
99
- state.w.commandPaletteBox = null;
100
- expect(shouldBlockNavigation()).toBe(false);
101
- });
102
-
103
- test('should use try/finally pattern to ensure flag is always cleared', () => {
104
- // This test verifies the code pattern used in closeCommandPalette()
105
- // The flag should be cleared even if an error occurs
106
-
107
- const state = {
108
- _commandPaletteClosing: false,
109
- };
110
-
111
- // Simulate the closeCommandPalette pattern
112
- const simulateClose = async (shouldThrow = false) => {
113
- state._commandPaletteClosing = true;
114
- try {
115
- if (shouldThrow) {
116
- throw new Error('Transition failed');
117
- }
118
- // Simulate async transition
119
- await new Promise(resolve => setTimeout(resolve, 10));
120
- } finally {
121
- state._commandPaletteClosing = false;
122
- }
123
- };
124
-
125
- // Normal close
126
- return simulateClose(false).then(() => {
127
- expect(state._commandPaletteClosing).toBe(false);
128
-
129
- // Close with error
130
- return simulateClose(true).catch(() => {
131
- // Error is expected
132
- });
133
- }).then(() => {
134
- // Flag should still be cleared even after error
135
- expect(state._commandPaletteClosing).toBe(false);
136
- });
137
- });
138
- });
139
-
140
- describe('isModalActive state management', () => {
141
- test('should be set false synchronously when closeCommandPalette starts', () => {
142
- // This test verifies the fix for the race condition where
143
- // isModalActive was only set after the async transition completed
144
-
145
- const state = {
146
- isModalActive: false,
147
- _commandPaletteClosing: false,
148
- };
149
-
150
- // Simulate closeCommandPalette() start
151
- const closeCommandPaletteStart = () => {
152
- state._commandPaletteClosing = true;
153
- state.isModalActive = false;
154
- // At this point, even though transition hasn't completed,
155
- // isModalActive is already false
156
- };
157
-
158
- // Open command palette first
159
- state.isModalActive = true;
160
- expect(state.isModalActive).toBe(true);
161
-
162
- // Close starts - isModalActive should be false immediately
163
- closeCommandPaletteStart();
164
- expect(state.isModalActive).toBe(false);
165
- expect(state._commandPaletteClosing).toBe(true);
166
- });
167
- });
168
-
169
- describe('Command filtering', () => {
170
- test('should filter commands by name', () => {
171
- const commands = [
172
- { name: 'Toggle Help', shortcut: '?', category: 'Navigation' },
173
- { name: 'Open Settings', shortcut: 's', category: 'Navigation' },
174
- { name: 'Force Refresh', shortcut: 'r', category: 'Display' },
175
- ];
176
-
177
- const filterCommands = (query) => {
178
- if (!query) return commands;
179
- return commands.filter(cmd =>
180
- cmd.name.toLowerCase().includes(query) ||
181
- cmd.shortcut.toLowerCase().includes(query) ||
182
- cmd.category.toLowerCase().includes(query)
183
- );
184
- };
185
-
186
- // Filter by name
187
- expect(filterCommands('help')).toHaveLength(1);
188
- expect(filterCommands('help')[0].name).toBe('Toggle Help');
189
-
190
- // Filter by shortcut (exact match)
191
- expect(filterCommands('r')).toHaveLength(1);
192
- expect(filterCommands('r')[0].name).toBe('Force Refresh');
193
-
194
- // Filter by category
195
- expect(filterCommands('display')).toHaveLength(1);
196
- expect(filterCommands('display')[0].name).toBe('Force Refresh');
197
-
198
- // No results
199
- expect(filterCommands('xyz')).toHaveLength(0);
200
-
201
- // Empty query returns all
202
- expect(filterCommands('')).toHaveLength(3);
203
- });
204
-
205
- test('should handle case-insensitive filtering', () => {
206
- const commands = [
207
- { name: 'Toggle CPU Widget', shortcut: '1', category: 'Widgets' },
208
- { name: 'Toggle Memory Widget', shortcut: '2', category: 'Widgets' },
209
- ];
210
-
211
- const filterCommands = (query) => {
212
- if (!query) return commands;
213
- const lowerQuery = query.toLowerCase();
214
- return commands.filter(cmd =>
215
- cmd.name.toLowerCase().includes(lowerQuery) ||
216
- cmd.shortcut.toLowerCase().includes(lowerQuery) ||
217
- cmd.category.toLowerCase().includes(lowerQuery)
218
- );
219
- };
220
-
221
- // Case insensitive
222
- expect(filterCommands('CPU')).toHaveLength(1);
223
- expect(filterCommands('cpu')).toHaveLength(1);
224
- expect(filterCommands('Cpu')).toHaveLength(1);
225
- });
226
- });
227
-
228
- describe('Navigation guard logic', () => {
229
- test('should check _commandPaletteClosing first for performance', () => {
230
- // The guard uses || with _commandPaletteClosing first
231
- // This means if _commandPaletteClosing is true, we don't even check commandPaletteInput.focused
232
- // which could cause issues with destroyed widgets
233
-
234
- let inputFocusedCallCount = 0;
235
- const state = {
236
- _commandPaletteClosing: false,
237
- w: {
238
- commandPaletteBox: {},
239
- commandPaletteInput: {
240
- get focused() {
241
- inputFocusedCallCount++;
242
- return true;
243
- }
244
- }
245
- }
246
- };
247
-
248
- const shouldBlockNavigation = () => {
249
- return !!(state._commandPaletteClosing || (state.w.commandPaletteBox && state.w.commandPaletteInput && state.w.commandPaletteInput.focused));
250
- };
251
-
252
- // When _commandPaletteClosing is false, commandPaletteInput.focused is checked
253
- inputFocusedCallCount = 0;
254
- state._commandPaletteClosing = false;
255
- shouldBlockNavigation();
256
- expect(inputFocusedCallCount).toBe(1);
257
-
258
- // When _commandPaletteClosing is true, commandPaletteInput.focused is NOT checked (short-circuit)
259
- inputFocusedCallCount = 0;
260
- state._commandPaletteClosing = true;
261
- shouldBlockNavigation();
262
- expect(inputFocusedCallCount).toBe(0);
263
- });
264
-
265
- test('should handle null commandPaletteInput gracefully', () => {
266
- const state = {
267
- _commandPaletteClosing: false,
268
- w: {
269
- commandPaletteBox: null,
270
- commandPaletteInput: null,
271
- }
272
- };
273
-
274
- const shouldBlockNavigation = () => {
275
- return !!(state._commandPaletteClosing || (state.w.commandPaletteBox && state.w.commandPaletteInput && state.w.commandPaletteInput.focused));
276
- };
277
-
278
- // Should not throw when commandPaletteInput is null
279
- expect(() => shouldBlockNavigation()).not.toThrow();
280
- expect(shouldBlockNavigation()).toBe(false);
281
-
282
- // Should still work when _commandPaletteClosing is true
283
- state._commandPaletteClosing = true;
284
- expect(shouldBlockNavigation()).toBe(true);
285
- });
286
- });
287
-
288
- describe('Command execution', () => {
289
- test('should execute command action after closing modal', () => {
290
- let actionExecuted = false;
291
- const commands = [
292
- { name: 'Test Command', shortcut: 't', action: () => { actionExecuted = true; } }
293
- ];
294
-
295
- // Simulate command execution
296
- const executeCommand = (cmd) => {
297
- if (cmd.action && typeof cmd.action === 'function') {
298
- cmd.action();
299
- }
300
- };
301
-
302
- executeCommand(commands[0]);
303
- expect(actionExecuted).toBe(true);
304
- });
305
-
306
- test('should handle commands without actions gracefully', () => {
307
- const commands = [
308
- { name: 'No Action', shortcut: 'n' }
309
- ];
310
-
311
- // Should not throw when action is undefined
312
- expect(() => {
313
- const cmd = commands[0];
314
- if (cmd.action && typeof cmd.action === 'function') {
315
- cmd.action();
316
- }
317
- }).not.toThrow();
318
- });
319
- });
320
- });