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