@nocturnium/svelte-ide 1.0.1 → 1.0.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 (74) 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/CustomEditor.svelte +52 -33
  7. package/dist/components/editor/CustomEditor.svelte.d.ts +2 -2
  8. package/dist/components/editor/Editor.svelte +17 -0
  9. package/dist/components/editor/Editor.svelte.d.ts +9 -0
  10. package/dist/components/editor/EditorPane.svelte +18 -1
  11. package/dist/components/editor/EditorPane.svelte.d.ts +5 -0
  12. package/dist/components/editor/EditorSelections.svelte +27 -11
  13. package/dist/components/editor/EditorSelections.svelte.d.ts +1 -0
  14. package/dist/components/editor/core/folding.d.ts +11 -0
  15. package/dist/components/editor/core/folding.js +41 -0
  16. package/dist/components/editor/core/index.d.ts +0 -5
  17. package/dist/components/editor/core/index.js +4 -5
  18. package/dist/components/editor/core/state.d.ts +5 -0
  19. package/dist/components/editor/core/state.js +131 -12
  20. package/dist/components/editor/editor-find.d.ts +1 -0
  21. package/dist/components/editor/editor-find.js +6 -5
  22. package/dist/components/editor/editor-input.d.ts +1 -0
  23. package/dist/components/editor/editor-input.js +4 -1
  24. package/dist/components/editor/editor-scroll.d.ts +1 -0
  25. package/dist/components/editor/editor-scroll.js +2 -1
  26. package/dist/components/editor/index.d.ts +19 -3
  27. package/dist/components/editor/index.js +18 -4
  28. package/dist/components/editor/tokenizer/base.d.ts +1 -25
  29. package/dist/components/editor/tokenizer/base.js +0 -172
  30. package/dist/components/editor/tokenizer/index.d.ts +4 -0
  31. package/dist/components/editor/tokenizer/index.js +1 -1
  32. package/dist/components/editor/tokenizer/languages/html.d.ts +3 -2
  33. package/dist/components/editor/tokenizer/languages/html.js +64 -6
  34. package/dist/components/editor/tokenizer/languages/javascript.d.ts +13 -5
  35. package/dist/components/editor/tokenizer/languages/javascript.js +69 -57
  36. package/dist/components/editor/tokenizer/languages/svelte.d.ts +1 -1
  37. package/dist/components/editor/tokenizer/languages/svelte.js +6 -1
  38. package/dist/components/editor/tokenizer/types.d.ts +0 -28
  39. package/dist/crdt/awareness.d.ts +8 -2
  40. package/dist/crdt/awareness.js +11 -4
  41. package/dist/crdt/document.d.ts +10 -1
  42. package/dist/crdt/document.js +15 -7
  43. package/dist/crdt/index.d.ts +8 -2
  44. package/dist/crdt/index.js +5 -2
  45. package/dist/crdt/undo.d.ts +2 -7
  46. package/dist/crdt/undo.js +1 -8
  47. package/dist/index.d.ts +7 -9
  48. package/dist/index.js +7 -9
  49. package/dist/services/error-handling.d.ts +2 -11
  50. package/dist/services/error-handling.js +15 -4
  51. package/dist/services/lsp-client.d.ts +3 -0
  52. package/dist/services/lsp-client.js +55 -10
  53. package/dist/services/optimistic.d.ts +8 -5
  54. package/dist/services/optimistic.js +36 -10
  55. package/dist/services/vfs-client.js +11 -3
  56. package/dist/stores/agents.svelte.js +3 -2
  57. package/dist/stores/ai-persistence.svelte.js +7 -2
  58. package/dist/stores/ai.svelte.js +2 -1
  59. package/dist/stores/collaboration.svelte.d.ts +1 -1
  60. package/dist/stores/collaboration.svelte.js +3 -2
  61. package/dist/stores/editor.svelte.js +29 -5
  62. package/dist/stores/layout.svelte.js +3 -0
  63. package/dist/stores/plugin.svelte.js +9 -3
  64. package/dist/stores/vfs.svelte.js +26 -9
  65. package/dist/styles/theme.css +43 -0
  66. package/dist/types/vfs.d.ts +15 -1
  67. package/dist/types/vfs.js +9 -0
  68. package/dist/utils/language.d.ts +4 -3
  69. package/dist/utils/language.js +8 -18
  70. package/package.json +1 -1
  71. package/dist/components/editor/MinimalEditor.svelte +0 -75
  72. package/dist/components/editor/MinimalEditor.svelte.d.ts +0 -6
  73. package/dist/components/editor/MinimalEditor2.svelte +0 -84
  74. package/dist/components/editor/MinimalEditor2.svelte.d.ts +0 -6
@@ -5,13 +5,14 @@
5
5
  * Note: Svelte 5 modules cannot directly export $derived values.
6
6
  * We use getter functions to expose reactive derived state.
7
7
  */
8
+ import { SvelteMap } from 'svelte/reactivity';
8
9
  const state = $state({
9
- agents: new Map(),
10
+ agents: new SvelteMap(),
10
11
  events: [],
11
12
  maxEvents: 200,
12
13
  activities: [],
13
14
  maxActivities: 500,
14
- cursors: new Map(),
15
+ cursors: new SvelteMap(),
15
16
  selectedAgentId: null,
16
17
  filter: 'all',
17
18
  connected: false,
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * Handles saving and loading AI conversations to IndexedDB/localStorage
5
5
  */
6
+ import { browser } from '$app/environment';
6
7
  const DEFAULT_CONFIG = {
7
8
  dbName: 'svelte-ide-ai',
8
9
  storeName: 'conversations',
@@ -31,7 +32,7 @@ export async function initPersistence(options) {
31
32
  config = { ...DEFAULT_CONFIG, ...options };
32
33
  try {
33
34
  // Check if IndexedDB is available
34
- if (!('indexedDB' in globalThis)) {
35
+ if (!browser || !('indexedDB' in globalThis)) {
35
36
  console.warn('IndexedDB not available, using localStorage fallback');
36
37
  initialized = true;
37
38
  return true;
@@ -52,7 +53,7 @@ export async function initPersistence(options) {
52
53
  */
53
54
  function openDatabase() {
54
55
  return new Promise((resolve, reject) => {
55
- const request = indexedDB.open(config.dbName, config.version);
56
+ const request = globalThis.indexedDB.open(config.dbName, config.version);
56
57
  request.onerror = () => reject(request.error);
57
58
  request.onsuccess = () => resolve(request.result);
58
59
  request.onupgradeneeded = (event) => {
@@ -318,6 +319,8 @@ export async function pruneOldConversations() {
318
319
  }
319
320
  // localStorage helpers
320
321
  function getLocalStorageConversations() {
322
+ if (typeof localStorage === 'undefined')
323
+ return [];
321
324
  try {
322
325
  const data = localStorage.getItem('ai-conversations');
323
326
  return data ? JSON.parse(data) : [];
@@ -327,6 +330,8 @@ function getLocalStorageConversations() {
327
330
  }
328
331
  }
329
332
  function setLocalStorageConversations(conversations) {
333
+ if (typeof localStorage === 'undefined')
334
+ return;
330
335
  try {
331
336
  localStorage.setItem('ai-conversations', JSON.stringify(conversations));
332
337
  }
@@ -5,6 +5,7 @@
5
5
  * Note: Svelte 5 modules cannot directly export $derived values.
6
6
  * We use getter functions to expose reactive derived state.
7
7
  */
8
+ import { SvelteMap } from 'svelte/reactivity';
8
9
  // Default configuration
9
10
  const defaultConfig = {
10
11
  endpoint: '/api/chat',
@@ -19,7 +20,7 @@ const defaultConfig = {
19
20
  const state = $state({
20
21
  conversations: [],
21
22
  activeConversationId: null,
22
- tools: new Map(),
23
+ tools: new SvelteMap(),
23
24
  config: { ...defaultConfig },
24
25
  editSessions: [],
25
26
  suggestions: [],
@@ -24,7 +24,7 @@ export declare const config: {
24
24
  readonly current: CollaborationConfig | null;
25
25
  };
26
26
  export declare const status: {
27
- readonly current: "error" | "connected" | "disconnected" | "connecting";
27
+ readonly current: "error" | "disconnected" | "connecting" | "connected";
28
28
  };
29
29
  export declare const error: {
30
30
  readonly current: string | null;
@@ -5,6 +5,7 @@
5
5
  * Note: Svelte 5 modules cannot directly export $derived values.
6
6
  * We use getter functions to expose reactive derived state.
7
7
  */
8
+ import { SvelteMap } from 'svelte/reactivity';
8
9
  // Reactive state
9
10
  const state = $state({
10
11
  config: null,
@@ -12,8 +13,8 @@ const state = $state({
12
13
  error: null,
13
14
  synced: false,
14
15
  users: [],
15
- cursors: new Map(),
16
- awareness: new Map(),
16
+ cursors: new SvelteMap(),
17
+ awareness: new SvelteMap(),
17
18
  aiSessions: [],
18
19
  pendingChanges: [],
19
20
  snapshots: [],
@@ -5,6 +5,7 @@
5
5
  * Note: Svelte 5 modules cannot directly export $derived values.
6
6
  * We use getter functions to expose reactive derived state.
7
7
  */
8
+ import { SvelteMap } from 'svelte/reactivity';
8
9
  import { DEFAULT_EDITOR_PREFERENCES } from '../types';
9
10
  import { detectLanguage } from '../utils/language';
10
11
  // Reactive state using $state rune
@@ -17,6 +18,12 @@ const state = $state({
17
18
  loading: false,
18
19
  error: null
19
20
  });
21
+ // Private saved-content baseline used to compute dirty state without changing
22
+ // the exported EditorTab shape.
23
+ const savedContents = new SvelteMap();
24
+ function isTabDirty(tab) {
25
+ return tab.content !== (savedContents.get(tab.id) ?? tab.content);
26
+ }
20
27
  // Getter functions for derived values (Svelte 5 module-safe)
21
28
  export function getTabs() {
22
29
  return state.tabs;
@@ -122,6 +129,7 @@ export function openFile(path, content, options) {
122
129
  isDirty: false,
123
130
  cursorPosition: { line: 1, column: 1 }
124
131
  };
132
+ savedContents.set(id, content);
125
133
  state.tabs = [...state.tabs, tab];
126
134
  if (options?.focus !== false) {
127
135
  state.activeTabId = id;
@@ -138,10 +146,11 @@ export function closeTab(tabId) {
138
146
  if (index === -1)
139
147
  return false;
140
148
  const tab = state.tabs[index];
141
- if (tab.isDirty) {
149
+ if (isTabDirty(tab)) {
142
150
  // Could prompt for save here - returning false indicates unsaved changes
143
151
  return false;
144
152
  }
153
+ savedContents.delete(tabId);
145
154
  state.tabs = state.tabs.filter((t) => t.id !== tabId);
146
155
  // Update active tab if we closed the active one
147
156
  if (state.activeTabId === tabId) {
@@ -157,6 +166,7 @@ export function forceCloseTab(tabId) {
157
166
  const index = state.tabs.findIndex((t) => t.id === tabId);
158
167
  if (index === -1)
159
168
  return;
169
+ savedContents.delete(tabId);
160
170
  state.tabs = state.tabs.filter((t) => t.id !== tabId);
161
171
  if (state.activeTabId === tabId) {
162
172
  const newIndex = Math.min(index, state.tabs.length - 1);
@@ -167,9 +177,10 @@ export function forceCloseTab(tabId) {
167
177
  * Close all tabs
168
178
  */
169
179
  export function closeAllTabs(force = false) {
170
- if (!force && state.tabs.some((t) => t.isDirty)) {
180
+ if (!force && state.tabs.some(isTabDirty)) {
171
181
  return false;
172
182
  }
183
+ savedContents.clear();
173
184
  state.tabs = [];
174
185
  state.activeTabId = null;
175
186
  return true;
@@ -178,9 +189,14 @@ export function closeAllTabs(force = false) {
178
189
  * Close other tabs (keep the specified one)
179
190
  */
180
191
  export function closeOtherTabs(keepTabId, force = false) {
181
- if (!force && state.tabs.some((t) => t.id !== keepTabId && t.isDirty)) {
192
+ if (!force && state.tabs.some((t) => t.id !== keepTabId && isTabDirty(t))) {
182
193
  return false;
183
194
  }
195
+ for (const tab of state.tabs) {
196
+ if (tab.id !== keepTabId) {
197
+ savedContents.delete(tab.id);
198
+ }
199
+ }
184
200
  state.tabs = state.tabs.filter((t) => t.id === keepTabId);
185
201
  state.activeTabId = keepTabId;
186
202
  return true;
@@ -197,13 +213,21 @@ export function setActiveTab(tabId) {
197
213
  * Update content for a tab
198
214
  */
199
215
  export function updateContent(tabId, content) {
200
- state.tabs = state.tabs.map((t) => t.id === tabId ? { ...t, content, isDirty: t.content !== content || t.isDirty } : t);
216
+ state.tabs = state.tabs.map((t) => t.id === tabId
217
+ ? { ...t, content, isDirty: content !== (savedContents.get(tabId) ?? t.content) }
218
+ : t);
201
219
  }
202
220
  /**
203
221
  * Mark a tab as saved
204
222
  */
205
223
  export function markSaved(tabId, newContent) {
206
- state.tabs = state.tabs.map((t) => t.id === tabId ? { ...t, isDirty: false, content: newContent ?? t.content } : t);
224
+ state.tabs = state.tabs.map((t) => t.id === tabId
225
+ ? (() => {
226
+ const content = newContent ?? t.content;
227
+ savedContents.set(tabId, content);
228
+ return { ...t, isDirty: false, content };
229
+ })()
230
+ : t);
207
231
  }
208
232
  /**
209
233
  * Update cursor position for a tab
@@ -5,6 +5,7 @@
5
5
  * Note: Svelte 5 modules cannot directly export $derived values.
6
6
  * We use getter functions to expose reactive derived state.
7
7
  */
8
+ import { browser } from '$app/environment';
8
9
  // Default sizes
9
10
  const SIDEBAR_DEFAULT_WIDTH = 260;
10
11
  const SIDEBAR_MIN_WIDTH = 180;
@@ -219,6 +220,8 @@ export function toggleStatusBar() {
219
220
  */
220
221
  export function toggleFullScreen() {
221
222
  state.isFullScreen = !state.isFullScreen;
223
+ if (!browser || typeof document === 'undefined')
224
+ return;
222
225
  if (state.isFullScreen) {
223
226
  document.documentElement.requestFullscreen?.();
224
227
  }
@@ -10,12 +10,14 @@
10
10
  * Note: Svelte 5 modules cannot directly export $derived values.
11
11
  * We use getter functions to expose reactive derived state.
12
12
  */
13
+ import { browser } from '$app/environment';
14
+ import { SvelteMap } from 'svelte/reactivity';
13
15
  // Reactive state
14
16
  const state = $state({
15
17
  proposals: [],
16
- instances: new Map(),
17
- commands: new Map(),
18
- panels: new Map(),
18
+ instances: new SvelteMap(),
19
+ commands: new SvelteMap(),
20
+ panels: new SvelteMap(),
19
21
  eventSource: null,
20
22
  connected: false,
21
23
  loadingProposals: false,
@@ -151,6 +153,10 @@ export function connect(endpoint = '/api/plugins/stream') {
151
153
  if (state.eventSource) {
152
154
  state.eventSource.close();
153
155
  }
156
+ if (!browser || typeof EventSource === 'undefined') {
157
+ state.connected = false;
158
+ return;
159
+ }
154
160
  const eventSource = new EventSource(endpoint);
155
161
  eventSource.onopen = () => {
156
162
  state.connected = true;
@@ -6,7 +6,8 @@
6
6
  * We use getter functions to expose reactive derived state.
7
7
  */
8
8
  import * as vfsClient from '../services/vfs-client';
9
- import { SvelteMap } from 'svelte/reactivity';
9
+ import { browser } from '$app/environment';
10
+ import { SvelteMap, SvelteSet } from 'svelte/reactivity';
10
11
  import { parseError, isConflictError, logError } from '../services/error-handling';
11
12
  import { optimisticUpdate } from '../services/optimistic';
12
13
  // Reactive state using $state rune
@@ -14,12 +15,12 @@ const state = $state({
14
15
  workspace: null,
15
16
  workspaceLoading: false,
16
17
  files: [],
17
- fileMap: new Map(),
18
- dirtyFiles: new Set(),
19
- locks: new Map(),
20
- lockStatuses: new Map(),
21
- pendingLocks: new Set(),
22
- activeTransactions: new Map(),
18
+ fileMap: new SvelteMap(),
19
+ dirtyFiles: new SvelteSet(),
20
+ locks: new SvelteMap(),
21
+ lockStatuses: new SvelteMap(),
22
+ pendingLocks: new SvelteSet(),
23
+ activeTransactions: new SvelteMap(),
23
24
  transactionHistory: [],
24
25
  eventSource: null,
25
26
  connected: false,
@@ -30,7 +31,7 @@ const state = $state({
30
31
  errorCode: null,
31
32
  structuredError: null,
32
33
  version: 0,
33
- pendingRetries: new Map(),
34
+ pendingRetries: new SvelteMap(),
34
35
  conflictQueue: []
35
36
  });
36
37
  // Event handlers for SSE
@@ -39,6 +40,7 @@ const eventHandlers = new Map();
39
40
  // Lock TTL refresh intervals
40
41
  // eslint-disable-next-line svelte/prefer-svelte-reactivity -- non-reactive internal timer registry, not UI state
41
42
  const lockRefreshIntervals = new Map(); // path -> intervalId
43
+ let reconnectTimer = null;
42
44
  // Current user ID (set during initialization)
43
45
  let currentUserId = null;
44
46
  // ============================================================================
@@ -496,6 +498,8 @@ export function setLocks(locks) {
496
498
  }
497
499
  }
498
500
  function startLockRefresh(lock) {
501
+ if (!browser || typeof window === 'undefined')
502
+ return;
499
503
  // Refresh at 80% of TTL to ensure we don't lose the lock
500
504
  const refreshInterval = lock.ttl * 0.8;
501
505
  const intervalId = window.setInterval(async () => {
@@ -581,9 +585,17 @@ export function completeTransaction(transactionId, status) {
581
585
  const BASE_RECONNECT_DELAY = 1000;
582
586
  const MAX_RECONNECT_ATTEMPTS = 10;
583
587
  export function connect(workspaceId) {
588
+ if (reconnectTimer) {
589
+ clearTimeout(reconnectTimer);
590
+ reconnectTimer = null;
591
+ }
584
592
  if (state.eventSource) {
585
593
  state.eventSource.close();
586
594
  }
595
+ if (!browser || typeof EventSource === 'undefined') {
596
+ state.connected = false;
597
+ return;
598
+ }
587
599
  const endpoint = `/api/vfs/workspaces/${workspaceId}/stream`;
588
600
  const eventSource = new EventSource(endpoint);
589
601
  eventSource.onopen = () => {
@@ -597,7 +609,8 @@ export function connect(workspaceId) {
597
609
  if (state.reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
598
610
  const delay = BASE_RECONNECT_DELAY * Math.pow(2, state.reconnectAttempts);
599
611
  state.reconnectAttempts++;
600
- setTimeout(() => {
612
+ reconnectTimer = setTimeout(() => {
613
+ reconnectTimer = null;
601
614
  if (!state.connected) {
602
615
  connect(workspaceId);
603
616
  }
@@ -619,6 +632,10 @@ export function connect(workspaceId) {
619
632
  state.eventSource = eventSource;
620
633
  }
621
634
  export function disconnect() {
635
+ if (reconnectTimer) {
636
+ clearTimeout(reconnectTimer);
637
+ reconnectTimer = null;
638
+ }
622
639
  if (state.eventSource) {
623
640
  state.eventSource.close();
624
641
  state.eventSource = null;
@@ -43,6 +43,7 @@
43
43
  --ide-bg-elevated: color-mix(in srgb, var(--ide-bg-secondary) 90%, white 10%);
44
44
  --ide-bg-hover: color-mix(in srgb, var(--ide-bg-tertiary) 50%, transparent);
45
45
  --ide-bg-active: var(--ide-bg-tertiary);
46
+ --ide-bg-overlay: color-mix(in srgb, black 75%, transparent);
46
47
  --ide-bg-selection-secondary: color-mix(
47
48
  in srgb,
48
49
  var(--color-nocturnium-aurora-purple) 22%,
@@ -62,9 +63,11 @@
62
63
  --ide-interactive-active: var(--color-nocturnium-ember);
63
64
  --ide-interactive-focus: var(--color-nocturnium-aurora-blue);
64
65
  --ide-interactive-muted: color-mix(in srgb, var(--ide-interactive) 70%, transparent);
66
+ --ide-interactive-rgb: 74, 141, 183;
65
67
 
66
68
  /* IDE Accent Colors */
67
69
  --ide-accent: var(--color-nocturnium-wave);
70
+ --ide-accent-hover: var(--color-nocturnium-flame);
68
71
  --ide-accent-strong: var(--color-nocturnium-flame);
69
72
 
70
73
  /* IDE Semantic Colors */
@@ -98,8 +101,10 @@
98
101
 
99
102
  /* IDE Borders */
100
103
  --ide-border: color-mix(in srgb, var(--ide-text-secondary) 20%, transparent);
104
+ --ide-border-light: color-mix(in srgb, var(--ide-text-secondary) 12%, transparent);
101
105
  --ide-border-focus: var(--ide-interactive);
102
106
  --ide-border-error: var(--ide-error);
107
+ --ide-focus-ring: var(--ide-interactive-focus);
103
108
 
104
109
  /* IDE Shadows */
105
110
  --ide-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
@@ -113,6 +118,7 @@
113
118
  'JetBrains Mono', 'Fira Code', 'SF Mono', Menlo, Monaco, 'Courier New', monospace;
114
119
  --ide-font-size-xs: 0.75rem;
115
120
  --ide-font-size-sm: 0.875rem;
121
+ --ide-font-size-md: 1rem;
116
122
  --ide-font-size-base: 1rem;
117
123
  --ide-font-size-lg: 1.125rem;
118
124
  --ide-font-size-xl: 1.25rem;
@@ -130,8 +136,10 @@
130
136
  --ide-spacing-lg: 1.5rem;
131
137
  --ide-spacing-xl: 2rem;
132
138
  --ide-spacing-2xl: 3rem;
139
+ --ide-spacing-3xl: 4rem;
133
140
 
134
141
  /* IDE Radii */
142
+ --ide-radius-xs: 0.125rem;
135
143
  --ide-radius-sm: 0.25rem;
136
144
  --ide-radius-md: 0.375rem;
137
145
  --ide-radius-lg: 0.5rem;
@@ -165,6 +173,21 @@
165
173
  --ide-status-bar-height: 24px;
166
174
  --ide-tab-height: 36px;
167
175
  --ide-header-height: 40px;
176
+ --ide-scrollbar-thumb: var(--ide-bg-tertiary);
177
+
178
+ /* IDE Syntax Colors */
179
+ --ide-syntax-keyword: var(--color-nocturnium-aurora-purple);
180
+ --ide-syntax-string: var(--color-nocturnium-aurora-green);
181
+ --ide-syntax-function: var(--color-nocturnium-aurora-blue);
182
+ --ide-syntax-number: var(--color-nocturnium-aurora-yellow);
183
+ --ide-syntax-comment: var(--ide-text-muted);
184
+ --ide-syntax-type: var(--color-nocturnium-teal);
185
+ --ide-syntax-variable: var(--ide-text-primary);
186
+ --ide-syntax-operator: var(--ide-text-primary);
187
+ --ide-syntax-punctuation: var(--ide-text-secondary);
188
+ --ide-syntax-constant: var(--color-nocturnium-aurora-yellow);
189
+ --ide-syntax-tag: var(--color-nocturnium-aurora-pink);
190
+ --ide-syntax-attribute: var(--color-nocturnium-aurora-yellow);
168
191
 
169
192
  /* Agent Status Colors */
170
193
  --ide-agent-online: var(--color-nocturnium-aurora-green);
@@ -175,6 +198,7 @@
175
198
 
176
199
  /* Agent Type Colors */
177
200
  --ide-agent-human: var(--color-nocturnium-aurora-blue);
201
+ --ide-agent-ai: var(--color-nocturnium-ember);
178
202
  --ide-agent-ai-primary: var(--color-nocturnium-ember);
179
203
  --ide-agent-ai-secondary: var(--color-nocturnium-aurora-purple);
180
204
  --ide-agent-system: var(--color-nocturnium-ocean);
@@ -192,6 +216,12 @@
192
216
  --ide-transaction-committed: var(--color-nocturnium-aurora-green);
193
217
  --ide-transaction-failed: var(--ide-error);
194
218
 
219
+ /* File Status Colors */
220
+ --ide-status-created: var(--color-nocturnium-aurora-green);
221
+ --ide-status-modified: var(--color-nocturnium-aurora-yellow);
222
+ --ide-status-deleted: var(--ide-error);
223
+ --ide-status-renamed: var(--color-nocturnium-aurora-purple);
224
+
195
225
  /* Progress Indicators */
196
226
  --ide-progress-track: var(--ide-bg-tertiary);
197
227
  --ide-progress-fill: var(--color-nocturnium-wave);
@@ -623,6 +653,19 @@
623
653
  background: var(--ide-agent-ai-primary);
624
654
  }
625
655
 
656
+ @media (prefers-reduced-motion: reduce) {
657
+ .ide-agent-status-ring,
658
+ .ide-agent-status-ring--busy,
659
+ .ide-agent-status-ring--stalled,
660
+ .ide-lock-badge--pending,
661
+ .ide-lock-badge--conflict,
662
+ .ide-transaction-indicator--pending,
663
+ .ide-agent-cursor,
664
+ .ide-agent-cursor--typing::after {
665
+ animation: none;
666
+ }
667
+ }
668
+
626
669
  /* Force default cursor on editor gutter/line numbers */
627
670
  [class*='gutter'],
628
671
  [class*='line-number'],
@@ -112,9 +112,23 @@ export interface VFSLockAcquisitionOptions {
112
112
  export declare class VFSError extends Error {
113
113
  code: VFSErrorCode;
114
114
  details?: unknown | undefined;
115
+ statusCode?: number;
116
+ path?: string;
117
+ workspaceId?: string;
118
+ retryable: boolean;
119
+ userMessage: string;
120
+ technicalDetails?: string;
121
+ recoveryOptions: {
122
+ id: string;
123
+ label: string;
124
+ description: string;
125
+ action: 'retry' | 'force' | 'merge' | 'discard' | 'refresh' | 'wait' | 'cancel';
126
+ recommended?: boolean;
127
+ dangerous?: boolean;
128
+ }[];
115
129
  constructor(message: string, code: VFSErrorCode, details?: unknown | undefined);
116
130
  }
117
- export type VFSErrorCode = 'FILE_LOCKED' | 'VERSION_CONFLICT' | 'FILE_NOT_FOUND' | 'PERMISSION_DENIED' | 'TRANSACTION_FAILED' | 'WORKSPACE_NOT_FOUND' | 'INVALID_PATH' | 'NETWORK_ERROR';
131
+ export type VFSErrorCode = 'NETWORK_ERROR' | 'CONNECTION_LOST' | 'TIMEOUT' | 'FILE_LOCKED' | 'LOCK_EXPIRED' | 'LOCK_CONFLICT' | 'VERSION_CONFLICT' | 'FILE_NOT_FOUND' | 'PERMISSION_DENIED' | 'WORKSPACE_NOT_FOUND' | 'INVALID_OPERATION' | 'SERVER_ERROR' | 'RATE_LIMITED' | 'TRANSACTION_FAILED' | 'INVALID_PATH' | 'UNKNOWN';
118
132
  export type VFSEvent = VFSSnapshotEvent | VFSUpdateEvent | VFSPingEvent | VFSCompleteEvent | VFSErrorEvent | VFSIterationCompletedEvent | VFSChangeClassifiedEvent | VFSGateProgressEvent | VFSLockAcquiredEvent | VFSLockReleasedEvent;
119
133
  export interface VFSBaseEvent {
120
134
  timestamp: string;
package/dist/types/vfs.js CHANGED
@@ -9,10 +9,19 @@
9
9
  export class VFSError extends Error {
10
10
  code;
11
11
  details;
12
+ statusCode;
13
+ path;
14
+ workspaceId;
15
+ retryable = false;
16
+ userMessage;
17
+ technicalDetails;
18
+ recoveryOptions = [];
12
19
  constructor(message, code, details) {
13
20
  super(message);
14
21
  this.code = code;
15
22
  this.details = details;
16
23
  this.name = 'VFSError';
24
+ this.userMessage = message;
25
+ this.technicalDetails = message;
17
26
  }
18
27
  }
@@ -1,6 +1,3 @@
1
- /**
2
- * Language detection and utilities
3
- */
4
1
  /**
5
2
  * Detect language from filename
6
3
  */
@@ -17,6 +14,10 @@ export declare function getLanguageMimeType(language: string): string;
17
14
  * Check if a language is supported for syntax highlighting
18
15
  */
19
16
  export declare function isLanguageSupported(language: string): boolean;
17
+ /**
18
+ * Get languages with real syntax tokenizers.
19
+ */
20
+ export declare function getSupportedLanguages(): string[];
20
21
  /**
21
22
  * Get display name for a language
22
23
  */
@@ -1,3 +1,4 @@
1
+ import { getSupportedLanguages as getTokenizerSupportedLanguages, resolveLanguage } from '../components/editor/tokenizer';
1
2
  /**
2
3
  * Language detection and utilities
3
4
  */
@@ -144,24 +145,13 @@ export function getLanguageMimeType(language) {
144
145
  * Check if a language is supported for syntax highlighting
145
146
  */
146
147
  export function isLanguageSupported(language) {
147
- const supported = [
148
- 'javascript',
149
- 'typescript',
150
- 'html',
151
- 'css',
152
- 'json',
153
- 'markdown',
154
- 'python',
155
- 'go',
156
- 'rust',
157
- 'java',
158
- 'cpp',
159
- 'sql',
160
- 'xml',
161
- 'yaml',
162
- 'php'
163
- ];
164
- return supported.includes(language);
148
+ return getTokenizerSupportedLanguages().includes(resolveLanguage(language));
149
+ }
150
+ /**
151
+ * Get languages with real syntax tokenizers.
152
+ */
153
+ export function getSupportedLanguages() {
154
+ return getTokenizerSupportedLanguages();
165
155
  }
166
156
  /**
167
157
  * Get display name for a language
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocturnium/svelte-ide",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Svelte 5 code editor and IDE building blocks — custom editor, syntax highlighting, code folding, multi-cursor, LSP client, and optional realtime collaboration.",
5
5
  "author": "Nocturnium & Jordan Dziat <hello@nocturnium.ai> (https://nocturnium.ai)",
6
6
  "license": "MIT",
@@ -1,75 +0,0 @@
1
- <script lang="ts">
2
- // Minimal editor to debug cursor issue
3
- interface Props {
4
- content: string;
5
- }
6
-
7
- let { content }: Props = $props();
8
- const lines = $derived(content.split('\n'));
9
- </script>
10
-
11
- <div class="editor">
12
- <div class="editor__content">
13
- <div class="editor__lines">
14
- {#each lines as line, i (i)}
15
- <div class="editor__line">
16
- <div class="editor__gutter">
17
- <span class="editor__line-number">{i + 1}</span>
18
- </div>
19
- <div class="editor__line-content">{line || '\u00A0'}</div>
20
- </div>
21
- {/each}
22
- </div>
23
- </div>
24
- </div>
25
-
26
- <style>
27
- .editor {
28
- position: relative;
29
- width: 100%;
30
- height: 100%;
31
- background: #0d1421;
32
- font-family: monospace;
33
- font-size: 14px;
34
- }
35
-
36
- .editor__content {
37
- position: relative;
38
- width: 100%;
39
- height: 100%;
40
- overflow: auto;
41
- cursor: default;
42
- }
43
-
44
- .editor__lines {
45
- position: relative;
46
- min-height: 100%;
47
- }
48
-
49
- .editor__line {
50
- display: flex;
51
- line-height: 20px;
52
- cursor: default;
53
- }
54
-
55
- .editor__gutter {
56
- width: 50px;
57
- background: #1a2744;
58
- text-align: right;
59
- padding-right: 8px;
60
- user-select: none;
61
- cursor: default;
62
- }
63
-
64
- .editor__line-number {
65
- color: #666;
66
- cursor: default;
67
- }
68
-
69
- .editor__line-content {
70
- flex: 1;
71
- padding-left: 8px;
72
- color: #e8e8f0;
73
- cursor: text;
74
- }
75
- </style>
@@ -1,6 +0,0 @@
1
- interface Props {
2
- content: string;
3
- }
4
- declare const MinimalEditor: import("svelte").Component<Props, {}, "">;
5
- type MinimalEditor = ReturnType<typeof MinimalEditor>;
6
- export default MinimalEditor;