nuxt-devtools-observatory 0.1.28 → 0.1.30

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.
@@ -1,7 +1,12 @@
1
1
  <script setup lang="ts">
2
2
  import { ref, computed } from 'vue'
3
- import { useObservatoryData, setComposableMode, editComposableValue, openInEditor as openInEditorFromStore } from '../stores/observatory'
4
- import type { ComposableEntry as RuntimeComposableEntry } from '../../../src/types/snapshot'
3
+ import {
4
+ useObservatoryData,
5
+ setComposableMode,
6
+ editComposableValue,
7
+ openInEditor as openInEditorFromStore,
8
+ } from '@observatory-client/stores/observatory'
9
+ import type { ComposableEntry as RuntimeComposableEntry } from '@observatory/types/snapshot'
5
10
 
6
11
  const { composables: rawEntries, connected, features, clearComposables } = useObservatoryData()
7
12
 
@@ -291,19 +296,19 @@ function applyEdit() {
291
296
  </script>
292
297
 
293
298
  <template>
294
- <div class="view">
295
- <div class="stats-row">
299
+ <div class="composable-tracker tracker-view">
300
+ <div class="composable-tracker__stats tracker-stats-row">
296
301
  <div class="stat-card">
297
302
  <div class="stat-label">total</div>
298
303
  <div class="stat-val">{{ entries.length }}</div>
299
304
  </div>
300
305
  <div class="stat-card">
301
306
  <div class="stat-label">mounted</div>
302
- <div class="stat-val" style="color: var(--teal)">{{ counts.mounted }}</div>
307
+ <div class="stat-val stat-val--active">{{ counts.mounted }}</div>
303
308
  </div>
304
309
  <div class="stat-card">
305
310
  <div class="stat-label">leaks</div>
306
- <div class="stat-val" style="color: var(--red)">{{ counts.leaks }}</div>
311
+ <div class="stat-val stat-val--error">{{ counts.leaks }}</div>
307
312
  </div>
308
313
  <div class="stat-card">
309
314
  <div class="stat-label">instances</div>
@@ -311,38 +316,47 @@ function applyEdit() {
311
316
  </div>
312
317
  </div>
313
318
 
314
- <div class="toolbar">
319
+ <div class="composable-tracker__toolbar tracker-toolbar">
315
320
  <button :class="{ active: filter === 'all' }" @click="filter = 'all'">all</button>
316
321
  <button :class="{ active: filter === 'mounted' }" @click="filter = 'mounted'">mounted</button>
317
322
  <button :class="{ 'danger-active': filter === 'leak' }" @click="filter = 'leak'">leaks only</button>
318
323
  <button :class="{ active: filter === 'unmounted' }" @click="filter = 'unmounted'">unmounted</button>
319
324
  <button
320
- class="mode-btn"
325
+ class="composable-tracker__mode-btn"
321
326
  :title="`switch to ${composableMode === 'route' ? 'session' : 'route'} mode`"
322
327
  @click="toggleComposableMode"
323
328
  >
324
329
  mode: {{ composableMode }}
325
330
  </button>
326
- <input v-model="search" type="search" placeholder="search name, file, or ref…" style="max-width: 220px; margin-left: auto" />
327
- <button v-if="composableMode === 'session'" class="clear-btn" title="Clear session history" @click="clearSession">
331
+ <span class="tracker-toolbar__spacer"></span>
332
+ <input v-model="search" class="composable-tracker__search" type="search" placeholder="search name, file, or ref…" />
333
+ <button
334
+ v-if="composableMode === 'session'"
335
+ class="composable-tracker__clear-btn"
336
+ title="Clear session history"
337
+ @click="clearSession"
338
+ >
328
339
  clear session
329
340
  </button>
330
341
  </div>
331
342
 
332
- <div class="list">
343
+ <div class="composable-tracker__list">
333
344
  <div
334
345
  v-for="entry in filtered"
335
346
  :key="entry.id"
336
- class="comp-card"
337
- :class="{ leak: entry.leak, expanded: expanded === entry.id }"
347
+ class="composable-tracker__card"
348
+ :class="{
349
+ 'composable-tracker__card--leak': entry.leak,
350
+ 'composable-tracker__card--expanded': expanded === entry.id,
351
+ }"
338
352
  @click="expanded = expanded === entry.id ? null : entry.id"
339
353
  >
340
- <div class="comp-header">
341
- <div class="comp-identity">
342
- <span class="comp-name mono">{{ entry.name }}</span>
343
- <span class="comp-file muted mono">{{ basename(entry.componentFile) }}</span>
354
+ <div class="composable-tracker__card-header">
355
+ <div class="composable-tracker__identity">
356
+ <span class="composable-tracker__name mono">{{ entry.name }}</span>
357
+ <span class="composable-tracker__file muted mono">{{ basename(entry.componentFile) }}</span>
344
358
  </div>
345
- <div class="comp-meta">
359
+ <div class="composable-tracker__meta">
346
360
  <span v-if="entry.watcherCount > 0 && !entry.leak" class="badge badge-warn">{{ entry.watcherCount }}w</span>
347
361
  <span v-if="entry.intervalCount > 0 && !entry.leak" class="badge badge-warn">{{ entry.intervalCount }}t</span>
348
362
  <span v-if="entry.leak" class="badge badge-err">leak</span>
@@ -352,33 +366,33 @@ function applyEdit() {
352
366
  </div>
353
367
 
354
368
  <!-- Inline ref preview — shows up to 3 refs without expanding -->
355
- <div v-if="Object.keys(entry.refs).length" class="refs-preview">
369
+ <div v-if="Object.keys(entry.refs).length" class="composable-tracker__refs-preview">
356
370
  <span
357
371
  v-for="[k, v] in Object.entries(entry.refs).slice(0, 3)"
358
372
  :key="k"
359
- class="ref-chip"
373
+ class="composable-tracker__ref-chip"
360
374
  :class="{
361
- 'ref-chip--reactive': v.type === 'reactive',
362
- 'ref-chip--computed': v.type === 'computed',
363
- 'ref-chip--shared': entry.sharedKeys?.includes(k),
375
+ 'composable-tracker__ref-chip--reactive': v.type === 'reactive',
376
+ 'composable-tracker__ref-chip--computed': v.type === 'computed',
377
+ 'composable-tracker__ref-chip--shared': entry.sharedKeys?.includes(k),
364
378
  }"
365
379
  :title="entry.sharedKeys?.includes(k) ? 'shared global state' : ''"
366
380
  >
367
- <span class="ref-chip-key">{{ k }}</span>
368
- <span class="ref-chip-val">{{ formatVal(v.value) }}</span>
369
- <span v-if="entry.sharedKeys?.includes(k)" class="ref-chip-shared-dot" title="global"></span>
381
+ <span class="composable-tracker__ref-chip-key">{{ k }}</span>
382
+ <span class="composable-tracker__ref-chip-val">{{ formatVal(v.value) }}</span>
383
+ <span v-if="entry.sharedKeys?.includes(k)" class="composable-tracker__ref-chip-shared-dot" title="global"></span>
370
384
  </span>
371
385
  <span v-if="Object.keys(entry.refs).length > 3" class="muted text-sm">
372
386
  +{{ Object.keys(entry.refs).length - 3 }} more
373
387
  </span>
374
388
  </div>
375
389
 
376
- <div v-if="expanded === entry.id" class="comp-detail" @click.stop>
377
- <div v-if="entry.leak" class="leak-banner">{{ entry.leakReason }}</div>
390
+ <div v-if="expanded === entry.id" class="composable-tracker__detail" @click.stop>
391
+ <div v-if="entry.leak" class="composable-tracker__leak-banner">{{ entry.leakReason }}</div>
378
392
 
379
393
  <!-- Global state warning -->
380
- <div v-if="entry.sharedKeys?.length" class="global-banner">
381
- <span class="global-dot"></span>
394
+ <div v-if="entry.sharedKeys?.length" class="composable-tracker__global-banner">
395
+ <span class="composable-tracker__global-dot"></span>
382
396
  <span>
383
397
  <strong>global state</strong>
384
398
  — {{ entry.sharedKeys.join(', ') }}
@@ -387,31 +401,31 @@ function applyEdit() {
387
401
  </span>
388
402
  </div>
389
403
 
390
- <div class="section-label">reactive state</div>
391
- <div v-if="!Object.keys(entry.refs).length" class="muted text-sm" style="padding: 2px 0 6px">
404
+ <div class="composable-tracker__section-label tracker-section-label">reactive state</div>
405
+ <div v-if="!Object.keys(entry.refs).length" class="composable-tracker__compact-muted muted text-sm">
392
406
  no tracked state returned
393
407
  </div>
394
- <div v-for="[k, v] in Object.entries(entry.refs)" :key="k" class="ref-row">
408
+ <div v-for="[k, v] in Object.entries(entry.refs)" :key="k" class="composable-tracker__ref-row">
395
409
  <span
396
- class="mono text-sm ref-key ref-key--clickable"
410
+ class="composable-tracker__ref-key composable-tracker__ref-key--clickable mono text-sm"
397
411
  :title="`click to see all instances exposing '${k}'`"
398
412
  @click.stop="openLookup(k)"
399
413
  >
400
414
  {{ k }}
401
415
  </span>
402
416
  <span
403
- class="mono text-sm ref-val"
417
+ class="composable-tracker__ref-val mono text-sm"
404
418
  :class="{
405
- 'ref-val--full': isLongValue(v.value) && isRefExpanded(entry.id, k),
406
- 'ref-val--collapsed': isLongValue(v.value) && !isRefExpanded(entry.id, k),
419
+ 'composable-tracker__ref-val--full': isLongValue(v.value) && isRefExpanded(entry.id, k),
420
+ 'composable-tracker__ref-val--collapsed': isLongValue(v.value) && !isRefExpanded(entry.id, k),
407
421
  }"
408
422
  >
409
423
  {{ isLongValue(v.value) && !isRefExpanded(entry.id, k) ? formatVal(v.value) : formatValFull(v.value) }}
410
424
  </span>
411
- <div class="ref-row-actions">
425
+ <div class="composable-tracker__ref-row-actions">
412
426
  <button
413
427
  v-if="isLongValue(v.value)"
414
- class="expand-btn"
428
+ class="composable-tracker__expand-btn"
415
429
  :title="isRefExpanded(entry.id, k) ? 'Collapse' : 'Expand'"
416
430
  @click.stop="toggleRefExpand(entry.id, k)"
417
431
  >
@@ -421,7 +435,7 @@ function applyEdit() {
421
435
  <span v-if="entry.sharedKeys?.includes(k)" class="badge badge-amber text-xs">global</span>
422
436
  <button
423
437
  v-if="v.type === 'ref'"
424
- class="edit-btn"
438
+ class="composable-tracker__edit-btn"
425
439
  title="Edit value"
426
440
  @click.stop="openEdit(entry.id, k, v.value)"
427
441
  >
@@ -431,103 +445,127 @@ function applyEdit() {
431
445
  </div>
432
446
 
433
447
  <template v-if="entry.history && entry.history.length">
434
- <div class="section-label" style="margin-top: 10px">
448
+ <div class="composable-tracker__section-label composable-tracker__section-label--spaced tracker-section-label">
435
449
  change history
436
- <span class="muted" style="font-weight: 400; text-transform: none; letter-spacing: 0">
437
- ({{ entry.history.length }} events)
438
- </span>
450
+ <span class="composable-tracker__section-label-meta muted">({{ entry.history.length }} events)</span>
439
451
  </div>
440
- <div class="history-list">
441
- <div v-for="(evt, idx) in [...entry.history].reverse().slice(0, 20)" :key="idx" class="history-row">
442
- <span class="history-time mono muted">+{{ (evt.t / 1000).toFixed(2) }}s</span>
443
- <span class="history-key mono">{{ evt.key }}</span>
444
- <span class="history-val mono">{{ formatVal(evt.value) }}</span>
452
+ <div class="composable-tracker__history-list">
453
+ <div
454
+ v-for="(evt, idx) in [...entry.history].reverse().slice(0, 20)"
455
+ :key="idx"
456
+ class="composable-tracker__history-row"
457
+ >
458
+ <span class="composable-tracker__history-time mono muted">+{{ (evt.t / 1000).toFixed(2) }}s</span>
459
+ <span class="composable-tracker__history-key mono">{{ evt.key }}</span>
460
+ <span class="composable-tracker__history-val mono">{{ formatVal(evt.value) }}</span>
445
461
  </div>
446
- <div v-if="entry.history.length > 20" class="muted text-sm" style="padding: 2px 0">
462
+ <div v-if="entry.history.length > 20" class="composable-tracker__compact-muted muted text-sm">
447
463
  … {{ entry.history.length - 20 }} earlier events
448
464
  </div>
449
465
  </div>
450
466
  </template>
451
467
 
452
- <div class="section-label" style="margin-top: 10px">lifecycle</div>
453
- <div v-for="row in lifecycleRows(entry)" :key="row.label" class="lc-row">
454
- <span class="lc-dot" :style="{ background: row.ok ? 'var(--teal)' : 'var(--red)' }"></span>
455
- <span class="muted text-sm" style="min-width: 120px">{{ row.label }}</span>
456
- <span class="text-sm" :style="{ color: row.ok ? 'var(--teal)' : 'var(--red)' }">{{ row.status }}</span>
468
+ <div class="composable-tracker__section-label composable-tracker__section-label--spaced tracker-section-label">
469
+ lifecycle
470
+ </div>
471
+ <div v-for="row in lifecycleRows(entry)" :key="row.label" class="composable-tracker__lifecycle-row">
472
+ <span
473
+ class="composable-tracker__lifecycle-dot"
474
+ :class="row.ok ? 'composable-tracker__lifecycle-dot--ok' : 'composable-tracker__lifecycle-dot--error'"
475
+ ></span>
476
+ <span class="composable-tracker__context-label muted text-sm">{{ row.label }}</span>
477
+ <span
478
+ class="composable-tracker__lifecycle-status text-sm"
479
+ :class="row.ok ? 'composable-tracker__lifecycle-status--ok' : 'composable-tracker__lifecycle-status--error'"
480
+ >
481
+ {{ row.status }}
482
+ </span>
457
483
  </div>
458
484
 
459
- <div class="section-label" style="margin-top: 10px">context</div>
460
- <div class="lc-row">
461
- <span class="muted text-sm" style="min-width: 120px">component</span>
462
- <span class="mono text-sm">{{ basename(entry.componentFile) }}</span>
485
+ <div class="composable-tracker__section-label composable-tracker__section-label--spaced tracker-section-label">
486
+ context
487
+ </div>
488
+ <div class="composable-tracker__lifecycle-row">
489
+ <span class="composable-tracker__context-label muted text-sm">component</span>
490
+ <span class="composable-tracker__context-value mono text-sm">{{ basename(entry.componentFile) }}</span>
463
491
  </div>
464
- <div class="lc-row">
465
- <span class="muted text-sm" style="min-width: 120px">uid</span>
466
- <span class="mono text-sm muted">{{ entry.componentUid }}</span>
492
+ <div class="composable-tracker__lifecycle-row">
493
+ <span class="composable-tracker__context-label muted text-sm">uid</span>
494
+ <span class="composable-tracker__context-value composable-tracker__context-value--muted mono text-sm muted">
495
+ {{ entry.componentUid }}
496
+ </span>
467
497
  </div>
468
- <div class="lc-row">
469
- <span class="muted text-sm" style="min-width: 120px">defined in</span>
470
- <span class="mono text-sm muted" style="display: flex; align-items: center; gap: 6px">
498
+ <div class="composable-tracker__lifecycle-row">
499
+ <span class="composable-tracker__context-label muted text-sm">defined in</span>
500
+ <span
501
+ class="composable-tracker__context-value composable-tracker__context-value--row composable-tracker__context-value--muted mono text-sm muted"
502
+ >
471
503
  {{ entry.file }}:{{ entry.line }}
472
- <button class="jump-btn" title="Open in editor" @click.stop="openInEditor(entry.file)">open ↗</button>
504
+ <button class="composable-tracker__jump-btn" title="Open in editor" @click.stop="openInEditor(entry.file)">
505
+ open ↗
506
+ </button>
473
507
  </span>
474
508
  </div>
475
- <div class="lc-row">
476
- <span class="muted text-sm" style="min-width: 120px">route</span>
477
- <span class="mono text-sm muted">{{ entry.route }}</span>
509
+ <div class="composable-tracker__lifecycle-row">
510
+ <span class="composable-tracker__context-label muted text-sm">route</span>
511
+ <span class="composable-tracker__context-value composable-tracker__context-value--muted mono text-sm muted">
512
+ {{ entry.route }}
513
+ </span>
478
514
  </div>
479
- <div class="lc-row">
480
- <span class="muted text-sm" style="min-width: 120px">watchers</span>
481
- <span class="mono text-sm">{{ entry.watcherCount }}</span>
515
+ <div class="composable-tracker__lifecycle-row">
516
+ <span class="composable-tracker__context-label muted text-sm">watchers</span>
517
+ <span class="composable-tracker__context-value mono text-sm">{{ entry.watcherCount }}</span>
482
518
  </div>
483
- <div class="lc-row">
484
- <span class="muted text-sm" style="min-width: 120px">intervals</span>
485
- <span class="mono text-sm">{{ entry.intervalCount }}</span>
519
+ <div class="composable-tracker__lifecycle-row">
520
+ <span class="composable-tracker__context-label muted text-sm">intervals</span>
521
+ <span class="composable-tracker__context-value mono text-sm">{{ entry.intervalCount }}</span>
486
522
  </div>
487
523
  </div>
488
524
  </div>
489
525
 
490
- <div v-if="!filtered.length" class="muted text-sm" style="padding: 16px 0">
526
+ <div v-if="!filtered.length" class="composable-tracker__empty muted text-sm">
491
527
  {{ connected ? 'No composables recorded yet.' : 'Waiting for connection to the Nuxt app…' }}
492
528
  </div>
493
529
  </div>
494
530
 
495
531
  <!-- ── Reverse lookup panel ──────────────────────────────────────── -->
496
532
  <Transition name="slide">
497
- <div v-if="lookupKey" class="lookup-panel">
498
- <div class="lookup-header">
533
+ <div v-if="lookupKey" class="composable-tracker__lookup-panel">
534
+ <div class="composable-tracker__lookup-header">
499
535
  <span class="mono text-sm">{{ lookupKey }}</span>
500
536
  <span class="muted text-sm">— {{ lookupResults.length }} instance{{ lookupResults.length !== 1 ? 's' : '' }}</span>
501
- <button class="clear-btn" style="margin-left: auto" @click="lookupKey = null">✕</button>
537
+ <button class="composable-tracker__clear-btn composable-tracker__lookup-close" @click="lookupKey = null">✕</button>
538
+ </div>
539
+ <div v-if="!lookupResults.length" class="composable-tracker__lookup-empty muted text-sm">
540
+ No mounted instances expose this key.
502
541
  </div>
503
- <div v-if="!lookupResults.length" class="muted text-sm" style="padding: 6px 0">No mounted instances expose this key.</div>
504
- <div v-for="r in lookupResults" :key="r.id" class="lookup-row">
542
+ <div v-for="r in lookupResults" :key="r.id" class="composable-tracker__lookup-row">
505
543
  <span class="mono text-sm">{{ r.name }}</span>
506
544
  <span class="muted text-sm">{{ basename(r.componentFile) }}</span>
507
- <span class="muted text-sm" style="margin-left: auto">{{ r.route }}</span>
545
+ <span class="composable-tracker__lookup-route muted text-sm">{{ r.route }}</span>
508
546
  </div>
509
547
  </div>
510
548
  </Transition>
511
549
 
512
550
  <!-- ── Edit value dialog ─────────────────────────────────────────── -->
513
551
  <Transition name="fade">
514
- <div v-if="editTarget" class="edit-overlay" @click.self="editTarget = null">
515
- <div class="edit-dialog">
516
- <div class="edit-dialog-header">
552
+ <div v-if="editTarget" class="composable-tracker__edit-overlay" @click.self="editTarget = null">
553
+ <div class="composable-tracker__edit-dialog">
554
+ <div class="composable-tracker__edit-dialog-header">
517
555
  edit
518
556
  <span class="mono">{{ editTarget.key }}</span>
519
- <button class="clear-btn" style="margin-left: auto" @click="editTarget = null">✕</button>
557
+ <button class="composable-tracker__clear-btn composable-tracker__dialog-close" @click="editTarget = null">✕</button>
520
558
  </div>
521
- <p class="muted text-sm" style="padding: 4px 0 8px">
559
+ <p class="composable-tracker__edit-help muted text-sm">
522
560
  Value applied immediately to the live ref. Only
523
561
  <span class="mono">ref</span>
524
562
  values are writable.
525
563
  </p>
526
- <textarea v-model="editTarget.rawValue" class="edit-textarea" rows="6" spellcheck="false" />
527
- <div v-if="editError" class="edit-error text-sm">{{ editError }}</div>
528
- <div class="edit-actions">
564
+ <textarea v-model="editTarget.rawValue" class="composable-tracker__edit-textarea" rows="6" spellcheck="false" />
565
+ <div v-if="editError" class="composable-tracker__edit-error text-sm">{{ editError }}</div>
566
+ <div class="composable-tracker__edit-actions">
529
567
  <button @click="applyEdit">apply</button>
530
- <button class="clear-btn" @click="editTarget = null">cancel</button>
568
+ <button class="composable-tracker__clear-btn" @click="editTarget = null">cancel</button>
531
569
  </div>
532
570
  </div>
533
571
  </div>
@@ -536,239 +574,222 @@ function applyEdit() {
536
574
  </template>
537
575
 
538
576
  <style scoped>
539
- .view {
540
- display: flex;
541
- flex-direction: column;
542
- height: 100%;
543
- overflow: hidden;
544
- padding: 12px;
545
- gap: 10px;
546
- }
547
-
548
- .stats-row {
549
- display: grid;
550
- grid-template-columns: repeat(4, minmax(0, 1fr));
551
- gap: 8px;
552
- flex-shrink: 0;
553
- }
554
-
555
- .toolbar {
556
- display: flex;
557
- align-items: center;
558
- gap: 6px;
559
- flex-shrink: 0;
560
- flex-wrap: wrap;
561
- }
562
-
563
- .clear-btn {
577
+ .composable-tracker__clear-btn {
564
578
  color: var(--text3);
565
579
  border-color: var(--border);
566
580
  flex-shrink: 0;
567
581
  }
568
582
 
569
- .clear-btn:hover {
583
+ .composable-tracker__clear-btn:hover {
570
584
  color: var(--red);
571
585
  border-color: var(--red);
572
586
  background: transparent;
573
587
  }
574
588
 
575
- .mode-btn {
589
+ .composable-tracker__mode-btn {
576
590
  border-color: color-mix(in srgb, var(--blue) 40%, var(--border));
577
591
  color: var(--blue);
578
592
  }
579
593
 
580
- .mode-btn:hover {
594
+ .composable-tracker__mode-btn:hover {
581
595
  border-color: var(--blue);
582
596
  background: color-mix(in srgb, var(--blue) 12%, transparent);
583
597
  }
584
598
 
585
- .list {
599
+ .composable-tracker__search {
600
+ max-width: 220px;
601
+ }
602
+
603
+ .composable-tracker__list {
586
604
  flex: 1;
587
605
  overflow: auto;
588
606
  display: flex;
589
607
  flex-direction: column;
590
- gap: 5px;
608
+ gap: var(--tracker-space-2);
591
609
  min-height: 0;
592
610
  }
593
611
 
594
- .comp-card {
612
+ .composable-tracker__card {
595
613
  background: var(--bg3);
596
- border: 0.5px solid var(--border);
614
+ border: var(--tracker-border-width) solid var(--border);
597
615
  border-radius: var(--radius-lg);
598
616
  overflow: hidden;
599
617
  cursor: pointer;
600
618
  flex-shrink: 0;
601
619
  }
602
620
 
603
- .comp-card:hover {
621
+ .composable-tracker__card:hover {
604
622
  border-color: var(--text3);
605
623
  }
606
624
 
607
- .comp-card.leak {
625
+ .composable-tracker__card--leak {
608
626
  border-left: 2px solid var(--red);
609
627
  border-radius: 0 var(--radius-lg) var(--radius-lg) 0;
610
628
  }
611
629
 
612
- .comp-card.expanded {
630
+ .composable-tracker__card--expanded {
613
631
  border-color: var(--purple);
614
632
  }
615
633
 
616
- .comp-header {
634
+ .composable-tracker__card-header {
617
635
  display: flex;
618
636
  align-items: center;
619
637
  justify-content: space-between;
620
- padding: 8px 12px;
621
- gap: 8px;
638
+ padding: var(--tracker-space-3) var(--tracker-space-4);
639
+ gap: var(--tracker-space-3);
622
640
  }
623
641
 
624
- .comp-identity {
642
+ .composable-tracker__identity {
625
643
  display: flex;
626
644
  align-items: baseline;
627
- gap: 6px;
645
+ gap: var(--tracker-space-2);
628
646
  min-width: 0;
629
647
  flex: 1;
630
648
  }
631
649
 
632
- .comp-name {
633
- font-size: 12px;
650
+ .composable-tracker__name {
651
+ font-size: var(--tracker-font-size-md);
634
652
  font-weight: 500;
635
653
  color: var(--text);
636
654
  flex-shrink: 0;
637
655
  }
638
656
 
639
- .comp-file {
640
- font-size: 11px;
657
+ .composable-tracker__file {
658
+ font-size: var(--tracker-font-size-sm);
641
659
  color: var(--text3);
642
660
  overflow: hidden;
643
661
  text-overflow: ellipsis;
644
662
  white-space: nowrap;
645
663
  }
646
664
 
647
- .comp-meta {
665
+ .composable-tracker__meta {
648
666
  display: flex;
649
667
  align-items: center;
650
- gap: 5px;
668
+ gap: var(--tracker-space-2);
651
669
  flex-shrink: 0;
652
670
  }
653
671
 
654
- /* Inline ref preview chips */
655
- .refs-preview {
672
+ .composable-tracker__refs-preview {
656
673
  display: flex;
657
674
  flex-wrap: wrap;
658
- gap: 4px;
659
- padding: 0 12px 8px;
675
+ gap: var(--tracker-space-1);
676
+ padding: 0 var(--tracker-space-4) var(--tracker-space-3);
660
677
  align-items: center;
661
678
  }
662
679
 
663
- .ref-chip {
680
+ .composable-tracker__ref-chip {
664
681
  display: inline-flex;
665
682
  align-items: center;
666
- gap: 4px;
683
+ gap: var(--tracker-space-1);
667
684
  padding: 2px 7px;
668
685
  border-radius: 4px;
669
686
  background: var(--bg2);
670
- border: 0.5px solid var(--border);
671
- font-size: 11px;
687
+ border: var(--tracker-border-width) solid var(--border);
688
+ font-size: var(--tracker-font-size-sm);
672
689
  font-family: var(--mono);
673
690
  max-width: 220px;
674
691
  overflow: hidden;
675
692
  }
676
693
 
677
- .ref-chip--reactive {
694
+ .composable-tracker__ref-chip--reactive {
678
695
  border-color: color-mix(in srgb, var(--purple) 40%, var(--border));
679
696
  background: color-mix(in srgb, var(--purple) 8%, var(--bg2));
680
697
  }
681
698
 
682
- .ref-chip--computed {
699
+ .composable-tracker__ref-chip--computed {
683
700
  border-color: color-mix(in srgb, var(--blue) 40%, var(--border));
684
701
  background: color-mix(in srgb, var(--blue) 8%, var(--bg2));
685
702
  }
686
703
 
687
- .ref-chip-key {
704
+ .composable-tracker__ref-chip-key {
688
705
  color: var(--text2);
689
706
  flex-shrink: 0;
690
707
  }
691
708
 
692
- .ref-chip-val {
709
+ .composable-tracker__ref-chip-val {
693
710
  color: var(--teal);
694
711
  overflow: hidden;
695
712
  text-overflow: ellipsis;
696
713
  white-space: nowrap;
697
714
  }
698
715
 
699
- /* Expanded detail */
700
- .comp-detail {
701
- padding: 4px 12px 12px;
702
- border-top: 0.5px solid var(--border);
716
+ .composable-tracker__detail {
717
+ padding: var(--tracker-space-1) var(--tracker-space-4) var(--tracker-space-4);
718
+ border-top: var(--tracker-border-width) solid var(--border);
703
719
  display: flex;
704
720
  flex-direction: column;
705
- gap: 3px;
721
+ gap: var(--tracker-space-1);
706
722
  }
707
723
 
708
- .leak-banner {
724
+ .composable-tracker__leak-banner {
709
725
  background: color-mix(in srgb, var(--red) 12%, transparent);
710
- border: 0.5px solid color-mix(in srgb, var(--red) 40%, var(--border));
726
+ border: var(--tracker-border-width) solid color-mix(in srgb, var(--red) 40%, var(--border));
711
727
  border-radius: var(--radius);
712
728
  padding: 6px 10px;
713
- font-size: 11px;
729
+ font-size: var(--tracker-font-size-sm);
714
730
  color: var(--red);
715
- margin-bottom: 6px;
731
+ margin-bottom: var(--tracker-space-2);
716
732
  font-family: var(--mono);
717
733
  }
718
734
 
719
- .section-label {
720
- font-size: 10px;
721
- font-weight: 500;
722
- text-transform: uppercase;
723
- letter-spacing: 0.4px;
724
- color: var(--text3);
725
- margin-top: 6px;
726
- margin-bottom: 3px;
735
+ .composable-tracker__section-label {
736
+ margin-top: var(--tracker-space-2);
737
+ margin-bottom: var(--tracker-space-1);
727
738
  }
728
739
 
729
- .ref-row {
740
+ .composable-tracker__section-label--spaced {
741
+ margin-top: var(--tracker-space-4);
742
+ }
743
+
744
+ .composable-tracker__section-label-meta {
745
+ font-weight: 400;
746
+ text-transform: none;
747
+ letter-spacing: 0;
748
+ }
749
+
750
+ .composable-tracker__ref-row {
730
751
  display: flex;
731
752
  align-items: flex-start;
732
- gap: 8px;
753
+ gap: var(--tracker-space-3);
733
754
  padding: 3px 0;
734
755
  }
735
756
 
736
- .ref-key {
757
+ .composable-tracker__ref-key {
737
758
  min-width: 90px;
738
759
  color: var(--text2);
739
760
  flex-shrink: 0;
740
761
  }
741
762
 
742
- .ref-val {
763
+ .composable-tracker__ref-val {
743
764
  flex: 1;
744
765
  color: var(--teal);
745
766
  min-width: 0;
746
767
  }
747
768
 
748
- .ref-val--collapsed {
769
+ .composable-tracker__ref-val--collapsed {
749
770
  overflow: hidden;
750
771
  text-overflow: ellipsis;
751
772
  white-space: nowrap;
752
773
  }
753
774
 
754
- .ref-val--full {
775
+ .composable-tracker__ref-val--full {
755
776
  white-space: pre-wrap;
756
777
  word-break: break-all;
757
778
  line-height: 1.5;
758
779
  }
759
780
 
760
- .ref-row-actions {
781
+ .composable-tracker__ref-row-actions {
761
782
  display: flex;
762
783
  align-items: center;
763
- gap: 4px;
784
+ gap: var(--tracker-space-1);
764
785
  flex-shrink: 0;
765
786
  }
766
787
 
767
- .expand-btn {
768
- font-size: 9px;
788
+ .composable-tracker__expand-btn {
789
+ font-size: var(--tracker-font-size-xs);
769
790
  padding: 1px 5px;
770
791
  border-radius: 4px;
771
- border: 0.5px solid var(--border);
792
+ border: var(--tracker-border-width) solid var(--border);
772
793
  background: var(--bg2);
773
794
  color: var(--text3);
774
795
  cursor: pointer;
@@ -776,31 +797,66 @@ function applyEdit() {
776
797
  flex-shrink: 0;
777
798
  }
778
799
 
779
- .expand-btn:hover {
800
+ .composable-tracker__expand-btn:hover {
780
801
  border-color: var(--text3);
781
802
  color: var(--text);
782
803
  }
783
804
 
784
- .lc-row {
805
+ .composable-tracker__lifecycle-row {
785
806
  display: flex;
786
807
  align-items: center;
787
- gap: 8px;
808
+ gap: var(--tracker-space-3);
788
809
  padding: 2px 0;
789
810
  }
790
811
 
791
- .lc-dot {
812
+ .composable-tracker__lifecycle-dot {
792
813
  width: 6px;
793
814
  height: 6px;
794
815
  border-radius: 50%;
795
816
  flex-shrink: 0;
796
817
  }
797
818
 
798
- .ref-chip--shared {
819
+ .composable-tracker__lifecycle-dot--ok {
820
+ background: var(--teal);
821
+ }
822
+
823
+ .composable-tracker__lifecycle-dot--error {
824
+ background: var(--red);
825
+ }
826
+
827
+ .composable-tracker__lifecycle-status--ok {
828
+ color: var(--teal);
829
+ }
830
+
831
+ .composable-tracker__lifecycle-status--error {
832
+ color: var(--red);
833
+ }
834
+
835
+ .composable-tracker__context-label {
836
+ min-width: 120px;
837
+ }
838
+
839
+ .composable-tracker__context-value {
840
+ min-width: 0;
841
+ }
842
+
843
+ .composable-tracker__context-value--row {
844
+ display: flex;
845
+ align-items: center;
846
+ gap: var(--tracker-space-2);
847
+ flex-wrap: wrap;
848
+ }
849
+
850
+ .composable-tracker__context-value--muted {
851
+ color: var(--text3);
852
+ }
853
+
854
+ .composable-tracker__ref-chip--shared {
799
855
  border-color: color-mix(in srgb, var(--amber) 50%, var(--border));
800
856
  background: color-mix(in srgb, var(--amber) 10%, var(--bg2));
801
857
  }
802
858
 
803
- .ref-chip-shared-dot {
859
+ .composable-tracker__ref-chip-shared-dot {
804
860
  display: inline-block;
805
861
  width: 5px;
806
862
  height: 5px;
@@ -810,20 +866,20 @@ function applyEdit() {
810
866
  margin-left: 1px;
811
867
  }
812
868
 
813
- .global-banner {
869
+ .composable-tracker__global-banner {
814
870
  display: flex;
815
871
  align-items: flex-start;
816
- gap: 8px;
872
+ gap: var(--tracker-space-3);
817
873
  background: color-mix(in srgb, var(--amber) 10%, transparent);
818
- border: 0.5px solid color-mix(in srgb, var(--amber) 40%, var(--border));
874
+ border: var(--tracker-border-width) solid color-mix(in srgb, var(--amber) 40%, var(--border));
819
875
  border-radius: var(--radius);
820
876
  padding: 7px 10px;
821
- font-size: 11px;
877
+ font-size: var(--tracker-font-size-sm);
822
878
  color: var(--text2);
823
- margin-bottom: 6px;
879
+ margin-bottom: var(--tracker-space-2);
824
880
  }
825
881
 
826
- .global-dot {
882
+ .composable-tracker__global-dot {
827
883
  display: inline-block;
828
884
  width: 6px;
829
885
  height: 6px;
@@ -839,7 +895,7 @@ function applyEdit() {
839
895
  border: 0.5px solid color-mix(in srgb, var(--amber) 40%, var(--border));
840
896
  }
841
897
 
842
- .history-list {
898
+ .composable-tracker__history-list {
843
899
  display: flex;
844
900
  flex-direction: column;
845
901
  gap: 1px;
@@ -850,33 +906,33 @@ function applyEdit() {
850
906
  overflow-y: auto;
851
907
  }
852
908
 
853
- .history-row {
909
+ .composable-tracker__history-row {
854
910
  display: flex;
855
911
  align-items: center;
856
- gap: 8px;
912
+ gap: var(--tracker-space-3);
857
913
  padding: 2px 0;
858
- font-size: 11px;
914
+ font-size: var(--tracker-font-size-sm);
859
915
  font-family: var(--mono);
860
- border-bottom: 0.5px solid var(--border);
916
+ border-bottom: var(--tracker-border-width) solid var(--border);
861
917
  }
862
918
 
863
- .history-row:last-child {
919
+ .composable-tracker__history-row:last-child {
864
920
  border-bottom: none;
865
921
  }
866
922
 
867
- .history-time {
923
+ .composable-tracker__history-time {
868
924
  min-width: 52px;
869
925
  color: var(--text3);
870
926
  flex-shrink: 0;
871
927
  }
872
928
 
873
- .history-key {
929
+ .composable-tracker__history-key {
874
930
  min-width: 80px;
875
931
  color: var(--text2);
876
932
  flex-shrink: 0;
877
933
  }
878
934
 
879
- .history-val {
935
+ .composable-tracker__history-val {
880
936
  color: var(--amber);
881
937
  overflow: hidden;
882
938
  text-overflow: ellipsis;
@@ -884,48 +940,22 @@ function applyEdit() {
884
940
  flex: 1;
885
941
  }
886
942
 
887
- /* Stat cards */
888
- .stat-card {
889
- background: var(--bg3);
890
- border: 0.5px solid var(--border);
891
- border-radius: var(--radius-lg);
892
- padding: 10px 14px;
893
- }
894
-
895
- .stat-label {
896
- font-size: 10px;
897
- font-weight: 500;
898
- text-transform: uppercase;
899
- letter-spacing: 0.4px;
900
- color: var(--text3);
901
- margin-bottom: 4px;
902
- }
903
-
904
- .stat-val {
905
- font-size: 22px;
906
- font-weight: 500;
907
- line-height: 1;
908
- color: var(--text);
909
- }
910
-
911
- /* Clickable ref key */
912
- .ref-key--clickable {
943
+ .composable-tracker__ref-key--clickable {
913
944
  cursor: pointer;
914
945
  text-decoration: underline dotted var(--text3);
915
946
  text-underline-offset: 2px;
916
947
  }
917
948
 
918
- .ref-key--clickable:hover {
949
+ .composable-tracker__ref-key--clickable:hover {
919
950
  color: var(--purple);
920
951
  text-decoration-color: var(--purple);
921
952
  }
922
953
 
923
- /* Edit button inline with ref row */
924
- .edit-btn {
925
- font-size: 10px;
954
+ .composable-tracker__edit-btn {
955
+ font-size: var(--tracker-font-size-xs);
926
956
  padding: 1px 6px;
927
957
  border-radius: var(--radius);
928
- border: 0.5px solid var(--border);
958
+ border: var(--tracker-border-width) solid var(--border);
929
959
  background: transparent;
930
960
  color: var(--text3);
931
961
  cursor: pointer;
@@ -934,44 +964,60 @@ function applyEdit() {
934
964
  font-family: var(--mono);
935
965
  }
936
966
 
937
- .edit-btn:hover {
967
+ .composable-tracker__edit-btn:hover {
938
968
  border-color: var(--purple);
939
969
  color: var(--purple);
940
970
  background: color-mix(in srgb, var(--purple) 8%, transparent);
941
971
  }
942
972
 
943
- /* Reverse lookup panel — appears below the list */
944
- .lookup-panel {
973
+ .composable-tracker__empty {
974
+ padding: 16px 0;
975
+ }
976
+
977
+ .composable-tracker__compact-muted {
978
+ padding: 2px 0 6px;
979
+ }
980
+
981
+ .composable-tracker__lookup-panel {
945
982
  flex-shrink: 0;
946
- border: 0.5px solid var(--border);
983
+ border: var(--tracker-border-width) solid var(--border);
947
984
  border-radius: var(--radius-lg);
948
985
  background: var(--bg3);
949
986
  overflow: hidden;
950
987
  }
951
988
 
952
- .lookup-header {
989
+ .composable-tracker__lookup-header {
953
990
  display: flex;
954
991
  align-items: center;
955
- gap: 6px;
992
+ gap: var(--tracker-space-2);
956
993
  padding: 7px 12px;
957
- border-bottom: 0.5px solid var(--border);
994
+ border-bottom: var(--tracker-border-width) solid var(--border);
958
995
  background: var(--bg2);
959
996
  }
960
997
 
961
- .lookup-row {
998
+ .composable-tracker__lookup-close,
999
+ .composable-tracker__dialog-close,
1000
+ .composable-tracker__lookup-route {
1001
+ margin-left: auto;
1002
+ }
1003
+
1004
+ .composable-tracker__lookup-empty {
1005
+ padding: 6px 12px;
1006
+ }
1007
+
1008
+ .composable-tracker__lookup-row {
962
1009
  display: flex;
963
1010
  align-items: center;
964
- gap: 8px;
1011
+ gap: var(--tracker-space-3);
965
1012
  padding: 5px 12px;
966
- border-bottom: 0.5px solid var(--border);
1013
+ border-bottom: var(--tracker-border-width) solid var(--border);
967
1014
  }
968
1015
 
969
- .lookup-row:last-child {
1016
+ .composable-tracker__lookup-row:last-child {
970
1017
  border-bottom: none;
971
1018
  }
972
1019
 
973
- /* Edit overlay + dialog */
974
- .edit-overlay {
1020
+ .composable-tracker__edit-overlay {
975
1021
  position: fixed;
976
1022
  inset: 0;
977
1023
  background: rgb(0 0 0 / 40%);
@@ -981,9 +1027,9 @@ function applyEdit() {
981
1027
  justify-content: center;
982
1028
  }
983
1029
 
984
- .edit-dialog {
1030
+ .composable-tracker__edit-dialog {
985
1031
  background: var(--bg1, var(--bg2));
986
- border: 0.5px solid var(--border);
1032
+ border: var(--tracker-border-width) solid var(--border);
987
1033
  border-radius: var(--radius-lg);
988
1034
  padding: 14px 16px;
989
1035
  width: 380px;
@@ -994,44 +1040,47 @@ function applyEdit() {
994
1040
  box-shadow: 0 8px 32px rgb(0 0 0 / 30%);
995
1041
  }
996
1042
 
997
- .edit-dialog-header {
1043
+ .composable-tracker__edit-dialog-header {
998
1044
  display: flex;
999
1045
  align-items: center;
1000
- gap: 6px;
1001
- font-size: 12px;
1046
+ gap: var(--tracker-space-2);
1047
+ font-size: var(--tracker-font-size-md);
1002
1048
  color: var(--text2);
1003
1049
  margin-bottom: 2px;
1004
1050
  }
1005
1051
 
1006
- .edit-textarea {
1052
+ .composable-tracker__edit-help {
1053
+ padding: 4px 0 8px;
1054
+ }
1055
+
1056
+ .composable-tracker__edit-textarea {
1007
1057
  width: 100%;
1008
1058
  font-family: var(--mono);
1009
- font-size: 12px;
1059
+ font-size: var(--tracker-font-size-md);
1010
1060
  padding: 8px 10px;
1011
1061
  background: var(--bg2);
1012
- border: 0.5px solid var(--border);
1062
+ border: var(--tracker-border-width) solid var(--border);
1013
1063
  border-radius: var(--radius);
1014
1064
  color: var(--text);
1015
1065
  resize: vertical;
1016
1066
  outline: none;
1017
1067
  }
1018
1068
 
1019
- .edit-textarea:focus {
1069
+ .composable-tracker__edit-textarea:focus {
1020
1070
  border-color: var(--purple);
1021
1071
  }
1022
1072
 
1023
- .edit-error {
1073
+ .composable-tracker__edit-error {
1024
1074
  color: var(--red);
1025
1075
  font-family: var(--mono);
1026
1076
  }
1027
1077
 
1028
- .edit-actions {
1078
+ .composable-tracker__edit-actions {
1029
1079
  display: flex;
1030
- gap: 6px;
1080
+ gap: var(--tracker-space-2);
1031
1081
  padding-top: 4px;
1032
1082
  }
1033
1083
 
1034
- /* Slide / fade transitions for the panels */
1035
1084
  .slide-enter-active,
1036
1085
  .slide-leave-active {
1037
1086
  transition:
@@ -1055,10 +1104,10 @@ function applyEdit() {
1055
1104
  opacity: 0;
1056
1105
  }
1057
1106
 
1058
- .jump-btn {
1059
- font-size: 10px;
1107
+ .composable-tracker__jump-btn {
1108
+ font-size: var(--tracker-font-size-xs);
1060
1109
  padding: 1px 6px;
1061
- border: 0.5px solid var(--border);
1110
+ border: var(--tracker-border-width) solid var(--border);
1062
1111
  border-radius: var(--radius);
1063
1112
  background: transparent;
1064
1113
  color: var(--text3);
@@ -1067,9 +1116,25 @@ function applyEdit() {
1067
1116
  font-family: var(--mono);
1068
1117
  }
1069
1118
 
1070
- .jump-btn:hover {
1119
+ .composable-tracker__jump-btn:hover {
1071
1120
  border-color: var(--teal);
1072
1121
  color: var(--teal);
1073
1122
  background: color-mix(in srgb, var(--teal) 8%, transparent);
1074
1123
  }
1124
+
1125
+ @media (width <= 900px) {
1126
+ .composable-tracker__card-header,
1127
+ .composable-tracker__ref-row,
1128
+ .composable-tracker__lifecycle-row,
1129
+ .composable-tracker__lookup-row {
1130
+ flex-wrap: wrap;
1131
+ }
1132
+
1133
+ .composable-tracker__identity,
1134
+ .composable-tracker__context-label,
1135
+ .composable-tracker__lookup-route {
1136
+ min-width: 100%;
1137
+ margin-left: 0;
1138
+ }
1139
+ }
1075
1140
  </style>