nuxt-devtools-observatory 0.1.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.
Files changed (33) hide show
  1. package/README.md +209 -0
  2. package/client/dist/assets/index-C76d764s.js +17 -0
  3. package/client/dist/assets/index-yIuOV1_N.css +1 -0
  4. package/client/dist/index.html +47 -0
  5. package/client/index.html +46 -0
  6. package/client/src/App.vue +114 -0
  7. package/client/src/main.ts +5 -0
  8. package/client/src/stores/observatory.ts +65 -0
  9. package/client/src/style.css +261 -0
  10. package/client/src/views/ComposableTracker.vue +347 -0
  11. package/client/src/views/FetchDashboard.vue +492 -0
  12. package/client/src/views/ProvideInjectGraph.vue +481 -0
  13. package/client/src/views/RenderHeatmap.vue +492 -0
  14. package/client/src/views/TransitionTimeline.vue +527 -0
  15. package/client/tsconfig.json +16 -0
  16. package/client/vite.config.ts +12 -0
  17. package/dist/module.d.mts +38 -0
  18. package/dist/module.json +12 -0
  19. package/dist/module.mjs +562 -0
  20. package/dist/runtime/composables/composable-registry.d.ts +40 -0
  21. package/dist/runtime/composables/composable-registry.js +135 -0
  22. package/dist/runtime/composables/fetch-registry.d.ts +63 -0
  23. package/dist/runtime/composables/fetch-registry.js +83 -0
  24. package/dist/runtime/composables/provide-inject-registry.d.ts +57 -0
  25. package/dist/runtime/composables/provide-inject-registry.js +96 -0
  26. package/dist/runtime/composables/render-registry.d.ts +36 -0
  27. package/dist/runtime/composables/render-registry.js +85 -0
  28. package/dist/runtime/composables/transition-registry.d.ts +21 -0
  29. package/dist/runtime/composables/transition-registry.js +125 -0
  30. package/dist/runtime/plugin.d.ts +2 -0
  31. package/dist/runtime/plugin.js +66 -0
  32. package/dist/types.d.mts +3 -0
  33. package/package.json +89 -0
@@ -0,0 +1,46 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Observatory DevTools</title>
7
+ <style>
8
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
9
+ :root {
10
+ --font: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
11
+ --mono: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
12
+ --bg: #f8f7f4;
13
+ --bg2: #eeecea;
14
+ --bg3: #ffffff;
15
+ --border: rgba(0,0,0,0.1);
16
+ --text: #1a1a18;
17
+ --text2: #5f5e5a;
18
+ --text3: #888780;
19
+ --purple: #7f77dd;
20
+ --teal: #1d9e75;
21
+ --amber: #ef9f27;
22
+ --red: #e24b4a;
23
+ --blue: #378add;
24
+ --radius: 8px;
25
+ --radius-lg: 12px;
26
+ }
27
+ @media (prefers-color-scheme: dark) {
28
+ :root {
29
+ --bg: #1a1a18;
30
+ --bg2: #242422;
31
+ --bg3: #2c2c2a;
32
+ --border: rgba(255,255,255,0.1);
33
+ --text: #e8e6e0;
34
+ --text2: #b4b2a9;
35
+ --text3: #888780;
36
+ }
37
+ }
38
+ body { font-family: var(--font); background: var(--bg); color: var(--text); font-size: 13px; }
39
+ #app { height: 100vh; display: flex; flex-direction: column; }
40
+ </style>
41
+ </head>
42
+ <body>
43
+ <div id="app"></div>
44
+ <script type="module" src="/src/main.ts"></script>
45
+ </body>
46
+ </html>
@@ -0,0 +1,114 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+ import FetchDashboard from './views/FetchDashboard.vue'
4
+ import ProvideInjectGraph from './views/ProvideInjectGraph.vue'
5
+ import ComposableTracker from './views/ComposableTracker.vue'
6
+ import RenderHeatmap from './views/RenderHeatmap.vue'
7
+ import TransitionTimeline from './views/TransitionTimeline.vue'
8
+
9
+ const pathMap: Record<string, string> = {
10
+ fetch: 'fetch',
11
+ provide: 'provide',
12
+ composables: 'composable',
13
+ heatmap: 'heatmap',
14
+ transitions: 'transitions',
15
+ }
16
+
17
+ const segment = window.location.pathname.split('/').filter(Boolean).pop() ?? ''
18
+ const activeTab = ref(pathMap[segment] ?? 'fetch')
19
+
20
+ const tabs = [
21
+ { id: 'fetch', label: 'useFetch', icon: '⬡' },
22
+ { id: 'provide', label: 'provide/inject', icon: '⬡' },
23
+ { id: 'composable', label: 'Composables', icon: '⬡' },
24
+ { id: 'heatmap', label: 'Heatmap', icon: '⬡' },
25
+ { id: 'transitions', label: 'Transitions', icon: '⬡' },
26
+ ]
27
+ </script>
28
+
29
+ <template>
30
+ <div id="app-root">
31
+ <nav class="tabbar">
32
+ <div class="tabbar-brand">observatory</div>
33
+ <button v-for="tab in tabs" :key="tab.id" class="tab-btn" :class="{ active: activeTab === tab.id }" @click="activeTab = tab.id">
34
+ <span class="tab-icon">{{ tab.icon }}</span>
35
+ {{ tab.label }}
36
+ </button>
37
+ </nav>
38
+
39
+ <main class="tab-content">
40
+ <FetchDashboard v-if="activeTab === 'fetch'" />
41
+ <ProvideInjectGraph v-else-if="activeTab === 'provide'" />
42
+ <ComposableTracker v-else-if="activeTab === 'composable'" />
43
+ <RenderHeatmap v-else-if="activeTab === 'heatmap'" />
44
+ <TransitionTimeline v-else-if="activeTab === 'transitions'" />
45
+ </main>
46
+ </div>
47
+ </template>
48
+
49
+ <style scoped>
50
+ #app-root {
51
+ display: flex;
52
+ flex-direction: column;
53
+ height: 100vh;
54
+ overflow: hidden;
55
+ }
56
+
57
+ .tabbar {
58
+ display: flex;
59
+ align-items: center;
60
+ gap: 2px;
61
+ padding: 8px 12px 0;
62
+ border-bottom: 0.5px solid var(--border);
63
+ background: var(--bg3);
64
+ flex-shrink: 0;
65
+ }
66
+
67
+ .tabbar-brand {
68
+ font-size: 11px;
69
+ font-weight: 500;
70
+ color: var(--purple);
71
+ letter-spacing: 0.5px;
72
+ margin-right: 12px;
73
+ padding-bottom: 8px;
74
+ }
75
+
76
+ .tab-btn {
77
+ border: none;
78
+ border-bottom: 2px solid transparent;
79
+ border-radius: 0;
80
+ background: transparent;
81
+ color: var(--text3);
82
+ font-size: 12px;
83
+ padding: 6px 12px 8px;
84
+ cursor: pointer;
85
+ transition:
86
+ color 0.12s,
87
+ border-color 0.12s;
88
+ display: flex;
89
+ align-items: center;
90
+ gap: 5px;
91
+ }
92
+
93
+ .tab-btn:hover {
94
+ color: var(--text);
95
+ background: transparent;
96
+ }
97
+
98
+ .tab-btn.active {
99
+ color: var(--purple);
100
+ border-bottom-color: var(--purple);
101
+ }
102
+
103
+ .tab-icon {
104
+ font-size: 10px;
105
+ opacity: 0.6;
106
+ }
107
+
108
+ .tab-content {
109
+ flex: 1;
110
+ overflow: hidden;
111
+ display: flex;
112
+ flex-direction: column;
113
+ }
114
+ </style>
@@ -0,0 +1,5 @@
1
+ import { createApp } from 'vue'
2
+ import App from './App.vue'
3
+ import './style.css'
4
+
5
+ createApp(App).mount('#app')
@@ -0,0 +1,65 @@
1
+ /**
2
+ * useObservatoryData — live bridge between the Nuxt app and the client SPA.
3
+ *
4
+ * The client SPA runs at localhost:4949 (cross-origin from the Nuxt app at
5
+ * localhost:3000). Direct window.top property access is blocked by the browser.
6
+ * However postMessage IS allowed cross-origin:
7
+ *
8
+ * iframe (4949) → window.top.postMessage({ type: 'observatory:request' }) → Nuxt page (3000)
9
+ * Nuxt plugin.ts → event.source.postMessage({ type: 'observatory:snapshot', data }) → iframe
10
+ *
11
+ * The plugin.ts listener is registered immediately on plugin init (not deferred
12
+ * to app:mounted) so requests sent before full hydration are answered correctly.
13
+ */
14
+
15
+ import { ref, onUnmounted } from 'vue'
16
+
17
+ const POLL_MS = 500
18
+
19
+ export interface TransitionEntry {
20
+ id: string
21
+ transitionName: string
22
+ parentComponent: string
23
+ direction: 'enter' | 'leave'
24
+ phase: 'entering' | 'entered' | 'leaving' | 'left' | 'enter-cancelled' | 'leave-cancelled' | 'interrupted'
25
+ startTime: number
26
+ endTime?: number
27
+ durationMs?: number
28
+ cancelled: boolean
29
+ appear: boolean
30
+ mode?: string
31
+ }
32
+
33
+ interface ObservatorySnapshot {
34
+ transitions?: TransitionEntry[]
35
+ }
36
+
37
+ export function useObservatoryData() {
38
+ const transitions = ref<TransitionEntry[]>([])
39
+ const connected = ref(false)
40
+
41
+ function request() {
42
+ window.top?.postMessage({ type: 'observatory:request' }, '*')
43
+ }
44
+
45
+ function onMessage(event: MessageEvent) {
46
+ if (event.data?.type !== 'observatory:snapshot') {
47
+ return
48
+ }
49
+
50
+ const data = event.data.data as ObservatorySnapshot
51
+ transitions.value = data.transitions ?? []
52
+ connected.value = true
53
+ }
54
+
55
+ window.addEventListener('message', onMessage)
56
+ const timer = setInterval(request, POLL_MS)
57
+ request() // immediate first request
58
+
59
+ onUnmounted(() => {
60
+ window.removeEventListener('message', onMessage)
61
+ clearInterval(timer)
62
+ })
63
+
64
+ return { transitions, connected }
65
+ }
@@ -0,0 +1,261 @@
1
+ * {
2
+ box-sizing: border-box;
3
+ margin: 0;
4
+ padding: 0;
5
+ }
6
+
7
+ body {
8
+ font-family: var(--font);
9
+ background: var(--bg);
10
+ color: var(--text);
11
+ font-size: 13px;
12
+ line-height: 1.5;
13
+ }
14
+
15
+ /* ── Scrollbars ─────────────────────────────────────────────────────────── */
16
+ ::-webkit-scrollbar {
17
+ width: 6px;
18
+ height: 6px;
19
+ }
20
+
21
+ ::-webkit-scrollbar-track {
22
+ background: transparent;
23
+ }
24
+
25
+ ::-webkit-scrollbar-thumb {
26
+ background: var(--border);
27
+ border-radius: 3px;
28
+ }
29
+
30
+ /* ── Base elements ──────────────────────────────────────────────────────── */
31
+ button {
32
+ font-family: var(--font);
33
+ font-size: 12px;
34
+ cursor: pointer;
35
+ border: 0.5px solid var(--border);
36
+ background: transparent;
37
+ color: var(--text2);
38
+ padding: 4px 10px;
39
+ border-radius: var(--radius);
40
+ transition: background 0.12s;
41
+ }
42
+
43
+ button:hover {
44
+ background: var(--bg2);
45
+ }
46
+
47
+ button:active {
48
+ transform: scale(0.98);
49
+ }
50
+
51
+ button.active {
52
+ background: rgb(127 119 221 / 15%);
53
+ color: var(--purple);
54
+ border-color: var(--purple);
55
+ }
56
+
57
+ button.danger-active {
58
+ background: rgb(226 75 74 / 12%);
59
+ color: var(--red);
60
+ border-color: var(--red);
61
+ }
62
+
63
+ button.success-active {
64
+ background: rgb(29 158 117 / 12%);
65
+ color: var(--teal);
66
+ border-color: var(--teal);
67
+ }
68
+
69
+ input[type='text'],
70
+ input[type='search'] {
71
+ font-family: var(--font);
72
+ font-size: 12px;
73
+ border: 0.5px solid var(--border);
74
+ background: var(--bg2);
75
+ color: var(--text);
76
+ padding: 5px 10px;
77
+ border-radius: var(--radius);
78
+ outline: none;
79
+ width: 100%;
80
+ }
81
+
82
+ input[type='text']:focus,
83
+ input[type='search']:focus {
84
+ border-color: var(--purple);
85
+ box-shadow: 0 0 0 2px rgb(127 119 221 / 20%);
86
+ }
87
+
88
+ input[type='range'] {
89
+ accent-color: var(--purple);
90
+ cursor: pointer;
91
+ }
92
+
93
+ /* ── Typography helpers ─────────────────────────────────────────────────── */
94
+ .mono {
95
+ font-family: var(--mono);
96
+ }
97
+
98
+ .muted {
99
+ color: var(--text3);
100
+ }
101
+
102
+ .text-sm {
103
+ font-size: 11px;
104
+ }
105
+
106
+ .text-xs {
107
+ font-size: 10px;
108
+ }
109
+
110
+ .bold {
111
+ font-weight: 500;
112
+ }
113
+
114
+ /* ── Badge ──────────────────────────────────────────────────────────────── */
115
+ .badge {
116
+ display: inline-block;
117
+ font-size: 10px;
118
+ font-weight: 500;
119
+ padding: 2px 7px;
120
+ border-radius: 99px;
121
+ white-space: nowrap;
122
+ }
123
+
124
+ .badge-ok {
125
+ background: rgb(29 158 117 / 15%);
126
+ color: var(--teal);
127
+ }
128
+
129
+ .badge-err {
130
+ background: rgb(226 75 74 / 12%);
131
+ color: var(--red);
132
+ }
133
+
134
+ .badge-warn {
135
+ background: rgb(239 159 39 / 15%);
136
+ color: var(--amber);
137
+ }
138
+
139
+ .badge-info {
140
+ background: rgb(55 138 221 / 12%);
141
+ color: var(--blue);
142
+ }
143
+
144
+ .badge-gray {
145
+ background: var(--bg2);
146
+ color: var(--text3);
147
+ border: 0.5px solid var(--border);
148
+ }
149
+
150
+ .badge-purple {
151
+ background: rgb(127 119 221 / 15%);
152
+ color: var(--purple);
153
+ }
154
+
155
+ /* ── Card ───────────────────────────────────────────────────────────────── */
156
+ .card {
157
+ background: var(--bg3);
158
+ border: 0.5px solid var(--border);
159
+ border-radius: var(--radius-lg);
160
+ padding: 12px 14px;
161
+ }
162
+
163
+ /* ── Table ──────────────────────────────────────────────────────────────── */
164
+ .data-table {
165
+ width: 100%;
166
+ border-collapse: collapse;
167
+ font-size: 12px;
168
+ }
169
+
170
+ .data-table th {
171
+ text-align: left;
172
+ font-size: 10px;
173
+ font-weight: 500;
174
+ color: var(--text3);
175
+ padding: 6px 8px;
176
+ border-bottom: 0.5px solid var(--border);
177
+ text-transform: uppercase;
178
+ letter-spacing: 0.4px;
179
+ white-space: nowrap;
180
+ }
181
+
182
+ .data-table td {
183
+ padding: 8px;
184
+ border-bottom: 0.5px solid var(--border);
185
+ color: var(--text);
186
+ vertical-align: middle;
187
+ }
188
+
189
+ .data-table tr:hover td {
190
+ background: var(--bg2);
191
+ cursor: pointer;
192
+ }
193
+
194
+ .data-table tr.selected td {
195
+ background: rgb(127 119 221 / 8%);
196
+ }
197
+
198
+ /* ── Stat card ──────────────────────────────────────────────────────────── */
199
+ .stat-card {
200
+ background: var(--bg2);
201
+ border-radius: var(--radius);
202
+ padding: 10px 12px;
203
+ }
204
+
205
+ .stat-label {
206
+ font-size: 10px;
207
+ color: var(--text3);
208
+ text-transform: uppercase;
209
+ letter-spacing: 0.4px;
210
+ margin-bottom: 3px;
211
+ }
212
+
213
+ .stat-val {
214
+ font-size: 20px;
215
+ font-weight: 500;
216
+ }
217
+
218
+ /* ── Layout helpers ─────────────────────────────────────────────────────── */
219
+ .flex {
220
+ display: flex;
221
+ }
222
+
223
+ .items-center {
224
+ align-items: center;
225
+ }
226
+
227
+ .gap-2 {
228
+ gap: 8px;
229
+ }
230
+
231
+ .gap-3 {
232
+ gap: 12px;
233
+ }
234
+
235
+ .flex-1 {
236
+ flex: 1;
237
+ }
238
+
239
+ .overflow-auto {
240
+ overflow: auto;
241
+ }
242
+
243
+ .p-3 {
244
+ padding: 12px;
245
+ }
246
+
247
+ .p-4 {
248
+ padding: 16px;
249
+ }
250
+
251
+ .mb-2 {
252
+ margin-bottom: 8px;
253
+ }
254
+
255
+ .mb-3 {
256
+ margin-bottom: 12px;
257
+ }
258
+
259
+ .mt-2 {
260
+ margin-top: 8px;
261
+ }