@myrialabs/clopen 0.2.11 → 0.2.13

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 (45) hide show
  1. package/backend/chat/stream-manager.ts +106 -9
  2. package/backend/database/queries/project-queries.ts +1 -4
  3. package/backend/database/queries/session-queries.ts +36 -1
  4. package/backend/database/queries/snapshot-queries.ts +122 -0
  5. package/backend/database/utils/connection.ts +17 -11
  6. package/backend/engine/adapters/claude/stream.ts +14 -3
  7. package/backend/engine/types.ts +9 -0
  8. package/backend/index.ts +13 -2
  9. package/backend/mcp/config.ts +32 -6
  10. package/backend/snapshot/blob-store.ts +52 -72
  11. package/backend/snapshot/snapshot-service.ts +24 -0
  12. package/backend/terminal/stream-manager.ts +121 -131
  13. package/backend/ws/chat/stream.ts +14 -7
  14. package/backend/ws/engine/claude/accounts.ts +6 -8
  15. package/backend/ws/projects/crud.ts +72 -7
  16. package/backend/ws/sessions/crud.ts +119 -2
  17. package/backend/ws/system/operations.ts +14 -39
  18. package/backend/ws/terminal/persistence.ts +19 -33
  19. package/backend/ws/terminal/session.ts +37 -19
  20. package/bun.lock +6 -0
  21. package/frontend/components/auth/SetupPage.svelte +1 -1
  22. package/frontend/components/chat/input/ChatInput.svelte +22 -1
  23. package/frontend/components/chat/input/composables/use-animations.svelte.ts +127 -111
  24. package/frontend/components/chat/input/composables/use-textarea-resize.svelte.ts +11 -1
  25. package/frontend/components/chat/message/MessageBubble.svelte +13 -0
  26. package/frontend/components/chat/widgets/FloatingTodoList.svelte +2 -2
  27. package/frontend/components/common/form/FolderBrowser.svelte +17 -4
  28. package/frontend/components/common/overlay/Dialog.svelte +17 -15
  29. package/frontend/components/files/FileNode.svelte +0 -15
  30. package/frontend/components/git/ChangesSection.svelte +104 -13
  31. package/frontend/components/history/HistoryModal.svelte +94 -19
  32. package/frontend/components/history/HistoryView.svelte +29 -36
  33. package/frontend/components/settings/engines/AIEnginesSettings.svelte +1 -1
  34. package/frontend/components/settings/general/DataManagementSettings.svelte +1 -54
  35. package/frontend/components/terminal/Terminal.svelte +5 -1
  36. package/frontend/components/workspace/DesktopNavigator.svelte +57 -10
  37. package/frontend/components/workspace/MobileNavigator.svelte +57 -10
  38. package/frontend/components/workspace/WorkspaceLayout.svelte +0 -8
  39. package/frontend/services/chat/chat.service.ts +94 -23
  40. package/frontend/services/notification/global-stream-monitor.ts +5 -2
  41. package/frontend/services/terminal/project.service.ts +4 -60
  42. package/frontend/services/terminal/terminal.service.ts +18 -27
  43. package/frontend/stores/core/app.svelte.ts +10 -2
  44. package/frontend/stores/core/sessions.svelte.ts +10 -1
  45. package/package.json +4 -2
@@ -275,7 +275,7 @@
275
275
  }
276
276
  }
277
277
 
278
- // Delete session state
278
+ // Delete single session state
279
279
  let showDeleteDialog = $state(false);
280
280
  let sessionToDelete = $state<ChatSession | null>(null);
281
281
 
@@ -299,7 +299,7 @@
299
299
  addNotification({
300
300
  type: 'success',
301
301
  title: 'Session Deleted',
302
- message: 'Chat session has been deleted',
302
+ message: 'Chat session and related data have been deleted',
303
303
  duration: 3000
304
304
  });
305
305
 
@@ -321,6 +321,54 @@
321
321
  sessionToDelete = null;
322
322
  }
323
323
 
324
+ // Delete all sessions state
325
+ let showDeleteAllDialog = $state(false);
326
+ let deletingAll = $state(false);
327
+
328
+ async function confirmDeleteAllSessions() {
329
+ deletingAll = true;
330
+ try {
331
+ const result = await ws.http('sessions:delete-all', {});
332
+
333
+ // Remove all project sessions from local state
334
+ const projectId = projectState.currentProject?.id;
335
+ if (projectId) {
336
+ const toRemove = sessionState.sessions
337
+ .filter(s => s.project_id === projectId)
338
+ .map(s => s.id);
339
+ for (const id of toRemove) {
340
+ removeSession(id);
341
+ }
342
+ }
343
+
344
+ // Clear cache
345
+ sessionDataCache = {};
346
+
347
+ addNotification({
348
+ type: 'success',
349
+ title: 'All Sessions Deleted',
350
+ message: `${result.deletedCount} sessions and related data have been deleted`,
351
+ duration: 3000
352
+ });
353
+
354
+ showDeleteAllDialog = false;
355
+ } catch (error) {
356
+ debug.error('session', 'Failed to delete all sessions:', error);
357
+ addNotification({
358
+ type: 'error',
359
+ title: 'Error',
360
+ message: 'Failed to delete all sessions',
361
+ duration: 5000
362
+ });
363
+ } finally {
364
+ deletingAll = false;
365
+ }
366
+ }
367
+
368
+ function closeDeleteAllDialog() {
369
+ showDeleteAllDialog = false;
370
+ }
371
+
324
372
  function closeModal() {
325
373
  searchQuery = '';
326
374
  onClose();
@@ -331,21 +379,34 @@
331
379
  {#snippet header()}
332
380
  <div class="flex items-center justify-between px-4 py-3 md:px-6 md:py-4">
333
381
  <h2 class="text-base md:text-lg font-bold text-slate-900 dark:text-slate-100">Sessions</h2>
334
- <button
335
- type="button"
336
- class="p-1.5 md:p-2 rounded-lg text-slate-500 hover:text-slate-900 dark:hover:text-slate-100 hover:bg-violet-500/10 transition-colors"
337
- onclick={closeModal}
338
- aria-label="Close modal"
339
- >
340
- <svg class="w-4 h-4 md:w-5 md:h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
341
- <path
342
- stroke-linecap="round"
343
- stroke-linejoin="round"
344
- stroke-width="2"
345
- d="M6 18L18 6M6 6l12 12"
346
- />
347
- </svg>
348
- </button>
382
+ <div class="flex items-center gap-2">
383
+ {#if filteredSessions.length > 0}
384
+ <button
385
+ type="button"
386
+ class="flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-xs font-medium text-red-600 dark:text-red-400 hover:bg-red-500/10 transition-colors"
387
+ onclick={() => (showDeleteAllDialog = true)}
388
+ aria-label="Delete all sessions"
389
+ >
390
+ <Icon name="lucide:trash-2" class="w-3.5 h-3.5" />
391
+ <span class="hidden sm:inline">Delete All</span>
392
+ </button>
393
+ {/if}
394
+ <button
395
+ type="button"
396
+ class="p-1.5 md:p-2 rounded-lg text-slate-500 hover:text-slate-900 dark:hover:text-slate-100 hover:bg-violet-500/10 transition-colors"
397
+ onclick={closeModal}
398
+ aria-label="Close modal"
399
+ >
400
+ <svg class="w-4 h-4 md:w-5 md:h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
401
+ <path
402
+ stroke-linecap="round"
403
+ stroke-linejoin="round"
404
+ stroke-width="2"
405
+ d="M6 18L18 6M6 6l12 12"
406
+ />
407
+ </svg>
408
+ </button>
409
+ </div>
349
410
  </div>
350
411
  {/snippet}
351
412
 
@@ -514,14 +575,28 @@
514
575
  {/snippet}
515
576
  </Modal>
516
577
 
517
- <!-- Delete Confirmation Dialog -->
578
+ <!-- Delete Single Session Confirmation Dialog -->
518
579
  <Dialog
519
580
  bind:isOpen={showDeleteDialog}
520
581
  onClose={closeDeleteDialog}
521
582
  type="error"
522
583
  title="Delete Session"
523
- message="Are you sure you want to delete this session? All messages will be permanently removed."
584
+ message={sessionToDelete && isSessionStreaming(sessionToDelete.id)
585
+ ? 'This session is currently running. Deleting it will stop the active chat and permanently remove all messages, snapshots, and related data.'
586
+ : 'Are you sure you want to delete this session? All messages, snapshots, and related data will be permanently removed.'}
524
587
  confirmText="Delete"
525
588
  cancelText="Cancel"
526
589
  onConfirm={confirmDeleteSession}
527
590
  />
591
+
592
+ <!-- Delete All Sessions Confirmation Dialog -->
593
+ <Dialog
594
+ bind:isOpen={showDeleteAllDialog}
595
+ onClose={closeDeleteAllDialog}
596
+ type="error"
597
+ title="Delete All Sessions"
598
+ message={`Are you sure you want to delete all ${filteredSessions.length} sessions in this project? All messages, snapshots, and related data will be permanently removed. This only affects the current project.`}
599
+ confirmText={deletingAll ? 'Deleting...' : 'Delete All'}
600
+ cancelText="Cancel"
601
+ onConfirm={confirmDeleteAllSessions}
602
+ />
@@ -14,8 +14,17 @@
14
14
  import { showConfirm } from '$frontend/stores/ui/dialog.svelte';
15
15
  import Modal from '$frontend/components/common/overlay/Modal.svelte';
16
16
  import TimelineModal from '../checkpoint/TimelineModal.svelte';
17
+ import { presenceState } from '$frontend/stores/core/presence.svelte';
17
18
  import { debug } from '$shared/utils/logger';
18
19
 
20
+ // Check if a session has an active stream
21
+ function isSessionStreaming(chatSessionId: string): boolean {
22
+ for (const status of presenceState.statuses.values()) {
23
+ if (status.streams?.some(s => s.status === 'active' && s.chatSessionId === chatSessionId)) return true;
24
+ }
25
+ return false;
26
+ }
27
+
19
28
  // Use real session data from session store
20
29
  const sessions = $derived(sessionState.sessions);
21
30
 
@@ -303,27 +312,25 @@
303
312
  async function deleteSession(session: ChatSession) {
304
313
  const sessionData = await getSessionData(session.id);
305
314
  const title = sessionData.title;
306
-
315
+ const streaming = isSessionStreaming(session.id);
316
+
307
317
  const confirmed = await showConfirm({
308
318
  title: 'Delete Session',
309
- message: `Are you sure you want to delete session "${title}"? This action cannot be undone.`,
319
+ message: streaming
320
+ ? `This session "${title}" is currently running. Deleting it will stop the active chat and permanently remove all messages, snapshots, and related data.`
321
+ : `Are you sure you want to delete session "${title}"? All messages, snapshots, and related data will be permanently removed.`,
310
322
  type: 'error',
311
323
  confirmText: 'Delete',
312
324
  cancelText: 'Cancel'
313
325
  });
314
-
326
+
315
327
  if (confirmed) {
316
328
  try {
317
- // Delete from database via WebSocket
318
329
  await ws.http('sessions:delete', { id: session.id });
319
- // Remove from local state
320
330
  removeSession(session.id);
321
- // Clear cache
322
331
  delete sessionDataCache[session.id];
323
- // User already knows session was deleted from UI update
324
332
  } catch (error) {
325
333
  addNotification({
326
-
327
334
  type: 'error',
328
335
  title: 'Error',
329
336
  message: 'Failed to delete session',
@@ -332,11 +339,11 @@
332
339
  }
333
340
  }
334
341
  }
335
-
342
+
336
343
  async function clearHistory() {
337
344
  const confirmed = await showConfirm({
338
345
  title: 'Clear All Session History',
339
- message: 'Are you sure you want to clear all session history? This will delete all sessions. This action cannot be undone.',
346
+ message: 'Are you sure you want to clear all session history? All messages, snapshots, and related data will be permanently removed. This only affects the current project.',
340
347
  type: 'error',
341
348
  confirmText: 'Clear All',
342
349
  cancelText: 'Cancel'
@@ -349,34 +356,20 @@
349
356
  }
350
357
 
351
358
  try {
352
- // Delete all sessions from database
353
- const deletePromises = sessions.map(async (session) => {
354
- try {
355
- await ws.http('sessions:delete', { id: session.id });
356
- // Remove from local state
357
- removeSession(session.id);
358
- // Clear cache
359
- delete sessionDataCache[session.id];
360
- return { success: true };
361
- } catch (error) {
362
- debug.error('session', `Error deleting session ${session.id}:`, error);
363
- return { success: false, error: 'Failed to delete session' };
364
- }
365
- });
366
-
367
- // Wait for all deletions to complete
368
- const results = await Promise.all(deletePromises);
359
+ const result = await ws.http('sessions:delete-all', {});
369
360
 
370
- // Check if any deletions failed
371
- const failed = results.filter(r => !r.success);
372
- if (failed.length > 0) {
373
- addNotification({
374
- type: 'warning',
375
- title: 'Partial Deletion',
376
- message: `Failed to delete ${failed.length} session(s)`,
377
- duration: 5000
378
- });
361
+ // Remove all sessions from local state
362
+ const toRemove = [...sessions].map(s => s.id);
363
+ for (const id of toRemove) {
364
+ removeSession(id);
379
365
  }
366
+
367
+ addNotification({
368
+ type: 'success',
369
+ title: 'All Sessions Deleted',
370
+ message: `${result.deletedCount} sessions and related data have been deleted`,
371
+ duration: 3000
372
+ });
380
373
  } catch (error) {
381
374
  debug.error('session', 'Error clearing history:', error);
382
375
  addNotification({
@@ -73,7 +73,7 @@
73
73
  let openCodeCommandCopiedTimer: ReturnType<typeof setTimeout> | null = null;
74
74
 
75
75
  // Debug PTY (xterm.js)
76
- const showDebug = $state(false);
76
+ const showDebug = $state(true);
77
77
  let debugTermContainer = $state<HTMLDivElement>();
78
78
  let debugTerminal: Terminal | null = null;
79
79
  let debugFitAddon: FitAddon | null = null;
@@ -1,6 +1,5 @@
1
1
  <script lang="ts">
2
2
  import { addNotification } from '$frontend/stores/ui/notification.svelte';
3
- import { resetToDefaults } from '$frontend/stores/features/settings.svelte';
4
3
  import { showConfirm } from '$frontend/stores/ui/dialog.svelte';
5
4
  import Icon from '../../common/display/Icon.svelte';
6
5
  import { debug } from '$shared/utils/logger';
@@ -41,68 +40,16 @@
41
40
  }
42
41
  }
43
42
 
44
- async function resetSettings() {
45
- const confirmed = await showConfirm({
46
- title: 'Reset Settings',
47
- message:
48
- 'Are you sure you want to reset all settings to defaults? This will not delete your projects or conversations.',
49
- type: 'warning',
50
- confirmText: 'Reset Settings',
51
- cancelText: 'Cancel'
52
- });
53
43
 
54
- if (confirmed) {
55
- resetToDefaults();
56
- addNotification({
57
- type: 'success',
58
- title: 'Settings Reset',
59
- message: 'All settings have been restored to defaults'
60
- });
61
- }
62
- }
63
44
  </script>
64
45
 
65
46
  <div class="py-1">
66
47
  <h3 class="text-base font-bold text-slate-900 dark:text-slate-100 mb-1.5">Data Management</h3>
67
48
  <p class="text-sm text-slate-600 dark:text-slate-500 mb-5">
68
- Export, import, or clear your application data
49
+ Manage your application data
69
50
  </p>
70
51
 
71
52
  <div class="flex flex-col gap-4">
72
- <!-- Storage Info -->
73
- <div
74
- class="flex items-center gap-3.5 p-4 bg-slate-100/50 dark:bg-slate-800/50 border border-slate-200 dark:border-slate-700/50 rounded-lg"
75
- >
76
- <Icon name="lucide:hard-drive" class="w-5 h-5 text-slate-500 dark:text-slate-400" />
77
- <div class="flex-1">
78
- <div class="text-sm font-medium text-slate-900 dark:text-slate-100">Local Storage</div>
79
- <div class="text-xs text-slate-600 dark:text-slate-400">Data is stored locally in your browser</div>
80
- </div>
81
- </div>
82
-
83
- <!-- Action Cards -->
84
- <div class="flex flex-col gap-3">
85
- <!-- Reset Settings -->
86
- <div
87
- class="flex items-center justify-between gap-4 py-3 px-4 bg-slate-100/50 dark:bg-slate-800/50 border border-slate-200 dark:border-slate-700/50 rounded-lg"
88
- >
89
- <div class="flex items-center gap-3">
90
- <Icon name="lucide:refresh-cw" class="w-4.5 h-4.5 text-slate-600 dark:text-slate-400" />
91
- <div class="text-left">
92
- <div class="text-sm font-medium text-slate-900 dark:text-slate-100">Reset Settings</div>
93
- <div class="text-xs text-slate-600 dark:text-slate-400">Restore default settings</div>
94
- </div>
95
- </div>
96
- <button
97
- type="button"
98
- class="px-4 py-2 bg-slate-200 dark:bg-slate-700 border border-slate-300 dark:border-slate-600 text-slate-700 dark:text-slate-300 rounded-lg text-sm font-medium cursor-pointer transition-all duration-150 hover:bg-slate-300 dark:hover:bg-slate-600 hover:border-slate-400 dark:hover:border-slate-500 disabled:opacity-60 disabled:cursor-not-allowed whitespace-nowrap"
99
- onclick={resetSettings}
100
- >
101
- Reset
102
- </button>
103
- </div>
104
- </div>
105
-
106
53
  <!-- Danger Zone -->
107
54
  <div class="mt-2">
108
55
  <div class="flex items-center gap-2 mb-3 text-xs font-medium text-red-600 dark:text-red-400">
@@ -5,6 +5,7 @@
5
5
  <script lang="ts">
6
6
  import { terminalStore } from '$frontend/stores/features/terminal.svelte';
7
7
  import { projectState } from '$frontend/stores/core/projects.svelte';
8
+ import { terminalService } from '$frontend/services/terminal';
8
9
  import TerminalTabs from './TerminalTabs.svelte';
9
10
  import Icon from '$frontend/components/common/display/Icon.svelte';
10
11
  import XTerm from '$frontend/components/common/xterm/XTerm.svelte';
@@ -160,11 +161,14 @@
160
161
  if (activeSession) {
161
162
  // Clear the terminal store session
162
163
  terminalStore.clearSession(activeSession.id);
163
-
164
+
164
165
  // Also immediately clear the XTerm display
165
166
  if (xterminalRef) {
166
167
  xterminalRef.clear();
167
168
  }
169
+
170
+ // Sync clear with backend headless terminal
171
+ terminalService.clearHeadlessTerminal(activeSession.id);
168
172
  }
169
173
  }
170
174
 
@@ -93,12 +93,15 @@
93
93
  }
94
94
 
95
95
  // Delete project
96
- async function confirmDeleteProject() {
97
- if (!projectToDelete) return;
96
+ let deletingProject = $state(false);
97
+
98
+ async function confirmDeleteProject(mode: 'remove' | 'full') {
99
+ if (!projectToDelete || deletingProject) return;
98
100
  const deleteId = projectToDelete.id!;
101
+ deletingProject = true;
99
102
 
100
103
  try {
101
- await ws.http('projects:delete', { id: deleteId });
104
+ await ws.http('projects:delete', { id: deleteId, mode });
102
105
  removeProject(deleteId);
103
106
  existingProjects = existingProjects.filter(p => p.id !== deleteId);
104
107
  showDeleteDialog = false;
@@ -111,6 +114,8 @@
111
114
  message: 'Failed to delete project',
112
115
  duration: 5000
113
116
  });
117
+ } finally {
118
+ deletingProject = false;
114
119
  }
115
120
  }
116
121
 
@@ -386,13 +391,55 @@
386
391
  <Dialog
387
392
  bind:isOpen={showDeleteDialog}
388
393
  onClose={closeDeleteDialog}
389
- type="error"
390
- title="Delete Project"
391
- message='This will remove "{projectToDelete?.name}" from your project list. The actual project files on disk will not be deleted.'
392
- confirmText="Delete"
393
- cancelText="Cancel"
394
- onConfirm={confirmDeleteProject}
395
- />
394
+ type="warning"
395
+ title="Remove Project"
396
+ showCancel={false}
397
+ >
398
+ {#snippet children()}
399
+ <div class="flex items-start space-x-4">
400
+ <div class="bg-amber-50 dark:bg-amber-900/20 border-amber-200 dark:border-amber-700/50 rounded-xl p-3 border">
401
+ <Icon name="lucide:triangle-alert" class="w-6 h-6 text-amber-600 dark:text-amber-400" />
402
+ </div>
403
+ <div class="flex-1">
404
+ <h3 class="text-lg font-semibold text-slate-900 dark:text-slate-100">Remove Project</h3>
405
+ <p class="text-sm text-slate-600 dark:text-slate-400 leading-relaxed">
406
+ How would you like to remove <strong>"{projectToDelete?.name}"</strong>?
407
+ </p>
408
+ </div>
409
+ </div>
410
+ <div class="flex flex-col gap-2 pt-2">
411
+ <button
412
+ onclick={() => confirmDeleteProject('remove')}
413
+ disabled={deletingProject}
414
+ class="w-full flex items-center gap-3 p-3 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-700 transition-all duration-200 text-left disabled:opacity-50"
415
+ >
416
+ <Icon name="lucide:eye-off" class="w-5 h-5 text-slate-500 shrink-0" />
417
+ <div class="flex-1 min-w-0">
418
+ <p class="text-sm font-semibold text-slate-900 dark:text-slate-100">Remove from list</p>
419
+ <p class="text-xs text-slate-500 dark:text-slate-400">Sessions will be restored when you re-add this project.</p>
420
+ </div>
421
+ </button>
422
+ <button
423
+ onclick={() => confirmDeleteProject('full')}
424
+ disabled={deletingProject}
425
+ class="w-full flex items-center gap-3 p-3 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-700 transition-all duration-200 text-left disabled:opacity-50"
426
+ >
427
+ <Icon name="lucide:trash-2" class="w-5 h-5 text-slate-500 shrink-0" />
428
+ <div class="flex-1 min-w-0">
429
+ <p class="text-sm font-semibold text-slate-900 dark:text-slate-100">Delete with all data</p>
430
+ <p class="text-xs text-slate-500 dark:text-slate-400">Delete all sessions, snapshots, and related data.</p>
431
+ </div>
432
+ </button>
433
+ <button
434
+ onclick={closeDeleteDialog}
435
+ class="w-full py-2 text-sm font-semibold text-slate-600 dark:text-slate-400 bg-slate-100 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg hover:bg-slate-200 dark:hover:bg-slate-700 transition-colors"
436
+ >
437
+ Cancel
438
+ </button>
439
+ <p class="text-xs text-slate-400 dark:text-slate-500 text-center">Your project folder on disk will not be affected.</p>
440
+ </div>
441
+ {/snippet}
442
+ </Dialog>
396
443
 
397
444
  <!-- Tunnel Modal -->
398
445
  <TunnelModal bind:isOpen={showTunnelModal} onClose={() => (showTunnelModal = false)} />
@@ -87,12 +87,15 @@
87
87
  showDeleteDialog = true;
88
88
  }
89
89
 
90
- async function confirmDeleteProject() {
91
- if (!projectToDelete) return;
90
+ let deletingProject = $state(false);
91
+
92
+ async function confirmDeleteProject(mode: 'remove' | 'full') {
93
+ if (!projectToDelete || deletingProject) return;
92
94
  const deleteId = projectToDelete.id!;
95
+ deletingProject = true;
93
96
 
94
97
  try {
95
- await ws.http('projects:delete', { id: deleteId });
98
+ await ws.http('projects:delete', { id: deleteId, mode });
96
99
  removeProject(deleteId);
97
100
  showDeleteDialog = false;
98
101
  projectToDelete = null;
@@ -104,6 +107,8 @@
104
107
  message: 'Failed to delete project',
105
108
  duration: 5000
106
109
  });
110
+ } finally {
111
+ deletingProject = false;
107
112
  }
108
113
  }
109
114
 
@@ -368,13 +373,55 @@
368
373
  <Dialog
369
374
  bind:isOpen={showDeleteDialog}
370
375
  onClose={closeDeleteDialog}
371
- type="error"
372
- title="Delete Project"
373
- message='This will remove "{projectToDelete?.name}" from your project list. The actual project files on disk will not be deleted.'
374
- confirmText="Delete"
375
- cancelText="Cancel"
376
- onConfirm={confirmDeleteProject}
377
- />
376
+ type="warning"
377
+ title="Remove Project"
378
+ showCancel={false}
379
+ >
380
+ {#snippet children()}
381
+ <div class="flex items-start space-x-4">
382
+ <div class="bg-amber-50 dark:bg-amber-900/20 border-amber-200 dark:border-amber-700/50 rounded-xl p-3 border">
383
+ <Icon name="lucide:triangle-alert" class="w-6 h-6 text-amber-600 dark:text-amber-400" />
384
+ </div>
385
+ <div class="flex-1">
386
+ <h3 class="text-lg font-semibold text-slate-900 dark:text-slate-100">Remove Project</h3>
387
+ <p class="text-sm text-slate-600 dark:text-slate-400 leading-relaxed">
388
+ How would you like to remove <strong>"{projectToDelete?.name}"</strong>?
389
+ </p>
390
+ </div>
391
+ </div>
392
+ <div class="flex flex-col gap-2 pt-2">
393
+ <button
394
+ onclick={() => confirmDeleteProject('remove')}
395
+ disabled={deletingProject}
396
+ class="w-full flex items-center gap-3 p-3 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-700 transition-all duration-200 text-left disabled:opacity-50"
397
+ >
398
+ <Icon name="lucide:eye-off" class="w-5 h-5 text-slate-500 shrink-0" />
399
+ <div class="flex-1 min-w-0">
400
+ <p class="text-sm font-semibold text-slate-900 dark:text-slate-100">Remove from list</p>
401
+ <p class="text-xs text-slate-500 dark:text-slate-400">Sessions will be restored when you re-add this project.</p>
402
+ </div>
403
+ </button>
404
+ <button
405
+ onclick={() => confirmDeleteProject('full')}
406
+ disabled={deletingProject}
407
+ class="w-full flex items-center gap-3 p-3 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-700 transition-all duration-200 text-left disabled:opacity-50"
408
+ >
409
+ <Icon name="lucide:trash-2" class="w-5 h-5 text-slate-500 shrink-0" />
410
+ <div class="flex-1 min-w-0">
411
+ <p class="text-sm font-semibold text-slate-900 dark:text-slate-100">Delete with all data</p>
412
+ <p class="text-xs text-slate-500 dark:text-slate-400">Delete all sessions, snapshots, and related data.</p>
413
+ </div>
414
+ </button>
415
+ <button
416
+ onclick={closeDeleteDialog}
417
+ class="w-full py-2 text-sm font-semibold text-slate-600 dark:text-slate-400 bg-slate-100 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg hover:bg-slate-200 dark:hover:bg-slate-700 transition-colors"
418
+ >
419
+ Cancel
420
+ </button>
421
+ <p class="text-xs text-slate-400 dark:text-slate-500 text-center">Your project folder on disk will not be affected.</p>
422
+ </div>
423
+ {/snippet}
424
+ </Dialog>
378
425
 
379
426
  <!-- Tunnel Modal -->
380
427
  <TunnelModal bind:isOpen={showTunnelModal} onClose={() => (showTunnelModal = false)} />
@@ -139,14 +139,6 @@
139
139
  <div
140
140
  class="h-full w-full overflow-hidden {isMobile ? 'bg-white/90 dark:bg-slate-900/98' : 'bg-slate-50 dark:bg-slate-900/70'} text-slate-900 dark:text-slate-100 font-sans"
141
141
  >
142
- <!-- Skip link for accessibility -->
143
- <a
144
- href="#main-content"
145
- class="sr-only focus:not-sr-only focus:absolute focus:z-50 focus:p-4 focus:bg-violet-600 focus:text-white"
146
- >
147
- Skip to main content
148
- </a>
149
-
150
142
  {#if isMobile}
151
143
  <!-- Mobile Layout -->
152
144
  <div class="flex flex-col h-full w-full" in:fade={{ duration: 200 }}>