@myrialabs/clopen 0.1.1 → 0.1.3

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 (51) hide show
  1. package/backend/index.ts +4 -1
  2. package/backend/lib/engine/adapters/opencode/message-converter.ts +53 -2
  3. package/backend/lib/engine/adapters/opencode/stream.ts +89 -5
  4. package/backend/lib/project/status-manager.ts +221 -181
  5. package/frontend/lib/components/chat/message/ChatMessages.svelte +16 -4
  6. package/frontend/lib/components/chat/tools/AgentTool.svelte +12 -11
  7. package/frontend/lib/components/chat/tools/BashOutputTool.svelte +3 -3
  8. package/frontend/lib/components/chat/tools/BashTool.svelte +4 -4
  9. package/frontend/lib/components/chat/tools/CustomMcpTool.svelte +3 -1
  10. package/frontend/lib/components/chat/tools/EditTool.svelte +6 -6
  11. package/frontend/lib/components/chat/tools/ExitPlanModeTool.svelte +1 -1
  12. package/frontend/lib/components/chat/tools/GlobTool.svelte +12 -12
  13. package/frontend/lib/components/chat/tools/GrepTool.svelte +5 -5
  14. package/frontend/lib/components/chat/tools/ListMcpResourcesTool.svelte +1 -1
  15. package/frontend/lib/components/chat/tools/NotebookEditTool.svelte +6 -6
  16. package/frontend/lib/components/chat/tools/ReadMcpResourceTool.svelte +2 -2
  17. package/frontend/lib/components/chat/tools/ReadTool.svelte +4 -4
  18. package/frontend/lib/components/chat/tools/TaskStopTool.svelte +1 -1
  19. package/frontend/lib/components/chat/tools/TaskTool.svelte +1 -1
  20. package/frontend/lib/components/chat/tools/TodoWriteTool.svelte +4 -4
  21. package/frontend/lib/components/chat/tools/WebSearchTool.svelte +1 -1
  22. package/frontend/lib/components/chat/tools/WriteTool.svelte +3 -3
  23. package/frontend/lib/components/chat/tools/components/CodeBlock.svelte +3 -3
  24. package/frontend/lib/components/chat/tools/components/DiffBlock.svelte +2 -2
  25. package/frontend/lib/components/chat/tools/components/FileHeader.svelte +1 -1
  26. package/frontend/lib/components/chat/tools/components/InfoLine.svelte +2 -2
  27. package/frontend/lib/components/chat/tools/components/StatsBadges.svelte +1 -1
  28. package/frontend/lib/components/chat/tools/components/TerminalCommand.svelte +5 -5
  29. package/frontend/lib/components/common/Button.svelte +1 -1
  30. package/frontend/lib/components/common/Card.svelte +3 -3
  31. package/frontend/lib/components/common/Input.svelte +3 -3
  32. package/frontend/lib/components/common/LoadingSpinner.svelte +1 -1
  33. package/frontend/lib/components/common/Select.svelte +6 -6
  34. package/frontend/lib/components/common/Textarea.svelte +3 -3
  35. package/frontend/lib/components/files/FileViewer.svelte +1 -1
  36. package/frontend/lib/components/git/ChangesSection.svelte +2 -4
  37. package/frontend/lib/components/preview/browser/BrowserPreview.svelte +9 -29
  38. package/frontend/lib/components/preview/browser/components/Container.svelte +17 -0
  39. package/frontend/lib/components/preview/browser/components/VirtualCursor.svelte +2 -2
  40. package/frontend/lib/components/settings/appearance/AppearanceSettings.svelte +0 -6
  41. package/frontend/lib/components/settings/appearance/LayoutPresetSettings.svelte +15 -15
  42. package/frontend/lib/components/settings/appearance/LayoutPreview.svelte +2 -2
  43. package/frontend/lib/components/workspace/DesktopNavigator.svelte +380 -383
  44. package/frontend/lib/components/workspace/MobileNavigator.svelte +391 -395
  45. package/frontend/lib/components/workspace/PanelHeader.svelte +115 -4
  46. package/frontend/lib/components/workspace/ViewMenu.svelte +9 -25
  47. package/frontend/lib/components/workspace/layout/split-pane/Layout.svelte +29 -4
  48. package/frontend/lib/services/notification/global-stream-monitor.ts +77 -86
  49. package/frontend/lib/services/project/status.service.ts +160 -159
  50. package/frontend/lib/stores/ui/workspace.svelte.ts +326 -283
  51. package/package.json +1 -1
@@ -7,8 +7,8 @@
7
7
 
8
8
  const { toolInput }: { toolInput: GrepToolInput } = $props();
9
9
 
10
- const pattern = toolInput.input.pattern || '';
11
- const searchPath = toolInput.input.path || 'current directory';
10
+ const pattern = $derived(toolInput.input.pattern || '');
11
+ const searchPath = $derived(toolInput.input.path || 'current directory');
12
12
 
13
13
  // Get active search parameters for display
14
14
  function getActiveParameters() {
@@ -55,10 +55,10 @@
55
55
  return params;
56
56
  }
57
57
 
58
- const activeParameters = getActiveParameters();
58
+ const activeParameters = $derived(getActiveParameters());
59
59
 
60
- const formattedPattern = truncateText(pattern, 40);
61
- const formattedPath = formatPath(searchPath);
60
+ const formattedPattern = $derived(truncateText(pattern, 40));
61
+ const formattedPath = $derived(formatPath(searchPath));
62
62
  </script>
63
63
 
64
64
  <div class="bg-white dark:bg-slate-800 rounded-md border border-slate-200/60 dark:border-slate-700/60 p-3">
@@ -5,7 +5,7 @@
5
5
 
6
6
  const { toolInput }: { toolInput: ListMcpResourcesToolInput } = $props();
7
7
 
8
- const server = toolInput.input.server;
8
+ const server = $derived(toolInput.input.server);
9
9
  </script>
10
10
 
11
11
  <div class="bg-white dark:bg-slate-800 rounded-md border border-slate-200/60 dark:border-slate-700/60 p-3">
@@ -5,12 +5,12 @@
5
5
 
6
6
  const { toolInput }: { toolInput: NotebookEditToolInput } = $props();
7
7
 
8
- const notebookPath = toolInput.input.notebook_path;
9
- const fileName = notebookPath.split(/[/\\]/).pop() || notebookPath;
10
- const cellId = toolInput.input.cell_id;
11
- const cellType = toolInput.input.cell_type || 'code';
12
- const editMode = toolInput.input.edit_mode || 'replace';
13
- const newSource = toolInput.input.new_source;
8
+ const notebookPath = $derived(toolInput.input.notebook_path);
9
+ const fileName = $derived(notebookPath.split(/[/\\]/).pop() || notebookPath);
10
+ const cellId = $derived(toolInput.input.cell_id);
11
+ const cellType = $derived(toolInput.input.cell_type || 'code');
12
+ const editMode = $derived(toolInput.input.edit_mode || 'replace');
13
+ const newSource = $derived(toolInput.input.new_source);
14
14
  </script>
15
15
 
16
16
  <FileHeader filePath={notebookPath} fileName={fileName} />
@@ -5,8 +5,8 @@
5
5
 
6
6
  const { toolInput }: { toolInput: ReadMcpResourceToolInput } = $props();
7
7
 
8
- const server = toolInput.input.server;
9
- const uri = toolInput.input.uri;
8
+ const server = $derived(toolInput.input.server);
9
+ const uri = $derived(toolInput.input.uri);
10
10
  </script>
11
11
 
12
12
  <div class="bg-white dark:bg-slate-800 rounded-md border border-slate-200/60 dark:border-slate-700/60 p-3">
@@ -5,10 +5,10 @@
5
5
 
6
6
  const { toolInput }: { toolInput: ReadToolInput } = $props();
7
7
 
8
- const filePath = toolInput.input.file_path || '';
9
- const fileName = filePath.split(/[/\\]/).pop() || filePath || 'unknown';
10
- const hasLimit = toolInput.input.limit !== undefined;
11
- const hasOffset = toolInput.input.offset !== undefined;
8
+ const filePath = $derived(toolInput.input.file_path || '');
9
+ const fileName = $derived(filePath.split(/[/\\]/).pop() || filePath || 'unknown');
10
+ const hasLimit = $derived(toolInput.input.limit !== undefined);
11
+ const hasOffset = $derived(toolInput.input.offset !== undefined);
12
12
  </script>
13
13
 
14
14
  <div class="bg-white dark:bg-slate-800 rounded-md border border-slate-200/60 dark:border-slate-700/60 p-3">
@@ -5,7 +5,7 @@
5
5
 
6
6
  const { toolInput }: { toolInput: TaskStopToolInput } = $props();
7
7
 
8
- const taskId = toolInput.input.task_id || toolInput.input.shell_id || 'unknown';
8
+ const taskId = $derived(toolInput.input.task_id || toolInput.input.shell_id || 'unknown');
9
9
  </script>
10
10
 
11
11
  <div class="bg-white dark:bg-slate-800 rounded-md border border-slate-200/60 dark:border-slate-700/60 p-3">
@@ -15,7 +15,7 @@
15
15
  return desc;
16
16
  }
17
17
 
18
- const formattedDesc = formatDescription(toolInput.input.description || '');
18
+ const formattedDesc = $derived(formatDescription(toolInput.input.description || ''));
19
19
  </script>
20
20
 
21
21
  <div class="bg-white dark:bg-slate-800 rounded-md border border-slate-200/60 dark:border-slate-700/60 p-3">
@@ -5,10 +5,10 @@
5
5
 
6
6
  const { toolInput }: { toolInput: TodoWriteToolInput } = $props();
7
7
 
8
- const todos = toolInput.input.todos;
9
- const totalTodos = todos.length;
10
- const completedTodos = todos.filter((t) => t.status === 'completed').length;
11
- const percentage = totalTodos > 0 ? Math.round((completedTodos / totalTodos) * 100) : 0;
8
+ const todos = $derived(toolInput.input.todos);
9
+ const totalTodos = $derived(todos.length);
10
+ const completedTodos = $derived(todos.filter((t) => t.status === 'completed').length);
11
+ const percentage = $derived(totalTodos > 0 ? Math.round((completedTodos / totalTodos) * 100) : 0);
12
12
 
13
13
  // Helper functions for todo items
14
14
  function getStatusIcon(status: string): IconName {
@@ -15,7 +15,7 @@
15
15
  return query;
16
16
  }
17
17
 
18
- const formattedQuery = formatQuery(toolInput.input.query || '');
18
+ const formattedQuery = $derived(formatQuery(toolInput.input.query || ''));
19
19
  </script>
20
20
 
21
21
  <div class="bg-white dark:bg-slate-800 rounded-md border border-slate-200/60 dark:border-slate-700/60 p-3">
@@ -5,9 +5,9 @@
5
5
 
6
6
  const { toolInput }: { toolInput: WriteToolInput } = $props();
7
7
 
8
- const filePath = toolInput.input.file_path || '';
9
- const fileName = filePath.split(/[/\\]/).pop() || filePath || 'unknown';
10
- const content = toolInput.input.content || '';
8
+ const filePath = $derived(toolInput.input.file_path || '');
9
+ const fileName = $derived(filePath.split(/[/\\]/).pop() || filePath || 'unknown');
10
+ const content = $derived(toolInput.input.content || '');
11
11
  </script>
12
12
 
13
13
  <FileHeader
@@ -56,9 +56,9 @@
56
56
  }
57
57
  };
58
58
 
59
- const style = styles[type];
60
- const formattedCode = formatCode(code);
61
- const isTerminal = isTerminalOutput(code);
59
+ const style = $derived(styles[type]);
60
+ const formattedCode = $derived(formatCode(code));
61
+ const isTerminal = $derived(isTerminalOutput(code));
62
62
  </script>
63
63
 
64
64
  <div>
@@ -325,8 +325,8 @@
325
325
  return result;
326
326
  }
327
327
 
328
- const diffGroups = computeDiff(oldString, newString);
329
- const hasChanges = diffGroups.some(group => group.type === 'change');
328
+ const diffGroups = $derived(computeDiff(oldString, newString));
329
+ const hasChanges = $derived(diffGroups.some(group => group.type === 'change'));
330
330
  </script>
331
331
 
332
332
  <div>
@@ -14,7 +14,7 @@
14
14
 
15
15
  const { filePath, fileName, iconColor, badges = [], box = true }: Props = $props();
16
16
 
17
- const displayFileName = fileName || filePath.split(/[/\\]/).pop() || filePath;
17
+ const displayFileName = $derived(fileName || filePath.split(/[/\\]/).pop() || filePath);
18
18
  </script>
19
19
 
20
20
  <div class={box ? "bg-white dark:bg-slate-800 rounded-md border border-slate-200/60 dark:border-slate-700/60 p-3" : ""}>
@@ -13,7 +13,7 @@
13
13
  const { icon, text, iconColor = 'text-slate-500', textColor = 'text-slate-600 dark:text-slate-400', title="" }: Props = $props();
14
14
  </script>
15
15
 
16
- <div class="flex items-center gap-1.5" {title}>
17
- <Icon name={icon} class="{iconColor} w-3.5 h-3.5" />
16
+ <div class="flex items-start gap-1.5" {title}>
17
+ <Icon name={icon} class="{iconColor} w-3.5 h-3.5 flex-none" />
18
18
  <span class="text-xs {textColor}">{text}</span>
19
19
  </div>
@@ -12,7 +12,7 @@
12
12
 
13
13
  const { stats, columns = 3 }: Props = $props();
14
14
 
15
- const gridClass = columns === 2 ? 'grid-cols-2' : columns === 4 ? 'grid-cols-4' : 'grid-cols-3';
15
+ const gridClass = $derived(columns === 2 ? 'grid-cols-2' : columns === 4 ? 'grid-cols-4' : 'grid-cols-3');
16
16
  </script>
17
17
 
18
18
  <div class="grid {gridClass} gap-4 text-center text-sm">
@@ -15,7 +15,7 @@
15
15
  return { mainCommand, args };
16
16
  }
17
17
 
18
- const { mainCommand, args } = parseCommandParts(command);
18
+ const parsedCommand = $derived(parseCommandParts(command));
19
19
  </script>
20
20
 
21
21
  <!-- Description (if provided) -->
@@ -38,15 +38,15 @@
38
38
  </div>
39
39
  {/if}
40
40
  </div>
41
-
41
+
42
42
  <!-- Terminal-style command display -->
43
43
  <div class="bg-slate-50 dark:bg-slate-950 border border-slate-200/60 dark:border-slate-800/60 rounded-md p-2.5 font-mono text-sm">
44
44
  <div class="flex items-start gap-2">
45
45
  <span class="text-green-600 dark:text-green-400 select-none">$</span>
46
46
  <div class="flex-1 text-slate-900 dark:text-slate-200 break-all">
47
- <span class="text-violet-600 dark:text-violet-300 font-medium">{mainCommand}</span>
48
- {#if args.length > 0}
49
- <span class="text-slate-700 dark:text-slate-300"> {args.join(' ')}</span>
47
+ <span class="text-violet-600 dark:text-violet-300 font-medium">{parsedCommand.mainCommand}</span>
48
+ {#if parsedCommand.args.length > 0}
49
+ <span class="text-slate-700 dark:text-slate-300"> {parsedCommand.args.join(' ')}</span>
50
50
  {/if}
51
51
  </div>
52
52
  </div>
@@ -33,7 +33,7 @@
33
33
  lg: 'px-4 md:px-6 py-3 text-sm md:text-base rounded-lg'
34
34
  };
35
35
 
36
- const buttonClasses = `${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]} ${className}`;
36
+ const buttonClasses = $derived(`${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]} ${className}`);
37
37
 
38
38
  function handleClick() {
39
39
  if (!disabled && !loading && onclick) {
@@ -31,11 +31,11 @@
31
31
  lg: 'p-4 md:p-6'
32
32
  };
33
33
 
34
- const clickableClasses = clickable
34
+ const clickableClasses = $derived(clickable
35
35
  ? 'cursor-pointer hover:bg-slate-50 dark:hover:bg-slate-800'
36
- : '';
36
+ : '');
37
37
 
38
- const cardClasses = `${baseClasses} ${variantClasses[variant]} ${paddingClasses[padding]} ${clickableClasses} ${className}`;
38
+ const cardClasses = $derived(`${baseClasses} ${variantClasses[variant]} ${paddingClasses[padding]} ${clickableClasses} ${className}`);
39
39
 
40
40
  function handleClick() {
41
41
  if (clickable && onclick) {
@@ -22,14 +22,14 @@
22
22
  const baseClasses =
23
23
  'block w-full px-3 py-3 border border-slate-300 dark:border-slate-600 rounded-lg transition-colors duration-200 focus:outline-none text-sm font-medium';
24
24
 
25
- const stateClasses = error
25
+ const stateClasses = $derived(error
26
26
  ? 'border-red-400 focus:border-red-500 focus:ring-2 focus:ring-red-200 dark:focus:ring-red-900/20'
27
- : 'focus:border-violet-500 focus:ring-2 focus:ring-violet-200 dark:focus:ring-violet-900/20';
27
+ : 'focus:border-violet-500 focus:ring-2 focus:ring-violet-200 dark:focus:ring-violet-900/20');
28
28
 
29
29
  const backgroundClasses =
30
30
  'bg-white dark:bg-slate-900 text-slate-900 dark:text-slate-100 placeholder-slate-400 dark:placeholder-slate-500';
31
31
 
32
- const inputClasses = `${baseClasses} ${stateClasses} ${backgroundClasses} ${className}`;
32
+ const inputClasses = $derived(`${baseClasses} ${stateClasses} ${backgroundClasses} ${className}`);
33
33
 
34
34
  function handleInput(event: Event) {
35
35
  const target = event.target as HTMLInputElement;
@@ -35,7 +35,7 @@
35
35
  xl: 'text-xl'
36
36
  };
37
37
 
38
- const spinnerClasses = `animate-spin rounded-full border-2 ${sizeClasses[size]} ${color ? colorClasses[color] : ''} border-t-transparent ${className}`;
38
+ const spinnerClasses = $derived(`animate-spin rounded-full border-2 ${sizeClasses[size]} ${color ? colorClasses[color] : ''} border-t-transparent ${className}`);
39
39
  </script>
40
40
 
41
41
  <div class="flex flex-col items-center space-y-2" {...props}>
@@ -22,18 +22,18 @@
22
22
  const baseClasses =
23
23
  'block w-full px-3 py-3 pr-10 border border-slate-300 dark:border-slate-600 rounded-lg transition-colors duration-200 focus:outline-none text-sm font-medium appearance-none cursor-pointer';
24
24
 
25
- const stateClasses = error
25
+ const stateClasses = $derived(error
26
26
  ? 'border-red-400 focus:border-red-500 focus:ring-2 focus:ring-red-200 dark:focus:ring-red-900/20'
27
- : 'focus:border-violet-500 focus:ring-2 focus:ring-violet-200 dark:focus:ring-violet-900/20';
27
+ : 'focus:border-violet-500 focus:ring-2 focus:ring-violet-200 dark:focus:ring-violet-900/20');
28
28
 
29
29
  const backgroundClasses =
30
30
  'bg-white dark:bg-slate-900 text-slate-900 dark:text-slate-100';
31
31
 
32
- const disabledClasses = disabled
33
- ? 'cursor-not-allowed opacity-60'
34
- : '';
32
+ const disabledClasses = $derived(disabled
33
+ ? 'cursor-not-allowed opacity-60'
34
+ : '');
35
35
 
36
- const selectClasses = `${baseClasses} ${stateClasses} ${backgroundClasses} ${disabledClasses} ${className}`;
36
+ const selectClasses = $derived(`${baseClasses} ${stateClasses} ${backgroundClasses} ${disabledClasses} ${className}`);
37
37
 
38
38
  function handleChange(event: Event) {
39
39
  const target = event.target as HTMLSelectElement;
@@ -23,9 +23,9 @@
23
23
  const baseClasses =
24
24
  'block w-full px-3 py-3 border border-slate-300 dark:border-slate-600 rounded-lg transition-colors duration-200 focus:outline-none text-sm font-medium';
25
25
 
26
- const stateClasses = error
26
+ const stateClasses = $derived(error
27
27
  ? 'border-red-400 focus:border-red-500 focus:ring-2 focus:ring-red-200 dark:focus:ring-red-900/20'
28
- : 'focus:border-violet-500 focus:ring-2 focus:ring-violet-200 dark:focus:ring-violet-900/20';
28
+ : 'focus:border-violet-500 focus:ring-2 focus:ring-violet-200 dark:focus:ring-violet-900/20');
29
29
 
30
30
  const backgroundClasses =
31
31
  'bg-white dark:bg-slate-900 text-slate-900 dark:text-slate-100 placeholder-slate-400 dark:placeholder-slate-500';
@@ -37,7 +37,7 @@
37
37
  vertical: 'resize-y'
38
38
  };
39
39
 
40
- const textareaClasses = `${baseClasses} ${stateClasses} ${backgroundClasses} ${resizeClasses[resize]} ${className}`;
40
+ const textareaClasses = $derived(`${baseClasses} ${stateClasses} ${backgroundClasses} ${resizeClasses[resize]} ${className}`);
41
41
 
42
42
  function handleInput(event: Event) {
43
43
  const target = event.target as HTMLTextAreaElement;
@@ -439,7 +439,7 @@
439
439
  <!-- External change badge + refresh button -->
440
440
  {#if externallyChanged && onForceReload}
441
441
  <div class="flex items-center gap-1 mr-1">
442
- <span class="text-[10px] px-1.5 py-0.5 rounded bg-amber-100 dark:bg-amber-900/50 text-amber-700 dark:text-amber-400 font-medium whitespace-nowrap">
442
+ <span class="text-3xs px-1.5 py-0.5 rounded bg-amber-100 dark:bg-amber-900/50 text-amber-700 dark:text-amber-400 font-medium whitespace-nowrap">
443
443
  Changed externally
444
444
  </span>
445
445
  <button
@@ -20,15 +20,13 @@
20
20
  onResolve?: (path: string) => void;
21
21
  }
22
22
 
23
- const {
23
+ let {
24
24
  title, icon, files, section,
25
- collapsed = false,
25
+ collapsed: isCollapsed = $bindable(false),
26
26
  onStage, onUnstage, onDiscard,
27
27
  onStageAll, onUnstageAll, onDiscardAll,
28
28
  onViewDiff, onResolve
29
29
  }: Props = $props();
30
-
31
- let isCollapsed = $state(collapsed);
32
30
  </script>
33
31
 
34
32
  {#if files.length > 0}
@@ -112,7 +112,7 @@
112
112
  }
113
113
  },
114
114
  onMcpCursorHide: () => {
115
- mcpVirtualCursor = { ...mcpVirtualCursor, visible: false };
115
+ // Cursor stays visible between automated steps until user interacts manually
116
116
  },
117
117
  transformBrowserToDisplayCoordinates: (browserX, browserY) => {
118
118
  return transformBrowserToDisplayCoordinates(browserX, browserY);
@@ -360,44 +360,33 @@
360
360
  function handleCanvasInteraction(action: any) {
361
361
  const tab = activeTab;
362
362
  if (tab && tab.sessionId) {
363
+ // Hide the AI cursor instantly if the human explicitly interacts
364
+ mcpVirtualCursor = { ...mcpVirtualCursor, visible: false };
363
365
  coordinator.sendInteraction(action);
364
366
  }
365
367
  }
366
368
 
367
369
  function transformBrowserToDisplayCoordinates(browserX: number, browserY: number): { x: number, y: number } | null {
368
370
  let canvasElement: HTMLCanvasElement | null = null;
369
-
370
- // Try to get canvas element from canvasAPI first (preferred method)
371
371
  if (canvasAPI && canvasAPI.getCanvasElement) {
372
372
  canvasElement = canvasAPI.getCanvasElement();
373
373
  }
374
-
375
- // Fallback: Try to get canvas element directly from DOM if canvasAPI not ready yet
376
- // This handles race condition where context menu event arrives before canvasAPI is set
377
374
  if (!canvasElement && typeof document !== 'undefined') {
378
- // Canvas is rendered inside Container component
379
- const foundCanvas = document.querySelector('canvas[tabindex="0"]') as HTMLCanvasElement;
380
- if (foundCanvas) {
381
- canvasElement = foundCanvas;
382
- debug.log('preview', `Using fallback DOM query to get canvas element`);
383
- }
384
- }
385
-
386
- if (!canvasElement) {
387
- debug.warn('preview', `Transform coordinates failed: canvasElement not found (browser: ${browserX}, ${browserY})`);
388
- return null;
375
+ canvasElement = document.querySelector('canvas[tabindex="0"]') as HTMLCanvasElement;
389
376
  }
377
+ if (!canvasElement) return null;
390
378
 
391
379
  try {
392
380
  const canvasRect = canvasElement.getBoundingClientRect();
381
+ if (!canvasRect || canvasElement.width === 0 || canvasElement.height === 0) return null;
382
+
393
383
  const scaleX = canvasRect.width / canvasElement.width;
394
384
  const scaleY = canvasRect.height / canvasElement.height;
395
385
  const screenX = canvasRect.left + (browserX * scaleX);
396
386
  const screenY = canvasRect.top + (browserY * scaleY);
397
387
 
398
388
  return { x: screenX, y: screenY };
399
- } catch (error) {
400
- debug.error('preview', 'Error transforming coordinates:', error);
389
+ } catch (e) {
401
390
  return null;
402
391
  }
403
392
  }
@@ -500,6 +489,7 @@
500
489
  bind:isStreamReady
501
490
  bind:errorMessage
502
491
  bind:virtualCursor
492
+ bind:mcpVirtualCursor
503
493
  bind:canvasAPI
504
494
  bind:previewDimensions
505
495
  bind:lastFrameData={currentTabLastFrameData}
@@ -509,16 +499,6 @@
509
499
  />
510
500
  </div>
511
501
 
512
- <!-- Virtual Cursor - User -->
513
- {#if !isCurrentTabMcpControlled()}
514
- <VirtualCursor cursor={virtualCursor} />
515
- {/if}
516
-
517
- <!-- MCP Virtual Cursor -->
518
- {#if mcpHandler.mcpControlState.isControlled}
519
- <VirtualCursor cursor={mcpVirtualCursor} />
520
- {/if}
521
-
522
502
  <!-- Native UI Overlays -->
523
503
  <SelectDropdown
524
504
  bind:selectInfo={currentSelectInfo}
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import Icon from '$frontend/lib/components/common/Icon.svelte';
3
3
  import Canvas from './Canvas.svelte';
4
+ import VirtualCursor from './VirtualCursor.svelte';
4
5
  import { scale } from 'svelte/transition';
5
6
  import { cubicOut } from 'svelte/easing';
6
7
  import { onDestroy } from 'svelte';
@@ -27,6 +28,11 @@
27
28
  y: 0,
28
29
  visible: false
29
30
  }),
31
+ mcpVirtualCursor = $bindable<{ x: number; y: number; visible: boolean; clicking?: boolean }>({
32
+ x: 0,
33
+ y: 0,
34
+ visible: false
35
+ }),
30
36
  lastFrameData = $bindable<any>(null), // Add lastFrameData prop
31
37
 
32
38
  // MCP Control State
@@ -419,6 +425,7 @@
419
425
  </div> -->
420
426
  </div>
421
427
  {/if}
428
+
422
429
  </div>
423
430
  {:else}
424
431
  <div
@@ -431,6 +438,16 @@
431
438
  <p class="text-sm">Enter a URL to preview your web application</p>
432
439
  </div>
433
440
  {/if}
441
+
442
+ <!-- Virtual Cursor - User -->
443
+ {#if !isMcpControlled}
444
+ <VirtualCursor cursor={virtualCursor} />
445
+ {/if}
446
+
447
+ <!-- MCP Virtual Cursor -->
448
+ {#if mcpVirtualCursor.visible}
449
+ <VirtualCursor cursor={mcpVirtualCursor} />
450
+ {/if}
434
451
  </div>
435
452
 
436
453
  <style>
@@ -7,8 +7,8 @@
7
7
  <!-- Virtual Cursor for Autonomous Testing -->
8
8
  {#if cursor.visible}
9
9
  <div
10
- class="fixed pointer-events-none z-50 transition-all duration-100"
11
- style="left: {cursor.x - 4}px; top: {cursor.y - 2}px;"
10
+ class="fixed pointer-events-none z-50 transition-all duration-100 ease-out"
11
+ style="left: {cursor.x}px; top: {cursor.y}px; margin-left: -4.167px; margin-top: -2.5px;"
12
12
  >
13
13
  <!-- Cursor body with fixed size -->
14
14
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"
@@ -1,7 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { themeStore, toggleDarkMode } from '$frontend/lib/stores/ui/theme.svelte';
3
3
  import Icon from '../../common/Icon.svelte';
4
- import LayoutPresetSettings from './LayoutPresetSettings.svelte';
5
4
  </script>
6
5
 
7
6
  <div class="py-1">
@@ -44,8 +43,3 @@
44
43
  </div>
45
44
  </div>
46
45
  </div>
47
-
48
- <!-- Layout Presets -->
49
- <div class="mt-6">
50
- <LayoutPresetSettings />
51
- </div>
@@ -8,34 +8,34 @@
8
8
  // Group presets by category
9
9
  const presetCategories = [
10
10
  {
11
- name: 'Single Panel Focus',
12
- description: 'Full screen layouts for maximum focus',
11
+ name: 'Single Panel',
12
+ description: 'Full screen focus mode',
13
13
  icon: 'lucide:maximize-2',
14
- presets: builtInPresets.slice(0, 5)
14
+ presets: builtInPresets.slice(0, 1)
15
15
  },
16
16
  {
17
- name: 'Two Panel Layouts',
18
- description: 'Dual focus workflows',
17
+ name: 'Two Panels',
18
+ description: 'Dual panel layouts',
19
19
  icon: 'lucide:columns-2',
20
- presets: builtInPresets.slice(5, 13)
20
+ presets: builtInPresets.slice(1, 4)
21
21
  },
22
22
  {
23
- name: 'Three Panel Layouts',
24
- description: 'Balanced multi-panel setups',
23
+ name: 'Three Panels',
24
+ description: 'Multi-panel arrangements',
25
25
  icon: 'lucide:columns-3',
26
- presets: builtInPresets.slice(13, 22)
26
+ presets: builtInPresets.slice(4, 8)
27
27
  },
28
28
  {
29
- name: 'Four Panel Layouts',
30
- description: 'All panels visible',
29
+ name: 'Four Panels',
30
+ description: 'Complex grid layouts',
31
31
  icon: 'lucide:layout-grid',
32
- presets: builtInPresets.slice(22, 26)
32
+ presets: builtInPresets.slice(8, 11)
33
33
  },
34
34
  {
35
- name: 'Five Panel Layouts',
36
- description: 'Full workspace with source control',
35
+ name: 'Five Panels',
36
+ description: 'Full workspace grid',
37
37
  icon: 'lucide:layout-dashboard',
38
- presets: builtInPresets.slice(26, 28)
38
+ presets: builtInPresets.slice(11, 12)
39
39
  }
40
40
  ];
41
41
 
@@ -8,7 +8,7 @@
8
8
 
9
9
  const { layout, size = 'small' }: Props = $props();
10
10
 
11
- const height = size === 'small' ? 'h-8' : 'h-12';
11
+ const height = $derived(size === 'small' ? 'h-8' : 'h-12');
12
12
 
13
13
  // Panel colors - softer colors that blend with theme
14
14
  const panelColors: Record<PanelId, string> = {
@@ -16,7 +16,7 @@
16
16
  files: 'bg-blue-200/60 dark:bg-blue-700/40',
17
17
  preview: 'bg-emerald-200/60 dark:bg-emerald-700/40',
18
18
  terminal: 'bg-amber-200/60 dark:bg-amber-700/40',
19
- git: 'bg-orange-200/60 dark:bg-orange-700/40'
19
+ git: 'bg-pink-200/60 dark:bg-pink-700/40'
20
20
  };
21
21
 
22
22
  // Panel labels (optional, for larger previews)