@ethlete/query-devtools 1.0.0-next.2 → 1.0.0-next.4

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.
@@ -1,7 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { signal, ViewEncapsulation, Component, input, output, computed, viewChild, effect, numberAttribute, linkedSignal, InjectionToken, inject, untracked, ElementRef, NgZone, DestroyRef, booleanAttribute } from '@angular/core';
3
3
  import { copyToClipboard, injectStyleManager, injectErrorTheme, ProvideColorDirective, clamp, injectRenderer, injectFileDownload, injectViewportSize, injectIsDocumentVisible, injectBreakpointObserver, signalElementDimensions, randomId, createObjectUrlHandle, DragHandleDirective, ResizeHandlesComponent } from '@ethlete/core';
4
- import { queryDevtoolsAbout, hasQueryDevtoolsOverridesAtPath, detectPaginationShape, isDateShapedLeaf, generateQueryDevtoolsStringPreset, generateQueryDevtoolsNumberPreset, collectLeafPaths, armAllQueryDevtoolsMocks, clearQueryDevtoolsArmedMocks, clearQueryDevtoolsFaults, queryDevtoolsResponseHistory, queryDevtoolsMocks, queryDevtoolsArmedMocks, queryDevtoolsSettings, setQueryDevtoolsOverridesScope, setQueryDevtoolsArmedMocksScope, setQueryDevtoolsFaultsScope, setQueryDevtoolsSettings, QUERY_DEVTOOLS_TOKEN_TTL_LIMIT, setQueryDevtoolsTokenTtl, clearQueryDevtoolsTokenTtl, createQueryDevtoolsOverrides, applyQueryDevtoolsOverrides, queryDevtoolsSchemaNames, queryDevtoolsSchemaRoutes, queryDevtoolsSchemaState, measureQueryDevtoolsPayload, seedQueryDevtoolsSchemaBody, loadQueryDevtoolsSchema, queryDevtoolsMockId, saveQueryDevtoolsMock, armQueryDevtoolsMock, deleteQueryDevtoolsMock, collectQueryDevtoolsSchemaComponents, seedQueryDevtoolsSchemaRoute, QUERY_DEVTOOLS_FAULT_STATUSES, setQueryDevtoolsFault, serializeQueryDevtoolsOverrideTransfer, parseQueryDevtoolsOverrideTransfer, armQueryDevtoolsOverrideTransfer, countUnresolvedQueryDevtoolsOverrides, clearQueryDevtoolsTombstones, readQueryDevtoolsStore, createQueryKeyLockManager, queryDevtoolsEntries, queryDevtoolsTokenTtls, queryDevtoolsFaults, EMPTY_QUERY_DEVTOOLS_FAULT, isQueryDevtoolsFaultArmed, queryDevtoolsFaultsRestored, queryDevtoolsArmedMocksRestored, restoredQueryDevtoolsOverrides, queryDevtoolsRestoredOverridesScope, clearRestoredQueryDevtoolsOverrides, clearQueryDevtoolsStore, writeQueryDevtoolsStore, registerEthleteVersion, queryDevtoolsOverridePersistence, setQueryDevtoolsOverridePersistence, clearQueryDevtoolsOverrideStore, clearQueryDevtoolsMockStore, createQueryErrorResponse, applyQueryDevtoolsTokenTtl, canOverrideQueryDevtoolsTokenTtl, sumQueryDevtoolsStats } from '@ethlete/query';
4
+ import { queryDevtoolsAbout, hasQueryDevtoolsOverridesAtPath, detectPaginationShape, isDateShapedLeaf, generateQueryDevtoolsStringPreset, generateQueryDevtoolsNumberPreset, collectLeafPaths, armAllQueryDevtoolsMocks, clearQueryDevtoolsArmedMocks, clearQueryDevtoolsFaults, queryDevtoolsResponseHistory, queryDevtoolsMocks, queryDevtoolsArmedMocks, queryDevtoolsSettings, setQueryDevtoolsOverridesScope, setQueryDevtoolsArmedMocksScope, setQueryDevtoolsFaultsScope, setQueryDevtoolsSettings, QUERY_DEVTOOLS_TOKEN_TTL_LIMIT, setQueryDevtoolsTokenTtl, clearQueryDevtoolsTokenTtl, serializeQueryDevtoolsOverrideTransfer, parseQueryDevtoolsOverrideTransfer, armQueryDevtoolsOverrideTransfer, countUnresolvedQueryDevtoolsOverrides, createQueryDevtoolsOverrides, applyQueryDevtoolsOverrides, queryDevtoolsSchemaNames, queryDevtoolsSchemaRoutes, queryDevtoolsSchemaState, measureQueryDevtoolsPayload, seedQueryDevtoolsSchemaBody, loadQueryDevtoolsSchema, queryDevtoolsMockId, saveQueryDevtoolsMock, armQueryDevtoolsMock, deleteQueryDevtoolsMock, collectQueryDevtoolsSchemaComponents, seedQueryDevtoolsSchemaRoute, QUERY_DEVTOOLS_FAULT_STATUSES, setQueryDevtoolsFault, clearQueryDevtoolsTombstones, readQueryDevtoolsStore, createQueryKeyLockManager, queryDevtoolsEntries, queryDevtoolsTokenTtls, queryDevtoolsFaults, EMPTY_QUERY_DEVTOOLS_FAULT, isQueryDevtoolsFaultArmed, queryDevtoolsFaultsRestored, queryDevtoolsArmedMocksRestored, restoredQueryDevtoolsOverrides, queryDevtoolsRestoredOverridesScope, clearRestoredQueryDevtoolsOverrides, clearQueryDevtoolsStore, writeQueryDevtoolsStore, registerEthleteVersion, queryDevtoolsOverridePersistence, setQueryDevtoolsOverridePersistence, clearQueryDevtoolsOverrideStore, clearQueryDevtoolsMockStore, createQueryErrorResponse, applyQueryDevtoolsTokenTtl, canOverrideQueryDevtoolsTokenTtl, sumQueryDevtoolsStats } from '@ethlete/query';
5
5
  import { tap, Subject, switchMap, timer, interval, animationFrames, NEVER, takeUntil, finalize as finalize$1, EMPTY, startWith, merge, map, fromEvent, filter, take } from 'rxjs';
6
6
  import { takeUntilDestroyed, toSignal, toObservable } from '@angular/core/rxjs-interop';
7
7
  import { MenuDirective, MenuTriggerDirective, MenuSurfaceDirective, MenuComponent, MenuItemComponent, MenuSearchDirective, MenuItemShortcutComponent, MenuSeparatorComponent, SelectComponent, SelectSearchDirective, COMPONENTS_VERSION, MenuCheckboxItemComponent, MenuRadioGroupComponent, MenuRadioItemComponent } from '@ethlete/components';
@@ -1558,11 +1558,11 @@ class QueryDevtoolsCacheTabComponent {
1558
1558
  return DESTROY_CAUSE_LABELS[cause];
1559
1559
  }
1560
1560
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsCacheTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1561
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsCacheTabComponent, isStandalone: true, selector: "et-query-devtools-cache-tab", ngImport: i0, template: "<div class=\"et-query-devtools-scroll\">\n @for (client of host.cacheView(); track client.name) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong class=\"et-query-devtools-mono\">{{ client.baseUrl || client.name }}</strong>\n <span class=\"et-query-devtools-count\">{{ client.rows.length }} entries</span>\n @if (client.rows.length) {\n <span\n class=\"et-query-devtools-count\"\n title=\"The measured size of every cached response. Sizes are read from the decoded bodies, so they ignore transport compression.\"\n >\n {{ host.formatTransferred(client.bytes, client.isEstimatedBytes) }}\n </span>\n }\n @if (client.unused) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"Entries with no consumer left. They are waiting out their keepUnusedFor window, not leaking.\"\n >\n {{ client.unused }} collectible\n </span>\n }\n @if (client.client?.subtle?.persistence) {\n <span class=\"et-query-devtools-count\">{{ host.persistedCount(client.client!) }} on disk</span>\n <button (click)=\"host.clearPersistedQueries(client.client!)\" class=\"et-query-devtools-btn\" type=\"button\">\n Clear disk\n </button>\n }\n <button\n [disabled]=\"!client.rows.length\"\n (click)=\"host.evictAllCacheEntries(client.repository)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Drop every entry of this client, consumers included - the cold-start check without a reload\"\n >\n Evict all\n </button>\n </div>\n @if (host.clientFeatures(client.client); as features) {\n <et-query-devtools-features [features]=\"features\" />\n }\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Request</th>\n <th>Consumers</th>\n <th>Size</th>\n <th>Secure</th>\n <th>Freshness</th>\n <th>Sync</th>\n <th>Disk</th>\n <th>Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (row of client.rows; track row.entry.key) {\n @let cacheEntry = row.entry;\n @let expanded = host.isCacheValueExpanded(client.name, cacheEntry.key);\n <tr>\n <td [title]=\"cacheEntry.request.method + ' ' + cacheEntry.request.url\">\n <span class=\"et-query-devtools-method\">{{ cacheEntry.request.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(cacheEntry.request.url) }}</span>\n </td>\n <td>\n {{ cacheEntry.consumerCount }}\n <!-- An entry nothing holds any more is the one row worth explaining: it looks\n like a leak and is in fact a keepUnusedFor window running out. -->\n @if (cacheEntry.isUnused) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"No consumer left - kept for its keepUnusedFor window, then collected\"\n >\n collectible\n </span>\n }\n </td>\n <td>{{ row.bytes ? host.formatTransferred(row.bytes, row.isEstimatedBytes) : '\u2014' }}</td>\n <td>{{ cacheEntry.isSecure ? 'yes' : 'no' }}</td>\n <td>{{ host.cacheFreshness(cacheEntry) }}</td>\n <td>{{ host.cacheSync(cacheEntry, client.pollStates) }}</td>\n <td>{{ host.cachePersistence(cacheEntry) }}</td>\n <td>\n <span class=\"et-query-devtools-cell-actions\">\n <button\n [class.et-query-devtools-btn--active]=\"expanded\"\n [attr.aria-expanded]=\"expanded\"\n (click)=\"host.toggleCacheValue(client.name, cacheEntry.key)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Show the response held under this key\"\n >\n Value\n </button>\n <button (click)=\"host.refetchCacheEntry(cacheEntry)\" class=\"et-query-devtools-btn\" type=\"button\">\n Refetch\n </button>\n <button\n (click)=\"host.evictCacheEntry(client.repository, cacheEntry.key)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Evict\n </button>\n </span>\n </td>\n </tr>\n @if (expanded) {\n <tr>\n <td class=\"et-query-devtools-cell-value\" colspan=\"8\">\n <et-query-devtools-json [value]=\"cacheEntry.request.response()\" />\n </td>\n </tr>\n }\n } @empty {\n <tr>\n <td class=\"et-query-devtools-empty\" colspan=\"8\">Cache empty.</td>\n </tr>\n }\n </tbody>\n </table>\n\n @if (client.dropped.length) {\n <h4 class=\"et-query-devtools-section\">Dropped</h4>\n <ul class=\"et-query-devtools-dropped\">\n @for (entry of client.dropped; track entry.at) {\n <li>\n <span class=\"et-query-devtools-method\">{{ entry.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(entry.url) }}</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{\n dropCauseLabel(entry.cause)\n }}</span>\n <span class=\"et-query-devtools-count\">{{ host.formatTime(entry.at) }}</span>\n </li>\n }\n </ul>\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No clients with a cache yet.</p>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-dropped{display:flex;flex-direction:column;gap:.4rem;margin:0;padding:0;list-style:none;color:var(--_et-qdt-muted)}.et-query-devtools-dropped li{display:flex;align-items:center;gap:.8rem}.et-query-devtools-cell-value{padding-inline-start:1.6rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 3%,transparent)}}\n"], dependencies: [{ kind: "component", type: QueryDevtoolsFeaturesComponent, selector: "et-query-devtools-features", inputs: ["features"] }, { kind: "component", type: QueryDevtoolsJsonComponent, selector: "et-query-devtools-json", inputs: ["value", "nodeKey", "depth", "search", "chunk", "path", "expandedPaths", "collapsedPaths", "toggleFn", "jsonPath", "parentKind", "overrides", "annotations"] }], encapsulation: i0.ViewEncapsulation.None }); }
1561
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsCacheTabComponent, isStandalone: true, selector: "et-query-devtools-cache-tab", ngImport: i0, template: "<div class=\"et-query-devtools-scroll\">\n @for (client of host.cacheView(); track client.name) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong class=\"et-query-devtools-mono\">{{ client.baseUrl || client.name }}</strong>\n <span class=\"et-query-devtools-count\">{{ client.rows.length }} entries</span>\n @if (client.rows.length) {\n <span\n class=\"et-query-devtools-count\"\n title=\"The measured size of every cached response. Sizes are read from the decoded bodies, so they ignore transport compression.\"\n >\n {{ host.formatTransferred(client.bytes, client.isEstimatedBytes) }}\n </span>\n }\n @if (client.unused) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"Entries with no consumer left. They are waiting out their keepUnusedFor window, not leaking.\"\n >\n {{ client.unused }} collectible\n </span>\n }\n @if (client.client?.subtle?.persistence) {\n <span class=\"et-query-devtools-count\">{{ host.persistedCount(client.client!) }} on disk</span>\n <button (click)=\"host.clearPersistedQueries(client.client!)\" class=\"et-query-devtools-btn\" type=\"button\">\n Clear disk\n </button>\n }\n <button\n [disabled]=\"!client.rows.length\"\n (click)=\"host.evictAllCacheEntries(client.repository)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Drop every entry of this client, consumers included - the cold-start check without a reload\"\n >\n Evict all\n </button>\n </div>\n @if (host.clientFeatures(client.client); as features) {\n <et-query-devtools-features [features]=\"features\" />\n }\n <div class=\"et-query-devtools-table-scroll\">\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Request</th>\n <th>Consumers</th>\n <th>Size</th>\n <th>Secure</th>\n <th>Freshness</th>\n <th>Sync</th>\n <th>Disk</th>\n <th>Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (row of client.rows; track row.entry.key) {\n @let cacheEntry = row.entry;\n @let expanded = host.isCacheValueExpanded(client.name, cacheEntry.key);\n <tr>\n <td [title]=\"cacheEntry.request.method + ' ' + cacheEntry.request.url\">\n <span class=\"et-query-devtools-method\">{{ cacheEntry.request.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(cacheEntry.request.url) }}</span>\n </td>\n <td>\n {{ cacheEntry.consumerCount }}\n <!-- An entry nothing holds any more is the one row worth explaining: it looks\n like a leak and is in fact a keepUnusedFor window running out. -->\n @if (cacheEntry.isUnused) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"No consumer left - kept for its keepUnusedFor window, then collected\"\n >\n collectible\n </span>\n }\n </td>\n <td>{{ row.bytes ? host.formatTransferred(row.bytes, row.isEstimatedBytes) : '\u2014' }}</td>\n <td>{{ cacheEntry.isSecure ? 'yes' : 'no' }}</td>\n <td>{{ host.cacheFreshness(cacheEntry) }}</td>\n <td>{{ host.cacheSync(cacheEntry, client.pollStates) }}</td>\n <td>{{ host.cachePersistence(cacheEntry) }}</td>\n <td>\n <span class=\"et-query-devtools-cell-actions\">\n <button\n [class.et-query-devtools-btn--active]=\"expanded\"\n [attr.aria-expanded]=\"expanded\"\n (click)=\"host.toggleCacheValue(client.name, cacheEntry.key)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Show the response held under this key\"\n >\n Value\n </button>\n <button (click)=\"host.refetchCacheEntry(cacheEntry)\" class=\"et-query-devtools-btn\" type=\"button\">\n Refetch\n </button>\n <button\n (click)=\"host.evictCacheEntry(client.repository, cacheEntry.key)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Evict\n </button>\n </span>\n </td>\n </tr>\n @if (expanded) {\n <tr>\n <td class=\"et-query-devtools-cell-value\" colspan=\"8\">\n <et-query-devtools-json [value]=\"cacheEntry.request.response()\" />\n </td>\n </tr>\n }\n } @empty {\n <tr>\n <td class=\"et-query-devtools-empty\" colspan=\"8\">Cache empty.</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n @if (client.dropped.length) {\n <h4 class=\"et-query-devtools-section\">Dropped</h4>\n <ul class=\"et-query-devtools-dropped\">\n @for (entry of client.dropped; track entry.at) {\n <li>\n <span class=\"et-query-devtools-method\">{{ entry.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(entry.url) }}</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{\n dropCauseLabel(entry.cause)\n }}</span>\n <span class=\"et-query-devtools-count\">{{ host.formatTime(entry.at) }}</span>\n </li>\n }\n </ul>\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No clients with a cache yet.</p>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-dropped{display:flex;flex-direction:column;gap:.4rem;margin:0;padding:0;list-style:none;color:var(--_et-qdt-muted)}.et-query-devtools-dropped li{display:flex;align-items:center;gap:.8rem}.et-query-devtools-cell-value{padding-inline-start:1.6rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 3%,transparent)}}\n"], dependencies: [{ kind: "component", type: QueryDevtoolsFeaturesComponent, selector: "et-query-devtools-features", inputs: ["features"] }, { kind: "component", type: QueryDevtoolsJsonComponent, selector: "et-query-devtools-json", inputs: ["value", "nodeKey", "depth", "search", "chunk", "path", "expandedPaths", "collapsedPaths", "toggleFn", "jsonPath", "parentKind", "overrides", "annotations"] }], encapsulation: i0.ViewEncapsulation.None }); }
1562
1562
  }
1563
1563
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsCacheTabComponent, decorators: [{
1564
1564
  type: Component,
1565
- args: [{ selector: 'et-query-devtools-cache-tab', encapsulation: ViewEncapsulation.None, imports: [QueryDevtoolsFeaturesComponent, QueryDevtoolsJsonComponent], template: "<div class=\"et-query-devtools-scroll\">\n @for (client of host.cacheView(); track client.name) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong class=\"et-query-devtools-mono\">{{ client.baseUrl || client.name }}</strong>\n <span class=\"et-query-devtools-count\">{{ client.rows.length }} entries</span>\n @if (client.rows.length) {\n <span\n class=\"et-query-devtools-count\"\n title=\"The measured size of every cached response. Sizes are read from the decoded bodies, so they ignore transport compression.\"\n >\n {{ host.formatTransferred(client.bytes, client.isEstimatedBytes) }}\n </span>\n }\n @if (client.unused) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"Entries with no consumer left. They are waiting out their keepUnusedFor window, not leaking.\"\n >\n {{ client.unused }} collectible\n </span>\n }\n @if (client.client?.subtle?.persistence) {\n <span class=\"et-query-devtools-count\">{{ host.persistedCount(client.client!) }} on disk</span>\n <button (click)=\"host.clearPersistedQueries(client.client!)\" class=\"et-query-devtools-btn\" type=\"button\">\n Clear disk\n </button>\n }\n <button\n [disabled]=\"!client.rows.length\"\n (click)=\"host.evictAllCacheEntries(client.repository)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Drop every entry of this client, consumers included - the cold-start check without a reload\"\n >\n Evict all\n </button>\n </div>\n @if (host.clientFeatures(client.client); as features) {\n <et-query-devtools-features [features]=\"features\" />\n }\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Request</th>\n <th>Consumers</th>\n <th>Size</th>\n <th>Secure</th>\n <th>Freshness</th>\n <th>Sync</th>\n <th>Disk</th>\n <th>Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (row of client.rows; track row.entry.key) {\n @let cacheEntry = row.entry;\n @let expanded = host.isCacheValueExpanded(client.name, cacheEntry.key);\n <tr>\n <td [title]=\"cacheEntry.request.method + ' ' + cacheEntry.request.url\">\n <span class=\"et-query-devtools-method\">{{ cacheEntry.request.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(cacheEntry.request.url) }}</span>\n </td>\n <td>\n {{ cacheEntry.consumerCount }}\n <!-- An entry nothing holds any more is the one row worth explaining: it looks\n like a leak and is in fact a keepUnusedFor window running out. -->\n @if (cacheEntry.isUnused) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"No consumer left - kept for its keepUnusedFor window, then collected\"\n >\n collectible\n </span>\n }\n </td>\n <td>{{ row.bytes ? host.formatTransferred(row.bytes, row.isEstimatedBytes) : '\u2014' }}</td>\n <td>{{ cacheEntry.isSecure ? 'yes' : 'no' }}</td>\n <td>{{ host.cacheFreshness(cacheEntry) }}</td>\n <td>{{ host.cacheSync(cacheEntry, client.pollStates) }}</td>\n <td>{{ host.cachePersistence(cacheEntry) }}</td>\n <td>\n <span class=\"et-query-devtools-cell-actions\">\n <button\n [class.et-query-devtools-btn--active]=\"expanded\"\n [attr.aria-expanded]=\"expanded\"\n (click)=\"host.toggleCacheValue(client.name, cacheEntry.key)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Show the response held under this key\"\n >\n Value\n </button>\n <button (click)=\"host.refetchCacheEntry(cacheEntry)\" class=\"et-query-devtools-btn\" type=\"button\">\n Refetch\n </button>\n <button\n (click)=\"host.evictCacheEntry(client.repository, cacheEntry.key)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Evict\n </button>\n </span>\n </td>\n </tr>\n @if (expanded) {\n <tr>\n <td class=\"et-query-devtools-cell-value\" colspan=\"8\">\n <et-query-devtools-json [value]=\"cacheEntry.request.response()\" />\n </td>\n </tr>\n }\n } @empty {\n <tr>\n <td class=\"et-query-devtools-empty\" colspan=\"8\">Cache empty.</td>\n </tr>\n }\n </tbody>\n </table>\n\n @if (client.dropped.length) {\n <h4 class=\"et-query-devtools-section\">Dropped</h4>\n <ul class=\"et-query-devtools-dropped\">\n @for (entry of client.dropped; track entry.at) {\n <li>\n <span class=\"et-query-devtools-method\">{{ entry.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(entry.url) }}</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{\n dropCauseLabel(entry.cause)\n }}</span>\n <span class=\"et-query-devtools-count\">{{ host.formatTime(entry.at) }}</span>\n </li>\n }\n </ul>\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No clients with a cache yet.</p>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-dropped{display:flex;flex-direction:column;gap:.4rem;margin:0;padding:0;list-style:none;color:var(--_et-qdt-muted)}.et-query-devtools-dropped li{display:flex;align-items:center;gap:.8rem}.et-query-devtools-cell-value{padding-inline-start:1.6rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 3%,transparent)}}\n"] }]
1565
+ args: [{ selector: 'et-query-devtools-cache-tab', encapsulation: ViewEncapsulation.None, imports: [QueryDevtoolsFeaturesComponent, QueryDevtoolsJsonComponent], template: "<div class=\"et-query-devtools-scroll\">\n @for (client of host.cacheView(); track client.name) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong class=\"et-query-devtools-mono\">{{ client.baseUrl || client.name }}</strong>\n <span class=\"et-query-devtools-count\">{{ client.rows.length }} entries</span>\n @if (client.rows.length) {\n <span\n class=\"et-query-devtools-count\"\n title=\"The measured size of every cached response. Sizes are read from the decoded bodies, so they ignore transport compression.\"\n >\n {{ host.formatTransferred(client.bytes, client.isEstimatedBytes) }}\n </span>\n }\n @if (client.unused) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"Entries with no consumer left. They are waiting out their keepUnusedFor window, not leaking.\"\n >\n {{ client.unused }} collectible\n </span>\n }\n @if (client.client?.subtle?.persistence) {\n <span class=\"et-query-devtools-count\">{{ host.persistedCount(client.client!) }} on disk</span>\n <button (click)=\"host.clearPersistedQueries(client.client!)\" class=\"et-query-devtools-btn\" type=\"button\">\n Clear disk\n </button>\n }\n <button\n [disabled]=\"!client.rows.length\"\n (click)=\"host.evictAllCacheEntries(client.repository)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Drop every entry of this client, consumers included - the cold-start check without a reload\"\n >\n Evict all\n </button>\n </div>\n @if (host.clientFeatures(client.client); as features) {\n <et-query-devtools-features [features]=\"features\" />\n }\n <div class=\"et-query-devtools-table-scroll\">\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Request</th>\n <th>Consumers</th>\n <th>Size</th>\n <th>Secure</th>\n <th>Freshness</th>\n <th>Sync</th>\n <th>Disk</th>\n <th>Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (row of client.rows; track row.entry.key) {\n @let cacheEntry = row.entry;\n @let expanded = host.isCacheValueExpanded(client.name, cacheEntry.key);\n <tr>\n <td [title]=\"cacheEntry.request.method + ' ' + cacheEntry.request.url\">\n <span class=\"et-query-devtools-method\">{{ cacheEntry.request.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(cacheEntry.request.url) }}</span>\n </td>\n <td>\n {{ cacheEntry.consumerCount }}\n <!-- An entry nothing holds any more is the one row worth explaining: it looks\n like a leak and is in fact a keepUnusedFor window running out. -->\n @if (cacheEntry.isUnused) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"No consumer left - kept for its keepUnusedFor window, then collected\"\n >\n collectible\n </span>\n }\n </td>\n <td>{{ row.bytes ? host.formatTransferred(row.bytes, row.isEstimatedBytes) : '\u2014' }}</td>\n <td>{{ cacheEntry.isSecure ? 'yes' : 'no' }}</td>\n <td>{{ host.cacheFreshness(cacheEntry) }}</td>\n <td>{{ host.cacheSync(cacheEntry, client.pollStates) }}</td>\n <td>{{ host.cachePersistence(cacheEntry) }}</td>\n <td>\n <span class=\"et-query-devtools-cell-actions\">\n <button\n [class.et-query-devtools-btn--active]=\"expanded\"\n [attr.aria-expanded]=\"expanded\"\n (click)=\"host.toggleCacheValue(client.name, cacheEntry.key)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Show the response held under this key\"\n >\n Value\n </button>\n <button (click)=\"host.refetchCacheEntry(cacheEntry)\" class=\"et-query-devtools-btn\" type=\"button\">\n Refetch\n </button>\n <button\n (click)=\"host.evictCacheEntry(client.repository, cacheEntry.key)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Evict\n </button>\n </span>\n </td>\n </tr>\n @if (expanded) {\n <tr>\n <td class=\"et-query-devtools-cell-value\" colspan=\"8\">\n <et-query-devtools-json [value]=\"cacheEntry.request.response()\" />\n </td>\n </tr>\n }\n } @empty {\n <tr>\n <td class=\"et-query-devtools-empty\" colspan=\"8\">Cache empty.</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n @if (client.dropped.length) {\n <h4 class=\"et-query-devtools-section\">Dropped</h4>\n <ul class=\"et-query-devtools-dropped\">\n @for (entry of client.dropped; track entry.at) {\n <li>\n <span class=\"et-query-devtools-method\">{{ entry.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(entry.url) }}</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{\n dropCauseLabel(entry.cause)\n }}</span>\n <span class=\"et-query-devtools-count\">{{ host.formatTime(entry.at) }}</span>\n </li>\n }\n </ul>\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No clients with a cache yet.</p>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-dropped{display:flex;flex-direction:column;gap:.4rem;margin:0;padding:0;list-style:none;color:var(--_et-qdt-muted)}.et-query-devtools-dropped li{display:flex;align-items:center;gap:.8rem}.et-query-devtools-cell-value{padding-inline-start:1.6rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 3%,transparent)}}\n"] }]
1566
1566
  }] });
1567
1567
 
1568
1568
  const jsonBody$1 = (body) => {
@@ -1618,6 +1618,304 @@ const buildCurlCommand = (request) => {
1618
1618
  return lines.join(' \\\n ');
1619
1619
  };
1620
1620
 
1621
+ /**
1622
+ * Copies the whole set of response overrides armed on one query to the clipboard, and pastes one back -
1623
+ * onto this query or any other. The payload is the same JSON the override store persists, so a set can
1624
+ * also go into a ticket and come back from one.
1625
+ *
1626
+ * @internal
1627
+ */
1628
+ class QueryDevtoolsOverrideSetMenuComponent {
1629
+ constructor() {
1630
+ this.errorColorTheme = injectErrorTheme();
1631
+ this.overrides = input.required(/* @ts-ignore */
1632
+ ...(ngDevMode ? [{ debugName: "overrides" }] : /* istanbul ignore next */ []));
1633
+ /** The response a pasted set is measured against, to report the ops that land on nothing. */
1634
+ this.response = input(/* @ts-ignore */
1635
+ ...(ngDevMode ? [undefined, { debugName: "response" }] : /* istanbul ignore next */ []));
1636
+ /** Where the set came from, carried into the payload for whoever reads it next. */
1637
+ this.source = input(/* @ts-ignore */
1638
+ ...(ngDevMode ? [undefined, { debugName: "source" }] : /* istanbul ignore next */ []));
1639
+ this.pasteInput = viewChild('pasteBox', /* @ts-ignore */
1640
+ ...(ngDevMode ? [{ debugName: "pasteInput" }] : /* istanbul ignore next */ []));
1641
+ this.armedCount = computed(() => this.overrides().list().length, /* @ts-ignore */
1642
+ ...(ngDevMode ? [{ debugName: "armedCount" }] : /* istanbul ignore next */ []));
1643
+ this.pasteMode = signal(false, /* @ts-ignore */
1644
+ ...(ngDevMode ? [{ debugName: "pasteMode" }] : /* istanbul ignore next */ []));
1645
+ this.error = signal(null, /* @ts-ignore */
1646
+ ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
1647
+ this.note = signal(null, /* @ts-ignore */
1648
+ ...(ngDevMode ? [{ debugName: "note" }] : /* istanbul ignore next */ []));
1649
+ this.copied = signal(false, /* @ts-ignore */
1650
+ ...(ngDevMode ? [{ debugName: "copied" }] : /* istanbul ignore next */ []));
1651
+ injectStyleManager().mount(QueryDevtoolsOverrideMenuStylesComponent);
1652
+ effect(() => this.pasteInput()?.nativeElement.focus());
1653
+ }
1654
+ copySet() {
1655
+ const text = serializeQueryDevtoolsOverrideTransfer(this.overrides().list(), this.source());
1656
+ navigator.clipboard?.writeText(text).then(() => this.copied.set(true), () => this.error.set('The clipboard write was blocked'));
1657
+ }
1658
+ pasteSet() {
1659
+ this.reset();
1660
+ readQueryDevtoolsClipboard().then((read) => {
1661
+ if (read.ok) {
1662
+ this.armText(read.text);
1663
+ return;
1664
+ }
1665
+ this.pasteMode.set(true);
1666
+ this.note.set(read.reason === 'unavailable'
1667
+ ? 'This browser will not hand over the clipboard - press ⌘V / Ctrl+V here instead.'
1668
+ : 'The clipboard read was blocked - press ⌘V / Ctrl+V here instead.');
1669
+ });
1670
+ }
1671
+ pasteIntoBox(event) {
1672
+ const text = textFromQueryDevtoolsPaste(event);
1673
+ if (!text)
1674
+ return;
1675
+ event.preventDefault();
1676
+ this.armText(text);
1677
+ }
1678
+ commitPasteBox() {
1679
+ const raw = this.pasteInput()?.nativeElement.value;
1680
+ if (raw !== undefined)
1681
+ this.armText(raw);
1682
+ }
1683
+ resetOnClose(open) {
1684
+ if (!open) {
1685
+ this.pasteMode.set(false);
1686
+ this.copied.set(false);
1687
+ this.reset();
1688
+ }
1689
+ }
1690
+ reset() {
1691
+ this.error.set(null);
1692
+ this.note.set(null);
1693
+ }
1694
+ armText(text) {
1695
+ const parsed = parseQueryDevtoolsOverrideTransfer(text);
1696
+ if (!parsed.ok) {
1697
+ this.error.set(parsed.reason);
1698
+ return;
1699
+ }
1700
+ armQueryDevtoolsOverrideTransfer(this.overrides(), parsed.ops);
1701
+ const unresolved = countUnresolvedQueryDevtoolsOverrides(parsed.ops, this.response());
1702
+ const parts = [`Armed ${parsed.ops.length} ${parsed.ops.length === 1 ? 'op' : 'ops'}`];
1703
+ if (unresolved)
1704
+ parts.push(`${unresolved} of them do not resolve against this response`);
1705
+ if (parsed.skipped)
1706
+ parts.push(`${parsed.skipped} unknown to this build were dropped`);
1707
+ this.pasteMode.set(false);
1708
+ this.error.set(null);
1709
+ this.note.set(`${parts.join('; ')}.`);
1710
+ }
1711
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsOverrideSetMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1712
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsOverrideSetMenuComponent, isStandalone: true, selector: "et-query-devtools-override-set-menu", inputs: { overrides: { classPropertyName: "overrides", publicName: "overrides", isSignal: true, isRequired: true, transformFunction: null }, response: { classPropertyName: "response", publicName: "response", isSignal: true, isRequired: false, transformFunction: null }, source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "pasteInput", first: true, predicate: ["pasteBox"], descendants: true, isSignal: true }], ngImport: i0, template: "<div (openChange)=\"resetOnClose($event)\" class=\"et-query-devtools-override-set-menu\" etMenu>\n <button\n et-menu-item\n title=\"Copy every response override armed on this query, or paste a set copied off another one.\"\n type=\"button\"\n etMenuTrigger\n >\n Override set\n <et-menu-item-shortcut>\u203A</et-menu-item-shortcut>\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <ng-container ngProjectAs=\"input[etMenuSearch]\">\n @if (pasteMode()) {\n <input\n #pasteBox\n [error]=\"error()\"\n (paste)=\"pasteIntoBox($event)\"\n (keydown.enter)=\"commitPasteBox()\"\n etMenuSearch\n type=\"text\"\n placeholder=\"Paste a copied set here\"\n />\n }\n </ng-container>\n\n @if (armedCount()) {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"copySet()\" et-menu-item type=\"button\">\n {{ copied() ? '\u2713 Copied' : 'Copy ' + armedCount() + (armedCount() === 1 ? ' armed op' : ' armed ops') }}\n </button>\n }\n\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"pasteSet()\" et-menu-item type=\"button\">\n Paste a set{{ armedCount() ? ' on top' : '' }}\n </button>\n\n @if (error(); as err) {\n <p [etProvideColor]=\"errorColorTheme\" class=\"et-query-devtools-override-error\" role=\"alert\">{{ err }}</p>\n } @else if (note(); as text) {\n <p class=\"et-query-devtools-override-note\" role=\"status\">{{ text }}</p>\n }\n </et-menu>\n </ng-template>\n</div>\n", dependencies: [{ kind: "directive", type: MenuDirective, selector: "[etMenu]", inputs: ["placement", "fallbackPlacements", "offset", "viewportPadding", "arrow", "arrowPadding", "autoFocus", "hoverOpen", "loop", "hoverOpenDelay", "hoverCloseDelay", "disabled", "open"], outputs: ["openChange"], exportAs: ["etMenu"] }, { kind: "directive", type: MenuTriggerDirective, selector: "[etMenuTrigger]", exportAs: ["etMenuTrigger"] }, { kind: "directive", type: MenuSurfaceDirective, selector: "ng-template[etMenuSurface]", exportAs: ["etMenuSurface"] }, { kind: "directive", type: MenuSearchDirective, selector: "input[etMenuSearch]", inputs: ["query", "loading", "error"], outputs: ["queryChange"], exportAs: ["etMenuSearch"] }, { kind: "component", type: MenuComponent, selector: "et-menu" }, { kind: "component", type: MenuItemComponent, selector: "button[et-menu-item], a[et-menu-item]", inputs: ["variant"] }, { kind: "component", type: MenuItemShortcutComponent, selector: "et-menu-item-shortcut" }, { kind: "directive", type: ProvideColorDirective, selector: "[etProvideColor]", inputs: ["etProvideColor"] }], encapsulation: i0.ViewEncapsulation.None }); }
1713
+ }
1714
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsOverrideSetMenuComponent, decorators: [{
1715
+ type: Component,
1716
+ args: [{ selector: 'et-query-devtools-override-set-menu', encapsulation: ViewEncapsulation.None, imports: [
1717
+ MenuDirective,
1718
+ MenuTriggerDirective,
1719
+ MenuSurfaceDirective,
1720
+ MenuSearchDirective,
1721
+ MenuComponent,
1722
+ MenuItemComponent,
1723
+ MenuItemShortcutComponent,
1724
+ ProvideColorDirective,
1725
+ ], template: "<div (openChange)=\"resetOnClose($event)\" class=\"et-query-devtools-override-set-menu\" etMenu>\n <button\n et-menu-item\n title=\"Copy every response override armed on this query, or paste a set copied off another one.\"\n type=\"button\"\n etMenuTrigger\n >\n Override set\n <et-menu-item-shortcut>\u203A</et-menu-item-shortcut>\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <ng-container ngProjectAs=\"input[etMenuSearch]\">\n @if (pasteMode()) {\n <input\n #pasteBox\n [error]=\"error()\"\n (paste)=\"pasteIntoBox($event)\"\n (keydown.enter)=\"commitPasteBox()\"\n etMenuSearch\n type=\"text\"\n placeholder=\"Paste a copied set here\"\n />\n }\n </ng-container>\n\n @if (armedCount()) {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"copySet()\" et-menu-item type=\"button\">\n {{ copied() ? '\u2713 Copied' : 'Copy ' + armedCount() + (armedCount() === 1 ? ' armed op' : ' armed ops') }}\n </button>\n }\n\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"pasteSet()\" et-menu-item type=\"button\">\n Paste a set{{ armedCount() ? ' on top' : '' }}\n </button>\n\n @if (error(); as err) {\n <p [etProvideColor]=\"errorColorTheme\" class=\"et-query-devtools-override-error\" role=\"alert\">{{ err }}</p>\n } @else if (note(); as text) {\n <p class=\"et-query-devtools-override-note\" role=\"status\">{{ text }}</p>\n }\n </et-menu>\n </ng-template>\n</div>\n" }]
1726
+ }], ctorParameters: () => [], propDecorators: { overrides: [{ type: i0.Input, args: [{ isSignal: true, alias: "overrides", required: true }] }], response: [{ type: i0.Input, args: [{ isSignal: true, alias: "response", required: false }] }], source: [{ type: i0.Input, args: [{ isSignal: true, alias: "source", required: false }] }], pasteInput: [{ type: i0.ViewChild, args: ['pasteBox', { isSignal: true }] }] } });
1727
+
1728
+ /**
1729
+ * Drops a leading path prefix from a route's segments, splitting whichever segment the prefix ends
1730
+ * inside. `null` when the route does not start with the prefix - a caller then shows the whole route
1731
+ * rather than a tail that would start mid-token and read as a different endpoint.
1732
+ */
1733
+ const trimRouteSegments = (segments, prefix) => {
1734
+ if (!prefix)
1735
+ return null;
1736
+ if (!segments
1737
+ .map((segment) => segment.text)
1738
+ .join('')
1739
+ .startsWith(prefix))
1740
+ return null;
1741
+ let remaining = prefix.length;
1742
+ const trimmed = [];
1743
+ for (const segment of segments) {
1744
+ if (remaining <= 0) {
1745
+ trimmed.push(segment);
1746
+ continue;
1747
+ }
1748
+ if (segment.text.length <= remaining) {
1749
+ remaining -= segment.text.length;
1750
+ continue;
1751
+ }
1752
+ trimmed.push({ ...segment, text: segment.text.slice(remaining) });
1753
+ remaining = 0;
1754
+ }
1755
+ return trimmed.length ? trimmed : null;
1756
+ };
1757
+ /**
1758
+ * A route with its path params and query string picked out from the static path. The segments render
1759
+ * element-only: a text node between them would render as a space inside the route.
1760
+ */
1761
+ class QueryDevtoolsRouteComponent {
1762
+ constructor() {
1763
+ this.segments = input.required(/* @ts-ignore */
1764
+ ...(ngDevMode ? [{ debugName: "segments" }] : /* istanbul ignore next */ []));
1765
+ this.fallback = input('—', /* @ts-ignore */
1766
+ ...(ngDevMode ? [{ debugName: "fallback" }] : /* istanbul ignore next */ []));
1767
+ /**
1768
+ * A leading path the row's surroundings already state - the tree's folder heading. Rendering it again
1769
+ * on every child is noise, so it is replaced by a `…` that carries the full route in its `title`.
1770
+ */
1771
+ this.trimPrefix = input('', /* @ts-ignore */
1772
+ ...(ngDevMode ? [{ debugName: "trimPrefix" }] : /* istanbul ignore next */ []));
1773
+ this.trimmed = computed(() => trimRouteSegments(this.segments(), this.trimPrefix()), /* @ts-ignore */
1774
+ ...(ngDevMode ? [{ debugName: "trimmed" }] : /* istanbul ignore next */ []));
1775
+ this.trimTitle = computed(() => `${this.trimPrefix()}… — ${this.segments()
1776
+ .map((s) => s.text)
1777
+ .join('')}`, /* @ts-ignore */
1778
+ ...(ngDevMode ? [{ debugName: "trimTitle" }] : /* istanbul ignore next */ []));
1779
+ }
1780
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsRouteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1781
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsRouteComponent, isStandalone: true, selector: "et-query-devtools-route", inputs: { segments: { classPropertyName: "segments", publicName: "segments", isSignal: true, isRequired: true, transformFunction: null }, fallback: { classPropertyName: "fallback", publicName: "fallback", isSignal: true, isRequired: false, transformFunction: null }, trimPrefix: { classPropertyName: "trimPrefix", publicName: "trimPrefix", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
1782
+ @if (trimmed(); as trimmed) {
1783
+ <span [attr.title]="trimTitle()" class="et-query-devtools-route-trimmed">…</span>
1784
+ @for (segment of trimmed; track $index) {
1785
+ <span [class]="'et-query-devtools-route-' + segment.kind" [attr.title]="segment.name">{{ segment.text }}</span>
1786
+ }
1787
+ } @else {
1788
+ @for (segment of segments(); track $index) {
1789
+ <span [class]="'et-query-devtools-route-' + segment.kind" [attr.title]="segment.name">{{ segment.text }}</span>
1790
+ } @empty {
1791
+ {{ fallback() }}
1792
+ }
1793
+ }
1794
+ `, isInline: true, styles: ["@layer components{.et-query-devtools-route-param{color:var(--_et-qdt-accent)}.et-query-devtools-route-query{color:var(--_et-qdt-muted)}.et-query-devtools-route-trimmed{color:var(--_et-qdt-subtle);cursor:help}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
1795
+ }
1796
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsRouteComponent, decorators: [{
1797
+ type: Component,
1798
+ args: [{ selector: 'et-query-devtools-route', template: `
1799
+ @if (trimmed(); as trimmed) {
1800
+ <span [attr.title]="trimTitle()" class="et-query-devtools-route-trimmed">…</span>
1801
+ @for (segment of trimmed; track $index) {
1802
+ <span [class]="'et-query-devtools-route-' + segment.kind" [attr.title]="segment.name">{{ segment.text }}</span>
1803
+ }
1804
+ } @else {
1805
+ @for (segment of segments(); track $index) {
1806
+ <span [class]="'et-query-devtools-route-' + segment.kind" [attr.title]="segment.name">{{ segment.text }}</span>
1807
+ } @empty {
1808
+ {{ fallback() }}
1809
+ }
1810
+ }
1811
+ `, encapsulation: ViewEncapsulation.None, styles: ["@layer components{.et-query-devtools-route-param{color:var(--_et-qdt-accent)}.et-query-devtools-route-query{color:var(--_et-qdt-muted)}.et-query-devtools-route-trimmed{color:var(--_et-qdt-subtle);cursor:help}}\n"] }]
1812
+ }], propDecorators: { segments: [{ type: i0.Input, args: [{ isSignal: true, alias: "segments", required: true }] }], fallback: [{ type: i0.Input, args: [{ isSignal: true, alias: "fallback", required: false }] }], trimPrefix: [{ type: i0.Input, args: [{ isSignal: true, alias: "trimPrefix", required: false }] }] } });
1813
+
1814
+ /**
1815
+ * The query detail: head, live progress, run/edit/force actions and the overview/history/data
1816
+ * sub-tabs. Rendered inline by the Queries tab and wrapped in `<et-query-devtools-drawer>` by every
1817
+ * other tab's split view - both read/write the same {@link QueryDevtoolsHost.detailTab} and JIT editor
1818
+ * state, so switching sub-tab in one instance is switching it everywhere, exactly as when this was one
1819
+ * component holding everything.
1820
+ */
1821
+ class QueryDevtoolsDetailComponent {
1822
+ constructor() {
1823
+ this.host = injectQueryDevtoolsHost();
1824
+ this.sel = input.required(/* @ts-ignore */
1825
+ ...(ngDevMode ? [{ debugName: "sel" }] : /* istanbul ignore next */ []));
1826
+ /** Whether any of the three exports behind the Copy menu has just landed on the clipboard. */
1827
+ this.copied = computed(() => this.host.copiedReport() || this.host.copiedCurl() || this.host.copiedInsomnia(), /* @ts-ignore */
1828
+ ...(ngDevMode ? [{ debugName: "copied" }] : /* istanbul ignore next */ []));
1829
+ this.overrideSetSource = computed(() => {
1830
+ const sel = this.sel();
1831
+ const url = this.host.requestUrl(sel.query);
1832
+ return { id: sel.entry.id, ...(url ? { url } : {}) };
1833
+ }, /* @ts-ignore */
1834
+ ...(ngDevMode ? [{ debugName: "overrideSetSource" }] : /* istanbul ignore next */ []));
1835
+ /**
1836
+ * Whether this is a tombstone. Everything that would run, edit or force the query is hidden for one -
1837
+ * its handle is a frozen snapshot, so those actions have nothing to act on.
1838
+ */
1839
+ this.isGone = computed(() => !!this.sel().entry.destroyedAt, /* @ts-ignore */
1840
+ ...(ngDevMode ? [{ debugName: "isGone" }] : /* istanbul ignore next */ []));
1841
+ this.detailTabs = [
1842
+ { id: 'overview', label: 'Overview' },
1843
+ { id: 'history', label: 'History' },
1844
+ { id: 'data', label: 'Data' },
1845
+ ];
1846
+ /** Whether the tail of runs that can no longer be diffed is unfolded. */
1847
+ this.foldedRunsOpen = signal(false, /* @ts-ignore */
1848
+ ...(ngDevMode ? [{ debugName: "foldedRunsOpen" }] : /* istanbul ignore next */ []));
1849
+ /**
1850
+ * The runs split at the last one that still holds a body. Only the newest few can ever be an end of a
1851
+ * diff (`setQueryDevtoolsResponseHistory`, five by default) while the run log keeps twenty-five, so
1852
+ * without this the diff sits under twenty rows reading `body no longer held` and the pair and its
1853
+ * result never fit on one screen.
1854
+ *
1855
+ * Split, not filtered: a dead row *between* two live ones stays where it is, so the log keeps its order.
1856
+ */
1857
+ this.runSections = computed(() => {
1858
+ const runs = this.host.queryRuns(this.sel().entry);
1859
+ const isActionable = (run) => run.hasResponse || !!run.error?.hasBody;
1860
+ let lastActionable = -1;
1861
+ runs.forEach((run, index) => {
1862
+ if (isActionable(run))
1863
+ lastActionable = index;
1864
+ });
1865
+ return { listed: runs.slice(0, lastActionable + 1), folded: runs.slice(lastActionable + 1) };
1866
+ }, /* @ts-ignore */
1867
+ ...(ngDevMode ? [{ debugName: "runSections" }] : /* istanbul ignore next */ []));
1868
+ /** What a run's Diff button reads as at each end of the comparison. */
1869
+ this.diffRoles = {
1870
+ base: {
1871
+ label: 'Base',
1872
+ title: 'The older end of the comparison - click another run to set the newer one, or this one to clear it',
1873
+ },
1874
+ compare: { label: 'Compare', title: 'The newer end of the comparison - click to clear it' },
1875
+ };
1876
+ }
1877
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1878
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsDetailComponent, isStandalone: true, selector: "et-query-devtools-detail", inputs: { sel: { classPropertyName: "sel", publicName: "sel", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "@let s = sel();\n\n<div class=\"et-query-devtools-detail-head\">\n <span class=\"et-query-devtools-method\">{{ s.entry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.routeSegments(s.entry, s.query)\" />\n </span>\n <button\n [attr.aria-label]=\"host.copyableRouteTitle(s.entry, s.query)\"\n [title]=\"host.copyableRouteTitle(s.entry, s.query)\"\n [class.et-query-devtools-route-copy--copied]=\"host.copiedRoute()\"\n (click)=\"host.copyRoute(s.entry, s.query)\"\n class=\"et-query-devtools-route-copy\"\n type=\"button\"\n >\n {{ host.copiedRoute() ? '\u2713' : '\u29C9' }}\n </button>\n @if (host.isTampered(s.entry)) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"Showing an armed response override or a devtools-injected failure, not what the server actually sent\"\n >\n tampered\n </span>\n }\n @if (isGone()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">gone</span>\n }\n</div>\n\n@if (s.entry.destroyedAt; as destroyedAt) {\n <p class=\"et-query-devtools-gone-note\">\n Destroyed at {{ host.formatTime(destroyedAt) }}. This is the last state the query held - nothing here updates, and\n it cannot be run again.\n </p>\n}\n\n@if (host.requestProgress(s.query); as live) {\n <div class=\"et-query-devtools-live\">\n @if (live.retry; as retry) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"The retry policy is backing off before it tries again. A query stuck loading with this chip is waiting, not slow.\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n attempt {{ retry.attempt }} in {{ host.formatCountdown(live.retryInMs) }}\n </span>\n <span class=\"et-query-devtools-live-note\">\n {{ host.retryCause(retry.status) }} \u00B7 backing off {{ host.formatDuration(retry.delayMs) }}\n </span>\n } @else if (live.attempts > 1) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"How many HTTP attempts the last execution took - the retry policy fired for the rest\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n {{ live.attempts }} attempts\n </span>\n }\n\n @if (live.progress; as progress) {\n <span\n [attr.aria-valuenow]=\"progress.percentage\"\n class=\"et-query-devtools-progress\"\n role=\"progressbar\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n aria-label=\"Transfer progress\"\n >\n <span [style.inline-size.%]=\"progress.percentage\" class=\"et-query-devtools-progress-bar\"></span>\n </span>\n <span class=\"et-query-devtools-live-note\">\n {{ host.formatPercent(progress.percentage) }} \u00B7 {{ host.formatBytes(progress.loaded) }} of\n {{ host.formatBytes(progress.total) }}\n @if (progress.speed !== null) {\n \u00B7 {{ host.formatSpeed(progress.speed) }}\n }\n @if (progress.remainingTime !== null) {\n \u00B7 {{ host.formatDuration(progress.remainingTime) }} left\n }\n </span>\n }\n </div>\n}\n\n<!-- One row, one primary action. Everything a query can be *made* to do lives behind Override, and\n everything that leaves the panel behind Copy - a flat row of all twelve read as a wall of equal\n buttons in which Execute was no easier to find than Clear. -->\n<div class=\"et-query-devtools-actions\">\n @if (!isGone()) {\n <div class=\"et-query-devtools-btn-group\">\n <button\n (click)=\"host.executeQuery(s, false)\"\n class=\"et-query-devtools-btn et-query-devtools-btn--primary\"\n type=\"button\"\n title=\"Run the query again, ignoring the cache\"\n >\n <span aria-hidden=\"true\">\u25B6</span>\n Execute\n </button>\n <button\n (click)=\"host.executeQuery(s, true)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Run it the way the app would - a fresh enough cached response is used instead of a request\"\n >\n Cached\n </button>\n <button\n (click)=\"host.resetQuery(s.query)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Drop this query's state back to idle\"\n >\n Reset\n </button>\n </div>\n }\n @let locatable = host.locatableElement(s.entry);\n <button\n [disabled]=\"!locatable\"\n [class.et-query-devtools-btn--active]=\"host.locateState() === 'located'\"\n [title]=\"\n locatable\n ? 'Scroll to and outline the element this query was created in. That is where it was created, not necessarily where its data is rendered.'\n : 'This query was created outside a component or directive - a root service, a resolver or a guard - so it has no element to point at.'\n \"\n (click)=\"host.locateQuery(s.entry)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n <span aria-hidden=\"true\">\u2316</span>\n @switch (host.locateState()) {\n @case ('located') {\n Created here\n }\n @case ('offscreen') {\n Not on screen\n }\n @default {\n Locate\n }\n }\n </button>\n\n <!-- The tick has to live on the trigger: the menu closes on the pick, so a confirmation inside it\n would be gone before it could be read. -->\n <div class=\"et-query-devtools-menu\" etMenu>\n <button class=\"et-query-devtools-btn\" title=\"Take this request somewhere else\" type=\"button\" etMenuTrigger>\n <span class=\"et-query-devtools-menu-glyph\" aria-hidden=\"true\">{{ copied() ? '\u2713' : '\u29C9' }}</span>\n {{ copied() ? 'Copied' : 'Copy' }}\n <span class=\"et-query-devtools-menu-caret\" aria-hidden=\"true\">\u25BE</span>\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <button (activate)=\"host.copyReport(s.entry, s.query)\" et-menu-item type=\"button\">\n Copy report\n <span class=\"et-query-devtools-menu-item-note\">Everything the panel knows, as markdown</span>\n </button>\n <button (activate)=\"host.copyCurlRequest(s.entry, s.query)\" et-menu-item type=\"button\">\n Copy as cURL\n <span class=\"et-query-devtools-menu-item-note\">URL, resolved headers and body</span>\n </button>\n <button (activate)=\"host.copyInsomniaRequest(s.entry, s.query)\" et-menu-item type=\"button\">\n Copy for Insomnia\n <span class=\"et-query-devtools-menu-item-note\">A collection to import from the clipboard</span>\n </button>\n </et-menu>\n </ng-template>\n </div>\n\n @if (!isGone()) {\n <div class=\"et-query-devtools-menu\" etMenu>\n <button\n [class.et-query-devtools-btn--tampered]=\"host.isTampered(s.entry)\"\n class=\"et-query-devtools-btn\"\n title=\"Make this query answer with something other than what the API sent\"\n type=\"button\"\n etMenuTrigger\n >\n <span class=\"et-query-devtools-menu-glyph\" aria-hidden=\"true\">\u270E</span>\n Override\n <span class=\"et-query-devtools-menu-caret\" aria-hidden=\"true\">\u25BE</span>\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <button (activate)=\"host.openResponseEditor(s.query)\" et-menu-item type=\"button\">Edit response\u2026</button>\n <button (activate)=\"host.openArgsEditor(s)\" et-menu-item type=\"button\">Replay with edited args\u2026</button>\n <et-menu-separator />\n <button (activate)=\"host.forceLoading(s.query)\" et-menu-item type=\"button\">Force loading</button>\n <button (activate)=\"host.forceError(s.query)\" et-menu-item type=\"button\">Force error</button>\n <button (activate)=\"host.forceEmpty(s.query)\" et-menu-item type=\"button\">Force empty</button>\n <et-menu-separator />\n <button (activate)=\"host.clearForced(s.query)\" et-menu-item type=\"button\">Clear forced state</button>\n\n @if (s.entry.overrides; as overrides) {\n <et-menu-separator />\n <et-query-devtools-override-set-menu\n [overrides]=\"overrides\"\n [response]=\"s.query.response()\"\n [source]=\"overrideSetSource()\"\n />\n }\n </et-menu>\n </ng-template>\n </div>\n }\n</div>\n\n@if (!isGone() && host.editorMode() !== 'none') {\n <div class=\"et-query-devtools-editor\">\n @if (host.editorMode() === 'response') {\n <h4 class=\"et-query-devtools-section\">Edit response \u2192 setResponse()</h4>\n <textarea\n #respArea\n [value]=\"host.editorSeed\"\n (input)=\"host.responseDraft.set(respArea.value)\"\n class=\"et-query-devtools-textarea\"\n spellcheck=\"false\"\n ></textarea>\n <div class=\"et-query-devtools-actions\">\n <button (click)=\"host.applyResponse(s.query)\" class=\"et-query-devtools-btn\" type=\"button\">Apply</button>\n <button (click)=\"host.cancelEditor()\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n } @else {\n <h4 class=\"et-query-devtools-section\">Replay with edited args \u2192 execute()</h4>\n <textarea\n #argsArea\n [value]=\"host.editorSeed\"\n (input)=\"host.argsDraft.set(argsArea.value)\"\n class=\"et-query-devtools-textarea\"\n spellcheck=\"false\"\n ></textarea>\n <div class=\"et-query-devtools-actions\">\n <button (click)=\"host.applyArgs(s.query)\" class=\"et-query-devtools-btn\" type=\"button\">Execute</button>\n <button (click)=\"host.cancelEditor()\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n }\n @if (host.editError(); as err) {\n <p class=\"et-query-devtools-editor-error\">{{ err }}</p>\n }\n </div>\n}\n\n@let detailRuns = host.queryRuns(s.entry);\n<nav class=\"et-query-devtools-subtabs\" role=\"tablist\">\n @for (tab of detailTabs; track tab.id) {\n <button\n [class.et-query-devtools-subtab--active]=\"host.detailTab() === tab.id\"\n [attr.aria-selected]=\"host.detailTab() === tab.id\"\n (click)=\"host.detailTab.set(tab.id)\"\n class=\"et-query-devtools-subtab\"\n type=\"button\"\n role=\"tab\"\n >\n {{ tab.label }}\n @if (tab.id === 'history' && detailRuns.length) {\n <span class=\"et-query-devtools-tab-badge\">{{ detailRuns.length }}</span>\n }\n <!-- The error body lives under Data, so the tab has to say so - a failure must not hide\n behind a tab that is not open. -->\n @if (tab.id === 'data' && s.query.error()) {\n <span class=\"et-query-devtools-tab-badge et-query-devtools-tab-badge--error\">!</span>\n }\n </button>\n }\n</nav>\n\n@switch (host.detailTab()) {\n @case ('overview') {\n <dl class=\"et-query-devtools-meta\">\n <dt>Base URL</dt>\n <dd class=\"et-query-devtools-mono\">\n {{ s.entry.meta.clientBaseUrl || s.entry.meta.clientName || '\u2014' }}\n </dd>\n <dt>Route</dt>\n <dd class=\"et-query-devtools-mono\">{{ s.entry.meta.route || '\u2014' }}</dd>\n <dt>Request URL</dt>\n <dd class=\"et-query-devtools-mono\">{{ host.requestUrl(s.query) || '\u2014' }}</dd>\n <dt>Status</dt>\n <dd>{{ host.queryStatus(s.query) }}</dd>\n <dt>Cache key</dt>\n <dd class=\"et-query-devtools-mono\">{{ s.query.id() || '\u2014' }}</dd>\n <dt>Last executed</dt>\n <dd>{{ host.formatTime(s.query.lastTimeExecutedAt()) }}</dd>\n <dt>Triggered by</dt>\n <dd>{{ s.query.triggeredBy() || 'user' }}</dd>\n @let refreshes = host.refreshesFor(s.entry.id);\n @if (refreshes.length) {\n <dt title=\"Invalidations and mutation refreshes that re-executed this query, as far back as the event log goes\">\n Refetched by\n </dt>\n <dd>\n <div class=\"et-query-devtools-chips\">\n @for (refresh of refreshes.slice(0, 5); track refresh.id) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">\n {{ host.formatTime(refresh.timestamp) }} \u00B7 {{ refresh.label }}\n </span>\n }\n @if (refreshes.length > 5) {\n <span class=\"et-query-devtools-hint\">+{{ refreshes.length - 5 }} earlier</span>\n }\n </div>\n </dd>\n }\n @let drivers = host.formsDrivingQuery(s.entry);\n @if (drivers.length) {\n <dt>Args from</dt>\n <dd>\n <div class=\"et-query-devtools-chips\">\n @for (driver of drivers; track driver.id) {\n <button\n (click)=\"host.selectForm(driver.id)\"\n class=\"et-query-devtools-chip et-query-devtools-chip--button\"\n type=\"button\"\n title=\"Open the query form in the Forms tab\"\n >\n {{ driver.meta.name }}\n </button>\n }\n </div>\n </dd>\n }\n @if (s.entry.meta.features?.length) {\n <dt>Features</dt>\n <dd>\n <et-query-devtools-features [features]=\"s.entry.meta.features ?? []\" />\n </dd>\n }\n </dl>\n\n @let activity = host.queryActivity(s.entry);\n <div class=\"et-query-devtools-section-row\">\n <h4 class=\"et-query-devtools-section\">Activity</h4>\n <button\n [disabled]=\"!activity.hasActivity\"\n (click)=\"host.resetStats(s.entry)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Clear the counters and the run history, so the next interaction can be measured on its own\"\n >\n \u27F2 Reset\n </button>\n </div>\n\n <dl class=\"et-query-devtools-stats\">\n <div class=\"et-query-devtools-stat\" title=\"Every time the query ran, cache hits included\">\n <dt>Refreshes</dt>\n <dd>{{ activity.stats.executions }}</dd>\n @if (activity.cacheServed) {\n <span class=\"et-query-devtools-stat-sub\">{{ activity.cacheServed }} without a request</span>\n }\n </div>\n <div class=\"et-query-devtools-stat\" title=\"Executions that started a request, and how they ended\">\n <dt>Requests</dt>\n <dd>{{ activity.stats.requests }}</dd>\n <span class=\"et-query-devtools-stat-sub\">\n {{ activity.stats.responses }} ok\n @if (activity.stats.errors) {\n \u00B7 {{ activity.stats.errors }} failed\n }\n </span>\n </div>\n <div\n class=\"et-query-devtools-stat\"\n title=\"Total response payload received. Sizes without a content-length header are measured from the decoded body, so they ignore compression.\"\n >\n <dt>Received</dt>\n <dd>{{ host.formatTransferred(activity.stats.receivedBytes, activity.stats.hasEstimatedBytes) }}</dd>\n @if (activity.avgResponseBytes !== null) {\n <span class=\"et-query-devtools-stat-sub\">{{ host.formatBytes(activity.avgResponseBytes) }} per response</span>\n }\n </div>\n @if (activity.stats.sentBytes) {\n <div class=\"et-query-devtools-stat\" title=\"Total request body sent (headers not counted)\">\n <dt>Sent</dt>\n <dd>{{ host.formatTransferred(activity.stats.sentBytes, activity.stats.hasEstimatedBytes) }}</dd>\n </div>\n }\n @if (activity.stats.retries) {\n <div\n class=\"et-query-devtools-stat\"\n title=\"Attempts the retry policy added on top of the first one. Which run they belong to is under History.\"\n >\n <dt>Retries</dt>\n <dd>{{ activity.stats.retries }}</dd>\n </div>\n }\n <div class=\"et-query-devtools-stat\" title=\"Measured from the execution that preceded each response\">\n <dt>Duration</dt>\n <dd>{{ host.formatDuration(activity.stats.lastDurationMs) }}</dd>\n @if (activity.avgDurationMs !== null) {\n <span class=\"et-query-devtools-stat-sub\">{{ host.formatDuration(activity.avgDurationMs) }} on average</span>\n }\n </div>\n <div class=\"et-query-devtools-stat\">\n <dt>Last response</dt>\n <dd class=\"et-query-devtools-stat-value--sm\">{{ host.formatTime(activity.stats.lastResponseAt) }}</dd>\n @if (activity.stats.firstExecutedAt !== null) {\n <span class=\"et-query-devtools-stat-sub\"\n >first ran {{ host.formatTime(activity.stats.firstExecutedAt) }}</span\n >\n }\n </div>\n </dl>\n }\n @case ('history') {\n <div class=\"et-query-devtools-section-row\">\n <h4 class=\"et-query-devtools-section\">Runs</h4>\n <button\n [disabled]=\"!detailRuns.length\"\n (click)=\"host.resetStats(s.entry)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Clear the run history and the counters, so the next interaction can be measured on its own\"\n >\n \u27F2 Reset\n </button>\n </div>\n @if (detailRuns.length) {\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Run</th>\n <th>Started</th>\n <th>Duration</th>\n <th>Received</th>\n <th>Body</th>\n </tr>\n </thead>\n <tbody>\n @for (run of runSections().listed.concat(foldedRunsOpen() ? runSections().folded : []); track run.index) {\n <tr [attr.data-status]=\"run.status === 'error' ? 'error' : null\">\n <td>\n <span class=\"et-query-devtools-run-cell\">\n <span\n [attr.data-status]=\"host.runStatus(run)\"\n [title]=\"run.status\"\n class=\"et-query-devtools-status\"\n ></span>\n #{{ run.index }}\n @if (run.attempts > 1) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"Took {{\n run.attempts\n }} attempts - the duration next to it covers every one of them, backoff included\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n {{ run.attempts }}\n </span>\n }\n @if (!run.didRequest) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"Arrived without a request of this query's own - a poll, another consumer of the same cache entry, or another tab\"\n >\n shared\n </span>\n }\n @if (run.error; as error) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"{{ error.messages.join(' \u00B7 ') || 'The failure carried no message' }}\"\n >\n {{ error.code || 'failed' }}\n </span>\n }\n </span>\n </td>\n <td>{{ host.formatTime(run.startedAt) }}</td>\n <td>\n @if (run.endedAt === null) {\n in flight\n } @else {\n {{ host.formatDuration(run.endedAt - run.startedAt) }}\n }\n </td>\n <td>{{ run.receivedBytes ? host.formatBytes(run.receivedBytes) : '\u2014' }}</td>\n <td>\n <span class=\"et-query-devtools-cell-actions\">\n @if (host.canDiffRun(s.entry, run)) {\n @let role = host.diffRunRole(s.entry, run);\n @let armed = role ? diffRoles[role] : null;\n <button\n [class.et-query-devtools-btn--active]=\"!!armed\"\n [title]=\"\n armed?.title ?? 'Compare this response against another run - click a second row to pick which'\n \"\n (click)=\"host.toggleRunDiff(s.entry, run)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n {{ armed?.label ?? 'Diff' }}\n </button>\n } @else if (run.error?.hasBody) {\n <button\n [class.et-query-devtools-btn--active]=\"host.errorRunIndex() === run.index\"\n (click)=\"host.toggleRunError(run)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Show the body this failure came with\"\n >\n Error\n </button>\n } @else if (run.hasResponse) {\n <span class=\"et-query-devtools-count\">the only body held</span>\n } @else if (run.status === 'success' || run.error) {\n <span\n class=\"et-query-devtools-count\"\n title=\"Only the newest {{\n host.retainedResponseCount()\n }} bodies of a query are kept, so this run can no longer be an end of a diff\"\n >\n body no longer held\n </span>\n } @else {\n <span class=\"et-query-devtools-count\">\u2014</span>\n }\n </span>\n </td>\n </tr>\n }\n </tbody>\n </table>\n @let foldedRuns = runSections().folded.length;\n @if (foldedRuns) {\n <button\n [attr.aria-expanded]=\"foldedRunsOpen()\"\n (click)=\"foldedRunsOpen.set(!foldedRunsOpen())\"\n class=\"et-query-devtools-btn et-query-devtools-runs-fold\"\n type=\"button\"\n title=\"Only the newest {{\n host.retainedResponseCount()\n }} bodies of a query are kept, so none of these older runs can be an end of a diff\"\n >\n <span aria-hidden=\"true\">{{ foldedRunsOpen() ? '\u25BE' : '\u25B8' }}</span>\n {{ foldedRunsOpen() ? 'Hide' : 'Show' }} {{ foldedRuns }} older run{{ foldedRuns === 1 ? '' : 's' }} with no\n body\n </button>\n }\n\n @if (host.pickedRunError(s.entry); as failure) {\n <div class=\"et-query-devtools-section-row\">\n <h5 class=\"et-query-devtools-section\">\n Error body \u00B7 run #{{ failure.run.index }} \u00B7 {{ failure.error.code || 'no status' }}\n </h5>\n </div>\n @for (message of failure.error.messages; track message) {\n <p class=\"et-query-devtools-count\">{{ message }}</p>\n }\n <et-query-devtools-json\n [value]=\"failure.error.body\"\n [search]=\"host.jsonSearchTerm()\"\n [expandedPaths]=\"host.jsonExpandedPaths()\"\n [collapsedPaths]=\"host.jsonCollapsedPaths()\"\n [toggleFn]=\"host.toggleJsonPath\"\n path=\"runError\"\n />\n }\n\n @if (host.responseDiff(s.entry); as comparison) {\n <div class=\"et-query-devtools-section-row\">\n <h5 class=\"et-query-devtools-section\">\n Response diff \u00B7 run #{{ comparison.before.index }} \u2192 #{{ comparison.after.index }}\n </h5>\n <span class=\"et-query-devtools-diff-step\">\n <button\n [disabled]=\"!host.canStepRunDiff(s.entry, true)\"\n (click)=\"host.stepRunDiff(s.entry, true)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Compare the pair one run older, without going back to the table\"\n >\n \u25C2 Older\n </button>\n <button\n [disabled]=\"!host.canStepRunDiff(s.entry, false)\"\n (click)=\"host.stepRunDiff(s.entry, false)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Compare the pair one run newer, without going back to the table\"\n >\n Newer \u25B8\n </button>\n </span>\n <span class=\"et-query-devtools-count\">\n @if (host.diffBaseRunIndex() === null) {\n <span\n title=\"Nothing is picked to compare against, so this is the nearest older run that still holds a body - click another run's Diff to pick one\"\n >\n nearest older\n </span>\n \u00B7\n }\n @if (comparison.diff.entries.length) {\n {{ comparison.diff.entries.length }} path{{ comparison.diff.entries.length === 1 ? '' : 's' }}\n @if (comparison.diff.truncated) {\n (capped)\n }\n } @else {\n identical\n }\n </span>\n </div>\n @if (comparison.diff.entries.length) {\n <table class=\"et-query-devtools-table et-query-devtools-diff\">\n <thead>\n <tr>\n <th>Path</th>\n <th>Before</th>\n <th>After</th>\n </tr>\n </thead>\n <tbody>\n @for (change of comparison.diff.entries; track change.path) {\n <tr [attr.data-kind]=\"change.kind\">\n <td class=\"et-query-devtools-mono\">{{ change.path }}</td>\n <td class=\"et-query-devtools-mono et-query-devtools-diff-before\">\n {{ change.kind === 'added' ? '\u2014' : host.inlineValue(change.before) }}\n </td>\n <td class=\"et-query-devtools-mono et-query-devtools-diff-after\">\n {{ change.kind === 'removed' ? '\u2014' : host.inlineValue(change.after) }}\n </td>\n </tr>\n }\n </tbody>\n </table>\n } @else {\n <p class=\"et-query-devtools-empty\">The two responses are identical.</p>\n }\n }\n } @else {\n <p class=\"et-query-devtools-empty\">No runs recorded yet.</p>\n }\n }\n @case ('data') {\n @if (s.entry.meta.gqlQuery; as gqlQuery) {\n <div class=\"et-query-devtools-section-row\">\n <h5 class=\"et-query-devtools-section\">GraphQL document</h5>\n <button\n (click)=\"host.copyGqlDocument(gqlQuery)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Copy the GraphQL document\"\n >\n {{ host.copiedGql() ? '\u2713 Copied' : '\u29C9 Copy' }}\n </button>\n </div>\n <pre class=\"et-query-devtools-code\">{{ host.gqlDocument(gqlQuery) }}</pre>\n }\n\n <div class=\"et-query-devtools-section-row\">\n <h4 class=\"et-query-devtools-section\">Value explorer</h4>\n <input\n #jsonSearchInput\n [value]=\"host.jsonSearch()\"\n (input)=\"host.jsonSearch.set(jsonSearchInput.value)\"\n class=\"et-query-devtools-search\"\n type=\"search\"\n placeholder=\"filter keys / values\u2026\"\n />\n </div>\n\n <h5 class=\"et-query-devtools-section\">Args</h5>\n <et-query-devtools-json\n [value]=\"host.queryArgs(s.query)\"\n [search]=\"host.jsonSearchTerm()\"\n [expandedPaths]=\"host.jsonExpandedPaths()\"\n [collapsedPaths]=\"host.jsonCollapsedPaths()\"\n [toggleFn]=\"host.toggleJsonPath\"\n path=\"args\"\n />\n\n @if (s.query.error(); as error) {\n <h5 class=\"et-query-devtools-section\">Error ({{ error.raw.status }})</h5>\n <et-query-devtools-json\n [value]=\"error.isList ? error.errors : error.error\"\n [search]=\"host.jsonSearchTerm()\"\n [expandedPaths]=\"host.jsonExpandedPaths()\"\n [collapsedPaths]=\"host.jsonCollapsedPaths()\"\n [toggleFn]=\"host.toggleJsonPath\"\n path=\"error\"\n />\n } @else {\n <div class=\"et-query-devtools-section-row\">\n <h5 class=\"et-query-devtools-section\">Response</h5>\n @if (s.entry.overrides?.list()?.length || host.overridesPersist()) {\n <button\n [class.et-query-devtools-btn--active]=\"host.overridesPersist()\"\n [attr.aria-pressed]=\"host.overridesPersist()\"\n (click)=\"host.toggleOverridesPersist()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"{{\n host.overridesPersist()\n ? 'Panel-wide: every armed response override is kept in ' +\n host.overridesScopeLabel() +\n ' and replayed on the next load.'\n : 'Panel-wide: keep every armed response override and replay it on the next load.'\n }} Settings \u2699 chooses the scope. Armed faults are never kept.\"\n >\n Keep across reloads\n </button>\n }\n @if (s.entry.overrides?.list()?.length) {\n <button (click)=\"s.entry.overrides?.clearAll()\" class=\"et-query-devtools-btn\" type=\"button\">\n Reset all overrides ({{ s.entry.overrides?.list()?.length }})\n </button>\n }\n </div>\n <et-query-devtools-json\n [value]=\"s.query.response()\"\n [search]=\"host.jsonSearchTerm()\"\n [expandedPaths]=\"host.jsonExpandedPaths()\"\n [collapsedPaths]=\"host.jsonCollapsedPaths()\"\n [toggleFn]=\"host.toggleJsonPath\"\n [overrides]=\"s.entry.overrides ?? null\"\n path=\"response\"\n />\n }\n }\n}\n", styles: ["@layer components{.et-query-devtools-detail .et-query-devtools-detail-head,.et-query-devtools-drawer .et-query-devtools-detail-head{padding-block-end:.8rem;border-block-end:1px solid var(--_et-qdt-border)}.et-query-devtools-gone-note{margin:0 0 .8rem;color:var(--_et-qdt-muted);font-size:1.1rem}.et-query-devtools-detail-head{display:flex;align-items:center;gap:.8rem;margin-block-end:.8rem}.et-query-devtools-detail-head .et-query-devtools-route{flex:0 1 auto}.et-query-devtools-route-copy{appearance:none;margin-inline-end:auto;display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;inline-size:2.2rem;block-size:2.2rem;margin-inline-start:-.4rem;border:none;border-radius:.3rem;background:transparent;color:var(--_et-qdt-subtle);cursor:pointer;font:inherit;font-size:1.3rem;line-height:1}@media(hover:hover){.et-query-devtools-route-copy:hover:not(.et-query-devtools-route-copy--copied){background:var(--_et-qdt-active);color:var(--_et-qdt-fg)}}.et-query-devtools-route-copy--copied{color:var(--_et-qdt-success)}.et-query-devtools-live{display:flex;flex-wrap:wrap;align-items:center;gap:.6rem;margin-block-end:.8rem}.et-query-devtools-live-note{color:var(--_et-qdt-muted);font-size:1.15rem}.et-query-devtools-progress{display:block;flex:1 1 12rem;min-inline-size:8rem;max-inline-size:24rem;block-size:.5rem;border-radius:999px;overflow:hidden;background:var(--_et-qdt-border)}.et-query-devtools-progress-bar{display:block;block-size:100%;border-radius:inherit;background:var(--_et-qdt-accent)}.et-query-devtools-actions{display:flex;flex-wrap:wrap;align-items:center;gap:.4rem;margin-block-end:.8rem}.et-query-devtools-actions>.et-query-devtools-menu+.et-query-devtools-menu{margin-inline-start:.5rem;padding-inline-start:.9rem;border-inline-start:1px solid var(--_et-qdt-border)}.et-query-devtools-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(12rem,1fr));gap:.6rem;margin:0 0 1rem}.et-query-devtools-stat-value--sm{font-size:1.2rem}.et-query-devtools-code{margin:0 0 .8rem;padding:.8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);overflow:auto;white-space:pre;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:1.15rem;max-block-size:20rem}.et-query-devtools-diff-step{display:flex;gap:.4rem;margin-inline-start:auto}.et-query-devtools-runs-fold{margin-block-start:.4rem;color:var(--_et-qdt-muted)}.et-query-devtools-textarea{inline-size:100%;min-block-size:12rem;resize:vertical;background:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);color:var(--_et-qdt-fg);border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.6rem;font-family:inherit;font-size:1.2rem}.et-query-devtools-subtabs{display:flex;flex-wrap:wrap;gap:.2rem;border-block-end:1px solid var(--_et-qdt-border);margin-block:.8rem}.et-query-devtools-subtabs+.et-query-devtools-section-row{padding-block-start:0;border-block-start:0}.et-query-devtools-subtab{appearance:none;display:inline-flex;align-items:center;gap:.4rem;border:0;border-block-end:2px solid transparent;background:transparent;color:var(--_et-qdt-muted);font:inherit;font-size:1.15rem;padding:.4rem .6rem;margin-block-end:-1px;cursor:pointer}@media(hover:hover){.et-query-devtools-subtab:hover{color:var(--_et-qdt-fg)}}.et-query-devtools-subtab--active{color:var(--_et-qdt-fg);border-block-end-color:var(--_et-qdt-accent)}.et-query-devtools-run-cell{display:inline-flex;align-items:center;gap:.5rem}.et-query-devtools-diff td{max-inline-size:28rem;overflow-wrap:anywhere}.et-query-devtools-diff-before{color:var(--_et-qdt-muted)}.et-query-devtools-diff tr[data-kind=removed] .et-query-devtools-diff-before{color:var(--_et-qdt-error)}.et-query-devtools-diff tr[data-kind=added] .et-query-devtools-diff-after{color:var(--_et-qdt-success)}.et-query-devtools-diff tr[data-kind=changed] .et-query-devtools-diff-after{color:var(--_et-qdt-accent)}}\n"], dependencies: [{ kind: "component", type: MenuComponent, selector: "et-menu" }, { kind: "directive", type: MenuDirective, selector: "[etMenu]", inputs: ["placement", "fallbackPlacements", "offset", "viewportPadding", "arrow", "arrowPadding", "autoFocus", "hoverOpen", "loop", "hoverOpenDelay", "hoverCloseDelay", "disabled", "open"], outputs: ["openChange"], exportAs: ["etMenu"] }, { kind: "component", type: MenuItemComponent, selector: "button[et-menu-item], a[et-menu-item]", inputs: ["variant"] }, { kind: "component", type: MenuSeparatorComponent, selector: "et-menu-separator" }, { kind: "directive", type: MenuSurfaceDirective, selector: "ng-template[etMenuSurface]", exportAs: ["etMenuSurface"] }, { kind: "directive", type: MenuTriggerDirective, selector: "[etMenuTrigger]", exportAs: ["etMenuTrigger"] }, { kind: "component", type: QueryDevtoolsFeaturesComponent, selector: "et-query-devtools-features", inputs: ["features"] }, { kind: "component", type: QueryDevtoolsJsonComponent, selector: "et-query-devtools-json", inputs: ["value", "nodeKey", "depth", "search", "chunk", "path", "expandedPaths", "collapsedPaths", "toggleFn", "jsonPath", "parentKind", "overrides", "annotations"] }, { kind: "component", type: QueryDevtoolsOverrideSetMenuComponent, selector: "et-query-devtools-override-set-menu", inputs: ["overrides", "response", "source"] }, { kind: "component", type: QueryDevtoolsRouteComponent, selector: "et-query-devtools-route", inputs: ["segments", "fallback", "trimPrefix"] }], encapsulation: i0.ViewEncapsulation.None }); }
1879
+ }
1880
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsDetailComponent, decorators: [{
1881
+ type: Component,
1882
+ args: [{ selector: 'et-query-devtools-detail', encapsulation: ViewEncapsulation.None, imports: [
1883
+ MenuComponent,
1884
+ MenuDirective,
1885
+ MenuItemComponent,
1886
+ MenuSeparatorComponent,
1887
+ MenuSurfaceDirective,
1888
+ MenuTriggerDirective,
1889
+ QueryDevtoolsFeaturesComponent,
1890
+ QueryDevtoolsJsonComponent,
1891
+ QueryDevtoolsOverrideSetMenuComponent,
1892
+ QueryDevtoolsRouteComponent,
1893
+ ], template: "@let s = sel();\n\n<div class=\"et-query-devtools-detail-head\">\n <span class=\"et-query-devtools-method\">{{ s.entry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.routeSegments(s.entry, s.query)\" />\n </span>\n <button\n [attr.aria-label]=\"host.copyableRouteTitle(s.entry, s.query)\"\n [title]=\"host.copyableRouteTitle(s.entry, s.query)\"\n [class.et-query-devtools-route-copy--copied]=\"host.copiedRoute()\"\n (click)=\"host.copyRoute(s.entry, s.query)\"\n class=\"et-query-devtools-route-copy\"\n type=\"button\"\n >\n {{ host.copiedRoute() ? '\u2713' : '\u29C9' }}\n </button>\n @if (host.isTampered(s.entry)) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"Showing an armed response override or a devtools-injected failure, not what the server actually sent\"\n >\n tampered\n </span>\n }\n @if (isGone()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">gone</span>\n }\n</div>\n\n@if (s.entry.destroyedAt; as destroyedAt) {\n <p class=\"et-query-devtools-gone-note\">\n Destroyed at {{ host.formatTime(destroyedAt) }}. This is the last state the query held - nothing here updates, and\n it cannot be run again.\n </p>\n}\n\n@if (host.requestProgress(s.query); as live) {\n <div class=\"et-query-devtools-live\">\n @if (live.retry; as retry) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"The retry policy is backing off before it tries again. A query stuck loading with this chip is waiting, not slow.\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n attempt {{ retry.attempt }} in {{ host.formatCountdown(live.retryInMs) }}\n </span>\n <span class=\"et-query-devtools-live-note\">\n {{ host.retryCause(retry.status) }} \u00B7 backing off {{ host.formatDuration(retry.delayMs) }}\n </span>\n } @else if (live.attempts > 1) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"How many HTTP attempts the last execution took - the retry policy fired for the rest\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n {{ live.attempts }} attempts\n </span>\n }\n\n @if (live.progress; as progress) {\n <span\n [attr.aria-valuenow]=\"progress.percentage\"\n class=\"et-query-devtools-progress\"\n role=\"progressbar\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n aria-label=\"Transfer progress\"\n >\n <span [style.inline-size.%]=\"progress.percentage\" class=\"et-query-devtools-progress-bar\"></span>\n </span>\n <span class=\"et-query-devtools-live-note\">\n {{ host.formatPercent(progress.percentage) }} \u00B7 {{ host.formatBytes(progress.loaded) }} of\n {{ host.formatBytes(progress.total) }}\n @if (progress.speed !== null) {\n \u00B7 {{ host.formatSpeed(progress.speed) }}\n }\n @if (progress.remainingTime !== null) {\n \u00B7 {{ host.formatDuration(progress.remainingTime) }} left\n }\n </span>\n }\n </div>\n}\n\n<!-- One row, one primary action. Everything a query can be *made* to do lives behind Override, and\n everything that leaves the panel behind Copy - a flat row of all twelve read as a wall of equal\n buttons in which Execute was no easier to find than Clear. -->\n<div class=\"et-query-devtools-actions\">\n @if (!isGone()) {\n <div class=\"et-query-devtools-btn-group\">\n <button\n (click)=\"host.executeQuery(s, false)\"\n class=\"et-query-devtools-btn et-query-devtools-btn--primary\"\n type=\"button\"\n title=\"Run the query again, ignoring the cache\"\n >\n <span aria-hidden=\"true\">\u25B6</span>\n Execute\n </button>\n <button\n (click)=\"host.executeQuery(s, true)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Run it the way the app would - a fresh enough cached response is used instead of a request\"\n >\n Cached\n </button>\n <button\n (click)=\"host.resetQuery(s.query)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Drop this query's state back to idle\"\n >\n Reset\n </button>\n </div>\n }\n @let locatable = host.locatableElement(s.entry);\n <button\n [disabled]=\"!locatable\"\n [class.et-query-devtools-btn--active]=\"host.locateState() === 'located'\"\n [title]=\"\n locatable\n ? 'Scroll to and outline the element this query was created in. That is where it was created, not necessarily where its data is rendered.'\n : 'This query was created outside a component or directive - a root service, a resolver or a guard - so it has no element to point at.'\n \"\n (click)=\"host.locateQuery(s.entry)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n <span aria-hidden=\"true\">\u2316</span>\n @switch (host.locateState()) {\n @case ('located') {\n Created here\n }\n @case ('offscreen') {\n Not on screen\n }\n @default {\n Locate\n }\n }\n </button>\n\n <!-- The tick has to live on the trigger: the menu closes on the pick, so a confirmation inside it\n would be gone before it could be read. -->\n <div class=\"et-query-devtools-menu\" etMenu>\n <button class=\"et-query-devtools-btn\" title=\"Take this request somewhere else\" type=\"button\" etMenuTrigger>\n <span class=\"et-query-devtools-menu-glyph\" aria-hidden=\"true\">{{ copied() ? '\u2713' : '\u29C9' }}</span>\n {{ copied() ? 'Copied' : 'Copy' }}\n <span class=\"et-query-devtools-menu-caret\" aria-hidden=\"true\">\u25BE</span>\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <button (activate)=\"host.copyReport(s.entry, s.query)\" et-menu-item type=\"button\">\n Copy report\n <span class=\"et-query-devtools-menu-item-note\">Everything the panel knows, as markdown</span>\n </button>\n <button (activate)=\"host.copyCurlRequest(s.entry, s.query)\" et-menu-item type=\"button\">\n Copy as cURL\n <span class=\"et-query-devtools-menu-item-note\">URL, resolved headers and body</span>\n </button>\n <button (activate)=\"host.copyInsomniaRequest(s.entry, s.query)\" et-menu-item type=\"button\">\n Copy for Insomnia\n <span class=\"et-query-devtools-menu-item-note\">A collection to import from the clipboard</span>\n </button>\n </et-menu>\n </ng-template>\n </div>\n\n @if (!isGone()) {\n <div class=\"et-query-devtools-menu\" etMenu>\n <button\n [class.et-query-devtools-btn--tampered]=\"host.isTampered(s.entry)\"\n class=\"et-query-devtools-btn\"\n title=\"Make this query answer with something other than what the API sent\"\n type=\"button\"\n etMenuTrigger\n >\n <span class=\"et-query-devtools-menu-glyph\" aria-hidden=\"true\">\u270E</span>\n Override\n <span class=\"et-query-devtools-menu-caret\" aria-hidden=\"true\">\u25BE</span>\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <button (activate)=\"host.openResponseEditor(s.query)\" et-menu-item type=\"button\">Edit response\u2026</button>\n <button (activate)=\"host.openArgsEditor(s)\" et-menu-item type=\"button\">Replay with edited args\u2026</button>\n <et-menu-separator />\n <button (activate)=\"host.forceLoading(s.query)\" et-menu-item type=\"button\">Force loading</button>\n <button (activate)=\"host.forceError(s.query)\" et-menu-item type=\"button\">Force error</button>\n <button (activate)=\"host.forceEmpty(s.query)\" et-menu-item type=\"button\">Force empty</button>\n <et-menu-separator />\n <button (activate)=\"host.clearForced(s.query)\" et-menu-item type=\"button\">Clear forced state</button>\n\n @if (s.entry.overrides; as overrides) {\n <et-menu-separator />\n <et-query-devtools-override-set-menu\n [overrides]=\"overrides\"\n [response]=\"s.query.response()\"\n [source]=\"overrideSetSource()\"\n />\n }\n </et-menu>\n </ng-template>\n </div>\n }\n</div>\n\n@if (!isGone() && host.editorMode() !== 'none') {\n <div class=\"et-query-devtools-editor\">\n @if (host.editorMode() === 'response') {\n <h4 class=\"et-query-devtools-section\">Edit response \u2192 setResponse()</h4>\n <textarea\n #respArea\n [value]=\"host.editorSeed\"\n (input)=\"host.responseDraft.set(respArea.value)\"\n class=\"et-query-devtools-textarea\"\n spellcheck=\"false\"\n ></textarea>\n <div class=\"et-query-devtools-actions\">\n <button (click)=\"host.applyResponse(s.query)\" class=\"et-query-devtools-btn\" type=\"button\">Apply</button>\n <button (click)=\"host.cancelEditor()\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n } @else {\n <h4 class=\"et-query-devtools-section\">Replay with edited args \u2192 execute()</h4>\n <textarea\n #argsArea\n [value]=\"host.editorSeed\"\n (input)=\"host.argsDraft.set(argsArea.value)\"\n class=\"et-query-devtools-textarea\"\n spellcheck=\"false\"\n ></textarea>\n <div class=\"et-query-devtools-actions\">\n <button (click)=\"host.applyArgs(s.query)\" class=\"et-query-devtools-btn\" type=\"button\">Execute</button>\n <button (click)=\"host.cancelEditor()\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n }\n @if (host.editError(); as err) {\n <p class=\"et-query-devtools-editor-error\">{{ err }}</p>\n }\n </div>\n}\n\n@let detailRuns = host.queryRuns(s.entry);\n<nav class=\"et-query-devtools-subtabs\" role=\"tablist\">\n @for (tab of detailTabs; track tab.id) {\n <button\n [class.et-query-devtools-subtab--active]=\"host.detailTab() === tab.id\"\n [attr.aria-selected]=\"host.detailTab() === tab.id\"\n (click)=\"host.detailTab.set(tab.id)\"\n class=\"et-query-devtools-subtab\"\n type=\"button\"\n role=\"tab\"\n >\n {{ tab.label }}\n @if (tab.id === 'history' && detailRuns.length) {\n <span class=\"et-query-devtools-tab-badge\">{{ detailRuns.length }}</span>\n }\n <!-- The error body lives under Data, so the tab has to say so - a failure must not hide\n behind a tab that is not open. -->\n @if (tab.id === 'data' && s.query.error()) {\n <span class=\"et-query-devtools-tab-badge et-query-devtools-tab-badge--error\">!</span>\n }\n </button>\n }\n</nav>\n\n@switch (host.detailTab()) {\n @case ('overview') {\n <dl class=\"et-query-devtools-meta\">\n <dt>Base URL</dt>\n <dd class=\"et-query-devtools-mono\">\n {{ s.entry.meta.clientBaseUrl || s.entry.meta.clientName || '\u2014' }}\n </dd>\n <dt>Route</dt>\n <dd class=\"et-query-devtools-mono\">{{ s.entry.meta.route || '\u2014' }}</dd>\n <dt>Request URL</dt>\n <dd class=\"et-query-devtools-mono\">{{ host.requestUrl(s.query) || '\u2014' }}</dd>\n <dt>Status</dt>\n <dd>{{ host.queryStatus(s.query) }}</dd>\n <dt>Cache key</dt>\n <dd class=\"et-query-devtools-mono\">{{ s.query.id() || '\u2014' }}</dd>\n <dt>Last executed</dt>\n <dd>{{ host.formatTime(s.query.lastTimeExecutedAt()) }}</dd>\n <dt>Triggered by</dt>\n <dd>{{ s.query.triggeredBy() || 'user' }}</dd>\n @let refreshes = host.refreshesFor(s.entry.id);\n @if (refreshes.length) {\n <dt title=\"Invalidations and mutation refreshes that re-executed this query, as far back as the event log goes\">\n Refetched by\n </dt>\n <dd>\n <div class=\"et-query-devtools-chips\">\n @for (refresh of refreshes.slice(0, 5); track refresh.id) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">\n {{ host.formatTime(refresh.timestamp) }} \u00B7 {{ refresh.label }}\n </span>\n }\n @if (refreshes.length > 5) {\n <span class=\"et-query-devtools-hint\">+{{ refreshes.length - 5 }} earlier</span>\n }\n </div>\n </dd>\n }\n @let drivers = host.formsDrivingQuery(s.entry);\n @if (drivers.length) {\n <dt>Args from</dt>\n <dd>\n <div class=\"et-query-devtools-chips\">\n @for (driver of drivers; track driver.id) {\n <button\n (click)=\"host.selectForm(driver.id)\"\n class=\"et-query-devtools-chip et-query-devtools-chip--button\"\n type=\"button\"\n title=\"Open the query form in the Forms tab\"\n >\n {{ driver.meta.name }}\n </button>\n }\n </div>\n </dd>\n }\n @if (s.entry.meta.features?.length) {\n <dt>Features</dt>\n <dd>\n <et-query-devtools-features [features]=\"s.entry.meta.features ?? []\" />\n </dd>\n }\n </dl>\n\n @let activity = host.queryActivity(s.entry);\n <div class=\"et-query-devtools-section-row\">\n <h4 class=\"et-query-devtools-section\">Activity</h4>\n <button\n [disabled]=\"!activity.hasActivity\"\n (click)=\"host.resetStats(s.entry)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Clear the counters and the run history, so the next interaction can be measured on its own\"\n >\n \u27F2 Reset\n </button>\n </div>\n\n <dl class=\"et-query-devtools-stats\">\n <div class=\"et-query-devtools-stat\" title=\"Every time the query ran, cache hits included\">\n <dt>Refreshes</dt>\n <dd>{{ activity.stats.executions }}</dd>\n @if (activity.cacheServed) {\n <span class=\"et-query-devtools-stat-sub\">{{ activity.cacheServed }} without a request</span>\n }\n </div>\n <div class=\"et-query-devtools-stat\" title=\"Executions that started a request, and how they ended\">\n <dt>Requests</dt>\n <dd>{{ activity.stats.requests }}</dd>\n <span class=\"et-query-devtools-stat-sub\">\n {{ activity.stats.responses }} ok\n @if (activity.stats.errors) {\n \u00B7 {{ activity.stats.errors }} failed\n }\n </span>\n </div>\n <div\n class=\"et-query-devtools-stat\"\n title=\"Total response payload received. Sizes without a content-length header are measured from the decoded body, so they ignore compression.\"\n >\n <dt>Received</dt>\n <dd>{{ host.formatTransferred(activity.stats.receivedBytes, activity.stats.hasEstimatedBytes) }}</dd>\n @if (activity.avgResponseBytes !== null) {\n <span class=\"et-query-devtools-stat-sub\">{{ host.formatBytes(activity.avgResponseBytes) }} per response</span>\n }\n </div>\n @if (activity.stats.sentBytes) {\n <div class=\"et-query-devtools-stat\" title=\"Total request body sent (headers not counted)\">\n <dt>Sent</dt>\n <dd>{{ host.formatTransferred(activity.stats.sentBytes, activity.stats.hasEstimatedBytes) }}</dd>\n </div>\n }\n @if (activity.stats.retries) {\n <div\n class=\"et-query-devtools-stat\"\n title=\"Attempts the retry policy added on top of the first one. Which run they belong to is under History.\"\n >\n <dt>Retries</dt>\n <dd>{{ activity.stats.retries }}</dd>\n </div>\n }\n <div class=\"et-query-devtools-stat\" title=\"Measured from the execution that preceded each response\">\n <dt>Duration</dt>\n <dd>{{ host.formatDuration(activity.stats.lastDurationMs) }}</dd>\n @if (activity.avgDurationMs !== null) {\n <span class=\"et-query-devtools-stat-sub\">{{ host.formatDuration(activity.avgDurationMs) }} on average</span>\n }\n </div>\n <div class=\"et-query-devtools-stat\">\n <dt>Last response</dt>\n <dd class=\"et-query-devtools-stat-value--sm\">{{ host.formatTime(activity.stats.lastResponseAt) }}</dd>\n @if (activity.stats.firstExecutedAt !== null) {\n <span class=\"et-query-devtools-stat-sub\"\n >first ran {{ host.formatTime(activity.stats.firstExecutedAt) }}</span\n >\n }\n </div>\n </dl>\n }\n @case ('history') {\n <div class=\"et-query-devtools-section-row\">\n <h4 class=\"et-query-devtools-section\">Runs</h4>\n <button\n [disabled]=\"!detailRuns.length\"\n (click)=\"host.resetStats(s.entry)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Clear the run history and the counters, so the next interaction can be measured on its own\"\n >\n \u27F2 Reset\n </button>\n </div>\n @if (detailRuns.length) {\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Run</th>\n <th>Started</th>\n <th>Duration</th>\n <th>Received</th>\n <th>Body</th>\n </tr>\n </thead>\n <tbody>\n @for (run of runSections().listed.concat(foldedRunsOpen() ? runSections().folded : []); track run.index) {\n <tr [attr.data-status]=\"run.status === 'error' ? 'error' : null\">\n <td>\n <span class=\"et-query-devtools-run-cell\">\n <span\n [attr.data-status]=\"host.runStatus(run)\"\n [title]=\"run.status\"\n class=\"et-query-devtools-status\"\n ></span>\n #{{ run.index }}\n @if (run.attempts > 1) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"Took {{\n run.attempts\n }} attempts - the duration next to it covers every one of them, backoff included\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n {{ run.attempts }}\n </span>\n }\n @if (!run.didRequest) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"Arrived without a request of this query's own - a poll, another consumer of the same cache entry, or another tab\"\n >\n shared\n </span>\n }\n @if (run.error; as error) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"{{ error.messages.join(' \u00B7 ') || 'The failure carried no message' }}\"\n >\n {{ error.code || 'failed' }}\n </span>\n }\n </span>\n </td>\n <td>{{ host.formatTime(run.startedAt) }}</td>\n <td>\n @if (run.endedAt === null) {\n in flight\n } @else {\n {{ host.formatDuration(run.endedAt - run.startedAt) }}\n }\n </td>\n <td>{{ run.receivedBytes ? host.formatBytes(run.receivedBytes) : '\u2014' }}</td>\n <td>\n <span class=\"et-query-devtools-cell-actions\">\n @if (host.canDiffRun(s.entry, run)) {\n @let role = host.diffRunRole(s.entry, run);\n @let armed = role ? diffRoles[role] : null;\n <button\n [class.et-query-devtools-btn--active]=\"!!armed\"\n [title]=\"\n armed?.title ?? 'Compare this response against another run - click a second row to pick which'\n \"\n (click)=\"host.toggleRunDiff(s.entry, run)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n {{ armed?.label ?? 'Diff' }}\n </button>\n } @else if (run.error?.hasBody) {\n <button\n [class.et-query-devtools-btn--active]=\"host.errorRunIndex() === run.index\"\n (click)=\"host.toggleRunError(run)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Show the body this failure came with\"\n >\n Error\n </button>\n } @else if (run.hasResponse) {\n <span class=\"et-query-devtools-count\">the only body held</span>\n } @else if (run.status === 'success' || run.error) {\n <span\n class=\"et-query-devtools-count\"\n title=\"Only the newest {{\n host.retainedResponseCount()\n }} bodies of a query are kept, so this run can no longer be an end of a diff\"\n >\n body no longer held\n </span>\n } @else {\n <span class=\"et-query-devtools-count\">\u2014</span>\n }\n </span>\n </td>\n </tr>\n }\n </tbody>\n </table>\n @let foldedRuns = runSections().folded.length;\n @if (foldedRuns) {\n <button\n [attr.aria-expanded]=\"foldedRunsOpen()\"\n (click)=\"foldedRunsOpen.set(!foldedRunsOpen())\"\n class=\"et-query-devtools-btn et-query-devtools-runs-fold\"\n type=\"button\"\n title=\"Only the newest {{\n host.retainedResponseCount()\n }} bodies of a query are kept, so none of these older runs can be an end of a diff\"\n >\n <span aria-hidden=\"true\">{{ foldedRunsOpen() ? '\u25BE' : '\u25B8' }}</span>\n {{ foldedRunsOpen() ? 'Hide' : 'Show' }} {{ foldedRuns }} older run{{ foldedRuns === 1 ? '' : 's' }} with no\n body\n </button>\n }\n\n @if (host.pickedRunError(s.entry); as failure) {\n <div class=\"et-query-devtools-section-row\">\n <h5 class=\"et-query-devtools-section\">\n Error body \u00B7 run #{{ failure.run.index }} \u00B7 {{ failure.error.code || 'no status' }}\n </h5>\n </div>\n @for (message of failure.error.messages; track message) {\n <p class=\"et-query-devtools-count\">{{ message }}</p>\n }\n <et-query-devtools-json\n [value]=\"failure.error.body\"\n [search]=\"host.jsonSearchTerm()\"\n [expandedPaths]=\"host.jsonExpandedPaths()\"\n [collapsedPaths]=\"host.jsonCollapsedPaths()\"\n [toggleFn]=\"host.toggleJsonPath\"\n path=\"runError\"\n />\n }\n\n @if (host.responseDiff(s.entry); as comparison) {\n <div class=\"et-query-devtools-section-row\">\n <h5 class=\"et-query-devtools-section\">\n Response diff \u00B7 run #{{ comparison.before.index }} \u2192 #{{ comparison.after.index }}\n </h5>\n <span class=\"et-query-devtools-diff-step\">\n <button\n [disabled]=\"!host.canStepRunDiff(s.entry, true)\"\n (click)=\"host.stepRunDiff(s.entry, true)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Compare the pair one run older, without going back to the table\"\n >\n \u25C2 Older\n </button>\n <button\n [disabled]=\"!host.canStepRunDiff(s.entry, false)\"\n (click)=\"host.stepRunDiff(s.entry, false)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Compare the pair one run newer, without going back to the table\"\n >\n Newer \u25B8\n </button>\n </span>\n <span class=\"et-query-devtools-count\">\n @if (host.diffBaseRunIndex() === null) {\n <span\n title=\"Nothing is picked to compare against, so this is the nearest older run that still holds a body - click another run's Diff to pick one\"\n >\n nearest older\n </span>\n \u00B7\n }\n @if (comparison.diff.entries.length) {\n {{ comparison.diff.entries.length }} path{{ comparison.diff.entries.length === 1 ? '' : 's' }}\n @if (comparison.diff.truncated) {\n (capped)\n }\n } @else {\n identical\n }\n </span>\n </div>\n @if (comparison.diff.entries.length) {\n <table class=\"et-query-devtools-table et-query-devtools-diff\">\n <thead>\n <tr>\n <th>Path</th>\n <th>Before</th>\n <th>After</th>\n </tr>\n </thead>\n <tbody>\n @for (change of comparison.diff.entries; track change.path) {\n <tr [attr.data-kind]=\"change.kind\">\n <td class=\"et-query-devtools-mono\">{{ change.path }}</td>\n <td class=\"et-query-devtools-mono et-query-devtools-diff-before\">\n {{ change.kind === 'added' ? '\u2014' : host.inlineValue(change.before) }}\n </td>\n <td class=\"et-query-devtools-mono et-query-devtools-diff-after\">\n {{ change.kind === 'removed' ? '\u2014' : host.inlineValue(change.after) }}\n </td>\n </tr>\n }\n </tbody>\n </table>\n } @else {\n <p class=\"et-query-devtools-empty\">The two responses are identical.</p>\n }\n }\n } @else {\n <p class=\"et-query-devtools-empty\">No runs recorded yet.</p>\n }\n }\n @case ('data') {\n @if (s.entry.meta.gqlQuery; as gqlQuery) {\n <div class=\"et-query-devtools-section-row\">\n <h5 class=\"et-query-devtools-section\">GraphQL document</h5>\n <button\n (click)=\"host.copyGqlDocument(gqlQuery)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Copy the GraphQL document\"\n >\n {{ host.copiedGql() ? '\u2713 Copied' : '\u29C9 Copy' }}\n </button>\n </div>\n <pre class=\"et-query-devtools-code\">{{ host.gqlDocument(gqlQuery) }}</pre>\n }\n\n <div class=\"et-query-devtools-section-row\">\n <h4 class=\"et-query-devtools-section\">Value explorer</h4>\n <input\n #jsonSearchInput\n [value]=\"host.jsonSearch()\"\n (input)=\"host.jsonSearch.set(jsonSearchInput.value)\"\n class=\"et-query-devtools-search\"\n type=\"search\"\n placeholder=\"filter keys / values\u2026\"\n />\n </div>\n\n <h5 class=\"et-query-devtools-section\">Args</h5>\n <et-query-devtools-json\n [value]=\"host.queryArgs(s.query)\"\n [search]=\"host.jsonSearchTerm()\"\n [expandedPaths]=\"host.jsonExpandedPaths()\"\n [collapsedPaths]=\"host.jsonCollapsedPaths()\"\n [toggleFn]=\"host.toggleJsonPath\"\n path=\"args\"\n />\n\n @if (s.query.error(); as error) {\n <h5 class=\"et-query-devtools-section\">Error ({{ error.raw.status }})</h5>\n <et-query-devtools-json\n [value]=\"error.isList ? error.errors : error.error\"\n [search]=\"host.jsonSearchTerm()\"\n [expandedPaths]=\"host.jsonExpandedPaths()\"\n [collapsedPaths]=\"host.jsonCollapsedPaths()\"\n [toggleFn]=\"host.toggleJsonPath\"\n path=\"error\"\n />\n } @else {\n <div class=\"et-query-devtools-section-row\">\n <h5 class=\"et-query-devtools-section\">Response</h5>\n @if (s.entry.overrides?.list()?.length || host.overridesPersist()) {\n <button\n [class.et-query-devtools-btn--active]=\"host.overridesPersist()\"\n [attr.aria-pressed]=\"host.overridesPersist()\"\n (click)=\"host.toggleOverridesPersist()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"{{\n host.overridesPersist()\n ? 'Panel-wide: every armed response override is kept in ' +\n host.overridesScopeLabel() +\n ' and replayed on the next load.'\n : 'Panel-wide: keep every armed response override and replay it on the next load.'\n }} Settings \u2699 chooses the scope. Armed faults are never kept.\"\n >\n Keep across reloads\n </button>\n }\n @if (s.entry.overrides?.list()?.length) {\n <button (click)=\"s.entry.overrides?.clearAll()\" class=\"et-query-devtools-btn\" type=\"button\">\n Reset all overrides ({{ s.entry.overrides?.list()?.length }})\n </button>\n }\n </div>\n <et-query-devtools-json\n [value]=\"s.query.response()\"\n [search]=\"host.jsonSearchTerm()\"\n [expandedPaths]=\"host.jsonExpandedPaths()\"\n [collapsedPaths]=\"host.jsonCollapsedPaths()\"\n [toggleFn]=\"host.toggleJsonPath\"\n [overrides]=\"s.entry.overrides ?? null\"\n path=\"response\"\n />\n }\n }\n}\n", styles: ["@layer components{.et-query-devtools-detail .et-query-devtools-detail-head,.et-query-devtools-drawer .et-query-devtools-detail-head{padding-block-end:.8rem;border-block-end:1px solid var(--_et-qdt-border)}.et-query-devtools-gone-note{margin:0 0 .8rem;color:var(--_et-qdt-muted);font-size:1.1rem}.et-query-devtools-detail-head{display:flex;align-items:center;gap:.8rem;margin-block-end:.8rem}.et-query-devtools-detail-head .et-query-devtools-route{flex:0 1 auto}.et-query-devtools-route-copy{appearance:none;margin-inline-end:auto;display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;inline-size:2.2rem;block-size:2.2rem;margin-inline-start:-.4rem;border:none;border-radius:.3rem;background:transparent;color:var(--_et-qdt-subtle);cursor:pointer;font:inherit;font-size:1.3rem;line-height:1}@media(hover:hover){.et-query-devtools-route-copy:hover:not(.et-query-devtools-route-copy--copied){background:var(--_et-qdt-active);color:var(--_et-qdt-fg)}}.et-query-devtools-route-copy--copied{color:var(--_et-qdt-success)}.et-query-devtools-live{display:flex;flex-wrap:wrap;align-items:center;gap:.6rem;margin-block-end:.8rem}.et-query-devtools-live-note{color:var(--_et-qdt-muted);font-size:1.15rem}.et-query-devtools-progress{display:block;flex:1 1 12rem;min-inline-size:8rem;max-inline-size:24rem;block-size:.5rem;border-radius:999px;overflow:hidden;background:var(--_et-qdt-border)}.et-query-devtools-progress-bar{display:block;block-size:100%;border-radius:inherit;background:var(--_et-qdt-accent)}.et-query-devtools-actions{display:flex;flex-wrap:wrap;align-items:center;gap:.4rem;margin-block-end:.8rem}.et-query-devtools-actions>.et-query-devtools-menu+.et-query-devtools-menu{margin-inline-start:.5rem;padding-inline-start:.9rem;border-inline-start:1px solid var(--_et-qdt-border)}.et-query-devtools-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(12rem,1fr));gap:.6rem;margin:0 0 1rem}.et-query-devtools-stat-value--sm{font-size:1.2rem}.et-query-devtools-code{margin:0 0 .8rem;padding:.8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);overflow:auto;white-space:pre;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:1.15rem;max-block-size:20rem}.et-query-devtools-diff-step{display:flex;gap:.4rem;margin-inline-start:auto}.et-query-devtools-runs-fold{margin-block-start:.4rem;color:var(--_et-qdt-muted)}.et-query-devtools-textarea{inline-size:100%;min-block-size:12rem;resize:vertical;background:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);color:var(--_et-qdt-fg);border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.6rem;font-family:inherit;font-size:1.2rem}.et-query-devtools-subtabs{display:flex;flex-wrap:wrap;gap:.2rem;border-block-end:1px solid var(--_et-qdt-border);margin-block:.8rem}.et-query-devtools-subtabs+.et-query-devtools-section-row{padding-block-start:0;border-block-start:0}.et-query-devtools-subtab{appearance:none;display:inline-flex;align-items:center;gap:.4rem;border:0;border-block-end:2px solid transparent;background:transparent;color:var(--_et-qdt-muted);font:inherit;font-size:1.15rem;padding:.4rem .6rem;margin-block-end:-1px;cursor:pointer}@media(hover:hover){.et-query-devtools-subtab:hover{color:var(--_et-qdt-fg)}}.et-query-devtools-subtab--active{color:var(--_et-qdt-fg);border-block-end-color:var(--_et-qdt-accent)}.et-query-devtools-run-cell{display:inline-flex;align-items:center;gap:.5rem}.et-query-devtools-diff td{max-inline-size:28rem;overflow-wrap:anywhere}.et-query-devtools-diff-before{color:var(--_et-qdt-muted)}.et-query-devtools-diff tr[data-kind=removed] .et-query-devtools-diff-before{color:var(--_et-qdt-error)}.et-query-devtools-diff tr[data-kind=added] .et-query-devtools-diff-after{color:var(--_et-qdt-success)}.et-query-devtools-diff tr[data-kind=changed] .et-query-devtools-diff-after{color:var(--_et-qdt-accent)}}\n"] }]
1894
+ }], propDecorators: { sel: [{ type: i0.Input, args: [{ isSignal: true, alias: "sel", required: true }] }] } });
1895
+
1896
+ /**
1897
+ * The split-view drawer every two-pane tab (stacks/sequences/forms/timeline) opens a query in - the
1898
+ * resize handle, the `<aside>` chrome and the close button around a `<et-query-devtools-detail>`. The
1899
+ * Queries tab renders that same detail component inline instead, without this wrapper, since it has no
1900
+ * separate list/drawer split to close.
1901
+ */
1902
+ class QueryDevtoolsDrawerComponent {
1903
+ constructor() {
1904
+ this.host = injectQueryDevtoolsHost();
1905
+ this.sel = input(null, /* @ts-ignore */
1906
+ ...(ngDevMode ? [{ debugName: "sel" }] : /* istanbul ignore next */ []));
1907
+ this.container = input.required(/* @ts-ignore */
1908
+ ...(ngDevMode ? [{ debugName: "container" }] : /* istanbul ignore next */ []));
1909
+ this.dismiss = output();
1910
+ }
1911
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsDrawerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1912
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsDrawerComponent, isStandalone: true, selector: "et-query-devtools-drawer", inputs: { sel: { classPropertyName: "sel", publicName: "sel", isSignal: true, isRequired: false, transformFunction: null }, container: { classPropertyName: "container", publicName: "container", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { dismiss: "dismiss" }, ngImport: i0, template: "@if (sel(); as selected) {\n <div\n (pointerdown)=\"host.startPaneResize($event, { pane: 'drawer', container: container() })\"\n (dblclick)=\"host.resetPaneSize('drawer')\"\n class=\"et-query-devtools-pane-resize\"\n title=\"Drag to resize, double-click to reset\"\n aria-hidden=\"true\"\n ></div>\n\n <aside class=\"et-query-devtools-drawer\">\n <div class=\"et-query-devtools-drawer-head\">\n <span>Query detail</span>\n <button (click)=\"dismiss.emit()\" class=\"et-query-devtools-close\" type=\"button\" title=\"Close detail\">\u2715</button>\n </div>\n <et-query-devtools-detail [sel]=\"selected\" />\n </aside>\n}\n", styles: ["@layer components{et-query-devtools-drawer{display:contents}.et-query-devtools-drawer{flex:0 0 var(--_et-qdt-drawer-width, 42%);min-inline-size:var(--_et-qdt-drawer-width, 26rem);max-inline-size:var(--_et-qdt-drawer-width, 48rem);overflow-y:auto;padding:0 1rem 1rem;border-inline-start:1px solid var(--_et-qdt-border)}.et-query-devtools-drawer-head{display:flex;align-items:center;justify-content:space-between;gap:.8rem;padding:.8rem 0;position:sticky;inset-block-start:0;background:var(--_et-qdt-bg);color:var(--_et-qdt-muted);border-block-end:1px solid var(--_et-qdt-border);margin-block-end:.8rem}}\n"], dependencies: [{ kind: "component", type: QueryDevtoolsDetailComponent, selector: "et-query-devtools-detail", inputs: ["sel"] }], encapsulation: i0.ViewEncapsulation.None }); }
1913
+ }
1914
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsDrawerComponent, decorators: [{
1915
+ type: Component,
1916
+ args: [{ selector: 'et-query-devtools-drawer', encapsulation: ViewEncapsulation.None, imports: [QueryDevtoolsDetailComponent], template: "@if (sel(); as selected) {\n <div\n (pointerdown)=\"host.startPaneResize($event, { pane: 'drawer', container: container() })\"\n (dblclick)=\"host.resetPaneSize('drawer')\"\n class=\"et-query-devtools-pane-resize\"\n title=\"Drag to resize, double-click to reset\"\n aria-hidden=\"true\"\n ></div>\n\n <aside class=\"et-query-devtools-drawer\">\n <div class=\"et-query-devtools-drawer-head\">\n <span>Query detail</span>\n <button (click)=\"dismiss.emit()\" class=\"et-query-devtools-close\" type=\"button\" title=\"Close detail\">\u2715</button>\n </div>\n <et-query-devtools-detail [sel]=\"selected\" />\n </aside>\n}\n", styles: ["@layer components{et-query-devtools-drawer{display:contents}.et-query-devtools-drawer{flex:0 0 var(--_et-qdt-drawer-width, 42%);min-inline-size:var(--_et-qdt-drawer-width, 26rem);max-inline-size:var(--_et-qdt-drawer-width, 48rem);overflow-y:auto;padding:0 1rem 1rem;border-inline-start:1px solid var(--_et-qdt-border)}.et-query-devtools-drawer-head{display:flex;align-items:center;justify-content:space-between;gap:.8rem;padding:.8rem 0;position:sticky;inset-block-start:0;background:var(--_et-qdt-bg);color:var(--_et-qdt-muted);border-block-end:1px solid var(--_et-qdt-border);margin-block-end:.8rem}}\n"] }]
1917
+ }], propDecorators: { sel: [{ type: i0.Input, args: [{ isSignal: true, alias: "sel", required: false }] }], container: [{ type: i0.Input, args: [{ isSignal: true, alias: "container", required: true }] }], dismiss: [{ type: i0.Output, args: ["dismiss"] }] } });
1918
+
1621
1919
  /** The Events tab: the rolling log of repository traffic, refreshes and secure unbinds. */
1622
1920
  class QueryDevtoolsEventsTabComponent {
1623
1921
  constructor() {
@@ -1654,9 +1952,21 @@ class QueryDevtoolsEventsTabComponent {
1654
1952
  this.host.eventClient.set(null);
1655
1953
  this.host.eventErrorsOnly.set(false);
1656
1954
  }
1955
+ /**
1956
+ * Opens the query a row belongs to in this tab's own drawer, rather than jumping to the Queries tab -
1957
+ * the log is read by walking rows, and a jump ends that walk on every click.
1958
+ */
1657
1959
  selectEventRow(item) {
1658
1960
  if (item.queryId)
1659
- this.host.selectQuery(item.queryId);
1961
+ this.host.eventSelectedQueryId.set(item.queryId);
1962
+ }
1963
+ /**
1964
+ * Whether a row's query can still be opened. An id alone does not say so: a query batch destroys each
1965
+ * item's query as that item settles and leaves no tombstone behind, so a bulk run's rows would
1966
+ * otherwise all render as links that open nothing.
1967
+ */
1968
+ canOpenEventRow(id) {
1969
+ return !!id && !!this.host.findQuery(id);
1660
1970
  }
1661
1971
  /** Only a request settling is a success or a failure; a teardown or a refresh is neither. */
1662
1972
  eventRowStatus(event) {
@@ -1676,11 +1986,11 @@ class QueryDevtoolsEventsTabComponent {
1676
1986
  return event.type === 'request-error' ? `error ${event.status}` : 'success';
1677
1987
  }
1678
1988
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsEventsTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1679
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsEventsTabComponent, isStandalone: true, selector: "et-query-devtools-events-tab", ngImport: i0, template: "<div class=\"et-query-devtools-pane\">\n <div class=\"et-query-devtools-toolbar\">\n <div class=\"et-query-devtools-toolbar-row\">\n @if (eventClients().length > 1) {\n <label class=\"et-query-devtools-field\">\n <span>Client</span>\n <select\n #eventClientSelect\n [value]=\"host.eventClient() ?? ''\"\n (change)=\"host.eventClient.set(eventClientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n >\n <option value=\"\">All clients</option>\n @for (name of eventClients(); track name) {\n <option [value]=\"name\">{{ name }}</option>\n }\n </select>\n </label>\n }\n @let errorsOnly = host.eventErrorsOnly();\n <button\n [class.et-query-devtools-facet--active]=\"errorsOnly\"\n [attr.aria-pressed]=\"errorsOnly\"\n (click)=\"toggleEventErrorsOnly()\"\n class=\"et-query-devtools-chip et-query-devtools-facet\"\n type=\"button\"\n title=\"Show only the requests that failed\"\n >\n Errors only\n <span class=\"et-query-devtools-facet-count\">{{ host.tabBadges().events.errors }}</span>\n </button>\n <span class=\"et-query-devtools-count\">\n @if (isEventLogNarrowed()) {\n {{ filteredEvents().length }} of {{ host.eventLog().length }} events\n } @else {\n {{ host.eventLog().length }} events\n }\n </span>\n @if (isEventLogNarrowed()) {\n <button\n (click)=\"clearEventFilters()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the client scope and the errors-only toggle\"\n aria-label=\"Clear filters\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n </button>\n }\n <button (click)=\"clearEvents()\" class=\"et-query-devtools-btn et-query-devtools-toolbar-btn\" type=\"button\">\n Clear\n </button>\n </div>\n </div>\n <div class=\"et-query-devtools-scroll\">\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Time</th>\n <th>Base URL</th>\n <th>Type</th>\n <th>Request</th>\n <th>Duration</th>\n <th>Size</th>\n </tr>\n </thead>\n <tbody>\n @for (event of filteredEvents(); track event.id) {\n <tr [attr.data-status]=\"eventRowStatus(event)\">\n <td>{{ host.formatTime(event.timestamp) }}</td>\n <td class=\"et-query-devtools-mono\">{{ event.client }}</td>\n <td>\n {{ eventTypeLabel(event) }}\n @if (event.isSecure) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">secure</span>\n }\n </td>\n @if (event.cause; as cause) {\n <td>\n <span class=\"et-query-devtools-url\">{{ host.causeLabel(cause) }}</span>\n @if (event.refreshed?.length) {\n <div class=\"et-query-devtools-chips\">\n @for (refreshed of event.refreshed; track $index) {\n @if (refreshed.queryIds[0]; as queryId) {\n <button\n (click)=\"host.selectQuery(queryId)\"\n class=\"et-query-devtools-chip et-query-devtools-chip--button\"\n type=\"button\"\n title=\"Open {{ refreshed.method }} {{ refreshed.path }}\"\n >\n {{ refreshed.method }} {{ refreshed.path }}\n <!-- One request, several queries on the same cache key. Spelling the count\n out beats repeating an identical row per consumer. -->\n @if (refreshed.queryIds.length > 1) {\n <small>\u00D7{{ refreshed.queryIds.length }}</small>\n }\n </button>\n } @else {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"No registered query holds this cache entry any more\"\n >\n {{ refreshed.method }} {{ refreshed.path }}\n </span>\n }\n }\n </div>\n } @else {\n <span class=\"et-query-devtools-hint\">matched no query in use</span>\n }\n </td>\n } @else if (event.url; as url) {\n <td>\n @if (event.queryId) {\n <button\n (click)=\"selectEventRow(event)\"\n class=\"et-query-devtools-row-link\"\n type=\"button\"\n title=\"Open {{ event.method }} {{ url }}\"\n >\n <span class=\"et-query-devtools-method\">{{ event.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(url) }}</span>\n </button>\n } @else {\n <span [title]=\"event.method + ' ' + url\">\n <span class=\"et-query-devtools-method\">{{ event.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(url) }}</span>\n </span>\n }\n </td>\n } @else {\n <td class=\"et-query-devtools-url\">all secure entries dropped</td>\n }\n <td>{{ host.formatDuration(event.durationMs) }}</td>\n <td>{{ event.bytes === null ? '\u2014' : host.formatTransferred(event.bytes, event.isEstimatedBytes) }}</td>\n </tr>\n } @empty {\n <tr>\n @if (host.eventLog().length) {\n <td class=\"et-query-devtools-empty\" colspan=\"6\">\n No event matches the filter \u00B7 {{ host.eventLog().length }} in the log.\n </td>\n } @else {\n <td class=\"et-query-devtools-empty\" colspan=\"6\">No events captured yet.</td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n</div>\n", encapsulation: i0.ViewEncapsulation.None }); }
1989
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsEventsTabComponent, isStandalone: true, selector: "et-query-devtools-events-tab", ngImport: i0, template: "<div #panes class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-pane\">\n <div class=\"et-query-devtools-toolbar\">\n <div class=\"et-query-devtools-toolbar-row\">\n @if (eventClients().length > 1) {\n <label class=\"et-query-devtools-field\">\n <span>Client</span>\n <select\n #eventClientSelect\n [value]=\"host.eventClient() ?? ''\"\n (change)=\"host.eventClient.set(eventClientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n >\n <option value=\"\">All clients</option>\n @for (name of eventClients(); track name) {\n <option [value]=\"name\">{{ name }}</option>\n }\n </select>\n </label>\n }\n @let errorsOnly = host.eventErrorsOnly();\n <button\n [class.et-query-devtools-facet--active]=\"errorsOnly\"\n [attr.aria-pressed]=\"errorsOnly\"\n (click)=\"toggleEventErrorsOnly()\"\n class=\"et-query-devtools-chip et-query-devtools-facet\"\n type=\"button\"\n title=\"Show only the requests that failed\"\n >\n Errors only\n <span class=\"et-query-devtools-facet-count\">{{ host.tabBadges().events.errors }}</span>\n </button>\n <span class=\"et-query-devtools-count\">\n @if (isEventLogNarrowed()) {\n {{ filteredEvents().length }} of {{ host.eventLog().length }} events\n } @else {\n {{ host.eventLog().length }} events\n }\n </span>\n @if (isEventLogNarrowed()) {\n <button\n (click)=\"clearEventFilters()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the client scope and the errors-only toggle\"\n aria-label=\"Clear filters\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n </button>\n }\n <button (click)=\"clearEvents()\" class=\"et-query-devtools-btn et-query-devtools-toolbar-btn\" type=\"button\">\n Clear\n </button>\n </div>\n </div>\n <div class=\"et-query-devtools-scroll\">\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Time</th>\n <th class=\"et-query-devtools-cell-origin\">Base URL</th>\n <th>Type</th>\n <th>Request</th>\n <th>Duration</th>\n <th>Size</th>\n </tr>\n </thead>\n <tbody>\n @for (event of filteredEvents(); track event.id) {\n <tr\n [attr.data-status]=\"eventRowStatus(event)\"\n [class.et-query-devtools-row--active]=\"!!event.queryId && host.eventSelectedQueryId() === event.queryId\"\n >\n <td>{{ host.formatTime(event.timestamp) }}</td>\n <td [title]=\"event.client\" class=\"et-query-devtools-mono et-query-devtools-cell-origin\">\n {{ event.client }}\n </td>\n <td>\n {{ eventTypeLabel(event) }}\n @if (event.isSecure) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">secure</span>\n }\n </td>\n @if (event.cause; as cause) {\n <td>\n <span class=\"et-query-devtools-url\">{{ host.causeLabel(cause) }}</span>\n @if (event.refreshed?.length) {\n <div class=\"et-query-devtools-chips\">\n @for (refreshed of event.refreshed; track $index) {\n @if (\n canOpenEventRow(refreshed.queryIds[0] ?? null) ? refreshed.queryIds[0] : null;\n as queryId\n ) {\n <button\n (click)=\"host.eventSelectedQueryId.set(queryId)\"\n class=\"et-query-devtools-chip et-query-devtools-chip--button\"\n type=\"button\"\n title=\"Open {{ refreshed.method }} {{ refreshed.path }}\"\n >\n {{ refreshed.method }} {{ refreshed.path }}\n <!-- One request, several queries on the same cache key. Spelling the count\n out beats repeating an identical row per consumer. -->\n @if (refreshed.queryIds.length > 1) {\n <small>\u00D7{{ refreshed.queryIds.length }}</small>\n }\n </button>\n } @else {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"No registered query holds this cache entry any more\"\n >\n {{ refreshed.method }} {{ refreshed.path }}\n </span>\n }\n }\n </div>\n } @else {\n <span class=\"et-query-devtools-hint\">matched no query in use</span>\n }\n </td>\n } @else if (event.url; as url) {\n <td>\n @if (canOpenEventRow(event.queryId)) {\n <button\n (click)=\"selectEventRow(event)\"\n class=\"et-query-devtools-row-link\"\n type=\"button\"\n title=\"Open {{ event.method }} {{ url }}\"\n >\n <span class=\"et-query-devtools-method\">{{ event.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(url) }}</span>\n </button>\n } @else {\n <span [title]=\"event.method + ' ' + url\">\n <span class=\"et-query-devtools-method\">{{ event.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(url) }}</span>\n </span>\n }\n </td>\n } @else {\n <td class=\"et-query-devtools-url\">all secure entries dropped</td>\n }\n <td>{{ host.formatDuration(event.durationMs) }}</td>\n <td>{{ event.bytes === null ? '\u2014' : host.formatTransferred(event.bytes, event.isEstimatedBytes) }}</td>\n </tr>\n } @empty {\n <tr>\n @if (host.eventLog().length) {\n <td class=\"et-query-devtools-empty\" colspan=\"6\">\n No event matches the filter \u00B7 {{ host.eventLog().length }} in the log.\n </td>\n } @else {\n <td class=\"et-query-devtools-empty\" colspan=\"6\">No events captured yet.</td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.eventSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.eventSelectedQueryId.set(null)\"\n />\n</div>\n", dependencies: [{ kind: "component", type: QueryDevtoolsDrawerComponent, selector: "et-query-devtools-drawer", inputs: ["sel", "container"], outputs: ["dismiss"] }], encapsulation: i0.ViewEncapsulation.None }); }
1680
1990
  }
1681
1991
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsEventsTabComponent, decorators: [{
1682
1992
  type: Component,
1683
- args: [{ selector: 'et-query-devtools-events-tab', encapsulation: ViewEncapsulation.None, template: "<div class=\"et-query-devtools-pane\">\n <div class=\"et-query-devtools-toolbar\">\n <div class=\"et-query-devtools-toolbar-row\">\n @if (eventClients().length > 1) {\n <label class=\"et-query-devtools-field\">\n <span>Client</span>\n <select\n #eventClientSelect\n [value]=\"host.eventClient() ?? ''\"\n (change)=\"host.eventClient.set(eventClientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n >\n <option value=\"\">All clients</option>\n @for (name of eventClients(); track name) {\n <option [value]=\"name\">{{ name }}</option>\n }\n </select>\n </label>\n }\n @let errorsOnly = host.eventErrorsOnly();\n <button\n [class.et-query-devtools-facet--active]=\"errorsOnly\"\n [attr.aria-pressed]=\"errorsOnly\"\n (click)=\"toggleEventErrorsOnly()\"\n class=\"et-query-devtools-chip et-query-devtools-facet\"\n type=\"button\"\n title=\"Show only the requests that failed\"\n >\n Errors only\n <span class=\"et-query-devtools-facet-count\">{{ host.tabBadges().events.errors }}</span>\n </button>\n <span class=\"et-query-devtools-count\">\n @if (isEventLogNarrowed()) {\n {{ filteredEvents().length }} of {{ host.eventLog().length }} events\n } @else {\n {{ host.eventLog().length }} events\n }\n </span>\n @if (isEventLogNarrowed()) {\n <button\n (click)=\"clearEventFilters()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the client scope and the errors-only toggle\"\n aria-label=\"Clear filters\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n </button>\n }\n <button (click)=\"clearEvents()\" class=\"et-query-devtools-btn et-query-devtools-toolbar-btn\" type=\"button\">\n Clear\n </button>\n </div>\n </div>\n <div class=\"et-query-devtools-scroll\">\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Time</th>\n <th>Base URL</th>\n <th>Type</th>\n <th>Request</th>\n <th>Duration</th>\n <th>Size</th>\n </tr>\n </thead>\n <tbody>\n @for (event of filteredEvents(); track event.id) {\n <tr [attr.data-status]=\"eventRowStatus(event)\">\n <td>{{ host.formatTime(event.timestamp) }}</td>\n <td class=\"et-query-devtools-mono\">{{ event.client }}</td>\n <td>\n {{ eventTypeLabel(event) }}\n @if (event.isSecure) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">secure</span>\n }\n </td>\n @if (event.cause; as cause) {\n <td>\n <span class=\"et-query-devtools-url\">{{ host.causeLabel(cause) }}</span>\n @if (event.refreshed?.length) {\n <div class=\"et-query-devtools-chips\">\n @for (refreshed of event.refreshed; track $index) {\n @if (refreshed.queryIds[0]; as queryId) {\n <button\n (click)=\"host.selectQuery(queryId)\"\n class=\"et-query-devtools-chip et-query-devtools-chip--button\"\n type=\"button\"\n title=\"Open {{ refreshed.method }} {{ refreshed.path }}\"\n >\n {{ refreshed.method }} {{ refreshed.path }}\n <!-- One request, several queries on the same cache key. Spelling the count\n out beats repeating an identical row per consumer. -->\n @if (refreshed.queryIds.length > 1) {\n <small>\u00D7{{ refreshed.queryIds.length }}</small>\n }\n </button>\n } @else {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"No registered query holds this cache entry any more\"\n >\n {{ refreshed.method }} {{ refreshed.path }}\n </span>\n }\n }\n </div>\n } @else {\n <span class=\"et-query-devtools-hint\">matched no query in use</span>\n }\n </td>\n } @else if (event.url; as url) {\n <td>\n @if (event.queryId) {\n <button\n (click)=\"selectEventRow(event)\"\n class=\"et-query-devtools-row-link\"\n type=\"button\"\n title=\"Open {{ event.method }} {{ url }}\"\n >\n <span class=\"et-query-devtools-method\">{{ event.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(url) }}</span>\n </button>\n } @else {\n <span [title]=\"event.method + ' ' + url\">\n <span class=\"et-query-devtools-method\">{{ event.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(url) }}</span>\n </span>\n }\n </td>\n } @else {\n <td class=\"et-query-devtools-url\">all secure entries dropped</td>\n }\n <td>{{ host.formatDuration(event.durationMs) }}</td>\n <td>{{ event.bytes === null ? '\u2014' : host.formatTransferred(event.bytes, event.isEstimatedBytes) }}</td>\n </tr>\n } @empty {\n <tr>\n @if (host.eventLog().length) {\n <td class=\"et-query-devtools-empty\" colspan=\"6\">\n No event matches the filter \u00B7 {{ host.eventLog().length }} in the log.\n </td>\n } @else {\n <td class=\"et-query-devtools-empty\" colspan=\"6\">No events captured yet.</td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n</div>\n" }]
1993
+ args: [{ selector: 'et-query-devtools-events-tab', encapsulation: ViewEncapsulation.None, imports: [QueryDevtoolsDrawerComponent], template: "<div #panes class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-pane\">\n <div class=\"et-query-devtools-toolbar\">\n <div class=\"et-query-devtools-toolbar-row\">\n @if (eventClients().length > 1) {\n <label class=\"et-query-devtools-field\">\n <span>Client</span>\n <select\n #eventClientSelect\n [value]=\"host.eventClient() ?? ''\"\n (change)=\"host.eventClient.set(eventClientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n >\n <option value=\"\">All clients</option>\n @for (name of eventClients(); track name) {\n <option [value]=\"name\">{{ name }}</option>\n }\n </select>\n </label>\n }\n @let errorsOnly = host.eventErrorsOnly();\n <button\n [class.et-query-devtools-facet--active]=\"errorsOnly\"\n [attr.aria-pressed]=\"errorsOnly\"\n (click)=\"toggleEventErrorsOnly()\"\n class=\"et-query-devtools-chip et-query-devtools-facet\"\n type=\"button\"\n title=\"Show only the requests that failed\"\n >\n Errors only\n <span class=\"et-query-devtools-facet-count\">{{ host.tabBadges().events.errors }}</span>\n </button>\n <span class=\"et-query-devtools-count\">\n @if (isEventLogNarrowed()) {\n {{ filteredEvents().length }} of {{ host.eventLog().length }} events\n } @else {\n {{ host.eventLog().length }} events\n }\n </span>\n @if (isEventLogNarrowed()) {\n <button\n (click)=\"clearEventFilters()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the client scope and the errors-only toggle\"\n aria-label=\"Clear filters\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n </button>\n }\n <button (click)=\"clearEvents()\" class=\"et-query-devtools-btn et-query-devtools-toolbar-btn\" type=\"button\">\n Clear\n </button>\n </div>\n </div>\n <div class=\"et-query-devtools-scroll\">\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Time</th>\n <th class=\"et-query-devtools-cell-origin\">Base URL</th>\n <th>Type</th>\n <th>Request</th>\n <th>Duration</th>\n <th>Size</th>\n </tr>\n </thead>\n <tbody>\n @for (event of filteredEvents(); track event.id) {\n <tr\n [attr.data-status]=\"eventRowStatus(event)\"\n [class.et-query-devtools-row--active]=\"!!event.queryId && host.eventSelectedQueryId() === event.queryId\"\n >\n <td>{{ host.formatTime(event.timestamp) }}</td>\n <td [title]=\"event.client\" class=\"et-query-devtools-mono et-query-devtools-cell-origin\">\n {{ event.client }}\n </td>\n <td>\n {{ eventTypeLabel(event) }}\n @if (event.isSecure) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">secure</span>\n }\n </td>\n @if (event.cause; as cause) {\n <td>\n <span class=\"et-query-devtools-url\">{{ host.causeLabel(cause) }}</span>\n @if (event.refreshed?.length) {\n <div class=\"et-query-devtools-chips\">\n @for (refreshed of event.refreshed; track $index) {\n @if (\n canOpenEventRow(refreshed.queryIds[0] ?? null) ? refreshed.queryIds[0] : null;\n as queryId\n ) {\n <button\n (click)=\"host.eventSelectedQueryId.set(queryId)\"\n class=\"et-query-devtools-chip et-query-devtools-chip--button\"\n type=\"button\"\n title=\"Open {{ refreshed.method }} {{ refreshed.path }}\"\n >\n {{ refreshed.method }} {{ refreshed.path }}\n <!-- One request, several queries on the same cache key. Spelling the count\n out beats repeating an identical row per consumer. -->\n @if (refreshed.queryIds.length > 1) {\n <small>\u00D7{{ refreshed.queryIds.length }}</small>\n }\n </button>\n } @else {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"No registered query holds this cache entry any more\"\n >\n {{ refreshed.method }} {{ refreshed.path }}\n </span>\n }\n }\n </div>\n } @else {\n <span class=\"et-query-devtools-hint\">matched no query in use</span>\n }\n </td>\n } @else if (event.url; as url) {\n <td>\n @if (canOpenEventRow(event.queryId)) {\n <button\n (click)=\"selectEventRow(event)\"\n class=\"et-query-devtools-row-link\"\n type=\"button\"\n title=\"Open {{ event.method }} {{ url }}\"\n >\n <span class=\"et-query-devtools-method\">{{ event.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(url) }}</span>\n </button>\n } @else {\n <span [title]=\"event.method + ' ' + url\">\n <span class=\"et-query-devtools-method\">{{ event.method }}</span>\n <span class=\"et-query-devtools-url\">{{ host.requestPath(url) }}</span>\n </span>\n }\n </td>\n } @else {\n <td class=\"et-query-devtools-url\">all secure entries dropped</td>\n }\n <td>{{ host.formatDuration(event.durationMs) }}</td>\n <td>{{ event.bytes === null ? '\u2014' : host.formatTransferred(event.bytes, event.isEstimatedBytes) }}</td>\n </tr>\n } @empty {\n <tr>\n @if (host.eventLog().length) {\n <td class=\"et-query-devtools-empty\" colspan=\"6\">\n No event matches the filter \u00B7 {{ host.eventLog().length }} in the log.\n </td>\n } @else {\n <td class=\"et-query-devtools-empty\" colspan=\"6\">No events captured yet.</td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.eventSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.eventSelectedQueryId.set(null)\"\n />\n</div>\n" }]
1684
1994
  }] });
1685
1995
 
1686
1996
  /**
@@ -2742,362 +3052,64 @@ class QueryDevtoolsMocksTabComponent {
2742
3052
  }
2743
3053
  this.seedSource.set(source);
2744
3054
  this.draftSeed.set(seed);
2745
- this.draftError.set(null);
2746
- this.patchDraft({ body: JSON.stringify(seed.body, null, 2) });
2747
- }
2748
- countOf(value, fallback) {
2749
- const count = Math.trunc(Number(value));
2750
- return Number.isFinite(count) ? count : fallback;
2751
- }
2752
- idOf(entry) {
2753
- return queryDevtoolsMockId({
2754
- clientName: entry.meta.clientName ?? '',
2755
- method: entry.meta.method ?? 'GET',
2756
- pattern: entry.meta.route ?? '',
2757
- });
2758
- }
2759
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsMocksTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2760
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsMocksTabComponent, isStandalone: true, selector: "et-query-devtools-mocks-tab", ngImport: i0, template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar et-query-devtools-mocks-toolbar\">\n <p class=\"et-query-devtools-hint\">\n A mock replaces the request, so it answers a route that has never run - or does not exist yet. It bypasses the\n interceptor chain by construction, so a mocked secure route never exercises the token flow. The library is kept;\n whether a mock is <strong>armed</strong> is not, so a reload always stops serving them. GraphQL queries are not\n offered: they all POST one route, so matching them needs the document rather than the path.\n </p>\n <div class=\"et-query-devtools-settings-controls\">\n @if (rows().length && armedCount() < rows().length) {\n <button\n (click)=\"ARM_ALL()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Serve every mock in the library - the whole screen off the network at once\"\n >\n Arm all ({{ rows().length }})\n </button>\n }\n @if (armedCount()) {\n <button (click)=\"DISARM_ALL()\" class=\"et-query-devtools-btn\" type=\"button\">\n Disarm all ({{ armedCount() }})\n </button>\n }\n <button (click)=\"openDraft()\" class=\"et-query-devtools-btn et-query-devtools-btn--primary\" type=\"button\">\n <span aria-hidden=\"true\">\uFF0B</span>\n New mock\n </button>\n </div>\n </div>\n\n @if (draft(); as form) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>New mock</strong>\n <span class=\"et-query-devtools-count\">for any route, whether a query has ever called it or not</span>\n </div>\n\n <div class=\"et-query-devtools-mock-fields\">\n <label class=\"et-query-devtools-field\">\n <span>Client</span>\n <select #clientSelect (change)=\"pickClient(clientSelect.value)\" class=\"et-query-devtools-select\">\n @for (name of host.clientNames(); track name) {\n <option [value]=\"name\" [selected]=\"name === form.clientName\">{{ name }}</option>\n }\n </select>\n <small>a mock answers one client, matched by name</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Method</span>\n <select #methodSelect (change)=\"patchDraft({ method: methodSelect.value })\" class=\"et-query-devtools-select\">\n @for (method of METHODS; track method) {\n <option [value]=\"method\" [selected]=\"method === form.method\">{{ method }}</option>\n }\n </select>\n <small>matched exactly</small>\n </label>\n\n <label class=\"et-query-devtools-field et-query-devtools-field--wide\">\n <span>Path</span>\n <input\n #patternInput\n [value]=\"form.pattern\"\n (input)=\"patchDraft({ pattern: patternInput.value })\"\n class=\"et-query-devtools-input\"\n type=\"text\"\n placeholder=\"/posts/:id/comments\"\n />\n <small><code>:name</code> matches any one segment, so the route is mocked rather than one URL</small>\n </label>\n\n <label class=\"et-query-devtools-field et-query-devtools-field--wide\">\n <span>Query</span>\n <input\n #queryInput\n [value]=\"form.query\"\n (input)=\"patchDraft({ query: queryInput.value })\"\n class=\"et-query-devtools-input\"\n type=\"text\"\n placeholder=\"page=2&limit=10\"\n />\n <small>\n parameters the request must carry; anything else it asks for is ignored. Empty answers whatever the query\n string is - and a mock naming parameters wins over one that does not\n </small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Status</span>\n <input\n #statusDraftInput\n [value]=\"form.status\"\n (input)=\"patchDraft({ status: statusDraftInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"100\"\n max=\"599\"\n step=\"1\"\n />\n <small>400 and above arrive as a real error</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Latency</span>\n <input\n #latencyDraftInput\n [value]=\"form.latencyMs\"\n (input)=\"patchDraft({ latencyMs: latencyDraftInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"60000\"\n step=\"100\"\n />\n <small>ms before it settles</small>\n </label>\n </div>\n\n @if (hasSchema()) {\n <div class=\"et-query-devtools-seed\">\n <span class=\"et-query-devtools-section\">Seed</span>\n @if (isSchemaReady()) {\n <div class=\"et-query-devtools-seed-pick\">\n <et-select\n [options]=\"routeOptions()\"\n (pickOption)=\"pickRoute($event)\"\n class=\"et-query-devtools-combobox\"\n mirrorPanelWidth=\"false\"\n pickOnly\n >\n <input etSelectSearch placeholder=\"search the routes it declares\u2026\" />\n </et-select>\n <button\n (click)=\"seedFromDraftRoute()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Generate a body from what the description says this path returns\"\n >\n From this path\n </button>\n </div>\n <div class=\"et-query-devtools-seed-pick\">\n <et-select\n [value]=\"pickedSchema()\"\n [options]=\"schemaNameOptions()\"\n (valueChange)=\"pickSchema($event)\"\n class=\"et-query-devtools-combobox\"\n mirrorPanelWidth=\"false\"\n >\n <input etSelectSearch placeholder=\"search its named types\u2026\" />\n </et-select>\n <button\n [disabled]=\"!pickedSchema()\"\n (click)=\"seedFromSchema()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n From this type\n </button>\n </div>\n <select\n #stylePick\n (change)=\"pickSeedStyle(stylePick.value)\"\n class=\"et-query-devtools-select\"\n title=\"How lifelike the generated values are\"\n >\n @for (style of SEED_STYLES; track style.value) {\n <option [value]=\"style.value\" [selected]=\"style.value === seedStyle()\">{{ style.label }}</option>\n }\n </select>\n <small class=\"et-query-devtools-hint et-query-devtools-seed-hint\">{{ seedStyleHint() }}</small>\n } @else if (schemaError(); as error) {\n <span class=\"et-query-devtools-settings-warn\">The API description could not be loaded: {{ error }}</span>\n } @else {\n <span class=\"et-query-devtools-count\">Loading the API description\u2026</span>\n }\n </div>\n }\n\n <label class=\"et-query-devtools-mock-body\">\n <span class=\"et-query-devtools-section\">Body</span>\n <textarea\n #bodyInput\n [value]=\"form.body\"\n (input)=\"patchDraft({ body: bodyInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-mock-textarea\"\n rows=\"8\"\n spellcheck=\"false\"\n ></textarea>\n </label>\n\n @if (draftSeed(); as seed) {\n <p class=\"et-query-devtools-hint\">\n Generated from the API description{{ seed.schemaName ? ' as ' + seed.schemaName : '' }} - a starting point,\n not the contract. Saving opens the designer on it.\n </p>\n @if (seed.notes.length) {\n <ul class=\"et-query-devtools-designer-notes\">\n @for (note of seed.notes; track note) {\n <li>{{ note }}</li>\n }\n </ul>\n }\n }\n\n @if (draftError(); as error) {\n <p class=\"et-query-devtools-settings-warn\">{{ error }}</p>\n }\n\n <div class=\"et-query-devtools-mock-head\">\n <button (click)=\"saveDraft()\" class=\"et-query-devtools-btn et-query-devtools-btn--primary\" type=\"button\">\n Save to the library\n </button>\n <button (click)=\"closeDraft()\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n </div>\n }\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Designed</strong>\n <span class=\"et-query-devtools-count\">{{ rows().length }} in the library</span>\n @if (rows().length) {\n <div class=\"et-query-devtools-spec-export\">\n <select #formatPick (change)=\"pickSpecFormat(formatPick.value)\" class=\"et-query-devtools-select\">\n @for (format of SPEC_FORMATS; track format.value) {\n <option [value]=\"format.value\" [selected]=\"format.value === specFormat()\">{{ format.label }}</option>\n }\n </select>\n <button\n (click)=\"exportLibrary()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Download every designed route as one OpenAPI 3.1 document - the response schemas inferred from the designed bodies, each body as an example\"\n >\n <span aria-hidden=\"true\">\u21E9</span>\n Export OpenAPI\n </button>\n </div>\n }\n </div>\n\n @for (row of rows(); track row.mock.id) {\n <div [class.et-query-devtools-mock-row--armed]=\"row.armed\" class=\"et-query-devtools-mock-row\">\n <div class=\"et-query-devtools-mock-head\">\n <button\n [class.et-query-devtools-btn--tampered]=\"row.armed\"\n [attr.aria-pressed]=\"row.armed\"\n (click)=\"toggleArmed(row)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"{{ row.armed ? 'Stop serving this response' : 'Serve this response instead of the request' }}\"\n >\n {{ row.armed ? 'Armed' : 'Arm' }}\n </button>\n <span class=\"et-query-devtools-method\">{{ row.mock.method }}</span>\n <span class=\"et-query-devtools-route et-query-devtools-mono\">{{ row.mock.pattern }}</span>\n @if (row.mock.query) {\n <span class=\"et-query-devtools-url\">?{{ row.mock.query }}</span>\n }\n <span class=\"et-query-devtools-url\">{{ row.mock.clientName }}</span>\n @if (row.mock.capturedAt) {\n <span class=\"et-query-devtools-chip\">captured</span>\n }\n @if (row.mock.schemaName; as schemaName) {\n <span class=\"et-query-devtools-chip\" title=\"Seeded from the API description\">{{ schemaName }}</span>\n }\n @if (row.isSecure) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"A mock sends nothing, so no token is attached and the auth flow is never exercised\"\n >\n no auth\n </span>\n }\n <span class=\"et-query-devtools-count\">{{ host.formatBytes(row.bytes) }}</span>\n <button (click)=\"editBody(row)\" class=\"et-query-devtools-btn\" type=\"button\">Body</button>\n <button\n (click)=\"copyDefinition(row)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Copy this route as a @ethlete/query definition - the response type inferred from the body, the args contract and the creator call\"\n >\n <span aria-hidden=\"true\">\u29C9</span>\n {{ copiedId() === row.mock.id ? 'Copied' : 'TS' }}\n </button>\n <button\n (click)=\"copyPathItem(row)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Copy this route as an OpenAPI paths entry, ready to paste under an existing description\"\n >\n <span aria-hidden=\"true\">\u29C9</span>\n {{ copiedSpecId() === row.mock.id ? 'Copied' : 'OAS' }}\n </button>\n <button (click)=\"remove(row)\" class=\"et-query-devtools-btn et-query-devtools-btn--danger\" type=\"button\">\n Delete\n </button>\n </div>\n\n <div class=\"et-query-devtools-mock-fields\">\n <label class=\"et-query-devtools-field\">\n <span>Status</span>\n <input\n #statusInput\n [value]=\"row.mock.status\"\n (change)=\"setStatus(row, statusInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"100\"\n max=\"599\"\n step=\"1\"\n />\n <small>400 and above arrive as a real error, body and all</small>\n </label>\n <label class=\"et-query-devtools-field\">\n <span>Latency</span>\n <input\n #latencyInput\n [value]=\"row.mock.latencyMs\"\n (change)=\"setLatency(row, latencyInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"60000\"\n step=\"100\"\n />\n <small>ms before it settles, so the route still has a loading state</small>\n </label>\n </div>\n\n @if (editingId() === row.mock.id) {\n <et-query-devtools-mock-designer\n [body]=\"row.mock.body\"\n [annotations]=\"editingAnnotations()\"\n (save)=\"saveBody(row, $event)\"\n (dismiss)=\"cancelBody()\"\n />\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">\n Nothing designed yet. <strong>New mock</strong> writes one from scratch; capturing below seeds one from\n something that actually happened.\n </p>\n }\n\n @if (specNotes().length) {\n <ul class=\"et-query-devtools-designer-notes\">\n @for (note of specNotes(); track note) {\n <li>{{ note }}</li>\n }\n </ul>\n }\n </div>\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Capture</strong>\n <span class=\"et-query-devtools-count\">live queries holding a response</span>\n </div>\n\n @for (row of captureRows(); track row.entry.id) {\n <div class=\"et-query-devtools-mock-head\">\n <span class=\"et-query-devtools-method\">{{ row.method }}</span>\n <button\n (click)=\"host.selectQuery(row.entry.id)\"\n class=\"et-query-devtools-route et-query-devtools-mono et-query-devtools-row-link\"\n type=\"button\"\n title=\"Open this query\"\n >\n {{ row.pattern }}\n </button>\n <span class=\"et-query-devtools-url\">{{ row.clientName }}</span>\n @if (row.shared > 1) {\n <span class=\"et-query-devtools-count\" title=\"A mock answers the route, so it answers all of them\">\n {{ row.shared }} queries\n </span>\n }\n <span class=\"et-query-devtools-count\">{{ host.formatBytes(row.bytes) }}</span>\n <button (click)=\"capture(row)\" class=\"et-query-devtools-btn\" type=\"button\">\n {{ row.exists ? 'Re-capture' : 'Capture' }}\n </button>\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No live query has a response yet.</p>\n }\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-mocks-toolbar{align-items:flex-start}.et-query-devtools-mock-row{padding-block:.6rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-card>.et-query-devtools-mock-row:first-of-type{border-block-start:0;padding-block-start:0}.et-query-devtools-mock-row--armed{background:color-mix(in srgb,var(--_et-qdt-error) 6%,transparent)}.et-query-devtools-mock-head{display:flex;align-items:center;flex-wrap:wrap;gap:.6rem;min-block-size:var(--_et-qdt-row-h)}.et-query-devtools-mock-fields,.et-query-devtools-seed,.et-query-devtools-mock-body{max-inline-size:96rem}.et-query-devtools-mock-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(38rem,1fr));align-items:start;gap:.4rem 1.6rem;padding-block-start:.4rem}.et-query-devtools-mock-fields .et-query-devtools-field{display:grid;grid-template-columns:7rem minmax(0,1fr);align-items:baseline;row-gap:.2rem;color:var(--_et-qdt-fg)}.et-query-devtools-mock-fields .et-query-devtools-select{justify-self:start;max-inline-size:100%}.et-query-devtools-mock-fields .et-query-devtools-field small{grid-column:2;color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-mock-fields .et-query-devtools-field--wide{grid-column:1 / -1;grid-template-columns:7rem minmax(0,1fr)}.et-query-devtools-spec-export{display:flex;align-items:center;gap:.6rem;margin-inline-start:auto}.et-query-devtools-seed{display:flex;align-items:center;flex-wrap:wrap;gap:.4rem 2.4rem;padding-block-start:.4rem;min-block-size:var(--_et-qdt-row-h)}.et-query-devtools-seed-pick{display:flex;align-items:center;gap:.2rem}.et-query-devtools-seed .et-query-devtools-select{max-inline-size:32rem}.et-query-devtools-combobox{flex:0 1 auto;inline-size:32rem;min-block-size:var(--_et-qdt-control-h);box-sizing:border-box;background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,var(--_et-qdt-bg));color:var(--_et-qdt-fg);border:1px solid transparent;border-radius:var(--_et-qdt-radius);padding:.4rem .6rem;font-size:1.15rem}.et-query-devtools-combobox input{background:none;border:0;padding:0;color:inherit;font:inherit}.et-query-devtools-seed-hint{flex-basis:100%;margin:0}.et-query-devtools-mock-body{display:block;padding-block-start:.4rem}.et-query-devtools-mock-textarea{display:block;inline-size:100%;resize:vertical;font-family:inherit;font-size:1.15rem;line-height:1.5;tab-size:2}}\n"], dependencies: [{ kind: "component", type: QueryDevtoolsMockDesignerComponent, selector: "et-query-devtools-mock-designer", inputs: ["body", "annotations", "notes"], outputs: ["save", "dismiss"] }, { kind: "component", type: SelectComponent, selector: "et-select", inputs: ["loadMoreLabel", "addNewLabel", "createLabel", "clearable", "clearLabel"] }, { kind: "directive", type: SelectSearchDirective, selector: "input[etSelectSearch]", inputs: ["query"], outputs: ["queryChange"], exportAs: ["etSelectSearch"] }], encapsulation: i0.ViewEncapsulation.None }); }
2761
- }
2762
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsMocksTabComponent, decorators: [{
2763
- type: Component,
2764
- args: [{ selector: 'et-query-devtools-mocks-tab', encapsulation: ViewEncapsulation.None, imports: [QueryDevtoolsMockDesignerComponent, SelectComponent, SelectSearchDirective], template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar et-query-devtools-mocks-toolbar\">\n <p class=\"et-query-devtools-hint\">\n A mock replaces the request, so it answers a route that has never run - or does not exist yet. It bypasses the\n interceptor chain by construction, so a mocked secure route never exercises the token flow. The library is kept;\n whether a mock is <strong>armed</strong> is not, so a reload always stops serving them. GraphQL queries are not\n offered: they all POST one route, so matching them needs the document rather than the path.\n </p>\n <div class=\"et-query-devtools-settings-controls\">\n @if (rows().length && armedCount() < rows().length) {\n <button\n (click)=\"ARM_ALL()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Serve every mock in the library - the whole screen off the network at once\"\n >\n Arm all ({{ rows().length }})\n </button>\n }\n @if (armedCount()) {\n <button (click)=\"DISARM_ALL()\" class=\"et-query-devtools-btn\" type=\"button\">\n Disarm all ({{ armedCount() }})\n </button>\n }\n <button (click)=\"openDraft()\" class=\"et-query-devtools-btn et-query-devtools-btn--primary\" type=\"button\">\n <span aria-hidden=\"true\">\uFF0B</span>\n New mock\n </button>\n </div>\n </div>\n\n @if (draft(); as form) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>New mock</strong>\n <span class=\"et-query-devtools-count\">for any route, whether a query has ever called it or not</span>\n </div>\n\n <div class=\"et-query-devtools-mock-fields\">\n <label class=\"et-query-devtools-field\">\n <span>Client</span>\n <select #clientSelect (change)=\"pickClient(clientSelect.value)\" class=\"et-query-devtools-select\">\n @for (name of host.clientNames(); track name) {\n <option [value]=\"name\" [selected]=\"name === form.clientName\">{{ name }}</option>\n }\n </select>\n <small>a mock answers one client, matched by name</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Method</span>\n <select #methodSelect (change)=\"patchDraft({ method: methodSelect.value })\" class=\"et-query-devtools-select\">\n @for (method of METHODS; track method) {\n <option [value]=\"method\" [selected]=\"method === form.method\">{{ method }}</option>\n }\n </select>\n <small>matched exactly</small>\n </label>\n\n <label class=\"et-query-devtools-field et-query-devtools-field--wide\">\n <span>Path</span>\n <input\n #patternInput\n [value]=\"form.pattern\"\n (input)=\"patchDraft({ pattern: patternInput.value })\"\n class=\"et-query-devtools-input\"\n type=\"text\"\n placeholder=\"/posts/:id/comments\"\n />\n <small><code>:name</code> matches any one segment, so the route is mocked rather than one URL</small>\n </label>\n\n <label class=\"et-query-devtools-field et-query-devtools-field--wide\">\n <span>Query</span>\n <input\n #queryInput\n [value]=\"form.query\"\n (input)=\"patchDraft({ query: queryInput.value })\"\n class=\"et-query-devtools-input\"\n type=\"text\"\n placeholder=\"page=2&limit=10\"\n />\n <small>\n parameters the request must carry; anything else it asks for is ignored. Empty answers whatever the query\n string is - and a mock naming parameters wins over one that does not\n </small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Status</span>\n <input\n #statusDraftInput\n [value]=\"form.status\"\n (input)=\"patchDraft({ status: statusDraftInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"100\"\n max=\"599\"\n step=\"1\"\n />\n <small>400 and above arrive as a real error</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Latency</span>\n <input\n #latencyDraftInput\n [value]=\"form.latencyMs\"\n (input)=\"patchDraft({ latencyMs: latencyDraftInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"60000\"\n step=\"100\"\n />\n <small>ms before it settles</small>\n </label>\n </div>\n\n @if (hasSchema()) {\n <div class=\"et-query-devtools-seed\">\n <span class=\"et-query-devtools-section\">Seed</span>\n @if (isSchemaReady()) {\n <div class=\"et-query-devtools-seed-pick\">\n <et-select\n [options]=\"routeOptions()\"\n (pickOption)=\"pickRoute($event)\"\n class=\"et-query-devtools-combobox\"\n mirrorPanelWidth=\"false\"\n pickOnly\n >\n <input etSelectSearch placeholder=\"search the routes it declares\u2026\" />\n </et-select>\n <button\n (click)=\"seedFromDraftRoute()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Generate a body from what the description says this path returns\"\n >\n From this path\n </button>\n </div>\n <div class=\"et-query-devtools-seed-pick\">\n <et-select\n [value]=\"pickedSchema()\"\n [options]=\"schemaNameOptions()\"\n (valueChange)=\"pickSchema($event)\"\n class=\"et-query-devtools-combobox\"\n mirrorPanelWidth=\"false\"\n >\n <input etSelectSearch placeholder=\"search its named types\u2026\" />\n </et-select>\n <button\n [disabled]=\"!pickedSchema()\"\n (click)=\"seedFromSchema()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n From this type\n </button>\n </div>\n <select\n #stylePick\n (change)=\"pickSeedStyle(stylePick.value)\"\n class=\"et-query-devtools-select\"\n title=\"How lifelike the generated values are\"\n >\n @for (style of SEED_STYLES; track style.value) {\n <option [value]=\"style.value\" [selected]=\"style.value === seedStyle()\">{{ style.label }}</option>\n }\n </select>\n <small class=\"et-query-devtools-hint et-query-devtools-seed-hint\">{{ seedStyleHint() }}</small>\n } @else if (schemaError(); as error) {\n <span class=\"et-query-devtools-settings-warn\">The API description could not be loaded: {{ error }}</span>\n } @else {\n <span class=\"et-query-devtools-count\">Loading the API description\u2026</span>\n }\n </div>\n }\n\n <label class=\"et-query-devtools-mock-body\">\n <span class=\"et-query-devtools-section\">Body</span>\n <textarea\n #bodyInput\n [value]=\"form.body\"\n (input)=\"patchDraft({ body: bodyInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-mock-textarea\"\n rows=\"8\"\n spellcheck=\"false\"\n ></textarea>\n </label>\n\n @if (draftSeed(); as seed) {\n <p class=\"et-query-devtools-hint\">\n Generated from the API description{{ seed.schemaName ? ' as ' + seed.schemaName : '' }} - a starting point,\n not the contract. Saving opens the designer on it.\n </p>\n @if (seed.notes.length) {\n <ul class=\"et-query-devtools-designer-notes\">\n @for (note of seed.notes; track note) {\n <li>{{ note }}</li>\n }\n </ul>\n }\n }\n\n @if (draftError(); as error) {\n <p class=\"et-query-devtools-settings-warn\">{{ error }}</p>\n }\n\n <div class=\"et-query-devtools-mock-head\">\n <button (click)=\"saveDraft()\" class=\"et-query-devtools-btn et-query-devtools-btn--primary\" type=\"button\">\n Save to the library\n </button>\n <button (click)=\"closeDraft()\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n </div>\n }\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Designed</strong>\n <span class=\"et-query-devtools-count\">{{ rows().length }} in the library</span>\n @if (rows().length) {\n <div class=\"et-query-devtools-spec-export\">\n <select #formatPick (change)=\"pickSpecFormat(formatPick.value)\" class=\"et-query-devtools-select\">\n @for (format of SPEC_FORMATS; track format.value) {\n <option [value]=\"format.value\" [selected]=\"format.value === specFormat()\">{{ format.label }}</option>\n }\n </select>\n <button\n (click)=\"exportLibrary()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Download every designed route as one OpenAPI 3.1 document - the response schemas inferred from the designed bodies, each body as an example\"\n >\n <span aria-hidden=\"true\">\u21E9</span>\n Export OpenAPI\n </button>\n </div>\n }\n </div>\n\n @for (row of rows(); track row.mock.id) {\n <div [class.et-query-devtools-mock-row--armed]=\"row.armed\" class=\"et-query-devtools-mock-row\">\n <div class=\"et-query-devtools-mock-head\">\n <button\n [class.et-query-devtools-btn--tampered]=\"row.armed\"\n [attr.aria-pressed]=\"row.armed\"\n (click)=\"toggleArmed(row)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"{{ row.armed ? 'Stop serving this response' : 'Serve this response instead of the request' }}\"\n >\n {{ row.armed ? 'Armed' : 'Arm' }}\n </button>\n <span class=\"et-query-devtools-method\">{{ row.mock.method }}</span>\n <span class=\"et-query-devtools-route et-query-devtools-mono\">{{ row.mock.pattern }}</span>\n @if (row.mock.query) {\n <span class=\"et-query-devtools-url\">?{{ row.mock.query }}</span>\n }\n <span class=\"et-query-devtools-url\">{{ row.mock.clientName }}</span>\n @if (row.mock.capturedAt) {\n <span class=\"et-query-devtools-chip\">captured</span>\n }\n @if (row.mock.schemaName; as schemaName) {\n <span class=\"et-query-devtools-chip\" title=\"Seeded from the API description\">{{ schemaName }}</span>\n }\n @if (row.isSecure) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"A mock sends nothing, so no token is attached and the auth flow is never exercised\"\n >\n no auth\n </span>\n }\n <span class=\"et-query-devtools-count\">{{ host.formatBytes(row.bytes) }}</span>\n <button (click)=\"editBody(row)\" class=\"et-query-devtools-btn\" type=\"button\">Body</button>\n <button\n (click)=\"copyDefinition(row)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Copy this route as a @ethlete/query definition - the response type inferred from the body, the args contract and the creator call\"\n >\n <span aria-hidden=\"true\">\u29C9</span>\n {{ copiedId() === row.mock.id ? 'Copied' : 'TS' }}\n </button>\n <button\n (click)=\"copyPathItem(row)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Copy this route as an OpenAPI paths entry, ready to paste under an existing description\"\n >\n <span aria-hidden=\"true\">\u29C9</span>\n {{ copiedSpecId() === row.mock.id ? 'Copied' : 'OAS' }}\n </button>\n <button (click)=\"remove(row)\" class=\"et-query-devtools-btn et-query-devtools-btn--danger\" type=\"button\">\n Delete\n </button>\n </div>\n\n <div class=\"et-query-devtools-mock-fields\">\n <label class=\"et-query-devtools-field\">\n <span>Status</span>\n <input\n #statusInput\n [value]=\"row.mock.status\"\n (change)=\"setStatus(row, statusInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"100\"\n max=\"599\"\n step=\"1\"\n />\n <small>400 and above arrive as a real error, body and all</small>\n </label>\n <label class=\"et-query-devtools-field\">\n <span>Latency</span>\n <input\n #latencyInput\n [value]=\"row.mock.latencyMs\"\n (change)=\"setLatency(row, latencyInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"60000\"\n step=\"100\"\n />\n <small>ms before it settles, so the route still has a loading state</small>\n </label>\n </div>\n\n @if (editingId() === row.mock.id) {\n <et-query-devtools-mock-designer\n [body]=\"row.mock.body\"\n [annotations]=\"editingAnnotations()\"\n (save)=\"saveBody(row, $event)\"\n (dismiss)=\"cancelBody()\"\n />\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">\n Nothing designed yet. <strong>New mock</strong> writes one from scratch; capturing below seeds one from\n something that actually happened.\n </p>\n }\n\n @if (specNotes().length) {\n <ul class=\"et-query-devtools-designer-notes\">\n @for (note of specNotes(); track note) {\n <li>{{ note }}</li>\n }\n </ul>\n }\n </div>\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Capture</strong>\n <span class=\"et-query-devtools-count\">live queries holding a response</span>\n </div>\n\n @for (row of captureRows(); track row.entry.id) {\n <div class=\"et-query-devtools-mock-head\">\n <span class=\"et-query-devtools-method\">{{ row.method }}</span>\n <button\n (click)=\"host.selectQuery(row.entry.id)\"\n class=\"et-query-devtools-route et-query-devtools-mono et-query-devtools-row-link\"\n type=\"button\"\n title=\"Open this query\"\n >\n {{ row.pattern }}\n </button>\n <span class=\"et-query-devtools-url\">{{ row.clientName }}</span>\n @if (row.shared > 1) {\n <span class=\"et-query-devtools-count\" title=\"A mock answers the route, so it answers all of them\">\n {{ row.shared }} queries\n </span>\n }\n <span class=\"et-query-devtools-count\">{{ host.formatBytes(row.bytes) }}</span>\n <button (click)=\"capture(row)\" class=\"et-query-devtools-btn\" type=\"button\">\n {{ row.exists ? 'Re-capture' : 'Capture' }}\n </button>\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No live query has a response yet.</p>\n }\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-mocks-toolbar{align-items:flex-start}.et-query-devtools-mock-row{padding-block:.6rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-card>.et-query-devtools-mock-row:first-of-type{border-block-start:0;padding-block-start:0}.et-query-devtools-mock-row--armed{background:color-mix(in srgb,var(--_et-qdt-error) 6%,transparent)}.et-query-devtools-mock-head{display:flex;align-items:center;flex-wrap:wrap;gap:.6rem;min-block-size:var(--_et-qdt-row-h)}.et-query-devtools-mock-fields,.et-query-devtools-seed,.et-query-devtools-mock-body{max-inline-size:96rem}.et-query-devtools-mock-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(38rem,1fr));align-items:start;gap:.4rem 1.6rem;padding-block-start:.4rem}.et-query-devtools-mock-fields .et-query-devtools-field{display:grid;grid-template-columns:7rem minmax(0,1fr);align-items:baseline;row-gap:.2rem;color:var(--_et-qdt-fg)}.et-query-devtools-mock-fields .et-query-devtools-select{justify-self:start;max-inline-size:100%}.et-query-devtools-mock-fields .et-query-devtools-field small{grid-column:2;color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-mock-fields .et-query-devtools-field--wide{grid-column:1 / -1;grid-template-columns:7rem minmax(0,1fr)}.et-query-devtools-spec-export{display:flex;align-items:center;gap:.6rem;margin-inline-start:auto}.et-query-devtools-seed{display:flex;align-items:center;flex-wrap:wrap;gap:.4rem 2.4rem;padding-block-start:.4rem;min-block-size:var(--_et-qdt-row-h)}.et-query-devtools-seed-pick{display:flex;align-items:center;gap:.2rem}.et-query-devtools-seed .et-query-devtools-select{max-inline-size:32rem}.et-query-devtools-combobox{flex:0 1 auto;inline-size:32rem;min-block-size:var(--_et-qdt-control-h);box-sizing:border-box;background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,var(--_et-qdt-bg));color:var(--_et-qdt-fg);border:1px solid transparent;border-radius:var(--_et-qdt-radius);padding:.4rem .6rem;font-size:1.15rem}.et-query-devtools-combobox input{background:none;border:0;padding:0;color:inherit;font:inherit}.et-query-devtools-seed-hint{flex-basis:100%;margin:0}.et-query-devtools-mock-body{display:block;padding-block-start:.4rem}.et-query-devtools-mock-textarea{display:block;inline-size:100%;resize:vertical;font-family:inherit;font-size:1.15rem;line-height:1.5;tab-size:2}}\n"] }]
2765
- }], ctorParameters: () => [] });
2766
-
2767
- /** The Faults tab: arms latency/failure injection per client - in-memory only, not persisted. */
2768
- class QueryDevtoolsFaultsTabComponent {
2769
- constructor() {
2770
- this.host = injectQueryDevtoolsHost();
2771
- this.FAULT_STATUSES = QUERY_DEVTOOLS_FAULT_STATUSES;
2772
- this.CLEAR_FAULTS = clearQueryDevtoolsFaults;
2773
- /** The ceiling each numeric fault field is clamped to. */
2774
- this.FAULT_LIMITS = {
2775
- latencyMs: 60_000,
2776
- failNext: 99,
2777
- failRate: 100,
2778
- };
2779
- }
2780
- /**
2781
- * Arms one numeric field of a client's fault from an input's raw value. Clamped here rather than left
2782
- * to the input's `min`/`max`, which a typed-in (or pasted) value ignores.
2783
- */
2784
- armFaultValue(options) {
2785
- const { clientName, field, value } = options;
2786
- const parsed = clamp(Math.trunc(Number(value) || 0), 0, this.FAULT_LIMITS[field]);
2787
- setQueryDevtoolsFault({ clientName, patch: { [field]: parsed } });
2788
- }
2789
- armFaultStatus(options) {
2790
- setQueryDevtoolsFault({ clientName: options.clientName, patch: { status: Number(options.value) } });
2791
- }
2792
- /** Whether the default retry policy retries the status a client is armed to fail with. */
2793
- isFaultStatusRetryable(status) {
2794
- return QUERY_DEVTOOLS_FAULT_STATUSES.find((entry) => entry.status === status)?.retryable ?? false;
2795
- }
2796
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsFaultsTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2797
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsFaultsTabComponent, isStandalone: true, selector: "et-query-devtools-faults-tab", ngImport: i0, template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar\">\n <p class=\"et-query-devtools-hint\">\n Faults are injected into the request pipeline, so retries, error handling features and the cache see them exactly\n as they would see a real one. Unlike the rest of the panel, what you arm here is not persisted - a page reload\n disarms every client.\n </p>\n </div>\n\n @for (client of host.faultClients(); track client.name) {\n @let fault = client.fault;\n <div [class.et-query-devtools-card--armed]=\"client.armed\" class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong class=\"et-query-devtools-mono\">{{ client.name }}</strong>\n <span class=\"et-query-devtools-url\">{{ client.baseUrl }}</span>\n @if (client.armed) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--error\">armed</span>\n <button (click)=\"CLEAR_FAULTS(client.name)\" class=\"et-query-devtools-btn\" type=\"button\">Disarm</button>\n }\n </div>\n\n <div class=\"et-query-devtools-faults\">\n <label class=\"et-query-devtools-field\">\n <span>Latency</span>\n <input\n #latencyInput\n [value]=\"fault.latencyMs\"\n (change)=\"armFaultValue({ clientName: client.name, field: 'latencyMs', value: latencyInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"60000\"\n step=\"100\"\n />\n <small>ms before every attempt starts</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Fail next</span>\n <input\n #failNextInput\n [value]=\"fault.failNext\"\n (change)=\"armFaultValue({ clientName: client.name, field: 'failNext', value: failNextInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"99\"\n step=\"1\"\n />\n <small>attempts, counted down as they are spent</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Fail rate</span>\n <input\n #failRateInput\n [value]=\"fault.failRate\"\n (change)=\"armFaultValue({ clientName: client.name, field: 'failRate', value: failRateInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"100\"\n step=\"5\"\n />\n <small>% of attempts, rolled per attempt</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Status</span>\n <select\n #statusSelect\n (change)=\"armFaultStatus({ clientName: client.name, value: statusSelect.value })\"\n class=\"et-query-devtools-select\"\n >\n @for (option of FAULT_STATUSES; track option.status) {\n <!-- `selected` on the option, not `value` on the select: the select's value is\n applied before its options exist, so it would fall back to the first one. -->\n <option [value]=\"option.status\" [selected]=\"option.status === fault.status\">\n {{ option.status }} \u00B7 {{ option.label }}\n </option>\n }\n </select>\n <small>\n @if (isFaultStatusRetryable(fault.status)) {\n the default retry policy retries this\n } @else {\n the default retry policy gives up on this\n }\n </small>\n </label>\n </div>\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No query clients registered yet.</p>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-faults{display:grid;grid-template-columns:repeat(auto-fit,minmax(20rem,1fr));gap:.8rem 1.6rem}.et-query-devtools-faults .et-query-devtools-field{display:grid;grid-template-columns:8rem minmax(0,1fr);align-items:baseline;row-gap:.2rem;color:var(--_et-qdt-fg)}.et-query-devtools-faults .et-query-devtools-field small{grid-column:2;color:var(--_et-qdt-subtle);font-size:1.1rem}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
2798
- }
2799
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsFaultsTabComponent, decorators: [{
2800
- type: Component,
2801
- args: [{ selector: 'et-query-devtools-faults-tab', encapsulation: ViewEncapsulation.None, template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar\">\n <p class=\"et-query-devtools-hint\">\n Faults are injected into the request pipeline, so retries, error handling features and the cache see them exactly\n as they would see a real one. Unlike the rest of the panel, what you arm here is not persisted - a page reload\n disarms every client.\n </p>\n </div>\n\n @for (client of host.faultClients(); track client.name) {\n @let fault = client.fault;\n <div [class.et-query-devtools-card--armed]=\"client.armed\" class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong class=\"et-query-devtools-mono\">{{ client.name }}</strong>\n <span class=\"et-query-devtools-url\">{{ client.baseUrl }}</span>\n @if (client.armed) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--error\">armed</span>\n <button (click)=\"CLEAR_FAULTS(client.name)\" class=\"et-query-devtools-btn\" type=\"button\">Disarm</button>\n }\n </div>\n\n <div class=\"et-query-devtools-faults\">\n <label class=\"et-query-devtools-field\">\n <span>Latency</span>\n <input\n #latencyInput\n [value]=\"fault.latencyMs\"\n (change)=\"armFaultValue({ clientName: client.name, field: 'latencyMs', value: latencyInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"60000\"\n step=\"100\"\n />\n <small>ms before every attempt starts</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Fail next</span>\n <input\n #failNextInput\n [value]=\"fault.failNext\"\n (change)=\"armFaultValue({ clientName: client.name, field: 'failNext', value: failNextInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"99\"\n step=\"1\"\n />\n <small>attempts, counted down as they are spent</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Fail rate</span>\n <input\n #failRateInput\n [value]=\"fault.failRate\"\n (change)=\"armFaultValue({ clientName: client.name, field: 'failRate', value: failRateInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"100\"\n step=\"5\"\n />\n <small>% of attempts, rolled per attempt</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Status</span>\n <select\n #statusSelect\n (change)=\"armFaultStatus({ clientName: client.name, value: statusSelect.value })\"\n class=\"et-query-devtools-select\"\n >\n @for (option of FAULT_STATUSES; track option.status) {\n <!-- `selected` on the option, not `value` on the select: the select's value is\n applied before its options exist, so it would fall back to the first one. -->\n <option [value]=\"option.status\" [selected]=\"option.status === fault.status\">\n {{ option.status }} \u00B7 {{ option.label }}\n </option>\n }\n </select>\n <small>\n @if (isFaultStatusRetryable(fault.status)) {\n the default retry policy retries this\n } @else {\n the default retry policy gives up on this\n }\n </small>\n </label>\n </div>\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No query clients registered yet.</p>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-faults{display:grid;grid-template-columns:repeat(auto-fit,minmax(20rem,1fr));gap:.8rem 1.6rem}.et-query-devtools-faults .et-query-devtools-field{display:grid;grid-template-columns:8rem minmax(0,1fr);align-items:baseline;row-gap:.2rem;color:var(--_et-qdt-fg)}.et-query-devtools-faults .et-query-devtools-field small{grid-column:2;color:var(--_et-qdt-subtle);font-size:1.1rem}}\n"] }]
2802
- }] });
2803
-
2804
- /**
2805
- * Copies the whole set of response overrides armed on one query to the clipboard, and pastes one back -
2806
- * onto this query or any other. The payload is the same JSON the override store persists, so a set can
2807
- * also go into a ticket and come back from one.
2808
- *
2809
- * @internal
2810
- */
2811
- class QueryDevtoolsOverrideSetMenuComponent {
2812
- constructor() {
2813
- this.errorColorTheme = injectErrorTheme();
2814
- this.overrides = input.required(/* @ts-ignore */
2815
- ...(ngDevMode ? [{ debugName: "overrides" }] : /* istanbul ignore next */ []));
2816
- /** The response a pasted set is measured against, to report the ops that land on nothing. */
2817
- this.response = input(/* @ts-ignore */
2818
- ...(ngDevMode ? [undefined, { debugName: "response" }] : /* istanbul ignore next */ []));
2819
- /** Where the set came from, carried into the payload for whoever reads it next. */
2820
- this.source = input(/* @ts-ignore */
2821
- ...(ngDevMode ? [undefined, { debugName: "source" }] : /* istanbul ignore next */ []));
2822
- this.pasteInput = viewChild('pasteBox', /* @ts-ignore */
2823
- ...(ngDevMode ? [{ debugName: "pasteInput" }] : /* istanbul ignore next */ []));
2824
- this.armedCount = computed(() => this.overrides().list().length, /* @ts-ignore */
2825
- ...(ngDevMode ? [{ debugName: "armedCount" }] : /* istanbul ignore next */ []));
2826
- this.pasteMode = signal(false, /* @ts-ignore */
2827
- ...(ngDevMode ? [{ debugName: "pasteMode" }] : /* istanbul ignore next */ []));
2828
- this.error = signal(null, /* @ts-ignore */
2829
- ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
2830
- this.note = signal(null, /* @ts-ignore */
2831
- ...(ngDevMode ? [{ debugName: "note" }] : /* istanbul ignore next */ []));
2832
- this.copied = signal(false, /* @ts-ignore */
2833
- ...(ngDevMode ? [{ debugName: "copied" }] : /* istanbul ignore next */ []));
2834
- injectStyleManager().mount(QueryDevtoolsOverrideMenuStylesComponent);
2835
- effect(() => this.pasteInput()?.nativeElement.focus());
2836
- }
2837
- copySet() {
2838
- const text = serializeQueryDevtoolsOverrideTransfer(this.overrides().list(), this.source());
2839
- navigator.clipboard?.writeText(text).then(() => this.copied.set(true), () => this.error.set('The clipboard write was blocked'));
2840
- }
2841
- pasteSet() {
2842
- this.reset();
2843
- readQueryDevtoolsClipboard().then((read) => {
2844
- if (read.ok) {
2845
- this.armText(read.text);
2846
- return;
2847
- }
2848
- this.pasteMode.set(true);
2849
- this.note.set(read.reason === 'unavailable'
2850
- ? 'This browser will not hand over the clipboard - press ⌘V / Ctrl+V here instead.'
2851
- : 'The clipboard read was blocked - press ⌘V / Ctrl+V here instead.');
2852
- });
2853
- }
2854
- pasteIntoBox(event) {
2855
- const text = textFromQueryDevtoolsPaste(event);
2856
- if (!text)
2857
- return;
2858
- event.preventDefault();
2859
- this.armText(text);
2860
- }
2861
- commitPasteBox() {
2862
- const raw = this.pasteInput()?.nativeElement.value;
2863
- if (raw !== undefined)
2864
- this.armText(raw);
2865
- }
2866
- resetOnClose(open) {
2867
- if (!open) {
2868
- this.pasteMode.set(false);
2869
- this.copied.set(false);
2870
- this.reset();
2871
- }
2872
- }
2873
- reset() {
2874
- this.error.set(null);
2875
- this.note.set(null);
2876
- }
2877
- armText(text) {
2878
- const parsed = parseQueryDevtoolsOverrideTransfer(text);
2879
- if (!parsed.ok) {
2880
- this.error.set(parsed.reason);
2881
- return;
2882
- }
2883
- armQueryDevtoolsOverrideTransfer(this.overrides(), parsed.ops);
2884
- const unresolved = countUnresolvedQueryDevtoolsOverrides(parsed.ops, this.response());
2885
- const parts = [`Armed ${parsed.ops.length} ${parsed.ops.length === 1 ? 'op' : 'ops'}`];
2886
- if (unresolved)
2887
- parts.push(`${unresolved} of them do not resolve against this response`);
2888
- if (parsed.skipped)
2889
- parts.push(`${parsed.skipped} unknown to this build were dropped`);
2890
- this.pasteMode.set(false);
2891
- this.error.set(null);
2892
- this.note.set(`${parts.join('; ')}.`);
2893
- }
2894
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsOverrideSetMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2895
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsOverrideSetMenuComponent, isStandalone: true, selector: "et-query-devtools-override-set-menu", inputs: { overrides: { classPropertyName: "overrides", publicName: "overrides", isSignal: true, isRequired: true, transformFunction: null }, response: { classPropertyName: "response", publicName: "response", isSignal: true, isRequired: false, transformFunction: null }, source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "pasteInput", first: true, predicate: ["pasteBox"], descendants: true, isSignal: true }], ngImport: i0, template: "<div (openChange)=\"resetOnClose($event)\" class=\"et-query-devtools-override-set-menu\" etMenu>\n <button\n et-menu-item\n title=\"Copy every response override armed on this query, or paste a set copied off another one.\"\n type=\"button\"\n etMenuTrigger\n >\n Override set\n <et-menu-item-shortcut>\u203A</et-menu-item-shortcut>\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <ng-container ngProjectAs=\"input[etMenuSearch]\">\n @if (pasteMode()) {\n <input\n #pasteBox\n [error]=\"error()\"\n (paste)=\"pasteIntoBox($event)\"\n (keydown.enter)=\"commitPasteBox()\"\n etMenuSearch\n type=\"text\"\n placeholder=\"Paste a copied set here\"\n />\n }\n </ng-container>\n\n @if (armedCount()) {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"copySet()\" et-menu-item type=\"button\">\n {{ copied() ? '\u2713 Copied' : 'Copy ' + armedCount() + (armedCount() === 1 ? ' armed op' : ' armed ops') }}\n </button>\n }\n\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"pasteSet()\" et-menu-item type=\"button\">\n Paste a set{{ armedCount() ? ' on top' : '' }}\n </button>\n\n @if (error(); as err) {\n <p [etProvideColor]=\"errorColorTheme\" class=\"et-query-devtools-override-error\" role=\"alert\">{{ err }}</p>\n } @else if (note(); as text) {\n <p class=\"et-query-devtools-override-note\" role=\"status\">{{ text }}</p>\n }\n </et-menu>\n </ng-template>\n</div>\n", dependencies: [{ kind: "directive", type: MenuDirective, selector: "[etMenu]", inputs: ["placement", "fallbackPlacements", "offset", "viewportPadding", "arrow", "arrowPadding", "autoFocus", "hoverOpen", "loop", "hoverOpenDelay", "hoverCloseDelay", "disabled", "open"], outputs: ["openChange"], exportAs: ["etMenu"] }, { kind: "directive", type: MenuTriggerDirective, selector: "[etMenuTrigger]", exportAs: ["etMenuTrigger"] }, { kind: "directive", type: MenuSurfaceDirective, selector: "ng-template[etMenuSurface]", exportAs: ["etMenuSurface"] }, { kind: "directive", type: MenuSearchDirective, selector: "input[etMenuSearch]", inputs: ["query", "loading", "error"], outputs: ["queryChange"], exportAs: ["etMenuSearch"] }, { kind: "component", type: MenuComponent, selector: "et-menu" }, { kind: "component", type: MenuItemComponent, selector: "button[et-menu-item], a[et-menu-item]", inputs: ["variant"] }, { kind: "component", type: MenuItemShortcutComponent, selector: "et-menu-item-shortcut" }, { kind: "directive", type: ProvideColorDirective, selector: "[etProvideColor]", inputs: ["etProvideColor"] }], encapsulation: i0.ViewEncapsulation.None }); }
2896
- }
2897
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsOverrideSetMenuComponent, decorators: [{
2898
- type: Component,
2899
- args: [{ selector: 'et-query-devtools-override-set-menu', encapsulation: ViewEncapsulation.None, imports: [
2900
- MenuDirective,
2901
- MenuTriggerDirective,
2902
- MenuSurfaceDirective,
2903
- MenuSearchDirective,
2904
- MenuComponent,
2905
- MenuItemComponent,
2906
- MenuItemShortcutComponent,
2907
- ProvideColorDirective,
2908
- ], template: "<div (openChange)=\"resetOnClose($event)\" class=\"et-query-devtools-override-set-menu\" etMenu>\n <button\n et-menu-item\n title=\"Copy every response override armed on this query, or paste a set copied off another one.\"\n type=\"button\"\n etMenuTrigger\n >\n Override set\n <et-menu-item-shortcut>\u203A</et-menu-item-shortcut>\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <ng-container ngProjectAs=\"input[etMenuSearch]\">\n @if (pasteMode()) {\n <input\n #pasteBox\n [error]=\"error()\"\n (paste)=\"pasteIntoBox($event)\"\n (keydown.enter)=\"commitPasteBox()\"\n etMenuSearch\n type=\"text\"\n placeholder=\"Paste a copied set here\"\n />\n }\n </ng-container>\n\n @if (armedCount()) {\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"copySet()\" et-menu-item type=\"button\">\n {{ copied() ? '\u2713 Copied' : 'Copy ' + armedCount() + (armedCount() === 1 ? ' armed op' : ' armed ops') }}\n </button>\n }\n\n <!-- eslint-disable-next-line ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <button [closeOnActivate]=\"false\" (activate)=\"pasteSet()\" et-menu-item type=\"button\">\n Paste a set{{ armedCount() ? ' on top' : '' }}\n </button>\n\n @if (error(); as err) {\n <p [etProvideColor]=\"errorColorTheme\" class=\"et-query-devtools-override-error\" role=\"alert\">{{ err }}</p>\n } @else if (note(); as text) {\n <p class=\"et-query-devtools-override-note\" role=\"status\">{{ text }}</p>\n }\n </et-menu>\n </ng-template>\n</div>\n" }]
2909
- }], ctorParameters: () => [], propDecorators: { overrides: [{ type: i0.Input, args: [{ isSignal: true, alias: "overrides", required: true }] }], response: [{ type: i0.Input, args: [{ isSignal: true, alias: "response", required: false }] }], source: [{ type: i0.Input, args: [{ isSignal: true, alias: "source", required: false }] }], pasteInput: [{ type: i0.ViewChild, args: ['pasteBox', { isSignal: true }] }] } });
2910
-
2911
- /**
2912
- * Drops a leading path prefix from a route's segments, splitting whichever segment the prefix ends
2913
- * inside. `null` when the route does not start with the prefix - a caller then shows the whole route
2914
- * rather than a tail that would start mid-token and read as a different endpoint.
2915
- */
2916
- const trimRouteSegments = (segments, prefix) => {
2917
- if (!prefix)
2918
- return null;
2919
- if (!segments
2920
- .map((segment) => segment.text)
2921
- .join('')
2922
- .startsWith(prefix))
2923
- return null;
2924
- let remaining = prefix.length;
2925
- const trimmed = [];
2926
- for (const segment of segments) {
2927
- if (remaining <= 0) {
2928
- trimmed.push(segment);
2929
- continue;
2930
- }
2931
- if (segment.text.length <= remaining) {
2932
- remaining -= segment.text.length;
2933
- continue;
2934
- }
2935
- trimmed.push({ ...segment, text: segment.text.slice(remaining) });
2936
- remaining = 0;
2937
- }
2938
- return trimmed.length ? trimmed : null;
2939
- };
2940
- /**
2941
- * A route with its path params and query string picked out from the static path. The segments render
2942
- * element-only: a text node between them would render as a space inside the route.
2943
- */
2944
- class QueryDevtoolsRouteComponent {
2945
- constructor() {
2946
- this.segments = input.required(/* @ts-ignore */
2947
- ...(ngDevMode ? [{ debugName: "segments" }] : /* istanbul ignore next */ []));
2948
- this.fallback = input('—', /* @ts-ignore */
2949
- ...(ngDevMode ? [{ debugName: "fallback" }] : /* istanbul ignore next */ []));
2950
- /**
2951
- * A leading path the row's surroundings already state - the tree's folder heading. Rendering it again
2952
- * on every child is noise, so it is replaced by a `…` that carries the full route in its `title`.
2953
- */
2954
- this.trimPrefix = input('', /* @ts-ignore */
2955
- ...(ngDevMode ? [{ debugName: "trimPrefix" }] : /* istanbul ignore next */ []));
2956
- this.trimmed = computed(() => trimRouteSegments(this.segments(), this.trimPrefix()), /* @ts-ignore */
2957
- ...(ngDevMode ? [{ debugName: "trimmed" }] : /* istanbul ignore next */ []));
2958
- this.trimTitle = computed(() => `${this.trimPrefix()}… — ${this.segments()
2959
- .map((s) => s.text)
2960
- .join('')}`, /* @ts-ignore */
2961
- ...(ngDevMode ? [{ debugName: "trimTitle" }] : /* istanbul ignore next */ []));
3055
+ this.draftError.set(null);
3056
+ this.patchDraft({ body: JSON.stringify(seed.body, null, 2) });
2962
3057
  }
2963
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsRouteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2964
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsRouteComponent, isStandalone: true, selector: "et-query-devtools-route", inputs: { segments: { classPropertyName: "segments", publicName: "segments", isSignal: true, isRequired: true, transformFunction: null }, fallback: { classPropertyName: "fallback", publicName: "fallback", isSignal: true, isRequired: false, transformFunction: null }, trimPrefix: { classPropertyName: "trimPrefix", publicName: "trimPrefix", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
2965
- @if (trimmed(); as trimmed) {
2966
- <span [attr.title]="trimTitle()" class="et-query-devtools-route-trimmed">…</span>
2967
- @for (segment of trimmed; track $index) {
2968
- <span [class]="'et-query-devtools-route-' + segment.kind" [attr.title]="segment.name">{{ segment.text }}</span>
2969
- }
2970
- } @else {
2971
- @for (segment of segments(); track $index) {
2972
- <span [class]="'et-query-devtools-route-' + segment.kind" [attr.title]="segment.name">{{ segment.text }}</span>
2973
- } @empty {
2974
- {{ fallback() }}
2975
- }
3058
+ countOf(value, fallback) {
3059
+ const count = Math.trunc(Number(value));
3060
+ return Number.isFinite(count) ? count : fallback;
2976
3061
  }
2977
- `, isInline: true, styles: ["@layer components{.et-query-devtools-route-param{color:var(--_et-qdt-accent)}.et-query-devtools-route-query{color:var(--_et-qdt-muted)}.et-query-devtools-route-trimmed{color:var(--_et-qdt-subtle);cursor:help}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
3062
+ idOf(entry) {
3063
+ return queryDevtoolsMockId({
3064
+ clientName: entry.meta.clientName ?? '',
3065
+ method: entry.meta.method ?? 'GET',
3066
+ pattern: entry.meta.route ?? '',
3067
+ });
3068
+ }
3069
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsMocksTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3070
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsMocksTabComponent, isStandalone: true, selector: "et-query-devtools-mocks-tab", ngImport: i0, template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar et-query-devtools-mocks-toolbar\">\n <p class=\"et-query-devtools-hint\">\n A mock replaces the request, so it answers a route that has never run - or does not exist yet. It bypasses the\n interceptor chain by construction, so a mocked secure route never exercises the token flow. The library is kept;\n whether a mock is <strong>armed</strong> is not, so a reload always stops serving them. GraphQL queries are not\n offered: they all POST one route, so matching them needs the document rather than the path.\n </p>\n <div class=\"et-query-devtools-settings-controls\">\n @if (rows().length && armedCount() < rows().length) {\n <button\n (click)=\"ARM_ALL()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Serve every mock in the library - the whole screen off the network at once\"\n >\n Arm all ({{ rows().length }})\n </button>\n }\n @if (armedCount()) {\n <button (click)=\"DISARM_ALL()\" class=\"et-query-devtools-btn\" type=\"button\">\n Disarm all ({{ armedCount() }})\n </button>\n }\n <button (click)=\"openDraft()\" class=\"et-query-devtools-btn et-query-devtools-btn--primary\" type=\"button\">\n <span aria-hidden=\"true\">\uFF0B</span>\n New mock\n </button>\n </div>\n </div>\n\n @if (draft(); as form) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>New mock</strong>\n <span class=\"et-query-devtools-count\">for any route, whether a query has ever called it or not</span>\n </div>\n\n <div class=\"et-query-devtools-mock-fields\">\n <label class=\"et-query-devtools-field\">\n <span>Client</span>\n <select #clientSelect (change)=\"pickClient(clientSelect.value)\" class=\"et-query-devtools-select\">\n @for (name of host.clientNames(); track name) {\n <option [value]=\"name\" [selected]=\"name === form.clientName\">{{ name }}</option>\n }\n </select>\n <small>a mock answers one client, matched by name</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Method</span>\n <select #methodSelect (change)=\"patchDraft({ method: methodSelect.value })\" class=\"et-query-devtools-select\">\n @for (method of METHODS; track method) {\n <option [value]=\"method\" [selected]=\"method === form.method\">{{ method }}</option>\n }\n </select>\n <small>matched exactly</small>\n </label>\n\n <label class=\"et-query-devtools-field et-query-devtools-field--wide\">\n <span>Path</span>\n <input\n #patternInput\n [value]=\"form.pattern\"\n (input)=\"patchDraft({ pattern: patternInput.value })\"\n class=\"et-query-devtools-input\"\n type=\"text\"\n placeholder=\"/posts/:id/comments\"\n />\n <small><code>:name</code> matches any one segment, so the route is mocked rather than one URL</small>\n </label>\n\n <label class=\"et-query-devtools-field et-query-devtools-field--wide\">\n <span>Query</span>\n <input\n #queryInput\n [value]=\"form.query\"\n (input)=\"patchDraft({ query: queryInput.value })\"\n class=\"et-query-devtools-input\"\n type=\"text\"\n placeholder=\"page=2&limit=10\"\n />\n <small>\n parameters the request must carry; anything else it asks for is ignored. Empty answers whatever the query\n string is - and a mock naming parameters wins over one that does not\n </small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Status</span>\n <input\n #statusDraftInput\n [value]=\"form.status\"\n (input)=\"patchDraft({ status: statusDraftInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"100\"\n max=\"599\"\n step=\"1\"\n />\n <small>400 and above arrive as a real error</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Latency</span>\n <input\n #latencyDraftInput\n [value]=\"form.latencyMs\"\n (input)=\"patchDraft({ latencyMs: latencyDraftInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"60000\"\n step=\"100\"\n />\n <small>ms before it settles</small>\n </label>\n </div>\n\n @if (hasSchema()) {\n <div class=\"et-query-devtools-seed\">\n <span class=\"et-query-devtools-section\">Seed</span>\n @if (isSchemaReady()) {\n <div class=\"et-query-devtools-seed-pick\">\n <et-select\n [options]=\"routeOptions()\"\n (pickOption)=\"pickRoute($event)\"\n class=\"et-query-devtools-combobox\"\n mirrorPanelWidth=\"false\"\n pickOnly\n >\n <input etSelectSearch placeholder=\"search the routes it declares\u2026\" />\n </et-select>\n <button\n (click)=\"seedFromDraftRoute()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Generate a body from what the description says this path returns\"\n >\n From this path\n </button>\n </div>\n <div class=\"et-query-devtools-seed-pick\">\n <et-select\n [value]=\"pickedSchema()\"\n [options]=\"schemaNameOptions()\"\n (valueChange)=\"pickSchema($event)\"\n class=\"et-query-devtools-combobox\"\n mirrorPanelWidth=\"false\"\n >\n <input etSelectSearch placeholder=\"search its named types\u2026\" />\n </et-select>\n <button\n [disabled]=\"!pickedSchema()\"\n (click)=\"seedFromSchema()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n From this type\n </button>\n </div>\n <select\n #stylePick\n (change)=\"pickSeedStyle(stylePick.value)\"\n class=\"et-query-devtools-select\"\n title=\"How lifelike the generated values are\"\n >\n @for (style of SEED_STYLES; track style.value) {\n <option [value]=\"style.value\" [selected]=\"style.value === seedStyle()\">{{ style.label }}</option>\n }\n </select>\n <small class=\"et-query-devtools-hint et-query-devtools-seed-hint\">{{ seedStyleHint() }}</small>\n } @else if (schemaError(); as error) {\n <span class=\"et-query-devtools-settings-warn\">The API description could not be loaded: {{ error }}</span>\n } @else {\n <span class=\"et-query-devtools-count\">Loading the API description\u2026</span>\n }\n </div>\n }\n\n <label class=\"et-query-devtools-mock-body\">\n <span class=\"et-query-devtools-section\">Body</span>\n <textarea\n #bodyInput\n [value]=\"form.body\"\n (input)=\"patchDraft({ body: bodyInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-mock-textarea\"\n rows=\"8\"\n spellcheck=\"false\"\n ></textarea>\n </label>\n\n @if (draftSeed(); as seed) {\n <p class=\"et-query-devtools-hint\">\n Generated from the API description{{ seed.schemaName ? ' as ' + seed.schemaName : '' }} - a starting point,\n not the contract. Saving opens the designer on it.\n </p>\n @if (seed.notes.length) {\n <ul class=\"et-query-devtools-designer-notes\">\n @for (note of seed.notes; track note) {\n <li>{{ note }}</li>\n }\n </ul>\n }\n }\n\n @if (draftError(); as error) {\n <p class=\"et-query-devtools-settings-warn\">{{ error }}</p>\n }\n\n <div class=\"et-query-devtools-mock-head\">\n <button (click)=\"saveDraft()\" class=\"et-query-devtools-btn et-query-devtools-btn--primary\" type=\"button\">\n Save to the library\n </button>\n <button (click)=\"closeDraft()\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n </div>\n }\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Designed</strong>\n <span class=\"et-query-devtools-count\">{{ rows().length }} in the library</span>\n @if (rows().length) {\n <div class=\"et-query-devtools-spec-export\">\n <select #formatPick (change)=\"pickSpecFormat(formatPick.value)\" class=\"et-query-devtools-select\">\n @for (format of SPEC_FORMATS; track format.value) {\n <option [value]=\"format.value\" [selected]=\"format.value === specFormat()\">{{ format.label }}</option>\n }\n </select>\n <button\n (click)=\"exportLibrary()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Download every designed route as one OpenAPI 3.1 document - the response schemas inferred from the designed bodies, each body as an example\"\n >\n <span aria-hidden=\"true\">\u21E9</span>\n Export OpenAPI\n </button>\n </div>\n }\n </div>\n\n @for (row of rows(); track row.mock.id) {\n <div [class.et-query-devtools-mock-row--armed]=\"row.armed\" class=\"et-query-devtools-mock-row\">\n <div class=\"et-query-devtools-mock-head\">\n <button\n [class.et-query-devtools-btn--tampered]=\"row.armed\"\n [attr.aria-pressed]=\"row.armed\"\n (click)=\"toggleArmed(row)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"{{ row.armed ? 'Stop serving this response' : 'Serve this response instead of the request' }}\"\n >\n {{ row.armed ? 'Armed' : 'Arm' }}\n </button>\n <span class=\"et-query-devtools-method\">{{ row.mock.method }}</span>\n <span class=\"et-query-devtools-route et-query-devtools-mono\">{{ row.mock.pattern }}</span>\n @if (row.mock.query) {\n <span class=\"et-query-devtools-url\">?{{ row.mock.query }}</span>\n }\n <span class=\"et-query-devtools-url\">{{ row.mock.clientName }}</span>\n @if (row.mock.capturedAt) {\n <span class=\"et-query-devtools-chip\">captured</span>\n }\n @if (row.mock.schemaName; as schemaName) {\n <span class=\"et-query-devtools-chip\" title=\"Seeded from the API description\">{{ schemaName }}</span>\n }\n @if (row.isSecure) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"A mock sends nothing, so no token is attached and the auth flow is never exercised\"\n >\n no auth\n </span>\n }\n <span class=\"et-query-devtools-count\">{{ host.formatBytes(row.bytes) }}</span>\n <button (click)=\"editBody(row)\" class=\"et-query-devtools-btn\" type=\"button\">Body</button>\n <button\n (click)=\"copyDefinition(row)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Copy this route as a @ethlete/query definition - the response type inferred from the body, the args contract and the creator call\"\n >\n <span aria-hidden=\"true\">\u29C9</span>\n {{ copiedId() === row.mock.id ? 'Copied' : 'TS' }}\n </button>\n <button\n (click)=\"copyPathItem(row)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Copy this route as an OpenAPI paths entry, ready to paste under an existing description\"\n >\n <span aria-hidden=\"true\">\u29C9</span>\n {{ copiedSpecId() === row.mock.id ? 'Copied' : 'OAS' }}\n </button>\n <button (click)=\"remove(row)\" class=\"et-query-devtools-btn et-query-devtools-btn--danger\" type=\"button\">\n Delete\n </button>\n </div>\n\n <div class=\"et-query-devtools-mock-fields\">\n <label class=\"et-query-devtools-field\">\n <span>Status</span>\n <input\n #statusInput\n [value]=\"row.mock.status\"\n (change)=\"setStatus(row, statusInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"100\"\n max=\"599\"\n step=\"1\"\n />\n <small>400 and above arrive as a real error, body and all</small>\n </label>\n <label class=\"et-query-devtools-field\">\n <span>Latency</span>\n <input\n #latencyInput\n [value]=\"row.mock.latencyMs\"\n (change)=\"setLatency(row, latencyInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"60000\"\n step=\"100\"\n />\n <small>ms before it settles, so the route still has a loading state</small>\n </label>\n </div>\n\n @if (editingId() === row.mock.id) {\n <et-query-devtools-mock-designer\n [body]=\"row.mock.body\"\n [annotations]=\"editingAnnotations()\"\n (save)=\"saveBody(row, $event)\"\n (dismiss)=\"cancelBody()\"\n />\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">\n Nothing designed yet. <strong>New mock</strong> writes one from scratch; capturing below seeds one from\n something that actually happened.\n </p>\n }\n\n @if (specNotes().length) {\n <ul class=\"et-query-devtools-designer-notes\">\n @for (note of specNotes(); track note) {\n <li>{{ note }}</li>\n }\n </ul>\n }\n </div>\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Capture</strong>\n <span class=\"et-query-devtools-count\">live queries holding a response</span>\n </div>\n\n @for (row of captureRows(); track row.entry.id) {\n <div class=\"et-query-devtools-mock-head\">\n <span class=\"et-query-devtools-method\">{{ row.method }}</span>\n <button\n (click)=\"host.selectQuery(row.entry.id)\"\n class=\"et-query-devtools-route et-query-devtools-mono et-query-devtools-row-link\"\n type=\"button\"\n title=\"Open this query\"\n >\n {{ row.pattern }}\n </button>\n <span class=\"et-query-devtools-url\">{{ row.clientName }}</span>\n @if (row.shared > 1) {\n <span class=\"et-query-devtools-count\" title=\"A mock answers the route, so it answers all of them\">\n {{ row.shared }} queries\n </span>\n }\n <span class=\"et-query-devtools-count\">{{ host.formatBytes(row.bytes) }}</span>\n <button (click)=\"capture(row)\" class=\"et-query-devtools-btn\" type=\"button\">\n {{ row.exists ? 'Re-capture' : 'Capture' }}\n </button>\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No live query has a response yet.</p>\n }\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-mocks-toolbar{align-items:flex-start}.et-query-devtools-mock-row{padding-block:.6rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-card>.et-query-devtools-mock-row:first-of-type{border-block-start:0;padding-block-start:0}.et-query-devtools-mock-row--armed{background:color-mix(in srgb,var(--_et-qdt-error) 6%,transparent)}.et-query-devtools-mock-head{display:flex;align-items:center;flex-wrap:wrap;gap:.6rem;min-block-size:var(--_et-qdt-row-h)}.et-query-devtools-mock-fields,.et-query-devtools-seed,.et-query-devtools-mock-body{max-inline-size:96rem}.et-query-devtools-mock-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(38rem,1fr));align-items:start;gap:.4rem 1.6rem;padding-block-start:.4rem}.et-query-devtools-mock-fields .et-query-devtools-field{display:grid;grid-template-columns:7rem minmax(0,1fr);align-items:baseline;row-gap:.2rem;color:var(--_et-qdt-fg)}.et-query-devtools-mock-fields .et-query-devtools-select{justify-self:start;max-inline-size:100%}.et-query-devtools-mock-fields .et-query-devtools-field small{grid-column:2;color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-mock-fields .et-query-devtools-field--wide{grid-column:1 / -1;grid-template-columns:7rem minmax(0,1fr)}.et-query-devtools-spec-export{display:flex;align-items:center;gap:.6rem;margin-inline-start:auto}.et-query-devtools-seed{display:flex;align-items:center;flex-wrap:wrap;gap:.4rem 2.4rem;padding-block-start:.4rem;min-block-size:var(--_et-qdt-row-h)}.et-query-devtools-seed-pick{display:flex;align-items:center;gap:.2rem}.et-query-devtools-seed .et-query-devtools-select{max-inline-size:32rem}.et-query-devtools-combobox{flex:0 1 auto;inline-size:32rem;min-block-size:var(--_et-qdt-control-h);box-sizing:border-box;background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,var(--_et-qdt-bg));color:var(--_et-qdt-fg);border:1px solid transparent;border-radius:var(--_et-qdt-radius);padding:.4rem .6rem;font-size:1.15rem}.et-query-devtools-combobox input{background:none;border:0;padding:0;color:inherit;font:inherit}.et-query-devtools-seed-hint{flex-basis:100%;margin:0}.et-query-devtools-mock-body{display:block;padding-block-start:.4rem}.et-query-devtools-mock-textarea{display:block;inline-size:100%;resize:vertical;font-family:inherit;font-size:1.15rem;line-height:1.5;tab-size:2}}\n"], dependencies: [{ kind: "component", type: QueryDevtoolsMockDesignerComponent, selector: "et-query-devtools-mock-designer", inputs: ["body", "annotations", "notes"], outputs: ["save", "dismiss"] }, { kind: "component", type: SelectComponent, selector: "et-select", inputs: ["loadMoreLabel", "addNewLabel", "createLabel", "clearable", "clearLabel"] }, { kind: "directive", type: SelectSearchDirective, selector: "input[etSelectSearch]", inputs: ["query"], outputs: ["queryChange"], exportAs: ["etSelectSearch"] }], encapsulation: i0.ViewEncapsulation.None }); }
2978
3071
  }
2979
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsRouteComponent, decorators: [{
3072
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsMocksTabComponent, decorators: [{
2980
3073
  type: Component,
2981
- args: [{ selector: 'et-query-devtools-route', template: `
2982
- @if (trimmed(); as trimmed) {
2983
- <span [attr.title]="trimTitle()" class="et-query-devtools-route-trimmed">…</span>
2984
- @for (segment of trimmed; track $index) {
2985
- <span [class]="'et-query-devtools-route-' + segment.kind" [attr.title]="segment.name">{{ segment.text }}</span>
2986
- }
2987
- } @else {
2988
- @for (segment of segments(); track $index) {
2989
- <span [class]="'et-query-devtools-route-' + segment.kind" [attr.title]="segment.name">{{ segment.text }}</span>
2990
- } @empty {
2991
- {{ fallback() }}
2992
- }
2993
- }
2994
- `, encapsulation: ViewEncapsulation.None, styles: ["@layer components{.et-query-devtools-route-param{color:var(--_et-qdt-accent)}.et-query-devtools-route-query{color:var(--_et-qdt-muted)}.et-query-devtools-route-trimmed{color:var(--_et-qdt-subtle);cursor:help}}\n"] }]
2995
- }], propDecorators: { segments: [{ type: i0.Input, args: [{ isSignal: true, alias: "segments", required: true }] }], fallback: [{ type: i0.Input, args: [{ isSignal: true, alias: "fallback", required: false }] }], trimPrefix: [{ type: i0.Input, args: [{ isSignal: true, alias: "trimPrefix", required: false }] }] } });
3074
+ args: [{ selector: 'et-query-devtools-mocks-tab', encapsulation: ViewEncapsulation.None, imports: [QueryDevtoolsMockDesignerComponent, SelectComponent, SelectSearchDirective], template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar et-query-devtools-mocks-toolbar\">\n <p class=\"et-query-devtools-hint\">\n A mock replaces the request, so it answers a route that has never run - or does not exist yet. It bypasses the\n interceptor chain by construction, so a mocked secure route never exercises the token flow. The library is kept;\n whether a mock is <strong>armed</strong> is not, so a reload always stops serving them. GraphQL queries are not\n offered: they all POST one route, so matching them needs the document rather than the path.\n </p>\n <div class=\"et-query-devtools-settings-controls\">\n @if (rows().length && armedCount() < rows().length) {\n <button\n (click)=\"ARM_ALL()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Serve every mock in the library - the whole screen off the network at once\"\n >\n Arm all ({{ rows().length }})\n </button>\n }\n @if (armedCount()) {\n <button (click)=\"DISARM_ALL()\" class=\"et-query-devtools-btn\" type=\"button\">\n Disarm all ({{ armedCount() }})\n </button>\n }\n <button (click)=\"openDraft()\" class=\"et-query-devtools-btn et-query-devtools-btn--primary\" type=\"button\">\n <span aria-hidden=\"true\">\uFF0B</span>\n New mock\n </button>\n </div>\n </div>\n\n @if (draft(); as form) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>New mock</strong>\n <span class=\"et-query-devtools-count\">for any route, whether a query has ever called it or not</span>\n </div>\n\n <div class=\"et-query-devtools-mock-fields\">\n <label class=\"et-query-devtools-field\">\n <span>Client</span>\n <select #clientSelect (change)=\"pickClient(clientSelect.value)\" class=\"et-query-devtools-select\">\n @for (name of host.clientNames(); track name) {\n <option [value]=\"name\" [selected]=\"name === form.clientName\">{{ name }}</option>\n }\n </select>\n <small>a mock answers one client, matched by name</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Method</span>\n <select #methodSelect (change)=\"patchDraft({ method: methodSelect.value })\" class=\"et-query-devtools-select\">\n @for (method of METHODS; track method) {\n <option [value]=\"method\" [selected]=\"method === form.method\">{{ method }}</option>\n }\n </select>\n <small>matched exactly</small>\n </label>\n\n <label class=\"et-query-devtools-field et-query-devtools-field--wide\">\n <span>Path</span>\n <input\n #patternInput\n [value]=\"form.pattern\"\n (input)=\"patchDraft({ pattern: patternInput.value })\"\n class=\"et-query-devtools-input\"\n type=\"text\"\n placeholder=\"/posts/:id/comments\"\n />\n <small><code>:name</code> matches any one segment, so the route is mocked rather than one URL</small>\n </label>\n\n <label class=\"et-query-devtools-field et-query-devtools-field--wide\">\n <span>Query</span>\n <input\n #queryInput\n [value]=\"form.query\"\n (input)=\"patchDraft({ query: queryInput.value })\"\n class=\"et-query-devtools-input\"\n type=\"text\"\n placeholder=\"page=2&limit=10\"\n />\n <small>\n parameters the request must carry; anything else it asks for is ignored. Empty answers whatever the query\n string is - and a mock naming parameters wins over one that does not\n </small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Status</span>\n <input\n #statusDraftInput\n [value]=\"form.status\"\n (input)=\"patchDraft({ status: statusDraftInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"100\"\n max=\"599\"\n step=\"1\"\n />\n <small>400 and above arrive as a real error</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Latency</span>\n <input\n #latencyDraftInput\n [value]=\"form.latencyMs\"\n (input)=\"patchDraft({ latencyMs: latencyDraftInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"60000\"\n step=\"100\"\n />\n <small>ms before it settles</small>\n </label>\n </div>\n\n @if (hasSchema()) {\n <div class=\"et-query-devtools-seed\">\n <span class=\"et-query-devtools-section\">Seed</span>\n @if (isSchemaReady()) {\n <div class=\"et-query-devtools-seed-pick\">\n <et-select\n [options]=\"routeOptions()\"\n (pickOption)=\"pickRoute($event)\"\n class=\"et-query-devtools-combobox\"\n mirrorPanelWidth=\"false\"\n pickOnly\n >\n <input etSelectSearch placeholder=\"search the routes it declares\u2026\" />\n </et-select>\n <button\n (click)=\"seedFromDraftRoute()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Generate a body from what the description says this path returns\"\n >\n From this path\n </button>\n </div>\n <div class=\"et-query-devtools-seed-pick\">\n <et-select\n [value]=\"pickedSchema()\"\n [options]=\"schemaNameOptions()\"\n (valueChange)=\"pickSchema($event)\"\n class=\"et-query-devtools-combobox\"\n mirrorPanelWidth=\"false\"\n >\n <input etSelectSearch placeholder=\"search its named types\u2026\" />\n </et-select>\n <button\n [disabled]=\"!pickedSchema()\"\n (click)=\"seedFromSchema()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n From this type\n </button>\n </div>\n <select\n #stylePick\n (change)=\"pickSeedStyle(stylePick.value)\"\n class=\"et-query-devtools-select\"\n title=\"How lifelike the generated values are\"\n >\n @for (style of SEED_STYLES; track style.value) {\n <option [value]=\"style.value\" [selected]=\"style.value === seedStyle()\">{{ style.label }}</option>\n }\n </select>\n <small class=\"et-query-devtools-hint et-query-devtools-seed-hint\">{{ seedStyleHint() }}</small>\n } @else if (schemaError(); as error) {\n <span class=\"et-query-devtools-settings-warn\">The API description could not be loaded: {{ error }}</span>\n } @else {\n <span class=\"et-query-devtools-count\">Loading the API description\u2026</span>\n }\n </div>\n }\n\n <label class=\"et-query-devtools-mock-body\">\n <span class=\"et-query-devtools-section\">Body</span>\n <textarea\n #bodyInput\n [value]=\"form.body\"\n (input)=\"patchDraft({ body: bodyInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-mock-textarea\"\n rows=\"8\"\n spellcheck=\"false\"\n ></textarea>\n </label>\n\n @if (draftSeed(); as seed) {\n <p class=\"et-query-devtools-hint\">\n Generated from the API description{{ seed.schemaName ? ' as ' + seed.schemaName : '' }} - a starting point,\n not the contract. Saving opens the designer on it.\n </p>\n @if (seed.notes.length) {\n <ul class=\"et-query-devtools-designer-notes\">\n @for (note of seed.notes; track note) {\n <li>{{ note }}</li>\n }\n </ul>\n }\n }\n\n @if (draftError(); as error) {\n <p class=\"et-query-devtools-settings-warn\">{{ error }}</p>\n }\n\n <div class=\"et-query-devtools-mock-head\">\n <button (click)=\"saveDraft()\" class=\"et-query-devtools-btn et-query-devtools-btn--primary\" type=\"button\">\n Save to the library\n </button>\n <button (click)=\"closeDraft()\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n </div>\n }\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Designed</strong>\n <span class=\"et-query-devtools-count\">{{ rows().length }} in the library</span>\n @if (rows().length) {\n <div class=\"et-query-devtools-spec-export\">\n <select #formatPick (change)=\"pickSpecFormat(formatPick.value)\" class=\"et-query-devtools-select\">\n @for (format of SPEC_FORMATS; track format.value) {\n <option [value]=\"format.value\" [selected]=\"format.value === specFormat()\">{{ format.label }}</option>\n }\n </select>\n <button\n (click)=\"exportLibrary()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Download every designed route as one OpenAPI 3.1 document - the response schemas inferred from the designed bodies, each body as an example\"\n >\n <span aria-hidden=\"true\">\u21E9</span>\n Export OpenAPI\n </button>\n </div>\n }\n </div>\n\n @for (row of rows(); track row.mock.id) {\n <div [class.et-query-devtools-mock-row--armed]=\"row.armed\" class=\"et-query-devtools-mock-row\">\n <div class=\"et-query-devtools-mock-head\">\n <button\n [class.et-query-devtools-btn--tampered]=\"row.armed\"\n [attr.aria-pressed]=\"row.armed\"\n (click)=\"toggleArmed(row)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"{{ row.armed ? 'Stop serving this response' : 'Serve this response instead of the request' }}\"\n >\n {{ row.armed ? 'Armed' : 'Arm' }}\n </button>\n <span class=\"et-query-devtools-method\">{{ row.mock.method }}</span>\n <span class=\"et-query-devtools-route et-query-devtools-mono\">{{ row.mock.pattern }}</span>\n @if (row.mock.query) {\n <span class=\"et-query-devtools-url\">?{{ row.mock.query }}</span>\n }\n <span class=\"et-query-devtools-url\">{{ row.mock.clientName }}</span>\n @if (row.mock.capturedAt) {\n <span class=\"et-query-devtools-chip\">captured</span>\n }\n @if (row.mock.schemaName; as schemaName) {\n <span class=\"et-query-devtools-chip\" title=\"Seeded from the API description\">{{ schemaName }}</span>\n }\n @if (row.isSecure) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"A mock sends nothing, so no token is attached and the auth flow is never exercised\"\n >\n no auth\n </span>\n }\n <span class=\"et-query-devtools-count\">{{ host.formatBytes(row.bytes) }}</span>\n <button (click)=\"editBody(row)\" class=\"et-query-devtools-btn\" type=\"button\">Body</button>\n <button\n (click)=\"copyDefinition(row)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Copy this route as a @ethlete/query definition - the response type inferred from the body, the args contract and the creator call\"\n >\n <span aria-hidden=\"true\">\u29C9</span>\n {{ copiedId() === row.mock.id ? 'Copied' : 'TS' }}\n </button>\n <button\n (click)=\"copyPathItem(row)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Copy this route as an OpenAPI paths entry, ready to paste under an existing description\"\n >\n <span aria-hidden=\"true\">\u29C9</span>\n {{ copiedSpecId() === row.mock.id ? 'Copied' : 'OAS' }}\n </button>\n <button (click)=\"remove(row)\" class=\"et-query-devtools-btn et-query-devtools-btn--danger\" type=\"button\">\n Delete\n </button>\n </div>\n\n <div class=\"et-query-devtools-mock-fields\">\n <label class=\"et-query-devtools-field\">\n <span>Status</span>\n <input\n #statusInput\n [value]=\"row.mock.status\"\n (change)=\"setStatus(row, statusInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"100\"\n max=\"599\"\n step=\"1\"\n />\n <small>400 and above arrive as a real error, body and all</small>\n </label>\n <label class=\"et-query-devtools-field\">\n <span>Latency</span>\n <input\n #latencyInput\n [value]=\"row.mock.latencyMs\"\n (change)=\"setLatency(row, latencyInput.value)\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"60000\"\n step=\"100\"\n />\n <small>ms before it settles, so the route still has a loading state</small>\n </label>\n </div>\n\n @if (editingId() === row.mock.id) {\n <et-query-devtools-mock-designer\n [body]=\"row.mock.body\"\n [annotations]=\"editingAnnotations()\"\n (save)=\"saveBody(row, $event)\"\n (dismiss)=\"cancelBody()\"\n />\n }\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">\n Nothing designed yet. <strong>New mock</strong> writes one from scratch; capturing below seeds one from\n something that actually happened.\n </p>\n }\n\n @if (specNotes().length) {\n <ul class=\"et-query-devtools-designer-notes\">\n @for (note of specNotes(); track note) {\n <li>{{ note }}</li>\n }\n </ul>\n }\n </div>\n\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong>Capture</strong>\n <span class=\"et-query-devtools-count\">live queries holding a response</span>\n </div>\n\n @for (row of captureRows(); track row.entry.id) {\n <div class=\"et-query-devtools-mock-head\">\n <span class=\"et-query-devtools-method\">{{ row.method }}</span>\n <button\n (click)=\"host.selectQuery(row.entry.id)\"\n class=\"et-query-devtools-route et-query-devtools-mono et-query-devtools-row-link\"\n type=\"button\"\n title=\"Open this query\"\n >\n {{ row.pattern }}\n </button>\n <span class=\"et-query-devtools-url\">{{ row.clientName }}</span>\n @if (row.shared > 1) {\n <span class=\"et-query-devtools-count\" title=\"A mock answers the route, so it answers all of them\">\n {{ row.shared }} queries\n </span>\n }\n <span class=\"et-query-devtools-count\">{{ host.formatBytes(row.bytes) }}</span>\n <button (click)=\"capture(row)\" class=\"et-query-devtools-btn\" type=\"button\">\n {{ row.exists ? 'Re-capture' : 'Capture' }}\n </button>\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No live query has a response yet.</p>\n }\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-mocks-toolbar{align-items:flex-start}.et-query-devtools-mock-row{padding-block:.6rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-card>.et-query-devtools-mock-row:first-of-type{border-block-start:0;padding-block-start:0}.et-query-devtools-mock-row--armed{background:color-mix(in srgb,var(--_et-qdt-error) 6%,transparent)}.et-query-devtools-mock-head{display:flex;align-items:center;flex-wrap:wrap;gap:.6rem;min-block-size:var(--_et-qdt-row-h)}.et-query-devtools-mock-fields,.et-query-devtools-seed,.et-query-devtools-mock-body{max-inline-size:96rem}.et-query-devtools-mock-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(38rem,1fr));align-items:start;gap:.4rem 1.6rem;padding-block-start:.4rem}.et-query-devtools-mock-fields .et-query-devtools-field{display:grid;grid-template-columns:7rem minmax(0,1fr);align-items:baseline;row-gap:.2rem;color:var(--_et-qdt-fg)}.et-query-devtools-mock-fields .et-query-devtools-select{justify-self:start;max-inline-size:100%}.et-query-devtools-mock-fields .et-query-devtools-field small{grid-column:2;color:var(--_et-qdt-subtle);font-size:1.1rem}.et-query-devtools-mock-fields .et-query-devtools-field--wide{grid-column:1 / -1;grid-template-columns:7rem minmax(0,1fr)}.et-query-devtools-spec-export{display:flex;align-items:center;gap:.6rem;margin-inline-start:auto}.et-query-devtools-seed{display:flex;align-items:center;flex-wrap:wrap;gap:.4rem 2.4rem;padding-block-start:.4rem;min-block-size:var(--_et-qdt-row-h)}.et-query-devtools-seed-pick{display:flex;align-items:center;gap:.2rem}.et-query-devtools-seed .et-query-devtools-select{max-inline-size:32rem}.et-query-devtools-combobox{flex:0 1 auto;inline-size:32rem;min-block-size:var(--_et-qdt-control-h);box-sizing:border-box;background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,var(--_et-qdt-bg));color:var(--_et-qdt-fg);border:1px solid transparent;border-radius:var(--_et-qdt-radius);padding:.4rem .6rem;font-size:1.15rem}.et-query-devtools-combobox input{background:none;border:0;padding:0;color:inherit;font:inherit}.et-query-devtools-seed-hint{flex-basis:100%;margin:0}.et-query-devtools-mock-body{display:block;padding-block-start:.4rem}.et-query-devtools-mock-textarea{display:block;inline-size:100%;resize:vertical;font-family:inherit;font-size:1.15rem;line-height:1.5;tab-size:2}}\n"] }]
3075
+ }], ctorParameters: () => [] });
2996
3076
 
2997
- /**
2998
- * The query detail: head, live progress, run/edit/force actions and the overview/history/data
2999
- * sub-tabs. Rendered inline by the Queries tab and wrapped in `<et-query-devtools-drawer>` by every
3000
- * other tab's split view - both read/write the same {@link QueryDevtoolsHost.detailTab} and JIT editor
3001
- * state, so switching sub-tab in one instance is switching it everywhere, exactly as when this was one
3002
- * component holding everything.
3003
- */
3004
- class QueryDevtoolsDetailComponent {
3077
+ /** The Faults tab: arms latency/failure injection per client - in-memory only, not persisted. */
3078
+ class QueryDevtoolsFaultsTabComponent {
3005
3079
  constructor() {
3006
3080
  this.host = injectQueryDevtoolsHost();
3007
- this.sel = input.required(/* @ts-ignore */
3008
- ...(ngDevMode ? [{ debugName: "sel" }] : /* istanbul ignore next */ []));
3009
- /** Whether any of the three exports behind the Copy menu has just landed on the clipboard. */
3010
- this.copied = computed(() => this.host.copiedReport() || this.host.copiedCurl() || this.host.copiedInsomnia(), /* @ts-ignore */
3011
- ...(ngDevMode ? [{ debugName: "copied" }] : /* istanbul ignore next */ []));
3012
- this.overrideSetSource = computed(() => {
3013
- const sel = this.sel();
3014
- const url = this.host.requestUrl(sel.query);
3015
- return { id: sel.entry.id, ...(url ? { url } : {}) };
3016
- }, /* @ts-ignore */
3017
- ...(ngDevMode ? [{ debugName: "overrideSetSource" }] : /* istanbul ignore next */ []));
3018
- /**
3019
- * Whether this is a tombstone. Everything that would run, edit or force the query is hidden for one -
3020
- * its handle is a frozen snapshot, so those actions have nothing to act on.
3021
- */
3022
- this.isGone = computed(() => !!this.sel().entry.destroyedAt, /* @ts-ignore */
3023
- ...(ngDevMode ? [{ debugName: "isGone" }] : /* istanbul ignore next */ []));
3024
- this.detailTabs = [
3025
- { id: 'overview', label: 'Overview' },
3026
- { id: 'history', label: 'History' },
3027
- { id: 'data', label: 'Data' },
3028
- ];
3029
- /** Whether the tail of runs that can no longer be diffed is unfolded. */
3030
- this.foldedRunsOpen = signal(false, /* @ts-ignore */
3031
- ...(ngDevMode ? [{ debugName: "foldedRunsOpen" }] : /* istanbul ignore next */ []));
3032
- /**
3033
- * The runs split at the last one that still holds a body. Only the newest few can ever be an end of a
3034
- * diff (`setQueryDevtoolsResponseHistory`, five by default) while the run log keeps twenty-five, so
3035
- * without this the diff sits under twenty rows reading `body no longer held` and the pair and its
3036
- * result never fit on one screen.
3037
- *
3038
- * Split, not filtered: a dead row *between* two live ones stays where it is, so the log keeps its order.
3039
- */
3040
- this.runSections = computed(() => {
3041
- const runs = this.host.queryRuns(this.sel().entry);
3042
- const isActionable = (run) => run.hasResponse || !!run.error?.hasBody;
3043
- let lastActionable = -1;
3044
- runs.forEach((run, index) => {
3045
- if (isActionable(run))
3046
- lastActionable = index;
3047
- });
3048
- return { listed: runs.slice(0, lastActionable + 1), folded: runs.slice(lastActionable + 1) };
3049
- }, /* @ts-ignore */
3050
- ...(ngDevMode ? [{ debugName: "runSections" }] : /* istanbul ignore next */ []));
3051
- /** What a run's Diff button reads as at each end of the comparison. */
3052
- this.diffRoles = {
3053
- base: {
3054
- label: 'Base',
3055
- title: 'The older end of the comparison - click another run to set the newer one, or this one to clear it',
3056
- },
3057
- compare: { label: 'Compare', title: 'The newer end of the comparison - click to clear it' },
3081
+ this.FAULT_STATUSES = QUERY_DEVTOOLS_FAULT_STATUSES;
3082
+ this.CLEAR_FAULTS = clearQueryDevtoolsFaults;
3083
+ /** The ceiling each numeric fault field is clamped to. */
3084
+ this.FAULT_LIMITS = {
3085
+ latencyMs: 60_000,
3086
+ failNext: 99,
3087
+ failRate: 100,
3058
3088
  };
3059
3089
  }
3060
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3061
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsDetailComponent, isStandalone: true, selector: "et-query-devtools-detail", inputs: { sel: { classPropertyName: "sel", publicName: "sel", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "@let s = sel();\n\n<div class=\"et-query-devtools-detail-head\">\n <span class=\"et-query-devtools-method\">{{ s.entry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.routeSegments(s.entry, s.query)\" />\n </span>\n <button\n [attr.aria-label]=\"host.copyableRouteTitle(s.entry, s.query)\"\n [title]=\"host.copyableRouteTitle(s.entry, s.query)\"\n [class.et-query-devtools-route-copy--copied]=\"host.copiedRoute()\"\n (click)=\"host.copyRoute(s.entry, s.query)\"\n class=\"et-query-devtools-route-copy\"\n type=\"button\"\n >\n {{ host.copiedRoute() ? '\u2713' : '\u29C9' }}\n </button>\n @if (host.isTampered(s.entry)) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"Showing an armed response override or a devtools-injected failure, not what the server actually sent\"\n >\n tampered\n </span>\n }\n @if (isGone()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">gone</span>\n }\n</div>\n\n@if (s.entry.destroyedAt; as destroyedAt) {\n <p class=\"et-query-devtools-gone-note\">\n Destroyed at {{ host.formatTime(destroyedAt) }}. This is the last state the query held - nothing here updates, and\n it cannot be run again.\n </p>\n}\n\n@if (host.requestProgress(s.query); as live) {\n <div class=\"et-query-devtools-live\">\n @if (live.retry; as retry) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"The retry policy is backing off before it tries again. A query stuck loading with this chip is waiting, not slow.\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n attempt {{ retry.attempt }} in {{ host.formatCountdown(live.retryInMs) }}\n </span>\n <span class=\"et-query-devtools-live-note\">\n {{ host.retryCause(retry.status) }} \u00B7 backing off {{ host.formatDuration(retry.delayMs) }}\n </span>\n } @else if (live.attempts > 1) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"How many HTTP attempts the last execution took - the retry policy fired for the rest\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n {{ live.attempts }} attempts\n </span>\n }\n\n @if (live.progress; as progress) {\n <span\n [attr.aria-valuenow]=\"progress.percentage\"\n class=\"et-query-devtools-progress\"\n role=\"progressbar\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n aria-label=\"Transfer progress\"\n >\n <span [style.inline-size.%]=\"progress.percentage\" class=\"et-query-devtools-progress-bar\"></span>\n </span>\n <span class=\"et-query-devtools-live-note\">\n {{ host.formatPercent(progress.percentage) }} \u00B7 {{ host.formatBytes(progress.loaded) }} of\n {{ host.formatBytes(progress.total) }}\n @if (progress.speed !== null) {\n \u00B7 {{ host.formatSpeed(progress.speed) }}\n }\n @if (progress.remainingTime !== null) {\n \u00B7 {{ host.formatDuration(progress.remainingTime) }} left\n }\n </span>\n }\n </div>\n}\n\n<!-- One row, one primary action. Everything a query can be *made* to do lives behind Override, and\n everything that leaves the panel behind Copy - a flat row of all twelve read as a wall of equal\n buttons in which Execute was no easier to find than Clear. -->\n<div class=\"et-query-devtools-actions\">\n @if (!isGone()) {\n <div class=\"et-query-devtools-btn-group\">\n <button\n (click)=\"host.executeQuery(s, false)\"\n class=\"et-query-devtools-btn et-query-devtools-btn--primary\"\n type=\"button\"\n title=\"Run the query again, ignoring the cache\"\n >\n <span aria-hidden=\"true\">\u25B6</span>\n Execute\n </button>\n <button\n (click)=\"host.executeQuery(s, true)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Run it the way the app would - a fresh enough cached response is used instead of a request\"\n >\n Cached\n </button>\n <button\n (click)=\"host.resetQuery(s.query)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Drop this query's state back to idle\"\n >\n Reset\n </button>\n </div>\n }\n @let locatable = host.locatableElement(s.entry);\n <button\n [disabled]=\"!locatable\"\n [class.et-query-devtools-btn--active]=\"host.locateState() === 'located'\"\n [title]=\"\n locatable\n ? 'Scroll to and outline the element this query was created in. That is where it was created, not necessarily where its data is rendered.'\n : 'This query was created outside a component or directive - a root service, a resolver or a guard - so it has no element to point at.'\n \"\n (click)=\"host.locateQuery(s.entry)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n <span aria-hidden=\"true\">\u2316</span>\n @switch (host.locateState()) {\n @case ('located') {\n Created here\n }\n @case ('offscreen') {\n Not on screen\n }\n @default {\n Locate\n }\n }\n </button>\n\n <!-- The tick has to live on the trigger: the menu closes on the pick, so a confirmation inside it\n would be gone before it could be read. -->\n <div class=\"et-query-devtools-menu\" etMenu>\n <button class=\"et-query-devtools-btn\" title=\"Take this request somewhere else\" type=\"button\" etMenuTrigger>\n <span class=\"et-query-devtools-menu-glyph\" aria-hidden=\"true\">{{ copied() ? '\u2713' : '\u29C9' }}</span>\n {{ copied() ? 'Copied' : 'Copy' }}\n <span class=\"et-query-devtools-menu-caret\" aria-hidden=\"true\">\u25BE</span>\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <button (activate)=\"host.copyReport(s.entry, s.query)\" et-menu-item type=\"button\">\n Copy report\n <span class=\"et-query-devtools-menu-item-note\">Everything the panel knows, as markdown</span>\n </button>\n <button (activate)=\"host.copyCurlRequest(s.entry, s.query)\" et-menu-item type=\"button\">\n Copy as cURL\n <span class=\"et-query-devtools-menu-item-note\">URL, resolved headers and body</span>\n </button>\n <button (activate)=\"host.copyInsomniaRequest(s.entry, s.query)\" et-menu-item type=\"button\">\n Copy for Insomnia\n <span class=\"et-query-devtools-menu-item-note\">A collection to import from the clipboard</span>\n </button>\n </et-menu>\n </ng-template>\n </div>\n\n @if (!isGone()) {\n <div class=\"et-query-devtools-menu\" etMenu>\n <button\n [class.et-query-devtools-btn--tampered]=\"host.isTampered(s.entry)\"\n class=\"et-query-devtools-btn\"\n title=\"Make this query answer with something other than what the API sent\"\n type=\"button\"\n etMenuTrigger\n >\n <span class=\"et-query-devtools-menu-glyph\" aria-hidden=\"true\">\u270E</span>\n Override\n <span class=\"et-query-devtools-menu-caret\" aria-hidden=\"true\">\u25BE</span>\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <button (activate)=\"host.openResponseEditor(s.query)\" et-menu-item type=\"button\">Edit response\u2026</button>\n <button (activate)=\"host.openArgsEditor(s)\" et-menu-item type=\"button\">Replay with edited args\u2026</button>\n <et-menu-separator />\n <button (activate)=\"host.forceLoading(s.query)\" et-menu-item type=\"button\">Force loading</button>\n <button (activate)=\"host.forceError(s.query)\" et-menu-item type=\"button\">Force error</button>\n <button (activate)=\"host.forceEmpty(s.query)\" et-menu-item type=\"button\">Force empty</button>\n <et-menu-separator />\n <button (activate)=\"host.clearForced(s.query)\" et-menu-item type=\"button\">Clear forced state</button>\n\n @if (s.entry.overrides; as overrides) {\n <et-menu-separator />\n <et-query-devtools-override-set-menu\n [overrides]=\"overrides\"\n [response]=\"s.query.response()\"\n [source]=\"overrideSetSource()\"\n />\n }\n </et-menu>\n </ng-template>\n </div>\n }\n</div>\n\n@if (!isGone() && host.editorMode() !== 'none') {\n <div class=\"et-query-devtools-editor\">\n @if (host.editorMode() === 'response') {\n <h4 class=\"et-query-devtools-section\">Edit response \u2192 setResponse()</h4>\n <textarea\n #respArea\n [value]=\"host.editorSeed\"\n (input)=\"host.responseDraft.set(respArea.value)\"\n class=\"et-query-devtools-textarea\"\n spellcheck=\"false\"\n ></textarea>\n <div class=\"et-query-devtools-actions\">\n <button (click)=\"host.applyResponse(s.query)\" class=\"et-query-devtools-btn\" type=\"button\">Apply</button>\n <button (click)=\"host.cancelEditor()\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n } @else {\n <h4 class=\"et-query-devtools-section\">Replay with edited args \u2192 execute()</h4>\n <textarea\n #argsArea\n [value]=\"host.editorSeed\"\n (input)=\"host.argsDraft.set(argsArea.value)\"\n class=\"et-query-devtools-textarea\"\n spellcheck=\"false\"\n ></textarea>\n <div class=\"et-query-devtools-actions\">\n <button (click)=\"host.applyArgs(s.query)\" class=\"et-query-devtools-btn\" type=\"button\">Execute</button>\n <button (click)=\"host.cancelEditor()\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n }\n @if (host.editError(); as err) {\n <p class=\"et-query-devtools-editor-error\">{{ err }}</p>\n }\n </div>\n}\n\n@let detailRuns = host.queryRuns(s.entry);\n<nav class=\"et-query-devtools-subtabs\" role=\"tablist\">\n @for (tab of detailTabs; track tab.id) {\n <button\n [class.et-query-devtools-subtab--active]=\"host.detailTab() === tab.id\"\n [attr.aria-selected]=\"host.detailTab() === tab.id\"\n (click)=\"host.detailTab.set(tab.id)\"\n class=\"et-query-devtools-subtab\"\n type=\"button\"\n role=\"tab\"\n >\n {{ tab.label }}\n @if (tab.id === 'history' && detailRuns.length) {\n <span class=\"et-query-devtools-tab-badge\">{{ detailRuns.length }}</span>\n }\n <!-- The error body lives under Data, so the tab has to say so - a failure must not hide\n behind a tab that is not open. -->\n @if (tab.id === 'data' && s.query.error()) {\n <span class=\"et-query-devtools-tab-badge et-query-devtools-tab-badge--error\">!</span>\n }\n </button>\n }\n</nav>\n\n@switch (host.detailTab()) {\n @case ('overview') {\n <dl class=\"et-query-devtools-meta\">\n <dt>Base URL</dt>\n <dd class=\"et-query-devtools-mono\">\n {{ s.entry.meta.clientBaseUrl || s.entry.meta.clientName || '\u2014' }}\n </dd>\n <dt>Route</dt>\n <dd class=\"et-query-devtools-mono\">{{ s.entry.meta.route || '\u2014' }}</dd>\n <dt>Request URL</dt>\n <dd class=\"et-query-devtools-mono\">{{ host.requestUrl(s.query) || '\u2014' }}</dd>\n <dt>Status</dt>\n <dd>{{ host.queryStatus(s.query) }}</dd>\n <dt>Cache key</dt>\n <dd class=\"et-query-devtools-mono\">{{ s.query.id() || '\u2014' }}</dd>\n <dt>Last executed</dt>\n <dd>{{ host.formatTime(s.query.lastTimeExecutedAt()) }}</dd>\n <dt>Triggered by</dt>\n <dd>{{ s.query.triggeredBy() || 'user' }}</dd>\n @let refreshes = host.refreshesFor(s.entry.id);\n @if (refreshes.length) {\n <dt title=\"Invalidations and mutation refreshes that re-executed this query, as far back as the event log goes\">\n Refetched by\n </dt>\n <dd>\n <div class=\"et-query-devtools-chips\">\n @for (refresh of refreshes.slice(0, 5); track refresh.id) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">\n {{ host.formatTime(refresh.timestamp) }} \u00B7 {{ refresh.label }}\n </span>\n }\n @if (refreshes.length > 5) {\n <span class=\"et-query-devtools-hint\">+{{ refreshes.length - 5 }} earlier</span>\n }\n </div>\n </dd>\n }\n @let drivers = host.formsDrivingQuery(s.entry);\n @if (drivers.length) {\n <dt>Args from</dt>\n <dd>\n <div class=\"et-query-devtools-chips\">\n @for (driver of drivers; track driver.id) {\n <button\n (click)=\"host.selectForm(driver.id)\"\n class=\"et-query-devtools-chip et-query-devtools-chip--button\"\n type=\"button\"\n title=\"Open the query form in the Forms tab\"\n >\n {{ driver.meta.name }}\n </button>\n }\n </div>\n </dd>\n }\n @if (s.entry.meta.features?.length) {\n <dt>Features</dt>\n <dd>\n <et-query-devtools-features [features]=\"s.entry.meta.features ?? []\" />\n </dd>\n }\n </dl>\n\n @let activity = host.queryActivity(s.entry);\n <div class=\"et-query-devtools-section-row\">\n <h4 class=\"et-query-devtools-section\">Activity</h4>\n <button\n [disabled]=\"!activity.hasActivity\"\n (click)=\"host.resetStats(s.entry)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Clear the counters and the run history, so the next interaction can be measured on its own\"\n >\n \u27F2 Reset\n </button>\n </div>\n\n <dl class=\"et-query-devtools-stats\">\n <div class=\"et-query-devtools-stat\" title=\"Every time the query ran, cache hits included\">\n <dt>Refreshes</dt>\n <dd>{{ activity.stats.executions }}</dd>\n @if (activity.cacheServed) {\n <span class=\"et-query-devtools-stat-sub\">{{ activity.cacheServed }} without a request</span>\n }\n </div>\n <div class=\"et-query-devtools-stat\" title=\"Executions that started a request, and how they ended\">\n <dt>Requests</dt>\n <dd>{{ activity.stats.requests }}</dd>\n <span class=\"et-query-devtools-stat-sub\">\n {{ activity.stats.responses }} ok\n @if (activity.stats.errors) {\n \u00B7 {{ activity.stats.errors }} failed\n }\n </span>\n </div>\n <div\n class=\"et-query-devtools-stat\"\n title=\"Total response payload received. Sizes without a content-length header are measured from the decoded body, so they ignore compression.\"\n >\n <dt>Received</dt>\n <dd>{{ host.formatTransferred(activity.stats.receivedBytes, activity.stats.hasEstimatedBytes) }}</dd>\n @if (activity.avgResponseBytes !== null) {\n <span class=\"et-query-devtools-stat-sub\">{{ host.formatBytes(activity.avgResponseBytes) }} per response</span>\n }\n </div>\n @if (activity.stats.sentBytes) {\n <div class=\"et-query-devtools-stat\" title=\"Total request body sent (headers not counted)\">\n <dt>Sent</dt>\n <dd>{{ host.formatTransferred(activity.stats.sentBytes, activity.stats.hasEstimatedBytes) }}</dd>\n </div>\n }\n @if (activity.stats.retries) {\n <div\n class=\"et-query-devtools-stat\"\n title=\"Attempts the retry policy added on top of the first one. Which run they belong to is under History.\"\n >\n <dt>Retries</dt>\n <dd>{{ activity.stats.retries }}</dd>\n </div>\n }\n <div class=\"et-query-devtools-stat\" title=\"Measured from the execution that preceded each response\">\n <dt>Duration</dt>\n <dd>{{ host.formatDuration(activity.stats.lastDurationMs) }}</dd>\n @if (activity.avgDurationMs !== null) {\n <span class=\"et-query-devtools-stat-sub\">{{ host.formatDuration(activity.avgDurationMs) }} on average</span>\n }\n </div>\n <div class=\"et-query-devtools-stat\">\n <dt>Last response</dt>\n <dd class=\"et-query-devtools-stat-value--sm\">{{ host.formatTime(activity.stats.lastResponseAt) }}</dd>\n @if (activity.stats.firstExecutedAt !== null) {\n <span class=\"et-query-devtools-stat-sub\"\n >first ran {{ host.formatTime(activity.stats.firstExecutedAt) }}</span\n >\n }\n </div>\n </dl>\n }\n @case ('history') {\n <div class=\"et-query-devtools-section-row\">\n <h4 class=\"et-query-devtools-section\">Runs</h4>\n <button\n [disabled]=\"!detailRuns.length\"\n (click)=\"host.resetStats(s.entry)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Clear the run history and the counters, so the next interaction can be measured on its own\"\n >\n \u27F2 Reset\n </button>\n </div>\n @if (detailRuns.length) {\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Run</th>\n <th>Started</th>\n <th>Duration</th>\n <th>Received</th>\n <th>Body</th>\n </tr>\n </thead>\n <tbody>\n @for (run of runSections().listed.concat(foldedRunsOpen() ? runSections().folded : []); track run.index) {\n <tr [attr.data-status]=\"run.status === 'error' ? 'error' : null\">\n <td>\n <span class=\"et-query-devtools-run-cell\">\n <span\n [attr.data-status]=\"host.runStatus(run)\"\n [title]=\"run.status\"\n class=\"et-query-devtools-status\"\n ></span>\n #{{ run.index }}\n @if (run.attempts > 1) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"Took {{\n run.attempts\n }} attempts - the duration next to it covers every one of them, backoff included\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n {{ run.attempts }}\n </span>\n }\n @if (!run.didRequest) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"Arrived without a request of this query's own - a poll, another consumer of the same cache entry, or another tab\"\n >\n shared\n </span>\n }\n @if (run.error; as error) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"{{ error.messages.join(' \u00B7 ') || 'The failure carried no message' }}\"\n >\n {{ error.code || 'failed' }}\n </span>\n }\n </span>\n </td>\n <td>{{ host.formatTime(run.startedAt) }}</td>\n <td>\n @if (run.endedAt === null) {\n in flight\n } @else {\n {{ host.formatDuration(run.endedAt - run.startedAt) }}\n }\n </td>\n <td>{{ run.receivedBytes ? host.formatBytes(run.receivedBytes) : '\u2014' }}</td>\n <td>\n <span class=\"et-query-devtools-cell-actions\">\n @if (host.canDiffRun(s.entry, run)) {\n @let role = host.diffRunRole(s.entry, run);\n @let armed = role ? diffRoles[role] : null;\n <button\n [class.et-query-devtools-btn--active]=\"!!armed\"\n [title]=\"\n armed?.title ?? 'Compare this response against another run - click a second row to pick which'\n \"\n (click)=\"host.toggleRunDiff(s.entry, run)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n {{ armed?.label ?? 'Diff' }}\n </button>\n } @else if (run.error?.hasBody) {\n <button\n [class.et-query-devtools-btn--active]=\"host.errorRunIndex() === run.index\"\n (click)=\"host.toggleRunError(run)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Show the body this failure came with\"\n >\n Error\n </button>\n } @else if (run.hasResponse) {\n <span class=\"et-query-devtools-count\">the only body held</span>\n } @else if (run.status === 'success' || run.error) {\n <span\n class=\"et-query-devtools-count\"\n title=\"Only the newest {{\n host.retainedResponseCount()\n }} bodies of a query are kept, so this run can no longer be an end of a diff\"\n >\n body no longer held\n </span>\n } @else {\n <span class=\"et-query-devtools-count\">\u2014</span>\n }\n </span>\n </td>\n </tr>\n }\n </tbody>\n </table>\n @let foldedRuns = runSections().folded.length;\n @if (foldedRuns) {\n <button\n [attr.aria-expanded]=\"foldedRunsOpen()\"\n (click)=\"foldedRunsOpen.set(!foldedRunsOpen())\"\n class=\"et-query-devtools-btn et-query-devtools-runs-fold\"\n type=\"button\"\n title=\"Only the newest {{\n host.retainedResponseCount()\n }} bodies of a query are kept, so none of these older runs can be an end of a diff\"\n >\n <span aria-hidden=\"true\">{{ foldedRunsOpen() ? '\u25BE' : '\u25B8' }}</span>\n {{ foldedRunsOpen() ? 'Hide' : 'Show' }} {{ foldedRuns }} older run{{ foldedRuns === 1 ? '' : 's' }} with no\n body\n </button>\n }\n\n @if (host.pickedRunError(s.entry); as failure) {\n <div class=\"et-query-devtools-section-row\">\n <h5 class=\"et-query-devtools-section\">\n Error body \u00B7 run #{{ failure.run.index }} \u00B7 {{ failure.error.code || 'no status' }}\n </h5>\n </div>\n @for (message of failure.error.messages; track message) {\n <p class=\"et-query-devtools-count\">{{ message }}</p>\n }\n <et-query-devtools-json\n [value]=\"failure.error.body\"\n [search]=\"host.jsonSearchTerm()\"\n [expandedPaths]=\"host.jsonExpandedPaths()\"\n [collapsedPaths]=\"host.jsonCollapsedPaths()\"\n [toggleFn]=\"host.toggleJsonPath\"\n path=\"runError\"\n />\n }\n\n @if (host.responseDiff(s.entry); as comparison) {\n <div class=\"et-query-devtools-section-row\">\n <h5 class=\"et-query-devtools-section\">\n Response diff \u00B7 run #{{ comparison.before.index }} \u2192 #{{ comparison.after.index }}\n </h5>\n <span class=\"et-query-devtools-diff-step\">\n <button\n [disabled]=\"!host.canStepRunDiff(s.entry, true)\"\n (click)=\"host.stepRunDiff(s.entry, true)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Compare the pair one run older, without going back to the table\"\n >\n \u25C2 Older\n </button>\n <button\n [disabled]=\"!host.canStepRunDiff(s.entry, false)\"\n (click)=\"host.stepRunDiff(s.entry, false)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Compare the pair one run newer, without going back to the table\"\n >\n Newer \u25B8\n </button>\n </span>\n <span class=\"et-query-devtools-count\">\n @if (host.diffBaseRunIndex() === null) {\n <span\n title=\"Nothing is picked to compare against, so this is the nearest older run that still holds a body - click another run's Diff to pick one\"\n >\n nearest older\n </span>\n \u00B7\n }\n @if (comparison.diff.entries.length) {\n {{ comparison.diff.entries.length }} path{{ comparison.diff.entries.length === 1 ? '' : 's' }}\n @if (comparison.diff.truncated) {\n (capped)\n }\n } @else {\n identical\n }\n </span>\n </div>\n @if (comparison.diff.entries.length) {\n <table class=\"et-query-devtools-table et-query-devtools-diff\">\n <thead>\n <tr>\n <th>Path</th>\n <th>Before</th>\n <th>After</th>\n </tr>\n </thead>\n <tbody>\n @for (change of comparison.diff.entries; track change.path) {\n <tr [attr.data-kind]=\"change.kind\">\n <td class=\"et-query-devtools-mono\">{{ change.path }}</td>\n <td class=\"et-query-devtools-mono et-query-devtools-diff-before\">\n {{ change.kind === 'added' ? '\u2014' : host.inlineValue(change.before) }}\n </td>\n <td class=\"et-query-devtools-mono et-query-devtools-diff-after\">\n {{ change.kind === 'removed' ? '\u2014' : host.inlineValue(change.after) }}\n </td>\n </tr>\n }\n </tbody>\n </table>\n } @else {\n <p class=\"et-query-devtools-empty\">The two responses are identical.</p>\n }\n }\n } @else {\n <p class=\"et-query-devtools-empty\">No runs recorded yet.</p>\n }\n }\n @case ('data') {\n @if (s.entry.meta.gqlQuery; as gqlQuery) {\n <div class=\"et-query-devtools-section-row\">\n <h5 class=\"et-query-devtools-section\">GraphQL document</h5>\n <button\n (click)=\"host.copyGqlDocument(gqlQuery)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Copy the GraphQL document\"\n >\n {{ host.copiedGql() ? '\u2713 Copied' : '\u29C9 Copy' }}\n </button>\n </div>\n <pre class=\"et-query-devtools-code\">{{ host.gqlDocument(gqlQuery) }}</pre>\n }\n\n <div class=\"et-query-devtools-section-row\">\n <h4 class=\"et-query-devtools-section\">Value explorer</h4>\n <input\n #jsonSearchInput\n [value]=\"host.jsonSearch()\"\n (input)=\"host.jsonSearch.set(jsonSearchInput.value)\"\n class=\"et-query-devtools-search\"\n type=\"search\"\n placeholder=\"filter keys / values\u2026\"\n />\n </div>\n\n <h5 class=\"et-query-devtools-section\">Args</h5>\n <et-query-devtools-json\n [value]=\"host.queryArgs(s.query)\"\n [search]=\"host.jsonSearchTerm()\"\n [expandedPaths]=\"host.jsonExpandedPaths()\"\n [collapsedPaths]=\"host.jsonCollapsedPaths()\"\n [toggleFn]=\"host.toggleJsonPath\"\n path=\"args\"\n />\n\n @if (s.query.error(); as error) {\n <h5 class=\"et-query-devtools-section\">Error ({{ error.raw.status }})</h5>\n <et-query-devtools-json\n [value]=\"error.isList ? error.errors : error.error\"\n [search]=\"host.jsonSearchTerm()\"\n [expandedPaths]=\"host.jsonExpandedPaths()\"\n [collapsedPaths]=\"host.jsonCollapsedPaths()\"\n [toggleFn]=\"host.toggleJsonPath\"\n path=\"error\"\n />\n } @else {\n <div class=\"et-query-devtools-section-row\">\n <h5 class=\"et-query-devtools-section\">Response</h5>\n @if (s.entry.overrides?.list()?.length || host.overridesPersist()) {\n <button\n [class.et-query-devtools-btn--active]=\"host.overridesPersist()\"\n [attr.aria-pressed]=\"host.overridesPersist()\"\n (click)=\"host.toggleOverridesPersist()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"{{\n host.overridesPersist()\n ? 'Panel-wide: every armed response override is kept in ' +\n host.overridesScopeLabel() +\n ' and replayed on the next load.'\n : 'Panel-wide: keep every armed response override and replay it on the next load.'\n }} Settings \u2699 chooses the scope. Armed faults are never kept.\"\n >\n Keep across reloads\n </button>\n }\n @if (s.entry.overrides?.list()?.length) {\n <button (click)=\"s.entry.overrides?.clearAll()\" class=\"et-query-devtools-btn\" type=\"button\">\n Reset all overrides ({{ s.entry.overrides?.list()?.length }})\n </button>\n }\n </div>\n <et-query-devtools-json\n [value]=\"s.query.response()\"\n [search]=\"host.jsonSearchTerm()\"\n [expandedPaths]=\"host.jsonExpandedPaths()\"\n [collapsedPaths]=\"host.jsonCollapsedPaths()\"\n [toggleFn]=\"host.toggleJsonPath\"\n [overrides]=\"s.entry.overrides ?? null\"\n path=\"response\"\n />\n }\n }\n}\n", styles: ["@layer components{.et-query-devtools-detail .et-query-devtools-detail-head,.et-query-devtools-drawer .et-query-devtools-detail-head{padding-block-end:.8rem;border-block-end:1px solid var(--_et-qdt-border)}.et-query-devtools-gone-note{margin:0 0 .8rem;color:var(--_et-qdt-muted);font-size:1.1rem}.et-query-devtools-detail-head{display:flex;align-items:center;gap:.8rem;margin-block-end:.8rem}.et-query-devtools-detail-head .et-query-devtools-route{flex:0 1 auto}.et-query-devtools-route-copy{appearance:none;margin-inline-end:auto;display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;inline-size:2.2rem;block-size:2.2rem;margin-inline-start:-.4rem;border:none;border-radius:.3rem;background:transparent;color:var(--_et-qdt-subtle);cursor:pointer;font:inherit;font-size:1.3rem;line-height:1}@media(hover:hover){.et-query-devtools-route-copy:hover:not(.et-query-devtools-route-copy--copied){background:var(--_et-qdt-active);color:var(--_et-qdt-fg)}}.et-query-devtools-route-copy--copied{color:var(--_et-qdt-success)}.et-query-devtools-live{display:flex;flex-wrap:wrap;align-items:center;gap:.6rem;margin-block-end:.8rem}.et-query-devtools-live-note{color:var(--_et-qdt-muted);font-size:1.15rem}.et-query-devtools-progress{display:block;flex:1 1 12rem;min-inline-size:8rem;max-inline-size:24rem;block-size:.5rem;border-radius:999px;overflow:hidden;background:var(--_et-qdt-border)}.et-query-devtools-progress-bar{display:block;block-size:100%;border-radius:inherit;background:var(--_et-qdt-accent)}.et-query-devtools-actions{display:flex;flex-wrap:wrap;align-items:center;gap:.4rem;margin-block-end:.8rem}.et-query-devtools-actions>.et-query-devtools-menu+.et-query-devtools-menu{margin-inline-start:.5rem;padding-inline-start:.9rem;border-inline-start:1px solid var(--_et-qdt-border)}.et-query-devtools-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(12rem,1fr));gap:.6rem;margin:0 0 1rem}.et-query-devtools-stat-value--sm{font-size:1.2rem}.et-query-devtools-code{margin:0 0 .8rem;padding:.8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);overflow:auto;white-space:pre;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:1.15rem;max-block-size:20rem}.et-query-devtools-diff-step{display:flex;gap:.4rem;margin-inline-start:auto}.et-query-devtools-runs-fold{margin-block-start:.4rem;color:var(--_et-qdt-muted)}.et-query-devtools-textarea{inline-size:100%;min-block-size:12rem;resize:vertical;background:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);color:var(--_et-qdt-fg);border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.6rem;font-family:inherit;font-size:1.2rem}.et-query-devtools-subtabs{display:flex;flex-wrap:wrap;gap:.2rem;border-block-end:1px solid var(--_et-qdt-border);margin-block:.8rem}.et-query-devtools-subtabs+.et-query-devtools-section-row{padding-block-start:0;border-block-start:0}.et-query-devtools-subtab{appearance:none;display:inline-flex;align-items:center;gap:.4rem;border:0;border-block-end:2px solid transparent;background:transparent;color:var(--_et-qdt-muted);font:inherit;font-size:1.15rem;padding:.4rem .6rem;margin-block-end:-1px;cursor:pointer}@media(hover:hover){.et-query-devtools-subtab:hover{color:var(--_et-qdt-fg)}}.et-query-devtools-subtab--active{color:var(--_et-qdt-fg);border-block-end-color:var(--_et-qdt-accent)}.et-query-devtools-run-cell{display:inline-flex;align-items:center;gap:.5rem}.et-query-devtools-diff td{max-inline-size:28rem;overflow-wrap:anywhere}.et-query-devtools-diff-before{color:var(--_et-qdt-muted)}.et-query-devtools-diff tr[data-kind=removed] .et-query-devtools-diff-before{color:var(--_et-qdt-error)}.et-query-devtools-diff tr[data-kind=added] .et-query-devtools-diff-after{color:var(--_et-qdt-success)}.et-query-devtools-diff tr[data-kind=changed] .et-query-devtools-diff-after{color:var(--_et-qdt-accent)}}\n"], dependencies: [{ kind: "component", type: MenuComponent, selector: "et-menu" }, { kind: "directive", type: MenuDirective, selector: "[etMenu]", inputs: ["placement", "fallbackPlacements", "offset", "viewportPadding", "arrow", "arrowPadding", "autoFocus", "hoverOpen", "loop", "hoverOpenDelay", "hoverCloseDelay", "disabled", "open"], outputs: ["openChange"], exportAs: ["etMenu"] }, { kind: "component", type: MenuItemComponent, selector: "button[et-menu-item], a[et-menu-item]", inputs: ["variant"] }, { kind: "component", type: MenuSeparatorComponent, selector: "et-menu-separator" }, { kind: "directive", type: MenuSurfaceDirective, selector: "ng-template[etMenuSurface]", exportAs: ["etMenuSurface"] }, { kind: "directive", type: MenuTriggerDirective, selector: "[etMenuTrigger]", exportAs: ["etMenuTrigger"] }, { kind: "component", type: QueryDevtoolsFeaturesComponent, selector: "et-query-devtools-features", inputs: ["features"] }, { kind: "component", type: QueryDevtoolsJsonComponent, selector: "et-query-devtools-json", inputs: ["value", "nodeKey", "depth", "search", "chunk", "path", "expandedPaths", "collapsedPaths", "toggleFn", "jsonPath", "parentKind", "overrides", "annotations"] }, { kind: "component", type: QueryDevtoolsOverrideSetMenuComponent, selector: "et-query-devtools-override-set-menu", inputs: ["overrides", "response", "source"] }, { kind: "component", type: QueryDevtoolsRouteComponent, selector: "et-query-devtools-route", inputs: ["segments", "fallback", "trimPrefix"] }], encapsulation: i0.ViewEncapsulation.None }); }
3062
- }
3063
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsDetailComponent, decorators: [{
3064
- type: Component,
3065
- args: [{ selector: 'et-query-devtools-detail', encapsulation: ViewEncapsulation.None, imports: [
3066
- MenuComponent,
3067
- MenuDirective,
3068
- MenuItemComponent,
3069
- MenuSeparatorComponent,
3070
- MenuSurfaceDirective,
3071
- MenuTriggerDirective,
3072
- QueryDevtoolsFeaturesComponent,
3073
- QueryDevtoolsJsonComponent,
3074
- QueryDevtoolsOverrideSetMenuComponent,
3075
- QueryDevtoolsRouteComponent,
3076
- ], template: "@let s = sel();\n\n<div class=\"et-query-devtools-detail-head\">\n <span class=\"et-query-devtools-method\">{{ s.entry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.routeSegments(s.entry, s.query)\" />\n </span>\n <button\n [attr.aria-label]=\"host.copyableRouteTitle(s.entry, s.query)\"\n [title]=\"host.copyableRouteTitle(s.entry, s.query)\"\n [class.et-query-devtools-route-copy--copied]=\"host.copiedRoute()\"\n (click)=\"host.copyRoute(s.entry, s.query)\"\n class=\"et-query-devtools-route-copy\"\n type=\"button\"\n >\n {{ host.copiedRoute() ? '\u2713' : '\u29C9' }}\n </button>\n @if (host.isTampered(s.entry)) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"Showing an armed response override or a devtools-injected failure, not what the server actually sent\"\n >\n tampered\n </span>\n }\n @if (isGone()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">gone</span>\n }\n</div>\n\n@if (s.entry.destroyedAt; as destroyedAt) {\n <p class=\"et-query-devtools-gone-note\">\n Destroyed at {{ host.formatTime(destroyedAt) }}. This is the last state the query held - nothing here updates, and\n it cannot be run again.\n </p>\n}\n\n@if (host.requestProgress(s.query); as live) {\n <div class=\"et-query-devtools-live\">\n @if (live.retry; as retry) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"The retry policy is backing off before it tries again. A query stuck loading with this chip is waiting, not slow.\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n attempt {{ retry.attempt }} in {{ host.formatCountdown(live.retryInMs) }}\n </span>\n <span class=\"et-query-devtools-live-note\">\n {{ host.retryCause(retry.status) }} \u00B7 backing off {{ host.formatDuration(retry.delayMs) }}\n </span>\n } @else if (live.attempts > 1) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"How many HTTP attempts the last execution took - the retry policy fired for the rest\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n {{ live.attempts }} attempts\n </span>\n }\n\n @if (live.progress; as progress) {\n <span\n [attr.aria-valuenow]=\"progress.percentage\"\n class=\"et-query-devtools-progress\"\n role=\"progressbar\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n aria-label=\"Transfer progress\"\n >\n <span [style.inline-size.%]=\"progress.percentage\" class=\"et-query-devtools-progress-bar\"></span>\n </span>\n <span class=\"et-query-devtools-live-note\">\n {{ host.formatPercent(progress.percentage) }} \u00B7 {{ host.formatBytes(progress.loaded) }} of\n {{ host.formatBytes(progress.total) }}\n @if (progress.speed !== null) {\n \u00B7 {{ host.formatSpeed(progress.speed) }}\n }\n @if (progress.remainingTime !== null) {\n \u00B7 {{ host.formatDuration(progress.remainingTime) }} left\n }\n </span>\n }\n </div>\n}\n\n<!-- One row, one primary action. Everything a query can be *made* to do lives behind Override, and\n everything that leaves the panel behind Copy - a flat row of all twelve read as a wall of equal\n buttons in which Execute was no easier to find than Clear. -->\n<div class=\"et-query-devtools-actions\">\n @if (!isGone()) {\n <div class=\"et-query-devtools-btn-group\">\n <button\n (click)=\"host.executeQuery(s, false)\"\n class=\"et-query-devtools-btn et-query-devtools-btn--primary\"\n type=\"button\"\n title=\"Run the query again, ignoring the cache\"\n >\n <span aria-hidden=\"true\">\u25B6</span>\n Execute\n </button>\n <button\n (click)=\"host.executeQuery(s, true)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Run it the way the app would - a fresh enough cached response is used instead of a request\"\n >\n Cached\n </button>\n <button\n (click)=\"host.resetQuery(s.query)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Drop this query's state back to idle\"\n >\n Reset\n </button>\n </div>\n }\n @let locatable = host.locatableElement(s.entry);\n <button\n [disabled]=\"!locatable\"\n [class.et-query-devtools-btn--active]=\"host.locateState() === 'located'\"\n [title]=\"\n locatable\n ? 'Scroll to and outline the element this query was created in. That is where it was created, not necessarily where its data is rendered.'\n : 'This query was created outside a component or directive - a root service, a resolver or a guard - so it has no element to point at.'\n \"\n (click)=\"host.locateQuery(s.entry)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n <span aria-hidden=\"true\">\u2316</span>\n @switch (host.locateState()) {\n @case ('located') {\n Created here\n }\n @case ('offscreen') {\n Not on screen\n }\n @default {\n Locate\n }\n }\n </button>\n\n <!-- The tick has to live on the trigger: the menu closes on the pick, so a confirmation inside it\n would be gone before it could be read. -->\n <div class=\"et-query-devtools-menu\" etMenu>\n <button class=\"et-query-devtools-btn\" title=\"Take this request somewhere else\" type=\"button\" etMenuTrigger>\n <span class=\"et-query-devtools-menu-glyph\" aria-hidden=\"true\">{{ copied() ? '\u2713' : '\u29C9' }}</span>\n {{ copied() ? 'Copied' : 'Copy' }}\n <span class=\"et-query-devtools-menu-caret\" aria-hidden=\"true\">\u25BE</span>\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <button (activate)=\"host.copyReport(s.entry, s.query)\" et-menu-item type=\"button\">\n Copy report\n <span class=\"et-query-devtools-menu-item-note\">Everything the panel knows, as markdown</span>\n </button>\n <button (activate)=\"host.copyCurlRequest(s.entry, s.query)\" et-menu-item type=\"button\">\n Copy as cURL\n <span class=\"et-query-devtools-menu-item-note\">URL, resolved headers and body</span>\n </button>\n <button (activate)=\"host.copyInsomniaRequest(s.entry, s.query)\" et-menu-item type=\"button\">\n Copy for Insomnia\n <span class=\"et-query-devtools-menu-item-note\">A collection to import from the clipboard</span>\n </button>\n </et-menu>\n </ng-template>\n </div>\n\n @if (!isGone()) {\n <div class=\"et-query-devtools-menu\" etMenu>\n <button\n [class.et-query-devtools-btn--tampered]=\"host.isTampered(s.entry)\"\n class=\"et-query-devtools-btn\"\n title=\"Make this query answer with something other than what the API sent\"\n type=\"button\"\n etMenuTrigger\n >\n <span class=\"et-query-devtools-menu-glyph\" aria-hidden=\"true\">\u270E</span>\n Override\n <span class=\"et-query-devtools-menu-caret\" aria-hidden=\"true\">\u25BE</span>\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <button (activate)=\"host.openResponseEditor(s.query)\" et-menu-item type=\"button\">Edit response\u2026</button>\n <button (activate)=\"host.openArgsEditor(s)\" et-menu-item type=\"button\">Replay with edited args\u2026</button>\n <et-menu-separator />\n <button (activate)=\"host.forceLoading(s.query)\" et-menu-item type=\"button\">Force loading</button>\n <button (activate)=\"host.forceError(s.query)\" et-menu-item type=\"button\">Force error</button>\n <button (activate)=\"host.forceEmpty(s.query)\" et-menu-item type=\"button\">Force empty</button>\n <et-menu-separator />\n <button (activate)=\"host.clearForced(s.query)\" et-menu-item type=\"button\">Clear forced state</button>\n\n @if (s.entry.overrides; as overrides) {\n <et-menu-separator />\n <et-query-devtools-override-set-menu\n [overrides]=\"overrides\"\n [response]=\"s.query.response()\"\n [source]=\"overrideSetSource()\"\n />\n }\n </et-menu>\n </ng-template>\n </div>\n }\n</div>\n\n@if (!isGone() && host.editorMode() !== 'none') {\n <div class=\"et-query-devtools-editor\">\n @if (host.editorMode() === 'response') {\n <h4 class=\"et-query-devtools-section\">Edit response \u2192 setResponse()</h4>\n <textarea\n #respArea\n [value]=\"host.editorSeed\"\n (input)=\"host.responseDraft.set(respArea.value)\"\n class=\"et-query-devtools-textarea\"\n spellcheck=\"false\"\n ></textarea>\n <div class=\"et-query-devtools-actions\">\n <button (click)=\"host.applyResponse(s.query)\" class=\"et-query-devtools-btn\" type=\"button\">Apply</button>\n <button (click)=\"host.cancelEditor()\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n } @else {\n <h4 class=\"et-query-devtools-section\">Replay with edited args \u2192 execute()</h4>\n <textarea\n #argsArea\n [value]=\"host.editorSeed\"\n (input)=\"host.argsDraft.set(argsArea.value)\"\n class=\"et-query-devtools-textarea\"\n spellcheck=\"false\"\n ></textarea>\n <div class=\"et-query-devtools-actions\">\n <button (click)=\"host.applyArgs(s.query)\" class=\"et-query-devtools-btn\" type=\"button\">Execute</button>\n <button (click)=\"host.cancelEditor()\" class=\"et-query-devtools-btn\" type=\"button\">Cancel</button>\n </div>\n }\n @if (host.editError(); as err) {\n <p class=\"et-query-devtools-editor-error\">{{ err }}</p>\n }\n </div>\n}\n\n@let detailRuns = host.queryRuns(s.entry);\n<nav class=\"et-query-devtools-subtabs\" role=\"tablist\">\n @for (tab of detailTabs; track tab.id) {\n <button\n [class.et-query-devtools-subtab--active]=\"host.detailTab() === tab.id\"\n [attr.aria-selected]=\"host.detailTab() === tab.id\"\n (click)=\"host.detailTab.set(tab.id)\"\n class=\"et-query-devtools-subtab\"\n type=\"button\"\n role=\"tab\"\n >\n {{ tab.label }}\n @if (tab.id === 'history' && detailRuns.length) {\n <span class=\"et-query-devtools-tab-badge\">{{ detailRuns.length }}</span>\n }\n <!-- The error body lives under Data, so the tab has to say so - a failure must not hide\n behind a tab that is not open. -->\n @if (tab.id === 'data' && s.query.error()) {\n <span class=\"et-query-devtools-tab-badge et-query-devtools-tab-badge--error\">!</span>\n }\n </button>\n }\n</nav>\n\n@switch (host.detailTab()) {\n @case ('overview') {\n <dl class=\"et-query-devtools-meta\">\n <dt>Base URL</dt>\n <dd class=\"et-query-devtools-mono\">\n {{ s.entry.meta.clientBaseUrl || s.entry.meta.clientName || '\u2014' }}\n </dd>\n <dt>Route</dt>\n <dd class=\"et-query-devtools-mono\">{{ s.entry.meta.route || '\u2014' }}</dd>\n <dt>Request URL</dt>\n <dd class=\"et-query-devtools-mono\">{{ host.requestUrl(s.query) || '\u2014' }}</dd>\n <dt>Status</dt>\n <dd>{{ host.queryStatus(s.query) }}</dd>\n <dt>Cache key</dt>\n <dd class=\"et-query-devtools-mono\">{{ s.query.id() || '\u2014' }}</dd>\n <dt>Last executed</dt>\n <dd>{{ host.formatTime(s.query.lastTimeExecutedAt()) }}</dd>\n <dt>Triggered by</dt>\n <dd>{{ s.query.triggeredBy() || 'user' }}</dd>\n @let refreshes = host.refreshesFor(s.entry.id);\n @if (refreshes.length) {\n <dt title=\"Invalidations and mutation refreshes that re-executed this query, as far back as the event log goes\">\n Refetched by\n </dt>\n <dd>\n <div class=\"et-query-devtools-chips\">\n @for (refresh of refreshes.slice(0, 5); track refresh.id) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">\n {{ host.formatTime(refresh.timestamp) }} \u00B7 {{ refresh.label }}\n </span>\n }\n @if (refreshes.length > 5) {\n <span class=\"et-query-devtools-hint\">+{{ refreshes.length - 5 }} earlier</span>\n }\n </div>\n </dd>\n }\n @let drivers = host.formsDrivingQuery(s.entry);\n @if (drivers.length) {\n <dt>Args from</dt>\n <dd>\n <div class=\"et-query-devtools-chips\">\n @for (driver of drivers; track driver.id) {\n <button\n (click)=\"host.selectForm(driver.id)\"\n class=\"et-query-devtools-chip et-query-devtools-chip--button\"\n type=\"button\"\n title=\"Open the query form in the Forms tab\"\n >\n {{ driver.meta.name }}\n </button>\n }\n </div>\n </dd>\n }\n @if (s.entry.meta.features?.length) {\n <dt>Features</dt>\n <dd>\n <et-query-devtools-features [features]=\"s.entry.meta.features ?? []\" />\n </dd>\n }\n </dl>\n\n @let activity = host.queryActivity(s.entry);\n <div class=\"et-query-devtools-section-row\">\n <h4 class=\"et-query-devtools-section\">Activity</h4>\n <button\n [disabled]=\"!activity.hasActivity\"\n (click)=\"host.resetStats(s.entry)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Clear the counters and the run history, so the next interaction can be measured on its own\"\n >\n \u27F2 Reset\n </button>\n </div>\n\n <dl class=\"et-query-devtools-stats\">\n <div class=\"et-query-devtools-stat\" title=\"Every time the query ran, cache hits included\">\n <dt>Refreshes</dt>\n <dd>{{ activity.stats.executions }}</dd>\n @if (activity.cacheServed) {\n <span class=\"et-query-devtools-stat-sub\">{{ activity.cacheServed }} without a request</span>\n }\n </div>\n <div class=\"et-query-devtools-stat\" title=\"Executions that started a request, and how they ended\">\n <dt>Requests</dt>\n <dd>{{ activity.stats.requests }}</dd>\n <span class=\"et-query-devtools-stat-sub\">\n {{ activity.stats.responses }} ok\n @if (activity.stats.errors) {\n \u00B7 {{ activity.stats.errors }} failed\n }\n </span>\n </div>\n <div\n class=\"et-query-devtools-stat\"\n title=\"Total response payload received. Sizes without a content-length header are measured from the decoded body, so they ignore compression.\"\n >\n <dt>Received</dt>\n <dd>{{ host.formatTransferred(activity.stats.receivedBytes, activity.stats.hasEstimatedBytes) }}</dd>\n @if (activity.avgResponseBytes !== null) {\n <span class=\"et-query-devtools-stat-sub\">{{ host.formatBytes(activity.avgResponseBytes) }} per response</span>\n }\n </div>\n @if (activity.stats.sentBytes) {\n <div class=\"et-query-devtools-stat\" title=\"Total request body sent (headers not counted)\">\n <dt>Sent</dt>\n <dd>{{ host.formatTransferred(activity.stats.sentBytes, activity.stats.hasEstimatedBytes) }}</dd>\n </div>\n }\n @if (activity.stats.retries) {\n <div\n class=\"et-query-devtools-stat\"\n title=\"Attempts the retry policy added on top of the first one. Which run they belong to is under History.\"\n >\n <dt>Retries</dt>\n <dd>{{ activity.stats.retries }}</dd>\n </div>\n }\n <div class=\"et-query-devtools-stat\" title=\"Measured from the execution that preceded each response\">\n <dt>Duration</dt>\n <dd>{{ host.formatDuration(activity.stats.lastDurationMs) }}</dd>\n @if (activity.avgDurationMs !== null) {\n <span class=\"et-query-devtools-stat-sub\">{{ host.formatDuration(activity.avgDurationMs) }} on average</span>\n }\n </div>\n <div class=\"et-query-devtools-stat\">\n <dt>Last response</dt>\n <dd class=\"et-query-devtools-stat-value--sm\">{{ host.formatTime(activity.stats.lastResponseAt) }}</dd>\n @if (activity.stats.firstExecutedAt !== null) {\n <span class=\"et-query-devtools-stat-sub\"\n >first ran {{ host.formatTime(activity.stats.firstExecutedAt) }}</span\n >\n }\n </div>\n </dl>\n }\n @case ('history') {\n <div class=\"et-query-devtools-section-row\">\n <h4 class=\"et-query-devtools-section\">Runs</h4>\n <button\n [disabled]=\"!detailRuns.length\"\n (click)=\"host.resetStats(s.entry)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Clear the run history and the counters, so the next interaction can be measured on its own\"\n >\n \u27F2 Reset\n </button>\n </div>\n @if (detailRuns.length) {\n <table class=\"et-query-devtools-table\">\n <thead>\n <tr>\n <th>Run</th>\n <th>Started</th>\n <th>Duration</th>\n <th>Received</th>\n <th>Body</th>\n </tr>\n </thead>\n <tbody>\n @for (run of runSections().listed.concat(foldedRunsOpen() ? runSections().folded : []); track run.index) {\n <tr [attr.data-status]=\"run.status === 'error' ? 'error' : null\">\n <td>\n <span class=\"et-query-devtools-run-cell\">\n <span\n [attr.data-status]=\"host.runStatus(run)\"\n [title]=\"run.status\"\n class=\"et-query-devtools-status\"\n ></span>\n #{{ run.index }}\n @if (run.attempts > 1) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"Took {{\n run.attempts\n }} attempts - the duration next to it covers every one of them, backoff included\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n {{ run.attempts }}\n </span>\n }\n @if (!run.didRequest) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"Arrived without a request of this query's own - a poll, another consumer of the same cache entry, or another tab\"\n >\n shared\n </span>\n }\n @if (run.error; as error) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"{{ error.messages.join(' \u00B7 ') || 'The failure carried no message' }}\"\n >\n {{ error.code || 'failed' }}\n </span>\n }\n </span>\n </td>\n <td>{{ host.formatTime(run.startedAt) }}</td>\n <td>\n @if (run.endedAt === null) {\n in flight\n } @else {\n {{ host.formatDuration(run.endedAt - run.startedAt) }}\n }\n </td>\n <td>{{ run.receivedBytes ? host.formatBytes(run.receivedBytes) : '\u2014' }}</td>\n <td>\n <span class=\"et-query-devtools-cell-actions\">\n @if (host.canDiffRun(s.entry, run)) {\n @let role = host.diffRunRole(s.entry, run);\n @let armed = role ? diffRoles[role] : null;\n <button\n [class.et-query-devtools-btn--active]=\"!!armed\"\n [title]=\"\n armed?.title ?? 'Compare this response against another run - click a second row to pick which'\n \"\n (click)=\"host.toggleRunDiff(s.entry, run)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n {{ armed?.label ?? 'Diff' }}\n </button>\n } @else if (run.error?.hasBody) {\n <button\n [class.et-query-devtools-btn--active]=\"host.errorRunIndex() === run.index\"\n (click)=\"host.toggleRunError(run)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Show the body this failure came with\"\n >\n Error\n </button>\n } @else if (run.hasResponse) {\n <span class=\"et-query-devtools-count\">the only body held</span>\n } @else if (run.status === 'success' || run.error) {\n <span\n class=\"et-query-devtools-count\"\n title=\"Only the newest {{\n host.retainedResponseCount()\n }} bodies of a query are kept, so this run can no longer be an end of a diff\"\n >\n body no longer held\n </span>\n } @else {\n <span class=\"et-query-devtools-count\">\u2014</span>\n }\n </span>\n </td>\n </tr>\n }\n </tbody>\n </table>\n @let foldedRuns = runSections().folded.length;\n @if (foldedRuns) {\n <button\n [attr.aria-expanded]=\"foldedRunsOpen()\"\n (click)=\"foldedRunsOpen.set(!foldedRunsOpen())\"\n class=\"et-query-devtools-btn et-query-devtools-runs-fold\"\n type=\"button\"\n title=\"Only the newest {{\n host.retainedResponseCount()\n }} bodies of a query are kept, so none of these older runs can be an end of a diff\"\n >\n <span aria-hidden=\"true\">{{ foldedRunsOpen() ? '\u25BE' : '\u25B8' }}</span>\n {{ foldedRunsOpen() ? 'Hide' : 'Show' }} {{ foldedRuns }} older run{{ foldedRuns === 1 ? '' : 's' }} with no\n body\n </button>\n }\n\n @if (host.pickedRunError(s.entry); as failure) {\n <div class=\"et-query-devtools-section-row\">\n <h5 class=\"et-query-devtools-section\">\n Error body \u00B7 run #{{ failure.run.index }} \u00B7 {{ failure.error.code || 'no status' }}\n </h5>\n </div>\n @for (message of failure.error.messages; track message) {\n <p class=\"et-query-devtools-count\">{{ message }}</p>\n }\n <et-query-devtools-json\n [value]=\"failure.error.body\"\n [search]=\"host.jsonSearchTerm()\"\n [expandedPaths]=\"host.jsonExpandedPaths()\"\n [collapsedPaths]=\"host.jsonCollapsedPaths()\"\n [toggleFn]=\"host.toggleJsonPath\"\n path=\"runError\"\n />\n }\n\n @if (host.responseDiff(s.entry); as comparison) {\n <div class=\"et-query-devtools-section-row\">\n <h5 class=\"et-query-devtools-section\">\n Response diff \u00B7 run #{{ comparison.before.index }} \u2192 #{{ comparison.after.index }}\n </h5>\n <span class=\"et-query-devtools-diff-step\">\n <button\n [disabled]=\"!host.canStepRunDiff(s.entry, true)\"\n (click)=\"host.stepRunDiff(s.entry, true)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Compare the pair one run older, without going back to the table\"\n >\n \u25C2 Older\n </button>\n <button\n [disabled]=\"!host.canStepRunDiff(s.entry, false)\"\n (click)=\"host.stepRunDiff(s.entry, false)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Compare the pair one run newer, without going back to the table\"\n >\n Newer \u25B8\n </button>\n </span>\n <span class=\"et-query-devtools-count\">\n @if (host.diffBaseRunIndex() === null) {\n <span\n title=\"Nothing is picked to compare against, so this is the nearest older run that still holds a body - click another run's Diff to pick one\"\n >\n nearest older\n </span>\n \u00B7\n }\n @if (comparison.diff.entries.length) {\n {{ comparison.diff.entries.length }} path{{ comparison.diff.entries.length === 1 ? '' : 's' }}\n @if (comparison.diff.truncated) {\n (capped)\n }\n } @else {\n identical\n }\n </span>\n </div>\n @if (comparison.diff.entries.length) {\n <table class=\"et-query-devtools-table et-query-devtools-diff\">\n <thead>\n <tr>\n <th>Path</th>\n <th>Before</th>\n <th>After</th>\n </tr>\n </thead>\n <tbody>\n @for (change of comparison.diff.entries; track change.path) {\n <tr [attr.data-kind]=\"change.kind\">\n <td class=\"et-query-devtools-mono\">{{ change.path }}</td>\n <td class=\"et-query-devtools-mono et-query-devtools-diff-before\">\n {{ change.kind === 'added' ? '\u2014' : host.inlineValue(change.before) }}\n </td>\n <td class=\"et-query-devtools-mono et-query-devtools-diff-after\">\n {{ change.kind === 'removed' ? '\u2014' : host.inlineValue(change.after) }}\n </td>\n </tr>\n }\n </tbody>\n </table>\n } @else {\n <p class=\"et-query-devtools-empty\">The two responses are identical.</p>\n }\n }\n } @else {\n <p class=\"et-query-devtools-empty\">No runs recorded yet.</p>\n }\n }\n @case ('data') {\n @if (s.entry.meta.gqlQuery; as gqlQuery) {\n <div class=\"et-query-devtools-section-row\">\n <h5 class=\"et-query-devtools-section\">GraphQL document</h5>\n <button\n (click)=\"host.copyGqlDocument(gqlQuery)\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Copy the GraphQL document\"\n >\n {{ host.copiedGql() ? '\u2713 Copied' : '\u29C9 Copy' }}\n </button>\n </div>\n <pre class=\"et-query-devtools-code\">{{ host.gqlDocument(gqlQuery) }}</pre>\n }\n\n <div class=\"et-query-devtools-section-row\">\n <h4 class=\"et-query-devtools-section\">Value explorer</h4>\n <input\n #jsonSearchInput\n [value]=\"host.jsonSearch()\"\n (input)=\"host.jsonSearch.set(jsonSearchInput.value)\"\n class=\"et-query-devtools-search\"\n type=\"search\"\n placeholder=\"filter keys / values\u2026\"\n />\n </div>\n\n <h5 class=\"et-query-devtools-section\">Args</h5>\n <et-query-devtools-json\n [value]=\"host.queryArgs(s.query)\"\n [search]=\"host.jsonSearchTerm()\"\n [expandedPaths]=\"host.jsonExpandedPaths()\"\n [collapsedPaths]=\"host.jsonCollapsedPaths()\"\n [toggleFn]=\"host.toggleJsonPath\"\n path=\"args\"\n />\n\n @if (s.query.error(); as error) {\n <h5 class=\"et-query-devtools-section\">Error ({{ error.raw.status }})</h5>\n <et-query-devtools-json\n [value]=\"error.isList ? error.errors : error.error\"\n [search]=\"host.jsonSearchTerm()\"\n [expandedPaths]=\"host.jsonExpandedPaths()\"\n [collapsedPaths]=\"host.jsonCollapsedPaths()\"\n [toggleFn]=\"host.toggleJsonPath\"\n path=\"error\"\n />\n } @else {\n <div class=\"et-query-devtools-section-row\">\n <h5 class=\"et-query-devtools-section\">Response</h5>\n @if (s.entry.overrides?.list()?.length || host.overridesPersist()) {\n <button\n [class.et-query-devtools-btn--active]=\"host.overridesPersist()\"\n [attr.aria-pressed]=\"host.overridesPersist()\"\n (click)=\"host.toggleOverridesPersist()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"{{\n host.overridesPersist()\n ? 'Panel-wide: every armed response override is kept in ' +\n host.overridesScopeLabel() +\n ' and replayed on the next load.'\n : 'Panel-wide: keep every armed response override and replay it on the next load.'\n }} Settings \u2699 chooses the scope. Armed faults are never kept.\"\n >\n Keep across reloads\n </button>\n }\n @if (s.entry.overrides?.list()?.length) {\n <button (click)=\"s.entry.overrides?.clearAll()\" class=\"et-query-devtools-btn\" type=\"button\">\n Reset all overrides ({{ s.entry.overrides?.list()?.length }})\n </button>\n }\n </div>\n <et-query-devtools-json\n [value]=\"s.query.response()\"\n [search]=\"host.jsonSearchTerm()\"\n [expandedPaths]=\"host.jsonExpandedPaths()\"\n [collapsedPaths]=\"host.jsonCollapsedPaths()\"\n [toggleFn]=\"host.toggleJsonPath\"\n [overrides]=\"s.entry.overrides ?? null\"\n path=\"response\"\n />\n }\n }\n}\n", styles: ["@layer components{.et-query-devtools-detail .et-query-devtools-detail-head,.et-query-devtools-drawer .et-query-devtools-detail-head{padding-block-end:.8rem;border-block-end:1px solid var(--_et-qdt-border)}.et-query-devtools-gone-note{margin:0 0 .8rem;color:var(--_et-qdt-muted);font-size:1.1rem}.et-query-devtools-detail-head{display:flex;align-items:center;gap:.8rem;margin-block-end:.8rem}.et-query-devtools-detail-head .et-query-devtools-route{flex:0 1 auto}.et-query-devtools-route-copy{appearance:none;margin-inline-end:auto;display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;inline-size:2.2rem;block-size:2.2rem;margin-inline-start:-.4rem;border:none;border-radius:.3rem;background:transparent;color:var(--_et-qdt-subtle);cursor:pointer;font:inherit;font-size:1.3rem;line-height:1}@media(hover:hover){.et-query-devtools-route-copy:hover:not(.et-query-devtools-route-copy--copied){background:var(--_et-qdt-active);color:var(--_et-qdt-fg)}}.et-query-devtools-route-copy--copied{color:var(--_et-qdt-success)}.et-query-devtools-live{display:flex;flex-wrap:wrap;align-items:center;gap:.6rem;margin-block-end:.8rem}.et-query-devtools-live-note{color:var(--_et-qdt-muted);font-size:1.15rem}.et-query-devtools-progress{display:block;flex:1 1 12rem;min-inline-size:8rem;max-inline-size:24rem;block-size:.5rem;border-radius:999px;overflow:hidden;background:var(--_et-qdt-border)}.et-query-devtools-progress-bar{display:block;block-size:100%;border-radius:inherit;background:var(--_et-qdt-accent)}.et-query-devtools-actions{display:flex;flex-wrap:wrap;align-items:center;gap:.4rem;margin-block-end:.8rem}.et-query-devtools-actions>.et-query-devtools-menu+.et-query-devtools-menu{margin-inline-start:.5rem;padding-inline-start:.9rem;border-inline-start:1px solid var(--_et-qdt-border)}.et-query-devtools-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(12rem,1fr));gap:.6rem;margin:0 0 1rem}.et-query-devtools-stat-value--sm{font-size:1.2rem}.et-query-devtools-code{margin:0 0 .8rem;padding:.8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);overflow:auto;white-space:pre;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:1.15rem;max-block-size:20rem}.et-query-devtools-diff-step{display:flex;gap:.4rem;margin-inline-start:auto}.et-query-devtools-runs-fold{margin-block-start:.4rem;color:var(--_et-qdt-muted)}.et-query-devtools-textarea{inline-size:100%;min-block-size:12rem;resize:vertical;background:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);color:var(--_et-qdt-fg);border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.6rem;font-family:inherit;font-size:1.2rem}.et-query-devtools-subtabs{display:flex;flex-wrap:wrap;gap:.2rem;border-block-end:1px solid var(--_et-qdt-border);margin-block:.8rem}.et-query-devtools-subtabs+.et-query-devtools-section-row{padding-block-start:0;border-block-start:0}.et-query-devtools-subtab{appearance:none;display:inline-flex;align-items:center;gap:.4rem;border:0;border-block-end:2px solid transparent;background:transparent;color:var(--_et-qdt-muted);font:inherit;font-size:1.15rem;padding:.4rem .6rem;margin-block-end:-1px;cursor:pointer}@media(hover:hover){.et-query-devtools-subtab:hover{color:var(--_et-qdt-fg)}}.et-query-devtools-subtab--active{color:var(--_et-qdt-fg);border-block-end-color:var(--_et-qdt-accent)}.et-query-devtools-run-cell{display:inline-flex;align-items:center;gap:.5rem}.et-query-devtools-diff td{max-inline-size:28rem;overflow-wrap:anywhere}.et-query-devtools-diff-before{color:var(--_et-qdt-muted)}.et-query-devtools-diff tr[data-kind=removed] .et-query-devtools-diff-before{color:var(--_et-qdt-error)}.et-query-devtools-diff tr[data-kind=added] .et-query-devtools-diff-after{color:var(--_et-qdt-success)}.et-query-devtools-diff tr[data-kind=changed] .et-query-devtools-diff-after{color:var(--_et-qdt-accent)}}\n"] }]
3077
- }], propDecorators: { sel: [{ type: i0.Input, args: [{ isSignal: true, alias: "sel", required: true }] }] } });
3078
-
3079
- /**
3080
- * The split-view drawer every two-pane tab (stacks/sequences/forms/timeline) opens a query in - the
3081
- * resize handle, the `<aside>` chrome and the close button around a `<et-query-devtools-detail>`. The
3082
- * Queries tab renders that same detail component inline instead, without this wrapper, since it has no
3083
- * separate list/drawer split to close.
3084
- */
3085
- class QueryDevtoolsDrawerComponent {
3086
- constructor() {
3087
- this.host = injectQueryDevtoolsHost();
3088
- this.sel = input(null, /* @ts-ignore */
3089
- ...(ngDevMode ? [{ debugName: "sel" }] : /* istanbul ignore next */ []));
3090
- this.container = input.required(/* @ts-ignore */
3091
- ...(ngDevMode ? [{ debugName: "container" }] : /* istanbul ignore next */ []));
3092
- this.dismiss = output();
3090
+ /**
3091
+ * Arms one numeric field of a client's fault from an input's raw value. Clamped here rather than left
3092
+ * to the input's `min`/`max`, which a typed-in (or pasted) value ignores.
3093
+ */
3094
+ armFaultValue(options) {
3095
+ const { clientName, field, value } = options;
3096
+ const parsed = clamp(Math.trunc(Number(value) || 0), 0, this.FAULT_LIMITS[field]);
3097
+ setQueryDevtoolsFault({ clientName, patch: { [field]: parsed } });
3093
3098
  }
3094
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsDrawerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3095
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsDrawerComponent, isStandalone: true, selector: "et-query-devtools-drawer", inputs: { sel: { classPropertyName: "sel", publicName: "sel", isSignal: true, isRequired: false, transformFunction: null }, container: { classPropertyName: "container", publicName: "container", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { dismiss: "dismiss" }, ngImport: i0, template: "@if (sel(); as selected) {\n <div\n (pointerdown)=\"host.startPaneResize($event, { pane: 'drawer', container: container() })\"\n (dblclick)=\"host.resetPaneSize('drawer')\"\n class=\"et-query-devtools-pane-resize\"\n title=\"Drag to resize, double-click to reset\"\n aria-hidden=\"true\"\n ></div>\n\n <aside class=\"et-query-devtools-drawer\">\n <div class=\"et-query-devtools-drawer-head\">\n <span>Query detail</span>\n <button (click)=\"dismiss.emit()\" class=\"et-query-devtools-close\" type=\"button\" title=\"Close detail\">\u2715</button>\n </div>\n <et-query-devtools-detail [sel]=\"selected\" />\n </aside>\n}\n", styles: ["@layer components{.et-query-devtools-drawer{flex:0 0 var(--_et-qdt-drawer-width, 42%);min-inline-size:var(--_et-qdt-drawer-width, 26rem);max-inline-size:var(--_et-qdt-drawer-width, 48rem);overflow-y:auto;padding:0 1rem 1rem;border-inline-start:1px solid var(--_et-qdt-border)}.et-query-devtools-drawer-head{display:flex;align-items:center;justify-content:space-between;gap:.8rem;padding:.8rem 0;position:sticky;inset-block-start:0;background:var(--_et-qdt-bg);color:var(--_et-qdt-muted);border-block-end:1px solid var(--_et-qdt-border);margin-block-end:.8rem}}\n"], dependencies: [{ kind: "component", type: QueryDevtoolsDetailComponent, selector: "et-query-devtools-detail", inputs: ["sel"] }], encapsulation: i0.ViewEncapsulation.None }); }
3099
+ armFaultStatus(options) {
3100
+ setQueryDevtoolsFault({ clientName: options.clientName, patch: { status: Number(options.value) } });
3101
+ }
3102
+ /** Whether the default retry policy retries the status a client is armed to fail with. */
3103
+ isFaultStatusRetryable(status) {
3104
+ return QUERY_DEVTOOLS_FAULT_STATUSES.find((entry) => entry.status === status)?.retryable ?? false;
3105
+ }
3106
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsFaultsTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3107
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsFaultsTabComponent, isStandalone: true, selector: "et-query-devtools-faults-tab", ngImport: i0, template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar\">\n <p class=\"et-query-devtools-hint\">\n Faults are injected into the request pipeline, so retries, error handling features and the cache see them exactly\n as they would see a real one. Unlike the rest of the panel, what you arm here is not persisted - a page reload\n disarms every client.\n </p>\n </div>\n\n @for (client of host.faultClients(); track client.name) {\n @let fault = client.fault;\n <div [class.et-query-devtools-card--armed]=\"client.armed\" class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong class=\"et-query-devtools-mono\">{{ client.name }}</strong>\n <span class=\"et-query-devtools-url\">{{ client.baseUrl }}</span>\n @if (client.armed) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--error\">armed</span>\n <button (click)=\"CLEAR_FAULTS(client.name)\" class=\"et-query-devtools-btn\" type=\"button\">Disarm</button>\n }\n </div>\n\n <div class=\"et-query-devtools-faults\">\n <label class=\"et-query-devtools-field\">\n <span>Latency</span>\n <input\n #latencyInput\n [value]=\"fault.latencyMs\"\n (change)=\"armFaultValue({ clientName: client.name, field: 'latencyMs', value: latencyInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"60000\"\n step=\"100\"\n />\n <small>ms before every attempt starts</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Fail next</span>\n <input\n #failNextInput\n [value]=\"fault.failNext\"\n (change)=\"armFaultValue({ clientName: client.name, field: 'failNext', value: failNextInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"99\"\n step=\"1\"\n />\n <small>attempts, counted down as they are spent</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Fail rate</span>\n <input\n #failRateInput\n [value]=\"fault.failRate\"\n (change)=\"armFaultValue({ clientName: client.name, field: 'failRate', value: failRateInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"100\"\n step=\"5\"\n />\n <small>% of attempts, rolled per attempt</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Status</span>\n <select\n #statusSelect\n (change)=\"armFaultStatus({ clientName: client.name, value: statusSelect.value })\"\n class=\"et-query-devtools-select\"\n >\n @for (option of FAULT_STATUSES; track option.status) {\n <!-- `selected` on the option, not `value` on the select: the select's value is\n applied before its options exist, so it would fall back to the first one. -->\n <option [value]=\"option.status\" [selected]=\"option.status === fault.status\">\n {{ option.status }} \u00B7 {{ option.label }}\n </option>\n }\n </select>\n <small>\n @if (isFaultStatusRetryable(fault.status)) {\n the default retry policy retries this\n } @else {\n the default retry policy gives up on this\n }\n </small>\n </label>\n </div>\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No query clients registered yet.</p>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-faults{display:grid;grid-template-columns:repeat(auto-fit,minmax(20rem,1fr));gap:.8rem 1.6rem}.et-query-devtools-faults .et-query-devtools-field{display:grid;grid-template-columns:8rem minmax(0,1fr);align-items:baseline;row-gap:.2rem;color:var(--_et-qdt-fg)}.et-query-devtools-faults .et-query-devtools-field small{grid-column:2;color:var(--_et-qdt-subtle);font-size:1.1rem}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
3096
3108
  }
3097
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsDrawerComponent, decorators: [{
3109
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsFaultsTabComponent, decorators: [{
3098
3110
  type: Component,
3099
- args: [{ selector: 'et-query-devtools-drawer', encapsulation: ViewEncapsulation.None, imports: [QueryDevtoolsDetailComponent], template: "@if (sel(); as selected) {\n <div\n (pointerdown)=\"host.startPaneResize($event, { pane: 'drawer', container: container() })\"\n (dblclick)=\"host.resetPaneSize('drawer')\"\n class=\"et-query-devtools-pane-resize\"\n title=\"Drag to resize, double-click to reset\"\n aria-hidden=\"true\"\n ></div>\n\n <aside class=\"et-query-devtools-drawer\">\n <div class=\"et-query-devtools-drawer-head\">\n <span>Query detail</span>\n <button (click)=\"dismiss.emit()\" class=\"et-query-devtools-close\" type=\"button\" title=\"Close detail\">\u2715</button>\n </div>\n <et-query-devtools-detail [sel]=\"selected\" />\n </aside>\n}\n", styles: ["@layer components{.et-query-devtools-drawer{flex:0 0 var(--_et-qdt-drawer-width, 42%);min-inline-size:var(--_et-qdt-drawer-width, 26rem);max-inline-size:var(--_et-qdt-drawer-width, 48rem);overflow-y:auto;padding:0 1rem 1rem;border-inline-start:1px solid var(--_et-qdt-border)}.et-query-devtools-drawer-head{display:flex;align-items:center;justify-content:space-between;gap:.8rem;padding:.8rem 0;position:sticky;inset-block-start:0;background:var(--_et-qdt-bg);color:var(--_et-qdt-muted);border-block-end:1px solid var(--_et-qdt-border);margin-block-end:.8rem}}\n"] }]
3100
- }], propDecorators: { sel: [{ type: i0.Input, args: [{ isSignal: true, alias: "sel", required: false }] }], container: [{ type: i0.Input, args: [{ isSignal: true, alias: "container", required: true }] }], dismiss: [{ type: i0.Output, args: ["dismiss"] }] } });
3111
+ args: [{ selector: 'et-query-devtools-faults-tab', encapsulation: ViewEncapsulation.None, template: "<div class=\"et-query-devtools-scroll\">\n <div class=\"et-query-devtools-toolbar\">\n <p class=\"et-query-devtools-hint\">\n Faults are injected into the request pipeline, so retries, error handling features and the cache see them exactly\n as they would see a real one. Unlike the rest of the panel, what you arm here is not persisted - a page reload\n disarms every client.\n </p>\n </div>\n\n @for (client of host.faultClients(); track client.name) {\n @let fault = client.fault;\n <div [class.et-query-devtools-card--armed]=\"client.armed\" class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <strong class=\"et-query-devtools-mono\">{{ client.name }}</strong>\n <span class=\"et-query-devtools-url\">{{ client.baseUrl }}</span>\n @if (client.armed) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--error\">armed</span>\n <button (click)=\"CLEAR_FAULTS(client.name)\" class=\"et-query-devtools-btn\" type=\"button\">Disarm</button>\n }\n </div>\n\n <div class=\"et-query-devtools-faults\">\n <label class=\"et-query-devtools-field\">\n <span>Latency</span>\n <input\n #latencyInput\n [value]=\"fault.latencyMs\"\n (change)=\"armFaultValue({ clientName: client.name, field: 'latencyMs', value: latencyInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"60000\"\n step=\"100\"\n />\n <small>ms before every attempt starts</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Fail next</span>\n <input\n #failNextInput\n [value]=\"fault.failNext\"\n (change)=\"armFaultValue({ clientName: client.name, field: 'failNext', value: failNextInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"99\"\n step=\"1\"\n />\n <small>attempts, counted down as they are spent</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Fail rate</span>\n <input\n #failRateInput\n [value]=\"fault.failRate\"\n (change)=\"armFaultValue({ clientName: client.name, field: 'failRate', value: failRateInput.value })\"\n class=\"et-query-devtools-input et-query-devtools-input--number\"\n type=\"number\"\n min=\"0\"\n max=\"100\"\n step=\"5\"\n />\n <small>% of attempts, rolled per attempt</small>\n </label>\n\n <label class=\"et-query-devtools-field\">\n <span>Status</span>\n <select\n #statusSelect\n (change)=\"armFaultStatus({ clientName: client.name, value: statusSelect.value })\"\n class=\"et-query-devtools-select\"\n >\n @for (option of FAULT_STATUSES; track option.status) {\n <!-- `selected` on the option, not `value` on the select: the select's value is\n applied before its options exist, so it would fall back to the first one. -->\n <option [value]=\"option.status\" [selected]=\"option.status === fault.status\">\n {{ option.status }} \u00B7 {{ option.label }}\n </option>\n }\n </select>\n <small>\n @if (isFaultStatusRetryable(fault.status)) {\n the default retry policy retries this\n } @else {\n the default retry policy gives up on this\n }\n </small>\n </label>\n </div>\n </div>\n } @empty {\n <p class=\"et-query-devtools-empty\">No query clients registered yet.</p>\n }\n</div>\n", styles: ["@layer components{.et-query-devtools-faults{display:grid;grid-template-columns:repeat(auto-fit,minmax(20rem,1fr));gap:.8rem 1.6rem}.et-query-devtools-faults .et-query-devtools-field{display:grid;grid-template-columns:8rem minmax(0,1fr);align-items:baseline;row-gap:.2rem;color:var(--_et-qdt-fg)}.et-query-devtools-faults .et-query-devtools-field small{grid-column:2;color:var(--_et-qdt-subtle);font-size:1.1rem}}\n"] }]
3112
+ }] });
3101
3113
 
3102
3114
  /** The Forms tab: registered `defineQueryForm()` handles, each expandable to its field table. */
3103
3115
  class QueryDevtoolsFormsTabComponent {
@@ -3193,7 +3205,7 @@ const summarizeLocks = (options) => {
3193
3205
 
3194
3206
  // Generated from package.json by tools/scripts/generate-version.js - do not edit.
3195
3207
  /** The version of `@ethlete/query-devtools` this build was cut from. */
3196
- const QUERY_DEVTOOLS_VERSION = '1.0.0-next.2';
3208
+ const QUERY_DEVTOOLS_VERSION = '1.0.0-next.4';
3197
3209
 
3198
3210
  /* eslint-disable @typescript-eslint/naming-convention -- the `__export_*` keys are Insomnia's file format */
3199
3211
  const WORKSPACE_ID = 'wrk_ethlete_query_devtools';
@@ -3550,7 +3562,7 @@ class QueryDevtoolsQueriesTabComponent {
3550
3562
  * {@link groupKey} is - so `/post/1` and `/post/2` are two leaves under `post` rather than one, which
3551
3563
  * is what makes the tree answer "which ids did this screen fetch".
3552
3564
  */
3553
- this.queryTree = computed(() => flattenQueryPathTree(buildQueryPathTree(this.queryGroups().map((group) => ({ path: queryRoutePathSegments(this.groupRoute(group.head)), item: group }))), {
3565
+ this.queryTree = computed(() => flattenQueryPathTree(buildQueryPathTree(this.queryGroups().map((group) => ({ path: queryRoutePathSegments(this.groupPathRoute(group)), item: group }))), {
3554
3566
  isCollapsed: (key) => this.host.collapsedQueryPaths().has(key),
3555
3567
  keyOf: (group) => group.key,
3556
3568
  }), /* @ts-ignore */
@@ -3628,6 +3640,13 @@ class QueryDevtoolsQueriesTabComponent {
3628
3640
  consumerLabel(item) {
3629
3641
  return this.host.locatableElement(item.entry)?.localName ?? null;
3630
3642
  }
3643
+ /**
3644
+ * The batch that created a fold's queries, or `null` for a fold of ordinary ones. Every query in a
3645
+ * batch fold has the same one, so the head answers for all of them.
3646
+ */
3647
+ batchEntryOf(group) {
3648
+ return this.host.batchOf(group.head.entry);
3649
+ }
3631
3650
  /** The worst state in a folded group, so a collapsed row cannot hide the one instance that is failing. */
3632
3651
  groupStatus(group) {
3633
3652
  const statuses = group.items.map((item) => this.host.queryStatus(item.query));
@@ -3668,6 +3687,11 @@ class QueryDevtoolsQueriesTabComponent {
3668
3687
  * Gone chip asks for it - or the detail is showing it, so a query dying under the detail keeps its row.
3669
3688
  */
3670
3689
  listsTombstone(entry, facets) {
3690
+ // A batch's settled items are the exception: the batch destroys each query the moment its item lands,
3691
+ // so every row of a finished run is a tombstone and the fold would vanish the moment it completed.
3692
+ // What the row stands for is the batch - which is still registered - not a query that is gone.
3693
+ if (this.host.batchOf(entry))
3694
+ return true;
3671
3695
  return facets.has('gone') || (!facets.size && entry.id === this.host.selectedQueryId());
3672
3696
  }
3673
3697
  /**
@@ -3713,8 +3737,23 @@ class QueryDevtoolsQueriesTabComponent {
3713
3737
  .map((segment) => segment.text)
3714
3738
  .join('');
3715
3739
  }
3740
+ /**
3741
+ * The route a whole fold sits under in the path tree. A batch's is its route *template*, not the
3742
+ * resolved route of whichever item happens to be the head - the items differ in every path param, so
3743
+ * the head's route would put the run under a different leaf on every re-render.
3744
+ */
3745
+ groupPathRoute(group) {
3746
+ const batch = this.batchEntryOf(group);
3747
+ return batch ? (batch.meta.route ?? '') : this.groupRoute(group.head);
3748
+ }
3716
3749
  /** What makes two rows indistinguishable on screen. A tombstone never folds into a live query. */
3717
3750
  groupKey(item) {
3751
+ const batch = this.host.batchOf(item.entry);
3752
+ // A batch's items fold by the run that created them rather than by what the row shows: they differ
3753
+ // in every path param, so nothing else would fold them, and a run only ever has `concurrency` of
3754
+ // them alive at a time - so an unfolded list is a handful of rows churning through the same slot.
3755
+ if (batch)
3756
+ return `batch|${batch.id}`;
3718
3757
  return `${item.entry.destroyedAt ? 'gone' : 'live'}|${item.entry.meta.method ?? ''}|${this.groupRoute(item)}`;
3719
3758
  }
3720
3759
  /**
@@ -3734,25 +3773,79 @@ class QueryDevtoolsQueriesTabComponent {
3734
3773
  return parts.join(' ').toLowerCase();
3735
3774
  }
3736
3775
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsQueriesTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3737
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsQueriesTabComponent, isStandalone: true, selector: "et-query-devtools-queries-tab", ngImport: i0, template: "<div #panes class=\"et-query-devtools-master-detail\">\n <div class=\"et-query-devtools-list\">\n <div [class.et-query-devtools-toolbar--filtered]=\"!!host.inspectFilterIds()\" class=\"et-query-devtools-toolbar\">\n <!-- Search leads: it is what the list is narrowed with most of the time. The scope controls sit\n after it, and the count reads as the search's result rather than as one more control. -->\n <div class=\"et-query-devtools-toolbar-row\">\n <input\n #queryFilterInput\n [value]=\"host.queryFilter()\"\n (input)=\"host.queryFilter.set(queryFilterInput.value)\"\n class=\"et-query-devtools-search\"\n type=\"search\"\n placeholder=\"filter method / route / url\u2026\"\n aria-label=\"Filter queries\"\n />\n @if (host.inspectFilterIds(); as ids) {\n <span class=\"et-query-devtools-filter-chip\">\n <span class=\"et-query-devtools-filter-icon\" aria-hidden=\"true\">\u2316</span>\n Inspected element\n <span class=\"et-query-devtools-filter-count\">{{ ids.length }}</span>\n </span>\n <button\n (click)=\"host.clearInspectFilter()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the inspection filter and show all queries\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n Clear\n </button>\n } @else {\n <select\n #clientSelect\n [value]=\"host.selectedClientName() ?? ''\"\n (change)=\"host.selectClient(clientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n aria-label=\"Client\"\n >\n <option value=\"\">All clients</option>\n @for (name of host.clientNames(); track name) {\n <option [value]=\"name\">{{ name }}</option>\n }\n </select>\n }\n <span class=\"et-query-devtools-count\">\n @if (isQueryListNarrowed()) {\n {{ filteredQueries().length }} of {{ scopedQueryCount() }}\n } @else {\n {{ scopedQueryCount() }}\n }\n </span>\n <button\n [disabled]=\"!filteredQueries().length\"\n (click)=\"downloadInsomniaCollection()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Download the listed queries as an Insomnia collection, with the auth provider's token refresh\"\n >\n <span aria-hidden=\"true\">\u2913</span>\n Insomnia\n </button>\n </div>\n\n <div class=\"et-query-devtools-toolbar-row\">\n @let recentFirst = host.queryRecentFirst();\n <div class=\"et-query-devtools-btn-group\">\n <button\n [title]=\"\n (recentFirst ? 'Newest run first.' : 'Oldest run first.') +\n ' Click to reverse. Queries that have never run stay at the bottom either way.'\n \"\n (click)=\"host.queryRecentFirst.set(!recentFirst)\"\n class=\"et-query-devtools-btn et-query-devtools-sort\"\n type=\"button\"\n >\n recent <span aria-hidden=\"true\">{{ recentFirst ? '\u2193' : '\u2191' }}</span>\n </button>\n @let treeView = host.queryTreeView();\n <button\n [class.et-query-devtools-btn--active]=\"treeView\"\n [attr.aria-pressed]=\"treeView\"\n [title]=\"\n treeView\n ? 'Grouped by route path. Click for the flat list.'\n : 'Flat list. Click to group by route path - segments nothing branches off are folded into one row.'\n \"\n (click)=\"host.queryTreeView.set(!treeView)\"\n class=\"et-query-devtools-btn et-query-devtools-sort\"\n type=\"button\"\n >\n <span aria-hidden=\"true\">\u2442</span> tree\n </button>\n </div>\n <!-- Only the statuses something is actually in. Five chips, four of them reading `0`, were four\n controls that could not be pressed taking up the row that says what is wrong. -->\n @for (facet of visibleFacets(); track facet.id) {\n <button\n [class.et-query-devtools-facet--active]=\"facet.active\"\n [attr.aria-pressed]=\"facet.active\"\n (click)=\"host.toggleFacet(facet.id)\"\n class=\"et-query-devtools-chip et-query-devtools-facet\"\n type=\"button\"\n >\n {{ facet.label }}\n <span class=\"et-query-devtools-facet-count\">{{ facet.count }}</span>\n </button>\n }\n @let goneListed = listedGoneQueries().length;\n @if (goneListed) {\n <button\n (click)=\"forgetGoneQueries()\"\n class=\"et-query-devtools-forget\"\n type=\"button\"\n title=\"Drop the {{ goneListed }} destroyed queries listed below - they cannot be brought back\"\n >\n Forget {{ goneListed }}\n </button>\n }\n @if (isQueryListNarrowed()) {\n <button\n (click)=\"host.clearQueryFilters()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the search term and the status chips\"\n aria-label=\"Clear filters\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n </button>\n }\n </div>\n </div>\n\n @if (host.queryTreeView()) {\n @for (row of queryTree(); track row.key) {\n @if (row.kind === 'folder') {\n <div [style.--_et-qdt-tree-depth]=\"row.depth\" class=\"et-query-devtools-row et-query-devtools-row--folder\">\n <button\n [attr.aria-expanded]=\"!row.collapsed\"\n [title]=\"\n (row.collapsed ? 'Expand' : 'Collapse') + ' /' + row.label + ' - ' + row.count + ' queries below it'\n \"\n (click)=\"host.toggleQueryPath(row.key)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ row.collapsed ? '\u25B8' : '\u25BE' }}</span>\n <span class=\"et-query-devtools-folder\">/{{ row.label }}</span>\n <span class=\"et-query-devtools-row-count\">{{ row.count }}</span>\n </button>\n </div>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"queryGroupBlock\"\n [ngTemplateOutletContext]=\"{ group: row.item, depth: row.depth, trim: row.parentPath }\"\n />\n }\n } @empty {\n <ng-container [ngTemplateOutlet]=\"emptyList\" />\n }\n } @else {\n @for (group of queryGroups(); track group.key) {\n <ng-container [ngTemplateOutlet]=\"queryGroupBlock\" [ngTemplateOutletContext]=\"{ group, depth: 0, trim: '' }\" />\n } @empty {\n <ng-container [ngTemplateOutlet]=\"emptyList\" />\n }\n }\n\n <!-- An empty scope is a different problem from a filter that matches nothing, and only the\n latter is the user's own doing - so it names the way out. -->\n <ng-template #emptyList>\n @if (scopedQueryCount()) {\n <p class=\"et-query-devtools-empty\">No query matches the filter \u00B7 {{ scopedQueryCount() }} in scope.</p>\n } @else if (goneQueryCount()) {\n <p class=\"et-query-devtools-empty\">\n No query in scope still exists \u00B7 pick Gone to list the {{ goneQueryCount() }} destroyed.\n </p>\n } @else if (host.inspectFilterIds()) {\n <p class=\"et-query-devtools-empty\">\n The inspected element's queries are no longer registered. Clear the filter.\n </p>\n } @else {\n <p class=\"et-query-devtools-empty\">No queries registered.</p>\n }\n </ng-template>\n\n <ng-template #queryGroupBlock let-group=\"group\" let-depth=\"depth\" let-trim=\"trim\">\n @if (group.items.length === 1) {\n <ng-container [ngTemplateOutlet]=\"queryRow\" [ngTemplateOutletContext]=\"{ item: group.head, depth, trim }\" />\n } @else {\n @let expanded = isGroupExpanded(group);\n @let head = group.head;\n @let groupGone = !!head.entry.destroyedAt;\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"holdsSelection(group)\"\n class=\"et-query-devtools-row et-query-devtools-row--group\"\n >\n <!-- The caret is its own control: every row in the fold is the same request, so opening the\n fold and inspecting what it holds are two different intentions. -->\n <button\n [attr.aria-expanded]=\"expanded\"\n [attr.aria-label]=\"\n (expanded ? 'Collapse' : 'Expand') +\n ' the ' +\n group.items.length +\n ' consumers of this query - one registry entry each'\n \"\n [title]=\"\n (expanded ? 'Collapse' : 'Expand') +\n ' the ' +\n group.items.length +\n ' consumers of this query - one registry entry each'\n \"\n (click)=\"host.toggleQueryGroup(group.key)\"\n class=\"et-query-devtools-row-caret-btn\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ expanded ? '\u25BE' : '\u25B8' }}</span>\n </button>\n <button\n (click)=\"selectGroup(group)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n title=\"Inspect this request - all {{\n group.items.length\n }} consumers share it, so any of them answers for it\"\n >\n <span\n [attr.data-status]=\"groupGone ? null : groupStatus(group)\"\n [title]=\"groupGone ? 'destroyed' : groupStatus(group)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ head.entry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.routeSegments(head.entry, head.query)\" [trimPrefix]=\"trim\" />\n </span>\n @if (groupGone) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"These queries no longer exist - what you see is the last state they held, frozen\"\n >\n gone\n </span>\n }\n @if (!groupGone && isGroupStale(group)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">stale</span>\n }\n @if (isGroupTampered(group)) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"At least one of these is showing an armed response override or a devtools-injected failure\"\n >\n tampered\n </span>\n }\n <span class=\"et-query-devtools-row-count\" title=\"Consumers of this one request\">\n \u00D7{{ group.items.length }}\n </span>\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(head)) }}</span>\n </button>\n </div>\n @if (expanded) {\n @for (item of group.items; track item.entry.id) {\n <ng-container\n [ngTemplateOutlet]=\"queryRow\"\n [ngTemplateOutletContext]=\"{ item, nested: true, ordinal: $index + 1, depth, trim }\"\n />\n }\n }\n }\n </ng-template>\n\n <ng-template #queryRow let-item=\"item\" let-nested=\"nested\" let-ordinal=\"ordinal\" let-depth=\"depth\" let-trim=\"trim\">\n @let isGone = !!item.entry.destroyedAt;\n @let isPinned = host.isQueryPinned(item.entry);\n @let pinLabel = isPinned ? 'Unpin this query' : 'Pin this query to the top of the list';\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"host.selectedQueryId() === item.entry.id\"\n [class.et-query-devtools-row--gone]=\"isGone\"\n [class.et-query-devtools-row--nested]=\"nested\"\n class=\"et-query-devtools-row\"\n >\n <button (click)=\"host.selectedQueryId.set(item.entry.id)\" class=\"et-query-devtools-row-select\" type=\"button\">\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\"></span>\n <span\n [attr.data-status]=\"isGone ? null : host.queryStatus(item.query)\"\n [title]=\"isGone ? 'destroyed' : host.queryStatus(item.query)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ item.entry.meta.method }}</span>\n <!-- Inside a fold the route is the fold's own head, repeated once per consumer. Where the query\n was created is the only thing that differs, so that is what the row says instead. -->\n @if (nested) {\n <span class=\"et-query-devtools-consumer\">\n @if (consumerLabel(item); as consumer) {\n &lt;{{ consumer }}&gt;\n } @else {\n created outside a component\n }\n </span>\n <span class=\"et-query-devtools-row-count\">#{{ ordinal }}</span>\n } @else {\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.routeSegments(item.entry, item.query)\" [trimPrefix]=\"trim\" />\n </span>\n }\n @if (isGone) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"This query no longer exists - what you see is the last state it held, frozen\"\n >\n gone\n </span>\n }\n <!-- A retry is the one thing a loading dot actively misrepresents: it reads as a request\n on its way when nothing is in flight at all. -->\n @if (!isGone && host.requestProgress(item.query)?.retry; as retry) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"Waiting out a retry backoff before attempt {{ retry.attempt }}\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n {{ retry.attempt }}\n </span>\n }\n @if (!isGone && host.isStale(item.query)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">stale</span>\n }\n @if (host.isTampered(item.entry)) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"Showing an armed response override or a devtools-injected failure, not what the server actually sent\"\n >\n tampered\n </span>\n }\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(item)) }}</span>\n </button>\n <button\n [class.et-query-devtools-row-pin--active]=\"isPinned\"\n [title]=\"pinLabel\"\n [attr.aria-label]=\"pinLabel\"\n [attr.aria-pressed]=\"isPinned\"\n (click)=\"host.toggleQueryPin(item.entry)\"\n class=\"et-query-devtools-row-pin\"\n type=\"button\"\n >\n <span aria-hidden=\"true\">{{ isPinned ? '\u2605' : '\u2606' }}</span>\n </button>\n </div>\n </ng-template>\n </div>\n\n <div\n (pointerdown)=\"host.startPaneResize($event, { pane: 'list', container: panes })\"\n (dblclick)=\"host.resetPaneSize('list')\"\n class=\"et-query-devtools-pane-resize\"\n title=\"Drag to resize, double-click to reset\"\n aria-hidden=\"true\"\n ></div>\n\n <div class=\"et-query-devtools-detail\">\n @if (host.selectedQuery(); as sel) {\n <et-query-devtools-detail [sel]=\"sel\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Select a query to inspect it.</p>\n }\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-master-detail{display:flex;block-size:100%;min-block-size:0}.et-query-devtools-list{flex:0 0 var(--_et-qdt-list-width, 34%);min-inline-size:var(--_et-qdt-list-width, 22rem);max-inline-size:var(--_et-qdt-list-width, 40rem);overflow-y:auto;border-inline-end:1px solid var(--_et-qdt-border)}.et-query-devtools-forget{appearance:none;display:inline-flex;align-items:center;gap:.5rem;border:1px solid color-mix(in srgb,var(--_et-qdt-error) 50%,transparent);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-error) 10%,transparent);color:var(--_et-qdt-error);cursor:pointer;padding:.3rem .6rem;font:inherit;font-size:1.15rem;line-height:1.4}@media(hover:hover){.et-query-devtools-forget:hover{border-color:var(--_et-qdt-error);background:color-mix(in srgb,var(--_et-qdt-error) 22%,transparent)}}.et-query-devtools-list .et-query-devtools-toolbar-row .et-query-devtools-search{flex:1 1 100%}.et-query-devtools-toolbar--filtered{background:linear-gradient(to right,color-mix(in srgb,var(--_et-qdt-accent) 12%,transparent),transparent 65%),var(--_et-qdt-bg);box-shadow:inset .2rem 0 0 var(--_et-qdt-accent),inset 0 -1px color-mix(in srgb,var(--_et-qdt-accent) 30%,transparent)}.et-query-devtools-row-select{display:flex;flex:1 1 auto;align-items:center;gap:.8rem;min-inline-size:0;text-align:start;appearance:none;border:none;background:transparent;color:inherit;padding:.8rem;cursor:pointer;font:inherit}.et-query-devtools-row-select>.et-query-devtools-chip{padding-block:.1rem;line-height:1.2}.et-query-devtools-row-pin{display:flex;flex:0 0 auto;align-self:center;align-items:center;justify-content:center;inline-size:3.2rem;block-size:3.2rem;margin-inline-end:.4rem;appearance:none;border:none;border-radius:.4rem;background:transparent;color:var(--_et-qdt-subtle);cursor:pointer;font-family:inherit;font-size:1.8rem;line-height:1;opacity:0}.et-query-devtools-row-pin:focus-visible,.et-query-devtools-row-pin--active{opacity:1}@media(hover:hover){.et-query-devtools-row:hover .et-query-devtools-row-pin{opacity:1}}@media(hover:none){.et-query-devtools-row-pin{opacity:1}}@media(hover:hover){.et-query-devtools-row-pin:hover{background:var(--_et-qdt-active);color:var(--_et-qdt-fg)}}.et-query-devtools-row-pin--active,.et-query-devtools-row-pin--active:hover{color:var(--_et-qdt-accent)}.et-query-devtools-row--active{background:var(--_et-qdt-active)}.et-query-devtools-row--gone{color:var(--_et-qdt-muted)}.et-query-devtools-row-caret{flex:0 0 auto;inline-size:1rem;color:var(--_et-qdt-subtle);font-size:1rem;line-height:1}.et-query-devtools-row-caret-btn{display:flex;flex:0 0 auto;align-items:center;appearance:none;border:none;background:transparent;color:inherit;cursor:pointer;padding-block:0;padding-inline:calc(.8rem + var(--_et-qdt-tree-depth, 0) * 1.4rem) 0;font:inherit}@media(hover:hover){.et-query-devtools-row-caret-btn:hover .et-query-devtools-row-caret{color:var(--_et-qdt-fg)}}.et-query-devtools-row>.et-query-devtools-row-select{padding-inline-start:calc(.8rem + var(--_et-qdt-tree-depth, 0) * 1.4rem)}.et-query-devtools-row--nested>.et-query-devtools-row-select{padding-inline-start:calc(2.6rem + var(--_et-qdt-tree-depth, 0) * 1.4rem)}.et-query-devtools-row--group>.et-query-devtools-row-select{padding-inline:.8rem 4.4rem}.et-query-devtools-consumer{flex:1 1 auto;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--_et-qdt-muted)}.et-query-devtools-row--folder{background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,transparent)}.et-query-devtools-folder{flex:1 1 auto;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--_et-qdt-muted)}.et-query-devtools-row--group{background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,transparent)}.et-query-devtools-row--group.et-query-devtools-row--active{background:var(--_et-qdt-active)}.et-query-devtools-row-count{flex:0 0 auto;color:var(--_et-qdt-subtle);font-variant-numeric:tabular-nums;font-size:1.1rem}.et-query-devtools-row-time{flex:0 0 auto;inline-size:8ch;text-align:end;color:var(--_et-qdt-subtle);font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:1.1rem}.et-query-devtools-sort{flex:0 0 auto;color:var(--_et-qdt-muted)}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: QueryDevtoolsDetailComponent, selector: "et-query-devtools-detail", inputs: ["sel"] }, { kind: "component", type: QueryDevtoolsRouteComponent, selector: "et-query-devtools-route", inputs: ["segments", "fallback", "trimPrefix"] }], encapsulation: i0.ViewEncapsulation.None }); }
3776
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsQueriesTabComponent, isStandalone: true, selector: "et-query-devtools-queries-tab", ngImport: i0, template: "<div #panes class=\"et-query-devtools-master-detail\">\n <div class=\"et-query-devtools-list\">\n <div [class.et-query-devtools-toolbar--filtered]=\"!!host.inspectFilterIds()\" class=\"et-query-devtools-toolbar\">\n <!-- Search leads: it is what the list is narrowed with most of the time. The scope controls sit\n after it, and the count reads as the search's result rather than as one more control. -->\n <div class=\"et-query-devtools-toolbar-row\">\n <input\n #queryFilterInput\n [value]=\"host.queryFilter()\"\n (input)=\"host.queryFilter.set(queryFilterInput.value)\"\n class=\"et-query-devtools-search\"\n type=\"search\"\n placeholder=\"filter method / route / url\u2026\"\n aria-label=\"Filter queries\"\n />\n @if (host.inspectFilterIds(); as ids) {\n <span class=\"et-query-devtools-filter-chip\">\n <span class=\"et-query-devtools-filter-icon\" aria-hidden=\"true\">\u2316</span>\n Inspected element\n <span class=\"et-query-devtools-filter-count\">{{ ids.length }}</span>\n </span>\n <button\n (click)=\"host.clearInspectFilter()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the inspection filter and show all queries\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n Clear\n </button>\n } @else {\n <select\n #clientSelect\n [value]=\"host.selectedClientName() ?? ''\"\n (change)=\"host.selectClient(clientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n aria-label=\"Client\"\n >\n <option value=\"\">All clients</option>\n @for (name of host.clientNames(); track name) {\n <option [value]=\"name\">{{ name }}</option>\n }\n </select>\n }\n <span class=\"et-query-devtools-count\">\n @if (isQueryListNarrowed()) {\n {{ filteredQueries().length }} of {{ scopedQueryCount() }}\n } @else {\n {{ scopedQueryCount() }}\n }\n </span>\n <button\n [disabled]=\"!filteredQueries().length\"\n (click)=\"downloadInsomniaCollection()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Download the listed queries as an Insomnia collection, with the auth provider's token refresh\"\n >\n <span aria-hidden=\"true\">\u2913</span>\n Insomnia\n </button>\n </div>\n\n <div class=\"et-query-devtools-toolbar-row\">\n @let recentFirst = host.queryRecentFirst();\n <div class=\"et-query-devtools-btn-group\">\n <button\n [title]=\"\n (recentFirst ? 'Newest run first.' : 'Oldest run first.') +\n ' Click to reverse. Queries that have never run stay at the bottom either way.'\n \"\n (click)=\"host.queryRecentFirst.set(!recentFirst)\"\n class=\"et-query-devtools-btn et-query-devtools-sort\"\n type=\"button\"\n >\n recent <span aria-hidden=\"true\">{{ recentFirst ? '\u2193' : '\u2191' }}</span>\n </button>\n @let treeView = host.queryTreeView();\n <button\n [class.et-query-devtools-btn--active]=\"treeView\"\n [attr.aria-pressed]=\"treeView\"\n [title]=\"\n treeView\n ? 'Grouped by route path. Click for the flat list.'\n : 'Flat list. Click to group by route path - segments nothing branches off are folded into one row.'\n \"\n (click)=\"host.queryTreeView.set(!treeView)\"\n class=\"et-query-devtools-btn et-query-devtools-sort\"\n type=\"button\"\n >\n <span aria-hidden=\"true\">\u2442</span> tree\n </button>\n </div>\n <!-- Only the statuses something is actually in. Five chips, four of them reading `0`, were four\n controls that could not be pressed taking up the row that says what is wrong. -->\n @for (facet of visibleFacets(); track facet.id) {\n <button\n [class.et-query-devtools-facet--active]=\"facet.active\"\n [attr.aria-pressed]=\"facet.active\"\n (click)=\"host.toggleFacet(facet.id)\"\n class=\"et-query-devtools-chip et-query-devtools-facet\"\n type=\"button\"\n >\n {{ facet.label }}\n <span class=\"et-query-devtools-facet-count\">{{ facet.count }}</span>\n </button>\n }\n @let goneListed = listedGoneQueries().length;\n @if (goneListed) {\n <button\n (click)=\"forgetGoneQueries()\"\n class=\"et-query-devtools-forget\"\n type=\"button\"\n title=\"Drop the {{ goneListed }} destroyed queries listed below - they cannot be brought back\"\n >\n Forget {{ goneListed }}\n </button>\n }\n @if (isQueryListNarrowed()) {\n <button\n (click)=\"host.clearQueryFilters()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the search term and the status chips\"\n aria-label=\"Clear filters\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n </button>\n }\n </div>\n </div>\n\n @if (host.queryTreeView()) {\n @for (row of queryTree(); track row.key) {\n @if (row.kind === 'folder') {\n <div [style.--_et-qdt-tree-depth]=\"row.depth\" class=\"et-query-devtools-row et-query-devtools-row--folder\">\n <button\n [attr.aria-expanded]=\"!row.collapsed\"\n [title]=\"\n (row.collapsed ? 'Expand' : 'Collapse') + ' /' + row.label + ' - ' + row.count + ' queries below it'\n \"\n (click)=\"host.toggleQueryPath(row.key)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ row.collapsed ? '\u25B8' : '\u25BE' }}</span>\n <span class=\"et-query-devtools-folder\">/{{ row.label }}</span>\n <span class=\"et-query-devtools-row-count\">{{ row.count }}</span>\n </button>\n </div>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"queryGroupBlock\"\n [ngTemplateOutletContext]=\"{ group: row.item, depth: row.depth, trim: row.parentPath }\"\n />\n }\n } @empty {\n <ng-container [ngTemplateOutlet]=\"emptyList\" />\n }\n } @else {\n @for (group of queryGroups(); track group.key) {\n <ng-container [ngTemplateOutlet]=\"queryGroupBlock\" [ngTemplateOutletContext]=\"{ group, depth: 0, trim: '' }\" />\n } @empty {\n <ng-container [ngTemplateOutlet]=\"emptyList\" />\n }\n }\n\n <!-- An empty scope is a different problem from a filter that matches nothing, and only the\n latter is the user's own doing - so it names the way out. -->\n <ng-template #emptyList>\n @if (scopedQueryCount()) {\n <p class=\"et-query-devtools-empty\">No query matches the filter \u00B7 {{ scopedQueryCount() }} in scope.</p>\n } @else if (goneQueryCount()) {\n <p class=\"et-query-devtools-empty\">\n No query in scope still exists \u00B7 pick Gone to list the {{ goneQueryCount() }} destroyed.\n </p>\n } @else if (host.inspectFilterIds()) {\n <p class=\"et-query-devtools-empty\">\n The inspected element's queries are no longer registered. Clear the filter.\n </p>\n } @else {\n <p class=\"et-query-devtools-empty\">No queries registered.</p>\n }\n </ng-template>\n\n <ng-template #queryGroupBlock let-group=\"group\" let-depth=\"depth\" let-trim=\"trim\">\n @if (batchEntryOf(group); as batchEntry) {\n <ng-container\n [ngTemplateOutlet]=\"batchGroupBlock\"\n [ngTemplateOutletContext]=\"{ group, batchEntry, depth, trim }\"\n />\n } @else if (group.items.length === 1) {\n <ng-container [ngTemplateOutlet]=\"queryRow\" [ngTemplateOutletContext]=\"{ item: group.head, depth, trim }\" />\n } @else {\n @let expanded = isGroupExpanded(group);\n @let head = group.head;\n @let groupGone = !!head.entry.destroyedAt;\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"holdsSelection(group)\"\n class=\"et-query-devtools-row et-query-devtools-row--group\"\n >\n <!-- The caret is its own control: every row in the fold is the same request, so opening the\n fold and inspecting what it holds are two different intentions. -->\n <button\n [attr.aria-expanded]=\"expanded\"\n [attr.aria-label]=\"\n (expanded ? 'Collapse' : 'Expand') +\n ' the ' +\n group.items.length +\n ' consumers of this query - one registry entry each'\n \"\n [title]=\"\n (expanded ? 'Collapse' : 'Expand') +\n ' the ' +\n group.items.length +\n ' consumers of this query - one registry entry each'\n \"\n (click)=\"host.toggleQueryGroup(group.key)\"\n class=\"et-query-devtools-row-caret-btn\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ expanded ? '\u25BE' : '\u25B8' }}</span>\n </button>\n <button\n (click)=\"selectGroup(group)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n title=\"Inspect this request - all {{\n group.items.length\n }} consumers share it, so any of them answers for it\"\n >\n <span\n [attr.data-status]=\"groupGone ? null : groupStatus(group)\"\n [title]=\"groupGone ? 'destroyed' : groupStatus(group)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ head.entry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.routeSegments(head.entry, head.query)\" [trimPrefix]=\"trim\" />\n </span>\n @if (groupGone) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"These queries no longer exist - what you see is the last state they held, frozen\"\n >\n gone\n </span>\n }\n @if (!groupGone && isGroupStale(group)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">stale</span>\n }\n @if (isGroupTampered(group)) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"At least one of these is showing an armed response override or a devtools-injected failure\"\n >\n tampered\n </span>\n }\n <span class=\"et-query-devtools-row-count\" title=\"Consumers of this one request\">\n \u00D7{{ group.items.length }}\n </span>\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(head)) }}</span>\n </button>\n </div>\n @if (expanded) {\n @for (item of group.items; track item.entry.id) {\n <ng-container\n [ngTemplateOutlet]=\"queryRow\"\n [ngTemplateOutletContext]=\"{ item, nested: true, ordinal: $index + 1, depth, trim }\"\n />\n }\n }\n }\n </ng-template>\n\n <!-- A bulk run's items, folded under the run rather than under what a row shows. They only exist\n while in flight - the batch destroys each item's query as it settles - so this row stands for\n the whole run, and the Batches tab is where its settled items are. -->\n <ng-template #batchGroupBlock let-group=\"group\" let-batchEntry=\"batchEntry\" let-depth=\"depth\" let-trim=\"trim\">\n @let batch = host.asBatch(batchEntry);\n @let expanded = isGroupExpanded(group);\n @let toggleLabel =\n (expanded ? 'Collapse' : 'Expand') + ' the ' + group.items.length + ' requests this batch has in flight';\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"holdsSelection(group)\"\n class=\"et-query-devtools-row et-query-devtools-row--group\"\n >\n <button\n [attr.aria-expanded]=\"expanded\"\n [attr.aria-label]=\"toggleLabel\"\n [title]=\"toggleLabel\"\n (click)=\"host.toggleQueryGroup(group.key)\"\n class=\"et-query-devtools-row-caret-btn\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ expanded ? '\u25BE' : '\u25B8' }}</span>\n </button>\n <button\n (click)=\"selectGroup(group)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n title=\"One item of a batch run - {{ batch.completed() }} of {{\n batch.total()\n }} settled. The Batches tab has the whole run.\"\n >\n <span\n [attr.data-status]=\"groupStatus(group)\"\n [title]=\"groupStatus(group)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ batchEntry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.batchRouteSegments(batchEntry)\" [trimPrefix]=\"trim\" />\n </span>\n <span\n class=\"et-query-devtools-chip\"\n title=\"Registered by createQueryBatch - inspect the run on the Batches tab\"\n >\n batch {{ batch.completed() }}/{{ batch.total() }}\n </span>\n @if (batch.failed()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--error\">{{ batch.failed() }} failed</span>\n }\n <span class=\"et-query-devtools-row-count\" title=\"Requests this batch has in flight\">\n \u00D7{{ group.items.length }}\n </span>\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(group.head)) }}</span>\n </button>\n </div>\n @if (expanded) {\n @for (item of group.items; track item.entry.id) {\n <ng-container\n [ngTemplateOutlet]=\"queryRow\"\n [ngTemplateOutletContext]=\"{ item, nested: true, showRoute: true, depth, trim }\"\n />\n }\n }\n </ng-template>\n\n <ng-template\n #queryRow\n let-item=\"item\"\n let-nested=\"nested\"\n let-showRoute=\"showRoute\"\n let-ordinal=\"ordinal\"\n let-depth=\"depth\"\n let-trim=\"trim\"\n >\n @let isGone = !!item.entry.destroyedAt;\n @let isPinned = host.isQueryPinned(item.entry);\n @let pinLabel = isPinned ? 'Unpin this query' : 'Pin this query to the top of the list';\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"host.selectedQueryId() === item.entry.id\"\n [class.et-query-devtools-row--gone]=\"isGone\"\n [class.et-query-devtools-row--nested]=\"nested\"\n class=\"et-query-devtools-row\"\n >\n <button (click)=\"host.selectedQueryId.set(item.entry.id)\" class=\"et-query-devtools-row-select\" type=\"button\">\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\"></span>\n <span\n [attr.data-status]=\"isGone ? null : host.queryStatus(item.query)\"\n [title]=\"isGone ? 'destroyed' : host.queryStatus(item.query)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ item.entry.meta.method }}</span>\n <!-- Inside a fold the route is the fold's own head, repeated once per consumer. Where the query\n was created is the only thing that differs, so that is what the row says instead. A batch\n fold is the exception: its items differ in the route, which is why `showRoute` is set. -->\n @if (nested && !showRoute) {\n <span class=\"et-query-devtools-consumer\">\n @if (consumerLabel(item); as consumer) {\n &lt;{{ consumer }}&gt;\n } @else {\n created outside a component\n }\n </span>\n <span class=\"et-query-devtools-row-count\">#{{ ordinal }}</span>\n } @else {\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.routeSegments(item.entry, item.query)\" [trimPrefix]=\"trim\" />\n </span>\n }\n @if (isGone) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"This query no longer exists - what you see is the last state it held, frozen\"\n >\n gone\n </span>\n }\n <!-- A retry is the one thing a loading dot actively misrepresents: it reads as a request\n on its way when nothing is in flight at all. -->\n @if (!isGone && host.requestProgress(item.query)?.retry; as retry) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"Waiting out a retry backoff before attempt {{ retry.attempt }}\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n {{ retry.attempt }}\n </span>\n }\n @if (!isGone && host.isStale(item.query)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">stale</span>\n }\n @if (host.isTampered(item.entry)) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"Showing an armed response override or a devtools-injected failure, not what the server actually sent\"\n >\n tampered\n </span>\n }\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(item)) }}</span>\n </button>\n <button\n [class.et-query-devtools-row-pin--active]=\"isPinned\"\n [title]=\"pinLabel\"\n [attr.aria-label]=\"pinLabel\"\n [attr.aria-pressed]=\"isPinned\"\n (click)=\"host.toggleQueryPin(item.entry)\"\n class=\"et-query-devtools-row-pin\"\n type=\"button\"\n >\n <span aria-hidden=\"true\">{{ isPinned ? '\u2605' : '\u2606' }}</span>\n </button>\n </div>\n </ng-template>\n </div>\n\n <div\n (pointerdown)=\"host.startPaneResize($event, { pane: 'list', container: panes })\"\n (dblclick)=\"host.resetPaneSize('list')\"\n class=\"et-query-devtools-pane-resize\"\n title=\"Drag to resize, double-click to reset\"\n aria-hidden=\"true\"\n ></div>\n\n <div class=\"et-query-devtools-detail\">\n @if (host.selectedQuery(); as sel) {\n <et-query-devtools-detail [sel]=\"sel\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Select a query to inspect it.</p>\n }\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-master-detail{display:flex;block-size:100%;min-block-size:0}.et-query-devtools-list{flex:0 0 var(--_et-qdt-list-width, 34%);min-inline-size:var(--_et-qdt-list-width, 22rem);max-inline-size:var(--_et-qdt-list-width, 40rem);overflow-y:auto;border-inline-end:1px solid var(--_et-qdt-border)}.et-query-devtools-forget{appearance:none;display:inline-flex;align-items:center;gap:.5rem;border:1px solid color-mix(in srgb,var(--_et-qdt-error) 50%,transparent);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-error) 10%,transparent);color:var(--_et-qdt-error);cursor:pointer;padding:.3rem .6rem;font:inherit;font-size:1.15rem;line-height:1.4}@media(hover:hover){.et-query-devtools-forget:hover{border-color:var(--_et-qdt-error);background:color-mix(in srgb,var(--_et-qdt-error) 22%,transparent)}}.et-query-devtools-list .et-query-devtools-toolbar-row .et-query-devtools-search{flex:1 1 100%}.et-query-devtools-toolbar--filtered{background:linear-gradient(to right,color-mix(in srgb,var(--_et-qdt-accent) 12%,transparent),transparent 65%),var(--_et-qdt-bg);box-shadow:inset .2rem 0 0 var(--_et-qdt-accent),inset 0 -1px color-mix(in srgb,var(--_et-qdt-accent) 30%,transparent)}.et-query-devtools-row-select{display:flex;flex:1 1 auto;align-items:center;gap:.8rem;min-inline-size:0;text-align:start;appearance:none;border:none;background:transparent;color:inherit;padding:.8rem;cursor:pointer;font:inherit}.et-query-devtools-row-select>.et-query-devtools-chip{padding-block:.1rem;line-height:1.2}.et-query-devtools-row-pin{display:flex;flex:0 0 auto;align-self:center;align-items:center;justify-content:center;inline-size:3.2rem;block-size:3.2rem;margin-inline-end:.4rem;appearance:none;border:none;border-radius:.4rem;background:transparent;color:var(--_et-qdt-subtle);cursor:pointer;font-family:inherit;font-size:1.8rem;line-height:1;opacity:0}.et-query-devtools-row-pin:focus-visible,.et-query-devtools-row-pin--active{opacity:1}@media(hover:hover){.et-query-devtools-row:hover .et-query-devtools-row-pin{opacity:1}}@media(hover:none){.et-query-devtools-row-pin{opacity:1}}@media(hover:hover){.et-query-devtools-row-pin:hover{background:var(--_et-qdt-active);color:var(--_et-qdt-fg)}}.et-query-devtools-row-pin--active,.et-query-devtools-row-pin--active:hover{color:var(--_et-qdt-accent)}.et-query-devtools-row--active{background:var(--_et-qdt-active)}.et-query-devtools-row--gone{color:var(--_et-qdt-muted)}.et-query-devtools-row-caret{flex:0 0 auto;inline-size:1rem;color:var(--_et-qdt-subtle);font-size:1rem;line-height:1}.et-query-devtools-row-caret-btn{display:flex;flex:0 0 auto;align-items:center;appearance:none;border:none;background:transparent;color:inherit;cursor:pointer;padding-block:0;padding-inline:calc(.8rem + var(--_et-qdt-tree-depth, 0) * 1.4rem) 0;font:inherit}@media(hover:hover){.et-query-devtools-row-caret-btn:hover .et-query-devtools-row-caret{color:var(--_et-qdt-fg)}}.et-query-devtools-row>.et-query-devtools-row-select{padding-inline-start:calc(.8rem + var(--_et-qdt-tree-depth, 0) * 1.4rem)}.et-query-devtools-row--nested>.et-query-devtools-row-select{padding-inline-start:calc(2.6rem + var(--_et-qdt-tree-depth, 0) * 1.4rem)}.et-query-devtools-row--group>.et-query-devtools-row-select{padding-inline:.8rem 4.4rem}.et-query-devtools-consumer{flex:1 1 auto;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--_et-qdt-muted)}.et-query-devtools-row--folder{background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,transparent)}.et-query-devtools-folder{flex:1 1 auto;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--_et-qdt-muted)}.et-query-devtools-row--group{background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,transparent)}.et-query-devtools-row--group.et-query-devtools-row--active{background:var(--_et-qdt-active)}.et-query-devtools-row-count{flex:0 0 auto;color:var(--_et-qdt-subtle);font-variant-numeric:tabular-nums;font-size:1.1rem}.et-query-devtools-row-time{flex:0 0 auto;inline-size:8ch;text-align:end;color:var(--_et-qdt-subtle);font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:1.1rem}.et-query-devtools-sort{flex:0 0 auto;color:var(--_et-qdt-muted)}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: QueryDevtoolsDetailComponent, selector: "et-query-devtools-detail", inputs: ["sel"] }, { kind: "component", type: QueryDevtoolsRouteComponent, selector: "et-query-devtools-route", inputs: ["segments", "fallback", "trimPrefix"] }], encapsulation: i0.ViewEncapsulation.None }); }
3738
3777
  }
3739
3778
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsQueriesTabComponent, decorators: [{
3740
3779
  type: Component,
3741
- args: [{ selector: 'et-query-devtools-queries-tab', encapsulation: ViewEncapsulation.None, imports: [NgTemplateOutlet, QueryDevtoolsDetailComponent, QueryDevtoolsRouteComponent], template: "<div #panes class=\"et-query-devtools-master-detail\">\n <div class=\"et-query-devtools-list\">\n <div [class.et-query-devtools-toolbar--filtered]=\"!!host.inspectFilterIds()\" class=\"et-query-devtools-toolbar\">\n <!-- Search leads: it is what the list is narrowed with most of the time. The scope controls sit\n after it, and the count reads as the search's result rather than as one more control. -->\n <div class=\"et-query-devtools-toolbar-row\">\n <input\n #queryFilterInput\n [value]=\"host.queryFilter()\"\n (input)=\"host.queryFilter.set(queryFilterInput.value)\"\n class=\"et-query-devtools-search\"\n type=\"search\"\n placeholder=\"filter method / route / url\u2026\"\n aria-label=\"Filter queries\"\n />\n @if (host.inspectFilterIds(); as ids) {\n <span class=\"et-query-devtools-filter-chip\">\n <span class=\"et-query-devtools-filter-icon\" aria-hidden=\"true\">\u2316</span>\n Inspected element\n <span class=\"et-query-devtools-filter-count\">{{ ids.length }}</span>\n </span>\n <button\n (click)=\"host.clearInspectFilter()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the inspection filter and show all queries\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n Clear\n </button>\n } @else {\n <select\n #clientSelect\n [value]=\"host.selectedClientName() ?? ''\"\n (change)=\"host.selectClient(clientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n aria-label=\"Client\"\n >\n <option value=\"\">All clients</option>\n @for (name of host.clientNames(); track name) {\n <option [value]=\"name\">{{ name }}</option>\n }\n </select>\n }\n <span class=\"et-query-devtools-count\">\n @if (isQueryListNarrowed()) {\n {{ filteredQueries().length }} of {{ scopedQueryCount() }}\n } @else {\n {{ scopedQueryCount() }}\n }\n </span>\n <button\n [disabled]=\"!filteredQueries().length\"\n (click)=\"downloadInsomniaCollection()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Download the listed queries as an Insomnia collection, with the auth provider's token refresh\"\n >\n <span aria-hidden=\"true\">\u2913</span>\n Insomnia\n </button>\n </div>\n\n <div class=\"et-query-devtools-toolbar-row\">\n @let recentFirst = host.queryRecentFirst();\n <div class=\"et-query-devtools-btn-group\">\n <button\n [title]=\"\n (recentFirst ? 'Newest run first.' : 'Oldest run first.') +\n ' Click to reverse. Queries that have never run stay at the bottom either way.'\n \"\n (click)=\"host.queryRecentFirst.set(!recentFirst)\"\n class=\"et-query-devtools-btn et-query-devtools-sort\"\n type=\"button\"\n >\n recent <span aria-hidden=\"true\">{{ recentFirst ? '\u2193' : '\u2191' }}</span>\n </button>\n @let treeView = host.queryTreeView();\n <button\n [class.et-query-devtools-btn--active]=\"treeView\"\n [attr.aria-pressed]=\"treeView\"\n [title]=\"\n treeView\n ? 'Grouped by route path. Click for the flat list.'\n : 'Flat list. Click to group by route path - segments nothing branches off are folded into one row.'\n \"\n (click)=\"host.queryTreeView.set(!treeView)\"\n class=\"et-query-devtools-btn et-query-devtools-sort\"\n type=\"button\"\n >\n <span aria-hidden=\"true\">\u2442</span> tree\n </button>\n </div>\n <!-- Only the statuses something is actually in. Five chips, four of them reading `0`, were four\n controls that could not be pressed taking up the row that says what is wrong. -->\n @for (facet of visibleFacets(); track facet.id) {\n <button\n [class.et-query-devtools-facet--active]=\"facet.active\"\n [attr.aria-pressed]=\"facet.active\"\n (click)=\"host.toggleFacet(facet.id)\"\n class=\"et-query-devtools-chip et-query-devtools-facet\"\n type=\"button\"\n >\n {{ facet.label }}\n <span class=\"et-query-devtools-facet-count\">{{ facet.count }}</span>\n </button>\n }\n @let goneListed = listedGoneQueries().length;\n @if (goneListed) {\n <button\n (click)=\"forgetGoneQueries()\"\n class=\"et-query-devtools-forget\"\n type=\"button\"\n title=\"Drop the {{ goneListed }} destroyed queries listed below - they cannot be brought back\"\n >\n Forget {{ goneListed }}\n </button>\n }\n @if (isQueryListNarrowed()) {\n <button\n (click)=\"host.clearQueryFilters()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the search term and the status chips\"\n aria-label=\"Clear filters\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n </button>\n }\n </div>\n </div>\n\n @if (host.queryTreeView()) {\n @for (row of queryTree(); track row.key) {\n @if (row.kind === 'folder') {\n <div [style.--_et-qdt-tree-depth]=\"row.depth\" class=\"et-query-devtools-row et-query-devtools-row--folder\">\n <button\n [attr.aria-expanded]=\"!row.collapsed\"\n [title]=\"\n (row.collapsed ? 'Expand' : 'Collapse') + ' /' + row.label + ' - ' + row.count + ' queries below it'\n \"\n (click)=\"host.toggleQueryPath(row.key)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ row.collapsed ? '\u25B8' : '\u25BE' }}</span>\n <span class=\"et-query-devtools-folder\">/{{ row.label }}</span>\n <span class=\"et-query-devtools-row-count\">{{ row.count }}</span>\n </button>\n </div>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"queryGroupBlock\"\n [ngTemplateOutletContext]=\"{ group: row.item, depth: row.depth, trim: row.parentPath }\"\n />\n }\n } @empty {\n <ng-container [ngTemplateOutlet]=\"emptyList\" />\n }\n } @else {\n @for (group of queryGroups(); track group.key) {\n <ng-container [ngTemplateOutlet]=\"queryGroupBlock\" [ngTemplateOutletContext]=\"{ group, depth: 0, trim: '' }\" />\n } @empty {\n <ng-container [ngTemplateOutlet]=\"emptyList\" />\n }\n }\n\n <!-- An empty scope is a different problem from a filter that matches nothing, and only the\n latter is the user's own doing - so it names the way out. -->\n <ng-template #emptyList>\n @if (scopedQueryCount()) {\n <p class=\"et-query-devtools-empty\">No query matches the filter \u00B7 {{ scopedQueryCount() }} in scope.</p>\n } @else if (goneQueryCount()) {\n <p class=\"et-query-devtools-empty\">\n No query in scope still exists \u00B7 pick Gone to list the {{ goneQueryCount() }} destroyed.\n </p>\n } @else if (host.inspectFilterIds()) {\n <p class=\"et-query-devtools-empty\">\n The inspected element's queries are no longer registered. Clear the filter.\n </p>\n } @else {\n <p class=\"et-query-devtools-empty\">No queries registered.</p>\n }\n </ng-template>\n\n <ng-template #queryGroupBlock let-group=\"group\" let-depth=\"depth\" let-trim=\"trim\">\n @if (group.items.length === 1) {\n <ng-container [ngTemplateOutlet]=\"queryRow\" [ngTemplateOutletContext]=\"{ item: group.head, depth, trim }\" />\n } @else {\n @let expanded = isGroupExpanded(group);\n @let head = group.head;\n @let groupGone = !!head.entry.destroyedAt;\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"holdsSelection(group)\"\n class=\"et-query-devtools-row et-query-devtools-row--group\"\n >\n <!-- The caret is its own control: every row in the fold is the same request, so opening the\n fold and inspecting what it holds are two different intentions. -->\n <button\n [attr.aria-expanded]=\"expanded\"\n [attr.aria-label]=\"\n (expanded ? 'Collapse' : 'Expand') +\n ' the ' +\n group.items.length +\n ' consumers of this query - one registry entry each'\n \"\n [title]=\"\n (expanded ? 'Collapse' : 'Expand') +\n ' the ' +\n group.items.length +\n ' consumers of this query - one registry entry each'\n \"\n (click)=\"host.toggleQueryGroup(group.key)\"\n class=\"et-query-devtools-row-caret-btn\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ expanded ? '\u25BE' : '\u25B8' }}</span>\n </button>\n <button\n (click)=\"selectGroup(group)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n title=\"Inspect this request - all {{\n group.items.length\n }} consumers share it, so any of them answers for it\"\n >\n <span\n [attr.data-status]=\"groupGone ? null : groupStatus(group)\"\n [title]=\"groupGone ? 'destroyed' : groupStatus(group)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ head.entry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.routeSegments(head.entry, head.query)\" [trimPrefix]=\"trim\" />\n </span>\n @if (groupGone) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"These queries no longer exist - what you see is the last state they held, frozen\"\n >\n gone\n </span>\n }\n @if (!groupGone && isGroupStale(group)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">stale</span>\n }\n @if (isGroupTampered(group)) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"At least one of these is showing an armed response override or a devtools-injected failure\"\n >\n tampered\n </span>\n }\n <span class=\"et-query-devtools-row-count\" title=\"Consumers of this one request\">\n \u00D7{{ group.items.length }}\n </span>\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(head)) }}</span>\n </button>\n </div>\n @if (expanded) {\n @for (item of group.items; track item.entry.id) {\n <ng-container\n [ngTemplateOutlet]=\"queryRow\"\n [ngTemplateOutletContext]=\"{ item, nested: true, ordinal: $index + 1, depth, trim }\"\n />\n }\n }\n }\n </ng-template>\n\n <ng-template #queryRow let-item=\"item\" let-nested=\"nested\" let-ordinal=\"ordinal\" let-depth=\"depth\" let-trim=\"trim\">\n @let isGone = !!item.entry.destroyedAt;\n @let isPinned = host.isQueryPinned(item.entry);\n @let pinLabel = isPinned ? 'Unpin this query' : 'Pin this query to the top of the list';\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"host.selectedQueryId() === item.entry.id\"\n [class.et-query-devtools-row--gone]=\"isGone\"\n [class.et-query-devtools-row--nested]=\"nested\"\n class=\"et-query-devtools-row\"\n >\n <button (click)=\"host.selectedQueryId.set(item.entry.id)\" class=\"et-query-devtools-row-select\" type=\"button\">\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\"></span>\n <span\n [attr.data-status]=\"isGone ? null : host.queryStatus(item.query)\"\n [title]=\"isGone ? 'destroyed' : host.queryStatus(item.query)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ item.entry.meta.method }}</span>\n <!-- Inside a fold the route is the fold's own head, repeated once per consumer. Where the query\n was created is the only thing that differs, so that is what the row says instead. -->\n @if (nested) {\n <span class=\"et-query-devtools-consumer\">\n @if (consumerLabel(item); as consumer) {\n &lt;{{ consumer }}&gt;\n } @else {\n created outside a component\n }\n </span>\n <span class=\"et-query-devtools-row-count\">#{{ ordinal }}</span>\n } @else {\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.routeSegments(item.entry, item.query)\" [trimPrefix]=\"trim\" />\n </span>\n }\n @if (isGone) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"This query no longer exists - what you see is the last state it held, frozen\"\n >\n gone\n </span>\n }\n <!-- A retry is the one thing a loading dot actively misrepresents: it reads as a request\n on its way when nothing is in flight at all. -->\n @if (!isGone && host.requestProgress(item.query)?.retry; as retry) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"Waiting out a retry backoff before attempt {{ retry.attempt }}\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n {{ retry.attempt }}\n </span>\n }\n @if (!isGone && host.isStale(item.query)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">stale</span>\n }\n @if (host.isTampered(item.entry)) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"Showing an armed response override or a devtools-injected failure, not what the server actually sent\"\n >\n tampered\n </span>\n }\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(item)) }}</span>\n </button>\n <button\n [class.et-query-devtools-row-pin--active]=\"isPinned\"\n [title]=\"pinLabel\"\n [attr.aria-label]=\"pinLabel\"\n [attr.aria-pressed]=\"isPinned\"\n (click)=\"host.toggleQueryPin(item.entry)\"\n class=\"et-query-devtools-row-pin\"\n type=\"button\"\n >\n <span aria-hidden=\"true\">{{ isPinned ? '\u2605' : '\u2606' }}</span>\n </button>\n </div>\n </ng-template>\n </div>\n\n <div\n (pointerdown)=\"host.startPaneResize($event, { pane: 'list', container: panes })\"\n (dblclick)=\"host.resetPaneSize('list')\"\n class=\"et-query-devtools-pane-resize\"\n title=\"Drag to resize, double-click to reset\"\n aria-hidden=\"true\"\n ></div>\n\n <div class=\"et-query-devtools-detail\">\n @if (host.selectedQuery(); as sel) {\n <et-query-devtools-detail [sel]=\"sel\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Select a query to inspect it.</p>\n }\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-master-detail{display:flex;block-size:100%;min-block-size:0}.et-query-devtools-list{flex:0 0 var(--_et-qdt-list-width, 34%);min-inline-size:var(--_et-qdt-list-width, 22rem);max-inline-size:var(--_et-qdt-list-width, 40rem);overflow-y:auto;border-inline-end:1px solid var(--_et-qdt-border)}.et-query-devtools-forget{appearance:none;display:inline-flex;align-items:center;gap:.5rem;border:1px solid color-mix(in srgb,var(--_et-qdt-error) 50%,transparent);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-error) 10%,transparent);color:var(--_et-qdt-error);cursor:pointer;padding:.3rem .6rem;font:inherit;font-size:1.15rem;line-height:1.4}@media(hover:hover){.et-query-devtools-forget:hover{border-color:var(--_et-qdt-error);background:color-mix(in srgb,var(--_et-qdt-error) 22%,transparent)}}.et-query-devtools-list .et-query-devtools-toolbar-row .et-query-devtools-search{flex:1 1 100%}.et-query-devtools-toolbar--filtered{background:linear-gradient(to right,color-mix(in srgb,var(--_et-qdt-accent) 12%,transparent),transparent 65%),var(--_et-qdt-bg);box-shadow:inset .2rem 0 0 var(--_et-qdt-accent),inset 0 -1px color-mix(in srgb,var(--_et-qdt-accent) 30%,transparent)}.et-query-devtools-row-select{display:flex;flex:1 1 auto;align-items:center;gap:.8rem;min-inline-size:0;text-align:start;appearance:none;border:none;background:transparent;color:inherit;padding:.8rem;cursor:pointer;font:inherit}.et-query-devtools-row-select>.et-query-devtools-chip{padding-block:.1rem;line-height:1.2}.et-query-devtools-row-pin{display:flex;flex:0 0 auto;align-self:center;align-items:center;justify-content:center;inline-size:3.2rem;block-size:3.2rem;margin-inline-end:.4rem;appearance:none;border:none;border-radius:.4rem;background:transparent;color:var(--_et-qdt-subtle);cursor:pointer;font-family:inherit;font-size:1.8rem;line-height:1;opacity:0}.et-query-devtools-row-pin:focus-visible,.et-query-devtools-row-pin--active{opacity:1}@media(hover:hover){.et-query-devtools-row:hover .et-query-devtools-row-pin{opacity:1}}@media(hover:none){.et-query-devtools-row-pin{opacity:1}}@media(hover:hover){.et-query-devtools-row-pin:hover{background:var(--_et-qdt-active);color:var(--_et-qdt-fg)}}.et-query-devtools-row-pin--active,.et-query-devtools-row-pin--active:hover{color:var(--_et-qdt-accent)}.et-query-devtools-row--active{background:var(--_et-qdt-active)}.et-query-devtools-row--gone{color:var(--_et-qdt-muted)}.et-query-devtools-row-caret{flex:0 0 auto;inline-size:1rem;color:var(--_et-qdt-subtle);font-size:1rem;line-height:1}.et-query-devtools-row-caret-btn{display:flex;flex:0 0 auto;align-items:center;appearance:none;border:none;background:transparent;color:inherit;cursor:pointer;padding-block:0;padding-inline:calc(.8rem + var(--_et-qdt-tree-depth, 0) * 1.4rem) 0;font:inherit}@media(hover:hover){.et-query-devtools-row-caret-btn:hover .et-query-devtools-row-caret{color:var(--_et-qdt-fg)}}.et-query-devtools-row>.et-query-devtools-row-select{padding-inline-start:calc(.8rem + var(--_et-qdt-tree-depth, 0) * 1.4rem)}.et-query-devtools-row--nested>.et-query-devtools-row-select{padding-inline-start:calc(2.6rem + var(--_et-qdt-tree-depth, 0) * 1.4rem)}.et-query-devtools-row--group>.et-query-devtools-row-select{padding-inline:.8rem 4.4rem}.et-query-devtools-consumer{flex:1 1 auto;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--_et-qdt-muted)}.et-query-devtools-row--folder{background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,transparent)}.et-query-devtools-folder{flex:1 1 auto;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--_et-qdt-muted)}.et-query-devtools-row--group{background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,transparent)}.et-query-devtools-row--group.et-query-devtools-row--active{background:var(--_et-qdt-active)}.et-query-devtools-row-count{flex:0 0 auto;color:var(--_et-qdt-subtle);font-variant-numeric:tabular-nums;font-size:1.1rem}.et-query-devtools-row-time{flex:0 0 auto;inline-size:8ch;text-align:end;color:var(--_et-qdt-subtle);font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:1.1rem}.et-query-devtools-sort{flex:0 0 auto;color:var(--_et-qdt-muted)}}\n"] }]
3780
+ args: [{ selector: 'et-query-devtools-queries-tab', encapsulation: ViewEncapsulation.None, imports: [NgTemplateOutlet, QueryDevtoolsDetailComponent, QueryDevtoolsRouteComponent], template: "<div #panes class=\"et-query-devtools-master-detail\">\n <div class=\"et-query-devtools-list\">\n <div [class.et-query-devtools-toolbar--filtered]=\"!!host.inspectFilterIds()\" class=\"et-query-devtools-toolbar\">\n <!-- Search leads: it is what the list is narrowed with most of the time. The scope controls sit\n after it, and the count reads as the search's result rather than as one more control. -->\n <div class=\"et-query-devtools-toolbar-row\">\n <input\n #queryFilterInput\n [value]=\"host.queryFilter()\"\n (input)=\"host.queryFilter.set(queryFilterInput.value)\"\n class=\"et-query-devtools-search\"\n type=\"search\"\n placeholder=\"filter method / route / url\u2026\"\n aria-label=\"Filter queries\"\n />\n @if (host.inspectFilterIds(); as ids) {\n <span class=\"et-query-devtools-filter-chip\">\n <span class=\"et-query-devtools-filter-icon\" aria-hidden=\"true\">\u2316</span>\n Inspected element\n <span class=\"et-query-devtools-filter-count\">{{ ids.length }}</span>\n </span>\n <button\n (click)=\"host.clearInspectFilter()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the inspection filter and show all queries\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n Clear\n </button>\n } @else {\n <select\n #clientSelect\n [value]=\"host.selectedClientName() ?? ''\"\n (change)=\"host.selectClient(clientSelect.value || null)\"\n class=\"et-query-devtools-select\"\n aria-label=\"Client\"\n >\n <option value=\"\">All clients</option>\n @for (name of host.clientNames(); track name) {\n <option [value]=\"name\">{{ name }}</option>\n }\n </select>\n }\n <span class=\"et-query-devtools-count\">\n @if (isQueryListNarrowed()) {\n {{ filteredQueries().length }} of {{ scopedQueryCount() }}\n } @else {\n {{ scopedQueryCount() }}\n }\n </span>\n <button\n [disabled]=\"!filteredQueries().length\"\n (click)=\"downloadInsomniaCollection()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Download the listed queries as an Insomnia collection, with the auth provider's token refresh\"\n >\n <span aria-hidden=\"true\">\u2913</span>\n Insomnia\n </button>\n </div>\n\n <div class=\"et-query-devtools-toolbar-row\">\n @let recentFirst = host.queryRecentFirst();\n <div class=\"et-query-devtools-btn-group\">\n <button\n [title]=\"\n (recentFirst ? 'Newest run first.' : 'Oldest run first.') +\n ' Click to reverse. Queries that have never run stay at the bottom either way.'\n \"\n (click)=\"host.queryRecentFirst.set(!recentFirst)\"\n class=\"et-query-devtools-btn et-query-devtools-sort\"\n type=\"button\"\n >\n recent <span aria-hidden=\"true\">{{ recentFirst ? '\u2193' : '\u2191' }}</span>\n </button>\n @let treeView = host.queryTreeView();\n <button\n [class.et-query-devtools-btn--active]=\"treeView\"\n [attr.aria-pressed]=\"treeView\"\n [title]=\"\n treeView\n ? 'Grouped by route path. Click for the flat list.'\n : 'Flat list. Click to group by route path - segments nothing branches off are folded into one row.'\n \"\n (click)=\"host.queryTreeView.set(!treeView)\"\n class=\"et-query-devtools-btn et-query-devtools-sort\"\n type=\"button\"\n >\n <span aria-hidden=\"true\">\u2442</span> tree\n </button>\n </div>\n <!-- Only the statuses something is actually in. Five chips, four of them reading `0`, were four\n controls that could not be pressed taking up the row that says what is wrong. -->\n @for (facet of visibleFacets(); track facet.id) {\n <button\n [class.et-query-devtools-facet--active]=\"facet.active\"\n [attr.aria-pressed]=\"facet.active\"\n (click)=\"host.toggleFacet(facet.id)\"\n class=\"et-query-devtools-chip et-query-devtools-facet\"\n type=\"button\"\n >\n {{ facet.label }}\n <span class=\"et-query-devtools-facet-count\">{{ facet.count }}</span>\n </button>\n }\n @let goneListed = listedGoneQueries().length;\n @if (goneListed) {\n <button\n (click)=\"forgetGoneQueries()\"\n class=\"et-query-devtools-forget\"\n type=\"button\"\n title=\"Drop the {{ goneListed }} destroyed queries listed below - they cannot be brought back\"\n >\n Forget {{ goneListed }}\n </button>\n }\n @if (isQueryListNarrowed()) {\n <button\n (click)=\"host.clearQueryFilters()\"\n class=\"et-query-devtools-filter-clear\"\n type=\"button\"\n title=\"Clear the search term and the status chips\"\n aria-label=\"Clear filters\"\n >\n <span aria-hidden=\"true\">\u2715</span>\n </button>\n }\n </div>\n </div>\n\n @if (host.queryTreeView()) {\n @for (row of queryTree(); track row.key) {\n @if (row.kind === 'folder') {\n <div [style.--_et-qdt-tree-depth]=\"row.depth\" class=\"et-query-devtools-row et-query-devtools-row--folder\">\n <button\n [attr.aria-expanded]=\"!row.collapsed\"\n [title]=\"\n (row.collapsed ? 'Expand' : 'Collapse') + ' /' + row.label + ' - ' + row.count + ' queries below it'\n \"\n (click)=\"host.toggleQueryPath(row.key)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ row.collapsed ? '\u25B8' : '\u25BE' }}</span>\n <span class=\"et-query-devtools-folder\">/{{ row.label }}</span>\n <span class=\"et-query-devtools-row-count\">{{ row.count }}</span>\n </button>\n </div>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"queryGroupBlock\"\n [ngTemplateOutletContext]=\"{ group: row.item, depth: row.depth, trim: row.parentPath }\"\n />\n }\n } @empty {\n <ng-container [ngTemplateOutlet]=\"emptyList\" />\n }\n } @else {\n @for (group of queryGroups(); track group.key) {\n <ng-container [ngTemplateOutlet]=\"queryGroupBlock\" [ngTemplateOutletContext]=\"{ group, depth: 0, trim: '' }\" />\n } @empty {\n <ng-container [ngTemplateOutlet]=\"emptyList\" />\n }\n }\n\n <!-- An empty scope is a different problem from a filter that matches nothing, and only the\n latter is the user's own doing - so it names the way out. -->\n <ng-template #emptyList>\n @if (scopedQueryCount()) {\n <p class=\"et-query-devtools-empty\">No query matches the filter \u00B7 {{ scopedQueryCount() }} in scope.</p>\n } @else if (goneQueryCount()) {\n <p class=\"et-query-devtools-empty\">\n No query in scope still exists \u00B7 pick Gone to list the {{ goneQueryCount() }} destroyed.\n </p>\n } @else if (host.inspectFilterIds()) {\n <p class=\"et-query-devtools-empty\">\n The inspected element's queries are no longer registered. Clear the filter.\n </p>\n } @else {\n <p class=\"et-query-devtools-empty\">No queries registered.</p>\n }\n </ng-template>\n\n <ng-template #queryGroupBlock let-group=\"group\" let-depth=\"depth\" let-trim=\"trim\">\n @if (batchEntryOf(group); as batchEntry) {\n <ng-container\n [ngTemplateOutlet]=\"batchGroupBlock\"\n [ngTemplateOutletContext]=\"{ group, batchEntry, depth, trim }\"\n />\n } @else if (group.items.length === 1) {\n <ng-container [ngTemplateOutlet]=\"queryRow\" [ngTemplateOutletContext]=\"{ item: group.head, depth, trim }\" />\n } @else {\n @let expanded = isGroupExpanded(group);\n @let head = group.head;\n @let groupGone = !!head.entry.destroyedAt;\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"holdsSelection(group)\"\n class=\"et-query-devtools-row et-query-devtools-row--group\"\n >\n <!-- The caret is its own control: every row in the fold is the same request, so opening the\n fold and inspecting what it holds are two different intentions. -->\n <button\n [attr.aria-expanded]=\"expanded\"\n [attr.aria-label]=\"\n (expanded ? 'Collapse' : 'Expand') +\n ' the ' +\n group.items.length +\n ' consumers of this query - one registry entry each'\n \"\n [title]=\"\n (expanded ? 'Collapse' : 'Expand') +\n ' the ' +\n group.items.length +\n ' consumers of this query - one registry entry each'\n \"\n (click)=\"host.toggleQueryGroup(group.key)\"\n class=\"et-query-devtools-row-caret-btn\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ expanded ? '\u25BE' : '\u25B8' }}</span>\n </button>\n <button\n (click)=\"selectGroup(group)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n title=\"Inspect this request - all {{\n group.items.length\n }} consumers share it, so any of them answers for it\"\n >\n <span\n [attr.data-status]=\"groupGone ? null : groupStatus(group)\"\n [title]=\"groupGone ? 'destroyed' : groupStatus(group)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ head.entry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.routeSegments(head.entry, head.query)\" [trimPrefix]=\"trim\" />\n </span>\n @if (groupGone) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"These queries no longer exist - what you see is the last state they held, frozen\"\n >\n gone\n </span>\n }\n @if (!groupGone && isGroupStale(group)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">stale</span>\n }\n @if (isGroupTampered(group)) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"At least one of these is showing an armed response override or a devtools-injected failure\"\n >\n tampered\n </span>\n }\n <span class=\"et-query-devtools-row-count\" title=\"Consumers of this one request\">\n \u00D7{{ group.items.length }}\n </span>\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(head)) }}</span>\n </button>\n </div>\n @if (expanded) {\n @for (item of group.items; track item.entry.id) {\n <ng-container\n [ngTemplateOutlet]=\"queryRow\"\n [ngTemplateOutletContext]=\"{ item, nested: true, ordinal: $index + 1, depth, trim }\"\n />\n }\n }\n }\n </ng-template>\n\n <!-- A bulk run's items, folded under the run rather than under what a row shows. They only exist\n while in flight - the batch destroys each item's query as it settles - so this row stands for\n the whole run, and the Batches tab is where its settled items are. -->\n <ng-template #batchGroupBlock let-group=\"group\" let-batchEntry=\"batchEntry\" let-depth=\"depth\" let-trim=\"trim\">\n @let batch = host.asBatch(batchEntry);\n @let expanded = isGroupExpanded(group);\n @let toggleLabel =\n (expanded ? 'Collapse' : 'Expand') + ' the ' + group.items.length + ' requests this batch has in flight';\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"holdsSelection(group)\"\n class=\"et-query-devtools-row et-query-devtools-row--group\"\n >\n <button\n [attr.aria-expanded]=\"expanded\"\n [attr.aria-label]=\"toggleLabel\"\n [title]=\"toggleLabel\"\n (click)=\"host.toggleQueryGroup(group.key)\"\n class=\"et-query-devtools-row-caret-btn\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\">{{ expanded ? '\u25BE' : '\u25B8' }}</span>\n </button>\n <button\n (click)=\"selectGroup(group)\"\n class=\"et-query-devtools-row-select\"\n type=\"button\"\n title=\"One item of a batch run - {{ batch.completed() }} of {{\n batch.total()\n }} settled. The Batches tab has the whole run.\"\n >\n <span\n [attr.data-status]=\"groupStatus(group)\"\n [title]=\"groupStatus(group)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ batchEntry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.batchRouteSegments(batchEntry)\" [trimPrefix]=\"trim\" />\n </span>\n <span\n class=\"et-query-devtools-chip\"\n title=\"Registered by createQueryBatch - inspect the run on the Batches tab\"\n >\n batch {{ batch.completed() }}/{{ batch.total() }}\n </span>\n @if (batch.failed()) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--error\">{{ batch.failed() }} failed</span>\n }\n <span class=\"et-query-devtools-row-count\" title=\"Requests this batch has in flight\">\n \u00D7{{ group.items.length }}\n </span>\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(group.head)) }}</span>\n </button>\n </div>\n @if (expanded) {\n @for (item of group.items; track item.entry.id) {\n <ng-container\n [ngTemplateOutlet]=\"queryRow\"\n [ngTemplateOutletContext]=\"{ item, nested: true, showRoute: true, depth, trim }\"\n />\n }\n }\n </ng-template>\n\n <ng-template\n #queryRow\n let-item=\"item\"\n let-nested=\"nested\"\n let-showRoute=\"showRoute\"\n let-ordinal=\"ordinal\"\n let-depth=\"depth\"\n let-trim=\"trim\"\n >\n @let isGone = !!item.entry.destroyedAt;\n @let isPinned = host.isQueryPinned(item.entry);\n @let pinLabel = isPinned ? 'Unpin this query' : 'Pin this query to the top of the list';\n <div\n [style.--_et-qdt-tree-depth]=\"depth\"\n [class.et-query-devtools-row--active]=\"host.selectedQueryId() === item.entry.id\"\n [class.et-query-devtools-row--gone]=\"isGone\"\n [class.et-query-devtools-row--nested]=\"nested\"\n class=\"et-query-devtools-row\"\n >\n <button (click)=\"host.selectedQueryId.set(item.entry.id)\" class=\"et-query-devtools-row-select\" type=\"button\">\n <span class=\"et-query-devtools-row-caret\" aria-hidden=\"true\"></span>\n <span\n [attr.data-status]=\"isGone ? null : host.queryStatus(item.query)\"\n [title]=\"isGone ? 'destroyed' : host.queryStatus(item.query)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ item.entry.meta.method }}</span>\n <!-- Inside a fold the route is the fold's own head, repeated once per consumer. Where the query\n was created is the only thing that differs, so that is what the row says instead. A batch\n fold is the exception: its items differ in the route, which is why `showRoute` is set. -->\n @if (nested && !showRoute) {\n <span class=\"et-query-devtools-consumer\">\n @if (consumerLabel(item); as consumer) {\n &lt;{{ consumer }}&gt;\n } @else {\n created outside a component\n }\n </span>\n <span class=\"et-query-devtools-row-count\">#{{ ordinal }}</span>\n } @else {\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.routeSegments(item.entry, item.query)\" [trimPrefix]=\"trim\" />\n </span>\n }\n @if (isGone) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--muted\"\n title=\"This query no longer exists - what you see is the last state it held, frozen\"\n >\n gone\n </span>\n }\n <!-- A retry is the one thing a loading dot actively misrepresents: it reads as a request\n on its way when nothing is in flight at all. -->\n @if (!isGone && host.requestProgress(item.query)?.retry; as retry) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--loading\"\n title=\"Waiting out a retry backoff before attempt {{ retry.attempt }}\"\n >\n <span class=\"et-query-devtools-retry-glyph\" aria-hidden=\"true\">\u27F3</span>\n {{ retry.attempt }}\n </span>\n }\n @if (!isGone && host.isStale(item.query)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">stale</span>\n }\n @if (host.isTampered(item.entry)) {\n <span\n class=\"et-query-devtools-chip et-query-devtools-chip--error\"\n title=\"Showing an armed response override or a devtools-injected failure, not what the server actually sent\"\n >\n tampered\n </span>\n }\n <span class=\"et-query-devtools-row-time\">{{ host.formatTime(lastExecutedAt(item)) }}</span>\n </button>\n <button\n [class.et-query-devtools-row-pin--active]=\"isPinned\"\n [title]=\"pinLabel\"\n [attr.aria-label]=\"pinLabel\"\n [attr.aria-pressed]=\"isPinned\"\n (click)=\"host.toggleQueryPin(item.entry)\"\n class=\"et-query-devtools-row-pin\"\n type=\"button\"\n >\n <span aria-hidden=\"true\">{{ isPinned ? '\u2605' : '\u2606' }}</span>\n </button>\n </div>\n </ng-template>\n </div>\n\n <div\n (pointerdown)=\"host.startPaneResize($event, { pane: 'list', container: panes })\"\n (dblclick)=\"host.resetPaneSize('list')\"\n class=\"et-query-devtools-pane-resize\"\n title=\"Drag to resize, double-click to reset\"\n aria-hidden=\"true\"\n ></div>\n\n <div class=\"et-query-devtools-detail\">\n @if (host.selectedQuery(); as sel) {\n <et-query-devtools-detail [sel]=\"sel\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Select a query to inspect it.</p>\n }\n </div>\n</div>\n", styles: ["@layer components{.et-query-devtools-master-detail{display:flex;block-size:100%;min-block-size:0}.et-query-devtools-list{flex:0 0 var(--_et-qdt-list-width, 34%);min-inline-size:var(--_et-qdt-list-width, 22rem);max-inline-size:var(--_et-qdt-list-width, 40rem);overflow-y:auto;border-inline-end:1px solid var(--_et-qdt-border)}.et-query-devtools-forget{appearance:none;display:inline-flex;align-items:center;gap:.5rem;border:1px solid color-mix(in srgb,var(--_et-qdt-error) 50%,transparent);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-error) 10%,transparent);color:var(--_et-qdt-error);cursor:pointer;padding:.3rem .6rem;font:inherit;font-size:1.15rem;line-height:1.4}@media(hover:hover){.et-query-devtools-forget:hover{border-color:var(--_et-qdt-error);background:color-mix(in srgb,var(--_et-qdt-error) 22%,transparent)}}.et-query-devtools-list .et-query-devtools-toolbar-row .et-query-devtools-search{flex:1 1 100%}.et-query-devtools-toolbar--filtered{background:linear-gradient(to right,color-mix(in srgb,var(--_et-qdt-accent) 12%,transparent),transparent 65%),var(--_et-qdt-bg);box-shadow:inset .2rem 0 0 var(--_et-qdt-accent),inset 0 -1px color-mix(in srgb,var(--_et-qdt-accent) 30%,transparent)}.et-query-devtools-row-select{display:flex;flex:1 1 auto;align-items:center;gap:.8rem;min-inline-size:0;text-align:start;appearance:none;border:none;background:transparent;color:inherit;padding:.8rem;cursor:pointer;font:inherit}.et-query-devtools-row-select>.et-query-devtools-chip{padding-block:.1rem;line-height:1.2}.et-query-devtools-row-pin{display:flex;flex:0 0 auto;align-self:center;align-items:center;justify-content:center;inline-size:3.2rem;block-size:3.2rem;margin-inline-end:.4rem;appearance:none;border:none;border-radius:.4rem;background:transparent;color:var(--_et-qdt-subtle);cursor:pointer;font-family:inherit;font-size:1.8rem;line-height:1;opacity:0}.et-query-devtools-row-pin:focus-visible,.et-query-devtools-row-pin--active{opacity:1}@media(hover:hover){.et-query-devtools-row:hover .et-query-devtools-row-pin{opacity:1}}@media(hover:none){.et-query-devtools-row-pin{opacity:1}}@media(hover:hover){.et-query-devtools-row-pin:hover{background:var(--_et-qdt-active);color:var(--_et-qdt-fg)}}.et-query-devtools-row-pin--active,.et-query-devtools-row-pin--active:hover{color:var(--_et-qdt-accent)}.et-query-devtools-row--active{background:var(--_et-qdt-active)}.et-query-devtools-row--gone{color:var(--_et-qdt-muted)}.et-query-devtools-row-caret{flex:0 0 auto;inline-size:1rem;color:var(--_et-qdt-subtle);font-size:1rem;line-height:1}.et-query-devtools-row-caret-btn{display:flex;flex:0 0 auto;align-items:center;appearance:none;border:none;background:transparent;color:inherit;cursor:pointer;padding-block:0;padding-inline:calc(.8rem + var(--_et-qdt-tree-depth, 0) * 1.4rem) 0;font:inherit}@media(hover:hover){.et-query-devtools-row-caret-btn:hover .et-query-devtools-row-caret{color:var(--_et-qdt-fg)}}.et-query-devtools-row>.et-query-devtools-row-select{padding-inline-start:calc(.8rem + var(--_et-qdt-tree-depth, 0) * 1.4rem)}.et-query-devtools-row--nested>.et-query-devtools-row-select{padding-inline-start:calc(2.6rem + var(--_et-qdt-tree-depth, 0) * 1.4rem)}.et-query-devtools-row--group>.et-query-devtools-row-select{padding-inline:.8rem 4.4rem}.et-query-devtools-consumer{flex:1 1 auto;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--_et-qdt-muted)}.et-query-devtools-row--folder{background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,transparent)}.et-query-devtools-folder{flex:1 1 auto;min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--_et-qdt-muted)}.et-query-devtools-row--group{background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,transparent)}.et-query-devtools-row--group.et-query-devtools-row--active{background:var(--_et-qdt-active)}.et-query-devtools-row-count{flex:0 0 auto;color:var(--_et-qdt-subtle);font-variant-numeric:tabular-nums;font-size:1.1rem}.et-query-devtools-row-time{flex:0 0 auto;inline-size:8ch;text-align:end;color:var(--_et-qdt-subtle);font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:1.1rem}.et-query-devtools-sort{flex:0 0 auto;color:var(--_et-qdt-muted)}}\n"] }]
3781
+ }], ctorParameters: () => [] });
3782
+
3783
+ /**
3784
+ * The expandable step/item row shared by the Sequences and Batches tabs, as a styles-only component each
3785
+ * of them mounts. It cannot live in either tab's own stylesheet: a tab's CSS is injected when that tab is
3786
+ * first opened, so the Batches tab would render unstyled rows until Sequences had been visited.
3787
+ *
3788
+ * @internal
3789
+ */
3790
+ class QueryDevtoolsStepStylesComponent {
3791
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsStepStylesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3792
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.7", type: QueryDevtoolsStepStylesComponent, isStandalone: true, selector: "et-query-devtools-step-styles", ngImport: i0, template: '', isInline: true, styles: ["@layer components{.et-query-devtools-step-list{display:flex;flex-direction:column;gap:.4rem;margin:0;padding:0;list-style:none}.et-query-devtools-step-item{border:1px solid var(--_et-qdt-border);border-radius:.4rem;overflow:hidden}.et-query-devtools-step-item[data-status=error]{border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-step-head{display:flex;align-items:stretch}.et-query-devtools-step-head .et-query-devtools-inner-row{border:none;border-radius:0;flex:1 1 auto;min-inline-size:0}.et-query-devtools-step-caret-btn{appearance:none;border:none;border-inline-start:1px solid var(--_et-qdt-border);background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:0 .7rem;font:inherit}@media(hover:hover){.et-query-devtools-step-caret-btn:hover{background:var(--_et-qdt-hover);color:var(--_et-qdt-fg)}}.et-query-devtools-step-io{padding:0 .8rem .8rem;border-block-start:1px solid var(--_et-qdt-border)}}\n"], encapsulation: i0.ViewEncapsulation.None }); }
3793
+ }
3794
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsStepStylesComponent, decorators: [{
3795
+ type: Component,
3796
+ args: [{ selector: 'et-query-devtools-step-styles', template: '', encapsulation: ViewEncapsulation.None, styles: ["@layer components{.et-query-devtools-step-list{display:flex;flex-direction:column;gap:.4rem;margin:0;padding:0;list-style:none}.et-query-devtools-step-item{border:1px solid var(--_et-qdt-border);border-radius:.4rem;overflow:hidden}.et-query-devtools-step-item[data-status=error]{border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-step-head{display:flex;align-items:stretch}.et-query-devtools-step-head .et-query-devtools-inner-row{border:none;border-radius:0;flex:1 1 auto;min-inline-size:0}.et-query-devtools-step-caret-btn{appearance:none;border:none;border-inline-start:1px solid var(--_et-qdt-border);background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:0 .7rem;font:inherit}@media(hover:hover){.et-query-devtools-step-caret-btn:hover{background:var(--_et-qdt-hover);color:var(--_et-qdt-fg)}}.et-query-devtools-step-io{padding:0 .8rem .8rem;border-block-start:1px solid var(--_et-qdt-border)}}\n"] }]
3797
+ }] });
3798
+
3799
+ /** The Batches tab: registered `createQueryBatch` runs, their progress and every item's outcome. */
3800
+ class QueryDevtoolsBatchesTabComponent {
3801
+ constructor() {
3802
+ this.host = injectQueryDevtoolsHost();
3803
+ injectStyleManager().mount(QueryDevtoolsStepStylesComponent);
3804
+ }
3805
+ /**
3806
+ * The dot an item's outcome gets. Skipped and not-attempted items share the neutral one: neither
3807
+ * reached the API, and a red dot for either would read as something the run has to recover from.
3808
+ */
3809
+ itemStatus(result) {
3810
+ return result.status === 'success' || result.status === 'error' ? result.status : null;
3811
+ }
3812
+ /** Whether the item was actually sent, which is what decides it has an in/out worth rendering. */
3813
+ wasSent(result) {
3814
+ return result.status === 'success' || result.status === 'error';
3815
+ }
3816
+ itemArgs(result) {
3817
+ return this.wasSent(result) ? result.args : null;
3818
+ }
3819
+ itemResponse(result) {
3820
+ return result.status === 'success' ? result.response : null;
3821
+ }
3822
+ /** The error body of a failed item, as the value explorer wants it. */
3823
+ itemError(result) {
3824
+ if (result.status !== 'error')
3825
+ return null;
3826
+ return { status: result.error.raw.status, body: result.error.isList ? result.error.errors : result.error.error };
3827
+ }
3828
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsBatchesTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3829
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsBatchesTabComponent, isStandalone: true, selector: "et-query-devtools-batches-tab", ngImport: i0, template: "<div #panes class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-scroll\">\n @for (entry of host.batchEntries(); track entry.id) {\n @let batch = host.asBatch(entry);\n @if (batch) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-chip\">batch</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ batch.status() }}</span>\n <span class=\"et-query-devtools-method\">{{ entry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.batchRouteSegments(entry)\" fallback=\"no route\" />\n </span>\n <span class=\"et-query-devtools-count\">{{ batch.completed() }} / {{ batch.total() }}</span>\n </div>\n\n <div class=\"et-query-devtools-batch-bar\" role=\"presentation\">\n <!-- Coloured by the run, not by whether an item failed: a bar filled red end to end reads as\n \"nothing landed\", which is the opposite of what a partial run means. -->\n <span\n [style.inline-size.%]=\"batch.progress()\"\n [attr.data-status]=\"batch.status()\"\n class=\"et-query-devtools-batch-bar-fill\"\n ></span>\n </div>\n\n <dl class=\"et-query-devtools-meta\">\n <dt>Outcomes</dt>\n <dd>\n @if (batch.total()) {\n {{ batch.succeeded() }} ok\n @if (batch.failed()) {\n \u00B7 <span class=\"et-query-devtools-batch-failed\">{{ batch.failed() }} failed</span>\n }\n @if (batch.skipped()) {\n \u00B7 {{ batch.skipped() }} skipped\n }\n @let notAttempted = batch.total() - batch.completed();\n @if (!batch.running() && notAttempted) {\n \u00B7 {{ notAttempted }} not attempted\n }\n } @else {\n \u2014 never run\n }\n </dd>\n\n <dt>Throughput</dt>\n <dd>\n @if (host.batchThroughput(batch); as throughput) {\n {{ throughput }}\n } @else if (batch.running()) {\n measuring\u2026\n } @else if (batch.total()) {\n <span title=\"A rate needs the first `concurrency` items and a couple of seconds of run time\">\n run too short to measure\n </span>\n } @else {\n \u2014\n }\n </dd>\n\n <dt>In flight</dt>\n <dd>\n {{ batch.inFlight() }} of {{ entry.meta.concurrency }} concurrent\n @if (entry.meta.stopOnError) {\n \u00B7 stops on the first failure\n }\n </dd>\n </dl>\n\n <!-- Only what is in flight: the batch destroys an item's query the moment it settles, so a\n settled item is in the list below instead of here. -->\n @let inFlight = host.queriesForBatch(entry);\n @if (inFlight.length) {\n <h5 class=\"et-query-devtools-section\">In flight</h5>\n <ul class=\"et-query-devtools-step-list\">\n @for (link of inFlight; track link.id) {\n <li>\n <button\n [class.et-query-devtools-inner-row--active]=\"host.batchSelectedQueryId() === link.id\"\n (click)=\"host.batchSelectedQueryId.set(link.id)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-status\" data-status=\"loading\"></span>\n <span class=\"et-query-devtools-method\">{{ link.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"link.segments\" fallback=\"in flight\" />\n </span>\n </button>\n </li>\n }\n </ul>\n }\n\n @let items = host.batchItems(entry);\n @if (items.rows.length) {\n <h5 class=\"et-query-devtools-section\">\n Items\n @if (items.hidden) {\n <span class=\"et-query-devtools-count\" title=\"Failures are listed first, so the cap never drops one\">\n {{ items.rows.length }} of {{ items.total }} \u00B7 {{ items.hidden }} not listed\n </span>\n } @else {\n <span class=\"et-query-devtools-count\">{{ items.total }}</span>\n }\n </h5>\n\n <ol class=\"et-query-devtools-step-list\">\n @for (row of items.rows; track row.result.index) {\n @let result = row.result;\n @let expanded = host.isBatchItemExpanded(entry.id, result.index);\n @let itemQueryId = host.batchItemQueryId(entry, result.index);\n <li [attr.data-status]=\"itemStatus(result)\" class=\"et-query-devtools-step-item\">\n <div class=\"et-query-devtools-step-head\">\n <!-- The row opens the item's own query in the drawer, the way a sequence step does.\n Disabled once that query has fallen out of the batch's capped tombstone tail -\n the caret below still has this item's args and outcome either way. -->\n <button\n [class.et-query-devtools-inner-row--active]=\"\n !!itemQueryId && host.batchSelectedQueryId() === itemQueryId\n \"\n [disabled]=\"!itemQueryId\"\n [title]=\"\n itemQueryId\n ? 'Inspect this item\\'s request'\n : 'This item\\'s query is no longer kept - open the row for its args and outcome'\n \"\n (click)=\"host.batchSelectedQueryId.set(itemQueryId)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span\n [attr.data-status]=\"itemStatus(result)\"\n [title]=\"result.status\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">#{{ result.index }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"row.segments\" fallback=\"not sent\" />\n </span>\n <!-- The dot already says success or error; only an item that never went out needs\n a word, since a neutral dot alone cannot tell the two reasons apart. -->\n @if (!wasSent(result)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ result.status }}</span>\n }\n </button>\n <button\n [attr.aria-expanded]=\"expanded\"\n (click)=\"host.toggleBatchItem(entry.id, result.index)\"\n class=\"et-query-devtools-step-caret-btn\"\n type=\"button\"\n title=\"Toggle in / out\"\n >\n {{ expanded ? '\u25BE' : '\u25B8' }}\n </button>\n </div>\n\n @if (expanded) {\n <div class=\"et-query-devtools-step-io\">\n <h5 class=\"et-query-devtools-section\">Item</h5>\n <et-query-devtools-json [value]=\"result.item\" />\n\n @if (wasSent(result)) {\n <h5 class=\"et-query-devtools-section\">In \u00B7 args</h5>\n <et-query-devtools-json [value]=\"itemArgs(result)\" />\n\n @if (itemError(result); as error) {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 error ({{ error.status }})</h5>\n <et-query-devtools-json [value]=\"error.body\" />\n } @else {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 response</h5>\n <et-query-devtools-json [value]=\"itemResponse(result)\" />\n }\n } @else {\n <p class=\"et-query-devtools-empty\">\n @if (result.status === 'skipped') {\n The args function returned null, so nothing was sent.\n } @else {\n Still queued when the run stopped - nothing was sent.\n }\n </p>\n }\n </div>\n }\n </li>\n }\n </ol>\n }\n </div>\n }\n } @empty {\n <p class=\"et-query-devtools-empty\">No batches registered.</p>\n }\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.batchSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.batchSelectedQueryId.set(null)\"\n />\n</div>\n", styles: ["@layer components{.et-query-devtools-batch-bar{block-size:.6rem;border-radius:999px;background:color-mix(in srgb,var(--_et-qdt-interaction) 10%,transparent);overflow:hidden;margin-block-end:1rem}.et-query-devtools-batch-bar-fill{display:block;block-size:100%;background:var(--_et-qdt-accent);transition:inline-size .15s linear}.et-query-devtools-batch-bar-fill[data-status=success]{background:var(--_et-qdt-success)}.et-query-devtools-batch-bar-fill[data-status=error]{background:var(--_et-qdt-error)}.et-query-devtools-batch-failed{color:var(--_et-qdt-error)}}\n"], dependencies: [{ kind: "component", type: QueryDevtoolsDrawerComponent, selector: "et-query-devtools-drawer", inputs: ["sel", "container"], outputs: ["dismiss"] }, { kind: "component", type: QueryDevtoolsJsonComponent, selector: "et-query-devtools-json", inputs: ["value", "nodeKey", "depth", "search", "chunk", "path", "expandedPaths", "collapsedPaths", "toggleFn", "jsonPath", "parentKind", "overrides", "annotations"] }, { kind: "component", type: QueryDevtoolsRouteComponent, selector: "et-query-devtools-route", inputs: ["segments", "fallback", "trimPrefix"] }], encapsulation: i0.ViewEncapsulation.None }); }
3830
+ }
3831
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsBatchesTabComponent, decorators: [{
3832
+ type: Component,
3833
+ args: [{ selector: 'et-query-devtools-batches-tab', encapsulation: ViewEncapsulation.None, imports: [QueryDevtoolsDrawerComponent, QueryDevtoolsJsonComponent, QueryDevtoolsRouteComponent], template: "<div #panes class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-scroll\">\n @for (entry of host.batchEntries(); track entry.id) {\n @let batch = host.asBatch(entry);\n @if (batch) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-chip\">batch</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ batch.status() }}</span>\n <span class=\"et-query-devtools-method\">{{ entry.meta.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"host.batchRouteSegments(entry)\" fallback=\"no route\" />\n </span>\n <span class=\"et-query-devtools-count\">{{ batch.completed() }} / {{ batch.total() }}</span>\n </div>\n\n <div class=\"et-query-devtools-batch-bar\" role=\"presentation\">\n <!-- Coloured by the run, not by whether an item failed: a bar filled red end to end reads as\n \"nothing landed\", which is the opposite of what a partial run means. -->\n <span\n [style.inline-size.%]=\"batch.progress()\"\n [attr.data-status]=\"batch.status()\"\n class=\"et-query-devtools-batch-bar-fill\"\n ></span>\n </div>\n\n <dl class=\"et-query-devtools-meta\">\n <dt>Outcomes</dt>\n <dd>\n @if (batch.total()) {\n {{ batch.succeeded() }} ok\n @if (batch.failed()) {\n \u00B7 <span class=\"et-query-devtools-batch-failed\">{{ batch.failed() }} failed</span>\n }\n @if (batch.skipped()) {\n \u00B7 {{ batch.skipped() }} skipped\n }\n @let notAttempted = batch.total() - batch.completed();\n @if (!batch.running() && notAttempted) {\n \u00B7 {{ notAttempted }} not attempted\n }\n } @else {\n \u2014 never run\n }\n </dd>\n\n <dt>Throughput</dt>\n <dd>\n @if (host.batchThroughput(batch); as throughput) {\n {{ throughput }}\n } @else if (batch.running()) {\n measuring\u2026\n } @else if (batch.total()) {\n <span title=\"A rate needs the first `concurrency` items and a couple of seconds of run time\">\n run too short to measure\n </span>\n } @else {\n \u2014\n }\n </dd>\n\n <dt>In flight</dt>\n <dd>\n {{ batch.inFlight() }} of {{ entry.meta.concurrency }} concurrent\n @if (entry.meta.stopOnError) {\n \u00B7 stops on the first failure\n }\n </dd>\n </dl>\n\n <!-- Only what is in flight: the batch destroys an item's query the moment it settles, so a\n settled item is in the list below instead of here. -->\n @let inFlight = host.queriesForBatch(entry);\n @if (inFlight.length) {\n <h5 class=\"et-query-devtools-section\">In flight</h5>\n <ul class=\"et-query-devtools-step-list\">\n @for (link of inFlight; track link.id) {\n <li>\n <button\n [class.et-query-devtools-inner-row--active]=\"host.batchSelectedQueryId() === link.id\"\n (click)=\"host.batchSelectedQueryId.set(link.id)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span class=\"et-query-devtools-status\" data-status=\"loading\"></span>\n <span class=\"et-query-devtools-method\">{{ link.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"link.segments\" fallback=\"in flight\" />\n </span>\n </button>\n </li>\n }\n </ul>\n }\n\n @let items = host.batchItems(entry);\n @if (items.rows.length) {\n <h5 class=\"et-query-devtools-section\">\n Items\n @if (items.hidden) {\n <span class=\"et-query-devtools-count\" title=\"Failures are listed first, so the cap never drops one\">\n {{ items.rows.length }} of {{ items.total }} \u00B7 {{ items.hidden }} not listed\n </span>\n } @else {\n <span class=\"et-query-devtools-count\">{{ items.total }}</span>\n }\n </h5>\n\n <ol class=\"et-query-devtools-step-list\">\n @for (row of items.rows; track row.result.index) {\n @let result = row.result;\n @let expanded = host.isBatchItemExpanded(entry.id, result.index);\n @let itemQueryId = host.batchItemQueryId(entry, result.index);\n <li [attr.data-status]=\"itemStatus(result)\" class=\"et-query-devtools-step-item\">\n <div class=\"et-query-devtools-step-head\">\n <!-- The row opens the item's own query in the drawer, the way a sequence step does.\n Disabled once that query has fallen out of the batch's capped tombstone tail -\n the caret below still has this item's args and outcome either way. -->\n <button\n [class.et-query-devtools-inner-row--active]=\"\n !!itemQueryId && host.batchSelectedQueryId() === itemQueryId\n \"\n [disabled]=\"!itemQueryId\"\n [title]=\"\n itemQueryId\n ? 'Inspect this item\\'s request'\n : 'This item\\'s query is no longer kept - open the row for its args and outcome'\n \"\n (click)=\"host.batchSelectedQueryId.set(itemQueryId)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span\n [attr.data-status]=\"itemStatus(result)\"\n [title]=\"result.status\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">#{{ result.index }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"row.segments\" fallback=\"not sent\" />\n </span>\n <!-- The dot already says success or error; only an item that never went out needs\n a word, since a neutral dot alone cannot tell the two reasons apart. -->\n @if (!wasSent(result)) {\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ result.status }}</span>\n }\n </button>\n <button\n [attr.aria-expanded]=\"expanded\"\n (click)=\"host.toggleBatchItem(entry.id, result.index)\"\n class=\"et-query-devtools-step-caret-btn\"\n type=\"button\"\n title=\"Toggle in / out\"\n >\n {{ expanded ? '\u25BE' : '\u25B8' }}\n </button>\n </div>\n\n @if (expanded) {\n <div class=\"et-query-devtools-step-io\">\n <h5 class=\"et-query-devtools-section\">Item</h5>\n <et-query-devtools-json [value]=\"result.item\" />\n\n @if (wasSent(result)) {\n <h5 class=\"et-query-devtools-section\">In \u00B7 args</h5>\n <et-query-devtools-json [value]=\"itemArgs(result)\" />\n\n @if (itemError(result); as error) {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 error ({{ error.status }})</h5>\n <et-query-devtools-json [value]=\"error.body\" />\n } @else {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 response</h5>\n <et-query-devtools-json [value]=\"itemResponse(result)\" />\n }\n } @else {\n <p class=\"et-query-devtools-empty\">\n @if (result.status === 'skipped') {\n The args function returned null, so nothing was sent.\n } @else {\n Still queued when the run stopped - nothing was sent.\n }\n </p>\n }\n </div>\n }\n </li>\n }\n </ol>\n }\n </div>\n }\n } @empty {\n <p class=\"et-query-devtools-empty\">No batches registered.</p>\n }\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.batchSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.batchSelectedQueryId.set(null)\"\n />\n</div>\n", styles: ["@layer components{.et-query-devtools-batch-bar{block-size:.6rem;border-radius:999px;background:color-mix(in srgb,var(--_et-qdt-interaction) 10%,transparent);overflow:hidden;margin-block-end:1rem}.et-query-devtools-batch-bar-fill{display:block;block-size:100%;background:var(--_et-qdt-accent);transition:inline-size .15s linear}.et-query-devtools-batch-bar-fill[data-status=success]{background:var(--_et-qdt-success)}.et-query-devtools-batch-bar-fill[data-status=error]{background:var(--_et-qdt-error)}.et-query-devtools-batch-failed{color:var(--_et-qdt-error)}}\n"] }]
3742
3834
  }], ctorParameters: () => [] });
3743
3835
 
3744
3836
  /** The Sequences tab: registered `QuerySequence` instances, step-by-step, with a split-view drawer. */
3745
3837
  class QueryDevtoolsSequencesTabComponent {
3746
3838
  constructor() {
3747
3839
  this.host = injectQueryDevtoolsHost();
3840
+ injectStyleManager().mount(QueryDevtoolsStepStylesComponent);
3748
3841
  }
3749
3842
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsSequencesTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3750
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsSequencesTabComponent, isStandalone: true, selector: "et-query-devtools-sequences-tab", ngImport: i0, template: "<div #panes class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-scroll\">\n @for (entry of host.sequenceEntries(); track entry.id) {\n @let sequence = host.asSequence(entry);\n @if (sequence) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-chip\">sequence</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ sequence.status() }}</span>\n <span class=\"et-query-devtools-count\">step {{ sequence.currentStep() }} / {{ sequence.total }}</span>\n </div>\n\n <dl class=\"et-query-devtools-meta\">\n <dt>Traffic</dt>\n <dd>\n @let activity = host.sequenceActivity(sequence);\n @if (activity.hasActivity) {\n {{ activity.stats.requests }} request{{ activity.stats.requests === 1 ? '' : 's' }} \u00B7 \u2193\n {{ host.formatTransferred(activity.stats.receivedBytes, activity.stats.hasEstimatedBytes) }}\n @if (activity.stats.sentBytes) {\n \u00B7 \u2191 {{ host.formatTransferred(activity.stats.sentBytes, activity.stats.hasEstimatedBytes) }}\n }\n @if (activity.cacheServed) {\n \u00B7 {{ activity.cacheServed }} from cache\n }\n @if (activity.stats.errors) {\n \u00B7 {{ activity.stats.errors }} failed\n }\n } @else {\n \u2014\n }\n </dd>\n </dl>\n\n <ol class=\"et-query-devtools-step-list\">\n @for (link of host.queriesForSequence(sequence); track $index; let i = $index) {\n <li [attr.data-status]=\"host.sequenceStepStatus(sequence, i)\" class=\"et-query-devtools-step-item\">\n <div class=\"et-query-devtools-step-head\">\n <button\n [class.et-query-devtools-inner-row--active]=\"host.sequenceSelectedQueryId() === link.id\"\n [disabled]=\"!link.id\"\n (click)=\"host.sequenceSelectedQueryId.set(link.id)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span\n [attr.data-status]=\"host.sequenceStepStatus(sequence, i)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ i + 1 }}. {{ link.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"link.segments\" [fallback]=\"'step ' + (i + 1)\" />\n </span>\n </button>\n <button\n [attr.aria-expanded]=\"host.isStepExpanded(entry.id, i)\"\n (click)=\"host.toggleStep(entry.id, i)\"\n class=\"et-query-devtools-step-caret-btn\"\n type=\"button\"\n title=\"Toggle in / out\"\n >\n {{ host.isStepExpanded(entry.id, i) ? '\u25BE' : '\u25B8' }}\n </button>\n </div>\n\n @if (host.isStepExpanded(entry.id, i)) {\n @let snap = host.stepSnapshot(sequence, i);\n <div class=\"et-query-devtools-step-io\">\n <h5 class=\"et-query-devtools-section\">In \u00B7 args</h5>\n @if (sequence.stepArgs().length > i) {\n <et-query-devtools-json [value]=\"sequence.stepArgs()[i]\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Not run yet.</p>\n }\n\n @if (snap?.error(); as error) {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 error ({{ error.raw.status }})</h5>\n <et-query-devtools-json [value]=\"error.isList ? error.errors : error.error\" />\n } @else {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 response</h5>\n @if (snap) {\n <et-query-devtools-json [value]=\"snap.response()\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Not run yet.</p>\n }\n }\n </div>\n }\n </li>\n }\n </ol>\n </div>\n }\n } @empty {\n <p class=\"et-query-devtools-empty\">No sequences registered.</p>\n }\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.sequenceSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.sequenceSelectedQueryId.set(null)\"\n />\n</div>\n", styles: ["@layer components{.et-query-devtools-step-list{display:flex;flex-direction:column;gap:.4rem;margin:0;padding:0;list-style:none}.et-query-devtools-step-item{border:1px solid var(--_et-qdt-border);border-radius:.4rem;overflow:hidden}.et-query-devtools-step-item[data-status=error]{border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-step-head{display:flex;align-items:stretch}.et-query-devtools-step-head .et-query-devtools-inner-row{border:none;border-radius:0;flex:1 1 auto}.et-query-devtools-step-caret-btn{appearance:none;border:none;border-inline-start:1px solid var(--_et-qdt-border);background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:0 .7rem;font:inherit}@media(hover:hover){.et-query-devtools-step-caret-btn:hover{background:var(--_et-qdt-hover);color:var(--_et-qdt-fg)}}.et-query-devtools-step-io{padding:0 .8rem .8rem;border-block-start:1px solid var(--_et-qdt-border)}}\n"], dependencies: [{ kind: "component", type: QueryDevtoolsDrawerComponent, selector: "et-query-devtools-drawer", inputs: ["sel", "container"], outputs: ["dismiss"] }, { kind: "component", type: QueryDevtoolsJsonComponent, selector: "et-query-devtools-json", inputs: ["value", "nodeKey", "depth", "search", "chunk", "path", "expandedPaths", "collapsedPaths", "toggleFn", "jsonPath", "parentKind", "overrides", "annotations"] }, { kind: "component", type: QueryDevtoolsRouteComponent, selector: "et-query-devtools-route", inputs: ["segments", "fallback", "trimPrefix"] }], encapsulation: i0.ViewEncapsulation.None }); }
3843
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsSequencesTabComponent, isStandalone: true, selector: "et-query-devtools-sequences-tab", ngImport: i0, template: "<div #panes class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-scroll\">\n @for (entry of host.sequenceEntries(); track entry.id) {\n @let sequence = host.asSequence(entry);\n @if (sequence) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-chip\">sequence</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ sequence.status() }}</span>\n <span class=\"et-query-devtools-count\">step {{ sequence.currentStep() }} / {{ sequence.total }}</span>\n </div>\n\n <dl class=\"et-query-devtools-meta\">\n <dt>Traffic</dt>\n <dd>\n @let activity = host.sequenceActivity(sequence);\n @if (activity.hasActivity) {\n {{ activity.stats.requests }} request{{ activity.stats.requests === 1 ? '' : 's' }} \u00B7 \u2193\n {{ host.formatTransferred(activity.stats.receivedBytes, activity.stats.hasEstimatedBytes) }}\n @if (activity.stats.sentBytes) {\n \u00B7 \u2191 {{ host.formatTransferred(activity.stats.sentBytes, activity.stats.hasEstimatedBytes) }}\n }\n @if (activity.cacheServed) {\n \u00B7 {{ activity.cacheServed }} from cache\n }\n @if (activity.stats.errors) {\n \u00B7 {{ activity.stats.errors }} failed\n }\n } @else {\n \u2014\n }\n </dd>\n </dl>\n\n <ol class=\"et-query-devtools-step-list\">\n @for (link of host.queriesForSequence(sequence); track $index; let i = $index) {\n <li [attr.data-status]=\"host.sequenceStepStatus(sequence, i)\" class=\"et-query-devtools-step-item\">\n <div class=\"et-query-devtools-step-head\">\n <button\n [class.et-query-devtools-inner-row--active]=\"host.sequenceSelectedQueryId() === link.id\"\n [disabled]=\"!link.id\"\n (click)=\"host.sequenceSelectedQueryId.set(link.id)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span\n [attr.data-status]=\"host.sequenceStepStatus(sequence, i)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ i + 1 }}. {{ link.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"link.segments\" [fallback]=\"'step ' + (i + 1)\" />\n </span>\n </button>\n <button\n [attr.aria-expanded]=\"host.isStepExpanded(entry.id, i)\"\n (click)=\"host.toggleStep(entry.id, i)\"\n class=\"et-query-devtools-step-caret-btn\"\n type=\"button\"\n title=\"Toggle in / out\"\n >\n {{ host.isStepExpanded(entry.id, i) ? '\u25BE' : '\u25B8' }}\n </button>\n </div>\n\n @if (host.isStepExpanded(entry.id, i)) {\n @let snap = host.stepSnapshot(sequence, i);\n <div class=\"et-query-devtools-step-io\">\n <h5 class=\"et-query-devtools-section\">In \u00B7 args</h5>\n @if (sequence.stepArgs().length > i) {\n <et-query-devtools-json [value]=\"sequence.stepArgs()[i]\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Not run yet.</p>\n }\n\n @if (snap?.error(); as error) {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 error ({{ error.raw.status }})</h5>\n <et-query-devtools-json [value]=\"error.isList ? error.errors : error.error\" />\n } @else {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 response</h5>\n @if (snap) {\n <et-query-devtools-json [value]=\"snap.response()\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Not run yet.</p>\n }\n }\n </div>\n }\n </li>\n }\n </ol>\n </div>\n }\n } @empty {\n <p class=\"et-query-devtools-empty\">No sequences registered.</p>\n }\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.sequenceSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.sequenceSelectedQueryId.set(null)\"\n />\n</div>\n", dependencies: [{ kind: "component", type: QueryDevtoolsDrawerComponent, selector: "et-query-devtools-drawer", inputs: ["sel", "container"], outputs: ["dismiss"] }, { kind: "component", type: QueryDevtoolsJsonComponent, selector: "et-query-devtools-json", inputs: ["value", "nodeKey", "depth", "search", "chunk", "path", "expandedPaths", "collapsedPaths", "toggleFn", "jsonPath", "parentKind", "overrides", "annotations"] }, { kind: "component", type: QueryDevtoolsRouteComponent, selector: "et-query-devtools-route", inputs: ["segments", "fallback", "trimPrefix"] }], encapsulation: i0.ViewEncapsulation.None }); }
3751
3844
  }
3752
3845
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsSequencesTabComponent, decorators: [{
3753
3846
  type: Component,
3754
- args: [{ selector: 'et-query-devtools-sequences-tab', encapsulation: ViewEncapsulation.None, imports: [QueryDevtoolsDrawerComponent, QueryDevtoolsJsonComponent, QueryDevtoolsRouteComponent], template: "<div #panes class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-scroll\">\n @for (entry of host.sequenceEntries(); track entry.id) {\n @let sequence = host.asSequence(entry);\n @if (sequence) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-chip\">sequence</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ sequence.status() }}</span>\n <span class=\"et-query-devtools-count\">step {{ sequence.currentStep() }} / {{ sequence.total }}</span>\n </div>\n\n <dl class=\"et-query-devtools-meta\">\n <dt>Traffic</dt>\n <dd>\n @let activity = host.sequenceActivity(sequence);\n @if (activity.hasActivity) {\n {{ activity.stats.requests }} request{{ activity.stats.requests === 1 ? '' : 's' }} \u00B7 \u2193\n {{ host.formatTransferred(activity.stats.receivedBytes, activity.stats.hasEstimatedBytes) }}\n @if (activity.stats.sentBytes) {\n \u00B7 \u2191 {{ host.formatTransferred(activity.stats.sentBytes, activity.stats.hasEstimatedBytes) }}\n }\n @if (activity.cacheServed) {\n \u00B7 {{ activity.cacheServed }} from cache\n }\n @if (activity.stats.errors) {\n \u00B7 {{ activity.stats.errors }} failed\n }\n } @else {\n \u2014\n }\n </dd>\n </dl>\n\n <ol class=\"et-query-devtools-step-list\">\n @for (link of host.queriesForSequence(sequence); track $index; let i = $index) {\n <li [attr.data-status]=\"host.sequenceStepStatus(sequence, i)\" class=\"et-query-devtools-step-item\">\n <div class=\"et-query-devtools-step-head\">\n <button\n [class.et-query-devtools-inner-row--active]=\"host.sequenceSelectedQueryId() === link.id\"\n [disabled]=\"!link.id\"\n (click)=\"host.sequenceSelectedQueryId.set(link.id)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span\n [attr.data-status]=\"host.sequenceStepStatus(sequence, i)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ i + 1 }}. {{ link.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"link.segments\" [fallback]=\"'step ' + (i + 1)\" />\n </span>\n </button>\n <button\n [attr.aria-expanded]=\"host.isStepExpanded(entry.id, i)\"\n (click)=\"host.toggleStep(entry.id, i)\"\n class=\"et-query-devtools-step-caret-btn\"\n type=\"button\"\n title=\"Toggle in / out\"\n >\n {{ host.isStepExpanded(entry.id, i) ? '\u25BE' : '\u25B8' }}\n </button>\n </div>\n\n @if (host.isStepExpanded(entry.id, i)) {\n @let snap = host.stepSnapshot(sequence, i);\n <div class=\"et-query-devtools-step-io\">\n <h5 class=\"et-query-devtools-section\">In \u00B7 args</h5>\n @if (sequence.stepArgs().length > i) {\n <et-query-devtools-json [value]=\"sequence.stepArgs()[i]\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Not run yet.</p>\n }\n\n @if (snap?.error(); as error) {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 error ({{ error.raw.status }})</h5>\n <et-query-devtools-json [value]=\"error.isList ? error.errors : error.error\" />\n } @else {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 response</h5>\n @if (snap) {\n <et-query-devtools-json [value]=\"snap.response()\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Not run yet.</p>\n }\n }\n </div>\n }\n </li>\n }\n </ol>\n </div>\n }\n } @empty {\n <p class=\"et-query-devtools-empty\">No sequences registered.</p>\n }\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.sequenceSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.sequenceSelectedQueryId.set(null)\"\n />\n</div>\n", styles: ["@layer components{.et-query-devtools-step-list{display:flex;flex-direction:column;gap:.4rem;margin:0;padding:0;list-style:none}.et-query-devtools-step-item{border:1px solid var(--_et-qdt-border);border-radius:.4rem;overflow:hidden}.et-query-devtools-step-item[data-status=error]{border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-step-head{display:flex;align-items:stretch}.et-query-devtools-step-head .et-query-devtools-inner-row{border:none;border-radius:0;flex:1 1 auto}.et-query-devtools-step-caret-btn{appearance:none;border:none;border-inline-start:1px solid var(--_et-qdt-border);background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:0 .7rem;font:inherit}@media(hover:hover){.et-query-devtools-step-caret-btn:hover{background:var(--_et-qdt-hover);color:var(--_et-qdt-fg)}}.et-query-devtools-step-io{padding:0 .8rem .8rem;border-block-start:1px solid var(--_et-qdt-border)}}\n"] }]
3755
- }] });
3847
+ args: [{ selector: 'et-query-devtools-sequences-tab', encapsulation: ViewEncapsulation.None, imports: [QueryDevtoolsDrawerComponent, QueryDevtoolsJsonComponent, QueryDevtoolsRouteComponent], template: "<div #panes class=\"et-query-devtools-split\">\n <div class=\"et-query-devtools-scroll\">\n @for (entry of host.sequenceEntries(); track entry.id) {\n @let sequence = host.asSequence(entry);\n @if (sequence) {\n <div class=\"et-query-devtools-card\">\n <div class=\"et-query-devtools-card-head\">\n <span class=\"et-query-devtools-chip\">sequence</span>\n <span class=\"et-query-devtools-chip et-query-devtools-chip--muted\">{{ sequence.status() }}</span>\n <span class=\"et-query-devtools-count\">step {{ sequence.currentStep() }} / {{ sequence.total }}</span>\n </div>\n\n <dl class=\"et-query-devtools-meta\">\n <dt>Traffic</dt>\n <dd>\n @let activity = host.sequenceActivity(sequence);\n @if (activity.hasActivity) {\n {{ activity.stats.requests }} request{{ activity.stats.requests === 1 ? '' : 's' }} \u00B7 \u2193\n {{ host.formatTransferred(activity.stats.receivedBytes, activity.stats.hasEstimatedBytes) }}\n @if (activity.stats.sentBytes) {\n \u00B7 \u2191 {{ host.formatTransferred(activity.stats.sentBytes, activity.stats.hasEstimatedBytes) }}\n }\n @if (activity.cacheServed) {\n \u00B7 {{ activity.cacheServed }} from cache\n }\n @if (activity.stats.errors) {\n \u00B7 {{ activity.stats.errors }} failed\n }\n } @else {\n \u2014\n }\n </dd>\n </dl>\n\n <ol class=\"et-query-devtools-step-list\">\n @for (link of host.queriesForSequence(sequence); track $index; let i = $index) {\n <li [attr.data-status]=\"host.sequenceStepStatus(sequence, i)\" class=\"et-query-devtools-step-item\">\n <div class=\"et-query-devtools-step-head\">\n <button\n [class.et-query-devtools-inner-row--active]=\"host.sequenceSelectedQueryId() === link.id\"\n [disabled]=\"!link.id\"\n (click)=\"host.sequenceSelectedQueryId.set(link.id)\"\n class=\"et-query-devtools-inner-row\"\n type=\"button\"\n >\n <span\n [attr.data-status]=\"host.sequenceStepStatus(sequence, i)\"\n class=\"et-query-devtools-status\"\n ></span>\n <span class=\"et-query-devtools-method\">{{ i + 1 }}. {{ link.method }}</span>\n <span class=\"et-query-devtools-route\">\n <et-query-devtools-route [segments]=\"link.segments\" [fallback]=\"'step ' + (i + 1)\" />\n </span>\n </button>\n <button\n [attr.aria-expanded]=\"host.isStepExpanded(entry.id, i)\"\n (click)=\"host.toggleStep(entry.id, i)\"\n class=\"et-query-devtools-step-caret-btn\"\n type=\"button\"\n title=\"Toggle in / out\"\n >\n {{ host.isStepExpanded(entry.id, i) ? '\u25BE' : '\u25B8' }}\n </button>\n </div>\n\n @if (host.isStepExpanded(entry.id, i)) {\n @let snap = host.stepSnapshot(sequence, i);\n <div class=\"et-query-devtools-step-io\">\n <h5 class=\"et-query-devtools-section\">In \u00B7 args</h5>\n @if (sequence.stepArgs().length > i) {\n <et-query-devtools-json [value]=\"sequence.stepArgs()[i]\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Not run yet.</p>\n }\n\n @if (snap?.error(); as error) {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 error ({{ error.raw.status }})</h5>\n <et-query-devtools-json [value]=\"error.isList ? error.errors : error.error\" />\n } @else {\n <h5 class=\"et-query-devtools-section\">Out \u00B7 response</h5>\n @if (snap) {\n <et-query-devtools-json [value]=\"snap.response()\" />\n } @else {\n <p class=\"et-query-devtools-empty\">Not run yet.</p>\n }\n }\n </div>\n }\n </li>\n }\n </ol>\n </div>\n }\n } @empty {\n <p class=\"et-query-devtools-empty\">No sequences registered.</p>\n }\n </div>\n\n <et-query-devtools-drawer\n [sel]=\"host.sequenceSelectedQuery()\"\n [container]=\"panes\"\n (dismiss)=\"host.sequenceSelectedQueryId.set(null)\"\n />\n</div>\n" }]
3848
+ }], ctorParameters: () => [] });
3756
3849
 
3757
3850
  /** The Sockets tab: registered `WebSocketDevtoolsHandle`s, their rooms, and a message log per client. */
3758
3851
  class QueryDevtoolsSocketsTabComponent {
@@ -3957,6 +4050,11 @@ const buildQueryDevtoolsSessionExport = (options) => ({
3957
4050
 
3958
4051
  /** How long a copy button stays ticked after a successful write. */
3959
4052
  const COPIED_RESET_MS = 1200;
4053
+ /**
4054
+ * How many of a batch's items its card lists. A bulk run settles thousands, and a row per item is a
4055
+ * list nobody scrolls and a render the panel pays for on every settle.
4056
+ */
4057
+ const MAX_BATCH_ITEM_ROWS = 100;
3960
4058
  /** How long the locate box stays up. Long enough to outlast a smooth scroll and still be read. */
3961
4059
  const LOCATE_HOLD_MS = 2500;
3962
4060
  const LOCATE_MAX_DEPTH = 4;
@@ -4340,6 +4438,7 @@ class QueryDevtoolsComponent {
4340
4438
  { id: 'queries', label: 'Queries' },
4341
4439
  { id: 'stacks', label: 'Stacks' },
4342
4440
  { id: 'sequences', label: 'Sequences' },
4441
+ { id: 'batches', label: 'Batches' },
4343
4442
  { id: 'forms', label: 'Forms' },
4344
4443
  { id: 'auth', label: 'Auth' },
4345
4444
  { id: 'ws', label: 'Sockets' },
@@ -4490,14 +4589,19 @@ class QueryDevtoolsComponent {
4490
4589
  this.selectedFormId = signal(this.persisted.selectedFormId ?? null, /* @ts-ignore */
4491
4590
  ...(ngDevMode ? [{ debugName: "selectedFormId" }] : /* istanbul ignore next */ []));
4492
4591
  // Independent per-drawer selection so no drawer shares the Queries tab's selection - or another
4493
- // drawer's: opening a query from the Timeline must not also change what the Forms drawer shows.
4494
- this.stackSelectedQueryId = signal(null, /* @ts-ignore */
4592
+ // drawer's: opening a query from the Timeline must not also change what the Forms drawer shows. Each
4593
+ // is persisted the way the Queries tab's is, so a reload puts every drawer back on what it was showing.
4594
+ this.stackSelectedQueryId = signal(this.persisted.stackSelectedQueryId ?? null, /* @ts-ignore */
4495
4595
  ...(ngDevMode ? [{ debugName: "stackSelectedQueryId" }] : /* istanbul ignore next */ []));
4496
- this.sequenceSelectedQueryId = signal(null, /* @ts-ignore */
4596
+ this.sequenceSelectedQueryId = signal(this.persisted.sequenceSelectedQueryId ?? null, /* @ts-ignore */
4497
4597
  ...(ngDevMode ? [{ debugName: "sequenceSelectedQueryId" }] : /* istanbul ignore next */ []));
4498
- this.formSelectedQueryId = signal(null, /* @ts-ignore */
4598
+ this.batchSelectedQueryId = signal(this.persisted.batchSelectedQueryId ?? null, /* @ts-ignore */
4599
+ ...(ngDevMode ? [{ debugName: "batchSelectedQueryId" }] : /* istanbul ignore next */ []));
4600
+ this.eventSelectedQueryId = signal(this.persisted.eventSelectedQueryId ?? null, /* @ts-ignore */
4601
+ ...(ngDevMode ? [{ debugName: "eventSelectedQueryId" }] : /* istanbul ignore next */ []));
4602
+ this.formSelectedQueryId = signal(this.persisted.formSelectedQueryId ?? null, /* @ts-ignore */
4499
4603
  ...(ngDevMode ? [{ debugName: "formSelectedQueryId" }] : /* istanbul ignore next */ []));
4500
- this.timelineSelectedQueryId = signal(null, /* @ts-ignore */
4604
+ this.timelineSelectedQueryId = signal(this.persisted.timelineSelectedQueryId ?? null, /* @ts-ignore */
4501
4605
  ...(ngDevMode ? [{ debugName: "timelineSelectedQueryId" }] : /* istanbul ignore next */ []));
4502
4606
  /** Free-text narrowing of the Queries list. Every whitespace-separated term has to match. */
4503
4607
  this.queryFilter = signal(this.persisted.queryFilter ?? '', /* @ts-ignore */
@@ -4556,6 +4660,9 @@ class QueryDevtoolsComponent {
4556
4660
  /** Keys (`<entryId>:<stepIndex>`) of the sequence steps whose in/out detail is expanded. */
4557
4661
  this.expandedSteps = signal(new Set(this.persisted.expandedSteps ?? []), /* @ts-ignore */
4558
4662
  ...(ngDevMode ? [{ debugName: "expandedSteps" }] : /* istanbul ignore next */ []));
4663
+ /** Keys (`<entryId>:<itemIndex>`) of the batch items whose args/response detail is expanded. */
4664
+ this.expandedBatchItems = signal(new Set(this.persisted.expandedBatchItems ?? []), /* @ts-ignore */
4665
+ ...(ngDevMode ? [{ debugName: "expandedBatchItems" }] : /* istanbul ignore next */ []));
4559
4666
  /** Keys of the Queries-list groups the user opened - the tab is rebuilt on every switch back. */
4560
4667
  this.expandedQueryGroups = signal(new Set(this.persisted.expandedQueryGroups ?? []), /* @ts-ignore */
4561
4668
  ...(ngDevMode ? [{ debugName: "expandedQueryGroups" }] : /* istanbul ignore next */ []));
@@ -4662,6 +4769,8 @@ class QueryDevtoolsComponent {
4662
4769
  ...(ngDevMode ? [{ debugName: "stackEntries" }] : /* istanbul ignore next */ []));
4663
4770
  this.sequenceEntries = computed(() => queryDevtoolsEntries().filter((e) => e.kind === 'query-sequence'), /* @ts-ignore */
4664
4771
  ...(ngDevMode ? [{ debugName: "sequenceEntries" }] : /* istanbul ignore next */ []));
4772
+ this.batchEntries = computed(() => queryDevtoolsEntries().filter((e) => e.kind === 'query-batch'), /* @ts-ignore */
4773
+ ...(ngDevMode ? [{ debugName: "batchEntries" }] : /* istanbul ignore next */ []));
4665
4774
  this.formEntries = computed(() => queryDevtoolsEntries().filter((e) => e.kind === 'query-form'), /* @ts-ignore */
4666
4775
  ...(ngDevMode ? [{ debugName: "formEntries" }] : /* istanbul ignore next */ []));
4667
4776
  this.authEntries = computed(() => queryDevtoolsEntries().filter((e) => e.kind === 'auth-provider'), /* @ts-ignore */
@@ -4776,6 +4885,7 @@ class QueryDevtoolsComponent {
4776
4885
  const queries = this.liveQueryEntries();
4777
4886
  const stacks = this.stackEntries();
4778
4887
  const sequences = this.sequenceEntries();
4888
+ const batches = this.batchEntries();
4779
4889
  const events = this.eventLog();
4780
4890
  return {
4781
4891
  queries: {
@@ -4792,6 +4902,13 @@ class QueryDevtoolsComponent {
4792
4902
  count: sequences.length,
4793
4903
  errors: sequences.filter((entry) => (this.asSequence(entry)?.failedAt() ?? null) !== null).length,
4794
4904
  },
4905
+ // A batch tolerates a partial failure by design, so the flag counts the runs that lost an item -
4906
+ // the number a bulk edit is judged on - rather than the batches that failed outright.
4907
+ batches: {
4908
+ count: batches.length,
4909
+ errors: batches.filter((entry) => this.asBatch(entry)?.failed() > 0).length,
4910
+ errorNoun: 'partial',
4911
+ },
4795
4912
  forms: { count: this.formEntries().length, errors: 0 },
4796
4913
  auth: { count: this.authEntries().length, errors: 0 },
4797
4914
  ws: { count: this.wsEntries().length, errors: 0 },
@@ -4830,6 +4947,10 @@ class QueryDevtoolsComponent {
4830
4947
  ...(ngDevMode ? [{ debugName: "stackSelectedQuery" }] : /* istanbul ignore next */ []));
4831
4948
  this.sequenceSelectedQuery = computed(() => this.findQuery(this.sequenceSelectedQueryId()), /* @ts-ignore */
4832
4949
  ...(ngDevMode ? [{ debugName: "sequenceSelectedQuery" }] : /* istanbul ignore next */ []));
4950
+ this.batchSelectedQuery = computed(() => this.findQuery(this.batchSelectedQueryId()), /* @ts-ignore */
4951
+ ...(ngDevMode ? [{ debugName: "batchSelectedQuery" }] : /* istanbul ignore next */ []));
4952
+ this.eventSelectedQuery = computed(() => this.findQuery(this.eventSelectedQueryId()), /* @ts-ignore */
4953
+ ...(ngDevMode ? [{ debugName: "eventSelectedQuery" }] : /* istanbul ignore next */ []));
4833
4954
  this.formSelectedQuery = computed(() => this.findQuery(this.formSelectedQueryId()), /* @ts-ignore */
4834
4955
  ...(ngDevMode ? [{ debugName: "formSelectedQuery" }] : /* istanbul ignore next */ []));
4835
4956
  this.timelineSelectedQuery = computed(() => this.findQuery(this.timelineSelectedQueryId()), /* @ts-ignore */
@@ -4986,6 +5107,12 @@ class QueryDevtoolsComponent {
4986
5107
  detailTab: this.detailTab(),
4987
5108
  selectedClientName: this.selectedClientName(),
4988
5109
  selectedQueryId: this.selectedQueryId(),
5110
+ stackSelectedQueryId: this.stackSelectedQueryId(),
5111
+ sequenceSelectedQueryId: this.sequenceSelectedQueryId(),
5112
+ batchSelectedQueryId: this.batchSelectedQueryId(),
5113
+ eventSelectedQueryId: this.eventSelectedQueryId(),
5114
+ formSelectedQueryId: this.formSelectedQueryId(),
5115
+ timelineSelectedQueryId: this.timelineSelectedQueryId(),
4989
5116
  selectedFormId: this.selectedFormId(),
4990
5117
  inspectFilterIds: this.inspectFilterIds(),
4991
5118
  queryFilter: this.queryFilter(),
@@ -4998,6 +5125,7 @@ class QueryDevtoolsComponent {
4998
5125
  socketFilter: this.socketFilter(),
4999
5126
  jsonSearch: this.jsonSearch(),
5000
5127
  expandedSteps: [...this.expandedSteps()],
5128
+ expandedBatchItems: [...this.expandedBatchItems()],
5001
5129
  expandedQueryGroups: [...this.expandedQueryGroups()],
5002
5130
  jsonExpanded: [...this.jsonExpandedPaths()],
5003
5131
  jsonCollapsed: [...this.jsonCollapsedPaths()],
@@ -5160,6 +5288,8 @@ class QueryDevtoolsComponent {
5160
5288
  this.selectedFormId.set(null);
5161
5289
  this.stackSelectedQueryId.set(null);
5162
5290
  this.sequenceSelectedQueryId.set(null);
5291
+ this.batchSelectedQueryId.set(null);
5292
+ this.eventSelectedQueryId.set(null);
5163
5293
  this.formSelectedQueryId.set(null);
5164
5294
  this.timelineSelectedQueryId.set(null);
5165
5295
  this.queryFilter.set('');
@@ -5168,6 +5298,8 @@ class QueryDevtoolsComponent {
5168
5298
  this.queryTreeView.set(false);
5169
5299
  this.collapsedQueryPaths.set(new Set());
5170
5300
  this.expandedQueryGroups.set(new Set());
5301
+ this.expandedSteps.set(new Set());
5302
+ this.expandedBatchItems.set(new Set());
5171
5303
  this.eventClient.set(null);
5172
5304
  this.eventErrorsOnly.set(false);
5173
5305
  this.socketFilter.set('');
@@ -5335,20 +5467,11 @@ class QueryDevtoolsComponent {
5335
5467
  * request that ran - which is what tells two requests to the same endpoint apart.
5336
5468
  */
5337
5469
  routeSegments(entry, query) {
5338
- const parts = entry?.meta.routeParts;
5470
+ const path = this.renderRoute(entry, this.queryArgs(query));
5339
5471
  const search = query.subtle.request()?.url.split('?')[1];
5340
- const querySegment = search ? [{ text: `?${search}`, kind: 'query' }] : [];
5341
- if (!parts?.length) {
5342
- return entry?.meta.route ? [{ text: entry.meta.route, kind: 'static' }, ...querySegment] : [];
5343
- }
5344
- const pathParams = this.queryArgs(query)?.['pathParams'];
5345
- const pathSegments = parts.map(({ text, param }) => {
5346
- if (!param)
5347
- return { text, kind: 'static' };
5348
- const value = pathParams?.[param];
5349
- return { text: value === undefined || value === null ? `:${param}` : String(value), kind: 'param', name: param };
5350
- });
5351
- return [...pathSegments, ...querySegment];
5472
+ // Only onto a route that rendered: an entry with no route at all reads as no route, not as a bare
5473
+ // query string.
5474
+ return search && path.length ? [...path, { text: `?${search}`, kind: 'query' }] : path;
5352
5475
  }
5353
5476
  /** The full URL of the request a query last made, or `null` while it has not executed. */
5354
5477
  requestUrl(query) {
@@ -5919,6 +6042,9 @@ class QueryDevtoolsComponent {
5919
6042
  asSequence(entry) {
5920
6043
  return entry.handle.current;
5921
6044
  }
6045
+ asBatch(entry) {
6046
+ return entry.handle.current;
6047
+ }
5922
6048
  asAuth(entry) {
5923
6049
  return entry.handle;
5924
6050
  }
@@ -6144,6 +6270,66 @@ class QueryDevtoolsComponent {
6144
6270
  }
6145
6271
  this.expandedSteps.set(next);
6146
6272
  }
6273
+ /** A batch's route with its path params left as `:name` - a batch fills them in per item, not once. */
6274
+ batchRouteSegments(entry) {
6275
+ return this.renderRoute(entry, null);
6276
+ }
6277
+ batchOf(entry) {
6278
+ const handle = entry.meta.batch;
6279
+ return handle ? (this.batchEntries().find((e) => e.handle === handle) ?? null) : null;
6280
+ }
6281
+ queriesForBatch(entry) {
6282
+ return this.queryEntries()
6283
+ .filter((e) => e.meta.batch === entry.handle && !e.destroyedAt)
6284
+ .map((e) => this.queryLinkFor(e, e.handle));
6285
+ }
6286
+ /**
6287
+ * The registry entry of one of a batch's items, or `null` once it has fallen out of the batch's capped
6288
+ * tombstone tail - which is what makes an item row openable only while its query is still kept.
6289
+ */
6290
+ batchItemQueryId(entry, index) {
6291
+ const item = this.queryEntries().find((e) => e.meta.batch === entry.handle && e.meta.batchItemIndex === index);
6292
+ return item?.id ?? null;
6293
+ }
6294
+ batchItems(entry) {
6295
+ const results = this.asBatch(entry).results();
6296
+ // Failures first: a bulk edit is read for the items it could not apply, and those are the ones a
6297
+ // cap must never be what drops. Everything else keeps input order.
6298
+ const ordered = [
6299
+ ...results.filter((result) => result.status === 'error'),
6300
+ ...results.filter((result) => result.status !== 'error'),
6301
+ ];
6302
+ return {
6303
+ rows: ordered.slice(0, MAX_BATCH_ITEM_ROWS).map((result) => ({
6304
+ result,
6305
+ segments: this.argsRouteSegments(entry, 'args' in result ? result.args : null),
6306
+ })),
6307
+ total: results.length,
6308
+ hidden: Math.max(0, results.length - MAX_BATCH_ITEM_ROWS),
6309
+ };
6310
+ }
6311
+ batchThroughput(batch) {
6312
+ const rate = batch.itemsPerSecond();
6313
+ if (rate === null)
6314
+ return null;
6315
+ const remaining = batch.remainingTime();
6316
+ const perSecond = `${rate.toFixed(rate < 10 ? 1 : 0)} items/s`;
6317
+ return remaining === null ? perSecond : `${perSecond} · ~${this.formatDuration(remaining)} left`;
6318
+ }
6319
+ isBatchItemExpanded(entryId, index) {
6320
+ return this.expandedBatchItems().has(this.stepKey(entryId, index));
6321
+ }
6322
+ toggleBatchItem(entryId, index) {
6323
+ const key = this.stepKey(entryId, index);
6324
+ const next = new Set(this.expandedBatchItems());
6325
+ if (next.has(key)) {
6326
+ next.delete(key);
6327
+ }
6328
+ else {
6329
+ next.add(key);
6330
+ }
6331
+ this.expandedBatchItems.set(next);
6332
+ }
6147
6333
  /** Dedents a GraphQL document (template-literal indentation) for readable display. */
6148
6334
  gqlDocument(doc) {
6149
6335
  const lines = doc.replace(/\t/g, ' ').split('\n');
@@ -6209,6 +6395,26 @@ class QueryDevtoolsComponent {
6209
6395
  downloadTextFile(file) {
6210
6396
  this.download({ content: file.content, filename: file.name, type: file.type });
6211
6397
  }
6398
+ /**
6399
+ * The same route rendered from a set of args rather than from a query, which is what a batch item leaves
6400
+ * behind: the batch destroys its query the moment the item settles.
6401
+ */
6402
+ argsRouteSegments(entry, args) {
6403
+ return this.renderRoute(entry, args);
6404
+ }
6405
+ renderRoute(entry, args) {
6406
+ const parts = entry?.meta.routeParts;
6407
+ if (!parts?.length) {
6408
+ return entry?.meta.route ? [{ text: entry.meta.route, kind: 'static' }] : [];
6409
+ }
6410
+ const pathParams = args?.['pathParams'];
6411
+ return parts.map(({ text, param }) => {
6412
+ if (!param)
6413
+ return { text, kind: 'static' };
6414
+ const value = pathParams?.[param];
6415
+ return { text: value === undefined || value === null ? `:${param}` : String(value), kind: 'param', name: param };
6416
+ });
6417
+ }
6212
6418
  /**
6213
6419
  * Takes - or gives back - the lock whose name only this tab can have produced. Its `clientId` in the
6214
6420
  * snapshot is what tells this tab's rows from another tab's: `LockInfo` carries no tab identity, and
@@ -6553,6 +6759,8 @@ class QueryDevtoolsComponent {
6553
6759
  }
6554
6760
  if (entry.kind === 'query-sequence')
6555
6761
  return { ...base, detail: this.sessionSequence(entry) };
6762
+ if (entry.kind === 'query-batch')
6763
+ return { ...base, method: entry.meta.method ?? null, detail: this.sessionBatch(entry) };
6556
6764
  if (entry.kind === 'query-form')
6557
6765
  return { ...base, detail: this.sessionForm(entry) };
6558
6766
  if (entry.kind === 'auth-provider')
@@ -6606,6 +6814,38 @@ class QueryDevtoolsComponent {
6606
6814
  })),
6607
6815
  };
6608
6816
  }
6817
+ sessionBatch(entry) {
6818
+ const batch = this.asBatch(entry);
6819
+ const items = this.batchItems(entry);
6820
+ return {
6821
+ route: entry.meta.route ?? null,
6822
+ status: batch.status(),
6823
+ concurrency: entry.meta.concurrency ?? null,
6824
+ stopOnError: entry.meta.stopOnError ?? null,
6825
+ total: batch.total(),
6826
+ completed: batch.completed(),
6827
+ inFlight: batch.inFlight(),
6828
+ succeeded: batch.succeeded(),
6829
+ failed: batch.failed(),
6830
+ skipped: batch.skipped(),
6831
+ itemsPerSecond: batch.itemsPerSecond(),
6832
+ remainingTimeMs: batch.remainingTime(),
6833
+ // Capped the way the card is, and reported the same way, so an export cannot read as the whole run.
6834
+ listedItems: items.rows.length,
6835
+ hiddenItems: items.hidden,
6836
+ items: items.rows.map((row) => {
6837
+ const error = row.result.status === 'error' ? row.result.error : null;
6838
+ return {
6839
+ index: row.result.index,
6840
+ status: row.result.status,
6841
+ route: row.segments.map((segment) => segment.text).join(''),
6842
+ error: error
6843
+ ? { status: error.raw.status, body: slimForReport(error.isList ? error.errors : error.error) }
6844
+ : null,
6845
+ };
6846
+ }),
6847
+ };
6848
+ }
6609
6849
  sessionForm(entry) {
6610
6850
  const form = this.asForm(entry);
6611
6851
  return {
@@ -6782,6 +7022,8 @@ class QueryDevtoolsComponent {
6782
7022
  this.selectedQueryId(),
6783
7023
  this.stackSelectedQueryId(),
6784
7024
  this.sequenceSelectedQueryId(),
7025
+ this.batchSelectedQueryId(),
7026
+ this.eventSelectedQueryId(),
6785
7027
  this.formSelectedQueryId(),
6786
7028
  this.timelineSelectedQueryId(),
6787
7029
  ].join('|');
@@ -6939,12 +7181,13 @@ class QueryDevtoolsComponent {
6939
7181
  this.eventLog.update((log) => [item, ...log].slice(0, queryDevtoolsSettings().maxEvents));
6940
7182
  }
6941
7183
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
6942
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsComponent, isStandalone: true, selector: "et-query-devtools", inputs: { startOpen: { classPropertyName: "startOpen", publicName: "startOpen", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-dock": "dock()" }, classAttribute: "et-query-devtools-host" }, providers: [{ provide: QUERY_DEVTOOLS_HOST, useExisting: QueryDevtoolsComponent }], viewQueries: [{ propertyName: "panelEl", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (open()) {\n <section\n #panel\n [class.et-query-devtools-panel--resizing]=\"resizing()\"\n [attr.data-dock]=\"poppedOut() ? 'popout' : dock()\"\n [attr.data-pane-axis]=\"paneAxis()\"\n [attr.data-parked]=\"floatParked() ? '' : null\"\n [style.block-size.px]=\"panelBlockSize()\"\n [style.inline-size.px]=\"panelInlineSize()\"\n [style.inset-block-start.px]=\"panelInsetBlock()\"\n [style.inset-inline-start.px]=\"panelInsetInline()\"\n [style.--_et-qdt-list-width.px]=\"listWidth()\"\n [style.--_et-qdt-drawer-width.px]=\"drawerWidth()\"\n [style.--_et-qdt-list-height.px]=\"listHeight()\"\n [style.--_et-qdt-drawer-height.px]=\"drawerHeight()\"\n class=\"et-query-devtools-panel\"\n >\n <!-- A pop-out is sized by its window, so there is no edge to drag. A float is a window in the page:\n a title bar to move it and every edge to resize it, on the same `@ethlete/core` primitives the\n stream pip window uses. A dock has one edge, and it does both. -->\n @if (floating()) {\n <!-- Parked, the panel is a grab strip: the edge handles would eat most of the peek that is left\n to click, and resizing something you cannot see is not a thing to offer. -->\n <et-resize-handles\n [edges]=\"FLOAT_RESIZE_EDGES\"\n [disabled]=\"floatParked()\"\n (resizeStarted)=\"startFloatResize()\"\n (resizeMoved)=\"resizeFloat($event)\"\n (resizeEnded)=\"endFloatResize()\"\n (resizeCancelled)=\"endFloatResize()\"\n />\n <div\n (dragMoved)=\"moveFloat($event)\"\n (dragEnded)=\"endFloatMove()\"\n (dragCancelled)=\"endFloatMove()\"\n (dragTapped)=\"restoreFloat()\"\n class=\"et-query-devtools-move\"\n title=\"Drag to move \u2014 shove it off an edge to park it, click the strip to bring it back\"\n aria-hidden=\"true\"\n etDragHandle\n ></div>\n } @else if (!poppedOut()) {\n <div\n (pointerdown)=\"startResize($event)\"\n class=\"et-query-devtools-resize\"\n title=\"Drag to resize\"\n aria-hidden=\"true\"\n ></div>\n }\n <header class=\"et-query-devtools-header\">\n <nav class=\"et-query-devtools-tabs\" role=\"tablist\">\n @for (tab of visibleTabs(); track tab.id) {\n @let badge = tabBadges()[tab.id];\n <button\n [class.et-query-devtools-tab--active]=\"activeTab() === tab.id\"\n [attr.aria-selected]=\"activeTab() === tab.id\"\n (click)=\"activeTab.set(tab.id)\"\n class=\"et-query-devtools-tab\"\n type=\"button\"\n role=\"tab\"\n >\n {{ tab.label }}\n @if (badge.count) {\n <span class=\"et-query-devtools-tab-badge\">{{ badge.count }}</span>\n }\n @if (badge.errors) {\n <span\n class=\"et-query-devtools-tab-badge et-query-devtools-tab-badge--error\"\n title=\"{{ badge.errors }} {{ badge.errorNoun ?? 'failing' }}\"\n >\n {{ badge.errors }}\n </span>\n }\n </button>\n }\n\n @if (overflowTabs().length; as overflowCount) {\n <div class=\"et-query-devtools-menu\" etMenu>\n <button\n [title]=\"overflowCount + ' tabs with nothing in them'\"\n class=\"et-query-devtools-tab\"\n type=\"button\"\n etMenuTrigger\n >\n More\n <span class=\"et-query-devtools-menu-caret\" aria-hidden=\"true\">\u25BE</span>\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n @for (tab of overflowTabs(); track tab.id) {\n <button (activate)=\"activeTab.set(tab.id)\" et-menu-item type=\"button\">{{ tab.label }}</button>\n }\n </et-menu>\n </ng-template>\n </div>\n }\n </nav>\n\n <div class=\"et-query-devtools-header-actions\">\n <button\n [class.et-query-devtools-inspect-btn--active]=\"inspectActive()\"\n [attr.aria-pressed]=\"inspectActive()\"\n (click)=\"toggleInspect()\"\n class=\"et-query-devtools-btn et-query-devtools-inspect-btn\"\n type=\"button\"\n title=\"Inspect: hover the UI to find its query\"\n >\n <svg\n class=\"et-query-devtools-inspect-icon\"\n viewBox=\"0 0 16 16\"\n width=\"14\"\n height=\"14\"\n fill=\"none\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <circle class=\"et-query-devtools-inspect-icon-ring\" cx=\"8\" cy=\"8\" r=\"4.25\" />\n <circle class=\"et-query-devtools-inspect-icon-dot\" cx=\"8\" cy=\"8\" r=\"1.15\" />\n <path d=\"M8 0.9v2.1M8 13v2.1M0.9 8h2.1M13 8h2.1\" />\n </svg>\n {{ inspectActive() ? 'Inspecting' : 'Inspect' }}\n @if (inspectActive()) {\n <kbd class=\"et-query-devtools-kbd et-query-devtools-kbd--inverse\">Esc</kbd>\n }\n </button>\n <button\n (click)=\"downloadSession()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Download every registered entry, the event log, the cache totals and anything armed as one JSON file - the attachment for a bug report\"\n >\n <span aria-hidden=\"true\">\u2913</span>\n Session\n </button>\n <!-- A gear, not a tab: Settings holds nothing to count, so the badge/overflow logic would push it\n behind More - and the one control that explains a panel behaving oddly may not be hidden. -->\n <button\n [class.et-query-devtools-btn--active]=\"activeTab() === 'settings'\"\n [attr.aria-pressed]=\"activeTab() === 'settings'\"\n (click)=\"toggleSettings()\"\n class=\"et-query-devtools-btn et-query-devtools-icon-btn\"\n type=\"button\"\n title=\"Settings: where panel state is kept, and the limits it holds\"\n aria-label=\"Settings\"\n >\n <span aria-hidden=\"true\">\u2699</span>\n </button>\n @if (!poppedOut()) {\n @let layout = currentLayout();\n <div class=\"et-query-devtools-menu\" etMenu placement=\"bottom-end\">\n <button class=\"et-query-devtools-btn\" title=\"Where the panel sits\" type=\"button\" etMenuTrigger>\n <span class=\"et-query-devtools-menu-glyph\" aria-hidden=\"true\">{{ layout.glyph }}</span>\n {{ layout.label }}\n <span class=\"et-query-devtools-menu-caret\" aria-hidden=\"true\">\u25BE</span>\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <et-menu-radio-group [value]=\"dock()\">\n @for (option of LAYOUTS; track option.id) {\n <!-- eslint-disable ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <et-menu-radio-item\n [value]=\"option.id\"\n [title]=\"option.hint\"\n [closeOnActivate]=\"true\"\n (activate)=\"selectLayout(option.id)\"\n >\n <!-- eslint-enable ethlete/prefer-static-boolean-properties -->\n <span class=\"et-query-devtools-menu-item-glyph\" aria-hidden=\"true\">{{ option.glyph }}</span>\n {{ option.label }}\n </et-menu-radio-item>\n }\n </et-menu-radio-group>\n <et-menu-separator />\n <et-menu-checkbox-item\n [checked]=\"reservesSpace()\"\n [disabled]=\"floating()\"\n (checkedChange)=\"reservesSpace.set($event)\"\n title=\"The page gives up the room the panel covers, so its last rows can still be scrolled to. Docks only - a float sits wherever it was dragged.\"\n >\n Reserve page space\n </et-menu-checkbox-item>\n </et-menu>\n </ng-template>\n </div>\n }\n <span class=\"et-query-devtools-header-divider\" aria-hidden=\"true\"></span>\n <button\n (click)=\"toggleOpen()\"\n class=\"et-query-devtools-btn et-query-devtools-btn--danger\"\n type=\"button\"\n title=\"{{ poppedOut() ? 'Bring the panel back into the app window' : 'Close devtools (' + shortcut + ')' }}\"\n >\n @if (poppedOut()) {\n <span aria-hidden=\"true\">\u29C8</span>\n Dock back\n } @else {\n <span aria-hidden=\"true\">\u2715</span>\n Close\n <kbd class=\"et-query-devtools-kbd\">{{ shortcut }}</kbd>\n }\n </button>\n </div>\n </header>\n\n @if (popOutBlocked()) {\n <div class=\"et-query-devtools-notice-bar\" role=\"status\">\n <span class=\"et-query-devtools-armed-text\">\n The browser blocked the pop-up window. Allow pop-ups for this site, or float the panel over the page instead \u2014\n a float needs no permission.\n </span>\n <button (click)=\"floatPanel()\" class=\"et-query-devtools-btn\" type=\"button\">Float instead</button>\n <button (click)=\"popOutBlocked.set(false)\" class=\"et-query-devtools-btn\" type=\"button\">Dismiss</button>\n </div>\n }\n\n @if (armedFaultClients(); as armed) {\n <div class=\"et-query-devtools-armed-bar\" role=\"status\">\n <span class=\"et-query-devtools-armed-dot\" aria-hidden=\"true\"></span>\n <span class=\"et-query-devtools-armed-text\">\n <strong>Faults armed</strong> on\n <span class=\"et-query-devtools-mono\">{{ armed.join(', ') }}</span>\n \u2014 every failure and delay you see from\n {{ armed.length === 1 ? 'this client' : 'these clients' }} is injected, not the API.\n @if (faultsRestored()) {\n They came back from your last page load \u2014 set <em>Armed faults</em> back to <em>none</em> in Settings to\n stop that.\n }\n </span>\n <button\n [disabled]=\"activeTab() === 'faults'\"\n (click)=\"activeTab.set('faults')\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Review\n </button>\n <button (click)=\"CLEAR_FAULTS()\" class=\"et-query-devtools-btn\" type=\"button\">Disarm all</button>\n </div>\n }\n\n @if (armedMockRoutes(); as mocked) {\n <div class=\"et-query-devtools-armed-bar\" role=\"status\">\n <span class=\"et-query-devtools-armed-dot\" aria-hidden=\"true\"></span>\n <span class=\"et-query-devtools-armed-text\">\n <strong>Mocks armed</strong> on\n <span class=\"et-query-devtools-mono\">{{ mocked.join(', ') }}</span>\n \u2014 {{ mocked.length === 1 ? 'that route is' : 'those routes are' }} answered by the panel, so nothing they show\n reached the API.\n @if (mocksRestored()) {\n They came back from your last page load \u2014 set <em>Armed mocks</em> back to <em>none</em> in Settings to stop\n that.\n }\n </span>\n <button\n [disabled]=\"activeTab() === 'mocks'\"\n (click)=\"activeTab.set('mocks')\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Review\n </button>\n <button (click)=\"DISARM_MOCKS()\" class=\"et-query-devtools-btn\" type=\"button\">Disarm all</button>\n </div>\n }\n\n @if (restoredOverrides(); as restored) {\n <div class=\"et-query-devtools-armed-bar\" role=\"status\">\n <span class=\"et-query-devtools-armed-dot\" aria-hidden=\"true\"></span>\n <span class=\"et-query-devtools-armed-text\">\n @if (restored.ops) {\n <strong>Overrides re-armed</strong> \u2014 {{ restored.ops }} {{ restored.ops === 1 ? 'edit' : 'edits' }} from\n your last page load {{ restored.ops === 1 ? 'is' : 'are' }} back on {{ restored.queries }}\n {{ restored.queries === 1 ? 'query' : 'queries' }}, so those responses are not what the API sent.\n @if (restored.fromLocalStorage) {\n They came back from <strong>localStorage</strong>, so they outlive closing this tab \u2014 clear them, or set\n overrides back to <em>session</em> in Settings.\n }\n } @else {\n <strong>Overrides could not be re-armed.</strong>\n }\n @if (restored.orphaned.length) {\n <span class=\"et-query-devtools-armed-orphans\">\n {{ restored.orphaned.length }} stored\n {{ restored.orphaned.length === 1 ? 'override' : 'overrides' }} matched no query \u2014\n <span class=\"et-query-devtools-mono\">{{ restored.orphaned.join(', ') }}</span>\n </span>\n }\n </span>\n @if (restored.firstId; as firstId) {\n <button (click)=\"reviewRestoredOverrides(firstId)\" class=\"et-query-devtools-btn\" type=\"button\">Review</button>\n }\n <button (click)=\"DROP_RESTORED_OVERRIDES()\" class=\"et-query-devtools-btn\" type=\"button\">Drop all</button>\n </div>\n }\n\n <div class=\"et-query-devtools-body\">\n @switch (activeTab()) {\n <!-- QUERIES -->\n @case ('queries') {\n <et-query-devtools-queries-tab />\n }\n\n <!-- STACKS -->\n @case ('stacks') {\n <et-query-devtools-stacks-tab />\n }\n\n <!-- SEQUENCES -->\n @case ('sequences') {\n <et-query-devtools-sequences-tab />\n }\n\n <!-- FORMS -->\n @case ('forms') {\n <et-query-devtools-forms-tab />\n }\n\n <!-- AUTH -->\n @case ('auth') {\n <et-query-devtools-auth-tab />\n }\n\n <!-- SOCKETS -->\n @case ('ws') {\n <et-query-devtools-sockets-tab />\n }\n\n <!-- CACHE -->\n @case ('cache') {\n <et-query-devtools-cache-tab />\n }\n\n <!-- LOCKS -->\n @case ('locks') {\n <et-query-devtools-locks-tab />\n }\n\n <!-- TIMELINE -->\n @case ('timeline') {\n <et-query-devtools-timeline-tab />\n }\n\n <!-- EVENTS -->\n @case ('events') {\n <et-query-devtools-events-tab />\n }\n\n <!-- FAULTS -->\n @case ('faults') {\n <et-query-devtools-faults-tab />\n }\n\n <!-- MOCKS -->\n @case ('mocks') {\n <et-query-devtools-mocks-tab />\n }\n\n <!-- ABOUT -->\n @case ('about') {\n <et-query-devtools-about />\n }\n\n <!-- SETTINGS -->\n @case ('settings') {\n <et-query-devtools-settings />\n }\n }\n </div>\n </section>\n}\n\n@if (!open()) {\n <et-query-devtools-toggle [tampered]=\"panelTampered()\" (openChange)=\"toggleOpen()\" />\n}\n\n@if (inspectActive() && inspectHover(); as hover) {\n <div\n [style.left.px]=\"hover.rect.left\"\n [style.top.px]=\"hover.rect.top\"\n [style.width.px]=\"hover.rect.width\"\n [style.height.px]=\"hover.rect.height\"\n class=\"et-query-devtools-inspect-box\"\n >\n <span class=\"et-query-devtools-inspect-tag\">{{ inspectLabel(hover.entries) }}</span>\n </div>\n}\n\n@if (locatedRect(); as rect) {\n <div\n [style.left.px]=\"rect.left\"\n [style.top.px]=\"rect.top\"\n [style.width.px]=\"rect.width\"\n [style.height.px]=\"rect.height\"\n class=\"et-query-devtools-inspect-box\"\n >\n <span class=\"et-query-devtools-inspect-tag\">created here</span>\n </div>\n}\n", styles: ["@layer components{.et-query-devtools-host{--_et-qdt-bg: var(--et-surface-background-solid, rgb(24 24 27));--_et-qdt-fg: var(--et-surface-color-solid, rgb(244 244 245));--_et-qdt-muted: var(--et-surface-color-muted-solid, rgb(161 161 170));--_et-qdt-subtle: var(--et-surface-color-subtle-solid, rgb(113 113 122));--_et-qdt-border: var(--et-surface-border-solid, rgb(255 255 255 / .12));--_et-qdt-interaction: var(--et-surface-interaction-solid, rgb(255 255 255));--_et-qdt-accent: var(--et-theme-color-primary-solid, rgb(96 165 250));--_et-qdt-hover: color-mix(in srgb, var(--_et-qdt-interaction) 10%, transparent);--_et-qdt-active: color-mix(in srgb, var(--_et-qdt-interaction) 18%, transparent);--_et-qdt-success: rgb(34 197 94);--_et-qdt-error: rgb(239 68 68);--_et-qdt-loading: rgb(234 179 8);--_et-qdt-title-bar: 2.4rem;--_et-qdt-radius: .5rem;--_et-qdt-control-h: 2.4rem;--_et-qdt-row-h: 3.6rem;position:fixed;inset-inline:0;inset-block-end:0;z-index:2147483000;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,Liberation Mono,monospace;font-size:1.2rem;line-height:1.4;pointer-events:none}.et-query-devtools-host>*{pointer-events:auto}@media(pointer:coarse){.et-query-devtools-host{--_et-qdt-control-h: 3.4rem;--_et-qdt-row-h: 4.4rem}}.et-query-devtools-panel{position:relative;display:flex;flex-direction:column;inline-size:100%;min-block-size:20rem;max-block-size:90vh;background:var(--_et-qdt-bg);color:var(--_et-qdt-fg);border-block-start:1px solid var(--_et-qdt-border);box-shadow:0 -.4rem 2rem #00000059}.et-query-devtools-host:where([data-dock=right]){inset-block:0;inset-inline-start:auto}.et-query-devtools-panel:where([data-dock=right]){inline-size:auto;block-size:100%;max-block-size:none;max-inline-size:90vw;border-block-start:0;border-inline-start:1px solid var(--_et-qdt-border);box-shadow:-.4rem 0 2rem #00000059}.et-query-devtools-host:where([data-dock=float]){inset:0}.et-query-devtools-panel:where([data-dock=float]){--_et-qdt-frame: .8rem;--et-resize-handles-edge-size: var(--_et-qdt-frame);--et-resize-handles-touch-edge-size: var(--_et-qdt-frame);--et-resize-handles-corner-size: 1.6rem;--et-resize-handles-touch-corner-size: 1.6rem;--et-resize-handles-edge-inset: 1.6rem;--et-resize-handles-side-top: var(--_et-qdt-title-bar);--et-resize-handles-side-bottom: 1.6rem;--et-resize-handles-z-index: 3;position:absolute;inline-size:auto;min-block-size:0;max-block-size:none;padding-block-start:var(--_et-qdt-title-bar);padding-inline:var(--_et-qdt-frame);padding-block-end:var(--_et-qdt-frame);border:1px solid var(--_et-qdt-border);border-radius:.6rem;box-shadow:0 1rem 4rem #00000073;overflow:hidden}.et-query-devtools-move{position:absolute;inset-inline:0;inset-block-start:0;block-size:var(--_et-qdt-title-bar);cursor:move;z-index:2}.et-query-devtools-panel:where([data-dock=float]) .et-query-devtools-body{border-radius:.3rem;overflow:hidden}.et-query-devtools-move:after{content:\"\";position:absolute;inset-inline:50%;inset-block-start:.9rem;inline-size:3rem;margin-inline-start:-1.5rem;block-size:.6rem;background-image:radial-gradient(currentcolor 40%,transparent 42%);background-size:.4rem .3rem;color:var(--_et-qdt-subtle)}@media(hover:hover){.et-query-devtools-move:hover:after{color:var(--_et-qdt-accent)}}.et-query-devtools-panel:where([data-dock=float][data-parked]){opacity:.85}@media(hover:hover){.et-query-devtools-panel:where([data-dock=float][data-parked]):hover{opacity:1}}.et-query-devtools-panel:where([data-dock=float][data-parked]) .et-query-devtools-move{cursor:grab;background:var(--_et-qdt-active)}.et-query-devtools-panel:where([data-dock=float][data-parked]) .et-query-devtools-move:after{color:var(--_et-qdt-accent)}.et-query-devtools-host:where([data-dock=left]){inset-block:0;inset-inline-end:auto}.et-query-devtools-panel:where([data-dock=left]){inline-size:auto;block-size:100%;max-block-size:none;max-inline-size:90vw;border-block-start:0;border-inline-end:1px solid var(--_et-qdt-border);box-shadow:.4rem 0 2rem #00000059}.et-query-devtools-host:where([data-dock=top]){inset-block-start:0;inset-block-end:auto}.et-query-devtools-panel:where([data-dock=top]){border-block-start:0;border-block-end:1px solid var(--_et-qdt-border);box-shadow:0 .4rem 2rem #00000059}.et-query-devtools-host:where([data-dock=popout]){position:static;inline-size:100%;block-size:100%}.et-query-devtools-panel:where([data-dock=popout]){block-size:100%;max-block-size:none;border:0;box-shadow:none}.et-query-devtools-panel:where([data-pane-axis=block]) .et-query-devtools-header{flex-wrap:wrap}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-tabs,.et-query-devtools-header-actions){flex:1 1 auto;flex-wrap:nowrap;min-inline-size:0;overflow-x:auto}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-tabs,.et-query-devtools-header-actions)>*{flex:none}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-master-detail,.et-query-devtools-split){flex-direction:column}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-list,.et-query-devtools-detail,.et-query-devtools-drawer,.et-query-devtools-split .et-query-devtools-scroll){flex:1 1 50%;min-inline-size:0;min-block-size:0;max-inline-size:none;border-inline-start:0;border-inline-end:0}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-detail,.et-query-devtools-drawer){border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-panel:where([data-pane-axis=block]) .et-query-devtools-master-detail>.et-query-devtools-list{flex:0 1 var(--_et-qdt-list-height, 50%);min-block-size:var(--_et-qdt-list-height, 0);max-block-size:var(--_et-qdt-list-height, none)}.et-query-devtools-panel:where([data-pane-axis=block]) .et-query-devtools-split>.et-query-devtools-drawer{flex:0 1 var(--_et-qdt-drawer-height, 50%);min-block-size:var(--_et-qdt-drawer-height, 0);max-block-size:var(--_et-qdt-drawer-height, none)}.et-query-devtools-panel:where([data-pane-axis=block]) .et-query-devtools-pane-resize{cursor:ns-resize}.et-query-devtools-panel--resizing{-webkit-user-select:none;user-select:none}.et-query-devtools-resize,.et-query-devtools-pane-resize{touch-action:none}.et-query-devtools-resize{position:absolute;inset-inline:0;inset-block-start:0;block-size:.6rem;cursor:ns-resize;z-index:1}.et-query-devtools-panel:where([data-dock=right]) .et-query-devtools-resize{inset-inline:0 auto;inset-block:0;inline-size:.6rem;block-size:auto;cursor:ew-resize}.et-query-devtools-panel:where([data-dock=left]) .et-query-devtools-resize{inset-inline:auto 0;inset-block:0;inline-size:.6rem;block-size:auto;cursor:ew-resize}.et-query-devtools-panel:where([data-dock=top]) .et-query-devtools-resize{inset-block:auto 0}.et-query-devtools-resize:after{content:\"\";position:absolute;inset-inline:50%;inset-block-start:.2rem;inline-size:4rem;margin-inline-start:-2rem;block-size:.2rem;border-radius:999px;background:var(--_et-qdt-border)}.et-query-devtools-panel:where([data-dock=right]) .et-query-devtools-resize:after{inset-inline:.2rem auto;inset-block:50% auto;inline-size:.2rem;block-size:4rem;margin-inline-start:0;margin-block-start:-2rem}.et-query-devtools-panel:where([data-dock=left]) .et-query-devtools-resize:after{inset-inline:auto .2rem;inset-block:50% auto;inline-size:.2rem;block-size:4rem;margin-inline-start:0;margin-block-start:-2rem}.et-query-devtools-panel:where([data-dock=top]) .et-query-devtools-resize:after{inset-block:auto .2rem}@media(hover:hover){.et-query-devtools-resize:hover:after{background:var(--_et-qdt-accent)}}.et-query-devtools-header{display:flex;align-items:center;justify-content:space-between;gap:.8rem;padding-inline:.8rem;border-block-end:1px solid var(--_et-qdt-border);flex:0 0 auto}.et-query-devtools-armed-bar{display:flex;align-items:center;gap:.8rem;flex:0 0 auto;padding:.6rem .8rem;background:color-mix(in srgb,var(--_et-qdt-error) 14%,transparent);border-block-end:1px solid color-mix(in srgb,var(--_et-qdt-error) 45%,transparent);color:var(--_et-qdt-error);font-size:1.1rem}.et-query-devtools-armed-text{flex:1 1 auto;min-inline-size:0}.et-query-devtools-notice-bar{display:flex;align-items:center;gap:.8rem;flex:0 0 auto;padding:.6rem .8rem;background:var(--_et-qdt-hover);border-block-end:1px solid var(--_et-qdt-border);color:var(--_et-qdt-fg);font-size:1.1rem}.et-query-devtools-notice-bar .et-query-devtools-btn{flex:0 0 auto}.et-query-devtools-armed-orphans{display:block;opacity:.85}.et-query-devtools-armed-dot{flex:0 0 auto;inline-size:.8rem;block-size:.8rem;border-radius:50%;background:var(--_et-qdt-error);animation:et-query-devtools-armed-pulse 1.6s ease-in-out infinite}@media(prefers-reduced-motion:reduce){.et-query-devtools-armed-dot{animation:none}}@keyframes et-query-devtools-armed-pulse{50%{opacity:.25}}.et-query-devtools-armed-bar .et-query-devtools-btn{flex:0 0 auto;border-color:color-mix(in srgb,var(--_et-qdt-error) 55%,transparent);color:var(--_et-qdt-error)}.et-query-devtools-tabs{display:flex;flex-wrap:wrap;gap:.2rem}.et-query-devtools-tab{appearance:none;display:inline-flex;align-items:center;gap:.5rem;border:none;background:transparent;color:var(--_et-qdt-muted);padding:.9rem 1rem;cursor:pointer;border-block-end:2px solid transparent;font:inherit}@media(hover:hover){.et-query-devtools-tab:hover{color:var(--_et-qdt-fg)}}.et-query-devtools-tab--active{color:var(--_et-qdt-fg);border-block-end-color:var(--_et-qdt-accent)}.et-query-devtools-tab-badge{display:inline-block;padding-inline:.45rem;border-radius:999px;background:color-mix(in srgb,var(--_et-qdt-interaction) 12%,transparent);color:var(--_et-qdt-muted);font-size:1rem;font-variant-numeric:tabular-nums}.et-query-devtools-tab-badge--error{background:color-mix(in srgb,var(--_et-qdt-error) 25%,transparent);color:var(--_et-qdt-fg)}.et-query-devtools-menu{display:contents}.et-query-devtools-menu-caret{font-size:.9em;opacity:.7}.et-query-devtools-menu-glyph{display:inline-block;min-inline-size:1.2rem;text-align:center}.et-query-devtools-menu>[data-menu-open]{background:var(--_et-qdt-hover);color:var(--_et-qdt-fg)}.et-menu .et-query-devtools-menu-item-glyph{display:inline-block;inline-size:16px;text-align:center}.et-menu-item-content:has(.et-query-devtools-menu-item-note){display:flex;flex-direction:column;align-items:start;gap:2px;padding-block:8px}.et-query-devtools-menu-item-note{color:var(--et-surface-color-muted-solid, rgb(255 255 255 / .55));font-size:12px}.et-query-devtools-header-actions{display:flex;align-items:center;gap:.2rem;flex:0 0 auto}.et-query-devtools-header-divider{inline-size:1px;block-size:1.6rem;margin-inline:.4rem;background:var(--_et-qdt-border)}.et-query-devtools-inspect-btn{position:relative}.et-query-devtools-inspect-icon{inline-size:1.4rem;block-size:1.4rem;flex:0 0 auto;fill:none;stroke:currentColor;stroke-width:1.25;stroke-linecap:round}.et-query-devtools-inspect-icon-dot{fill:currentColor;stroke:none}.et-query-devtools-btn.et-query-devtools-inspect-btn--active,.et-query-devtools-btn.et-query-devtools-inspect-btn--active:hover{color:#09090b;background:var(--_et-qdt-accent);border-color:var(--_et-qdt-accent)}.et-query-devtools-inspect-btn--active:after{content:\"\";position:absolute;inset:-.3rem;border:1px solid var(--_et-qdt-accent);border-radius:calc(var(--_et-qdt-radius) + .3rem);pointer-events:none;animation:et-query-devtools-inspect-pulse 1.4s ease-in-out infinite}.et-query-devtools-inspect-btn--active .et-query-devtools-inspect-icon-ring{animation:et-query-devtools-inspect-pulse 1.4s ease-in-out infinite}@keyframes et-query-devtools-inspect-pulse{0%,to{opacity:.2}50%{opacity:1}}@media(prefers-reduced-motion:reduce){.et-query-devtools-inspect-btn--active:after,.et-query-devtools-inspect-btn--active .et-query-devtools-inspect-icon-ring{animation:none;opacity:1}}.et-query-devtools-close{appearance:none;border:none;background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:.6rem;font:inherit}.et-query-devtools-kbd{border-radius:.3rem;background:var(--_et-qdt-hover);box-shadow:inset 0 0 0 1px var(--_et-qdt-border);color:var(--_et-qdt-muted);padding:.15rem .4rem;font:inherit;font-size:1.1rem;line-height:1.2;white-space:nowrap}.et-query-devtools-kbd--inverse{background:#00000026;box-shadow:inset 0 0 0 1px #00000040;color:inherit}@media(hover:none)and (pointer:coarse){.et-query-devtools-kbd{display:none}}.et-query-devtools-inspect-box{position:fixed;z-index:2147483002;pointer-events:none;border:2px solid var(--_et-qdt-accent);background:color-mix(in srgb,var(--_et-qdt-accent) 15%,transparent);border-radius:.2rem}.et-query-devtools-inspect-tag{position:absolute;inset-block-start:100%;inset-inline-start:0;margin-block-start:.2rem;padding:.2rem .5rem;border-radius:.3rem;background:var(--_et-qdt-accent);color:#09090b;font-family:ui-monospace,monospace;font-size:1.1rem;font-weight:600;white-space:nowrap}@media(hover:hover){.et-query-devtools-close:hover{color:var(--_et-qdt-fg)}}.et-query-devtools-body{flex:1 1 auto;min-block-size:0;overflow:hidden}.et-query-devtools-detail{flex:1 1 auto;min-inline-size:0;overflow-y:auto;padding:1rem}.et-query-devtools-detail .et-query-devtools-meta,.et-query-devtools-drawer .et-query-devtools-meta{padding:.8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 3%,transparent)}.et-query-devtools-detail .et-query-devtools-section-row,.et-query-devtools-drawer .et-query-devtools-section-row{padding-block-start:.8rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-filter-chip{display:inline-flex;align-items:center;gap:.6rem;padding:.3rem .35rem .3rem .7rem;border-radius:999px;background:color-mix(in srgb,var(--_et-qdt-accent) 16%,transparent);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--_et-qdt-accent) 45%,transparent);color:var(--_et-qdt-accent);font-size:1.1rem;font-weight:600;letter-spacing:.02em}.et-query-devtools-filter-icon{font-size:1.3rem;line-height:1}.et-query-devtools-filter-count{border-radius:999px;background:var(--_et-qdt-accent);color:#09090b;min-inline-size:1.8rem;padding:0 .5rem;text-align:center}.et-query-devtools-filter-clear{appearance:none;display:inline-flex;align-items:center;gap:.5rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:.3rem .6rem;font:inherit;font-size:1.15rem;line-height:1.4}@media(hover:hover){.et-query-devtools-filter-clear:hover{border-color:color-mix(in srgb,var(--_et-qdt-accent) 60%,transparent);color:var(--_et-qdt-fg)}}.et-query-devtools-scroll{block-size:100%;overflow:auto;padding:1rem;display:flex;flex-direction:column;gap:1rem}.et-query-devtools-split{display:flex;block-size:100%;min-block-size:0}.et-query-devtools-split .et-query-devtools-scroll{flex:1 1 auto;min-inline-size:0}.et-query-devtools-pane{display:flex;flex-direction:column;flex:1 1 auto;block-size:100%;min-inline-size:0;min-block-size:0;overflow:hidden}.et-query-devtools-pane>.et-query-devtools-scroll{padding-block:0 1rem}.et-query-devtools-pane-resize{flex:0 0 .5rem;align-self:stretch;cursor:ew-resize;background:transparent;transition:background .12s ease}.et-query-devtools-pane-resize:active{background:var(--_et-qdt-accent)}@media(hover:hover){.et-query-devtools-pane-resize:hover{background:var(--_et-qdt-accent)}}.et-query-devtools-toolbar{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.6rem;padding:.8rem;position:sticky;inset-block-start:0;z-index:1;background:var(--_et-qdt-bg);border-block-end:1px solid var(--_et-qdt-border)}.et-query-devtools-scroll>.et-query-devtools-toolbar{inset-block-start:-1rem}.et-query-devtools-toolbar-row{display:flex;align-items:center;flex-wrap:wrap;flex:1 1 100%;gap:.5rem}.et-query-devtools-field{display:inline-flex;align-items:center;gap:.6rem;color:var(--_et-qdt-muted)}.et-query-devtools-select{flex:0 0 auto;min-block-size:var(--_et-qdt-control-h);background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,var(--_et-qdt-bg));color:var(--_et-qdt-fg);border:1px solid transparent;border-radius:var(--_et-qdt-radius);padding:.4rem .6rem;font:inherit;font-size:1.15rem}.et-query-devtools-select option{background-color:var(--_et-qdt-bg);color:var(--_et-qdt-fg)}.et-query-devtools-count{color:var(--_et-qdt-subtle)}.et-query-devtools-row{display:flex;align-items:stretch;inline-size:100%;min-block-size:var(--_et-qdt-row-h);border-block-end:1px solid var(--_et-qdt-border);color:var(--_et-qdt-fg)}@media(hover:hover){.et-query-devtools-row:hover{background:var(--_et-qdt-hover)}}.et-query-devtools-method{font-weight:700;font-size:1.1rem;color:var(--_et-qdt-muted);white-space:nowrap}.et-query-devtools-route{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1 1 auto;min-inline-size:0}.et-query-devtools-status{inline-size:.8rem;block-size:.8rem;border-radius:999px;flex:0 0 auto;background:var(--_et-qdt-subtle)}.et-query-devtools-status[data-status=success]{background:var(--_et-qdt-success)}.et-query-devtools-status[data-status=error]{background:var(--_et-qdt-error)}.et-query-devtools-status[data-status=loading],.et-query-devtools-status[data-status=running]{background:var(--_et-qdt-loading)}.et-query-devtools-btn{appearance:none;display:inline-flex;align-items:center;justify-content:center;gap:.5rem;flex:0 0 auto;min-block-size:var(--_et-qdt-control-h);border:1px solid transparent;border-radius:var(--_et-qdt-radius);background:transparent;color:var(--_et-qdt-muted);padding:.25rem .7rem;font:inherit;font-size:1.15rem;line-height:1.4;white-space:nowrap;cursor:pointer;transition:color .12s ease,background-color .12s ease,border-color .12s ease}@media(hover:hover){.et-query-devtools-btn:hover:not(:disabled){background:var(--_et-qdt-hover);color:var(--_et-qdt-fg)}}.et-query-devtools-btn:focus-visible{outline:2px solid var(--_et-qdt-accent);outline-offset:1px}.et-query-devtools-btn:disabled{color:var(--_et-qdt-subtle);cursor:default;background:transparent;opacity:.6}.et-query-devtools-btn--primary,.et-query-devtools-btn--primary:hover:not(:disabled){background:color-mix(in srgb,var(--_et-qdt-accent) 16%,transparent);border-color:color-mix(in srgb,var(--_et-qdt-accent) 40%,transparent);color:var(--_et-qdt-accent)}@media(hover:hover){.et-query-devtools-btn--primary:hover:not(:disabled){background:color-mix(in srgb,var(--_et-qdt-accent) 26%,transparent)}}@media(hover:hover){.et-query-devtools-btn--danger:hover:not(:disabled){background:color-mix(in srgb,var(--_et-qdt-error) 16%,transparent);color:var(--_et-qdt-error)}}.et-query-devtools-btn--tampered,.et-query-devtools-btn--tampered:hover:not(:disabled){background:color-mix(in srgb,var(--_et-qdt-error) 14%,transparent);border-color:color-mix(in srgb,var(--_et-qdt-error) 45%,transparent);color:var(--_et-qdt-error)}.et-query-devtools-icon-btn{inline-size:var(--_et-qdt-control-h);padding-inline:0;font-size:1.3rem}.et-query-devtools-btn-group{display:inline-flex;flex:0 0 auto;border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius);overflow:hidden}.et-query-devtools-btn-group>.et-query-devtools-btn{border-radius:0;border-block:0;border-inline-start:0;border-inline-end:1px solid var(--_et-qdt-border)}.et-query-devtools-btn-group>.et-query-devtools-btn:last-child{border-inline-end:0}.et-query-devtools-toolbar-btn{margin-inline-start:auto;flex:0 0 auto}.et-query-devtools-meta{display:grid;grid-template-columns:auto 1fr;gap:.3rem 1rem;margin:0 0 1rem}.et-query-devtools-meta dt{color:var(--_et-qdt-muted)}.et-query-devtools-meta dd{margin:0;overflow-wrap:anywhere}.et-query-devtools-section{margin:1rem 0 .4rem;font-size:1.1rem;text-transform:uppercase;letter-spacing:.05em;color:var(--_et-qdt-muted)}.et-query-devtools-stat{padding:.6rem .8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 3%,transparent)}.et-query-devtools-stat dt{font-size:1rem;text-transform:uppercase;letter-spacing:.05em;color:var(--_et-qdt-muted)}.et-query-devtools-stat dd{margin:0;font-size:1.5rem;font-variant-numeric:tabular-nums}.et-query-devtools-stat-sub{display:block;font-size:1rem;color:var(--_et-qdt-subtle)}.et-query-devtools-json{margin:0;padding:.8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);overflow-x:auto;white-space:pre-wrap;overflow-wrap:anywhere;max-block-size:24rem;overflow-y:auto}.et-query-devtools-json--error{border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-mono{font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.et-query-devtools-url{display:inline-block;max-inline-size:32rem;margin-inline-start:.6rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.et-query-devtools-chips,.et-query-devtools-chip-row{display:flex;flex-wrap:wrap;gap:.4rem;margin-block-end:.8rem}.et-query-devtools-chip{display:inline-flex;align-items:center;gap:.3rem;padding:.2rem .6rem;border-radius:999px;border:1px solid var(--_et-qdt-border);font-size:1.1rem;color:var(--_et-qdt-fg);background:color-mix(in srgb,var(--_et-qdt-interaction) 6%,transparent)}.et-query-devtools-chip--muted{color:var(--_et-qdt-muted)}.et-query-devtools-retry-glyph{font-size:.7em;line-height:1}.et-query-devtools-chip--loading{color:var(--_et-qdt-loading);border-color:color-mix(in srgb,var(--_et-qdt-loading) 50%,transparent)}.et-query-devtools-chip--error{color:var(--_et-qdt-error);border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-chip--success{color:var(--_et-qdt-success);border-color:color-mix(in srgb,var(--_et-qdt-success) 50%,transparent)}button.et-query-devtools-chip--button{appearance:none;cursor:pointer;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}@media(hover:hover){button.et-query-devtools-chip--button:hover{border-color:var(--_et-qdt-accent);color:var(--_et-qdt-accent)}}.et-query-devtools-facet{appearance:none;min-block-size:var(--_et-qdt-control-h);padding-inline:.8rem;background:transparent;cursor:pointer;color:var(--_et-qdt-muted);font:inherit;font-size:1.1rem;line-height:1.4}@media(hover:hover){.et-query-devtools-facet:hover:not(:disabled){color:var(--_et-qdt-fg);background:var(--_et-qdt-hover);border-color:color-mix(in srgb,var(--_et-qdt-accent) 60%,transparent)}}.et-query-devtools-facet:disabled{color:var(--_et-qdt-subtle);cursor:default}.et-query-devtools-facet--active{color:var(--_et-qdt-accent);border-color:color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent);background:color-mix(in srgb,var(--_et-qdt-accent) 14%,transparent)}.et-query-devtools-facet-count{font-variant-numeric:tabular-nums;color:inherit;opacity:.75}.et-query-devtools-card{border:1px solid var(--_et-qdt-border);border-radius:.6rem;padding:1rem}.et-query-devtools-card--armed{border-color:color-mix(in srgb,var(--_et-qdt-error) 55%,transparent);background:color-mix(in srgb,var(--_et-qdt-error) 6%,transparent)}.et-query-devtools-card-head{display:flex;align-items:center;gap:.6rem;margin-block-end:.8rem}.et-query-devtools-inner-queries{display:flex;flex-direction:column;gap:.3rem}.et-query-devtools-inner-row{display:flex;align-items:center;gap:.6rem;inline-size:100%;text-align:start;appearance:none;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:transparent;color:var(--_et-qdt-fg);padding:.4rem .6rem;cursor:pointer;font:inherit}@media(hover:hover){.et-query-devtools-inner-row:hover:not([disabled]){background:var(--_et-qdt-hover)}}.et-query-devtools-inner-row--active{background:var(--_et-qdt-active);border-color:var(--_et-qdt-accent)}.et-query-devtools-inner-row[disabled]{cursor:default;opacity:.6}.et-query-devtools-step-toggle{display:flex;align-items:center;gap:.6rem;inline-size:100%;text-align:start;appearance:none;border:none;background:transparent;color:var(--_et-qdt-fg);padding:.6rem;cursor:pointer;font:inherit}@media(hover:hover){.et-query-devtools-step-toggle:hover{background:var(--_et-qdt-hover)}}.et-query-devtools-step-index{flex:1 1 auto;min-inline-size:0}.et-query-devtools-step-caret{color:var(--_et-qdt-muted)}.et-query-devtools-table{inline-size:100%;border-collapse:collapse}.et-query-devtools-table th,.et-query-devtools-table td{text-align:start;padding:.5rem .6rem;border-block-end:1px solid var(--_et-qdt-border);vertical-align:top}.et-query-devtools-table td .et-query-devtools-chip{padding-block:0;line-height:1.4}.et-query-devtools-table th{color:var(--_et-qdt-muted);font-weight:600;position:sticky;inset-block-start:0;background:var(--_et-qdt-bg)}.et-query-devtools-table tr[data-status=error] td{color:var(--_et-qdt-error)}.et-query-devtools-empty{color:var(--_et-qdt-subtle);padding:1.2rem;text-align:center}.et-query-devtools-cell-actions{display:inline-flex;gap:.4rem;white-space:nowrap}.et-query-devtools-section-row{display:flex;align-items:center;justify-content:space-between;gap:.8rem;margin-block-start:1rem}.et-query-devtools-search{flex:1 1 16rem;min-inline-size:0;min-block-size:var(--_et-qdt-control-h);background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,transparent);color:var(--_et-qdt-fg);border:1px solid transparent;border-radius:var(--_et-qdt-radius);padding:.4rem .8rem;font:inherit;font-size:1.15rem}.et-query-devtools-search:focus{outline:none;border-color:color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent);background:var(--_et-qdt-bg)}.et-query-devtools-row-link{display:inline-flex;align-items:center;gap:.6rem;appearance:none;border:0;background:none;padding:0;color:inherit;font:inherit;text-align:start;cursor:pointer}.et-query-devtools-row-link:focus-visible .et-query-devtools-url{color:var(--_et-qdt-accent);text-decoration:underline}@media(hover:hover){.et-query-devtools-row-link:hover .et-query-devtools-url{color:var(--_et-qdt-accent);text-decoration:underline}}.et-query-devtools-hint{flex:1 1 24rem;color:var(--_et-qdt-muted);font-size:1.1rem;margin:0}.et-query-devtools-input{inline-size:100%;background:var(--_et-qdt-bg);color:var(--_et-qdt-fg);border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.4rem .6rem;font:inherit}.et-query-devtools-input--number{inline-size:8rem;font-variant-numeric:tabular-nums}.et-query-devtools-editor{border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.8rem;margin-block-end:1rem}.et-query-devtools-editor-error{color:var(--_et-qdt-error);margin:.4rem 0 0}.et-query-devtools-btn--active{color:var(--_et-qdt-accent);border-color:color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent);background:color-mix(in srgb,var(--_et-qdt-accent) 12%,transparent)}}\n"], dependencies: [{ kind: "component", type: QueryDevtoolsAuthTabComponent, selector: "et-query-devtools-auth-tab" }, { kind: "component", type: QueryDevtoolsCacheTabComponent, selector: "et-query-devtools-cache-tab" }, { kind: "component", type: QueryDevtoolsEventsTabComponent, selector: "et-query-devtools-events-tab" }, { kind: "component", type: QueryDevtoolsAboutComponent, selector: "et-query-devtools-about" }, { kind: "component", type: QueryDevtoolsSettingsComponent, selector: "et-query-devtools-settings" }, { kind: "component", type: QueryDevtoolsFaultsTabComponent, selector: "et-query-devtools-faults-tab" }, { kind: "component", type: QueryDevtoolsFormsTabComponent, selector: "et-query-devtools-forms-tab" }, { kind: "component", type: QueryDevtoolsLocksTabComponent, selector: "et-query-devtools-locks-tab" }, { kind: "component", type: QueryDevtoolsMocksTabComponent, selector: "et-query-devtools-mocks-tab" }, { kind: "component", type: QueryDevtoolsQueriesTabComponent, selector: "et-query-devtools-queries-tab" }, { kind: "component", type: QueryDevtoolsSequencesTabComponent, selector: "et-query-devtools-sequences-tab" }, { kind: "component", type: QueryDevtoolsSocketsTabComponent, selector: "et-query-devtools-sockets-tab" }, { kind: "component", type: QueryDevtoolsStacksTabComponent, selector: "et-query-devtools-stacks-tab" }, { kind: "component", type: QueryDevtoolsTimelineTabComponent, selector: "et-query-devtools-timeline-tab" }, { kind: "component", type: QueryDevtoolsToggleComponent, selector: "et-query-devtools-toggle", inputs: ["tampered"], outputs: ["openChange"] }, { kind: "directive", type: DragHandleDirective, selector: "[etDragHandle]", inputs: ["commitThreshold", "disabled"], outputs: ["dragTapped", "dragStarted", "dragMoved", "dragEnded", "dragCancelled"] }, { kind: "component", type: MenuCheckboxItemComponent, selector: "et-menu-checkbox-item", inputs: ["icon"] }, { kind: "component", type: MenuComponent, selector: "et-menu" }, { kind: "directive", type: MenuDirective, selector: "[etMenu]", inputs: ["placement", "fallbackPlacements", "offset", "viewportPadding", "arrow", "arrowPadding", "autoFocus", "hoverOpen", "loop", "hoverOpenDelay", "hoverCloseDelay", "disabled", "open"], outputs: ["openChange"], exportAs: ["etMenu"] }, { kind: "component", type: MenuItemComponent, selector: "button[et-menu-item], a[et-menu-item]", inputs: ["variant"] }, { kind: "component", type: MenuRadioGroupComponent, selector: "et-menu-radio-group" }, { kind: "component", type: MenuRadioItemComponent, selector: "et-menu-radio-item", inputs: ["icon"] }, { kind: "component", type: MenuSeparatorComponent, selector: "et-menu-separator" }, { kind: "directive", type: MenuSurfaceDirective, selector: "ng-template[etMenuSurface]", exportAs: ["etMenuSurface"] }, { kind: "directive", type: MenuTriggerDirective, selector: "[etMenuTrigger]", exportAs: ["etMenuTrigger"] }, { kind: "component", type: ResizeHandlesComponent, selector: "et-resize-handles", inputs: ["edges", "disabled"], outputs: ["resizeStarted", "resizeMoved", "resizeEnded", "resizeCancelled"] }], encapsulation: i0.ViewEncapsulation.None }); }
7184
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsComponent, isStandalone: true, selector: "et-query-devtools", inputs: { startOpen: { classPropertyName: "startOpen", publicName: "startOpen", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-et-overlay-layer": "2147483020" }, properties: { "attr.data-dock": "dock()" }, classAttribute: "et-query-devtools-host" }, providers: [{ provide: QUERY_DEVTOOLS_HOST, useExisting: QueryDevtoolsComponent }], viewQueries: [{ propertyName: "panelEl", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (open()) {\n <section\n #panel\n [class.et-query-devtools-panel--resizing]=\"resizing()\"\n [attr.data-dock]=\"poppedOut() ? 'popout' : dock()\"\n [attr.data-pane-axis]=\"paneAxis()\"\n [attr.data-parked]=\"floatParked() ? '' : null\"\n [style.block-size.px]=\"panelBlockSize()\"\n [style.inline-size.px]=\"panelInlineSize()\"\n [style.inset-block-start.px]=\"panelInsetBlock()\"\n [style.inset-inline-start.px]=\"panelInsetInline()\"\n [style.--_et-qdt-list-width.px]=\"listWidth()\"\n [style.--_et-qdt-drawer-width.px]=\"drawerWidth()\"\n [style.--_et-qdt-list-height.px]=\"listHeight()\"\n [style.--_et-qdt-drawer-height.px]=\"drawerHeight()\"\n class=\"et-query-devtools-panel\"\n >\n <!-- A pop-out is sized by its window, so there is no edge to drag. A float is a window in the page:\n a title bar to move it and every edge to resize it, on the same `@ethlete/core` primitives the\n stream pip window uses. A dock has one edge, and it does both. -->\n @if (floating()) {\n <!-- Parked, the panel is a grab strip: the edge handles would eat most of the peek that is left\n to click, and resizing something you cannot see is not a thing to offer. -->\n <et-resize-handles\n [edges]=\"FLOAT_RESIZE_EDGES\"\n [disabled]=\"floatParked()\"\n (resizeStarted)=\"startFloatResize()\"\n (resizeMoved)=\"resizeFloat($event)\"\n (resizeEnded)=\"endFloatResize()\"\n (resizeCancelled)=\"endFloatResize()\"\n />\n <div\n (dragMoved)=\"moveFloat($event)\"\n (dragEnded)=\"endFloatMove()\"\n (dragCancelled)=\"endFloatMove()\"\n (dragTapped)=\"restoreFloat()\"\n class=\"et-query-devtools-move\"\n title=\"Drag to move \u2014 shove it off an edge to park it, click the strip to bring it back\"\n aria-hidden=\"true\"\n etDragHandle\n ></div>\n } @else if (!poppedOut()) {\n <div\n (pointerdown)=\"startResize($event)\"\n class=\"et-query-devtools-resize\"\n title=\"Drag to resize\"\n aria-hidden=\"true\"\n ></div>\n }\n <header class=\"et-query-devtools-header\">\n <nav class=\"et-query-devtools-tabs\" role=\"tablist\">\n @for (tab of visibleTabs(); track tab.id) {\n @let badge = tabBadges()[tab.id];\n <button\n [class.et-query-devtools-tab--active]=\"activeTab() === tab.id\"\n [attr.aria-selected]=\"activeTab() === tab.id\"\n (click)=\"activeTab.set(tab.id)\"\n class=\"et-query-devtools-tab\"\n type=\"button\"\n role=\"tab\"\n >\n {{ tab.label }}\n @if (badge.count) {\n <span class=\"et-query-devtools-tab-badge\">{{ badge.count }}</span>\n }\n @if (badge.errors) {\n <span\n class=\"et-query-devtools-tab-badge et-query-devtools-tab-badge--error\"\n title=\"{{ badge.errors }} {{ badge.errorNoun ?? 'failing' }}\"\n >\n {{ badge.errors }}\n </span>\n }\n </button>\n }\n\n @if (overflowTabs().length; as overflowCount) {\n <div class=\"et-query-devtools-menu\" etMenu>\n <button\n [title]=\"overflowCount + ' tabs with nothing in them'\"\n class=\"et-query-devtools-tab\"\n type=\"button\"\n etMenuTrigger\n >\n More\n <span class=\"et-query-devtools-menu-caret\" aria-hidden=\"true\">\u25BE</span>\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n @for (tab of overflowTabs(); track tab.id) {\n <button (activate)=\"activeTab.set(tab.id)\" et-menu-item type=\"button\">{{ tab.label }}</button>\n }\n </et-menu>\n </ng-template>\n </div>\n }\n </nav>\n\n <div class=\"et-query-devtools-header-actions\">\n <button\n [class.et-query-devtools-inspect-btn--active]=\"inspectActive()\"\n [attr.aria-pressed]=\"inspectActive()\"\n (click)=\"toggleInspect()\"\n class=\"et-query-devtools-btn et-query-devtools-inspect-btn\"\n type=\"button\"\n title=\"Inspect: hover the UI to find its query\"\n >\n <svg\n class=\"et-query-devtools-inspect-icon\"\n viewBox=\"0 0 16 16\"\n width=\"14\"\n height=\"14\"\n fill=\"none\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <circle class=\"et-query-devtools-inspect-icon-ring\" cx=\"8\" cy=\"8\" r=\"4.25\" />\n <circle class=\"et-query-devtools-inspect-icon-dot\" cx=\"8\" cy=\"8\" r=\"1.15\" />\n <path d=\"M8 0.9v2.1M8 13v2.1M0.9 8h2.1M13 8h2.1\" />\n </svg>\n {{ inspectActive() ? 'Inspecting' : 'Inspect' }}\n @if (inspectActive()) {\n <kbd class=\"et-query-devtools-kbd et-query-devtools-kbd--inverse\">Esc</kbd>\n }\n </button>\n <button\n (click)=\"downloadSession()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Download every registered entry, the event log, the cache totals and anything armed as one JSON file - the attachment for a bug report\"\n >\n <span aria-hidden=\"true\">\u2913</span>\n Session\n </button>\n <!-- A gear, not a tab: Settings holds nothing to count, so the badge/overflow logic would push it\n behind More - and the one control that explains a panel behaving oddly may not be hidden. -->\n <button\n [class.et-query-devtools-btn--active]=\"activeTab() === 'settings'\"\n [attr.aria-pressed]=\"activeTab() === 'settings'\"\n (click)=\"toggleSettings()\"\n class=\"et-query-devtools-btn et-query-devtools-icon-btn\"\n type=\"button\"\n title=\"Settings: where panel state is kept, and the limits it holds\"\n aria-label=\"Settings\"\n >\n <span aria-hidden=\"true\">\u2699</span>\n </button>\n @if (!poppedOut()) {\n @let layout = currentLayout();\n <div class=\"et-query-devtools-menu\" etMenu placement=\"bottom-end\">\n <button class=\"et-query-devtools-btn\" title=\"Where the panel sits\" type=\"button\" etMenuTrigger>\n <span class=\"et-query-devtools-menu-glyph\" aria-hidden=\"true\">{{ layout.glyph }}</span>\n {{ layout.label }}\n <span class=\"et-query-devtools-menu-caret\" aria-hidden=\"true\">\u25BE</span>\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <et-menu-radio-group [value]=\"dock()\">\n @for (option of LAYOUTS; track option.id) {\n <!-- eslint-disable ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <et-menu-radio-item\n [value]=\"option.id\"\n [title]=\"option.hint\"\n [closeOnActivate]=\"true\"\n (activate)=\"selectLayout(option.id)\"\n >\n <!-- eslint-enable ethlete/prefer-static-boolean-properties -->\n <span class=\"et-query-devtools-menu-item-glyph\" aria-hidden=\"true\">{{ option.glyph }}</span>\n {{ option.label }}\n </et-menu-radio-item>\n }\n </et-menu-radio-group>\n <et-menu-separator />\n <et-menu-checkbox-item\n [checked]=\"reservesSpace()\"\n [disabled]=\"floating()\"\n (checkedChange)=\"reservesSpace.set($event)\"\n title=\"The page gives up the room the panel covers, so its last rows can still be scrolled to. Docks only - a float sits wherever it was dragged.\"\n >\n Reserve page space\n </et-menu-checkbox-item>\n </et-menu>\n </ng-template>\n </div>\n }\n <span class=\"et-query-devtools-header-divider\" aria-hidden=\"true\"></span>\n <button\n (click)=\"toggleOpen()\"\n class=\"et-query-devtools-btn et-query-devtools-btn--danger\"\n type=\"button\"\n title=\"{{ poppedOut() ? 'Bring the panel back into the app window' : 'Close devtools (' + shortcut + ')' }}\"\n >\n @if (poppedOut()) {\n <span aria-hidden=\"true\">\u29C8</span>\n Dock back\n } @else {\n <span aria-hidden=\"true\">\u2715</span>\n Close\n <kbd class=\"et-query-devtools-kbd\">{{ shortcut }}</kbd>\n }\n </button>\n </div>\n </header>\n\n @if (popOutBlocked()) {\n <div class=\"et-query-devtools-notice-bar\" role=\"status\">\n <span class=\"et-query-devtools-armed-text\">\n The browser blocked the pop-up window. Allow pop-ups for this site, or float the panel over the page instead \u2014\n a float needs no permission.\n </span>\n <button (click)=\"floatPanel()\" class=\"et-query-devtools-btn\" type=\"button\">Float instead</button>\n <button (click)=\"popOutBlocked.set(false)\" class=\"et-query-devtools-btn\" type=\"button\">Dismiss</button>\n </div>\n }\n\n @if (armedFaultClients(); as armed) {\n <div class=\"et-query-devtools-armed-bar\" role=\"status\">\n <span class=\"et-query-devtools-armed-dot\" aria-hidden=\"true\"></span>\n <span class=\"et-query-devtools-armed-text\">\n <strong>Faults armed</strong> on\n <span class=\"et-query-devtools-mono\">{{ armed.join(', ') }}</span>\n \u2014 every failure and delay you see from\n {{ armed.length === 1 ? 'this client' : 'these clients' }} is injected, not the API.\n @if (faultsRestored()) {\n They came back from your last page load \u2014 set <em>Armed faults</em> back to <em>none</em> in Settings to\n stop that.\n }\n </span>\n <button\n [disabled]=\"activeTab() === 'faults'\"\n (click)=\"activeTab.set('faults')\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Review\n </button>\n <button (click)=\"CLEAR_FAULTS()\" class=\"et-query-devtools-btn\" type=\"button\">Disarm all</button>\n </div>\n }\n\n @if (armedMockRoutes(); as mocked) {\n <div class=\"et-query-devtools-armed-bar\" role=\"status\">\n <span class=\"et-query-devtools-armed-dot\" aria-hidden=\"true\"></span>\n <span class=\"et-query-devtools-armed-text\">\n <strong>Mocks armed</strong> on\n <span class=\"et-query-devtools-mono\">{{ mocked.join(', ') }}</span>\n \u2014 {{ mocked.length === 1 ? 'that route is' : 'those routes are' }} answered by the panel, so nothing they show\n reached the API.\n @if (mocksRestored()) {\n They came back from your last page load \u2014 set <em>Armed mocks</em> back to <em>none</em> in Settings to stop\n that.\n }\n </span>\n <button\n [disabled]=\"activeTab() === 'mocks'\"\n (click)=\"activeTab.set('mocks')\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Review\n </button>\n <button (click)=\"DISARM_MOCKS()\" class=\"et-query-devtools-btn\" type=\"button\">Disarm all</button>\n </div>\n }\n\n @if (restoredOverrides(); as restored) {\n <div class=\"et-query-devtools-armed-bar\" role=\"status\">\n <span class=\"et-query-devtools-armed-dot\" aria-hidden=\"true\"></span>\n <span class=\"et-query-devtools-armed-text\">\n @if (restored.ops) {\n <strong>Overrides re-armed</strong> \u2014 {{ restored.ops }} {{ restored.ops === 1 ? 'edit' : 'edits' }} from\n your last page load {{ restored.ops === 1 ? 'is' : 'are' }} back on {{ restored.queries }}\n {{ restored.queries === 1 ? 'query' : 'queries' }}, so those responses are not what the API sent.\n @if (restored.fromLocalStorage) {\n They came back from <strong>localStorage</strong>, so they outlive closing this tab \u2014 clear them, or set\n overrides back to <em>session</em> in Settings.\n }\n } @else {\n <strong>Overrides could not be re-armed.</strong>\n }\n @if (restored.orphaned.length) {\n <span class=\"et-query-devtools-armed-orphans\">\n {{ restored.orphaned.length }} stored\n {{ restored.orphaned.length === 1 ? 'override' : 'overrides' }} matched no query \u2014\n <span class=\"et-query-devtools-mono\">{{ restored.orphaned.join(', ') }}</span>\n </span>\n }\n </span>\n @if (restored.firstId; as firstId) {\n <button (click)=\"reviewRestoredOverrides(firstId)\" class=\"et-query-devtools-btn\" type=\"button\">Review</button>\n }\n <button (click)=\"DROP_RESTORED_OVERRIDES()\" class=\"et-query-devtools-btn\" type=\"button\">Drop all</button>\n </div>\n }\n\n <div class=\"et-query-devtools-body\">\n @switch (activeTab()) {\n <!-- QUERIES -->\n @case ('queries') {\n <et-query-devtools-queries-tab />\n }\n\n <!-- STACKS -->\n @case ('stacks') {\n <et-query-devtools-stacks-tab />\n }\n\n <!-- SEQUENCES -->\n @case ('sequences') {\n <et-query-devtools-sequences-tab />\n }\n\n <!-- BATCHES -->\n @case ('batches') {\n <et-query-devtools-batches-tab />\n }\n\n <!-- FORMS -->\n @case ('forms') {\n <et-query-devtools-forms-tab />\n }\n\n <!-- AUTH -->\n @case ('auth') {\n <et-query-devtools-auth-tab />\n }\n\n <!-- SOCKETS -->\n @case ('ws') {\n <et-query-devtools-sockets-tab />\n }\n\n <!-- CACHE -->\n @case ('cache') {\n <et-query-devtools-cache-tab />\n }\n\n <!-- LOCKS -->\n @case ('locks') {\n <et-query-devtools-locks-tab />\n }\n\n <!-- TIMELINE -->\n @case ('timeline') {\n <et-query-devtools-timeline-tab />\n }\n\n <!-- EVENTS -->\n @case ('events') {\n <et-query-devtools-events-tab />\n }\n\n <!-- FAULTS -->\n @case ('faults') {\n <et-query-devtools-faults-tab />\n }\n\n <!-- MOCKS -->\n @case ('mocks') {\n <et-query-devtools-mocks-tab />\n }\n\n <!-- ABOUT -->\n @case ('about') {\n <et-query-devtools-about />\n }\n\n <!-- SETTINGS -->\n @case ('settings') {\n <et-query-devtools-settings />\n }\n }\n </div>\n </section>\n}\n\n@if (!open()) {\n <et-query-devtools-toggle [tampered]=\"panelTampered()\" (openChange)=\"toggleOpen()\" />\n}\n\n@if (inspectActive() && inspectHover(); as hover) {\n <div\n [style.left.px]=\"hover.rect.left\"\n [style.top.px]=\"hover.rect.top\"\n [style.width.px]=\"hover.rect.width\"\n [style.height.px]=\"hover.rect.height\"\n class=\"et-query-devtools-inspect-box\"\n >\n <span class=\"et-query-devtools-inspect-tag\">{{ inspectLabel(hover.entries) }}</span>\n </div>\n}\n\n@if (locatedRect(); as rect) {\n <div\n [style.left.px]=\"rect.left\"\n [style.top.px]=\"rect.top\"\n [style.width.px]=\"rect.width\"\n [style.height.px]=\"rect.height\"\n class=\"et-query-devtools-inspect-box\"\n >\n <span class=\"et-query-devtools-inspect-tag\">created here</span>\n </div>\n}\n", styles: ["@layer components{.et-query-devtools-host{--_et-qdt-bg: var(--et-surface-background-solid, rgb(24 24 27));--_et-qdt-fg: var(--et-surface-color-solid, rgb(244 244 245));--_et-qdt-muted: var(--et-surface-color-muted-solid, rgb(161 161 170));--_et-qdt-subtle: var(--et-surface-color-subtle-solid, rgb(113 113 122));--_et-qdt-border: var(--et-surface-border-solid, rgb(255 255 255 / .12));--_et-qdt-interaction: var(--et-surface-interaction-solid, rgb(255 255 255));--_et-qdt-accent: var(--et-theme-color-primary-solid, rgb(96 165 250));--_et-qdt-hover: color-mix(in srgb, var(--_et-qdt-interaction) 10%, transparent);--_et-qdt-active: color-mix(in srgb, var(--_et-qdt-interaction) 18%, transparent);--_et-qdt-success: rgb(34 197 94);--_et-qdt-error: rgb(239 68 68);--_et-qdt-loading: rgb(234 179 8);--_et-qdt-title-bar: 2.4rem;--_et-qdt-radius: .5rem;--_et-qdt-control-h: 2.4rem;--_et-qdt-row-h: 3.6rem;position:fixed;inset-inline:0;inset-block-end:0;z-index:2147483010;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,Liberation Mono,monospace;font-size:1.2rem;line-height:1.4;pointer-events:none}.et-query-devtools-host>*{pointer-events:auto}@media(pointer:coarse){.et-query-devtools-host{--_et-qdt-control-h: 3.4rem;--_et-qdt-row-h: 4.4rem}}.et-query-devtools-panel{position:relative;display:flex;flex-direction:column;inline-size:100%;min-block-size:20rem;max-block-size:90vh;background:var(--_et-qdt-bg);color:var(--_et-qdt-fg);border-block-start:1px solid var(--_et-qdt-border);box-shadow:0 -.4rem 2rem #00000059}.et-query-devtools-host:where([data-dock=right]){inset-block:0;inset-inline-start:auto}.et-query-devtools-panel:where([data-dock=right]){inline-size:auto;block-size:100%;max-block-size:none;max-inline-size:90vw;border-block-start:0;border-inline-start:1px solid var(--_et-qdt-border);box-shadow:-.4rem 0 2rem #00000059}.et-query-devtools-host:where([data-dock=float]){inset:0}.et-query-devtools-panel:where([data-dock=float]){--_et-qdt-frame: .8rem;--et-resize-handles-edge-size: var(--_et-qdt-frame);--et-resize-handles-touch-edge-size: var(--_et-qdt-frame);--et-resize-handles-corner-size: 1.6rem;--et-resize-handles-touch-corner-size: 1.6rem;--et-resize-handles-edge-inset: 1.6rem;--et-resize-handles-side-top: var(--_et-qdt-title-bar);--et-resize-handles-side-bottom: 1.6rem;--et-resize-handles-z-index: 3;position:absolute;inline-size:auto;min-block-size:0;max-block-size:none;padding-block-start:var(--_et-qdt-title-bar);padding-inline:var(--_et-qdt-frame);padding-block-end:var(--_et-qdt-frame);border:1px solid var(--_et-qdt-border);border-radius:.6rem;box-shadow:0 1rem 4rem #00000073;overflow:hidden}.et-query-devtools-move{position:absolute;inset-inline:0;inset-block-start:0;block-size:var(--_et-qdt-title-bar);cursor:move;z-index:2}.et-query-devtools-panel:where([data-dock=float]) .et-query-devtools-body{border-radius:.3rem;overflow:hidden}.et-query-devtools-move:after{content:\"\";position:absolute;inset-inline:50%;inset-block-start:.9rem;inline-size:3rem;margin-inline-start:-1.5rem;block-size:.6rem;background-image:radial-gradient(currentcolor 40%,transparent 42%);background-size:.4rem .3rem;color:var(--_et-qdt-subtle)}@media(hover:hover){.et-query-devtools-move:hover:after{color:var(--_et-qdt-accent)}}.et-query-devtools-panel:where([data-dock=float][data-parked]){opacity:.85}@media(hover:hover){.et-query-devtools-panel:where([data-dock=float][data-parked]):hover{opacity:1}}.et-query-devtools-panel:where([data-dock=float][data-parked]) .et-query-devtools-move{cursor:grab;background:var(--_et-qdt-active)}.et-query-devtools-panel:where([data-dock=float][data-parked]) .et-query-devtools-move:after{color:var(--_et-qdt-accent)}.et-query-devtools-host:where([data-dock=left]){inset-block:0;inset-inline-end:auto}.et-query-devtools-panel:where([data-dock=left]){inline-size:auto;block-size:100%;max-block-size:none;max-inline-size:90vw;border-block-start:0;border-inline-end:1px solid var(--_et-qdt-border);box-shadow:.4rem 0 2rem #00000059}.et-query-devtools-host:where([data-dock=top]){inset-block-start:0;inset-block-end:auto}.et-query-devtools-panel:where([data-dock=top]){border-block-start:0;border-block-end:1px solid var(--_et-qdt-border);box-shadow:0 .4rem 2rem #00000059}.et-query-devtools-host:where([data-dock=popout]){position:static;inline-size:100%;block-size:100%}.et-query-devtools-panel:where([data-dock=popout]){block-size:100%;max-block-size:none;border:0;box-shadow:none}.et-query-devtools-panel:where([data-pane-axis=block]) .et-query-devtools-header{flex-wrap:wrap}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-tabs,.et-query-devtools-header-actions){flex:1 1 auto;flex-wrap:nowrap;min-inline-size:0;overflow-x:auto}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-tabs,.et-query-devtools-header-actions)>*{flex:none}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-master-detail,.et-query-devtools-split){flex-direction:column}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-list,.et-query-devtools-detail,.et-query-devtools-drawer,.et-query-devtools-split .et-query-devtools-scroll){flex:1 1 50%;min-inline-size:0;min-block-size:0;max-inline-size:none;border-inline-start:0;border-inline-end:0}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-detail,.et-query-devtools-drawer){border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-panel:where([data-pane-axis=block]) .et-query-devtools-master-detail>.et-query-devtools-list{flex:0 1 var(--_et-qdt-list-height, 50%);min-block-size:var(--_et-qdt-list-height, 0);max-block-size:var(--_et-qdt-list-height, none)}.et-query-devtools-panel:where([data-pane-axis=block]) .et-query-devtools-split .et-query-devtools-drawer{flex:0 1 var(--_et-qdt-drawer-height, 50%);min-block-size:var(--_et-qdt-drawer-height, 0);max-block-size:var(--_et-qdt-drawer-height, none)}.et-query-devtools-panel:where([data-pane-axis=block]) .et-query-devtools-pane-resize{cursor:ns-resize}.et-query-devtools-panel--resizing{-webkit-user-select:none;user-select:none}.et-query-devtools-resize,.et-query-devtools-pane-resize{touch-action:none}.et-query-devtools-resize{position:absolute;inset-inline:0;inset-block-start:0;block-size:.6rem;cursor:ns-resize;z-index:1}.et-query-devtools-panel:where([data-dock=right]) .et-query-devtools-resize{inset-inline:0 auto;inset-block:0;inline-size:.6rem;block-size:auto;cursor:ew-resize}.et-query-devtools-panel:where([data-dock=left]) .et-query-devtools-resize{inset-inline:auto 0;inset-block:0;inline-size:.6rem;block-size:auto;cursor:ew-resize}.et-query-devtools-panel:where([data-dock=top]) .et-query-devtools-resize{inset-block:auto 0}.et-query-devtools-resize:after{content:\"\";position:absolute;inset-inline:50%;inset-block-start:.2rem;inline-size:4rem;margin-inline-start:-2rem;block-size:.2rem;border-radius:999px;background:var(--_et-qdt-border)}.et-query-devtools-panel:where([data-dock=right]) .et-query-devtools-resize:after{inset-inline:.2rem auto;inset-block:50% auto;inline-size:.2rem;block-size:4rem;margin-inline-start:0;margin-block-start:-2rem}.et-query-devtools-panel:where([data-dock=left]) .et-query-devtools-resize:after{inset-inline:auto .2rem;inset-block:50% auto;inline-size:.2rem;block-size:4rem;margin-inline-start:0;margin-block-start:-2rem}.et-query-devtools-panel:where([data-dock=top]) .et-query-devtools-resize:after{inset-block:auto .2rem}@media(hover:hover){.et-query-devtools-resize:hover:after{background:var(--_et-qdt-accent)}}.et-query-devtools-header{display:flex;align-items:center;justify-content:space-between;gap:.8rem;padding-inline:.8rem;border-block-end:1px solid var(--_et-qdt-border);flex:0 0 auto}.et-query-devtools-armed-bar{display:flex;align-items:center;gap:.8rem;flex:0 0 auto;padding:.6rem .8rem;background:color-mix(in srgb,var(--_et-qdt-error) 14%,transparent);border-block-end:1px solid color-mix(in srgb,var(--_et-qdt-error) 45%,transparent);color:var(--_et-qdt-error);font-size:1.1rem}.et-query-devtools-armed-text{flex:1 1 auto;min-inline-size:0}.et-query-devtools-notice-bar{display:flex;align-items:center;gap:.8rem;flex:0 0 auto;padding:.6rem .8rem;background:var(--_et-qdt-hover);border-block-end:1px solid var(--_et-qdt-border);color:var(--_et-qdt-fg);font-size:1.1rem}.et-query-devtools-notice-bar .et-query-devtools-btn{flex:0 0 auto}.et-query-devtools-armed-orphans{display:block;opacity:.85}.et-query-devtools-armed-dot{flex:0 0 auto;inline-size:.8rem;block-size:.8rem;border-radius:50%;background:var(--_et-qdt-error);animation:et-query-devtools-armed-pulse 1.6s ease-in-out infinite}@media(prefers-reduced-motion:reduce){.et-query-devtools-armed-dot{animation:none}}@keyframes et-query-devtools-armed-pulse{50%{opacity:.25}}.et-query-devtools-armed-bar .et-query-devtools-btn{flex:0 0 auto;border-color:color-mix(in srgb,var(--_et-qdt-error) 55%,transparent);color:var(--_et-qdt-error)}.et-query-devtools-tabs{display:flex;flex-wrap:wrap;gap:.2rem}.et-query-devtools-tab{appearance:none;display:inline-flex;align-items:center;gap:.5rem;border:none;background:transparent;color:var(--_et-qdt-muted);padding:.9rem 1rem;cursor:pointer;border-block-end:2px solid transparent;font:inherit}@media(hover:hover){.et-query-devtools-tab:hover{color:var(--_et-qdt-fg)}}.et-query-devtools-tab--active{color:var(--_et-qdt-fg);border-block-end-color:var(--_et-qdt-accent)}.et-query-devtools-tab-badge{display:inline-block;padding-inline:.45rem;border-radius:999px;background:color-mix(in srgb,var(--_et-qdt-interaction) 12%,transparent);color:var(--_et-qdt-muted);font-size:1rem;font-variant-numeric:tabular-nums}.et-query-devtools-tab-badge--error{background:color-mix(in srgb,var(--_et-qdt-error) 25%,transparent);color:var(--_et-qdt-fg)}.et-query-devtools-menu{display:contents}.et-query-devtools-menu-caret{font-size:.9em;opacity:.7}.et-query-devtools-menu-glyph{display:inline-block;min-inline-size:1.2rem;text-align:center}.et-query-devtools-menu>[data-menu-open]{background:var(--_et-qdt-hover);color:var(--_et-qdt-fg)}.et-menu .et-query-devtools-menu-item-glyph{display:inline-block;inline-size:16px;text-align:center}.et-menu-item-content:has(.et-query-devtools-menu-item-note){display:flex;flex-direction:column;align-items:start;gap:2px;padding-block:8px}.et-query-devtools-menu-item-note{color:var(--et-surface-color-muted-solid, rgb(255 255 255 / .55));font-size:12px}.et-query-devtools-header-actions{display:flex;align-items:center;gap:.2rem;flex:0 0 auto}.et-query-devtools-header-divider{inline-size:1px;block-size:1.6rem;margin-inline:.4rem;background:var(--_et-qdt-border)}.et-query-devtools-inspect-btn{position:relative}.et-query-devtools-inspect-icon{inline-size:1.4rem;block-size:1.4rem;flex:0 0 auto;fill:none;stroke:currentColor;stroke-width:1.25;stroke-linecap:round}.et-query-devtools-inspect-icon-dot{fill:currentColor;stroke:none}.et-query-devtools-btn.et-query-devtools-inspect-btn--active,.et-query-devtools-btn.et-query-devtools-inspect-btn--active:hover{color:#09090b;background:var(--_et-qdt-accent);border-color:var(--_et-qdt-accent)}.et-query-devtools-inspect-btn--active:after{content:\"\";position:absolute;inset:-.3rem;border:1px solid var(--_et-qdt-accent);border-radius:calc(var(--_et-qdt-radius) + .3rem);pointer-events:none;animation:et-query-devtools-inspect-pulse 1.4s ease-in-out infinite}.et-query-devtools-inspect-btn--active .et-query-devtools-inspect-icon-ring{animation:et-query-devtools-inspect-pulse 1.4s ease-in-out infinite}@keyframes et-query-devtools-inspect-pulse{0%,to{opacity:.2}50%{opacity:1}}@media(prefers-reduced-motion:reduce){.et-query-devtools-inspect-btn--active:after,.et-query-devtools-inspect-btn--active .et-query-devtools-inspect-icon-ring{animation:none;opacity:1}}.et-query-devtools-close{appearance:none;border:none;background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:.6rem;font:inherit}.et-query-devtools-kbd{border-radius:.3rem;background:var(--_et-qdt-hover);box-shadow:inset 0 0 0 1px var(--_et-qdt-border);color:var(--_et-qdt-muted);padding:.15rem .4rem;font:inherit;font-size:1.1rem;line-height:1.2;white-space:nowrap}.et-query-devtools-kbd--inverse{background:#00000026;box-shadow:inset 0 0 0 1px #00000040;color:inherit}@media(hover:none)and (pointer:coarse){.et-query-devtools-kbd{display:none}}.et-query-devtools-inspect-box{position:fixed;z-index:2147483002;pointer-events:none;border:2px solid var(--_et-qdt-accent);background:color-mix(in srgb,var(--_et-qdt-accent) 15%,transparent);border-radius:.2rem}.et-query-devtools-inspect-tag{position:absolute;inset-block-start:100%;inset-inline-start:0;margin-block-start:.2rem;padding:.2rem .5rem;border-radius:.3rem;background:var(--_et-qdt-accent);color:#09090b;font-family:ui-monospace,monospace;font-size:1.1rem;font-weight:600;white-space:nowrap}@media(hover:hover){.et-query-devtools-close:hover{color:var(--_et-qdt-fg)}}.et-query-devtools-body{flex:1 1 auto;min-block-size:0;overflow:hidden}.et-query-devtools-detail{flex:1 1 auto;min-inline-size:0;overflow-y:auto;padding:1rem}.et-query-devtools-detail .et-query-devtools-meta,.et-query-devtools-drawer .et-query-devtools-meta{padding:.8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 3%,transparent)}.et-query-devtools-detail .et-query-devtools-section-row,.et-query-devtools-drawer .et-query-devtools-section-row{padding-block-start:.8rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-filter-chip{display:inline-flex;align-items:center;gap:.6rem;padding:.3rem .35rem .3rem .7rem;border-radius:999px;background:color-mix(in srgb,var(--_et-qdt-accent) 16%,transparent);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--_et-qdt-accent) 45%,transparent);color:var(--_et-qdt-accent);font-size:1.1rem;font-weight:600;letter-spacing:.02em}.et-query-devtools-filter-icon{font-size:1.3rem;line-height:1}.et-query-devtools-filter-count{border-radius:999px;background:var(--_et-qdt-accent);color:#09090b;min-inline-size:1.8rem;padding:0 .5rem;text-align:center}.et-query-devtools-filter-clear{appearance:none;display:inline-flex;align-items:center;gap:.5rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:.3rem .6rem;font:inherit;font-size:1.15rem;line-height:1.4}@media(hover:hover){.et-query-devtools-filter-clear:hover{border-color:color-mix(in srgb,var(--_et-qdt-accent) 60%,transparent);color:var(--_et-qdt-fg)}}.et-query-devtools-scroll{block-size:100%;overflow:auto;padding:1rem;display:flex;flex-direction:column;gap:1rem}.et-query-devtools-split{display:flex;block-size:100%;min-block-size:0}.et-query-devtools-split .et-query-devtools-scroll{flex:1 1 auto;min-inline-size:0}.et-query-devtools-pane{display:flex;flex-direction:column;flex:1 1 auto;block-size:100%;min-inline-size:0;min-block-size:0;overflow:hidden}.et-query-devtools-pane>.et-query-devtools-scroll{padding-block:0 1rem}.et-query-devtools-pane-resize{flex:0 0 .5rem;align-self:stretch;cursor:ew-resize;background:transparent;transition:background .12s ease}.et-query-devtools-pane-resize:active{background:var(--_et-qdt-accent)}@media(hover:hover){.et-query-devtools-pane-resize:hover{background:var(--_et-qdt-accent)}}.et-query-devtools-toolbar{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.6rem;padding:.8rem;position:sticky;inset-block-start:0;z-index:1;background:var(--_et-qdt-bg);border-block-end:1px solid var(--_et-qdt-border)}.et-query-devtools-scroll>.et-query-devtools-toolbar{inset-block-start:-1rem}.et-query-devtools-toolbar-row{display:flex;align-items:center;flex-wrap:wrap;flex:1 1 100%;gap:.5rem}.et-query-devtools-field{display:inline-flex;align-items:center;gap:.6rem;color:var(--_et-qdt-muted)}.et-query-devtools-select{flex:0 0 auto;min-block-size:var(--_et-qdt-control-h);background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,var(--_et-qdt-bg));color:var(--_et-qdt-fg);border:1px solid transparent;border-radius:var(--_et-qdt-radius);padding:.4rem .6rem;font:inherit;font-size:1.15rem}.et-query-devtools-select option{background-color:var(--_et-qdt-bg);color:var(--_et-qdt-fg)}.et-query-devtools-count{color:var(--_et-qdt-subtle)}.et-query-devtools-row{display:flex;align-items:stretch;inline-size:100%;min-block-size:var(--_et-qdt-row-h);border-block-end:1px solid var(--_et-qdt-border);color:var(--_et-qdt-fg)}@media(hover:hover){.et-query-devtools-row:hover{background:var(--_et-qdt-hover)}}.et-query-devtools-method{font-weight:700;font-size:1.1rem;color:var(--_et-qdt-muted);white-space:nowrap}.et-query-devtools-route{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1 1 auto;min-inline-size:0}.et-query-devtools-status{inline-size:.8rem;block-size:.8rem;border-radius:999px;flex:0 0 auto;background:var(--_et-qdt-subtle)}.et-query-devtools-status[data-status=success]{background:var(--_et-qdt-success)}.et-query-devtools-status[data-status=error]{background:var(--_et-qdt-error)}.et-query-devtools-status[data-status=loading],.et-query-devtools-status[data-status=running]{background:var(--_et-qdt-loading)}.et-query-devtools-btn{appearance:none;display:inline-flex;align-items:center;justify-content:center;gap:.5rem;flex:0 0 auto;min-block-size:var(--_et-qdt-control-h);border:1px solid transparent;border-radius:var(--_et-qdt-radius);background:transparent;color:var(--_et-qdt-muted);padding:.25rem .7rem;font:inherit;font-size:1.15rem;line-height:1.4;white-space:nowrap;cursor:pointer;transition:color .12s ease,background-color .12s ease,border-color .12s ease}@media(hover:hover){.et-query-devtools-btn:hover:not(:disabled){background:var(--_et-qdt-hover);color:var(--_et-qdt-fg)}}.et-query-devtools-btn:focus-visible{outline:2px solid var(--_et-qdt-accent);outline-offset:1px}.et-query-devtools-btn:disabled{color:var(--_et-qdt-subtle);cursor:default;background:transparent;opacity:.6}.et-query-devtools-btn--primary,.et-query-devtools-btn--primary:hover:not(:disabled){background:color-mix(in srgb,var(--_et-qdt-accent) 16%,transparent);border-color:color-mix(in srgb,var(--_et-qdt-accent) 40%,transparent);color:var(--_et-qdt-accent)}@media(hover:hover){.et-query-devtools-btn--primary:hover:not(:disabled){background:color-mix(in srgb,var(--_et-qdt-accent) 26%,transparent)}}@media(hover:hover){.et-query-devtools-btn--danger:hover:not(:disabled){background:color-mix(in srgb,var(--_et-qdt-error) 16%,transparent);color:var(--_et-qdt-error)}}.et-query-devtools-btn--tampered,.et-query-devtools-btn--tampered:hover:not(:disabled){background:color-mix(in srgb,var(--_et-qdt-error) 14%,transparent);border-color:color-mix(in srgb,var(--_et-qdt-error) 45%,transparent);color:var(--_et-qdt-error)}.et-query-devtools-icon-btn{inline-size:var(--_et-qdt-control-h);padding-inline:0;font-size:1.3rem}.et-query-devtools-btn-group{display:inline-flex;flex:0 0 auto;border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius);overflow:hidden}.et-query-devtools-btn-group>.et-query-devtools-btn{border-radius:0;border-block:0;border-inline-start:0;border-inline-end:1px solid var(--_et-qdt-border)}.et-query-devtools-btn-group>.et-query-devtools-btn:last-child{border-inline-end:0}.et-query-devtools-toolbar-btn{margin-inline-start:auto;flex:0 0 auto}.et-query-devtools-meta{display:grid;grid-template-columns:auto 1fr;gap:.3rem 1rem;margin:0 0 1rem}.et-query-devtools-meta dt{color:var(--_et-qdt-muted)}.et-query-devtools-meta dd{margin:0;overflow-wrap:anywhere}.et-query-devtools-section{margin:1rem 0 .4rem;font-size:1.1rem;text-transform:uppercase;letter-spacing:.05em;color:var(--_et-qdt-muted)}.et-query-devtools-stat{padding:.6rem .8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 3%,transparent)}.et-query-devtools-stat dt{font-size:1rem;text-transform:uppercase;letter-spacing:.05em;color:var(--_et-qdt-muted)}.et-query-devtools-stat dd{margin:0;font-size:1.5rem;font-variant-numeric:tabular-nums}.et-query-devtools-stat-sub{display:block;font-size:1rem;color:var(--_et-qdt-subtle)}.et-query-devtools-json{margin:0;padding:.8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);overflow-x:auto;white-space:pre-wrap;overflow-wrap:anywhere;max-block-size:24rem;overflow-y:auto}.et-query-devtools-json--error{border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-mono{font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.et-query-devtools-url{display:inline-block;max-inline-size:32rem;margin-inline-start:.6rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.et-query-devtools-chips,.et-query-devtools-chip-row{display:flex;flex-wrap:wrap;gap:.4rem;margin-block-end:.8rem}.et-query-devtools-chip{display:inline-flex;align-items:center;gap:.3rem;padding:.2rem .6rem;border-radius:999px;border:1px solid var(--_et-qdt-border);font-size:1.1rem;color:var(--_et-qdt-fg);background:color-mix(in srgb,var(--_et-qdt-interaction) 6%,transparent)}.et-query-devtools-chip--muted{color:var(--_et-qdt-muted)}.et-query-devtools-retry-glyph{font-size:.7em;line-height:1}.et-query-devtools-chip--loading{color:var(--_et-qdt-loading);border-color:color-mix(in srgb,var(--_et-qdt-loading) 50%,transparent)}.et-query-devtools-chip--error{color:var(--_et-qdt-error);border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-chip--success{color:var(--_et-qdt-success);border-color:color-mix(in srgb,var(--_et-qdt-success) 50%,transparent)}button.et-query-devtools-chip--button{appearance:none;cursor:pointer;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}@media(hover:hover){button.et-query-devtools-chip--button:hover{border-color:var(--_et-qdt-accent);color:var(--_et-qdt-accent)}}.et-query-devtools-facet{appearance:none;min-block-size:var(--_et-qdt-control-h);padding-inline:.8rem;background:transparent;cursor:pointer;color:var(--_et-qdt-muted);font:inherit;font-size:1.1rem;line-height:1.4}@media(hover:hover){.et-query-devtools-facet:hover:not(:disabled){color:var(--_et-qdt-fg);background:var(--_et-qdt-hover);border-color:color-mix(in srgb,var(--_et-qdt-accent) 60%,transparent)}}.et-query-devtools-facet:disabled{color:var(--_et-qdt-subtle);cursor:default}.et-query-devtools-facet--active{color:var(--_et-qdt-accent);border-color:color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent);background:color-mix(in srgb,var(--_et-qdt-accent) 14%,transparent)}.et-query-devtools-facet-count{font-variant-numeric:tabular-nums;color:inherit;opacity:.75}.et-query-devtools-card{border:1px solid var(--_et-qdt-border);border-radius:.6rem;padding:1rem}.et-query-devtools-card--armed{border-color:color-mix(in srgb,var(--_et-qdt-error) 55%,transparent);background:color-mix(in srgb,var(--_et-qdt-error) 6%,transparent)}.et-query-devtools-card-head{display:flex;align-items:center;gap:.6rem;margin-block-end:.8rem}.et-query-devtools-inner-queries{display:flex;flex-direction:column;gap:.3rem}.et-query-devtools-inner-row{display:flex;align-items:center;gap:.6rem;inline-size:100%;text-align:start;appearance:none;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:transparent;color:var(--_et-qdt-fg);padding:.4rem .6rem;cursor:pointer;font:inherit}@media(hover:hover){.et-query-devtools-inner-row:hover:not([disabled]){background:var(--_et-qdt-hover)}}.et-query-devtools-inner-row--active{background:var(--_et-qdt-active);border-color:var(--_et-qdt-accent)}.et-query-devtools-inner-row[disabled]{cursor:default;opacity:.6}.et-query-devtools-step-toggle{display:flex;align-items:center;gap:.6rem;inline-size:100%;text-align:start;appearance:none;border:none;background:transparent;color:var(--_et-qdt-fg);padding:.6rem;cursor:pointer;font:inherit}@media(hover:hover){.et-query-devtools-step-toggle:hover{background:var(--_et-qdt-hover)}}.et-query-devtools-step-index{flex:1 1 auto;min-inline-size:0}.et-query-devtools-step-caret{color:var(--_et-qdt-muted)}.et-query-devtools-table{inline-size:100%;border-collapse:collapse}.et-query-devtools-table-scroll{overflow-x:auto}.et-query-devtools-table-scroll>.et-query-devtools-table{min-inline-size:max-content}.et-query-devtools-table th,.et-query-devtools-table td{text-align:start;padding:.5rem .6rem;border-block-end:1px solid var(--_et-qdt-border);vertical-align:top}.et-query-devtools-table td .et-query-devtools-chip{padding-block:0;line-height:1.4}.et-query-devtools-table th{color:var(--_et-qdt-muted);font-weight:600;position:sticky;inset-block-start:0;background:var(--_et-qdt-bg)}.et-query-devtools-table tr[data-status=error] td{color:var(--_et-qdt-error)}.et-query-devtools-table .et-query-devtools-cell-origin{inline-size:12ch;max-inline-size:12ch;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;overflow-wrap:normal}.et-query-devtools-empty{color:var(--_et-qdt-subtle);padding:1.2rem;text-align:center}.et-query-devtools-cell-actions{display:inline-flex;gap:.4rem;white-space:nowrap}.et-query-devtools-section-row{display:flex;align-items:center;justify-content:space-between;gap:.8rem;margin-block-start:1rem}.et-query-devtools-search{flex:1 1 16rem;min-inline-size:0;min-block-size:var(--_et-qdt-control-h);background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,transparent);color:var(--_et-qdt-fg);border:1px solid transparent;border-radius:var(--_et-qdt-radius);padding:.4rem .8rem;font:inherit;font-size:1.15rem}.et-query-devtools-search:focus{outline:none;border-color:color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent);background:var(--_et-qdt-bg)}.et-query-devtools-row-link{display:inline-flex;align-items:center;gap:.6rem;appearance:none;border:0;background:none;padding:0;color:inherit;font:inherit;text-align:start;cursor:pointer}.et-query-devtools-row-link:focus-visible .et-query-devtools-url{color:var(--_et-qdt-accent);text-decoration:underline}@media(hover:hover){.et-query-devtools-row-link:hover .et-query-devtools-url{color:var(--_et-qdt-accent);text-decoration:underline}}.et-query-devtools-hint{flex:1 1 24rem;color:var(--_et-qdt-muted);font-size:1.1rem;margin:0}.et-query-devtools-input{inline-size:100%;background:var(--_et-qdt-bg);color:var(--_et-qdt-fg);border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.4rem .6rem;font:inherit}.et-query-devtools-input--number{inline-size:8rem;font-variant-numeric:tabular-nums}.et-query-devtools-editor{border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.8rem;margin-block-end:1rem}.et-query-devtools-editor-error{color:var(--_et-qdt-error);margin:.4rem 0 0}.et-query-devtools-btn--active{color:var(--_et-qdt-accent);border-color:color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent);background:color-mix(in srgb,var(--_et-qdt-accent) 12%,transparent)}}\n"], dependencies: [{ kind: "component", type: QueryDevtoolsAuthTabComponent, selector: "et-query-devtools-auth-tab" }, { kind: "component", type: QueryDevtoolsBatchesTabComponent, selector: "et-query-devtools-batches-tab" }, { kind: "component", type: QueryDevtoolsCacheTabComponent, selector: "et-query-devtools-cache-tab" }, { kind: "component", type: QueryDevtoolsEventsTabComponent, selector: "et-query-devtools-events-tab" }, { kind: "component", type: QueryDevtoolsAboutComponent, selector: "et-query-devtools-about" }, { kind: "component", type: QueryDevtoolsSettingsComponent, selector: "et-query-devtools-settings" }, { kind: "component", type: QueryDevtoolsFaultsTabComponent, selector: "et-query-devtools-faults-tab" }, { kind: "component", type: QueryDevtoolsFormsTabComponent, selector: "et-query-devtools-forms-tab" }, { kind: "component", type: QueryDevtoolsLocksTabComponent, selector: "et-query-devtools-locks-tab" }, { kind: "component", type: QueryDevtoolsMocksTabComponent, selector: "et-query-devtools-mocks-tab" }, { kind: "component", type: QueryDevtoolsQueriesTabComponent, selector: "et-query-devtools-queries-tab" }, { kind: "component", type: QueryDevtoolsSequencesTabComponent, selector: "et-query-devtools-sequences-tab" }, { kind: "component", type: QueryDevtoolsSocketsTabComponent, selector: "et-query-devtools-sockets-tab" }, { kind: "component", type: QueryDevtoolsStacksTabComponent, selector: "et-query-devtools-stacks-tab" }, { kind: "component", type: QueryDevtoolsTimelineTabComponent, selector: "et-query-devtools-timeline-tab" }, { kind: "component", type: QueryDevtoolsToggleComponent, selector: "et-query-devtools-toggle", inputs: ["tampered"], outputs: ["openChange"] }, { kind: "directive", type: DragHandleDirective, selector: "[etDragHandle]", inputs: ["commitThreshold", "disabled"], outputs: ["dragTapped", "dragStarted", "dragMoved", "dragEnded", "dragCancelled"] }, { kind: "component", type: MenuCheckboxItemComponent, selector: "et-menu-checkbox-item", inputs: ["icon"] }, { kind: "component", type: MenuComponent, selector: "et-menu" }, { kind: "directive", type: MenuDirective, selector: "[etMenu]", inputs: ["placement", "fallbackPlacements", "offset", "viewportPadding", "arrow", "arrowPadding", "autoFocus", "hoverOpen", "loop", "hoverOpenDelay", "hoverCloseDelay", "disabled", "open"], outputs: ["openChange"], exportAs: ["etMenu"] }, { kind: "component", type: MenuItemComponent, selector: "button[et-menu-item], a[et-menu-item]", inputs: ["variant"] }, { kind: "component", type: MenuRadioGroupComponent, selector: "et-menu-radio-group" }, { kind: "component", type: MenuRadioItemComponent, selector: "et-menu-radio-item", inputs: ["icon"] }, { kind: "component", type: MenuSeparatorComponent, selector: "et-menu-separator" }, { kind: "directive", type: MenuSurfaceDirective, selector: "ng-template[etMenuSurface]", exportAs: ["etMenuSurface"] }, { kind: "directive", type: MenuTriggerDirective, selector: "[etMenuTrigger]", exportAs: ["etMenuTrigger"] }, { kind: "component", type: ResizeHandlesComponent, selector: "et-resize-handles", inputs: ["edges", "disabled"], outputs: ["resizeStarted", "resizeMoved", "resizeEnded", "resizeCancelled"] }], encapsulation: i0.ViewEncapsulation.None }); }
6943
7185
  }
6944
7186
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsComponent, decorators: [{
6945
7187
  type: Component,
6946
7188
  args: [{ selector: 'et-query-devtools', encapsulation: ViewEncapsulation.None, imports: [
6947
7189
  QueryDevtoolsAuthTabComponent,
7190
+ QueryDevtoolsBatchesTabComponent,
6948
7191
  QueryDevtoolsCacheTabComponent,
6949
7192
  QueryDevtoolsEventsTabComponent,
6950
7193
  QueryDevtoolsAboutComponent,
@@ -6973,7 +7216,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
6973
7216
  ], providers: [{ provide: QUERY_DEVTOOLS_HOST, useExisting: QueryDevtoolsComponent }], host: {
6974
7217
  class: 'et-query-devtools-host',
6975
7218
  '[attr.data-dock]': 'dock()',
6976
- }, template: "@if (open()) {\n <section\n #panel\n [class.et-query-devtools-panel--resizing]=\"resizing()\"\n [attr.data-dock]=\"poppedOut() ? 'popout' : dock()\"\n [attr.data-pane-axis]=\"paneAxis()\"\n [attr.data-parked]=\"floatParked() ? '' : null\"\n [style.block-size.px]=\"panelBlockSize()\"\n [style.inline-size.px]=\"panelInlineSize()\"\n [style.inset-block-start.px]=\"panelInsetBlock()\"\n [style.inset-inline-start.px]=\"panelInsetInline()\"\n [style.--_et-qdt-list-width.px]=\"listWidth()\"\n [style.--_et-qdt-drawer-width.px]=\"drawerWidth()\"\n [style.--_et-qdt-list-height.px]=\"listHeight()\"\n [style.--_et-qdt-drawer-height.px]=\"drawerHeight()\"\n class=\"et-query-devtools-panel\"\n >\n <!-- A pop-out is sized by its window, so there is no edge to drag. A float is a window in the page:\n a title bar to move it and every edge to resize it, on the same `@ethlete/core` primitives the\n stream pip window uses. A dock has one edge, and it does both. -->\n @if (floating()) {\n <!-- Parked, the panel is a grab strip: the edge handles would eat most of the peek that is left\n to click, and resizing something you cannot see is not a thing to offer. -->\n <et-resize-handles\n [edges]=\"FLOAT_RESIZE_EDGES\"\n [disabled]=\"floatParked()\"\n (resizeStarted)=\"startFloatResize()\"\n (resizeMoved)=\"resizeFloat($event)\"\n (resizeEnded)=\"endFloatResize()\"\n (resizeCancelled)=\"endFloatResize()\"\n />\n <div\n (dragMoved)=\"moveFloat($event)\"\n (dragEnded)=\"endFloatMove()\"\n (dragCancelled)=\"endFloatMove()\"\n (dragTapped)=\"restoreFloat()\"\n class=\"et-query-devtools-move\"\n title=\"Drag to move \u2014 shove it off an edge to park it, click the strip to bring it back\"\n aria-hidden=\"true\"\n etDragHandle\n ></div>\n } @else if (!poppedOut()) {\n <div\n (pointerdown)=\"startResize($event)\"\n class=\"et-query-devtools-resize\"\n title=\"Drag to resize\"\n aria-hidden=\"true\"\n ></div>\n }\n <header class=\"et-query-devtools-header\">\n <nav class=\"et-query-devtools-tabs\" role=\"tablist\">\n @for (tab of visibleTabs(); track tab.id) {\n @let badge = tabBadges()[tab.id];\n <button\n [class.et-query-devtools-tab--active]=\"activeTab() === tab.id\"\n [attr.aria-selected]=\"activeTab() === tab.id\"\n (click)=\"activeTab.set(tab.id)\"\n class=\"et-query-devtools-tab\"\n type=\"button\"\n role=\"tab\"\n >\n {{ tab.label }}\n @if (badge.count) {\n <span class=\"et-query-devtools-tab-badge\">{{ badge.count }}</span>\n }\n @if (badge.errors) {\n <span\n class=\"et-query-devtools-tab-badge et-query-devtools-tab-badge--error\"\n title=\"{{ badge.errors }} {{ badge.errorNoun ?? 'failing' }}\"\n >\n {{ badge.errors }}\n </span>\n }\n </button>\n }\n\n @if (overflowTabs().length; as overflowCount) {\n <div class=\"et-query-devtools-menu\" etMenu>\n <button\n [title]=\"overflowCount + ' tabs with nothing in them'\"\n class=\"et-query-devtools-tab\"\n type=\"button\"\n etMenuTrigger\n >\n More\n <span class=\"et-query-devtools-menu-caret\" aria-hidden=\"true\">\u25BE</span>\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n @for (tab of overflowTabs(); track tab.id) {\n <button (activate)=\"activeTab.set(tab.id)\" et-menu-item type=\"button\">{{ tab.label }}</button>\n }\n </et-menu>\n </ng-template>\n </div>\n }\n </nav>\n\n <div class=\"et-query-devtools-header-actions\">\n <button\n [class.et-query-devtools-inspect-btn--active]=\"inspectActive()\"\n [attr.aria-pressed]=\"inspectActive()\"\n (click)=\"toggleInspect()\"\n class=\"et-query-devtools-btn et-query-devtools-inspect-btn\"\n type=\"button\"\n title=\"Inspect: hover the UI to find its query\"\n >\n <svg\n class=\"et-query-devtools-inspect-icon\"\n viewBox=\"0 0 16 16\"\n width=\"14\"\n height=\"14\"\n fill=\"none\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <circle class=\"et-query-devtools-inspect-icon-ring\" cx=\"8\" cy=\"8\" r=\"4.25\" />\n <circle class=\"et-query-devtools-inspect-icon-dot\" cx=\"8\" cy=\"8\" r=\"1.15\" />\n <path d=\"M8 0.9v2.1M8 13v2.1M0.9 8h2.1M13 8h2.1\" />\n </svg>\n {{ inspectActive() ? 'Inspecting' : 'Inspect' }}\n @if (inspectActive()) {\n <kbd class=\"et-query-devtools-kbd et-query-devtools-kbd--inverse\">Esc</kbd>\n }\n </button>\n <button\n (click)=\"downloadSession()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Download every registered entry, the event log, the cache totals and anything armed as one JSON file - the attachment for a bug report\"\n >\n <span aria-hidden=\"true\">\u2913</span>\n Session\n </button>\n <!-- A gear, not a tab: Settings holds nothing to count, so the badge/overflow logic would push it\n behind More - and the one control that explains a panel behaving oddly may not be hidden. -->\n <button\n [class.et-query-devtools-btn--active]=\"activeTab() === 'settings'\"\n [attr.aria-pressed]=\"activeTab() === 'settings'\"\n (click)=\"toggleSettings()\"\n class=\"et-query-devtools-btn et-query-devtools-icon-btn\"\n type=\"button\"\n title=\"Settings: where panel state is kept, and the limits it holds\"\n aria-label=\"Settings\"\n >\n <span aria-hidden=\"true\">\u2699</span>\n </button>\n @if (!poppedOut()) {\n @let layout = currentLayout();\n <div class=\"et-query-devtools-menu\" etMenu placement=\"bottom-end\">\n <button class=\"et-query-devtools-btn\" title=\"Where the panel sits\" type=\"button\" etMenuTrigger>\n <span class=\"et-query-devtools-menu-glyph\" aria-hidden=\"true\">{{ layout.glyph }}</span>\n {{ layout.label }}\n <span class=\"et-query-devtools-menu-caret\" aria-hidden=\"true\">\u25BE</span>\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <et-menu-radio-group [value]=\"dock()\">\n @for (option of LAYOUTS; track option.id) {\n <!-- eslint-disable ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <et-menu-radio-item\n [value]=\"option.id\"\n [title]=\"option.hint\"\n [closeOnActivate]=\"true\"\n (activate)=\"selectLayout(option.id)\"\n >\n <!-- eslint-enable ethlete/prefer-static-boolean-properties -->\n <span class=\"et-query-devtools-menu-item-glyph\" aria-hidden=\"true\">{{ option.glyph }}</span>\n {{ option.label }}\n </et-menu-radio-item>\n }\n </et-menu-radio-group>\n <et-menu-separator />\n <et-menu-checkbox-item\n [checked]=\"reservesSpace()\"\n [disabled]=\"floating()\"\n (checkedChange)=\"reservesSpace.set($event)\"\n title=\"The page gives up the room the panel covers, so its last rows can still be scrolled to. Docks only - a float sits wherever it was dragged.\"\n >\n Reserve page space\n </et-menu-checkbox-item>\n </et-menu>\n </ng-template>\n </div>\n }\n <span class=\"et-query-devtools-header-divider\" aria-hidden=\"true\"></span>\n <button\n (click)=\"toggleOpen()\"\n class=\"et-query-devtools-btn et-query-devtools-btn--danger\"\n type=\"button\"\n title=\"{{ poppedOut() ? 'Bring the panel back into the app window' : 'Close devtools (' + shortcut + ')' }}\"\n >\n @if (poppedOut()) {\n <span aria-hidden=\"true\">\u29C8</span>\n Dock back\n } @else {\n <span aria-hidden=\"true\">\u2715</span>\n Close\n <kbd class=\"et-query-devtools-kbd\">{{ shortcut }}</kbd>\n }\n </button>\n </div>\n </header>\n\n @if (popOutBlocked()) {\n <div class=\"et-query-devtools-notice-bar\" role=\"status\">\n <span class=\"et-query-devtools-armed-text\">\n The browser blocked the pop-up window. Allow pop-ups for this site, or float the panel over the page instead \u2014\n a float needs no permission.\n </span>\n <button (click)=\"floatPanel()\" class=\"et-query-devtools-btn\" type=\"button\">Float instead</button>\n <button (click)=\"popOutBlocked.set(false)\" class=\"et-query-devtools-btn\" type=\"button\">Dismiss</button>\n </div>\n }\n\n @if (armedFaultClients(); as armed) {\n <div class=\"et-query-devtools-armed-bar\" role=\"status\">\n <span class=\"et-query-devtools-armed-dot\" aria-hidden=\"true\"></span>\n <span class=\"et-query-devtools-armed-text\">\n <strong>Faults armed</strong> on\n <span class=\"et-query-devtools-mono\">{{ armed.join(', ') }}</span>\n \u2014 every failure and delay you see from\n {{ armed.length === 1 ? 'this client' : 'these clients' }} is injected, not the API.\n @if (faultsRestored()) {\n They came back from your last page load \u2014 set <em>Armed faults</em> back to <em>none</em> in Settings to\n stop that.\n }\n </span>\n <button\n [disabled]=\"activeTab() === 'faults'\"\n (click)=\"activeTab.set('faults')\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Review\n </button>\n <button (click)=\"CLEAR_FAULTS()\" class=\"et-query-devtools-btn\" type=\"button\">Disarm all</button>\n </div>\n }\n\n @if (armedMockRoutes(); as mocked) {\n <div class=\"et-query-devtools-armed-bar\" role=\"status\">\n <span class=\"et-query-devtools-armed-dot\" aria-hidden=\"true\"></span>\n <span class=\"et-query-devtools-armed-text\">\n <strong>Mocks armed</strong> on\n <span class=\"et-query-devtools-mono\">{{ mocked.join(', ') }}</span>\n \u2014 {{ mocked.length === 1 ? 'that route is' : 'those routes are' }} answered by the panel, so nothing they show\n reached the API.\n @if (mocksRestored()) {\n They came back from your last page load \u2014 set <em>Armed mocks</em> back to <em>none</em> in Settings to stop\n that.\n }\n </span>\n <button\n [disabled]=\"activeTab() === 'mocks'\"\n (click)=\"activeTab.set('mocks')\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Review\n </button>\n <button (click)=\"DISARM_MOCKS()\" class=\"et-query-devtools-btn\" type=\"button\">Disarm all</button>\n </div>\n }\n\n @if (restoredOverrides(); as restored) {\n <div class=\"et-query-devtools-armed-bar\" role=\"status\">\n <span class=\"et-query-devtools-armed-dot\" aria-hidden=\"true\"></span>\n <span class=\"et-query-devtools-armed-text\">\n @if (restored.ops) {\n <strong>Overrides re-armed</strong> \u2014 {{ restored.ops }} {{ restored.ops === 1 ? 'edit' : 'edits' }} from\n your last page load {{ restored.ops === 1 ? 'is' : 'are' }} back on {{ restored.queries }}\n {{ restored.queries === 1 ? 'query' : 'queries' }}, so those responses are not what the API sent.\n @if (restored.fromLocalStorage) {\n They came back from <strong>localStorage</strong>, so they outlive closing this tab \u2014 clear them, or set\n overrides back to <em>session</em> in Settings.\n }\n } @else {\n <strong>Overrides could not be re-armed.</strong>\n }\n @if (restored.orphaned.length) {\n <span class=\"et-query-devtools-armed-orphans\">\n {{ restored.orphaned.length }} stored\n {{ restored.orphaned.length === 1 ? 'override' : 'overrides' }} matched no query \u2014\n <span class=\"et-query-devtools-mono\">{{ restored.orphaned.join(', ') }}</span>\n </span>\n }\n </span>\n @if (restored.firstId; as firstId) {\n <button (click)=\"reviewRestoredOverrides(firstId)\" class=\"et-query-devtools-btn\" type=\"button\">Review</button>\n }\n <button (click)=\"DROP_RESTORED_OVERRIDES()\" class=\"et-query-devtools-btn\" type=\"button\">Drop all</button>\n </div>\n }\n\n <div class=\"et-query-devtools-body\">\n @switch (activeTab()) {\n <!-- QUERIES -->\n @case ('queries') {\n <et-query-devtools-queries-tab />\n }\n\n <!-- STACKS -->\n @case ('stacks') {\n <et-query-devtools-stacks-tab />\n }\n\n <!-- SEQUENCES -->\n @case ('sequences') {\n <et-query-devtools-sequences-tab />\n }\n\n <!-- FORMS -->\n @case ('forms') {\n <et-query-devtools-forms-tab />\n }\n\n <!-- AUTH -->\n @case ('auth') {\n <et-query-devtools-auth-tab />\n }\n\n <!-- SOCKETS -->\n @case ('ws') {\n <et-query-devtools-sockets-tab />\n }\n\n <!-- CACHE -->\n @case ('cache') {\n <et-query-devtools-cache-tab />\n }\n\n <!-- LOCKS -->\n @case ('locks') {\n <et-query-devtools-locks-tab />\n }\n\n <!-- TIMELINE -->\n @case ('timeline') {\n <et-query-devtools-timeline-tab />\n }\n\n <!-- EVENTS -->\n @case ('events') {\n <et-query-devtools-events-tab />\n }\n\n <!-- FAULTS -->\n @case ('faults') {\n <et-query-devtools-faults-tab />\n }\n\n <!-- MOCKS -->\n @case ('mocks') {\n <et-query-devtools-mocks-tab />\n }\n\n <!-- ABOUT -->\n @case ('about') {\n <et-query-devtools-about />\n }\n\n <!-- SETTINGS -->\n @case ('settings') {\n <et-query-devtools-settings />\n }\n }\n </div>\n </section>\n}\n\n@if (!open()) {\n <et-query-devtools-toggle [tampered]=\"panelTampered()\" (openChange)=\"toggleOpen()\" />\n}\n\n@if (inspectActive() && inspectHover(); as hover) {\n <div\n [style.left.px]=\"hover.rect.left\"\n [style.top.px]=\"hover.rect.top\"\n [style.width.px]=\"hover.rect.width\"\n [style.height.px]=\"hover.rect.height\"\n class=\"et-query-devtools-inspect-box\"\n >\n <span class=\"et-query-devtools-inspect-tag\">{{ inspectLabel(hover.entries) }}</span>\n </div>\n}\n\n@if (locatedRect(); as rect) {\n <div\n [style.left.px]=\"rect.left\"\n [style.top.px]=\"rect.top\"\n [style.width.px]=\"rect.width\"\n [style.height.px]=\"rect.height\"\n class=\"et-query-devtools-inspect-box\"\n >\n <span class=\"et-query-devtools-inspect-tag\">created here</span>\n </div>\n}\n", styles: ["@layer components{.et-query-devtools-host{--_et-qdt-bg: var(--et-surface-background-solid, rgb(24 24 27));--_et-qdt-fg: var(--et-surface-color-solid, rgb(244 244 245));--_et-qdt-muted: var(--et-surface-color-muted-solid, rgb(161 161 170));--_et-qdt-subtle: var(--et-surface-color-subtle-solid, rgb(113 113 122));--_et-qdt-border: var(--et-surface-border-solid, rgb(255 255 255 / .12));--_et-qdt-interaction: var(--et-surface-interaction-solid, rgb(255 255 255));--_et-qdt-accent: var(--et-theme-color-primary-solid, rgb(96 165 250));--_et-qdt-hover: color-mix(in srgb, var(--_et-qdt-interaction) 10%, transparent);--_et-qdt-active: color-mix(in srgb, var(--_et-qdt-interaction) 18%, transparent);--_et-qdt-success: rgb(34 197 94);--_et-qdt-error: rgb(239 68 68);--_et-qdt-loading: rgb(234 179 8);--_et-qdt-title-bar: 2.4rem;--_et-qdt-radius: .5rem;--_et-qdt-control-h: 2.4rem;--_et-qdt-row-h: 3.6rem;position:fixed;inset-inline:0;inset-block-end:0;z-index:2147483000;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,Liberation Mono,monospace;font-size:1.2rem;line-height:1.4;pointer-events:none}.et-query-devtools-host>*{pointer-events:auto}@media(pointer:coarse){.et-query-devtools-host{--_et-qdt-control-h: 3.4rem;--_et-qdt-row-h: 4.4rem}}.et-query-devtools-panel{position:relative;display:flex;flex-direction:column;inline-size:100%;min-block-size:20rem;max-block-size:90vh;background:var(--_et-qdt-bg);color:var(--_et-qdt-fg);border-block-start:1px solid var(--_et-qdt-border);box-shadow:0 -.4rem 2rem #00000059}.et-query-devtools-host:where([data-dock=right]){inset-block:0;inset-inline-start:auto}.et-query-devtools-panel:where([data-dock=right]){inline-size:auto;block-size:100%;max-block-size:none;max-inline-size:90vw;border-block-start:0;border-inline-start:1px solid var(--_et-qdt-border);box-shadow:-.4rem 0 2rem #00000059}.et-query-devtools-host:where([data-dock=float]){inset:0}.et-query-devtools-panel:where([data-dock=float]){--_et-qdt-frame: .8rem;--et-resize-handles-edge-size: var(--_et-qdt-frame);--et-resize-handles-touch-edge-size: var(--_et-qdt-frame);--et-resize-handles-corner-size: 1.6rem;--et-resize-handles-touch-corner-size: 1.6rem;--et-resize-handles-edge-inset: 1.6rem;--et-resize-handles-side-top: var(--_et-qdt-title-bar);--et-resize-handles-side-bottom: 1.6rem;--et-resize-handles-z-index: 3;position:absolute;inline-size:auto;min-block-size:0;max-block-size:none;padding-block-start:var(--_et-qdt-title-bar);padding-inline:var(--_et-qdt-frame);padding-block-end:var(--_et-qdt-frame);border:1px solid var(--_et-qdt-border);border-radius:.6rem;box-shadow:0 1rem 4rem #00000073;overflow:hidden}.et-query-devtools-move{position:absolute;inset-inline:0;inset-block-start:0;block-size:var(--_et-qdt-title-bar);cursor:move;z-index:2}.et-query-devtools-panel:where([data-dock=float]) .et-query-devtools-body{border-radius:.3rem;overflow:hidden}.et-query-devtools-move:after{content:\"\";position:absolute;inset-inline:50%;inset-block-start:.9rem;inline-size:3rem;margin-inline-start:-1.5rem;block-size:.6rem;background-image:radial-gradient(currentcolor 40%,transparent 42%);background-size:.4rem .3rem;color:var(--_et-qdt-subtle)}@media(hover:hover){.et-query-devtools-move:hover:after{color:var(--_et-qdt-accent)}}.et-query-devtools-panel:where([data-dock=float][data-parked]){opacity:.85}@media(hover:hover){.et-query-devtools-panel:where([data-dock=float][data-parked]):hover{opacity:1}}.et-query-devtools-panel:where([data-dock=float][data-parked]) .et-query-devtools-move{cursor:grab;background:var(--_et-qdt-active)}.et-query-devtools-panel:where([data-dock=float][data-parked]) .et-query-devtools-move:after{color:var(--_et-qdt-accent)}.et-query-devtools-host:where([data-dock=left]){inset-block:0;inset-inline-end:auto}.et-query-devtools-panel:where([data-dock=left]){inline-size:auto;block-size:100%;max-block-size:none;max-inline-size:90vw;border-block-start:0;border-inline-end:1px solid var(--_et-qdt-border);box-shadow:.4rem 0 2rem #00000059}.et-query-devtools-host:where([data-dock=top]){inset-block-start:0;inset-block-end:auto}.et-query-devtools-panel:where([data-dock=top]){border-block-start:0;border-block-end:1px solid var(--_et-qdt-border);box-shadow:0 .4rem 2rem #00000059}.et-query-devtools-host:where([data-dock=popout]){position:static;inline-size:100%;block-size:100%}.et-query-devtools-panel:where([data-dock=popout]){block-size:100%;max-block-size:none;border:0;box-shadow:none}.et-query-devtools-panel:where([data-pane-axis=block]) .et-query-devtools-header{flex-wrap:wrap}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-tabs,.et-query-devtools-header-actions){flex:1 1 auto;flex-wrap:nowrap;min-inline-size:0;overflow-x:auto}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-tabs,.et-query-devtools-header-actions)>*{flex:none}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-master-detail,.et-query-devtools-split){flex-direction:column}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-list,.et-query-devtools-detail,.et-query-devtools-drawer,.et-query-devtools-split .et-query-devtools-scroll){flex:1 1 50%;min-inline-size:0;min-block-size:0;max-inline-size:none;border-inline-start:0;border-inline-end:0}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-detail,.et-query-devtools-drawer){border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-panel:where([data-pane-axis=block]) .et-query-devtools-master-detail>.et-query-devtools-list{flex:0 1 var(--_et-qdt-list-height, 50%);min-block-size:var(--_et-qdt-list-height, 0);max-block-size:var(--_et-qdt-list-height, none)}.et-query-devtools-panel:where([data-pane-axis=block]) .et-query-devtools-split>.et-query-devtools-drawer{flex:0 1 var(--_et-qdt-drawer-height, 50%);min-block-size:var(--_et-qdt-drawer-height, 0);max-block-size:var(--_et-qdt-drawer-height, none)}.et-query-devtools-panel:where([data-pane-axis=block]) .et-query-devtools-pane-resize{cursor:ns-resize}.et-query-devtools-panel--resizing{-webkit-user-select:none;user-select:none}.et-query-devtools-resize,.et-query-devtools-pane-resize{touch-action:none}.et-query-devtools-resize{position:absolute;inset-inline:0;inset-block-start:0;block-size:.6rem;cursor:ns-resize;z-index:1}.et-query-devtools-panel:where([data-dock=right]) .et-query-devtools-resize{inset-inline:0 auto;inset-block:0;inline-size:.6rem;block-size:auto;cursor:ew-resize}.et-query-devtools-panel:where([data-dock=left]) .et-query-devtools-resize{inset-inline:auto 0;inset-block:0;inline-size:.6rem;block-size:auto;cursor:ew-resize}.et-query-devtools-panel:where([data-dock=top]) .et-query-devtools-resize{inset-block:auto 0}.et-query-devtools-resize:after{content:\"\";position:absolute;inset-inline:50%;inset-block-start:.2rem;inline-size:4rem;margin-inline-start:-2rem;block-size:.2rem;border-radius:999px;background:var(--_et-qdt-border)}.et-query-devtools-panel:where([data-dock=right]) .et-query-devtools-resize:after{inset-inline:.2rem auto;inset-block:50% auto;inline-size:.2rem;block-size:4rem;margin-inline-start:0;margin-block-start:-2rem}.et-query-devtools-panel:where([data-dock=left]) .et-query-devtools-resize:after{inset-inline:auto .2rem;inset-block:50% auto;inline-size:.2rem;block-size:4rem;margin-inline-start:0;margin-block-start:-2rem}.et-query-devtools-panel:where([data-dock=top]) .et-query-devtools-resize:after{inset-block:auto .2rem}@media(hover:hover){.et-query-devtools-resize:hover:after{background:var(--_et-qdt-accent)}}.et-query-devtools-header{display:flex;align-items:center;justify-content:space-between;gap:.8rem;padding-inline:.8rem;border-block-end:1px solid var(--_et-qdt-border);flex:0 0 auto}.et-query-devtools-armed-bar{display:flex;align-items:center;gap:.8rem;flex:0 0 auto;padding:.6rem .8rem;background:color-mix(in srgb,var(--_et-qdt-error) 14%,transparent);border-block-end:1px solid color-mix(in srgb,var(--_et-qdt-error) 45%,transparent);color:var(--_et-qdt-error);font-size:1.1rem}.et-query-devtools-armed-text{flex:1 1 auto;min-inline-size:0}.et-query-devtools-notice-bar{display:flex;align-items:center;gap:.8rem;flex:0 0 auto;padding:.6rem .8rem;background:var(--_et-qdt-hover);border-block-end:1px solid var(--_et-qdt-border);color:var(--_et-qdt-fg);font-size:1.1rem}.et-query-devtools-notice-bar .et-query-devtools-btn{flex:0 0 auto}.et-query-devtools-armed-orphans{display:block;opacity:.85}.et-query-devtools-armed-dot{flex:0 0 auto;inline-size:.8rem;block-size:.8rem;border-radius:50%;background:var(--_et-qdt-error);animation:et-query-devtools-armed-pulse 1.6s ease-in-out infinite}@media(prefers-reduced-motion:reduce){.et-query-devtools-armed-dot{animation:none}}@keyframes et-query-devtools-armed-pulse{50%{opacity:.25}}.et-query-devtools-armed-bar .et-query-devtools-btn{flex:0 0 auto;border-color:color-mix(in srgb,var(--_et-qdt-error) 55%,transparent);color:var(--_et-qdt-error)}.et-query-devtools-tabs{display:flex;flex-wrap:wrap;gap:.2rem}.et-query-devtools-tab{appearance:none;display:inline-flex;align-items:center;gap:.5rem;border:none;background:transparent;color:var(--_et-qdt-muted);padding:.9rem 1rem;cursor:pointer;border-block-end:2px solid transparent;font:inherit}@media(hover:hover){.et-query-devtools-tab:hover{color:var(--_et-qdt-fg)}}.et-query-devtools-tab--active{color:var(--_et-qdt-fg);border-block-end-color:var(--_et-qdt-accent)}.et-query-devtools-tab-badge{display:inline-block;padding-inline:.45rem;border-radius:999px;background:color-mix(in srgb,var(--_et-qdt-interaction) 12%,transparent);color:var(--_et-qdt-muted);font-size:1rem;font-variant-numeric:tabular-nums}.et-query-devtools-tab-badge--error{background:color-mix(in srgb,var(--_et-qdt-error) 25%,transparent);color:var(--_et-qdt-fg)}.et-query-devtools-menu{display:contents}.et-query-devtools-menu-caret{font-size:.9em;opacity:.7}.et-query-devtools-menu-glyph{display:inline-block;min-inline-size:1.2rem;text-align:center}.et-query-devtools-menu>[data-menu-open]{background:var(--_et-qdt-hover);color:var(--_et-qdt-fg)}.et-menu .et-query-devtools-menu-item-glyph{display:inline-block;inline-size:16px;text-align:center}.et-menu-item-content:has(.et-query-devtools-menu-item-note){display:flex;flex-direction:column;align-items:start;gap:2px;padding-block:8px}.et-query-devtools-menu-item-note{color:var(--et-surface-color-muted-solid, rgb(255 255 255 / .55));font-size:12px}.et-query-devtools-header-actions{display:flex;align-items:center;gap:.2rem;flex:0 0 auto}.et-query-devtools-header-divider{inline-size:1px;block-size:1.6rem;margin-inline:.4rem;background:var(--_et-qdt-border)}.et-query-devtools-inspect-btn{position:relative}.et-query-devtools-inspect-icon{inline-size:1.4rem;block-size:1.4rem;flex:0 0 auto;fill:none;stroke:currentColor;stroke-width:1.25;stroke-linecap:round}.et-query-devtools-inspect-icon-dot{fill:currentColor;stroke:none}.et-query-devtools-btn.et-query-devtools-inspect-btn--active,.et-query-devtools-btn.et-query-devtools-inspect-btn--active:hover{color:#09090b;background:var(--_et-qdt-accent);border-color:var(--_et-qdt-accent)}.et-query-devtools-inspect-btn--active:after{content:\"\";position:absolute;inset:-.3rem;border:1px solid var(--_et-qdt-accent);border-radius:calc(var(--_et-qdt-radius) + .3rem);pointer-events:none;animation:et-query-devtools-inspect-pulse 1.4s ease-in-out infinite}.et-query-devtools-inspect-btn--active .et-query-devtools-inspect-icon-ring{animation:et-query-devtools-inspect-pulse 1.4s ease-in-out infinite}@keyframes et-query-devtools-inspect-pulse{0%,to{opacity:.2}50%{opacity:1}}@media(prefers-reduced-motion:reduce){.et-query-devtools-inspect-btn--active:after,.et-query-devtools-inspect-btn--active .et-query-devtools-inspect-icon-ring{animation:none;opacity:1}}.et-query-devtools-close{appearance:none;border:none;background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:.6rem;font:inherit}.et-query-devtools-kbd{border-radius:.3rem;background:var(--_et-qdt-hover);box-shadow:inset 0 0 0 1px var(--_et-qdt-border);color:var(--_et-qdt-muted);padding:.15rem .4rem;font:inherit;font-size:1.1rem;line-height:1.2;white-space:nowrap}.et-query-devtools-kbd--inverse{background:#00000026;box-shadow:inset 0 0 0 1px #00000040;color:inherit}@media(hover:none)and (pointer:coarse){.et-query-devtools-kbd{display:none}}.et-query-devtools-inspect-box{position:fixed;z-index:2147483002;pointer-events:none;border:2px solid var(--_et-qdt-accent);background:color-mix(in srgb,var(--_et-qdt-accent) 15%,transparent);border-radius:.2rem}.et-query-devtools-inspect-tag{position:absolute;inset-block-start:100%;inset-inline-start:0;margin-block-start:.2rem;padding:.2rem .5rem;border-radius:.3rem;background:var(--_et-qdt-accent);color:#09090b;font-family:ui-monospace,monospace;font-size:1.1rem;font-weight:600;white-space:nowrap}@media(hover:hover){.et-query-devtools-close:hover{color:var(--_et-qdt-fg)}}.et-query-devtools-body{flex:1 1 auto;min-block-size:0;overflow:hidden}.et-query-devtools-detail{flex:1 1 auto;min-inline-size:0;overflow-y:auto;padding:1rem}.et-query-devtools-detail .et-query-devtools-meta,.et-query-devtools-drawer .et-query-devtools-meta{padding:.8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 3%,transparent)}.et-query-devtools-detail .et-query-devtools-section-row,.et-query-devtools-drawer .et-query-devtools-section-row{padding-block-start:.8rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-filter-chip{display:inline-flex;align-items:center;gap:.6rem;padding:.3rem .35rem .3rem .7rem;border-radius:999px;background:color-mix(in srgb,var(--_et-qdt-accent) 16%,transparent);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--_et-qdt-accent) 45%,transparent);color:var(--_et-qdt-accent);font-size:1.1rem;font-weight:600;letter-spacing:.02em}.et-query-devtools-filter-icon{font-size:1.3rem;line-height:1}.et-query-devtools-filter-count{border-radius:999px;background:var(--_et-qdt-accent);color:#09090b;min-inline-size:1.8rem;padding:0 .5rem;text-align:center}.et-query-devtools-filter-clear{appearance:none;display:inline-flex;align-items:center;gap:.5rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:.3rem .6rem;font:inherit;font-size:1.15rem;line-height:1.4}@media(hover:hover){.et-query-devtools-filter-clear:hover{border-color:color-mix(in srgb,var(--_et-qdt-accent) 60%,transparent);color:var(--_et-qdt-fg)}}.et-query-devtools-scroll{block-size:100%;overflow:auto;padding:1rem;display:flex;flex-direction:column;gap:1rem}.et-query-devtools-split{display:flex;block-size:100%;min-block-size:0}.et-query-devtools-split .et-query-devtools-scroll{flex:1 1 auto;min-inline-size:0}.et-query-devtools-pane{display:flex;flex-direction:column;flex:1 1 auto;block-size:100%;min-inline-size:0;min-block-size:0;overflow:hidden}.et-query-devtools-pane>.et-query-devtools-scroll{padding-block:0 1rem}.et-query-devtools-pane-resize{flex:0 0 .5rem;align-self:stretch;cursor:ew-resize;background:transparent;transition:background .12s ease}.et-query-devtools-pane-resize:active{background:var(--_et-qdt-accent)}@media(hover:hover){.et-query-devtools-pane-resize:hover{background:var(--_et-qdt-accent)}}.et-query-devtools-toolbar{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.6rem;padding:.8rem;position:sticky;inset-block-start:0;z-index:1;background:var(--_et-qdt-bg);border-block-end:1px solid var(--_et-qdt-border)}.et-query-devtools-scroll>.et-query-devtools-toolbar{inset-block-start:-1rem}.et-query-devtools-toolbar-row{display:flex;align-items:center;flex-wrap:wrap;flex:1 1 100%;gap:.5rem}.et-query-devtools-field{display:inline-flex;align-items:center;gap:.6rem;color:var(--_et-qdt-muted)}.et-query-devtools-select{flex:0 0 auto;min-block-size:var(--_et-qdt-control-h);background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,var(--_et-qdt-bg));color:var(--_et-qdt-fg);border:1px solid transparent;border-radius:var(--_et-qdt-radius);padding:.4rem .6rem;font:inherit;font-size:1.15rem}.et-query-devtools-select option{background-color:var(--_et-qdt-bg);color:var(--_et-qdt-fg)}.et-query-devtools-count{color:var(--_et-qdt-subtle)}.et-query-devtools-row{display:flex;align-items:stretch;inline-size:100%;min-block-size:var(--_et-qdt-row-h);border-block-end:1px solid var(--_et-qdt-border);color:var(--_et-qdt-fg)}@media(hover:hover){.et-query-devtools-row:hover{background:var(--_et-qdt-hover)}}.et-query-devtools-method{font-weight:700;font-size:1.1rem;color:var(--_et-qdt-muted);white-space:nowrap}.et-query-devtools-route{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1 1 auto;min-inline-size:0}.et-query-devtools-status{inline-size:.8rem;block-size:.8rem;border-radius:999px;flex:0 0 auto;background:var(--_et-qdt-subtle)}.et-query-devtools-status[data-status=success]{background:var(--_et-qdt-success)}.et-query-devtools-status[data-status=error]{background:var(--_et-qdt-error)}.et-query-devtools-status[data-status=loading],.et-query-devtools-status[data-status=running]{background:var(--_et-qdt-loading)}.et-query-devtools-btn{appearance:none;display:inline-flex;align-items:center;justify-content:center;gap:.5rem;flex:0 0 auto;min-block-size:var(--_et-qdt-control-h);border:1px solid transparent;border-radius:var(--_et-qdt-radius);background:transparent;color:var(--_et-qdt-muted);padding:.25rem .7rem;font:inherit;font-size:1.15rem;line-height:1.4;white-space:nowrap;cursor:pointer;transition:color .12s ease,background-color .12s ease,border-color .12s ease}@media(hover:hover){.et-query-devtools-btn:hover:not(:disabled){background:var(--_et-qdt-hover);color:var(--_et-qdt-fg)}}.et-query-devtools-btn:focus-visible{outline:2px solid var(--_et-qdt-accent);outline-offset:1px}.et-query-devtools-btn:disabled{color:var(--_et-qdt-subtle);cursor:default;background:transparent;opacity:.6}.et-query-devtools-btn--primary,.et-query-devtools-btn--primary:hover:not(:disabled){background:color-mix(in srgb,var(--_et-qdt-accent) 16%,transparent);border-color:color-mix(in srgb,var(--_et-qdt-accent) 40%,transparent);color:var(--_et-qdt-accent)}@media(hover:hover){.et-query-devtools-btn--primary:hover:not(:disabled){background:color-mix(in srgb,var(--_et-qdt-accent) 26%,transparent)}}@media(hover:hover){.et-query-devtools-btn--danger:hover:not(:disabled){background:color-mix(in srgb,var(--_et-qdt-error) 16%,transparent);color:var(--_et-qdt-error)}}.et-query-devtools-btn--tampered,.et-query-devtools-btn--tampered:hover:not(:disabled){background:color-mix(in srgb,var(--_et-qdt-error) 14%,transparent);border-color:color-mix(in srgb,var(--_et-qdt-error) 45%,transparent);color:var(--_et-qdt-error)}.et-query-devtools-icon-btn{inline-size:var(--_et-qdt-control-h);padding-inline:0;font-size:1.3rem}.et-query-devtools-btn-group{display:inline-flex;flex:0 0 auto;border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius);overflow:hidden}.et-query-devtools-btn-group>.et-query-devtools-btn{border-radius:0;border-block:0;border-inline-start:0;border-inline-end:1px solid var(--_et-qdt-border)}.et-query-devtools-btn-group>.et-query-devtools-btn:last-child{border-inline-end:0}.et-query-devtools-toolbar-btn{margin-inline-start:auto;flex:0 0 auto}.et-query-devtools-meta{display:grid;grid-template-columns:auto 1fr;gap:.3rem 1rem;margin:0 0 1rem}.et-query-devtools-meta dt{color:var(--_et-qdt-muted)}.et-query-devtools-meta dd{margin:0;overflow-wrap:anywhere}.et-query-devtools-section{margin:1rem 0 .4rem;font-size:1.1rem;text-transform:uppercase;letter-spacing:.05em;color:var(--_et-qdt-muted)}.et-query-devtools-stat{padding:.6rem .8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 3%,transparent)}.et-query-devtools-stat dt{font-size:1rem;text-transform:uppercase;letter-spacing:.05em;color:var(--_et-qdt-muted)}.et-query-devtools-stat dd{margin:0;font-size:1.5rem;font-variant-numeric:tabular-nums}.et-query-devtools-stat-sub{display:block;font-size:1rem;color:var(--_et-qdt-subtle)}.et-query-devtools-json{margin:0;padding:.8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);overflow-x:auto;white-space:pre-wrap;overflow-wrap:anywhere;max-block-size:24rem;overflow-y:auto}.et-query-devtools-json--error{border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-mono{font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.et-query-devtools-url{display:inline-block;max-inline-size:32rem;margin-inline-start:.6rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.et-query-devtools-chips,.et-query-devtools-chip-row{display:flex;flex-wrap:wrap;gap:.4rem;margin-block-end:.8rem}.et-query-devtools-chip{display:inline-flex;align-items:center;gap:.3rem;padding:.2rem .6rem;border-radius:999px;border:1px solid var(--_et-qdt-border);font-size:1.1rem;color:var(--_et-qdt-fg);background:color-mix(in srgb,var(--_et-qdt-interaction) 6%,transparent)}.et-query-devtools-chip--muted{color:var(--_et-qdt-muted)}.et-query-devtools-retry-glyph{font-size:.7em;line-height:1}.et-query-devtools-chip--loading{color:var(--_et-qdt-loading);border-color:color-mix(in srgb,var(--_et-qdt-loading) 50%,transparent)}.et-query-devtools-chip--error{color:var(--_et-qdt-error);border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-chip--success{color:var(--_et-qdt-success);border-color:color-mix(in srgb,var(--_et-qdt-success) 50%,transparent)}button.et-query-devtools-chip--button{appearance:none;cursor:pointer;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}@media(hover:hover){button.et-query-devtools-chip--button:hover{border-color:var(--_et-qdt-accent);color:var(--_et-qdt-accent)}}.et-query-devtools-facet{appearance:none;min-block-size:var(--_et-qdt-control-h);padding-inline:.8rem;background:transparent;cursor:pointer;color:var(--_et-qdt-muted);font:inherit;font-size:1.1rem;line-height:1.4}@media(hover:hover){.et-query-devtools-facet:hover:not(:disabled){color:var(--_et-qdt-fg);background:var(--_et-qdt-hover);border-color:color-mix(in srgb,var(--_et-qdt-accent) 60%,transparent)}}.et-query-devtools-facet:disabled{color:var(--_et-qdt-subtle);cursor:default}.et-query-devtools-facet--active{color:var(--_et-qdt-accent);border-color:color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent);background:color-mix(in srgb,var(--_et-qdt-accent) 14%,transparent)}.et-query-devtools-facet-count{font-variant-numeric:tabular-nums;color:inherit;opacity:.75}.et-query-devtools-card{border:1px solid var(--_et-qdt-border);border-radius:.6rem;padding:1rem}.et-query-devtools-card--armed{border-color:color-mix(in srgb,var(--_et-qdt-error) 55%,transparent);background:color-mix(in srgb,var(--_et-qdt-error) 6%,transparent)}.et-query-devtools-card-head{display:flex;align-items:center;gap:.6rem;margin-block-end:.8rem}.et-query-devtools-inner-queries{display:flex;flex-direction:column;gap:.3rem}.et-query-devtools-inner-row{display:flex;align-items:center;gap:.6rem;inline-size:100%;text-align:start;appearance:none;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:transparent;color:var(--_et-qdt-fg);padding:.4rem .6rem;cursor:pointer;font:inherit}@media(hover:hover){.et-query-devtools-inner-row:hover:not([disabled]){background:var(--_et-qdt-hover)}}.et-query-devtools-inner-row--active{background:var(--_et-qdt-active);border-color:var(--_et-qdt-accent)}.et-query-devtools-inner-row[disabled]{cursor:default;opacity:.6}.et-query-devtools-step-toggle{display:flex;align-items:center;gap:.6rem;inline-size:100%;text-align:start;appearance:none;border:none;background:transparent;color:var(--_et-qdt-fg);padding:.6rem;cursor:pointer;font:inherit}@media(hover:hover){.et-query-devtools-step-toggle:hover{background:var(--_et-qdt-hover)}}.et-query-devtools-step-index{flex:1 1 auto;min-inline-size:0}.et-query-devtools-step-caret{color:var(--_et-qdt-muted)}.et-query-devtools-table{inline-size:100%;border-collapse:collapse}.et-query-devtools-table th,.et-query-devtools-table td{text-align:start;padding:.5rem .6rem;border-block-end:1px solid var(--_et-qdt-border);vertical-align:top}.et-query-devtools-table td .et-query-devtools-chip{padding-block:0;line-height:1.4}.et-query-devtools-table th{color:var(--_et-qdt-muted);font-weight:600;position:sticky;inset-block-start:0;background:var(--_et-qdt-bg)}.et-query-devtools-table tr[data-status=error] td{color:var(--_et-qdt-error)}.et-query-devtools-empty{color:var(--_et-qdt-subtle);padding:1.2rem;text-align:center}.et-query-devtools-cell-actions{display:inline-flex;gap:.4rem;white-space:nowrap}.et-query-devtools-section-row{display:flex;align-items:center;justify-content:space-between;gap:.8rem;margin-block-start:1rem}.et-query-devtools-search{flex:1 1 16rem;min-inline-size:0;min-block-size:var(--_et-qdt-control-h);background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,transparent);color:var(--_et-qdt-fg);border:1px solid transparent;border-radius:var(--_et-qdt-radius);padding:.4rem .8rem;font:inherit;font-size:1.15rem}.et-query-devtools-search:focus{outline:none;border-color:color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent);background:var(--_et-qdt-bg)}.et-query-devtools-row-link{display:inline-flex;align-items:center;gap:.6rem;appearance:none;border:0;background:none;padding:0;color:inherit;font:inherit;text-align:start;cursor:pointer}.et-query-devtools-row-link:focus-visible .et-query-devtools-url{color:var(--_et-qdt-accent);text-decoration:underline}@media(hover:hover){.et-query-devtools-row-link:hover .et-query-devtools-url{color:var(--_et-qdt-accent);text-decoration:underline}}.et-query-devtools-hint{flex:1 1 24rem;color:var(--_et-qdt-muted);font-size:1.1rem;margin:0}.et-query-devtools-input{inline-size:100%;background:var(--_et-qdt-bg);color:var(--_et-qdt-fg);border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.4rem .6rem;font:inherit}.et-query-devtools-input--number{inline-size:8rem;font-variant-numeric:tabular-nums}.et-query-devtools-editor{border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.8rem;margin-block-end:1rem}.et-query-devtools-editor-error{color:var(--_et-qdt-error);margin:.4rem 0 0}.et-query-devtools-btn--active{color:var(--_et-qdt-accent);border-color:color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent);background:color-mix(in srgb,var(--_et-qdt-accent) 12%,transparent)}}\n"] }]
7219
+ // Every overlay opened from inside the panel - its menus, its tooltips - mounts one level above
7220
+ // the panel's own z-index, which is itself above the default overlay layer. Both numbers live in
7221
+ // query-devtools.component.css; keep them in step.
7222
+ 'data-et-overlay-layer': '2147483020',
7223
+ }, template: "@if (open()) {\n <section\n #panel\n [class.et-query-devtools-panel--resizing]=\"resizing()\"\n [attr.data-dock]=\"poppedOut() ? 'popout' : dock()\"\n [attr.data-pane-axis]=\"paneAxis()\"\n [attr.data-parked]=\"floatParked() ? '' : null\"\n [style.block-size.px]=\"panelBlockSize()\"\n [style.inline-size.px]=\"panelInlineSize()\"\n [style.inset-block-start.px]=\"panelInsetBlock()\"\n [style.inset-inline-start.px]=\"panelInsetInline()\"\n [style.--_et-qdt-list-width.px]=\"listWidth()\"\n [style.--_et-qdt-drawer-width.px]=\"drawerWidth()\"\n [style.--_et-qdt-list-height.px]=\"listHeight()\"\n [style.--_et-qdt-drawer-height.px]=\"drawerHeight()\"\n class=\"et-query-devtools-panel\"\n >\n <!-- A pop-out is sized by its window, so there is no edge to drag. A float is a window in the page:\n a title bar to move it and every edge to resize it, on the same `@ethlete/core` primitives the\n stream pip window uses. A dock has one edge, and it does both. -->\n @if (floating()) {\n <!-- Parked, the panel is a grab strip: the edge handles would eat most of the peek that is left\n to click, and resizing something you cannot see is not a thing to offer. -->\n <et-resize-handles\n [edges]=\"FLOAT_RESIZE_EDGES\"\n [disabled]=\"floatParked()\"\n (resizeStarted)=\"startFloatResize()\"\n (resizeMoved)=\"resizeFloat($event)\"\n (resizeEnded)=\"endFloatResize()\"\n (resizeCancelled)=\"endFloatResize()\"\n />\n <div\n (dragMoved)=\"moveFloat($event)\"\n (dragEnded)=\"endFloatMove()\"\n (dragCancelled)=\"endFloatMove()\"\n (dragTapped)=\"restoreFloat()\"\n class=\"et-query-devtools-move\"\n title=\"Drag to move \u2014 shove it off an edge to park it, click the strip to bring it back\"\n aria-hidden=\"true\"\n etDragHandle\n ></div>\n } @else if (!poppedOut()) {\n <div\n (pointerdown)=\"startResize($event)\"\n class=\"et-query-devtools-resize\"\n title=\"Drag to resize\"\n aria-hidden=\"true\"\n ></div>\n }\n <header class=\"et-query-devtools-header\">\n <nav class=\"et-query-devtools-tabs\" role=\"tablist\">\n @for (tab of visibleTabs(); track tab.id) {\n @let badge = tabBadges()[tab.id];\n <button\n [class.et-query-devtools-tab--active]=\"activeTab() === tab.id\"\n [attr.aria-selected]=\"activeTab() === tab.id\"\n (click)=\"activeTab.set(tab.id)\"\n class=\"et-query-devtools-tab\"\n type=\"button\"\n role=\"tab\"\n >\n {{ tab.label }}\n @if (badge.count) {\n <span class=\"et-query-devtools-tab-badge\">{{ badge.count }}</span>\n }\n @if (badge.errors) {\n <span\n class=\"et-query-devtools-tab-badge et-query-devtools-tab-badge--error\"\n title=\"{{ badge.errors }} {{ badge.errorNoun ?? 'failing' }}\"\n >\n {{ badge.errors }}\n </span>\n }\n </button>\n }\n\n @if (overflowTabs().length; as overflowCount) {\n <div class=\"et-query-devtools-menu\" etMenu>\n <button\n [title]=\"overflowCount + ' tabs with nothing in them'\"\n class=\"et-query-devtools-tab\"\n type=\"button\"\n etMenuTrigger\n >\n More\n <span class=\"et-query-devtools-menu-caret\" aria-hidden=\"true\">\u25BE</span>\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n @for (tab of overflowTabs(); track tab.id) {\n <button (activate)=\"activeTab.set(tab.id)\" et-menu-item type=\"button\">{{ tab.label }}</button>\n }\n </et-menu>\n </ng-template>\n </div>\n }\n </nav>\n\n <div class=\"et-query-devtools-header-actions\">\n <button\n [class.et-query-devtools-inspect-btn--active]=\"inspectActive()\"\n [attr.aria-pressed]=\"inspectActive()\"\n (click)=\"toggleInspect()\"\n class=\"et-query-devtools-btn et-query-devtools-inspect-btn\"\n type=\"button\"\n title=\"Inspect: hover the UI to find its query\"\n >\n <svg\n class=\"et-query-devtools-inspect-icon\"\n viewBox=\"0 0 16 16\"\n width=\"14\"\n height=\"14\"\n fill=\"none\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <circle class=\"et-query-devtools-inspect-icon-ring\" cx=\"8\" cy=\"8\" r=\"4.25\" />\n <circle class=\"et-query-devtools-inspect-icon-dot\" cx=\"8\" cy=\"8\" r=\"1.15\" />\n <path d=\"M8 0.9v2.1M8 13v2.1M0.9 8h2.1M13 8h2.1\" />\n </svg>\n {{ inspectActive() ? 'Inspecting' : 'Inspect' }}\n @if (inspectActive()) {\n <kbd class=\"et-query-devtools-kbd et-query-devtools-kbd--inverse\">Esc</kbd>\n }\n </button>\n <button\n (click)=\"downloadSession()\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n title=\"Download every registered entry, the event log, the cache totals and anything armed as one JSON file - the attachment for a bug report\"\n >\n <span aria-hidden=\"true\">\u2913</span>\n Session\n </button>\n <!-- A gear, not a tab: Settings holds nothing to count, so the badge/overflow logic would push it\n behind More - and the one control that explains a panel behaving oddly may not be hidden. -->\n <button\n [class.et-query-devtools-btn--active]=\"activeTab() === 'settings'\"\n [attr.aria-pressed]=\"activeTab() === 'settings'\"\n (click)=\"toggleSettings()\"\n class=\"et-query-devtools-btn et-query-devtools-icon-btn\"\n type=\"button\"\n title=\"Settings: where panel state is kept, and the limits it holds\"\n aria-label=\"Settings\"\n >\n <span aria-hidden=\"true\">\u2699</span>\n </button>\n @if (!poppedOut()) {\n @let layout = currentLayout();\n <div class=\"et-query-devtools-menu\" etMenu placement=\"bottom-end\">\n <button class=\"et-query-devtools-btn\" title=\"Where the panel sits\" type=\"button\" etMenuTrigger>\n <span class=\"et-query-devtools-menu-glyph\" aria-hidden=\"true\">{{ layout.glyph }}</span>\n {{ layout.label }}\n <span class=\"et-query-devtools-menu-caret\" aria-hidden=\"true\">\u25BE</span>\n </button>\n\n <ng-template etMenuSurface>\n <et-menu>\n <et-menu-radio-group [value]=\"dock()\">\n @for (option of LAYOUTS; track option.id) {\n <!-- eslint-disable ethlete/prefer-static-boolean-properties -- closeOnActivate is tri-state (boolean | undefined); a transform would collapse its unset state -->\n <et-menu-radio-item\n [value]=\"option.id\"\n [title]=\"option.hint\"\n [closeOnActivate]=\"true\"\n (activate)=\"selectLayout(option.id)\"\n >\n <!-- eslint-enable ethlete/prefer-static-boolean-properties -->\n <span class=\"et-query-devtools-menu-item-glyph\" aria-hidden=\"true\">{{ option.glyph }}</span>\n {{ option.label }}\n </et-menu-radio-item>\n }\n </et-menu-radio-group>\n <et-menu-separator />\n <et-menu-checkbox-item\n [checked]=\"reservesSpace()\"\n [disabled]=\"floating()\"\n (checkedChange)=\"reservesSpace.set($event)\"\n title=\"The page gives up the room the panel covers, so its last rows can still be scrolled to. Docks only - a float sits wherever it was dragged.\"\n >\n Reserve page space\n </et-menu-checkbox-item>\n </et-menu>\n </ng-template>\n </div>\n }\n <span class=\"et-query-devtools-header-divider\" aria-hidden=\"true\"></span>\n <button\n (click)=\"toggleOpen()\"\n class=\"et-query-devtools-btn et-query-devtools-btn--danger\"\n type=\"button\"\n title=\"{{ poppedOut() ? 'Bring the panel back into the app window' : 'Close devtools (' + shortcut + ')' }}\"\n >\n @if (poppedOut()) {\n <span aria-hidden=\"true\">\u29C8</span>\n Dock back\n } @else {\n <span aria-hidden=\"true\">\u2715</span>\n Close\n <kbd class=\"et-query-devtools-kbd\">{{ shortcut }}</kbd>\n }\n </button>\n </div>\n </header>\n\n @if (popOutBlocked()) {\n <div class=\"et-query-devtools-notice-bar\" role=\"status\">\n <span class=\"et-query-devtools-armed-text\">\n The browser blocked the pop-up window. Allow pop-ups for this site, or float the panel over the page instead \u2014\n a float needs no permission.\n </span>\n <button (click)=\"floatPanel()\" class=\"et-query-devtools-btn\" type=\"button\">Float instead</button>\n <button (click)=\"popOutBlocked.set(false)\" class=\"et-query-devtools-btn\" type=\"button\">Dismiss</button>\n </div>\n }\n\n @if (armedFaultClients(); as armed) {\n <div class=\"et-query-devtools-armed-bar\" role=\"status\">\n <span class=\"et-query-devtools-armed-dot\" aria-hidden=\"true\"></span>\n <span class=\"et-query-devtools-armed-text\">\n <strong>Faults armed</strong> on\n <span class=\"et-query-devtools-mono\">{{ armed.join(', ') }}</span>\n \u2014 every failure and delay you see from\n {{ armed.length === 1 ? 'this client' : 'these clients' }} is injected, not the API.\n @if (faultsRestored()) {\n They came back from your last page load \u2014 set <em>Armed faults</em> back to <em>none</em> in Settings to\n stop that.\n }\n </span>\n <button\n [disabled]=\"activeTab() === 'faults'\"\n (click)=\"activeTab.set('faults')\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Review\n </button>\n <button (click)=\"CLEAR_FAULTS()\" class=\"et-query-devtools-btn\" type=\"button\">Disarm all</button>\n </div>\n }\n\n @if (armedMockRoutes(); as mocked) {\n <div class=\"et-query-devtools-armed-bar\" role=\"status\">\n <span class=\"et-query-devtools-armed-dot\" aria-hidden=\"true\"></span>\n <span class=\"et-query-devtools-armed-text\">\n <strong>Mocks armed</strong> on\n <span class=\"et-query-devtools-mono\">{{ mocked.join(', ') }}</span>\n \u2014 {{ mocked.length === 1 ? 'that route is' : 'those routes are' }} answered by the panel, so nothing they show\n reached the API.\n @if (mocksRestored()) {\n They came back from your last page load \u2014 set <em>Armed mocks</em> back to <em>none</em> in Settings to stop\n that.\n }\n </span>\n <button\n [disabled]=\"activeTab() === 'mocks'\"\n (click)=\"activeTab.set('mocks')\"\n class=\"et-query-devtools-btn\"\n type=\"button\"\n >\n Review\n </button>\n <button (click)=\"DISARM_MOCKS()\" class=\"et-query-devtools-btn\" type=\"button\">Disarm all</button>\n </div>\n }\n\n @if (restoredOverrides(); as restored) {\n <div class=\"et-query-devtools-armed-bar\" role=\"status\">\n <span class=\"et-query-devtools-armed-dot\" aria-hidden=\"true\"></span>\n <span class=\"et-query-devtools-armed-text\">\n @if (restored.ops) {\n <strong>Overrides re-armed</strong> \u2014 {{ restored.ops }} {{ restored.ops === 1 ? 'edit' : 'edits' }} from\n your last page load {{ restored.ops === 1 ? 'is' : 'are' }} back on {{ restored.queries }}\n {{ restored.queries === 1 ? 'query' : 'queries' }}, so those responses are not what the API sent.\n @if (restored.fromLocalStorage) {\n They came back from <strong>localStorage</strong>, so they outlive closing this tab \u2014 clear them, or set\n overrides back to <em>session</em> in Settings.\n }\n } @else {\n <strong>Overrides could not be re-armed.</strong>\n }\n @if (restored.orphaned.length) {\n <span class=\"et-query-devtools-armed-orphans\">\n {{ restored.orphaned.length }} stored\n {{ restored.orphaned.length === 1 ? 'override' : 'overrides' }} matched no query \u2014\n <span class=\"et-query-devtools-mono\">{{ restored.orphaned.join(', ') }}</span>\n </span>\n }\n </span>\n @if (restored.firstId; as firstId) {\n <button (click)=\"reviewRestoredOverrides(firstId)\" class=\"et-query-devtools-btn\" type=\"button\">Review</button>\n }\n <button (click)=\"DROP_RESTORED_OVERRIDES()\" class=\"et-query-devtools-btn\" type=\"button\">Drop all</button>\n </div>\n }\n\n <div class=\"et-query-devtools-body\">\n @switch (activeTab()) {\n <!-- QUERIES -->\n @case ('queries') {\n <et-query-devtools-queries-tab />\n }\n\n <!-- STACKS -->\n @case ('stacks') {\n <et-query-devtools-stacks-tab />\n }\n\n <!-- SEQUENCES -->\n @case ('sequences') {\n <et-query-devtools-sequences-tab />\n }\n\n <!-- BATCHES -->\n @case ('batches') {\n <et-query-devtools-batches-tab />\n }\n\n <!-- FORMS -->\n @case ('forms') {\n <et-query-devtools-forms-tab />\n }\n\n <!-- AUTH -->\n @case ('auth') {\n <et-query-devtools-auth-tab />\n }\n\n <!-- SOCKETS -->\n @case ('ws') {\n <et-query-devtools-sockets-tab />\n }\n\n <!-- CACHE -->\n @case ('cache') {\n <et-query-devtools-cache-tab />\n }\n\n <!-- LOCKS -->\n @case ('locks') {\n <et-query-devtools-locks-tab />\n }\n\n <!-- TIMELINE -->\n @case ('timeline') {\n <et-query-devtools-timeline-tab />\n }\n\n <!-- EVENTS -->\n @case ('events') {\n <et-query-devtools-events-tab />\n }\n\n <!-- FAULTS -->\n @case ('faults') {\n <et-query-devtools-faults-tab />\n }\n\n <!-- MOCKS -->\n @case ('mocks') {\n <et-query-devtools-mocks-tab />\n }\n\n <!-- ABOUT -->\n @case ('about') {\n <et-query-devtools-about />\n }\n\n <!-- SETTINGS -->\n @case ('settings') {\n <et-query-devtools-settings />\n }\n }\n </div>\n </section>\n}\n\n@if (!open()) {\n <et-query-devtools-toggle [tampered]=\"panelTampered()\" (openChange)=\"toggleOpen()\" />\n}\n\n@if (inspectActive() && inspectHover(); as hover) {\n <div\n [style.left.px]=\"hover.rect.left\"\n [style.top.px]=\"hover.rect.top\"\n [style.width.px]=\"hover.rect.width\"\n [style.height.px]=\"hover.rect.height\"\n class=\"et-query-devtools-inspect-box\"\n >\n <span class=\"et-query-devtools-inspect-tag\">{{ inspectLabel(hover.entries) }}</span>\n </div>\n}\n\n@if (locatedRect(); as rect) {\n <div\n [style.left.px]=\"rect.left\"\n [style.top.px]=\"rect.top\"\n [style.width.px]=\"rect.width\"\n [style.height.px]=\"rect.height\"\n class=\"et-query-devtools-inspect-box\"\n >\n <span class=\"et-query-devtools-inspect-tag\">created here</span>\n </div>\n}\n", styles: ["@layer components{.et-query-devtools-host{--_et-qdt-bg: var(--et-surface-background-solid, rgb(24 24 27));--_et-qdt-fg: var(--et-surface-color-solid, rgb(244 244 245));--_et-qdt-muted: var(--et-surface-color-muted-solid, rgb(161 161 170));--_et-qdt-subtle: var(--et-surface-color-subtle-solid, rgb(113 113 122));--_et-qdt-border: var(--et-surface-border-solid, rgb(255 255 255 / .12));--_et-qdt-interaction: var(--et-surface-interaction-solid, rgb(255 255 255));--_et-qdt-accent: var(--et-theme-color-primary-solid, rgb(96 165 250));--_et-qdt-hover: color-mix(in srgb, var(--_et-qdt-interaction) 10%, transparent);--_et-qdt-active: color-mix(in srgb, var(--_et-qdt-interaction) 18%, transparent);--_et-qdt-success: rgb(34 197 94);--_et-qdt-error: rgb(239 68 68);--_et-qdt-loading: rgb(234 179 8);--_et-qdt-title-bar: 2.4rem;--_et-qdt-radius: .5rem;--_et-qdt-control-h: 2.4rem;--_et-qdt-row-h: 3.6rem;position:fixed;inset-inline:0;inset-block-end:0;z-index:2147483010;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,Liberation Mono,monospace;font-size:1.2rem;line-height:1.4;pointer-events:none}.et-query-devtools-host>*{pointer-events:auto}@media(pointer:coarse){.et-query-devtools-host{--_et-qdt-control-h: 3.4rem;--_et-qdt-row-h: 4.4rem}}.et-query-devtools-panel{position:relative;display:flex;flex-direction:column;inline-size:100%;min-block-size:20rem;max-block-size:90vh;background:var(--_et-qdt-bg);color:var(--_et-qdt-fg);border-block-start:1px solid var(--_et-qdt-border);box-shadow:0 -.4rem 2rem #00000059}.et-query-devtools-host:where([data-dock=right]){inset-block:0;inset-inline-start:auto}.et-query-devtools-panel:where([data-dock=right]){inline-size:auto;block-size:100%;max-block-size:none;max-inline-size:90vw;border-block-start:0;border-inline-start:1px solid var(--_et-qdt-border);box-shadow:-.4rem 0 2rem #00000059}.et-query-devtools-host:where([data-dock=float]){inset:0}.et-query-devtools-panel:where([data-dock=float]){--_et-qdt-frame: .8rem;--et-resize-handles-edge-size: var(--_et-qdt-frame);--et-resize-handles-touch-edge-size: var(--_et-qdt-frame);--et-resize-handles-corner-size: 1.6rem;--et-resize-handles-touch-corner-size: 1.6rem;--et-resize-handles-edge-inset: 1.6rem;--et-resize-handles-side-top: var(--_et-qdt-title-bar);--et-resize-handles-side-bottom: 1.6rem;--et-resize-handles-z-index: 3;position:absolute;inline-size:auto;min-block-size:0;max-block-size:none;padding-block-start:var(--_et-qdt-title-bar);padding-inline:var(--_et-qdt-frame);padding-block-end:var(--_et-qdt-frame);border:1px solid var(--_et-qdt-border);border-radius:.6rem;box-shadow:0 1rem 4rem #00000073;overflow:hidden}.et-query-devtools-move{position:absolute;inset-inline:0;inset-block-start:0;block-size:var(--_et-qdt-title-bar);cursor:move;z-index:2}.et-query-devtools-panel:where([data-dock=float]) .et-query-devtools-body{border-radius:.3rem;overflow:hidden}.et-query-devtools-move:after{content:\"\";position:absolute;inset-inline:50%;inset-block-start:.9rem;inline-size:3rem;margin-inline-start:-1.5rem;block-size:.6rem;background-image:radial-gradient(currentcolor 40%,transparent 42%);background-size:.4rem .3rem;color:var(--_et-qdt-subtle)}@media(hover:hover){.et-query-devtools-move:hover:after{color:var(--_et-qdt-accent)}}.et-query-devtools-panel:where([data-dock=float][data-parked]){opacity:.85}@media(hover:hover){.et-query-devtools-panel:where([data-dock=float][data-parked]):hover{opacity:1}}.et-query-devtools-panel:where([data-dock=float][data-parked]) .et-query-devtools-move{cursor:grab;background:var(--_et-qdt-active)}.et-query-devtools-panel:where([data-dock=float][data-parked]) .et-query-devtools-move:after{color:var(--_et-qdt-accent)}.et-query-devtools-host:where([data-dock=left]){inset-block:0;inset-inline-end:auto}.et-query-devtools-panel:where([data-dock=left]){inline-size:auto;block-size:100%;max-block-size:none;max-inline-size:90vw;border-block-start:0;border-inline-end:1px solid var(--_et-qdt-border);box-shadow:.4rem 0 2rem #00000059}.et-query-devtools-host:where([data-dock=top]){inset-block-start:0;inset-block-end:auto}.et-query-devtools-panel:where([data-dock=top]){border-block-start:0;border-block-end:1px solid var(--_et-qdt-border);box-shadow:0 .4rem 2rem #00000059}.et-query-devtools-host:where([data-dock=popout]){position:static;inline-size:100%;block-size:100%}.et-query-devtools-panel:where([data-dock=popout]){block-size:100%;max-block-size:none;border:0;box-shadow:none}.et-query-devtools-panel:where([data-pane-axis=block]) .et-query-devtools-header{flex-wrap:wrap}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-tabs,.et-query-devtools-header-actions){flex:1 1 auto;flex-wrap:nowrap;min-inline-size:0;overflow-x:auto}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-tabs,.et-query-devtools-header-actions)>*{flex:none}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-master-detail,.et-query-devtools-split){flex-direction:column}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-list,.et-query-devtools-detail,.et-query-devtools-drawer,.et-query-devtools-split .et-query-devtools-scroll){flex:1 1 50%;min-inline-size:0;min-block-size:0;max-inline-size:none;border-inline-start:0;border-inline-end:0}.et-query-devtools-panel:where([data-pane-axis=block]) :is(.et-query-devtools-detail,.et-query-devtools-drawer){border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-panel:where([data-pane-axis=block]) .et-query-devtools-master-detail>.et-query-devtools-list{flex:0 1 var(--_et-qdt-list-height, 50%);min-block-size:var(--_et-qdt-list-height, 0);max-block-size:var(--_et-qdt-list-height, none)}.et-query-devtools-panel:where([data-pane-axis=block]) .et-query-devtools-split .et-query-devtools-drawer{flex:0 1 var(--_et-qdt-drawer-height, 50%);min-block-size:var(--_et-qdt-drawer-height, 0);max-block-size:var(--_et-qdt-drawer-height, none)}.et-query-devtools-panel:where([data-pane-axis=block]) .et-query-devtools-pane-resize{cursor:ns-resize}.et-query-devtools-panel--resizing{-webkit-user-select:none;user-select:none}.et-query-devtools-resize,.et-query-devtools-pane-resize{touch-action:none}.et-query-devtools-resize{position:absolute;inset-inline:0;inset-block-start:0;block-size:.6rem;cursor:ns-resize;z-index:1}.et-query-devtools-panel:where([data-dock=right]) .et-query-devtools-resize{inset-inline:0 auto;inset-block:0;inline-size:.6rem;block-size:auto;cursor:ew-resize}.et-query-devtools-panel:where([data-dock=left]) .et-query-devtools-resize{inset-inline:auto 0;inset-block:0;inline-size:.6rem;block-size:auto;cursor:ew-resize}.et-query-devtools-panel:where([data-dock=top]) .et-query-devtools-resize{inset-block:auto 0}.et-query-devtools-resize:after{content:\"\";position:absolute;inset-inline:50%;inset-block-start:.2rem;inline-size:4rem;margin-inline-start:-2rem;block-size:.2rem;border-radius:999px;background:var(--_et-qdt-border)}.et-query-devtools-panel:where([data-dock=right]) .et-query-devtools-resize:after{inset-inline:.2rem auto;inset-block:50% auto;inline-size:.2rem;block-size:4rem;margin-inline-start:0;margin-block-start:-2rem}.et-query-devtools-panel:where([data-dock=left]) .et-query-devtools-resize:after{inset-inline:auto .2rem;inset-block:50% auto;inline-size:.2rem;block-size:4rem;margin-inline-start:0;margin-block-start:-2rem}.et-query-devtools-panel:where([data-dock=top]) .et-query-devtools-resize:after{inset-block:auto .2rem}@media(hover:hover){.et-query-devtools-resize:hover:after{background:var(--_et-qdt-accent)}}.et-query-devtools-header{display:flex;align-items:center;justify-content:space-between;gap:.8rem;padding-inline:.8rem;border-block-end:1px solid var(--_et-qdt-border);flex:0 0 auto}.et-query-devtools-armed-bar{display:flex;align-items:center;gap:.8rem;flex:0 0 auto;padding:.6rem .8rem;background:color-mix(in srgb,var(--_et-qdt-error) 14%,transparent);border-block-end:1px solid color-mix(in srgb,var(--_et-qdt-error) 45%,transparent);color:var(--_et-qdt-error);font-size:1.1rem}.et-query-devtools-armed-text{flex:1 1 auto;min-inline-size:0}.et-query-devtools-notice-bar{display:flex;align-items:center;gap:.8rem;flex:0 0 auto;padding:.6rem .8rem;background:var(--_et-qdt-hover);border-block-end:1px solid var(--_et-qdt-border);color:var(--_et-qdt-fg);font-size:1.1rem}.et-query-devtools-notice-bar .et-query-devtools-btn{flex:0 0 auto}.et-query-devtools-armed-orphans{display:block;opacity:.85}.et-query-devtools-armed-dot{flex:0 0 auto;inline-size:.8rem;block-size:.8rem;border-radius:50%;background:var(--_et-qdt-error);animation:et-query-devtools-armed-pulse 1.6s ease-in-out infinite}@media(prefers-reduced-motion:reduce){.et-query-devtools-armed-dot{animation:none}}@keyframes et-query-devtools-armed-pulse{50%{opacity:.25}}.et-query-devtools-armed-bar .et-query-devtools-btn{flex:0 0 auto;border-color:color-mix(in srgb,var(--_et-qdt-error) 55%,transparent);color:var(--_et-qdt-error)}.et-query-devtools-tabs{display:flex;flex-wrap:wrap;gap:.2rem}.et-query-devtools-tab{appearance:none;display:inline-flex;align-items:center;gap:.5rem;border:none;background:transparent;color:var(--_et-qdt-muted);padding:.9rem 1rem;cursor:pointer;border-block-end:2px solid transparent;font:inherit}@media(hover:hover){.et-query-devtools-tab:hover{color:var(--_et-qdt-fg)}}.et-query-devtools-tab--active{color:var(--_et-qdt-fg);border-block-end-color:var(--_et-qdt-accent)}.et-query-devtools-tab-badge{display:inline-block;padding-inline:.45rem;border-radius:999px;background:color-mix(in srgb,var(--_et-qdt-interaction) 12%,transparent);color:var(--_et-qdt-muted);font-size:1rem;font-variant-numeric:tabular-nums}.et-query-devtools-tab-badge--error{background:color-mix(in srgb,var(--_et-qdt-error) 25%,transparent);color:var(--_et-qdt-fg)}.et-query-devtools-menu{display:contents}.et-query-devtools-menu-caret{font-size:.9em;opacity:.7}.et-query-devtools-menu-glyph{display:inline-block;min-inline-size:1.2rem;text-align:center}.et-query-devtools-menu>[data-menu-open]{background:var(--_et-qdt-hover);color:var(--_et-qdt-fg)}.et-menu .et-query-devtools-menu-item-glyph{display:inline-block;inline-size:16px;text-align:center}.et-menu-item-content:has(.et-query-devtools-menu-item-note){display:flex;flex-direction:column;align-items:start;gap:2px;padding-block:8px}.et-query-devtools-menu-item-note{color:var(--et-surface-color-muted-solid, rgb(255 255 255 / .55));font-size:12px}.et-query-devtools-header-actions{display:flex;align-items:center;gap:.2rem;flex:0 0 auto}.et-query-devtools-header-divider{inline-size:1px;block-size:1.6rem;margin-inline:.4rem;background:var(--_et-qdt-border)}.et-query-devtools-inspect-btn{position:relative}.et-query-devtools-inspect-icon{inline-size:1.4rem;block-size:1.4rem;flex:0 0 auto;fill:none;stroke:currentColor;stroke-width:1.25;stroke-linecap:round}.et-query-devtools-inspect-icon-dot{fill:currentColor;stroke:none}.et-query-devtools-btn.et-query-devtools-inspect-btn--active,.et-query-devtools-btn.et-query-devtools-inspect-btn--active:hover{color:#09090b;background:var(--_et-qdt-accent);border-color:var(--_et-qdt-accent)}.et-query-devtools-inspect-btn--active:after{content:\"\";position:absolute;inset:-.3rem;border:1px solid var(--_et-qdt-accent);border-radius:calc(var(--_et-qdt-radius) + .3rem);pointer-events:none;animation:et-query-devtools-inspect-pulse 1.4s ease-in-out infinite}.et-query-devtools-inspect-btn--active .et-query-devtools-inspect-icon-ring{animation:et-query-devtools-inspect-pulse 1.4s ease-in-out infinite}@keyframes et-query-devtools-inspect-pulse{0%,to{opacity:.2}50%{opacity:1}}@media(prefers-reduced-motion:reduce){.et-query-devtools-inspect-btn--active:after,.et-query-devtools-inspect-btn--active .et-query-devtools-inspect-icon-ring{animation:none;opacity:1}}.et-query-devtools-close{appearance:none;border:none;background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:.6rem;font:inherit}.et-query-devtools-kbd{border-radius:.3rem;background:var(--_et-qdt-hover);box-shadow:inset 0 0 0 1px var(--_et-qdt-border);color:var(--_et-qdt-muted);padding:.15rem .4rem;font:inherit;font-size:1.1rem;line-height:1.2;white-space:nowrap}.et-query-devtools-kbd--inverse{background:#00000026;box-shadow:inset 0 0 0 1px #00000040;color:inherit}@media(hover:none)and (pointer:coarse){.et-query-devtools-kbd{display:none}}.et-query-devtools-inspect-box{position:fixed;z-index:2147483002;pointer-events:none;border:2px solid var(--_et-qdt-accent);background:color-mix(in srgb,var(--_et-qdt-accent) 15%,transparent);border-radius:.2rem}.et-query-devtools-inspect-tag{position:absolute;inset-block-start:100%;inset-inline-start:0;margin-block-start:.2rem;padding:.2rem .5rem;border-radius:.3rem;background:var(--_et-qdt-accent);color:#09090b;font-family:ui-monospace,monospace;font-size:1.1rem;font-weight:600;white-space:nowrap}@media(hover:hover){.et-query-devtools-close:hover{color:var(--_et-qdt-fg)}}.et-query-devtools-body{flex:1 1 auto;min-block-size:0;overflow:hidden}.et-query-devtools-detail{flex:1 1 auto;min-inline-size:0;overflow-y:auto;padding:1rem}.et-query-devtools-detail .et-query-devtools-meta,.et-query-devtools-drawer .et-query-devtools-meta{padding:.8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 3%,transparent)}.et-query-devtools-detail .et-query-devtools-section-row,.et-query-devtools-drawer .et-query-devtools-section-row{padding-block-start:.8rem;border-block-start:1px solid var(--_et-qdt-border)}.et-query-devtools-filter-chip{display:inline-flex;align-items:center;gap:.6rem;padding:.3rem .35rem .3rem .7rem;border-radius:999px;background:color-mix(in srgb,var(--_et-qdt-accent) 16%,transparent);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--_et-qdt-accent) 45%,transparent);color:var(--_et-qdt-accent);font-size:1.1rem;font-weight:600;letter-spacing:.02em}.et-query-devtools-filter-icon{font-size:1.3rem;line-height:1}.et-query-devtools-filter-count{border-radius:999px;background:var(--_et-qdt-accent);color:#09090b;min-inline-size:1.8rem;padding:0 .5rem;text-align:center}.et-query-devtools-filter-clear{appearance:none;display:inline-flex;align-items:center;gap:.5rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:transparent;color:var(--_et-qdt-muted);cursor:pointer;padding:.3rem .6rem;font:inherit;font-size:1.15rem;line-height:1.4}@media(hover:hover){.et-query-devtools-filter-clear:hover{border-color:color-mix(in srgb,var(--_et-qdt-accent) 60%,transparent);color:var(--_et-qdt-fg)}}.et-query-devtools-scroll{block-size:100%;overflow:auto;padding:1rem;display:flex;flex-direction:column;gap:1rem}.et-query-devtools-split{display:flex;block-size:100%;min-block-size:0}.et-query-devtools-split .et-query-devtools-scroll{flex:1 1 auto;min-inline-size:0}.et-query-devtools-pane{display:flex;flex-direction:column;flex:1 1 auto;block-size:100%;min-inline-size:0;min-block-size:0;overflow:hidden}.et-query-devtools-pane>.et-query-devtools-scroll{padding-block:0 1rem}.et-query-devtools-pane-resize{flex:0 0 .5rem;align-self:stretch;cursor:ew-resize;background:transparent;transition:background .12s ease}.et-query-devtools-pane-resize:active{background:var(--_et-qdt-accent)}@media(hover:hover){.et-query-devtools-pane-resize:hover{background:var(--_et-qdt-accent)}}.et-query-devtools-toolbar{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.6rem;padding:.8rem;position:sticky;inset-block-start:0;z-index:1;background:var(--_et-qdt-bg);border-block-end:1px solid var(--_et-qdt-border)}.et-query-devtools-scroll>.et-query-devtools-toolbar{inset-block-start:-1rem}.et-query-devtools-toolbar-row{display:flex;align-items:center;flex-wrap:wrap;flex:1 1 100%;gap:.5rem}.et-query-devtools-field{display:inline-flex;align-items:center;gap:.6rem;color:var(--_et-qdt-muted)}.et-query-devtools-select{flex:0 0 auto;min-block-size:var(--_et-qdt-control-h);background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,var(--_et-qdt-bg));color:var(--_et-qdt-fg);border:1px solid transparent;border-radius:var(--_et-qdt-radius);padding:.4rem .6rem;font:inherit;font-size:1.15rem}.et-query-devtools-select option{background-color:var(--_et-qdt-bg);color:var(--_et-qdt-fg)}.et-query-devtools-count{color:var(--_et-qdt-subtle)}.et-query-devtools-row{display:flex;align-items:stretch;inline-size:100%;min-block-size:var(--_et-qdt-row-h);border-block-end:1px solid var(--_et-qdt-border);color:var(--_et-qdt-fg)}@media(hover:hover){.et-query-devtools-row:hover{background:var(--_et-qdt-hover)}}.et-query-devtools-method{font-weight:700;font-size:1.1rem;color:var(--_et-qdt-muted);white-space:nowrap}.et-query-devtools-route{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1 1 auto;min-inline-size:0}.et-query-devtools-status{inline-size:.8rem;block-size:.8rem;border-radius:999px;flex:0 0 auto;background:var(--_et-qdt-subtle)}.et-query-devtools-status[data-status=success]{background:var(--_et-qdt-success)}.et-query-devtools-status[data-status=error]{background:var(--_et-qdt-error)}.et-query-devtools-status[data-status=loading],.et-query-devtools-status[data-status=running]{background:var(--_et-qdt-loading)}.et-query-devtools-btn{appearance:none;display:inline-flex;align-items:center;justify-content:center;gap:.5rem;flex:0 0 auto;min-block-size:var(--_et-qdt-control-h);border:1px solid transparent;border-radius:var(--_et-qdt-radius);background:transparent;color:var(--_et-qdt-muted);padding:.25rem .7rem;font:inherit;font-size:1.15rem;line-height:1.4;white-space:nowrap;cursor:pointer;transition:color .12s ease,background-color .12s ease,border-color .12s ease}@media(hover:hover){.et-query-devtools-btn:hover:not(:disabled){background:var(--_et-qdt-hover);color:var(--_et-qdt-fg)}}.et-query-devtools-btn:focus-visible{outline:2px solid var(--_et-qdt-accent);outline-offset:1px}.et-query-devtools-btn:disabled{color:var(--_et-qdt-subtle);cursor:default;background:transparent;opacity:.6}.et-query-devtools-btn--primary,.et-query-devtools-btn--primary:hover:not(:disabled){background:color-mix(in srgb,var(--_et-qdt-accent) 16%,transparent);border-color:color-mix(in srgb,var(--_et-qdt-accent) 40%,transparent);color:var(--_et-qdt-accent)}@media(hover:hover){.et-query-devtools-btn--primary:hover:not(:disabled){background:color-mix(in srgb,var(--_et-qdt-accent) 26%,transparent)}}@media(hover:hover){.et-query-devtools-btn--danger:hover:not(:disabled){background:color-mix(in srgb,var(--_et-qdt-error) 16%,transparent);color:var(--_et-qdt-error)}}.et-query-devtools-btn--tampered,.et-query-devtools-btn--tampered:hover:not(:disabled){background:color-mix(in srgb,var(--_et-qdt-error) 14%,transparent);border-color:color-mix(in srgb,var(--_et-qdt-error) 45%,transparent);color:var(--_et-qdt-error)}.et-query-devtools-icon-btn{inline-size:var(--_et-qdt-control-h);padding-inline:0;font-size:1.3rem}.et-query-devtools-btn-group{display:inline-flex;flex:0 0 auto;border:1px solid var(--_et-qdt-border);border-radius:var(--_et-qdt-radius);overflow:hidden}.et-query-devtools-btn-group>.et-query-devtools-btn{border-radius:0;border-block:0;border-inline-start:0;border-inline-end:1px solid var(--_et-qdt-border)}.et-query-devtools-btn-group>.et-query-devtools-btn:last-child{border-inline-end:0}.et-query-devtools-toolbar-btn{margin-inline-start:auto;flex:0 0 auto}.et-query-devtools-meta{display:grid;grid-template-columns:auto 1fr;gap:.3rem 1rem;margin:0 0 1rem}.et-query-devtools-meta dt{color:var(--_et-qdt-muted)}.et-query-devtools-meta dd{margin:0;overflow-wrap:anywhere}.et-query-devtools-section{margin:1rem 0 .4rem;font-size:1.1rem;text-transform:uppercase;letter-spacing:.05em;color:var(--_et-qdt-muted)}.et-query-devtools-stat{padding:.6rem .8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 3%,transparent)}.et-query-devtools-stat dt{font-size:1rem;text-transform:uppercase;letter-spacing:.05em;color:var(--_et-qdt-muted)}.et-query-devtools-stat dd{margin:0;font-size:1.5rem;font-variant-numeric:tabular-nums}.et-query-devtools-stat-sub{display:block;font-size:1rem;color:var(--_et-qdt-subtle)}.et-query-devtools-json{margin:0;padding:.8rem;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:color-mix(in srgb,var(--_et-qdt-interaction) 4%,transparent);overflow-x:auto;white-space:pre-wrap;overflow-wrap:anywhere;max-block-size:24rem;overflow-y:auto}.et-query-devtools-json--error{border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-mono{font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.et-query-devtools-url{display:inline-block;max-inline-size:32rem;margin-inline-start:.6rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.et-query-devtools-chips,.et-query-devtools-chip-row{display:flex;flex-wrap:wrap;gap:.4rem;margin-block-end:.8rem}.et-query-devtools-chip{display:inline-flex;align-items:center;gap:.3rem;padding:.2rem .6rem;border-radius:999px;border:1px solid var(--_et-qdt-border);font-size:1.1rem;color:var(--_et-qdt-fg);background:color-mix(in srgb,var(--_et-qdt-interaction) 6%,transparent)}.et-query-devtools-chip--muted{color:var(--_et-qdt-muted)}.et-query-devtools-retry-glyph{font-size:.7em;line-height:1}.et-query-devtools-chip--loading{color:var(--_et-qdt-loading);border-color:color-mix(in srgb,var(--_et-qdt-loading) 50%,transparent)}.et-query-devtools-chip--error{color:var(--_et-qdt-error);border-color:color-mix(in srgb,var(--_et-qdt-error) 50%,transparent)}.et-query-devtools-chip--success{color:var(--_et-qdt-success);border-color:color-mix(in srgb,var(--_et-qdt-success) 50%,transparent)}button.et-query-devtools-chip--button{appearance:none;cursor:pointer;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}@media(hover:hover){button.et-query-devtools-chip--button:hover{border-color:var(--_et-qdt-accent);color:var(--_et-qdt-accent)}}.et-query-devtools-facet{appearance:none;min-block-size:var(--_et-qdt-control-h);padding-inline:.8rem;background:transparent;cursor:pointer;color:var(--_et-qdt-muted);font:inherit;font-size:1.1rem;line-height:1.4}@media(hover:hover){.et-query-devtools-facet:hover:not(:disabled){color:var(--_et-qdt-fg);background:var(--_et-qdt-hover);border-color:color-mix(in srgb,var(--_et-qdt-accent) 60%,transparent)}}.et-query-devtools-facet:disabled{color:var(--_et-qdt-subtle);cursor:default}.et-query-devtools-facet--active{color:var(--_et-qdt-accent);border-color:color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent);background:color-mix(in srgb,var(--_et-qdt-accent) 14%,transparent)}.et-query-devtools-facet-count{font-variant-numeric:tabular-nums;color:inherit;opacity:.75}.et-query-devtools-card{border:1px solid var(--_et-qdt-border);border-radius:.6rem;padding:1rem}.et-query-devtools-card--armed{border-color:color-mix(in srgb,var(--_et-qdt-error) 55%,transparent);background:color-mix(in srgb,var(--_et-qdt-error) 6%,transparent)}.et-query-devtools-card-head{display:flex;align-items:center;gap:.6rem;margin-block-end:.8rem}.et-query-devtools-inner-queries{display:flex;flex-direction:column;gap:.3rem}.et-query-devtools-inner-row{display:flex;align-items:center;gap:.6rem;inline-size:100%;text-align:start;appearance:none;border:1px solid var(--_et-qdt-border);border-radius:.4rem;background:transparent;color:var(--_et-qdt-fg);padding:.4rem .6rem;cursor:pointer;font:inherit}@media(hover:hover){.et-query-devtools-inner-row:hover:not([disabled]){background:var(--_et-qdt-hover)}}.et-query-devtools-inner-row--active{background:var(--_et-qdt-active);border-color:var(--_et-qdt-accent)}.et-query-devtools-inner-row[disabled]{cursor:default;opacity:.6}.et-query-devtools-step-toggle{display:flex;align-items:center;gap:.6rem;inline-size:100%;text-align:start;appearance:none;border:none;background:transparent;color:var(--_et-qdt-fg);padding:.6rem;cursor:pointer;font:inherit}@media(hover:hover){.et-query-devtools-step-toggle:hover{background:var(--_et-qdt-hover)}}.et-query-devtools-step-index{flex:1 1 auto;min-inline-size:0}.et-query-devtools-step-caret{color:var(--_et-qdt-muted)}.et-query-devtools-table{inline-size:100%;border-collapse:collapse}.et-query-devtools-table-scroll{overflow-x:auto}.et-query-devtools-table-scroll>.et-query-devtools-table{min-inline-size:max-content}.et-query-devtools-table th,.et-query-devtools-table td{text-align:start;padding:.5rem .6rem;border-block-end:1px solid var(--_et-qdt-border);vertical-align:top}.et-query-devtools-table td .et-query-devtools-chip{padding-block:0;line-height:1.4}.et-query-devtools-table th{color:var(--_et-qdt-muted);font-weight:600;position:sticky;inset-block-start:0;background:var(--_et-qdt-bg)}.et-query-devtools-table tr[data-status=error] td{color:var(--_et-qdt-error)}.et-query-devtools-table .et-query-devtools-cell-origin{inline-size:12ch;max-inline-size:12ch;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;overflow-wrap:normal}.et-query-devtools-empty{color:var(--_et-qdt-subtle);padding:1.2rem;text-align:center}.et-query-devtools-cell-actions{display:inline-flex;gap:.4rem;white-space:nowrap}.et-query-devtools-section-row{display:flex;align-items:center;justify-content:space-between;gap:.8rem;margin-block-start:1rem}.et-query-devtools-search{flex:1 1 16rem;min-inline-size:0;min-block-size:var(--_et-qdt-control-h);background:color-mix(in srgb,var(--_et-qdt-interaction) 5%,transparent);color:var(--_et-qdt-fg);border:1px solid transparent;border-radius:var(--_et-qdt-radius);padding:.4rem .8rem;font:inherit;font-size:1.15rem}.et-query-devtools-search:focus{outline:none;border-color:color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent);background:var(--_et-qdt-bg)}.et-query-devtools-row-link{display:inline-flex;align-items:center;gap:.6rem;appearance:none;border:0;background:none;padding:0;color:inherit;font:inherit;text-align:start;cursor:pointer}.et-query-devtools-row-link:focus-visible .et-query-devtools-url{color:var(--_et-qdt-accent);text-decoration:underline}@media(hover:hover){.et-query-devtools-row-link:hover .et-query-devtools-url{color:var(--_et-qdt-accent);text-decoration:underline}}.et-query-devtools-hint{flex:1 1 24rem;color:var(--_et-qdt-muted);font-size:1.1rem;margin:0}.et-query-devtools-input{inline-size:100%;background:var(--_et-qdt-bg);color:var(--_et-qdt-fg);border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.4rem .6rem;font:inherit}.et-query-devtools-input--number{inline-size:8rem;font-variant-numeric:tabular-nums}.et-query-devtools-editor{border:1px solid var(--_et-qdt-border);border-radius:.4rem;padding:.8rem;margin-block-end:1rem}.et-query-devtools-editor-error{color:var(--_et-qdt-error);margin:.4rem 0 0}.et-query-devtools-btn--active{color:var(--_et-qdt-accent);border-color:color-mix(in srgb,var(--_et-qdt-accent) 55%,transparent);background:color-mix(in srgb,var(--_et-qdt-accent) 12%,transparent)}}\n"] }]
6977
7224
  }], ctorParameters: () => [], propDecorators: { startOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "startOpen", required: false }] }], panelEl: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }] } });
6978
7225
 
6979
7226
  const QUERY_DEVTOOLS_IMPORTS = [QueryDevtoolsComponent];