nuxt-devtools-observatory 0.1.11 → 0.1.13
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.
- package/LICENSE +9 -0
- package/README.md +114 -31
- package/client/dist/assets/index-BFrWlkvI.js +17 -0
- package/client/dist/assets/index-BUQNNbrq.css +1 -0
- package/client/dist/index.html +2 -2
- package/client/src/stores/observatory.ts +59 -15
- package/client/src/views/ComposableTracker.vue +685 -101
- package/client/src/views/ProvideInjectGraph.vue +232 -61
- package/client/src/views/RenderHeatmap.vue +138 -37
- package/client/src/views/TransitionTimeline.vue +2 -2
- package/client/src/views/ValueInspector.vue +124 -0
- package/dist/module.json +1 -1
- package/dist/runtime/composables/composable-registry.d.ts +21 -0
- package/dist/runtime/composables/composable-registry.js +208 -53
- package/dist/runtime/composables/provide-inject-registry.d.ts +13 -1
- package/dist/runtime/composables/provide-inject-registry.js +34 -6
- package/dist/runtime/composables/render-registry.d.ts +18 -8
- package/dist/runtime/composables/render-registry.js +29 -35
- package/dist/runtime/composables/transition-registry.js +13 -7
- package/dist/runtime/plugin.js +49 -17
- package/package.json +7 -3
- package/client/dist/assets/index--Igqz_EM.js +0 -17
- package/client/dist/assets/index-BoC4M4Nb.css +0 -1
|
@@ -5,9 +5,21 @@ import { useObservatoryData, type InjectEntry, type ProvideEntry } from '../stor
|
|
|
5
5
|
interface TreeNodeData {
|
|
6
6
|
id: string
|
|
7
7
|
label: string
|
|
8
|
+
componentName: string
|
|
8
9
|
type: 'provider' | 'consumer' | 'both' | 'error'
|
|
9
|
-
provides: Array<{
|
|
10
|
-
|
|
10
|
+
provides: Array<{
|
|
11
|
+
key: string
|
|
12
|
+
val: string
|
|
13
|
+
raw: unknown
|
|
14
|
+
reactive: boolean
|
|
15
|
+
complex: boolean
|
|
16
|
+
scope: 'global' | 'layout' | 'component'
|
|
17
|
+
isShadowing: boolean
|
|
18
|
+
/** UIDs of components that inject this key */
|
|
19
|
+
consumerUids: number[]
|
|
20
|
+
consumerNames: string[]
|
|
21
|
+
}>
|
|
22
|
+
injects: Array<{ key: string; from: string | null; fromName: string | null; ok: boolean }>
|
|
11
23
|
children: TreeNodeData[]
|
|
12
24
|
}
|
|
13
25
|
|
|
@@ -43,11 +55,38 @@ function nodeColor(node: TreeNodeData): string {
|
|
|
43
55
|
function matchesFilter(node: TreeNodeData, filter: string): boolean {
|
|
44
56
|
if (filter === 'all') return true
|
|
45
57
|
if (filter === 'warn') return node.injects.some((entry) => !entry.ok)
|
|
58
|
+
if (filter === 'shadow') return node.provides.some((entry) => entry.isShadowing)
|
|
46
59
|
return node.provides.some((entry) => entry.key === filter) || node.injects.some((entry) => entry.key === filter)
|
|
47
60
|
}
|
|
48
61
|
|
|
49
|
-
function
|
|
50
|
-
|
|
62
|
+
function matchesSearch(node: TreeNodeData, query: string): boolean {
|
|
63
|
+
if (!query) return true
|
|
64
|
+
const q = query.toLowerCase()
|
|
65
|
+
return (
|
|
66
|
+
node.label.toLowerCase().includes(q) ||
|
|
67
|
+
node.componentName.toLowerCase().includes(q) ||
|
|
68
|
+
node.provides.some((p) => p.key.toLowerCase().includes(q)) ||
|
|
69
|
+
node.injects.some((i) => i.key.toLowerCase().includes(q))
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Count leaf nodes in a subtree iteratively to avoid stack overflow on
|
|
75
|
+
* pathologically deep provide/inject trees (e.g. every component re-providing
|
|
76
|
+
* the same key creates a chain as long as the component tree itself).
|
|
77
|
+
*/
|
|
78
|
+
function countLeaves(root: TreeNodeData): number {
|
|
79
|
+
let count = 0
|
|
80
|
+
const stack: TreeNodeData[] = [root]
|
|
81
|
+
while (stack.length) {
|
|
82
|
+
const node = stack.pop()!
|
|
83
|
+
if (node.children.length === 0) {
|
|
84
|
+
count++
|
|
85
|
+
} else {
|
|
86
|
+
stack.push(...node.children)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return count
|
|
51
90
|
}
|
|
52
91
|
|
|
53
92
|
function stringifyValue(value: unknown) {
|
|
@@ -122,6 +161,7 @@ const nodes = computed<TreeNodeData[]>(() => {
|
|
|
122
161
|
const created: TreeNodeData = {
|
|
123
162
|
id,
|
|
124
163
|
label: basename(entry.componentFile),
|
|
164
|
+
componentName: entry.componentName ?? basename(entry.componentFile),
|
|
125
165
|
type: 'consumer',
|
|
126
166
|
provides: [],
|
|
127
167
|
injects: [],
|
|
@@ -133,14 +173,36 @@ const nodes = computed<TreeNodeData[]>(() => {
|
|
|
133
173
|
return created
|
|
134
174
|
}
|
|
135
175
|
|
|
176
|
+
// Build a lookup: key → list of inject entries (to compute consumer lists)
|
|
177
|
+
const injectsByKey = new Map<string, InjectEntry[]>()
|
|
178
|
+
for (const entry of provideInject.value.injects) {
|
|
179
|
+
const list = injectsByKey.get(entry.key) ?? []
|
|
180
|
+
list.push(entry)
|
|
181
|
+
injectsByKey.set(entry.key, list)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Build a lookup: uid → componentName for resolvedFrom display
|
|
185
|
+
const nameByUid = new Map<number, string>()
|
|
186
|
+
for (const entry of provideInject.value.provides) {
|
|
187
|
+
nameByUid.set(entry.componentUid, entry.componentName)
|
|
188
|
+
}
|
|
189
|
+
for (const entry of provideInject.value.injects) {
|
|
190
|
+
nameByUid.set(entry.componentUid, entry.componentName)
|
|
191
|
+
}
|
|
192
|
+
|
|
136
193
|
for (const entry of provideInject.value.provides) {
|
|
137
194
|
const node = ensureNode(entry)
|
|
195
|
+
const consumers = injectsByKey.get(entry.key) ?? []
|
|
138
196
|
node.provides.push({
|
|
139
197
|
key: entry.key,
|
|
140
198
|
val: formatValuePreview(entry.valueSnapshot),
|
|
141
199
|
raw: entry.valueSnapshot,
|
|
142
200
|
reactive: entry.isReactive,
|
|
143
201
|
complex: isComplexValue(entry.valueSnapshot),
|
|
202
|
+
scope: entry.scope ?? 'component',
|
|
203
|
+
isShadowing: entry.isShadowing ?? false,
|
|
204
|
+
consumerUids: consumers.map((c) => c.componentUid),
|
|
205
|
+
consumerNames: consumers.map((c) => c.componentName),
|
|
144
206
|
})
|
|
145
207
|
}
|
|
146
208
|
|
|
@@ -149,6 +211,7 @@ const nodes = computed<TreeNodeData[]>(() => {
|
|
|
149
211
|
node.injects.push({
|
|
150
212
|
key: entry.key,
|
|
151
213
|
from: entry.resolvedFromFile ?? null,
|
|
214
|
+
fromName: entry.resolvedFromUid !== undefined ? (nameByUid.get(entry.resolvedFromUid) ?? null) : null,
|
|
152
215
|
ok: entry.resolved,
|
|
153
216
|
})
|
|
154
217
|
}
|
|
@@ -182,6 +245,7 @@ const nodes = computed<TreeNodeData[]>(() => {
|
|
|
182
245
|
})
|
|
183
246
|
|
|
184
247
|
const activeFilter = ref('all')
|
|
248
|
+
const searchQuery = ref('')
|
|
185
249
|
const selectedNode = ref<TreeNodeData | null>(null)
|
|
186
250
|
const expandedProvideValues = ref<Set<string>>(new Set())
|
|
187
251
|
|
|
@@ -203,36 +267,52 @@ function toggleProvideValue(id: string) {
|
|
|
203
267
|
|
|
204
268
|
const allKeys = computed(() => {
|
|
205
269
|
const keys = new Set<string>()
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
})
|
|
270
|
+
const stack = [...nodes.value]
|
|
271
|
+
while (stack.length) {
|
|
272
|
+
const node = stack.pop()!
|
|
273
|
+
node.provides.forEach((entry) => keys.add(entry.key))
|
|
274
|
+
node.injects.forEach((entry) => keys.add(entry.key))
|
|
275
|
+
stack.push(...node.children)
|
|
213
276
|
}
|
|
214
|
-
|
|
215
|
-
collect(nodes.value)
|
|
216
|
-
|
|
217
277
|
return [...keys]
|
|
218
278
|
})
|
|
219
279
|
|
|
220
280
|
const visibleNodes = computed<TreeNodeData[]>(() => {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
281
|
+
// Iterative post-order prune — avoids stack overflow on deep trees.
|
|
282
|
+
// We process nodes bottom-up so each parent can inspect its children's
|
|
283
|
+
// already-computed visibility before deciding its own.
|
|
284
|
+
function pruneIterative(root: TreeNodeData): TreeNodeData | null {
|
|
285
|
+
// Phase 1: collect nodes in pre-order (parent before children)
|
|
286
|
+
const order: TreeNodeData[] = []
|
|
287
|
+
const stack: TreeNodeData[] = [root]
|
|
288
|
+
while (stack.length) {
|
|
289
|
+
const node = stack.pop()!
|
|
290
|
+
order.push(node)
|
|
291
|
+
for (let i = node.children.length - 1; i >= 0; i--) {
|
|
292
|
+
stack.push(node.children[i])
|
|
293
|
+
}
|
|
227
294
|
}
|
|
228
295
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
296
|
+
// Phase 2: process in reverse pre-order (children before parents)
|
|
297
|
+
const pruned = new Map<TreeNodeData, TreeNodeData | null>()
|
|
298
|
+
for (let i = order.length - 1; i >= 0; i--) {
|
|
299
|
+
const node = order[i]
|
|
300
|
+
const visibleChildren = node.children
|
|
301
|
+
.map((child) => pruned.get(child) ?? null)
|
|
302
|
+
.filter((child): child is TreeNodeData => child !== null)
|
|
303
|
+
const selfMatches = matchesFilter(node, activeFilter.value) && matchesSearch(node, searchQuery.value)
|
|
304
|
+
|
|
305
|
+
if (!selfMatches && !visibleChildren.length) {
|
|
306
|
+
pruned.set(node, null)
|
|
307
|
+
} else {
|
|
308
|
+
pruned.set(node, { ...node, children: visibleChildren })
|
|
309
|
+
}
|
|
232
310
|
}
|
|
311
|
+
|
|
312
|
+
return pruned.get(root) ?? null
|
|
233
313
|
}
|
|
234
314
|
|
|
235
|
-
return nodes.value.map(
|
|
315
|
+
return nodes.value.map(pruneIterative).filter(Boolean) as TreeNodeData[]
|
|
236
316
|
})
|
|
237
317
|
|
|
238
318
|
watch([visibleNodes, selectedNode], ([currentNodes, currentSelected]) => {
|
|
@@ -241,16 +321,13 @@ watch([visibleNodes, selectedNode], ([currentNodes, currentSelected]) => {
|
|
|
241
321
|
}
|
|
242
322
|
|
|
243
323
|
const ids = new Set<string>()
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
})
|
|
324
|
+
const stack = [...currentNodes]
|
|
325
|
+
while (stack.length) {
|
|
326
|
+
const node = stack.pop()!
|
|
327
|
+
ids.add(node.id)
|
|
328
|
+
stack.push(...node.children)
|
|
250
329
|
}
|
|
251
330
|
|
|
252
|
-
collect(currentNodes)
|
|
253
|
-
|
|
254
331
|
if (!ids.has(currentSelected.id)) {
|
|
255
332
|
selectedNode.value = null
|
|
256
333
|
}
|
|
@@ -260,32 +337,47 @@ const layout = computed<LayoutNode[]>(() => {
|
|
|
260
337
|
const flat: LayoutNode[] = []
|
|
261
338
|
const pad = H_GAP
|
|
262
339
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
x: Math.round(slotLeft + slotWidth / 2),
|
|
271
|
-
y: Math.round(pad + depth * (NODE_H + V_GAP) + NODE_H / 2),
|
|
272
|
-
})
|
|
273
|
-
|
|
274
|
-
let childLeft = slotLeft
|
|
275
|
-
|
|
276
|
-
for (const child of node.children) {
|
|
277
|
-
const childLeaves = countLeaves(child)
|
|
278
|
-
place(child, depth + 1, childLeft, node.id)
|
|
279
|
-
childLeft += childLeaves * (NODE_W + H_GAP)
|
|
280
|
-
}
|
|
340
|
+
// Iterative replacement for the recursive place() — avoids stack overflow
|
|
341
|
+
// on deep component trees. Uses an explicit stack of pending work items.
|
|
342
|
+
interface WorkItem {
|
|
343
|
+
node: TreeNodeData
|
|
344
|
+
depth: number
|
|
345
|
+
slotLeft: number
|
|
346
|
+
parentId: string | null
|
|
281
347
|
}
|
|
282
348
|
|
|
283
349
|
let left = pad
|
|
284
350
|
|
|
285
351
|
for (const root of visibleNodes.value) {
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
352
|
+
const stack: WorkItem[] = [{ node: root, depth: 0, slotLeft: left, parentId: null }]
|
|
353
|
+
|
|
354
|
+
while (stack.length) {
|
|
355
|
+
const { node, depth, slotLeft, parentId } = stack.pop()!
|
|
356
|
+
const leaves = countLeaves(node)
|
|
357
|
+
const slotWidth = leaves * (NODE_W + H_GAP) - H_GAP
|
|
358
|
+
|
|
359
|
+
flat.push({
|
|
360
|
+
data: node,
|
|
361
|
+
parentId,
|
|
362
|
+
x: Math.round(slotLeft + slotWidth / 2),
|
|
363
|
+
y: Math.round(pad + depth * (NODE_H + V_GAP) + NODE_H / 2),
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
// Push children in reverse so leftmost child is processed first
|
|
367
|
+
let childLeft = slotLeft
|
|
368
|
+
const childWork: WorkItem[] = []
|
|
369
|
+
for (const child of node.children) {
|
|
370
|
+
const childLeaves = countLeaves(child)
|
|
371
|
+
childWork.push({ node: child, depth: depth + 1, slotLeft: childLeft, parentId: node.id })
|
|
372
|
+
childLeft += childLeaves * (NODE_W + H_GAP)
|
|
373
|
+
}
|
|
374
|
+
for (let i = childWork.length - 1; i >= 0; i--) {
|
|
375
|
+
stack.push(childWork[i])
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const rootLeaves = countLeaves(root)
|
|
380
|
+
left += rootLeaves * (NODE_W + H_GAP) + H_GAP * 2
|
|
289
381
|
}
|
|
290
382
|
|
|
291
383
|
return flat
|
|
@@ -325,9 +417,17 @@ const edges = computed<Edge[]>(() => {
|
|
|
325
417
|
>
|
|
326
418
|
{{ key }}
|
|
327
419
|
</button>
|
|
328
|
-
<button
|
|
329
|
-
|
|
420
|
+
<button
|
|
421
|
+
style="margin-left: auto"
|
|
422
|
+
:class="{ 'danger-active': activeFilter === 'shadow' }"
|
|
423
|
+
@click="activeFilter = activeFilter === 'shadow' ? 'all' : 'shadow'"
|
|
424
|
+
>
|
|
425
|
+
shadowed
|
|
426
|
+
</button>
|
|
427
|
+
<button :class="{ 'danger-active': activeFilter === 'warn' }" @click="activeFilter = activeFilter === 'warn' ? 'all' : 'warn'">
|
|
428
|
+
warnings
|
|
330
429
|
</button>
|
|
430
|
+
<input v-model="searchQuery" type="search" placeholder="search component or key…" style="max-width: 200px" />
|
|
331
431
|
</div>
|
|
332
432
|
|
|
333
433
|
<div class="split">
|
|
@@ -368,7 +468,9 @@ const edges = computed<Edge[]>(() => {
|
|
|
368
468
|
>
|
|
369
469
|
<span class="node-dot" :style="{ background: nodeColor(layoutNode.data) }"></span>
|
|
370
470
|
<span class="mono node-label">{{ layoutNode.data.label }}</span>
|
|
371
|
-
<span v-if="layoutNode.data.provides.length" class="badge badge-ok badge-xs"
|
|
471
|
+
<span v-if="layoutNode.data.provides.length" class="badge badge-ok badge-xs">
|
|
472
|
+
+{{ layoutNode.data.provides.length }}
|
|
473
|
+
</span>
|
|
372
474
|
<span v-if="layoutNode.data.injects.some((entry) => !entry.ok)" class="badge badge-err badge-xs">!</span>
|
|
373
475
|
</div>
|
|
374
476
|
</div>
|
|
@@ -395,6 +497,7 @@ const edges = computed<Edge[]>(() => {
|
|
|
395
497
|
<div class="row-main">
|
|
396
498
|
<span class="mono text-sm row-key">{{ entry.key }}</span>
|
|
397
499
|
<span class="mono text-sm muted row-value-preview" :title="entry.val">{{ entry.val }}</span>
|
|
500
|
+
<span class="badge scope-badge" :class="`scope-${entry.scope}`">{{ entry.scope }}</span>
|
|
398
501
|
<span class="badge" :class="entry.reactive ? 'badge-ok' : 'badge-gray'">
|
|
399
502
|
{{ entry.reactive ? 'reactive' : 'static' }}
|
|
400
503
|
</span>
|
|
@@ -406,22 +509,43 @@ const edges = computed<Edge[]>(() => {
|
|
|
406
509
|
{{ expandedProvideValues.has(provideValueId(selectedNode.id, entry.key, index)) ? 'hide' : 'view' }}
|
|
407
510
|
</button>
|
|
408
511
|
</div>
|
|
512
|
+
<!-- Shadowing warning -->
|
|
513
|
+
<div v-if="entry.isShadowing" class="row-warning">shadows a parent provide with the same key</div>
|
|
514
|
+
<!-- Consumer list -->
|
|
515
|
+
<div v-if="entry.consumerNames.length" class="row-consumers">
|
|
516
|
+
<span class="muted text-sm">used by:</span>
|
|
517
|
+
<span v-for="name in entry.consumerNames" :key="name" class="consumer-chip mono">{{ name }}</span>
|
|
518
|
+
<span v-if="!entry.consumerNames.length" class="muted text-sm">no consumers</span>
|
|
519
|
+
</div>
|
|
520
|
+
<div v-else class="muted text-sm" style="padding: 2px 0; font-size: 11px">no consumers detected</div>
|
|
409
521
|
<pre
|
|
410
522
|
v-if="entry.complex && expandedProvideValues.has(provideValueId(selectedNode.id, entry.key, index))"
|
|
411
523
|
class="value-box"
|
|
412
|
-
|
|
524
|
+
>{{ formatValueDetail(entry.raw) }}</pre
|
|
525
|
+
>
|
|
413
526
|
</div>
|
|
414
527
|
</div>
|
|
415
528
|
</div>
|
|
416
529
|
|
|
417
|
-
<div
|
|
530
|
+
<div
|
|
531
|
+
v-if="selectedNode.injects.length"
|
|
532
|
+
class="detail-section"
|
|
533
|
+
:style="{ marginTop: selectedNode.provides.length ? '10px' : '0' }"
|
|
534
|
+
>
|
|
418
535
|
<div class="section-label">injects ({{ selectedNode.injects.length }})</div>
|
|
419
536
|
<div class="detail-list">
|
|
420
|
-
<div
|
|
537
|
+
<div
|
|
538
|
+
v-for="entry in selectedNode.injects"
|
|
539
|
+
:key="entry.key"
|
|
540
|
+
class="inject-row"
|
|
541
|
+
:class="{ 'inject-miss': !entry.ok }"
|
|
542
|
+
>
|
|
421
543
|
<span class="mono text-sm row-key">{{ entry.key }}</span>
|
|
422
544
|
<span v-if="entry.ok" class="badge badge-ok">resolved</span>
|
|
423
545
|
<span v-else class="badge badge-err">no provider</span>
|
|
424
|
-
<span class="mono
|
|
546
|
+
<span class="mono text-sm row-from" :class="entry.fromName ? '' : 'muted'" :title="entry.from ?? 'undefined'">
|
|
547
|
+
{{ entry.fromName ?? entry.from ?? 'undefined' }}
|
|
548
|
+
</span>
|
|
425
549
|
</div>
|
|
426
550
|
</div>
|
|
427
551
|
</div>
|
|
@@ -628,13 +752,60 @@ const edges = computed<Edge[]>(() => {
|
|
|
628
752
|
.provide-row {
|
|
629
753
|
display: flex;
|
|
630
754
|
flex-direction: column;
|
|
631
|
-
gap:
|
|
755
|
+
gap: 4px;
|
|
632
756
|
padding: 5px 8px;
|
|
633
757
|
background: var(--bg2);
|
|
634
758
|
border-radius: var(--radius);
|
|
635
759
|
margin-bottom: 3px;
|
|
636
760
|
}
|
|
637
761
|
|
|
762
|
+
.row-warning {
|
|
763
|
+
font-size: 11px;
|
|
764
|
+
color: var(--amber);
|
|
765
|
+
padding: 2px 0;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
.row-consumers {
|
|
769
|
+
display: flex;
|
|
770
|
+
flex-wrap: wrap;
|
|
771
|
+
align-items: center;
|
|
772
|
+
gap: 4px;
|
|
773
|
+
padding: 2px 0;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
.consumer-chip {
|
|
777
|
+
font-size: 10px;
|
|
778
|
+
padding: 1px 6px;
|
|
779
|
+
border-radius: 4px;
|
|
780
|
+
background: color-mix(in srgb, var(--blue) 10%, var(--bg3));
|
|
781
|
+
border: 0.5px solid color-mix(in srgb, var(--blue) 30%, var(--border));
|
|
782
|
+
color: var(--text2);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
.scope-badge {
|
|
786
|
+
font-size: 10px;
|
|
787
|
+
padding: 1px 6px;
|
|
788
|
+
border-radius: 4px;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
.scope-global {
|
|
792
|
+
background: color-mix(in srgb, var(--amber) 15%, transparent);
|
|
793
|
+
border: 0.5px solid color-mix(in srgb, var(--amber) 40%, var(--border));
|
|
794
|
+
color: color-mix(in srgb, var(--amber) 80%, var(--text));
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
.scope-layout {
|
|
798
|
+
background: color-mix(in srgb, var(--purple) 15%, transparent);
|
|
799
|
+
border: 0.5px solid color-mix(in srgb, var(--purple) 40%, var(--border));
|
|
800
|
+
color: color-mix(in srgb, var(--purple) 80%, var(--text));
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
.scope-component {
|
|
804
|
+
background: var(--bg3);
|
|
805
|
+
border: 0.5px solid var(--border);
|
|
806
|
+
color: var(--text3);
|
|
807
|
+
}
|
|
808
|
+
|
|
638
809
|
.row-main {
|
|
639
810
|
display: flex;
|
|
640
811
|
align-items: center;
|