@nocturnium/svelte-ide 1.0.0-rc.1 → 1.0.0
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.
- package/dist/components/agents/AgentCursor.svelte +1 -1
- package/dist/components/agents/AgentPresenceBar.svelte +0 -1
- package/dist/components/ai/AIConversationList.svelte +1 -0
- package/dist/components/ai/AIInlineEdit.svelte +6 -1
- package/dist/components/ai/AIMessage.svelte +1 -1
- package/dist/components/ai/AIMessageActions.svelte +0 -1
- package/dist/components/ai/AIMessageContent.svelte +2 -2
- package/dist/components/ai/AIPanel.svelte +7 -8
- package/dist/components/core/Avatar.svelte +1 -1
- package/dist/components/core/ContextMenu.svelte +1 -1
- package/dist/components/core/ErrorBoundary.svelte +1 -1
- package/dist/components/editor/BreakpointLayer.svelte +3 -3
- package/dist/components/editor/CognitiveLoadMeter.svelte +2 -2
- package/dist/components/editor/CollaborativeEditor.svelte +2 -2
- package/dist/components/editor/CommandPalette.svelte +7 -6
- package/dist/components/editor/ComplexityLayer.svelte +2 -1
- package/dist/components/editor/ComplexityLayer.svelte.d.ts +0 -7
- package/dist/components/editor/ConflictZoneLayer.svelte +2 -2
- package/dist/components/editor/ContextLens.svelte +1 -1
- package/dist/components/editor/CustomEditor.svelte +2 -4
- package/dist/components/editor/DebugConsole.svelte +6 -11
- package/dist/components/editor/EchoCursorLayer.svelte +4 -6
- package/dist/components/editor/EditorGutter.svelte +2 -2
- package/dist/components/editor/EditorPane.svelte +1 -1
- package/dist/components/editor/EditorSelections.svelte +2 -2
- package/dist/components/editor/FileExplorer.svelte +6 -18
- package/dist/components/editor/FindReplace.svelte +2 -5
- package/dist/components/editor/GhostBracketLayer.svelte +1 -2
- package/dist/components/editor/InlineDiagnosticsLayer.svelte +4 -3
- package/dist/components/editor/MinimalEditor.svelte +1 -1
- package/dist/components/editor/MinimalEditor2.svelte +1 -1
- package/dist/components/editor/PluginPreviewSandbox.svelte +2 -3
- package/dist/components/editor/PluginPreviewSandbox.svelte.d.ts +7 -0
- package/dist/components/editor/ProblemsPanel.svelte +6 -7
- package/dist/components/editor/QuickActionsMenu.svelte +1 -3
- package/dist/components/editor/SnippetPalette.svelte +6 -5
- package/dist/components/editor/SnippetPalette.svelte.d.ts +6 -0
- package/dist/components/editor/StructureMap.svelte +8 -3
- package/dist/components/editor/SymbolOutline.svelte +13 -25
- package/dist/components/editor/TimelineScrubber.svelte +3 -4
- package/dist/components/editor/TokenRenderer.svelte +1 -1
- package/dist/components/editor/core/bracket-healer.js +1 -1
- package/dist/components/editor/core/crdt-binding.js +1 -1
- package/dist/components/editor/core/folding.js +2 -2
- package/dist/components/editor/core/git-blame.js +1 -1
- package/dist/components/editor/core/keybindings.js +0 -4
- package/dist/components/editor/core/multi-cursor.d.ts +1 -1
- package/dist/components/editor/core/multi-cursor.js +1 -1
- package/dist/components/editor/core/snippet-manager.js +1 -1
- package/dist/components/editor/core/state.js +2 -2
- package/dist/components/editor/editor-input.js +4 -4
- package/dist/components/editor/languages.js +1 -1
- package/dist/components/editor/tokenizer/languages/css.js +0 -5
- package/dist/components/editor/tokenizer/languages/html.js +1 -1
- package/dist/components/editor/tokenizer/languages/javascript.js +1 -3
- package/dist/components/editor/tokenizer/languages/markdown.js +2 -2
- package/dist/components/editor/tokenizer/languages/svelte.js +1 -1
- package/dist/components/layout/IDELayout.svelte +28 -7
- package/dist/components/layout/IDELayout.svelte.d.ts +26 -24
- package/dist/components/layout/StatusBar.svelte +0 -1
- package/dist/components/lsp/AutocompleteWidget.svelte +8 -6
- package/dist/components/lsp/DiagnosticMarker.svelte +2 -1
- package/dist/components/lsp/DiagnosticsPanel.svelte +9 -15
- package/dist/components/lsp/HoverTooltip.svelte +4 -3
- package/dist/components/lsp/LSPEditor.svelte +9 -10
- package/dist/components/plugins/PluginCard.svelte +1 -0
- package/dist/components/plugins/PluginProposalForm.svelte +2 -3
- package/dist/components/vfs/LockConflictDialog.svelte +2 -2
- package/dist/components/vfs/LockIndicator.svelte +0 -6
- package/dist/components/vfs/VersionConflictDialog.svelte +3 -0
- package/dist/services/lsp-client.js +2 -2
- package/dist/services/optimistic.d.ts +1 -1
- package/dist/services/vfs-client.js +1 -1
- package/dist/stores/agents.svelte.js +19 -2
- package/dist/stores/ai-persistence.svelte.d.ts +3 -1
- package/dist/stores/ai-persistence.svelte.js +19 -6
- package/dist/stores/ai.svelte.js +9 -1
- package/dist/stores/collaboration.svelte.js +11 -3
- package/dist/stores/editor.svelte.js +1 -1
- package/dist/stores/layout.svelte.js +1 -1
- package/dist/stores/plugin.svelte.js +4 -2
- package/dist/stores/vfs.svelte.d.ts +1 -1
- package/dist/stores/vfs.svelte.js +12 -6
- package/dist/types/plugin.d.ts +0 -12
- package/package.json +1 -178
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - Typing indicator animation
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import type { Agent,
|
|
12
|
+
import type { Agent, CursorPosition, CursorSelection } from '../../types';
|
|
13
13
|
|
|
14
14
|
interface Props {
|
|
15
15
|
/** The agent this cursor belongs to */
|
|
@@ -45,7 +45,6 @@
|
|
|
45
45
|
let overflowCount = $derived(Math.max(0, activeAgents.length - maxVisible));
|
|
46
46
|
|
|
47
47
|
// Group by status for summary
|
|
48
|
-
let onlineCount = $derived(agents.filter((a) => a.status === 'online').length);
|
|
49
48
|
let busyCount = $derived(agents.filter((a) => a.status === 'busy').length);
|
|
50
49
|
let stalledCount = $derived(agents.filter((a) => a.status === 'stalled').length);
|
|
51
50
|
</script>
|
|
@@ -78,6 +78,7 @@
|
|
|
78
78
|
if (filter === 'starred') {
|
|
79
79
|
result = result.filter((c) => c.starred);
|
|
80
80
|
} else if (filter === 'today') {
|
|
81
|
+
// eslint-disable-next-line svelte/prefer-svelte-reactivity -- temporary comparison value, not reactive UI state
|
|
81
82
|
const today = new Date();
|
|
82
83
|
today.setHours(0, 0, 0, 0);
|
|
83
84
|
result = result.filter((c) => new Date(c.updatedAt) >= today);
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
class: className = ''
|
|
18
18
|
}: Props = $props();
|
|
19
19
|
|
|
20
|
+
// Seeded once from the prop: this is the editable input buffer, mounted fresh per edit.
|
|
21
|
+
// svelte-ignore state_referenced_locally
|
|
20
22
|
let prompt = $state(initialPrompt);
|
|
21
23
|
let isSubmitting = $state(false);
|
|
22
24
|
|
|
@@ -40,6 +42,9 @@
|
|
|
40
42
|
}
|
|
41
43
|
</script>
|
|
42
44
|
|
|
45
|
+
<!-- Wrapper-level keydown only captures Escape / Cmd+Enter shortcuts; the real controls
|
|
46
|
+
(textarea, buttons) inside are the focusable interactive elements. -->
|
|
47
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
43
48
|
<div class="ai-inline-edit {className}" onkeydown={handleKeydown}>
|
|
44
49
|
<div class="ai-inline-edit__header">
|
|
45
50
|
<Icon name="sparkles" size={14} />
|
|
@@ -75,7 +80,7 @@
|
|
|
75
80
|
{#if isSubmitting}
|
|
76
81
|
<Spinner size="xs" />
|
|
77
82
|
{:else}
|
|
78
|
-
{#snippet
|
|
83
|
+
{#snippet _icon()}<Icon name="wand" size={14} />{/snippet}
|
|
79
84
|
{/if}
|
|
80
85
|
Apply
|
|
81
86
|
</Button>
|
|
@@ -154,7 +154,7 @@
|
|
|
154
154
|
</script>
|
|
155
155
|
|
|
156
156
|
<div class="message-content" class:streaming={isStreaming}>
|
|
157
|
-
{#each blocks as block, i}
|
|
157
|
+
{#each blocks as block, i (i)}
|
|
158
158
|
{#if block.type === 'code'}
|
|
159
159
|
<div class="code-block">
|
|
160
160
|
<div class="code-header">
|
|
@@ -173,7 +173,7 @@
|
|
|
173
173
|
{/if}
|
|
174
174
|
</button>
|
|
175
175
|
</div>
|
|
176
|
-
<pre class="code-content"><code>{#each tokenizeCode(block.content, block.language || 'plaintext') as token}<span class="token-{token.type}">{token.text}</span>{/each}</code></pre>
|
|
176
|
+
<pre class="code-content"><code>{#each tokenizeCode(block.content, block.language || 'plaintext') as token, ti (ti)}<span class="token-{token.type}">{token.text}</span>{/each}</code></pre>
|
|
177
177
|
</div>
|
|
178
178
|
{:else if block.type === 'inline-code'}
|
|
179
179
|
<code class="inline-code">{block.content}</code>
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import {
|
|
18
18
|
initPersistence,
|
|
19
19
|
saveConversation,
|
|
20
|
+
autoSaveConversation,
|
|
20
21
|
loadConversations,
|
|
21
22
|
deleteConversation as deletePersisted,
|
|
22
23
|
toggleStarConversation,
|
|
@@ -37,12 +38,8 @@
|
|
|
37
38
|
let inputValue = $state('');
|
|
38
39
|
let messagesContainer: HTMLDivElement;
|
|
39
40
|
let textareaEl: HTMLTextAreaElement;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
// Sync with prop changes
|
|
43
|
-
$effect(() => {
|
|
44
|
-
sidebarOpen = showSidebar;
|
|
45
|
-
});
|
|
41
|
+
// Writable derived: tracks the prop, but can be toggled locally (Svelte ≥5.25).
|
|
42
|
+
let sidebarOpen = $derived(showSidebar);
|
|
46
43
|
let persistedConversations = $state<AIConversation[]>([]);
|
|
47
44
|
|
|
48
45
|
// Auto-resize textarea without causing cursor jump
|
|
@@ -72,11 +69,13 @@
|
|
|
72
69
|
}
|
|
73
70
|
});
|
|
74
71
|
|
|
75
|
-
// Auto-save conversation when messages change
|
|
72
|
+
// Auto-save conversation when messages change. Use the debounced helper (not
|
|
73
|
+
// saveConversation) so a burst of streamed tokens collapses into one write and
|
|
74
|
+
// the persistence call is deferred out of this effect's synchronous run.
|
|
76
75
|
$effect(() => {
|
|
77
76
|
const conv = getActiveConversation();
|
|
78
77
|
if (conv && getMessages().length > 0) {
|
|
79
|
-
|
|
78
|
+
autoSaveConversation(conv);
|
|
80
79
|
}
|
|
81
80
|
});
|
|
82
81
|
|
|
@@ -86,7 +86,7 @@ export function clearError() {
|
|
|
86
86
|
|
|
87
87
|
{#if recoveryOptions.length > 0}
|
|
88
88
|
<div class="recovery-options">
|
|
89
|
-
{#each recoveryOptions as option}
|
|
89
|
+
{#each recoveryOptions as option (option.id)}
|
|
90
90
|
<button
|
|
91
91
|
class="recovery-btn"
|
|
92
92
|
class:recommended={option.recommended}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { onMount } from 'svelte';
|
|
13
|
-
import type { BreakpointManager, Breakpoint
|
|
13
|
+
import type { BreakpointManager, Breakpoint } from './core/breakpoints';
|
|
14
14
|
import { getBreakpointIcon, getBreakpointColor } from './core/breakpoints';
|
|
15
15
|
|
|
16
16
|
interface Props {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
gutterWidth = 50,
|
|
38
38
|
enabled = true,
|
|
39
39
|
onToggle,
|
|
40
|
-
onEdit
|
|
40
|
+
onEdit: _onEdit
|
|
41
41
|
}: Props = $props();
|
|
42
42
|
|
|
43
43
|
let breakpoints = $state<Breakpoint[]>([]);
|
|
@@ -251,7 +251,7 @@
|
|
|
251
251
|
}}
|
|
252
252
|
onkeydown={(e) => {
|
|
253
253
|
if (e.key === 'Enter') {
|
|
254
|
-
const
|
|
254
|
+
const _rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
255
255
|
handleGutterClick(0, e as unknown as MouseEvent);
|
|
256
256
|
}
|
|
257
257
|
}}
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
<div class="cognitive-meter__tooltip-section">
|
|
122
122
|
<span class="cognitive-meter__tooltip-label">High complexity regions:</span>
|
|
123
123
|
<ul class="cognitive-meter__tooltip-list">
|
|
124
|
-
{#each highComplexityRegions.slice(0, 5) as region}
|
|
124
|
+
{#each highComplexityRegions.slice(0, 5) as region (region.startLine)}
|
|
125
125
|
<li>
|
|
126
126
|
<span class="cognitive-meter__tooltip-region-name">
|
|
127
127
|
{region.name || `${region.type} at line ${region.startLine + 1}`}
|
|
@@ -143,7 +143,7 @@
|
|
|
143
143
|
{#if metrics.regions.some((r) => r.suggestion)}
|
|
144
144
|
<div class="cognitive-meter__tooltip-section">
|
|
145
145
|
<span class="cognitive-meter__tooltip-label">Suggestions:</span>
|
|
146
|
-
{#each metrics.regions.filter((r) => r.suggestion).slice(0, 2) as region}
|
|
146
|
+
{#each metrics.regions.filter((r) => r.suggestion).slice(0, 2) as region (region.startLine)}
|
|
147
147
|
<p class="cognitive-meter__tooltip-suggestion">
|
|
148
148
|
{region.suggestion}
|
|
149
149
|
</p>
|
|
@@ -46,14 +46,14 @@
|
|
|
46
46
|
|
|
47
47
|
let {
|
|
48
48
|
doc: externalDoc,
|
|
49
|
-
documentId,
|
|
49
|
+
documentId: _documentId,
|
|
50
50
|
initialContent = '',
|
|
51
51
|
textName = 'content',
|
|
52
52
|
language = 'plaintext',
|
|
53
53
|
readonly = false,
|
|
54
54
|
preferences = {},
|
|
55
55
|
class: className = '',
|
|
56
|
-
currentUser,
|
|
56
|
+
currentUser: _currentUser,
|
|
57
57
|
onChange,
|
|
58
58
|
onCursorChange,
|
|
59
59
|
onSave
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { getCommandRegistry, type Command } from './core/commands';
|
|
10
|
-
import {
|
|
10
|
+
import { SvelteMap } from 'svelte/reactivity';
|
|
11
11
|
|
|
12
12
|
interface Props {
|
|
13
13
|
/** Whether the palette is open */
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
|
|
44
44
|
// Group commands by category
|
|
45
45
|
let groupedCommands = $derived.by(() => {
|
|
46
|
-
const groups = new
|
|
46
|
+
const groups = new SvelteMap<string, Command[]>();
|
|
47
47
|
for (const cmd of commands) {
|
|
48
48
|
const existing = groups.get(cmd.category) || [];
|
|
49
49
|
existing.push(cmd);
|
|
@@ -57,7 +57,8 @@
|
|
|
57
57
|
|
|
58
58
|
// Reset selection when query changes
|
|
59
59
|
$effect(() => {
|
|
60
|
-
query
|
|
60
|
+
// Track query to reset selection on each change
|
|
61
|
+
void query;
|
|
61
62
|
selectedIndex = 0;
|
|
62
63
|
});
|
|
63
64
|
|
|
@@ -169,7 +170,6 @@
|
|
|
169
170
|
</script>
|
|
170
171
|
|
|
171
172
|
{#if open}
|
|
172
|
-
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
173
173
|
<div
|
|
174
174
|
class="command-palette__backdrop"
|
|
175
175
|
onclick={handleBackdropClick}
|
|
@@ -200,13 +200,13 @@
|
|
|
200
200
|
{#if flatCommands.length === 0}
|
|
201
201
|
<div class="command-palette__empty">No commands found</div>
|
|
202
202
|
{:else}
|
|
203
|
-
{#each [...groupedCommands] as [category, categoryCommands],
|
|
203
|
+
{#each [...groupedCommands] as [category, categoryCommands], _categoryIdx (category)}
|
|
204
204
|
<div class="command-palette__group">
|
|
205
205
|
<div class="command-palette__group-header">
|
|
206
206
|
<span class="command-palette__group-icon">{getCategoryIcon(category)}</span>
|
|
207
207
|
<span class="command-palette__group-label">{getCategoryLabel(category)}</span>
|
|
208
208
|
</div>
|
|
209
|
-
{#each categoryCommands as command, cmdIdx}
|
|
209
|
+
{#each categoryCommands as command, cmdIdx (command.id)}
|
|
210
210
|
{@const flatIdx = getFlatIndex(category, cmdIdx)}
|
|
211
211
|
<button
|
|
212
212
|
class="command-palette__item"
|
|
@@ -216,6 +216,7 @@
|
|
|
216
216
|
>
|
|
217
217
|
<span class="command-palette__item-icon">{command.icon || ''}</span>
|
|
218
218
|
<span class="command-palette__item-label">
|
|
219
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -- content is sanitized via escapeHtml before regex substitution -->
|
|
219
220
|
{@html highlightMatch(command.label, query)}
|
|
220
221
|
</span>
|
|
221
222
|
{#if command.shortcut}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* Hover over gutter indicators for details.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { SvelteMap } from 'svelte/reactivity';
|
|
10
11
|
import type { ComplexityMetrics, ComplexityRegion } from './core/complexity-analyzer';
|
|
11
12
|
|
|
12
13
|
interface Props {
|
|
@@ -58,7 +59,7 @@
|
|
|
58
59
|
|
|
59
60
|
// Group by line (in case of overlapping regions, take highest score)
|
|
60
61
|
let lineScores = $derived.by(() => {
|
|
61
|
-
const scores = new
|
|
62
|
+
const scores = new SvelteMap<number, { score: number; region: ComplexityRegion; isStart: boolean; isEnd: boolean }>();
|
|
62
63
|
|
|
63
64
|
for (const indicator of lineIndicators) {
|
|
64
65
|
const existing = scores.get(indicator.line);
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Complexity Layer
|
|
3
|
-
*
|
|
4
|
-
* Shows complexity indicators in the gutter area only.
|
|
5
|
-
* No background highlighting - keeps editing area clean.
|
|
6
|
-
* Hover over gutter indicators for details.
|
|
7
|
-
*/
|
|
8
1
|
import type { ComplexityMetrics } from './core/complexity-analyzer';
|
|
9
2
|
interface Props {
|
|
10
3
|
/** Complexity metrics for the current document */
|
|
@@ -149,7 +149,7 @@
|
|
|
149
149
|
<!-- Participant avatars -->
|
|
150
150
|
{#if showParticipants && zone.participants.length > 0}
|
|
151
151
|
<div class="conflict-zone__participants">
|
|
152
|
-
{#each zone.participants.slice(0, 3) as participant, i}
|
|
152
|
+
{#each zone.participants.slice(0, 3) as participant, i (participant.userId)}
|
|
153
153
|
<div
|
|
154
154
|
class="conflict-zone__avatar"
|
|
155
155
|
class:conflict-zone__avatar--ai={participant.isAI}
|
|
@@ -209,7 +209,7 @@
|
|
|
209
209
|
</div>
|
|
210
210
|
|
|
211
211
|
<div class="conflict-tooltip__participants">
|
|
212
|
-
{#each hoveredZone.participants as participant}
|
|
212
|
+
{#each hoveredZone.participants as participant (participant.userId)}
|
|
213
213
|
<div class="conflict-tooltip__participant">
|
|
214
214
|
<span
|
|
215
215
|
class="conflict-tooltip__dot"
|
|
@@ -154,7 +154,6 @@
|
|
|
154
154
|
let cursorBlinkInterval: ReturnType<typeof setInterval>;
|
|
155
155
|
|
|
156
156
|
// Track previous content prop to detect external changes only
|
|
157
|
-
// svelte-ignore state_referenced_locally
|
|
158
157
|
let previousContentProp = $state(content);
|
|
159
158
|
|
|
160
159
|
// Track the content the editor itself last emitted (via onChange / bind:content
|
|
@@ -163,7 +162,6 @@
|
|
|
163
162
|
// rebuild the document model every keystroke, wiping undo/redo and collapsing
|
|
164
163
|
// multi-cursor. Only a genuine external change (content !== what we emitted and
|
|
165
164
|
// !== the current editor content) should re-apply via setContent.
|
|
166
|
-
// svelte-ignore state_referenced_locally
|
|
167
165
|
let lastEmittedContent = content;
|
|
168
166
|
|
|
169
167
|
// Track last cursor position to avoid unnecessary blink resets
|
|
@@ -571,7 +569,7 @@
|
|
|
571
569
|
});
|
|
572
570
|
}
|
|
573
571
|
|
|
574
|
-
function openFind(
|
|
572
|
+
function openFind(_withReplace = false) {
|
|
575
573
|
showFindReplace = true;
|
|
576
574
|
|
|
577
575
|
// Pre-fill with selected text if any
|
|
@@ -934,7 +932,7 @@
|
|
|
934
932
|
<!-- Search match highlights (below selection) -->
|
|
935
933
|
{#if showFindReplace && searchMatches.length > 0}
|
|
936
934
|
<div class="custom-editor__matches">
|
|
937
|
-
{#each matchRects as rect}
|
|
935
|
+
{#each matchRects as rect, i (i)}
|
|
938
936
|
<div
|
|
939
937
|
class="custom-editor__match"
|
|
940
938
|
class:custom-editor__match--current={rect.isCurrent}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - Command history
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import { SvelteSet } from 'svelte/reactivity';
|
|
13
13
|
|
|
14
14
|
export type ConsoleEntryType = 'input' | 'output' | 'error' | 'warning' | 'info' | 'log';
|
|
15
15
|
|
|
@@ -69,10 +69,7 @@
|
|
|
69
69
|
let inputRef = $state<HTMLInputElement>(null!);
|
|
70
70
|
let consoleRef = $state<HTMLDivElement>(null!);
|
|
71
71
|
let filter = $state<ConsoleEntryType | 'all'>('all');
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
// Entry counter for unique IDs
|
|
75
|
-
let entryCounter = 0;
|
|
72
|
+
const expandedIds = new SvelteSet<string>();
|
|
76
73
|
|
|
77
74
|
// Filtered entries
|
|
78
75
|
const filteredEntries = $derived(
|
|
@@ -156,13 +153,11 @@
|
|
|
156
153
|
* Toggle entry expansion
|
|
157
154
|
*/
|
|
158
155
|
function toggleExpand(id: string) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
newExpanded.delete(id);
|
|
156
|
+
if (expandedIds.has(id)) {
|
|
157
|
+
expandedIds.delete(id);
|
|
162
158
|
} else {
|
|
163
|
-
|
|
159
|
+
expandedIds.add(id);
|
|
164
160
|
}
|
|
165
|
-
expandedIds = newExpanded;
|
|
166
161
|
}
|
|
167
162
|
|
|
168
163
|
/**
|
|
@@ -265,7 +260,7 @@
|
|
|
265
260
|
</div>
|
|
266
261
|
|
|
267
262
|
<div class="header-filters">
|
|
268
|
-
{#each (['all', 'error', 'warning', 'info', 'log'] as const) as filterType}
|
|
263
|
+
{#each (['all', 'error', 'warning', 'info', 'log'] as const) as filterType (filterType)}
|
|
269
264
|
{@const count = entryCounts()[filterType]}
|
|
270
265
|
<button
|
|
271
266
|
class="filter-btn"
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import type { EchoCursor, EchoCursorManager, EchoCursorEvent } from './core/echo-cursor';
|
|
10
10
|
import { onMount } from 'svelte';
|
|
11
|
+
import { SvelteMap } from 'svelte/reactivity';
|
|
11
12
|
|
|
12
13
|
interface Props {
|
|
13
14
|
/** Echo cursor manager instance */
|
|
@@ -30,12 +31,12 @@
|
|
|
30
31
|
charWidth,
|
|
31
32
|
gutterWidth = 50,
|
|
32
33
|
enabled = true,
|
|
33
|
-
onAddEchoPoint
|
|
34
|
+
onAddEchoPoint: _onAddEchoPoint
|
|
34
35
|
}: Props = $props();
|
|
35
36
|
|
|
36
37
|
let echoCursors = $state<EchoCursor[]>([]);
|
|
37
38
|
let replayingCursors = $state<Set<string>>(new Set());
|
|
38
|
-
let recentReplay =
|
|
39
|
+
let recentReplay = new SvelteMap<string, { text: string; opacity: number }>();
|
|
39
40
|
|
|
40
41
|
// Subscribe to echo cursor events
|
|
41
42
|
onMount(() => {
|
|
@@ -58,7 +59,6 @@
|
|
|
58
59
|
text: event.keystroke.data.text,
|
|
59
60
|
opacity: 1
|
|
60
61
|
});
|
|
61
|
-
recentReplay = new Map(recentReplay);
|
|
62
62
|
}
|
|
63
63
|
break;
|
|
64
64
|
case 'replay-completed':
|
|
@@ -68,10 +68,8 @@
|
|
|
68
68
|
const entry = recentReplay.get(event.cursorId);
|
|
69
69
|
if (entry) {
|
|
70
70
|
entry.opacity = 0;
|
|
71
|
-
recentReplay = new Map(recentReplay);
|
|
72
71
|
setTimeout(() => {
|
|
73
72
|
recentReplay.delete(event.cursorId);
|
|
74
|
-
recentReplay = new Map(recentReplay);
|
|
75
73
|
}, 200);
|
|
76
74
|
}
|
|
77
75
|
}, 100);
|
|
@@ -80,7 +78,7 @@
|
|
|
80
78
|
if (!event.enabled) {
|
|
81
79
|
echoCursors = [];
|
|
82
80
|
replayingCursors = new Set();
|
|
83
|
-
recentReplay
|
|
81
|
+
recentReplay.clear();
|
|
84
82
|
}
|
|
85
83
|
break;
|
|
86
84
|
}
|
|
@@ -20,11 +20,11 @@
|
|
|
20
20
|
let {
|
|
21
21
|
lineIndex,
|
|
22
22
|
lineNumbers,
|
|
23
|
-
gutterWidth,
|
|
23
|
+
gutterWidth: _gutterWidth,
|
|
24
24
|
folding,
|
|
25
25
|
hasFoldIndicator,
|
|
26
26
|
isFoldCollapsed,
|
|
27
|
-
hiddenLineCount,
|
|
27
|
+
hiddenLineCount: _hiddenLineCount,
|
|
28
28
|
onFoldIndicatorClick
|
|
29
29
|
}: Props = $props();
|
|
30
30
|
</script>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import Editor from './Editor.svelte';
|
|
3
3
|
import EditorTabs from './EditorTabs.svelte';
|
|
4
|
-
import type {
|
|
4
|
+
import type { EditorPreferences } from '../../types';
|
|
5
5
|
import {
|
|
6
6
|
getTabs,
|
|
7
7
|
getActiveTab,
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
getSelectionEnd,
|
|
13
13
|
isSelectionEmpty
|
|
14
14
|
} from './core';
|
|
15
|
-
import {
|
|
15
|
+
import { FALLBACK_VIEWPORT_HEIGHT } from './constants';
|
|
16
16
|
|
|
17
17
|
interface Props {
|
|
18
18
|
cursors: readonly Cursor[];
|
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
|
|
129
129
|
<!-- Selection layer (all cursor selections) -->
|
|
130
130
|
<div class="custom-editor__selections">
|
|
131
|
-
{#each getSelectionRects() as rect}
|
|
131
|
+
{#each getSelectionRects() as rect, i (i)}
|
|
132
132
|
<div
|
|
133
133
|
class="custom-editor__selection"
|
|
134
134
|
class:custom-editor__selection--secondary={!rect.isPrimary}
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
import AgentAvatar from '../agents/AgentAvatar.svelte';
|
|
32
32
|
import type { VFSLockStatus, Agent } from '../../types';
|
|
33
33
|
import { tick, onDestroy } from 'svelte';
|
|
34
|
+
import { SvelteSet } from 'svelte/reactivity';
|
|
34
35
|
|
|
35
36
|
interface Props {
|
|
36
37
|
/** Root file nodes */
|
|
@@ -110,15 +111,6 @@
|
|
|
110
111
|
return statusMap.get(path) ?? 'clean';
|
|
111
112
|
}
|
|
112
113
|
|
|
113
|
-
// Helper to check if a folder contains any files with changes
|
|
114
|
-
function folderHasChanges(node: FileNode): boolean {
|
|
115
|
-
if (node.type === 'file') {
|
|
116
|
-
return getFileStatus(node.path) !== 'clean';
|
|
117
|
-
}
|
|
118
|
-
if (!node.children) return false;
|
|
119
|
-
return node.children.some((child) => folderHasChanges(child));
|
|
120
|
-
}
|
|
121
|
-
|
|
122
114
|
// Get the most "important" status for a folder (deleted > modified > created > renamed)
|
|
123
115
|
function getFolderStatus(node: FileNode): FileChangeStatus {
|
|
124
116
|
if (node.type === 'file') return getFileStatus(node.path);
|
|
@@ -171,7 +163,7 @@
|
|
|
171
163
|
return agentsByFile.get(path) ?? [];
|
|
172
164
|
}
|
|
173
165
|
|
|
174
|
-
let expandedFolders =
|
|
166
|
+
let expandedFolders = new SvelteSet<string>();
|
|
175
167
|
let contextMenuNode = $state<FileNode | null>(null);
|
|
176
168
|
let contextMenuPos = $state({ x: 0, y: 0 });
|
|
177
169
|
let showContextMenu = $state(false);
|
|
@@ -194,13 +186,11 @@
|
|
|
194
186
|
function toggleFolder(node: FileNode) {
|
|
195
187
|
if (node.type !== 'folder') return;
|
|
196
188
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
newExpanded.delete(node.path);
|
|
189
|
+
if (expandedFolders.has(node.path)) {
|
|
190
|
+
expandedFolders.delete(node.path);
|
|
200
191
|
} else {
|
|
201
|
-
|
|
192
|
+
expandedFolders.add(node.path);
|
|
202
193
|
}
|
|
203
|
-
expandedFolders = newExpanded;
|
|
204
194
|
onToggle?.(node);
|
|
205
195
|
}
|
|
206
196
|
|
|
@@ -208,9 +198,7 @@
|
|
|
208
198
|
async function startInlineCreation(parentPath: string, type: 'file' | 'folder') {
|
|
209
199
|
// Expand parent folder if it's not root
|
|
210
200
|
if (parentPath) {
|
|
211
|
-
|
|
212
|
-
newExpanded.add(parentPath);
|
|
213
|
-
expandedFolders = newExpanded;
|
|
201
|
+
expandedFolders.add(parentPath);
|
|
214
202
|
}
|
|
215
203
|
|
|
216
204
|
inlineCreation = {
|
|
@@ -57,12 +57,9 @@
|
|
|
57
57
|
|
|
58
58
|
let findInput = $state<HTMLInputElement | null>(null);
|
|
59
59
|
let replaceInput = $state<HTMLInputElement | null>(null);
|
|
60
|
-
let showReplaceLocal = $state(false);
|
|
61
60
|
|
|
62
|
-
//
|
|
63
|
-
$
|
|
64
|
-
showReplaceLocal = showReplace;
|
|
65
|
-
});
|
|
61
|
+
// Writable derived: tracks the showReplace prop but allows local overrides (e.g. toggle)
|
|
62
|
+
let showReplaceLocal = $derived(showReplace);
|
|
66
63
|
|
|
67
64
|
// Focus find input on mount
|
|
68
65
|
onMount(() => {
|
|
@@ -143,7 +143,6 @@
|
|
|
143
143
|
{/if}
|
|
144
144
|
|
|
145
145
|
<!-- Ghost bracket marker -->
|
|
146
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
147
146
|
<div
|
|
148
147
|
class="ghost-bracket"
|
|
149
148
|
role="tooltip"
|
|
@@ -187,7 +186,7 @@
|
|
|
187
186
|
{/each}
|
|
188
187
|
|
|
189
188
|
<!-- Bracket mismatch markers -->
|
|
190
|
-
{#each mismatches as mismatch}
|
|
189
|
+
{#each mismatches as mismatch (`${mismatch.position.line}:${mismatch.position.column}:${mismatch.issue}`)}
|
|
191
190
|
{@const color = getSeverityColor(mismatch.severity)}
|
|
192
191
|
<div
|
|
193
192
|
class="bracket-mismatch bracket-mismatch--{mismatch.issue}"
|
|
@@ -72,6 +72,7 @@
|
|
|
72
72
|
|
|
73
73
|
// Group diagnostics by line for gutter icons
|
|
74
74
|
const diagnosticsByLine = $derived(() => {
|
|
75
|
+
// eslint-disable-next-line svelte/prefer-svelte-reactivity -- local computation variable recreated each derivation, not mutated reactive state
|
|
75
76
|
const byLine = new Map<number, Diagnostic[]>();
|
|
76
77
|
for (const d of diagnostics) {
|
|
77
78
|
const line = d.range.start.line;
|
|
@@ -235,7 +236,6 @@
|
|
|
235
236
|
|
|
236
237
|
<!-- Tooltip -->
|
|
237
238
|
{#if showTooltip && hoveredDiagnostic}
|
|
238
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
239
239
|
<div
|
|
240
240
|
class="diagnostic-tooltip"
|
|
241
241
|
role="tooltip"
|
|
@@ -254,6 +254,7 @@
|
|
|
254
254
|
{#if hoveredDiagnostic.code}
|
|
255
255
|
<span class="tooltip-code">
|
|
256
256
|
{#if hoveredDiagnostic.codeUrl}
|
|
257
|
+
<!-- eslint-disable-next-line svelte/no-navigation-without-resolve -- codeUrl is an external URL from diagnostic data, not a local app path -->
|
|
257
258
|
<a href={hoveredDiagnostic.codeUrl} target="_blank" rel="noopener">
|
|
258
259
|
{hoveredDiagnostic.code}
|
|
259
260
|
</a>
|
|
@@ -268,7 +269,7 @@
|
|
|
268
269
|
|
|
269
270
|
{#if hoveredDiagnostic.relatedInfo && hoveredDiagnostic.relatedInfo.length > 0}
|
|
270
271
|
<div class="tooltip-related">
|
|
271
|
-
{#each hoveredDiagnostic.relatedInfo as info}
|
|
272
|
+
{#each hoveredDiagnostic.relatedInfo as info, i (i)}
|
|
272
273
|
<div class="related-item">
|
|
273
274
|
<span class="related-location">
|
|
274
275
|
{info.filePath || 'this file'}:{info.range.start.line + 1}
|
|
@@ -282,7 +283,7 @@
|
|
|
282
283
|
{#if hoveredDiagnostic.fixes && hoveredDiagnostic.fixes.length > 0}
|
|
283
284
|
<div class="tooltip-fixes">
|
|
284
285
|
<div class="fixes-header">Quick Fixes</div>
|
|
285
|
-
{#each hoveredDiagnostic.fixes as fix}
|
|
286
|
+
{#each hoveredDiagnostic.fixes as fix, i (i)}
|
|
286
287
|
<button
|
|
287
288
|
class="fix-button"
|
|
288
289
|
class:fix-button--preferred={fix.isPreferred}
|