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.
@@ -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}
@@ -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-BFrWlkvI.js"></script>
42
- <link rel="stylesheet" crossorigin href="/assets/index-BUQNNbrq.css">
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
- return 'null'
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
- return false
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
- return 'badge-info'
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">{{ entry.file }}:{{ entry.line }}</span>
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)) return 'var(--red)'
50
- if (node.type === 'both') return 'var(--blue)'
51
- if (node.type === 'provider') return 'var(--teal)'
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') return true
57
- if (filter === 'warn') return node.injects.some((entry) => !entry.ok)
58
- if (filter === 'shadow') return node.provides.some((entry) => entry.isShadowing)
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) return true
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
- // Iterative post-order prune — avoids stack overflow on deep trees.
282
- // We process nodes bottom-up so each parent can inspect its children's
283
- // already-computed visibility before deciding its own.
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>