@nocturnium/svelte-ide 1.1.1 → 1.2.1

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 (33) hide show
  1. package/dist/components/editor/CognitiveLoadMeter.svelte +27 -0
  2. package/dist/components/editor/ComplexityHeatLayer.svelte +157 -0
  3. package/dist/components/editor/ComplexityHeatLayer.svelte.d.ts +24 -0
  4. package/dist/components/editor/ComplexityLayer.svelte +325 -109
  5. package/dist/components/editor/ComplexityLayer.svelte.d.ts +13 -0
  6. package/dist/components/editor/ConflictZoneLayer.svelte +22 -15
  7. package/dist/components/editor/CustomEditor.svelte +80 -1
  8. package/dist/components/editor/CustomEditor.svelte.d.ts +3 -1
  9. package/dist/components/editor/EchoCursorLayer.svelte +60 -0
  10. package/dist/components/editor/PluginPreviewSandbox.svelte +43 -9
  11. package/dist/components/editor/PluginPreviewSandbox.svelte.d.ts +4 -4
  12. package/dist/components/editor/core/complexity-analyzer.d.ts +31 -0
  13. package/dist/components/editor/core/complexity-analyzer.js +479 -29
  14. package/dist/components/editor/core/conflict-predictor.d.ts +32 -0
  15. package/dist/components/editor/core/conflict-predictor.js +55 -0
  16. package/dist/components/editor/core/crdt-binding.d.ts +4 -0
  17. package/dist/components/editor/core/crdt-binding.js +34 -9
  18. package/dist/components/editor/core/echo-cursor.d.ts +18 -1
  19. package/dist/components/editor/core/echo-cursor.js +117 -6
  20. package/dist/components/editor/core/extract-function.d.ts +27 -0
  21. package/dist/components/editor/core/extract-function.js +865 -0
  22. package/dist/components/editor/core/index.d.ts +1 -0
  23. package/dist/components/editor/core/index.js +1 -0
  24. package/dist/components/editor/core/state.d.ts +38 -5
  25. package/dist/components/editor/core/state.js +175 -98
  26. package/dist/components/editor/core/timeline.js +6 -1
  27. package/dist/components/editor/editor-find.js +15 -3
  28. package/dist/components/editor/theme.d.ts +8 -0
  29. package/dist/components/editor/theme.js +52 -0
  30. package/dist/services/lsp-client.d.ts +3 -0
  31. package/dist/services/lsp-client.js +86 -14
  32. package/dist/styles/theme.css +4 -1
  33. package/package.json +1 -1
@@ -7,7 +7,6 @@
7
7
  * Hover over gutter indicators for details.
8
8
  */
9
9
 
10
- import { SvelteMap } from 'svelte/reactivity';
11
10
  import type { ComplexityMetrics, ComplexityRegion } from './core/complexity-analyzer';
12
11
 
13
12
  interface Props {
@@ -24,10 +23,16 @@
24
23
  * that sat past the initial view.
25
24
  */
26
25
  totalHeight?: number;
26
+ /** Estimated full scrollable content width in pixels */
27
+ contentWidth?: number;
27
28
  /** Minimum score to show indicators (default: 50) */
28
29
  minScore?: number;
29
30
  /** Whether indicators are enabled */
30
31
  enabled?: boolean;
32
+ /** Region key to briefly emphasize after jump-to-hottest */
33
+ flashRegionKey?: string;
34
+ /** Maps a raw document line to its rendered visual row */
35
+ lineToVisualRow?: (line: number) => number;
31
36
  }
32
37
 
33
38
  let {
@@ -35,8 +40,11 @@
35
40
  lineHeight,
36
41
  gutterWidth = 50,
37
42
  totalHeight = 0,
43
+ contentWidth = 0,
38
44
  minScore = 50,
39
- enabled = true
45
+ enabled = true,
46
+ flashRegionKey = '',
47
+ lineToVisualRow = (line: number) => line
40
48
  }: Props = $props();
41
49
 
42
50
  // Filter regions that exceed the threshold
@@ -44,55 +52,6 @@
44
52
  enabled && metrics ? metrics.regions.filter((r) => r.score >= minScore) : []
45
53
  );
46
54
 
47
- // Expand regions to individual line indicators for gutter
48
- let lineIndicators = $derived.by(() => {
49
- if (!enabled || !metrics) return [];
50
-
51
- const indicators: Array<{
52
- line: number;
53
- score: number;
54
- region: ComplexityRegion;
55
- isStart: boolean;
56
- isEnd: boolean;
57
- }> = [];
58
-
59
- for (const region of highlightedRegions) {
60
- for (let line = region.startLine; line <= region.endLine; line++) {
61
- indicators.push({
62
- line,
63
- score: region.score,
64
- region,
65
- isStart: line === region.startLine,
66
- isEnd: line === region.endLine
67
- });
68
- }
69
- }
70
-
71
- return indicators;
72
- });
73
-
74
- // Group by line (in case of overlapping regions, take highest score)
75
- let lineScores = $derived.by(() => {
76
- const scores = new SvelteMap<
77
- number,
78
- { score: number; region: ComplexityRegion; isStart: boolean; isEnd: boolean }
79
- >();
80
-
81
- for (const indicator of lineIndicators) {
82
- const existing = scores.get(indicator.line);
83
- if (!existing || indicator.score > existing.score) {
84
- scores.set(indicator.line, {
85
- score: indicator.score,
86
- region: indicator.region,
87
- isStart: indicator.isStart,
88
- isEnd: indicator.isEnd
89
- });
90
- }
91
- }
92
-
93
- return scores;
94
- });
95
-
96
55
  /**
97
56
  * Get the color based on score level
98
57
  */
@@ -101,7 +60,8 @@
101
60
  // medium otherwise (lines below the minScore threshold aren't shown).
102
61
  if (score >= 85) return 'var(--ide-error)'; // Critical
103
62
  if (score >= 70) return 'var(--ide-warning)'; // High
104
- return 'var(--ide-info)'; // Medium
63
+ if (score >= 50) return 'var(--ide-info)'; // Medium
64
+ return 'var(--ide-success)'; // Low
105
65
  }
106
66
 
107
67
  /**
@@ -109,19 +69,64 @@
109
69
  */
110
70
  function getOpacity(score: number): number {
111
71
  const normalized = Math.min(1, Math.max(0, (score - minScore) / (100 - minScore)));
112
- return 0.4 + normalized * 0.6;
72
+ return 0.65 + normalized * 0.35;
113
73
  }
114
74
 
115
75
  let hoveredRegion = $state<ComplexityRegion | null>(null);
116
- let tooltipPosition = $state({ top: 0, left: 0 });
76
+ let tooltipPosition = $state({ top: 0, left: 0, right: 0 });
77
+ let tooltipAnchor = $state<'left' | 'right'>('right');
78
+ let visibleContributions = $derived(
79
+ hoveredRegion
80
+ ? [...hoveredRegion.contributions].sort((a, b) => a.line - b.line).slice(0, 8)
81
+ : []
82
+ );
83
+ let hiddenContributionCount = $derived(
84
+ hoveredRegion
85
+ ? Math.max(0, hoveredRegion.contributions.length - visibleContributions.length)
86
+ : 0
87
+ );
88
+
89
+ function getRegionKey(region: ComplexityRegion): string {
90
+ return `${region.startLine}:${region.endLine}:${region.name ?? region.type}:${region.score}`;
91
+ }
92
+
93
+ function getLevelLabel(score: number): string {
94
+ if (score >= 85) return 'Critical';
95
+ if (score >= 70) return 'High';
96
+ if (score >= 50) return 'Medium';
97
+ return 'Low';
98
+ }
117
99
 
118
- function handleMouseEnter(region: ComplexityRegion, event: MouseEvent) {
100
+ function getRegionTop(region: ComplexityRegion): number {
101
+ return lineToVisualRow(region.startLine) * lineHeight;
102
+ }
103
+
104
+ function getRegionHeight(region: ComplexityRegion): number {
105
+ const startRow = lineToVisualRow(region.startLine);
106
+ const endRow = Math.max(startRow, lineToVisualRow(region.endLine));
107
+ return Math.max(lineHeight, (endRow - startRow + 1) * lineHeight);
108
+ }
109
+
110
+ function getContributionLabel(contribution: ComplexityRegion['contributions'][number]): string {
111
+ return contribution.kind || contribution.reason;
112
+ }
113
+
114
+ function handleMouseEnter(
115
+ region: ComplexityRegion,
116
+ event: MouseEvent,
117
+ anchor: 'left' | 'right' = 'right'
118
+ ) {
119
119
  hoveredRegion = region;
120
- const rect = (event.target as HTMLElement).getBoundingClientRect();
121
- tooltipPosition = {
122
- top: rect.top + window.scrollY,
123
- left: rect.right + 8
124
- };
120
+ const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();
121
+ tooltipAnchor = anchor;
122
+ // The gutter spine opens the tooltip to its right (over the code); the score
123
+ // badge sits at the right edge, so it opens to its left. Anchor the left case
124
+ // by `right` (not a translateX) so the box grows leftward cleanly — translateX
125
+ // could subpixel-clip the first glyph of the region name.
126
+ tooltipPosition =
127
+ anchor === 'right'
128
+ ? { top: rect.top, left: rect.right + 8, right: 0 }
129
+ : { top: rect.top, left: 0, right: window.innerWidth - rect.left + 8 };
125
130
  }
126
131
 
127
132
  function handleMouseLeave() {
@@ -129,31 +134,45 @@
129
134
  }
130
135
  </script>
131
136
 
132
- {#if enabled && lineScores.size > 0}
137
+ {#if enabled && highlightedRegions.length > 0}
133
138
  <div
134
139
  class="complexity-gutter"
135
140
  aria-hidden="true"
136
- style="width: {gutterWidth}px;{totalHeight ? ` height: ${totalHeight}px;` : ''}"
141
+ style="width: {contentWidth ? `${contentWidth}px` : '100%'};{totalHeight
142
+ ? ` height: ${totalHeight}px;`
143
+ : ''} --editor-gutter-width: {gutterWidth}px;"
137
144
  >
138
- {#each [...lineScores.entries()] as [line, data] (line)}
145
+ {#each highlightedRegions as region (getRegionKey(region))}
146
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
139
147
  <div
140
- class="complexity-gutter__indicator"
141
- class:complexity-gutter__indicator--start={data.isStart}
142
- class:complexity-gutter__indicator--end={data.isEnd}
148
+ class="complexity-gutter__spine"
149
+ class:complexity-gutter__spine--critical={region.score >= 85}
150
+ class:complexity-gutter__spine--flash={flashRegionKey === getRegionKey(region)}
143
151
  style="
144
- top: {line * lineHeight}px;
145
- height: {lineHeight}px;
146
- --indicator-color: {getColor(data.score)};
147
- --indicator-opacity: {getOpacity(data.score)};
152
+ top: {getRegionTop(region)}px;
153
+ height: {getRegionHeight(region)}px;
154
+ --indicator-color: {getColor(region.score)};
155
+ --indicator-opacity: {getOpacity(region.score)};
156
+ --indicator-glow: {region.score >= 85 ? 18 : region.score >= 70 ? 14 : 9}px;
148
157
  "
149
- onmouseenter={(e) => handleMouseEnter(data.region, e)}
158
+ onmouseenter={(e) => handleMouseEnter(region, e)}
159
+ onmouseleave={handleMouseLeave}
160
+ ></div>
161
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
162
+ <div
163
+ class="complexity-gutter__score"
164
+ class:complexity-gutter__score--critical={region.score >= 85}
165
+ class:complexity-gutter__score--flash={flashRegionKey === getRegionKey(region)}
166
+ style="
167
+ top: {getRegionTop(region) + 3}px;
168
+ right: 18px;
169
+ --indicator-color: {getColor(region.score)};
170
+ --indicator-opacity: {getOpacity(region.score)};
171
+ "
172
+ onmouseenter={(e) => handleMouseEnter(region, e, 'left')}
150
173
  onmouseleave={handleMouseLeave}
151
- role="button"
152
- tabindex="-1"
153
174
  >
154
- {#if data.isStart}
155
- <span class="complexity-gutter__score">{data.score}</span>
156
- {/if}
175
+ {region.score}
157
176
  </div>
158
177
  {/each}
159
178
  </div>
@@ -162,19 +181,40 @@
162
181
  {#if hoveredRegion}
163
182
  <div
164
183
  class="complexity-tooltip"
165
- style="top: {tooltipPosition.top}px; left: {tooltipPosition.left}px;"
184
+ style={tooltipAnchor === 'left'
185
+ ? `top: ${tooltipPosition.top}px; right: ${tooltipPosition.right}px;`
186
+ : `top: ${tooltipPosition.top}px; left: ${tooltipPosition.left}px;`}
166
187
  >
167
188
  <div class="complexity-tooltip__header">
168
- <span class="complexity-tooltip__badge" style="background: {getColor(hoveredRegion.score)}">
169
- {hoveredRegion.score}
170
- </span>
171
- <span class="complexity-tooltip__title">
172
- {hoveredRegion.name || hoveredRegion.type}
189
+ <span class="complexity-tooltip__badge" style="color: {getColor(hoveredRegion.score)}">
190
+ {hoveredRegion.cognitiveComplexity}
173
191
  </span>
192
+ <div class="complexity-tooltip__heading">
193
+ <span class="complexity-tooltip__title">
194
+ {getLevelLabel(hoveredRegion.score)} Cognitive Complexity
195
+ </span>
196
+ <span class="complexity-tooltip__source">SonarSource Cognitive Complexity metric</span>
197
+ </div>
174
198
  </div>
175
199
  <div class="complexity-tooltip__lines">
176
- Lines {hoveredRegion.startLine + 1} - {hoveredRegion.endLine + 1}
200
+ {hoveredRegion.name || hoveredRegion.type} · Lines {hoveredRegion.startLine + 1} - {hoveredRegion.endLine +
201
+ 1}
202
+ · Score {hoveredRegion.score}
177
203
  </div>
204
+ {#if visibleContributions.length > 0}
205
+ <ul class="complexity-tooltip__contributions">
206
+ {#each visibleContributions as contribution, index (`${contribution.line}:${contribution.kind}:${index}`)}
207
+ <li>
208
+ <span>line {contribution.line + 1}</span>
209
+ <strong>+{contribution.increment}</strong>
210
+ <span>{getContributionLabel(contribution)} (nesting {contribution.nesting})</span>
211
+ </li>
212
+ {/each}
213
+ {#if hiddenContributionCount > 0}
214
+ <li class="complexity-tooltip__more">+{hiddenContributionCount} more</li>
215
+ {/if}
216
+ </ul>
217
+ {/if}
178
218
  {#if hoveredRegion.suggestion}
179
219
  <div class="complexity-tooltip__suggestion">
180
220
  {hoveredRegion.suggestion}
@@ -199,53 +239,190 @@
199
239
  bottom: 0;
200
240
  pointer-events: none;
201
241
  z-index: 5;
242
+ overflow: visible;
202
243
  }
203
244
 
204
- .complexity-gutter__indicator {
245
+ .complexity-gutter__spine {
205
246
  position: absolute;
206
- right: 2px;
247
+ left: calc(var(--editor-gutter-width, 50px) - 3px);
207
248
  width: 4px;
208
249
  pointer-events: auto;
209
250
  cursor: help;
210
251
  background: var(--indicator-color);
211
252
  opacity: var(--indicator-opacity);
253
+ border-radius: 999px;
254
+ box-shadow: 0 0 0 1px color-mix(in srgb, var(--indicator-color) 35%, transparent);
255
+ isolation: isolate;
212
256
  transition:
213
257
  opacity 0.15s ease,
214
- width 0.15s ease;
258
+ width 0.15s ease,
259
+ box-shadow 0.15s ease,
260
+ transform 0.15s ease;
261
+ }
262
+
263
+ .complexity-gutter__spine::before {
264
+ content: '';
265
+ position: absolute;
266
+ inset: -2px -1px;
267
+ border-radius: inherit;
268
+ background: var(--indicator-color);
269
+ filter: blur(6px);
270
+ opacity: 0.55;
271
+ transform: scaleX(1);
272
+ transform-origin: center;
273
+ z-index: -1;
215
274
  }
216
275
 
217
- .complexity-gutter__indicator:hover {
276
+ .complexity-gutter__spine:hover {
218
277
  opacity: 1;
219
278
  width: 6px;
279
+ transform: translateX(-1px);
220
280
  }
221
281
 
222
- .complexity-gutter__indicator--start {
223
- border-radius: 2px 2px 0 0;
282
+ .complexity-gutter__spine--critical {
283
+ animation: complexity-spine-pulse 2.4s ease-in-out infinite;
224
284
  }
225
285
 
226
- .complexity-gutter__indicator--end {
227
- border-radius: 0 0 2px 2px;
286
+ .complexity-gutter__spine--critical::before {
287
+ animation: complexity-spine-bloom-pulse 2.4s ease-in-out infinite;
228
288
  }
229
289
 
230
- .complexity-gutter__indicator--start.complexity-gutter__indicator--end {
231
- border-radius: 2px;
290
+ .complexity-gutter__spine--flash {
291
+ animation: complexity-spine-flash 0.9s ease-out 1;
292
+ }
293
+
294
+ .complexity-gutter__spine--flash::before {
295
+ animation: complexity-spine-bloom-flash 0.9s ease-out 1;
232
296
  }
233
297
 
234
298
  .complexity-gutter__score {
235
299
  position: absolute;
236
- right: 8px;
237
- top: 50%;
238
- transform: translateY(-50%);
239
- font-size: 9px;
240
- font-weight: 700;
300
+ display: inline-flex;
301
+ align-items: center;
302
+ justify-content: center;
303
+ min-width: 44px;
304
+ height: 30px;
305
+ padding: 0 10px;
306
+ border: 1px solid color-mix(in srgb, var(--indicator-color) 65%, transparent);
307
+ border-radius: 999px;
308
+ background: color-mix(in srgb, var(--indicator-color) 18%, var(--ide-bg-primary));
309
+ box-shadow:
310
+ 0 0 18px color-mix(in srgb, var(--indicator-color) 32%, transparent),
311
+ inset 0 1px 0 color-mix(in srgb, #fff 12%, transparent);
241
312
  color: var(--indicator-color);
242
- opacity: 0;
243
- transition: opacity 0.15s ease;
244
- pointer-events: none;
313
+ font-size: 22px;
314
+ font-weight: 800;
315
+ font-variant-numeric: tabular-nums;
316
+ line-height: 1;
317
+ text-shadow: 0 0 12px color-mix(in srgb, var(--indicator-color) 48%, transparent);
318
+ opacity: calc(0.76 + (var(--indicator-opacity) - 0.65) * 0.7);
319
+ transform: scale(1);
320
+ transform-origin: center;
321
+ /* The badge is a primary hover target for the explain-on-hover tooltip,
322
+ not only the 4px spine. */
323
+ pointer-events: auto;
324
+ cursor: help;
325
+ box-sizing: border-box;
245
326
  }
246
327
 
247
- .complexity-gutter__indicator:hover .complexity-gutter__score {
248
- opacity: 1;
328
+ .complexity-gutter__score--critical {
329
+ animation: complexity-score-glow-pulse 2.4s ease-in-out infinite;
330
+ }
331
+
332
+ .complexity-gutter__score--flash {
333
+ animation: complexity-score-arrival-pop 0.35s ease-out 1;
334
+ }
335
+
336
+ @keyframes complexity-spine-pulse {
337
+ 0%,
338
+ 100% {
339
+ transform: scaleX(1);
340
+ }
341
+ 50% {
342
+ transform: scaleX(1.12);
343
+ }
344
+ }
345
+
346
+ @keyframes complexity-spine-bloom-pulse {
347
+ 0%,
348
+ 100% {
349
+ filter: blur(6px);
350
+ opacity: 0.55;
351
+ transform: scaleX(1);
352
+ }
353
+ 50% {
354
+ filter: blur(10px);
355
+ opacity: 0.78;
356
+ transform: scaleX(1.35);
357
+ }
358
+ }
359
+
360
+ @keyframes complexity-score-glow-pulse {
361
+ 0%,
362
+ 100% {
363
+ box-shadow:
364
+ 0 0 18px color-mix(in srgb, var(--indicator-color) 32%, transparent),
365
+ inset 0 1px 0 color-mix(in srgb, #fff 12%, transparent);
366
+ transform: scale(1);
367
+ }
368
+ 50% {
369
+ box-shadow:
370
+ 0 0 28px color-mix(in srgb, var(--indicator-color) 52%, transparent),
371
+ 0 0 44px color-mix(in srgb, var(--indicator-color) 28%, transparent),
372
+ inset 0 1px 0 color-mix(in srgb, #fff 16%, transparent);
373
+ transform: scale(1.04);
374
+ }
375
+ }
376
+
377
+ @keyframes complexity-score-arrival-pop {
378
+ 0% {
379
+ transform: scale(1.15);
380
+ box-shadow:
381
+ 0 0 30px color-mix(in srgb, var(--indicator-color) 62%, transparent),
382
+ 0 0 58px color-mix(in srgb, var(--indicator-color) 36%, transparent),
383
+ inset 0 1px 0 color-mix(in srgb, #fff 18%, transparent);
384
+ }
385
+ 100% {
386
+ transform: scale(1);
387
+ }
388
+ }
389
+
390
+ @keyframes complexity-spine-flash {
391
+ 0% {
392
+ opacity: 1;
393
+ width: 8px;
394
+ box-shadow: 0 0 0 1px color-mix(in srgb, var(--indicator-color) 70%, transparent);
395
+ transform: translateX(-2px);
396
+ }
397
+ 100% {
398
+ opacity: var(--indicator-opacity);
399
+ width: 4px;
400
+ transform: translateX(0);
401
+ }
402
+ }
403
+
404
+ @keyframes complexity-spine-bloom-flash {
405
+ 0% {
406
+ filter: blur(12px);
407
+ opacity: 0.9;
408
+ transform: scaleX(1.45);
409
+ }
410
+ 100% {
411
+ filter: blur(6px);
412
+ opacity: 0.55;
413
+ transform: scaleX(1);
414
+ }
415
+ }
416
+
417
+ @media (prefers-reduced-motion: reduce) {
418
+ .complexity-gutter__spine--critical,
419
+ .complexity-gutter__spine--critical::before,
420
+ .complexity-gutter__spine--flash,
421
+ .complexity-gutter__spine--flash::before,
422
+ .complexity-gutter__score--critical,
423
+ .complexity-gutter__score--flash {
424
+ animation: none;
425
+ }
249
426
  }
250
427
 
251
428
  /* Tooltip */
@@ -253,7 +430,7 @@
253
430
  position: fixed;
254
431
  z-index: 1000;
255
432
  min-width: 200px;
256
- max-width: 300px;
433
+ max-width: 360px;
257
434
  padding: 12px;
258
435
  background: var(--ide-bg-secondary, #1a2744);
259
436
  border: 1px solid var(--ide-border, #a8c5d9);
@@ -274,12 +451,18 @@
274
451
  display: flex;
275
452
  align-items: center;
276
453
  justify-content: center;
277
- width: 28px;
278
- height: 28px;
279
- border-radius: 50%;
280
- font-size: 11px;
454
+ min-width: 38px;
455
+ height: 38px;
456
+ font-size: 24px;
281
457
  font-weight: 700;
282
- color: #fff;
458
+ color: var(--indicator-color);
459
+ font-variant-numeric: tabular-nums;
460
+ }
461
+
462
+ .complexity-tooltip__heading {
463
+ display: flex;
464
+ flex-direction: column;
465
+ gap: 2px;
283
466
  }
284
467
 
285
468
  .complexity-tooltip__title {
@@ -287,12 +470,45 @@
287
470
  color: var(--ide-text-primary, #f4f1e0);
288
471
  }
289
472
 
473
+ .complexity-tooltip__source {
474
+ font-size: 10px;
475
+ color: var(--ide-text-muted, #a8c5d9);
476
+ }
477
+
290
478
  .complexity-tooltip__lines {
291
479
  color: var(--ide-text-muted, #a8c5d9);
292
480
  font-size: 11px;
293
481
  margin-bottom: 8px;
294
482
  }
295
483
 
484
+ .complexity-tooltip__contributions {
485
+ list-style: none;
486
+ margin: 0 0 8px;
487
+ padding: 0;
488
+ display: grid;
489
+ gap: 3px;
490
+ }
491
+
492
+ .complexity-tooltip__contributions li {
493
+ display: grid;
494
+ grid-template-columns: 52px 34px minmax(0, 1fr);
495
+ gap: 6px;
496
+ align-items: baseline;
497
+ font-size: 11px;
498
+ color: var(--ide-text-secondary, #a8c5d9);
499
+ }
500
+
501
+ .complexity-tooltip__contributions strong {
502
+ color: var(--ide-text-primary, #f4f1e0);
503
+ font-variant-numeric: tabular-nums;
504
+ }
505
+
506
+ .complexity-tooltip__contributions .complexity-tooltip__more {
507
+ display: block;
508
+ color: var(--ide-text-muted, #a8c5d9);
509
+ font-style: italic;
510
+ }
511
+
296
512
  .complexity-tooltip__suggestion {
297
513
  padding: 8px;
298
514
  background: rgba(255, 255, 255, 0.05);
@@ -1,3 +1,10 @@
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
+ */
1
8
  import type { ComplexityMetrics } from './core/complexity-analyzer';
2
9
  interface Props {
3
10
  /** Complexity metrics for the current document */
@@ -13,10 +20,16 @@ interface Props {
13
20
  * that sat past the initial view.
14
21
  */
15
22
  totalHeight?: number;
23
+ /** Estimated full scrollable content width in pixels */
24
+ contentWidth?: number;
16
25
  /** Minimum score to show indicators (default: 50) */
17
26
  minScore?: number;
18
27
  /** Whether indicators are enabled */
19
28
  enabled?: boolean;
29
+ /** Region key to briefly emphasize after jump-to-hottest */
30
+ flashRegionKey?: string;
31
+ /** Maps a raw document line to its rendered visual row */
32
+ lineToVisualRow?: (line: number) => number;
20
33
  }
21
34
  declare const ComplexityLayer: import("svelte").Component<Props, {}, "">;
22
35
  type ComplexityLayer = ReturnType<typeof ComplexityLayer>;
@@ -40,17 +40,20 @@
40
40
  * Get color for severity level
41
41
  */
42
42
  function getSeverityColor(severity: ConflictZone['severity']): string {
43
+ // Route through the shared --ide-* band tokens so conflict severity stays
44
+ // in lockstep with the complexity band system (one source of truth) and
45
+ // avoids the repo's phantom hardcoded-hex footgun.
43
46
  switch (severity) {
44
47
  case 'critical':
45
- return '#ef4444';
48
+ return 'var(--ide-error)';
46
49
  case 'high':
47
- return '#f59e0b';
50
+ return 'var(--ide-warning)';
48
51
  case 'medium':
49
- return '#eab308';
52
+ return 'var(--ide-info)';
50
53
  case 'low':
51
- return '#22c55e';
54
+ return 'var(--ide-success)';
52
55
  default:
53
- return '#6b7280';
56
+ return 'var(--ide-text-muted)';
54
57
  }
55
58
  }
56
59
 
@@ -243,21 +246,25 @@
243
246
  .conflict-zone {
244
247
  position: absolute;
245
248
  right: 0;
249
+ /* Tint by the zone's own severity color (was hardcoded red for every
250
+ severity, so low/medium zones still washed red). */
246
251
  background: linear-gradient(
247
252
  90deg,
248
- rgba(var(--zone-color-rgb, 239, 68, 68), var(--zone-opacity)),
253
+ color-mix(in srgb, var(--zone-color) calc(var(--zone-opacity) * 100%), transparent),
249
254
  transparent
250
255
  );
251
- background-color: rgba(239, 68, 68, var(--zone-opacity));
252
- border-left: 3px solid rgba(239, 68, 68, var(--border-opacity));
253
- border-left-color: var(--zone-color);
256
+ border-left: 3px solid var(--zone-color);
254
257
  pointer-events: auto;
255
258
  cursor: pointer;
256
- transition: background-color 0.2s ease;
259
+ transition: background 0.2s ease;
257
260
  }
258
261
 
259
262
  .conflict-zone:hover {
260
- background-color: rgba(239, 68, 68, calc(var(--zone-opacity) + 0.1));
263
+ background: linear-gradient(
264
+ 90deg,
265
+ color-mix(in srgb, var(--zone-color) calc((var(--zone-opacity) + 0.1) * 100%), transparent),
266
+ transparent
267
+ );
261
268
  }
262
269
 
263
270
  .conflict-zone--critical {
@@ -417,17 +424,17 @@
417
424
  display: inline-block;
418
425
  padding: 1px 4px;
419
426
  margin-left: 4px;
420
- background: rgba(168, 85, 247, 0.3);
427
+ background: color-mix(in srgb, var(--ide-ai-assistant) 30%, transparent);
421
428
  border-radius: 3px;
422
429
  font-size: 9px;
423
- color: #a855f7;
430
+ color: var(--ide-ai-assistant);
424
431
  }
425
432
 
426
433
  .conflict-tooltip__suggestion {
427
434
  padding: 8px;
428
- background: rgba(255, 200, 0, 0.1);
435
+ background: color-mix(in srgb, var(--ide-warning) 12%, transparent);
429
436
  border-radius: 4px;
430
- border-left: 2px solid #eab308;
437
+ border-left: 2px solid var(--ide-warning);
431
438
  color: var(--ide-text-secondary, #a8c5d9);
432
439
  font-size: 11px;
433
440
  line-height: 1.4;