nuxt-devtools-observatory 0.1.13 → 0.1.14
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 +70 -63
- package/client/dist/assets/index-Cs2LyjO_.js +17 -0
- package/client/dist/assets/index-P76p-0dT.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/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-f074866d]{display:flex;flex-direction:column;height:100%;overflow:hidden;padding:12px;gap:10px}.toolbar[data-v-f074866d]{display:flex;align-items:center;gap:6px;flex-shrink:0;flex-wrap:wrap}.split[data-v-f074866d]{display:flex;gap:12px;flex:1;overflow:hidden;min-height:0}.graph-area[data-v-f074866d]{flex:1;overflow:auto;border:.5px solid var(--border);border-radius:var(--radius-lg);padding:12px;background:var(--bg3)}.legend[data-v-f074866d]{display:flex;align-items:center;gap:12px;font-size:11px;color:var(--text2);margin-bottom:12px}.canvas-stage[data-v-f074866d]{display:flex;justify-content:center;align-items:flex-start;min-width:100%}.dot[data-v-f074866d]{width:8px;height:8px;border-radius:50%;display:inline-block;margin-right:2px}.canvas-wrap[data-v-f074866d]{position:relative}.edges-svg[data-v-f074866d]{position:absolute;top:0;left:0;pointer-events:none}.edge[data-v-f074866d]{stroke:var(--border);stroke-width:1.5}.graph-node[data-v-f074866d]{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-f074866d]:hover{border-color:var(--text3)}.graph-node.is-selected[data-v-f074866d]{border-color:var(--node-color);background:color-mix(in srgb,var(--node-color) 8%,transparent)}.node-dot[data-v-f074866d]{width:7px;height:7px;border-radius:50%;flex-shrink:0}.node-label[data-v-f074866d]{font-size:11px;flex:1;overflow:hidden;text-overflow:ellipsis}.badge-xs[data-v-f074866d]{font-size:9px;padding:1px 4px}.detail-panel[data-v-f074866d]{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-f074866d]{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-f074866d]{display:flex;align-items:center;justify-content:center;min-height:180px;color:var(--text3);font-size:12px}.detail-header[data-v-f074866d]{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.section-label[data-v-f074866d]{font-size:10px;font-weight:500;text-transform:uppercase;letter-spacing:.4px;color:var(--text3);margin:8px 0 5px}.detail-section[data-v-f074866d]{display:flex;flex-direction:column;min-height:0}.detail-list[data-v-f074866d]{display:flex;flex-direction:column;gap:3px;overflow:auto;max-height:220px;padding-right:2px}.provide-row[data-v-f074866d]{display:flex;flex-direction:column;gap:4px;padding:5px 8px;background:var(--bg2);border-radius:var(--radius);margin-bottom:3px}.row-warning[data-v-f074866d]{font-size:11px;color:var(--amber);padding:2px 0}.row-consumers[data-v-f074866d]{display:flex;flex-wrap:wrap;align-items:center;gap:4px;padding:2px 0}.consumer-chip[data-v-f074866d]{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-f074866d]{font-size:10px;padding:1px 6px;border-radius:4px}.scope-global[data-v-f074866d]{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-f074866d]{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-f074866d]{background:var(--bg3);border:.5px solid var(--border);color:var(--text3)}.row-main[data-v-f074866d]{display:flex;align-items:center;gap:8px;min-width:0}.row-key[data-v-f074866d]{min-width:100px;color:var(--text2);flex-shrink:0}.row-value-preview[data-v-f074866d]{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.row-toggle[data-v-f074866d]{padding:2px 8px;font-size:10px}.value-box[data-v-f074866d]{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-f074866d]{display:flex;align-items:center;gap:8px;padding:5px 8px;background:var(--bg2);border-radius:var(--radius);margin-bottom:3px}.row-from[data-v-f074866d]{margin-left:auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.inject-miss[data-v-f074866d]{background:#e24b4a14}.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-Cs2LyjO_.js"></script>
|
|
42
|
+
<link rel="stylesheet" crossorigin href="/assets/index-P76p-0dT.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,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed, defineComponent, h, ref, watch, type VNode } from 'vue'
|
|
3
|
-
import { useObservatoryData, type RenderEntry } from '../stores/observatory'
|
|
3
|
+
import { useObservatoryData, getObservatoryOrigin, type RenderEntry, type RenderEvent } from '../stores/observatory'
|
|
4
4
|
|
|
5
5
|
interface ComponentNode {
|
|
6
6
|
id: string
|
|
@@ -13,11 +13,13 @@ interface ComponentNode {
|
|
|
13
13
|
mountCount: number
|
|
14
14
|
avgMs: number
|
|
15
15
|
triggers: string[]
|
|
16
|
+
timeline: RenderEvent[]
|
|
16
17
|
children: ComponentNode[]
|
|
17
18
|
parentId?: string
|
|
18
19
|
parentLabel?: string
|
|
19
20
|
isPersistent: boolean
|
|
20
21
|
isHydrationMount: boolean
|
|
22
|
+
route: string
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
const TreeNode = defineComponent({
|
|
@@ -128,6 +130,20 @@ const TreeNode = defineComponent({
|
|
|
128
130
|
)
|
|
129
131
|
: null,
|
|
130
132
|
h('span', { class: 'tree-metric-pill' }, `${metric} ${metricLabel}`),
|
|
133
|
+
node.file && node.file !== 'unknown'
|
|
134
|
+
? h(
|
|
135
|
+
'button',
|
|
136
|
+
{
|
|
137
|
+
class: 'tree-jump-btn',
|
|
138
|
+
title: `Open ${node.file} in editor`,
|
|
139
|
+
onClick: (e: MouseEvent) => {
|
|
140
|
+
e.stopPropagation()
|
|
141
|
+
openInEditor(node.file)
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
'↗'
|
|
145
|
+
)
|
|
146
|
+
: null,
|
|
131
147
|
]),
|
|
132
148
|
]
|
|
133
149
|
),
|
|
@@ -156,6 +172,8 @@ const TreeNode = defineComponent({
|
|
|
156
172
|
const { renders, connected } = useObservatoryData()
|
|
157
173
|
|
|
158
174
|
const activeMode = ref<'count' | 'time'>('count')
|
|
175
|
+
// Route filter — '' means show all routes
|
|
176
|
+
const activeRoute = ref('')
|
|
159
177
|
// Separate thresholds per mode so switching modes doesn't produce nonsense results.
|
|
160
178
|
// Count: flag components that rendered 3+ times (1 hydration mount is normal).
|
|
161
179
|
// Time: flag components averaging 16ms+ (one animation frame budget).
|
|
@@ -222,10 +240,12 @@ function buildNodes(entries: RenderEntry[]) {
|
|
|
222
240
|
mountCount: entry.mountCount ?? 1,
|
|
223
241
|
avgMs: entry.avgMs,
|
|
224
242
|
triggers: entry.triggers.map(formatTrigger),
|
|
243
|
+
timeline: entry.timeline ?? [],
|
|
225
244
|
children: [],
|
|
226
245
|
parentId: entry.parentUid !== undefined ? String(entry.parentUid) : undefined,
|
|
227
246
|
isPersistent: Boolean(entry.isPersistent),
|
|
228
247
|
isHydrationMount: Boolean(entry.isHydrationMount),
|
|
248
|
+
route: entry.route ?? '/',
|
|
229
249
|
})
|
|
230
250
|
}
|
|
231
251
|
|
|
@@ -392,11 +412,24 @@ function subtreeHasHotNode(node: ComponentNode): boolean {
|
|
|
392
412
|
return isHot(node) || node.children.some((child) => subtreeHasHotNode(child))
|
|
393
413
|
}
|
|
394
414
|
|
|
415
|
+
function nodeMatchesRoute(node: ComponentNode): boolean {
|
|
416
|
+
if (!activeRoute.value) return true
|
|
417
|
+
// A component is visible for a route if it was first seen on that route
|
|
418
|
+
// OR if any of its timeline events happened on that route.
|
|
419
|
+
if (node.route === activeRoute.value) return true
|
|
420
|
+
return node.timeline.some((e) => e.route === activeRoute.value)
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function subtreeMatchesRoute(node: ComponentNode): boolean {
|
|
424
|
+
return nodeMatchesRoute(node) || node.children.some((child) => subtreeMatchesRoute(child))
|
|
425
|
+
}
|
|
426
|
+
|
|
395
427
|
function isVisibleRoot(node: ComponentNode, searchTerm: string): boolean {
|
|
396
428
|
const matchesCurrentSearch = treeMatches(node, searchTerm)
|
|
397
429
|
const matchesCurrentHeat = !activeHotOnly.value || subtreeHasHotNode(node)
|
|
430
|
+
const matchesCurrentRoute = !activeRoute.value || subtreeMatchesRoute(node)
|
|
398
431
|
|
|
399
|
-
return matchesCurrentSearch && matchesCurrentHeat
|
|
432
|
+
return matchesCurrentSearch && matchesCurrentHeat && matchesCurrentRoute
|
|
400
433
|
}
|
|
401
434
|
|
|
402
435
|
function pruneVisibleTree(node: ComponentNode, searchTerm: string): ComponentNode | null {
|
|
@@ -406,8 +439,9 @@ function pruneVisibleTree(node: ComponentNode, searchTerm: string): ComponentNod
|
|
|
406
439
|
|
|
407
440
|
const matchesCurrentSearch = !searchTerm || matchesSearch(node, searchTerm) || visibleChildren.length > 0
|
|
408
441
|
const matchesCurrentHeat = !activeHotOnly.value || isHot(node) || visibleChildren.length > 0
|
|
442
|
+
const matchesCurrentRoute = !activeRoute.value || nodeMatchesRoute(node) || visibleChildren.length > 0
|
|
409
443
|
|
|
410
|
-
if (!matchesCurrentSearch || !matchesCurrentHeat) {
|
|
444
|
+
if (!matchesCurrentSearch || !matchesCurrentHeat || !matchesCurrentRoute) {
|
|
411
445
|
return null
|
|
412
446
|
}
|
|
413
447
|
|
|
@@ -459,6 +493,17 @@ const appEntries = computed(() =>
|
|
|
459
493
|
}))
|
|
460
494
|
)
|
|
461
495
|
|
|
496
|
+
const knownRoutes = computed(() => {
|
|
497
|
+
const routes = new Set<string>()
|
|
498
|
+
for (const node of allComponents.value) {
|
|
499
|
+
if (node.route) routes.add(node.route)
|
|
500
|
+
for (const event of node.timeline) {
|
|
501
|
+
if (event.route) routes.add(event.route)
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return [...routes].sort()
|
|
505
|
+
})
|
|
506
|
+
|
|
462
507
|
const activeSelected = computed(() => allComponents.value.find((node) => node.id === activeSelectedId.value) ?? null)
|
|
463
508
|
const totalRenders = computed(() => allComponents.value.reduce((sum, node) => sum + node.rerenders + node.mountCount, 0))
|
|
464
509
|
const hotCount = computed(() => allComponents.value.filter((node) => isHot(node)).length)
|
|
@@ -617,9 +662,25 @@ function basename(file: string) {
|
|
|
617
662
|
)
|
|
618
663
|
}
|
|
619
664
|
|
|
665
|
+
function openInEditor(file: string) {
|
|
666
|
+
if (!file || file === 'unknown') return
|
|
667
|
+
const origin = getObservatoryOrigin()
|
|
668
|
+
if (!origin) return
|
|
669
|
+
window.top?.postMessage({ type: 'observatory:open-in-editor', file }, origin)
|
|
670
|
+
}
|
|
671
|
+
|
|
620
672
|
function pathLabel(node: ComponentNode) {
|
|
621
673
|
return node.path.join(' / ')
|
|
622
674
|
}
|
|
675
|
+
|
|
676
|
+
function formatMs(ms: number): string {
|
|
677
|
+
return ms < 1 ? '<1ms' : `${ms.toFixed(1)}ms`
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
function formatTimestamp(t: number): string {
|
|
681
|
+
// t is performance.now() — show as relative seconds from page load
|
|
682
|
+
return `+${(t / 1000).toFixed(2)}s`
|
|
683
|
+
}
|
|
623
684
|
</script>
|
|
624
685
|
|
|
625
686
|
<template>
|
|
@@ -642,6 +703,10 @@ function pathLabel(node: ComponentNode) {
|
|
|
642
703
|
<span class="mono text-sm">{{ activeThreshold }}{{ activeMode === 'count' ? '+ renders' : 'ms+' }}</span>
|
|
643
704
|
</div>
|
|
644
705
|
<button :class="{ active: activeHotOnly }" @click="activeHotOnly = !activeHotOnly">hot only</button>
|
|
706
|
+
<select v-model="activeRoute" class="route-select mono text-sm" title="Filter by route">
|
|
707
|
+
<option value="">all routes</option>
|
|
708
|
+
<option v-for="r in knownRoutes" :key="r" :value="r">{{ r }}</option>
|
|
709
|
+
</select>
|
|
645
710
|
<button :class="{ active: frozen }" style="margin-left: auto" @click="toggleFreeze">
|
|
646
711
|
{{ frozen ? 'unfreeze' : 'freeze snapshot' }}
|
|
647
712
|
</button>
|
|
@@ -744,7 +809,17 @@ function pathLabel(node: ComponentNode) {
|
|
|
744
809
|
<span class="muted text-sm">path</span>
|
|
745
810
|
<span class="mono text-sm">{{ pathLabel(activeSelected) }}</span>
|
|
746
811
|
<span class="muted text-sm">file</span>
|
|
747
|
-
<span class="mono text-sm muted"
|
|
812
|
+
<span class="mono text-sm muted" style="display: flex; align-items: center; gap: 6px">
|
|
813
|
+
{{ activeSelected.file }}
|
|
814
|
+
<button
|
|
815
|
+
v-if="activeSelected.file && activeSelected.file !== 'unknown'"
|
|
816
|
+
class="jump-btn"
|
|
817
|
+
title="Open in editor"
|
|
818
|
+
@click="openInEditor(activeSelected.file)"
|
|
819
|
+
>
|
|
820
|
+
open ↗
|
|
821
|
+
</button>
|
|
822
|
+
</span>
|
|
748
823
|
<span class="muted text-sm">file name</span>
|
|
749
824
|
<span class="mono text-sm">{{ basename(activeSelected.file) }}</span>
|
|
750
825
|
<span class="muted text-sm">parent</span>
|
|
@@ -778,6 +853,26 @@ function pathLabel(node: ComponentNode) {
|
|
|
778
853
|
<div class="section-label">triggers</div>
|
|
779
854
|
<div v-for="trigger in activeSelected.triggers" :key="trigger" class="trigger-item mono text-sm">{{ trigger }}</div>
|
|
780
855
|
<div v-if="!activeSelected.triggers.length" class="muted text-sm">no triggers recorded</div>
|
|
856
|
+
|
|
857
|
+
<div class="section-label" style="margin-top: 8px">
|
|
858
|
+
render timeline
|
|
859
|
+
<span class="muted" style="font-weight: 400; text-transform: none; letter-spacing: 0">
|
|
860
|
+
({{ activeSelected.timeline.length }})
|
|
861
|
+
</span>
|
|
862
|
+
</div>
|
|
863
|
+
<div v-if="!activeSelected.timeline.length" class="muted text-sm">no timeline events yet</div>
|
|
864
|
+
<div v-else class="timeline-list">
|
|
865
|
+
<div v-for="(event, idx) in [...activeSelected.timeline].reverse().slice(0, 30)" :key="idx" class="timeline-row">
|
|
866
|
+
<span class="timeline-kind mono" :class="event.kind">{{ event.kind }}</span>
|
|
867
|
+
<span class="timeline-time mono muted">{{ formatTimestamp(event.t) }}</span>
|
|
868
|
+
<span class="timeline-dur mono">{{ formatMs(event.durationMs) }}</span>
|
|
869
|
+
<span v-if="event.triggerKey" class="timeline-trigger mono muted">{{ event.triggerKey }}</span>
|
|
870
|
+
<span class="timeline-route mono muted" style="margin-left: auto">{{ event.route }}</span>
|
|
871
|
+
</div>
|
|
872
|
+
<div v-if="activeSelected.timeline.length > 30" class="muted text-sm" style="padding: 2px 0">
|
|
873
|
+
… {{ activeSelected.timeline.length - 30 }} earlier events
|
|
874
|
+
</div>
|
|
875
|
+
</div>
|
|
781
876
|
</template>
|
|
782
877
|
<div v-else class="detail-empty">click a component to inspect</div>
|
|
783
878
|
</aside>
|
|
@@ -1153,6 +1248,123 @@ function pathLabel(node: ComponentNode) {
|
|
|
1153
1248
|
color: var(--text2);
|
|
1154
1249
|
}
|
|
1155
1250
|
|
|
1251
|
+
:deep(.tree-jump-btn) {
|
|
1252
|
+
display: none;
|
|
1253
|
+
padding: 0 4px;
|
|
1254
|
+
border: none;
|
|
1255
|
+
background: transparent;
|
|
1256
|
+
color: var(--text3);
|
|
1257
|
+
font-size: 11px;
|
|
1258
|
+
cursor: pointer;
|
|
1259
|
+
line-height: 1;
|
|
1260
|
+
flex-shrink: 0;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
:deep(.tree-row:hover .tree-jump-btn),
|
|
1264
|
+
:deep(.tree-row.selected .tree-jump-btn) {
|
|
1265
|
+
display: inline-flex;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
:deep(.tree-jump-btn:hover) {
|
|
1269
|
+
color: var(--teal);
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
.jump-btn {
|
|
1273
|
+
font-size: 10px;
|
|
1274
|
+
padding: 1px 6px;
|
|
1275
|
+
border: 0.5px solid var(--border);
|
|
1276
|
+
border-radius: var(--radius);
|
|
1277
|
+
background: transparent;
|
|
1278
|
+
color: var(--text3);
|
|
1279
|
+
cursor: pointer;
|
|
1280
|
+
flex-shrink: 0;
|
|
1281
|
+
font-family: var(--mono);
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
.jump-btn:hover {
|
|
1285
|
+
border-color: var(--teal);
|
|
1286
|
+
color: var(--teal);
|
|
1287
|
+
background: color-mix(in srgb, var(--teal) 8%, transparent);
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
.route-select {
|
|
1291
|
+
padding: 3px 7px;
|
|
1292
|
+
border: 0.5px solid var(--border);
|
|
1293
|
+
border-radius: var(--radius);
|
|
1294
|
+
background: var(--bg2);
|
|
1295
|
+
color: var(--text);
|
|
1296
|
+
font-size: 11px;
|
|
1297
|
+
cursor: pointer;
|
|
1298
|
+
max-width: 140px;
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
.timeline-list {
|
|
1302
|
+
display: flex;
|
|
1303
|
+
flex-direction: column;
|
|
1304
|
+
gap: 1px;
|
|
1305
|
+
background: var(--bg2);
|
|
1306
|
+
border-radius: var(--radius);
|
|
1307
|
+
padding: 4px 8px;
|
|
1308
|
+
max-height: 200px;
|
|
1309
|
+
overflow-y: auto;
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
.timeline-row {
|
|
1313
|
+
display: flex;
|
|
1314
|
+
align-items: center;
|
|
1315
|
+
gap: 6px;
|
|
1316
|
+
padding: 2px 0;
|
|
1317
|
+
font-size: 11px;
|
|
1318
|
+
border-bottom: 0.5px solid var(--border);
|
|
1319
|
+
min-width: 0;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
.timeline-row:last-child {
|
|
1323
|
+
border-bottom: none;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
.timeline-kind {
|
|
1327
|
+
flex-shrink: 0;
|
|
1328
|
+
font-size: 10px;
|
|
1329
|
+
font-weight: 500;
|
|
1330
|
+
min-width: 40px;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
.timeline-kind.mount {
|
|
1334
|
+
color: var(--teal);
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
.timeline-kind.update {
|
|
1338
|
+
color: var(--amber);
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
.timeline-time {
|
|
1342
|
+
flex-shrink: 0;
|
|
1343
|
+
min-width: 52px;
|
|
1344
|
+
color: var(--text3);
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
.timeline-dur {
|
|
1348
|
+
flex-shrink: 0;
|
|
1349
|
+
min-width: 38px;
|
|
1350
|
+
color: var(--text2);
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
.timeline-trigger {
|
|
1354
|
+
overflow: hidden;
|
|
1355
|
+
text-overflow: ellipsis;
|
|
1356
|
+
white-space: nowrap;
|
|
1357
|
+
color: var(--text3);
|
|
1358
|
+
flex: 1;
|
|
1359
|
+
min-width: 0;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
.timeline-route {
|
|
1363
|
+
flex-shrink: 0;
|
|
1364
|
+
color: var(--text3);
|
|
1365
|
+
font-size: 10px;
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1156
1368
|
@media (max-width: 1180px) {
|
|
1157
1369
|
.inspector {
|
|
1158
1370
|
grid-template-columns: minmax(200px, 240px) minmax(0, 1fr);
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -362,7 +362,7 @@ function composableTrackerPlugin() {
|
|
|
362
362
|
const args = path.node.arguments;
|
|
363
363
|
const loc = path.node.loc;
|
|
364
364
|
const meta = t.objectExpression([
|
|
365
|
-
t.objectProperty(t.identifier("file"), t.stringLiteral(id
|
|
365
|
+
t.objectProperty(t.identifier("file"), t.stringLiteral(id)),
|
|
366
366
|
t.objectProperty(t.identifier("line"), t.numericLiteral(loc?.start.line ?? 0))
|
|
367
367
|
]);
|
|
368
368
|
path.replaceWith(
|
|
@@ -521,6 +521,9 @@ const module$1 = defineNuxtModule({
|
|
|
521
521
|
if (!nuxt.options.dev) {
|
|
522
522
|
return;
|
|
523
523
|
}
|
|
524
|
+
if (!process.env.LAUNCH_EDITOR && !process.env.VITE_EDITOR) {
|
|
525
|
+
process.env.LAUNCH_EDITOR = "code";
|
|
526
|
+
}
|
|
524
527
|
const resolver = createResolver(import.meta.url);
|
|
525
528
|
nuxt.hook("vite:extendConfig", (config) => {
|
|
526
529
|
const alias = config.resolve?.alias;
|