nuxt-devtools-observatory 0.1.26 → 0.1.30

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
+ .fetch-dashboard__search[data-v-2d842102]{max-width:240px}.fetch-dashboard__bar-column[data-v-2d842102]{min-width:80px}.fetch-dashboard__url[data-v-2d842102]{max-width:200px}.fetch-dashboard__detail-header[data-v-2d842102]{display:flex;align-items:center;justify-content:space-between}.fetch-dashboard__detail-title[data-v-2d842102]{font-size:var(--tracker-font-size-md)}.fetch-dashboard__meta-grid[data-v-2d842102]{display:grid;grid-template-columns:auto 1fr;gap:var(--tracker-space-1) var(--tracker-space-3);font-size:var(--tracker-font-size-sm)}.fetch-dashboard__meta-value[data-v-2d842102]{word-break:break-all}.fetch-dashboard__section-label[data-v-2d842102]{margin-top:6px;min-height:fit-content}.fetch-dashboard__section-label--source[data-v-2d842102]{margin-top:10px}.fetch-dashboard__payload-box[data-v-2d842102]{font-family:var(--mono);font-size:var(--tracker-font-size-sm);color:var(--text2);background:var(--bg2);border-radius:var(--radius);padding:var(--tracker-space-2) 10px;overflow:auto;white-space:pre;max-height:160px}.fetch-dashboard__waterfall[data-v-2d842102]{flex-shrink:0;background:var(--bg3);border:var(--tracker-border-width) solid var(--border);border-radius:var(--radius-lg);padding:10px var(--tracker-space-3)}.fetch-dashboard__waterfall-header[data-v-2d842102]{display:flex;align-items:center;justify-content:space-between;gap:var(--tracker-space-2)}.fetch-dashboard__waterfall-label[data-v-2d842102]{margin:0}.fetch-dashboard__waterfall-body[data-v-2d842102]{margin-top:var(--tracker-gap-toolbar)}.fetch-dashboard__waterfall-row[data-v-2d842102]{display:flex;align-items:center;gap:var(--tracker-space-2);margin-bottom:var(--tracker-space-1)}.fetch-dashboard__waterfall-key[data-v-2d842102]{width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex-shrink:0}.fetch-dashboard__waterfall-track[data-v-2d842102]{position:relative;flex:1;height:8px;background:var(--bg2);border-radius:2px;overflow:hidden}.fetch-dashboard__waterfall-bar[data-v-2d842102]{position:absolute;top:0;height:100%;border-radius:2px;opacity:.8}.fetch-dashboard__waterfall-time[data-v-2d842102]{width:44px;text-align:right;flex-shrink:0}.provide-graph__toolbar-spacer[data-v-f19461a8]{margin-left:auto}.provide-graph__search[data-v-f19461a8]{max-width:200px}.provide-graph__graph-area[data-v-f19461a8]{flex:1;overflow:auto;border:var(--tracker-border-width) solid var(--border);border-radius:var(--radius-lg);padding:var(--tracker-space-3);background:var(--bg3)}.provide-graph__legend[data-v-f19461a8]{display:flex;align-items:center;gap:var(--tracker-space-3);font-size:var(--tracker-font-size-sm);color:var(--text2);margin-bottom:var(--tracker-space-3)}.provide-graph__canvas-stage[data-v-f19461a8]{display:flex;justify-content:center;align-items:flex-start;min-width:100%}.provide-graph__legend-dot[data-v-f19461a8]{width:8px;height:8px;border-radius:50%;display:inline-block;margin-right:2px}.provide-graph__legend-dot--provides[data-v-f19461a8]{background:var(--teal)}.provide-graph__legend-dot--both[data-v-f19461a8]{background:var(--blue)}.provide-graph__legend-dot--injects[data-v-f19461a8]{background:var(--text3)}.provide-graph__legend-dot--missing[data-v-f19461a8]{background:var(--red)}.provide-graph__canvas-wrap[data-v-f19461a8]{position:relative}.provide-graph__edges-svg[data-v-f19461a8]{position:absolute;top:0;left:0;pointer-events:none}.provide-graph__edge[data-v-f19461a8]{stroke:var(--border);stroke-width:1.5}.provide-graph__node[data-v-f19461a8]{position:absolute;display:flex;align-items:center;gap:7px;padding:0 10px;height:32px;border-radius:var(--radius);border:var(--tracker-border-width) solid var(--border);background:var(--bg3);cursor:pointer;transition:border-color var(--tracker-transition-fast),background var(--tracker-transition-fast);overflow:hidden;box-sizing:border-box;white-space:nowrap}.provide-graph__node[data-v-f19461a8]:hover{border-color:var(--text3)}.provide-graph__node--selected[data-v-f19461a8]{border-color:var(--node-color);background:color-mix(in srgb,var(--node-color) 8%,transparent)}.provide-graph__node-dot[data-v-f19461a8]{width:7px;height:7px;border-radius:50%;flex-shrink:0}.provide-graph__node-label[data-v-f19461a8]{font-size:var(--tracker-font-size-sm);flex:1;overflow:hidden;text-overflow:ellipsis}.badge-xs[data-v-f19461a8]{font-size:9px;padding:1px 4px}.provide-graph__detail[data-v-f19461a8]{min-height:0;gap:var(--tracker-space-1)}.provide-graph__graph-empty[data-v-f19461a8]{display:flex;align-items:center;justify-content:center;min-height:180px;color:var(--text3);font-size:var(--tracker-font-size-md)}.provide-graph__detail-header[data-v-f19461a8]{display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--tracker-gap-toolbar)}.provide-graph__detail-title[data-v-f19461a8]{font-size:var(--tracker-font-size-md)}.provide-graph__section-label[data-v-f19461a8]{margin:8px 0 5px}.detail-section[data-v-f19461a8]{display:flex;flex-direction:column;min-height:0}.detail-list[data-v-f19461a8]{display:flex;flex-direction:column;gap:3px;overflow:auto;padding-right:2px}.provide-row[data-v-f19461a8]{display:flex;flex-direction:column;gap:4px;padding:5px 8px;background:var(--bg2);border-radius:var(--radius);margin-bottom:3px}.row-warning[data-v-f19461a8]{font-size:var(--tracker-font-size-sm);color:var(--amber);padding:2px 0}.provide-graph__compact-muted[data-v-f19461a8]{padding:2px 0;font-size:var(--tracker-font-size-sm)}.provide-graph__empty-detail[data-v-f19461a8]{margin-top:var(--tracker-space-2)}.row-consumers[data-v-f19461a8]{display:flex;flex-wrap:wrap;align-items:center;gap:4px;padding:2px 0}.consumer-chip[data-v-f19461a8]{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-f19461a8]{font-size:10px;padding:1px 6px;border-radius:4px}.scope-global[data-v-f19461a8]{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-f19461a8]{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-f19461a8]{background:var(--bg3);border:.5px solid var(--border);color:var(--text3)}.row-main[data-v-f19461a8]{display:flex;align-items:center;flex-wrap:wrap;gap:4px 8px;min-width:0}.row-key[data-v-f19461a8]{min-width:100px;color:var(--text2);flex-shrink:0}.row-value-preview[data-v-f19461a8]{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.row-toggle[data-v-f19461a8]{padding:2px 8px;font-size:10px}.value-box[data-v-f19461a8]{font-family:var(--mono);font-size:11px;color:var(--text2);background:#0000001a;border-radius:var(--radius);padding:8px 10px;white-space:pre-wrap;overflow-wrap:break-word;overflow:auto;max-height:180px}.inject-row[data-v-f19461a8]{display:flex;align-items:center;gap:8px;padding:5px 8px;background:var(--bg2);border-radius:var(--radius);margin-bottom:3px}.row-from[data-v-f19461a8]{margin-left:auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.inject-miss[data-v-f19461a8]{background:#e24b4a14}.jump-btn[data-v-f19461a8]{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-f19461a8]:hover{border-color:var(--teal);color:var(--teal);background:color-mix(in srgb,var(--teal) 8%,transparent)}.composable-tracker__clear-btn[data-v-30451db2]{color:var(--text3);border-color:var(--border);flex-shrink:0}.composable-tracker__clear-btn[data-v-30451db2]:hover{color:var(--red);border-color:var(--red);background:transparent}.composable-tracker__mode-btn[data-v-30451db2]{border-color:color-mix(in srgb,var(--blue) 40%,var(--border));color:var(--blue)}.composable-tracker__mode-btn[data-v-30451db2]:hover{border-color:var(--blue);background:color-mix(in srgb,var(--blue) 12%,transparent)}.composable-tracker__search[data-v-30451db2]{max-width:220px}.composable-tracker__list[data-v-30451db2]{flex:1;overflow:auto;display:flex;flex-direction:column;gap:var(--tracker-space-2);min-height:0}.composable-tracker__card[data-v-30451db2]{background:var(--bg3);border:var(--tracker-border-width) solid var(--border);border-radius:var(--radius-lg);overflow:hidden;cursor:pointer;flex-shrink:0}.composable-tracker__card[data-v-30451db2]:hover{border-color:var(--text3)}.composable-tracker__card--leak[data-v-30451db2]{border-left:2px solid var(--red);border-radius:0 var(--radius-lg) var(--radius-lg) 0}.composable-tracker__card--expanded[data-v-30451db2]{border-color:var(--purple)}.composable-tracker__card-header[data-v-30451db2]{display:flex;align-items:center;justify-content:space-between;padding:var(--tracker-space-3) var(--tracker-space-4);gap:var(--tracker-space-3)}.composable-tracker__identity[data-v-30451db2]{display:flex;align-items:baseline;gap:var(--tracker-space-2);min-width:0;flex:1}.composable-tracker__name[data-v-30451db2]{font-size:var(--tracker-font-size-md);font-weight:500;color:var(--text);flex-shrink:0}.composable-tracker__file[data-v-30451db2]{font-size:var(--tracker-font-size-sm);color:var(--text3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.composable-tracker__meta[data-v-30451db2]{display:flex;align-items:center;gap:var(--tracker-space-2);flex-shrink:0}.composable-tracker__refs-preview[data-v-30451db2]{display:flex;flex-wrap:wrap;gap:var(--tracker-space-1);padding:0 var(--tracker-space-4) var(--tracker-space-3);align-items:center}.composable-tracker__ref-chip[data-v-30451db2]{display:inline-flex;align-items:center;gap:var(--tracker-space-1);padding:2px 7px;border-radius:4px;background:var(--bg2);border:var(--tracker-border-width) solid var(--border);font-size:var(--tracker-font-size-sm);font-family:var(--mono);max-width:220px;overflow:hidden}.composable-tracker__ref-chip--reactive[data-v-30451db2]{border-color:color-mix(in srgb,var(--purple) 40%,var(--border));background:color-mix(in srgb,var(--purple) 8%,var(--bg2))}.composable-tracker__ref-chip--computed[data-v-30451db2]{border-color:color-mix(in srgb,var(--blue) 40%,var(--border));background:color-mix(in srgb,var(--blue) 8%,var(--bg2))}.composable-tracker__ref-chip-key[data-v-30451db2]{color:var(--text2);flex-shrink:0}.composable-tracker__ref-chip-val[data-v-30451db2]{color:var(--teal);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.composable-tracker__detail[data-v-30451db2]{padding:var(--tracker-space-1) var(--tracker-space-4) var(--tracker-space-4);border-top:var(--tracker-border-width) solid var(--border);display:flex;flex-direction:column;gap:var(--tracker-space-1)}.composable-tracker__leak-banner[data-v-30451db2]{background:color-mix(in srgb,var(--red) 12%,transparent);border:var(--tracker-border-width) solid color-mix(in srgb,var(--red) 40%,var(--border));border-radius:var(--radius);padding:6px 10px;font-size:var(--tracker-font-size-sm);color:var(--red);margin-bottom:var(--tracker-space-2);font-family:var(--mono)}.composable-tracker__section-label[data-v-30451db2]{margin-top:var(--tracker-space-2);margin-bottom:var(--tracker-space-1)}.composable-tracker__section-label--spaced[data-v-30451db2]{margin-top:var(--tracker-space-4)}.composable-tracker__section-label-meta[data-v-30451db2]{font-weight:400;text-transform:none;letter-spacing:0}.composable-tracker__ref-row[data-v-30451db2]{display:flex;align-items:flex-start;gap:var(--tracker-space-3);padding:3px 0}.composable-tracker__ref-key[data-v-30451db2]{min-width:90px;color:var(--text2);flex-shrink:0}.composable-tracker__ref-val[data-v-30451db2]{flex:1;color:var(--teal);min-width:0}.composable-tracker__ref-val--collapsed[data-v-30451db2]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.composable-tracker__ref-val--full[data-v-30451db2]{white-space:pre-wrap;word-break:break-all;line-height:1.5}.composable-tracker__ref-row-actions[data-v-30451db2]{display:flex;align-items:center;gap:var(--tracker-space-1);flex-shrink:0}.composable-tracker__expand-btn[data-v-30451db2]{font-size:var(--tracker-font-size-xs);padding:1px 5px;border-radius:4px;border:var(--tracker-border-width) solid var(--border);background:var(--bg2);color:var(--text3);cursor:pointer;line-height:1.4;flex-shrink:0}.composable-tracker__expand-btn[data-v-30451db2]:hover{border-color:var(--text3);color:var(--text)}.composable-tracker__lifecycle-row[data-v-30451db2]{display:flex;align-items:center;gap:var(--tracker-space-3);padding:2px 0}.composable-tracker__lifecycle-dot[data-v-30451db2]{width:6px;height:6px;border-radius:50%;flex-shrink:0}.composable-tracker__lifecycle-dot--ok[data-v-30451db2]{background:var(--teal)}.composable-tracker__lifecycle-dot--error[data-v-30451db2]{background:var(--red)}.composable-tracker__lifecycle-status--ok[data-v-30451db2]{color:var(--teal)}.composable-tracker__lifecycle-status--error[data-v-30451db2]{color:var(--red)}.composable-tracker__context-label[data-v-30451db2]{min-width:120px}.composable-tracker__context-value[data-v-30451db2]{min-width:0}.composable-tracker__context-value--row[data-v-30451db2]{display:flex;align-items:center;gap:var(--tracker-space-2);flex-wrap:wrap}.composable-tracker__context-value--muted[data-v-30451db2]{color:var(--text3)}.composable-tracker__ref-chip--shared[data-v-30451db2]{border-color:color-mix(in srgb,var(--amber) 50%,var(--border));background:color-mix(in srgb,var(--amber) 10%,var(--bg2))}.composable-tracker__ref-chip-shared-dot[data-v-30451db2]{display:inline-block;width:5px;height:5px;border-radius:50%;background:var(--amber);flex-shrink:0;margin-left:1px}.composable-tracker__global-banner[data-v-30451db2]{display:flex;align-items:flex-start;gap:var(--tracker-space-3);background:color-mix(in srgb,var(--amber) 10%,transparent);border:var(--tracker-border-width) solid color-mix(in srgb,var(--amber) 40%,var(--border));border-radius:var(--radius);padding:7px 10px;font-size:var(--tracker-font-size-sm);color:var(--text2);margin-bottom:var(--tracker-space-2)}.composable-tracker__global-dot[data-v-30451db2]{display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--amber);flex-shrink:0;margin-top:3px}.badge-amber[data-v-30451db2]{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))}.composable-tracker__history-list[data-v-30451db2]{display:flex;flex-direction:column;gap:1px;background:var(--bg2);border-radius:var(--radius);padding:4px 8px;max-height:180px;overflow-y:auto}.composable-tracker__history-row[data-v-30451db2]{display:flex;align-items:center;gap:var(--tracker-space-3);padding:2px 0;font-size:var(--tracker-font-size-sm);font-family:var(--mono);border-bottom:var(--tracker-border-width) solid var(--border)}.composable-tracker__history-row[data-v-30451db2]:last-child{border-bottom:none}.composable-tracker__history-time[data-v-30451db2]{min-width:52px;color:var(--text3);flex-shrink:0}.composable-tracker__history-key[data-v-30451db2]{min-width:80px;color:var(--text2);flex-shrink:0}.composable-tracker__history-val[data-v-30451db2]{color:var(--amber);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.composable-tracker__ref-key--clickable[data-v-30451db2]{cursor:pointer;text-decoration:underline dotted var(--text3);text-underline-offset:2px}.composable-tracker__ref-key--clickable[data-v-30451db2]:hover{color:var(--purple);text-decoration-color:var(--purple)}.composable-tracker__edit-btn[data-v-30451db2]{font-size:var(--tracker-font-size-xs);padding:1px 6px;border-radius:var(--radius);border:var(--tracker-border-width) solid var(--border);background:transparent;color:var(--text3);cursor:pointer;margin-left:auto;flex-shrink:0;font-family:var(--mono)}.composable-tracker__edit-btn[data-v-30451db2]:hover{border-color:var(--purple);color:var(--purple);background:color-mix(in srgb,var(--purple) 8%,transparent)}.composable-tracker__empty[data-v-30451db2]{padding:16px 0}.composable-tracker__compact-muted[data-v-30451db2]{padding:2px 0 6px}.composable-tracker__lookup-panel[data-v-30451db2]{flex-shrink:0;border:var(--tracker-border-width) solid var(--border);border-radius:var(--radius-lg);background:var(--bg3);overflow:hidden}.composable-tracker__lookup-header[data-v-30451db2]{display:flex;align-items:center;gap:var(--tracker-space-2);padding:7px 12px;border-bottom:var(--tracker-border-width) solid var(--border);background:var(--bg2)}.composable-tracker__lookup-close[data-v-30451db2],.composable-tracker__dialog-close[data-v-30451db2],.composable-tracker__lookup-route[data-v-30451db2]{margin-left:auto}.composable-tracker__lookup-empty[data-v-30451db2]{padding:6px 12px}.composable-tracker__lookup-row[data-v-30451db2]{display:flex;align-items:center;gap:var(--tracker-space-3);padding:5px 12px;border-bottom:var(--tracker-border-width) solid var(--border)}.composable-tracker__lookup-row[data-v-30451db2]:last-child{border-bottom:none}.composable-tracker__edit-overlay[data-v-30451db2]{position:fixed;top:0;right:0;bottom:0;left:0;background:#0006;z-index:100;display:flex;align-items:center;justify-content:center}.composable-tracker__edit-dialog[data-v-30451db2]{background:var(--bg1, var(--bg2));border:var(--tracker-border-width) 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}.composable-tracker__edit-dialog-header[data-v-30451db2]{display:flex;align-items:center;gap:var(--tracker-space-2);font-size:var(--tracker-font-size-md);color:var(--text2);margin-bottom:2px}.composable-tracker__edit-help[data-v-30451db2]{padding:4px 0 8px}.composable-tracker__edit-textarea[data-v-30451db2]{width:100%;font-family:var(--mono);font-size:var(--tracker-font-size-md);padding:8px 10px;background:var(--bg2);border:var(--tracker-border-width) solid var(--border);border-radius:var(--radius);color:var(--text);resize:vertical;outline:none}.composable-tracker__edit-textarea[data-v-30451db2]:focus{border-color:var(--purple)}.composable-tracker__edit-error[data-v-30451db2]{color:var(--red);font-family:var(--mono)}.composable-tracker__edit-actions[data-v-30451db2]{display:flex;gap:var(--tracker-space-2);padding-top:4px}.slide-enter-active[data-v-30451db2],.slide-leave-active[data-v-30451db2]{transition:opacity .15s,transform .15s}.slide-enter-from[data-v-30451db2],.slide-leave-to[data-v-30451db2]{opacity:0;transform:translateY(6px)}.fade-enter-active[data-v-30451db2],.fade-leave-active[data-v-30451db2]{transition:opacity .15s}.fade-enter-from[data-v-30451db2],.fade-leave-to[data-v-30451db2]{opacity:0}.composable-tracker__jump-btn[data-v-30451db2]{font-size:var(--tracker-font-size-xs);padding:1px 6px;border:var(--tracker-border-width) solid var(--border);border-radius:var(--radius);background:transparent;color:var(--text3);cursor:pointer;flex-shrink:0;font-family:var(--mono)}.composable-tracker__jump-btn[data-v-30451db2]:hover{border-color:var(--teal);color:var(--teal);background:color-mix(in srgb,var(--teal) 8%,transparent)}@media(max-width:900px){.composable-tracker__card-header[data-v-30451db2],.composable-tracker__ref-row[data-v-30451db2],.composable-tracker__lifecycle-row[data-v-30451db2],.composable-tracker__lookup-row[data-v-30451db2]{flex-wrap:wrap}.composable-tracker__identity[data-v-30451db2],.composable-tracker__context-label[data-v-30451db2],.composable-tracker__lookup-route[data-v-30451db2]{min-width:100%;margin-left:0}}.render-heatmap__controls[data-v-3dd8b492]{display:flex;align-items:center;gap:8px;flex-shrink:0;flex-wrap:wrap}.render-heatmap__mode-group[data-v-3dd8b492]{display:flex;gap:2px}.render-heatmap__threshold-group[data-v-3dd8b492]{display:flex;align-items:center;gap:6px}.render-heatmap__threshold-range[data-v-3dd8b492]{width:90px}.stat-sub[data-v-3dd8b492]{margin-top:var(--tracker-space-1);font-size:var(--tracker-font-size-sm);color:var(--text3)}.render-heatmap__inspector[data-v-3dd8b492]{display:flex;gap:0;flex:1;min-height:0}.render-heatmap__roots-panel[data-v-3dd8b492],.render-heatmap__detail-panel[data-v-3dd8b492]{flex-shrink:0}.render-heatmap__roots-panel[data-v-3dd8b492]{width:240px;margin-right:12px}.render-heatmap__roots-panel[data-v-3dd8b492],.render-heatmap__tree-panel[data-v-3dd8b492],.render-heatmap__detail-panel[data-v-3dd8b492]{border:var(--tracker-border-width) solid var(--border);border-radius:var(--radius-lg);background:var(--bg3);min-height:0}.render-heatmap__roots-panel[data-v-3dd8b492],.render-heatmap__detail-panel[data-v-3dd8b492]{display:flex;flex-direction:column;overflow:auto;padding:var(--tracker-space-3);gap:var(--tracker-space-2)}.render-heatmap__panel-title[data-v-3dd8b492]{margin:0}.render-heatmap__root-item[data-v-3dd8b492]{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}.render-heatmap__root-item.active[data-v-3dd8b492]{border-color:var(--teal);background:color-mix(in srgb,var(--teal) 16%,var(--bg2))}.render-heatmap__root-label[data-v-3dd8b492]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.render-heatmap__root-copy[data-v-3dd8b492]{display:flex;flex-direction:column;min-width:0}.render-heatmap__root-sub[data-v-3dd8b492]{font-size:var(--tracker-font-size-sm)}.render-heatmap__root-meta[data-v-3dd8b492]{color:var(--text3);font-size:var(--tracker-font-size-sm)}.render-heatmap__tree-panel[data-v-3dd8b492]{display:flex;flex-direction:column;overflow:hidden;flex:1;min-width:0}.render-heatmap__tree-toolbar[data-v-3dd8b492]{padding:var(--tracker-space-3);border-bottom:var(--tracker-border-width) solid var(--border)}.render-heatmap__search-input[data-v-3dd8b492]{width:100%;padding:10px 12px;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg2);color:var(--text)}.render-heatmap__tree-frame[data-v-3dd8b492]{flex:1;min-height:0;overflow:auto;padding:var(--tracker-space-3)}.render-heatmap__tree-canvas[data-v-3dd8b492]{display:inline-block;min-width:100%;width:max-content}[data-v-3dd8b492] .tree-node{margin-bottom:4px}[data-v-3dd8b492] .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-3dd8b492] .tree-row:hover{background:var(--bg2)}[data-v-3dd8b492] .tree-row.selected{background:color-mix(in srgb,var(--teal) 12%,var(--bg2));border-color:var(--teal)}[data-v-3dd8b492] .tree-row.hot{box-shadow:inset 2px 0 0 var(--red)}[data-v-3dd8b492] .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-3dd8b492] .tree-toggle:disabled{cursor:default}[data-v-3dd8b492] .tree-toggle.empty{opacity:0}[data-v-3dd8b492] .tree-rail{display:block;width:2px;height:14px;border-radius:999px;background:color-mix(in srgb,var(--border) 75%,transparent)}[data-v-3dd8b492] .tree-copy{display:flex;align-items:center;min-width:0;gap:6px;overflow:hidden}[data-v-3dd8b492] .tree-name{font-size:12px;color:var(--text);min-width:0;overflow:hidden;text-overflow:ellipsis}[data-v-3dd8b492] .tree-badges{display:flex;gap:6px;flex-shrink:0;overflow:hidden}[data-v-3dd8b492] .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-3dd8b492] .tree-metrics{display:flex;align-items:center;min-width:92px;justify-content:flex-end;flex-shrink:0;gap:6px}[data-v-3dd8b492] .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-3dd8b492] .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-3dd8b492] .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-3dd8b492] .tree-children{margin-left:7px;padding-left:11px;border-left:1px solid color-mix(in srgb,var(--border) 72%,transparent)}.render-heatmap__detail-empty[data-v-3dd8b492]{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text3);font-size:var(--tracker-font-size-md)}.render-heatmap__detail-header[data-v-3dd8b492]{display:flex;align-items:center;justify-content:space-between}.render-heatmap__detail-title[data-v-3dd8b492]{font-size:var(--tracker-font-size-md)}.render-heatmap__meta-grid[data-v-3dd8b492]{display:grid;grid-template-columns:auto 1fr;gap:var(--tracker-space-1) var(--tracker-space-3)}.render-heatmap__detail-pill-row[data-v-3dd8b492]{display:flex;flex-wrap:wrap;gap:6px}.render-heatmap__detail-pill[data-v-3dd8b492]{border:1px solid var(--border);border-radius:999px;padding:4px 8px;background:var(--bg2);font-size:var(--tracker-font-size-sm)}.render-heatmap__detail-pill--hot[data-v-3dd8b492]{border-color:color-mix(in srgb,var(--red) 50%,var(--border));color:var(--red)}.render-heatmap__detail-pill--persistent[data-v-3dd8b492]{border-color:color-mix(in srgb,var(--amber) 55%,var(--border));color:color-mix(in srgb,var(--amber) 80%,var(--text))}.render-heatmap__detail-pill--hydrated[data-v-3dd8b492]{border-color:color-mix(in srgb,var(--teal) 55%,var(--border));color:color-mix(in srgb,var(--teal) 80%,var(--text))}.render-heatmap__detail-pill--muted[data-v-3dd8b492]{color:var(--text3);border-color:var(--border)}.render-heatmap__section-label[data-v-3dd8b492]{margin-top:8px;margin-bottom:4px}.render-heatmap__file-row[data-v-3dd8b492]{display:flex;align-items:center;gap:6px}.render-heatmap__trigger-item[data-v-3dd8b492]{background:var(--bg2);border-radius:var(--radius);padding:4px 8px;margin-bottom:3px;color:var(--text2)}.render-heatmap__persistent-value[data-v-3dd8b492]{color:color-mix(in srgb,var(--amber) 80%,var(--text))}.render-heatmap__section-label--timeline[data-v-3dd8b492]{margin-top:var(--tracker-space-2)}.render-heatmap__section-label-meta[data-v-3dd8b492]{font-weight:400;text-transform:none;letter-spacing:0}[data-v-3dd8b492] .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-3dd8b492] .tree-row:hover .tree-jump-btn,[data-v-3dd8b492] .tree-row.selected .tree-jump-btn{display:inline-flex}[data-v-3dd8b492] .tree-jump-btn:hover{color:var(--teal)}.jump-btn[data-v-3dd8b492]{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-3dd8b492]:hover{border-color:var(--teal);color:var(--teal);background:color-mix(in srgb,var(--teal) 8%,transparent)}.route-select[data-v-3dd8b492]{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}.render-heatmap__timeline-list[data-v-3dd8b492]{display:flex;flex-direction:column;gap:1px;background:var(--bg2);border-radius:var(--radius);padding:4px 8px;max-height:200px;overflow-y:auto;min-height:fit-content}.render-heatmap__timeline-row[data-v-3dd8b492]{display:flex;align-items:center;gap:6px;padding:2px 0;font-size:var(--tracker-font-size-sm);border-bottom:var(--tracker-border-width) solid var(--border);min-width:0;min-height:fit-content}.render-heatmap__timeline-row[data-v-3dd8b492]:last-child{border-bottom:none}.render-heatmap__timeline-kind[data-v-3dd8b492]{flex-shrink:0;font-size:var(--tracker-font-size-xs);font-weight:500;min-width:40px}.render-heatmap__timeline-kind.mount[data-v-3dd8b492]{color:var(--teal)}.render-heatmap__timeline-kind.update[data-v-3dd8b492]{color:var(--amber)}.render-heatmap__timeline-time[data-v-3dd8b492]{flex-shrink:0;min-width:52px;color:var(--text3)}.render-heatmap__timeline-dur[data-v-3dd8b492]{flex-shrink:0;min-width:38px;color:var(--text2)}.render-heatmap__timeline-trigger[data-v-3dd8b492]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text3);flex:1;min-width:0}.render-heatmap__timeline-route[data-v-3dd8b492]{flex-shrink:0;margin-left:auto;color:var(--text3);font-size:var(--tracker-font-size-xs)}.render-heatmap__compact-muted[data-v-3dd8b492]{padding:2px 0}@media(max-width:1180px){.render-heatmap__roots-panel[data-v-3dd8b492]{display:none}.render-heatmap__detail-panel[data-v-3dd8b492]{max-height:220px}}.transition-timeline__stats[data-v-17493931]{display:flex;gap:10px}.transition-timeline__stats[data-v-17493931] .stat-card{min-width:72px;text-align:center;padding:var(--tracker-space-2) 14px;border:var(--tracker-border-width) solid var(--border)}.transition-timeline__stats[data-v-17493931] .stat-val{font-weight:600;font-family:var(--mono);line-height:1.1}.stat-unit[data-v-17493931]{font-size:var(--tracker-font-size-md);opacity:.6;margin-left:1px}.transition-timeline__toolbar[data-v-17493931]{border-bottom:var(--tracker-border-width) solid var(--border);padding-bottom:10px}.transition-timeline__search[data-v-17493931]{flex:1;max-width:260px}.transition-timeline__filters[data-v-17493931]{display:flex;gap:4px}.transition-timeline__content[data-v-17493931]{align-items:stretch}.transition-timeline__table[data-v-17493931]{overflow:hidden auto;min-width:0;border:none;border-radius:0}.transition-timeline__bar-cell[data-v-17493931]{width:200px;padding:4px 8px}.transition-timeline__bar-track[data-v-17493931]{position:relative;height:8px;background:var(--bg2);border-radius:4px;overflow:hidden}.transition-timeline__bar-fill[data-v-17493931]{position:absolute;top:0;height:100%;min-width:3px;border-radius:4px;transition:width var(--tracker-transition-ui)}.transition-timeline__col-name[data-v-17493931]{width:110px}.transition-timeline__col-dir[data-v-17493931]{width:80px}.transition-timeline__col-phase[data-v-17493931]{width:90px}.transition-timeline__col-duration[data-v-17493931]{width:70px}.transition-timeline__name[data-v-17493931]{font-size:var(--tracker-font-size-sm);font-weight:500}.transition-timeline__direction[data-v-17493931]{font-size:var(--tracker-font-size-sm)}.transition-timeline__duration[data-v-17493931]{font-size:var(--tracker-font-size-sm);color:var(--text2)}.transition-timeline__component[data-v-17493931]{font-size:var(--tracker-font-size-sm)}.transition-timeline__detail[data-v-17493931]{display:flex;flex-direction:column;flex-shrink:0;border:var(--tracker-border-width) solid var(--border);border-radius:var(--radius-lg);overflow-y:auto;background:var(--bg3);padding:0 0 16px}.transition-timeline__detail-header[data-v-17493931]{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}.transition-timeline__detail-title[data-v-17493931]{font-family:var(--mono);font-size:13px;font-weight:500}.transition-timeline__close-btn[data-v-17493931]{border:none;background:transparent;color:var(--text3);font-size:var(--tracker-font-size-sm);padding:2px 6px;cursor:pointer}.transition-timeline__detail-section[data-v-17493931]{padding:10px 14px 6px;border-bottom:var(--tracker-border-width) solid var(--border)}.transition-timeline__section-title[data-v-17493931]{margin-bottom:8px}.transition-timeline__detail-row[data-v-17493931]{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:3px 0;font-size:var(--tracker-font-size-md)}.transition-timeline__detail-key[data-v-17493931]{color:var(--text3);flex-shrink:0}.transition-timeline__detail-val[data-v-17493931]{color:var(--text);text-align:right;word-break:break-all}.transition-timeline__detail-val--mono[data-v-17493931]{font-family:var(--mono)}.transition-timeline__detail-val--strong[data-v-17493931]{font-weight:500}.transition-timeline__notice[data-v-17493931]{margin:10px 14px 0;font-size:var(--tracker-font-size-sm);line-height:1.6;padding:8px 10px;border-radius:var(--radius)}.transition-timeline__notice--cancelled[data-v-17493931]{background:#e24b4a1a;color:var(--red);border:.5px solid rgb(226 75 74 / 30%)}.transition-timeline__notice--active[data-v-17493931]{background:#7f77dd1a;color:var(--purple);border:.5px solid rgb(127 119 221 / 30%)}code[data-v-17493931]{font-family:var(--mono);font-size:10px;background:#00000026;padding:1px 4px;border-radius:3px}.panel-slide-enter-active[data-v-17493931],.panel-slide-leave-active[data-v-17493931]{transition:transform .18s ease,opacity .18s ease}.panel-slide-enter-from[data-v-17493931],.panel-slide-leave-to[data-v-17493931]{transform:translate(12px);opacity:0}#app-root[data-v-b91d81f3]{display:flex;flex-direction:column;height:100vh;overflow:hidden}.tabbar[data-v-b91d81f3]{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-b91d81f3]{font-size:11px;font-weight:500;color:var(--purple);letter-spacing:.5px;margin-right:12px;padding-bottom:8px}.tab-btn[data-v-b91d81f3]{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-b91d81f3]:hover{color:var(--text);background:transparent}.tab-btn.active[data-v-b91d81f3]{color:var(--purple);border-bottom-color:var(--purple)}.tab-icon[data-v-b91d81f3]{font-size:10px;opacity:.6}.tab-content[data-v-b91d81f3]{flex:1;overflow:hidden;display:flex;flex-direction:column}:root{--tracker-space-1: 4px;--tracker-space-2: 8px;--tracker-space-3: 12px;--tracker-space-4: 16px;--tracker-space-5: 24px;--tracker-gap-view: 10px;--tracker-gap-toolbar: 6px;--tracker-gap-grid: 8px;--tracker-font-size-xs: 10px;--tracker-font-size-sm: 11px;--tracker-font-size-md: 12px;--tracker-font-size-stat: 20px;--tracker-border-width: .5px;--tracker-resize-handle-width: 8px;--tracker-resize-handle-gutter: 2px;--tracker-panel-width: 280px;--tracker-transition-fast: .12s;--tracker-transition-ui: .15s;--tracker-tint-purple: rgb(127 119 221 / 15%);--tracker-tint-purple-strong: rgb(127 119 221 / 20%);--tracker-tint-purple-soft: rgb(127 119 221 / 8%);--tracker-tint-red: rgb(226 75 74 / 12%);--tracker-tint-teal: rgb(29 158 117 / 12%);--tracker-tint-teal-badge: rgb(29 158 117 / 15%);--tracker-tint-amber: rgb(239 159 39 / 15%);--tracker-tint-blue: rgb(55 138 221 / 12%)}*{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:var(--tracker-font-size-md);cursor:pointer;border:var(--tracker-border-width) solid var(--border);background:transparent;color:var(--text2);padding:var(--tracker-space-1) 10px;border-radius:var(--radius);transition:background var(--tracker-transition-fast)}button:hover{background:var(--bg2)}button:active{transform:scale(.98)}button.active{background:var(--tracker-tint-purple);color:var(--purple);border-color:var(--purple)}button.danger-active{background:var(--tracker-tint-red);color:var(--red);border-color:var(--red)}button.success-active{background:var(--tracker-tint-teal);color:var(--teal);border-color:var(--teal)}input[type=text],input[type=search]{font-family:var(--font);font-size:var(--tracker-font-size-md);border:var(--tracker-border-width) 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 var(--tracker-tint-purple-strong)}input[type=range]{accent-color:var(--purple);cursor:pointer}.mono{font-family:var(--mono)}.muted{color:var(--text3)}.text-sm{font-size:var(--tracker-font-size-sm)}.text-xs{font-size:var(--tracker-font-size-xs)}.bold{font-weight:500}.badge{display:inline-block;font-size:var(--tracker-font-size-xs);font-weight:500;padding:2px 7px;border-radius:99px;white-space:nowrap}.badge-ok{background:var(--tracker-tint-teal-badge);color:var(--teal)}.badge-err{background:var(--tracker-tint-red);color:var(--red)}.badge-warn{background:var(--tracker-tint-amber);color:var(--amber)}.badge-info{background:var(--tracker-tint-blue);color:var(--blue)}.badge-gray{background:var(--bg2);color:var(--text3);border:var(--tracker-border-width) solid var(--border)}.badge-purple{background:var(--tracker-tint-purple);color:var(--purple)}.card{background:var(--bg3);border:var(--tracker-border-width) solid var(--border);border-radius:var(--radius-lg);padding:var(--tracker-space-3) 14px}.data-table{width:100%;border-collapse:collapse;font-size:12px}.data-table th{text-align:left;font-size:var(--tracker-font-size-xs);font-weight:500;color:var(--text3);padding:6px 8px;border-bottom:var(--tracker-border-width) solid var(--border);text-transform:uppercase;letter-spacing:.4px;white-space:nowrap}.data-table td{padding:8px;border-bottom:var(--tracker-border-width) 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:var(--tracker-tint-purple-soft)}.stat-card{background:var(--bg2);border-radius:var(--radius);padding:var(--tracker-space-2) var(--tracker-space-3)}.stat-label{font-size:var(--tracker-font-size-xs);color:var(--text3);text-transform:uppercase;letter-spacing:.4px;margin-bottom:3px}.stat-val{font-size:var(--tracker-font-size-stat);font-weight:500}.stat-val--ok{color:var(--teal)}.stat-val--pending{color:var(--amber)}.stat-val--error{color:var(--red)}.stat-val--active{color:var(--purple)}.tracker-view{display:flex;flex-direction:column;height:100%;overflow:hidden;padding:var(--tracker-space-3);gap:var(--tracker-gap-view)}.tracker-stats-row{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--tracker-gap-grid);flex-shrink:0}.tracker-toolbar{display:flex;align-items:center;gap:var(--tracker-gap-toolbar);flex-shrink:0;flex-wrap:wrap}.tracker-toolbar__spacer{margin-left:auto}.tracker-split{display:flex;gap:0;flex:1;overflow:hidden;min-height:0}.tracker-table-wrap{flex:1;overflow:auto;border:var(--tracker-border-width) solid var(--border);border-radius:var(--radius-lg)}.tracker-resize-handle{width:var(--tracker-resize-handle-width);flex-shrink:0;cursor:col-resize;background:transparent;position:relative;z-index:1;margin:0 var(--tracker-resize-handle-gutter)}.tracker-resize-handle:after{content:"";position:absolute;top:0;right:3px;bottom:0;left:3px;border-radius:2px;background:var(--border);opacity:0;transition:opacity var(--tracker-transition-ui)}.tracker-resize-handle:hover:after{opacity:1}.tracker-detail-panel{flex-shrink:0;display:flex;flex-direction:column;gap:var(--tracker-space-2);overflow:auto;border:var(--tracker-border-width) solid var(--border);border-radius:var(--radius-lg);padding:var(--tracker-space-3);background:var(--bg3)}.tracker-detail-empty{width:var(--tracker-panel-width);flex-shrink:0;display:flex;align-items:center;justify-content:center;color:var(--text3);font-size:var(--tracker-font-size-md);border:var(--tracker-border-width) dashed var(--border);border-radius:var(--radius-lg)}.tracker-section-label{font-size:var(--tracker-font-size-xs);font-weight:500;text-transform:uppercase;letter-spacing:.4px;color:var(--text3)}.tracker-empty-cell{text-align:center;color:var(--text3);padding:var(--tracker-space-5)}.tracker-mono-secondary{font-size:var(--tracker-font-size-sm);color:var(--text2)}.tracker-truncate{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tracker-progress-bar{height:4px;background:var(--bg2);border-radius:2px;overflow:hidden}.tracker-progress-bar__fill{height:100%;border-radius:2px}.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-eSUuhYQ0.js"></script>
42
- <link rel="stylesheet" crossorigin href="/assets/index-1-H6UMCK.css">
41
+ <script type="module" crossorigin src="/__observatory/assets/index-BCaKoHBH.js"></script>
42
+ <link rel="stylesheet" crossorigin href="/__observatory/assets/index-BmGW_M3W.css">
43
43
  </head>
44
44
  <body>
45
45
  <div id="app"></div>
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
- import { ref, computed, onUnmounted } from 'vue'
3
- import { useObservatoryData, stopObservatoryPolling } from './stores/observatory'
2
+ import { ref, computed } from 'vue'
3
+ import { useObservatoryData } from './stores/observatory'
4
4
  import FetchDashboard from './views/FetchDashboard.vue'
5
5
  import ProvideInjectGraph from './views/ProvideInjectGraph.vue'
6
6
  import ComposableTracker from './views/ComposableTracker.vue'
@@ -20,10 +20,6 @@ const activeTab = ref(pathMap[segment] ?? 'fetch')
20
20
 
21
21
  const { features } = useObservatoryData()
22
22
 
23
- onUnmounted(() => {
24
- stopObservatoryPolling()
25
- })
26
-
27
23
  const tabs = computed(() => {
28
24
  const f = features.value || {}
29
25
  return [
@@ -0,0 +1,65 @@
1
+ import { ref, onBeforeUnmount } from 'vue'
2
+
3
+ /**
4
+ * Adds drag-to-resize behaviour for a right-side detail panel.
5
+ * The handle sits between the main area and the panel; dragging it
6
+ * left/right adjusts the panel width.
7
+ * @param {number} defaultWidth Initial panel width in px.
8
+ * @param {string} [storageKey] Optional localStorage key to persist the width.
9
+ * @returns {{ paneWidth: import('vue').Ref<number>, onHandleMouseDown: (e: MouseEvent) => void }} `paneWidth` ref (in px) and `onHandleMouseDown` event handler to attach to the resize handle element.
10
+ */
11
+ export function useResizablePane(defaultWidth: number, storageKey?: string) {
12
+ const stored = storageKey ? Number(localStorage.getItem(storageKey)) || defaultWidth : defaultWidth
13
+ const paneWidth = ref(Math.max(160, Math.min(600, stored)))
14
+
15
+ let dragging = false
16
+ let startX = 0
17
+ let startWidth = 0
18
+
19
+ function onMouseMove(e: MouseEvent) {
20
+ if (!dragging) {
21
+ return
22
+ }
23
+
24
+ // Handle is on the LEFT edge of the panel → moving left increases width
25
+ const delta = startX - e.clientX
26
+ paneWidth.value = Math.max(160, Math.min(600, startWidth + delta))
27
+
28
+ if (storageKey) {
29
+ // eslint-disable-next-line
30
+ localStorage.setItem(storageKey, String(paneWidth.value))
31
+ }
32
+ }
33
+
34
+ function onMouseUp() {
35
+ if (!dragging) {
36
+ return
37
+ }
38
+
39
+ dragging = false
40
+ document.removeEventListener('mousemove', onMouseMove)
41
+ document.removeEventListener('mouseup', onMouseUp)
42
+ document.body.style.cursor = ''
43
+ document.body.style.userSelect = ''
44
+ }
45
+
46
+ function onHandleMouseDown(e: MouseEvent) {
47
+ e.preventDefault()
48
+ dragging = true
49
+ startX = e.clientX
50
+ startWidth = paneWidth.value
51
+ document.addEventListener('mousemove', onMouseMove)
52
+ document.addEventListener('mouseup', onMouseUp)
53
+ document.body.style.cursor = 'col-resize'
54
+ document.body.style.userSelect = 'none'
55
+ }
56
+
57
+ onBeforeUnmount(() => {
58
+ document.removeEventListener('mousemove', onMouseMove)
59
+ document.removeEventListener('mouseup', onMouseUp)
60
+ document.body.style.cursor = ''
61
+ document.body.style.userSelect = ''
62
+ })
63
+
64
+ return { paneWidth, onHandleMouseDown }
65
+ }
@@ -1,145 +1,9 @@
1
1
  import { ref } from 'vue'
2
+ import { useDevtoolsClient, onDevtoolsClientConnected } from '@nuxt/devtools-kit/iframe-client'
3
+ import type { ObservatorySnapshot, ObservatoryServerFunctions, ObservatoryClientFunctions } from '@observatory/types/rpc'
4
+ import type { FetchEntry, ProvideEntry, InjectEntry, ComposableEntry, RenderEntry, TransitionEntry } from '@observatory/types/snapshot'
2
5
 
3
- const POLL_MS = 500
4
-
5
- export interface FetchEntry {
6
- id: string
7
- key: string
8
- url: string
9
- status: 'pending' | 'ok' | 'error' | 'cached'
10
- origin: 'ssr' | 'csr'
11
- startTime: number
12
- endTime?: number
13
- ms?: number
14
- size?: number
15
- cached: boolean
16
- payload?: unknown
17
- error?: unknown
18
- file?: string
19
- line?: number
20
- }
21
-
22
- export interface ProvideEntry {
23
- key: string
24
- componentName: string
25
- componentFile: string
26
- componentUid: number
27
- parentUid?: number
28
- parentFile?: string
29
- isReactive: boolean
30
- valueSnapshot: unknown
31
- line: number
32
- scope: 'global' | 'layout' | 'component'
33
- isShadowing: boolean
34
- }
35
-
36
- export interface InjectEntry {
37
- key: string
38
- componentName: string
39
- componentFile: string
40
- componentUid: number
41
- parentUid?: number
42
- parentFile?: string
43
- resolved: boolean
44
- resolvedFromFile?: string
45
- resolvedFromUid?: number
46
- line: number
47
- }
48
-
49
- export interface ProvideInjectSnapshot {
50
- provides: ProvideEntry[]
51
- injects: InjectEntry[]
52
- }
53
-
54
- export interface RefChangeEvent {
55
- t: number
56
- key: string
57
- value: unknown
58
- }
59
-
60
- export interface ComposableEntry {
61
- id: string
62
- name: string
63
- componentFile: string
64
- componentUid: number
65
- status: 'mounted' | 'unmounted'
66
- leak: boolean
67
- leakReason?: string
68
- refs: Record<string, { type: 'ref' | 'computed' | 'reactive'; value: unknown }>
69
- history: RefChangeEvent[]
70
- sharedKeys: string[]
71
- watcherCount: number
72
- intervalCount: number
73
- lifecycle: {
74
- hasOnMounted: boolean
75
- hasOnUnmounted: boolean
76
- watchersCleaned: boolean
77
- intervalsCleaned: boolean
78
- }
79
- file: string
80
- line: number
81
- route?: string
82
- /** File path of the component that called this composable. */
83
- callerComponentFile?: string
84
- /** Whether this composable is called from a layout component (persists across pages). */
85
- isLayoutComposable?: boolean
86
- }
87
-
88
- export interface RenderEvent {
89
- kind: 'mount' | 'update'
90
- t: number
91
- durationMs: number
92
- triggerKey?: string
93
- route: string
94
- }
95
-
96
- export interface RenderEntry {
97
- uid: number
98
- name: string
99
- file: string
100
- element?: string
101
- mountCount: number
102
- rerenders: number
103
- totalMs: number
104
- avgMs: number
105
- triggers: Array<{ key: string; type: string; timestamp: number }>
106
- timeline: RenderEvent[]
107
- rect?: { x: number; y: number; width: number; height: number; top: number; left: number }
108
- parentUid?: number
109
- isPersistent: boolean
110
- isHydrationMount: boolean
111
- route: string
112
- }
113
-
114
- export interface TransitionEntry {
115
- id: string
116
- transitionName: string
117
- parentComponent: string
118
- direction: 'enter' | 'leave'
119
- phase: 'entering' | 'entered' | 'leaving' | 'left' | 'enter-cancelled' | 'leave-cancelled' | 'interrupted'
120
- startTime: number
121
- endTime?: number
122
- durationMs?: number
123
- cancelled: boolean
124
- appear: boolean
125
- mode?: string
126
- }
127
-
128
- interface ObservatorySnapshot {
129
- fetch?: FetchEntry[]
130
- provideInject?: ProvideInjectSnapshot
131
- composables?: ComposableEntry[]
132
- renders?: RenderEntry[]
133
- transitions?: TransitionEntry[]
134
- features?: {
135
- fetchDashboard?: boolean
136
- provideInjectGraph?: boolean
137
- composableTracker?: boolean
138
- composableNavigationMode?: 'route' | 'session'
139
- renderHeatmap?: boolean
140
- transitionTracker?: boolean
141
- }
142
- }
6
+ type ProvideInjectSnapshot = { provides: ProvideEntry[]; injects: InjectEntry[] }
143
7
 
144
8
  const fetchEntries = ref<FetchEntry[]>([])
145
9
  const provideInject = ref<ProvideInjectSnapshot>({ provides: [], injects: [] })
@@ -148,12 +12,20 @@ const renders = ref<RenderEntry[]>([])
148
12
  const transitions = ref<TransitionEntry[]>([])
149
13
  const connected = ref(false)
150
14
  const features = ref<ObservatorySnapshot['features']>({})
15
+ const debugRpc = typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('debugRpc')
151
16
 
152
17
  let started = false
153
- // parentOrigin is only used for the outgoing postMessage target. We always
154
- // validate incoming messages strictly — see onMessage below.
155
- let parentOrigin = '*'
156
- let pollIntervalId: number | null = null
18
+ let rpc: ObservatoryServerFunctions | null = null
19
+ let loggedFirstSnapshot = false
20
+ let queuedMode: 'route' | 'session' | null = null
21
+ let desiredMode: 'route' | 'session' | null = null
22
+
23
+ function debugLog(...args: unknown[]) {
24
+ if (debugRpc) {
25
+ // eslint-disable-next-line no-console
26
+ console.info('[observatory][rpc][iframe]', ...args)
27
+ }
28
+ }
157
29
 
158
30
  function cloneArray<T>(value: T[] | undefined): T[] {
159
31
  return value ? value.map((item) => ({ ...item })) : []
@@ -167,113 +39,185 @@ function normalizeRenderEntries(value: RenderEntry[] | undefined): RenderEntry[]
167
39
  : []
168
40
  }
169
41
 
170
- function getParentOrigin(): string {
171
- if (typeof document === 'undefined') return ''
172
- // Prefer the opener/embedder origin from document.referrer.
173
- if (document.referrer) {
174
- try {
175
- return new URL(document.referrer).origin
176
- } catch {
177
- /* fall through */
178
- }
179
- }
180
- // When opened as a top-level window (e.g. direct navigation) there is no
181
- // referrer and no parent to receive messages from — return empty string so
182
- // we never send to or accept from '*'.
183
- return ''
184
- }
42
+ function applySnapshot(data: ObservatorySnapshot) {
43
+ fetchEntries.value = cloneArray(data.fetch as FetchEntry[] | undefined)
44
+ provideInject.value = data.provideInject
45
+ ? {
46
+ provides: cloneArray(data.provideInject.provides as ProvideInjectSnapshot['provides']),
47
+ injects: cloneArray(data.provideInject.injects as ProvideInjectSnapshot['injects']),
48
+ }
49
+ : { provides: [], injects: [] }
50
+ composables.value = cloneArray(data.composables as ComposableEntry[] | undefined)
51
+ renders.value = normalizeRenderEntries(data.renders as RenderEntry[] | undefined)
52
+ transitions.value = cloneArray(data.transitions as TransitionEntry[] | undefined)
53
+ features.value = data.features || {}
185
54
 
186
- function requestSnapshot() {
187
- if (!parentOrigin) {
188
- return
189
- }
55
+ // If the server snapshot disagrees with the user's requested mode,
56
+ // keep trying to reconcile so mode doesn't silently snap back.
57
+ const snapshotMode = features.value?.composableNavigationMode
190
58
 
191
- window.top?.postMessage({ type: 'observatory:request' }, parentOrigin)
192
- }
59
+ if (desiredMode && snapshotMode !== desiredMode) {
60
+ features.value = { ...(features.value || {}), composableNavigationMode: desiredMode }
193
61
 
194
- function onMessage(event: MessageEvent) {
195
- if (event.data?.type !== 'observatory:snapshot') {
196
- return
62
+ rpc?.setComposableMode(desiredMode)
63
+ .then(() => rpc?.requestSnapshot())
64
+ .catch((error) => {
65
+ debugLog('setComposableMode reconcile failed', error)
66
+ })
197
67
  }
198
68
 
199
- // Always validate the origin of incoming snapshot messages.
200
- // Accepting '*' would allow any page to inject arbitrary devtools data.
201
- // For local screenshots/dev, allow any origin if parentOrigin is empty (SPA running standalone)
202
- if (parentOrigin && event.origin !== parentOrigin) {
203
- return
69
+ if (desiredMode && snapshotMode === desiredMode) {
70
+ desiredMode = null
204
71
  }
205
72
 
206
- const data = event.data.data as ObservatorySnapshot
207
- fetchEntries.value = cloneArray(data.fetch)
208
- provideInject.value = data.provideInject
209
- ? {
210
- provides: cloneArray(data.provideInject.provides),
211
- injects: cloneArray(data.provideInject.injects),
212
- }
213
- : { provides: [], injects: [] }
214
- composables.value = cloneArray(data.composables)
215
- renders.value = normalizeRenderEntries(data.renders)
216
- transitions.value = cloneArray(data.transitions)
217
- features.value = data.features || {}
218
73
  connected.value = true
219
- }
220
74
 
221
- function onVisibilityChange() {
222
- if (document.visibilityState === 'hidden') {
223
- // Tab is no longer visible — pause polling to avoid triggering
224
- // buildSnapshot() in the parent app for results nobody is looking at.
225
- if (pollIntervalId !== null) {
226
- window.clearInterval(pollIntervalId)
227
- pollIntervalId = null
228
- }
229
- } else {
230
- // Tab became visible again — resume polling and request immediately.
231
- if (pollIntervalId === null && started) {
232
- pollIntervalId = window.setInterval(requestSnapshot, POLL_MS)
233
- requestSnapshot()
234
- }
75
+ if (!loggedFirstSnapshot) {
76
+ loggedFirstSnapshot = true
77
+ debugLog('first snapshot received', {
78
+ fetch: fetchEntries.value.length,
79
+ composables: composables.value.length,
80
+ renders: renders.value.length,
81
+ transitions: transitions.value.length,
82
+ })
235
83
  }
236
84
  }
237
85
 
238
86
  function ensureStarted() {
239
- if (started || typeof window === 'undefined') {
87
+ if (started) {
240
88
  return
241
89
  }
242
90
 
243
91
  started = true
244
- parentOrigin = getParentOrigin()
245
- window.addEventListener('message', onMessage)
246
- document.addEventListener('visibilitychange', onVisibilityChange)
247
- pollIntervalId = window.setInterval(requestSnapshot, POLL_MS)
248
- requestSnapshot()
92
+
93
+ // Support mock data injection via postMessage (used by the screenshot capture script).
94
+ if (typeof window !== 'undefined') {
95
+ window.addEventListener('message', (event) => {
96
+ if (event.data && event.data.type === 'observatory:snapshot') {
97
+ applySnapshot(event.data.data)
98
+ }
99
+ })
100
+ }
101
+
102
+ const client = useDevtoolsClient()
103
+
104
+ const setupRpc = () => {
105
+ if (!client.value || rpc) {
106
+ return
107
+ }
108
+
109
+ rpc = client.value.devtools.extendClientRpc<ObservatoryServerFunctions, ObservatoryClientFunctions>('observatory', {
110
+ onSnapshot(snapshot) {
111
+ applySnapshot(snapshot)
112
+ },
113
+ })
114
+
115
+ debugLog('RPC connected')
116
+
117
+ if (queuedMode) {
118
+ const mode = queuedMode
119
+ queuedMode = null
120
+ rpc.setComposableMode(mode).catch((error) => {
121
+ debugLog('setComposableMode failed (queued)', error)
122
+ })
123
+ }
124
+
125
+ rpc.getSnapshot()
126
+ .then((snapshot) => {
127
+ if (snapshot) {
128
+ applySnapshot(snapshot)
129
+ }
130
+ })
131
+ .catch(() => {
132
+ // Keep the UI usable while the host app is reloading.
133
+ })
134
+
135
+ rpc.requestSnapshot().catch(() => {
136
+ // Host app may still be initializing; a later push will update the UI.
137
+ })
138
+ }
139
+
140
+ setupRpc()
141
+ onDevtoolsClientConnected(() => {
142
+ setupRpc()
143
+ })
249
144
  }
250
145
 
251
146
  /**
252
- * Stops polling and removes the message listener.
253
- * Call this when tearing down the SPA (e.g. in an onUnmounted hook at the
254
- * root component level) to prevent the interval from running indefinitely.
147
+ * Kept as a no-op for backwards compatibility.
255
148
  */
256
149
  export function stopObservatoryPolling() {
257
- if (pollIntervalId !== null) {
258
- window.clearInterval(pollIntervalId)
259
- pollIntervalId = null
260
- }
261
- window.removeEventListener('message', onMessage)
262
- document.removeEventListener('visibilitychange', onVisibilityChange)
263
- started = false
150
+ // No polling to stop after birpc migration.
264
151
  }
265
152
 
266
153
  export function getObservatoryOrigin() {
267
- return parentOrigin
154
+ return window.location.origin
268
155
  }
269
156
 
270
157
  export function clearComposables() {
271
158
  composables.value = []
159
+ rpc?.clearComposables()
160
+ .then(() => rpc?.requestSnapshot())
161
+ .catch((error) => {
162
+ debugLog('clearComposables failed', error)
163
+ })
164
+ }
165
+
166
+ export function setComposableMode(mode: 'route' | 'session') {
167
+ desiredMode = mode
168
+
169
+ // Keep UI responsive even when RPC is still initializing.
170
+ features.value = { ...(features.value || {}), composableNavigationMode: mode }
171
+
172
+ if (!rpc) {
173
+ queuedMode = mode
174
+ debugLog('setComposableMode queued', mode)
175
+
176
+ return
177
+ }
178
+
179
+ rpc.setComposableMode(mode)
180
+ .then(() => rpc?.requestSnapshot())
181
+ .catch((error) => {
182
+ debugLog('setComposableMode failed', error)
183
+ })
184
+ }
185
+
186
+ export function editComposableValue(id: string, key: string, value: unknown) {
187
+ rpc?.editComposableValue(id, key, value).catch((error) => {
188
+ debugLog('editComposableValue failed', error)
189
+ })
190
+ }
191
+
192
+ export function openInEditor(file: string) {
193
+ if (!file || file === 'unknown') {
194
+ return
195
+ }
196
+
197
+ // Uses the built-in Nuxt DevTools RPC when available.
198
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
199
+ const devtoolsRpc = (useDevtoolsClient().value?.devtools as any)?.rpc
200
+
201
+ if (devtoolsRpc?.openInEditor) {
202
+ devtoolsRpc.openInEditor(file)
203
+ }
272
204
  }
273
205
 
274
206
  export function useObservatoryData() {
275
207
  ensureStarted()
276
208
 
209
+ const refresh = () => {
210
+ rpc?.getSnapshot()
211
+ .then((snapshot) => {
212
+ if (snapshot) {
213
+ applySnapshot(snapshot)
214
+ }
215
+ })
216
+ .catch(() => {})
217
+
218
+ rpc?.requestSnapshot().catch(() => {})
219
+ }
220
+
277
221
  return {
278
222
  fetch: fetchEntries,
279
223
  provideInject,
@@ -282,7 +226,7 @@ export function useObservatoryData() {
282
226
  transitions,
283
227
  features,
284
228
  connected,
285
- refresh: requestSnapshot,
229
+ refresh,
286
230
  clearComposables,
287
231
  }
288
232
  }