@nocturnium/svelte-ide 1.0.2 → 1.0.4

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 (78) hide show
  1. package/README.md +5 -3
  2. package/dist/components/ai/AIMessageContent.svelte +24 -14
  3. package/dist/components/ai/AIPanel.svelte +22 -0
  4. package/dist/components/editor/CollaborativeEditor.svelte +68 -5
  5. package/dist/components/editor/CollaborativeEditor.svelte.d.ts +14 -0
  6. package/dist/components/editor/ContextLens.svelte +16 -10
  7. package/dist/components/editor/CustomEditor.svelte +52 -33
  8. package/dist/components/editor/CustomEditor.svelte.d.ts +2 -2
  9. package/dist/components/editor/EchoCursorLayer.svelte +43 -11
  10. package/dist/components/editor/Editor.svelte +17 -0
  11. package/dist/components/editor/Editor.svelte.d.ts +9 -0
  12. package/dist/components/editor/EditorPane.svelte +18 -1
  13. package/dist/components/editor/EditorPane.svelte.d.ts +5 -0
  14. package/dist/components/editor/EditorSelections.svelte +27 -11
  15. package/dist/components/editor/EditorSelections.svelte.d.ts +1 -0
  16. package/dist/components/editor/GhostBracketLayer.svelte +38 -25
  17. package/dist/components/editor/core/folding.d.ts +11 -0
  18. package/dist/components/editor/core/folding.js +41 -0
  19. package/dist/components/editor/core/index.d.ts +0 -5
  20. package/dist/components/editor/core/index.js +4 -5
  21. package/dist/components/editor/core/state.d.ts +5 -0
  22. package/dist/components/editor/core/state.js +131 -12
  23. package/dist/components/editor/editor-find.d.ts +1 -0
  24. package/dist/components/editor/editor-find.js +6 -5
  25. package/dist/components/editor/editor-input.d.ts +1 -0
  26. package/dist/components/editor/editor-input.js +4 -1
  27. package/dist/components/editor/editor-scroll.d.ts +1 -0
  28. package/dist/components/editor/editor-scroll.js +2 -1
  29. package/dist/components/editor/index.d.ts +19 -3
  30. package/dist/components/editor/index.js +18 -4
  31. package/dist/components/editor/tokenizer/base.d.ts +1 -25
  32. package/dist/components/editor/tokenizer/base.js +0 -172
  33. package/dist/components/editor/tokenizer/index.d.ts +4 -0
  34. package/dist/components/editor/tokenizer/index.js +1 -1
  35. package/dist/components/editor/tokenizer/languages/html.d.ts +3 -2
  36. package/dist/components/editor/tokenizer/languages/html.js +64 -6
  37. package/dist/components/editor/tokenizer/languages/javascript.d.ts +0 -3
  38. package/dist/components/editor/tokenizer/languages/javascript.js +1 -2
  39. package/dist/components/editor/tokenizer/languages/svelte.d.ts +1 -1
  40. package/dist/components/editor/tokenizer/languages/svelte.js +6 -1
  41. package/dist/components/editor/tokenizer/types.d.ts +0 -28
  42. package/dist/crdt/awareness.d.ts +8 -2
  43. package/dist/crdt/awareness.js +11 -4
  44. package/dist/crdt/document.d.ts +10 -1
  45. package/dist/crdt/document.js +15 -7
  46. package/dist/crdt/index.d.ts +8 -2
  47. package/dist/crdt/index.js +5 -2
  48. package/dist/crdt/undo.d.ts +2 -7
  49. package/dist/crdt/undo.js +1 -8
  50. package/dist/index.d.ts +7 -9
  51. package/dist/index.js +7 -9
  52. package/dist/services/error-handling.d.ts +2 -11
  53. package/dist/services/error-handling.js +15 -4
  54. package/dist/services/lsp-client.d.ts +3 -0
  55. package/dist/services/lsp-client.js +55 -10
  56. package/dist/services/mock-ai.js +1 -1
  57. package/dist/services/optimistic.d.ts +8 -5
  58. package/dist/services/optimistic.js +36 -10
  59. package/dist/services/vfs-client.js +11 -3
  60. package/dist/stores/agents.svelte.js +3 -2
  61. package/dist/stores/ai-persistence.svelte.js +7 -2
  62. package/dist/stores/ai.svelte.js +3 -2
  63. package/dist/stores/collaboration.svelte.d.ts +1 -1
  64. package/dist/stores/collaboration.svelte.js +3 -2
  65. package/dist/stores/editor.svelte.js +29 -5
  66. package/dist/stores/layout.svelte.js +3 -0
  67. package/dist/stores/plugin.svelte.js +9 -3
  68. package/dist/stores/vfs.svelte.js +26 -9
  69. package/dist/styles/theme.css +43 -0
  70. package/dist/types/vfs.d.ts +15 -1
  71. package/dist/types/vfs.js +9 -0
  72. package/dist/utils/language.d.ts +4 -3
  73. package/dist/utils/language.js +8 -18
  74. package/package.json +1 -1
  75. package/dist/components/editor/MinimalEditor.svelte +0 -75
  76. package/dist/components/editor/MinimalEditor.svelte.d.ts +0 -6
  77. package/dist/components/editor/MinimalEditor2.svelte +0 -84
  78. package/dist/components/editor/MinimalEditor2.svelte.d.ts +0 -6
@@ -96,6 +96,23 @@
96
96
  return `top: ${top}px; left: ${left}px;`;
97
97
  }
98
98
 
99
+ function getEchoColorToken(cursor: EchoCursor): string {
100
+ const tokens = [
101
+ '--ide-success',
102
+ '--ide-warning',
103
+ '--ide-syntax-tag',
104
+ '--ide-syntax-type',
105
+ '--ide-ai-assistant',
106
+ '--ide-error',
107
+ '--ide-accent',
108
+ '--ide-accent-strong',
109
+ '--ide-syntax-function',
110
+ '--ide-syntax-string'
111
+ ];
112
+ const index = echoCursors.findIndex((echo) => echo.id === cursor.id);
113
+ return tokens[Math.max(index, 0) % tokens.length];
114
+ }
115
+
99
116
  /**
100
117
  * Handle remove echo click
101
118
  */
@@ -116,7 +133,7 @@
116
133
  class="echo-cursor"
117
134
  class:echo-cursor--replaying={isReplaying}
118
135
  class:echo-cursor--active={cursor.active}
119
- style="{getCursorStyle(cursor)} --echo-color: {cursor.color};"
136
+ style="{getCursorStyle(cursor)} --echo-color: var({getEchoColorToken(cursor)});"
120
137
  >
121
138
  <!-- Cursor line -->
122
139
  <div class="echo-cursor__caret" style="height: {lineHeight}px;"></div>
@@ -228,7 +245,7 @@
228
245
  left: 0;
229
246
  padding: 2px 6px;
230
247
  background: var(--echo-color);
231
- color: #fff;
248
+ color: var(--ide-text-inverse);
232
249
  font-size: 10px;
233
250
  font-weight: 600;
234
251
  border-radius: 4px;
@@ -241,7 +258,7 @@
241
258
  left: 0;
242
259
  margin-top: 2px;
243
260
  padding: 1px 4px;
244
- background: rgba(0, 0, 0, 0.7);
261
+ background: var(--ide-bg-overlay);
245
262
  color: var(--echo-color);
246
263
  font-size: 9px;
247
264
  font-family: monospace;
@@ -262,10 +279,10 @@
262
279
  width: 14px;
263
280
  height: 14px;
264
281
  padding: 0;
265
- background: rgba(239, 68, 68, 0.9);
282
+ background: color-mix(in srgb, var(--ide-error) 90%, transparent);
266
283
  border: none;
267
284
  border-radius: 50%;
268
- color: #fff;
285
+ color: var(--ide-text-inverse);
269
286
  font-size: 10px;
270
287
  line-height: 14px;
271
288
  text-align: center;
@@ -279,7 +296,7 @@
279
296
  }
280
297
 
281
298
  .echo-cursor__remove:hover {
282
- background: #ef4444;
299
+ background: var(--ide-error);
283
300
  }
284
301
 
285
302
  .echo-cursor__replay {
@@ -288,13 +305,13 @@
288
305
  left: 4px;
289
306
  padding: 2px 6px;
290
307
  background: var(--echo-color);
291
- color: #fff;
308
+ color: var(--ide-text-inverse);
292
309
  font-size: 12px;
293
310
  font-family: monospace;
294
311
  border-radius: 4px;
295
312
  white-space: pre;
296
313
  transition: opacity 0.2s ease;
297
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
314
+ box-shadow: var(--ide-shadow-md);
298
315
  }
299
316
 
300
317
  .echo-cursor__ripple {
@@ -330,11 +347,11 @@
330
347
  align-items: center;
331
348
  gap: 6px;
332
349
  padding: 6px 12px;
333
- background: rgba(34, 197, 94, 0.15);
334
- border: 1px solid rgba(34, 197, 94, 0.3);
350
+ background: color-mix(in srgb, var(--ide-success) 15%, transparent);
351
+ border: 1px solid color-mix(in srgb, var(--ide-success) 30%, transparent);
335
352
  border-radius: 6px;
336
353
  font-size: 12px;
337
- color: #22c55e;
354
+ color: var(--ide-success);
338
355
  pointer-events: auto;
339
356
  }
340
357
 
@@ -360,4 +377,19 @@
360
377
  .echo-mode-indicator__count {
361
378
  font-weight: 500;
362
379
  }
380
+
381
+ @media (prefers-reduced-motion: reduce) {
382
+ .echo-cursor__caret,
383
+ .echo-cursor--replaying .echo-cursor__caret,
384
+ .echo-cursor__ripple,
385
+ .echo-mode-indicator__icon {
386
+ animation: none;
387
+ }
388
+
389
+ .echo-cursor__delay,
390
+ .echo-cursor__remove,
391
+ .echo-cursor__replay {
392
+ transition: none;
393
+ }
394
+ }
363
395
  </style>
@@ -8,6 +8,7 @@
8
8
 
9
9
  import CustomEditor from './CustomEditor.svelte';
10
10
  import type { EditorPreferences } from '../../types';
11
+ import type { AIAwareness } from './core/ai-awareness';
11
12
 
12
13
  interface Props {
13
14
  /** Document content */
@@ -20,6 +21,14 @@
20
21
  preferences?: Partial<EditorPreferences>;
21
22
  /** Additional CSS class */
22
23
  class?: string;
24
+ /** Enable code folding */
25
+ folding?: boolean;
26
+ /** Enable multi-cursor editing */
27
+ multiCursor?: boolean;
28
+ /** Maximum number of cursors */
29
+ maxCursors?: number;
30
+ /** AI agents for Ghost Pair visualization */
31
+ aiAgents?: AIAwareness[];
23
32
  /** Called when content changes */
24
33
  onChange?: (content: string) => void;
25
34
  /** Called when cursor position changes */
@@ -34,6 +43,10 @@
34
43
  readonly = false,
35
44
  preferences = {},
36
45
  class: className = '',
46
+ folding = true,
47
+ multiCursor = true,
48
+ maxCursors = 100,
49
+ aiAgents = [],
37
50
  onChange,
38
51
  onCursorChange,
39
52
  onSave
@@ -46,6 +59,10 @@
46
59
  {language}
47
60
  {readonly}
48
61
  {preferences}
62
+ {folding}
63
+ {multiCursor}
64
+ {maxCursors}
65
+ {aiAgents}
49
66
  {onChange}
50
67
  {onCursorChange}
51
68
  {onSave}
@@ -1,4 +1,5 @@
1
1
  import type { EditorPreferences } from '../../types';
2
+ import type { AIAwareness } from './core/ai-awareness';
2
3
  interface Props {
3
4
  /** Document content */
4
5
  content: string;
@@ -10,6 +11,14 @@ interface Props {
10
11
  preferences?: Partial<EditorPreferences>;
11
12
  /** Additional CSS class */
12
13
  class?: string;
14
+ /** Enable code folding */
15
+ folding?: boolean;
16
+ /** Enable multi-cursor editing */
17
+ multiCursor?: boolean;
18
+ /** Maximum number of cursors */
19
+ maxCursors?: number;
20
+ /** AI agents for Ghost Pair visualization */
21
+ aiAgents?: AIAwareness[];
13
22
  /** Called when content changes */
14
23
  onChange?: (content: string) => void;
15
24
  /** Called when cursor position changes */
@@ -2,6 +2,7 @@
2
2
  import Editor from './Editor.svelte';
3
3
  import EditorTabs from './EditorTabs.svelte';
4
4
  import type { EditorPreferences } from '../../types';
5
+ import type { AIAwareness } from './core/ai-awareness';
5
6
  import {
6
7
  getTabs,
7
8
  getActiveTab,
@@ -15,11 +16,23 @@
15
16
 
16
17
  interface Props {
17
18
  preferences?: Partial<EditorPreferences>;
19
+ folding?: boolean;
20
+ multiCursor?: boolean;
21
+ maxCursors?: number;
22
+ aiAgents?: AIAwareness[];
18
23
  onSave?: (path: string, content: string) => Promise<void>;
19
24
  class?: string;
20
25
  }
21
26
 
22
- let { preferences = {}, onSave, class: className = '' }: Props = $props();
27
+ let {
28
+ preferences = {},
29
+ folding = true,
30
+ multiCursor = true,
31
+ maxCursors = 100,
32
+ aiAgents = [],
33
+ onSave,
34
+ class: className = ''
35
+ }: Props = $props();
23
36
 
24
37
  // Use getter functions for reactive access
25
38
  let tabs = $derived(getTabs());
@@ -70,6 +83,10 @@
70
83
  language={activeTab.language}
71
84
  readonly={activeTab.aiEditing}
72
85
  {preferences}
86
+ {folding}
87
+ {multiCursor}
88
+ {maxCursors}
89
+ {aiAgents}
73
90
  onChange={handleChange}
74
91
  onCursorChange={handleCursorChange}
75
92
  onSave={handleSave}
@@ -1,6 +1,11 @@
1
1
  import type { EditorPreferences } from '../../types';
2
+ import type { AIAwareness } from './core/ai-awareness';
2
3
  interface Props {
3
4
  preferences?: Partial<EditorPreferences>;
5
+ folding?: boolean;
6
+ multiCursor?: boolean;
7
+ maxCursors?: number;
8
+ aiAgents?: AIAwareness[];
4
9
  onSave?: (path: string, content: string) => Promise<void>;
5
10
  class?: string;
6
11
  }
@@ -26,6 +26,7 @@
26
26
  viewportHeight: number;
27
27
  getLine: (n: number) => { text: string } | undefined;
28
28
  lineCount: number;
29
+ lineToVisualRow?: (line: number) => number;
29
30
  }
30
31
 
31
32
  let {
@@ -39,7 +40,8 @@
39
40
  scrollTop,
40
41
  viewportHeight,
41
42
  getLine,
42
- lineCount
43
+ lineCount,
44
+ lineToVisualRow = (line) => line
43
45
  }: Props = $props();
44
46
 
45
47
  // Selection rects memoization
@@ -70,17 +72,17 @@
70
72
 
71
73
  // Calculate visible line range (with 1-line buffer for smooth scrolling)
72
74
  const vh = viewportHeight || FALLBACK_VIEWPORT_HEIGHT;
73
- const firstVisibleLine = Math.max(0, Math.floor(scrollTop / lineHeight) - 1);
74
- const lastVisibleLine = Math.min(lineCount - 1, Math.ceil((scrollTop + vh) / lineHeight) + 1);
75
+ const firstVisibleRow = Math.max(0, Math.floor(scrollTop / lineHeight) - 1);
76
+ const lastVisibleRow = Math.max(0, Math.ceil((scrollTop + vh) / lineHeight) + 1);
75
77
 
76
78
  // Create cache key from all cursors, scroll position, and measurement values
77
79
  const cursorKeys = cursors
78
80
  .map(
79
81
  (c) =>
80
- `${c.id}:${c.selection.anchor.line}:${c.selection.anchor.column}-${c.selection.head.line}:${c.selection.head.column}`
82
+ `${c.id}:${c.selection.anchor.line}/${lineToVisualRow(c.selection.anchor.line)}:${c.selection.anchor.column}-${c.selection.head.line}/${lineToVisualRow(c.selection.head.line)}:${c.selection.head.column}`
81
83
  )
82
84
  .join('|');
83
- const key = `${cursorKeys}@${firstVisibleLine}-${lastVisibleLine}:${charWidth}:${lineHeight}:${gutterWidth}`;
85
+ const key = `${cursorKeys}@${firstVisibleRow}-${lastVisibleRow}:${charWidth}:${lineHeight}:${gutterWidth}`;
84
86
  if (key === cachedSelectionKey) {
85
87
  return cachedSelectionRects;
86
88
  }
@@ -100,13 +102,14 @@
100
102
  const start = getSelectionStart(cursor.selection);
101
103
  const end = getSelectionEnd(cursor.selection);
102
104
 
103
- // Only iterate over lines that are both selected AND visible
104
- const renderStart = Math.max(start.line, firstVisibleLine);
105
- const renderEnd = Math.min(end.line, lastVisibleLine);
105
+ const renderStart = Math.max(start.line, 0);
106
+ const renderEnd = Math.min(end.line, lineCount - 1);
106
107
 
107
108
  for (let line = renderStart; line <= renderEnd; line++) {
108
109
  const lineContent = getLine(line);
109
110
  if (!lineContent) continue;
111
+ const visualRow = lineToVisualRow(line);
112
+ if (visualRow < firstVisibleRow || visualRow > lastVisibleRow) continue;
110
113
 
111
114
  let startCol = 0;
112
115
  let endCol = lineContent.text.length;
@@ -122,7 +125,7 @@
122
125
  const width = Math.max((endCol - startCol) * charWidth, 4);
123
126
 
124
127
  rects.push({
125
- top: line * lineHeight,
128
+ top: visualRow * lineHeight,
126
129
  left: gutterWidth + contentPadding + startCol * charWidth,
127
130
  width,
128
131
  height: lineHeight,
@@ -139,7 +142,7 @@
139
142
  // Get cursor style for a specific cursor position
140
143
  function getCursorStyleForPosition(pos: Position): string {
141
144
  const left = gutterWidth + contentPadding + pos.column * charWidth;
142
- const top = pos.line * lineHeight;
145
+ const top = lineToVisualRow(pos.line) * lineHeight;
143
146
  return `left: ${left}px; top: ${top}px; height: ${lineHeight}px;`;
144
147
  }
145
148
  </script>
@@ -156,11 +159,12 @@
156
159
  </div>
157
160
 
158
161
  <!-- Cursors (all cursors rendered with primary/secondary distinction) -->
159
- {#if cursorVisible && !isReadonly}
162
+ {#if !isReadonly}
160
163
  {#each cursors as cursor (cursor.id)}
161
164
  <div
162
165
  class="custom-editor__cursor"
163
166
  class:custom-editor__cursor--secondary={!cursor.isPrimary}
167
+ class:custom-editor__cursor--hidden={!cursorVisible}
164
168
  style={getCursorStyleForPosition(cursor.selection.head)}
165
169
  ></div>
166
170
  {/each}
@@ -193,6 +197,7 @@
193
197
  background: var(--color-nocturnium-aurora-blue);
194
198
  z-index: 10;
195
199
  pointer-events: none;
200
+ opacity: 1;
196
201
  transition: opacity 80ms;
197
202
  }
198
203
 
@@ -201,4 +206,15 @@
201
206
  background: var(--ide-interactive-muted);
202
207
  opacity: 0.85;
203
208
  }
209
+
210
+ .custom-editor__cursor--hidden {
211
+ opacity: 0;
212
+ }
213
+
214
+ @media (prefers-reduced-motion: reduce) {
215
+ .custom-editor__cursor {
216
+ opacity: 1;
217
+ transition: none;
218
+ }
219
+ }
204
220
  </style>
@@ -19,6 +19,7 @@ interface Props {
19
19
  text: string;
20
20
  } | undefined;
21
21
  lineCount: number;
22
+ lineToVisualRow?: (line: number) => number;
22
23
  }
23
24
  declare const EditorSelections: import("svelte").Component<Props, {}, "">;
24
25
  type EditorSelections = ReturnType<typeof EditorSelections>;
@@ -107,23 +107,23 @@
107
107
  /**
108
108
  * Get color for confidence level
109
109
  */
110
- function getConfidenceColor(confidence: number): string {
111
- if (confidence >= 0.85) return '#22c55e'; // green
112
- if (confidence >= 0.7) return '#eab308'; // yellow
113
- return '#f59e0b'; // amber
110
+ function getConfidenceToken(confidence: number): string {
111
+ if (confidence >= 0.85) return '--ide-status-created';
112
+ if (confidence >= 0.7) return '--ide-status-modified';
113
+ return '--ide-accent-strong';
114
114
  }
115
115
 
116
116
  /**
117
117
  * Get severity color
118
118
  */
119
- function getSeverityColor(severity: BracketMismatch['severity']): string {
119
+ function getSeverityToken(severity: BracketMismatch['severity']): string {
120
120
  switch (severity) {
121
121
  case 'error':
122
- return '#ef4444';
122
+ return '--ide-status-deleted';
123
123
  case 'warning':
124
- return '#f59e0b';
124
+ return '--ide-status-modified';
125
125
  default:
126
- return '#6b7280';
126
+ return '--ide-text-muted';
127
127
  }
128
128
  }
129
129
  </script>
@@ -132,12 +132,12 @@
132
132
  <div class="ghost-bracket-layer" aria-hidden="true">
133
133
  <!-- Ghost bracket suggestions -->
134
134
  {#each ghosts as ghost (ghost.id)}
135
- {@const color = getConfidenceColor(ghost.confidence)}
135
+ {@const color = getConfidenceToken(ghost.confidence)}
136
136
  {@const connectorStyle = getConnectorStyle(ghost)}
137
137
 
138
138
  <!-- Connector line to matching bracket -->
139
139
  {#if connectorStyle && hoveredGhost?.id === ghost.id}
140
- <div class="ghost-connector" style="{connectorStyle} --ghost-color: {color};">
140
+ <div class="ghost-connector" style="{connectorStyle} --ghost-color: var({color});">
141
141
  <div class="ghost-connector__line"></div>
142
142
  </div>
143
143
  {/if}
@@ -146,7 +146,7 @@
146
146
  <div
147
147
  class="ghost-bracket"
148
148
  role="tooltip"
149
- style="{getGhostStyle(ghost)} --ghost-color: {color};"
149
+ style="{getGhostStyle(ghost)} --ghost-color: var({color});"
150
150
  onmouseenter={() => (hoveredGhost = ghost)}
151
151
  onmouseleave={() => (hoveredGhost = null)}
152
152
  >
@@ -187,10 +187,10 @@
187
187
 
188
188
  <!-- Bracket mismatch markers -->
189
189
  {#each mismatches as mismatch (`${mismatch.position.line}:${mismatch.position.column}:${mismatch.issue}`)}
190
- {@const color = getSeverityColor(mismatch.severity)}
190
+ {@const color = getSeverityToken(mismatch.severity)}
191
191
  <div
192
192
  class="bracket-mismatch bracket-mismatch--{mismatch.issue}"
193
- style="{getMismatchStyle(mismatch)} --mismatch-color: {color};"
193
+ style="{getMismatchStyle(mismatch)} --mismatch-color: var({color});"
194
194
  title={mismatch.issue === 'unclosed'
195
195
  ? `Unclosed '${mismatch.character}'`
196
196
  : mismatch.issue === 'unexpected'
@@ -253,7 +253,7 @@
253
253
  top: -16px;
254
254
  left: 0;
255
255
  padding: 1px 4px;
256
- background: rgba(0, 0, 0, 0.7);
256
+ background: var(--ide-bg-overlay);
257
257
  color: var(--ghost-color);
258
258
  font-size: 9px;
259
259
  font-family: monospace;
@@ -272,17 +272,17 @@
272
272
  left: 0;
273
273
  margin-top: 4px;
274
274
  padding: 8px;
275
- background: var(--color-surface, #1e1e2e);
276
- border: 1px solid var(--color-border, #333);
275
+ background: var(--ide-bg-elevated);
276
+ border: 1px solid var(--ide-border);
277
277
  border-radius: 6px;
278
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
278
+ box-shadow: var(--ide-shadow-lg);
279
279
  min-width: 140px;
280
280
  z-index: 100;
281
281
  }
282
282
 
283
283
  .ghost-bracket__reason {
284
284
  font-size: 11px;
285
- color: var(--color-text-secondary, #aaa);
285
+ color: var(--ide-text-secondary);
286
286
  margin-bottom: 8px;
287
287
  line-height: 1.3;
288
288
  }
@@ -304,21 +304,21 @@
304
304
  }
305
305
 
306
306
  .ghost-bracket__btn--accept {
307
- background: rgba(34, 197, 94, 0.2);
308
- color: #22c55e;
307
+ background: color-mix(in srgb, var(--ide-status-created) 20%, transparent);
308
+ color: var(--ide-status-created);
309
309
  }
310
310
 
311
311
  .ghost-bracket__btn--accept:hover {
312
- background: rgba(34, 197, 94, 0.3);
312
+ background: color-mix(in srgb, var(--ide-status-created) 30%, transparent);
313
313
  }
314
314
 
315
315
  .ghost-bracket__btn--dismiss {
316
- background: rgba(239, 68, 68, 0.2);
317
- color: #ef4444;
316
+ background: color-mix(in srgb, var(--ide-status-deleted) 20%, transparent);
317
+ color: var(--ide-status-deleted);
318
318
  }
319
319
 
320
320
  .ghost-bracket__btn--dismiss:hover {
321
- background: rgba(239, 68, 68, 0.3);
321
+ background: color-mix(in srgb, var(--ide-status-deleted) 30%, transparent);
322
322
  }
323
323
 
324
324
  /* Connector line */
@@ -376,7 +376,7 @@
376
376
  left: 0;
377
377
  padding: 1px 3px;
378
378
  background: var(--mismatch-color);
379
- color: #fff;
379
+ color: var(--ide-text-inverse);
380
380
  font-size: 9px;
381
381
  font-family: monospace;
382
382
  border-radius: 2px;
@@ -387,4 +387,17 @@
387
387
  .bracket-mismatch:hover .bracket-mismatch__expected {
388
388
  opacity: 1;
389
389
  }
390
+
391
+ @media (prefers-reduced-motion: reduce) {
392
+ .ghost-bracket__char,
393
+ .bracket-mismatch--unclosed .bracket-mismatch__underline {
394
+ animation: none;
395
+ }
396
+
397
+ .ghost-bracket__confidence,
398
+ .ghost-bracket__btn,
399
+ .bracket-mismatch__expected {
400
+ transition: none;
401
+ }
402
+ }
390
403
  </style>
@@ -109,6 +109,17 @@ export declare class FoldManager {
109
109
  * Get visible lines (line numbers that should be rendered)
110
110
  */
111
111
  getVisibleLines(totalLines: number): number[];
112
+ /**
113
+ * Map a raw document line to its compacted visual row.
114
+ *
115
+ * Visible lines map to their exact row in getVisibleLines(totalLines). Hidden
116
+ * lines map to the row of the nearest visible fold header at or above them.
117
+ */
118
+ lineToVisualRow(line: number, totalLines: number): number;
119
+ /**
120
+ * Map a compacted visual row back to the raw document line rendered there.
121
+ */
122
+ visualRowToLine(visualRow: number, totalLines: number): number;
112
123
  /**
113
124
  * Get the number of hidden lines after a fold start
114
125
  */
@@ -641,6 +641,47 @@ export class FoldManager {
641
641
  }
642
642
  return visible;
643
643
  }
644
+ /**
645
+ * Map a raw document line to its compacted visual row.
646
+ *
647
+ * Visible lines map to their exact row in getVisibleLines(totalLines). Hidden
648
+ * lines map to the row of the nearest visible fold header at or above them.
649
+ */
650
+ lineToVisualRow(line, totalLines) {
651
+ if (totalLines <= 0)
652
+ return 0;
653
+ const clampedLine = Math.max(0, Math.min(Math.floor(line), totalLines - 1));
654
+ const visible = this.getVisibleLines(totalLines);
655
+ if (visible.length === 0)
656
+ return 0;
657
+ let low = 0;
658
+ let high = visible.length;
659
+ while (low < high) {
660
+ const mid = Math.floor((low + high) / 2);
661
+ if (visible[mid] < clampedLine) {
662
+ low = mid + 1;
663
+ }
664
+ else {
665
+ high = mid;
666
+ }
667
+ }
668
+ if (visible[low] === clampedLine) {
669
+ return low;
670
+ }
671
+ return Math.max(0, low - 1);
672
+ }
673
+ /**
674
+ * Map a compacted visual row back to the raw document line rendered there.
675
+ */
676
+ visualRowToLine(visualRow, totalLines) {
677
+ if (totalLines <= 0)
678
+ return 0;
679
+ const visible = this.getVisibleLines(totalLines);
680
+ if (visible.length === 0)
681
+ return 0;
682
+ const clampedRow = Math.max(0, Math.min(Math.floor(visualRow), visible.length - 1));
683
+ return visible[clampedRow];
684
+ }
644
685
  /**
645
686
  * Get the number of hidden lines after a fold start
646
687
  */
@@ -4,18 +4,13 @@
4
4
  export * from './state';
5
5
  export * from './navigation';
6
6
  export * from './keybindings';
7
- export * from './crdt-binding';
8
7
  export * from './search';
9
8
  export * from './folding';
10
9
  export * from './multi-cursor';
11
10
  export * from './complexity-analyzer';
12
11
  export * from './ai-awareness';
13
- export * from './ghost-pair';
14
12
  export * from './semantic-analyzer';
15
13
  export * from './commands';
16
- export * from './timeline';
17
- export * from './conflict-predictor';
18
- export * from './echo-cursor';
19
14
  export * from './bracket-healer';
20
15
  export * from './git-blame';
21
16
  export * from './snippet-manager';
@@ -4,18 +4,17 @@
4
4
  export * from './state';
5
5
  export * from './navigation';
6
6
  export * from './keybindings';
7
- export * from './crdt-binding';
7
+ // NOTE: './crdt-binding' is intentionally NOT re-exported here. It imports yjs
8
+ // (an optional peer dependency), so leaking it through this barrel would force
9
+ // yjs onto every consumer of the non-collaborative editor. The CRDT binding is
10
+ // available through the dedicated `@nocturnium/svelte-ide/crdt` entry instead.
8
11
  export * from './search';
9
12
  export * from './folding';
10
13
  export * from './multi-cursor';
11
14
  export * from './complexity-analyzer';
12
15
  export * from './ai-awareness';
13
- export * from './ghost-pair';
14
16
  export * from './semantic-analyzer';
15
17
  export * from './commands';
16
- export * from './timeline';
17
- export * from './conflict-predictor';
18
- export * from './echo-cursor';
19
18
  export * from './bracket-healer';
20
19
  export * from './git-blame';
21
20
  export * from './snippet-manager';
@@ -305,6 +305,10 @@ export declare class EditorState {
305
305
  * Deep copy a selection to prevent reference issues in history
306
306
  */
307
307
  private deepCopySelection;
308
+ private transformCursorUpdatesForInsert;
309
+ private transformPositionForInsert;
310
+ private transformCursorUpdatesForDelete;
311
+ private transformPositionForDelete;
308
312
  /**
309
313
  * Check if undo is available
310
314
  */
@@ -317,6 +321,7 @@ export declare class EditorState {
317
321
  * Compare two tokenizer states for equality
318
322
  */
319
323
  private tokenizerStatesEqual;
324
+ private tokenizerStateValuesEqual;
320
325
  /**
321
326
  * Re-tokenize a range of lines with early-exit optimization
322
327
  */