@plures/praxis 1.1.0 → 1.1.2

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.
@@ -1,506 +1,506 @@
1
- <!--
2
- Advanced Todo App - Svelte 5 Component
3
-
4
- This component demonstrates:
5
- - Svelte 5 runes integration with Praxis
6
- - History state pattern with undo/redo
7
- - Time-travel debugging
8
- - Reactive derived values
9
- -->
10
-
11
- <script lang="ts">
12
- import { usePraxisEngine } from '@plures/praxis/svelte';
13
- import {
14
- createTodoEngine,
15
- getFilteredTodos,
16
- getStats,
17
- AddTodo,
18
- ToggleTodo,
19
- RemoveTodo,
20
- SetFilter,
21
- CompleteAll,
22
- ClearCompleted,
23
- type TodoItem,
24
- } from './index';
25
-
26
- // Create engine with history enabled
27
- const engine = createTodoEngine();
28
- const {
29
- context,
30
- dispatch,
31
- undo,
32
- redo,
33
- canUndo,
34
- canRedo,
35
- snapshots,
36
- historyIndex,
37
- goToSnapshot,
38
- } = usePraxisEngine(engine, {
39
- enableHistory: true,
40
- maxHistorySize: 50,
41
- });
42
-
43
- // Local UI state
44
- let newTodoText = $state('');
45
- let showDebugger = $state(false);
46
-
47
- // Derived reactive values
48
- $: filteredTodos = getFilteredTodos(context.todos, context.filter);
49
- $: stats = getStats(context.todos);
50
- $: allCompleted = stats.total > 0 && stats.completed === stats.total;
51
-
52
- // Event handlers
53
- function handleAddTodo() {
54
- if (newTodoText.trim()) {
55
- dispatch([AddTodo.create({ text: newTodoText })], 'Add Todo');
56
- newTodoText = '';
57
- }
58
- }
59
-
60
- function handleToggle(id: string) {
61
- dispatch([ToggleTodo.create({ id })], 'Toggle Todo');
62
- }
63
-
64
- function handleRemove(id: string) {
65
- dispatch([RemoveTodo.create({ id })], 'Remove Todo');
66
- }
67
-
68
- function handleFilterChange(filter: 'all' | 'active' | 'completed') {
69
- dispatch([SetFilter.create({ filter })], 'Change Filter');
70
- }
71
-
72
- function handleCompleteAll() {
73
- dispatch([CompleteAll.create({})], 'Complete All');
74
- }
75
-
76
- function handleClearCompleted() {
77
- if (stats.completed > 0) {
78
- dispatch([ClearCompleted.create({})], 'Clear Completed');
79
- }
80
- }
81
-
82
- // Keyboard shortcuts
83
- function handleKeydown(event: KeyboardEvent) {
84
- if (event.ctrlKey || event.metaKey) {
85
- if (event.key === 'z' && canUndo) {
86
- event.preventDefault();
87
- undo();
88
- } else if (event.key === 'y' && canRedo) {
89
- event.preventDefault();
90
- redo();
91
- } else if (event.key === 'd') {
92
- event.preventDefault();
93
- showDebugger = !showDebugger;
94
- }
95
- }
96
- }
97
- </script>
98
-
99
- <svelte:window onkeydown={handleKeydown} />
100
-
101
- <div class="todo-app">
102
- <!-- Header -->
103
- <header class="header">
104
- <h1>Praxis Todos</h1>
105
- <div class="history-controls">
106
- <button onclick={undo} disabled={!canUndo} title="Undo (Ctrl+Z)">
107
- ⟲ Undo
108
- </button>
109
- <span class="history-info">
110
- {historyIndex + 1} / {snapshots.length}
111
- </span>
112
- <button onclick={redo} disabled={!canRedo} title="Redo (Ctrl+Y)">
113
- ⟳ Redo
114
- </button>
115
- <button
116
- onclick={() => showDebugger = !showDebugger}
117
- class:active={showDebugger}
118
- title="Toggle Debugger (Ctrl+D)"
119
- >
120
- 🐛 Debug
121
- </button>
122
- </div>
123
- </header>
124
-
125
- <!-- Main Content -->
126
- <main class="main">
127
- <!-- Input -->
128
- <div class="input-section">
129
- <input
130
- type="text"
131
- bind:value={newTodoText}
132
- onkeypress={(e) => e.key === 'Enter' && handleAddTodo()}
133
- placeholder="What needs to be done?"
134
- class="new-todo"
135
- />
136
- <button onclick={handleAddTodo} class="add-btn">
137
- Add
138
- </button>
139
- </div>
140
-
141
- {#if stats.total > 0}
142
- <!-- Filters -->
143
- <div class="filters">
144
- <button
145
- class:active={context.filter === 'all'}
146
- onclick={() => handleFilterChange('all')}
147
- >
148
- All ({stats.total})
149
- </button>
150
- <button
151
- class:active={context.filter === 'active'}
152
- onclick={() => handleFilterChange('active')}
153
- >
154
- Active ({stats.active})
155
- </button>
156
- <button
157
- class:active={context.filter === 'completed'}
158
- onclick={() => handleFilterChange('completed')}
159
- >
160
- Completed ({stats.completed})
161
- </button>
162
- </div>
163
-
164
- <!-- Todo List -->
165
- <ul class="todo-list">
166
- {#each filteredTodos as todo (todo.id)}
167
- <li class="todo-item" class:completed={todo.completed}>
168
- <input
169
- type="checkbox"
170
- checked={todo.completed}
171
- onchange={() => handleToggle(todo.id)}
172
- />
173
- <span class="todo-text">{todo.text}</span>
174
- <button onclick={() => handleRemove(todo.id)} class="remove-btn">
175
-
176
- </button>
177
- </li>
178
- {/each}
179
- </ul>
180
-
181
- <!-- Footer -->
182
- <footer class="footer">
183
- <span class="stats">
184
- {stats.active} {stats.active === 1 ? 'item' : 'items'} left
185
- </span>
186
-
187
- <div class="actions">
188
- <button onclick={handleCompleteAll} disabled={allCompleted}>
189
- Complete All
190
- </button>
191
- <button onclick={handleClearCompleted} disabled={stats.completed === 0}>
192
- Clear Completed
193
- </button>
194
- </div>
195
- </footer>
196
- {:else}
197
- <div class="empty-state">
198
- <p>No todos yet. Add one above!</p>
199
- </div>
200
- {/if}
201
- </main>
202
-
203
- <!-- Time-Travel Debugger -->
204
- {#if showDebugger}
205
- <aside class="debugger">
206
- <h2>Time-Travel Debugger</h2>
207
-
208
- <div class="timeline">
209
- {#each snapshots as snapshot, index}
210
- <button
211
- class="snapshot"
212
- class:active={index === historyIndex}
213
- onclick={() => goToSnapshot(index)}
214
- title={`Snapshot ${index + 1}`}
215
- >
216
- <div class="snapshot-time">
217
- {new Date(snapshot.timestamp).toLocaleTimeString()}
218
- </div>
219
- <div class="snapshot-events">
220
- {snapshot.events.length} events
221
- </div>
222
- </button>
223
- {/each}
224
- </div>
225
-
226
- <div class="state-viewer">
227
- <h3>Current State</h3>
228
- <pre>{JSON.stringify(context, null, 2)}</pre>
229
- </div>
230
- </aside>
231
- {/if}
232
- </div>
233
-
234
- <style>
235
- .todo-app {
236
- max-width: 800px;
237
- margin: 0 auto;
238
- padding: 2rem;
239
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
240
- }
241
-
242
- .header {
243
- display: flex;
244
- justify-content: space-between;
245
- align-items: center;
246
- margin-bottom: 2rem;
247
- padding-bottom: 1rem;
248
- border-bottom: 2px solid #e0e0e0;
249
- }
250
-
251
- .header h1 {
252
- margin: 0;
253
- font-size: 2rem;
254
- color: #333;
255
- }
256
-
257
- .history-controls {
258
- display: flex;
259
- gap: 0.5rem;
260
- align-items: center;
261
- }
262
-
263
- .history-controls button {
264
- padding: 0.5rem 1rem;
265
- border: 1px solid #ddd;
266
- background: white;
267
- border-radius: 4px;
268
- cursor: pointer;
269
- font-size: 0.9rem;
270
- }
271
-
272
- .history-controls button:hover:not(:disabled) {
273
- background: #f5f5f5;
274
- }
275
-
276
- .history-controls button:disabled {
277
- opacity: 0.5;
278
- cursor: not-allowed;
279
- }
280
-
281
- .history-controls button.active {
282
- background: #e3f2fd;
283
- border-color: #2196f3;
284
- }
285
-
286
- .history-info {
287
- padding: 0 0.5rem;
288
- color: #666;
289
- font-size: 0.9rem;
290
- }
291
-
292
- .input-section {
293
- display: flex;
294
- gap: 0.5rem;
295
- margin-bottom: 1rem;
296
- }
297
-
298
- .new-todo {
299
- flex: 1;
300
- padding: 0.75rem;
301
- border: 2px solid #ddd;
302
- border-radius: 4px;
303
- font-size: 1rem;
304
- }
305
-
306
- .new-todo:focus {
307
- outline: none;
308
- border-color: #2196f3;
309
- }
310
-
311
- .add-btn {
312
- padding: 0.75rem 1.5rem;
313
- background: #2196f3;
314
- color: white;
315
- border: none;
316
- border-radius: 4px;
317
- cursor: pointer;
318
- font-size: 1rem;
319
- font-weight: 500;
320
- }
321
-
322
- .add-btn:hover {
323
- background: #1976d2;
324
- }
325
-
326
- .filters {
327
- display: flex;
328
- gap: 0.5rem;
329
- margin-bottom: 1rem;
330
- }
331
-
332
- .filters button {
333
- flex: 1;
334
- padding: 0.5rem;
335
- border: 1px solid #ddd;
336
- background: white;
337
- border-radius: 4px;
338
- cursor: pointer;
339
- }
340
-
341
- .filters button.active {
342
- background: #2196f3;
343
- color: white;
344
- border-color: #2196f3;
345
- }
346
-
347
- .todo-list {
348
- list-style: none;
349
- padding: 0;
350
- margin: 0;
351
- }
352
-
353
- .todo-item {
354
- display: flex;
355
- align-items: center;
356
- gap: 0.75rem;
357
- padding: 1rem;
358
- border: 1px solid #ddd;
359
- border-radius: 4px;
360
- margin-bottom: 0.5rem;
361
- background: white;
362
- }
363
-
364
- .todo-item.completed {
365
- background: #f5f5f5;
366
- }
367
-
368
- .todo-item.completed .todo-text {
369
- text-decoration: line-through;
370
- color: #999;
371
- }
372
-
373
- .todo-item input[type="checkbox"] {
374
- width: 1.25rem;
375
- height: 1.25rem;
376
- cursor: pointer;
377
- }
378
-
379
- .todo-text {
380
- flex: 1;
381
- font-size: 1rem;
382
- }
383
-
384
- .remove-btn {
385
- padding: 0.25rem 0.5rem;
386
- background: transparent;
387
- border: none;
388
- color: #f44336;
389
- cursor: pointer;
390
- font-size: 1.25rem;
391
- }
392
-
393
- .remove-btn:hover {
394
- color: #d32f2f;
395
- }
396
-
397
- .footer {
398
- display: flex;
399
- justify-content: space-between;
400
- align-items: center;
401
- margin-top: 1rem;
402
- padding-top: 1rem;
403
- border-top: 1px solid #e0e0e0;
404
- }
405
-
406
- .stats {
407
- color: #666;
408
- font-size: 0.9rem;
409
- }
410
-
411
- .actions {
412
- display: flex;
413
- gap: 0.5rem;
414
- }
415
-
416
- .actions button {
417
- padding: 0.5rem 1rem;
418
- border: 1px solid #ddd;
419
- background: white;
420
- border-radius: 4px;
421
- cursor: pointer;
422
- font-size: 0.9rem;
423
- }
424
-
425
- .actions button:hover:not(:disabled) {
426
- background: #f5f5f5;
427
- }
428
-
429
- .actions button:disabled {
430
- opacity: 0.5;
431
- cursor: not-allowed;
432
- }
433
-
434
- .empty-state {
435
- text-align: center;
436
- padding: 3rem;
437
- color: #999;
438
- }
439
-
440
- .debugger {
441
- margin-top: 2rem;
442
- padding: 1rem;
443
- border: 2px solid #2196f3;
444
- border-radius: 4px;
445
- background: #f5f5f5;
446
- }
447
-
448
- .debugger h2 {
449
- margin-top: 0;
450
- color: #2196f3;
451
- }
452
-
453
- .timeline {
454
- display: flex;
455
- gap: 0.5rem;
456
- overflow-x: auto;
457
- padding: 1rem 0;
458
- margin-bottom: 1rem;
459
- }
460
-
461
- .snapshot {
462
- min-width: 100px;
463
- padding: 0.5rem;
464
- border: 2px solid #ddd;
465
- background: white;
466
- border-radius: 4px;
467
- cursor: pointer;
468
- text-align: center;
469
- }
470
-
471
- .snapshot.active {
472
- border-color: #2196f3;
473
- background: #e3f2fd;
474
- }
475
-
476
- .snapshot-time {
477
- font-size: 0.8rem;
478
- font-weight: 500;
479
- margin-bottom: 0.25rem;
480
- }
481
-
482
- .snapshot-events {
483
- font-size: 0.75rem;
484
- color: #666;
485
- }
486
-
487
- .state-viewer {
488
- background: white;
489
- border: 1px solid #ddd;
490
- border-radius: 4px;
491
- padding: 1rem;
492
- }
493
-
494
- .state-viewer h3 {
495
- margin-top: 0;
496
- margin-bottom: 0.5rem;
497
- font-size: 1rem;
498
- }
499
-
500
- .state-viewer pre {
501
- margin: 0;
502
- overflow-x: auto;
503
- font-size: 0.85rem;
504
- line-height: 1.5;
505
- }
506
- </style>
1
+ <!--
2
+ Advanced Todo App - Svelte 5 Component
3
+
4
+ This component demonstrates:
5
+ - Svelte 5 runes integration with Praxis
6
+ - History state pattern with undo/redo
7
+ - Time-travel debugging
8
+ - Reactive derived values
9
+ -->
10
+
11
+ <script lang="ts">
12
+ import { usePraxisEngine } from '@plures/praxis/svelte';
13
+ import {
14
+ createTodoEngine,
15
+ getFilteredTodos,
16
+ getStats,
17
+ AddTodo,
18
+ ToggleTodo,
19
+ RemoveTodo,
20
+ SetFilter,
21
+ CompleteAll,
22
+ ClearCompleted,
23
+ type TodoItem,
24
+ } from './index';
25
+
26
+ // Create engine with history enabled
27
+ const engine = createTodoEngine();
28
+ const {
29
+ context,
30
+ dispatch,
31
+ undo,
32
+ redo,
33
+ canUndo,
34
+ canRedo,
35
+ snapshots,
36
+ historyIndex,
37
+ goToSnapshot,
38
+ } = usePraxisEngine(engine, {
39
+ enableHistory: true,
40
+ maxHistorySize: 50,
41
+ });
42
+
43
+ // Local UI state
44
+ let newTodoText = $state('');
45
+ let showDebugger = $state(false);
46
+
47
+ // Derived reactive values
48
+ $: filteredTodos = getFilteredTodos(context.todos, context.filter);
49
+ $: stats = getStats(context.todos);
50
+ $: allCompleted = stats.total > 0 && stats.completed === stats.total;
51
+
52
+ // Event handlers
53
+ function handleAddTodo() {
54
+ if (newTodoText.trim()) {
55
+ dispatch([AddTodo.create({ text: newTodoText })], 'Add Todo');
56
+ newTodoText = '';
57
+ }
58
+ }
59
+
60
+ function handleToggle(id: string) {
61
+ dispatch([ToggleTodo.create({ id })], 'Toggle Todo');
62
+ }
63
+
64
+ function handleRemove(id: string) {
65
+ dispatch([RemoveTodo.create({ id })], 'Remove Todo');
66
+ }
67
+
68
+ function handleFilterChange(filter: 'all' | 'active' | 'completed') {
69
+ dispatch([SetFilter.create({ filter })], 'Change Filter');
70
+ }
71
+
72
+ function handleCompleteAll() {
73
+ dispatch([CompleteAll.create({})], 'Complete All');
74
+ }
75
+
76
+ function handleClearCompleted() {
77
+ if (stats.completed > 0) {
78
+ dispatch([ClearCompleted.create({})], 'Clear Completed');
79
+ }
80
+ }
81
+
82
+ // Keyboard shortcuts
83
+ function handleKeydown(event: KeyboardEvent) {
84
+ if (event.ctrlKey || event.metaKey) {
85
+ if (event.key === 'z' && canUndo) {
86
+ event.preventDefault();
87
+ undo();
88
+ } else if (event.key === 'y' && canRedo) {
89
+ event.preventDefault();
90
+ redo();
91
+ } else if (event.key === 'd') {
92
+ event.preventDefault();
93
+ showDebugger = !showDebugger;
94
+ }
95
+ }
96
+ }
97
+ </script>
98
+
99
+ <svelte:window onkeydown={handleKeydown} />
100
+
101
+ <div class="todo-app">
102
+ <!-- Header -->
103
+ <header class="header">
104
+ <h1>Praxis Todos</h1>
105
+ <div class="history-controls">
106
+ <button onclick={undo} disabled={!canUndo} title="Undo (Ctrl+Z)">
107
+ ⟲ Undo
108
+ </button>
109
+ <span class="history-info">
110
+ {historyIndex + 1} / {snapshots.length}
111
+ </span>
112
+ <button onclick={redo} disabled={!canRedo} title="Redo (Ctrl+Y)">
113
+ ⟳ Redo
114
+ </button>
115
+ <button
116
+ onclick={() => showDebugger = !showDebugger}
117
+ class:active={showDebugger}
118
+ title="Toggle Debugger (Ctrl+D)"
119
+ >
120
+ 🐛 Debug
121
+ </button>
122
+ </div>
123
+ </header>
124
+
125
+ <!-- Main Content -->
126
+ <main class="main">
127
+ <!-- Input -->
128
+ <div class="input-section">
129
+ <input
130
+ type="text"
131
+ bind:value={newTodoText}
132
+ onkeypress={(e) => e.key === 'Enter' && handleAddTodo()}
133
+ placeholder="What needs to be done?"
134
+ class="new-todo"
135
+ />
136
+ <button onclick={handleAddTodo} class="add-btn">
137
+ Add
138
+ </button>
139
+ </div>
140
+
141
+ {#if stats.total > 0}
142
+ <!-- Filters -->
143
+ <div class="filters">
144
+ <button
145
+ class:active={context.filter === 'all'}
146
+ onclick={() => handleFilterChange('all')}
147
+ >
148
+ All ({stats.total})
149
+ </button>
150
+ <button
151
+ class:active={context.filter === 'active'}
152
+ onclick={() => handleFilterChange('active')}
153
+ >
154
+ Active ({stats.active})
155
+ </button>
156
+ <button
157
+ class:active={context.filter === 'completed'}
158
+ onclick={() => handleFilterChange('completed')}
159
+ >
160
+ Completed ({stats.completed})
161
+ </button>
162
+ </div>
163
+
164
+ <!-- Todo List -->
165
+ <ul class="todo-list">
166
+ {#each filteredTodos as todo (todo.id)}
167
+ <li class="todo-item" class:completed={todo.completed}>
168
+ <input
169
+ type="checkbox"
170
+ checked={todo.completed}
171
+ onchange={() => handleToggle(todo.id)}
172
+ />
173
+ <span class="todo-text">{todo.text}</span>
174
+ <button onclick={() => handleRemove(todo.id)} class="remove-btn">
175
+
176
+ </button>
177
+ </li>
178
+ {/each}
179
+ </ul>
180
+
181
+ <!-- Footer -->
182
+ <footer class="footer">
183
+ <span class="stats">
184
+ {stats.active} {stats.active === 1 ? 'item' : 'items'} left
185
+ </span>
186
+
187
+ <div class="actions">
188
+ <button onclick={handleCompleteAll} disabled={allCompleted}>
189
+ Complete All
190
+ </button>
191
+ <button onclick={handleClearCompleted} disabled={stats.completed === 0}>
192
+ Clear Completed
193
+ </button>
194
+ </div>
195
+ </footer>
196
+ {:else}
197
+ <div class="empty-state">
198
+ <p>No todos yet. Add one above!</p>
199
+ </div>
200
+ {/if}
201
+ </main>
202
+
203
+ <!-- Time-Travel Debugger -->
204
+ {#if showDebugger}
205
+ <aside class="debugger">
206
+ <h2>Time-Travel Debugger</h2>
207
+
208
+ <div class="timeline">
209
+ {#each snapshots as snapshot, index}
210
+ <button
211
+ class="snapshot"
212
+ class:active={index === historyIndex}
213
+ onclick={() => goToSnapshot(index)}
214
+ title={`Snapshot ${index + 1}`}
215
+ >
216
+ <div class="snapshot-time">
217
+ {new Date(snapshot.timestamp).toLocaleTimeString()}
218
+ </div>
219
+ <div class="snapshot-events">
220
+ {snapshot.events.length} events
221
+ </div>
222
+ </button>
223
+ {/each}
224
+ </div>
225
+
226
+ <div class="state-viewer">
227
+ <h3>Current State</h3>
228
+ <pre>{JSON.stringify(context, null, 2)}</pre>
229
+ </div>
230
+ </aside>
231
+ {/if}
232
+ </div>
233
+
234
+ <style>
235
+ .todo-app {
236
+ max-width: 800px;
237
+ margin: 0 auto;
238
+ padding: 2rem;
239
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
240
+ }
241
+
242
+ .header {
243
+ display: flex;
244
+ justify-content: space-between;
245
+ align-items: center;
246
+ margin-bottom: 2rem;
247
+ padding-bottom: 1rem;
248
+ border-bottom: 2px solid #e0e0e0;
249
+ }
250
+
251
+ .header h1 {
252
+ margin: 0;
253
+ font-size: 2rem;
254
+ color: #333;
255
+ }
256
+
257
+ .history-controls {
258
+ display: flex;
259
+ gap: 0.5rem;
260
+ align-items: center;
261
+ }
262
+
263
+ .history-controls button {
264
+ padding: 0.5rem 1rem;
265
+ border: 1px solid #ddd;
266
+ background: white;
267
+ border-radius: 4px;
268
+ cursor: pointer;
269
+ font-size: 0.9rem;
270
+ }
271
+
272
+ .history-controls button:hover:not(:disabled) {
273
+ background: #f5f5f5;
274
+ }
275
+
276
+ .history-controls button:disabled {
277
+ opacity: 0.5;
278
+ cursor: not-allowed;
279
+ }
280
+
281
+ .history-controls button.active {
282
+ background: #e3f2fd;
283
+ border-color: #2196f3;
284
+ }
285
+
286
+ .history-info {
287
+ padding: 0 0.5rem;
288
+ color: #666;
289
+ font-size: 0.9rem;
290
+ }
291
+
292
+ .input-section {
293
+ display: flex;
294
+ gap: 0.5rem;
295
+ margin-bottom: 1rem;
296
+ }
297
+
298
+ .new-todo {
299
+ flex: 1;
300
+ padding: 0.75rem;
301
+ border: 2px solid #ddd;
302
+ border-radius: 4px;
303
+ font-size: 1rem;
304
+ }
305
+
306
+ .new-todo:focus {
307
+ outline: none;
308
+ border-color: #2196f3;
309
+ }
310
+
311
+ .add-btn {
312
+ padding: 0.75rem 1.5rem;
313
+ background: #2196f3;
314
+ color: white;
315
+ border: none;
316
+ border-radius: 4px;
317
+ cursor: pointer;
318
+ font-size: 1rem;
319
+ font-weight: 500;
320
+ }
321
+
322
+ .add-btn:hover {
323
+ background: #1976d2;
324
+ }
325
+
326
+ .filters {
327
+ display: flex;
328
+ gap: 0.5rem;
329
+ margin-bottom: 1rem;
330
+ }
331
+
332
+ .filters button {
333
+ flex: 1;
334
+ padding: 0.5rem;
335
+ border: 1px solid #ddd;
336
+ background: white;
337
+ border-radius: 4px;
338
+ cursor: pointer;
339
+ }
340
+
341
+ .filters button.active {
342
+ background: #2196f3;
343
+ color: white;
344
+ border-color: #2196f3;
345
+ }
346
+
347
+ .todo-list {
348
+ list-style: none;
349
+ padding: 0;
350
+ margin: 0;
351
+ }
352
+
353
+ .todo-item {
354
+ display: flex;
355
+ align-items: center;
356
+ gap: 0.75rem;
357
+ padding: 1rem;
358
+ border: 1px solid #ddd;
359
+ border-radius: 4px;
360
+ margin-bottom: 0.5rem;
361
+ background: white;
362
+ }
363
+
364
+ .todo-item.completed {
365
+ background: #f5f5f5;
366
+ }
367
+
368
+ .todo-item.completed .todo-text {
369
+ text-decoration: line-through;
370
+ color: #999;
371
+ }
372
+
373
+ .todo-item input[type="checkbox"] {
374
+ width: 1.25rem;
375
+ height: 1.25rem;
376
+ cursor: pointer;
377
+ }
378
+
379
+ .todo-text {
380
+ flex: 1;
381
+ font-size: 1rem;
382
+ }
383
+
384
+ .remove-btn {
385
+ padding: 0.25rem 0.5rem;
386
+ background: transparent;
387
+ border: none;
388
+ color: #f44336;
389
+ cursor: pointer;
390
+ font-size: 1.25rem;
391
+ }
392
+
393
+ .remove-btn:hover {
394
+ color: #d32f2f;
395
+ }
396
+
397
+ .footer {
398
+ display: flex;
399
+ justify-content: space-between;
400
+ align-items: center;
401
+ margin-top: 1rem;
402
+ padding-top: 1rem;
403
+ border-top: 1px solid #e0e0e0;
404
+ }
405
+
406
+ .stats {
407
+ color: #666;
408
+ font-size: 0.9rem;
409
+ }
410
+
411
+ .actions {
412
+ display: flex;
413
+ gap: 0.5rem;
414
+ }
415
+
416
+ .actions button {
417
+ padding: 0.5rem 1rem;
418
+ border: 1px solid #ddd;
419
+ background: white;
420
+ border-radius: 4px;
421
+ cursor: pointer;
422
+ font-size: 0.9rem;
423
+ }
424
+
425
+ .actions button:hover:not(:disabled) {
426
+ background: #f5f5f5;
427
+ }
428
+
429
+ .actions button:disabled {
430
+ opacity: 0.5;
431
+ cursor: not-allowed;
432
+ }
433
+
434
+ .empty-state {
435
+ text-align: center;
436
+ padding: 3rem;
437
+ color: #999;
438
+ }
439
+
440
+ .debugger {
441
+ margin-top: 2rem;
442
+ padding: 1rem;
443
+ border: 2px solid #2196f3;
444
+ border-radius: 4px;
445
+ background: #f5f5f5;
446
+ }
447
+
448
+ .debugger h2 {
449
+ margin-top: 0;
450
+ color: #2196f3;
451
+ }
452
+
453
+ .timeline {
454
+ display: flex;
455
+ gap: 0.5rem;
456
+ overflow-x: auto;
457
+ padding: 1rem 0;
458
+ margin-bottom: 1rem;
459
+ }
460
+
461
+ .snapshot {
462
+ min-width: 100px;
463
+ padding: 0.5rem;
464
+ border: 2px solid #ddd;
465
+ background: white;
466
+ border-radius: 4px;
467
+ cursor: pointer;
468
+ text-align: center;
469
+ }
470
+
471
+ .snapshot.active {
472
+ border-color: #2196f3;
473
+ background: #e3f2fd;
474
+ }
475
+
476
+ .snapshot-time {
477
+ font-size: 0.8rem;
478
+ font-weight: 500;
479
+ margin-bottom: 0.25rem;
480
+ }
481
+
482
+ .snapshot-events {
483
+ font-size: 0.75rem;
484
+ color: #666;
485
+ }
486
+
487
+ .state-viewer {
488
+ background: white;
489
+ border: 1px solid #ddd;
490
+ border-radius: 4px;
491
+ padding: 1rem;
492
+ }
493
+
494
+ .state-viewer h3 {
495
+ margin-top: 0;
496
+ margin-bottom: 0.5rem;
497
+ font-size: 1rem;
498
+ }
499
+
500
+ .state-viewer pre {
501
+ margin: 0;
502
+ overflow-x: auto;
503
+ font-size: 0.85rem;
504
+ line-height: 1.5;
505
+ }
506
+ </style>