@nocturnium/svelte-ide 1.4.0 → 1.5.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.
@@ -34,6 +34,13 @@
34
34
  showSquiggles?: boolean;
35
35
  /** Show gutter icons */
36
36
  showGutterIcons?: boolean;
37
+ /**
38
+ * Show the diagnostic message inline at the end of its line (Error Lens
39
+ * style), always visible — so the message no longer requires hovering the
40
+ * thin squiggle. The whole line becomes the hover/click target for the
41
+ * detailed tooltip. Default true.
42
+ */
43
+ showInlineMessages?: boolean;
37
44
  /** Callback when fix is applied */
38
45
  onApplyFix?: (diagnostic: Diagnostic, fix: DiagnosticFix) => void;
39
46
  /** Callback when diagnostic is clicked */
@@ -49,10 +56,19 @@
49
56
  enabled = true,
50
57
  showSquiggles = true,
51
58
  showGutterIcons = true,
59
+ showInlineMessages = true,
52
60
  onApplyFix,
53
61
  onDiagnosticClick
54
62
  }: Props = $props();
55
63
 
64
+ // Severity ordering for picking a line's primary (most severe) diagnostic.
65
+ const SEVERITY_RANK: Record<DiagnosticSeverity, number> = {
66
+ error: 0,
67
+ warning: 1,
68
+ info: 2,
69
+ hint: 3
70
+ };
71
+
56
72
  let diagnostics = $state<Diagnostic[]>([]);
57
73
  let hoveredDiagnostic = $state<Diagnostic | null>(null);
58
74
  let tooltipPosition = $state({ top: 0, left: 0 });
@@ -100,10 +116,22 @@
100
116
  return 'hint';
101
117
  }
102
118
 
119
+ // One always-visible inline message per affected line: the most severe
120
+ // diagnostic on that line, plus a count of how many others share it.
121
+ const lineMessages = $derived(() =>
122
+ linesWithDiagnostics().map((line) => {
123
+ const diags = diagnosticsByLine().get(line) || [];
124
+ const primary = [...diags].sort(
125
+ (a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity]
126
+ )[0];
127
+ return { line, primary, count: diags.length, severity: getLineSeverity(line) };
128
+ })
129
+ );
130
+
103
131
  /**
104
132
  * Handle mouse enter on diagnostic
105
133
  */
106
- function handleMouseEnter(diagnostic: Diagnostic, event: MouseEvent) {
134
+ function handleMouseEnter(diagnostic: Diagnostic, event: MouseEvent | FocusEvent) {
107
135
  hoveredDiagnostic = diagnostic;
108
136
  const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();
109
137
  tooltipPosition = {
@@ -168,7 +196,38 @@
168
196
  </script>
169
197
 
170
198
  {#if enabled && diagnostics.length > 0}
171
- <div class="diagnostics-layer" aria-hidden="true">
199
+ <div class="diagnostics-layer">
200
+ <!-- Inline messages (Error Lens style): the message is ALWAYS visible at the
201
+ end of its line, and the full-line-height row — not the 4px squiggle — is
202
+ the hover/click/focus target for the detailed tooltip. -->
203
+ {#if showInlineMessages}
204
+ <div class="diagnostics-messages" style="left: {gutterWidth}px;">
205
+ {#each lineMessages() as { line, primary, count, severity } (line)}
206
+ <button
207
+ type="button"
208
+ class="diagnostic-row diagnostic-row--{severity}"
209
+ style="top: {line * lineHeight}px; height: {lineHeight}px;"
210
+ aria-label="{severity}: {primary.message}{count > 1
211
+ ? ` (and ${count - 1} more on this line)`
212
+ : ''}"
213
+ onmouseenter={(e) => handleMouseEnter(primary, e)}
214
+ onmouseleave={handleMouseLeave}
215
+ onfocus={(e) => handleMouseEnter(primary, e)}
216
+ onblur={handleMouseLeave}
217
+ onclick={() => onDiagnosticClick?.(primary)}
218
+ >
219
+ <span class="row-chip">
220
+ <span class="row-icon" aria-hidden="true">{getSeverityIcon(severity)}</span>
221
+ <span class="row-message">{primary.message}</span>
222
+ {#if count > 1}
223
+ <span class="row-count" aria-hidden="true">+{count - 1}</span>
224
+ {/if}
225
+ </span>
226
+ </button>
227
+ {/each}
228
+ </div>
229
+ {/if}
230
+
172
231
  <!-- Gutter icons -->
173
232
  {#if showGutterIcons}
174
233
  <div class="diagnostics-gutter" style="width: {gutterWidth}px;">
@@ -192,9 +251,9 @@
192
251
  </div>
193
252
  {/if}
194
253
 
195
- <!-- Squiggly underlines -->
254
+ <!-- Squiggly underlines (decorative cue; interaction lives on the row above) -->
196
255
  {#if showSquiggles}
197
- <div class="diagnostics-squiggles" style="left: {gutterWidth}px;">
256
+ <div class="diagnostics-squiggles" style="left: {gutterWidth}px;" aria-hidden="true">
198
257
  {#each diagnostics as diagnostic (diagnostic.id)}
199
258
  {@const startCol = diagnostic.range.start.column}
200
259
  {@const endCol = diagnostic.range.end.column}
@@ -210,10 +269,6 @@
210
269
  left: {startCol * charWidth}px;
211
270
  width: {width}px;
212
271
  "
213
- onmouseenter={(e) => handleMouseEnter(diagnostic, e)}
214
- onmouseleave={handleMouseLeave}
215
- role="button"
216
- tabindex={-1}
217
272
  >
218
273
  <svg class="squiggle-svg" {width} height="4" viewBox="0 0 {width} 4">
219
274
  <path d={generateSquigglePath(width)} stroke={color} stroke-width="1" fill="none" />
@@ -336,19 +391,19 @@
336
391
  }
337
392
 
338
393
  .gutter-icon--error .icon-text {
339
- color: #f44747;
394
+ color: var(--ide-error);
340
395
  }
341
396
 
342
397
  .gutter-icon--warning .icon-text {
343
- color: #ff8c00;
398
+ color: var(--ide-warning);
344
399
  }
345
400
 
346
401
  .gutter-icon--info .icon-text {
347
- color: #3794ff;
402
+ color: var(--ide-info);
348
403
  }
349
404
 
350
405
  .gutter-icon--hint .icon-text {
351
- color: #6c6c6c;
406
+ color: var(--ide-text-muted);
352
407
  }
353
408
 
354
409
  .icon-count {
@@ -376,8 +431,166 @@
376
431
 
377
432
  .squiggle {
378
433
  position: absolute;
379
- pointer-events: auto;
434
+ /* Decorative now — the full-line row above owns hover/click. */
435
+ pointer-events: none;
436
+ }
437
+
438
+ /* Inline messages (Error Lens style) */
439
+ .diagnostics-messages {
440
+ position: absolute;
441
+ top: 0;
442
+ right: 0;
443
+ height: 100%;
444
+ }
445
+
446
+ .diagnostic-row {
447
+ position: absolute;
448
+ left: 0;
449
+ right: 0;
450
+ display: flex;
451
+ align-items: center;
452
+ justify-content: flex-end;
453
+ margin: 0;
454
+ /* Reserve a left gap so the right-aligned chip never butts directly against
455
+ code text on a long line — code and message keep a visible breathing gap. */
456
+ padding: 0 8px 0 16px;
457
+ background: transparent;
458
+ border: none;
459
+ border-left: 2px solid transparent;
460
+ font: inherit;
380
461
  cursor: pointer;
462
+ pointer-events: auto;
463
+ overflow: hidden;
464
+ transition: background 0.12s ease;
465
+ }
466
+
467
+ /* The message sits in an opaque chip so it stays legible even if it overlaps
468
+ code on a long line — the row's faint full-width tint is just the line
469
+ highlight / hit target underneath. */
470
+ .row-chip {
471
+ display: inline-flex;
472
+ align-items: center;
473
+ gap: 6px;
474
+ max-width: min(100%, 64ch);
475
+ padding: 1px 8px;
476
+ border-radius: 6px;
477
+ overflow: hidden;
478
+ }
479
+
480
+ .row-icon {
481
+ flex-shrink: 0;
482
+ font-size: 11px;
483
+ }
484
+
485
+ .row-message {
486
+ overflow: hidden;
487
+ font-size: 12px;
488
+ line-height: 1.4;
489
+ white-space: nowrap;
490
+ text-overflow: ellipsis;
491
+ }
492
+
493
+ .row-count {
494
+ flex-shrink: 0;
495
+ min-width: 16px;
496
+ padding: 1px 5px;
497
+ border-radius: 8px;
498
+ background: color-mix(in srgb, var(--ide-text-muted) 22%, transparent);
499
+ font-size: 10px;
500
+ font-weight: 600;
501
+ color: var(--ide-text-secondary, #aaa);
502
+ text-align: center;
503
+ }
504
+
505
+ /* On genuinely narrow (phone) viewports an inline message would overprint code
506
+ with no room for the reserved gap — drop it there and rely on the gutter icon
507
+ + tooltip instead. Tablets (641–768px) keep the message: the opaque severity
508
+ chip and the reserved left gap below keep it legible and off the code. */
509
+ @media (max-width: 480px) {
510
+ .diagnostics-messages {
511
+ display: none;
512
+ }
513
+ }
514
+
515
+ /* Tablet (481–768px): keep the message on-screen but cap the chip so a long
516
+ message ellipsizes within the pane instead of clipping past the right edge,
517
+ and hold the reserved left gap so it never overprints code. */
518
+ @media (max-width: 768px) {
519
+ .diagnostic-row .row-chip {
520
+ max-width: min(100%, 40ch);
521
+ }
522
+ }
523
+
524
+ .diagnostic-row:focus-visible {
525
+ outline: 1px solid var(--ide-interactive, #4f8cc9);
526
+ outline-offset: -1px;
527
+ }
528
+
529
+ .diagnostic-row--error {
530
+ background: color-mix(in srgb, var(--ide-error) 9%, transparent);
531
+ border-left-color: color-mix(in srgb, var(--ide-error) 55%, transparent);
532
+ }
533
+ .diagnostic-row--error:hover,
534
+ .diagnostic-row--error:focus-visible {
535
+ background: color-mix(in srgb, var(--ide-error) 17%, transparent);
536
+ }
537
+ .diagnostic-row--error .row-icon,
538
+ .diagnostic-row--error .row-message {
539
+ color: var(--ide-error);
540
+ }
541
+
542
+ .diagnostic-row--warning {
543
+ background: color-mix(in srgb, var(--ide-warning) 9%, transparent);
544
+ border-left-color: color-mix(in srgb, var(--ide-warning) 55%, transparent);
545
+ }
546
+ .diagnostic-row--warning:hover,
547
+ .diagnostic-row--warning:focus-visible {
548
+ background: color-mix(in srgb, var(--ide-warning) 17%, transparent);
549
+ }
550
+ .diagnostic-row--warning .row-icon,
551
+ .diagnostic-row--warning .row-message {
552
+ color: var(--ide-warning);
553
+ }
554
+
555
+ .diagnostic-row--info {
556
+ background: color-mix(in srgb, var(--ide-info) 8%, transparent);
557
+ border-left-color: color-mix(in srgb, var(--ide-info) 50%, transparent);
558
+ }
559
+ .diagnostic-row--info:hover,
560
+ .diagnostic-row--info:focus-visible {
561
+ background: color-mix(in srgb, var(--ide-info) 16%, transparent);
562
+ }
563
+ .diagnostic-row--info .row-icon,
564
+ .diagnostic-row--info .row-message {
565
+ color: var(--ide-info);
566
+ }
567
+
568
+ .diagnostic-row--hint {
569
+ background: color-mix(in srgb, var(--ide-text-muted) 7%, transparent);
570
+ border-left-color: color-mix(in srgb, var(--ide-text-muted) 45%, transparent);
571
+ }
572
+ .diagnostic-row--hint:hover,
573
+ .diagnostic-row--hint:focus-visible {
574
+ background: color-mix(in srgb, var(--ide-text-muted) 14%, transparent);
575
+ }
576
+ .diagnostic-row--hint .row-icon,
577
+ .diagnostic-row--hint .row-message {
578
+ color: var(--ide-text-muted);
579
+ }
580
+
581
+ /* Opaque chip surfaces: an elevated base tinted by severity, so the message
582
+ reads cleanly whether it floats over empty space or over code. */
583
+ .diagnostic-row--error .row-chip {
584
+ background: color-mix(in srgb, var(--ide-error) 16%, var(--ide-bg-elevated, #252536));
585
+ }
586
+ .diagnostic-row--warning .row-chip {
587
+ background: color-mix(in srgb, var(--ide-warning) 16%, var(--ide-bg-elevated, #252536));
588
+ }
589
+ .diagnostic-row--info .row-chip {
590
+ background: color-mix(in srgb, var(--ide-info) 14%, var(--ide-bg-elevated, #252536));
591
+ }
592
+ .diagnostic-row--hint .row-chip {
593
+ background: color-mix(in srgb, var(--ide-text-muted) 14%, var(--ide-bg-elevated, #252536));
381
594
  }
382
595
 
383
596
  .squiggle--deprecated {
@@ -25,6 +25,13 @@ interface Props {
25
25
  showSquiggles?: boolean;
26
26
  /** Show gutter icons */
27
27
  showGutterIcons?: boolean;
28
+ /**
29
+ * Show the diagnostic message inline at the end of its line (Error Lens
30
+ * style), always visible — so the message no longer requires hovering the
31
+ * thin squiggle. The whole line becomes the hover/click target for the
32
+ * detailed tooltip. Default true.
33
+ */
34
+ showInlineMessages?: boolean;
28
35
  /** Callback when fix is applied */
29
36
  onApplyFix?: (diagnostic: Diagnostic, fix: DiagnosticFix) => void;
30
37
  /** Callback when diagnostic is clicked */
@@ -50,7 +50,10 @@
50
50
  case 'added':
51
51
  return 'var(--ide-status-created)';
52
52
  case 'modified':
53
- return 'var(--ide-status-modified)';
53
+ // Modified indicator is BLUE to match the diff legend (Modified = blue).
54
+ // --ide-status-modified resolves to aurora-yellow (amber), which
55
+ // contradicts the legend; drive it from --ide-info (#60a5fa) instead.
56
+ return 'var(--ide-info)';
54
57
  case 'removed':
55
58
  return 'var(--ide-status-deleted)';
56
59
  default:
@@ -175,7 +178,10 @@
175
178
  onclick={() => handleClick(group.changes[0])}
176
179
  onkeydown={(e) => e.key === 'Enter' && handleClick(group.changes[0])}
177
180
  role="button"
178
- tabindex={-1}
181
+ tabindex={onChangeClick ? 0 : -1}
182
+ aria-label={onChangeClick
183
+ ? `${getLabel(group.type)}: ${group.changes.length} line${group.changes.length !== 1 ? 's' : ''} — view full diff`
184
+ : undefined}
179
185
  title="{getLabel(group.type)}: {group.changes.length} line{group.changes.length !== 1
180
186
  ? 's'
181
187
  : ''}"
@@ -739,7 +739,10 @@
739
739
  line-height: 1.5;
740
740
  color: var(--ide-text-primary, #f4f1e0);
741
741
  overflow: auto;
742
- white-space: pre;
742
+ /* Wrap long single-line transformed output (e.g. Minify) instead of
743
+ clipping it off the pane's right edge with no scroll affordance. */
744
+ white-space: pre-wrap;
745
+ overflow-wrap: anywhere;
743
746
  }
744
747
 
745
748
  /* Split view */
@@ -793,6 +796,7 @@
793
796
 
794
797
  .diff-line__num {
795
798
  width: 40px;
799
+ flex-shrink: 0;
796
800
  color: var(--ide-text-muted, #a8c5d9);
797
801
  text-align: right;
798
802
  padding-right: 8px;
@@ -801,6 +805,7 @@
801
805
 
802
806
  .diff-line__sign {
803
807
  width: 16px;
808
+ flex-shrink: 0;
804
809
  color: var(--ide-text-muted, #a8c5d9);
805
810
  }
806
811
 
@@ -814,7 +819,10 @@
814
819
 
815
820
  .diff-line__content {
816
821
  flex: 1;
817
- white-space: pre;
822
+ min-width: 0;
823
+ /* Wrap long single-line diff output instead of clipping it off-pane. */
824
+ white-space: pre-wrap;
825
+ overflow-wrap: anywhere;
818
826
  }
819
827
 
820
828
  .diff-line--add .diff-line__content {
@@ -426,7 +426,9 @@
426
426
  }
427
427
 
428
428
  .action-btn.active {
429
- color: #a855f7;
429
+ /* interactive accent stays BLUE per owner brand decision
430
+ (was an off-token #a855f7 purple literal) */
431
+ color: var(--ide-interactive, #4f8cc9);
430
432
  }
431
433
 
432
434
  .close-btn {
@@ -571,6 +573,9 @@
571
573
 
572
574
  .diagnostic-message {
573
575
  flex: 1;
576
+ /* min-width:0 lets the message shrink/ellipsize instead of forcing the
577
+ metadata (code/chip/[Ln,Col]) to absorb the squeeze first */
578
+ min-width: 0;
574
579
  overflow: hidden;
575
580
  text-overflow: ellipsis;
576
581
  white-space: nowrap;
@@ -582,22 +587,52 @@
582
587
  }
583
588
 
584
589
  .diagnostic-source {
590
+ flex-shrink: 0;
585
591
  font-size: 10px;
586
592
  padding: 1px 4px;
587
- background: rgba(255, 255, 255, 0.1);
593
+ /* nudged a notch lighter (0.1 -> 0.16) so the chip label clears AA */
594
+ background: rgba(255, 255, 255, 0.16);
588
595
  border-radius: 2px;
589
- color: var(--ide-text-muted, #888);
596
+ /* lifted from --ide-text-muted (~3:1, below AA at 10-11px) to clear 4.5:1 */
597
+ color: var(--ide-text-secondary, #aaa);
590
598
  }
591
599
 
592
600
  .diagnostic-code {
601
+ flex-shrink: 0;
593
602
  font-family: monospace;
594
603
  font-size: 10px;
595
- color: var(--ide-text-muted, #888);
604
+ /* lifted from --ide-text-muted so TS codes (TS2552/TS6133) clear AA */
605
+ color: var(--ide-text-secondary, #aaa);
596
606
  }
597
607
 
598
608
  .diagnostic-location {
609
+ flex-shrink: 0;
599
610
  font-family: monospace;
600
611
  font-size: 10px;
601
- color: var(--ide-text-muted, #666);
612
+ /* lifted from --ide-text-muted so [Ln, Col] coords clear AA */
613
+ color: var(--ide-text-secondary, #aaa);
614
+ }
615
+
616
+ /* Narrow viewports: collapse the single-line row into two lines so the
617
+ message stays readable instead of truncating to a single glyph while the
618
+ code / chip / [Ln, Col] keep full width. The icon + message hold line one
619
+ (message gets a 2-line clamp); the source/code/location metadata wrap to a
620
+ second, gutter-indented line. */
621
+ @media (max-width: 480px) {
622
+ .diagnostic-item {
623
+ flex-wrap: wrap;
624
+ row-gap: 2px;
625
+ }
626
+
627
+ .diagnostic-message {
628
+ /* take the full first line so metadata is pushed to line two */
629
+ flex: 1 1 100%;
630
+ /* message is the LAST field to truncate: allow up to 2 lines */
631
+ white-space: normal;
632
+ display: -webkit-box;
633
+ -webkit-line-clamp: 2;
634
+ line-clamp: 2;
635
+ -webkit-box-orient: vertical;
636
+ }
602
637
  }
603
638
  </style>
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import type { SnippetManager, Snippet } from './core/snippet-manager';
10
+ import Icon from '../core/Icon.svelte';
10
11
 
11
12
  interface Props {
12
13
  /** Snippet manager instance */
@@ -174,6 +175,9 @@
174
175
  bind:value={searchQuery}
175
176
  />
176
177
  <span class="snippet-hint">Tab: categories, Enter: insert, Esc: close</span>
178
+ <button class="snippet-close" type="button" aria-label="Close" onclick={() => onClose?.()}>
179
+ <Icon name="close" size={18} />
180
+ </button>
177
181
  </div>
178
182
 
179
183
  <div class="snippet-categories">
@@ -318,10 +322,39 @@
318
322
 
319
323
  .snippet-hint {
320
324
  font-size: 11px;
321
- color: var(--ide-text-muted, #a8c5d9);
325
+ color: var(--ide-text-secondary, #a8c5d9);
322
326
  white-space: nowrap;
323
327
  }
324
328
 
329
+ .snippet-close {
330
+ display: inline-flex;
331
+ align-items: center;
332
+ justify-content: center;
333
+ flex: none;
334
+ width: 44px;
335
+ height: 44px;
336
+ margin: -8px -4px -8px 0;
337
+ padding: 0;
338
+ background: transparent;
339
+ border: none;
340
+ border-radius: 6px;
341
+ color: var(--ide-text-secondary, #a8c5d9);
342
+ cursor: pointer;
343
+ transition:
344
+ background 0.15s ease,
345
+ color 0.15s ease;
346
+ }
347
+
348
+ .snippet-close:hover {
349
+ background: color-mix(in srgb, var(--ide-text-primary) 8%, transparent);
350
+ color: var(--ide-text-primary, #f4f1e0);
351
+ }
352
+
353
+ .snippet-close:focus-visible {
354
+ outline: 2px solid var(--ide-interactive-focus);
355
+ outline-offset: 2px;
356
+ }
357
+
325
358
  .snippet-categories {
326
359
  display: flex;
327
360
  gap: 4px;
@@ -343,13 +376,18 @@
343
376
  }
344
377
 
345
378
  .category-tab:hover {
346
- background: rgba(255, 255, 255, 0.05);
379
+ background: color-mix(in srgb, var(--ide-text-primary) 5%, transparent);
347
380
  color: var(--ide-text-primary, #f4f1e0);
348
381
  }
349
382
 
350
383
  .category-tab.active {
351
- background: rgba(168, 85, 247, 0.2);
352
- color: #a855f7;
384
+ background: color-mix(in srgb, var(--ide-interactive) 20%, transparent);
385
+ color: var(--ide-interactive);
386
+ }
387
+
388
+ .category-tab:focus-visible {
389
+ outline: 2px solid var(--ide-interactive-focus);
390
+ outline-offset: 2px;
353
391
  }
354
392
 
355
393
  .snippet-content {
@@ -367,7 +405,7 @@
367
405
  .snippet-empty {
368
406
  padding: 24px;
369
407
  text-align: center;
370
- color: var(--ide-text-muted, #a8c5d9);
408
+ color: var(--ide-text-secondary, #a8c5d9);
371
409
  font-size: 13px;
372
410
  }
373
411
 
@@ -385,7 +423,12 @@
385
423
 
386
424
  .snippet-item:hover,
387
425
  .snippet-item.selected {
388
- background: rgba(168, 85, 247, 0.1);
426
+ background: color-mix(in srgb, var(--ide-interactive) 12%, transparent);
427
+ }
428
+
429
+ .snippet-item:focus-visible {
430
+ outline: 2px solid var(--ide-interactive-focus);
431
+ outline-offset: -2px;
389
432
  }
390
433
 
391
434
  .snippet-item-header {
@@ -399,9 +442,9 @@
399
442
  font-family: monospace;
400
443
  font-size: 12px;
401
444
  padding: 2px 6px;
402
- background: rgba(168, 85, 247, 0.2);
445
+ background: color-mix(in srgb, var(--ide-interactive) 20%, transparent);
403
446
  border-radius: 4px;
404
- color: #a855f7;
447
+ color: var(--ide-interactive);
405
448
  }
406
449
 
407
450
  .snippet-name {
@@ -413,15 +456,15 @@
413
456
  .snippet-badge {
414
457
  margin-left: auto;
415
458
  padding: 1px 6px;
416
- background: rgba(34, 197, 94, 0.2);
459
+ background: color-mix(in srgb, var(--ide-success) 20%, transparent);
417
460
  border-radius: 4px;
418
461
  font-size: 10px;
419
- color: #22c55e;
462
+ color: var(--ide-success);
420
463
  }
421
464
 
422
465
  .snippet-item-desc {
423
466
  font-size: 11px;
424
- color: var(--ide-text-muted, #a8c5d9);
467
+ color: var(--ide-text-secondary, #a8c5d9);
425
468
  }
426
469
 
427
470
  /* Preview pane */
@@ -448,7 +491,7 @@
448
491
  .preview-category {
449
492
  font-size: 11px;
450
493
  padding: 2px 8px;
451
- background: rgba(255, 255, 255, 0.1);
494
+ background: color-mix(in srgb, var(--ide-text-primary) 10%, transparent);
452
495
  border-radius: 4px;
453
496
  color: var(--ide-text-secondary, #a8c5d9);
454
497
  }
@@ -472,13 +515,13 @@
472
515
  }
473
516
 
474
517
  :global(.preview-tabstop) {
475
- background: rgba(168, 85, 247, 0.3);
518
+ background: color-mix(in srgb, var(--ide-interactive) 30%, transparent);
476
519
  border-radius: 2px;
477
520
  padding: 0 2px;
478
521
  }
479
522
 
480
523
  :global(.preview-variable) {
481
- color: #22c55e;
524
+ color: var(--ide-success);
482
525
  }
483
526
 
484
527
  .preview-footer {
@@ -486,12 +529,12 @@
486
529
  justify-content: space-between;
487
530
  margin-top: 12px;
488
531
  font-size: 11px;
489
- color: var(--ide-text-muted, #a8c5d9);
532
+ color: var(--ide-text-secondary, #a8c5d9);
490
533
  }
491
534
 
492
535
  .preview-footer code {
493
536
  font-family: monospace;
494
- background: rgba(255, 255, 255, 0.1);
537
+ background: color-mix(in srgb, var(--ide-text-primary) 10%, transparent);
495
538
  padding: 1px 4px;
496
539
  border-radius: 2px;
497
540
  }
@@ -505,9 +548,9 @@
505
548
  .lang-tag {
506
549
  font-size: 10px;
507
550
  padding: 2px 6px;
508
- background: rgba(59, 130, 246, 0.2);
551
+ background: color-mix(in srgb, var(--ide-info) 20%, transparent);
509
552
  border-radius: 3px;
510
- color: #3b82f6;
553
+ color: var(--ide-info);
511
554
  }
512
555
 
513
556
  .preview-empty {
@@ -515,7 +558,7 @@
515
558
  align-items: center;
516
559
  justify-content: center;
517
560
  height: 100%;
518
- color: var(--ide-text-muted, #a8c5d9);
561
+ color: var(--ide-text-secondary, #a8c5d9);
519
562
  font-size: 13px;
520
563
  }
521
564
 
@@ -525,6 +568,6 @@
525
568
  padding: 8px 16px;
526
569
  border-top: 1px solid var(--ide-border, #a8c5d9);
527
570
  font-size: 11px;
528
- color: var(--ide-text-muted, #a8c5d9);
571
+ color: var(--ide-text-secondary, #a8c5d9);
529
572
  }
530
573
  </style>
@@ -226,7 +226,10 @@ export function getSeverityIcon(severity) {
226
226
  case 'info':
227
227
  return 'ℹ';
228
228
  case 'hint':
229
- return '💡';
229
+ // Monochrome glyph (not the 💡 emoji): a color emoji ignores `color` and
230
+ // would render the LOWEST severity louder than a warning, inverting the
231
+ // severity hierarchy.
232
+ return '◇';
230
233
  }
231
234
  }
232
235
  /**