nuxt-devtools-observatory 0.1.13 → 0.1.15
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/README.md +71 -55
- package/client/dist/assets/index-B1qWBxxI.js +17 -0
- package/client/dist/assets/index-XYlDyaMH.css +1 -0
- package/client/dist/index.html +2 -2
- package/client/src/stores/observatory.ts +10 -0
- package/client/src/views/ComposableTracker.vue +42 -59
- package/client/src/views/ProvideInjectGraph.vue +89 -11
- package/client/src/views/RenderHeatmap.vue +216 -4
- package/dist/module.json +1 -1
- package/dist/module.mjs +4 -1
- package/dist/runtime/composables/render-registry.d.ts +17 -0
- package/dist/runtime/composables/render-registry.js +36 -9
- package/dist/runtime/plugin.js +9 -0
- package/package.json +1 -1
- package/client/dist/assets/index-BFrWlkvI.js +0 -17
- package/client/dist/assets/index-BUQNNbrq.css +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.view[data-v-8c1465c0]{display:flex;flex-direction:column;height:100%;overflow:hidden;padding:12px;gap:10px}.stats-row[data-v-8c1465c0]{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;flex-shrink:0}.toolbar[data-v-8c1465c0]{display:flex;align-items:center;gap:6px;flex-shrink:0;flex-wrap:wrap}.split[data-v-8c1465c0]{display:flex;gap:12px;flex:1;overflow:hidden;min-height:0}.table-wrap[data-v-8c1465c0]{flex:1;overflow:auto;border:.5px solid var(--border);border-radius:var(--radius-lg)}.detail-panel[data-v-8c1465c0]{width:280px;flex-shrink:0;display:flex;flex-direction:column;gap:8px;overflow:auto;border:.5px solid var(--border);border-radius:var(--radius-lg);padding:12px;background:var(--bg3)}.detail-empty[data-v-8c1465c0]{width:280px;flex-shrink:0;display:flex;align-items:center;justify-content:center;color:var(--text3);font-size:12px;border:.5px dashed var(--border);border-radius:var(--radius-lg)}.detail-header[data-v-8c1465c0]{display:flex;align-items:center;justify-content:space-between}.meta-grid[data-v-8c1465c0]{display:grid;grid-template-columns:auto 1fr;gap:4px 12px;font-size:11px}.section-label[data-v-8c1465c0]{font-size:10px;font-weight:500;text-transform:uppercase;letter-spacing:.4px;color:var(--text3);margin-top:6px}.payload-box[data-v-8c1465c0]{font-family:var(--mono);font-size:11px;color:var(--text2);background:var(--bg2);border-radius:var(--radius);padding:8px 10px;overflow:auto;white-space:pre;max-height:160px}.waterfall[data-v-8c1465c0]{flex-shrink:0;background:var(--bg3);border:.5px solid var(--border);border-radius:var(--radius-lg);padding:10px 12px}.waterfall-header[data-v-8c1465c0]{display:flex;align-items:center;justify-content:space-between;gap:8px}.waterfall-body[data-v-8c1465c0]{margin-top:6px}.wf-row[data-v-8c1465c0]{display:flex;align-items:center;gap:8px;margin-bottom:4px}.wf-track[data-v-8c1465c0]{flex:1;position:relative;height:8px;background:var(--bg2);border-radius:2px;overflow:hidden}.wf-bar[data-v-8c1465c0]{position:absolute;top:0;height:100%;border-radius:2px;opacity:.8}.view[data-v-0c07531b]{display:flex;flex-direction:column;height:100%;overflow:hidden;padding:12px;gap:10px}.toolbar[data-v-0c07531b]{display:flex;align-items:center;gap:6px;flex-shrink:0;flex-wrap:wrap}.split[data-v-0c07531b]{display:flex;gap:12px;flex:1;overflow:hidden;min-height:0}.graph-area[data-v-0c07531b]{flex:1;overflow:auto;border:.5px solid var(--border);border-radius:var(--radius-lg);padding:12px;background:var(--bg3)}.legend[data-v-0c07531b]{display:flex;align-items:center;gap:12px;font-size:11px;color:var(--text2);margin-bottom:12px}.canvas-stage[data-v-0c07531b]{display:flex;justify-content:center;align-items:flex-start;min-width:100%}.dot[data-v-0c07531b]{width:8px;height:8px;border-radius:50%;display:inline-block;margin-right:2px}.canvas-wrap[data-v-0c07531b]{position:relative}.edges-svg[data-v-0c07531b]{position:absolute;top:0;left:0;pointer-events:none}.edge[data-v-0c07531b]{stroke:var(--border);stroke-width:1.5}.graph-node[data-v-0c07531b]{position:absolute;display:flex;align-items:center;gap:7px;padding:0 10px;height:32px;border-radius:var(--radius);border:.5px solid var(--border);background:var(--bg3);cursor:pointer;transition:border-color .12s,background .12s;overflow:hidden;box-sizing:border-box;white-space:nowrap}.graph-node[data-v-0c07531b]:hover{border-color:var(--text3)}.graph-node.is-selected[data-v-0c07531b]{border-color:var(--node-color);background:color-mix(in srgb,var(--node-color) 8%,transparent)}.node-dot[data-v-0c07531b]{width:7px;height:7px;border-radius:50%;flex-shrink:0}.node-label[data-v-0c07531b]{font-size:11px;flex:1;overflow:hidden;text-overflow:ellipsis}.badge-xs[data-v-0c07531b]{font-size:9px;padding:1px 4px}.detail-panel[data-v-0c07531b]{width:280px;flex-shrink:0;overflow:auto;border:.5px solid var(--border);border-radius:var(--radius-lg);padding:12px;background:var(--bg3);display:flex;flex-direction:column;gap:4px;min-height:0}.detail-empty[data-v-0c07531b]{width:280px;display:flex;align-items:center;justify-content:center;color:var(--text3);font-size:12px;border:.5px dashed var(--border);border-radius:var(--radius-lg);flex-shrink:0}.graph-empty[data-v-0c07531b]{display:flex;align-items:center;justify-content:center;min-height:180px;color:var(--text3);font-size:12px}.detail-header[data-v-0c07531b]{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.section-label[data-v-0c07531b]{font-size:10px;font-weight:500;text-transform:uppercase;letter-spacing:.4px;color:var(--text3);margin:8px 0 5px}.detail-section[data-v-0c07531b]{display:flex;flex-direction:column;min-height:0}.detail-list[data-v-0c07531b]{display:flex;flex-direction:column;gap:3px;overflow:auto;max-height:220px;padding-right:2px}.provide-row[data-v-0c07531b]{display:flex;flex-direction:column;gap:4px;padding:5px 8px;background:var(--bg2);border-radius:var(--radius);margin-bottom:3px}.row-warning[data-v-0c07531b]{font-size:11px;color:var(--amber);padding:2px 0}.row-consumers[data-v-0c07531b]{display:flex;flex-wrap:wrap;align-items:center;gap:4px;padding:2px 0}.consumer-chip[data-v-0c07531b]{font-size:10px;padding:1px 6px;border-radius:4px;background:color-mix(in srgb,var(--blue) 10%,var(--bg3));border:.5px solid color-mix(in srgb,var(--blue) 30%,var(--border));color:var(--text2)}.scope-badge[data-v-0c07531b]{font-size:10px;padding:1px 6px;border-radius:4px}.scope-global[data-v-0c07531b]{background:color-mix(in srgb,var(--amber) 15%,transparent);border:.5px solid color-mix(in srgb,var(--amber) 40%,var(--border));color:color-mix(in srgb,var(--amber) 80%,var(--text))}.scope-layout[data-v-0c07531b]{background:color-mix(in srgb,var(--purple) 15%,transparent);border:.5px solid color-mix(in srgb,var(--purple) 40%,var(--border));color:color-mix(in srgb,var(--purple) 80%,var(--text))}.scope-component[data-v-0c07531b]{background:var(--bg3);border:.5px solid var(--border);color:var(--text3)}.row-main[data-v-0c07531b]{display:flex;align-items:center;gap:8px;min-width:0}.row-key[data-v-0c07531b]{min-width:100px;color:var(--text2);flex-shrink:0}.row-value-preview[data-v-0c07531b]{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.row-toggle[data-v-0c07531b]{padding:2px 8px;font-size:10px}.value-box[data-v-0c07531b]{font-family:var(--mono);font-size:11px;color:var(--text2);background:#0000001a;border-radius:var(--radius);padding:8px 10px;white-space:pre-wrap;word-break:break-word;overflow:auto;max-height:180px}.inject-row[data-v-0c07531b]{display:flex;align-items:center;gap:8px;padding:5px 8px;background:var(--bg2);border-radius:var(--radius);margin-bottom:3px}.row-from[data-v-0c07531b]{margin-left:auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.inject-miss[data-v-0c07531b]{background:#e24b4a14}.jump-btn[data-v-0c07531b]{font-size:10px;padding:1px 6px;border:.5px solid var(--border);border-radius:var(--radius);background:transparent;color:var(--text3);cursor:pointer;flex-shrink:0;font-family:var(--mono)}.jump-btn[data-v-0c07531b]:hover{border-color:var(--teal);color:var(--teal);background:color-mix(in srgb,var(--teal) 8%,transparent)}.view[data-v-47ca40b0]{display:flex;flex-direction:column;height:100%;overflow:hidden;padding:12px;gap:10px}.stats-row[data-v-47ca40b0]{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;flex-shrink:0}.toolbar[data-v-47ca40b0]{display:flex;align-items:center;gap:6px;flex-shrink:0;flex-wrap:wrap}.clear-btn[data-v-47ca40b0]{color:var(--text3);border-color:var(--border);flex-shrink:0}.clear-btn[data-v-47ca40b0]:hover{color:var(--red);border-color:var(--red);background:transparent}.list[data-v-47ca40b0]{flex:1;overflow:auto;display:flex;flex-direction:column;gap:5px;min-height:0}.comp-card[data-v-47ca40b0]{background:var(--bg3);border:.5px solid var(--border);border-radius:var(--radius-lg);overflow:hidden;cursor:pointer;flex-shrink:0}.comp-card[data-v-47ca40b0]:hover{border-color:var(--text3)}.comp-card.leak[data-v-47ca40b0]{border-left:2px solid var(--red);border-radius:0 var(--radius-lg) var(--radius-lg) 0}.comp-card.expanded[data-v-47ca40b0]{border-color:var(--purple)}.comp-header[data-v-47ca40b0]{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;gap:8px}.comp-identity[data-v-47ca40b0]{display:flex;align-items:baseline;gap:6px;min-width:0;flex:1}.comp-name[data-v-47ca40b0]{font-size:12px;font-weight:500;color:var(--text);flex-shrink:0}.comp-file[data-v-47ca40b0]{font-size:11px;color:var(--text3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.comp-meta[data-v-47ca40b0]{display:flex;align-items:center;gap:5px;flex-shrink:0}.refs-preview[data-v-47ca40b0]{display:flex;flex-wrap:wrap;gap:4px;padding:0 12px 8px;align-items:center}.ref-chip[data-v-47ca40b0]{display:inline-flex;align-items:center;gap:4px;padding:2px 7px;border-radius:4px;background:var(--bg2);border:.5px solid var(--border);font-size:11px;font-family:var(--mono);max-width:220px;overflow:hidden}.ref-chip--reactive[data-v-47ca40b0]{border-color:color-mix(in srgb,var(--purple) 40%,var(--border));background:color-mix(in srgb,var(--purple) 8%,var(--bg2))}.ref-chip--computed[data-v-47ca40b0]{border-color:color-mix(in srgb,var(--blue) 40%,var(--border));background:color-mix(in srgb,var(--blue) 8%,var(--bg2))}.ref-chip-key[data-v-47ca40b0]{color:var(--text2);flex-shrink:0}.ref-chip-val[data-v-47ca40b0]{color:var(--teal);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.comp-detail[data-v-47ca40b0]{padding:4px 12px 12px;border-top:.5px solid var(--border);display:flex;flex-direction:column;gap:3px}.leak-banner[data-v-47ca40b0]{background:color-mix(in srgb,var(--red) 12%,transparent);border:.5px solid color-mix(in srgb,var(--red) 40%,var(--border));border-radius:var(--radius);padding:6px 10px;font-size:11px;color:var(--red);margin-bottom:6px;font-family:var(--mono)}.section-label[data-v-47ca40b0]{font-size:10px;font-weight:500;text-transform:uppercase;letter-spacing:.4px;color:var(--text3);margin-top:6px;margin-bottom:3px}.ref-row[data-v-47ca40b0]{display:flex;align-items:center;gap:8px;padding:3px 0}.ref-key[data-v-47ca40b0]{min-width:90px;color:var(--text2);flex-shrink:0}.ref-val[data-v-47ca40b0]{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--teal)}.lc-row[data-v-47ca40b0]{display:flex;align-items:center;gap:8px;padding:2px 0}.lc-dot[data-v-47ca40b0]{width:6px;height:6px;border-radius:50%;flex-shrink:0}.ref-chip--shared[data-v-47ca40b0]{border-color:color-mix(in srgb,var(--amber) 50%,var(--border));background:color-mix(in srgb,var(--amber) 10%,var(--bg2))}.ref-chip-shared-dot[data-v-47ca40b0]{display:inline-block;width:5px;height:5px;border-radius:50%;background:var(--amber);flex-shrink:0;margin-left:1px}.global-banner[data-v-47ca40b0]{display:flex;align-items:flex-start;gap:8px;background:color-mix(in srgb,var(--amber) 10%,transparent);border:.5px solid color-mix(in srgb,var(--amber) 40%,var(--border));border-radius:var(--radius);padding:7px 10px;font-size:11px;color:var(--text2);margin-bottom:6px}.global-dot[data-v-47ca40b0]{display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--amber);flex-shrink:0;margin-top:3px}.badge-amber[data-v-47ca40b0]{background:color-mix(in srgb,var(--amber) 15%,transparent);color:color-mix(in srgb,var(--amber) 80%,var(--text));border:.5px solid color-mix(in srgb,var(--amber) 40%,var(--border))}.history-list[data-v-47ca40b0]{display:flex;flex-direction:column;gap:1px;background:var(--bg2);border-radius:var(--radius);padding:4px 8px;max-height:180px;overflow-y:auto}.history-row[data-v-47ca40b0]{display:flex;align-items:center;gap:8px;padding:2px 0;font-size:11px;font-family:var(--mono);border-bottom:.5px solid var(--border)}.history-row[data-v-47ca40b0]:last-child{border-bottom:none}.history-time[data-v-47ca40b0]{min-width:52px;color:var(--text3);flex-shrink:0}.history-key[data-v-47ca40b0]{min-width:80px;color:var(--text2);flex-shrink:0}.history-val[data-v-47ca40b0]{color:var(--amber);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.stat-card[data-v-47ca40b0]{background:var(--bg3);border:.5px solid var(--border);border-radius:var(--radius-lg);padding:10px 14px}.stat-label[data-v-47ca40b0]{font-size:10px;font-weight:500;text-transform:uppercase;letter-spacing:.4px;color:var(--text3);margin-bottom:4px}.stat-val[data-v-47ca40b0]{font-size:22px;font-weight:500;line-height:1;color:var(--text)}.ref-key--clickable[data-v-47ca40b0]{cursor:pointer;text-decoration:underline dotted var(--text3);text-underline-offset:2px}.ref-key--clickable[data-v-47ca40b0]:hover{color:var(--purple);text-decoration-color:var(--purple)}.edit-btn[data-v-47ca40b0]{font-size:10px;padding:1px 6px;border-radius:var(--radius);border:.5px solid var(--border);background:transparent;color:var(--text3);cursor:pointer;margin-left:auto;flex-shrink:0;font-family:var(--mono)}.edit-btn[data-v-47ca40b0]:hover{border-color:var(--purple);color:var(--purple);background:color-mix(in srgb,var(--purple) 8%,transparent)}.lookup-panel[data-v-47ca40b0]{flex-shrink:0;border:.5px solid var(--border);border-radius:var(--radius-lg);background:var(--bg3);overflow:hidden}.lookup-header[data-v-47ca40b0]{display:flex;align-items:center;gap:6px;padding:7px 12px;border-bottom:.5px solid var(--border);background:var(--bg2)}.lookup-row[data-v-47ca40b0]{display:flex;align-items:center;gap:8px;padding:5px 12px;border-bottom:.5px solid var(--border)}.lookup-row[data-v-47ca40b0]:last-child{border-bottom:none}.edit-overlay[data-v-47ca40b0]{position:fixed;top:0;right:0;bottom:0;left:0;background:#0006;z-index:100;display:flex;align-items:center;justify-content:center}.edit-dialog[data-v-47ca40b0]{background:var(--bg1, var(--bg2));border:.5px solid var(--border);border-radius:var(--radius-lg);padding:14px 16px;width:380px;max-width:92vw;display:flex;flex-direction:column;gap:6px;box-shadow:0 8px 32px #0000004d}.edit-dialog-header[data-v-47ca40b0]{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--text2);margin-bottom:2px}.edit-textarea[data-v-47ca40b0]{width:100%;font-family:var(--mono);font-size:12px;padding:8px 10px;background:var(--bg2);border:.5px solid var(--border);border-radius:var(--radius);color:var(--text);resize:vertical;outline:none}.edit-textarea[data-v-47ca40b0]:focus{border-color:var(--purple)}.edit-error[data-v-47ca40b0]{color:var(--red);font-family:var(--mono)}.edit-actions[data-v-47ca40b0]{display:flex;gap:6px;padding-top:4px}.slide-enter-active[data-v-47ca40b0],.slide-leave-active[data-v-47ca40b0]{transition:opacity .15s,transform .15s}.slide-enter-from[data-v-47ca40b0],.slide-leave-to[data-v-47ca40b0]{opacity:0;transform:translateY(6px)}.fade-enter-active[data-v-47ca40b0],.fade-leave-active[data-v-47ca40b0]{transition:opacity .15s}.fade-enter-from[data-v-47ca40b0],.fade-leave-to[data-v-47ca40b0]{opacity:0}.jump-btn[data-v-47ca40b0]{font-size:10px;padding:1px 6px;border:.5px solid var(--border);border-radius:var(--radius);background:transparent;color:var(--text3);cursor:pointer;flex-shrink:0;font-family:var(--mono)}.jump-btn[data-v-47ca40b0]:hover{border-color:var(--teal);color:var(--teal);background:color-mix(in srgb,var(--teal) 8%,transparent)}.view[data-v-6dbeb710]{display:flex;flex-direction:column;height:100%;overflow:hidden;padding:12px;gap:10px}.controls[data-v-6dbeb710]{display:flex;align-items:center;gap:8px;flex-shrink:0;flex-wrap:wrap}.mode-group[data-v-6dbeb710]{display:flex;gap:2px}.threshold-group[data-v-6dbeb710]{display:flex;align-items:center;gap:6px}.stats-row[data-v-6dbeb710]{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;flex-shrink:0}.stat-sub[data-v-6dbeb710]{margin-top:4px;font-size:11px;color:var(--text3)}.inspector[data-v-6dbeb710]{display:grid;grid-template-columns:minmax(220px,280px) minmax(0,1fr) minmax(260px,320px);gap:12px;flex:1;min-height:0}.roots-panel[data-v-6dbeb710],.tree-panel[data-v-6dbeb710],.detail-panel[data-v-6dbeb710]{border:.5px solid var(--border);border-radius:var(--radius-lg);background:var(--bg3);min-height:0}.roots-panel[data-v-6dbeb710],.detail-panel[data-v-6dbeb710]{display:flex;flex-direction:column;overflow:auto;padding:12px;gap:8px}.panel-title[data-v-6dbeb710]{font-size:10px;font-weight:500;text-transform:uppercase;letter-spacing:.4px;color:var(--text3)}.root-item[data-v-6dbeb710]{display:flex;align-items:center;justify-content:space-between;gap:8px;width:100%;padding:10px 12px;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg2);color:var(--text);text-align:left}.root-item.active[data-v-6dbeb710]{border-color:var(--teal);background:color-mix(in srgb,var(--teal) 16%,var(--bg2))}.root-label[data-v-6dbeb710]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.root-copy[data-v-6dbeb710]{display:flex;flex-direction:column;min-width:0}.root-sub[data-v-6dbeb710]{font-size:11px}.root-meta[data-v-6dbeb710]{color:var(--text3);font-size:11px}.tree-panel[data-v-6dbeb710]{display:flex;flex-direction:column;overflow:hidden}.tree-toolbar[data-v-6dbeb710]{padding:12px;border-bottom:.5px solid var(--border)}.search-input[data-v-6dbeb710]{width:100%;padding:10px 12px;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg2);color:var(--text)}.tree-frame[data-v-6dbeb710]{flex:1;min-height:0;overflow:auto;padding:12px}[data-v-6dbeb710] .tree-canvas{display:inline-block;min-width:100%;width:max-content}[data-v-6dbeb710] .tree-node{margin-bottom:4px}[data-v-6dbeb710] .tree-row{display:grid;grid-template-columns:8px 18px minmax(0,1fr) auto;align-items:center;gap:6px;min-width:0;width:100%;padding:4px 8px;padding-left:calc(8px + (var(--tree-depth, 0) * 16px));border:1px solid transparent;border-radius:var(--radius);cursor:pointer;white-space:nowrap}[data-v-6dbeb710] .tree-row:hover{background:var(--bg2)}[data-v-6dbeb710] .tree-row.selected{background:color-mix(in srgb,var(--teal) 12%,var(--bg2));border-color:var(--teal)}[data-v-6dbeb710] .tree-row.hot{box-shadow:inset 2px 0 0 var(--red)}[data-v-6dbeb710] .tree-toggle{width:16px;height:16px;border:none;background:transparent;color:var(--text3);padding:0;font-size:14px;display:inline-flex;align-items:center;justify-content:center}[data-v-6dbeb710] .tree-toggle:disabled{cursor:default}[data-v-6dbeb710] .tree-toggle.empty{opacity:0}[data-v-6dbeb710] .tree-rail{display:block;width:2px;height:14px;border-radius:999px;background:color-mix(in srgb,var(--border) 75%,transparent)}[data-v-6dbeb710] .tree-copy{display:flex;align-items:center;min-width:0;gap:6px;overflow:hidden}[data-v-6dbeb710] .tree-name{font-size:12px;color:var(--text);min-width:0;overflow:hidden;text-overflow:ellipsis}[data-v-6dbeb710] .tree-badges{display:flex;gap:6px;flex-shrink:0;overflow:hidden}[data-v-6dbeb710] .tree-badge{border:1px solid var(--border);border-radius:999px;padding:2px 7px;font-size:10px;color:var(--text3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:160px}[data-v-6dbeb710] .tree-metrics{display:flex;align-items:center;min-width:92px;justify-content:flex-end;flex-shrink:0;gap:6px}[data-v-6dbeb710] .tree-metric-pill{display:inline-flex;align-items:center;justify-content:center;min-width:78px;padding:2px 8px;border:1px solid var(--border);border-radius:999px;background:var(--bg2);font-size:10px;color:var(--text3)}[data-v-6dbeb710] .tree-persistent-pill{display:inline-flex;align-items:center;padding:2px 8px;border:1px solid color-mix(in srgb,var(--amber) 55%,var(--border));border-radius:999px;background:color-mix(in srgb,var(--amber) 10%,var(--bg2));font-size:10px;color:color-mix(in srgb,var(--amber) 80%,var(--text))}[data-v-6dbeb710] .tree-hydration-pill{display:inline-flex;align-items:center;padding:2px 8px;border:1px solid color-mix(in srgb,var(--teal) 55%,var(--border));border-radius:999px;background:color-mix(in srgb,var(--teal) 10%,var(--bg2));font-size:10px;color:color-mix(in srgb,var(--teal) 80%,var(--text))}[data-v-6dbeb710] .tree-children{margin-left:7px;padding-left:11px;border-left:1px solid color-mix(in srgb,var(--border) 72%,transparent)}.detail-empty[data-v-6dbeb710]{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text3);font-size:12px}.detail-header[data-v-6dbeb710]{display:flex;align-items:center;justify-content:space-between}.meta-grid[data-v-6dbeb710]{display:grid;grid-template-columns:auto 1fr;gap:4px 12px}.detail-pill-row[data-v-6dbeb710]{display:flex;flex-wrap:wrap;gap:6px}.detail-pill[data-v-6dbeb710]{border:1px solid var(--border);border-radius:999px;padding:4px 8px;background:var(--bg2);font-size:11px}.detail-pill.hot[data-v-6dbeb710]{border-color:color-mix(in srgb,var(--red) 50%,var(--border));color:var(--red)}.detail-pill.persistent[data-v-6dbeb710]{border-color:color-mix(in srgb,var(--amber) 55%,var(--border));color:color-mix(in srgb,var(--amber) 80%,var(--text))}.detail-pill.hydrated[data-v-6dbeb710]{border-color:color-mix(in srgb,var(--teal) 55%,var(--border));color:color-mix(in srgb,var(--teal) 80%,var(--text))}.detail-pill.muted[data-v-6dbeb710]{color:var(--text3);border-color:var(--border)}.section-label[data-v-6dbeb710]{font-size:10px;font-weight:500;text-transform:uppercase;letter-spacing:.4px;color:var(--text3);margin-top:8px;margin-bottom:4px}.trigger-item[data-v-6dbeb710]{background:var(--bg2);border-radius:var(--radius);padding:4px 8px;margin-bottom:3px;color:var(--text2)}[data-v-6dbeb710] .tree-jump-btn{display:none;padding:0 4px;border:none;background:transparent;color:var(--text3);font-size:11px;cursor:pointer;line-height:1;flex-shrink:0}[data-v-6dbeb710] .tree-row:hover .tree-jump-btn,[data-v-6dbeb710] .tree-row.selected .tree-jump-btn{display:inline-flex}[data-v-6dbeb710] .tree-jump-btn:hover{color:var(--teal)}.jump-btn[data-v-6dbeb710]{font-size:10px;padding:1px 6px;border:.5px solid var(--border);border-radius:var(--radius);background:transparent;color:var(--text3);cursor:pointer;flex-shrink:0;font-family:var(--mono)}.jump-btn[data-v-6dbeb710]:hover{border-color:var(--teal);color:var(--teal);background:color-mix(in srgb,var(--teal) 8%,transparent)}.route-select[data-v-6dbeb710]{padding:3px 7px;border:.5px solid var(--border);border-radius:var(--radius);background:var(--bg2);color:var(--text);font-size:11px;cursor:pointer;max-width:140px}.timeline-list[data-v-6dbeb710]{display:flex;flex-direction:column;gap:1px;background:var(--bg2);border-radius:var(--radius);padding:4px 8px;max-height:200px;overflow-y:auto}.timeline-row[data-v-6dbeb710]{display:flex;align-items:center;gap:6px;padding:2px 0;font-size:11px;border-bottom:.5px solid var(--border);min-width:0}.timeline-row[data-v-6dbeb710]:last-child{border-bottom:none}.timeline-kind[data-v-6dbeb710]{flex-shrink:0;font-size:10px;font-weight:500;min-width:40px}.timeline-kind.mount[data-v-6dbeb710]{color:var(--teal)}.timeline-kind.update[data-v-6dbeb710]{color:var(--amber)}.timeline-time[data-v-6dbeb710]{flex-shrink:0;min-width:52px;color:var(--text3)}.timeline-dur[data-v-6dbeb710]{flex-shrink:0;min-width:38px;color:var(--text2)}.timeline-trigger[data-v-6dbeb710]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text3);flex:1;min-width:0}.timeline-route[data-v-6dbeb710]{flex-shrink:0;color:var(--text3);font-size:10px}@media(max-width:1180px){.inspector[data-v-6dbeb710]{grid-template-columns:minmax(200px,240px) minmax(0,1fr)}.detail-panel[data-v-6dbeb710]{grid-column:1 / -1;max-height:220px}}.timeline-root[data-v-da869dac]{display:flex;flex-direction:column;height:100%;overflow:hidden}.stats-row[data-v-da869dac]{display:flex;gap:10px;padding:12px 14px 0;flex-shrink:0}.stat-card[data-v-da869dac]{background:var(--bg2);border:.5px solid var(--border);border-radius:var(--radius);padding:8px 14px;min-width:72px;text-align:center}.stat-val[data-v-da869dac]{font-size:20px;font-weight:600;font-family:var(--mono);line-height:1.1}.stat-unit[data-v-da869dac]{font-size:12px;opacity:.6;margin-left:1px}.stat-label[data-v-da869dac]{font-size:10px;color:var(--text3);margin-top:2px;text-transform:uppercase;letter-spacing:.4px}.toolbar[data-v-da869dac]{display:flex;align-items:center;gap:8px;padding:10px 14px;flex-shrink:0;border-bottom:.5px solid var(--border)}.search-input[data-v-da869dac]{flex:1;max-width:260px}.filter-group[data-v-da869dac]{display:flex;gap:4px}.content-area[data-v-da869dac]{display:flex;flex:1;overflow:hidden;min-height:0}.table-pane[data-v-da869dac]{flex:1;overflow:hidden auto;min-width:0}.bar-cell[data-v-da869dac]{width:200px;padding:4px 8px}.bar-track[data-v-da869dac]{position:relative;height:8px;background:var(--bg2);border-radius:4px;overflow:hidden}.bar-fill[data-v-da869dac]{position:absolute;top:0;height:100%;min-width:3px;border-radius:4px;transition:width .15s}.detail-panel[data-v-da869dac]{width:260px;flex-shrink:0;border-left:.5px solid var(--border);overflow-y:auto;background:var(--bg3);padding:0 0 16px}.panel-header[data-v-da869dac]{display:flex;align-items:center;justify-content:space-between;padding:10px 14px 8px;border-bottom:.5px solid var(--border);position:sticky;top:0;background:var(--bg3);z-index:1}.panel-title[data-v-da869dac]{font-family:var(--mono);font-size:13px;font-weight:500}.close-btn[data-v-da869dac]{border:none;background:transparent;color:var(--text3);font-size:11px;padding:2px 6px;cursor:pointer}.panel-section[data-v-da869dac]{padding:10px 14px 6px;border-bottom:.5px solid var(--border)}.panel-section-title[data-v-da869dac]{font-size:10px;font-weight:500;color:var(--text3);text-transform:uppercase;letter-spacing:.4px;margin-bottom:8px}.panel-row[data-v-da869dac]{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:3px 0;font-size:12px}.panel-key[data-v-da869dac]{color:var(--text3);flex-shrink:0}.panel-val[data-v-da869dac]{color:var(--text);text-align:right;word-break:break-all}.cancel-notice[data-v-da869dac],.active-notice[data-v-da869dac]{margin:10px 14px 0;font-size:11px;line-height:1.6;padding:8px 10px;border-radius:var(--radius)}.cancel-notice[data-v-da869dac]{background:#e24b4a1a;color:var(--red);border:.5px solid rgb(226 75 74 / 30%)}.active-notice[data-v-da869dac]{background:#7f77dd1a;color:var(--purple);border:.5px solid rgb(127 119 221 / 30%)}code[data-v-da869dac]{font-family:var(--mono);font-size:10px;background:#00000026;padding:1px 4px;border-radius:3px}.panel-slide-enter-active[data-v-da869dac],.panel-slide-leave-active[data-v-da869dac]{transition:transform .18s ease,opacity .18s ease}.panel-slide-enter-from[data-v-da869dac],.panel-slide-leave-to[data-v-da869dac]{transform:translate(12px);opacity:0}#app-root[data-v-08e3ddb5]{display:flex;flex-direction:column;height:100vh;overflow:hidden}.tabbar[data-v-08e3ddb5]{display:flex;align-items:center;gap:2px;padding:8px 12px 0;border-bottom:.5px solid var(--border);background:var(--bg3);flex-shrink:0}.tabbar-brand[data-v-08e3ddb5]{font-size:11px;font-weight:500;color:var(--purple);letter-spacing:.5px;margin-right:12px;padding-bottom:8px}.tab-btn[data-v-08e3ddb5]{border:none;border-bottom:2px solid transparent;border-radius:0;background:transparent;color:var(--text3);font-size:12px;padding:6px 12px 8px;cursor:pointer;transition:color .12s,border-color .12s;display:flex;align-items:center;gap:5px}.tab-btn[data-v-08e3ddb5]:hover{color:var(--text);background:transparent}.tab-btn.active[data-v-08e3ddb5]{color:var(--purple);border-bottom-color:var(--purple)}.tab-icon[data-v-08e3ddb5]{font-size:10px;opacity:.6}.tab-content[data-v-08e3ddb5]{flex:1;overflow:hidden;display:flex;flex-direction:column}*{box-sizing:border-box;margin:0;padding:0}body{font-family:var(--font);background:var(--bg);color:var(--text);font-size:13px;line-height:1.5}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}button{font-family:var(--font);font-size:12px;cursor:pointer;border:.5px solid var(--border);background:transparent;color:var(--text2);padding:4px 10px;border-radius:var(--radius);transition:background .12s}button:hover{background:var(--bg2)}button:active{transform:scale(.98)}button.active{background:#7f77dd26;color:var(--purple);border-color:var(--purple)}button.danger-active{background:#e24b4a1f;color:var(--red);border-color:var(--red)}button.success-active{background:#1d9e751f;color:var(--teal);border-color:var(--teal)}input[type=text],input[type=search]{font-family:var(--font);font-size:12px;border:.5px solid var(--border);background:var(--bg2);color:var(--text);padding:5px 10px;border-radius:var(--radius);outline:none;width:100%}input[type=text]:focus,input[type=search]:focus{border-color:var(--purple);box-shadow:0 0 0 2px #7f77dd33}input[type=range]{accent-color:var(--purple);cursor:pointer}.mono{font-family:var(--mono)}.muted{color:var(--text3)}.text-sm{font-size:11px}.text-xs{font-size:10px}.bold{font-weight:500}.badge{display:inline-block;font-size:10px;font-weight:500;padding:2px 7px;border-radius:99px;white-space:nowrap}.badge-ok{background:#1d9e7526;color:var(--teal)}.badge-err{background:#e24b4a1f;color:var(--red)}.badge-warn{background:#ef9f2726;color:var(--amber)}.badge-info{background:#378add1f;color:var(--blue)}.badge-gray{background:var(--bg2);color:var(--text3);border:.5px solid var(--border)}.badge-purple{background:#7f77dd26;color:var(--purple)}.card{background:var(--bg3);border:.5px solid var(--border);border-radius:var(--radius-lg);padding:12px 14px}.data-table{width:100%;border-collapse:collapse;font-size:12px}.data-table th{text-align:left;font-size:10px;font-weight:500;color:var(--text3);padding:6px 8px;border-bottom:.5px solid var(--border);text-transform:uppercase;letter-spacing:.4px;white-space:nowrap}.data-table td{padding:8px;border-bottom:.5px solid var(--border);color:var(--text);vertical-align:middle}.data-table tr:hover td{background:var(--bg2);cursor:pointer}.data-table tr.selected td{background:#7f77dd14}.stat-card{background:var(--bg2);border-radius:var(--radius);padding:10px 12px}.stat-label{font-size:10px;color:var(--text3);text-transform:uppercase;letter-spacing:.4px;margin-bottom:3px}.stat-val{font-size:20px;font-weight:500}.flex{display:flex}.items-center{align-items:center}.gap-2{gap:8px}.gap-3{gap:12px}.flex-1{flex:1}.overflow-auto{overflow:auto}.p-3{padding:12px}.p-4{padding:16px}.mb-2{margin-bottom:8px}.mb-3{margin-bottom:12px}.mt-2{margin-top:8px}
|
package/client/dist/index.html
CHANGED
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
body { font-family: var(--font); background: var(--bg); color: var(--text); font-size: 13px; }
|
|
39
39
|
#app { height: 100vh; display: flex; flex-direction: column; }
|
|
40
40
|
</style>
|
|
41
|
-
<script type="module" crossorigin src="/assets/index-
|
|
42
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
41
|
+
<script type="module" crossorigin src="/assets/index-B1qWBxxI.js"></script>
|
|
42
|
+
<link rel="stylesheet" crossorigin href="/assets/index-XYlDyaMH.css">
|
|
43
43
|
</head>
|
|
44
44
|
<body>
|
|
45
45
|
<div id="app"></div>
|
|
@@ -81,6 +81,14 @@ export interface ComposableEntry {
|
|
|
81
81
|
route?: string
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
export interface RenderEvent {
|
|
85
|
+
kind: 'mount' | 'update'
|
|
86
|
+
t: number
|
|
87
|
+
durationMs: number
|
|
88
|
+
triggerKey?: string
|
|
89
|
+
route: string
|
|
90
|
+
}
|
|
91
|
+
|
|
84
92
|
export interface RenderEntry {
|
|
85
93
|
uid: number
|
|
86
94
|
name: string
|
|
@@ -91,10 +99,12 @@ export interface RenderEntry {
|
|
|
91
99
|
totalMs: number
|
|
92
100
|
avgMs: number
|
|
93
101
|
triggers: Array<{ key: string; type: string; timestamp: number }>
|
|
102
|
+
timeline: RenderEvent[]
|
|
94
103
|
rect?: { x: number; y: number; width: number; height: number; top: number; left: number }
|
|
95
104
|
parentUid?: number
|
|
96
105
|
isPersistent: boolean
|
|
97
106
|
isHydrationMount: boolean
|
|
107
|
+
route: string
|
|
98
108
|
}
|
|
99
109
|
|
|
100
110
|
export interface TransitionEntry {
|
|
@@ -6,11 +6,7 @@ const { composables: rawEntries, connected, clearComposables } = useObservatoryD
|
|
|
6
6
|
|
|
7
7
|
function clearSession() {
|
|
8
8
|
const origin = getObservatoryOrigin()
|
|
9
|
-
|
|
10
|
-
if (!origin) {
|
|
11
|
-
return
|
|
12
|
-
}
|
|
13
|
-
|
|
9
|
+
if (!origin) return
|
|
14
10
|
clearComposables()
|
|
15
11
|
window.top?.postMessage({ type: 'observatory:clear-composables' }, origin)
|
|
16
12
|
}
|
|
@@ -21,28 +17,17 @@ function clearSession() {
|
|
|
21
17
|
// same composable in different components are two separate rows.
|
|
22
18
|
|
|
23
19
|
function formatVal(v: unknown): string {
|
|
24
|
-
if (v === null)
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (v === undefined) {
|
|
29
|
-
return 'undefined'
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (typeof v === 'string') {
|
|
33
|
-
return `"${v}"`
|
|
34
|
-
}
|
|
35
|
-
|
|
20
|
+
if (v === null) return 'null'
|
|
21
|
+
if (v === undefined) return 'undefined'
|
|
22
|
+
if (typeof v === 'string') return `"${v}"`
|
|
36
23
|
if (typeof v === 'object') {
|
|
37
24
|
try {
|
|
38
25
|
const s = JSON.stringify(v)
|
|
39
|
-
|
|
40
26
|
return s.length > 80 ? s.slice(0, 80) + '…' : s
|
|
41
27
|
} catch {
|
|
42
28
|
return String(v)
|
|
43
29
|
}
|
|
44
30
|
}
|
|
45
|
-
|
|
46
31
|
return String(v)
|
|
47
32
|
}
|
|
48
33
|
|
|
@@ -50,6 +35,13 @@ function basename(file: string) {
|
|
|
50
35
|
return file.split('/').pop() ?? file
|
|
51
36
|
}
|
|
52
37
|
|
|
38
|
+
function openInEditor(file: string) {
|
|
39
|
+
if (!file || file === 'unknown') return
|
|
40
|
+
const origin = getObservatoryOrigin()
|
|
41
|
+
if (!origin) return
|
|
42
|
+
window.top?.postMessage({ type: 'observatory:open-in-editor', file }, origin)
|
|
43
|
+
}
|
|
44
|
+
|
|
53
45
|
const filter = ref('all')
|
|
54
46
|
const search = ref('')
|
|
55
47
|
const expanded = ref<string | null>(null)
|
|
@@ -63,20 +55,10 @@ const counts = computed(() => ({
|
|
|
63
55
|
|
|
64
56
|
const filtered = computed(() => {
|
|
65
57
|
return entries.value.filter((entry) => {
|
|
66
|
-
if (filter.value === 'leak' && !entry.leak)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (filter.value === 'mounted' && entry.status !== 'mounted') {
|
|
71
|
-
return false
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (filter.value === 'unmounted' && entry.status !== 'unmounted') {
|
|
75
|
-
return false
|
|
76
|
-
}
|
|
77
|
-
|
|
58
|
+
if (filter.value === 'leak' && !entry.leak) return false
|
|
59
|
+
if (filter.value === 'mounted' && entry.status !== 'mounted') return false
|
|
60
|
+
if (filter.value === 'unmounted' && entry.status !== 'unmounted') return false
|
|
78
61
|
const q = search.value.toLowerCase()
|
|
79
|
-
|
|
80
62
|
if (q) {
|
|
81
63
|
const matchesName = entry.name.toLowerCase().includes(q)
|
|
82
64
|
const matchesFile = entry.componentFile.toLowerCase().includes(q)
|
|
@@ -88,12 +70,8 @@ const filtered = computed(() => {
|
|
|
88
70
|
return false
|
|
89
71
|
}
|
|
90
72
|
})
|
|
91
|
-
|
|
92
|
-
if (!matchesName && !matchesFile && !matchesRef && !matchesVal) {
|
|
93
|
-
return false
|
|
94
|
-
}
|
|
73
|
+
if (!matchesName && !matchesFile && !matchesRef && !matchesVal) return false
|
|
95
74
|
}
|
|
96
|
-
|
|
97
75
|
return true
|
|
98
76
|
})
|
|
99
77
|
})
|
|
@@ -124,14 +102,8 @@ function lifecycleRows(entry: RuntimeComposableEntry) {
|
|
|
124
102
|
}
|
|
125
103
|
|
|
126
104
|
function typeBadgeClass(type: string) {
|
|
127
|
-
if (type === 'computed')
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (type === 'reactive') {
|
|
132
|
-
return 'badge-purple'
|
|
133
|
-
}
|
|
134
|
-
|
|
105
|
+
if (type === 'computed') return 'badge-info'
|
|
106
|
+
if (type === 'reactive') return 'badge-purple'
|
|
135
107
|
return 'badge-gray'
|
|
136
108
|
}
|
|
137
109
|
|
|
@@ -141,12 +113,8 @@ function typeBadgeClass(type: string) {
|
|
|
141
113
|
const lookupKey = ref<string | null>(null)
|
|
142
114
|
|
|
143
115
|
const lookupResults = computed(() => {
|
|
144
|
-
if (!lookupKey.value)
|
|
145
|
-
return []
|
|
146
|
-
}
|
|
147
|
-
|
|
116
|
+
if (!lookupKey.value) return []
|
|
148
117
|
const key = lookupKey.value
|
|
149
|
-
|
|
150
118
|
return entries.value.filter((e) => key in e.refs)
|
|
151
119
|
})
|
|
152
120
|
|
|
@@ -176,9 +144,7 @@ function openEdit(id: string, key: string, currentValue: unknown) {
|
|
|
176
144
|
}
|
|
177
145
|
|
|
178
146
|
function applyEdit() {
|
|
179
|
-
if (!editTarget.value)
|
|
180
|
-
return
|
|
181
|
-
}
|
|
147
|
+
if (!editTarget.value) return
|
|
182
148
|
|
|
183
149
|
let parsed: unknown
|
|
184
150
|
|
|
@@ -187,15 +153,11 @@ function applyEdit() {
|
|
|
187
153
|
editError.value = ''
|
|
188
154
|
} catch (err) {
|
|
189
155
|
editError.value = `Invalid JSON: ${(err as Error).message}`
|
|
190
|
-
|
|
191
156
|
return
|
|
192
157
|
}
|
|
193
158
|
|
|
194
159
|
const origin = getObservatoryOrigin()
|
|
195
|
-
|
|
196
|
-
if (!origin) {
|
|
197
|
-
return
|
|
198
|
-
}
|
|
160
|
+
if (!origin) return
|
|
199
161
|
|
|
200
162
|
window.top?.postMessage(
|
|
201
163
|
{
|
|
@@ -356,7 +318,10 @@ function applyEdit() {
|
|
|
356
318
|
</div>
|
|
357
319
|
<div class="lc-row">
|
|
358
320
|
<span class="muted text-sm" style="min-width: 120px">defined in</span>
|
|
359
|
-
<span class="mono text-sm muted"
|
|
321
|
+
<span class="mono text-sm muted" style="display: flex; align-items: center; gap: 6px">
|
|
322
|
+
{{ entry.file }}:{{ entry.line }}
|
|
323
|
+
<button class="jump-btn" title="Open in editor" @click.stop="openInEditor(entry.file)">open ↗</button>
|
|
324
|
+
</span>
|
|
360
325
|
</div>
|
|
361
326
|
<div class="lc-row">
|
|
362
327
|
<span class="muted text-sm" style="min-width: 120px">route</span>
|
|
@@ -896,4 +861,22 @@ function applyEdit() {
|
|
|
896
861
|
.fade-leave-to {
|
|
897
862
|
opacity: 0;
|
|
898
863
|
}
|
|
864
|
+
|
|
865
|
+
.jump-btn {
|
|
866
|
+
font-size: 10px;
|
|
867
|
+
padding: 1px 6px;
|
|
868
|
+
border: 0.5px solid var(--border);
|
|
869
|
+
border-radius: var(--radius);
|
|
870
|
+
background: transparent;
|
|
871
|
+
color: var(--text3);
|
|
872
|
+
cursor: pointer;
|
|
873
|
+
flex-shrink: 0;
|
|
874
|
+
font-family: var(--mono);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
.jump-btn:hover {
|
|
878
|
+
border-color: var(--teal);
|
|
879
|
+
color: var(--teal);
|
|
880
|
+
background: color-mix(in srgb, var(--teal) 8%, transparent);
|
|
881
|
+
}
|
|
899
882
|
</style>
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { ref, computed, watch } from 'vue'
|
|
3
|
-
import { useObservatoryData, type InjectEntry, type ProvideEntry } from '../stores/observatory'
|
|
3
|
+
import { useObservatoryData, getObservatoryOrigin, type InjectEntry, type ProvideEntry } from '../stores/observatory'
|
|
4
4
|
|
|
5
5
|
interface TreeNodeData {
|
|
6
6
|
id: string
|
|
7
7
|
label: string
|
|
8
8
|
componentName: string
|
|
9
|
+
componentFile: string
|
|
9
10
|
type: 'provider' | 'consumer' | 'both' | 'error'
|
|
10
11
|
provides: Array<{
|
|
11
12
|
key: string
|
|
@@ -46,22 +47,44 @@ const H_GAP = 18
|
|
|
46
47
|
const { provideInject, connected } = useObservatoryData()
|
|
47
48
|
|
|
48
49
|
function nodeColor(node: TreeNodeData): string {
|
|
49
|
-
if (node.injects.some((entry) => !entry.ok))
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
if (node.injects.some((entry) => !entry.ok)) {
|
|
51
|
+
return 'var(--red)'
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (node.type === 'both') {
|
|
55
|
+
return 'var(--blue)'
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (node.type === 'provider') {
|
|
59
|
+
return 'var(--teal)'
|
|
60
|
+
}
|
|
61
|
+
|
|
52
62
|
return 'var(--text3)'
|
|
53
63
|
}
|
|
54
64
|
|
|
55
65
|
function matchesFilter(node: TreeNodeData, filter: string): boolean {
|
|
56
|
-
if (filter === 'all')
|
|
57
|
-
|
|
58
|
-
|
|
66
|
+
if (filter === 'all') {
|
|
67
|
+
return true
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (filter === 'warn') {
|
|
71
|
+
return node.injects.some((entry) => !entry.ok)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (filter === 'shadow') {
|
|
75
|
+
return node.provides.some((entry) => entry.isShadowing)
|
|
76
|
+
}
|
|
77
|
+
|
|
59
78
|
return node.provides.some((entry) => entry.key === filter) || node.injects.some((entry) => entry.key === filter)
|
|
60
79
|
}
|
|
61
80
|
|
|
62
81
|
function matchesSearch(node: TreeNodeData, query: string): boolean {
|
|
63
|
-
if (!query)
|
|
82
|
+
if (!query) {
|
|
83
|
+
return true
|
|
84
|
+
}
|
|
85
|
+
|
|
64
86
|
const q = query.toLowerCase()
|
|
87
|
+
|
|
65
88
|
return (
|
|
66
89
|
node.label.toLowerCase().includes(q) ||
|
|
67
90
|
node.componentName.toLowerCase().includes(q) ||
|
|
@@ -74,18 +97,24 @@ function matchesSearch(node: TreeNodeData, query: string): boolean {
|
|
|
74
97
|
* Count leaf nodes in a subtree iteratively to avoid stack overflow on
|
|
75
98
|
* pathologically deep provide/inject trees (e.g. every component re-providing
|
|
76
99
|
* the same key creates a chain as long as the component tree itself).
|
|
100
|
+
*
|
|
101
|
+
* @param {TreeNodeData} root - The root node of the subtree to count leaves for.
|
|
102
|
+
* @returns {number} The number of leaf nodes in the subtree.
|
|
77
103
|
*/
|
|
78
104
|
function countLeaves(root: TreeNodeData): number {
|
|
79
105
|
let count = 0
|
|
80
106
|
const stack: TreeNodeData[] = [root]
|
|
107
|
+
|
|
81
108
|
while (stack.length) {
|
|
82
109
|
const node = stack.pop()!
|
|
110
|
+
|
|
83
111
|
if (node.children.length === 0) {
|
|
84
112
|
count++
|
|
85
113
|
} else {
|
|
86
114
|
stack.push(...node.children)
|
|
87
115
|
}
|
|
88
116
|
}
|
|
117
|
+
|
|
89
118
|
return count
|
|
90
119
|
}
|
|
91
120
|
|
|
@@ -116,6 +145,7 @@ function formatValuePreview(value: unknown) {
|
|
|
116
145
|
|
|
117
146
|
if (typeof value === 'object') {
|
|
118
147
|
const keys = Object.keys(value as Record<string, unknown>)
|
|
148
|
+
|
|
119
149
|
return keys.length ? `{ ${keys.join(', ')} }` : '{}'
|
|
120
150
|
}
|
|
121
151
|
|
|
@@ -142,6 +172,17 @@ function basename(file: string) {
|
|
|
142
172
|
return file.split('/').pop() ?? file
|
|
143
173
|
}
|
|
144
174
|
|
|
175
|
+
function openInEditor(file: string) {
|
|
176
|
+
if (!file || file === 'unknown') {
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
const origin = getObservatoryOrigin()
|
|
180
|
+
if (!origin) {
|
|
181
|
+
return
|
|
182
|
+
}
|
|
183
|
+
window.top?.postMessage({ type: 'observatory:open-in-editor', file }, origin)
|
|
184
|
+
}
|
|
185
|
+
|
|
145
186
|
function componentId(entry: ProvideEntry | InjectEntry) {
|
|
146
187
|
return String(entry.componentUid)
|
|
147
188
|
}
|
|
@@ -162,6 +203,7 @@ const nodes = computed<TreeNodeData[]>(() => {
|
|
|
162
203
|
id,
|
|
163
204
|
label: basename(entry.componentFile),
|
|
164
205
|
componentName: entry.componentName ?? basename(entry.componentFile),
|
|
206
|
+
componentFile: entry.componentFile,
|
|
165
207
|
type: 'consumer',
|
|
166
208
|
provides: [],
|
|
167
209
|
injects: [],
|
|
@@ -278,16 +320,22 @@ const allKeys = computed(() => {
|
|
|
278
320
|
})
|
|
279
321
|
|
|
280
322
|
const visibleNodes = computed<TreeNodeData[]>(() => {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
323
|
+
/**
|
|
324
|
+
* Iterative post-order prune — avoids stack overflow on deep trees.
|
|
325
|
+
* Processes nodes bottom-up so each parent can inspect its children's
|
|
326
|
+
* already-computed visibility before deciding its own.
|
|
327
|
+
* @param {TreeNodeData} root - The root node of the tree to prune.
|
|
328
|
+
* @returns {TreeNodeData | null} The pruned tree node or null if the node is not visible.
|
|
329
|
+
*/
|
|
284
330
|
function pruneIterative(root: TreeNodeData): TreeNodeData | null {
|
|
285
331
|
// Phase 1: collect nodes in pre-order (parent before children)
|
|
286
332
|
const order: TreeNodeData[] = []
|
|
287
333
|
const stack: TreeNodeData[] = [root]
|
|
334
|
+
|
|
288
335
|
while (stack.length) {
|
|
289
336
|
const node = stack.pop()!
|
|
290
337
|
order.push(node)
|
|
338
|
+
|
|
291
339
|
for (let i = node.children.length - 1; i >= 0; i--) {
|
|
292
340
|
stack.push(node.children[i])
|
|
293
341
|
}
|
|
@@ -295,6 +343,7 @@ const visibleNodes = computed<TreeNodeData[]>(() => {
|
|
|
295
343
|
|
|
296
344
|
// Phase 2: process in reverse pre-order (children before parents)
|
|
297
345
|
const pruned = new Map<TreeNodeData, TreeNodeData | null>()
|
|
346
|
+
|
|
298
347
|
for (let i = order.length - 1; i >= 0; i--) {
|
|
299
348
|
const node = order[i]
|
|
300
349
|
const visibleChildren = node.children
|
|
@@ -322,6 +371,7 @@ watch([visibleNodes, selectedNode], ([currentNodes, currentSelected]) => {
|
|
|
322
371
|
|
|
323
372
|
const ids = new Set<string>()
|
|
324
373
|
const stack = [...currentNodes]
|
|
374
|
+
|
|
325
375
|
while (stack.length) {
|
|
326
376
|
const node = stack.pop()!
|
|
327
377
|
ids.add(node.id)
|
|
@@ -366,11 +416,13 @@ const layout = computed<LayoutNode[]>(() => {
|
|
|
366
416
|
// Push children in reverse so leftmost child is processed first
|
|
367
417
|
let childLeft = slotLeft
|
|
368
418
|
const childWork: WorkItem[] = []
|
|
419
|
+
|
|
369
420
|
for (const child of node.children) {
|
|
370
421
|
const childLeaves = countLeaves(child)
|
|
371
422
|
childWork.push({ node: child, depth: depth + 1, slotLeft: childLeft, parentId: node.id })
|
|
372
423
|
childLeft += childLeaves * (NODE_W + H_GAP)
|
|
373
424
|
}
|
|
425
|
+
|
|
374
426
|
for (let i = childWork.length - 1; i >= 0; i--) {
|
|
375
427
|
stack.push(childWork[i])
|
|
376
428
|
}
|
|
@@ -483,6 +535,14 @@ const edges = computed<Edge[]>(() => {
|
|
|
483
535
|
<div v-if="selectedNode" class="detail-panel">
|
|
484
536
|
<div class="detail-header">
|
|
485
537
|
<span class="mono bold" style="font-size: 12px">{{ selectedNode.label }}</span>
|
|
538
|
+
<button
|
|
539
|
+
v-if="selectedNode.componentFile && selectedNode.componentFile !== 'unknown'"
|
|
540
|
+
class="jump-btn"
|
|
541
|
+
title="Open in editor"
|
|
542
|
+
@click="openInEditor(selectedNode.componentFile)"
|
|
543
|
+
>
|
|
544
|
+
open ↗
|
|
545
|
+
</button>
|
|
486
546
|
<button @click="selectedNode = null">×</button>
|
|
487
547
|
</div>
|
|
488
548
|
|
|
@@ -866,4 +926,22 @@ const edges = computed<Edge[]>(() => {
|
|
|
866
926
|
.inject-miss {
|
|
867
927
|
background: rgb(226 75 74 / 8%);
|
|
868
928
|
}
|
|
929
|
+
|
|
930
|
+
.jump-btn {
|
|
931
|
+
font-size: 10px;
|
|
932
|
+
padding: 1px 6px;
|
|
933
|
+
border: 0.5px solid var(--border);
|
|
934
|
+
border-radius: var(--radius);
|
|
935
|
+
background: transparent;
|
|
936
|
+
color: var(--text3);
|
|
937
|
+
cursor: pointer;
|
|
938
|
+
flex-shrink: 0;
|
|
939
|
+
font-family: var(--mono);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
.jump-btn:hover {
|
|
943
|
+
border-color: var(--teal);
|
|
944
|
+
color: var(--teal);
|
|
945
|
+
background: color-mix(in srgb, var(--teal) 8%, transparent);
|
|
946
|
+
}
|
|
869
947
|
</style>
|