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,9 +1,11 @@
1
1
  <script setup lang="ts">
2
2
  import { ref, computed } from 'vue'
3
- import { useObservatoryData } from '../stores/observatory'
4
- import type { TransitionEntry } from '../../../src/types/snapshot'
3
+ import { useResizablePane } from '@observatory-client/composables/useResizablePane'
4
+ import { useObservatoryData } from '@observatory-client/stores/observatory'
5
+ import type { TransitionEntry } from '@observatory/types/snapshot'
5
6
 
6
7
  const { transitions: entries, connected } = useObservatoryData()
8
+ const { paneWidth: detailWidth, onHandleMouseDown } = useResizablePane(260, 'observatory:transitions:detailWidth')
7
9
 
8
10
  type FilterMode = 'all' | 'cancelled' | 'active' | 'completed'
9
11
  const filter = ref<FilterMode>('all')
@@ -129,19 +131,19 @@ function directionColor(e: TransitionEntry): string {
129
131
  </script>
130
132
 
131
133
  <template>
132
- <div class="timeline-root">
134
+ <div class="transition-timeline tracker-view">
133
135
  <!-- Stats bar -->
134
- <div class="stats-row">
136
+ <div class="transition-timeline__stats tracker-stats-row">
135
137
  <div class="stat-card">
136
138
  <div class="stat-val">{{ stats.total }}</div>
137
139
  <div class="stat-label">total</div>
138
140
  </div>
139
141
  <div class="stat-card">
140
- <div class="stat-val" style="color: var(--purple)">{{ stats.active }}</div>
142
+ <div class="stat-val stat-val--active">{{ stats.active }}</div>
141
143
  <div class="stat-label">active</div>
142
144
  </div>
143
145
  <div class="stat-card">
144
- <div class="stat-val" style="color: var(--red)">{{ stats.cancelled }}</div>
146
+ <div class="stat-val stat-val--error">{{ stats.cancelled }}</div>
145
147
  <div class="stat-label">cancelled</div>
146
148
  </div>
147
149
  <div class="stat-card">
@@ -154,9 +156,9 @@ function directionColor(e: TransitionEntry): string {
154
156
  </div>
155
157
 
156
158
  <!-- Toolbar -->
157
- <div class="toolbar">
158
- <input v-model="search" type="search" placeholder="filter by name or component…" class="search-input" />
159
- <div class="filter-group">
159
+ <div class="transition-timeline__toolbar tracker-toolbar">
160
+ <input v-model="search" type="search" placeholder="filter by name or component…" class="transition-timeline__search" />
161
+ <div class="transition-timeline__filters">
160
162
  <button :class="{ active: filter === 'all' }" @click="filter = 'all'">All</button>
161
163
  <button :class="{ active: filter === 'active' }" @click="filter = 'active'">Active</button>
162
164
  <button :class="{ active: filter === 'completed' }" @click="filter = 'completed'">Completed</button>
@@ -167,16 +169,16 @@ function directionColor(e: TransitionEntry): string {
167
169
  </div>
168
170
 
169
171
  <!-- Main content -->
170
- <div class="content-area">
172
+ <div class="transition-timeline__content tracker-split">
171
173
  <!-- Timeline table -->
172
- <div class="table-pane" :class="{ 'has-panel': selected }">
174
+ <div class="transition-timeline__table tracker-table-wrap">
173
175
  <table class="data-table">
174
176
  <thead>
175
177
  <tr>
176
- <th style="width: 110px">NAME</th>
177
- <th style="width: 80px">DIR</th>
178
- <th style="width: 90px">PHASE</th>
179
- <th style="width: 70px">DURATION</th>
178
+ <th class="transition-timeline__col-name">NAME</th>
179
+ <th class="transition-timeline__col-dir">DIR</th>
180
+ <th class="transition-timeline__col-phase">PHASE</th>
181
+ <th class="transition-timeline__col-duration">DURATION</th>
180
182
  <th>COMPONENT</th>
181
183
  <th>TIMELINE</th>
182
184
  </tr>
@@ -189,24 +191,24 @@ function directionColor(e: TransitionEntry): string {
189
191
  @click="selected = selected?.id === entry.id ? null : entry"
190
192
  >
191
193
  <td>
192
- <span class="mono" style="font-size: 11px; font-weight: 500">{{ entry.transitionName }}</span>
194
+ <span class="transition-timeline__name mono">{{ entry.transitionName }}</span>
193
195
  </td>
194
196
  <td>
195
- <span class="mono" style="font-size: 11px" :style="{ color: directionColor(entry) }">
197
+ <span class="transition-timeline__direction mono" :style="{ color: directionColor(entry) }">
196
198
  {{ directionLabel(entry) }}
197
199
  </span>
198
200
  </td>
199
201
  <td>
200
202
  <span class="badge" :class="phaseBadgeClass(entry.phase)">{{ entry.phase }}</span>
201
203
  </td>
202
- <td class="mono" style="font-size: 11px; color: var(--text2)">
204
+ <td class="transition-timeline__duration mono">
203
205
  {{ entry.durationMs !== undefined ? entry.durationMs + 'ms' : '—' }}
204
206
  </td>
205
- <td class="muted" style="font-size: 11px">{{ entry.parentComponent }}</td>
206
- <td class="bar-cell">
207
- <div class="bar-track">
207
+ <td class="transition-timeline__component muted">{{ entry.parentComponent }}</td>
208
+ <td class="transition-timeline__bar-cell">
209
+ <div class="transition-timeline__bar-track">
208
210
  <div
209
- class="bar-fill"
211
+ class="transition-timeline__bar-fill"
210
212
  :style="{
211
213
  left: timelineGeometry[i]?.left + '%',
212
214
  width: Math.max(timelineGeometry[i]?.width ?? 1, 1) + '%',
@@ -219,7 +221,7 @@ function directionColor(e: TransitionEntry): string {
219
221
  </tr>
220
222
 
221
223
  <tr v-if="!filtered.length">
222
- <td colspan="6" style="text-align: center; color: var(--text3); padding: 24px">
224
+ <td colspan="6" class="tracker-empty-cell">
223
225
  {{
224
226
  connected
225
227
  ? 'No transitions recorded yet — trigger one on the page.'
@@ -233,46 +235,59 @@ function directionColor(e: TransitionEntry): string {
233
235
 
234
236
  <!-- Detail panel -->
235
237
  <transition name="panel-slide">
236
- <aside v-if="selected" class="detail-panel">
237
- <div class="panel-header">
238
- <span class="panel-title">{{ selected.transitionName }}</span>
239
- <button class="close-btn" @click="selected = null">✕</button>
238
+ <div v-if="selected" class="tracker-resize-handle" @mousedown="onHandleMouseDown" />
239
+ </transition>
240
+ <transition name="panel-slide">
241
+ <aside v-if="selected" class="transition-timeline__detail" :style="{ width: detailWidth + 'px' }">
242
+ <div class="transition-timeline__detail-header">
243
+ <span class="transition-timeline__detail-title">{{ selected.transitionName }}</span>
244
+ <button class="transition-timeline__close-btn" @click="selected = null">✕</button>
240
245
  </div>
241
246
 
242
- <div class="panel-section">
243
- <div class="panel-row">
244
- <span class="panel-key">Direction</span>
245
- <span class="panel-val" :style="{ color: directionColor(selected) }">{{ directionLabel(selected) }}</span>
247
+ <div class="transition-timeline__detail-section">
248
+ <div class="transition-timeline__detail-row">
249
+ <span class="transition-timeline__detail-key">Direction</span>
250
+ <span class="transition-timeline__detail-val" :style="{ color: directionColor(selected) }">
251
+ {{ directionLabel(selected) }}
252
+ </span>
246
253
  </div>
247
- <div class="panel-row">
248
- <span class="panel-key">Phase</span>
254
+ <div class="transition-timeline__detail-row">
255
+ <span class="transition-timeline__detail-key">Phase</span>
249
256
  <span class="badge" :class="phaseBadgeClass(selected.phase)">{{ selected.phase }}</span>
250
257
  </div>
251
- <div class="panel-row">
252
- <span class="panel-key">Component</span>
253
- <span class="panel-val mono">{{ selected.parentComponent }}</span>
258
+ <div class="transition-timeline__detail-row">
259
+ <span class="transition-timeline__detail-key">Component</span>
260
+ <span class="transition-timeline__detail-val transition-timeline__detail-val--mono mono">
261
+ {{ selected.parentComponent }}
262
+ </span>
254
263
  </div>
255
- <div v-if="selected.mode" class="panel-row">
256
- <span class="panel-key">Mode</span>
257
- <span class="panel-val mono">{{ selected.mode }}</span>
264
+ <div v-if="selected.mode" class="transition-timeline__detail-row">
265
+ <span class="transition-timeline__detail-key">Mode</span>
266
+ <span class="transition-timeline__detail-val transition-timeline__detail-val--mono mono">
267
+ {{ selected.mode }}
268
+ </span>
258
269
  </div>
259
270
  </div>
260
271
 
261
- <div class="panel-section">
262
- <div class="panel-section-title">Timing</div>
263
- <div class="panel-row">
264
- <span class="panel-key">Start</span>
265
- <span class="panel-val mono">{{ selected.startTime.toFixed(2) }}ms</span>
272
+ <div class="transition-timeline__detail-section">
273
+ <div class="tracker-section-label transition-timeline__section-title">Timing</div>
274
+ <div class="transition-timeline__detail-row">
275
+ <span class="transition-timeline__detail-key">Start</span>
276
+ <span class="transition-timeline__detail-val transition-timeline__detail-val--mono mono">
277
+ {{ selected.startTime.toFixed(2) }}ms
278
+ </span>
266
279
  </div>
267
- <div class="panel-row">
268
- <span class="panel-key">End</span>
269
- <span class="panel-val mono">
280
+ <div class="transition-timeline__detail-row">
281
+ <span class="transition-timeline__detail-key">End</span>
282
+ <span class="transition-timeline__detail-val transition-timeline__detail-val--mono mono">
270
283
  {{ selected.endTime !== undefined ? selected.endTime.toFixed(2) + 'ms' : '—' }}
271
284
  </span>
272
285
  </div>
273
- <div class="panel-row">
274
- <span class="panel-key">Duration</span>
275
- <span class="panel-val mono" style="font-weight: 500">
286
+ <div class="transition-timeline__detail-row">
287
+ <span class="transition-timeline__detail-key">Duration</span>
288
+ <span
289
+ class="transition-timeline__detail-val transition-timeline__detail-val--mono transition-timeline__detail-val--strong mono"
290
+ >
276
291
  {{
277
292
  selected.durationMs !== undefined
278
293
  ? selected.durationMs + 'ms'
@@ -284,23 +299,29 @@ function directionColor(e: TransitionEntry): string {
284
299
  </div>
285
300
  </div>
286
301
 
287
- <div class="panel-section">
288
- <div class="panel-section-title">Flags</div>
289
- <div class="panel-row">
290
- <span class="panel-key">Appear</span>
291
- <span class="panel-val" :style="{ color: selected.appear ? 'var(--amber)' : 'var(--text3)' }">
302
+ <div class="transition-timeline__detail-section">
303
+ <div class="tracker-section-label transition-timeline__section-title">Flags</div>
304
+ <div class="transition-timeline__detail-row">
305
+ <span class="transition-timeline__detail-key">Appear</span>
306
+ <span
307
+ class="transition-timeline__detail-val"
308
+ :style="{ color: selected.appear ? 'var(--amber)' : 'var(--text3)' }"
309
+ >
292
310
  {{ selected.appear ? 'yes' : 'no' }}
293
311
  </span>
294
312
  </div>
295
- <div class="panel-row">
296
- <span class="panel-key">Cancelled</span>
297
- <span class="panel-val" :style="{ color: selected.cancelled ? 'var(--red)' : 'var(--text3)' }">
313
+ <div class="transition-timeline__detail-row">
314
+ <span class="transition-timeline__detail-key">Cancelled</span>
315
+ <span
316
+ class="transition-timeline__detail-val"
317
+ :style="{ color: selected.cancelled ? 'var(--red)' : 'var(--text3)' }"
318
+ >
298
319
  {{ selected.cancelled ? 'yes' : 'no' }}
299
320
  </span>
300
321
  </div>
301
322
  </div>
302
323
 
303
- <div v-if="selected.cancelled" class="cancel-notice">
324
+ <div v-if="selected.cancelled" class="transition-timeline__notice transition-timeline__notice--cancelled">
304
325
  This transition was cancelled mid-flight. The element may be stuck in a partial animation state if the interruption
305
326
  was not handled with
306
327
  <code>onEnterCancelled</code>
@@ -309,7 +330,10 @@ function directionColor(e: TransitionEntry): string {
309
330
  .
310
331
  </div>
311
332
 
312
- <div v-if="selected.phase === 'entering' || selected.phase === 'leaving'" class="active-notice">
333
+ <div
334
+ v-if="selected.phase === 'entering' || selected.phase === 'leaving'"
335
+ class="transition-timeline__notice transition-timeline__notice--active"
336
+ >
313
337
  Transition is currently in progress. If it stays in this state longer than expected, the
314
338
  <code>done()</code>
315
339
  callback may not be getting called (JS-mode transition stall).
@@ -321,92 +345,65 @@ function directionColor(e: TransitionEntry): string {
321
345
  </template>
322
346
 
323
347
  <style scoped>
324
- .timeline-root {
325
- display: flex;
326
- flex-direction: column;
327
- height: 100%;
328
- overflow: hidden;
329
- }
330
-
331
- /* ── Stats ───────────────────────────────────────────────────────────────── */
332
- .stats-row {
348
+ .transition-timeline__stats {
333
349
  display: flex;
334
350
  gap: 10px;
335
- padding: 12px 14px 0;
336
- flex-shrink: 0;
337
351
  }
338
352
 
339
- .stat-card {
340
- background: var(--bg2);
341
- border: 0.5px solid var(--border);
342
- border-radius: var(--radius);
343
- padding: 8px 14px;
353
+ .transition-timeline__stats :deep(.stat-card) {
344
354
  min-width: 72px;
345
355
  text-align: center;
356
+ padding: var(--tracker-space-2) 14px;
357
+ border: var(--tracker-border-width) solid var(--border);
346
358
  }
347
359
 
348
- .stat-val {
349
- font-size: 20px;
360
+ .transition-timeline__stats :deep(.stat-val) {
350
361
  font-weight: 600;
351
362
  font-family: var(--mono);
352
363
  line-height: 1.1;
353
364
  }
354
365
 
355
366
  .stat-unit {
356
- font-size: 12px;
367
+ font-size: var(--tracker-font-size-md);
357
368
  opacity: 0.6;
358
369
  margin-left: 1px;
359
370
  }
360
371
 
361
- .stat-label {
362
- font-size: 10px;
363
- color: var(--text3);
364
- margin-top: 2px;
365
- text-transform: uppercase;
366
- letter-spacing: 0.4px;
367
- }
368
-
369
372
  /* ── Toolbar ─────────────────────────────────────────────────────────────── */
370
- .toolbar {
371
- display: flex;
372
- align-items: center;
373
- gap: 8px;
374
- padding: 10px 14px;
375
- flex-shrink: 0;
376
- border-bottom: 0.5px solid var(--border);
373
+ .transition-timeline__toolbar {
374
+ border-bottom: var(--tracker-border-width) solid var(--border);
375
+ padding-bottom: 10px;
377
376
  }
378
377
 
379
- .search-input {
378
+ .transition-timeline__search {
380
379
  flex: 1;
381
380
  max-width: 260px;
382
381
  }
383
382
 
384
- .filter-group {
383
+ .transition-timeline__filters {
385
384
  display: flex;
386
385
  gap: 4px;
387
386
  }
388
387
 
389
388
  /* ── Content ─────────────────────────────────────────────────────────────── */
390
- .content-area {
391
- display: flex;
392
- flex: 1;
393
- overflow: hidden;
394
- min-height: 0;
389
+ .transition-timeline__content {
390
+ align-items: stretch;
395
391
  }
396
392
 
397
- .table-pane {
398
- flex: 1;
393
+ .transition-timeline__table {
399
394
  overflow: hidden auto;
400
395
  min-width: 0;
396
+ border: none;
397
+ border-radius: 0;
401
398
  }
402
399
 
403
400
  /* ── Timeline bar ────────────────────────────────────────────────────────── */
404
- .bar-cell {
401
+ .transition-timeline__bar-cell {
405
402
  width: 200px;
406
403
  padding: 4px 8px;
407
404
  }
408
405
 
409
- .bar-track {
406
+ .transition-timeline__bar-track {
410
407
  position: relative;
411
408
  height: 8px;
412
409
  background: var(--bg2);
@@ -414,26 +411,62 @@ function directionColor(e: TransitionEntry): string {
414
411
  overflow: hidden;
415
412
  }
416
413
 
417
- .bar-fill {
414
+ .transition-timeline__bar-fill {
418
415
  position: absolute;
419
416
  top: 0;
420
417
  height: 100%;
421
418
  min-width: 3px;
422
419
  border-radius: 4px;
423
- transition: width 0.15s;
420
+ transition: width var(--tracker-transition-ui);
421
+ }
422
+
423
+ .transition-timeline__col-name {
424
+ width: 110px;
425
+ }
426
+
427
+ .transition-timeline__col-dir {
428
+ width: 80px;
429
+ }
430
+
431
+ .transition-timeline__col-phase {
432
+ width: 90px;
433
+ }
434
+
435
+ .transition-timeline__col-duration {
436
+ width: 70px;
437
+ }
438
+
439
+ .transition-timeline__name {
440
+ font-size: var(--tracker-font-size-sm);
441
+ font-weight: 500;
442
+ }
443
+
444
+ .transition-timeline__direction {
445
+ font-size: var(--tracker-font-size-sm);
446
+ }
447
+
448
+ .transition-timeline__duration {
449
+ font-size: var(--tracker-font-size-sm);
450
+ color: var(--text2);
451
+ }
452
+
453
+ .transition-timeline__component {
454
+ font-size: var(--tracker-font-size-sm);
424
455
  }
425
456
 
426
457
  /* ── Detail panel ────────────────────────────────────────────────────────── */
427
- .detail-panel {
428
- width: 260px;
458
+ .transition-timeline__detail {
459
+ display: flex;
460
+ flex-direction: column;
429
461
  flex-shrink: 0;
430
- border-left: 0.5px solid var(--border);
462
+ border: var(--tracker-border-width) solid var(--border);
463
+ border-radius: var(--radius-lg);
431
464
  overflow-y: auto;
432
465
  background: var(--bg3);
433
466
  padding: 0 0 16px;
434
467
  }
435
468
 
436
- .panel-header {
469
+ .transition-timeline__detail-header {
437
470
  display: flex;
438
471
  align-items: center;
439
472
  justify-content: space-between;
@@ -445,71 +478,73 @@ function directionColor(e: TransitionEntry): string {
445
478
  z-index: 1;
446
479
  }
447
480
 
448
- .panel-title {
481
+ .transition-timeline__detail-title {
449
482
  font-family: var(--mono);
450
483
  font-size: 13px;
451
484
  font-weight: 500;
452
485
  }
453
486
 
454
- .close-btn {
487
+ .transition-timeline__close-btn {
455
488
  border: none;
456
489
  background: transparent;
457
490
  color: var(--text3);
458
- font-size: 11px;
491
+ font-size: var(--tracker-font-size-sm);
459
492
  padding: 2px 6px;
460
493
  cursor: pointer;
461
494
  }
462
495
 
463
- .panel-section {
496
+ .transition-timeline__detail-section {
464
497
  padding: 10px 14px 6px;
465
- border-bottom: 0.5px solid var(--border);
498
+ border-bottom: var(--tracker-border-width) solid var(--border);
466
499
  }
467
500
 
468
- .panel-section-title {
469
- font-size: 10px;
470
- font-weight: 500;
471
- color: var(--text3);
472
- text-transform: uppercase;
473
- letter-spacing: 0.4px;
501
+ .transition-timeline__section-title {
474
502
  margin-bottom: 8px;
475
503
  }
476
504
 
477
- .panel-row {
505
+ .transition-timeline__detail-row {
478
506
  display: flex;
479
507
  justify-content: space-between;
480
508
  align-items: center;
481
509
  gap: 8px;
482
510
  padding: 3px 0;
483
- font-size: 12px;
511
+ font-size: var(--tracker-font-size-md);
484
512
  }
485
513
 
486
- .panel-key {
514
+ .transition-timeline__detail-key {
487
515
  color: var(--text3);
488
516
  flex-shrink: 0;
489
517
  }
490
518
 
491
- .panel-val {
519
+ .transition-timeline__detail-val {
492
520
  color: var(--text);
493
521
  text-align: right;
494
522
  word-break: break-all;
495
523
  }
496
524
 
497
- .cancel-notice,
498
- .active-notice {
525
+ .transition-timeline__detail-val--mono {
526
+ font-family: var(--mono);
527
+ }
528
+
529
+ .transition-timeline__detail-val--strong {
530
+ font-weight: 500;
531
+ }
532
+
533
+ .transition-timeline__notice {
499
534
  margin: 10px 14px 0;
500
- font-size: 11px;
535
+ font-size: var(--tracker-font-size-sm);
501
536
  line-height: 1.6;
502
537
  padding: 8px 10px;
503
538
  border-radius: var(--radius);
504
539
  }
505
540
 
506
- .cancel-notice {
541
+ .transition-timeline__notice--cancelled {
507
542
  background: rgb(226 75 74 / 10%);
508
543
  color: var(--red);
509
544
  border: 0.5px solid rgb(226 75 74 / 30%);
510
545
  }
511
546
 
512
- .active-notice {
547
+ .transition-timeline__notice--active {
513
548
  background: rgb(127 119 221 / 10%);
514
549
  color: var(--purple);
515
550
  border: 0.5px solid rgb(127 119 221 / 30%);
@@ -6,9 +6,11 @@
6
6
  "strict": true,
7
7
  "jsx": "preserve",
8
8
  "lib": ["ESNext", "DOM"],
9
- "baseUrl": ".",
10
9
  "forceConsistentCasingInFileNames": true,
11
10
  "paths": {
11
+ "@observatory/*": ["../src/*"],
12
+ "@observatory-client/*": ["./src/*"],
13
+ "@observatory-tests/*": ["../tests/*"],
12
14
  "*": ["../node_modules/*"]
13
15
  }
14
16
  },
@@ -1,3 +1,4 @@
1
+ import path from 'node:path'
1
2
  import { defineConfig } from 'vite'
2
3
  import vue from '@vitejs/plugin-vue'
3
4
 
@@ -8,6 +9,13 @@ export default defineConfig({
8
9
  // the DevTools iframe without needing a separate dev server on a different port.
9
10
  base: '/__observatory/',
10
11
  plugins: [vue()],
12
+ resolve: {
13
+ alias: {
14
+ '@observatory': path.resolve(__dirname, '../src'),
15
+ '@observatory-client': path.resolve(__dirname, './src'),
16
+ '@observatory-tests': path.resolve(__dirname, '../tests'),
17
+ },
18
+ },
11
19
  build: {
12
20
  outDir: './dist',
13
21
  emptyOutDir: true,
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": "^3.0.0 || ^4.0.0"
6
6
  },
7
- "version": "0.1.28",
7
+ "version": "0.1.30",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"